Add support for daemons that don't have the new 'getblock' RPC call that returns the block in hex, the workaround is to manually recreate the block bytes. The recreated block bytes may not be the exact ones as in the underlying blockchain but it is good enough for our indexing purposes.
314 lines
10 KiB
Python
314 lines
10 KiB
Python
# Copyright (c) 2016-2017, Neil Booth
|
|
# Copyright (c) 2017, the ElectrumX authors
|
|
#
|
|
# All rights reserved.
|
|
#
|
|
# The MIT License (MIT)
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
# a copy of this software and associated documentation files (the
|
|
# "Software"), to deal in the Software without restriction, including
|
|
# without limitation the rights to use, copy, modify, merge, publish,
|
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
# the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be
|
|
# included in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
# and warranty status of this software.
|
|
|
|
'''Transaction-related classes and functions.'''
|
|
|
|
|
|
from collections import namedtuple
|
|
from struct import unpack_from
|
|
|
|
from lib.util import cachedproperty
|
|
from lib.hash import double_sha256, hash_to_str
|
|
|
|
|
|
class Tx(namedtuple("Tx", "version inputs outputs locktime")):
|
|
'''Class representing a transaction.'''
|
|
|
|
@cachedproperty
|
|
def is_coinbase(self):
|
|
return self.inputs[0].is_coinbase
|
|
|
|
# FIXME: add hash as a cached property?
|
|
|
|
|
|
class TxInput(namedtuple("TxInput", "prev_hash prev_idx script sequence")):
|
|
'''Class representing a transaction input.'''
|
|
|
|
ZERO = bytes(32)
|
|
MINUS_1 = 4294967295
|
|
|
|
@cachedproperty
|
|
def is_coinbase(self):
|
|
return (self.prev_hash == TxInput.ZERO and
|
|
self.prev_idx == TxInput.MINUS_1)
|
|
|
|
def __str__(self):
|
|
script = self.script.hex()
|
|
prev_hash = hash_to_str(self.prev_hash)
|
|
return ("Input({}, {:d}, script={}, sequence={:d})"
|
|
.format(prev_hash, self.prev_idx, script, self.sequence))
|
|
|
|
|
|
class TxOutput(namedtuple("TxOutput", "value pk_script")):
|
|
pass
|
|
|
|
class Deserializer(object):
|
|
'''Deserializes blocks into transactions.
|
|
|
|
External entry points are read_tx() and read_block().
|
|
|
|
This code is performance sensitive as it is executed 100s of
|
|
millions of times during sync.
|
|
'''
|
|
|
|
def __init__(self, binary):
|
|
assert isinstance(binary, bytes)
|
|
self.binary = binary
|
|
self.cursor = 0
|
|
|
|
def read_tx(self):
|
|
'''Return a (Deserialized TX, TX_HASH) pair.
|
|
|
|
The hash needs to be reversed for human display; for efficiency
|
|
we process it in the natural serialized order.
|
|
'''
|
|
start = self.cursor
|
|
return Tx(
|
|
self._read_le_int32(), # version
|
|
self._read_inputs(), # inputs
|
|
self._read_outputs(), # outputs
|
|
self._read_le_uint32() # locktime
|
|
), double_sha256(self.binary[start:self.cursor])
|
|
|
|
def read_tx_block(self):
|
|
'''Returns a list of (deserialized_tx, tx_hash) pairs.'''
|
|
read_tx = self.read_tx
|
|
txs = [read_tx() for _ in range(self._read_varint())]
|
|
# Some coins have excess data beyond the end of the transactions
|
|
return txs
|
|
|
|
def _read_inputs(self):
|
|
read_input = self._read_input
|
|
return [read_input() for i in range(self._read_varint())]
|
|
|
|
def _read_input(self):
|
|
return TxInput(
|
|
self._read_nbytes(32), # prev_hash
|
|
self._read_le_uint32(), # prev_idx
|
|
self._read_varbytes(), # script
|
|
self._read_le_uint32() # sequence
|
|
)
|
|
|
|
def _read_outputs(self):
|
|
read_output = self._read_output
|
|
return [read_output() for i in range(self._read_varint())]
|
|
|
|
def _read_output(self):
|
|
return TxOutput(
|
|
self._read_le_int64(), # value
|
|
self._read_varbytes(), # pk_script
|
|
)
|
|
|
|
def _read_byte(self):
|
|
cursor = self.cursor
|
|
self.cursor += 1
|
|
return self.binary[cursor]
|
|
|
|
def _read_nbytes(self, n):
|
|
cursor = self.cursor
|
|
self.cursor = end = cursor + n
|
|
assert len(self.binary) >= end
|
|
return self.binary[cursor:end]
|
|
|
|
def _read_varbytes(self):
|
|
return self._read_nbytes(self._read_varint())
|
|
|
|
def _read_varint(self):
|
|
n = self.binary[self.cursor]
|
|
self.cursor += 1
|
|
if n < 253:
|
|
return n
|
|
if n == 253:
|
|
return self._read_le_uint16()
|
|
if n == 254:
|
|
return self._read_le_uint32()
|
|
return self._read_le_uint64()
|
|
|
|
def _read_le_int32(self):
|
|
result, = unpack_from('<i', self.binary, self.cursor)
|
|
self.cursor += 4
|
|
return result
|
|
|
|
def _read_le_int64(self):
|
|
result, = unpack_from('<q', self.binary, self.cursor)
|
|
self.cursor += 8
|
|
return result
|
|
|
|
def _read_le_uint16(self):
|
|
result, = unpack_from('<H', self.binary, self.cursor)
|
|
self.cursor += 2
|
|
return result
|
|
|
|
def _read_le_uint32(self):
|
|
result, = unpack_from('<I', self.binary, self.cursor)
|
|
self.cursor += 4
|
|
return result
|
|
|
|
def _read_le_uint64(self):
|
|
result, = unpack_from('<Q', self.binary, self.cursor)
|
|
self.cursor += 8
|
|
return result
|
|
|
|
|
|
class TxSegWit(namedtuple("Tx", "version marker flag inputs outputs "
|
|
"witness locktime")):
|
|
'''Class representing a SegWit transaction.'''
|
|
|
|
@cachedproperty
|
|
def is_coinbase(self):
|
|
return self.inputs[0].is_coinbase
|
|
|
|
|
|
class DeserializerSegWit(Deserializer):
|
|
|
|
# https://bitcoincore.org/en/segwit_wallet_dev/#transaction-serialization
|
|
|
|
def _read_witness(self, fields):
|
|
read_witness_field = self._read_witness_field
|
|
return [read_witness_field() for i in range(fields)]
|
|
|
|
def _read_witness_field(self):
|
|
read_varbytes = self._read_varbytes
|
|
return [read_varbytes() for i in range(self._read_varint())]
|
|
|
|
def read_tx(self):
|
|
'''Return a (Deserialized TX, TX_HASH) pair.
|
|
|
|
The hash needs to be reversed for human display; for efficiency
|
|
we process it in the natural serialized order.
|
|
'''
|
|
marker = self.binary[self.cursor + 4]
|
|
if marker:
|
|
return super().read_tx()
|
|
|
|
# Ugh, this is nasty.
|
|
start = self.cursor
|
|
version = self._read_le_int32()
|
|
orig_ser = self.binary[start:self.cursor]
|
|
|
|
marker = self._read_byte()
|
|
flag = self._read_byte()
|
|
|
|
start = self.cursor
|
|
inputs = self._read_inputs()
|
|
outputs = self._read_outputs()
|
|
orig_ser += self.binary[start:self.cursor]
|
|
|
|
witness = self._read_witness(len(inputs))
|
|
|
|
start = self.cursor
|
|
locktime = self._read_le_uint32()
|
|
orig_ser += self.binary[start:self.cursor]
|
|
|
|
return TxSegWit(version, marker, flag, inputs,
|
|
outputs, witness, locktime), double_sha256(orig_ser)
|
|
|
|
|
|
class DeserializerAuxPow(Deserializer):
|
|
VERSION_AUXPOW = (1 << 8)
|
|
|
|
def read_header(self, height, static_header_size):
|
|
'''Return the AuxPow block header bytes'''
|
|
start = self.cursor
|
|
version = self._read_le_uint32()
|
|
if version & self.VERSION_AUXPOW:
|
|
# We are going to calculate the block size then read it as bytes
|
|
self.cursor = start
|
|
self.cursor += static_header_size # Block normal header
|
|
self.read_tx() # AuxPow transaction
|
|
self.cursor += 32 # Parent block hash
|
|
merkle_size = self._read_varint()
|
|
self.cursor += 32 * merkle_size # Merkle branch
|
|
self.cursor += 4 # Index
|
|
merkle_size = self._read_varint()
|
|
self.cursor += 32 * merkle_size # Chain merkle branch
|
|
self.cursor += 4 # Chain index
|
|
self.cursor += 80 # Parent block header
|
|
header_end = self.cursor
|
|
else:
|
|
header_end = static_header_size
|
|
self.cursor = start
|
|
return self._read_nbytes(header_end)
|
|
|
|
|
|
class TxJoinSplit(namedtuple("Tx", "version inputs outputs locktime")):
|
|
'''Class representing a JoinSplit transaction.'''
|
|
|
|
@cachedproperty
|
|
def is_coinbase(self):
|
|
return self.inputs[0].is_coinbase if len(self.inputs) > 0 else False
|
|
|
|
|
|
class DeserializerZcash(Deserializer):
|
|
def read_header(self, height, static_header_size):
|
|
'''Return the block header bytes'''
|
|
start = self.cursor
|
|
# We are going to calculate the block size then read it as bytes
|
|
self.cursor += static_header_size
|
|
solution_size = self._read_varint()
|
|
self.cursor += solution_size
|
|
header_end = self.cursor
|
|
self.cursor = start
|
|
return self._read_nbytes(header_end)
|
|
|
|
def read_tx(self):
|
|
start = self.cursor
|
|
base_tx = TxJoinSplit(
|
|
self._read_le_int32(), # version
|
|
self._read_inputs(), # inputs
|
|
self._read_outputs(), # outputs
|
|
self._read_le_uint32() # locktime
|
|
)
|
|
if base_tx.version >= 2:
|
|
joinsplit_size = self._read_varint()
|
|
if joinsplit_size > 0:
|
|
self.cursor += joinsplit_size * 1802 # JSDescription
|
|
self.cursor += 32 # joinSplitPubKey
|
|
self.cursor += 64 # joinSplitSig
|
|
return base_tx, double_sha256(self.binary[start:self.cursor])
|
|
|
|
|
|
class TxTime(namedtuple("Tx", "version time inputs outputs locktime")):
|
|
'''Class representing transaction that has a time field.'''
|
|
|
|
@cachedproperty
|
|
def is_coinbase(self):
|
|
return self.inputs[0].is_coinbase
|
|
|
|
|
|
class DeserializerTxTime(Deserializer):
|
|
def read_tx(self):
|
|
start = self.cursor
|
|
|
|
return TxTime(
|
|
self._read_le_int32(), # version
|
|
self._read_le_uint32(), # time
|
|
self._read_inputs(), # inputs
|
|
self._read_outputs(), # outputs
|
|
self._read_le_uint32(), # locktime
|
|
), double_sha256(self.binary[start:self.cursor])
|