From 2c70f51c94657baa345994bc881f5f0fff366c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Sat, 1 Apr 2023 18:02:52 +0200 Subject: [PATCH] add L1 message store to rawdb --- core/rawdb/schema.go | 22 +++++ core/rawdb/scroll.go | 170 ++++++++++++++++++++++++++++++++++++++ core/rawdb/scroll_test.go | 130 +++++++++++++++++++++++++++++ 3 files changed, 322 insertions(+) create mode 100644 core/rawdb/scroll.go create mode 100644 core/rawdb/scroll_test.go diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 73dc69ea3122..1243872269d0 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -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 ( @@ -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()...) +} diff --git a/core/rawdb/scroll.go b/core/rawdb/scroll.go new file mode 100644 index 000000000000..00eb2f357749 --- /dev/null +++ b/core/rawdb/scroll.go @@ -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 } diff --git a/core/rawdb/scroll_test.go b/core/rawdb/scroll_test.go new file mode 100644 index 000000000000..7f368f585c2e --- /dev/null +++ b/core/rawdb/scroll_test.go @@ -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) + } +}