From 89003d3555b8bcbf597dbdf7f162fa25a4bf2ac9 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 4 Dec 2023 11:39:00 +0100 Subject: [PATCH 1/5] Add stateless builder --- suave/builder/api/api.go | 14 +++ suave/builder/api/api_client.go | 44 +++++++ suave/builder/api/api_server.go | 38 +++++++ suave/builder/api/api_test.go | 55 +++++++++ suave/builder/builder.go | 68 +++++++++++ suave/builder/builder_test.go | 190 +++++++++++++++++++++++++++++++ suave/builder/session_manager.go | 139 ++++++++++++++++++++++ 7 files changed, 548 insertions(+) create mode 100644 suave/builder/api/api.go create mode 100644 suave/builder/api/api_client.go create mode 100644 suave/builder/api/api_server.go create mode 100644 suave/builder/api/api_test.go create mode 100644 suave/builder/builder.go create mode 100644 suave/builder/builder_test.go create mode 100644 suave/builder/session_manager.go diff --git a/suave/builder/api/api.go b/suave/builder/api/api.go new file mode 100644 index 000000000..4043a8c1b --- /dev/null +++ b/suave/builder/api/api.go @@ -0,0 +1,14 @@ +package api + +import ( + "context" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/core/types" +) + +type API interface { + NewSession(ctx context.Context) (string, error) + AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) error + Finalize(ctx context.Context, sessionId string) (*engine.ExecutionPayloadEnvelope, error) +} diff --git a/suave/builder/api/api_client.go b/suave/builder/api/api_client.go new file mode 100644 index 000000000..d5560ecbc --- /dev/null +++ b/suave/builder/api/api_client.go @@ -0,0 +1,44 @@ +package api + +import ( + "context" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" +) + +var _ API = (*APIClient)(nil) + +type APIClient struct { + rpc *rpc.Client +} + +func NewClient(endpoint string) (*APIClient, error) { + clt, err := rpc.Dial(endpoint) + if err != nil { + return nil, err + } + return NewClientFromRPC(clt), nil +} + +func NewClientFromRPC(rpc *rpc.Client) *APIClient { + return &APIClient{rpc: rpc} +} + +func (a *APIClient) NewSession(ctx context.Context) (string, error) { + var id string + err := a.rpc.CallContext(ctx, &id, "builder_newSession") + return id, err +} + +func (a *APIClient) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) error { + err := a.rpc.CallContext(ctx, nil, "builder_addTransaction", sessionId, tx) + return err +} + +func (a *APIClient) Finalize(ctx context.Context, sessionId string) (*engine.ExecutionPayloadEnvelope, error) { + var res *engine.ExecutionPayloadEnvelope + err := a.rpc.CallContext(ctx, &res, "builder_finalize", sessionId) + return res, err +} diff --git a/suave/builder/api/api_server.go b/suave/builder/api/api_server.go new file mode 100644 index 000000000..a28bcf41a --- /dev/null +++ b/suave/builder/api/api_server.go @@ -0,0 +1,38 @@ +package api + +import ( + "context" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/core/types" +) + +// sessionManager is the backend that manages the session state of the builder API. +type sessionManager interface { + NewSession() (string, error) + AddTransaction(sessionId string, tx *types.Transaction) error + Finalize(sessionId string) (*engine.ExecutionPayloadEnvelope, error) +} + +func NewServer(s sessionManager) *Server { + api := &Server{ + sessionMngr: s, + } + return api +} + +type Server struct { + sessionMngr sessionManager +} + +func (s *Server) NewSession(ctx context.Context) (string, error) { + return s.sessionMngr.NewSession() +} + +func (s *Server) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) error { + return s.sessionMngr.AddTransaction(sessionId, tx) +} + +func (s *Server) Finalize(ctx context.Context, sessionId string) (*engine.ExecutionPayloadEnvelope, error) { + return s.sessionMngr.Finalize(sessionId) +} diff --git a/suave/builder/api/api_test.go b/suave/builder/api/api_test.go new file mode 100644 index 000000000..92fee6ddd --- /dev/null +++ b/suave/builder/api/api_test.go @@ -0,0 +1,55 @@ +package api + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" +) + +func TestAPI(t *testing.T) { + srv := rpc.NewServer() + + builderAPI := NewServer(&nullSessionManager{}) + srv.RegisterName("builder", builderAPI) + + c := NewClientFromRPC(rpc.DialInProc(srv)) + + res0, err := c.NewSession(context.Background()) + require.NoError(t, err) + require.Equal(t, res0, "1") + + txn := types.NewTransaction(0, common.Address{}, big.NewInt(1), 1, big.NewInt(1), []byte{}) + err = c.AddTransaction(context.Background(), "1", txn) + require.NoError(t, err) + + res1, err := c.Finalize(context.Background(), "1") + require.NoError(t, err) + require.Equal(t, res1.BlockValue, big.NewInt(1)) +} + +type nullSessionManager struct{} + +func (n *nullSessionManager) NewSession() (string, error) { + return "1", nil +} + +func (n *nullSessionManager) AddTransaction(sessionId string, tx *types.Transaction) error { + return nil +} + +func (n *nullSessionManager) Finalize(sessionId string) (*engine.ExecutionPayloadEnvelope, error) { + return &engine.ExecutionPayloadEnvelope{ + BlockValue: big.NewInt(1), + ExecutionPayload: &engine.ExecutableData{ + Number: 1, + BaseFeePerGas: big.NewInt(1), + Transactions: [][]byte{}, + }, + }, nil +} diff --git a/suave/builder/builder.go b/suave/builder/builder.go new file mode 100644 index 000000000..f71ababad --- /dev/null +++ b/suave/builder/builder.go @@ -0,0 +1,68 @@ +package builder + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +type builder struct { + config *builderConfig + txns []*types.Transaction + receipts []*types.Receipt + state *state.StateDB + gasPool *core.GasPool + gasUsed *uint64 +} + +type builderConfig struct { + preState *state.StateDB + header *types.Header + config *params.ChainConfig + context core.ChainContext + vmConfig vm.Config +} + +func newBuilder(config *builderConfig) *builder { + gp := core.GasPool(config.header.GasLimit) + var gasUsed uint64 + + return &builder{ + config: config, + state: config.preState.Copy(), + gasPool: &gp, + gasUsed: &gasUsed, + } +} + +func (b *builder) AddTransaction(txn *types.Transaction) error { + dummyAuthor := common.Address{} + + receipt, err := core.ApplyTransaction(b.config.config, b.config.context, &dummyAuthor, b.gasPool, b.state, b.config.header, txn, b.gasUsed, b.config.vmConfig) + if err != nil { + return err + } + + b.txns = append(b.txns, txn) + b.receipts = append(b.receipts, receipt) + + return nil +} + +func (b *builder) Finalize() (*types.Block, error) { + root, err := b.state.Commit(true) + if err != nil { + return nil, err + } + + header := b.config.header + header.Root = root + header.GasUsed = *b.gasUsed + + block := types.NewBlock(header, b.txns, nil, b.receipts, trie.NewStackTrie(nil)) + return block, nil +} diff --git a/suave/builder/builder_test.go b/suave/builder/builder_test.go new file mode 100644 index 000000000..6c87cb65f --- /dev/null +++ b/suave/builder/builder_test.go @@ -0,0 +1,190 @@ +package builder + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" +) + +func TestBuilder_AddTxn_Simple(t *testing.T) { + to := common.Address{0x01, 0x10, 0xab} + + mock := newMockBuilder(t) + txn := mock.newTransfer(t, to, big.NewInt(1)) + + err := mock.builder.AddTransaction(txn) + require.NoError(t, err) + + mock.expect(t, expectedResult{ + txns: []*types.Transaction{ + txn, + }, + balances: map[common.Address]*big.Int{ + to: big.NewInt(1), + }, + }) + + block, err := mock.builder.Finalize() + require.NoError(t, err) + + require.Equal(t, uint64(21000), block.GasUsed()) + require.Len(t, block.Transactions(), 1) + require.Equal(t, txn.Hash(), block.Transactions()[0].Hash()) +} + +func newMockBuilder(t *testing.T) *mockBuilder { + // create a dummy header at 0 + header := &types.Header{ + Number: big.NewInt(0), + GasLimit: 1000000000000, + Time: 1000, + Difficulty: big.NewInt(1), + } + + var stateRef *state.StateDB + + premineKey, _ := crypto.GenerateKey() // TODO: it would be nice to have it deterministic + premineKeyAddr := crypto.PubkeyToAddress(premineKey.PublicKey) + + // create a state reference with at least one premined account + // In order to test the statedb in isolation, we are going + // to commit this pre-state to a memory database + { + db := state.NewDatabase(rawdb.NewMemoryDatabase()) + preState, err := state.New(types.EmptyRootHash, db, nil) + require.NoError(t, err) + + preState.AddBalance(premineKeyAddr, big.NewInt(1000000000000000000)) + + root, err := preState.Commit(true) + require.NoError(t, err) + + stateRef, err = state.New(root, db, nil) + require.NoError(t, err) + } + + // for the sake of this test, we only need all the forks enabled + chainConfig := params.SuaveChainConfig + + // Disable london so that we do not check gasFeeCap (TODO: Fix) + chainConfig.LondonBlock = big.NewInt(100) + + m := &mockBuilder{ + premineKey: premineKey, + premineKeyAddr: premineKeyAddr, + signer: types.NewEIP155Signer(chainConfig.ChainID), + } + + config := &builderConfig{ + header: header, + preState: stateRef, + config: chainConfig, + context: m, // m implements ChainContext with panics + vmConfig: vm.Config{}, + } + m.builder = newBuilder(config) + + return m +} + +type mockBuilder struct { + builder *builder + + // builtin private keys + premineKey *ecdsa.PrivateKey + premineKeyAddr common.Address + + nextNonce uint64 // figure out a better way + signer types.Signer +} + +func (m *mockBuilder) Engine() consensus.Engine { + panic("TODO") +} + +func (m *mockBuilder) GetHeader(common.Hash, uint64) *types.Header { + panic("TODO") +} + +func (m *mockBuilder) getNonce() uint64 { + next := m.nextNonce + m.nextNonce++ + return next +} + +func (m *mockBuilder) newTransfer(t *testing.T, to common.Address, amount *big.Int) *types.Transaction { + tx := types.NewTransaction(m.getNonce(), to, amount, 1000000, big.NewInt(1), nil) + return m.newTxn(t, tx) +} + +func (m *mockBuilder) newTxn(t *testing.T, tx *types.Transaction) *types.Transaction { + // sign the transaction + signature, err := crypto.Sign(m.signer.Hash(tx).Bytes(), m.premineKey) + require.NoError(t, err) + + // include the signature in the transaction + tx, err = tx.WithSignature(m.signer, signature) + require.NoError(t, err) + + return tx +} + +type expectedResult struct { + txns []*types.Transaction + balances map[common.Address]*big.Int +} + +func (m *mockBuilder) expect(t *testing.T, res expectedResult) { + // validate txns + if len(res.txns) != len(m.builder.txns) { + t.Fatalf("expected %d txns, got %d", len(res.txns), len(m.builder.txns)) + } + for indx, txn := range res.txns { + if txn.Hash() != m.builder.txns[indx].Hash() { + t.Fatalf("expected txn %d to be %s, got %s", indx, txn.Hash(), m.builder.txns[indx].Hash()) + } + } + + // The receipts must be the same as the txns + if len(res.txns) != len(m.builder.receipts) { + t.Fatalf("expected %d receipts, got %d", len(res.txns), len(m.builder.receipts)) + } + for indx, txn := range res.txns { + if txn.Hash() != m.builder.receipts[indx].TxHash { + t.Fatalf("expected receipt %d to be %s, got %s", indx, txn.Hash(), m.builder.receipts[indx].TxHash) + } + } + + // The gas left in the pool must be the header gas limit minus + // the total gas consumed by all the transactions in the block. + totalGasConsumed := uint64(0) + for _, receipt := range m.builder.receipts { + totalGasConsumed += receipt.GasUsed + } + if m.builder.gasPool.Gas() != m.builder.config.header.GasLimit-totalGasConsumed { + t.Fatalf("expected gas pool to be %d, got %d", m.builder.config.header.GasLimit-totalGasConsumed, m.builder.gasPool.Gas()) + } + + // The 'gasUsed' must match the total gas consumed by all the transactions + if *m.builder.gasUsed != totalGasConsumed { + t.Fatalf("expected gas used to be %d, got %d", totalGasConsumed, m.builder.gasUsed) + } + + // The state must match the expected balances + for addr, expectedBalance := range res.balances { + balance := m.builder.state.GetBalance(addr) + if balance.Cmp(expectedBalance) != 0 { + t.Fatalf("expected balance of %s to be %d, got %d", addr, expectedBalance, balance) + } + } +} diff --git a/suave/builder/session_manager.go b/suave/builder/session_manager.go new file mode 100644 index 000000000..6c278df97 --- /dev/null +++ b/suave/builder/session_manager.go @@ -0,0 +1,139 @@ +package builder + +import ( + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/google/uuid" +) + +// blockchain is the minimum interface to the blockchain +// required to build a block +type blockchain interface { + // Header returns the current tip of the chain + Header() *types.Header + + // StateAt returns the state at the given root + StateAt(root common.Hash) (*state.StateDB, error) +} + +type Config struct { + GasCeil uint64 +} + +type SessionManager struct { + sessions map[string]*builder + sessionsLock sync.RWMutex + blockchain blockchain + config *Config +} + +func NewSessionManager(blockchain blockchain, config *Config) *SessionManager { + if config.GasCeil == 0 { + config.GasCeil = 1000000000000000000 + } + + s := &SessionManager{ + sessions: make(map[string]*builder), + blockchain: blockchain, + config: config, + } + return s +} + +// NewSession creates a new builder session and returns the session id +func (s *SessionManager) NewSession() (string, error) { + s.sessionsLock.Lock() + defer s.sessionsLock.Unlock() + + parent := s.blockchain.Header() + + header := &types.Header{ + ParentHash: parent.Hash(), + Number: new(big.Int).Add(parent.Number, common.Big1), + GasLimit: core.CalcGasLimit(parent.GasLimit, s.config.GasCeil), + Time: 1000, // TODO: fix this + Coinbase: common.Address{}, // TODO: fix this + } + + stateRef, err := s.blockchain.StateAt(parent.Root) + if err != nil { + return "", err + } + + cfg := &builderConfig{ + preState: stateRef, + header: header, + } + + id := uuid.New().String()[:7] + s.sessions[id] = newBuilder(cfg) + + return id, nil +} + +func (s *SessionManager) getSession(sessionId string) (*builder, error) { + s.sessionsLock.RLock() + defer s.sessionsLock.RUnlock() + + session, ok := s.sessions[sessionId] + if !ok { + return nil, fmt.Errorf("session %s not found", sessionId) + } + return session, nil +} + +func (s *SessionManager) AddTransaction(sessionId string, tx *types.Transaction) error { + builder, err := s.getSession(sessionId) + if err != nil { + return err + } + return builder.AddTransaction(tx) +} + +func (s *SessionManager) Finalize(sessionId string) (*engine.ExecutionPayloadEnvelope, error) { + builder, err := s.getSession(sessionId) + if err != nil { + return nil, err + } + + block, err := builder.Finalize() + if err != nil { + return nil, err + } + data := &engine.ExecutableData{ + ParentHash: block.ParentHash(), + Number: block.Number().Uint64(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + LogsBloom: block.Bloom().Bytes(), + ReceiptsRoot: block.ReceiptHash(), + BlockHash: block.Hash(), + StateRoot: block.Root(), + Timestamp: block.Time(), + ExtraData: block.Extra(), + BaseFeePerGas: &big.Int{}, // TODO + Transactions: [][]byte{}, + } + + // convert transactions to bytes + for _, txn := range block.Transactions() { + txnData, err := txn.MarshalBinary() + if err != nil { + return nil, err + } + data.Transactions = append(data.Transactions, txnData) + } + + payload := &engine.ExecutionPayloadEnvelope{ + BlockValue: big.NewInt(0), // TODO + ExecutionPayload: data, + } + return payload, nil +} From fa08c0e4604a0e2e8a1a09331dc385c7f2aee45b Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 13 Dec 2023 07:50:52 +0000 Subject: [PATCH 2/5] Add receipt --- suave/builder/api/api_server.go | 4 ++-- suave/builder/api/api_test.go | 4 ++-- suave/builder/builder.go | 6 +++--- suave/builder/builder_test.go | 2 +- suave/builder/session_manager.go | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/suave/builder/api/api_server.go b/suave/builder/api/api_server.go index a28bcf41a..803b372c4 100644 --- a/suave/builder/api/api_server.go +++ b/suave/builder/api/api_server.go @@ -10,7 +10,7 @@ import ( // sessionManager is the backend that manages the session state of the builder API. type sessionManager interface { NewSession() (string, error) - AddTransaction(sessionId string, tx *types.Transaction) error + AddTransaction(sessionId string, tx *types.Transaction) (*types.Receipt, error) Finalize(sessionId string) (*engine.ExecutionPayloadEnvelope, error) } @@ -29,7 +29,7 @@ func (s *Server) NewSession(ctx context.Context) (string, error) { return s.sessionMngr.NewSession() } -func (s *Server) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) error { +func (s *Server) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) { return s.sessionMngr.AddTransaction(sessionId, tx) } diff --git a/suave/builder/api/api_test.go b/suave/builder/api/api_test.go index 92fee6ddd..a7e108494 100644 --- a/suave/builder/api/api_test.go +++ b/suave/builder/api/api_test.go @@ -39,8 +39,8 @@ func (n *nullSessionManager) NewSession() (string, error) { return "1", nil } -func (n *nullSessionManager) AddTransaction(sessionId string, tx *types.Transaction) error { - return nil +func (n *nullSessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.Receipt, error) { + return nil, nil } func (n *nullSessionManager) Finalize(sessionId string) (*engine.ExecutionPayloadEnvelope, error) { diff --git a/suave/builder/builder.go b/suave/builder/builder.go index f71ababad..d2c81767c 100644 --- a/suave/builder/builder.go +++ b/suave/builder/builder.go @@ -39,18 +39,18 @@ func newBuilder(config *builderConfig) *builder { } } -func (b *builder) AddTransaction(txn *types.Transaction) error { +func (b *builder) AddTransaction(txn *types.Transaction) (*types.Receipt, error) { dummyAuthor := common.Address{} receipt, err := core.ApplyTransaction(b.config.config, b.config.context, &dummyAuthor, b.gasPool, b.state, b.config.header, txn, b.gasUsed, b.config.vmConfig) if err != nil { - return err + return nil, err } b.txns = append(b.txns, txn) b.receipts = append(b.receipts, receipt) - return nil + return receipt, nil } func (b *builder) Finalize() (*types.Block, error) { diff --git a/suave/builder/builder_test.go b/suave/builder/builder_test.go index 6c87cb65f..daf8ee08f 100644 --- a/suave/builder/builder_test.go +++ b/suave/builder/builder_test.go @@ -22,7 +22,7 @@ func TestBuilder_AddTxn_Simple(t *testing.T) { mock := newMockBuilder(t) txn := mock.newTransfer(t, to, big.NewInt(1)) - err := mock.builder.AddTransaction(txn) + _, err := mock.builder.AddTransaction(txn) require.NoError(t, err) mock.expect(t, expectedResult{ diff --git a/suave/builder/session_manager.go b/suave/builder/session_manager.go index 6c278df97..5b0f598b1 100644 --- a/suave/builder/session_manager.go +++ b/suave/builder/session_manager.go @@ -89,10 +89,10 @@ func (s *SessionManager) getSession(sessionId string) (*builder, error) { return session, nil } -func (s *SessionManager) AddTransaction(sessionId string, tx *types.Transaction) error { +func (s *SessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.Receipt, error) { builder, err := s.getSession(sessionId) if err != nil { - return err + return nil, err } return builder.AddTransaction(tx) } From f7bb43c26a87b9623512a0097a4a22146af02204 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 18 Dec 2023 14:05:33 +0000 Subject: [PATCH 3/5] Interlude changes --- core/types/suave_structs.go | 13 +- core/vm/contracts_suave.go | 31 +++ core/vm/contracts_suave_runtime_adapter.go | 88 ++++++++- .../contracts_suave_runtime_adapter_test.go | 8 + core/vm/contracts_suave_test.go | 8 + eth/backend.go | 9 + suave/artifacts/SuaveLib.json | 2 +- suave/artifacts/addresses.go | 10 +- suave/backends/eth_backends.go | 20 +- suave/builder/api/api.go | 4 +- suave/builder/api/api_client.go | 24 ++- suave/builder/api/api_server.go | 16 +- suave/builder/api/api_test.go | 22 +-- suave/builder/builder.go | 17 +- suave/builder/builder_test.go | 82 +------- suave/builder/session_manager.go | 91 ++++----- suave/builder/session_manager_test.go | 183 ++++++++++++++++++ suave/core/types.go | 3 + suave/e2e/workflow_test.go | 19 ++ suave/gen/suave_spec.yaml | 31 +++ suave/sol/libraries/Suave.sol | 37 ++++ suave/sol/libraries/SuaveForge.sol | 16 ++ suave/sol/standard_peekers/example.sol | 13 ++ 23 files changed, 556 insertions(+), 191 deletions(-) create mode 100644 suave/builder/session_manager_test.go diff --git a/core/types/suave_structs.go b/core/types/suave_structs.go index f7922dd96..8d04039d3 100755 --- a/core/types/suave_structs.go +++ b/core/types/suave_structs.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: cdc3a0bf9b0cd1b4a4b17e6245d7e942f42ffc5f7333bc11a220abbc67d6587b +// Hash: deab96918f508e1db1bae632f0afe06dfd000b4f96392df15ff78673eb8f1a14 package types import "github.com/ethereum/go-ethereum/common" @@ -36,6 +36,17 @@ type HttpRequest struct { Body []byte } +type SimulateTransactionResult struct { + Egp uint64 + Logs []*SimulatedLog +} + +type SimulatedLog struct { + Data []byte + Addr common.Address + Topics []common.Hash +} + type Withdrawal struct { Index uint64 Validator uint64 diff --git a/core/vm/contracts_suave.go b/core/vm/contracts_suave.go index 8f4c94b55..30eb64fdc 100644 --- a/core/vm/contracts_suave.go +++ b/core/vm/contracts_suave.go @@ -2,6 +2,7 @@ package vm import ( "bytes" + "context" "fmt" "io" "net/http" @@ -196,3 +197,33 @@ func (s *suaveRuntime) doHTTPRequest(request types.HttpRequest) ([]byte, error) } return data, nil } + +func (s *suaveRuntime) newBuilder() (string, error) { + fmt.Println("-- new builder --") + return s.suaveContext.Backend.ConfidentialEthBackend.NewSession(context.Background()) +} + +func (s *suaveRuntime) simulateTransaction(session string, txnBytes []byte) (types.SimulateTransactionResult, error) { + txn := new(types.Transaction) + if err := txn.UnmarshalBinary(txnBytes); err != nil { + return types.SimulateTransactionResult{}, err + } + + receipt, err := s.suaveContext.Backend.ConfidentialEthBackend.AddTransaction(context.Background(), session, txn) + if err != nil { + return types.SimulateTransactionResult{}, err + } + + res := types.SimulateTransactionResult{ + Logs: []*types.SimulatedLog{}, + } + for _, log := range receipt.Logs { + res.Logs = append(res.Logs, &types.SimulatedLog{ + Addr: log.Address, + Topics: log.Topics, + Data: log.Data, + }) + } + + return res, nil +} diff --git a/core/vm/contracts_suave_runtime_adapter.go b/core/vm/contracts_suave_runtime_adapter.go index 6c0995879..1c8e47eed 100644 --- a/core/vm/contracts_suave_runtime_adapter.go +++ b/core/vm/contracts_suave_runtime_adapter.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: cdc3a0bf9b0cd1b4a4b17e6245d7e942f42ffc5f7333bc11a220abbc67d6587b +// Hash: deab96918f508e1db1bae632f0afe06dfd000b4f96392df15ff78673eb8f1a14 package vm import ( @@ -27,8 +27,10 @@ type SuaveRuntime interface { fetchBids(cond uint64, namespace string) ([]types.Bid, error) fillMevShareBundle(bidId types.BidId) ([]byte, error) newBid(decryptionCondition uint64, allowedPeekers []common.Address, allowedStores []common.Address, bidType string) (types.Bid, error) + newBuilder() (string, error) signEthTransaction(txn []byte, chainId string, signingKey string) ([]byte, error) simulateBundle(bundleData []byte) (uint64, error) + simulateTransaction(session string, txn []byte) (types.SimulateTransactionResult, error) submitBundleJsonRPC(url string, method string, params []byte) ([]byte, error) submitEthBlockBidToRelay(relayUrl string, builderBid []byte) ([]byte, error) } @@ -44,14 +46,16 @@ var ( fetchBidsAddr = common.HexToAddress("0x0000000000000000000000000000000042030001") fillMevShareBundleAddr = common.HexToAddress("0x0000000000000000000000000000000043200001") newBidAddr = common.HexToAddress("0x0000000000000000000000000000000042030000") + newBuilderAddr = common.HexToAddress("0x0000000000000000000000000000000053200001") signEthTransactionAddr = common.HexToAddress("0x0000000000000000000000000000000040100001") simulateBundleAddr = common.HexToAddress("0x0000000000000000000000000000000042100000") + simulateTransactionAddr = common.HexToAddress("0x0000000000000000000000000000000053200002") submitBundleJsonRPCAddr = common.HexToAddress("0x0000000000000000000000000000000043000001") submitEthBlockBidToRelayAddr = common.HexToAddress("0x0000000000000000000000000000000042100002") ) var addrList = []common.Address{ - buildEthBlockAddr, confidentialInputsAddr, confidentialRetrieveAddr, confidentialStoreAddr, doHTTPRequestAddr, ethcallAddr, extractHintAddr, fetchBidsAddr, fillMevShareBundleAddr, newBidAddr, signEthTransactionAddr, simulateBundleAddr, submitBundleJsonRPCAddr, submitEthBlockBidToRelayAddr, + buildEthBlockAddr, confidentialInputsAddr, confidentialRetrieveAddr, confidentialStoreAddr, doHTTPRequestAddr, ethcallAddr, extractHintAddr, fetchBidsAddr, fillMevShareBundleAddr, newBidAddr, newBuilderAddr, signEthTransactionAddr, simulateBundleAddr, simulateTransactionAddr, submitBundleJsonRPCAddr, submitEthBlockBidToRelayAddr, } type SuaveRuntimeAdapter struct { @@ -90,12 +94,18 @@ func (b *SuaveRuntimeAdapter) run(addr common.Address, input []byte) ([]byte, er case newBidAddr: return b.newBid(input) + case newBuilderAddr: + return b.newBuilder(input) + case signEthTransactionAddr: return b.signEthTransaction(input) case simulateBundleAddr: return b.simulateBundle(input) + case simulateTransactionAddr: + return b.simulateTransaction(input) + case submitBundleJsonRPCAddr: return b.submitBundleJsonRPC(input) @@ -503,6 +513,40 @@ func (b *SuaveRuntimeAdapter) newBid(input []byte) (res []byte, err error) { } +func (b *SuaveRuntimeAdapter) newBuilder(input []byte) (res []byte, err error) { + var ( + unpacked []interface{} + result []byte + ) + + _ = unpacked + _ = result + + unpacked, err = artifacts.SuaveAbi.Methods["newBuilder"].Inputs.Unpack(input) + if err != nil { + err = errFailedToUnpackInput + return + } + + var () + + var ( + id string + ) + + if id, err = b.impl.newBuilder(); err != nil { + return + } + + result, err = artifacts.SuaveAbi.Methods["newBuilder"].Outputs.Pack(id) + if err != nil { + err = errFailedToPackOutput + return + } + return result, nil + +} + func (b *SuaveRuntimeAdapter) signEthTransaction(input []byte) (res []byte, err error) { var ( unpacked []interface{} @@ -583,6 +627,46 @@ func (b *SuaveRuntimeAdapter) simulateBundle(input []byte) (res []byte, err erro } +func (b *SuaveRuntimeAdapter) simulateTransaction(input []byte) (res []byte, err error) { + var ( + unpacked []interface{} + result []byte + ) + + _ = unpacked + _ = result + + unpacked, err = artifacts.SuaveAbi.Methods["simulateTransaction"].Inputs.Unpack(input) + if err != nil { + err = errFailedToUnpackInput + return + } + + var ( + session string + txn []byte + ) + + session = unpacked[0].(string) + txn = unpacked[1].([]byte) + + var ( + output1 types.SimulateTransactionResult + ) + + if output1, err = b.impl.simulateTransaction(session, txn); err != nil { + return + } + + result, err = artifacts.SuaveAbi.Methods["simulateTransaction"].Outputs.Pack(output1) + if err != nil { + err = errFailedToPackOutput + return + } + return result, nil + +} + func (b *SuaveRuntimeAdapter) submitBundleJsonRPC(input []byte) (res []byte, err error) { var ( unpacked []interface{} diff --git a/core/vm/contracts_suave_runtime_adapter_test.go b/core/vm/contracts_suave_runtime_adapter_test.go index dde519b23..7c5ab35ff 100644 --- a/core/vm/contracts_suave_runtime_adapter_test.go +++ b/core/vm/contracts_suave_runtime_adapter_test.go @@ -71,6 +71,14 @@ func (m *mockRuntime) doHTTPRequest(request types.HttpRequest) ([]byte, error) { return []byte{0x1}, nil } +func (m *mockRuntime) newBuilder() (string, error) { + return "", nil +} + +func (m *mockRuntime) simulateTransaction(session string, txn []byte) (types.SimulateTransactionResult, error) { + return types.SimulateTransactionResult{}, nil +} + func TestRuntimeAdapter(t *testing.T) { adapter := &SuaveRuntimeAdapter{ impl: &mockRuntime{}, diff --git a/core/vm/contracts_suave_test.go b/core/vm/contracts_suave_test.go index 995c088c8..4bca8f6c5 100644 --- a/core/vm/contracts_suave_test.go +++ b/core/vm/contracts_suave_test.go @@ -20,6 +20,14 @@ type mockSuaveBackend struct { func (m *mockSuaveBackend) Start() error { return nil } func (m *mockSuaveBackend) Stop() error { return nil } +func (m *mockSuaveBackend) NewSession(ctx context.Context) (string, error) { + return "", nil +} + +func (m *mockSuaveBackend) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) { + return &types.Receipt{}, nil +} + func (m *mockSuaveBackend) InitializeBid(bid suave.Bid) error { return nil } diff --git a/eth/backend.go b/eth/backend.go index 8c374a4b9..601d49016 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -59,6 +59,8 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/suave/backends" suave_backends "github.com/ethereum/go-ethereum/suave/backends" + suave_builder "github.com/ethereum/go-ethereum/suave/builder" + suave_builder_api "github.com/ethereum/go-ethereum/suave/builder/api" suave "github.com/ethereum/go-ethereum/suave/core" "github.com/ethereum/go-ethereum/suave/cstore" "github.com/flashbots/go-boost-utils/bls" @@ -351,6 +353,13 @@ func (s *Ethereum) APIs() []rpc.API { Service: backends.NewEthBackendServer(s.APIBackend), }) + sessionManager := suave_builder.NewSessionManager(s.blockchain, &suave_builder.Config{}) + + apis = append(apis, rpc.API{ + Namespace: "suavex", + Service: suave_builder_api.NewServer(sessionManager), + }) + // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) diff --git a/suave/artifacts/SuaveLib.json b/suave/artifacts/SuaveLib.json index 425fea3be..874ef9865 100644 --- a/suave/artifacts/SuaveLib.json +++ b/suave/artifacts/SuaveLib.json @@ -1 +1 @@ -[{"type":"error","name":"PeekerReverted","inputs":[{"name":"addr","type":"address"},{"name":"err","type":"bytes"}]},{"type":"function","name":"buildEthBlock","inputs":[{"name":"blockArgs","type":"tuple","internalType":"struct Suave.BuildBlockArgs","components":[{"name":"slot","type":"uint64","internalType":"uint64"},{"name":"proposerPubkey","type":"bytes","internalType":"bytes"},{"name":"parent","type":"bytes32","internalType":"bytes32"},{"name":"timestamp","type":"uint64","internalType":"uint64"},{"name":"feeRecipient","type":"address","internalType":"address"},{"name":"gasLimit","type":"uint64","internalType":"uint64"},{"name":"random","type":"bytes32","internalType":"bytes32"},{"name":"withdrawals","type":"tuple[]","internalType":"struct Suave.Withdrawal[]","components":[{"name":"index","type":"uint64","internalType":"uint64"},{"name":"validator","type":"uint64","internalType":"uint64"},{"name":"Address","type":"address","internalType":"address"},{"name":"amount","type":"uint64","internalType":"uint64"}]},{"name":"extra","type":"bytes","internalType":"bytes"}]},{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"},{"name":"output2","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialInputs","outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialRetrieve","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialStore","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"key","type":"string","internalType":"string"},{"name":"data1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"doHTTPRequest","inputs":[{"name":"request","type":"tuple","internalType":"struct Suave.HttpRequest","components":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"headers","type":"string[]","internalType":"string[]"},{"name":"body","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"response","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"ethcall","inputs":[{"name":"contractAddr","type":"address","internalType":"address"},{"name":"input1","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"extractHint","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"fetchBids","inputs":[{"name":"cond","type":"uint64","internalType":"uint64"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"bid","type":"tuple[]","internalType":"struct Suave.Bid[]","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"fillMevShareBundle","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"}],"outputs":[{"name":"encodedBundle","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"newBid","inputs":[{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"bidType","type":"string","internalType":"string"}],"outputs":[{"name":"bid","type":"tuple","internalType":"struct Suave.Bid","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"signEthTransaction","inputs":[{"name":"txn","type":"bytes","internalType":"bytes"},{"name":"chainId","type":"string","internalType":"string"},{"name":"signingKey","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"simulateBundle","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"uint64","internalType":"uint64"}]},{"type":"function","name":"submitBundleJsonRPC","inputs":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"params","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"submitEthBlockBidToRelay","inputs":[{"name":"relayUrl","type":"string","internalType":"string"},{"name":"builderBid","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]}] \ No newline at end of file +[{"type":"error","name":"PeekerReverted","inputs":[{"name":"addr","type":"address"},{"name":"err","type":"bytes"}]},{"type":"function","name":"buildEthBlock","inputs":[{"name":"blockArgs","type":"tuple","internalType":"struct Suave.BuildBlockArgs","components":[{"name":"slot","type":"uint64","internalType":"uint64"},{"name":"proposerPubkey","type":"bytes","internalType":"bytes"},{"name":"parent","type":"bytes32","internalType":"bytes32"},{"name":"timestamp","type":"uint64","internalType":"uint64"},{"name":"feeRecipient","type":"address","internalType":"address"},{"name":"gasLimit","type":"uint64","internalType":"uint64"},{"name":"random","type":"bytes32","internalType":"bytes32"},{"name":"withdrawals","type":"tuple[]","internalType":"struct Suave.Withdrawal[]","components":[{"name":"index","type":"uint64","internalType":"uint64"},{"name":"validator","type":"uint64","internalType":"uint64"},{"name":"Address","type":"address","internalType":"address"},{"name":"amount","type":"uint64","internalType":"uint64"}]},{"name":"extra","type":"bytes","internalType":"bytes"}]},{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"},{"name":"output2","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialInputs","outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialRetrieve","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialStore","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"key","type":"string","internalType":"string"},{"name":"data1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"doHTTPRequest","inputs":[{"name":"request","type":"tuple","internalType":"struct Suave.HttpRequest","components":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"headers","type":"string[]","internalType":"string[]"},{"name":"body","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"response","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"ethcall","inputs":[{"name":"contractAddr","type":"address","internalType":"address"},{"name":"input1","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"extractHint","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"fetchBids","inputs":[{"name":"cond","type":"uint64","internalType":"uint64"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"bid","type":"tuple[]","internalType":"struct Suave.Bid[]","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"fillMevShareBundle","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"}],"outputs":[{"name":"encodedBundle","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"newBid","inputs":[{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"bidType","type":"string","internalType":"string"}],"outputs":[{"name":"bid","type":"tuple","internalType":"struct Suave.Bid","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"newBuilder","outputs":[{"name":"id","type":"string","internalType":"string"}]},{"type":"function","name":"signEthTransaction","inputs":[{"name":"txn","type":"bytes","internalType":"bytes"},{"name":"chainId","type":"string","internalType":"string"},{"name":"signingKey","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"simulateBundle","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"uint64","internalType":"uint64"}]},{"type":"function","name":"simulateTransaction","inputs":[{"name":"session","type":"string","internalType":"string"},{"name":"txn","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"tuple","internalType":"struct Suave.SimulateTransactionResult","components":[{"name":"egp","type":"uint64","internalType":"uint64"},{"name":"logs","type":"tuple[]","internalType":"struct Suave.SimulatedLog[]","components":[{"name":"data","type":"bytes","internalType":"bytes"},{"name":"addr","type":"address","internalType":"address"},{"name":"topics","type":"bytes32[]","internalType":"bytes32[]"}]}]}]},{"type":"function","name":"submitBundleJsonRPC","inputs":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"params","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"submitEthBlockBidToRelay","inputs":[{"name":"relayUrl","type":"string","internalType":"string"},{"name":"builderBid","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]}] \ No newline at end of file diff --git a/suave/artifacts/addresses.go b/suave/artifacts/addresses.go index a8fcdca8e..df818450a 100644 --- a/suave/artifacts/addresses.go +++ b/suave/artifacts/addresses.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: cdc3a0bf9b0cd1b4a4b17e6245d7e942f42ffc5f7333bc11a220abbc67d6587b +// Hash: deab96918f508e1db1bae632f0afe06dfd000b4f96392df15ff78673eb8f1a14 package artifacts import ( @@ -18,8 +18,10 @@ var ( fetchBidsAddr = common.HexToAddress("0x0000000000000000000000000000000042030001") fillMevShareBundleAddr = common.HexToAddress("0x0000000000000000000000000000000043200001") newBidAddr = common.HexToAddress("0x0000000000000000000000000000000042030000") + newBuilderAddr = common.HexToAddress("0x0000000000000000000000000000000053200001") signEthTransactionAddr = common.HexToAddress("0x0000000000000000000000000000000040100001") simulateBundleAddr = common.HexToAddress("0x0000000000000000000000000000000042100000") + simulateTransactionAddr = common.HexToAddress("0x0000000000000000000000000000000053200002") submitBundleJsonRPCAddr = common.HexToAddress("0x0000000000000000000000000000000043000001") submitEthBlockBidToRelayAddr = common.HexToAddress("0x0000000000000000000000000000000042100002") ) @@ -35,8 +37,10 @@ var SuaveMethods = map[string]common.Address{ "fetchBids": fetchBidsAddr, "fillMevShareBundle": fillMevShareBundleAddr, "newBid": newBidAddr, + "newBuilder": newBuilderAddr, "signEthTransaction": signEthTransactionAddr, "simulateBundle": simulateBundleAddr, + "simulateTransaction": simulateTransactionAddr, "submitBundleJsonRPC": submitBundleJsonRPCAddr, "submitEthBlockBidToRelay": submitEthBlockBidToRelayAddr, } @@ -63,10 +67,14 @@ func PrecompileAddressToName(addr common.Address) string { return "fillMevShareBundle" case newBidAddr: return "newBid" + case newBuilderAddr: + return "newBuilder" case signEthTransactionAddr: return "signEthTransaction" case simulateBundleAddr: return "simulateBundle" + case simulateTransactionAddr: + return "simulateTransaction" case submitBundleJsonRPCAddr: return "submitBundleJsonRPC" case submitEthBlockBidToRelayAddr: diff --git a/suave/backends/eth_backends.go b/suave/backends/eth_backends.go index 98940e595..f76552a7a 100644 --- a/suave/backends/eth_backends.go +++ b/suave/backends/eth_backends.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" + builder "github.com/ethereum/go-ethereum/suave/builder/api" suave "github.com/ethereum/go-ethereum/suave/core" "github.com/ethereum/go-ethereum/trie" ) @@ -17,7 +18,9 @@ var ( _ EthBackend = &RemoteEthBackend{} ) -type EthMock struct{} +type EthMock struct { + *builder.MockServer +} func (e *EthMock) BuildEthBlock(ctx context.Context, args *suave.BuildBlockArgs, txs types.Transactions) (*engine.ExecutionPayloadEnvelope, error) { block := types.NewBlock(&types.Header{GasUsed: 1000}, txs, nil, nil, trie.NewStackTrie(nil)) @@ -40,15 +43,20 @@ func (e *EthMock) Call(ctx context.Context, contractAddr common.Address, input [ type RemoteEthBackend struct { endpoint string client *rpc.Client + + *builder.APIClient } func NewRemoteEthBackend(endpoint string) *RemoteEthBackend { - return &RemoteEthBackend{ + r := &RemoteEthBackend{ endpoint: endpoint, } + + r.APIClient = builder.NewClientFromRPC(r) + return r } -func (e *RemoteEthBackend) call(ctx context.Context, result interface{}, method string, args ...interface{}) error { +func (e *RemoteEthBackend) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { if e.client == nil { // should lock var err error @@ -72,21 +80,21 @@ func (e *RemoteEthBackend) call(ctx context.Context, result interface{}, method func (e *RemoteEthBackend) BuildEthBlock(ctx context.Context, args *suave.BuildBlockArgs, txs types.Transactions) (*engine.ExecutionPayloadEnvelope, error) { var result engine.ExecutionPayloadEnvelope - err := e.call(ctx, &result, "suavex_buildEthBlock", args, txs) + err := e.CallContext(ctx, &result, "suavex_buildEthBlock", args, txs) return &result, err } func (e *RemoteEthBackend) BuildEthBlockFromBundles(ctx context.Context, args *suave.BuildBlockArgs, bundles []types.SBundle) (*engine.ExecutionPayloadEnvelope, error) { var result engine.ExecutionPayloadEnvelope - err := e.call(ctx, &result, "suavex_buildEthBlockFromBundles", args, bundles) + err := e.CallContext(ctx, &result, "suavex_buildEthBlockFromBundles", args, bundles) return &result, err } func (e *RemoteEthBackend) Call(ctx context.Context, contractAddr common.Address, input []byte) ([]byte, error) { var result []byte - err := e.call(ctx, &result, "suavex_call", contractAddr, input) + err := e.CallContext(ctx, &result, "suavex_call", contractAddr, input) return result, err } diff --git a/suave/builder/api/api.go b/suave/builder/api/api.go index 4043a8c1b..048120e79 100644 --- a/suave/builder/api/api.go +++ b/suave/builder/api/api.go @@ -3,12 +3,10 @@ package api import ( "context" - "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/core/types" ) type API interface { NewSession(ctx context.Context) (string, error) - AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) error - Finalize(ctx context.Context, sessionId string) (*engine.ExecutionPayloadEnvelope, error) + AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) } diff --git a/suave/builder/api/api_client.go b/suave/builder/api/api_client.go index d5560ecbc..a8b934a95 100644 --- a/suave/builder/api/api_client.go +++ b/suave/builder/api/api_client.go @@ -3,7 +3,6 @@ package api import ( "context" - "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" ) @@ -11,7 +10,7 @@ import ( var _ API = (*APIClient)(nil) type APIClient struct { - rpc *rpc.Client + rpc rpcClient } func NewClient(endpoint string) (*APIClient, error) { @@ -22,23 +21,22 @@ func NewClient(endpoint string) (*APIClient, error) { return NewClientFromRPC(clt), nil } -func NewClientFromRPC(rpc *rpc.Client) *APIClient { +type rpcClient interface { + CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error +} + +func NewClientFromRPC(rpc rpcClient) *APIClient { return &APIClient{rpc: rpc} } func (a *APIClient) NewSession(ctx context.Context) (string, error) { var id string - err := a.rpc.CallContext(ctx, &id, "builder_newSession") + err := a.rpc.CallContext(ctx, &id, "suavex_newSession") return id, err } -func (a *APIClient) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) error { - err := a.rpc.CallContext(ctx, nil, "builder_addTransaction", sessionId, tx) - return err -} - -func (a *APIClient) Finalize(ctx context.Context, sessionId string) (*engine.ExecutionPayloadEnvelope, error) { - var res *engine.ExecutionPayloadEnvelope - err := a.rpc.CallContext(ctx, &res, "builder_finalize", sessionId) - return res, err +func (a *APIClient) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) { + var receipt *types.Receipt + err := a.rpc.CallContext(ctx, &receipt, "suavex_addTransaction", sessionId, tx) + return receipt, err } diff --git a/suave/builder/api/api_server.go b/suave/builder/api/api_server.go index 803b372c4..7c94d70b1 100644 --- a/suave/builder/api/api_server.go +++ b/suave/builder/api/api_server.go @@ -2,8 +2,8 @@ package api import ( "context" + "fmt" - "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/core/types" ) @@ -11,7 +11,6 @@ import ( type sessionManager interface { NewSession() (string, error) AddTransaction(sessionId string, tx *types.Transaction) (*types.Receipt, error) - Finalize(sessionId string) (*engine.ExecutionPayloadEnvelope, error) } func NewServer(s sessionManager) *Server { @@ -26,6 +25,7 @@ type Server struct { } func (s *Server) NewSession(ctx context.Context) (string, error) { + fmt.Println("__ NEW SESSION __") return s.sessionMngr.NewSession() } @@ -33,6 +33,14 @@ func (s *Server) AddTransaction(ctx context.Context, sessionId string, tx *types return s.sessionMngr.AddTransaction(sessionId, tx) } -func (s *Server) Finalize(ctx context.Context, sessionId string) (*engine.ExecutionPayloadEnvelope, error) { - return s.sessionMngr.Finalize(sessionId) +type MockServer struct { +} + +func (s *MockServer) NewSession(ctx context.Context) (string, error) { + fmt.Println("_ NEW SESSION 2 _") + return "", nil +} + +func (s *MockServer) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) { + return &types.Receipt{}, nil } diff --git a/suave/builder/api/api_test.go b/suave/builder/api/api_test.go index a7e108494..756657064 100644 --- a/suave/builder/api/api_test.go +++ b/suave/builder/api/api_test.go @@ -5,7 +5,6 @@ import ( "math/big" "testing" - "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" @@ -16,7 +15,7 @@ func TestAPI(t *testing.T) { srv := rpc.NewServer() builderAPI := NewServer(&nullSessionManager{}) - srv.RegisterName("builder", builderAPI) + srv.RegisterName("suavex", builderAPI) c := NewClientFromRPC(rpc.DialInProc(srv)) @@ -25,12 +24,8 @@ func TestAPI(t *testing.T) { require.Equal(t, res0, "1") txn := types.NewTransaction(0, common.Address{}, big.NewInt(1), 1, big.NewInt(1), []byte{}) - err = c.AddTransaction(context.Background(), "1", txn) + _, err = c.AddTransaction(context.Background(), "1", txn) require.NoError(t, err) - - res1, err := c.Finalize(context.Background(), "1") - require.NoError(t, err) - require.Equal(t, res1.BlockValue, big.NewInt(1)) } type nullSessionManager struct{} @@ -40,16 +35,5 @@ func (n *nullSessionManager) NewSession() (string, error) { } func (n *nullSessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.Receipt, error) { - return nil, nil -} - -func (n *nullSessionManager) Finalize(sessionId string) (*engine.ExecutionPayloadEnvelope, error) { - return &engine.ExecutionPayloadEnvelope{ - BlockValue: big.NewInt(1), - ExecutionPayload: &engine.ExecutableData{ - Number: 1, - BaseFeePerGas: big.NewInt(1), - Transactions: [][]byte{}, - }, - }, nil + return &types.Receipt{Logs: []*types.Log{}}, nil } diff --git a/suave/builder/builder.go b/suave/builder/builder.go index d2c81767c..daefd00c0 100644 --- a/suave/builder/builder.go +++ b/suave/builder/builder.go @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie" ) type builder struct { @@ -31,6 +30,8 @@ func newBuilder(config *builderConfig) *builder { gp := core.GasPool(config.header.GasLimit) var gasUsed uint64 + config.vmConfig.NoBaseFee = true + return &builder{ config: config, state: config.preState.Copy(), @@ -52,17 +53,3 @@ func (b *builder) AddTransaction(txn *types.Transaction) (*types.Receipt, error) return receipt, nil } - -func (b *builder) Finalize() (*types.Block, error) { - root, err := b.state.Commit(true) - if err != nil { - return nil, err - } - - header := b.config.header - header.Root = root - header.GasUsed = *b.gasUsed - - block := types.NewBlock(header, b.txns, nil, b.receipts, trie.NewStackTrie(nil)) - return block, nil -} diff --git a/suave/builder/builder_test.go b/suave/builder/builder_test.go index daf8ee08f..baed0cebe 100644 --- a/suave/builder/builder_test.go +++ b/suave/builder/builder_test.go @@ -1,18 +1,13 @@ package builder import ( - "crypto/ecdsa" "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" ) @@ -20,7 +15,7 @@ func TestBuilder_AddTxn_Simple(t *testing.T) { to := common.Address{0x01, 0x10, 0xab} mock := newMockBuilder(t) - txn := mock.newTransfer(t, to, big.NewInt(1)) + txn := mock.state.newTransfer(t, to, big.NewInt(1)) _, err := mock.builder.AddTransaction(txn) require.NoError(t, err) @@ -33,13 +28,6 @@ func TestBuilder_AddTxn_Simple(t *testing.T) { to: big.NewInt(1), }, }) - - block, err := mock.builder.Finalize() - require.NoError(t, err) - - require.Equal(t, uint64(21000), block.GasUsed()) - require.Len(t, block.Transactions(), 1) - require.Equal(t, txn.Hash(), block.Transactions()[0].Hash()) } func newMockBuilder(t *testing.T) *mockBuilder { @@ -51,44 +39,19 @@ func newMockBuilder(t *testing.T) *mockBuilder { Difficulty: big.NewInt(1), } - var stateRef *state.StateDB - - premineKey, _ := crypto.GenerateKey() // TODO: it would be nice to have it deterministic - premineKeyAddr := crypto.PubkeyToAddress(premineKey.PublicKey) - - // create a state reference with at least one premined account - // In order to test the statedb in isolation, we are going - // to commit this pre-state to a memory database - { - db := state.NewDatabase(rawdb.NewMemoryDatabase()) - preState, err := state.New(types.EmptyRootHash, db, nil) - require.NoError(t, err) - - preState.AddBalance(premineKeyAddr, big.NewInt(1000000000000000000)) - - root, err := preState.Commit(true) - require.NoError(t, err) - - stateRef, err = state.New(root, db, nil) - require.NoError(t, err) - } - - // for the sake of this test, we only need all the forks enabled - chainConfig := params.SuaveChainConfig - - // Disable london so that we do not check gasFeeCap (TODO: Fix) - chainConfig.LondonBlock = big.NewInt(100) + mState := newMockState(t) m := &mockBuilder{ - premineKey: premineKey, - premineKeyAddr: premineKeyAddr, - signer: types.NewEIP155Signer(chainConfig.ChainID), + state: mState, } + stateRef, err := mState.stateAt(mState.stateRoot) + require.NoError(t, err) + config := &builderConfig{ header: header, preState: stateRef, - config: chainConfig, + config: mState.chainConfig, context: m, // m implements ChainContext with panics vmConfig: vm.Config{}, } @@ -99,13 +62,7 @@ func newMockBuilder(t *testing.T) *mockBuilder { type mockBuilder struct { builder *builder - - // builtin private keys - premineKey *ecdsa.PrivateKey - premineKeyAddr common.Address - - nextNonce uint64 // figure out a better way - signer types.Signer + state *mockState } func (m *mockBuilder) Engine() consensus.Engine { @@ -116,29 +73,6 @@ func (m *mockBuilder) GetHeader(common.Hash, uint64) *types.Header { panic("TODO") } -func (m *mockBuilder) getNonce() uint64 { - next := m.nextNonce - m.nextNonce++ - return next -} - -func (m *mockBuilder) newTransfer(t *testing.T, to common.Address, amount *big.Int) *types.Transaction { - tx := types.NewTransaction(m.getNonce(), to, amount, 1000000, big.NewInt(1), nil) - return m.newTxn(t, tx) -} - -func (m *mockBuilder) newTxn(t *testing.T, tx *types.Transaction) *types.Transaction { - // sign the transaction - signature, err := crypto.Sign(m.signer.Hash(tx).Bytes(), m.premineKey) - require.NoError(t, err) - - // include the signature in the transaction - tx, err = tx.WithSignature(m.signer, signature) - require.NoError(t, err) - - return tx -} - type expectedResult struct { txns []*types.Transaction balances map[common.Address]*big.Int diff --git a/suave/builder/session_manager.go b/suave/builder/session_manager.go index 5b0f598b1..b75e0d51d 100644 --- a/suave/builder/session_manager.go +++ b/suave/builder/session_manager.go @@ -4,45 +4,57 @@ import ( "fmt" "math/big" "sync" + "time" - "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" "github.com/google/uuid" ) // blockchain is the minimum interface to the blockchain // required to build a block type blockchain interface { + core.ChainContext + // Header returns the current tip of the chain - Header() *types.Header + CurrentHeader() *types.Header // StateAt returns the state at the given root StateAt(root common.Hash) (*state.StateDB, error) + + // Config returns the chain config + Config() *params.ChainConfig } type Config struct { - GasCeil uint64 + GasCeil uint64 + SessionIdleTimeout time.Duration } type SessionManager struct { - sessions map[string]*builder - sessionsLock sync.RWMutex - blockchain blockchain - config *Config + sessions map[string]*builder + sessionTimers map[string]*time.Timer + sessionsLock sync.RWMutex + blockchain blockchain + config *Config } func NewSessionManager(blockchain blockchain, config *Config) *SessionManager { if config.GasCeil == 0 { config.GasCeil = 1000000000000000000 } + if config.SessionIdleTimeout == 0 { + config.SessionIdleTimeout = 5 * time.Second + } s := &SessionManager{ - sessions: make(map[string]*builder), - blockchain: blockchain, - config: config, + sessions: make(map[string]*builder), + sessionTimers: make(map[string]*time.Timer), + blockchain: blockchain, + config: config, } return s } @@ -52,7 +64,7 @@ func (s *SessionManager) NewSession() (string, error) { s.sessionsLock.Lock() defer s.sessionsLock.Unlock() - parent := s.blockchain.Header() + parent := s.blockchain.CurrentHeader() header := &types.Header{ ParentHash: parent.Hash(), @@ -60,6 +72,7 @@ func (s *SessionManager) NewSession() (string, error) { GasLimit: core.CalcGasLimit(parent.GasLimit, s.config.GasCeil), Time: 1000, // TODO: fix this Coinbase: common.Address{}, // TODO: fix this + Difficulty: big.NewInt(1), } stateRef, err := s.blockchain.StateAt(parent.Root) @@ -70,11 +83,22 @@ func (s *SessionManager) NewSession() (string, error) { cfg := &builderConfig{ preState: stateRef, header: header, + config: s.blockchain.Config(), + context: s.blockchain, } id := uuid.New().String()[:7] s.sessions[id] = newBuilder(cfg) + // start session timer + s.sessionTimers[id] = time.AfterFunc(s.config.SessionIdleTimeout, func() { + s.sessionsLock.Lock() + defer s.sessionsLock.Unlock() + + delete(s.sessions, id) + delete(s.sessionTimers, id) + }) + return id, nil } @@ -86,6 +110,10 @@ func (s *SessionManager) getSession(sessionId string) (*builder, error) { if !ok { return nil, fmt.Errorf("session %s not found", sessionId) } + + // reset session timer + s.sessionTimers[sessionId].Reset(s.config.SessionIdleTimeout) + return session, nil } @@ -96,44 +124,3 @@ func (s *SessionManager) AddTransaction(sessionId string, tx *types.Transaction) } return builder.AddTransaction(tx) } - -func (s *SessionManager) Finalize(sessionId string) (*engine.ExecutionPayloadEnvelope, error) { - builder, err := s.getSession(sessionId) - if err != nil { - return nil, err - } - - block, err := builder.Finalize() - if err != nil { - return nil, err - } - data := &engine.ExecutableData{ - ParentHash: block.ParentHash(), - Number: block.Number().Uint64(), - GasLimit: block.GasLimit(), - GasUsed: block.GasUsed(), - LogsBloom: block.Bloom().Bytes(), - ReceiptsRoot: block.ReceiptHash(), - BlockHash: block.Hash(), - StateRoot: block.Root(), - Timestamp: block.Time(), - ExtraData: block.Extra(), - BaseFeePerGas: &big.Int{}, // TODO - Transactions: [][]byte{}, - } - - // convert transactions to bytes - for _, txn := range block.Transactions() { - txnData, err := txn.MarshalBinary() - if err != nil { - return nil, err - } - data.Transactions = append(data.Transactions, txnData) - } - - payload := &engine.ExecutionPayloadEnvelope{ - BlockValue: big.NewInt(0), // TODO - ExecutionPayload: data, - } - return payload, nil -} diff --git a/suave/builder/session_manager_test.go b/suave/builder/session_manager_test.go new file mode 100644 index 000000000..0c8f2072b --- /dev/null +++ b/suave/builder/session_manager_test.go @@ -0,0 +1,183 @@ +package builder + +import ( + "crypto/ecdsa" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" +) + +func TestSessionManager_SessionTimeout(t *testing.T) { + mngr, _ := newSessionManager(t, &Config{ + SessionIdleTimeout: 500 * time.Millisecond, + }) + + id, err := mngr.NewSession() + require.NoError(t, err) + + time.Sleep(1 * time.Second) + + _, err = mngr.getSession(id) + require.Error(t, err) +} + +func TestSessionManager_SessionRefresh(t *testing.T) { + mngr, _ := newSessionManager(t, &Config{ + SessionIdleTimeout: 500 * time.Millisecond, + }) + + id, err := mngr.NewSession() + require.NoError(t, err) + + // if we query the session under the idle timeout, + // we should be able to refresh it + for i := 0; i < 5; i++ { + time.Sleep(250 * time.Millisecond) + + _, err = mngr.getSession(id) + require.NoError(t, err) + } + + // if we query the session after the idle timeout, + // we should get an error + + time.Sleep(1 * time.Second) + + _, err = mngr.getSession(id) + require.Error(t, err) +} + +func TestSessionManager_StartSession(t *testing.T) { + // test that the session starts and it can simulate transactions + mngr, bMock := newSessionManager(t, &Config{}) + + id, err := mngr.NewSession() + require.NoError(t, err) + + txn := bMock.state.newTransfer(t, common.Address{}, big.NewInt(1)) + receipt, err := mngr.AddTransaction(id, txn) + require.NoError(t, err) + require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) +} + +func newSessionManager(t *testing.T, cfg *Config) (*SessionManager, *blockchainMock) { + if cfg == nil { + cfg = &Config{} + } + + state := newMockState(t) + + bMock := &blockchainMock{ + state: state, + } + return NewSessionManager(bMock, cfg), bMock +} + +type blockchainMock struct { + state *mockState +} + +func (b *blockchainMock) Engine() consensus.Engine { + panic("TODO") +} + +func (b *blockchainMock) GetHeader(common.Hash, uint64) *types.Header { + panic("TODO") +} + +func (b *blockchainMock) Config() *params.ChainConfig { + return b.state.chainConfig +} + +func (b *blockchainMock) CurrentHeader() *types.Header { + return &types.Header{ + Number: big.NewInt(1), + Difficulty: big.NewInt(1), + Root: b.state.stateRoot, + } +} + +func (b *blockchainMock) StateAt(root common.Hash) (*state.StateDB, error) { + return b.state.stateAt(root) +} + +type mockState struct { + stateRoot common.Hash + statedb state.Database + + premineKey *ecdsa.PrivateKey + premineKeyAdd common.Address + + nextNonce uint64 // figure out a better way + signer types.Signer + + chainConfig *params.ChainConfig +} + +func newMockState(t *testing.T) *mockState { + premineKey, _ := crypto.GenerateKey() // TODO: it would be nice to have it deterministic + premineKeyAddr := crypto.PubkeyToAddress(premineKey.PublicKey) + + // create a state reference with at least one premined account + // In order to test the statedb in isolation, we are going + // to commit this pre-state to a memory database + db := state.NewDatabase(rawdb.NewMemoryDatabase()) + preState, err := state.New(types.EmptyRootHash, db, nil) + require.NoError(t, err) + + preState.AddBalance(premineKeyAddr, big.NewInt(1000000000000000000)) + + root, err := preState.Commit(true) + require.NoError(t, err) + + // for the sake of this test, we only need all the forks enabled + chainConfig := params.SuaveChainConfig + + // Disable london so that we do not check gasFeeCap (TODO: Fix) + chainConfig.LondonBlock = big.NewInt(100) + + return &mockState{ + statedb: db, + stateRoot: root, + premineKey: premineKey, + premineKeyAdd: premineKeyAddr, + signer: types.NewEIP155Signer(chainConfig.ChainID), + chainConfig: chainConfig, + } +} + +func (m *mockState) stateAt(root common.Hash) (*state.StateDB, error) { + return state.New(root, m.statedb, nil) +} + +func (m *mockState) getNonce() uint64 { + next := m.nextNonce + m.nextNonce++ + return next +} + +func (m *mockState) newTransfer(t *testing.T, to common.Address, amount *big.Int) *types.Transaction { + tx := types.NewTransaction(m.getNonce(), to, amount, 1000000, big.NewInt(1), nil) + return m.newTxn(t, tx) +} + +func (m *mockState) newTxn(t *testing.T, tx *types.Transaction) *types.Transaction { + // sign the transaction + signature, err := crypto.Sign(m.signer.Hash(tx).Bytes(), m.premineKey) + require.NoError(t, err) + + // include the signature in the transaction + tx, err = tx.WithSignature(m.signer, signature) + require.NoError(t, err) + + return tx +} diff --git a/suave/core/types.go b/suave/core/types.go index e21605103..2d3b34cf9 100644 --- a/suave/core/types.go +++ b/suave/core/types.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/node" + builder "github.com/ethereum/go-ethereum/suave/builder/api" ) var AllowedPeekerAny = common.HexToAddress("0xC8df3686b4Afb2BB53e60EAe97EF043FE03Fb829") // "*" @@ -64,4 +65,6 @@ type ConfidentialEthBackend interface { BuildEthBlock(ctx context.Context, args *BuildBlockArgs, txs types.Transactions) (*engine.ExecutionPayloadEnvelope, error) BuildEthBlockFromBundles(ctx context.Context, args *BuildBlockArgs, bundles []types.SBundle) (*engine.ExecutionPayloadEnvelope, error) Call(ctx context.Context, contractAddr common.Address, input []byte) ([]byte, error) + + builder.API } diff --git a/suave/e2e/workflow_test.go b/suave/e2e/workflow_test.go index f78a800fb..144ec9adf 100644 --- a/suave/e2e/workflow_test.go +++ b/suave/e2e/workflow_test.go @@ -1176,6 +1176,25 @@ func TestE2ERemoteCalls(t *testing.T) { }) } +func TestE2EPrecompile_Builder(t *testing.T) { + fr := newFramework(t, WithKettleAddress()) + defer fr.Close() + + clt := fr.NewSDKClient() + + // TODO: We do this all the time, unify in a single function? + contractAddr := common.Address{0x3} + sourceContract := sdk.GetContract(contractAddr, exampleCallSourceContract.Abi, clt) + + // build a txn that calls the contract 'func1' in 'ExampleEthCallTarget' + subTxn, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + To: &testAddr3, + }), signer, testKey) + + subTxnBytes, _ := subTxn.MarshalBinary() + sourceContract.SendTransaction("sessionE2ETest", []interface{}{subTxnBytes}, nil) +} + type clientWrapper struct { t *testing.T diff --git a/suave/gen/suave_spec.yaml b/suave/gen/suave_spec.yaml index 15a411dfe..dccb1aee3 100644 --- a/suave/gen/suave_spec.yaml +++ b/suave/gen/suave_spec.yaml @@ -56,6 +56,20 @@ structs: type: string[] - name: body type: bytes + - name: SimulateTransactionResult + fields: + - name: egp + type: uint64 + - name: logs + type: SimulatedLog[] + - name: SimulatedLog + fields: + - name: data + type: bytes + - name: addr + type: address + - name: topics + type: bytes32[] functions: - name: confidentialInputs address: "0x0000000000000000000000000000000042010001" @@ -218,3 +232,20 @@ functions: fields: - name: response type: bytes + - name: newBuilder + address: "0x0000000000000000000000000000000053200001" + output: + fields: + - name: id + type: string + - name: simulateTransaction + address: "0x0000000000000000000000000000000053200002" + input: + - name: session + type: string + - name: txn + type: bytes + output: + fields: + - name: output1 + type: SimulateTransactionResult \ No newline at end of file diff --git a/suave/sol/libraries/Suave.sol b/suave/sol/libraries/Suave.sol index 098752750..49f999b53 100644 --- a/suave/sol/libraries/Suave.sol +++ b/suave/sol/libraries/Suave.sol @@ -34,6 +34,17 @@ library Suave { bytes body; } + struct SimulateTransactionResult { + uint64 egp; + SimulatedLog[] logs; + } + + struct SimulatedLog { + bytes data; + address addr; + bytes32[] topics; + } + struct Withdrawal { uint64 index; uint64 validator; @@ -65,10 +76,14 @@ library Suave { address public constant NEW_BID = 0x0000000000000000000000000000000042030000; + address public constant NEW_BUILDER = 0x0000000000000000000000000000000053200001; + address public constant SIGN_ETH_TRANSACTION = 0x0000000000000000000000000000000040100001; address public constant SIMULATE_BUNDLE = 0x0000000000000000000000000000000042100000; + address public constant SIMULATE_TRANSACTION = 0x0000000000000000000000000000000053200002; + address public constant SUBMIT_BUNDLE_JSON_RPC = 0x0000000000000000000000000000000043000001; address public constant SUBMIT_ETH_BLOCK_BID_TO_RELAY = 0x0000000000000000000000000000000042100002; @@ -187,6 +202,15 @@ library Suave { return abi.decode(data, (Bid)); } + function newBuilder() internal view returns (string memory) { + (bool success, bytes memory data) = NEW_BUILDER.staticcall(abi.encode()); + if (!success) { + revert PeekerReverted(NEW_BUILDER, data); + } + + return abi.decode(data, (string)); + } + function signEthTransaction(bytes memory txn, string memory chainId, string memory signingKey) internal view @@ -209,6 +233,19 @@ library Suave { return abi.decode(data, (uint64)); } + function simulateTransaction(string memory session, bytes memory txn) + internal + view + returns (SimulateTransactionResult memory) + { + (bool success, bytes memory data) = SIMULATE_TRANSACTION.staticcall(abi.encode(session, txn)); + if (!success) { + revert PeekerReverted(SIMULATE_TRANSACTION, data); + } + + return abi.decode(data, (SimulateTransactionResult)); + } + function submitBundleJsonRPC(string memory url, string memory method, bytes memory params) internal view diff --git a/suave/sol/libraries/SuaveForge.sol b/suave/sol/libraries/SuaveForge.sol index 4f3bbc4cc..86795b219 100644 --- a/suave/sol/libraries/SuaveForge.sol +++ b/suave/sol/libraries/SuaveForge.sol @@ -107,6 +107,12 @@ library SuaveForge { return abi.decode(data, (Suave.Bid)); } + function newBuilder() internal view returns (string memory) { + bytes memory data = forgeIt("0x0000000000000000000000000000000053200001", abi.encode()); + + return abi.decode(data, (string)); + } + function signEthTransaction(bytes memory txn, string memory chainId, string memory signingKey) internal view @@ -123,6 +129,16 @@ library SuaveForge { return abi.decode(data, (uint64)); } + function simulateTransaction(string memory session, bytes memory txn) + internal + view + returns (Suave.SimulateTransactionResult memory) + { + bytes memory data = forgeIt("0x0000000000000000000000000000000053200002", abi.encode(session, txn)); + + return abi.decode(data, (Suave.SimulateTransactionResult)); + } + function submitBundleJsonRPC(string memory url, string memory method, bytes memory params) internal view diff --git a/suave/sol/standard_peekers/example.sol b/suave/sol/standard_peekers/example.sol index 0e03fff71..5cc428b0d 100644 --- a/suave/sol/standard_peekers/example.sol +++ b/suave/sol/standard_peekers/example.sol @@ -18,10 +18,23 @@ contract ExampleEthCallSource { function remoteCall(Suave.HttpRequest memory request) public { Suave.doHTTPRequest(request); } + + function sessionE2ETest(bytes memory subTxn) public payable { + string memory id = Suave.newBuilder(); + Suave.simulateTransaction(id, subTxn); + } } contract ExampleEthCallTarget { function get() public view returns (uint256) { return 101; } + + event Example ( + uint256 num + ); + + function func1() public payable { + emit Example(1); + } } From 0007401782961b2bffc4bfb80bd4cb5ed8771e8d Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 19 Dec 2023 11:04:55 +0000 Subject: [PATCH 4/5] Add precompiles + test --- core/types/suave_structs.go | 8 +++-- core/vm/contracts_suave.go | 17 ++-------- core/vm/contracts_suave_runtime_adapter.go | 2 +- core/vm/contracts_suave_test.go | 4 +-- suave/artifacts/SuaveLib.json | 2 +- suave/artifacts/addresses.go | 2 +- suave/builder/api/api.go | 2 +- suave/builder/api/api_client.go | 4 +-- suave/builder/api/api_server.go | 11 +++---- suave/builder/api/api_test.go | 4 +-- suave/builder/builder.go | 36 +++++++++++++++++----- suave/builder/builder_test.go | 2 -- suave/builder/session_manager.go | 14 ++++++++- suave/builder/session_manager_test.go | 2 +- suave/e2e/workflow_test.go | 22 ++++++++++--- suave/gen/suave_spec.yaml | 4 +++ suave/sol/libraries/Suave.sol | 2 ++ suave/sol/standard_peekers/example.sol | 29 +++++++++++++++-- 18 files changed, 113 insertions(+), 54 deletions(-) diff --git a/core/types/suave_structs.go b/core/types/suave_structs.go index 8d04039d3..200b1bafd 100755 --- a/core/types/suave_structs.go +++ b/core/types/suave_structs.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: deab96918f508e1db1bae632f0afe06dfd000b4f96392df15ff78673eb8f1a14 +// Hash: 343e8850910a6ab3575037c3545ce16def559866d8918dad8436973646ed5b62 package types import "github.com/ethereum/go-ethereum/common" @@ -37,8 +37,10 @@ type HttpRequest struct { } type SimulateTransactionResult struct { - Egp uint64 - Logs []*SimulatedLog + Egp uint64 + Logs []*SimulatedLog + Success bool + Error string } type SimulatedLog struct { diff --git a/core/vm/contracts_suave.go b/core/vm/contracts_suave.go index 30eb64fdc..ce541db3d 100644 --- a/core/vm/contracts_suave.go +++ b/core/vm/contracts_suave.go @@ -199,7 +199,6 @@ func (s *suaveRuntime) doHTTPRequest(request types.HttpRequest) ([]byte, error) } func (s *suaveRuntime) newBuilder() (string, error) { - fmt.Println("-- new builder --") return s.suaveContext.Backend.ConfidentialEthBackend.NewSession(context.Background()) } @@ -209,21 +208,9 @@ func (s *suaveRuntime) simulateTransaction(session string, txnBytes []byte) (typ return types.SimulateTransactionResult{}, err } - receipt, err := s.suaveContext.Backend.ConfidentialEthBackend.AddTransaction(context.Background(), session, txn) + result, err := s.suaveContext.Backend.ConfidentialEthBackend.AddTransaction(context.Background(), session, txn) if err != nil { return types.SimulateTransactionResult{}, err } - - res := types.SimulateTransactionResult{ - Logs: []*types.SimulatedLog{}, - } - for _, log := range receipt.Logs { - res.Logs = append(res.Logs, &types.SimulatedLog{ - Addr: log.Address, - Topics: log.Topics, - Data: log.Data, - }) - } - - return res, nil + return *result, nil } diff --git a/core/vm/contracts_suave_runtime_adapter.go b/core/vm/contracts_suave_runtime_adapter.go index 1c8e47eed..16d49c8e6 100644 --- a/core/vm/contracts_suave_runtime_adapter.go +++ b/core/vm/contracts_suave_runtime_adapter.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: deab96918f508e1db1bae632f0afe06dfd000b4f96392df15ff78673eb8f1a14 +// Hash: 343e8850910a6ab3575037c3545ce16def559866d8918dad8436973646ed5b62 package vm import ( diff --git a/core/vm/contracts_suave_test.go b/core/vm/contracts_suave_test.go index 4bca8f6c5..67b122112 100644 --- a/core/vm/contracts_suave_test.go +++ b/core/vm/contracts_suave_test.go @@ -24,8 +24,8 @@ func (m *mockSuaveBackend) NewSession(ctx context.Context) (string, error) { return "", nil } -func (m *mockSuaveBackend) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) { - return &types.Receipt{}, nil +func (m *mockSuaveBackend) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) { + return &types.SimulateTransactionResult{}, nil } func (m *mockSuaveBackend) InitializeBid(bid suave.Bid) error { diff --git a/suave/artifacts/SuaveLib.json b/suave/artifacts/SuaveLib.json index 874ef9865..00bac6e34 100644 --- a/suave/artifacts/SuaveLib.json +++ b/suave/artifacts/SuaveLib.json @@ -1 +1 @@ -[{"type":"error","name":"PeekerReverted","inputs":[{"name":"addr","type":"address"},{"name":"err","type":"bytes"}]},{"type":"function","name":"buildEthBlock","inputs":[{"name":"blockArgs","type":"tuple","internalType":"struct Suave.BuildBlockArgs","components":[{"name":"slot","type":"uint64","internalType":"uint64"},{"name":"proposerPubkey","type":"bytes","internalType":"bytes"},{"name":"parent","type":"bytes32","internalType":"bytes32"},{"name":"timestamp","type":"uint64","internalType":"uint64"},{"name":"feeRecipient","type":"address","internalType":"address"},{"name":"gasLimit","type":"uint64","internalType":"uint64"},{"name":"random","type":"bytes32","internalType":"bytes32"},{"name":"withdrawals","type":"tuple[]","internalType":"struct Suave.Withdrawal[]","components":[{"name":"index","type":"uint64","internalType":"uint64"},{"name":"validator","type":"uint64","internalType":"uint64"},{"name":"Address","type":"address","internalType":"address"},{"name":"amount","type":"uint64","internalType":"uint64"}]},{"name":"extra","type":"bytes","internalType":"bytes"}]},{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"},{"name":"output2","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialInputs","outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialRetrieve","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialStore","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"key","type":"string","internalType":"string"},{"name":"data1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"doHTTPRequest","inputs":[{"name":"request","type":"tuple","internalType":"struct Suave.HttpRequest","components":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"headers","type":"string[]","internalType":"string[]"},{"name":"body","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"response","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"ethcall","inputs":[{"name":"contractAddr","type":"address","internalType":"address"},{"name":"input1","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"extractHint","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"fetchBids","inputs":[{"name":"cond","type":"uint64","internalType":"uint64"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"bid","type":"tuple[]","internalType":"struct Suave.Bid[]","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"fillMevShareBundle","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"}],"outputs":[{"name":"encodedBundle","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"newBid","inputs":[{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"bidType","type":"string","internalType":"string"}],"outputs":[{"name":"bid","type":"tuple","internalType":"struct Suave.Bid","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"newBuilder","outputs":[{"name":"id","type":"string","internalType":"string"}]},{"type":"function","name":"signEthTransaction","inputs":[{"name":"txn","type":"bytes","internalType":"bytes"},{"name":"chainId","type":"string","internalType":"string"},{"name":"signingKey","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"simulateBundle","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"uint64","internalType":"uint64"}]},{"type":"function","name":"simulateTransaction","inputs":[{"name":"session","type":"string","internalType":"string"},{"name":"txn","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"tuple","internalType":"struct Suave.SimulateTransactionResult","components":[{"name":"egp","type":"uint64","internalType":"uint64"},{"name":"logs","type":"tuple[]","internalType":"struct Suave.SimulatedLog[]","components":[{"name":"data","type":"bytes","internalType":"bytes"},{"name":"addr","type":"address","internalType":"address"},{"name":"topics","type":"bytes32[]","internalType":"bytes32[]"}]}]}]},{"type":"function","name":"submitBundleJsonRPC","inputs":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"params","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"submitEthBlockBidToRelay","inputs":[{"name":"relayUrl","type":"string","internalType":"string"},{"name":"builderBid","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]}] \ No newline at end of file +[{"type":"error","name":"PeekerReverted","inputs":[{"name":"addr","type":"address"},{"name":"err","type":"bytes"}]},{"type":"function","name":"buildEthBlock","inputs":[{"name":"blockArgs","type":"tuple","internalType":"struct Suave.BuildBlockArgs","components":[{"name":"slot","type":"uint64","internalType":"uint64"},{"name":"proposerPubkey","type":"bytes","internalType":"bytes"},{"name":"parent","type":"bytes32","internalType":"bytes32"},{"name":"timestamp","type":"uint64","internalType":"uint64"},{"name":"feeRecipient","type":"address","internalType":"address"},{"name":"gasLimit","type":"uint64","internalType":"uint64"},{"name":"random","type":"bytes32","internalType":"bytes32"},{"name":"withdrawals","type":"tuple[]","internalType":"struct Suave.Withdrawal[]","components":[{"name":"index","type":"uint64","internalType":"uint64"},{"name":"validator","type":"uint64","internalType":"uint64"},{"name":"Address","type":"address","internalType":"address"},{"name":"amount","type":"uint64","internalType":"uint64"}]},{"name":"extra","type":"bytes","internalType":"bytes"}]},{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"},{"name":"output2","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialInputs","outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialRetrieve","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialStore","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"key","type":"string","internalType":"string"},{"name":"data1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"doHTTPRequest","inputs":[{"name":"request","type":"tuple","internalType":"struct Suave.HttpRequest","components":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"headers","type":"string[]","internalType":"string[]"},{"name":"body","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"response","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"ethcall","inputs":[{"name":"contractAddr","type":"address","internalType":"address"},{"name":"input1","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"extractHint","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"fetchBids","inputs":[{"name":"cond","type":"uint64","internalType":"uint64"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"bid","type":"tuple[]","internalType":"struct Suave.Bid[]","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"fillMevShareBundle","inputs":[{"name":"bidId","type":"bytes16","internalType":"struct Suave.BidId"}],"outputs":[{"name":"encodedBundle","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"newBid","inputs":[{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"bidType","type":"string","internalType":"string"}],"outputs":[{"name":"bid","type":"tuple","internalType":"struct Suave.Bid","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.BidId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"newBuilder","outputs":[{"name":"id","type":"string","internalType":"string"}]},{"type":"function","name":"signEthTransaction","inputs":[{"name":"txn","type":"bytes","internalType":"bytes"},{"name":"chainId","type":"string","internalType":"string"},{"name":"signingKey","type":"string","internalType":"string"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"simulateBundle","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"uint64","internalType":"uint64"}]},{"type":"function","name":"simulateTransaction","inputs":[{"name":"session","type":"string","internalType":"string"},{"name":"txn","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"tuple","internalType":"struct Suave.SimulateTransactionResult","components":[{"name":"egp","type":"uint64","internalType":"uint64"},{"name":"logs","type":"tuple[]","internalType":"struct Suave.SimulatedLog[]","components":[{"name":"data","type":"bytes","internalType":"bytes"},{"name":"addr","type":"address","internalType":"address"},{"name":"topics","type":"bytes32[]","internalType":"bytes32[]"}]},{"name":"success","type":"bool","internalType":"bool"},{"name":"error","type":"string","internalType":"string"}]}]},{"type":"function","name":"submitBundleJsonRPC","inputs":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"params","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"submitEthBlockBidToRelay","inputs":[{"name":"relayUrl","type":"string","internalType":"string"},{"name":"builderBid","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"output1","type":"bytes","internalType":"bytes"}]}] \ No newline at end of file diff --git a/suave/artifacts/addresses.go b/suave/artifacts/addresses.go index df818450a..a5198fc70 100644 --- a/suave/artifacts/addresses.go +++ b/suave/artifacts/addresses.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: deab96918f508e1db1bae632f0afe06dfd000b4f96392df15ff78673eb8f1a14 +// Hash: 343e8850910a6ab3575037c3545ce16def559866d8918dad8436973646ed5b62 package artifacts import ( diff --git a/suave/builder/api/api.go b/suave/builder/api/api.go index 048120e79..b11d237ba 100644 --- a/suave/builder/api/api.go +++ b/suave/builder/api/api.go @@ -8,5 +8,5 @@ import ( type API interface { NewSession(ctx context.Context) (string, error) - AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) + AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) } diff --git a/suave/builder/api/api_client.go b/suave/builder/api/api_client.go index a8b934a95..3baefde53 100644 --- a/suave/builder/api/api_client.go +++ b/suave/builder/api/api_client.go @@ -35,8 +35,8 @@ func (a *APIClient) NewSession(ctx context.Context) (string, error) { return id, err } -func (a *APIClient) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) { - var receipt *types.Receipt +func (a *APIClient) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) { + var receipt *types.SimulateTransactionResult err := a.rpc.CallContext(ctx, &receipt, "suavex_addTransaction", sessionId, tx) return receipt, err } diff --git a/suave/builder/api/api_server.go b/suave/builder/api/api_server.go index 7c94d70b1..7eb6daf79 100644 --- a/suave/builder/api/api_server.go +++ b/suave/builder/api/api_server.go @@ -2,7 +2,6 @@ package api import ( "context" - "fmt" "github.com/ethereum/go-ethereum/core/types" ) @@ -10,7 +9,7 @@ import ( // sessionManager is the backend that manages the session state of the builder API. type sessionManager interface { NewSession() (string, error) - AddTransaction(sessionId string, tx *types.Transaction) (*types.Receipt, error) + AddTransaction(sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) } func NewServer(s sessionManager) *Server { @@ -25,11 +24,10 @@ type Server struct { } func (s *Server) NewSession(ctx context.Context) (string, error) { - fmt.Println("__ NEW SESSION __") return s.sessionMngr.NewSession() } -func (s *Server) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) { +func (s *Server) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) { return s.sessionMngr.AddTransaction(sessionId, tx) } @@ -37,10 +35,9 @@ type MockServer struct { } func (s *MockServer) NewSession(ctx context.Context) (string, error) { - fmt.Println("_ NEW SESSION 2 _") return "", nil } -func (s *MockServer) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.Receipt, error) { - return &types.Receipt{}, nil +func (s *MockServer) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) { + return &types.SimulateTransactionResult{}, nil } diff --git a/suave/builder/api/api_test.go b/suave/builder/api/api_test.go index 756657064..51122eef0 100644 --- a/suave/builder/api/api_test.go +++ b/suave/builder/api/api_test.go @@ -34,6 +34,6 @@ func (n *nullSessionManager) NewSession() (string, error) { return "1", nil } -func (n *nullSessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.Receipt, error) { - return &types.Receipt{Logs: []*types.Log{}}, nil +func (n *nullSessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) { + return &types.SimulateTransactionResult{Logs: []*types.SimulatedLog{}}, nil } diff --git a/suave/builder/builder.go b/suave/builder/builder.go index daefd00c0..0d5c30ce9 100644 --- a/suave/builder/builder.go +++ b/suave/builder/builder.go @@ -23,15 +23,12 @@ type builderConfig struct { header *types.Header config *params.ChainConfig context core.ChainContext - vmConfig vm.Config } func newBuilder(config *builderConfig) *builder { gp := core.GasPool(config.header.GasLimit) var gasUsed uint64 - config.vmConfig.NoBaseFee = true - return &builder{ config: config, state: config.preState.Copy(), @@ -40,16 +37,41 @@ func newBuilder(config *builderConfig) *builder { } } -func (b *builder) AddTransaction(txn *types.Transaction) (*types.Receipt, error) { +func (b *builder) AddTransaction(txn *types.Transaction) (*types.SimulateTransactionResult, error) { dummyAuthor := common.Address{} - receipt, err := core.ApplyTransaction(b.config.config, b.config.context, &dummyAuthor, b.gasPool, b.state, b.config.header, txn, b.gasUsed, b.config.vmConfig) + vmConfig := vm.Config{ + NoBaseFee: true, + } + + snap := b.state.Snapshot() + + b.state.SetTxContext(txn.Hash(), len(b.txns)) + receipt, err := core.ApplyTransaction(b.config.config, b.config.context, &dummyAuthor, b.gasPool, b.state, b.config.header, txn, b.gasUsed, vmConfig) if err != nil { - return nil, err + b.state.RevertToSnapshot(snap) + + result := &types.SimulateTransactionResult{ + Success: false, + Error: err.Error(), + } + return result, nil } b.txns = append(b.txns, txn) b.receipts = append(b.receipts, receipt) - return receipt, nil + result := &types.SimulateTransactionResult{ + Success: true, + Logs: []*types.SimulatedLog{}, + } + for _, log := range receipt.Logs { + result.Logs = append(result.Logs, &types.SimulatedLog{ + Addr: log.Address, + Topics: log.Topics, + Data: log.Data, + }) + } + + return result, nil } diff --git a/suave/builder/builder_test.go b/suave/builder/builder_test.go index baed0cebe..0ceae209a 100644 --- a/suave/builder/builder_test.go +++ b/suave/builder/builder_test.go @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/stretchr/testify/require" ) @@ -53,7 +52,6 @@ func newMockBuilder(t *testing.T) *mockBuilder { preState: stateRef, config: mState.chainConfig, context: m, // m implements ChainContext with panics - vmConfig: vm.Config{}, } m.builder = newBuilder(config) diff --git a/suave/builder/session_manager.go b/suave/builder/session_manager.go index b75e0d51d..b3e740cbd 100644 --- a/suave/builder/session_manager.go +++ b/suave/builder/session_manager.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -66,6 +67,8 @@ func (s *SessionManager) NewSession() (string, error) { parent := s.blockchain.CurrentHeader() + chainConfig := s.blockchain.Config() + header := &types.Header{ ParentHash: parent.Hash(), Number: new(big.Int).Add(parent.Number, common.Big1), @@ -75,6 +78,15 @@ func (s *SessionManager) NewSession() (string, error) { Difficulty: big.NewInt(1), } + // Set baseFee and GasLimit if we are on an EIP-1559 chain + if chainConfig.IsLondon(header.Number) { + header.BaseFee = misc.CalcBaseFee(chainConfig, parent) + if !chainConfig.IsLondon(parent.Number) { + parentGasLimit := parent.GasLimit * chainConfig.ElasticityMultiplier() + header.GasLimit = core.CalcGasLimit(parentGasLimit, s.config.GasCeil) + } + } + stateRef, err := s.blockchain.StateAt(parent.Root) if err != nil { return "", err @@ -117,7 +129,7 @@ func (s *SessionManager) getSession(sessionId string) (*builder, error) { return session, nil } -func (s *SessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.Receipt, error) { +func (s *SessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) { builder, err := s.getSession(sessionId) if err != nil { return nil, err diff --git a/suave/builder/session_manager_test.go b/suave/builder/session_manager_test.go index 0c8f2072b..8d9dbaef4 100644 --- a/suave/builder/session_manager_test.go +++ b/suave/builder/session_manager_test.go @@ -66,7 +66,7 @@ func TestSessionManager_StartSession(t *testing.T) { txn := bMock.state.newTransfer(t, common.Address{}, big.NewInt(1)) receipt, err := mngr.AddTransaction(id, txn) require.NoError(t, err) - require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) + require.NotNil(t, receipt) } func newSessionManager(t *testing.T, cfg *Config) (*SessionManager, *blockchainMock) { diff --git a/suave/e2e/workflow_test.go b/suave/e2e/workflow_test.go index 144ec9adf..262e678d3 100644 --- a/suave/e2e/workflow_test.go +++ b/suave/e2e/workflow_test.go @@ -1187,12 +1187,24 @@ func TestE2EPrecompile_Builder(t *testing.T) { sourceContract := sdk.GetContract(contractAddr, exampleCallSourceContract.Abi, clt) // build a txn that calls the contract 'func1' in 'ExampleEthCallTarget' - subTxn, _ := types.SignTx(types.NewTx(&types.LegacyTx{ - To: &testAddr3, - }), signer, testKey) + var subTxns []*types.Transaction + for i := 0; i < 2; i++ { + subTxn, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + To: &testAddr3, + Gas: 220000, + GasPrice: big.NewInt(13), + Nonce: uint64(i), + Data: exampleCallTargetContract.Abi.Methods["func1"].ID, + }), signer, testKey) + + subTxns = append(subTxns, subTxn) + } + + subTxnBytes1, _ := subTxns[0].MarshalBinary() + subTxnBytes2, _ := subTxns[1].MarshalBinary() - subTxnBytes, _ := subTxn.MarshalBinary() - sourceContract.SendTransaction("sessionE2ETest", []interface{}{subTxnBytes}, nil) + _, err := sourceContract.SendTransaction("sessionE2ETest", []interface{}{subTxnBytes1, subTxnBytes2}, nil) + require.NoError(t, err) } type clientWrapper struct { diff --git a/suave/gen/suave_spec.yaml b/suave/gen/suave_spec.yaml index dccb1aee3..48dec2002 100644 --- a/suave/gen/suave_spec.yaml +++ b/suave/gen/suave_spec.yaml @@ -62,6 +62,10 @@ structs: type: uint64 - name: logs type: SimulatedLog[] + - name: success + type: bool + - name: error + type: string - name: SimulatedLog fields: - name: data diff --git a/suave/sol/libraries/Suave.sol b/suave/sol/libraries/Suave.sol index 49f999b53..0961b8177 100644 --- a/suave/sol/libraries/Suave.sol +++ b/suave/sol/libraries/Suave.sol @@ -37,6 +37,8 @@ library Suave { struct SimulateTransactionResult { uint64 egp; SimulatedLog[] logs; + bool success; + string error; } struct SimulatedLog { diff --git a/suave/sol/standard_peekers/example.sol b/suave/sol/standard_peekers/example.sol index 5cc428b0d..d7e3f83dd 100644 --- a/suave/sol/standard_peekers/example.sol +++ b/suave/sol/standard_peekers/example.sol @@ -19,13 +19,32 @@ contract ExampleEthCallSource { Suave.doHTTPRequest(request); } - function sessionE2ETest(bytes memory subTxn) public payable { + function emptyCallback() public payable { + } + + function sessionE2ETest(bytes memory subTxn, bytes memory subTxn2) public payable returns (bytes memory) { string memory id = Suave.newBuilder(); - Suave.simulateTransaction(id, subTxn); + + Suave.SimulateTransactionResult memory sim1 = Suave.simulateTransaction(id, subTxn); + require(sim1.success == true); + require(sim1.logs.length == 1); + + // simulate the same transaction again should fail because the nonce is the same + Suave.SimulateTransactionResult memory sim2 = Suave.simulateTransaction(id, subTxn); + require(sim2.success == false); + + // now, simulate the transaction with the correct nonce + Suave.SimulateTransactionResult memory sim3 = Suave.simulateTransaction(id, subTxn2); + require(sim3.success == true); + require(sim3.logs.length == 2); + + return abi.encodeWithSelector(this.emptyCallback.selector); } } contract ExampleEthCallTarget { + uint256 stateCount; + function get() public view returns (uint256) { return 101; } @@ -35,6 +54,10 @@ contract ExampleEthCallTarget { ); function func1() public payable { - emit Example(1); + stateCount++; + + for (uint256 i = 0; i < stateCount; i++) { + emit Example(1); + } } } From 5697eef082849edec498ca5c36b87659fc0ac9e5 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 19 Dec 2023 11:30:36 +0000 Subject: [PATCH 5/5] Apply correct format --- suave/sol/standard_peekers/example.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/suave/sol/standard_peekers/example.sol b/suave/sol/standard_peekers/example.sol index d7e3f83dd..f2bfeb84e 100644 --- a/suave/sol/standard_peekers/example.sol +++ b/suave/sol/standard_peekers/example.sol @@ -19,8 +19,7 @@ contract ExampleEthCallSource { Suave.doHTTPRequest(request); } - function emptyCallback() public payable { - } + function emptyCallback() public payable {} function sessionE2ETest(bytes memory subTxn, bytes memory subTxn2) public payable returns (bytes memory) { string memory id = Suave.newBuilder(); @@ -49,9 +48,7 @@ contract ExampleEthCallTarget { return 101; } - event Example ( - uint256 num - ); + event Example(uint256 num); function func1() public payable { stateCount++;