1
1
from __future__ import annotations
2
2
3
+ import asyncio
3
4
import cProfile
4
5
import random
5
6
import sqlite3
13
14
import click
14
15
import zstd
15
16
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
17
20
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
20
26
from chia .simulator .keyring import TempKeyring
21
27
from chia .types .blockchain_format .coin import Coin
22
28
from chia .util .chia_logging import initialize_logging
29
+ from chia .util .db_wrapper import DBWrapper2
23
30
24
31
25
32
@contextmanager
@@ -55,7 +62,7 @@ def enable_profiler(profile: bool, counter: int) -> Iterator[None]:
55
62
@click .option (
56
63
"--output" , type = str , required = False , default = None , help = "the filename to write the resulting sqlite database to"
57
64
)
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 :
59
66
if fill_rate < 0 or fill_rate > 100 :
60
67
print ("fill-rate must be within [0, 100]" )
61
68
sys .exit (1 )
@@ -76,150 +83,226 @@ def main(length: int, fill_rate: int, profile: bool, block_refs: bool, output: O
76
83
if output is None :
77
84
output = f"stress-test-blockchain-{ length } -{ fill_rate } { '-refs' if block_refs else '' } .sqlite"
78
85
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
+
79
102
root_path = Path ("./test-chain" ).resolve ()
80
103
root_path .mkdir (parents = True , exist_ok = True )
81
104
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 )
83
106
initialize_logging (
84
107
"generate_chain" , {"log_level" : "DEBUG" , "log_stdout" : False , "log_syslog" : False }, root_path = root_path
85
108
)
109
+ # Create types used below.
110
+ unspent_coins : list [Coin ] = []
111
+ new_coins : list [Coin ] = []
112
+ transaction_blocks : list [uint32 ] = []
86
113
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
97
116
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 ,
109
125
)
110
126
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 :
117
129
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)"
126
136
)
127
- db .commit ()
128
137
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 ()
130
141
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
+ )
132
149
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
136
167
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 }:
177
171
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(?, ?, ?, ?, ?)" ,
209
174
(
210
- b .header_hash ,
211
- b .prev_header_hash ,
212
- b .height ,
175
+ bl .header_hash ,
176
+ bl .prev_header_hash ,
177
+ bl .height ,
213
178
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 ,
215
230
)
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
222
305
223
306
224
307
if __name__ == "__main__" :
225
- main ()
308
+ main_cli ()
0 commit comments