Skip to content

Commit

Permalink
add L1 message store to rawdb
Browse files Browse the repository at this point in the history
  • Loading branch information
Thegaram authored and NazariiDenha committed Apr 17, 2023
1 parent 218bde2 commit 2c70f51
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 0 deletions.
22 changes: 22 additions & 0 deletions core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ var (

preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)

// Scroll L1 message store
syncedL1BlockNumberKey = []byte("LastSyncedL1BlockNumber")
L1MessagePrefix = []byte("l1") // L1MessageTxPrefix + enqueueIndex (uint64 big endian) -> L1MessageTx
L1MessagesInBlockPrefix = []byte("l1b")
)

const (
Expand Down Expand Up @@ -230,3 +235,20 @@ func IsCodeKey(key []byte) (bool, []byte) {
func configKey(hash common.Hash) []byte {
return append(configPrefix, hash.Bytes()...)
}

// encodeBlockNumber encodes an L1 enqueue index as big endian uint64
func encodeEnqueueIndex(index uint64) []byte {
enc := make([]byte, 8)
binary.BigEndian.PutUint64(enc, index)
return enc
}

// L1MessageKey = L1MessagePrefix + enqueueIndex (uint64 big endian)
func L1MessageKey(enqueueIndex uint64) []byte {
return append(L1MessagePrefix, encodeEnqueueIndex(enqueueIndex)...)
}

// L1MessagesInBlockKey = L1MessagesInBlockPrefix + hash
func L1MessagesInBlockKey(hash common.Hash) []byte {
return append(L1MessagesInBlockPrefix, hash.Bytes()...)
}
170 changes: 170 additions & 0 deletions core/rawdb/scroll.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package rawdb

import (
"bytes"
"encoding/binary"
"math/big"

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rlp"
)

//
func WriteSyncedL1BlockNumber(db ethdb.KeyValueWriter, blockNumber *big.Int) {
value := []byte{0}
if blockNumber != nil {
value = blockNumber.Bytes()
}
if err := db.Put(syncedL1BlockNumberKey, value); err != nil {
log.Crit("Failed to synced L1 block number", "err", err)
}
}

//
func ReadSyncedL1BlockNumber(db ethdb.Reader) *big.Int {
data, _ := db.Get(syncedL1BlockNumberKey)
if len(data) == 0 {
return nil
}
return new(big.Int).SetBytes(data)
}

//
func WriteL1Message(db ethdb.KeyValueWriter, msg *types.L1MessageTx) {
bytes, err := rlp.EncodeToBytes(msg)
if err != nil {
log.Crit("Failed to RLP encode L1 message", "err", err)
}
enqueueIndex := msg.Nonce
if err := db.Put(L1MessageKey(enqueueIndex), bytes); err != nil {
log.Crit("Failed to store L1 message", "err", err)
}
}

//
// TODO: consider writing messages in batches
func WriteL1Messages(db ethdb.KeyValueWriter, msgs []types.L1MessageTx) {
for _, msg := range msgs {
WriteL1Message(db, &msg)
}
}

//
func ReadL1MessageRLP(db ethdb.Reader, enqueueIndex uint64) rlp.RawValue {
data, err := db.Get(L1MessageKey(enqueueIndex))
if err != nil {
log.Crit("Failed to load L1 message", "enqueueIndex", enqueueIndex, "err", err)
}
return data
}

//
func ReadL1Message(db ethdb.Reader, enqueueIndex uint64) *types.L1MessageTx {
data := ReadL1MessageRLP(db, enqueueIndex)
if len(data) == 0 {
return nil
}
msg := new(types.L1MessageTx)
if err := rlp.Decode(bytes.NewReader(data), msg); err != nil {
log.Crit("Invalid L1 message RLP", "enqueueIndex", enqueueIndex, "err", err)
}
return msg
}

type L1MessageIterator struct {
inner ethdb.Iterator
keyLength int
}

func IterateL1MessagesFrom(db ethdb.Iteratee, from uint64) L1MessageIterator {
start := encodeEnqueueIndex(from)
it := db.NewIterator(L1MessagePrefix, start)
keyLength := len(L1MessagePrefix) + 8

return L1MessageIterator{
inner: it,
keyLength: keyLength,
}
}

func (it *L1MessageIterator) Next() bool {
for it.inner.Next() {
key := it.inner.Key()
if len(key) == it.keyLength {
return true
}
}
return false
}

func (it *L1MessageIterator) EnqueueIndex() uint64 {
key := it.inner.Key()
enqueueIndex := binary.BigEndian.Uint64(key[len(L1MessagePrefix) : len(L1MessagePrefix)+8])
return enqueueIndex
}

func (it *L1MessageIterator) L1Message() types.L1MessageTx {
data := it.inner.Value()
msg := types.L1MessageTx{}
if err := rlp.DecodeBytes(data, &msg); err != nil {
log.Crit("Invalid L1 message RLP", "err", err)
}
return msg
}

func (it *L1MessageIterator) Release() {
it.inner.Release()
}

//
func ReadLMessagesInRange(db ethdb.Iteratee, first, last uint64) []types.L1MessageTx {
msgs := make([]types.L1MessageTx, 0, last-first+1)
it := IterateL1MessagesFrom(db, first)
defer it.Release()

for it.Next() {
if it.EnqueueIndex() > last {
break
}
msgs = append(msgs, it.L1Message())
}

return msgs
}

type L1MessagesInBlock struct {
FirstEnqueueIndex uint64
LastEnqueueIndex uint64
}

//
func WriteL1MessagesInBlock(db ethdb.KeyValueWriter, hash common.Hash, entry L1MessagesInBlock) {
bytes, err := rlp.EncodeToBytes(entry)
if err != nil {
log.Crit("Failed to RLP encode L1 messages in block", "err", err)
}
if err := db.Put(L1MessagesInBlockKey(hash), bytes); err != nil {
log.Crit("Failed to store L1 messages in block", "hash", hash, "err", err)
}
}

//
func ReadL1MessagesInBlock(db ethdb.Reader, hash common.Hash) *L1MessagesInBlock {
data, _ := db.Get(L1MessagesInBlockKey(hash))
if len(data) == 0 {
return nil
}
var entry L1MessagesInBlock
if err := rlp.DecodeBytes(data, &entry); err != nil {
log.Error("Invalid L1 messages in block RLP", "hash", hash, "blob", data, "err", err)
return nil
}
return &entry
}

// write messages
// new block received
// set block index: block number => { first_enqueue_index, last_enqueue_index }
130 changes: 130 additions & 0 deletions core/rawdb/scroll_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package rawdb

import (
"math/big"
"testing"

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
)

func TestReadWriteSyncedL1BlockNumber(t *testing.T) {
blockNumbers := []*big.Int{
big.NewInt(0).SetUint64(1),
big.NewInt(0).SetUint64(1 << 2),
big.NewInt(0).SetUint64(1 << 8),
big.NewInt(0).SetUint64(1 << 16),
big.NewInt(0).SetUint64(1 << 32),
}

db := NewMemoryDatabase()
for _, num := range blockNumbers {
WriteSyncedL1BlockNumber(db, num)
got := ReadSyncedL1BlockNumber(db)

if num.Cmp(got) != 0 {
t.Fatal("Block number mismatch")
}
}
}

func newL1MessageTx(enqueueIndex uint64) types.L1MessageTx {
return types.L1MessageTx{
Nonce: enqueueIndex,
Gas: 0,
To: nil,
Value: big.NewInt(0),
Data: nil,
Sender: &common.Address{},
}
}

func TestReadWriteL1Message(t *testing.T) {
enqueueIndex := uint64(123)
msg := newL1MessageTx(enqueueIndex)
db := NewMemoryDatabase()
WriteL1Message(db, &msg)

got := ReadL1Message(db, enqueueIndex)
if got == nil || got.Nonce != msg.Nonce {
t.Fatal("L1 message mismatch", "got", got)
}
}

func TestIterateL1Message(t *testing.T) {
msgs := []types.L1MessageTx{
newL1MessageTx(100),
newL1MessageTx(101),
newL1MessageTx(103),
newL1MessageTx(200),
newL1MessageTx(1000),
}

db := NewMemoryDatabase()
WriteL1Messages(db, msgs)

it := IterateL1MessagesFrom(db, 103)
defer it.Release()

it.Next()
got := it.L1Message()
if got.Nonce != 103 {
t.Fatal("Invalid result", "nonce", got.Nonce)
}

it.Next()
got = it.L1Message()
if got.Nonce != 200 {
t.Fatal("Invalid result", "nonce", got.Nonce)
}

it.Next()
got = it.L1Message()
if got.Nonce != 1000 {
t.Fatal("Invalid result", "nonce", got.Nonce)
}

finished := it.Next()
if finished {
t.Fatal("Invalid result", "finished", finished)
}
}

func TestReadL1MessageTxRange(t *testing.T) {
msgs := []types.L1MessageTx{
newL1MessageTx(100),
newL1MessageTx(101),
newL1MessageTx(103),
newL1MessageTx(200),
newL1MessageTx(1000),
}

db := NewMemoryDatabase()
WriteL1Messages(db, msgs)

got := ReadLMessagesInRange(db, 100, 199)

if len(got) != 3 {
t.Fatal("Invalid length", "length", len(got))
}

if got[0].Nonce != 100 || got[1].Nonce != 101 || got[2].Nonce != 103 {
t.Fatal("Invalid result", "got", got)
}
}

func TestReadWriteL1MessagesInBlock(t *testing.T) {
hash := common.Hash{1}
db := NewMemoryDatabase()

WriteL1MessagesInBlock(db, hash, L1MessagesInBlock{
FirstEnqueueIndex: 1,
LastEnqueueIndex: 9,
})

got := ReadL1MessagesInBlock(db, hash)

if got == nil || got.FirstEnqueueIndex != 1 || got.LastEnqueueIndex != 9 {
t.Fatal("Incorrect result", "got", got)
}
}

0 comments on commit 2c70f51

Please sign in to comment.