Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter transaction #2807

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 40 additions & 25 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,13 @@ func ProduceBlockAdvanced(
return nil, nil, err
}

if err = hooks.PreTxFilter(chainConfig, header, statedb, state, 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 {
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 @@ -307,6 +300,18 @@ func ProduceBlockAdvanced(
}

snap := statedb.Snapshot()

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

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

statedb.SetTxContext(tx.Hash(), len(receipts)) // the number of successful state transitions

gasPool := gethGas
Expand All @@ -322,7 +327,7 @@ 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 {
Expand All @@ -332,7 +337,7 @@ func ProduceBlockAdvanced(
}

// 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)
return nil, nil, err
}
Expand Down Expand Up @@ -363,13 +368,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 +460,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)
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
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)
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
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)
ganeshvanahalli marked this conversation as resolved.
Show resolved Hide resolved
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
}
Loading