diff --git a/cmd/stateless/main.go b/cmd/stateless/main.go new file mode 100644 index 0000000000..1ef49041d4 --- /dev/null +++ b/cmd/stateless/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "fmt" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/params" + "github.com/urfave/cli/v2" + "go.uber.org/automaxprocs/maxprocs" + "os" +) + +var ( + BlockWitnessFlag = &cli.StringFlag{ + Name: "block-witness", + Usage: "foo bar", + } +) + +var app = flags.NewApp("stateless block executor") + +func init() { + // Initialize the CLI app and start Geth + app.Action = stateless + app.Copyright = "Copyright 2013-2023 The go-ethereum Authors" + + app.Flags = []cli.Flag{ + BlockWitnessFlag, + } + + app.Before = func(ctx *cli.Context) error { + maxprocs.Set() // Automatically set GOMAXPROCS to match Linux container CPU quota. + if err := debug.Setup(ctx); err != nil { + return err + } + return nil + } + app.After = func(ctx *cli.Context) error { + debug.Exit() + prompt.Stdin.Close() // Resets terminal mode. + return nil + } +} + +func stateless(ctx *cli.Context) error { + var vmConfig vm.Config + + blockWitnessPath := ctx.String(BlockWitnessFlag.Name) + if blockWitnessPath == "" { + panic("block witness required") + } + + f, err := os.Open(blockWitnessPath) + if err != nil { + panic(err) + } + + var b []byte + f.Read(b) + block, witness, err := state.DecodeWitnessRLP(b) + if err != nil { + panic(err) + } + + memoryDb := witness.PopulateMemoryDB() + db, err := state.New(witness.Root(), state.NewDatabase(memoryDb), nil) + if err != nil { + panic(err) + } + chainConfig := params.MainnetChainConfig + engine, err := ethconfig.CreateConsensusEngine(chainConfig, memoryDb) + if err != nil { + panic(err) + } + validator := core.NewBlockValidator(chainConfig, nil, engine) + processor := core.NewStateProcessor(chainConfig, nil, engine) + + receipts, logs, usedGas, err := processor.ProcessStateless(witness, block, db, vmConfig) + if err != nil { + panic(err) + } + + _ = logs + + if err := validator.ValidateState(block, db, receipts, usedGas); err != nil { + panic(err) + } + + return nil +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/core/blockchain.go b/core/blockchain.go index 01c65fdedf..bbb54be0a4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1852,6 +1852,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } else { status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) } + statedb.GetWitness().Dump() state.DumpBlockWithWitnessToFile(statedb.GetWitness(), block) followupInterrupt.Store(true) diff --git a/core/evm.go b/core/evm.go index c4801dc797..79650cd525 100644 --- a/core/evm.go +++ b/core/evm.go @@ -17,6 +17,7 @@ package core import ( + "github.com/ethereum/go-ethereum/core/state" "math/big" "github.com/ethereum/go-ethereum/common" @@ -36,6 +37,42 @@ type ChainContext interface { GetHeader(common.Hash, uint64) *types.Header } +func NewStatelessEVMBlockContext(witness *state.Witness, header *types.Header, engine consensus.Engine) vm.BlockContext { + getBlockHash := func(num uint64) common.Hash { + return witness.GetBlockHash(num) + } + var ( + baseFee *big.Int + blobBaseFee *big.Int + random *common.Hash + ) + + beneficiary, _ := engine.Author(header) // Ignore error, we're past header validation + + if header.BaseFee != nil { + baseFee = new(big.Int).Set(header.BaseFee) + } + if header.ExcessBlobGas != nil { + blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) + } + if header.Difficulty.Cmp(common.Big0) == 0 { + random = &header.MixDigest + } + return vm.BlockContext{ + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: getBlockHash, + Coinbase: beneficiary, + BlockNumber: new(big.Int).Set(header.Number), + Time: header.Time, + Difficulty: new(big.Int).Set(header.Difficulty), + BaseFee: baseFee, + BlobBaseFee: blobBaseFee, + GasLimit: header.GasLimit, + Random: random, + } +} + // NewEVMBlockContext creates a new context for use in the EVM. func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { var ( diff --git a/core/state/state_witness.go b/core/state/state_witness.go index 30303c3558..098cf8395c 100644 --- a/core/state/state_witness.go +++ b/core/state/state_witness.go @@ -4,7 +4,9 @@ import ( "bytes" "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rlp" "os" ) @@ -16,10 +18,58 @@ type Witness struct { lists map[common.Hash]map[string][]byte } +func (w *Witness) GetBlockHash(num uint64) common.Hash { + return w.blockHashes[num] +} + +func (w *Witness) Root() common.Hash { + return w.root +} + +type encodedWitness struct { + block types.Block + root common.Hash + owners []common.Hash + allPaths [][]string + allNodes [][][]byte + blockNums []uint64 + blockHashes []common.Hash + codes []Code + codeHashes []common.Hash +} + +func (e *encodedWitness) ToWitness() *Witness { + var res Witness + res.root = e.root + for i := 0; i < len(e.codes); i++ { + res.codes[e.codeHashes[i]] = e.codes[i] + } + for i, owner := range e.owners { + pathMap := make(map[string][]byte) + for j := 0; j < len(e.allPaths[i]); j++ { + pathMap[e.allPaths[i][j]] = e.allNodes[i][j] + } + res.lists[owner] = pathMap + } + for i, blockNum := range e.blockNums { + res.blockHashes[blockNum] = e.blockHashes[i] + } + return &res +} + +func DecodeWitnessRLP(b []byte) (*types.Block, *Witness, error) { + var res encodedWitness + if err := rlp.DecodeBytes(b, &res); err != nil { + return nil, nil, err + } + return &res.block, res.ToWitness(), nil +} + func (w *Witness) EncodeRLP(b *types.Block) []byte { buf := new(bytes.Buffer) eb := rlp.NewEncoderBuffer(buf) + var root common.Hash var owners []common.Hash var allPaths [][]string var allNodes [][][]byte @@ -52,6 +102,9 @@ func (w *Witness) EncodeRLP(b *types.Block) []byte { } l := eb.List() b.EncodeRLP(eb) + if err := rlp.Encode(eb, root); err != nil { + panic(err) + } if err := rlp.Encode(eb, owners); err != nil { panic(err) } @@ -107,6 +160,30 @@ func (w Witness) Copy() Witness { panic("not implemented") } +func (w *Witness) Dump() { + for owner, al := range w.lists { + fmt.Printf("owner %x:\n", owner) + for path, node := range al { + fmt.Printf("%x: %x\n", []byte(path), node) + } + } +} + +func (w *Witness) PopulateMemoryDB() ethdb.Database { + db := rawdb.NewMemoryDatabase() + for codeHash, code := range w.codes { + rawdb.WriteCode(db, codeHash, code) + } + + for owner, owned := range w.lists { + for path, node := range owned { + rawdb.WriteTrieNode(db, owner, []byte(path), common.Hash{}, node, rawdb.PathScheme) + } + } + + return db +} + func NewWitness() *Witness { return &Witness{ make(map[uint64]common.Hash), @@ -117,7 +194,7 @@ func NewWitness() *Witness { } func DumpBlockWithWitnessToFile(w *Witness, b *types.Block) { enc := w.EncodeRLP(b) - path := "/datadrive/" + path, _ := os.Getwd() //"/datadrive/" err := os.MkdirAll(fmt.Sprintf("%s/block-dump", path), 0755) if err != nil { panic("shite2") diff --git a/core/state_processor.go b/core/state_processor.go index 9a4333f723..9aca838bdf 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -104,6 +104,53 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return receipts, allLogs, *usedGas, nil } +func (p *StateProcessor) ProcessStateless(witness *state.Witness, block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { + var ( + receipts types.Receipts + usedGas = new(uint64) + header = block.Header() + blockHash = block.Hash() + blockNumber = block.Number() + allLogs []*types.Log + gp = new(GasPool).AddGas(block.GasLimit()) + ) + // Mutate the block and state according to any hard-fork specs + if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { + misc.ApplyDAOHardFork(statedb) + } + var ( + context = NewStatelessEVMBlockContext(witness, header, p.bc.Engine()) + vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) + signer = types.MakeSigner(p.config, header.Number, header.Time) + ) + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } + // Iterate over and process the individual transactions + for i, tx := range block.Transactions() { + msg, err := TransactionToMessage(tx, signer, header.BaseFee) + if err != nil { + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + statedb.SetTxContext(tx.Hash(), i) + receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) + if err != nil { + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) + } + // Fail if Shanghai not enabled and len(withdrawals) is non-zero. + withdrawals := block.Withdrawals() + if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number(), block.Time()) { + return nil, nil, 0, errors.New("withdrawals before shanghai") + } + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) + p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) + + return receipts, allLogs, *usedGas, nil +} + func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) diff --git a/trie/trie.go b/trie/trie.go index ab3c706a60..4a0ab4e238 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -21,7 +21,6 @@ import ( "bytes" "errors" "fmt" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log"