diff --git a/turbo/jsonrpc/otterscan_api.go b/turbo/jsonrpc/otterscan_api.go index 8ed625ded..9b14b8588 100644 --- a/turbo/jsonrpc/otterscan_api.go +++ b/turbo/jsonrpc/otterscan_api.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" "math/big" - "sync" hexutil2 "github.com/ledgerwatch/erigon-lib/common/hexutil" + "golang.org/x/sync/errgroup" "github.com/holiman/uint256" "github.com/ledgerwatch/log/v3" @@ -435,8 +435,6 @@ func (api *OtterscanAPIImpl) SearchTransactionsAfter(ctx context.Context, addr c } func (api *OtterscanAPIImpl) traceBlocks(ctx context.Context, addr common.Address, chainConfig *chain.Config, pageSize, resultCount uint16, callFromToProvider BlockProvider) ([]*TransactionsWithReceipts, bool, error) { - var wg sync.WaitGroup - // Estimate the common case of user address having at most 1 interaction/block and // trace N := remaining page matches as number of blocks to trace concurrently. // TODO: this is not optimimal for big contract addresses; implement some better heuristics. @@ -445,7 +443,11 @@ func (api *OtterscanAPIImpl) traceBlocks(ctx context.Context, addr common.Addres totalBlocksTraced := 0 hasMore := true + eg, ctx := errgroup.WithContext(ctx) + eg.SetLimit(1024) // we don't want limit much here, but protecting from infinity attack for i := 0; i < int(estBlocksToTrace); i++ { + i := i // we will pass it to goroutine + var nextBlock uint64 var err error nextBlock, hasMore, err = callFromToProvider() @@ -457,11 +459,19 @@ func (api *OtterscanAPIImpl) traceBlocks(ctx context.Context, addr common.Addres break } - wg.Add(1) totalBlocksTraced++ - go api.searchTraceBlock(ctx, &wg, addr, chainConfig, i, nextBlock, results) + + eg.Go(func() error { + // don't return error from searchTraceBlock - to avoid 1 block fail impact to other blocks + // if return error - `errgroup` will interrupt all other goroutines + // but passing `ctx` - then user still can cancel request + api.searchTraceBlock(ctx, addr, chainConfig, i, nextBlock, results) + return nil + }) + } + if err := eg.Wait(); err != nil { + return nil, false, err } - wg.Wait() return results[:totalBlocksTraced], hasMore, nil } diff --git a/turbo/jsonrpc/otterscan_contract_creator.go b/turbo/jsonrpc/otterscan_contract_creator.go index 072f05ccb..3cdd5386b 100644 --- a/turbo/jsonrpc/otterscan_contract_creator.go +++ b/turbo/jsonrpc/otterscan_contract_creator.go @@ -211,6 +211,12 @@ func (api *OtterscanAPIImpl) GetContractCreator(ctx context.Context, addr common defer bitmapdb.ReturnToPool64(bm) prevShardMaxBl := uint64(0) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + _, err := bm.ReadFrom(bytes.NewReader(v)) if err != nil { return nil, err diff --git a/turbo/jsonrpc/otterscan_generic_tracer.go b/turbo/jsonrpc/otterscan_generic_tracer.go index 7309f2584..182f07795 100644 --- a/turbo/jsonrpc/otterscan_generic_tracer.go +++ b/turbo/jsonrpc/otterscan_generic_tracer.go @@ -84,6 +84,12 @@ func (api *OtterscanAPIImpl) genericTracer(dbtx kv.Tx, ctx context.Context, bloc rules := chainConfig.Rules(block.NumberU64(), header.Time) signer := types.MakeSigner(chainConfig, blockNum, header.Time) for idx, tx := range block.Transactions() { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + ibs.SetTxContext(tx.Hash(), block.Hash(), idx) msg, _ := tx.AsMessage(*signer, header.BaseFee, rules) diff --git a/turbo/jsonrpc/otterscan_search_trace.go b/turbo/jsonrpc/otterscan_search_trace.go index bd2661461..c4ed35a25 100644 --- a/turbo/jsonrpc/otterscan_search_trace.go +++ b/turbo/jsonrpc/otterscan_search_trace.go @@ -2,7 +2,7 @@ package jsonrpc import ( "context" - "sync" + "fmt" "github.com/ledgerwatch/erigon-lib/chain" "github.com/ledgerwatch/erigon-lib/common" @@ -18,9 +18,7 @@ import ( "github.com/ledgerwatch/erigon/turbo/shards" ) -func (api *OtterscanAPIImpl) searchTraceBlock(ctx context.Context, wg *sync.WaitGroup, addr common.Address, chainConfig *chain.Config, idx int, bNum uint64, results []*TransactionsWithReceipts) { - defer wg.Done() - +func (api *OtterscanAPIImpl) searchTraceBlock(ctx context.Context, addr common.Address, chainConfig *chain.Config, idx int, bNum uint64, results []*TransactionsWithReceipts) { // Trace block for Txs newdbtx, err := api.db.BeginRo(ctx) if err != nil { @@ -80,6 +78,11 @@ func (api *OtterscanAPIImpl) traceBlock(dbtx kv.Tx, ctx context.Context, blockNu rules := chainConfig.Rules(block.NumberU64(), header.Time) found := false for idx, tx := range block.Transactions() { + select { + case <-ctx.Done(): + return false, nil, ctx.Err() + default: + } ibs.SetTxContext(tx.Hash(), block.Hash(), idx) msg, _ := tx.AsMessage(*signer, header.BaseFee, rules) @@ -95,6 +98,14 @@ func (api *OtterscanAPIImpl) traceBlock(dbtx kv.Tx, ctx context.Context, blockNu _ = ibs.FinalizeTx(rules, cachedWriter) if tracer.Found { + if idx > len(blockReceipts) { + select { // it may happen because request canceled, then return canelation error + case <-ctx.Done(): + return false, nil, ctx.Err() + default: + } + return false, nil, fmt.Errorf("requested receipt idx %d, but have only %d", idx, len(blockReceipts)) // otherwise return some error for debugging + } rpcTx := NewRPCTransaction(tx, block.Hash(), blockNum, uint64(idx), block.BaseFee()) mReceipt := marshalReceipt(blockReceipts[idx], tx, chainConfig, block.HeaderNoCopy(), tx.Hash(), true) mReceipt["timestamp"] = block.Time() diff --git a/turbo/jsonrpc/otterscan_transaction_by_sender_and_nonce.go b/turbo/jsonrpc/otterscan_transaction_by_sender_and_nonce.go index 8bb1d0ec9..86348ae06 100644 --- a/turbo/jsonrpc/otterscan_transaction_by_sender_and_nonce.go +++ b/turbo/jsonrpc/otterscan_transaction_by_sender_and_nonce.go @@ -173,6 +173,12 @@ func (api *OtterscanAPIImpl) GetTransactionBySenderAndNonce(ctx context.Context, maxBlPrevChunk := uint64(0) for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + if k == nil || !bytes.HasPrefix(k, addr.Bytes()) { // Check plain state data, err := tx.GetOne(kv.PlainState, addr.Bytes())