Skip to content

Commit

Permalink
Merge pull request #2807 from OffchainLabs/arb-tx-filter
Browse files Browse the repository at this point in the history
Filter transaction
  • Loading branch information
ganeshvanahalli authored Dec 28, 2024
2 parents 2409196 + c0620c6 commit 70dcc3d
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 20 deletions.
50 changes: 32 additions & 18 deletions arbos/block_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,12 @@ func createNewHeader(prevHeader *types.Header, l1info *L1Info, state *arbosState
type ConditionalOptionsForTx []*arbitrum_types.ConditionalOptions

type SequencingHooks struct {
TxErrors []error
DiscardInvalidTxsEarly bool
PreTxFilter func(*params.ChainConfig, *types.Header, *state.StateDB, *arbosState.ArbosState, *types.Transaction, *arbitrum_types.ConditionalOptions, common.Address, *L1Info) error
PostTxFilter func(*types.Header, *arbosState.ArbosState, *types.Transaction, common.Address, uint64, *core.ExecutionResult) error
ConditionalOptionsForTx []*arbitrum_types.ConditionalOptions
TxErrors []error // This can be unset
DiscardInvalidTxsEarly bool // This can be unset
PreTxFilter func(*params.ChainConfig, *types.Header, *state.StateDB, *arbosState.ArbosState, *types.Transaction, *arbitrum_types.ConditionalOptions, common.Address, *L1Info) error // This has to be set
PostTxFilter func(*types.Header, *state.StateDB, *arbosState.ArbosState, *types.Transaction, common.Address, uint64, *core.ExecutionResult) error // This has to be set
BlockFilter func(*types.Header, *state.StateDB, types.Transactions, types.Receipts) error // This can be unset
ConditionalOptionsForTx []*arbitrum_types.ConditionalOptions // This can be unset
}

func NoopSequencingHooks() *SequencingHooks {
Expand All @@ -129,10 +130,11 @@ func NoopSequencingHooks() *SequencingHooks {
func(*params.ChainConfig, *types.Header, *state.StateDB, *arbosState.ArbosState, *types.Transaction, *arbitrum_types.ConditionalOptions, common.Address, *L1Info) error {
return nil
},
func(*types.Header, *arbosState.ArbosState, *types.Transaction, common.Address, uint64, *core.ExecutionResult) error {
func(*types.Header, *state.StateDB, *arbosState.ArbosState, *types.Transaction, common.Address, uint64, *core.ExecutionResult) error {
return nil
},
nil,
nil,
}
}

Expand Down Expand Up @@ -172,7 +174,7 @@ func ProduceBlockAdvanced(
runMode core.MessageRunMode,
) (*types.Block, types.Receipts, error) {

state, err := arbosState.OpenSystemArbosState(statedb, nil, true)
arbState, err := arbosState.OpenSystemArbosState(statedb, nil, true)
if err != nil {
return nil, nil, err
}
Expand All @@ -189,11 +191,11 @@ func ProduceBlockAdvanced(
l1Timestamp: l1Header.Timestamp,
}

header := createNewHeader(lastBlockHeader, l1Info, state, chainConfig)
header := createNewHeader(lastBlockHeader, l1Info, arbState, chainConfig)
signer := types.MakeSigner(chainConfig, header.Number, header.Time)
// Note: blockGasLeft will diverge from the actual gas left during execution in the event of invalid txs,
// but it's only used as block-local representation limiting the amount of work done in a block.
blockGasLeft, _ := state.L2PricingState().PerBlockGasLimit()
blockGasLeft, _ := arbState.L2PricingState().PerBlockGasLimit()
l1BlockNum := l1Info.l1BlockNumber

// Prepend a tx before all others to touch up the state (update the L1 block num, pricing pools, etc)
Expand Down Expand Up @@ -226,7 +228,7 @@ func ProduceBlockAdvanced(
if !ok {
return nil, nil, errors.New("retryable tx is somehow not a retryable")
}
retryable, _ := state.RetryableState().OpenRetryable(retry.TicketId, time)
retryable, _ := arbState.RetryableState().OpenRetryable(retry.TicketId, time)
if retryable == nil {
// retryable was already deleted
continue
Expand Down Expand Up @@ -263,22 +265,22 @@ func ProduceBlockAdvanced(
return nil, nil, err
}

if err = hooks.PreTxFilter(chainConfig, header, statedb, state, tx, options, sender, l1Info); err != nil {
if err = hooks.PreTxFilter(chainConfig, header, statedb, arbState, tx, options, sender, l1Info); err != nil {
return nil, nil, err
}

// Additional pre-transaction validity check
if err = extraPreTxFilter(chainConfig, header, statedb, state, tx, options, sender, l1Info); err != nil {
if err = extraPreTxFilter(chainConfig, header, statedb, arbState, tx, options, sender, l1Info); err != nil {
return nil, nil, err
}

if basefee.Sign() > 0 {
dataGas = math.MaxUint64
brotliCompressionLevel, err := state.BrotliCompressionLevel()
brotliCompressionLevel, err := arbState.BrotliCompressionLevel()
if err != nil {
return nil, nil, fmt.Errorf("failed to get brotli compression level: %w", err)
}
posterCost, _ := state.L1PricingState().GetPosterInfo(tx, poster, brotliCompressionLevel)
posterCost, _ := arbState.L1PricingState().GetPosterInfo(tx, poster, brotliCompressionLevel)
posterCostInL2Gas := arbmath.BigDiv(posterCost, basefee)

if posterCostInL2Gas.IsUint64() {
Expand Down Expand Up @@ -322,18 +324,20 @@ func ProduceBlockAdvanced(
vm.Config{},
runMode,
func(result *core.ExecutionResult) error {
return hooks.PostTxFilter(header, state, tx, sender, dataGas, result)
return hooks.PostTxFilter(header, statedb, arbState, tx, sender, dataGas, result)
},
)
if err != nil {
// Ignore this transaction if it's invalid under the state transition function
statedb.RevertToSnapshot(snap)
statedb.ClearTxFilter()
return nil, nil, err
}

// Additional post-transaction validity check
if err = extraPostTxFilter(chainConfig, header, statedb, state, tx, options, sender, l1Info, result); err != nil {
if err = extraPostTxFilter(chainConfig, header, statedb, arbState, tx, options, sender, l1Info, result); err != nil {
statedb.RevertToSnapshot(snap)
statedb.ClearTxFilter()
return nil, nil, err
}

Expand Down Expand Up @@ -363,13 +367,13 @@ func ProduceBlockAdvanced(

if tx.Type() == types.ArbitrumInternalTxType {
// ArbOS might have upgraded to a new version, so we need to refresh our state
state, err = arbosState.OpenSystemArbosState(statedb, nil, true)
arbState, err = arbosState.OpenSystemArbosState(statedb, nil, true)
if err != nil {
return nil, nil, err
}
// Update the ArbOS version in the header (if it changed)
extraInfo := types.DeserializeHeaderExtraInformation(header)
extraInfo.ArbOSFormatVersion = state.ArbOSVersion()
extraInfo.ArbOSFormatVersion = arbState.ArbOSVersion()
extraInfo.UpdateHeaderWithInfo(header)
}

Expand Down Expand Up @@ -455,6 +459,16 @@ func ProduceBlockAdvanced(
}
}

if statedb.IsTxFiltered() {
return nil, nil, state.ErrArbTxFilter
}

if sequencingHooks.BlockFilter != nil {
if err = sequencingHooks.BlockFilter(header, statedb, complete, receipts); err != nil {
return nil, nil, err
}
}

binary.BigEndian.PutUint64(header.Nonce[:], delayedMessagesRead)

FinalizeBlock(header, complete, statedb, chainConfig)
Expand Down
5 changes: 4 additions & 1 deletion execution/gethexec/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,10 @@ func (s *Sequencer) preTxFilter(_ *params.ChainConfig, header *types.Header, sta
return nil
}

func (s *Sequencer) postTxFilter(header *types.Header, _ *arbosState.ArbosState, tx *types.Transaction, sender common.Address, dataGas uint64, result *core.ExecutionResult) error {
func (s *Sequencer) postTxFilter(header *types.Header, statedb *state.StateDB, _ *arbosState.ArbosState, tx *types.Transaction, sender common.Address, dataGas uint64, result *core.ExecutionResult) error {
if statedb.IsTxFiltered() {
return state.ErrArbTxFilter
}
if result.Err != nil && result.UsedGas > dataGas && result.UsedGas-dataGas <= s.config().MaxRevertGasReject {
return arbitrum.NewRevertReason(result)
}
Expand Down
2 changes: 1 addition & 1 deletion go-ethereum
146 changes: 146 additions & 0 deletions system_tests/seq_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package arbtest

import (
"context"
"math/big"
"testing"
"time"

"github.com/ethereum/go-ethereum/arbitrum_types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"

"github.com/offchainlabs/nitro/arbos"
"github.com/offchainlabs/nitro/arbos/arbosState"
"github.com/offchainlabs/nitro/arbos/arbostypes"
"github.com/offchainlabs/nitro/arbos/l1pricing"
"github.com/offchainlabs/nitro/util/arbmath"
)

func TestSequencerTxFilter(t *testing.T) {
t.Parallel()

builder, header, txes, hooks, cleanup := setupSequencerFilterTest(t, false)
defer cleanup()

block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, txes, hooks)
Require(t, err) // There shouldn't be any error in block generation
if block == nil {
t.Fatal("block should be generated as second tx should pass")
}
if len(block.Transactions()) != 2 {
t.Fatalf("expecting two txs found: %d", len(block.Transactions()))
}
if block.Transactions()[1].Hash() != txes[1].Hash() {
t.Fatal("tx hash mismatch, expecting second tx to be present in the block")
}
if len(hooks.TxErrors) != 2 {
t.Fatalf("expected 2 txErrors in hooks, found: %d", len(hooks.TxErrors))
}
if hooks.TxErrors[0].Error() != state.ErrArbTxFilter.Error() {
t.Fatalf("expected ErrArbTxFilter, found: %s", err.Error())
}
if hooks.TxErrors[1] != nil {
t.Fatalf("found a non-nil error for second transaction: %v", hooks.TxErrors[1])
}
}

func TestSequencerBlockFilterReject(t *testing.T) {
t.Parallel()

builder, header, txes, hooks, cleanup := setupSequencerFilterTest(t, true)
defer cleanup()

block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, txes, hooks)
if block != nil {
t.Fatal("block shouldn't be generated when all txes have failed")
}
if err == nil {
t.Fatal("expected ErrArbTxFilter but found nil")
}
if err.Error() != state.ErrArbTxFilter.Error() {
t.Fatalf("expected ErrArbTxFilter, found: %s", err.Error())
}
}

func TestSequencerBlockFilterAccept(t *testing.T) {
t.Parallel()

builder, header, txes, hooks, cleanup := setupSequencerFilterTest(t, true)
defer cleanup()

block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, txes[1:], hooks)
Require(t, err)
if block == nil {
t.Fatal("block should be generated as the tx should pass")
}
if len(block.Transactions()) != 2 {
t.Fatalf("expecting two txs found: %d", len(block.Transactions()))
}
if block.Transactions()[1].Hash() != txes[1].Hash() {
t.Fatal("tx hash mismatch, expecting second tx to be present in the block")
}
}

func setupSequencerFilterTest(t *testing.T, isBlockFilter bool) (*NodeBuilder, *arbostypes.L1IncomingMessageHeader, types.Transactions, *arbos.SequencingHooks, func()) {
ctx, cancel := context.WithCancel(context.Background())

builder := NewNodeBuilder(ctx).DefaultConfig(t, false)
builder.isSequencer = true
builderCleanup := builder.Build(t)

builder.L2Info.GenerateAccount("User")
var latestL2 uint64
var err error
for i := 0; latestL2 < 3; i++ {
_, _ = builder.L2.TransferBalance(t, "Owner", "User", big.NewInt(1e18), builder.L2Info)
latestL2, err = builder.L2.Client.BlockNumber(ctx)
Require(t, err)
}

header := &arbostypes.L1IncomingMessageHeader{
Kind: arbostypes.L1MessageType_L2Message,
Poster: l1pricing.BatchPosterAddress,
BlockNumber: 1,
Timestamp: arbmath.SaturatingUCast[uint64](time.Now().Unix()),
RequestId: nil,
L1BaseFee: nil,
}

var txes types.Transactions
txes = append(txes, builder.L2Info.PrepareTx("Owner", "User", builder.L2Info.TransferGas, big.NewInt(1e12), []byte{1, 2, 3}))
txes = append(txes, builder.L2Info.PrepareTx("User", "Owner", builder.L2Info.TransferGas, big.NewInt(1e12), nil))

hooks := arbos.NoopSequencingHooks()
if isBlockFilter {
hooks.BlockFilter = func(_ *types.Header, _ *state.StateDB, txes types.Transactions, _ types.Receipts) error {
if len(txes[1].Data()) > 0 {
return state.ErrArbTxFilter
}
return nil
}
} else {
hooks.PreTxFilter = func(_ *params.ChainConfig, _ *types.Header, statedb *state.StateDB, _ *arbosState.ArbosState, tx *types.Transaction, _ *arbitrum_types.ConditionalOptions, _ common.Address, _ *arbos.L1Info) error {
if len(tx.Data()) > 0 {
statedb.FilterTx()
}
return nil
}
hooks.PostTxFilter = func(_ *types.Header, statedb *state.StateDB, _ *arbosState.ArbosState, tx *types.Transaction, _ common.Address, _ uint64, _ *core.ExecutionResult) error {
if statedb.IsTxFiltered() {
return state.ErrArbTxFilter
}
return nil
}
}

cleanup := func() {
builderCleanup()
cancel()
}

return builder, header, txes, hooks, cleanup
}

0 comments on commit 70dcc3d

Please sign in to comment.