diff --git a/entity/dirty_state.go b/entity/dirty_state.go index cb1ebba..20a70ef 100644 --- a/entity/dirty_state.go +++ b/entity/dirty_state.go @@ -1,18 +1,18 @@ package entity +import "github.com/ethereum/go-ethereum/core/types" + type DirtyState struct { - accountStorage *AccountsStorage - accountState *AccountsState - transactionsState *TransactionStorage - blockStorage *BlockStorage + accountStorage *AccountsStorage + accountState *AccountsState + logs LogStorage } func NewDirtyState() *DirtyState { return &DirtyState{ - accountStorage: NewAccountsStorage(), - accountState: NewAccountsState(), - transactionsState: NewTransactionStorage(), - blockStorage: NewBlockStorage(), + accountStorage: NewAccountsStorage(), + accountState: NewAccountsState(), + logs: make(LogStorage, 0), } } @@ -24,10 +24,10 @@ func (ds *DirtyState) GetAccountState() *AccountsState { return ds.accountState } -func (ds *DirtyState) GetTransactionsState() *TransactionStorage { - return ds.transactionsState +func (ds *DirtyState) AddLog(log *types.Log) { + ds.logs = append(ds.logs, log) } -func (ds *DirtyState) GetBlockStorage() *BlockStorage { - return ds.blockStorage +func (ds *DirtyState) Logs() LogStorage { + return ds.logs } diff --git a/entity/transactions.go b/entity/transactions.go index 7a20546..9645b6c 100644 --- a/entity/transactions.go +++ b/entity/transactions.go @@ -60,3 +60,5 @@ func (ts *TransactionStorage) Apply(s *TransactionStorage) { ts.receipts[hash] = v } } + +type LogStorage []*types.Log diff --git a/executor/executor.go b/executor/executor.go index 329584a..dc44b34 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -2,33 +2,61 @@ package executor import ( "context" + "fmt" "math/big" "sync" "time" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/holiman/uint256" "github.com/rahul0tripathi/smelter/config" "github.com/rahul0tripathi/smelter/entity" "github.com/rahul0tripathi/smelter/fork" + "github.com/rahul0tripathi/smelter/producer" "github.com/rahul0tripathi/smelter/statedb" "github.com/rahul0tripathi/smelter/vm" ) type SerialExecutor struct { - mu sync.RWMutex - db *fork.DB - cfg *config.Config - provider entity.ChainStateReader + mu sync.RWMutex + db *fork.DB + cfg *config.Config + provider entity.ChainStateReader + txn *entity.TransactionStorage + blocks *entity.BlockStorage + prevBlockHash common.Hash + prevBlockNum uint64 } -func NewExecutor(cfg *config.Config, db *fork.DB, provider entity.ChainStateReader) *SerialExecutor { - return &SerialExecutor{ +func NewExecutor( + ctx context.Context, + cfg *config.Config, + db *fork.DB, + provider entity.ChainStateReader, +) (*SerialExecutor, error) { + e := &SerialExecutor{ db: db, cfg: cfg, provider: provider, + txn: entity.NewTransactionStorage(), + blocks: entity.NewBlockStorage(), } + + blockNum, err := provider.BlockNumber(ctx) + if err != nil { + return nil, err + } + + block, err := provider.BlockByNumber(ctx, new(big.Int).SetUint64(blockNum)) + if err != nil { + return nil, err + } + + e.prevBlockNum = blockNum + e.prevBlockHash = block.Hash() + return e, nil } func (e *SerialExecutor) CallAndPersist( @@ -36,14 +64,14 @@ func (e *SerialExecutor) CallAndPersist( tx ethereum.CallMsg, hooks *tracing.Hooks, overrides entity.StateOverrides, -) (ret []byte, leftOverGas uint64, err error) { +) (txHash *common.Hash, ret []byte, leftOverGas uint64, err error) { e.mu.Lock() defer e.mu.Unlock() - // TODO: use block from state + executionDB := statedb.NewDB(ctx, e.db) chainCfg, evmCfg := e.cfg.ExecutionConfig(hooks) if err = executionDB.ApplyOverrides(overrides); err != nil { - return nil, 0, err + return nil, nil, 0, err } env := vm.NewEVM(e.cfg.BlockContext(new(big.Int).Add(e.cfg.ForkConfig.ForkBlock, new(big.Int).SetUint64(1)), @@ -61,14 +89,51 @@ func (e *SerialExecutor) CallAndPersist( tx.Gas, value, ) - if err != nil { return } + txHash = e.roll(ctx, tx, leftOverGas, executionDB) + return +} + +func (e *SerialExecutor) roll( + ctx context.Context, + msg ethereum.CallMsg, + left uint64, + executionDB *statedb.StateDB, +) *common.Hash { e.db.ApplyStorage(executionDB.Dirty().GetAccountStorage()) e.db.ApplyState(executionDB.Dirty().GetAccountState()) - return + + nonce, err := e.db.GetNonce(ctx, msg.From) + if err != nil { + fmt.Println(err) + nonce = 0 + } + if err = e.db.SetNonce(ctx, msg.From, nonce+1); err != nil { + fmt.Println(err) + } + + tx := producer.NewTransactionContext(nonce+1, msg) + + hash, block, err := producer.MineBlockWithSignleTransaction( + tx, + left, + new(big.Int).SetUint64(e.prevBlockNum), + e.prevBlockHash, + executionDB.Dirty(), + e.txn, e.blocks) + if err != nil { + fmt.Println(err) + return nil + } + + e.prevBlockHash = hash + e.prevBlockNum = block.Uint64() + + txHash := tx.Hash() + return &txHash } func (e *SerialExecutor) Call( @@ -79,7 +144,7 @@ func (e *SerialExecutor) Call( ) (ret []byte, leftOverGas uint64, err error) { e.mu.Lock() defer e.mu.Unlock() - // TODO: use block from state + executionDB := statedb.NewDB(ctx, e.db) if err = executionDB.ApplyOverrides(overrides); err != nil { return nil, 0, err @@ -104,3 +169,15 @@ func (e *SerialExecutor) Call( return } + +func (e *SerialExecutor) TxnStorage() *entity.TransactionStorage { + return e.txn +} + +func (e *SerialExecutor) BlockStorage() *entity.BlockStorage { + return e.blocks +} + +func (e *SerialExecutor) Latest() (common.Hash, uint64) { + return e.prevBlockHash, e.prevBlockNum +} diff --git a/fork/fork.go b/fork/fork.go index 8f79bcf..4b41fbe 100644 --- a/fork/fork.go +++ b/fork/fork.go @@ -80,6 +80,14 @@ func (db *DB) GetNonce(ctx context.Context, addr common.Address) (uint64, error) return db.accountState.GetNonce(addr), nil } +func (db *DB) SetNonce(ctx context.Context, addr common.Address, nonce uint64) error { + if err := db.CreateState(ctx, addr); err != nil { + return err + } + db.accountState.SetNonce(addr, nonce) + return nil +} + func (db *DB) GetCodeHash(ctx context.Context, addr common.Address) (common.Hash, error) { code, err := db.GetCode(ctx, addr) if err != nil { diff --git a/producer/block_producer.go b/producer/block_producer.go new file mode 100644 index 0000000..6ffd285 --- /dev/null +++ b/producer/block_producer.go @@ -0,0 +1,54 @@ +package producer + +import ( + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/rahul0tripathi/smelter/entity" +) + +func NewTransactionContext(nonce uint64, msg ethereum.CallMsg) *types.Transaction { + return types.NewTx(&types.LegacyTx{ + Nonce: nonce, + GasPrice: msg.GasPrice, + Gas: msg.Gas, + To: msg.To, + Value: msg.Value, + Data: msg.Data, + }) +} + +func MineBlockWithSignleTransaction( + tx *types.Transaction, + left uint64, + prevBlockNumber *big.Int, + prevBlockHash common.Hash, + db postExecutionStateFetcher, + txStore transactionStorage, + blockStore blockStorage, +) (common.Hash, *big.Int, error) { + blockNumber := new(big.Int).Add(prevBlockNumber, new(big.Int).SetUint64(1)) + receipt := &types.Receipt{ + Type: tx.Type(), + Status: 1, + CumulativeGasUsed: tx.Gas() - left, + // TODO: create logs bloom + Bloom: types.Bloom{}, + Logs: db.Logs(), + TxHash: tx.Hash(), + ContractAddress: *tx.To(), + GasUsed: tx.Gas() - left, + EffectiveGasPrice: tx.GasPrice(), + BlockNumber: blockNumber, + TransactionIndex: 0, + } + + block := entity.NewBlock(prevBlockHash, blockNumber, types.Transactions{tx}, types.Receipts{receipt}) + receipt.BlockHash = block.Hash() + txStore.AddTransaction(tx) + txStore.AddReceipt(receipt) + blockStore.AddBlock(block) + return block.Hash(), blockNumber, nil +} diff --git a/producer/interfaces.go b/producer/interfaces.go new file mode 100644 index 0000000..3164a3b --- /dev/null +++ b/producer/interfaces.go @@ -0,0 +1,19 @@ +package producer + +import ( + "github.com/ethereum/go-ethereum/core/types" + "github.com/rahul0tripathi/smelter/entity" +) + +type transactionStorage interface { + AddTransaction(tx *types.Transaction) + AddReceipt(receipt *types.Receipt) +} + +type postExecutionStateFetcher interface { + Logs() entity.LogStorage +} + +type blockStorage interface { + AddBlock(block *types.Block) +} diff --git a/statedb/statedb.go b/statedb/statedb.go index 79fec23..7f0cd6e 100644 --- a/statedb/statedb.go +++ b/statedb/statedb.go @@ -285,7 +285,7 @@ func (s *StateDB) Snapshot() int { } func (s *StateDB) AddLog(log *types.Log) { - s.errorStack = append(s.errorStack, errors.New("unimplemented AddLog()")) + s.dirty.AddLog(log) } func (s *StateDB) AddPreimage(hash common.Hash, data []byte) { diff --git a/tests/execute_test.go b/tests/execute_test.go index 3e00b77..2599d36 100644 --- a/tests/execute_test.go +++ b/tests/execute_test.go @@ -3,12 +3,14 @@ package tests import ( "context" "math/big" + "reflect" "testing" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + types2 "github.com/ethereum/go-ethereum/core/types" "github.com/rahul0tripathi/smelter/config" "github.com/rahul0tripathi/smelter/entity" "github.com/rahul0tripathi/smelter/executor" @@ -32,13 +34,16 @@ func TestExecuteE2E(t *testing.T) { cfg.ForkConfig = &forkCfg stateTracer := tracer.NewTracer(true) target := types.Address0x69 - exec := executor.NewExecutor(cfg, db, &reader) + exec, err := executor.NewExecutor(ctx, cfg, db, &reader) + if err != nil { + panic(err) + } sender := common.HexToAddress("0x0000000000000000000000000000000000000006") // Deposit transaction deposit, _ := hexutil.Decode("0xd0e30db0") - ret, _, err := exec.CallAndPersist(ctx, ethereum.CallMsg{ + _, ret, _, err := exec.CallAndPersist(ctx, ethereum.CallMsg{ From: sender, To: &target, Data: deposit, @@ -65,7 +70,7 @@ func TestExecuteE2E(t *testing.T) { // Transfer transaction stateTracer = tracer.NewTracer(true) transferCall, _ := hexutil.Decode("0xa9059cbb00000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000001b37") - ret, _, err = exec.CallAndPersist(ctx, ethereum.CallMsg{ + _, ret, _, err = exec.CallAndPersist(ctx, ethereum.CallMsg{ From: sender, To: &target, Data: transferCall, @@ -100,3 +105,76 @@ func TestExecuteE2E(t *testing.T) { require.NoError(t, err, "failed to read 0x6 balance") require.Equal(t, new(big.Int).SetBytes(ret).Int64(), int64(2), "invalid 0x6 balance received post transfer") } + +func TestBlockProduction(t *testing.T) { + ctx := context.Background() + reader := mockProvider{} + accountsState := entity.NewAccountsState() + accountsStorage := entity.NewAccountsStorage() + forkCfg := entity.ForkConfig{ + ChainID: 69, + ForkBlock: new(big.Int).SetUint64(1), + } + db := fork.NewDB(&reader, forkCfg, accountsStorage, accountsState) + cfg := config.NewConfigWithDefaults() + cfg.ForkConfig = &forkCfg + stateTracer := tracer.NewTracer(true) + target := types.Address0x69 + exec, err := executor.NewExecutor(ctx, cfg, db, &reader) + if err != nil { + panic(err) + } + + sender := common.HexToAddress("0x0000000000000000000000000000000000000006") + + // Deposit transaction + deposit, _ := hexutil.Decode("0xd0e30db0") + msg := ethereum.CallMsg{ + From: sender, + To: &target, + Data: deposit, + Gas: 30000000, + Value: new(big.Int).SetInt64(6969), + } + hash, _, _, err := exec.CallAndPersist(ctx, msg, stateTracer.Hooks(), map[common.Address]entity.StateOverride{ + sender: {Balance: abi.MaxUint256}, + }) + require.NoError(t, err, "failed to deposit") + t.Log("trace", stateTracer.Fmt()) + + require.NotNil(t, hash, "missing tx hash") + + t.Log("transaction Hash", hash.Hex()) + require.Equal(t, "0xfa652df356f74e065519c07cd5473ba8d26383b7f000f6f06563906b6d3d83e0", hash.Hex(), "mismatch txn hash") + + txn := exec.TxnStorage().GetTransaction(*hash) + require.Equal(t, txn.Type(), uint8(0), "invalid txn type") + require.Equal(t, *txn.To(), target, "invalid txn target") + require.Equal(t, txn.Value().String(), "6969", "invalid txn value") + require.Equal(t, txn.Data(), deposit, "invalid txn data") + + receipt := exec.TxnStorage().GetReceipt(*hash) + + require.Equal(t, len(receipt.Logs), 1, "invalid logs emitted") + require.Equal(t, receipt.Logs[0].Address, target, "invalid emitter") + require.Equal(t, receipt.Logs[0].Topics[0].Hex(), "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", "invalid log topic [0]") + require.Equal(t, receipt.Logs[0].Topics[1].Hex(), "0x0000000000000000000000000000000000000000000000000000000000000006", "invalid log topic [1]") + require.Equal(t, hexutil.Encode(receipt.Logs[0].Data), "0x0000000000000000000000000000000000000000000000000000000000001b39", "invalid log data") + + txnBytes, err := txn.MarshalJSON() + require.NoError(t, err, "MarshalJSON") + t.Log("transaction", string(txnBytes)) + + receiptBytes, err := receipt.MarshalJSON() + require.NoError(t, err, "MarshalJSON") + t.Log("receipt", string(receiptBytes)) + + blockHash, blockNum := exec.Latest() + require.Equal(t, blockHash.Hex(), "0x399458e5436fe1e9b8c59fb4358657a1e3a3ab7c1d1e72bde3258c7a525a66b4", "invalid block hash") + require.Equal(t, blockNum, uint64(2), "invalid block number") + + block := exec.BlockStorage().GetBlockByHash(blockHash) + + require.True(t, reflect.DeepEqual(block.Transactions(), types2.Transactions{txn}), "invalid txn found uin block") + require.Equal(t, block.Number().Uint64(), uint64(2), "invalid block number") +} diff --git a/tests/mock_provider.go b/tests/mock_provider.go index da5deab..335f189 100644 --- a/tests/mock_provider.go +++ b/tests/mock_provider.go @@ -8,13 +8,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/rahul0tripathi/smelter/entity" ) type mockProvider struct { } func (m *mockProvider) BlockNumber(ctx context.Context) (uint64, error) { - return 0, nil + return 1, nil } func (m *mockProvider) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { @@ -22,7 +24,7 @@ func (m *mockProvider) BlockByHash(ctx context.Context, hash common.Hash) (*type } func (m *mockProvider) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - return &types.Block{}, nil + return entity.NewBlock(crypto.Keccak256Hash([]byte("genesis")), new(big.Int).SetInt64(1), nil, nil), nil } func (m *mockProvider) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {