diff --git a/go/enclave/enclave.go b/go/enclave/enclave.go index 9a06d043f8..10a8d37d85 100644 --- a/go/enclave/enclave.go +++ b/go/enclave/enclave.go @@ -198,7 +198,7 @@ func NewEnclave( sharedSecretProcessor := components.NewSharedSecretProcessor(mgmtContractLib, attestationProvider, storage, logger) blockchain := ethblockchain.NewEthBlockchain(big.NewInt(config.ObscuroChainID), registry, storage, logger) - mempool, err := txpool.NewTxPool(blockchain) + mempool, err := txpool.NewTxPool(blockchain, config.MinGasPrice) if err != nil { logger.Crit("unable to init eth tx pool", log.ErrKey, err) } diff --git a/go/enclave/nodetype/sequencer.go b/go/enclave/nodetype/sequencer.go index b51fddb929..e981c0a054 100644 --- a/go/enclave/nodetype/sequencer.go +++ b/go/enclave/nodetype/sequencer.go @@ -185,9 +185,10 @@ func (s *sequencer) createNewHeadBatch(l1HeadBlock *common.L1Block, skipBatchIfE // todo (@stefan) - limit on receipts too limiter := limiters.NewBatchSizeLimiter(s.settings.MaxBatchSize) - pendingTransactions := s.mempool.PendingTransactions() // minor does not request tip enforcement + pendingTransactions := s.mempool.PendingTransactions() var transactions []*types.Transaction for _, group := range pendingTransactions { + // lazily resolve transactions until the batch runs out of space for _, lazyTx := range group { if tx := lazyTx.Resolve(); tx != nil { err = limiter.AcceptTransaction(tx.Tx) diff --git a/go/enclave/txpool/txpool.go b/go/enclave/txpool/txpool.go index 44fb2dba6e..1ed470a840 100644 --- a/go/enclave/txpool/txpool.go +++ b/go/enclave/txpool/txpool.go @@ -19,10 +19,11 @@ type TxPool struct { legacyPool *legacypool.LegacyPool pool *gethtxpool.TxPool blockchain *ethblockchain.EthBlockchain + gasTip *big.Int } // NewTxPool returns a new instance of the tx pool -func NewTxPool(blockchain *ethblockchain.EthBlockchain) (*TxPool, error) { +func NewTxPool(blockchain *ethblockchain.EthBlockchain, gasTip *big.Int) (*TxPool, error) { txPoolConfig := ethblockchain.NewLegacyPoolConfig() legacyPool := legacypool.New(txPoolConfig, blockchain) @@ -30,6 +31,7 @@ func NewTxPool(blockchain *ethblockchain.EthBlockchain) (*TxPool, error) { blockchain: blockchain, txPoolConfig: txPoolConfig, legacyPool: legacyPool, + gasTip: gasTip, }, nil } @@ -40,7 +42,7 @@ func (t *TxPool) Start() error { return fmt.Errorf("tx pool already started") } - memp, err := gethtxpool.New(new(big.Int).SetUint64(0), t.blockchain, []gethtxpool.SubPool{t.legacyPool}) + memp, err := gethtxpool.New(t.gasTip, t.blockchain, []gethtxpool.SubPool{t.legacyPool}) if err != nil { return fmt.Errorf("unable to init geth tx pool - %w", err) } diff --git a/go/enclave/txpool/txpool_mock_test.go b/go/enclave/txpool/txpool_mock_test.go new file mode 100644 index 0000000000..b5ff6d3778 --- /dev/null +++ b/go/enclave/txpool/txpool_mock_test.go @@ -0,0 +1,342 @@ +package txpool + +import ( + "crypto/ecdsa" + "math/big" + + "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/rpc" + "github.com/ethereum/go-ethereum/trie" + "github.com/obscuronet/go-obscuro/go/common" + "github.com/obscuronet/go-obscuro/go/common/errutil" + "github.com/obscuronet/go-obscuro/go/common/tracers" + "github.com/obscuronet/go-obscuro/go/enclave/core" + "github.com/obscuronet/go-obscuro/go/enclave/crypto" + "github.com/obscuronet/go-obscuro/go/enclave/limiters" + + gethcommon "github.com/ethereum/go-ethereum/common" +) + +type mockBatchRegistry struct { + currentBatch *core.Batch +} + +func (m *mockBatchRegistry) BatchesAfter(_ uint64, _ uint64, _ limiters.RollupLimiter) ([]*core.Batch, []*types.Block, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockBatchRegistry) GetBatchStateAtHeight(_ *rpc.BlockNumber) (*state.StateDB, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockBatchRegistry) GetBatchAtHeight(_ rpc.BlockNumber) (*core.Batch, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockBatchRegistry) SubscribeForExecutedBatches(_ func(*core.Batch, types.Receipts)) { + // TODO implement me + panic("implement me") +} + +func (m *mockBatchRegistry) UnsubscribeFromBatches() { + // TODO implement me + panic("implement me") +} + +func (m *mockBatchRegistry) OnBatchExecuted(batch *core.Batch, _ types.Receipts) { + m.currentBatch = batch +} + +func (m *mockBatchRegistry) HasGenesisBatch() (bool, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockBatchRegistry) HeadBatchSeq() *big.Int { + return m.currentBatch.SeqNo() +} + +func newMockBatchRegistry() *mockBatchRegistry { + return &mockBatchRegistry{} +} + +type mockStorage struct { + currentBatch *core.Batch + batchesSeqNo map[uint64]*core.Batch + batchesHeight map[uint64]*core.Batch + batchesHash map[gethcommon.Hash]*core.Batch + stateDB state.Database +} + +func newMockStorage() *mockStorage { + db := state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{ + Cache: 1_000_000, + }) + stateDB, err := state.New(types.EmptyRootHash, db, nil) + if err != nil { + panic(err) + } + + _, err = stateDB.Commit(0, true) + if err != nil { + panic(err) + } + + return &mockStorage{ + batchesSeqNo: map[uint64]*core.Batch{}, + batchesHeight: map[uint64]*core.Batch{}, + batchesHash: map[gethcommon.Hash]*core.Batch{}, + stateDB: db, + } +} + +func (m *mockStorage) FetchBlock(_ common.L1BlockHash) (*types.Block, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchCanonicaBlockByHeight(_ *big.Int) (*types.Block, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchHeadBlock() (*types.Block, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) StoreBlock(_ *types.Block, _ *common.ChainFork) error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) IsAncestor(_ *types.Block, _ *types.Block) bool { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) IsBlockAncestor(_ *types.Block, _ common.L1BlockHash) bool { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchBatch(_ common.L2BatchHash) (*core.Batch, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchBatchHeader(_ common.L2BatchHash) (*common.BatchHeader, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchBatchByHeight(height uint64) (*core.Batch, error) { + batch, found := m.batchesHeight[height] + if !found { + return nil, errutil.ErrNotFound + } + return batch, nil +} + +func (m *mockStorage) FetchBatchBySeqNo(seqNum uint64) (*core.Batch, error) { + batch, found := m.batchesSeqNo[seqNum] + if !found { + return nil, errutil.ErrNotFound + } + return batch, nil +} + +func (m *mockStorage) FetchHeadBatch() (*core.Batch, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchCurrentSequencerNo() (*big.Int, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchBatchesByBlock(_ common.L1BlockHash) ([]*core.Batch, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchNonCanonicalBatchesBetween(_ uint64, _ uint64) ([]*core.Batch, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchCanonicalUnexecutedBatches(_ *big.Int) ([]*core.Batch, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) BatchWasExecuted(_ common.L2BatchHash) (bool, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchHeadBatchForBlock(_ common.L1BlockHash) (*core.Batch, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) StoreBatch(_ *core.Batch) error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) StoreExecutedBatch(batch *core.Batch, _ []*types.Receipt) error { + m.currentBatch = batch + m.batchesSeqNo[batch.SeqNo().Uint64()] = batch + m.batchesHeight[batch.Number().Uint64()] = batch + m.batchesHash[batch.Hash()] = batch + return nil +} + +func (m *mockStorage) StoreRollup(_ *common.ExtRollup, _ *common.CalldataRollupHeader) error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchReorgedRollup(_ []common.L1BlockHash) (*common.L2BatchHash, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) CreateStateDB(hash common.L2BatchHash) (*state.StateDB, error) { + batch, found := m.batchesHash[hash] + if !found { + return nil, errutil.ErrNotFound + } + return state.New(batch.Header.Root, m.stateDB, nil) +} + +func (m *mockStorage) EmptyStateDB() (*state.StateDB, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchSecret() (*crypto.SharedEnclaveSecret, error) { + return &crypto.SharedEnclaveSecret{}, nil +} + +func (m *mockStorage) StoreSecret(_ crypto.SharedEnclaveSecret) error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetTransaction(_ common.L2TxHash) (*types.Transaction, gethcommon.Hash, uint64, uint64, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetTransactionReceipt(_ common.L2TxHash) (*types.Receipt, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetReceiptsByBatchHash(_ common.L2BatchHash) (types.Receipts, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetContractCreationTx(_ gethcommon.Address) (*gethcommon.Hash, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FetchAttestedKey(_ gethcommon.Address) (*ecdsa.PublicKey, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) StoreAttestedKey(_ gethcommon.Address, _ *ecdsa.PublicKey) error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) StoreL1Messages(_ common.L1BlockHash, _ common.CrossChainMessages) error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetL1Messages(_ common.L1BlockHash) (common.CrossChainMessages, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) StoreValueTransfers(_ common.L1BlockHash, _ common.ValueTransferEvents) error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetL1Transfers(_ common.L1BlockHash) (common.ValueTransferEvents, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) StoreEnclaveKey(_ *ecdsa.PrivateKey) error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetEnclaveKey() (*ecdsa.PrivateKey, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetContractCount() (*big.Int, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetReceiptsPerAddress(_ *gethcommon.Address, _ *common.QueryPagination) (types.Receipts, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetPublicTransactionData(_ *common.QueryPagination) ([]common.PublicTransaction, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetPublicTransactionCount() (uint64, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) GetReceiptsPerAddressCount(_ *gethcommon.Address) (uint64, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) Close() error { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) HealthCheck() (bool, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) FilterLogs(_ *gethcommon.Address, _, _ *big.Int, _ *common.L2BatchHash, _ []gethcommon.Address, _ [][]gethcommon.Hash) ([]*types.Log, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) DebugGetLogs(_ common.TxHash) ([]*tracers.DebugLogs, error) { + // TODO implement me + panic("implement me") +} + +func (m *mockStorage) TrieDB() *trie.Database { + // TODO implement me + panic("implement me") +} diff --git a/go/enclave/txpool/txpool_test.go b/go/enclave/txpool/txpool_test.go new file mode 100644 index 0000000000..929709fbf8 --- /dev/null +++ b/go/enclave/txpool/txpool_test.go @@ -0,0 +1,111 @@ +package txpool + +import ( + "fmt" + "math/big" + "testing" + "time" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/obscuronet/go-obscuro/go/common" + "github.com/obscuronet/go-obscuro/go/enclave/core" + "github.com/obscuronet/go-obscuro/go/enclave/ethblockchain" + "github.com/obscuronet/go-obscuro/integration/common/testlog" + "github.com/obscuronet/go-obscuro/integration/datagenerator" + "github.com/stretchr/testify/require" +) + +func TestTxPool_AddTransaction_Pending(t *testing.T) { + chainID := datagenerator.RandomUInt64() + mockStore := newMockStorage() + mockRegistry := newMockBatchRegistry() + w := datagenerator.RandomWallet(int64(chainID)) + + genesisState, err := applyGenesisState(mockStore, []gethcommon.Address{w.Address()}) + require.NoError(t, err) + genesisBatch := &core.Batch{ + Header: &common.BatchHeader{ + ParentHash: common.L2BatchHash{}, + // L1Proof: common.ha, + Root: genesisState, + TxHash: types.EmptyRootHash, + Number: big.NewInt(int64(0)), + SequencerOrderNo: big.NewInt(int64(common.L2GenesisSeqNo)), // genesis batch has seq number 1 + ReceiptHash: types.EmptyRootHash, + TransfersTree: types.EmptyRootHash, + // Time: timeNow, + // Coinbase: coinbase, + // BaseFee: baseFee, + GasLimit: 1_000_000_000_000, // todo (@siliev) - does the batch header need uint64? + }, + Transactions: []*common.L2Tx{}, + } + + err = mockStore.StoreExecutedBatch(genesisBatch, nil) + require.NoError(t, err) + + mockRegistry.OnBatchExecuted(genesisBatch, nil) + + blockchain := ethblockchain.NewEthBlockchain( + big.NewInt(int64(chainID)), + mockRegistry, + mockStore, + testlog.Logger(), + ) + err = blockchain.IngestNewBlock(genesisBatch) + require.NoError(t, err) + + txPool, err := NewTxPool(blockchain, big.NewInt(1)) + require.NoError(t, err) + + // Start the TxPool + err = txPool.Start() + require.NoError(t, err) + + // Create and add a transaction + randAddr := datagenerator.RandomAddress() + transaction := &types.LegacyTx{ + Nonce: 0, + Value: big.NewInt(1_000_000_000), + Gas: uint64(1_000_000), + GasPrice: gethcommon.Big1, + To: &randAddr, + } + signedTx, err := w.SignTransaction(transaction) + require.NoError(t, err) + + err = txPool.Add(signedTx) + if err != nil { + t.Fatalf("Failed to add transaction: %v", err) + } + + time.Sleep(time.Second) // make sure the tx makes into the pool + + // Check if the transaction is in pending + pendingTxs := txPool.PendingTransactions() + require.Equal(t, len(pendingTxs), 1) + require.Equal(t, pendingTxs[w.Address()][0].Hash.Hex(), signedTx.Hash().Hex()) + + // TODO Mint a block and check if it's cleared from the pool +} + +func applyGenesisState(storage *mockStorage, accounts []gethcommon.Address) (common.StateRoot, error) { + statedb, err := state.New(types.EmptyRootHash, storage.stateDB, nil) + if err != nil { + return common.StateRoot{}, fmt.Errorf("could not create state DB. Cause: %w", err) + } + + // set the accounts funds + for _, acc := range accounts { + statedb.SetBalance(acc, big.NewInt(1_000_000_000_000_00)) + } + + _ = statedb.IntermediateRoot(true) + commit, err := statedb.Commit(0, true) + if err != nil { + panic(err) + } + return commit, nil +}