mirror of
https://github.com/aljazceru/python-teos.git
synced 2025-12-17 22:24:23 +01:00
The simulator has been updated to work with real transaction structures instead of transaction hashes. It now supports: - Non-SegWit transaction format - Generation of blocks event-wise and time-wise Some small issues have also been fixed. With the new approach, the simulator can be used in a broader range of tests. Moreover tests can run faster since they do not have to wait for blocks. Instead, the generation of new blocks can be triggered by the test.
151 lines
5.8 KiB
Python
151 lines
5.8 KiB
Python
# Porting some functionality from https://github.com/sr-gi/bitcoin_tools with some modifications <3
|
|
from os import urandom
|
|
|
|
from test.simulator.utils import *
|
|
|
|
|
|
class TX:
|
|
""" Defines a class TX (transaction) that holds all the modifiable fields of a Bitcoin transaction, such as
|
|
version, number of inputs, reference to previous transactions, input and output scripts, value, etc.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.version = None
|
|
self.inputs = None
|
|
self.outputs = None
|
|
self.nLockTime = None
|
|
self.prev_tx_id = []
|
|
self.prev_out_index = []
|
|
self.scriptSig = []
|
|
self.scriptSig_len = []
|
|
self.nSequence = []
|
|
self.value = []
|
|
self.scriptPubKey = []
|
|
self.scriptPubKey_len = []
|
|
|
|
self.offset = 0
|
|
self.hex = ""
|
|
|
|
@classmethod
|
|
def deserialize(cls, hex_tx):
|
|
""" Builds a transaction object from the hexadecimal serialization format of a transaction that
|
|
could be obtained, for example, from a blockexplorer.
|
|
:param hex_tx: Hexadecimal serialized transaction.
|
|
:type hex_tx: hex str
|
|
:return: The transaction build using the provided hex serialized transaction.
|
|
:rtype: TX
|
|
"""
|
|
|
|
tx = cls()
|
|
tx.hex = hex_tx
|
|
|
|
try:
|
|
tx.version = int(change_endianness(parse_element(tx, 4)), 16)
|
|
|
|
# INPUTS
|
|
tx.inputs = int(parse_varint(tx), 16)
|
|
|
|
for i in range(tx.inputs):
|
|
tx.prev_tx_id.append(change_endianness(parse_element(tx, 32)))
|
|
tx.prev_out_index.append(int(change_endianness(parse_element(tx, 4)), 16))
|
|
# ScriptSig
|
|
tx.scriptSig_len.append(int(parse_varint(tx), 16))
|
|
tx.scriptSig.append(parse_element(tx, tx.scriptSig_len[i]))
|
|
tx.nSequence.append(int(parse_element(tx, 4), 16))
|
|
|
|
# OUTPUTS
|
|
tx.outputs = int(parse_varint(tx), 16)
|
|
|
|
for i in range(tx.outputs):
|
|
tx.value.append(int(change_endianness(parse_element(tx, 8)), 16))
|
|
# ScriptPubKey
|
|
tx.scriptPubKey_len.append(int(parse_varint(tx), 16))
|
|
tx.scriptPubKey.append(parse_element(tx, tx.scriptPubKey_len[i]))
|
|
|
|
tx.nLockTime = int(parse_element(tx, 4), 16)
|
|
|
|
if tx.offset != len(tx.hex):
|
|
# There is some error in the serialized transaction passed as input. Transaction can't be built
|
|
tx = None
|
|
else:
|
|
tx.offset = 0
|
|
|
|
except ValueError:
|
|
# If a parsing error occurs, the deserialization stops and None is returned
|
|
tx = None
|
|
|
|
return tx
|
|
|
|
def serialize(self, rtype=hex):
|
|
""" Serialize all the transaction fields arranged in the proper order, resulting in a hexadecimal string
|
|
ready to be broadcast to the network.
|
|
:param self: self
|
|
:type self: TX
|
|
:param rtype: Whether the serialized transaction is returned as a hex str or a byte array.
|
|
:type rtype: hex or bool
|
|
:return: Serialized transaction representation (hexadecimal or bin depending on rtype parameter).
|
|
:rtype: hex str / bin
|
|
"""
|
|
|
|
if rtype not in [hex, bin]:
|
|
raise Exception("Invalid return type (rtype). It should be either hex or bin.")
|
|
serialized_tx = change_endianness(int2bytes(self.version, 4)) # 4-byte version number (LE).
|
|
|
|
# INPUTS
|
|
serialized_tx += encode_varint(self.inputs) # Varint number of inputs.
|
|
|
|
for i in range(self.inputs):
|
|
serialized_tx += change_endianness(self.prev_tx_id[i]) # 32-byte hash of the previous transaction (LE).
|
|
serialized_tx += change_endianness(int2bytes(self.prev_out_index[i], 4)) # 4-byte output index (LE)
|
|
serialized_tx += encode_varint(len(self.scriptSig[i]) // 2) # Varint input script length.
|
|
# ScriptSig
|
|
serialized_tx += self.scriptSig[i] # Input script.
|
|
serialized_tx += int2bytes(self.nSequence[i], 4) # 4-byte sequence number.
|
|
|
|
# OUTPUTS
|
|
serialized_tx += encode_varint(self.outputs) # Varint number of outputs.
|
|
|
|
if self.outputs != 0:
|
|
for i in range(self.outputs):
|
|
serialized_tx += change_endianness(int2bytes(self.value[i], 8)) # 8-byte field Satoshi value (LE)
|
|
# ScriptPubKey
|
|
serialized_tx += encode_varint(len(self.scriptPubKey[i]) // 2) # Varint Output script length.
|
|
serialized_tx += self.scriptPubKey[i] # Output script.
|
|
|
|
serialized_tx += int2bytes(self.nLockTime, 4) # 4-byte lock time field
|
|
|
|
# If return type has been set to binary, the serialized transaction is converted.
|
|
if rtype is bin:
|
|
serialized_tx = unhexlify(serialized_tx)
|
|
|
|
return serialized_tx
|
|
|
|
@staticmethod
|
|
def create_dummy_transaction(prev_tx_id=None, prev_out_index=None):
|
|
tx = TX()
|
|
|
|
if prev_tx_id is None:
|
|
prev_tx_id = urandom(32).hex()
|
|
|
|
if prev_out_index is None:
|
|
prev_out_index = 0
|
|
|
|
tx.version = 1
|
|
tx.inputs = 1
|
|
tx.outputs = 1
|
|
tx.prev_tx_id = [prev_tx_id]
|
|
tx.prev_out_index = [prev_out_index]
|
|
tx.nLockTime = 0
|
|
tx.scriptSig = [
|
|
'47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860'
|
|
'a4acdd12909d831cc56cbbac4622082221a8768d1d0901']
|
|
tx.scriptSig_len = [77]
|
|
tx.nSequence = [4294967295]
|
|
tx.value = [5000000000]
|
|
tx.scriptPubKey = [
|
|
'4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c'
|
|
'1b7303b8a0626f1baded5c72a704f7e6cd84cac']
|
|
tx.scriptPubKey_len = [67]
|
|
|
|
return tx.serialize()
|