Skip to content

Commit 4e6e594

Browse files
committed
not 100% working benchmark
1 parent 795b29e commit 4e6e594

File tree

1 file changed

+211
-128
lines changed

1 file changed

+211
-128
lines changed

tools/generate_chain.py

Lines changed: 211 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import asyncio
34
import cProfile
45
import random
56
import sqlite3
@@ -13,13 +14,19 @@
1314
import click
1415
import zstd
1516
from chia_rs import SpendBundle
16-
from chia_rs.sized_ints import uint32, uint64
17+
from chia_rs.chia_rs import FullBlock
18+
from chia_rs.sized_bytes import bytes32
19+
from chia_rs.sized_ints import uint8, uint32, uint64
1720

18-
from chia._tests.util.constants import test_constants
19-
from chia.simulator.block_tools import create_block_tools
21+
from benchmarks.mempool import BenchBlockRecord
22+
from chia._tests.util.constants import test_constants as tc
23+
from chia.full_node.coin_store import CoinStore
24+
from chia.full_node.mempool_manager import MempoolManager, SpendBundleAddInfo
25+
from chia.simulator.block_tools import create_block_tools_async
2026
from chia.simulator.keyring import TempKeyring
2127
from chia.types.blockchain_format.coin import Coin
2228
from chia.util.chia_logging import initialize_logging
29+
from chia.util.db_wrapper import DBWrapper2
2330

2431

2532
@contextmanager
@@ -55,7 +62,7 @@ def enable_profiler(profile: bool, counter: int) -> Iterator[None]:
5562
@click.option(
5663
"--output", type=str, required=False, default=None, help="the filename to write the resulting sqlite database to"
5764
)
58-
def main(length: int, fill_rate: int, profile: bool, block_refs: bool, output: Optional[str]) -> None:
65+
def main_cli(length: int, fill_rate: int, profile: bool, block_refs: bool, output: Optional[str]) -> None:
5966
if fill_rate < 0 or fill_rate > 100:
6067
print("fill-rate must be within [0, 100]")
6168
sys.exit(1)
@@ -76,150 +83,226 @@ def main(length: int, fill_rate: int, profile: bool, block_refs: bool, output: O
7683
if output is None:
7784
output = f"stress-test-blockchain-{length}-{fill_rate}{'-refs' if block_refs else ''}.sqlite"
7885

86+
# run the main async function
87+
asyncio.run(main(length, fill_rate, profile=profile, block_refs=block_refs, output=output))
88+
89+
90+
async def main(length: int, fill_rate: int, profile: bool, block_refs: bool, output: str) -> None:
91+
# this makes all blocks be generated with compressed generators
92+
test_constants = tc.replace(HARD_FORK_HEIGHT=uint32(0))
93+
94+
# configure mempool so we can fill blocks to what we want
95+
adusted_max_block_cost: uint64 = uint64(test_constants.MAX_BLOCK_COST_CLVM * fill_rate / 100)
96+
test_constants = test_constants.replace(MAX_BLOCK_COST_CLVM=adusted_max_block_cost)
97+
# 1 block buffer to only fill to max block cost
98+
test_constants = test_constants.replace(MEMPOOL_BLOCK_BUFFER=uint8(1))
99+
time_per_block = 18.75 # seconds
100+
block_timestamp: uint64 = uint64(1234567890)
101+
79102
root_path = Path("./test-chain").resolve()
80103
root_path.mkdir(parents=True, exist_ok=True)
81104
with TempKeyring() as keychain:
82-
bt = create_block_tools(constants=test_constants, root_path=root_path, keychain=keychain)
105+
bt = await create_block_tools_async(constants=test_constants, root_path=root_path, keychain=keychain)
83106
initialize_logging(
84107
"generate_chain", {"log_level": "DEBUG", "log_stdout": False, "log_syslog": False}, root_path=root_path
85108
)
109+
# Create types used below.
110+
unspent_coins: list[Coin] = []
111+
new_coins: list[Coin] = []
112+
transaction_blocks: list[uint32] = []
86113

87-
print(f"writing blockchain to {output}")
88-
with closing(sqlite3.connect(output)) as db:
89-
db.execute(
90-
"CREATE TABLE full_blocks("
91-
"header_hash blob PRIMARY KEY,"
92-
"prev_hash blob,"
93-
"height bigint,"
94-
"in_main_chain tinyint,"
95-
"block blob)"
96-
)
114+
print(f"initializing testing mempool with fill rate {fill_rate}%")
115+
# First we create a coin store and mempool manager, this seemed simpler then recreating one
97116

98-
wallet = bt.get_farmer_wallet_tool()
99-
farmer_puzzlehash = wallet.get_new_puzzlehash()
100-
pool_puzzlehash = wallet.get_new_puzzlehash()
101-
transaction_blocks: list[uint32] = []
102-
103-
blocks = bt.get_consecutive_blocks(
104-
3,
105-
farmer_reward_puzzle_hash=farmer_puzzlehash,
106-
pool_reward_puzzle_hash=pool_puzzlehash,
107-
keep_going_until_tx_block=True,
108-
genesis_timestamp=uint64(1234567890),
117+
# in memory db
118+
uri = "file::memory:?cache=shared"
119+
async with DBWrapper2.managed(database=uri, uri=True, reader_count=1, db_version=2) as db_wrapper:
120+
coin_store: CoinStore = await CoinStore.create(db_wrapper)
121+
mempool_manager: MempoolManager = MempoolManager(
122+
get_coin_records=coin_store.get_coin_records,
123+
get_unspent_lineage_info_for_puzzle_hash=coin_store.get_unspent_lineage_info_for_puzzle_hash,
124+
consensus_constants=test_constants,
109125
)
110126

111-
unspent_coins: list[Coin] = []
112-
113-
for b in blocks:
114-
for coin in b.get_included_reward_coins():
115-
if coin.puzzle_hash in {farmer_puzzlehash, pool_puzzlehash}:
116-
unspent_coins.append(coin)
127+
print(f"writing blockchain to {output}")
128+
with closing(sqlite3.connect(output)) as db:
117129
db.execute(
118-
"INSERT INTO full_blocks VALUES(?, ?, ?, ?, ?)",
119-
(
120-
b.header_hash,
121-
b.prev_header_hash,
122-
b.height,
123-
1, # in_main_chain
124-
zstd.compress(bytes(b)),
125-
),
130+
"CREATE TABLE full_blocks("
131+
"header_hash blob PRIMARY KEY,"
132+
"prev_hash blob,"
133+
"height bigint,"
134+
"in_main_chain tinyint,"
135+
"block blob)"
126136
)
127-
db.commit()
128137

129-
b = blocks[-1]
138+
wallet = bt.get_farmer_wallet_tool()
139+
farmer_puzzlehash = wallet.get_new_puzzlehash()
140+
pool_puzzlehash = wallet.get_new_puzzlehash()
130141

131-
num_tx_per_block = int(1010 * fill_rate / 100)
142+
blocks: list[FullBlock] = bt.get_consecutive_blocks(
143+
3,
144+
farmer_reward_puzzle_hash=farmer_puzzlehash,
145+
pool_reward_puzzle_hash=pool_puzzlehash,
146+
keep_going_until_tx_block=True,
147+
genesis_timestamp=block_timestamp,
148+
)
132149

133-
while True:
134-
with enable_profiler(profile, b.height):
135-
start_time = time.monotonic()
150+
prev_tx_height: uint32 = uint32(0)
151+
prev_tx_hash: Optional[bytes32] = None
152+
for bl in blocks:
153+
if bl.is_transaction_block():
154+
# Update coin store and mempool with new block
155+
br = BenchBlockRecord(
156+
header_hash=bl.header_hash,
157+
height=bl.height,
158+
timestamp=block_timestamp,
159+
prev_transaction_block_height=prev_tx_height,
160+
prev_transaction_block_hash=prev_tx_hash,
161+
)
162+
await coin_store.new_block(bl.height, block_timestamp, bl.get_included_reward_coins(), [], [])
163+
await mempool_manager.new_peak(br, [])
164+
prev_tx_height = bl.height
165+
prev_tx_hash = bl.header_hash
166+
prev_tx_block = bl # last transaction block
136167

137-
new_coins: list[Coin] = []
138-
spend_bundles: list[SpendBundle] = []
139-
i = 0
140-
for i in range(num_tx_per_block):
141-
if unspent_coins == []:
142-
break
143-
c = unspent_coins.pop(random.randrange(len(unspent_coins)))
144-
receiver = wallet.get_new_puzzlehash()
145-
bundle = wallet.generate_signed_transaction(uint64(c.amount // 2), receiver, c)
146-
new_coins.extend(bundle.additions())
147-
spend_bundles.append(bundle)
148-
149-
block_references: list[uint32]
150-
if block_refs:
151-
block_references = random.sample(transaction_blocks, min(len(transaction_blocks), 512))
152-
random.shuffle(block_references)
153-
else:
154-
block_references = []
155-
156-
farmer_puzzlehash = wallet.get_new_puzzlehash()
157-
pool_puzzlehash = wallet.get_new_puzzlehash()
158-
prev_num_blocks = len(blocks)
159-
blocks = bt.get_consecutive_blocks(
160-
1,
161-
blocks,
162-
farmer_reward_puzzle_hash=farmer_puzzlehash,
163-
pool_reward_puzzle_hash=pool_puzzlehash,
164-
keep_going_until_tx_block=True,
165-
transaction_data=SpendBundle.aggregate(spend_bundles),
166-
block_refs=block_references,
167-
)
168-
prev_tx_block = b
169-
prev_block = blocks[-2]
170-
b = blocks[-1]
171-
height = b.height
172-
assert b.is_transaction_block()
173-
transaction_blocks.append(height)
174-
175-
for bl in blocks[prev_num_blocks:]:
176-
for coin in bl.get_included_reward_coins():
168+
block_timestamp = uint64(block_timestamp + int(time_per_block))
169+
for coin in bl.get_included_reward_coins():
170+
if coin.puzzle_hash in {farmer_puzzlehash, pool_puzzlehash}:
177171
unspent_coins.append(coin)
178-
unspent_coins.extend(new_coins)
179-
180-
if b.transactions_info:
181-
actual_fill_rate = b.transactions_info.cost / test_constants.MAX_BLOCK_COST_CLVM
182-
if b.transactions_info.cost > test_constants.MAX_BLOCK_COST_CLVM:
183-
print(f"COST EXCEEDED: {b.transactions_info.cost}")
184-
else:
185-
actual_fill_rate = 0
186-
187-
end_time = time.monotonic()
188-
if prev_tx_block is not None:
189-
assert b.foliage_transaction_block
190-
assert prev_tx_block.foliage_transaction_block
191-
ts = b.foliage_transaction_block.timestamp - prev_tx_block.foliage_transaction_block.timestamp
192-
else:
193-
ts = 0
194-
195-
print(
196-
f"height: {b.height} "
197-
f"spends: {i + 1} "
198-
f"refs: {len(block_references)} "
199-
f"fill_rate: {actual_fill_rate * 100:.1f}% "
200-
f"new coins: {len(new_coins)} "
201-
f"unspent: {len(unspent_coins)} "
202-
f"difficulty: {b.weight - prev_block.weight} "
203-
f"timestamp: {ts} "
204-
f"time: {end_time - start_time:0.2f}s "
205-
f"tx-block-ratio: {len(transaction_blocks) * 100 / b.height:0.0f}% "
206-
)
207-
208-
new_blocks = [
172+
db.execute(
173+
"INSERT INTO full_blocks VALUES(?, ?, ?, ?, ?)",
209174
(
210-
b.header_hash,
211-
b.prev_header_hash,
212-
b.height,
175+
bl.header_hash,
176+
bl.prev_header_hash,
177+
bl.height,
213178
1, # in_main_chain
214-
zstd.compress(bytes(b)),
179+
zstd.compress(bytes(bl)),
180+
),
181+
)
182+
db.commit()
183+
184+
receiver = wallet.get_new_puzzlehash()
185+
while True:
186+
with enable_profiler(profile, prev_tx_block.height):
187+
start_time = time.monotonic()
188+
189+
# spend until we fill the block or we run out of coins
190+
spends = 0
191+
while unspent_coins != []:
192+
c = unspent_coins.pop(random.randrange(len(unspent_coins)))
193+
bundle: SpendBundle = wallet.generate_signed_transaction(uint64(c.amount // 2), receiver, c)
194+
b_name = bundle.name()
195+
# prevalidate the spend bundle
196+
pre_valid = await mempool_manager.pre_validate_spendbundle(bundle, b_name)
197+
new_coins.extend(bundle.additions())
198+
res: SpendBundleAddInfo = await mempool_manager.add_spend_bundle(
199+
bundle, pre_valid, b_name, prev_tx_block.height
200+
)
201+
if res.status != 1:
202+
# TODO: Validate this is correct
203+
# failed to add, put coin back as block should be full
204+
unspent_coins.append(c)
205+
break
206+
spends += 1
207+
208+
block_references: list[uint32]
209+
if block_refs:
210+
block_references = random.sample(transaction_blocks, min(len(transaction_blocks), 512))
211+
random.shuffle(block_references)
212+
else:
213+
block_references = []
214+
215+
farmer_puzzlehash = wallet.get_new_puzzlehash()
216+
pool_puzzlehash = wallet.get_new_puzzlehash()
217+
prev_num_blocks = len(blocks)
218+
# Get next spend bundle from mempool
219+
res_bundle = mempool_manager.create_bundle_from_mempool(prev_tx_block.header_hash)
220+
assert res_bundle is not None
221+
spend_bundle, _ = res_bundle
222+
blocks = bt.get_consecutive_blocks(
223+
1,
224+
blocks,
225+
farmer_reward_puzzle_hash=farmer_puzzlehash,
226+
pool_reward_puzzle_hash=pool_puzzlehash,
227+
keep_going_until_tx_block=True,
228+
transaction_data=spend_bundle,
229+
block_refs=block_references,
215230
)
216-
for b in blocks[prev_num_blocks:]
217-
]
218-
db.executemany("INSERT INTO full_blocks VALUES(?, ?, ?, ?, ?)", new_blocks)
219-
db.commit()
220-
if height >= length:
221-
break
231+
prev_block = blocks[-2]
232+
prev_tx_block = blocks[-1]
233+
height = prev_tx_block.height
234+
assert prev_tx_block.is_transaction_block()
235+
# Update coin store and mempool with new block
236+
transaction_blocks.append(height)
237+
await coin_store.new_block(
238+
prev_tx_block.height,
239+
block_timestamp,
240+
prev_tx_block.get_included_reward_coins(),
241+
[(coin.name(), coin, False) for coin in spend_bundle.additions()],
242+
[coin.name() for coin in spend_bundle.removals()],
243+
)
244+
br = BenchBlockRecord(
245+
header_hash=prev_tx_block.header_hash,
246+
height=prev_tx_block.height,
247+
timestamp=block_timestamp,
248+
prev_transaction_block_height=prev_tx_height,
249+
prev_transaction_block_hash=prev_tx_hash,
250+
)
251+
await mempool_manager.new_peak(br, [])
252+
block_timestamp = uint64(block_timestamp + int(time_per_block))
253+
prev_tx_height = prev_tx_block.height
254+
255+
for bl in blocks[prev_num_blocks:]:
256+
for coin in bl.get_included_reward_coins():
257+
unspent_coins.append(coin)
258+
unspent_coins.extend(new_coins)
259+
260+
if prev_tx_block.transactions_info:
261+
actual_fill_rate = prev_tx_block.transactions_info.cost / test_constants.MAX_BLOCK_COST_CLVM
262+
if prev_tx_block.transactions_info.cost > test_constants.MAX_BLOCK_COST_CLVM:
263+
print(f"COST EXCEEDED: {prev_tx_block.transactions_info.cost}")
264+
else:
265+
actual_fill_rate = 0
266+
267+
end_time = time.monotonic()
268+
if prev_tx_block is not None:
269+
assert prev_tx_block.foliage_transaction_block
270+
assert prev_tx_block.foliage_transaction_block
271+
ts = (
272+
prev_tx_block.foliage_transaction_block.timestamp
273+
- prev_tx_block.foliage_transaction_block.timestamp
274+
)
275+
else:
276+
ts = 0
277+
278+
print(
279+
f"height: {prev_tx_block.height} "
280+
f"spends: {spends + 1} "
281+
f"refs: {len(block_references)} "
282+
f"fill_rate: {actual_fill_rate * 100:.1f}% "
283+
f"new coins: {len(new_coins)} "
284+
f"unspent: {len(unspent_coins)} "
285+
f"difficulty: {prev_tx_block.weight - prev_block.weight} "
286+
f"timestamp: {ts} "
287+
f"time: {end_time - start_time:0.2f}s "
288+
f"tx-block-ratio: {len(transaction_blocks) * 100 / prev_tx_block.height:0.0f}% "
289+
)
290+
291+
new_blocks = [
292+
(
293+
prev_tx_block.header_hash,
294+
prev_tx_block.prev_header_hash,
295+
prev_tx_block.height,
296+
1, # in_main_chain
297+
zstd.compress(bytes(prev_tx_block)),
298+
)
299+
for prev_tx_block in blocks[prev_num_blocks:]
300+
]
301+
db.executemany("INSERT INTO full_blocks VALUES(?, ?, ?, ?, ?)", new_blocks)
302+
db.commit()
303+
if height >= length:
304+
break
222305

223306

224307
if __name__ == "__main__":
225-
main()
308+
main_cli()

0 commit comments

Comments
 (0)