Skip to content

Commit 5e949a4

Browse files
authored
Merge pull request #1967 from carver/mine-batched-txns
Benchmarks: Mine transactions in a batch to show real import speed
2 parents 16e69e0 + 83e35b5 commit 5e949a4

File tree

11 files changed

+282
-85
lines changed

11 files changed

+282
-85
lines changed

eth/abc.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,16 @@ class BlockAndMetaWitness(NamedTuple):
480480
meta_witness: MetaWitnessAPI
481481

482482

483+
class BlockPersistResult(NamedTuple):
484+
"""
485+
After persisting a block into the active chain, this information
486+
becomes available.
487+
"""
488+
imported_block: BlockAPI
489+
new_canonical_blocks: Tuple[BlockAPI, ...]
490+
old_canonical_blocks: Tuple[BlockAPI, ...]
491+
492+
483493
class BlockImportResult(NamedTuple):
484494
"""
485495
After importing and persisting a block into the active chain, this information
@@ -2785,9 +2795,9 @@ def import_block(self, block: BlockAPI) -> BlockAndMetaWitness:
27852795
...
27862796

27872797
@abstractmethod
2788-
def mine_block(self, *args: Any, **kwargs: Any) -> BlockAndMetaWitness:
2798+
def mine_block(self, block: BlockAPI, *args: Any, **kwargs: Any) -> BlockAndMetaWitness:
27892799
"""
2790-
Mine the current block. Proxies to self.pack_block method.
2800+
Mine the given block. Proxies to self.pack_block method.
27912801
"""
27922802
...
27932803

@@ -3586,6 +3596,24 @@ def __init__(self, base_db: AtomicDatabaseAPI, header: BlockHeaderAPI = None) ->
35863596
"""
35873597
...
35883598

3599+
@abstractmethod
3600+
def mine_all(
3601+
self,
3602+
transactions: Sequence[SignedTransactionAPI],
3603+
*args: Any,
3604+
parent_header: BlockHeaderAPI = None,
3605+
**kwargs: Any,
3606+
) -> Tuple[BlockImportResult, Tuple[ReceiptAPI, ...], Tuple[ComputationAPI, ...]]:
3607+
"""
3608+
Build a block with the given transactions, and mine it.
3609+
3610+
Optionally, supply the parent block header to mine on top of.
3611+
3612+
This is much faster than individually running :meth:`apply_transaction`
3613+
and then :meth:`mine_block`.
3614+
"""
3615+
...
3616+
35893617
@abstractmethod
35903618
def apply_transaction(self,
35913619
transaction: SignedTransactionAPI

eth/chains/base.py

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
AtomicDatabaseAPI,
4545
BlockHeaderAPI,
4646
BlockImportResult,
47+
BlockPersistResult,
4748
ChainAPI,
4849
ChainDatabaseAPI,
4950
ConsensusContextAPI,
@@ -481,17 +482,27 @@ def import_block(self,
481482
except ValidationError:
482483
self.logger.warning("Proposed %s doesn't follow EVM rules, rejecting...", block)
483484
raise
484-
self.validate_block(imported_block)
485+
486+
persist_result = self.persist_block(imported_block, perform_validation)
487+
return BlockImportResult(*persist_result, block_result.meta_witness)
488+
489+
def persist_block(
490+
self,
491+
block: BlockAPI,
492+
perform_validation: bool = True) -> BlockPersistResult:
493+
494+
if perform_validation:
495+
self.validate_block(block)
485496

486497
(
487498
new_canonical_hashes,
488499
old_canonical_hashes,
489-
) = self.chaindb.persist_block(imported_block)
500+
) = self.chaindb.persist_block(block)
490501

491502
self.logger.debug(
492-
'IMPORTED_BLOCK: number %s | hash %s',
493-
imported_block.number,
494-
encode_hex(imported_block.hash),
503+
'Persisted block: number %s | hash %s',
504+
block.number,
505+
encode_hex(block.hash),
495506
)
496507

497508
new_canonical_blocks = tuple(
@@ -505,11 +516,10 @@ def import_block(self,
505516
in old_canonical_hashes
506517
)
507518

508-
return BlockImportResult(
509-
imported_block=imported_block,
519+
return BlockPersistResult(
520+
imported_block=block,
510521
new_canonical_blocks=new_canonical_blocks,
511522
old_canonical_blocks=old_canonical_blocks,
512-
meta_witness=block_result.meta_witness,
513523
)
514524

515525
#
@@ -667,11 +677,43 @@ def import_block(self,
667677
self.header = self.ensure_header()
668678
return result
669679

680+
def mine_all(
681+
self,
682+
transactions: Sequence[SignedTransactionAPI],
683+
*args: Any,
684+
parent_header: BlockHeaderAPI = None,
685+
**kwargs: Any,
686+
) -> Tuple[BlockImportResult, Tuple[ReceiptAPI, ...], Tuple[ComputationAPI, ...]]:
687+
688+
if parent_header is None:
689+
base_header = self.header
690+
else:
691+
base_header = self.create_header_from_parent(parent_header)
692+
693+
vm = self.get_vm(base_header)
694+
695+
new_header, receipts, computations = vm.apply_all_transactions(transactions, base_header)
696+
filled_block = vm.set_block_transactions(vm.get_block(), new_header, transactions, receipts)
697+
698+
block_result = vm.mine_block(filled_block, *args, **kwargs)
699+
imported_block = block_result.block
700+
701+
block_persist_result = self.persist_block(imported_block)
702+
block_import_result = BlockImportResult(*block_persist_result, block_result.meta_witness)
703+
704+
self.header = self.create_header_from_parent(imported_block.header)
705+
return (block_import_result, receipts, computations)
706+
670707
def mine_block(self, *args: Any, **kwargs: Any) -> BlockAPI:
708+
"""
709+
Mine whatever transactions have been incrementally applied so far.
710+
"""
671711
return self.mine_block_extended(*args, **kwargs).block
672712

673713
def mine_block_extended(self, *args: Any, **kwargs: Any) -> BlockAndMetaWitness:
674-
mine_result = self.get_vm(self.header).mine_block(*args, **kwargs)
714+
vm = self.get_vm(self.header)
715+
current_block = vm.get_block()
716+
mine_result = vm.mine_block(current_block, *args, **kwargs)
675717
mined_block = mine_result.block
676718

677719
self.validate_block(mined_block)

eth/tools/builder/chain/builders.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,8 @@ def mine_block(chain: MiningChainAPI, **kwargs: Any) -> MiningChainAPI:
366366
raise ValidationError('`mine_block` may only be used on MiningChain instances')
367367

368368
transactions = kwargs.pop('transactions', ())
369-
for tx in transactions:
370-
chain.apply_transaction(tx)
371369

372-
chain.mine_block(**kwargs)
370+
chain.mine_all(transactions, **kwargs)
373371
return chain
374372

375373

eth/tools/factories/transaction.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ def new_transaction(
1515
gas_price=10,
1616
gas=100000,
1717
data=b'',
18+
nonce=None,
1819
chain_id=None):
1920
"""
2021
Create and return a transaction sending amount from <from_> to <to>.
2122
2223
The transaction will be signed with the given private key.
2324
"""
24-
nonce = vm.state.get_nonce(from_)
25+
if nonce is None:
26+
nonce = vm.state.get_nonce(from_)
27+
2528
tx = vm.create_unsigned_transaction(
2629
nonce=nonce,
2730
gas_price=gas_price,

eth/vm/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,17 +305,17 @@ def import_block(self, block: BlockAPI) -> BlockAndMetaWitness:
305305
# run all of the transactions.
306306
new_header, receipts, _ = self.apply_all_transactions(block.transactions, header)
307307

308-
self._block = self.set_block_transactions(
308+
block_with_transactions = self.set_block_transactions(
309309
self.get_block(),
310310
new_header,
311311
block.transactions,
312312
receipts,
313313
)
314314

315-
return self.mine_block()
315+
return self.mine_block(block_with_transactions)
316316

317-
def mine_block(self, *args: Any, **kwargs: Any) -> BlockAndMetaWitness:
318-
packed_block = self.pack_block(self.get_block(), *args, **kwargs)
317+
def mine_block(self, block: BlockAPI, *args: Any, **kwargs: Any) -> BlockAndMetaWitness:
318+
packed_block = self.pack_block(block, *args, **kwargs)
319319

320320
block_result = self.finalize_block(packed_block)
321321

newsfragments/1967.performance.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Got a >10x speedup of some benchmarks and other tests, by adding a new :meth`MiningChain.mine_all`
2+
API and using it. This is a public API, and should be used whenever all the transactions are known
3+
up front, to get a significant speedup.

0 commit comments

Comments
 (0)