Skip to content

Commit 83e35b5

Browse files
committed
Update benchmarks to use latest batched-mining API
- ERC20TransferBenchmark has a >10x performance improvement - ERC20DeployBenchmark - ERC20ApproveBenchmark - ERC20TransferFromBenchmark - Benchmark speedup for simple value transfers
1 parent e60423b commit 83e35b5

File tree

3 files changed

+132
-63
lines changed

3 files changed

+132
-63
lines changed

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.

scripts/benchmark/checks/erc20_interact.py

Lines changed: 108 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import logging
21
import pathlib
32
from typing import (
43
Tuple,
@@ -15,7 +14,6 @@
1514
from eth_utils import (
1615
encode_hex,
1716
decode_hex,
18-
to_int,
1917
)
2018

2119
from eth.constants import (
@@ -61,7 +59,7 @@
6159

6260
class BaseERC20Benchmark(BaseBenchmark):
6361

64-
def __init__(self, num_blocks: int = 10, num_tx: int = 2) -> None:
62+
def __init__(self, num_blocks: int = 2, num_tx: int = 50) -> None:
6563
super().__init__()
6664

6765
self.num_blocks = num_blocks
@@ -82,7 +80,7 @@ def _setup_benchmark(self, chain: MiningChain) -> None:
8280
pass
8381

8482
@abstractmethod
85-
def _apply_transaction(self, chain: MiningChain) -> None:
83+
def _next_transaction(self, chain: MiningChain) -> None:
8684
raise NotImplementedError(
8785
"Must be implemented by subclasses"
8886
)
@@ -114,7 +112,8 @@ def mine_blocks(self, chain: MiningChain, num_blocks: int, num_tx: int) -> Tuple
114112
total_gas_used = 0
115113
total_num_tx = 0
116114
for i in range(1, num_blocks + 1):
117-
block = self.mine_block(chain, i, num_tx)
115+
import_result = self.mine_block(chain, i, num_tx)
116+
block = import_result.imported_block
118117
total_gas_used = total_gas_used + block.header.gas_used
119118
total_num_tx = total_num_tx + len(block.transactions)
120119
return total_gas_used, total_num_tx
@@ -123,11 +122,19 @@ def mine_block(self,
123122
chain: MiningChain,
124123
block_number: int,
125124
num_tx: int) -> BaseBlock:
126-
for _ in range(1, num_tx + 1):
127-
self._apply_transaction(chain)
128-
return chain.mine_block()
125+
transactions, callbacks = zip(*(
126+
self._next_transaction(chain)
127+
for _ in range(num_tx)
128+
))
129129

130-
def _deploy_simple_token(self, chain: MiningChain) -> None:
130+
mining_result, receipts, computations = chain.mine_all(transactions)
131+
132+
for callback, receipt, computation in zip(callbacks, receipts, computations):
133+
callback(receipt, computation)
134+
135+
return mining_result
136+
137+
def _deploy_simple_token(self, chain: MiningChain, nonce: int = None) -> None:
131138
# Instantiate the contract
132139
SimpleToken = self.w3.eth.contract(
133140
abi=self.contract_interface['abi'],
@@ -143,21 +150,24 @@ def _deploy_simple_token(self, chain: MiningChain) -> None:
143150
amount=0,
144151
gas=FIRST_TX_GAS_LIMIT,
145152
data=decode_hex(w3_tx['data']),
153+
nonce=nonce,
146154
)
147-
logging.debug(f'Applying Transaction {tx}')
148-
block, receipt, computation = chain.apply_transaction(tx)
149-
# Keep track of deployed contract address
150-
self.deployed_contract_address = computation.msg.storage_address
151155

152-
computation.raise_if_error()
156+
def callback(receipt, computation) -> None:
157+
computation.raise_if_error()
153158

154-
# Keep track of simple_token object
155-
self.simple_token = self.w3.eth.contract(
156-
address=Web3.toChecksumAddress(encode_hex(self.deployed_contract_address)),
157-
abi=self.contract_interface['abi'],
158-
)
159+
# Keep track of deployed contract address
160+
self.deployed_contract_address = computation.msg.storage_address
161+
162+
# Keep track of simple_token object
163+
self.simple_token = self.w3.eth.contract(
164+
address=Web3.toChecksumAddress(encode_hex(self.deployed_contract_address)),
165+
abi=self.contract_interface['abi'],
166+
)
167+
168+
return tx, callback
159169

160-
def _erc_transfer(self, addr: str, chain: MiningChain) -> None:
170+
def _erc_transfer(self, addr: str, chain: MiningChain, nonce: int = None) -> None:
161171
w3_tx = self.simple_token.functions.transfer(
162172
addr,
163173
TRANSFER_AMOUNT
@@ -171,15 +181,15 @@ def _erc_transfer(self, addr: str, chain: MiningChain) -> None:
171181
amount=0,
172182
gas=SECOND_TX_GAS_LIMIT,
173183
data=decode_hex(w3_tx['data']),
184+
nonce=nonce,
174185
)
175186

176-
block, receipt, computation = chain.apply_transaction(tx)
187+
def callback(receipt, computation) -> None:
188+
computation.raise_if_error()
189+
assert computation.output == b'\0' * 31 + b'\x01', computation.output
190+
return tx, callback
177191

178-
computation.raise_if_error()
179-
180-
assert to_int(computation.output) == 1
181-
182-
def _erc_approve(self, addr2: str, chain: MiningChain) -> None:
192+
def _erc_approve(self, addr2: str, chain: MiningChain, nonce: int = None) -> None:
183193
w3_tx = self.simple_token.functions.approve(
184194
addr2,
185195
TRANSFER_AMOUNT
@@ -193,15 +203,21 @@ def _erc_approve(self, addr2: str, chain: MiningChain) -> None:
193203
amount=0,
194204
gas=SECOND_TX_GAS_LIMIT,
195205
data=decode_hex(w3_tx['data']),
206+
nonce=nonce,
196207
)
197208

198-
block, receipt, computation = chain.apply_transaction(tx)
209+
def callback(receipt, computation) -> None:
210+
computation.raise_if_error()
211+
assert computation.output == b'\0' * 31 + b'\x01', computation.output
199212

200-
computation.raise_if_error()
213+
return tx, callback
201214

202-
assert to_int(computation.output) == 1
203-
204-
def _erc_transfer_from(self, addr1: str, addr2: str, chain: MiningChain) -> None:
215+
def _erc_transfer_from(
216+
self,
217+
addr1: str,
218+
addr2: str,
219+
chain: MiningChain,
220+
nonce: int = None) -> None:
205221

206222
w3_tx = self.simple_token.functions.transferFrom(
207223
addr1,
@@ -217,41 +233,59 @@ def _erc_transfer_from(self, addr1: str, addr2: str, chain: MiningChain) -> None
217233
amount=0,
218234
gas=SECOND_TX_GAS_LIMIT,
219235
data=decode_hex(w3_tx['data']),
236+
nonce=nonce,
220237
)
221238

222-
block, receipt, computation = chain.apply_transaction(tx)
223-
224-
computation.raise_if_error()
239+
def callback(receipt, computation) -> None:
240+
computation.raise_if_error()
241+
assert computation.output == b'\0' * 31 + b'\x01', computation.output
225242

226-
assert to_int(computation.output) == 1
243+
return tx, callback
227244

228245

229246
class ERC20DeployBenchmark(BaseERC20Benchmark):
230247
def __init__(self) -> None:
231248
super().__init__()
249+
# Can only fit 2 deployments in a block
250+
self.num_tx = 2
232251

233252
@property
234253
def name(self) -> str:
235254
return 'ERC20 deployment'
236255

237-
def _apply_transaction(self, chain: MiningChain) -> None:
238-
self._deploy_simple_token(chain)
256+
def _setup_benchmark(self, chain: MiningChain) -> None:
257+
self._next_nonce = None
258+
259+
def _next_transaction(self, chain: MiningChain) -> None:
260+
txn_info = self._deploy_simple_token(chain, self._next_nonce)
261+
txn = txn_info[0]
262+
self._next_nonce = txn.nonce + 1
263+
return txn_info
239264

240265

241266
class ERC20TransferBenchmark(BaseERC20Benchmark):
242267
def __init__(self) -> None:
243268
super().__init__()
269+
self._next_nonce = None
244270

245271
@property
246272
def name(self) -> str:
247273
return 'ERC20 Transfer'
248274

249275
def _setup_benchmark(self, chain: MiningChain) -> None:
250-
self._deploy_simple_token(chain)
251-
chain.mine_block()
276+
self._next_nonce = None
252277

253-
def _apply_transaction(self, chain: MiningChain) -> None:
254-
self._erc_transfer(self.addr1, chain)
278+
txn, callback = self._deploy_simple_token(chain)
279+
_, receipts, computations = chain.mine_all([txn])
280+
assert len(receipts) == 1
281+
assert len(computations) == 1
282+
callback(receipts[0], computations[0])
283+
284+
def _next_transaction(self, chain: MiningChain) -> None:
285+
txn_info = self._erc_transfer(self.addr1, chain, self._next_nonce)
286+
txn = txn_info[0]
287+
self._next_nonce = txn.nonce + 1
288+
return txn_info
255289

256290

257291
class ERC20ApproveBenchmark(BaseERC20Benchmark):
@@ -263,11 +297,18 @@ def name(self) -> str:
263297
return 'ERC20 Approve'
264298

265299
def _setup_benchmark(self, chain: MiningChain) -> None:
266-
self._deploy_simple_token(chain)
267-
chain.mine_block()
300+
self._next_nonce = None
301+
txn, callback = self._deploy_simple_token(chain)
302+
_, receipts, computations = chain.mine_all([txn])
303+
assert len(receipts) == 1
304+
assert len(computations) == 1
305+
callback(receipts[0], computations[0])
268306

269-
def _apply_transaction(self, chain: MiningChain) -> None:
270-
self._erc_approve(self.addr2, chain)
307+
def _next_transaction(self, chain: MiningChain) -> None:
308+
txn_info = self._erc_approve(self.addr2, chain, self._next_nonce)
309+
txn = txn_info[0]
310+
self._next_nonce = txn.nonce + 1
311+
return txn_info
271312

272313

273314
class ERC20TransferFromBenchmark(BaseERC20Benchmark):
@@ -279,10 +320,25 @@ def name(self) -> str:
279320
return 'ERC20 TransferFrom'
280321

281322
def _setup_benchmark(self, chain: MiningChain) -> None:
282-
self._deploy_simple_token(chain)
283-
self._erc_transfer(self.addr1, chain)
284-
self._erc_approve(self.addr2, chain)
285-
chain.mine_block()
286-
287-
def _apply_transaction(self, chain: MiningChain) -> None:
288-
self._erc_transfer_from(self.addr1, self.addr2, chain)
323+
self._next_nonce = None
324+
txn, callback = self._deploy_simple_token(chain)
325+
_, receipts, computations = chain.mine_all([txn])
326+
assert len(receipts) == 1
327+
assert len(computations) == 1
328+
callback(receipts[0], computations[0])
329+
330+
actions = [
331+
self._erc_transfer(self.addr1, chain, nonce=1),
332+
self._erc_approve(self.addr2, chain, nonce=2),
333+
]
334+
transactions, callbacks = zip(*actions)
335+
_, receipts, computations = chain.mine_all(transactions)
336+
337+
for callback, receipt, computation in zip(callbacks, receipts, computations):
338+
callback(receipt, computation)
339+
340+
def _next_transaction(self, chain: MiningChain) -> None:
341+
txn_info = self._erc_transfer_from(self.addr1, self.addr2, chain, self._next_nonce)
342+
txn = txn_info[0]
343+
self._next_nonce = txn.nonce + 1
344+
return txn_info

scripts/benchmark/checks/simple_value_transfers.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
class SimpleValueTransferBenchmarkConfig(NamedTuple):
4242
to_address: Address
4343
greeter_info: str
44-
num_blocks: int = 1
44+
num_blocks: int = 2
4545

4646

4747
TO_EXISTING_ADDRESS_CONFIG = SimpleValueTransferBenchmarkConfig(
@@ -63,6 +63,7 @@ class SimpleValueTransferBenchmark(BaseBenchmark):
6363

6464
def __init__(self, config: SimpleValueTransferBenchmarkConfig) -> None:
6565
self.config = config
66+
self._next_nonce = None
6667

6768
@property
6869
def name(self) -> str:
@@ -77,6 +78,7 @@ def execute(self) -> DefaultStat:
7778
num_blocks = self.config.num_blocks
7879

7980
for chain in get_all_chains():
81+
self._next_nonce = None
8082

8183
value = self.as_timed_result(lambda: self.mine_blocks(chain, num_blocks))
8284

@@ -107,12 +109,18 @@ def mine_blocks(self, chain: MiningChain, num_blocks: int) -> Tuple[int, int]:
107109
return total_gas_used, total_num_tx
108110

109111
def mine_block(self, chain: MiningChain, block_number: int, num_tx: int) -> BaseBlock:
110-
for _ in range(1, num_tx + 1):
111-
self.apply_transaction(chain)
112+
actions = [self.next_transaction(chain) for _ in range(num_tx)]
112113

113-
return chain.mine_block()
114+
transactions, callbacks = zip(*actions)
114115

115-
def apply_transaction(self, chain: MiningChain) -> None:
116+
mining_result, receipts, computations = chain.mine_all(transactions)
117+
118+
for callback, receipt, computation in zip(callbacks, receipts, computations):
119+
callback(receipt, computation)
120+
121+
return mining_result.imported_block
122+
123+
def next_transaction(self, chain: MiningChain) -> None:
116124

117125
if self.config.to_address is None:
118126
to_address = generate_random_address()
@@ -125,13 +133,15 @@ def apply_transaction(self, chain: MiningChain) -> None:
125133
from_=FUNDED_ADDRESS,
126134
to=to_address,
127135
amount=100,
128-
data=b''
136+
data=b'',
137+
nonce=self._next_nonce,
129138
)
139+
logging.debug(f'Built Transaction {tx}')
130140

131-
logging.debug(f'Applying Transaction {tx}')
141+
self._next_nonce = tx.nonce + 1
132142

133-
block, receipt, computation = chain.apply_transaction(tx)
143+
def callback(receipt, computation) -> None:
144+
logging.debug(f'Receipt {receipt}')
145+
logging.debug(f'Computation {computation}')
134146

135-
logging.debug(f'Block {block}')
136-
logging.debug(f'Receipt {receipt}')
137-
logging.debug(f'Computation {computation}')
147+
return tx, callback

0 commit comments

Comments
 (0)