diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 438c3e4281..e9cfe1dd3a 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -18,6 +18,7 @@ import ( "github.com/andybalholm/brotli" "github.com/spf13/pflag" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -431,6 +432,35 @@ func AccessList(opts *AccessListOpts) types.AccessList { return l } +type txInfo struct { + Hash common.Hash `json:"hash"` + Nonce hexutil.Uint64 `json:"nonce"` + From common.Address `json:"from"` + To *common.Address `json:"to"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` + GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` + Input hexutil.Bytes `json:"input"` + Value *hexutil.Big `json:"value"` + Accesses *types.AccessList `json:"accessList,omitempty"` +} + +// getTxsInfoByBlock fetches all the transactions inside block of id 'number' using json rpc +// and returns an array of txInfo which has fields that are necessary in checking for batch reverts +func (b *BatchPoster) getTxsInfoByBlock(ctx context.Context, number int64) ([]txInfo, error) { + blockNrStr := rpc.BlockNumber(number).String() + rawRpcClient := b.l1Reader.Client().Client() + var blk struct { + Transactions []txInfo `json:"transactions"` + } + err := rawRpcClient.CallContext(ctx, &blk, "eth_getBlockByNumber", blockNrStr, true) + if err != nil { + return nil, fmt.Errorf("error fetching block %d : %w", number, err) + } + return blk.Transactions, nil +} + // checkRevert checks blocks with number in range [from, to] whether they // contain reverted batch_poster transaction. // It returns true if it finds batch posting needs to halt, which is true if a batch reverts @@ -440,20 +470,15 @@ func (b *BatchPoster) checkReverts(ctx context.Context, to int64) (bool, error) return false, fmt.Errorf("wrong range, from: %d > to: %d", b.nextRevertCheckBlock, to) } for ; b.nextRevertCheckBlock <= to; b.nextRevertCheckBlock++ { - number := big.NewInt(b.nextRevertCheckBlock) - block, err := b.l1Reader.Client().BlockByNumber(ctx, number) + txs, err := b.getTxsInfoByBlock(ctx, b.nextRevertCheckBlock) if err != nil { - return false, fmt.Errorf("getting block: %v by number: %w", number, err) + return false, fmt.Errorf("error getting transactions data of block %d: %w", b.nextRevertCheckBlock, err) } - for idx, tx := range block.Transactions() { - from, err := b.l1Reader.Client().TransactionSender(ctx, tx, block.Hash(), uint(idx)) - if err != nil { - return false, fmt.Errorf("getting sender of transaction tx: %v, %w", tx.Hash(), err) - } - if from == b.dataPoster.Sender() { - r, err := b.l1Reader.Client().TransactionReceipt(ctx, tx.Hash()) + for _, tx := range txs { + if tx.From == b.dataPoster.Sender() { + r, err := b.l1Reader.Client().TransactionReceipt(ctx, tx.Hash) if err != nil { - return false, fmt.Errorf("getting a receipt for transaction: %v, %w", tx.Hash(), err) + return false, fmt.Errorf("getting a receipt for transaction: %v, %w", tx.Hash, err) } if r.Status == types.ReceiptStatusFailed { shouldHalt := !b.config().DataPoster.UseNoOpStorage @@ -461,8 +486,22 @@ func (b *BatchPoster) checkReverts(ctx context.Context, to int64) (bool, error) if shouldHalt { logLevel = log.Error } - txErr := arbutil.DetailTxError(ctx, b.l1Reader.Client(), tx, r) - logLevel("Transaction from batch poster reverted", "nonce", tx.Nonce(), "txHash", tx.Hash(), "blockNumber", r.BlockNumber, "blockHash", r.BlockHash, "txErr", txErr) + al := types.AccessList{} + if tx.Accesses != nil { + al = *tx.Accesses + } + txErr := arbutil.DetailTxErrorUsingCallMsg(ctx, b.l1Reader.Client(), tx.Hash, r, ethereum.CallMsg{ + From: tx.From, + To: tx.To, + Gas: uint64(tx.Gas), + GasPrice: tx.GasPrice.ToInt(), + GasFeeCap: tx.GasFeeCap.ToInt(), + GasTipCap: tx.GasTipCap.ToInt(), + Value: tx.Value.ToInt(), + Data: tx.Input, + AccessList: al, + }) + logLevel("Transaction from batch poster reverted", "nonce", tx.Nonce, "txHash", tx.Hash, "blockNumber", r.BlockNumber, "blockHash", r.BlockHash, "txErr", txErr) return shouldHalt, nil } } diff --git a/arbutil/wait_for_l1.go b/arbutil/wait_for_l1.go index 180ce1c67e..cfe24cf636 100644 --- a/arbutil/wait_for_l1.go +++ b/arbutil/wait_for_l1.go @@ -96,3 +96,25 @@ func DetailTxError(ctx context.Context, client L1Interface, tx *types.Transactio } return fmt.Errorf("SendTxAsCall got: %w for tx hash %v", err, tx.Hash()) } + +func DetailTxErrorUsingCallMsg(ctx context.Context, client L1Interface, txHash common.Hash, txRes *types.Receipt, callMsg ethereum.CallMsg) error { + // Re-execute the transaction as a call to get a better error + if ctx.Err() != nil { + return ctx.Err() + } + if txRes == nil { + return errors.New("expected receipt") + } + if txRes.Status == types.ReceiptStatusSuccessful { + return nil + } + var err error + if _, err = client.CallContract(ctx, callMsg, txRes.BlockNumber); err == nil { + return fmt.Errorf("tx failed but call succeeded for tx hash %v", txHash) + } + callMsg.Gas = 0 + if _, err = client.CallContract(ctx, callMsg, txRes.BlockNumber); err == nil { + return fmt.Errorf("%w for tx hash %v", vm.ErrOutOfGas, txHash) + } + return fmt.Errorf("SendTxAsCall got: %w for tx hash %v", err, txHash) +}