Skip to content

Commit 100b8c7

Browse files
Bhargavasomupipermerriam
authored andcommitted
Implement get_transaction_receipt function as part of ChainDB (#1705)
* Implement get_transaction_receipt function as part of ChainDB * Add tests for get_transaction_receipt function of ChainDB
1 parent 5b9b9fe commit 100b8c7

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

eth/db/chain.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
)
2929
from eth.exceptions import (
3030
HeaderNotFound,
31+
ReceiptNotFound,
3132
TransactionNotFound,
3233
)
3334
from eth.db.header import BaseHeaderDB, HeaderDB
@@ -395,6 +396,27 @@ def get_transaction_index(self, transaction_hash: Hash32) -> Tuple[BlockNumber,
395396
transaction_key = rlp.decode(encoded_key, sedes=TransactionKey)
396397
return (transaction_key.block_number, transaction_key.index)
397398

399+
def get_receipt_by_index(self,
400+
block_number: BlockNumber,
401+
receipt_index: int) -> Receipt:
402+
"""
403+
Returns the Receipt of the transaction at specified index
404+
for the block header obtained by the specified block number
405+
"""
406+
try:
407+
block_header = self.get_canonical_block_header_by_number(block_number)
408+
except HeaderNotFound:
409+
raise ReceiptNotFound("Block {} is not in the canonical chain".format(block_number))
410+
411+
receipt_db = HexaryTrie(db=self.db, root_hash=block_header.receipt_root)
412+
receipt_key = rlp.encode(receipt_index)
413+
if receipt_key in receipt_db:
414+
receipt_data = receipt_db[receipt_key]
415+
return rlp.decode(receipt_data, sedes=Receipt)
416+
else:
417+
raise ReceiptNotFound(
418+
"Receipt with index {} not found in block".format(receipt_index))
419+
398420
@staticmethod
399421
def _get_block_transaction_data(db: BaseDB, transaction_root: Hash32) -> Iterable[Hash32]:
400422
"""

eth/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ class TransactionNotFound(PyEVMError):
3939
pass
4040

4141

42+
class ReceiptNotFound(PyEVMError):
43+
"""
44+
Raised when the Receipt with the given receipt index does not exist.
45+
"""
46+
pass
47+
48+
4249
class ParentNotFound(HeaderNotFound):
4350
"""
4451
Raised when the parent of a given block does not exist.

tests/database/test_eth1_chaindb.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,38 @@
1212
from eth.constants import (
1313
BLANK_ROOT_HASH,
1414
)
15+
from eth.chains.base import (
16+
MiningChain,
17+
)
1518
from eth.db.chain import (
1619
ChainDB,
1720
)
1821
from eth.db.schema import SchemaV1
1922
from eth.exceptions import (
2023
HeaderNotFound,
2124
ParentNotFound,
25+
ReceiptNotFound,
2226
)
2327
from eth.rlp.headers import (
2428
BlockHeader,
2529
)
2630
from eth.tools.rlp import (
2731
assert_headers_eq,
2832
)
33+
from eth._utils.address import (
34+
force_bytes_to_address,
35+
)
2936
from eth.vm.forks.frontier.blocks import (
3037
FrontierBlock,
3138
)
3239
from eth.vm.forks.homestead.blocks import (
3340
HomesteadBlock,
3441
)
3542

43+
from tests.core.helpers import (
44+
new_transaction,
45+
)
46+
3647

3748
A_ADDRESS = b"\xaa" * 20
3849
B_ADDRESS = b"\xbb" * 20
@@ -64,6 +75,14 @@ def block(request, header):
6475
return request.param(header)
6576

6677

78+
@pytest.fixture
79+
def chain(chain_without_block_validation):
80+
if not isinstance(chain_without_block_validation, MiningChain):
81+
pytest.skip("these tests require a mining chain implementation")
82+
else:
83+
return chain_without_block_validation
84+
85+
6786
def test_chaindb_add_block_number_to_hash_lookup(chaindb, block):
6887
block_number_to_hash_key = SchemaV1.make_block_number_to_hash_lookup_key(block.number)
6988
assert not chaindb.exists(block_number_to_hash_key)
@@ -129,3 +148,50 @@ def test_chaindb_get_canonical_block_hash(chaindb, block):
129148
chaindb.persist_block(block)
130149
block_hash = chaindb.get_canonical_block_hash(block.number)
131150
assert block_hash == block.hash
151+
152+
153+
def test_chaindb_get_receipt_by_index(
154+
chain,
155+
funded_address,
156+
funded_address_private_key):
157+
NUMBER_BLOCKS_IN_CHAIN = 5
158+
TRANSACTIONS_IN_BLOCK = 10
159+
REQUIRED_BLOCK_NUMBER = 2
160+
REQUIRED_RECEIPT_INDEX = 3
161+
162+
for block_number in range(NUMBER_BLOCKS_IN_CHAIN):
163+
for tx_index in range(TRANSACTIONS_IN_BLOCK):
164+
tx = new_transaction(
165+
chain.get_vm(),
166+
from_=funded_address,
167+
to=force_bytes_to_address(b'\x10\x10'),
168+
private_key=funded_address_private_key,
169+
)
170+
new_block, tx_receipt, computation = chain.apply_transaction(tx)
171+
assert computation.is_success
172+
173+
if (block_number + 1) == REQUIRED_BLOCK_NUMBER and tx_index == REQUIRED_RECEIPT_INDEX:
174+
actual_receipt = tx_receipt
175+
176+
chain.mine_block()
177+
178+
# Check that the receipt retrieved is indeed the actual one
179+
chaindb_retrieved_receipt = chain.chaindb.get_receipt_by_index(
180+
REQUIRED_BLOCK_NUMBER,
181+
REQUIRED_RECEIPT_INDEX,
182+
)
183+
assert chaindb_retrieved_receipt == actual_receipt
184+
185+
# Raise error if block number is not found
186+
with pytest.raises(ReceiptNotFound):
187+
chain.chaindb.get_receipt_by_index(
188+
NUMBER_BLOCKS_IN_CHAIN + 1,
189+
REQUIRED_RECEIPT_INDEX,
190+
)
191+
192+
# Raise error if receipt index is out of range
193+
with pytest.raises(ReceiptNotFound):
194+
chain.chaindb.get_receipt_by_index(
195+
NUMBER_BLOCKS_IN_CHAIN,
196+
TRANSACTIONS_IN_BLOCK + 1,
197+
)

0 commit comments

Comments
 (0)