Skip to content
Merged
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
3 changes: 3 additions & 0 deletions arbnode/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ type SequencerConfig struct {
MaxBlockSpeed time.Duration `koanf:"max-block-speed"`
MaxRevertGasReject uint64 `koanf:"max-revert-gas-reject"`
MaxAcceptableTimestampDelta time.Duration `koanf:"max-acceptable-timestamp-delta"`
SenderWhitelist []string `koanf:"sender-whitelist"`
Dangerous DangerousSequencerConfig `koanf:"dangerous"`
}

Expand All @@ -526,6 +527,7 @@ var TestSequencerConfig = SequencerConfig{
MaxBlockSpeed: time.Millisecond * 10,
MaxRevertGasReject: params.TxGas + 10000,
MaxAcceptableTimestampDelta: time.Hour,
SenderWhitelist: nil,
Dangerous: TestDangerousSequencerConfig,
}

Expand All @@ -534,6 +536,7 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) {
f.Duration(prefix+".max-block-speed", DefaultSequencerConfig.MaxBlockSpeed, "minimum delay between blocks (sets a maximum speed of block production)")
f.Uint64(prefix+".max-revert-gas-reject", DefaultSequencerConfig.MaxRevertGasReject, "maximum gas executed in a revert for the sequencer to reject the transaction instead of posting it (anti-DOS)")
f.Duration(prefix+".max-acceptable-timestamp-delta", DefaultSequencerConfig.MaxAcceptableTimestampDelta, "maximum acceptable time difference between the local time and the latest L1 block's timestamp")
f.StringArray(prefix+".sender-whitelist", DefaultSequencerConfig.SenderWhitelist, "whitelist of authorized senders (if empty, everyone is allowed)")
DangerousSequencerConfigAddOptions(prefix+".dangerous", f)
}

Expand Down
44 changes: 34 additions & 10 deletions arbnode/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ func (i *txQueueItem) returnResult(err error) {
type Sequencer struct {
stopwaiter.StopWaiter

txStreamer *TransactionStreamer
txQueue chan txQueueItem
l1Reader *headerreader.HeaderReader
config SequencerConfig
txStreamer *TransactionStreamer
txQueue chan txQueueItem
l1Reader *headerreader.HeaderReader
config SequencerConfig
senderWhitelist map[common.Address]struct{}

L1BlockAndTimeMutex sync.Mutex
l1BlockNumber uint64
Expand All @@ -56,17 +57,40 @@ type Sequencer struct {
}

func NewSequencer(txStreamer *TransactionStreamer, l1Reader *headerreader.HeaderReader, config SequencerConfig) (*Sequencer, error) {
senderWhitelist := make(map[common.Address]struct{})
for _, address := range config.SenderWhitelist {
if len(address) == 0 {
continue
}
if !common.IsHexAddress(address) {
return nil, fmt.Errorf("sequencer sender whitelist entry \"%v\" is not a valid address", address)
}
senderWhitelist[common.HexToAddress(address)] = struct{}{}
}
return &Sequencer{
txStreamer: txStreamer,
txQueue: make(chan txQueueItem, 128),
l1Reader: l1Reader,
config: config,
l1BlockNumber: 0,
l1Timestamp: 0,
txStreamer: txStreamer,
txQueue: make(chan txQueueItem, 128),
l1Reader: l1Reader,
config: config,
senderWhitelist: senderWhitelist,
l1BlockNumber: 0,
l1Timestamp: 0,
}, nil
}

func (s *Sequencer) PublishTransaction(ctx context.Context, tx *types.Transaction) error {
if len(s.senderWhitelist) > 0 {
signer := types.LatestSigner(s.txStreamer.bc.Config())
sender, err := types.Sender(signer, tx)
if err != nil {
return err
}
_, authorized := s.senderWhitelist[sender]
if !authorized {
return errors.New("transaction sender is not on the whitelist")
}
}

resultChan := make(chan error, 1)
queueItem := txQueueItem{
tx,
Expand Down
42 changes: 42 additions & 0 deletions system_tests/seq_whitelist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE

package arbtest

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

"github.com/ethereum/go-ethereum/params"
"github.com/offchainlabs/nitro/arbnode"
)

func TestSequencerWhitelist(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

config := arbnode.ConfigDefaultL2Test()
config.Sequencer.SenderWhitelist = []string{
GetTestAddressForAccountName(t, "Owner").String(),
GetTestAddressForAccountName(t, "User").String(),
}
l2info, _, client := CreateTestL2WithConfig(t, ctx, nil, config, true)

l2info.GenerateAccount("User")
l2info.GenerateAccount("User2")

// Owner is on the whitelist
TransferBalance(t, "Owner", "User", big.NewInt(params.Ether), l2info, client, ctx)
TransferBalance(t, "Owner", "User2", big.NewInt(params.Ether), l2info, client, ctx)

// User is on the whitelist
TransferBalance(t, "User", "User2", big.NewInt(params.Ether/10), l2info, client, ctx)

// User2 is *not* on the whitelist, therefore this should fail
tx := l2info.PrepareTx("User2", "User", l2info.TransferGas, big.NewInt(params.Ether/10), nil)
err := client.SendTransaction(ctx, tx)
if err == nil {
Fail(t, "transaction from user not on whitelist accepted")
}
}
18 changes: 14 additions & 4 deletions system_tests/test_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ func NewL1TestInfo(t *testing.T) *BlockchainTestInfo {
return NewBlockChainTestInfo(t, types.NewLondonSigner(simulatedChainID), big.NewInt(params.GWei*100), params.TxGas)
}

func (b *BlockchainTestInfo) GenerateAccount(name string) {
b.T.Helper()

func GetTestKeyForAccountName(t *testing.T, name string) *ecdsa.PrivateKey {
nameBytes := []byte(name)
seedBytes := make([]byte, 0, 128)
for len(seedBytes) < 64 {
Expand All @@ -75,8 +73,20 @@ func (b *BlockchainTestInfo) GenerateAccount(name string) {
seedReader := bytes.NewReader(seedBytes)
privateKey, err := ecdsa.GenerateKey(crypto.S256(), seedReader)
if err != nil {
b.T.Fatal(err)
t.Fatal(err)
}
return privateKey
}

func GetTestAddressForAccountName(t *testing.T, name string) common.Address {
privateKey := GetTestKeyForAccountName(t, name)
return crypto.PubkeyToAddress(privateKey.PublicKey)
}

func (b *BlockchainTestInfo) GenerateAccount(name string) {
b.T.Helper()

privateKey := GetTestKeyForAccountName(b.T, name)
if b.Accounts[name] != nil {
b.T.Fatal("account already exists")
}
Expand Down