From 798d625a6de0b4785163fff171582da2cf0f36f8 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 21 Feb 2024 10:38:29 +0000 Subject: [PATCH] Improve things --- suave/builder/api/api.go | 31 ++-- suave/builder/api/gen_buildblockargs_json.go | 86 +++++++++ suave/builder/session_manager.go | 28 ++- suave/builder/session_manager_test.go | 180 +++++++------------ 4 files changed, 198 insertions(+), 127 deletions(-) create mode 100644 suave/builder/api/gen_buildblockargs_json.go diff --git a/suave/builder/api/api.go b/suave/builder/api/api.go index 846e079b7c..ac80674bb9 100644 --- a/suave/builder/api/api.go +++ b/suave/builder/api/api.go @@ -5,9 +5,12 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" ) +//go:generate go run github.com/fjl/gencodec -type BuildBlockArgs -field-override buildBlockArgsMarshaling -out gen_buildblockargs_json.go + type Bundle struct { BlockNumber *big.Int `json:"blockNumber,omitempty"` // if BlockNumber is set it must match DecryptionCondition! MaxBlock *big.Int `json:"maxBlock,omitempty"` @@ -17,16 +20,24 @@ type Bundle struct { } type BuildBlockArgs struct { - Slot uint64 - ProposerPubkey []byte - Parent common.Hash - Timestamp uint64 - FeeRecipient common.Address - GasLimit uint64 - Random common.Hash - Withdrawals []*types.Withdrawal - Extra []byte - FillPending bool + Slot uint64 `json:"slot"` + ProposerPubkey []byte `json:"proposerPubkey"` + Parent common.Hash `json:"parent"` + Timestamp uint64 `json:"timestamp"` + FeeRecipient common.Address `json:"feeRecipient"` + GasLimit uint64 `json:"gasLimit"` + Random common.Hash `json:"random"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + Extra []byte `json:"extra"` +} + +// field type overrides for gencodec +type buildBlockArgsMarshaling struct { + Slot hexutil.Uint64 + ProposerPubkey hexutil.Bytes + Timestamp hexutil.Uint64 + GasLimit hexutil.Uint64 + Extra hexutil.Bytes } type API interface { diff --git a/suave/builder/api/gen_buildblockargs_json.go b/suave/builder/api/gen_buildblockargs_json.go new file mode 100644 index 0000000000..6fb8d03d8d --- /dev/null +++ b/suave/builder/api/gen_buildblockargs_json.go @@ -0,0 +1,86 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package api + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*buildBlockArgsMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (b BuildBlockArgs) MarshalJSON() ([]byte, error) { + type BuildBlockArgs struct { + Slot hexutil.Uint64 `json:"slot"` + ProposerPubkey hexutil.Bytes `json:"proposerPubkey"` + Parent common.Hash `json:"parent"` + Timestamp hexutil.Uint64 `json:"timestamp"` + FeeRecipient common.Address `json:"feeRecipient"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + Random common.Hash `json:"random"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + Extra hexutil.Bytes `json:"extra"` + } + var enc BuildBlockArgs + enc.Slot = hexutil.Uint64(b.Slot) + enc.ProposerPubkey = b.ProposerPubkey + enc.Parent = b.Parent + enc.Timestamp = hexutil.Uint64(b.Timestamp) + enc.FeeRecipient = b.FeeRecipient + enc.GasLimit = hexutil.Uint64(b.GasLimit) + enc.Random = b.Random + enc.Withdrawals = b.Withdrawals + enc.Extra = b.Extra + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (b *BuildBlockArgs) UnmarshalJSON(input []byte) error { + type BuildBlockArgs struct { + Slot *hexutil.Uint64 `json:"slot"` + ProposerPubkey *hexutil.Bytes `json:"proposerPubkey"` + Parent *common.Hash `json:"parent"` + Timestamp *hexutil.Uint64 `json:"timestamp"` + FeeRecipient *common.Address `json:"feeRecipient"` + GasLimit *hexutil.Uint64 `json:"gasLimit"` + Random *common.Hash `json:"random"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + Extra *hexutil.Bytes `json:"extra"` + } + var dec BuildBlockArgs + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Slot != nil { + b.Slot = uint64(*dec.Slot) + } + if dec.ProposerPubkey != nil { + b.ProposerPubkey = *dec.ProposerPubkey + } + if dec.Parent != nil { + b.Parent = *dec.Parent + } + if dec.Timestamp != nil { + b.Timestamp = uint64(*dec.Timestamp) + } + if dec.FeeRecipient != nil { + b.FeeRecipient = *dec.FeeRecipient + } + if dec.GasLimit != nil { + b.GasLimit = uint64(*dec.GasLimit) + } + if dec.Random != nil { + b.Random = *dec.Random + } + if dec.Withdrawals != nil { + b.Withdrawals = dec.Withdrawals + } + if dec.Extra != nil { + b.Extra = *dec.Extra + } + return nil +} diff --git a/suave/builder/session_manager.go b/suave/builder/session_manager.go index d2c03f5596..e81543f8ce 100644 --- a/suave/builder/session_manager.go +++ b/suave/builder/session_manager.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" @@ -44,11 +45,12 @@ type SessionManager struct { sessions map[string]*miner.Builder sessionTimers map[string]*time.Timer sessionsLock sync.RWMutex - blockchain blockchain + blockchain *core.BlockChain + pool *txpool.TxPool config *Config } -func NewSessionManager(blockchain blockchain, config *Config) *SessionManager { +func NewSessionManager(blockchain *core.BlockChain, pool *txpool.TxPool, config *Config) *SessionManager { if config.GasCeil == 0 { config.GasCeil = 1000000000000000000 } @@ -70,12 +72,24 @@ func NewSessionManager(blockchain blockchain, config *Config) *SessionManager { sessionTimers: make(map[string]*time.Timer), blockchain: blockchain, config: config, + pool: pool, } return s } +func (s *SessionManager) BlockChain() *core.BlockChain { + return s.blockchain +} + +func (s *SessionManager) TxPool() *txpool.TxPool { + return s.pool +} + // NewSession creates a new builder session and returns the session id func (s *SessionManager) NewSession(ctx context.Context, args *api.BuildBlockArgs) (string, error) { + if args == nil { + return "", fmt.Errorf("args cannot be nil") + } // Wait for session to become available select { case <-s.sem: @@ -88,11 +102,17 @@ func (s *SessionManager) NewSession(ctx context.Context, args *api.BuildBlockArg builderCfg := &miner.BuilderConfig{ ChainConfig: s.blockchain.Config(), Engine: s.blockchain.Engine(), - // TODO + Chain: s.blockchain, + EthBackend: s, + GasCeil: s.config.GasCeil, } builderArgs := &miner.BuilderArgs{ - ParentHash: args.Parent, + ParentHash: args.Parent, + FeeRecipient: args.FeeRecipient, + ProposerPubkey: args.ProposerPubkey, + Extra: args.Extra, + Slot: args.Slot, } id := uuid.New().String()[:7] diff --git a/suave/builder/session_manager_test.go b/suave/builder/session_manager_test.go index 994c5e082f..533f342466 100644 --- a/suave/builder/session_manager_test.go +++ b/suave/builder/session_manager_test.go @@ -2,18 +2,22 @@ package builder import ( "context" - "crypto/ecdsa" "math/big" "testing" "time" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "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/ethereum/go-ethereum/suave/builder/api" "github.com/stretchr/testify/require" ) @@ -22,7 +26,9 @@ func TestSessionManager_SessionTimeout(t *testing.T) { SessionIdleTimeout: 500 * time.Millisecond, }) - id, err := mngr.NewSession(context.TODO(), nil) + args := &api.BuildBlockArgs{} + + id, err := mngr.NewSession(context.TODO(), args) require.NoError(t, err) time.Sleep(1 * time.Second) @@ -35,6 +41,7 @@ func TestSessionManager_MaxConcurrentSessions(t *testing.T) { t.Parallel() const d = time.Millisecond * 100 + args := &api.BuildBlockArgs{} mngr, _ := newSessionManager(t, &Config{ MaxConcurrentSessions: 1, @@ -42,7 +49,7 @@ func TestSessionManager_MaxConcurrentSessions(t *testing.T) { }) t.Run("SessionAvailable", func(t *testing.T) { - sess, err := mngr.NewSession(context.TODO(), nil) + sess, err := mngr.NewSession(context.TODO(), args) require.NoError(t, err) require.NotZero(t, sess) }) @@ -53,7 +60,7 @@ func TestSessionManager_MaxConcurrentSessions(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - sess, err := mngr.NewSession(ctx, nil) + sess, err := mngr.NewSession(ctx, args) require.Zero(t, sess) require.ErrorIs(t, err, context.Canceled) }) @@ -62,7 +69,7 @@ func TestSessionManager_MaxConcurrentSessions(t *testing.T) { time.Sleep(d) // Wait for the session to expire. // We should be able to open a session again. - sess, err := mngr.NewSession(context.TODO(), nil) + sess, err := mngr.NewSession(context.TODO(), args) require.NoError(t, err) require.NotZero(t, sess) }) @@ -73,7 +80,8 @@ func TestSessionManager_SessionRefresh(t *testing.T) { SessionIdleTimeout: 500 * time.Millisecond, }) - id, err := mngr.NewSession(context.TODO(), nil) + args := &api.BuildBlockArgs{} + id, err := mngr.NewSession(context.TODO(), args) require.NoError(t, err) // if we query the session under the idle timeout, @@ -98,124 +106,70 @@ 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(context.TODO(), nil) + args := &api.BuildBlockArgs{} + id, err := mngr.NewSession(context.TODO(), args) require.NoError(t, err) - txn := bMock.state.newTransfer(t, common.Address{}, big.NewInt(1)) + txn := bMock.newTransfer(t, common.Address{}, big.NewInt(1)) receipt, err := mngr.AddTransaction(id, txn) require.NoError(t, err) require.NotNil(t, receipt) } -func newSessionManager(t *testing.T, cfg *Config) (*SessionManager, *blockchainMock) { +func newSessionManager(t *testing.T, cfg *Config) (*SessionManager, *testBackend) { + backend := newTestBackend(t) + if cfg == nil { cfg = &Config{} } - - state := newMockState(t) - - bMock := &blockchainMock{ - state: state, - } - return NewSessionManager(bMock, cfg), bMock + return NewSessionManager(backend.chain, backend.pool, cfg), backend } -type blockchainMock struct { - state *mockState +var ( + testBankKey, _ = crypto.GenerateKey() + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) +) + +type testBackend struct { + chain *core.BlockChain + pool *txpool.TxPool } -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(1, true) - require.NoError(t, err) - - // for the sake of this test, we only need all the forks enabled - chainConfig := params.TestChainConfig - - // 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) - +func (tb *testBackend) newTransfer(t *testing.T, to common.Address, amount *big.Int) *types.Transaction { + gasPrice := big.NewInt(10 * params.InitialBaseFee) + tx, _ := types.SignTx(types.NewTransaction(tb.pool.Nonce(testBankAddress), to, amount, params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey) return tx } + +func newTestBackend(t *testing.T) *testBackend { + // code based on miner 'newTestWorker' + testTxPoolConfig := legacypool.DefaultConfig + testTxPoolConfig.Journal = "" + + var ( + db = rawdb.NewMemoryDatabase() + config = *params.AllCliqueProtocolChanges + ) + config.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine := clique.New(config.Clique, db) + + var gspec = &core.Genesis{ + Config: &config, + Alloc: core.GenesisAlloc{testBankAddress: {Balance: big.NewInt(1000000000000000000)}}, + } + + gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) + copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes()) + engine.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), testBankKey) + }) + + chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("core.NewBlockChain failed: %v", err) + } + pool := legacypool.New(testTxPoolConfig, chain) + txpool, _ := txpool.New(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), chain, []txpool.SubPool{pool}) + + return &testBackend{chain: chain, pool: txpool} +}