Skip to content

Commit

Permalink
Add index, transaction order in block
Browse files Browse the repository at this point in the history
  • Loading branch information
Cryp Toon committed Feb 13, 2024
1 parent 71296b2 commit a3ae415
Show file tree
Hide file tree
Showing 11 changed files with 61 additions and 155 deletions.
4 changes: 3 additions & 1 deletion bitcoinlib/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,13 @@ def parse_bytesio(cls, raw, block_hash=None, height=None, parse_transactions=Fal
raw.seek(tx_start_pos)
transactions = []

index = 0
while parse_transactions and raw.tell() < txs_data_size:
if limit != 0 and len(transactions) >= limit:
break
t = Transaction.parse_bytesio(raw, strict=False)
t = Transaction.parse_bytesio(raw, strict=False, index=index)
transactions.append(t)
index += 1
# TODO: verify transactions, need input value from previous txs
# if verify and not t.verify():
# raise ValueError("Could not verify transaction %s in block %s" % (t.txid, block_hash))
Expand Down
2 changes: 1 addition & 1 deletion bitcoinlib/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ class DbTransaction(Base):
raw = Column(LargeBinary,
doc="Raw transaction hexadecimal string. Transaction is included in raw format on the blockchain")
verified = Column(Boolean, default=False, doc="Is transaction verified. Default is False")
order_n = Column(Integer, doc="Order of transaction in block")
index = Column(Integer, doc="Index of transaction in block")

__table_args__ = (
UniqueConstraint('wallet_id', 'txid', name='constraint_wallet_transaction_hash_unique'),
Expand Down
2 changes: 1 addition & 1 deletion bitcoinlib/db_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class DbCacheTransaction(Base):
fee = Column(BigInteger, doc="Transaction fee")
nodes = relationship("DbCacheTransactionNode", cascade="all,delete",
doc="List of all inputs and outputs as DbCacheTransactionNode objects")
order_n = Column(Integer, doc="Order of transaction in block")
index = Column(Integer, doc="Index of transaction in block")
witness_type = Column(Enum(WitnessTypeTransactions), default=WitnessTypeTransactions.legacy,
doc="Transaction type enum: legacy or segwit")

Expand Down
1 change: 1 addition & 0 deletions bitcoinlib/services/bcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def _parse_transaction(self, tx):
t.block_height = tx['height'] if tx['height'] > 0 else None
t.block_hash = tx['block']
t.status = status
t.index = tx['index']
if not t.coinbase:
for i in t.inputs:
i.value = tx['inputs'][t.inputs.index(i)]['coin']['value']
Expand Down
32 changes: 16 additions & 16 deletions bitcoinlib/services/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,11 +382,11 @@ def gettransactions(self, address, after_txid='', limit=MAX_TRANSACTIONS):
if len(txs):
last_txid = bytes.fromhex(txs[-1:][0].txid)
if len(self.results):
order_n = 0
index = 0
for t in txs:
if t.confirmations != 0:
res = self.cache.store_transaction(t, order_n, commit=False)
order_n += 1
res = self.cache.store_transaction(t, index, commit=False)
index += 1
# Failure to store transaction: stop caching transaction and store last tx block height - 1
if res is False:
if t.block_height:
Expand Down Expand Up @@ -556,11 +556,11 @@ def getblock(self, blockid, parse_transactions=True, page=1, limit=None):
block.page = page

if parse_transactions and self.min_providers <= 1:
order_n = (page-1)*limit
index = (page-1)*limit
for tx in block.transactions:
if isinstance(tx, Transaction):
self.cache.store_transaction(tx, order_n, commit=False)
order_n += 1
self.cache.store_transaction(tx, index, commit=False)
index += 1
self.cache.commit()
self.complete = True if len(block.transactions) == block.tx_count else False
self.cache.store_block(block)
Expand Down Expand Up @@ -717,7 +717,7 @@ def _parse_db_transaction(db_tx):
t = Transaction(locktime=db_tx.locktime, version=db_tx.version, network=db_tx.network_name,
fee=db_tx.fee, txid=db_tx.txid.hex(), date=db_tx.date, confirmations=db_tx.confirmations,
block_height=db_tx.block_height, status='confirmed', witness_type=db_tx.witness_type.value,
order_n=db_tx.order_n)
index=db_tx.index)
for n in db_tx.nodes:
if n.is_input:
if n.ref_txid == b'\00' * 32:
Expand Down Expand Up @@ -794,7 +794,7 @@ def gettransactions(self, address, after_txid='', limit=MAX_TRANSACTIONS):
filter(DbCacheTransactionNode.address == address,
DbCacheTransaction.block_height >= after_tx.block_height,
DbCacheTransaction.block_height <= db_addr.last_block).\
order_by(DbCacheTransaction.block_height, DbCacheTransaction.order_n).all()
order_by(DbCacheTransaction.block_height, DbCacheTransaction.index).all()
db_txs2 = []
for d in db_txs:
db_txs2.append(d)
Expand All @@ -806,7 +806,7 @@ def gettransactions(self, address, after_txid='', limit=MAX_TRANSACTIONS):
else:
db_txs = self.session.query(DbCacheTransaction).join(DbCacheTransactionNode). \
filter(DbCacheTransactionNode.address == address). \
order_by(DbCacheTransaction.block_height, DbCacheTransaction.order_n).all()
order_by(DbCacheTransaction.block_height, DbCacheTransaction.index).all()
for db_tx in db_txs:
t = self._parse_db_transaction(db_tx)
if t:
Expand Down Expand Up @@ -836,8 +836,8 @@ def getblocktransactions(self, height, page, limit):
n_from = (page-1) * limit
n_to = page * limit
db_txs = self.session.query(DbCacheTransaction).\
filter(DbCacheTransaction.block_height == height, DbCacheTransaction.order_n >= n_from,
DbCacheTransaction.order_n < n_to).all()
filter(DbCacheTransaction.block_height == height, DbCacheTransaction.index >= n_from,
DbCacheTransaction.index < n_to).all()
txs = []
for db_tx in db_txs:
t = self._parse_db_transaction(db_tx)
Expand Down Expand Up @@ -881,7 +881,7 @@ def getutxos(self, address, after_txid=''):
DbCacheTransactionNode.value, DbCacheTransaction.confirmations,
DbCacheTransaction.block_height, DbCacheTransaction.fee,
DbCacheTransaction.date, DbCacheTransaction.txid).join(DbCacheTransaction). \
order_by(DbCacheTransaction.block_height, DbCacheTransaction.order_n). \
order_by(DbCacheTransaction.block_height, DbCacheTransaction.index). \
filter(DbCacheTransactionNode.address == address, DbCacheTransactionNode.is_input == False,
DbCacheTransaction.network_name == self.network.name).all()
utxos = []
Expand Down Expand Up @@ -991,14 +991,14 @@ def store_blockcount(self, blockcount):
self.session.merge(dbvar)
self.commit()

def store_transaction(self, t, order_n=None, commit=True):
def store_transaction(self, t, index=None, commit=True):
"""
Store transaction in cache. Use order number to determine order in a block
:param t: Transaction
:type t: Transaction
:param order_n: Order in block
:type order_n: int
:param index: Order in block
:type index: int
:param commit: Commit transaction to database. Default is True. Can be disabled if a larger number of transactions are added to cache, so you can commit outside this method.
:type commit: bool
Expand All @@ -1023,7 +1023,7 @@ def store_transaction(self, t, order_n=None, commit=True):
return
new_tx = DbCacheTransaction(txid=txid, date=t.date, confirmations=t.confirmations,
block_height=t.block_height, network_name=t.network.name,
fee=t.fee, order_n=order_n, version=t.version_int,
fee=t.fee, index=index, version=t.version_int,
locktime=t.locktime, witness_type=t.witness_type)
self.session.add(new_tx)
for i in t.inputs:
Expand Down
12 changes: 6 additions & 6 deletions bitcoinlib/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ def parse(cls, rawtx, strict=True, network=DEFAULT_NETWORK):
return cls.parse_bytesio(rawtx, strict, network)

@classmethod
def parse_bytesio(cls, rawtx, strict=True, network=DEFAULT_NETWORK):
def parse_bytesio(cls, rawtx, strict=True, network=DEFAULT_NETWORK, index=None):
"""
Parse a raw transaction and create a Transaction object
Expand Down Expand Up @@ -997,7 +997,7 @@ def parse_bytesio(cls, rawtx, strict=True, network=DEFAULT_NETWORK):
raw_bytes = rawtx.read(raw_len)

return Transaction(inputs, outputs, locktime, version, network, size=raw_len, output_total=output_total,
coinbase=coinbase, flag=flag, witness_type=witness_type, rawtx=raw_bytes)
coinbase=coinbase, flag=flag, witness_type=witness_type, rawtx=raw_bytes, index=index)

@classmethod
def parse_hex(cls, rawtx, strict=True, network=DEFAULT_NETWORK):
Expand Down Expand Up @@ -1066,7 +1066,7 @@ def __init__(self, inputs=None, outputs=None, locktime=0, version=None,
network=DEFAULT_NETWORK, fee=None, fee_per_kb=None, size=None, txid='', txhash='', date=None,
confirmations=None, block_height=None, block_hash=None, input_total=0, output_total=0, rawtx=b'',
status='new', coinbase=False, verified=False, witness_type='segwit', flag=None, replace_by_fee=False,
order_n=None):
index=None):
"""
Create a new transaction class with provided inputs and outputs.
Expand Down Expand Up @@ -1119,8 +1119,8 @@ def __init__(self, inputs=None, outputs=None, locktime=0, version=None,
:type witness_type: str
:param flag: Transaction flag to indicate version, for example for SegWit
:type flag: bytes, str
:param order_n: Order of transaction in block. Used when parsing blocks
:type order_n: int
:param index: Index of transaction in block. Used when parsing blocks
:type index: int
"""

Expand Down Expand Up @@ -1181,7 +1181,7 @@ def __init__(self, inputs=None, outputs=None, locktime=0, version=None,
self.witness_type = witness_type
self.replace_by_fee = replace_by_fee
self.change = 0
self.order_n = order_n
self.index = index
self.calc_weight_units()
if self.witness_type not in ['legacy', 'segwit']:
raise TransactionError("Please specify a valid witness type: legacy or segwit")
Expand Down
104 changes: 0 additions & 104 deletions tests/benchmark.py

This file was deleted.

1 change: 1 addition & 0 deletions tests/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def test_blocks_parse_block_and_transactions_2(self):
self.assertEqual(b.tx_count, 81)
self.assertEqual(b.transactions[0].txid, 'dfd63430f8d14f6545117d74b20da63efd4a75c7e28f723b3dead431b88469ee')
self.assertEqual(b.transactions[4].txid, '717bc8b42f12baf771b6719c2e3b2742925fe3912917c716abef03e35fe49020')
self.assertEqual(b.transactions[4].index, 4)
self.assertEqual(len(b.transactions), 5)
b.parse_transactions(70)
self.assertEqual(len(b.transactions), 75)
Expand Down
16 changes: 8 additions & 8 deletions tests/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,22 +566,22 @@ def test_encrypt_private_key(self):
return
for v in self.vectors["valid"]:
k = Key(v['wif'])
print("Check %s + %s = %s " % (v['wif'], v['passphrase'], v['bip38']))
# print("Check %s + %s = %s " % (v['wif'], v['passphrase'], v['bip38']))
self.assertEqual(str(v['bip38']), k.encrypt(str(v['passphrase'])))

def test_decrypt_bip38_key(self):
if not USING_MODULE_SCRYPT:
return
for v in self.vectors["valid"]:
k = Key(v['bip38'], password=str(v['passphrase']))
print("Check %s - %s = %s " % (v['bip38'], v['passphrase'], v['wif']))
# print("Check %s - %s = %s " % (v['bip38'], v['passphrase'], v['wif']))
self.assertEqual(str(v['wif']), k.wif())

def test_bip38_invalid_keys(self):
if not USING_MODULE_SCRYPT:
return
for v in self.vectors["invalid"]["verify"]:
print("Checking invalid key %s" % v['base58'])
# print("Checking invalid key %s" % v['base58'])
self.assertRaisesRegex(Exception, "", Key, str(v['base58']))

def test_bip38_other_networks(self):
Expand Down Expand Up @@ -622,8 +622,8 @@ def test_hdkey_derive_from_public_and_private_index(self):
for i in range(BULKTESTCOUNT):
pub_with_pubparent = self.K.child_public(i).address()
pub_with_privparent = self.k.child_private(i).address()
if pub_with_privparent != pub_with_pubparent:
print("Error index %4d: pub-child %s, priv-child %s" % (i, pub_with_privparent, pub_with_pubparent))
# if pub_with_privparent != pub_with_pubparent:
# print("Error index %4d: pub-child %s, priv-child %s" % (i, pub_with_privparent, pub_with_pubparent))
self.assertEqual(pub_with_pubparent, pub_with_privparent)

def test_hdkey_derive_from_public_and_private_random(self):
Expand All @@ -635,9 +635,9 @@ def test_hdkey_derive_from_public_and_private_random(self):
pubk = HDKey(k.wif_public())
pub_with_pubparent = pubk.child_public().address()
pub_with_privparent = k.child_private().address()
if pub_with_privparent != pub_with_pubparent:
print("Error random key: %4d: pub-child %s, priv-child %s" %
(i, pub_with_privparent, pub_with_pubparent))
# if pub_with_privparent != pub_with_pubparent:
# print("Error random key: %4d: pub-child %s, priv-child %s" %
# (i, pub_with_privparent, pub_with_pubparent))
self.assertEqual(pub_with_pubparent, pub_with_privparent)


Expand Down
Loading

0 comments on commit a3ae415

Please sign in to comment.