Skip to content
Merged
10 changes: 10 additions & 0 deletions consensus/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,14 @@ var (

// ErrInvalidTxCount is returned if a block contains too many transactions.
ErrInvalidTxCount = errors.New("invalid transaction count")

// ErrInvalidL1MessageOrder is returned if a block contains L1 messages in the wrong
// order. Possible scenarios are: (1) L1 messages do not follow their QueueIndex order,
// (2) the block skipped one or more L1 messages, (3) L1 messages are not included in
// a contiguous block at the front of the block.
ErrInvalidL1MessageOrder = errors.New("invalid L1 message order")

// ErrUnknownL1Message is returned if a block contains an L1 message that does not
// match the corresponding message in the node's local database.
ErrUnknownL1Message = errors.New("unknown L1 message")
)
62 changes: 62 additions & 0 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"

"github.com/scroll-tech/go-ethereum/consensus"
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/params"
Expand Down Expand Up @@ -74,6 +75,67 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
}
return consensus.ErrPrunedAncestor
}
return v.ValidateL1Messages(block)
}

// ValidateL1Messages validates L1 messages contained in a block.
// We check the following conditions:
// - L1 messages are in a contiguous section at the front of the block.
// - The first L1 message's QueueIndex is right after the last L1 message included in the chain.
// - L1 messages follow the QueueIndex order. No L1 message is skipped.
// - The L1 messages included in the block match the node's view of the L1 ledger.
func (v *BlockValidator) ValidateL1Messages(block *types.Block) error {
if v.config.Scroll.L1Config == nil || v.config.Scroll.L1Config.NumL1MessagesPerBlock == 0 {
return nil
}

nextQueueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(v.bc.db, block.ParentHash())
if nextQueueIndex == nil {
// TODO: make sure we correctly re-process block after ErrUnknownAncestor
return consensus.ErrUnknownAncestor
}
queueIndex := *nextQueueIndex

L1SectionOver := false
it := rawdb.IterateL1MessagesFrom(v.bc.db, queueIndex)

for _, tx := range block.Transactions() {
if !tx.IsL1MessageTx() {
L1SectionOver = true
continue
}

// check that L1 messages are before L2 transactions
if L1SectionOver {
return consensus.ErrInvalidL1MessageOrder
}

// check queue index
if tx.AsL1MessageTx().QueueIndex != queueIndex {
return consensus.ErrInvalidL1MessageOrder
}

queueIndex += 1

if exists := it.Next(); !exists {
// TODO: make sure we correctly re-process block after ErrUnknownAncestor
return consensus.ErrUnknownAncestor
}

// check that the L1 message in the block is the same that we collected from L1
msg := it.L1Message()
expectedHash := types.NewTx(&msg).Hash()

if tx.Hash() != expectedHash {
return consensus.ErrUnknownL1Message
}
}

// write to db
// TODO: ValidateL1Messages should not have side effects, consider
// moving this elsewhere.
rawdb.WriteFirstQueueIndexNotInL2Block(v.bc.db, block.Hash(), queueIndex)

return nil
}

Expand Down
20 changes: 10 additions & 10 deletions core/rawdb/accessors_l1_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,20 +190,20 @@ func ReadL1MessagesInRange(db ethdb.Iteratee, firstEnqueueIndex, lastEnqueueInde
return msgs
}

// WriteLastL1MessageInL2Block writes the enqueue index of the last message included in the
// ledger up to and including the provided L2 block. The L2 block is identified by its block
// hash. If the L2 block contains zero L1 messages, this value MUST equal its parent's value.
func WriteLastL1MessageInL2Block(db ethdb.KeyValueWriter, l2BlockHash common.Hash, enqueueIndex uint64) {
if err := db.Put(LastL1MessageInL2BlockKey(l2BlockHash), encodeEnqueueIndex(enqueueIndex)); err != nil {
// WriteFirstQueueIndexNotInL2Block writes the queue index of the first message
// that is NOT included in the ledger up to and including the provided L2 block.
// The L2 block is identified by its block hash. If the L2 block contains zero
// L1 messages, this value MUST equal its parent's value.
func WriteFirstQueueIndexNotInL2Block(db ethdb.KeyValueWriter, l2BlockHash common.Hash, enqueueIndex uint64) {
if err := db.Put(FirstQueueIndexNotInL2BlockKey(l2BlockHash), encodeEnqueueIndex(enqueueIndex)); err != nil {
log.Crit("Failed to store last L1 message in L2 block", "l2BlockHash", l2BlockHash, "err", err)
}
}

// ReadLastL1MessageInL2Block retrieves the enqueue index of the last message
// included in the ledger up to and including the provided L2 block.
// The caller must add special handling for the L2 genesis block.
func ReadLastL1MessageInL2Block(db ethdb.Reader, l2BlockHash common.Hash) *uint64 {
data, err := db.Get(LastL1MessageInL2BlockKey(l2BlockHash))
// ReadFirstQueueIndexNotInL2Block retrieves the queue index of the first message
// that is NOT included in the ledger up to and including the provided L2 block.
func ReadFirstQueueIndexNotInL2Block(db ethdb.Reader, l2BlockHash common.Hash) *uint64 {
data, err := db.Get(FirstQueueIndexNotInL2BlockKey(l2BlockHash))
if err != nil && isNotFoundErr(err) {
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions core/rawdb/accessors_l1_message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ func TestReadWriteLastL1MessageInL2Block(t *testing.T) {
db := NewMemoryDatabase()
for _, num := range inputs {
l2BlockHash := common.Hash{byte(num)}
WriteLastL1MessageInL2Block(db, l2BlockHash, num)
got := ReadLastL1MessageInL2Block(db, l2BlockHash)
WriteFirstQueueIndexNotInL2Block(db, l2BlockHash, num)
got := ReadFirstQueueIndexNotInL2Block(db, l2BlockHash)

if got == nil || *got != num {
t.Fatal("Enqueue index mismatch", "expected", num, "got", got)
Expand Down
2 changes: 1 addition & 1 deletion core/rawdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
cliqueSnaps.Add(size)
case bytes.HasPrefix(key, L1MessagePrefix) && len(key) == len(L1MessagePrefix)+8:
l1Messages.Add(size)
case bytes.HasPrefix(key, LastL1MessageInL2BlockPrefix) && len(key) == len(LastL1MessageInL2BlockPrefix)+common.HashLength:
case bytes.HasPrefix(key, FirstQueueIndexNotInL2BlockPrefix) && len(key) == len(FirstQueueIndexNotInL2BlockPrefix)+common.HashLength:
lastL1Message.Add(size)
case bytes.HasPrefix(key, []byte("cht-")) ||
bytes.HasPrefix(key, []byte("chtIndexV2-")) ||
Expand Down
12 changes: 6 additions & 6 deletions core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ var (
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)

// Scroll L1 message store
syncedL1BlockNumberKey = []byte("LastSyncedL1BlockNumber")
L1MessagePrefix = []byte("l1") // L1MessagePrefix + enqueueIndex (uint64 big endian) -> L1MessageTx
LastL1MessageInL2BlockPrefix = []byte("l1b") // LastL1MessageInL2BlockPrefix + L2 block hash -> enqueue index
syncedL1BlockNumberKey = []byte("LastSyncedL1BlockNumber")
L1MessagePrefix = []byte("l1") // L1MessagePrefix + enqueueIndex (uint64 big endian) -> L1MessageTx
FirstQueueIndexNotInL2BlockPrefix = []byte("q") // FirstQueueIndexNotInL2BlockPrefix + L2 block hash -> enqueue index
)

const (
Expand Down Expand Up @@ -248,7 +248,7 @@ func L1MessageKey(enqueueIndex uint64) []byte {
return append(L1MessagePrefix, encodeEnqueueIndex(enqueueIndex)...)
}

// LastL1MessageInL2BlockKey = LastL1MessageInL2BlockPrefix + L2 block hash
func LastL1MessageInL2BlockKey(l2BlockHash common.Hash) []byte {
return append(LastL1MessageInL2BlockPrefix, l2BlockHash.Bytes()...)
// FirstQueueIndexNotInL2BlockKey = FirstQueueIndexNotInL2BlockPrefix + L2 block hash
func FirstQueueIndexNotInL2BlockKey(l2BlockHash common.Hash) []byte {
return append(FirstQueueIndexNotInL2BlockPrefix, l2BlockHash.Bytes()...)
}
9 changes: 9 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,15 @@ func (tx *Transaction) IsL1MessageTx() bool {
return tx.Type() == L1MessageTxType
}

// AsL1MessageTx casts the tx into an L1 cross-domain tx.
func (tx *Transaction) AsL1MessageTx() *L1MessageTx {
if tx.Type() != L1MessageTxType {
return nil
}

return tx.inner.(*L1MessageTx)
}

// Cost returns gas * gasPrice + value.
func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
Expand Down
1 change: 1 addition & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
return nil, genesisErr
}
rawdb.WriteFirstQueueIndexNotInL2Block(chainDb, genesisHash, 0)
log.Info("Initialised chain configuration", "config", chainConfig)

if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil {
Expand Down
18 changes: 12 additions & 6 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,9 @@ var (
}

// ScrollAlphaChainConfig contains the chain parameters to run a node on the Scroll Alpha test network.
ScrollFeeVaultAddress = common.HexToAddress("0x5300000000000000000000000000000000000005")
ScrollMaxTxPerBlock = 44
ScrollFeeVaultAddress = common.HexToAddress("0x5300000000000000000000000000000000000005")
ScrollL1MessageQueueAddress = common.HexToAddress("0x79DB48002Aa861C8cb189cabc21c6B1468BC83BB")
ScrollMaxTxPerBlock = 44

ScrollAlphaChainConfig = &ChainConfig{
ChainID: big.NewInt(534353),
Expand Down Expand Up @@ -284,6 +285,11 @@ var (
FeeVaultAddress: &ScrollFeeVaultAddress,
EnableEIP2718: false,
EnableEIP1559: false,
L1Config: &L1Config{
L1ChainId: 5,
L1MessageQueueAddress: &ScrollL1MessageQueueAddress,
NumL1MessagesPerBlock: 0,
},
},
}

Expand All @@ -299,7 +305,7 @@ var (
EnableEIP2718: true,
EnableEIP1559: true,
MaxTxPerBlock: nil,
L1Config: &L1Config{5, nil, 20},
L1Config: &L1Config{5, nil, 0},
}}

// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
Expand All @@ -314,7 +320,7 @@ var (
EnableEIP2718: true,
EnableEIP1559: true,
MaxTxPerBlock: nil,
L1Config: &L1Config{5, nil, 20},
L1Config: &L1Config{5, nil, 0},
}}

TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil,
Expand All @@ -324,7 +330,7 @@ var (
EnableEIP2718: true,
EnableEIP1559: true,
MaxTxPerBlock: nil,
L1Config: &L1Config{5, nil, 20},
L1Config: &L1Config{5, nil, 0},
}}
TestRules = TestChainConfig.Rules(new(big.Int))

Expand All @@ -335,7 +341,7 @@ var (
EnableEIP2718: true,
EnableEIP1559: true,
MaxTxPerBlock: nil,
L1Config: &L1Config{5, nil, 20},
L1Config: &L1Config{5, nil, 0},
}}
)

Expand Down