11import chalk from "chalk" ;
2- import { ApiPromise , WsProvider } from "@polkadot/api" ;
32import fs from "fs/promises" ;
43import { getApiFor , NETWORK_YARGS_OPTIONS } from "src/utils/networks.ts" ;
54import yargs from "yargs" ;
65import { runTask , spawnTask } from "src/utils/runner.ts" ;
76import { blake2AsHex } from "@polkadot/util-crypto" ;
8- import { stringToHex } from "@polkadot/util" ;
9- import { convertExponentials } from '@zombienet/utils' ;
10- import jsonBg from "json-bigint" ;
117import { ALITH_PRIVATE_KEY } from "src/utils/constants.ts" ;
12- import { getBlockDetails , listenBlocks } from "src/utils/monitoring.ts" ;
8+ import { getBlockDetails , } from "src/utils/monitoring.ts" ;
139import { TxWithEventAndFee } from "src/utils/types.ts" ;
10+ import { isAscii , u8aToString , } from "@polkadot/util" ;
1411
15- const JSONbig = jsonBg ( { useNativeBigInt : true } ) ;
12+ import type { GenericEvent } from "@polkadot/types/generic" ;
13+
14+ import Debug from "debug" ;
15+ const debug = Debug ( "tools:replay-block" ) ;
1616
1717export const NETWORK_WS_URLS : { [ name : string ] : string } = {
1818 rococo : "wss://rococo-rpc.polkadot.io" ,
@@ -34,6 +34,7 @@ const argv = yargs(process.argv.slice(2))
3434 type : "string" ,
3535 description : "HTTP(S) url" ,
3636 string : true ,
37+ required : true
3738 } ,
3839 "moonbeam-binary" : {
3940 type : "string" ,
@@ -52,7 +53,6 @@ const argv = yargs(process.argv.slice(2))
5253
5354const main = async ( ) => {
5455
55-
5656 const logHandlers = [ ] ;
5757 const exitPromises = [ ] ;
5858 const processes = [ ] ;
@@ -64,7 +64,7 @@ const main = async () => {
6464 const atBlock = argv . at ? argv . at : ( await api . rpc . chain . getHeader ( ) ) . number . toNumber ( ) ;
6565 const originalBlockHash = await api . rpc . chain . getBlockHash ( atBlock ) ;
6666 const originalBlock = await api . rpc . chain . getBlock ( originalBlockHash ) ;
67- const apiAt = await api . at ( originalBlockHash ) ;
67+ const originalApiAt = await api . at ( originalBlockHash ) ;
6868
6969 const parentHash = originalBlock . block . header . parentHash . toHex ( ) ;
7070 const moonbeamBinaryPath = argv [ "moonbeam-binary" ] ; // to improve
@@ -84,13 +84,14 @@ const main = async () => {
8484 try {
8585 alive = false ;
8686 await Promise . race ( exitPromises ) ;
87+ console . log ( `Disconnecting....` ) ;
88+ await Promise . all ( apis . map ( ( handler ) => handler . disconnect ( ) ) ) ;
89+ console . log ( `Killing....` ) ;
90+ await Promise . all ( processes . map ( ( p ) => p . close ( ) ) ) ;
8791 console . log ( `Closing....` ) ;
8892 await Promise . all ( logHandlers . map ( ( handler ) => handler . close ( ) ) ) ;
89- console . log ( `Killing....` ) ;
90- await Promise . all ( processes . map ( ( handler ) => handler . close ( ) ) ) ;
91- await Promise . all ( apis . map ( ( handler ) => handler . disconnect ( ) ) ) ;
92- await lazyApi . disconnect ( ) ;
93- await api . disconnect ( ) ;
93+ console . log ( `Done` ) ;
94+ process . exit ( 0 ) ;
9495 } catch ( err ) {
9596 // console.log(err.message);
9697 }
@@ -113,7 +114,8 @@ const main = async () => {
113114
114115 const lazyParams = [
115116 `--lazy-loading-remote-rpc=${ argv [ 'fork-url' ] } ` ,
116- `--lazy-loading-delay-between-requests 5` ,
117+ `--lazy-loading-delay-between-requests 1` ,
118+ `--lazy-loading-max-retries-per-request 0` ,
117119 `--lazy-loading-runtime-override=${ argv . runtime } ` ,
118120 `--block=${ parentHash } ` ,
119121 `--alice` ,
@@ -123,13 +125,30 @@ const main = async () => {
123125 `--sealing=manual` ,
124126 ]
125127
128+ const logs = [
129+ `debug` ,
130+ `author-filtering=info` ,
131+ // `basic-authorship=info`,
132+ `parachain=info,grandpa=info` ,
133+ `netlink=info,sync=info,lib=info,multi=info` ,
134+ `trie=info,parity-db=info,h2=info` ,
135+ `wasm_overrides=info,wasmtime_cranelift=info,wasmtime_jit=info,code-provider=info,wasm-heap=info` ,
136+ `evm=info` ,
137+ `txpool=info` ,
138+ `json=info` ,
139+ `lazy=info`
140+ ]
141+ const logParams = logs . length > 0 ? [ `--log=${ logs . join ( "," ) } ` ] : [ ] ;
142+
126143 const alithLogs = "./alith.log"
127144 const alithLogHandler = await fs . open ( alithLogs , "w" ) ;
128145 logHandlers . push ( alithLogHandler ) ;
146+
147+ process . stdout . write ( `\t - ${ chalk . yellow ( `${ moonbeamBinaryPath } ` ) } ...` ) ;
129148 const alithProcess = await spawnTask (
130- `${ moonbeamBinaryPath } ${ commonParams . join ( " " ) } ${ lazyParams . join ( " " ) } ` ,
149+ `${ moonbeamBinaryPath } ${ commonParams . join ( " " ) } ${ lazyParams . join ( " " ) } ${ logParams . join ( " " ) } `
131150 ) ;
132- processes . push ( alithProcess ) ;
151+ process . stdout . write ( ` ✓\n` ) ;
133152
134153 exitPromises . push ( new Promise < void > ( ( resolve ) => {
135154 alithProcess . stderr . pipe ( alithProcess . stdout . pipe ( alithLogHandler . createWriteStream ( ) ) ) ;
@@ -164,26 +183,41 @@ const main = async () => {
164183
165184 const specVersion = await lazyApi . runtimeVersion . specVersion . toNumber ( ) ;
166185 console . log ( `Lazy loaded chain spec version: ${ specVersion } ` ) ;
186+ console . log ( `Creating a block to ensure migration is done` ) ;
167187 await lazyApi . rpc . engine . createBlock ( true , false )
168- await lazyApi . rpc . engine . createBlock ( true , false ) ;
188+ // await lazyApi.rpc.engine.createBlock(true, false);
169189
170- const printExt = ( prefix : string , tx ) => {
190+ const formatExtrinsic = ( prefix : string , tx ) => {
171191 if ( ! tx ) {
172- console . log ( `[${ prefix } ] Transaction missing` ) ;
173- }
174- else {
175- console . log ( `[${ prefix } ] Transaction ${ tx . extrinsic . method . section . toString ( ) } .${ tx . extrinsic . method . method . toString ( ) } found ${ ! tx . dispatchError ? "✅" : "🟥" } (ref: ${ tx . dispatchInfo . weight . refTime . toString ( ) . padStart ( 12 ) } , pov: ${ tx . dispatchInfo . weight . proofSize . toString ( ) . padStart ( 9 ) } )` ) ;
192+ return `[${ prefix } ] Transaction missing` ;
176193 }
194+ return `[${ prefix } ] Transaction ${ tx . extrinsic . method . section . toString ( ) } .${ tx . extrinsic . method . method . toString ( ) } found ${ ! tx . dispatchError ? "✅" : "🟥" } (ref: ${ tx . dispatchInfo . weight . refTime . toString ( ) . padStart ( 12 ) } , pov: ${ tx . dispatchInfo . weight . proofSize . toString ( ) . padStart ( 9 ) } )` ;
177195 }
178196
197+ const formatEventDiff = ( original : GenericEvent , replayed : GenericEvent ) => {
198+ const types = original . typeDef [ 0 ] ;
199+ console . log ( types . namespace ) ;
200+ for ( let index = 0 ; index < types ?. sub ?. [ 0 ] ; index ++ ) {
201+ console . log ( original . data [ types . sub [ index ] . name ] , original . data [ types . sub [ index ] . name ] ) ;
202+ }
203+ }
179204
180- const printEvent = ( e : any ) => {
205+ const printEvent = ( e : any , index : number ) => {
181206 const types = e . typeDef ;
182207 //console.log(`\t\t${e.meta.documentation.toString()}`);
183208 const lines = e . data . map ( ( data , index ) => {
184- return `${ typeof types [ index ] . type == "object" ? "" : types [ index ] . type } : ${ data . toString ( ) } ` ;
209+ let line = ` [${ types [ index ] . lookupName || types [ index ] . typeName || types [ index ] . namespace || types [ index ] . type } ]` ;
210+ let subs = types [ index ] . sub || [ ] ;
211+ if ( subs . length > 0 ) {
212+ for ( let subIndex = 0 ; subIndex < subs . length ; subIndex ++ ) {
213+ line += `\n\t\t-${ subs [ subIndex ] . name } : ${ data [ subs [ subIndex ] . name ] ?. toString ?.( ) } ` ;
214+ }
215+ } else {
216+ line += ` ${ data . toString ( ) } ` ;
217+ }
218+ return line ;
185219 } ) . join ( ' - ' ) ;
186- console . log ( `\t${ e . section . toString ( ) } .${ e . method . toString ( ) } \t${ lines } ` )
220+ console . log ( `\t[ ${ index } ] ${ e . section . toString ( ) } .${ e . method . toString ( ) } \t${ lines } ` )
187221 }
188222
189223 const mapEventLine = ( e : any ) => {
@@ -202,91 +236,116 @@ const main = async () => {
202236 return data ;
203237 }
204238
205- const compare = ( txA : TxWithEventAndFee , txB : TxWithEventAndFee ) => {
239+ const compareExtrinsics = ( { original , replayed } : { original : TxWithEventAndFee , replayed ? : TxWithEventAndFee } ) => {
206240 // compareItem(txA, txB, " - Error", "dispatchError");
241+ debug ( `[${ original . extrinsic . hash . toHex ( ) } ] Checking transaction ${ original ?. fees ?. totalFees } vs ${ replayed ?. fees ?. totalFees } ` ) ;
207242 let valid = true ;
208- for ( let index = 0 ; index < txA . events . length ; index ++ ) {
209- const eventA = txA . events [ index ] ;
210- const eventB = txB . events [ index ] ;
211- if ( ! eventA || ! eventB || ! eventA . eq ( eventB ) ) {
212- if ( eventA . method == eventB . method &&
213- ( ( eventA . section . toString ( ) == "balances" && eventA . method . toString ( ) == "Deposit" ) ||
214- ( eventA . section . toString ( ) == "system" && eventA . method . toString ( ) == "ExtrinsicSuccess" ) ) ) {
215- continue ;
216- }
243+ const eventsA = original . events || [ ] ;
244+ const eventsB = replayed ? .events || [ ] ;
245+ for ( let index = 0 ; index < eventsA . length ; index ++ ) {
246+ const eventA = original ? eventsA [ index ] : null ;
247+ const eventB = replayed ? eventsB [ index ] : null ;
248+ debug ( `[ ${ original . extrinsic . hash . toHex ( ) } , ${ replayed . extrinsic . hash . toHex ( ) } ]` , eventA . section , eventA . method , eventB ?. section , eventB ?. method )
249+ // debug(' ', eventA?.data?.[0]?.['topics']?. toString?.(), eventB?.data?.[0]?.['topics']?. toString?.())
250+
251+ if ( ! eventA || ! eventB ) {
217252 valid = false ;
253+ } else if ( eventA . eq ( eventB ) ) {
254+ continue ;
255+ } else if ( eventA . method == eventB . method &&
256+ ( ( eventA . section == "balances" && eventA . method == "Deposit" ) ||
257+ ( eventA . section == "system" && eventA . method == "ExtrinsicSuccess" ) ) ) {
258+ continue ;
259+ } else if ( eventA . method == eventB . method &&
260+ eventA . section == "evm" && eventA . method == "Log" && eventA . data [ 0 ] [ 'topics' ] == "0x793ee8b0d8020fc042a920607e3cbd37f5132c011786c8dd10a685f4414ed381" ) {
261+ // This contains timestamp: see https://moonbeam.moonscan.io/tx/0x8c686e819c7656bef9a37421d30cb101218c71fc5608bc76d51656a0992d556a#eventlog
262+ continue ;
263+ } else if ( eventA . method == eventB . method &&
264+ eventA . section == "evm" && eventA . method == "Log" ) {
265+ // This contains timestamp: see https://moonbeam.moonscan.io/tx/0x8c686e819c7656bef9a37421d30cb101218c71fc5608bc76d51656a0992d556a#eventlog
266+ // console.log(eventA.data[0]['topics'].toString());
218267 }
268+ valid = false ;
219269 }
220270
221- if ( txA . events . length !== txB . events . length ) {
271+ if ( eventsA . length !== replayed ? .events ? .length ) {
222272 valid = false ;
223273 }
224- console . log ( `[${ txA . extrinsic . hash . toHex ( ) } ] Events match: ${ valid ? "✅" : "🟥" } ` ) ;
274+ const ethExecution = eventsB . find ( ( e ) => e . section == "ethereum" && e . method == "Executed" ) ;
275+ if ( ! valid && ethExecution ) {
276+ const extra = isAscii ( ethExecution . data [ 4 ] . toU8a ( ) )
277+ ? u8aToString ( ethExecution . data [ 4 ] . toU8a ( ) )
278+ : ethExecution . data [ 4 ] . toString ( )
279+ if ( extra . includes ( "Transaction too old" ) ) {
280+ console . log ( `[${ original . extrinsic . hash . toHex ( ) } ] ${ chalk . yellow ( "Skipping" ) } due to "${ extra } "` ) ;
281+ valid = true ;
282+ }
283+ }
284+
225285 if ( ! valid ) {
226- console . log ( `[${ txA . extrinsic . hash . toHex ( ) } ] Events do not match ` ) ;
227- for ( let index = 0 ; index < Math . max ( txA . events . length , txB . events . length ) ; index ++ ) {
228- const eventA = txA . events . length > index ? txA . events [ index ] : null ;
229- const eventB = txB . events . length > index ? txB . events [ index ] : null ;
286+ console . log ( `[${ original . extrinsic . hash . toHex ( ) } ] Events match: ${ valid ? "✅" : `🟥 ${ replayed ?. events ?. length ? '' : 'Missing events' } ` } ` ) ;
287+ for ( let index = 0 ; index < Math . max ( eventsA . length , eventsB . length ) ; index ++ ) {
288+ const eventA = eventsA . length > index ? eventsA [ index ] : null ;
289+ const eventB = eventsB . length > index ? eventsB [ index ] : null ;
230290
231- const simA = mapEventLine ( eventA ) ;
232- const simB = mapEventLine ( eventB ) ;
291+ // const simA = mapEventLine(eventA);
292+ // const simB = mapEventLine(eventB);
233293 // compareObjects(simA, simB);
234294 if ( ! eventA || ! eventB || ! eventA . eq ( eventB ) ) {
235- console . log ( ` ${ index } ` ) ;
236295 if ( eventA ) {
237- printEvent ( eventA ) ;
296+ printEvent ( eventA , index ) ;
238297 }
239298 if ( eventB ) {
240- printEvent ( eventB ) ;
299+ printEvent ( eventB , index ) ;
241300 }
242301 }
243302 }
244303 }
304+ return valid ;
245305 }
246306
247307 let foundBlock = 0 ;
248308
249309 const submitBlock = async ( exts ) => {
250- await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
251310 await lazyApi . rpc . engine . createBlock ( true , false ) ;
252- await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
253- const currentBlockNumber = ( await api . rpc . chain . getHeader ( ) ) . number . toNumber ( ) ;
254- const currentBlockHash = await api . rpc . chain . getBlockHash ( atBlock ) ;
255- console . log ( `Block #${ currentBlockNumber } [${ currentBlockHash . toString ( ) } ]` ) ;
311+ const currentBlockNumber = ( await lazyApi . rpc . chain . getHeader ( ) ) . number . toNumber ( ) ;
312+ const currentBlockHash = await lazyApi . rpc . chain . getBlockHash ( currentBlockNumber ) ;
256313 const block = await getBlockDetails ( lazyApi , currentBlockHash ) ;
257- console . log ( `Block # ${ currentBlockNumber } [ ${ block . txWithEvents . length } txs]` ) ;
314+ foundBlock ++ ;
258315 for ( const tx of block . txWithEvents ) {
259- console . log ( `[Lazy] Transaction ${ tx . extrinsic . method . section . toString ( ) } .${ tx . extrinsic . method . method . toString ( ) } found ${ ! tx . dispatchError ? "✅" : "🟥" } (ref: ${ tx . dispatchInfo . weight . refTime . toString ( ) . padStart ( 12 ) } , pov: ${ tx . dispatchInfo . weight . proofSize . toString ( ) . padStart ( 9 ) } )` ) ;
316+ // console.log(`[Lazy] Transaction ${tx.extrinsic.method.section.toString()}.${tx.extrinsic.method.method.toString()} found ${!tx.dispatchError ? "✅" : "🟥"} (ref: ${tx.dispatchInfo.weight.refTime.toString().padStart(12)}, pov: ${tx.dispatchInfo.weight.proofSize.toString().padStart(9)}) [${tx.events.length} events] `);
260317 if ( exts [ tx . extrinsic . hash . toHex ( ) ] ) {
261- foundBlock ++ ;
262- exts [ tx . extrinsic . hash . toHex ( ) ] . lazyEx = tx ;
318+ exts [ tx . extrinsic . hash . toHex ( ) ] . replayed = tx ;
263319 }
264320 }
265- if ( foundBlock > 0 ) {
266- for ( const hash in exts ) {
267- //printExt("Official", exts[hash].ex);
268- //printExt(" Lazy", exts[hash].lazyEx);
269- compare ( exts [ Object . keys ( exts ) [ 0 ] ] . ex , exts [ Object . keys ( exts ) [ 0 ] ] . lazyEx ) ;
321+ let valid = true ;
322+ for ( const hash in exts ) {
323+ debug ( formatExtrinsic ( "Official" , exts [ hash ] . ex ) ) ;
324+ debug ( formatExtrinsic ( " Lazy" , exts [ hash ] . lazyEx ) ) ;
325+ if ( ! compareExtrinsics ( exts [ hash ] ) ) {
326+ valid = false ;
270327 }
271328 }
329+ console . log ( ` - produced Block #${ currentBlockNumber } [${ block . txWithEvents . length } txs] ${ valid ? "✅" : "🟥" } ` ) ;
330+ return valid ;
272331 } ;
273332 let blockHash = "" ;
274333
275334 while ( alive ) {
276- const exts = { } ;
335+ const exts : { [ hash : string ] : { original : TxWithEventAndFee , replayed ?: TxWithEventAndFee } } = { } ;
277336 try {
278337 const newBlockHash = await api . rpc . chain . getBlockHash ( atBlock + foundBlock ) ;
279338 if ( blockHash . toString ( ) == newBlockHash . toString ( ) ) {
280339 await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) ) ;
281340 continue ;
282341 }
342+ blockHash = newBlockHash . toString ( )
283343 } catch ( e ) {
284344 await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) ) ;
285345 continue ;
286346 }
287- blockHash = originalBlockHash . toString ( )
288- console . log ( `===========================Checking block ${ atBlock + foundBlock } [${ blockHash . toString ( ) } ]` ) ;
289347 const blockDetails = await getBlockDetails ( api , blockHash ) ;
348+ console . log ( `===========Checking block ${ chalk . red ( atBlock + foundBlock ) } [${ blockHash . toString ( ) } ] [${ blockDetails . txWithEvents . length } txs]==============` ) ;
290349 await Promise . all ( blockDetails . txWithEvents . map ( async ( tx , index ) => {
291350 const { extrinsic : ex , dispatchInfo, dispatchError } = tx ;
292351 if ( ! dispatchInfo . class . isNormal ) {
@@ -295,32 +354,33 @@ const main = async () => {
295354 const { method, signature, isSigned, signer, nonce } = ex ;
296355 // console.log(index, `${ex.method.section.toString()}.${ex.method.method.toString()} [${ex.hash.toHex()}]`);
297356 if ( method . section === 'sudo' && method . method . startsWith ( 'sudo' ) ) {
357+ const apiAt = await api . at ( blockHash ) ;
298358 // Handle sudo extrinsics
299359 const nestedCall = method . args [ 0 ] ; // The "call" is the first argument in sudo methods
300360 const { section, method : nestedMethod , args : nestedArgs } = apiAt . registry . createType ( 'Call' , nestedCall ) ;
301361
302- console . log ( ` Nested Call: ${ section } .${ nestedMethod } ` ) ;
362+ debug ( ` Nested Call: ${ section } .${ nestedMethod } ` ) ;
303363 const nestedDecodedArgs = nestedArgs . map ( ( arg : any ) => arg . toHuman ( ) ) ;
304- console . log ( ` Nested Args: ${ JSON . stringify ( nestedDecodedArgs , null , 2 ) } ` ) ;
364+ debug ( ` Nested Args: ${ JSON . stringify ( nestedDecodedArgs , null , 2 ) } ` ) ;
305365 }
306- // console.log (`${ex.method.method.toString() == "setValidationData" ? "..." : ex.toHex()}`);
307- console . log ( `[Official] Transaction` , index , `${ ex . method . section . toString ( ) } .${ ex . method . method . toString ( ) } found ${ ! dispatchError ? "✅" : "🟥" } (ref: ${ dispatchInfo . weight . refTime . toString ( ) . padStart ( 12 ) } , pov: ${ dispatchInfo . weight . proofSize . toString ( ) . padStart ( 9 ) } )` ) ;
366+ // debug (`${ex.method.method.toString() == "setValidationData" ? "..." : ex.toHex()}`);
367+ // debug (`[Official] Transaction`, index, `${ex.method.section.toString()}.${ex.method.method.toString()} found ${!dispatchError ? "✅" : "🟥"} (ref: ${dispatchInfo.weight.refTime.toString().padStart(12)}, pov: ${dispatchInfo.weight.proofSize.toString().padStart(9)})`);
308368
309369 await lazyApi . rpc . author . submitExtrinsic ( ex . toHex ( ) ) . then ( ( hash ) => {
310- console . log ( `Submitted hash: ${ hash } ` ) ;
370+ debug ( `Submitted hash: ${ hash } ` ) ;
311371 } )
312372 exts [ ex . hash . toHex ( ) ] = {
313- ex : tx
373+ original : tx ,
374+ replayed : null
314375 }
315376 } ) ) ;
316- console . log ( "Ready for block !!!" ) ;
317- await submitBlock ( exts ) ;
377+ if ( ! await submitBlock ( exts ) ) {
378+ console . log ( chalk . red ( `Found broken block, waiting forever !!` ) ) ;
379+ while ( true ) {
380+ await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) ) ;
381+ }
382+ }
318383 }
319-
320-
321- console . log ( `Waiting....` ) ;
322- onProcessExit ( ) ;
323-
324384} ;
325385
326386
0 commit comments