diff --git a/backend/cmd/evm_node_indexer/main.go b/backend/cmd/evm_node_indexer/main.go index 670e0c185..ec277fafb 100644 --- a/backend/cmd/evm_node_indexer/main.go +++ b/backend/cmd/evm_node_indexer/main.go @@ -3,7 +3,6 @@ package evm_node_indexer // imports import ( "bytes" - "compress/gzip" "context" "database/sql" "encoding/binary" @@ -19,7 +18,6 @@ import ( "sync/atomic" "time" - gcp_bigtable "cloud.google.com/go/bigtable" "github.com/ethereum/go-ethereum" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" @@ -29,7 +27,8 @@ import ( "google.golang.org/api/option" "github.com/gobitfly/beaconchain/pkg/commons/db" - "github.com/gobitfly/beaconchain/pkg/commons/hexutil" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database" + "github.com/gobitfly/beaconchain/pkg/commons/db2/raw" "github.com/gobitfly/beaconchain/pkg/commons/log" "github.com/gobitfly/beaconchain/pkg/commons/metrics" "github.com/gobitfly/beaconchain/pkg/commons/types" @@ -40,15 +39,6 @@ import ( // defines const MAX_EL_BLOCK_NUMBER = int64(1_000_000_000_000 - 1) -const BT_COLUMNFAMILY_BLOCK = "b" -const BT_COLUMN_BLOCK = "b" -const BT_COLUMNFAMILY_RECEIPTS = "r" -const BT_COLUMN_RECEIPTS = "r" -const BT_COLUMNFAMILY_TRACES = "t" -const BT_COLUMN_TRACES = "t" -const BT_COLUMNFAMILY_UNCLES = "u" -const BT_COLUMN_UNCLES = "u" - const MAINNET_CHAINID = 1 const GOERLI_CHAINID = 5 const OPTIMISM_CHAINID = 10 @@ -68,17 +58,7 @@ const TRY_TO_RECOVER_ON_ERROR_COUNT = 8 // total retries, so with a value of 4, type jsonRpcReturnId struct { Id int64 `json:"id"` } -type fullBlockRawData struct { - blockNumber int64 - blockHash hexutil.Bytes - blockUnclesCount int - blockTxs []string - - blockCompressed hexutil.Bytes - receiptsCompressed hexutil.Bytes - tracesCompressed hexutil.Bytes - unclesCompressed hexutil.Bytes -} + type intRange struct { start int64 end int64 @@ -221,17 +201,14 @@ func Run() { log.Info("starting postgres completed") } - // init bigtable log.Info("init BT...") - btClient, err := gcp_bigtable.NewClient(context.Background(), utils.Config.Bigtable.Project, utils.Config.Bigtable.Instance, option.WithGRPCConnectionPool(1)) + bt, err := database.NewBigTable(utils.Config.Bigtable.Project, utils.Config.Bigtable.Instance, nil, option.WithGRPCConnectionPool(1)) if err != nil { log.Fatal(err, "creating new client for Bigtable", 0) // fatal, no point to continue without BT } - tableBlocksRaw := btClient.Open("blocks-raw") - if tableBlocksRaw == nil { - log.Fatal(err, "open blocks-raw table", 0) // fatal, no point to continue without BT - } - defer btClient.Close() + defer bt.Close() + + tableBlocksRaw := raw.NewStore(database.Wrap(bt, raw.Table)) log.Info("...init BT done.") // init el client @@ -344,9 +321,9 @@ func Run() { } // fill array with block numbers to check - blockRawData := make([]fullBlockRawData, l) + blockRawData := make([]raw.FullBlockData, l) for i := int64(0); i < l; i++ { - blockRawData[i].blockNumber = latestPGBlock + i - l + 1 + blockRawData[i].BlockNumber = latestPGBlock + i - l + 1 } // get all hashes from node @@ -387,20 +364,20 @@ func Run() { var i int var failCounter int for _, v := range blockRawData { - for i < matchingLength && v.blockNumber > matchingHashesBlockIdList[i] { + for i < matchingLength && v.BlockNumber > matchingHashesBlockIdList[i] { i++ } - if i >= matchingLength || v.blockNumber != matchingHashesBlockIdList[i] { + if i >= matchingLength || v.BlockNumber != matchingHashesBlockIdList[i] { failCounter++ if wrongHashRanges[wrongHashRangesIndex].start < 0 { - wrongHashRanges[wrongHashRangesIndex].start = v.blockNumber - wrongHashRanges[wrongHashRangesIndex].end = v.blockNumber - } else if wrongHashRanges[wrongHashRangesIndex].end+1 == v.blockNumber { - wrongHashRanges[wrongHashRangesIndex].end = v.blockNumber + wrongHashRanges[wrongHashRangesIndex].start = v.BlockNumber + wrongHashRanges[wrongHashRangesIndex].end = v.BlockNumber + } else if wrongHashRanges[wrongHashRangesIndex].end+1 == v.BlockNumber { + wrongHashRanges[wrongHashRangesIndex].end = v.BlockNumber } else { wrongHashRangesIndex++ - wrongHashRanges[wrongHashRangesIndex].start = v.blockNumber - wrongHashRanges[wrongHashRangesIndex].end = v.blockNumber + wrongHashRanges[wrongHashRangesIndex].start = v.BlockNumber + wrongHashRanges[wrongHashRangesIndex].end = v.BlockNumber } } } @@ -470,7 +447,7 @@ func Run() { } // improve the behaviour in case of an error -func _bulkExportBlocksHandler(tableBlocksRaw *gcp_bigtable.Table, blockRawData []fullBlockRawData, nodeRequestsAtOnce int64, deep int) error { +func _bulkExportBlocksHandler(tableBlocksRaw raw.Store, blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64, deep int) error { err := _bulkExportBlocksImpl(tableBlocksRaw, blockRawData, nodeRequestsAtOnce) if err != nil { if deep < TRY_TO_RECOVER_ON_ERROR_COUNT { @@ -480,9 +457,9 @@ func _bulkExportBlocksHandler(tableBlocksRaw *gcp_bigtable.Table, blockRawData [ { s := errorIdentifier.FindStringSubmatch(err.Error()) if len(s) >= 2 { // if we have a valid json error available, should be the case if it's a node issue - log.WarnWithFields(log.Fields{"deep": deep, "cause": s[1], "0block": blockRawData[0].blockNumber, "elements": elementCount}, "got an error and will try to fix it (sub)") + log.WarnWithFields(log.Fields{"deep": deep, "cause": s[1], "0block": blockRawData[0].BlockNumber, "elements": elementCount}, "got an error and will try to fix it (sub)") } else { // if we have a no json error available, should be the case if it's a BT or Postgres issue - log.WarnWithFields(log.Fields{"deep": deep, "cause": err, "0block": blockRawData[0].blockNumber, "elements": elementCount}, "got an error and will try to fix it (err)") + log.WarnWithFields(log.Fields{"deep": deep, "cause": err, "0block": blockRawData[0].BlockNumber, "elements": elementCount}, "got an error and will try to fix it (err)") } } @@ -512,13 +489,9 @@ func _bulkExportBlocksHandler(tableBlocksRaw *gcp_bigtable.Table, blockRawData [ } // export all blocks, heavy use of bulk & concurrency, providing a block raw data array (used by the other bulkExportBlocks+ functions) -func _bulkExportBlocksImpl(tableBlocksRaw *gcp_bigtable.Table, blockRawData []fullBlockRawData, nodeRequestsAtOnce int64) error { +func _bulkExportBlocksImpl(tableBlocksRaw raw.Store, blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64) error { // check values { - if tableBlocksRaw == nil { - return fmt.Errorf("tableBlocksRaw == nil") - } - l := int64(len(blockRawData)) if l < 1 || l > nodeRequestsAtOnce { return fmt.Errorf("blockRawData length (%d) is 0 or greater 'node requests at once' (%d)", l, nodeRequestsAtOnce) @@ -544,45 +517,12 @@ func _bulkExportBlocksImpl(tableBlocksRaw *gcp_bigtable.Table, blockRawData []fu } // write to bigtable - { - // prepare array - muts := []*gcp_bigtable.Mutation{} - keys := []string{} - for _, v := range blockRawData { - if len(v.blockCompressed) == 0 || len(v.tracesCompressed) == 0 { - log.Fatal(nil, "tried writing empty data to BT", 0, map[string]interface{}{"len(v.blockCompressed)": len(v.blockCompressed), "len(v.receiptsCompressed)": len(v.receiptsCompressed), "len(v.tracesCompressed)": len(v.tracesCompressed)}) // fatal, as if this is not working in the first place, it will never work - } - mut := gcp_bigtable.NewMutation() - mut.Set(BT_COLUMNFAMILY_BLOCK, BT_COLUMN_BLOCK, gcp_bigtable.Timestamp(0), v.blockCompressed) - if len(v.receiptsCompressed) < 1 { - log.Warnf("empty receipts at block %d lRec %d lTxs %d", v.blockNumber, len(v.receiptsCompressed), len(v.blockTxs)) - } - mut.Set(BT_COLUMNFAMILY_RECEIPTS, BT_COLUMN_RECEIPTS, gcp_bigtable.Timestamp(0), v.receiptsCompressed) - mut.Set(BT_COLUMNFAMILY_TRACES, BT_COLUMN_TRACES, gcp_bigtable.Timestamp(0), v.tracesCompressed) - if v.blockUnclesCount > 0 { - mut.Set(BT_COLUMNFAMILY_UNCLES, BT_COLUMN_UNCLES, gcp_bigtable.Timestamp(0), v.unclesCompressed) - } - muts = append(muts, mut) - keys = append(keys, fmt.Sprintf("%d:%12d", utils.Config.Chain.Id, MAX_EL_BLOCK_NUMBER-v.blockNumber)) - } - - // write - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - - var errs []error - errs, err = tableBlocksRaw.ApplyBulk(ctx, keys, muts) - if err != nil { - return fmt.Errorf("tableBlocksRaw.ApplyBulk err: %w", err) - } - for i, e := range errs { - return fmt.Errorf("tableBlocksRaw.ApplyBulk errs(%d): %w", i, e) - } + if err := tableBlocksRaw.AddBlocks(blockRawData); err != nil { + return fmt.Errorf("tableBlocksRaw.ApplyBulk err: %w", err) } // write to SQL - err = psqlAddElements(blockRawData) - if err != nil { + if err := psqlAddElements(blockRawData); err != nil { return fmt.Errorf("psqlAddElements: %w", err) } @@ -590,7 +530,7 @@ func _bulkExportBlocksImpl(tableBlocksRaw *gcp_bigtable.Table, blockRawData []fu } // export all blocks, heavy use of bulk & concurrency, providing a range array -func bulkExportBlocksRange(tableBlocksRaw *gcp_bigtable.Table, blockRanges []intRange, concurrency int64, nodeRequestsAtOnce int64, discordWebhookBlockThreshold *int64, discordWebhookReportUrl *string, discordWebhookUser *string) error { +func bulkExportBlocksRange(tableBlocksRaw raw.Store, blockRanges []intRange, concurrency int64, nodeRequestsAtOnce int64, discordWebhookBlockThreshold *int64, discordWebhookReportUrl *string, discordWebhookUser *string) error { { var blocksTotalCount int64 l := len(blockRanges) @@ -657,7 +597,7 @@ func bulkExportBlocksRange(tableBlocksRaw *gcp_bigtable.Table, blockRanges []int }() defer gOuterMustStop.Store(true) // kill the updater - blockRawData := make([]fullBlockRawData, 0, nodeRequestsAtOnce) + blockRawData := make([]raw.FullBlockData, 0, nodeRequestsAtOnce) blockRawDataLen := int64(0) Loop: for _, blockRange := range blockRanges { @@ -670,7 +610,7 @@ Loop: currentNodeBlockNumberLocalCopy := currentNodeBlockNumber.Load() for blockRawDataLen < nodeRequestsAtOnce && current <= blockRange.end { if currentNodeBlockNumberLocalCopy >= current { - blockRawData = append(blockRawData, fullBlockRawData{blockNumber: current}) + blockRawData = append(blockRawData, raw.FullBlockData{BlockNumber: current}) blockRawDataLen++ current++ } else { @@ -689,7 +629,7 @@ Loop: blocksProcessedIntv.Add(int64(len(brd))) return nil }) - blockRawData = make([]fullBlockRawData, 0, nodeRequestsAtOnce) + blockRawData = make([]raw.FullBlockData, 0, nodeRequestsAtOnce) blockRawDataLen = 0 } } @@ -769,32 +709,6 @@ func _formatInt64(value int64) string { return fmt.Sprintf("%d", value) } -// compress given byte slice -func compress(src []byte) []byte { - var buf bytes.Buffer - zw := gzip.NewWriter(&buf) - if _, err := zw.Write(src); err != nil { - log.Fatal(err, "error writing to gzip writer", 0) // fatal, as if this is not working in the first place, it will never work - } - if err := zw.Close(); err != nil { - log.Fatal(err, "error closing gzip writer", 0) // fatal, as if this is not working in the first place, it will never work - } - return buf.Bytes() -} - -// decompress given byte slice -/* func decompress(src []byte) []byte { - zr, err := gzip.NewReader(bytes.NewReader(src)) - if err != nil { - log.Fatal(err, "error creating gzip reader", 0) // fatal, as if this is not working in the first place, it will never work - } - data, err := io.ReadAll(zr) - if err != nil { - log.Fatal(err, "error reading from gzip reader", 0) // fatal, as if this is not working in the first place, it will never work - } - return data -} */ - // used by splitAndVerifyJsonArray to add an element to the list depending on its Id func _splitAndVerifyJsonArrayAddElement(r *[][]byte, element []byte, lastId int64) (int64, error) { // adding empty elements will cause issues, so we don't allow it @@ -1072,7 +986,7 @@ func psqlGetLatestBlock(useWriterDb bool) (int64, error) { // will add elements to sql, based on blockRawData // on conflict, it will only overwrite / change current entry if hash is different -func psqlAddElements(blockRawData []fullBlockRawData) error { +func psqlAddElements(blockRawData []raw.FullBlockData) error { l := len(blockRawData) if l <= 0 { return fmt.Errorf("error, got empty blockRawData array (%d)", l) @@ -1081,8 +995,8 @@ func psqlAddElements(blockRawData []fullBlockRawData) error { block_number := make([]int64, l) block_hash := make(pq.ByteaArray, l) for i, v := range blockRawData { - block_number[i] = v.blockNumber - block_hash[i] = v.blockHash + block_number[i] = v.BlockNumber + block_hash[i] = v.BlockHash } _, err := db.WriterDb.Exec(` @@ -1103,7 +1017,7 @@ func psqlAddElements(blockRawData []fullBlockRawData) error { } // will return a list of all provided block_ids where the hash in the database matches the provided list -func psqlGetHashHitsIdList(blockRawData []fullBlockRawData) ([]int64, error) { +func psqlGetHashHitsIdList(blockRawData []raw.FullBlockData) ([]int64, error) { l := len(blockRawData) if l <= 0 { return nil, fmt.Errorf("error, got empty blockRawData array (%d)", l) @@ -1112,8 +1026,8 @@ func psqlGetHashHitsIdList(blockRawData []fullBlockRawData) ([]int64, error) { block_number := make([]int64, l) block_hash := make(pq.ByteaArray, l) for i, v := range blockRawData { - block_number[i] = v.blockNumber - block_hash[i] = v.blockHash + block_number[i] = v.BlockNumber + block_hash[i] = v.BlockHash } // as there are corner cases, to be on the safe side, we will use WriterDb here @@ -1232,7 +1146,7 @@ func _rpciGetHttpResult(body []byte, nodeRequestsAtOnce int64, count int64) ([][ } // will fill only receipts_compressed based on block, used by rpciGetBulkRawReceipts function -func _rpciGetBulkRawBlockReceipts(blockRawData []fullBlockRawData, nodeRequestsAtOnce int64) error { +func _rpciGetBulkRawBlockReceipts(blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64) error { // check { l := int64(len(blockRawData)) @@ -1252,7 +1166,7 @@ func _rpciGetBulkRawBlockReceipts(blockRawData []fullBlockRawData, nodeRequestsA if i != 0 { bodyStr += "," } - bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getBlockReceipts","params":["0x%x"],"id":%d}`, v.blockNumber, i) + bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getBlockReceipts","params":["0x%x"],"id":%d}`, v.BlockNumber, i) } bodyStr += "]" var err error @@ -1264,14 +1178,14 @@ func _rpciGetBulkRawBlockReceipts(blockRawData []fullBlockRawData, nodeRequestsA // get data for i, v := range rawData { - blockRawData[i].receiptsCompressed = compress(v) + blockRawData[i].Receipts = v } return nil } // will fill only receipts_compressed based on transaction, used by rpciGetBulkRawReceipts function -func _rpciGetBulkRawTransactionReceipts(blockRawData []fullBlockRawData, nodeRequestsAtOnce int64) error { +func _rpciGetBulkRawTransactionReceipts(blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64) error { // check { l := int64(len(blockRawData)) @@ -1289,7 +1203,7 @@ func _rpciGetBulkRawTransactionReceipts(blockRawData []fullBlockRawData, nodeReq var dataAvailable bool bodyStr := "[" for i, v := range blockRawData { - l := int64(len(v.blockTxs)) + l := int64(len(v.BlockTxs)) if l < 1 { continue // skip empty } @@ -1304,10 +1218,10 @@ func _rpciGetBulkRawTransactionReceipts(blockRawData []fullBlockRawData, nodeReq } for _, vv := range rawData { - for len(blockRawData[blockRawDataWriteIndex].blockTxs) < 1 { + for len(blockRawData[blockRawDataWriteIndex].BlockTxs) < 1 { blockRawDataWriteIndex++ } - blockRawData[blockRawDataWriteIndex].receiptsCompressed = compress(vv) + blockRawData[blockRawDataWriteIndex].Receipts = vv blockRawDataWriteIndex++ } @@ -1321,7 +1235,7 @@ func _rpciGetBulkRawTransactionReceipts(blockRawData []fullBlockRawData, nodeReq // adding txs of current block dataAvailable = true currentElementCount += l - for txIndex, txValue := range v.blockTxs { + for txIndex, txValue := range v.BlockTxs { if txIndex != 0 { bodyStr += "," } @@ -1339,10 +1253,10 @@ func _rpciGetBulkRawTransactionReceipts(blockRawData []fullBlockRawData, nodeReq } for _, vv := range rawData { - for len(blockRawData[blockRawDataWriteIndex].blockTxs) < 1 { + for len(blockRawData[blockRawDataWriteIndex].BlockTxs) < 1 { blockRawDataWriteIndex++ } - blockRawData[blockRawDataWriteIndex].receiptsCompressed = compress(vv) + blockRawData[blockRawDataWriteIndex].Receipts = vv blockRawDataWriteIndex++ } } @@ -1352,7 +1266,7 @@ func _rpciGetBulkRawTransactionReceipts(blockRawData []fullBlockRawData, nodeReq } // will fill only block_hash, block_unclesCount, block_compressed & block_txs -func rpciGetBulkBlockRawData(blockRawData []fullBlockRawData, nodeRequestsAtOnce int64) error { +func rpciGetBulkBlockRawData(blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64) error { // check { l := int64(len(blockRawData)) @@ -1372,7 +1286,7 @@ func rpciGetBulkBlockRawData(blockRawData []fullBlockRawData, nodeRequestsAtOnce if i != 0 { bodyStr += "," } - bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x%x", true],"id":%d}`, v.blockNumber, i) + bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x%x", true],"id":%d}`, v.BlockNumber, i) } bodyStr += "]" var err error @@ -1387,10 +1301,10 @@ func rpciGetBulkBlockRawData(blockRawData []fullBlockRawData, nodeRequestsAtOnce for i, v := range rawData { // block { - blockRawData[i].blockCompressed = compress(v) + blockRawData[i].Block = v err := json.Unmarshal(v, blockParsed) if err != nil { - return fmt.Errorf("error decoding block '%d' response: %w", blockRawData[i].blockNumber, err) + return fmt.Errorf("error decoding block '%d' response: %w", blockRawData[i].BlockNumber, err) } } @@ -1402,29 +1316,29 @@ func rpciGetBulkBlockRawData(blockRawData []fullBlockRawData, nodeRequestsAtOnce // number { blockParsedResultNumber := int64(binary.BigEndian.Uint64(append(make([]byte, 8-len(blockParsed.Result.Number)), blockParsed.Result.Number...))) - if blockRawData[i].blockNumber != blockParsedResultNumber { - log.Error(nil, "Doesn't match", 0, map[string]interface{}{"blockRawData[i].blockNumber": blockRawData[i].blockNumber, "blockParsedResultNumber": blockParsedResultNumber}) + if blockRawData[i].BlockNumber != blockParsedResultNumber { + log.Error(nil, "Doesn't match", 0, map[string]interface{}{"blockRawData[i].BlockNumber": blockRawData[i].BlockNumber, "blockParsedResultNumber": blockParsedResultNumber}) } } // hash if blockParsed.Result.Hash == nil { - return fmt.Errorf("blockParsed.Result.Hash is nil at block '%d'", blockRawData[i].blockNumber) + return fmt.Errorf("blockParsed.Result.Hash is nil at block '%d'", blockRawData[i].BlockNumber) } - blockRawData[i].blockHash = blockParsed.Result.Hash + blockRawData[i].BlockHash = blockParsed.Result.Hash // transaction if blockParsed.Result.Transactions == nil { - return fmt.Errorf("blockParsed.Result.Transactions is nil at block '%d'", blockRawData[i].blockNumber) + return fmt.Errorf("blockParsed.Result.Transactions is nil at block '%d'", blockRawData[i].BlockNumber) } - blockRawData[i].blockTxs = make([]string, len(blockParsed.Result.Transactions)) + blockRawData[i].BlockTxs = make([]string, len(blockParsed.Result.Transactions)) for ii, tx := range blockParsed.Result.Transactions { - blockRawData[i].blockTxs[ii] = tx.Hash.String() + blockRawData[i].BlockTxs[ii] = tx.Hash.String() } // uncle count if blockParsed.Result.Uncles != nil { - blockRawData[i].blockUnclesCount = len(blockParsed.Result.Uncles) + blockRawData[i].BlockUnclesCount = len(blockParsed.Result.Uncles) } } @@ -1432,7 +1346,7 @@ func rpciGetBulkBlockRawData(blockRawData []fullBlockRawData, nodeRequestsAtOnce } // will fill only block_hash -func rpciGetBulkBlockRawHash(blockRawData []fullBlockRawData, nodeRequestsAtOnce int64) error { +func rpciGetBulkBlockRawHash(blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64) error { // check { l := int64(len(blockRawData)) @@ -1456,7 +1370,7 @@ func rpciGetBulkBlockRawHash(blockRawData []fullBlockRawData, nodeRequestsAtOnce if i != 0 { bodyStr += "," } - bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x%x", true],"id":%d}`, v.blockNumber, i) + bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x%x", true],"id":%d}`, v.BlockNumber, i) } bodyStr += "]" var err error @@ -1471,28 +1385,28 @@ func rpciGetBulkBlockRawHash(blockRawData []fullBlockRawData, nodeRequestsAtOnce for i, v := range rawData { err := json.Unmarshal(v, blockParsed) if err != nil { - return fmt.Errorf("error decoding block '%d' response: %w", blockRawData[i].blockNumber, err) + return fmt.Errorf("error decoding block '%d' response: %w", blockRawData[i].BlockNumber, err) } if i != blockParsed.Id { return fmt.Errorf("impossible error, i '%d' doesn't match blockParsed.Id '%d'", i, blockParsed.Id) } { blockParsedResultNumber := int64(binary.BigEndian.Uint64(append(make([]byte, 8-len(blockParsed.Result.Number)), blockParsed.Result.Number...))) - if blockRawData[i].blockNumber != blockParsedResultNumber { - log.Error(nil, "Doesn't match", 0, map[string]interface{}{"blockRawData[i].blockNumber": blockRawData[i].blockNumber, "blockParsedResultNumber": blockParsedResultNumber}) + if blockRawData[i].BlockNumber != blockParsedResultNumber { + log.Error(nil, "Doesn't match", 0, map[string]interface{}{"blockRawData[i].BlockNumber": blockRawData[i].BlockNumber, "blockParsedResultNumber": blockParsedResultNumber}) } } if blockParsed.Result.Hash == nil { - return fmt.Errorf("blockParsed.Result.Hash is nil at block '%d'", blockRawData[i].blockNumber) + return fmt.Errorf("blockParsed.Result.Hash is nil at block '%d'", blockRawData[i].BlockNumber) } - blockRawData[i].blockHash = blockParsed.Result.Hash + blockRawData[i].BlockHash = blockParsed.Result.Hash } return nil } // will fill only uncles (if available) -func rpciGetBulkRawUncles(blockRawData []fullBlockRawData, nodeRequestsAtOnce int64) error { +func rpciGetBulkRawUncles(blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64) error { // check { l := int64(len(blockRawData)) @@ -1512,10 +1426,10 @@ func rpciGetBulkRawUncles(blockRawData []fullBlockRawData, nodeRequestsAtOnce in firstElement := true bodyStr := "[" for _, v := range blockRawData { - if v.blockUnclesCount > 2 || v.blockUnclesCount < 0 { + if v.BlockUnclesCount > 2 || v.BlockUnclesCount < 0 { // fatal, as this is an impossible error - log.Fatal(nil, "impossible error, found impossible uncle count, expected 0, 1 or 2", 0, map[string]interface{}{"block_unclesCount": v.blockUnclesCount, "block_number": v.blockNumber}) - } else if v.blockUnclesCount == 0 { + log.Fatal(nil, "impossible error, found impossible uncle count, expected 0, 1 or 2", 0, map[string]interface{}{"block_unclesCount": v.BlockUnclesCount, "block_number": v.BlockNumber}) + } else if v.BlockUnclesCount == 0 { continue } else { if firstElement { @@ -1523,14 +1437,14 @@ func rpciGetBulkRawUncles(blockRawData []fullBlockRawData, nodeRequestsAtOnce in } else { bodyStr += "," } - if v.blockUnclesCount == 1 { + if v.BlockUnclesCount == 1 { requestedCount++ - bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getUncleByBlockNumberAndIndex","params":["0x%x", "0x0"],"id":%d}`, v.blockNumber, v.blockNumber) + bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getUncleByBlockNumberAndIndex","params":["0x%x", "0x0"],"id":%d}`, v.BlockNumber, v.BlockNumber) } else /* if v.block_unclesCount == 2 */ { requestedCount++ - bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getUncleByBlockNumberAndIndex","params":["0x%x", "0x0"],"id":%d},`, v.blockNumber, v.blockNumber) + bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getUncleByBlockNumberAndIndex","params":["0x%x", "0x0"],"id":%d},`, v.BlockNumber, v.BlockNumber) requestedCount++ - bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getUncleByBlockNumberAndIndex","params":["0x%x", "0x1"],"id":%d}`, v.blockNumber, v.blockNumber) + bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getUncleByBlockNumberAndIndex","params":["0x%x", "0x1"],"id":%d}`, v.BlockNumber, v.BlockNumber) } } } @@ -1549,8 +1463,8 @@ func rpciGetBulkRawUncles(blockRawData []fullBlockRawData, nodeRequestsAtOnce in // get data rdIndex := 0 for i, v := range blockRawData { - if v.blockUnclesCount > 0 { // Not the prettiest way, but the unmarshal would take much longer with the same result - blockRawData[i].unclesCompressed = compress(rawData[rdIndex]) + if v.BlockUnclesCount > 0 { // Not the prettiest way, but the unmarshal would take much longer with the same result + blockRawData[i].Uncles = rawData[rdIndex] rdIndex++ } } @@ -1559,7 +1473,7 @@ func rpciGetBulkRawUncles(blockRawData []fullBlockRawData, nodeRequestsAtOnce in } // will fill only receipts_compressed -func rpciGetBulkRawReceipts(blockRawData []fullBlockRawData, nodeRequestsAtOnce int64) error { +func rpciGetBulkRawReceipts(blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64) error { if utils.Config.Chain.Id == ARBITRUM_CHAINID { return _rpciGetBulkRawTransactionReceipts(blockRawData, nodeRequestsAtOnce) } @@ -1567,7 +1481,7 @@ func rpciGetBulkRawReceipts(blockRawData []fullBlockRawData, nodeRequestsAtOnce } // will fill only traces_compressed -func rpciGetBulkRawTraces(blockRawData []fullBlockRawData, nodeRequestsAtOnce int64) error { +func rpciGetBulkRawTraces(blockRawData []raw.FullBlockData, nodeRequestsAtOnce int64) error { // check { l := int64(len(blockRawData)) @@ -1587,10 +1501,10 @@ func rpciGetBulkRawTraces(blockRawData []fullBlockRawData, nodeRequestsAtOnce in if i != 0 { bodyStr += "," } - if utils.Config.Chain.Id == ARBITRUM_CHAINID && v.blockNumber < ARBITRUM_NITRO_BLOCKNUMBER { - bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"arbtrace_block","params":["0x%x"],"id":%d}`, v.blockNumber, i) + if utils.Config.Chain.Id == ARBITRUM_CHAINID && v.BlockNumber < ARBITRUM_NITRO_BLOCKNUMBER { + bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"arbtrace_block","params":["0x%x"],"id":%d}`, v.BlockNumber, i) } else { - bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["0x%x", {"tracer": "callTracer"}],"id":%d}`, v.blockNumber, i) + bodyStr += fmt.Sprintf(`{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["0x%x", {"tracer": "callTracer"}],"id":%d}`, v.BlockNumber, i) } } bodyStr += "]" @@ -1603,7 +1517,7 @@ func rpciGetBulkRawTraces(blockRawData []fullBlockRawData, nodeRequestsAtOnce in // get data for i, v := range rawData { - blockRawData[i].tracesCompressed = compress(v) + blockRawData[i].Traces = v } return nil diff --git a/backend/go.mod b/backend/go.mod index dc352f8fd..7498c8541 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -33,6 +33,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt/v4 v4.5.1 github.com/gomodule/redigo v1.9.2 + github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/gorilla/csrf v1.7.2 github.com/gorilla/handlers v1.5.2 diff --git a/backend/pkg/commons/chain/chainID.go b/backend/pkg/commons/chain/chainID.go index ff94a14ee..21142a6ff 100644 --- a/backend/pkg/commons/chain/chainID.go +++ b/backend/pkg/commons/chain/chainID.go @@ -9,6 +9,7 @@ type IDGetter struct { Sepolia *big.Int Gnosis *big.Int Optimistic *big.Int + Arbitrum *big.Int Holesky *big.Int } @@ -17,6 +18,7 @@ var DefaultIDs = IDGetter{ Sepolia: big.NewInt(11155111), Gnosis: big.NewInt(100), Optimistic: big.NewInt(10), + Arbitrum: big.NewInt(42161), Holesky: big.NewInt(17000), } diff --git a/backend/pkg/commons/db2/database/bigtable.go b/backend/pkg/commons/db2/database/bigtable.go index 62ed7f225..686a81cea 100644 --- a/backend/pkg/commons/db2/database/bigtable.go +++ b/backend/pkg/commons/db2/database/bigtable.go @@ -43,35 +43,65 @@ func Wrap(db *BigTable, table string) TableWrapper { } func (w TableWrapper) Add(key string, item Item, allowDuplicate bool) error { - return w.BigTable.Add(w.table, key, item, allowDuplicate) + if err := w.BigTable.Add(w.table, key, item, allowDuplicate); err != nil { + return fmt.Errorf("table %s: %w", w.table, err) + } + return nil } func (w TableWrapper) Read(prefix string) ([]Row, error) { - return w.BigTable.Read(w.table, prefix) + res, err := w.BigTable.Read(w.table, prefix) + if err != nil { + return nil, fmt.Errorf("table %s: %w", w.table, err) + } + return res, nil } func (w TableWrapper) GetLatestValue(key string) (*Row, error) { - return w.BigTable.GetLatestValue(w.table, key) + res, err := w.BigTable.GetLatestValue(w.table, key) + if err != nil { + return nil, fmt.Errorf("table %s: %w", w.table, err) + } + return res, nil } func (w TableWrapper) GetRow(key string) (*Row, error) { - return w.BigTable.GetRow(w.table, key) + res, err := w.BigTable.GetRow(w.table, key) + if err != nil { + return nil, fmt.Errorf("table %s: %w", w.table, err) + } + return res, nil } func (w TableWrapper) GetRowKeys(prefix string, opts ...Option) ([]string, error) { - return w.BigTable.GetRowKeys(w.table, prefix, opts...) + res, err := w.BigTable.GetRowKeys(w.table, prefix, opts...) + if err != nil { + return nil, fmt.Errorf("table %s: %w", w.table, err) + } + return res, nil } func (w TableWrapper) BulkAdd(itemsByKey map[string][]Item, opts ...Option) error { - return w.BigTable.BulkAdd(w.table, itemsByKey, opts...) + if err := w.BigTable.BulkAdd(w.table, itemsByKey, opts...); err != nil { + return fmt.Errorf("table %s: %w", w.table, err) + } + return nil } func (w TableWrapper) GetRowsRange(high, low string, opts ...Option) ([]Row, error) { - return w.BigTable.GetRowsRange(w.table, high, low, opts...) + res, err := w.BigTable.GetRowsRange(w.table, high, low, opts...) + if err != nil { + return nil, fmt.Errorf("table %s: %w", w.table, err) + } + return res, nil } func (w TableWrapper) GetRowsWithKeys(keys []string) ([]Row, error) { - return w.BigTable.GetRowsWithKeys(w.table, keys) + res, err := w.BigTable.GetRowsWithKeys(w.table, keys) + if err != nil { + return nil, fmt.Errorf("table %s: %w", w.table, err) + } + return res, nil } // BigTable is a wrapper around Google Cloud Bigtable for storing and retrieving data @@ -91,6 +121,7 @@ func NewBigTableWithClient(ctx context.Context, client *bigtable.Client, adminCl // NewBigTable initializes a new BigTable // It returns a BigTable and an error if any part of the setup fails +// if tablesAndFamilies is not nil it will try to create the associated tables and families if not already presents func NewBigTable(project, instance string, tablesAndFamilies map[string][]string, options ...option.ClientOption) (*BigTable, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -124,27 +155,27 @@ func createTableAndFamilies(ctx context.Context, admin *bigtable.AdminClient, ta // Get the list of existing tables tables, err := admin.Tables(ctx) if err != nil { - return fmt.Errorf("could not fetch table list: %v", err) + return fmt.Errorf("could not fetch table list: %w", err) } // Create the table if it doesn't exist if !slices.Contains(tables, tableName) { if err := admin.CreateTable(ctx, tableName); err != nil { - return fmt.Errorf("could not create table %s: %v", tableName, err) + return fmt.Errorf("could not create table %s: %w", tableName, err) } } // Retrieve information about the table tblInfo, err := admin.TableInfo(ctx, tableName) if err != nil { - return fmt.Errorf("could not read info for table %s: %v", tableName, err) + return fmt.Errorf("could not read info for table %s: %w", tableName, err) } for _, familyName := range familyNames { // Create the column family if it doesn't exist if !slices.Contains(tblInfo.Families, familyName) { if err := admin.CreateColumnFamily(ctx, tableName, familyName); err != nil { - return fmt.Errorf("could not create column family %s: %v", familyName, err) + return fmt.Errorf("could not create column family %s: %w", familyName, err) } } } @@ -211,7 +242,7 @@ func (b BigTable) Add(table, key string, item Item, allowDuplicate bool) error { } // Apply the mutation to the table using the given key if err := tbl.Apply(ctx, key, mut); err != nil { - return fmt.Errorf("could not apply row mutation: %v", err) + return fmt.Errorf("could not apply row mutation: %w", err) } return nil } @@ -240,7 +271,7 @@ func (b BigTable) Read(table, prefix string) ([]Row, error) { return true }) if err != nil { - return nil, fmt.Errorf("could not read rows: %v", err) + return nil, fmt.Errorf("could not read rows: %w", err) } return rows, nil @@ -268,7 +299,7 @@ func (b BigTable) GetLatestValue(table, key string) (*Row, error) { }) if err != nil { - return nil, fmt.Errorf("could not read rows: %v", err) + return nil, fmt.Errorf("could not read rows: %w", err) } return &data, nil @@ -283,7 +314,7 @@ func (b BigTable) GetRow(table, key string) (*Row, error) { var data *Row row, err := tbl.ReadRow(ctx, key) if err != nil { - return nil, fmt.Errorf("could not read row: %v", err) + return nil, fmt.Errorf("could not read row: %w", err) } if row == nil { return nil, ErrNotFound @@ -338,7 +369,7 @@ func (b BigTable) GetRowsRange(table, high, low string, opts ...Option) ([]Row, return true }, readOptions...) if err != nil { - return nil, fmt.Errorf("could not read rows: %v", err) + return nil, fmt.Errorf("could not read rows: %w", err) } if len(data) == 0 { return nil, ErrNotFound @@ -362,7 +393,7 @@ func (b BigTable) GetRowKeys(table, prefix string, opts ...Option) ([]string, er return true }, readOptions...) if err != nil { - return nil, fmt.Errorf("could not read rows: %v", err) + return nil, fmt.Errorf("could not read rows: %w", err) } return data, nil @@ -389,7 +420,7 @@ func (b BigTable) GetRowsWithKeys(table string, keys []string) ([]Row, error) { }) if err != nil { - return nil, fmt.Errorf("could not read rows: %v", err) + return nil, fmt.Errorf("could not read rows: %w", err) } if len(data) == 0 { return nil, ErrNotFound @@ -408,7 +439,7 @@ func (b BigTable) Clear() error { } for _, table := range tables { if err := b.admin.DropAllRows(ctx, table); err != nil { - return fmt.Errorf("could not drop all rows: %v", err) + return fmt.Errorf("could not drop all rows: %w", err) } } return nil @@ -421,11 +452,11 @@ func (b BigTable) Close() error { return fmt.Errorf("cannot close client: bigtable client is nil") } if err := b.client.Close(); err != nil && status.Code(err) != codes.Canceled { - return fmt.Errorf("cannot close client: %v", err) + return fmt.Errorf("cannot close client: %w", err) } if b.admin != nil { if err := b.admin.Close(); err != nil && status.Code(err) != codes.Canceled { - return fmt.Errorf("cannot close admin client: %v", err) + return fmt.Errorf("cannot close admin client: %w", err) } } return nil diff --git a/backend/pkg/commons/db2/database/remote.go b/backend/pkg/commons/db2/database/remote.go index ccac3028e..bff78aee2 100644 --- a/backend/pkg/commons/db2/database/remote.go +++ b/backend/pkg/commons/db2/database/remote.go @@ -3,6 +3,7 @@ package database import ( "bytes" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -72,6 +73,9 @@ func (api RemoteServer) GetRowsRange(w http.ResponseWriter, r *http.Request) { } rows, err := api.db.GetRowsRange(args.High, args.Low, WithOpenRange(args.OpenRange), WithLimit(args.Limit)) if err != nil { + if errors.Is(err, ErrNotFound) { + err = ErrNotFound + } respondWithErr(w, http.StatusInternalServerError, err) return } @@ -90,6 +94,9 @@ func (api RemoteServer) GetRow(w http.ResponseWriter, r *http.Request) { } row, err := api.db.GetRow(args.Key) if err != nil { + if errors.Is(err, ErrNotFound) { + err = ErrNotFound + } respondWithErr(w, http.StatusInternalServerError, err) return } diff --git a/backend/pkg/commons/db2/raw/client_test.go b/backend/pkg/commons/db2/raw/client_test.go index 0b2b2660d..c47c2336b 100644 --- a/backend/pkg/commons/db2/raw/client_test.go +++ b/backend/pkg/commons/db2/raw/client_test.go @@ -49,7 +49,7 @@ func TestBigTableClientRealCondition(t *testing.T) { } rawStore := NewStore(database.Wrap(bt, Table)) - rpcClient, err := rpc.DialOptions(context.Background(), "http://foo.bar", rpc.WithHTTPClient(&http.Client{ + rpcClient, err := rpc.DialOptions(context.Background(), "https://foo.bar", rpc.WithHTTPClient(&http.Client{ Transport: NewBigTableEthRaw(rawStore, chainID), })) if err != nil { @@ -131,7 +131,7 @@ func BenchmarkRawBigTable(b *testing.B) { } rawStore := WithCache(NewStore(database.Wrap(bt, Table))) - rpcClient, err := rpc.DialOptions(context.Background(), "http://foo.bar", rpc.WithHTTPClient(&http.Client{ + rpcClient, err := rpc.DialOptions(context.Background(), "https://foo.bar", rpc.WithHTTPClient(&http.Client{ Transport: NewBigTableEthRaw(rawStore, chainID), })) if err != nil { @@ -178,7 +178,7 @@ func TestBigTableClient(t *testing.T) { t.Fatal(err) } - rpcClient, err := rpc.DialOptions(context.Background(), "http://foo.bar", rpc.WithHTTPClient(&http.Client{ + rpcClient, err := rpc.DialOptions(context.Background(), "https://foo.bar", rpc.WithHTTPClient(&http.Client{ Transport: NewBigTableEthRaw(WithCache(rawStore), tt.block.ChainID), })) if err != nil { diff --git a/backend/pkg/commons/db2/raw/raw.go b/backend/pkg/commons/db2/raw/raw.go index a6c4a0ef0..ef14effa4 100644 --- a/backend/pkg/commons/db2/raw/raw.go +++ b/backend/pkg/commons/db2/raw/raw.go @@ -7,7 +7,6 @@ import ( "github.com/gobitfly/beaconchain/pkg/commons/db2/database" "github.com/gobitfly/beaconchain/pkg/commons/hexutil" - "github.com/gobitfly/beaconchain/pkg/commons/log" ) type compressor interface { @@ -30,6 +29,9 @@ func NewStore(store database.Database) Store { func (store Store) AddBlocks(blocks []FullBlockData) error { itemsByKey := make(map[string][]database.Item) for _, fullBlock := range blocks { + if err := validateBlock(fullBlock); err != nil { + return fmt.Errorf("block %d: %w", fullBlock.BlockNumber, err) + } if len(fullBlock.Block) == 0 || len(fullBlock.BlockTxs) != 0 && len(fullBlock.Traces) == 0 { return fmt.Errorf("block %d: empty data", fullBlock.BlockNumber) } @@ -64,10 +66,6 @@ func (store Store) AddBlocks(blocks []FullBlockData) error { Data: traces, }, } - if len(fullBlock.Receipts) == 0 { - // todo move that log higher up - log.Warn(fmt.Sprintf("empty receipts at block %d lRec %d lTxs %d", fullBlock.BlockNumber, len(fullBlock.Receipts), len(fullBlock.BlockTxs))) - } if fullBlock.BlockUnclesCount > 0 { uncles, err := store.compressor.compress(fullBlock.Uncles) if err != nil { @@ -80,7 +78,10 @@ func (store Store) AddBlocks(blocks []FullBlockData) error { }) } } - return store.db.BulkAdd(itemsByKey) + if err := store.db.BulkAdd(itemsByKey); err != nil { + return fmt.Errorf("cannot add blocks [%d-%d] to database: %w", blocks[0].BlockNumber, blocks[len(blocks)-1].BlockNumber, err) + } + return nil } func (store Store) ReadBlockByNumber(chainID uint64, number int64) (*FullBlockData, error) { diff --git a/backend/pkg/commons/db2/raw/raw_test.go b/backend/pkg/commons/db2/raw/raw_test.go index 5964ac72d..3d66b1b00 100644 --- a/backend/pkg/commons/db2/raw/raw_test.go +++ b/backend/pkg/commons/db2/raw/raw_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/google/go-cmp/cmp" "github.com/gobitfly/beaconchain/pkg/commons/db2/database" "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" @@ -23,27 +24,47 @@ func TestRaw(t *testing.T) { compressor: noOpCompressor{}, } - block := testFullBlock - if err := store.AddBlocks([]FullBlockData{block}); err != nil { - t.Fatal(err) + tests := []struct { + name string + block FullBlockData + }{ + { + name: "test block", + block: testFullBlock, + }, + { + name: "two uncles", + block: testTwoUnclesFullBlock, + }, + { + name: "arbitrum", + block: testArbFullBlock, + }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := store.AddBlocks([]FullBlockData{tt.block}); err != nil { + t.Fatal(err) + } - res, err := store.ReadBlockByNumber(block.ChainID, block.BlockNumber) - if err != nil { - t.Fatal(err) - } + res, err := store.ReadBlockByNumber(tt.block.ChainID, tt.block.BlockNumber) + if err != nil { + t.Fatal(err) + } - if got, want := string(res.Block), testBlock; got != want { - t.Errorf("got %v, want %v", got, want) - } - if got, want := string(res.Receipts), testReceipts; got != want { - t.Errorf("got %v, want %v", got, want) - } - if got, want := string(res.Traces), testTraces; got != want { - t.Errorf("got %v, want %v", got, want) - } - if got, want := string(res.Uncles), testUncles; got != want { - t.Errorf("got %v, want %v", got, want) + if diff := cmp.Diff(string(res.Block), string(tt.block.Block)); diff != "" { + t.Errorf("block mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(string(res.Receipts), string(tt.block.Receipts)); diff != "" { + t.Errorf("receipts mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(string(res.Traces), string(tt.block.Traces)); diff != "" { + t.Errorf("traces mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(string(res.Uncles), string(tt.block.Uncles)); diff != "" { + t.Errorf("uncles mismatch (-want +got):\n%s", diff) + } + }) } } @@ -430,4 +451,20 @@ const ( } } ]` + + testArbBlock = `{"jsonrpc":"2.0","id":16,"result":{"baseFeePerGas":"0x989680","difficulty":"0x1","extraData":"0x23269065093cbccefb21eb2151d032fdec1bfbf0ed16f28fd52310c429895f51","gasLimit":"0x4000000000000","gasUsed":"0x9d057e","hash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","l1BlockNumber":"0x149f366","logsBloom":"0x00104000000004200010000040000000000000000000000000003040000000010000004000000000000100000002000000004001000020000000000000200000000010010100000a0002080900000000084000000000000001000000002008000000002004000024800000140080000000008020000000000000001000080000000000000800000000000040000004000000000600200002000000003040040022020000000000000000020000000000000000000020001080000008000000000000a002080010000000000000000000000000010000000000000020001002000010000200080000000004000000200011001000000202100000000000000000","miner":"0xa4b000000000000000000073657175656e636572","mixHash":"0x0000000000021c08000000000149f36600000000000000200000000000000000","nonce":"0x00000000001bfe7f","number":"0x119b1fd3","parentHash":"0xd77c3a94447a37a2a2d818ecc1177db9323d9be855002f770edaa0438d83cb25","receiptsRoot":"0x819f4d7f55ea783eb980567a28b6d69e1ca9837297c1e4cdf1806b81e49cdb4a","sendCount":"0x21c08","sendRoot":"0x23269065093cbccefb21eb2151d032fdec1bfbf0ed16f28fd52310c429895f51","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0xb03","stateRoot":"0x9bd81dc4d8f70c654e96266be2ce6fd8d15da2ed92fc11c37ac35adf5695febd","timestamp":"0x67868408","totalDifficulty":"0x1048428b","transactions":[{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0x00000000000000000000000000000000000a4b05","gas":"0x0","gasPrice":"0x0","hash":"0x58cd6dc4c642eb0f96e0cf01f8e34e64f22a9d14581c043a9a23c6028c674052","input":"0x6bf6a42d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000149f36600000000000000000000000000000000000000000000000000000000119b1fd30000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","to":"0x00000000000000000000000000000000000a4b05","transactionIndex":"0x0","value":"0x0","type":"0x6a","chainId":"0xa4b1","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0x619e6175e65e8511c9f91b4e0f87cb2e75321651","gas":"0x30e4fa","gasPrice":"0x989680","hash":"0x334cbe7b01106ca506d239887ef7a471158372cca23a2d758916090283c10294","input":"0x5d9be1060000000000000000000000002495ca17f576e67242ecd171f01ad54791bfb710000000000000000000000000000000000000000000000000000095a43b2d4e0000000000000000000000000000000000000000000000021e19e0c9bab2400000000000000000000000000000000000000000000000000000000000006786845c00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000041898d17f65dfbed1b3f2b0e52705b0e45373af6a48d9ebea2c726c91fc67fe1bc33a6068034c641b20c1aae679a5b10e5fd4bddcd62f033bd6fed2196fbc1b2fc1b00000000000000000000000000000000000000000000000000000000000000","nonce":"0x2","to":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","transactionIndex":"0x1","value":"0x95a43b2d4e00","type":"0x0","chainId":"0xa4b1","v":"0x14986","r":"0x265cb88034488e27801b95e62017498073d46c0d1f286443d08a37f713e8efa1","s":"0x2033532649c48eccacef100897dcbfdbaf5703bddb6518a0a4434adc2d5d4bb1"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0x16fb1259a1fb59d3cabc6b1ad169762dcf0f8638","gas":"0x171060","gasPrice":"0x4c4b400","hash":"0x677ae449aea214ae1f60875330531d9e9c63dba18d01015ef7379007867e622f","input":"0x0b1a740e411301","nonce":"0xc5e2","to":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","transactionIndex":"0x2","value":"0x0","type":"0x0","chainId":"0xa4b1","v":"0x14986","r":"0xce45f4b68d3ee9beb93b7a9f2780a045a65b8332d25cc56393b82bb6a6d2cae","s":"0x7be86d8e60802e8a1bc0547c9a7571d3bab8e5bcea98a9ce503e352ef466f150"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0xe3c81b8221bcebccf5eac414cf52156c3c3b38ed","gas":"0x171060","gasPrice":"0x4c4b400","hash":"0x85c2d0969c7423a46197febc93ef19be5ef15108e0451c4d4f673c6cb2798e61","input":"0x08dbd00b5f6301","nonce":"0xc65a","to":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","transactionIndex":"0x3","value":"0x0","type":"0x0","chainId":"0xa4b1","v":"0x14986","r":"0x40c0e789faf29c2a1b576c4a7d0c454ba241577ded11b5e99bcee66f23fcbe1d","s":"0x19aa823c7cb5d9862a2f5363728b9dff5252e4b24061bdc4b9f9de8eefa8e31c"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0xf7ffaad1569d8c9626b6e90636793f072a95d259","gas":"0x232b0e","gasPrice":"0x989681","maxFeePerGas":"0xcdfe61","maxPriorityFeePerGas":"0x1","hash":"0x14617bb93ae8e7a349affa430390abd482bdb4d6658f20854ecf07895f7c621b","input":"0x095ea7b3000000000000000000000000b45c42fbf8af8df5a1fa080a351e9b2f8e0a56d100000000000000000000000000000000000000000000000000000000004e6566","nonce":"0x18","to":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","transactionIndex":"0x4","value":"0x0","type":"0x2","accessList":[],"chainId":"0xa4b1","v":"0x1","r":"0xc7ebff49ff328cff3d6b7f0ba6119b427a9916d7e8aee2ad2e22a46d337275f4","s":"0x7ed1881be854c7ddee336cf7091e32e4b43d80f1dae10b0f244741bd346f1165","yParity":"0x1"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0x5096fb354daca9b11ce0d5884fad532b056e05dd","gas":"0xf4240","gasPrice":"0x989680","maxFeePerGas":"0xe4e1c0","maxPriorityFeePerGas":"0x0","hash":"0x9a43b119e0247d39e498de5c0f4bc49702354d2368b796591352ed2a9bd55b3b","input":"0x00040000ff0503b54a8379","nonce":"0x2dcaad","to":"0xcd934b818ddd7da4a82cd3e73c014569024c26f8","transactionIndex":"0x5","value":"0x0","type":"0x2","accessList":[],"chainId":"0xa4b1","v":"0x1","r":"0x346096d438091fbe3e4c4fdd88679c913312abc3ab95ce3da564988ff87f34d2","s":"0x29cefca91ee1616bb20cada117ab49dd1b9d468636f71d0558562c3cf4a515be","yParity":"0x1"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0xb38e8c17e38363af6ebdcb3dae12e0243582891d","gas":"0x185130","gasPrice":"0x47868c00","maxFeePerGas":"0x47868c00","maxPriorityFeePerGas":"0x77359400","hash":"0x2eb017df8f91c0038177ad0cedd4713dab6f6f3cf775d1753f5773ed7890b5bd","input":"0x","nonce":"0x50935b","to":"0xf39cf285e20bffe688e542fce3ecbb3c5064aa5e","transactionIndex":"0x6","value":"0xadc3bfe9d2800","type":"0x2","accessList":[],"chainId":"0xa4b1","v":"0x1","r":"0xfec3760862c438687a3a8d139a336f3fd4410122196dee3d60658bd10cf02d5c","s":"0x20885d0cf282e48d2d12b669dd2e365d7c86af0bde6efa1a8033c4540e1eab5e","yParity":"0x1"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0x3931dab967c3e2dbb492fe12460a66d0fe4cc857","gas":"0x236c38","gasPrice":"0x47868c00","maxFeePerGas":"0x47868c00","maxPriorityFeePerGas":"0x77359400","hash":"0x533d4df22dabdfc80b090c7a69af5f38d80b5878d33b398273329f552d666d15","input":"0xa9059cbb000000000000000000000000f7e8ec11bd1c22643c2a2879192c818340242d1a00000000000000000000000000000000000000000000000000000000014d1970","nonce":"0xeaf67","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","transactionIndex":"0x7","value":"0x0","type":"0x2","accessList":[],"chainId":"0xa4b1","v":"0x0","r":"0x9b856e0b6e7d12eb9891192cd4f13463da8e197403e1d4f35f8951061d092e3a","s":"0x76d7789f981eb924541a04e127216e0970dffa98d5420eed6f460a7dc146824d","yParity":"0x0"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0x26feadf80ee3c6a13172a9324a00879f994563f7","gas":"0x927c0","gasPrice":"0x989680","hash":"0xa6e7a77e8462adcd7d60a4dca9208c2e8cac34380a7df53f22b21345427d76a6","input":"0x","nonce":"0x1","to":"0x2166f18a7dbdf7c4d4b6a8f4fbec537967939928","transactionIndex":"0x8","value":"0x45f9685b7f000","type":"0x0","chainId":"0xa4b1","v":"0x14985","r":"0xb2c0aada240b1b239de41a460b6bd4b4a9e0af39e21d5cdeed30c11db494d0d2","s":"0x7a843423b7054d7a7b7e83975c9552ed69066cbeaef669e2bf7ce1b1a32cdcc8"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0x25681ab599b4e2ceea31f8b498052c53fc2d74db","gas":"0x22dc94","gasPrice":"0x47868c00","maxFeePerGas":"0x47868c00","maxPriorityFeePerGas":"0x77359400","hash":"0x2a47a12b226d557bc1f3fafc54ab53531bfdeddc42f10e8b0104266f7613bb5e","input":"0xa9059cbb000000000000000000000000fdc77e8188735b1d889ce0f189c472ecd0eadd520000000000000000000000000000000000000000000000000000000000384e10","nonce":"0xf33b6","to":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","transactionIndex":"0x9","value":"0x0","type":"0x2","accessList":[],"chainId":"0xa4b1","v":"0x0","r":"0xe712c2a116e38383fced44ddf53e98a1aa52ffc0c544fcc4066e8035baffc4a","s":"0x2ef65b38770f5582ccdfeeda632eb22d05dc234b213a491af18ee3d433f5c7b0","yParity":"0x0"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0xa0084b9e25d9fcaf83a4ee5bd7ca2bb708c56c19","gas":"0x2f9563","gasPrice":"0x989680","hash":"0xb8aea70764d5dc3e14d6991dc1de4bebea18fa31b82f2ccca2f96578aba2d6a0","input":"0x5d9be106000000000000000000000000cb75db6b8e8268ea5053c85eb8606a3a65a5b10a000000000000000000000000000000000000000000000000000096b37a96900000000000000000000000000000000000000000000000021e19e0c9bab2400000000000000000000000000000000000000000000000000000000000006786845f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000041924cc2a7d7843a536cc63d7f599379d5bad2735caa791e496ac77c6df7e93ea638dcf6abf80b5425d3c818d5955709d5881321c7d9dfa9d5cb845783b023788e1c00000000000000000000000000000000000000000000000000000000000000","nonce":"0xd","to":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","transactionIndex":"0xa","value":"0x96b37a969000","type":"0x0","chainId":"0xa4b1","v":"0x14986","r":"0x840ed232eb930aafcddd8fbc939f971a29e134eface9f6588f2398b8518402ac","s":"0x65fda3edcba4426e4ad2c75c7cde491f6313d559c2a6a620dfdffa3e18bb101a"},{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","from":"0xa4c3e98e0ab7967ceb81d829bc6a038d9e114b87","gas":"0x1b56e4","gasPrice":"0x1312d00","hash":"0xf846e2254370f09b4538900cbb97403739ddab69da5e1e69326b7f90abbe2c27","input":"0xa9059cbb00000000000000000000000019fd9164a7f9544de573d8e27747c50c90f1bc390000000000000000000000000000000000000000000000000000000000001c76","nonce":"0x2","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","transactionIndex":"0xb","value":"0x0","type":"0x0","chainId":"0xa4b1","v":"0x14986","r":"0x46c55104612e9a38402b767879febf7e63e05d1605c22b51037032c28918bae1","s":"0x275c3214a730eb6a9b5beb777aee761ffd9e6951497af2c1d6a507d17e224ad6"}],"transactionsRoot":"0x80a5756e6fafb2be7b87718e8111762db7ca98c04c932d323bb1e9ef5bc6f825","uncles":[]}}` + testArbReceipts = `[{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x0","effectiveGasPrice":"0x989680","from":"0x00000000000000000000000000000000000a4b05","gasUsed":"0x0","gasUsedForL1":"0x0","l1BlockNumber":"0x149f366","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x00000000000000000000000000000000000a4b05","transactionHash":"0x58cd6dc4c642eb0f96e0cf01f8e34e64f22a9d14581c043a9a23c6028c674052","transactionIndex":"0x0","type":"0x6a"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x1a94a9","effectiveGasPrice":"0x989680","from":"0x619e6175e65e8511c9f91b4e0f87cb2e75321651","gasUsed":"0x1a94a9","gasUsedForL1":"0x1981c1","l1BlockNumber":"0x149f366","logs":[{"address":"0x1baa28178bec3127cfdcb0e11f874c263f502462","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000005c718762feac73fa9003d0c1f98c74ef8d5a6a26","0x000000000000000000000000619e6175e65e8511c9f91b4e0f87cb2e75321651"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000","blockNumber":"0x119b1fd3","transactionHash":"0x334cbe7b01106ca506d239887ef7a471158372cca23a2d758916090283c10294","transactionIndex":"0x1","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x0","removed":false},{"address":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","topics":["0x76c3da0779d2bcebe57c00bd8fc4cf03dfead2becd6568e7287bfe788ab3fa6f","0x000000000000000000000000619e6175e65e8511c9f91b4e0f87cb2e75321651","0x000000000000000000000000000000000000000000000000000095a43b2d4e00","0x0000000000000000000000002495ca17f576e67242ecd171f01ad54791bfb710"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000","blockNumber":"0x119b1fd3","transactionHash":"0x334cbe7b01106ca506d239887ef7a471158372cca23a2d758916090283c10294","transactionIndex":"0x1","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x1","removed":false}],"logsBloom":"0x00104000000000000000000000000000000000000000000000000040000000010000000000000000000000000000000000000000000000000000000000000000000000000100000200020008000000000800000000000000010000000020000000000000000000040000001000000000000000000000000000000010000000000000000008000000000000000000000000000000000000000000000000000000000200000000000000000200000000000000000000000000800000000000000000000002000010000000000000000000000000000000000000000000000002000000000200000000000004000000000000000000000002000000000000000000","status":"0x1","to":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","transactionHash":"0x334cbe7b01106ca506d239887ef7a471158372cca23a2d758916090283c10294","transactionIndex":"0x1","type":"0x0"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x2564e4","effectiveGasPrice":"0x989680","from":"0x16fb1259a1fb59d3cabc6b1ad169762dcf0f8638","gasUsed":"0xad03b","gasUsedForL1":"0x85c4f","l1BlockNumber":"0x149f366","logs":[{"address":"0x82af49447d8a07e3bd95bd0d56f35241523fbab1","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","0x000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76"],"data":"0x000000000000000000000000000000000000000000000000194ba12387825e1b","blockNumber":"0x119b1fd3","transactionHash":"0x677ae449aea214ae1f60875330531d9e9c63dba18d01015ef7379007867e622f","transactionIndex":"0x2","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x2","removed":false},{"address":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76","0x000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0"],"data":"0x000000000000000000000000000000000000000000000000000000015c0070a0","blockNumber":"0x119b1fd3","transactionHash":"0x677ae449aea214ae1f60875330531d9e9c63dba18d01015ef7379007867e622f","transactionIndex":"0x2","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x3","removed":false},{"address":"0xc6962004f452be9203591991d15f6b388e09e8d0","topics":["0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67","0x000000000000000000000000e3be1402bdc62d1e4ff4d5658129c5f82745fbad","0x000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76"],"data":"0xffffffffffffffffffffffffffffffffffffffffffffffffe6b45edc787da1e5000000000000000000000000000000000000000000000000000000015c0070a000000000000000000000000000000000000000000003b54d668d6c2f53c0754c000000000000000000000000000000000000000000000000589cdb85f042223cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd03ea","blockNumber":"0x119b1fd3","transactionHash":"0x677ae449aea214ae1f60875330531d9e9c63dba18d01015ef7379007867e622f","transactionIndex":"0x2","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x4","removed":false}],"logsBloom":"0x00000000000004000010000040000000000000000000000000000000000000000000004000000000000000000000000000004001000020000000000000000000000000010000000800000009000000000040000000000000000000000000000000000000000000000000000400000000000080000000000000000010000800000000000000000000000000400000000000000000002000000000000000000000200000000000000000000000000000000000000000000000000000080000000000002002000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000","status":"0x1","to":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","transactionHash":"0x677ae449aea214ae1f60875330531d9e9c63dba18d01015ef7379007867e622f","transactionIndex":"0x2","type":"0x0"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x30133f","effectiveGasPrice":"0x989680","from":"0xe3c81b8221bcebccf5eac414cf52156c3c3b38ed","gasUsed":"0xaae5b","gasUsedForL1":"0x85c4f","l1BlockNumber":"0x149f366","logs":[{"address":"0x82af49447d8a07e3bd95bd0d56f35241523fbab1","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","0x000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76"],"data":"0x000000000000000000000000000000000000000000000000142e60e8ac88e194","blockNumber":"0x119b1fd3","transactionHash":"0x85c2d0969c7423a46197febc93ef19be5ef15108e0451c4d4f673c6cb2798e61","transactionIndex":"0x3","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x5","removed":false},{"address":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76","0x000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0"],"data":"0x0000000000000000000000000000000000000000000000000000000115a67dc0","blockNumber":"0x119b1fd3","transactionHash":"0x85c2d0969c7423a46197febc93ef19be5ef15108e0451c4d4f673c6cb2798e61","transactionIndex":"0x3","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x6","removed":false},{"address":"0xc6962004f452be9203591991d15f6b388e09e8d0","topics":["0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67","0x000000000000000000000000e3be1402bdc62d1e4ff4d5658129c5f82745fbad","0x000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76"],"data":"0xffffffffffffffffffffffffffffffffffffffffffffffffebd19f1753771e6c0000000000000000000000000000000000000000000000000000000115a67dc000000000000000000000000000000000000000000003b5508846c72263561808000000000000000000000000000000000000000000000000589cdb85f042223cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd03ea","blockNumber":"0x119b1fd3","transactionHash":"0x85c2d0969c7423a46197febc93ef19be5ef15108e0451c4d4f673c6cb2798e61","transactionIndex":"0x3","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x7","removed":false}],"logsBloom":"0x00000000000004000010000040000000000000000000000000000000000000000000004000000000000000000000000000004001000020000000000000000000000000010000000800000009000000000040000000000000000000000000000000000000000000000000000400000000000080000000000000000010000800000000000000000000000000400000000000000000002000000000000000000000200000000000000000000000000000000000000000000000000000080000000000002002000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000","status":"0x1","to":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","transactionHash":"0x85c2d0969c7423a46197febc93ef19be5ef15108e0451c4d4f673c6cb2798e61","transactionIndex":"0x3","type":"0x0"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x3da54b","effectiveGasPrice":"0x989680","from":"0xf7ffaad1569d8c9626b6e90636793f072a95d259","gasUsed":"0xd920c","gasUsedForL1":"0xcc0e0","l1BlockNumber":"0x149f366","logs":[{"address":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x000000000000000000000000f7ffaad1569d8c9626b6e90636793f072a95d259","0x000000000000000000000000b45c42fbf8af8df5a1fa080a351e9b2f8e0a56d1"],"data":"0x00000000000000000000000000000000000000000000000000000000004e6566","blockNumber":"0x119b1fd3","transactionHash":"0x14617bb93ae8e7a349affa430390abd482bdb4d6658f20854ecf07895f7c621b","transactionIndex":"0x4","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x8","removed":false}],"logsBloom":"0x00000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000010000000000000000000008000000000000000000000000000000000000000000000000000001000000010000000000000000000000000000011001000000000000000000000000000","status":"0x1","to":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","transactionHash":"0x14617bb93ae8e7a349affa430390abd482bdb4d6658f20854ecf07895f7c621b","transactionIndex":"0x4","type":"0x2"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x471eb4","effectiveGasPrice":"0x989680","from":"0x5096fb354daca9b11ce0d5884fad532b056e05dd","gasUsed":"0x97969","gasUsedForL1":"0x8db46","l1BlockNumber":"0x149f366","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0xcd934b818ddd7da4a82cd3e73c014569024c26f8","transactionHash":"0x9a43b119e0247d39e498de5c0f4bc49702354d2368b796591352ed2a9bd55b3b","transactionIndex":"0x5","type":"0x2"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x505e26","effectiveGasPrice":"0x989680","from":"0xb38e8c17e38363af6ebdcb3dae12e0243582891d","gasUsed":"0x93f72","gasUsedForL1":"0x8ed6a","l1BlockNumber":"0x149f366","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0xf39cf285e20bffe688e542fce3ecbb3c5064aa5e","transactionHash":"0x2eb017df8f91c0038177ad0cedd4713dab6f6f3cf775d1753f5773ed7890b5bd","transactionIndex":"0x6","type":"0x2"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x5ea2fc","effectiveGasPrice":"0x989680","from":"0x3931dab967c3e2dbb492fe12460a66d0fe4cc857","gasUsed":"0xe44d6","gasUsedForL1":"0xd51fb","l1BlockNumber":"0x149f366","logs":[{"address":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003931dab967c3e2dbb492fe12460a66d0fe4cc857","0x000000000000000000000000f7e8ec11bd1c22643c2a2879192c818340242d1a"],"data":"0x00000000000000000000000000000000000000000000000000000000014d1970","blockNumber":"0x119b1fd3","transactionHash":"0x533d4df22dabdfc80b090c7a69af5f38d80b5878d33b398273329f552d666d15","transactionIndex":"0x7","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0x9","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000808000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000200000000000000000000000002002080000000000000000000000000000000000000000000020000000000000000000000000000000000000200000000000000000000000000000000000","status":"0x1","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","transactionHash":"0x533d4df22dabdfc80b090c7a69af5f38d80b5878d33b398273329f552d666d15","transactionIndex":"0x7","type":"0x2"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x671ae9","effectiveGasPrice":"0x989680","from":"0x26feadf80ee3c6a13172a9324a00879f994563f7","gasUsed":"0x877ed","gasUsedForL1":"0x825e5","l1BlockNumber":"0x149f366","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x2166f18a7dbdf7c4d4b6a8f4fbec537967939928","transactionHash":"0xa6e7a77e8462adcd7d60a4dca9208c2e8cac34380a7df53f22b21345427d76a6","transactionIndex":"0x8","type":"0x0"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x75187e","effectiveGasPrice":"0x989680","from":"0x25681ab599b4e2ceea31f8b498052c53fc2d74db","gasUsed":"0xdfd95","gasUsedForL1":"0xd51fb","l1BlockNumber":"0x149f366","logs":[{"address":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000025681ab599b4e2ceea31f8b498052c53fc2d74db","0x000000000000000000000000fdc77e8188735b1d889ce0f189c472ecd0eadd52"],"data":"0x0000000000000000000000000000000000000000000000000000000000384e10","blockNumber":"0x119b1fd3","transactionHash":"0x2a47a12b226d557bc1f3fafc54ab53531bfdeddc42f10e8b0104266f7613bb5e","transactionIndex":"0x9","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0xa","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000100000002000000000000000000000000000000000000000010000000000000000008000000000000000000000000000000000000080000000000000000008000000000800000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008002000000000000000000000000000000010000000000000000000000000000000000000000000000000000000010000000000000000000000000000000","status":"0x1","to":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","transactionHash":"0x2a47a12b226d557bc1f3fafc54ab53531bfdeddc42f10e8b0104266f7613bb5e","transactionIndex":"0x9","type":"0x2"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x8f88e1","effectiveGasPrice":"0x989680","from":"0xa0084b9e25d9fcaf83a4ee5bd7ca2bb708c56c19","gasUsed":"0x1a7063","gasUsedForL1":"0x195d7b","l1BlockNumber":"0x149f366","logs":[{"address":"0x1baa28178bec3127cfdcb0e11f874c263f502462","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000005c718762feac73fa9003d0c1f98c74ef8d5a6a26","0x000000000000000000000000a0084b9e25d9fcaf83a4ee5bd7ca2bb708c56c19"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000","blockNumber":"0x119b1fd3","transactionHash":"0xb8aea70764d5dc3e14d6991dc1de4bebea18fa31b82f2ccca2f96578aba2d6a0","transactionIndex":"0xa","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0xb","removed":false},{"address":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","topics":["0x76c3da0779d2bcebe57c00bd8fc4cf03dfead2becd6568e7287bfe788ab3fa6f","0x000000000000000000000000a0084b9e25d9fcaf83a4ee5bd7ca2bb708c56c19","0x000000000000000000000000000000000000000000000000000096b37a969000","0x000000000000000000000000cb75db6b8e8268ea5053c85eb8606a3a65a5b10a"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000","blockNumber":"0x119b1fd3","transactionHash":"0xb8aea70764d5dc3e14d6991dc1de4bebea18fa31b82f2ccca2f96578aba2d6a0","transactionIndex":"0xa","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0xc","removed":false}],"logsBloom":"0x00100000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000100000200020008000000000800000000000000010000000000000000000020040000200000000000000000000000000000000000000010000000000000000000000000000000000000040000000002000000000000000000400000000000000000000000000200000000000000000000000000000000000000000000002002000010000000000000000000000000000000000000000000000002000000000200000000000000000000000000000000000202100000000000000000","status":"0x1","to":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","transactionHash":"0xb8aea70764d5dc3e14d6991dc1de4bebea18fa31b82f2ccca2f96578aba2d6a0","transactionIndex":"0xa","type":"0x0"}},{"jsonrpc":"2.0","id":16,"result":{"blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","blockNumber":"0x119b1fd3","contractAddress":null,"cumulativeGasUsed":"0x9d057e","effectiveGasPrice":"0x989680","from":"0xa4c3e98e0ab7967ceb81d829bc6a038d9e114b87","gasUsed":"0xd7c9d","gasUsedForL1":"0xc9c9a","l1BlockNumber":"0x149f366","logs":[{"address":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a4c3e98e0ab7967ceb81d829bc6a038d9e114b87","0x00000000000000000000000019fd9164a7f9544de573d8e27747c50c90f1bc39"],"data":"0x0000000000000000000000000000000000000000000000000000000000001c76","blockNumber":"0x119b1fd3","transactionHash":"0xf846e2254370f09b4538900cbb97403739ddab69da5e1e69326b7f90abbe2c27","transactionIndex":"0xb","blockHash":"0x11caca47575fa6aa131858c777279b9174a74733dc75b07cd74992fc4be33eba","logIndex":"0xd","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000004000000020000000020000400000000000000000000000000000000000000000000000000000000000000000000002002000000000000000000000000000000000000000000000000000000000000000000080000000000000000200000000000000000000000000000000000","status":"0x1","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","transactionHash":"0xf846e2254370f09b4538900cbb97403739ddab69da5e1e69326b7f90abbe2c27","transactionIndex":"0xb","type":"0x0"}}]` + testArbTraces = `{"jsonrpc":"2.0","id":16,"result":[{"txHash":"0x58cd6dc4c642eb0f96e0cf01f8e34e64f22a9d14581c043a9a23c6028c674052","result":{"beforeEVMTransfers":[],"afterEVMTransfers":[],"from":"0x00000000000000000000000000000000000a4b05","gas":"0x0","gasUsed":"0x0","to":"0x00000000000000000000000000000000000a4b05","input":"0x6bf6a42d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000149f36600000000000000000000000000000000000000000000000000000000119b1fd30000000000000000000000000000000000000000000000000000000000000000","value":"0x0","type":"CALL"}},{"txHash":"0x334cbe7b01106ca506d239887ef7a471158372cca23a2d758916090283c10294","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x619E6175E65E8511c9F91B4e0F87CB2E75321651","to":null,"value":"0x1d24b30cf900"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x619E6175E65E8511c9F91B4e0F87CB2E75321651","value":"0xd4cce4f9e80"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0xa3db5d6400"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0xf34095ff680"}],"from":"0x619e6175e65e8511c9f91b4e0f87cb2e75321651","gas":"0x30e4fa","gasUsed":"0x1a94a9","to":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","input":"0x5d9be1060000000000000000000000002495ca17f576e67242ecd171f01ad54791bfb710000000000000000000000000000000000000000000000000000095a43b2d4e0000000000000000000000000000000000000000000000021e19e0c9bab2400000000000000000000000000000000000000000000000000000000000006786845c00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000041898d17f65dfbed1b3f2b0e52705b0e45373af6a48d9ebea2c726c91fc67fe1bc33a6068034c641b20c1aae679a5b10e5fd4bddcd62f033bd6fed2196fbc1b2fc1b00000000000000000000000000000000000000000000000000000000000000","calls":[{"from":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","gas":"0x16984e","gasUsed":"0xa3a8","to":"0xd8299bc94141f4c4655ef1ce001755649ec0f1c4","input":"0x5d9be1060000000000000000000000002495ca17f576e67242ecd171f01ad54791bfb710000000000000000000000000000000000000000000000000000095a43b2d4e0000000000000000000000000000000000000000000000021e19e0c9bab2400000000000000000000000000000000000000000000000000000000000006786845c00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000041898d17f65dfbed1b3f2b0e52705b0e45373af6a48d9ebea2c726c91fc67fe1bc33a6068034c641b20c1aae679a5b10e5fd4bddcd62f033bd6fed2196fbc1b2fc1b00000000000000000000000000000000000000000000000000000000000000","calls":[{"from":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","gas":"0x162700","gasUsed":"0xbb8","to":"0x0000000000000000000000000000000000000001","input":"0x2b27ad79a636224c46eddc17003ccbbb3a1876bcf91eda79dce4e94cdf226766000000000000000000000000000000000000000000000000000000000000001b898d17f65dfbed1b3f2b0e52705b0e45373af6a48d9ebea2c726c91fc67fe1bc33a6068034c641b20c1aae679a5b10e5fd4bddcd62f033bd6fed2196fbc1b2fc","output":"0x000000000000000000000000f5367e0e5a490491aca8575554b072839567dd19","type":"STATICCALL"},{"from":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","gas":"0x15f5b1","gasUsed":"0x0","to":"0x2495ca17f576e67242ecd171f01ad54791bfb710","input":"0x","value":"0x95a43b2d4e00","type":"CALL"},{"from":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","gas":"0x15e191","gasUsed":"0x3b5e","to":"0x1baa28178bec3127cfdcb0e11f874c263f502462","input":"0xa9059cbb000000000000000000000000619e6175e65e8511c9f91b4e0f87cb2e7532165100000000000000000000000000000000000000000000021e19e0c9bab2400000","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"CALL"}],"value":"0x95a43b2d4e00","type":"DELEGATECALL"}],"value":"0x95a43b2d4e00","type":"CALL"}},{"txHash":"0x677ae449aea214ae1f60875330531d9e9c63dba18d01015ef7379007867e622f","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x16Fb1259A1FB59D3CaBC6B1Ad169762dCF0F8638","to":null,"value":"0xdbf48207000"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x16Fb1259A1FB59D3CaBC6B1Ad169762dCF0F8638","value":"0x74d49adc080"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x17645463e00"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x4fbb92c7180"}],"from":"0x16fb1259a1fb59d3cabc6b1ad169762dcf0f8638","gas":"0x171060","gasUsed":"0xad03b","to":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","input":"0x0b1a740e411301","calls":[{"from":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","gas":"0xd8037","gasUsed":"0x1c10c","to":"0xc6962004f452be9203591991d15f6b388e09e8d0","input":"0x128acb08000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015c0070a0000000000000000000000000fffd8963efd1fc6a506488495d951d5263988d2500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000000000000000000000000000000000000000002baf88d065e77c8cc2239327c5edb3a432268e58310001f482af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000","output":"0xffffffffffffffffffffffffffffffffffffffffffffffffe6b45edc787da1e5000000000000000000000000000000000000000000000000000000015c0070a0","calls":[{"from":"0xc6962004f452be9203591991d15f6b388e09e8d0","gas":"0xcbb05","gasUsed":"0x4f4c","to":"0x82af49447d8a07e3bd95bd0d56f35241523fbab1","input":"0xa9059cbb000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000000000000000000000000000194ba12387825e1b","output":"0x0000000000000000000000000000000000000000000000000000000000000001","calls":[{"from":"0x82af49447d8a07e3bd95bd0d56f35241523fbab1","gas":"0xc6c65","gasUsed":"0x32fe","to":"0x8b194beae1d3e0788a1a35173978001acdfba668","input":"0xa9059cbb000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000000000000000000000000000194ba12387825e1b","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"DELEGATECALL"}],"value":"0x0","type":"CALL"},{"from":"0xc6962004f452be9203591991d15f6b388e09e8d0","gas":"0xc5ff4","gasUsed":"0x2616","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0x70a08231000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","output":"0x00000000000000000000000000000000000000000000000000000c066b4c2111","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0xc12f0","gasUsed":"0x9f9","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0x70a08231000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","output":"0x00000000000000000000000000000000000000000000000000000c066b4c2111","value":"0x0","type":"DELEGATECALL"}],"type":"STATICCALL"},{"from":"0xc6962004f452be9203591991d15f6b388e09e8d0","gas":"0xc375f","gasUsed":"0x9406","to":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","input":"0xfa461e33ffffffffffffffffffffffffffffffffffffffffffffffffe6b45edc787da1e5000000000000000000000000000000000000000000000000000000015c0070a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000000000000000000000000000000000000000002baf88d065e77c8cc2239327c5edb3a432268e58310001f482af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000","calls":[{"from":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","gas":"0xbe26c","gasUsed":"0xa6a","to":"0x1f98431c8ad98523631ae4a59f267346ea31f984","input":"0x1698ee82000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000000000001f4","output":"0x000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","type":"STATICCALL"},{"from":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","gas":"0xbcf6b","gasUsed":"0x592c","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0x23b872dd000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0000000000000000000000000000000000000000000000000000000015c0070a0","output":"0x0000000000000000000000000000000000000000000000000000000000000001","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0xb9dbf","gasUsed":"0x568a","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0x23b872dd000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0000000000000000000000000000000000000000000000000000000015c0070a0","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"DELEGATECALL"}],"value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"},{"from":"0xc6962004f452be9203591991d15f6b388e09e8d0","gas":"0xba332","gasUsed":"0x4e2","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0x70a08231000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","output":"0x00000000000000000000000000000000000000000000000000000c07c74c91b1","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0xb7220","gasUsed":"0x229","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0x70a08231000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","output":"0x00000000000000000000000000000000000000000000000000000c07c74c91b1","value":"0x0","type":"DELEGATECALL"}],"type":"STATICCALL"}],"value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}},{"txHash":"0x85c2d0969c7423a46197febc93ef19be5ef15108e0451c4d4f673c6cb2798e61","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0xE3C81b8221bceBCCf5EAc414Cf52156C3c3b38ed","to":null,"value":"0xdbf48207000"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0xE3C81b8221bceBCCf5EAc414Cf52156C3c3b38ed","value":"0x7617a97f080"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x162145c0e00"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x4fbb92c7180"}],"from":"0xe3c81b8221bcebccf5eac414cf52156c3c3b38ed","gas":"0x171060","gasUsed":"0xaae5b","to":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","input":"0x08dbd00b5f6301","calls":[{"from":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","gas":"0xd8037","gasUsed":"0x19f2c","to":"0xc6962004f452be9203591991d15f6b388e09e8d0","input":"0x128acb08000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f7600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000115a67dc0000000000000000000000000fffd8963efd1fc6a506488495d951d5263988d2500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000000000000000000000000000000000000000002baf88d065e77c8cc2239327c5edb3a432268e58310001f482af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000","output":"0xffffffffffffffffffffffffffffffffffffffffffffffffebd19f1753771e6c0000000000000000000000000000000000000000000000000000000115a67dc0","calls":[{"from":"0xc6962004f452be9203591991d15f6b388e09e8d0","gas":"0xcdc5d","gasUsed":"0x4f4c","to":"0x82af49447d8a07e3bd95bd0d56f35241523fbab1","input":"0xa9059cbb000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000000000000000000000000000142e60e8ac88e194","output":"0x0000000000000000000000000000000000000000000000000000000000000001","calls":[{"from":"0x82af49447d8a07e3bd95bd0d56f35241523fbab1","gas":"0xc8d38","gasUsed":"0x32fe","to":"0x8b194beae1d3e0788a1a35173978001acdfba668","input":"0xa9059cbb000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000000000000000000000000000142e60e8ac88e194","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"DELEGATECALL"}],"value":"0x0","type":"CALL"},{"from":"0xc6962004f452be9203591991d15f6b388e09e8d0","gas":"0xc814b","gasUsed":"0x2616","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0x70a08231000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","output":"0x00000000000000000000000000000000000000000000000000000c07c74c91b1","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0xc33c2","gasUsed":"0x9f9","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0x70a08231000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","output":"0x00000000000000000000000000000000000000000000000000000c07c74c91b1","value":"0x0","type":"DELEGATECALL"}],"type":"STATICCALL"},{"from":"0xc6962004f452be9203591991d15f6b388e09e8d0","gas":"0xc58b8","gasUsed":"0x9406","to":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","input":"0xfa461e33ffffffffffffffffffffffffffffffffffffffffffffffffebd19f1753771e6c0000000000000000000000000000000000000000000000000000000115a67dc0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000000000000000000000000000000000000000002baf88d065e77c8cc2239327c5edb3a432268e58310001f482af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000","calls":[{"from":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","gas":"0xc033f","gasUsed":"0xa6a","to":"0x1f98431c8ad98523631ae4a59f267346ea31f984","input":"0x1698ee82000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000000000001f4","output":"0x000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","type":"STATICCALL"},{"from":"0xe3be1402bdc62d1e4ff4d5658129c5f82745fbad","gas":"0xbf03e","gasUsed":"0x592c","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0x23b872dd000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d00000000000000000000000000000000000000000000000000000000115a67dc0","output":"0x0000000000000000000000000000000000000000000000000000000000000001","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0xbbe0e","gasUsed":"0x568a","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0x23b872dd000000000000000000000000975580eb2b1473ac0b9fdf9f5eec9c26c04a3f76000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d00000000000000000000000000000000000000000000000000000000115a67dc0","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"DELEGATECALL"}],"value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"},{"from":"0xc6962004f452be9203591991d15f6b388e09e8d0","gas":"0xbc48a","gasUsed":"0x4e2","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0x70a08231000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","output":"0x00000000000000000000000000000000000000000000000000000c08dcf30f71","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0xb92f3","gasUsed":"0x229","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0x70a08231000000000000000000000000c6962004f452be9203591991d15f6b388e09e8d0","output":"0x00000000000000000000000000000000000000000000000000000c08dcf30f71","value":"0x0","type":"DELEGATECALL"}],"type":"STATICCALL"}],"value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}},{"txHash":"0x14617bb93ae8e7a349affa430390abd482bdb4d6658f20854ecf07895f7c621b","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0xf7ffaAd1569D8C9626b6E90636793f072A95d259","to":null,"value":"0x14f63d1fbb00"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0xf7ffaAd1569D8C9626b6E90636793f072A95d259","value":"0xcdf8ba3ad00"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x7cad185e00"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x79a0463b000"}],"from":"0xf7ffaad1569d8c9626b6e90636793f072a95d259","gas":"0x232b0e","gasUsed":"0xd920c","to":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","input":"0x095ea7b3000000000000000000000000b45c42fbf8af8df5a1fa080a351e9b2f8e0a56d100000000000000000000000000000000000000000000000000000000004e6566","output":"0x0000000000000000000000000000000000000000000000000000000000000001","calls":[{"from":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","gas":"0x15a170","gasUsed":"0x602a","to":"0xf31e1ae27e7cd057c1d6795a5a083e0453d39b50","input":"0x095ea7b3000000000000000000000000b45c42fbf8af8df5a1fa080a351e9b2f8e0a56d100000000000000000000000000000000000000000000000000000000004e6566","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"DELEGATECALL"}],"value":"0x0","type":"CALL"}},{"txHash":"0x9a43b119e0247d39e498de5c0f4bc49702354d2368b796591352ed2a9bd55b3b","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x5096fB354daca9B11cE0d5884fad532b056E05dd","to":null,"value":"0x9184e72a000"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x5096fB354daca9B11cE0d5884fad532b056E05dd","value":"0x372a63a6580"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x5e41bf9380"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x5476678a700"}],"from":"0x5096fb354daca9b11ce0d5884fad532b056e05dd","gas":"0xf4240","gasUsed":"0x97969","to":"0xcd934b818ddd7da4a82cd3e73c014569024c26f8","input":"0x00040000ff0503b54a8379","calls":[{"from":"0xcd934b818ddd7da4a82cd3e73c014569024c26f8","gas":"0x5ed37","gasUsed":"0x2616","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0x70a08231000000000000000000000000cd934b818ddd7da4a82cd3e73c014569024c26f8","output":"0x00000000000000000000000000000000000000000000000000000000ca3c284a","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0x5b9fe","gasUsed":"0x9f9","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0x70a08231000000000000000000000000cd934b818ddd7da4a82cd3e73c014569024c26f8","output":"0x00000000000000000000000000000000000000000000000000000000ca3c284a","value":"0x0","type":"DELEGATECALL"}],"type":"STATICCALL"},{"from":"0xcd934b818ddd7da4a82cd3e73c014569024c26f8","gas":"0x5bd15","gasUsed":"0xa88","to":"0xc6962004f452be9203591991d15f6b388e09e8d0","input":"0x3850c7bd","output":"0x00000000000000000000000000000000000000000003b5508846c72263561808fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd03ea00000000000000000000000000000000000000000000000000000000000017a10000000000000000000000000000000000000000000000000000000000002328000000000000000000000000000000000000000000000000000000000000232800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001","type":"STATICCALL"}],"value":"0x0","type":"CALL"}},{"txHash":"0x2eb017df8f91c0038177ad0cedd4713dab6f6f3cf775d1753f5773ed7890b5bd","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0xB38e8c17e38363aF6EbdCb3dAE12e0243582891D","to":null,"value":"0xe7e803ab800"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0xB38e8c17e38363aF6EbdCb3dAE12e0243582891D","value":"0x8fb64be3300"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x30e4f9b400"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x5523682d100"}],"from":"0xb38e8c17e38363af6ebdcb3dae12e0243582891d","gas":"0x185130","gasUsed":"0x93f72","to":"0xf39cf285e20bffe688e542fce3ecbb3c5064aa5e","input":"0x","value":"0xadc3bfe9d2800","type":"CALL"}},{"txHash":"0x533d4df22dabdfc80b090c7a69af5f38d80b5878d33b398273329f552d666d15","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x3931dAb967C3E2dbb492FE12460a66d0fe4cC857","to":null,"value":"0x151d145eec00"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x3931dAb967C3E2dbb492FE12460a66d0fe4cC857","value":"0xc9bd1d71d00"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x90c0cdbf80"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x7f081ba0f80"}],"from":"0x3931dab967c3e2dbb492fe12460a66d0fe4cc857","gas":"0x236c38","gasUsed":"0xe44d6","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0xa9059cbb000000000000000000000000f7e8ec11bd1c22643c2a2879192c818340242d1a00000000000000000000000000000000000000000000000000000000014d1970","output":"0x0000000000000000000000000000000000000000000000000000000000000001","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0x155337","gasUsed":"0x8253","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0xa9059cbb000000000000000000000000f7e8ec11bd1c22643c2a2879192c818340242d1a00000000000000000000000000000000000000000000000000000000014d1970","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"DELEGATECALL"}],"value":"0x0","type":"CALL"}},{"txHash":"0xa6e7a77e8462adcd7d60a4dca9208c2e8cac34380a7df53f22b21345427d76a6","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x26FEadf80EE3c6a13172A9324A00879F994563f7","to":null,"value":"0x574fbde6000"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x26FEadf80EE3c6a13172A9324A00879F994563f7","value":"0x68cca58b80"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x30e4f9b400"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x4db4a3f2080"}],"from":"0x26feadf80ee3c6a13172a9324a00879f994563f7","gas":"0x927c0","gasUsed":"0x877ed","to":"0x2166f18a7dbdf7c4d4b6a8f4fbec537967939928","input":"0x","value":"0x45f9685b7f000","type":"CALL"}},{"txHash":"0x2a47a12b226d557bc1f3fafc54ab53531bfdeddc42f10e8b0104266f7613bb5e","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x25681Ab599B4E2CEea31F8B498052c53FC2D74db","to":null,"value":"0x14c7768d0200"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x25681Ab599B4E2CEea31F8B498052c53FC2D74db","value":"0xc70ac80e980"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x6648520900"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x7f081ba0f80"}],"from":"0x25681ab599b4e2ceea31f8b498052c53fc2d74db","gas":"0x22dc94","gasUsed":"0xdfd95","to":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","input":"0xa9059cbb000000000000000000000000fdc77e8188735b1d889ce0f189c472ecd0eadd520000000000000000000000000000000000000000000000000000000000384e10","output":"0x0000000000000000000000000000000000000000000000000000000000000001","calls":[{"from":"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9","gas":"0x14c559","gasUsed":"0x3a98","to":"0xf31e1ae27e7cd057c1d6795a5a083e0453d39b50","input":"0xa9059cbb000000000000000000000000fdc77e8188735b1d889ce0f189c472ecd0eadd520000000000000000000000000000000000000000000000000000000000384e10","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"DELEGATECALL"}],"value":"0x0","type":"CALL"}},{"txHash":"0xb8aea70764d5dc3e14d6991dc1de4bebea18fa31b82f2ccca2f96578aba2d6a0","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0xA0084B9E25D9FcAF83A4eE5BD7Ca2Bb708c56c19","to":null,"value":"0x1c5cac1ab380"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0xA0084B9E25D9FcAF83A4eE5BD7Ca2Bb708c56c19","value":"0xc9a66408000"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0xa3db5d6400"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0xf1e6a7ccf80"}],"from":"0xa0084b9e25d9fcaf83a4ee5bd7ca2bb708c56c19","gas":"0x2f9563","gasUsed":"0x1a7063","to":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","input":"0x5d9be106000000000000000000000000cb75db6b8e8268ea5053c85eb8606a3a65a5b10a000000000000000000000000000000000000000000000000000096b37a96900000000000000000000000000000000000000000000000021e19e0c9bab2400000000000000000000000000000000000000000000000000000000000006786845f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000041924cc2a7d7843a536cc63d7f599379d5bad2735caa791e496ac77c6df7e93ea638dcf6abf80b5425d3c818d5955709d5881321c7d9dfa9d5cb845783b023788e1c00000000000000000000000000000000000000000000000000000000000000","calls":[{"from":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","gas":"0x1571aa","gasUsed":"0xa3a8","to":"0xd8299bc94141f4c4655ef1ce001755649ec0f1c4","input":"0x5d9be106000000000000000000000000cb75db6b8e8268ea5053c85eb8606a3a65a5b10a000000000000000000000000000000000000000000000000000096b37a96900000000000000000000000000000000000000000000000021e19e0c9bab2400000000000000000000000000000000000000000000000000000000000006786845f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000041924cc2a7d7843a536cc63d7f599379d5bad2735caa791e496ac77c6df7e93ea638dcf6abf80b5425d3c818d5955709d5881321c7d9dfa9d5cb845783b023788e1c00000000000000000000000000000000000000000000000000000000000000","calls":[{"from":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","gas":"0x1504f7","gasUsed":"0xbb8","to":"0x0000000000000000000000000000000000000001","input":"0xc7ef40ba1abad2f0864f9cfef525dca255d4d5c5c8478a7d7f5a067a5e348198000000000000000000000000000000000000000000000000000000000000001c924cc2a7d7843a536cc63d7f599379d5bad2735caa791e496ac77c6df7e93ea638dcf6abf80b5425d3c818d5955709d5881321c7d9dfa9d5cb845783b023788e","output":"0x000000000000000000000000f5367e0e5a490491aca8575554b072839567dd19","type":"STATICCALL"},{"from":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","gas":"0x14d3a8","gasUsed":"0x0","to":"0xcb75db6b8e8268ea5053c85eb8606a3a65a5b10a","input":"0x","value":"0x96b37a969000","type":"CALL"},{"from":"0x5c718762feac73fa9003d0c1f98c74ef8d5a6a26","gas":"0x14bf87","gasUsed":"0x3b5e","to":"0x1baa28178bec3127cfdcb0e11f874c263f502462","input":"0xa9059cbb000000000000000000000000a0084b9e25d9fcaf83a4ee5bd7ca2bb708c56c1900000000000000000000000000000000000000000000021e19e0c9bab2400000","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"CALL"}],"value":"0x96b37a969000","type":"DELEGATECALL"}],"value":"0x96b37a969000","type":"CALL"}},{"txHash":"0xf846e2254370f09b4538900cbb97403739ddab69da5e1e69326b7f90abbe2c27","result":{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0xa4c3e98E0AB7967Ceb81D829BC6a038d9E114b87","to":null,"value":"0x104ba9f50a00"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0xa4c3e98E0AB7967Ceb81D829BC6a038d9E114b87","value":"0x841befabd80"},{"purpose":"feeCollection","from":null,"to":"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649","value":"0x858579c380"},{"purpose":"feeCollection","from":null,"to":"0xa4B00000000000000000000000000000000000F6","value":"0x78465808900"}],"from":"0xa4c3e98e0ab7967ceb81d829bc6a038d9e114b87","gas":"0x1b56e4","gasUsed":"0xd7c9d","to":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","input":"0xa9059cbb00000000000000000000000019fd9164a7f9544de573d8e27747c50c90f1bc390000000000000000000000000000000000000000000000000000000000001c76","output":"0x0000000000000000000000000000000000000000000000000000000000000001","calls":[{"from":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","gas":"0xe10db","gasUsed":"0x8253","to":"0x86e721b43d4ecfa71119dd38c0f938a75fdb57b3","input":"0xa9059cbb00000000000000000000000019fd9164a7f9544de573d8e27747c50c90f1bc390000000000000000000000000000000000000000000000000000000000001c76","output":"0x0000000000000000000000000000000000000000000000000000000000000001","value":"0x0","type":"DELEGATECALL"}],"value":"0x0","type":"CALL"}}]}` ) + +var testArbFullBlock = FullBlockData{ + ChainID: 42161, + BlockNumber: 295378899, + BlockHash: nil, + BlockUnclesCount: 0, + BlockTxs: nil, + Block: []byte(testArbBlock), + Receipts: []byte(testArbReceipts), + Traces: []byte(testArbTraces), + Uncles: nil, +} diff --git a/backend/pkg/commons/db2/raw/validation.go b/backend/pkg/commons/db2/raw/validation.go new file mode 100644 index 000000000..6fbe247bd --- /dev/null +++ b/backend/pkg/commons/db2/raw/validation.go @@ -0,0 +1,87 @@ +package raw + +import ( + "encoding/json" + "fmt" + + "github.com/gobitfly/beaconchain/pkg/commons/chain" + "github.com/gobitfly/beaconchain/pkg/commons/hexutil" +) + +type minimalBlock struct { + Result struct { + Hash string `json:"hash"` + Transactions []struct { + Hash string `json:"hash"` + } `json:"transactions"` + } `json:"result"` +} +type minimalReceipts struct { + Result []minimalReceipt `json:"result"` +} +type minimalReceiptResp struct { + Result minimalReceipt `json:"result"` +} +type minimalReceipt struct { + BlockHash string `json:"blockHash"` + Hash string `json:"transactionHash"` +} +type minimalTraces struct { + Result []struct { + TxHash string `json:"txHash"` + } `json:"result"` +} + +func validateBlock(fullBlock FullBlockData) error { + if len(fullBlock.Block) == 0 || len(fullBlock.BlockTxs) != 0 && len(fullBlock.Receipts) == 0 || len(fullBlock.BlockTxs) != 0 && len(fullBlock.Traces) == 0 { + return fmt.Errorf("empty data block=%d receipts=%d traces=%d", len(fullBlock.Block), len(fullBlock.Receipts), len(fullBlock.Traces)) + } + var block minimalBlock + _ = json.Unmarshal(fullBlock.Block, &block) + + var receipts minimalReceipts + _ = json.Unmarshal(fullBlock.Receipts, &receipts) + // Arbitrum did not support eth_getBlockReceipts at one point + // so the receipt data is the list of all eth_getTransactionReceipt responses + if fullBlock.ChainID == chain.IDs.Arbitrum.Uint64() { + receipts = normalizeReceipts(fullBlock.Receipts) + } + + var traces minimalTraces + _ = json.Unmarshal(fullBlock.Traces, &traces) + + if len(block.Result.Transactions) != len(receipts.Result) || len(block.Result.Transactions) != len(traces.Result) { + return fmt.Errorf("mismatch transaction count block=%d receipt=%d trace=%d", len(block.Result.Transactions), len(receipts.Result), len(traces.Result)) + } + + if len(block.Result.Transactions) == 0 { + return nil + } + + if block.Result.Hash != receipts.Result[0].BlockHash { + return fmt.Errorf("mismatch block hash block=%s receipt=%s", block.Result.Hash, receipts.Result[0].BlockHash) + } + + for i := 0; i < len(block.Result.Transactions); i++ { + tx, receipt, trace := block.Result.Transactions[i].Hash, block.Result.Transactions[i].Hash, traces.Result[i].TxHash + if tx != receipt || (trace != "" && tx != trace) { + return fmt.Errorf("mismatch transaction hash at index=%d block=%s receipt=%s trace=%s", i, tx, receipt, trace) + } + } + + return nil +} + +// normalizeReceipts parse a list of eth_getTransactionReceipt response and transform it to something similar to +// eth_getBlockReceipts response +func normalizeReceipts(rawReceipts hexutil.Bytes) minimalReceipts { + var receipts []minimalReceiptResp + _ = json.Unmarshal(rawReceipts, &receipts) + var normalizedReceipts []minimalReceipt + for _, receipt := range receipts { + normalizedReceipts = append(normalizedReceipts, receipt.Result) + } + return minimalReceipts{ + Result: normalizedReceipts, + } +}