diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go
index 08f76b2ab7..e9d608bbf1 100644
--- a/core/rawdb/accessors_state.go
+++ b/core/rawdb/accessors_state.go
@@ -98,8 +98,8 @@ func WriteCode(db ctxcdb.KeyValueWriter, hash common.Hash, code []byte) {
}
}
-// WriteTrieNode writes the provided trie node database.
-func WriteTrieNode(db ctxcdb.KeyValueWriter, hash common.Hash, node []byte) {
+// WriteLegacyTrieNode writes the provided trie node database.
+func WriteLegacyTrieNode(db ctxcdb.KeyValueWriter, hash common.Hash, node []byte) {
if err := db.Put(hash.Bytes(), node); err != nil {
log.Crit("Failed to store trie node", "err", err)
}
diff --git a/core/stateless/database.go b/core/stateless/database.go
new file mode 100644
index 0000000000..f431b6a1cd
--- /dev/null
+++ b/core/stateless/database.go
@@ -0,0 +1,60 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package stateless
+
+import (
+ "github.com/CortexFoundation/CortexTheseus/common"
+ "github.com/CortexFoundation/CortexTheseus/core/rawdb"
+ "github.com/CortexFoundation/CortexTheseus/crypto"
+ "github.com/CortexFoundation/CortexTheseus/ctxcdb"
+)
+
+// MakeHashDB imports tries, codes and block hashes from a witness into a new
+// hash-based memory db. We could eventually rewrite this into a pathdb, but
+// simple is better for now.
+func (w *Witness) MakeHashDB() ctxcdb.Database {
+ var (
+ memdb = rawdb.NewMemoryDatabase()
+ hasher = crypto.NewKeccakState()
+ hash = make([]byte, 32)
+ )
+ // Inject all the "block hashes" (i.e. headers) into the ephemeral database
+ for _, header := range w.Headers {
+ rawdb.WriteHeader(memdb, header)
+ }
+ // Inject all the bytecodes into the ephemeral database
+ for code := range w.Codes {
+ blob := []byte(code)
+
+ hasher.Reset()
+ hasher.Write(blob)
+ hasher.Read(hash)
+
+ rawdb.WriteCode(memdb, common.BytesToHash(hash), blob)
+ }
+ // Inject all the MPT trie nodes into the ephemeral database
+ for node := range w.State {
+ blob := []byte(node)
+
+ hasher.Reset()
+ hasher.Write(blob)
+ hasher.Read(hash)
+
+ rawdb.WriteLegacyTrieNode(memdb, common.BytesToHash(hash), blob)
+ }
+ return memdb
+}
diff --git a/core/stateless/encoding.go b/core/stateless/encoding.go
new file mode 100644
index 0000000000..9743aa6b7c
--- /dev/null
+++ b/core/stateless/encoding.go
@@ -0,0 +1,129 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package stateless
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "slices"
+
+ "github.com/CortexFoundation/CortexTheseus/common/hexutil"
+ "github.com/CortexFoundation/CortexTheseus/core/types"
+ "github.com/CortexFoundation/CortexTheseus/rlp"
+)
+
+//go:generate go run github.com/fjl/gencodec -type extWitness -field-override extWitnessMarshalling -out gen_encoding_json.go
+
+// toExtWitness converts our internal witness representation to the consensus one.
+func (w *Witness) toExtWitness() *extWitness {
+ ext := &extWitness{
+ Block: w.Block,
+ Headers: w.Headers,
+ }
+ ext.Codes = make([][]byte, 0, len(w.Codes))
+ for code := range w.Codes {
+ ext.Codes = append(ext.Codes, []byte(code))
+ }
+ slices.SortFunc(ext.Codes, bytes.Compare)
+
+ ext.State = make([][]byte, 0, len(w.State))
+ for node := range w.State {
+ ext.State = append(ext.State, []byte(node))
+ }
+ slices.SortFunc(ext.State, bytes.Compare)
+ return ext
+}
+
+// fromExtWitness converts the consensus witness format into our internal one.
+func (w *Witness) fromExtWitness(ext *extWitness) error {
+ w.Block, w.Headers = ext.Block, ext.Headers
+
+ w.Codes = make(map[string]struct{}, len(ext.Codes))
+ for _, code := range ext.Codes {
+ w.Codes[string(code)] = struct{}{}
+ }
+ w.State = make(map[string]struct{}, len(ext.State))
+ for _, node := range ext.State {
+ w.State[string(node)] = struct{}{}
+ }
+ return w.sanitize()
+}
+
+// MarshalJSON marshals a witness as JSON.
+func (w *Witness) MarshalJSON() ([]byte, error) {
+ return w.toExtWitness().MarshalJSON()
+}
+
+// EncodeRLP serializes a witness as RLP.
+func (w *Witness) EncodeRLP(wr io.Writer) error {
+ return rlp.Encode(wr, w.toExtWitness())
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (w *Witness) UnmarshalJSON(input []byte) error {
+ var ext extWitness
+ if err := ext.UnmarshalJSON(input); err != nil {
+ return err
+ }
+ return w.fromExtWitness(&ext)
+}
+
+// DecodeRLP decodes a witness from RLP.
+func (w *Witness) DecodeRLP(s *rlp.Stream) error {
+ var ext extWitness
+ if err := s.Decode(&ext); err != nil {
+ return err
+ }
+ return w.fromExtWitness(&ext)
+}
+
+// sanitize checks for some mandatory fields in the witness after decoding so
+// the rest of the code can assume invariants and doesn't have to deal with
+// corrupted data.
+func (w *Witness) sanitize() error {
+ // Verify that the "parent" header (i.e. index 0) is available, and is the
+ // true parent of the block-to-be executed, since we use that to link the
+ // current block to the pre-state.
+ if len(w.Headers) == 0 {
+ return errors.New("parent header (for pre-root hash) missing")
+ }
+ for i, header := range w.Headers {
+ if header == nil {
+ return fmt.Errorf("witness header nil at position %d", i)
+ }
+ }
+ if w.Headers[0].Hash() != w.Block.ParentHash() {
+ return fmt.Errorf("parent hash different: witness %v, block parent %v", w.Headers[0].Hash(), w.Block.ParentHash())
+ }
+ return nil
+}
+
+// extWitness is a witness RLP encoding for transferring across clients.
+type extWitness struct {
+ Block *types.Block `json:"block" gencodec:"required"`
+ Headers []*types.Header `json:"headers" gencodec:"required"`
+ Codes [][]byte `json:"codes"`
+ State [][]byte `json:"state"`
+}
+
+// extWitnessMarshalling defines the hex marshalling types for a witness.
+type extWitnessMarshalling struct {
+ Codes []hexutil.Bytes
+ State []hexutil.Bytes
+}
diff --git a/core/stateless/gen_encoding_json.go b/core/stateless/gen_encoding_json.go
new file mode 100644
index 0000000000..1388a42f88
--- /dev/null
+++ b/core/stateless/gen_encoding_json.go
@@ -0,0 +1,74 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package stateless
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/CortexFoundation/CortexTheseus/common/hexutil"
+ "github.com/CortexFoundation/CortexTheseus/core/types"
+)
+
+var _ = (*extWitnessMarshalling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (e extWitness) MarshalJSON() ([]byte, error) {
+ type extWitness struct {
+ Block *types.Block `json:"block" gencodec:"required"`
+ Headers []*types.Header `json:"headers" gencodec:"required"`
+ Codes []hexutil.Bytes `json:"codes"`
+ State []hexutil.Bytes `json:"state"`
+ }
+ var enc extWitness
+ enc.Block = e.Block
+ enc.Headers = e.Headers
+ if e.Codes != nil {
+ enc.Codes = make([]hexutil.Bytes, len(e.Codes))
+ for k, v := range e.Codes {
+ enc.Codes[k] = v
+ }
+ }
+ if e.State != nil {
+ enc.State = make([]hexutil.Bytes, len(e.State))
+ for k, v := range e.State {
+ enc.State[k] = v
+ }
+ }
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (e *extWitness) UnmarshalJSON(input []byte) error {
+ type extWitness struct {
+ Block *types.Block `json:"block" gencodec:"required"`
+ Headers []*types.Header `json:"headers" gencodec:"required"`
+ Codes []hexutil.Bytes `json:"codes"`
+ State []hexutil.Bytes `json:"state"`
+ }
+ var dec extWitness
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.Block == nil {
+ return errors.New("missing required field 'block' for extWitness")
+ }
+ e.Block = dec.Block
+ if dec.Headers == nil {
+ return errors.New("missing required field 'headers' for extWitness")
+ }
+ e.Headers = dec.Headers
+ if dec.Codes != nil {
+ e.Codes = make([][]byte, len(dec.Codes))
+ for k, v := range dec.Codes {
+ e.Codes[k] = v
+ }
+ }
+ if dec.State != nil {
+ e.State = make([][]byte, len(dec.State))
+ for k, v := range dec.State {
+ e.State[k] = v
+ }
+ }
+ return nil
+}
diff --git a/core/stateless/witness.go b/core/stateless/witness.go
new file mode 100644
index 0000000000..9fba19e118
--- /dev/null
+++ b/core/stateless/witness.go
@@ -0,0 +1,159 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package stateless
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "maps"
+ "slices"
+ "sync"
+
+ "github.com/CortexFoundation/CortexTheseus/common"
+ "github.com/CortexFoundation/CortexTheseus/core/types"
+ "github.com/CortexFoundation/CortexTheseus/rlp"
+)
+
+// HeaderReader is an interface to pull in headers in place of block hashes for
+// the witness.
+type HeaderReader interface {
+ // GetHeader retrieves a block header from the database by hash and number,
+ GetHeader(hash common.Hash, number uint64) *types.Header
+}
+
+// Witness encompasses a block, state and any other chain data required to apply
+// a set of transactions and derive a post state/receipt root.
+type Witness struct {
+ Block *types.Block // Current block with rootHash and receiptHash zeroed out
+ Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set.
+ Codes map[string]struct{} // Set of bytecodes ran or accessed
+ State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
+
+ chain HeaderReader // Chain reader to convert block hash ops to header proofs
+ lock sync.Mutex // Lock to allow concurrent state insertions
+}
+
+// NewWitness creates an empty witness ready for population.
+func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) {
+ // Zero out the result fields to avoid accidentally sending them to the verifier
+ header := block.Header()
+ header.Root = common.Hash{}
+ header.ReceiptHash = common.Hash{}
+
+ // Retrieve the parent header, which will *always* be included to act as a
+ // trustless pre-root hash container
+ parent := chain.GetHeader(block.ParentHash(), block.NumberU64()-1)
+ if parent == nil {
+ return nil, errors.New("failed to retrieve parent header")
+ }
+ // Create the wtness with a reconstructed gutted out block
+ return &Witness{
+ Block: types.NewBlockWithHeader(header).WithBody(*block.Body()),
+ Codes: make(map[string]struct{}),
+ State: make(map[string]struct{}),
+ Headers: []*types.Header{parent},
+ chain: chain,
+ }, nil
+}
+
+// AddBlockHash adds a "blockhash" to the witness with the designated offset from
+// chain head. Under the hood, this method actually pulls in enough headers from
+// the chain to cover the block being added.
+func (w *Witness) AddBlockHash(number uint64) {
+ // Keep pulling in headers until this hash is populated
+ for int(w.Block.NumberU64()-number) > len(w.Headers) {
+ tail := w.Block.Header()
+ if len(w.Headers) > 0 {
+ tail = w.Headers[len(w.Headers)-1]
+ }
+ w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1))
+ }
+}
+
+// AddCode adds a bytecode blob to the witness.
+func (w *Witness) AddCode(code []byte) {
+ if len(code) == 0 {
+ return
+ }
+ w.Codes[string(code)] = struct{}{}
+}
+
+// AddState inserts a batch of MPT trie nodes into the witness.
+func (w *Witness) AddState(nodes map[string]struct{}) {
+ if len(nodes) == 0 {
+ return
+ }
+ w.lock.Lock()
+ defer w.lock.Unlock()
+
+ for node := range nodes {
+ w.State[node] = struct{}{}
+ }
+}
+
+// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
+// is never mutated by Witness
+func (w *Witness) Copy() *Witness {
+ return &Witness{
+ Block: w.Block,
+ Headers: slices.Clone(w.Headers),
+ Codes: maps.Clone(w.Codes),
+ State: maps.Clone(w.State),
+ }
+}
+
+// String prints a human-readable summary containing the total size of the
+// witness and the sizes of the underlying components
+func (w *Witness) String() string {
+ blob, _ := rlp.EncodeToBytes(w)
+ bytesTotal := len(blob)
+
+ blob, _ = rlp.EncodeToBytes(w.Block)
+ bytesBlock := len(blob)
+
+ bytesHeaders := 0
+ for _, header := range w.Headers {
+ blob, _ = rlp.EncodeToBytes(header)
+ bytesHeaders += len(blob)
+ }
+ bytesCodes := 0
+ for code := range w.Codes {
+ bytesCodes += len(code)
+ }
+ bytesState := 0
+ for node := range w.State {
+ bytesState += len(node)
+ }
+ buf := new(bytes.Buffer)
+
+ fmt.Fprintf(buf, "Witness #%d: %v\n", w.Block.Number(), common.StorageSize(bytesTotal))
+ fmt.Fprintf(buf, " block (%4d txs): %10v\n", len(w.Block.Transactions()), common.StorageSize(bytesBlock))
+ fmt.Fprintf(buf, "%4d headers: %10v\n", len(w.Headers), common.StorageSize(bytesHeaders))
+ fmt.Fprintf(buf, "%4d trie nodes: %10v\n", len(w.State), common.StorageSize(bytesState))
+ fmt.Fprintf(buf, "%4d codes: %10v\n", len(w.Codes), common.StorageSize(bytesCodes))
+
+ return buf.String()
+}
+
+// Root returns the pre-state root from the first header.
+//
+// Note, this method will panic in case of a bad witness (but RLP decoding will
+// sanitize it and fail before that).
+func (w *Witness) Root() common.Hash {
+ return w.Headers[0].Root
+}
diff --git a/trie/database.go b/trie/database.go
index 10b333d4ee..01e13214ba 100644
--- a/trie/database.go
+++ b/trie/database.go
@@ -574,7 +574,7 @@ func (db *Database) Cap(limit common.StorageSize) error {
for size > limit && oldest != (common.Hash{}) {
// Fetch the oldest referenced node and push into the batch
node := db.dirties[oldest]
- rawdb.WriteTrieNode(batch, oldest, node.rlp())
+ rawdb.WriteLegacyTrieNode(batch, oldest, node.rlp())
// If we exceeded the ideal batch size, commit and reset
if batch.ValueSize() >= ctxcdb.IdealBatchSize {
@@ -704,7 +704,7 @@ func (db *Database) commit(hash common.Hash, batch ctxcdb.Batch, uncacher *clean
return err
}
// If we've reached an optimal batch size, commit and start over
- rawdb.WriteTrieNode(batch, hash, node.rlp())
+ rawdb.WriteLegacyTrieNode(batch, hash, node.rlp())
if batch.ValueSize() >= ctxcdb.IdealBatchSize {
if err := batch.Write(); err != nil {
return err
diff --git a/trie/sync.go b/trie/sync.go
index 959ceb7b5c..8f943d7f78 100644
--- a/trie/sync.go
+++ b/trie/sync.go
@@ -312,7 +312,7 @@ func (s *Sync) Process(result SyncResult) error {
func (s *Sync) Commit(dbw ctxcdb.Batch) error {
// Dump the membatch into a database dbw
for key, value := range s.membatch.nodes {
- rawdb.WriteTrieNode(dbw, key, value)
+ rawdb.WriteLegacyTrieNode(dbw, key, value)
s.bloom.Add(key[:])
}
for key, value := range s.membatch.codes {