From 40640d887772c59ea5636d5b81dd832cf941fd7b Mon Sep 17 00:00:00 2001 From: Jonathan Downing Date: Tue, 23 Apr 2024 16:54:10 -0500 Subject: [PATCH] Moved ETX Set to FIFO PMT with header commitment --- common/address.go | 4 + consensus/blake3pow/consensus.go | 1 + consensus/progpow/consensus.go | 1 + core/block_validator.go | 18 +--- core/bodydb.go | 4 +- core/core.go | 4 +- core/genesis.go | 4 +- core/headerchain.go | 44 +++------ core/rawdb/accessors_chain_test.go | 23 ----- core/slice.go | 39 ++++---- core/state/statedb.go | 145 ++++++++++++++++++++++++++- core/state_processor.go | 151 ++++++++++++++++------------- core/tx_pool.go | 6 +- core/types.go | 2 +- core/types/block.go | 46 +++++---- core/types/gen_header_json.go | 12 +-- core/types/proto_block.pb.go | 18 ++-- core/types/proto_block.proto | 2 +- core/types/wo.go | 16 ++- core/worker.go | 100 +++++++++---------- quai/api.go | 6 +- quai/api_backend.go | 4 +- quai/backend.go | 1 + quai/quaiconfig/config.go | 2 + 24 files changed, 393 insertions(+), 260 deletions(-) diff --git a/common/address.go b/common/address.go index c894f476ba..2ab5bbe8c1 100644 --- a/common/address.go +++ b/common/address.go @@ -403,3 +403,7 @@ func (a AddressBytes) IsInQuaiLedgerScope() bool { func MakeErrQiAddress(addr string) error { return fmt.Errorf("address %s is in Qi ledger scope and is not in Quai ledger scope", addr) } + +func (a Address) MixedcaseAddress() MixedcaseAddress { + return NewMixedcaseAddress(a) +} diff --git a/consensus/blake3pow/consensus.go b/consensus/blake3pow/consensus.go index 791e3ef8de..e576702f7f 100644 --- a/consensus/blake3pow/consensus.go +++ b/consensus/blake3pow/consensus.go @@ -582,6 +582,7 @@ func (blake3pow *Blake3pow) Finalize(chain consensus.ChainHeaderReader, header * } header.Header().SetUTXORoot(state.UTXORoot()) header.Header().SetEVMRoot(state.IntermediateRoot(true)) + header.Header().SetEtxSetRoot(state.ETXRoot()) } // FinalizeAndAssemble implements consensus.Engine, accumulating the block and diff --git a/consensus/progpow/consensus.go b/consensus/progpow/consensus.go index 5251732484..37ebe0d23d 100644 --- a/consensus/progpow/consensus.go +++ b/consensus/progpow/consensus.go @@ -618,6 +618,7 @@ func (progpow *Progpow) Finalize(chain consensus.ChainHeaderReader, header *type } header.Header().SetUTXORoot(state.UTXORoot()) header.Header().SetEVMRoot(state.IntermediateRoot(true)) + header.Header().SetEtxSetRoot(state.ETXRoot()) } // FinalizeAndAssemble implements consensus.Engine, accumulating the block and diff --git a/core/block_validator.go b/core/block_validator.go index 56cdf5001c..4b574773ce 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -107,7 +107,7 @@ func (v *BlockValidator) ValidateBody(block *types.WorkObject) error { // transition, such as amount of used gas, the receipt roots and the state root // itself. ValidateState returns a database batch if the validation was a success // otherwise nil and an error is returned. -func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.StateDB, receipts types.Receipts, utxoEtxs []*types.Transaction, etxSet *types.EtxSet, usedGas uint64) error { +func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.StateDB, receipts types.Receipts, utxoEtxs []*types.Transaction, usedGas uint64) error { start := time.Now() header := types.CopyHeader(block.Header()) time1 := common.PrettyDuration(time.Since(start)) @@ -130,6 +130,9 @@ func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.S if root := statedb.UTXORoot(); header.UTXORoot() != root { return fmt.Errorf("invalid utxo root (remote: %x local: %x)", header.UTXORoot(), root) } + if root := statedb.ETXRoot(); header.EtxSetRoot() != root { + return fmt.Errorf("invalid etx root (remote: %x local: %x)", header.EtxSetRoot(), root) + } time5 := common.PrettyDuration(time.Since(start)) // Collect ETXs emitted from each successful transaction var emittedEtxs types.Transactions @@ -146,19 +149,6 @@ func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.S if etxHash := types.DeriveSha(emittedEtxs, trie.NewStackTrie(nil)); etxHash != header.EtxHash() { return fmt.Errorf("invalid etx hash (remote: %x local: %x)", header.EtxHash(), etxHash) } - // Confirm the ETX set used by the block matches the ETX set given in the block body - // This is the resulting ETX set after all ETXs in the block have been processed - // After validation, this ETX set should be stored in the database - if etxSet != nil { - etxSetHash := etxSet.Hash() - if etxSetHash != block.EtxSetHash() { - return fmt.Errorf("expected ETX Set hash %x does not match block ETXSetHash %x", etxSetHash, block.EtxSetHash()) - } - } else { - if block.EtxSetHash() != types.EmptyEtxSetHash { - return fmt.Errorf("expected ETX Set hash %x does not match block ETXSetHash %x", types.EmptyRootHash, block.EtxSetHash()) - } - } // Check that the UncledS in the header matches the S from the block expectedUncledS := v.engine.UncledLogS(block) diff --git a/core/bodydb.go b/core/bodydb.go index 40262944c1..14066510b6 100644 --- a/core/bodydb.go +++ b/core/bodydb.go @@ -90,7 +90,7 @@ func NewBodyDb(db ethdb.Database, engine consensus.Engine, hc *HeaderChain, chai } // Append -func (bc *BodyDb) Append(block *types.WorkObject, newInboundEtxs types.Transactions) ([]*types.Log, error) { +func (bc *BodyDb) Append(block *types.WorkObject) ([]*types.Log, error) { bc.chainmu.Lock() defer bc.chainmu.Unlock() @@ -101,7 +101,7 @@ func (bc *BodyDb) Append(block *types.WorkObject, newInboundEtxs types.Transacti var err error if nodeCtx == common.ZONE_CTX && bc.ProcessingState() { // Process our block - logs, err = bc.processor.Apply(batch, block, newInboundEtxs) + logs, err = bc.processor.Apply(batch, block) if err != nil { return nil, err } diff --git a/core/core.go b/core/core.go index 19fbcfbd1d..2fb982cece 100644 --- a/core/core.go +++ b/core/core.go @@ -1056,8 +1056,8 @@ func (c *Core) State() (*state.StateDB, error) { } // StateAt returns a new mutable state based on a particular point in time. -func (c *Core) StateAt(root common.Hash, utxoRoot common.Hash) (*state.StateDB, error) { - return c.sl.hc.bc.processor.StateAt(root, utxoRoot) +func (c *Core) StateAt(root, utxoRoot, etxRoot common.Hash) (*state.StateDB, error) { + return c.sl.hc.bc.processor.StateAt(root, utxoRoot, etxRoot) } // StateCache returns the caching database underpinning the blockchain instance. diff --git a/core/genesis.go b/core/genesis.go index 3362464c7e..82f6e22a38 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -186,7 +186,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, nodeLoca // We have the genesis block in database(perhaps in ancient database) // but the corresponding state is missing. header := rawdb.ReadHeader(db, stored, 0) - if _, err := state.New(header.EVMRoot(), header.UTXORoot(), state.NewDatabaseWithConfig(db, nil), state.NewDatabaseWithConfig(db, nil), nil, nodeLocation); err != nil { + if _, err := state.New(header.EVMRoot(), header.UTXORoot(), header.EtxSetRoot(), state.NewDatabaseWithConfig(db, nil), state.NewDatabaseWithConfig(db, nil), state.NewDatabaseWithConfig(db, nil), nil, nodeLocation); err != nil { if genesis == nil { genesis = DefaultGenesisBlock() } @@ -286,7 +286,7 @@ func (g *Genesis) ToBlock(startingExpansionNumber uint64) *types.WorkObject { } head.Header().SetCoinbase(common.Zero) head.Header().SetBaseFee(new(big.Int).SetUint64(params.InitialBaseFee)) - head.Header().SetEtxSetHash(types.EmptyEtxSetHash) + head.Header().SetEtxSetRoot(types.EmptyRootHash) if g.GasLimit == 0 { head.Header().SetGasLimit(params.GenesisGasLimit) } diff --git a/core/headerchain.go b/core/headerchain.go index 928de244c0..e77522dc28 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -317,10 +317,10 @@ func (hc *HeaderChain) ProcessingState() bool { } // Append -func (hc *HeaderChain) AppendBlock(block *types.WorkObject, newInboundEtxs types.Transactions) error { +func (hc *HeaderChain) AppendBlock(block *types.WorkObject) error { blockappend := time.Now() // Append block else revert header append - logs, err := hc.bc.Append(block, newInboundEtxs) + logs, err := hc.bc.Append(block) if err != nil { return err } @@ -441,10 +441,10 @@ func (hc *HeaderChain) SetCurrentState(head *types.WorkObject) error { if hc.IsGenesisHash(header.Hash()) { break } - // Checking of the Etx set exists makes sure that we have processed the - // state of the parent block - etxSet := rawdb.ReadEtxSet(hc.headerDb, header.Hash(), header.NumberU64(nodeCtx)) - if etxSet != nil { + // This is not perfect because it's possible some blocks have the same root hash (no uniqueness guarantee) + // We probably need a better way to determine if we have processed the state and ETXs for a given block + _, err := hc.bc.processor.StateAt(header.EVMRoot(), header.UTXORoot(), header.EtxSetRoot()) + if err == nil { break } current = types.CopyWorkObject(header) @@ -452,7 +452,11 @@ func (hc *HeaderChain) SetCurrentState(head *types.WorkObject) error { // Run through the hash stack to update canonicalHash and forward state processor for i := len(headersWithoutState) - 1; i >= 0; i-- { - err := hc.ReadInboundEtxsAndAppendBlock(headersWithoutState[i]) + block := hc.GetBlockOrCandidate(headersWithoutState[i].Hash(), headersWithoutState[i].NumberU64(nodeCtx)) + if block == nil { + return errors.New("could not find block during SetCurrentState: " + headersWithoutState[i].Hash().String()) + } + err := hc.AppendBlock(block) if err != nil { return err } @@ -460,28 +464,6 @@ func (hc *HeaderChain) SetCurrentState(head *types.WorkObject) error { return nil } -// ReadInboundEtxsAndAppendBlock reads the inbound etxs from database and appends the block -func (hc *HeaderChain) ReadInboundEtxsAndAppendBlock(header *types.WorkObject) error { - nodeCtx := hc.NodeCtx() - block := hc.GetBlockOrCandidate(header.Hash(), header.NumberU64(nodeCtx)) - if block == nil { - return errors.New("could not find block during reorg") - } - _, order, err := hc.engine.CalcOrder(block) - if err != nil { - return err - } - var inboundEtxs types.Transactions - if order < nodeCtx { - inboundEtxs = rawdb.ReadInboundEtxs(hc.headerDb, header.Hash()) - } - err = hc.AppendBlock(block, inboundEtxs) - if err != nil { - return err - } - return nil -} - // findCommonAncestor func (hc *HeaderChain) findCommonAncestor(header *types.WorkObject) *types.WorkObject { current := types.CopyWorkObject(header) @@ -998,8 +980,8 @@ func (hc *HeaderChain) SubscribeChainSideEvent(ch chan<- ChainSideEvent) event.S return hc.scope.Track(hc.chainSideFeed.Subscribe(ch)) } -func (hc *HeaderChain) StateAt(root common.Hash, utxoRoot common.Hash) (*state.StateDB, error) { - return hc.bc.processor.StateAt(root, utxoRoot) +func (hc *HeaderChain) StateAt(root, utxoRoot, etxRoot common.Hash) (*state.StateDB, error) { + return hc.bc.processor.StateAt(root, utxoRoot, etxRoot) } func (hc *HeaderChain) SlicesRunning() []common.Location { diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 35e8bd2b04..cd48dddbf3 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -60,29 +60,6 @@ func TestTerminiStorage(t *testing.T) { } } -func TestEtxSetStorage(t *testing.T) { - db := NewMemoryDatabase() - - // Create a test etxSet to move around the database and make sure it's really new - etxSet := types.NewEtxSet() - hash := common.Hash{1} - var number uint64 = 0 - if entry := ReadEtxSet(db, hash, number); entry != nil { - t.Fatalf("Non existent etxSet returned: %v", entry) - } - t.Log("EtxSet Hash stored", hash) - // Write and verify the etxSet in the database - WriteEtxSet(db, hash, 0, etxSet) - if entry := ReadEtxSet(db, hash, number); entry == nil { - t.Fatalf("Stored etxSet not found with hash %s", hash) - } - // Delete the etxSet and verify the execution - DeleteEtxSet(db, hash, number) - if entry := ReadEtxSet(db, hash, number); entry != nil { - t.Fatalf("Deleted etxSet returned: %v", entry) - } -} - // Tests inbound etx storage and retrieval operations. func TestInboundEtxsStorage(t *testing.T) { db := NewMemoryDatabase() diff --git a/core/slice.go b/core/slice.go index 8a7bfa4a4b..286d0e749d 100644 --- a/core/slice.go +++ b/core/slice.go @@ -335,8 +335,9 @@ func (sl *Slice) Append(header *types.WorkObject, domPendingHeader *types.WorkOb subReorg = sl.miningStrategy(bestPh, tempPendingHeader) if order < nodeCtx { - // Store the inbound etxs for dom blocks that did not get picked and use + // Store the inbound etxs for all dom blocks and use // it in the future if dom switch happens + // This should be pruned at the re-org tolerance depth rawdb.WriteInboundEtxs(sl.sliceDb, block.Hash(), newInboundEtxs) } @@ -438,20 +439,23 @@ func (sl *Slice) Append(header *types.WorkObject, domPendingHeader *types.WorkOb }).Info("Times during sub append") sl.logger.WithFields(log.Fields{ - "dom number": block.Header().NumberArray(), - "zone number": block.Number(common.ZONE_CTX), - "hash": block.Hash(), - "difficulty": block.Difficulty(), - "uncles": len(block.Uncles()), - "txs": len(block.Transactions()), - "etxs": len(block.ExtTransactions()), - "utxos": len(block.QiTransactions()), - "gas": block.GasUsed(), - "gasLimit": block.GasLimit(), - "evmRoot": block.EVMRoot(), - "order": order, - "location": block.Location(), - "elapsed": common.PrettyDuration(time.Since(start)), + "dom number": block.Header().NumberArray(), + "zone number": block.Number(common.ZONE_CTX), + "hash": block.Hash(), + "difficulty": block.Difficulty(), + "uncles": len(block.Uncles()), + "totalTxs": len(block.Transactions()), + "etxs emitted": len(block.ExtTransactions()), + "qiTxs": len(block.QiTransactions()), + "quaiTxs": len(block.QuaiTransactions()), + "gas": block.GasUsed(), + "gasLimit": block.GasLimit(), + "evmRoot": block.EVMRoot(), + "utxoRoot": block.UTXORoot(), + "etxSetRoot": block.EtxSetRoot(), + "order": order, + "location": block.Location(), + "elapsed": common.PrettyDuration(time.Since(start)), }).Info("Appended new block") if nodeCtx == common.ZONE_CTX { @@ -1223,7 +1227,6 @@ func (sl *Slice) init() error { if err != nil { return err } - rawdb.WriteEtxSet(sl.sliceDb, genesisHash, 0, types.NewEtxSet()) // This is just done for the startup process sl.hc.SetCurrentHeader(genesisHeader) @@ -1345,10 +1348,11 @@ func (sl *Slice) combinePendingHeader(header *types.WorkObject, slPendingHeader combinedPendingHeader.Header().SetUncleHash(header.UncleHash()) combinedPendingHeader.Header().SetTxHash(header.Header().TxHash()) combinedPendingHeader.Header().SetEtxHash(header.EtxHash()) - combinedPendingHeader.Header().SetEtxSetHash(header.EtxSetHash()) + combinedPendingHeader.Header().SetEtxSetRoot(header.EtxSetRoot()) combinedPendingHeader.Header().SetReceiptHash(header.ReceiptHash()) combinedPendingHeader.Header().SetEVMRoot(header.EVMRoot()) combinedPendingHeader.Header().SetUTXORoot(header.UTXORoot()) + combinedPendingHeader.Header().SetEtxSetRoot(header.EtxSetRoot()) combinedPendingHeader.Header().SetCoinbase(header.Coinbase()) combinedPendingHeader.Header().SetBaseFee(header.BaseFee()) combinedPendingHeader.Header().SetGasLimit(header.GasLimit()) @@ -1406,7 +1410,6 @@ func (sl *Slice) WriteGenesisBlock(block *types.WorkObject, location common.Loca sl.AddPendingEtxsRollup(types.PendingEtxsRollup{block, emptyPendingEtxs}) sl.hc.AddBloom(types.Bloom{}, block.Hash()) sl.hc.currentHeader.Store(block) - rawdb.WriteEtxSet(sl.sliceDb, block.Hash(), block.NumberU64(sl.NodeCtx()), types.NewEtxSet()) } // NewGenesisPendingHeader creates a pending header on the genesis block diff --git a/core/state/statedb.go b/core/state/statedb.go index 6d948079b2..eed4467d52 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -45,7 +45,10 @@ type revision struct { var ( // emptyRoot is the known root hash of an empty trie. - emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + newestEtxKey = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff") // max hash + oldestEtxKey = common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffe") // max hash - 1 + ) type proofList [][]byte @@ -90,10 +93,12 @@ func registerMetrics() { type StateDB struct { db Database utxoDb Database + etxDb Database prefetcher *triePrefetcher originalRoot common.Hash // The pre-state root, before any changes were made trie Trie utxoTrie Trie + etxTrie Trie hasher crypto.KeccakState nodeLocation common.Location @@ -150,7 +155,7 @@ type StateDB struct { } // New creates a new state from a given trie. -func New(root common.Hash, utxoRoot common.Hash, db Database, utxoDb Database, snaps *snapshot.Tree, nodeLocation common.Location) (*StateDB, error) { +func New(root common.Hash, utxoRoot common.Hash, etxRoot common.Hash, db Database, utxoDb Database, etxDb Database, snaps *snapshot.Tree, nodeLocation common.Location) (*StateDB, error) { tr, err := db.OpenTrie(root) if err != nil { return nil, err @@ -159,11 +164,17 @@ func New(root common.Hash, utxoRoot common.Hash, db Database, utxoDb Database, s if err != nil { return nil, err } + etxTr, err := etxDb.OpenTrie(etxRoot) + if err != nil { + return nil, err + } sdb := &StateDB{ db: db, utxoDb: utxoDb, + etxDb: etxDb, trie: tr, utxoTrie: utxoTr, + etxTrie: etxTr, originalRoot: root, snaps: snaps, stateObjects: make(map[common.InternalAddress]*stateObject), @@ -385,6 +396,10 @@ func (s *StateDB) UTXODatabase() Database { return s.utxoDb } +func (s *StateDB) ETXDatabase() Database { + return s.etxDb +} + // StorageTrie returns the storage trie of an account. // The return value is a copy and is nil for non-existent accounts. func (s *StateDB) StorageTrie(addr common.InternalAddress) Trie { @@ -620,6 +635,132 @@ func (s *StateDB) GetUTXOProof(hash common.Hash, index uint16) ([][]byte, error) return proof, err } +func (s *StateDB) PushETX(etx *types.Transaction) error { + if metrics_config.MetricsEnabled() { + defer func(start time.Time) { stateMetrics.WithLabelValues("AddETX").Add(float64(time.Since(start))) }(time.Now()) + } + data, err := rlp.EncodeToBytes(etx) + if err != nil { + return err + } + newestIndex, err := s.GetNewestIndex() + if err != nil { + return err + } + if err := s.etxTrie.TryUpdate(newestIndex.Bytes(), data); err != nil { + return err + } + newestIndex.Add(newestIndex, big.NewInt(1)) + if err := s.etxTrie.TryUpdate(newestEtxKey[:], newestIndex.Bytes()); err != nil { + return err + } + return nil +} + +func (s *StateDB) PushETXs(etxs []*types.Transaction) error { + if metrics_config.MetricsEnabled() { + defer func(start time.Time) { stateMetrics.WithLabelValues("AddETX").Add(float64(time.Since(start))) }(time.Now()) + } + newestIndex, err := s.GetNewestIndex() + if err != nil { + return err + } + for _, etx := range etxs { + data, err := rlp.EncodeToBytes(etx) + if err != nil { + return err + } + if err := s.etxTrie.TryUpdate(newestIndex.Bytes(), data); err != nil { + return err + } + newestIndex.Add(newestIndex, big.NewInt(1)) + } + if err := s.etxTrie.TryUpdate(newestEtxKey[:], newestIndex.Bytes()); err != nil { + return err + } + return nil +} + +func (s *StateDB) PopETX() (*types.Transaction, error) { + if metrics_config.MetricsEnabled() { + defer func(start time.Time) { stateMetrics.WithLabelValues("PopETX").Add(float64(time.Since(start))) }(time.Now()) + } + oldestIndex, err := s.GetOldestIndex() + if err != nil { + return nil, err + } + enc, err := s.etxTrie.TryGet(oldestIndex.Bytes()) + if err != nil { + return nil, err + } + if len(enc) == 0 { + return nil, nil + } + etx := new(types.Transaction) + if err := rlp.DecodeBytes(enc, etx); err != nil { + return nil, err + } + if err := s.etxTrie.TryDelete(oldestIndex.Bytes()); err != nil { + return nil, err + } + oldestIndex.Add(oldestIndex, big.NewInt(1)) + if err := s.etxTrie.TryUpdate(oldestEtxKey[:], oldestIndex.Bytes()); err != nil { + return nil, err + } + return etx, nil +} + +func (s *StateDB) ReadETX(index *big.Int) (*types.Transaction, error) { + enc, err := s.etxTrie.TryGet(index.Bytes()) + if err != nil { + return nil, err + } + if len(enc) == 0 { + return nil, nil + } + etx := new(types.Transaction) + if err := rlp.DecodeBytes(enc, etx); err != nil { + return nil, err + } + return etx, nil +} + +func (s *StateDB) GetNewestIndex() (*big.Int, error) { + b, err := s.etxTrie.TryGet(newestEtxKey[:]) + if err != nil { + s.setError(fmt.Errorf("getNewestIndex error: %v", err)) + return nil, err + } + return new(big.Int).SetBytes(b), nil +} + +func (s *StateDB) GetOldestIndex() (*big.Int, error) { + b, err := s.etxTrie.TryGet(oldestEtxKey[:]) + if err != nil { + s.setError(fmt.Errorf("getOldestIndex error: %v", err)) + return nil, err + } + return new(big.Int).SetBytes(b), nil +} + +func (s *StateDB) ETXRoot() common.Hash { + return s.etxTrie.Hash() +} + +func (s *StateDB) CommitETXs() (common.Hash, error) { + if metrics_config.MetricsEnabled() { + defer func(start time.Time) { stateMetrics.WithLabelValues("CommitETXs").Add(float64(time.Since(start))) }(time.Now()) + } + if s.etxTrie == nil { + return common.Hash{}, errors.New("ETX trie is not initialized") + } + root, err := s.etxTrie.Commit(nil) + if err != nil { + s.setError(fmt.Errorf("commitETXs error: %v", err)) + } + return root, err +} + // getDeletedStateObject is similar to getStateObject, but instead of returning // nil for a deleted state object, it returns the actual object with the deleted // flag set. This is needed by the state journal to revert to the correct s- diff --git a/core/state_processor.go b/core/state_processor.go index c84d3d36ec..0752ef7037 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -81,6 +81,7 @@ type CacheConfig struct { TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory TrieCleanJournal string // Disk journal for saving clean cache entries. UTXOTrieCleanJournal string + ETXTrieCleanJournal string TrieCleanRejournal time.Duration // Time interval to dump clean cache to disk periodically TrieCleanNoPrefetch bool // Whether to disable heuristic state prefetching for followup blocks TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk @@ -111,6 +112,7 @@ type StateProcessor struct { cacheConfig *CacheConfig // CacheConfig for StateProcessor stateCache state.Database // State database to reuse between imports (contains state cache) utxoCache state.Database // UTXO database to reuse between imports (contains UTXO cache) + etxCache state.Database // ETX database to reuse between imports (contains ETX cache) receiptsCache *lru.Cache // Cache for the most recent receipts per block txLookupCache *lru.Cache validator Validator // Block and state validator interface @@ -154,6 +156,11 @@ func NewStateProcessor(config *params.ChainConfig, hc *HeaderChain, engine conse Journal: cacheConfig.UTXOTrieCleanJournal, Preimages: cacheConfig.Preimages, }), + etxCache: state.NewDatabaseWithConfig(hc.headerDb, &trie.Config{ + Cache: cacheConfig.TrieCleanLimit, + Journal: cacheConfig.ETXTrieCleanJournal, + Preimages: cacheConfig.Preimages, + }), engine: engine, triegc: prque.New(nil), quit: make(chan struct{}), @@ -181,11 +188,13 @@ func NewStateProcessor(config *params.ChainConfig, hc *HeaderChain, engine conse } triedb := sp.stateCache.TrieDB() utxoTrieDb := sp.utxoCache.TrieDB() + etxTrieDb := sp.etxCache.TrieDB() sp.wg.Add(1) go func() { defer sp.wg.Done() triedb.SaveCachePeriodically(sp.cacheConfig.TrieCleanJournal, sp.cacheConfig.TrieCleanRejournal, sp.quit) utxoTrieDb.SaveCachePeriodically(sp.cacheConfig.UTXOTrieCleanJournal, sp.cacheConfig.TrieCleanRejournal, sp.quit) + etxTrieDb.SaveCachePeriodically(sp.cacheConfig.ETXTrieCleanJournal, sp.cacheConfig.TrieCleanRejournal, sp.quit) }() } return sp @@ -198,7 +207,7 @@ func NewStateProcessor(config *params.ChainConfig, hc *HeaderChain, engine conse // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) (types.Receipts, []*types.Transaction, []*types.Log, *state.StateDB, uint64, error) { +func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*types.Transaction, []*types.Log, *state.StateDB, uint64, error) { var ( receipts types.Receipts usedGas = new(uint64) @@ -219,18 +228,27 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) parentEvmRoot := parent.Header().EVMRoot() parentUtxoRoot := parent.Header().UTXORoot() + parentEtxSetRoot := parent.Header().EtxSetRoot() if p.hc.IsGenesisHash(parent.Hash()) { parentEvmRoot = types.EmptyRootHash parentUtxoRoot = types.EmptyRootHash + parentEtxSetRoot = types.EmptyRootHash } // Initialize a statedb - statedb, err := state.New(parentEvmRoot, parentUtxoRoot, p.stateCache, p.utxoCache, p.snaps, nodeLocation) + statedb, err := state.New(parentEvmRoot, parentUtxoRoot, parentEtxSetRoot, p.stateCache, p.utxoCache, p.etxCache, p.snaps, nodeLocation) if err != nil { return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, err } if len(block.Transactions()) == 0 { return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, statedb, 0, nil } + // Apply the previous inbound ETXs to the ETX set state + prevInboundEtxs := rawdb.ReadInboundEtxs(p.hc.bc.db, header.ParentHash(nodeCtx)) + if len(prevInboundEtxs) > 0 { + if err := statedb.PushETXs(prevInboundEtxs); err != nil { + return nil, nil, nil, nil, 0, fmt.Errorf("could not push prev inbound etxs: %w", err) + } + } time2 := common.PrettyDuration(time.Since(start)) var timeSenders, timeSign, timePrepare, timeEtx, timeTx time.Duration @@ -302,19 +320,25 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) if tx.Type() == types.ExternalTxType { startTimeEtx := time.Now() // ETXs MUST be included in order, so popping the first from the queue must equal the first in the block - etxHash := etxSet.Pop() - if etxHash != tx.Hash() { + etx, err := statedb.PopETX() + if err != nil { + return nil, nil, nil, nil, 0, fmt.Errorf("could not pop etx from statedb: %w", err) + } + if etx == nil { + return nil, nil, nil, nil, 0, fmt.Errorf("etx %x is nil", tx.Hash()) + } + if etx.Hash() != tx.Hash() { return nil, nil, nil, nil, 0, fmt.Errorf("invalid external transaction: etx %x is not in order or not found in unspent etx set", tx.Hash()) } - if tx.To().IsInQiLedgerScope() { - if tx.ETXSender().Location().Equal(*tx.To().Location()) { // Quai->Qi Conversion + if etx.To().IsInQiLedgerScope() { + if etx.ETXSender().Location().Equal(*etx.To().Location()) { // Quai->Qi Conversion lock := new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod)) primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } - value := misc.QuaiToQi(primeTerminus, tx.Value()) // convert Quai to Qi - txGas := tx.Gas() + value := misc.QuaiToQi(primeTerminus, etx.Value()) // convert Quai to Qi + txGas := etx.Gas() denominations := misc.FindMinDenominations(value) outputIndex := uint16(0) // Iterate over the denominations in descending order @@ -335,7 +359,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) *usedGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is totalEtxGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is // the ETX hash is guaranteed to be unique - if err := statedb.CreateUTXO(tx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), lock))); err != nil { + if err := statedb.CreateUTXO(etx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), etx.To().Bytes(), lock))); err != nil { return nil, nil, nil, nil, 0, err } log.Global.Infof("Converting Quai to Qi %032x with denomination %d index %d lock %d", tx.Hash(), denomination, outputIndex, lock) @@ -344,7 +368,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) } } else { // There are no more checks to be made as the ETX is worked so add it to the set - if err := statedb.CreateUTXO(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Uint64()), tx.To().Bytes(), big.NewInt(0)))); err != nil { + if err := statedb.CreateUTXO(etx.OriginatingTxHash(), etx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(etx.Value().Uint64()), etx.To().Bytes(), big.NewInt(0)))); err != nil { return nil, nil, nil, nil, 0, err } // This Qi ETX should cost more gas @@ -358,19 +382,19 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) timeEtx += timeEtxDelta continue } else { - if tx.ETXSender().Location().Equal(*tx.To().Location()) { // Qi->Quai Conversion + if etx.ETXSender().Location().Equal(*etx.To().Location()) { // Qi->Quai Conversion msg.SetLock(new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod))) primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } // Convert Qi to Quai - msg.SetValue(misc.QiToQuai(primeTerminus, tx.Value())) + msg.SetValue(misc.QiToQuai(primeTerminus, etx.Value())) msg.SetData([]byte{}) // data is not used in conversion log.Global.Infof("Converting Qi to Quai for ETX %032x with value %d lock %d", tx.Hash(), msg.Value().Uint64(), msg.Lock().Uint64()) } prevZeroBal := prepareApplyETX(statedb, msg.Value(), nodeLocation) - receipt, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, &etxRLimit, &etxPLimit, p.logger) + receipt, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, etx, usedGas, vmenv, &etxRLimit, &etxPLimit, p.logger) statedb.SetBalance(common.ZeroInternal(nodeLocation), prevZeroBal) // Reset the balance to what it previously was. Residual balance will be lost if err != nil { return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) @@ -476,8 +500,20 @@ func (p *StateProcessor) Process(block *types.WorkObject, etxSet *types.EtxSet) return nil, nil, nil, nil, 0, errors.New("coinbase tx type not supported") } } - - if etxSet != nil && (etxSet.Len() > 0 && totalEtxGas < minimumEtxGas) || totalEtxGas > maximumEtxGas { + etxAvailable := false + oldestIndex, err := statedb.GetOldestIndex() + if err != nil { + return nil, nil, nil, nil, 0, fmt.Errorf("could not get oldest index: %w", err) + } + // Check if there is at least one ETX in the set + etx, err := statedb.ReadETX(oldestIndex) + if err != nil { + return nil, nil, nil, nil, 0, fmt.Errorf("could not read etx: %w", err) + } + if etx != nil { + etxAvailable = true + } + if (etxAvailable && totalEtxGas < minimumEtxGas) || totalEtxGas > maximumEtxGas { return nil, nil, nil, nil, 0, fmt.Errorf("total gas used by ETXs %d is not within the range %d to %d", totalEtxGas, minimumEtxGas, maximumEtxGas) } @@ -790,28 +826,22 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, cu } // Apply State -func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject, newInboundEtxs types.Transactions) ([]*types.Log, error) { +func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*types.Log, error) { nodeCtx := p.hc.NodeCtx() start := time.Now() blockHash := block.Hash() parentHash := block.ParentHash(nodeCtx) - parentNumber := block.NumberU64(nodeCtx) - 1 if p.hc.IsGenesisHash(block.ParentHash(nodeCtx)) { parent := p.hc.GetHeaderByHash(parentHash) if parent == nil { return nil, errors.New("failed to load parent block") } - parentNumber = parent.NumberU64(nodeCtx) } - etxSet := rawdb.ReadEtxSet(p.hc.bc.db, parentHash, parentNumber) time1 := common.PrettyDuration(time.Since(start)) - if etxSet == nil { - return nil, errors.New("failed to load etx set") - } time2 := common.PrettyDuration(time.Since(start)) // Process our block - receipts, utxoEtxs, logs, statedb, usedGas, err := p.Process(block, etxSet) + receipts, utxoEtxs, logs, statedb, usedGas, err := p.Process(block) if err != nil { return nil, err } @@ -822,7 +852,7 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject, newIn }).Warn("Block hash changed after Processing the block") } time3 := common.PrettyDuration(time.Since(start)) - err = p.validator.ValidateState(block, statedb, receipts, utxoEtxs, etxSet, usedGas) + err = p.validator.ValidateState(block, statedb, receipts, utxoEtxs, usedGas) if err != nil { return nil, err } @@ -844,27 +874,23 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject, newIn if err != nil { return nil, err } - triedb := p.stateCache.TrieDB() + etxRoot, err := statedb.CommitETXs() + if err != nil { + return nil, err + } + time7 := common.PrettyDuration(time.Since(start)) var time8 common.PrettyDuration - var time9 common.PrettyDuration - var time10 common.PrettyDuration - var time11 common.PrettyDuration - if err := triedb.Commit(root, false, nil); err != nil { + if err := p.stateCache.TrieDB().Commit(root, false, nil); err != nil { return nil, err } if err := p.utxoCache.TrieDB().Commit(utxoRoot, false, nil); err != nil { return nil, err } + if err := p.etxCache.TrieDB().Commit(etxRoot, false, nil); err != nil { + return nil, err + } time8 = common.PrettyDuration(time.Since(start)) - // Update the set of inbound ETXs which may be mined in the next block - // These new inbounds are not included in the ETX hash of the current block - // because they are not known a-priori - etxSet.Update(newInboundEtxs, p.hc.NodeLocation(), func(hash common.Hash, etx *types.Transaction) { - rawdb.WriteETX(batch, hash, etx) // This must be done because of rawdb <-> types import cycle - }) - rawdb.WriteEtxSet(batch, block.Hash(), block.NumberU64(nodeCtx), etxSet) - time12 := common.PrettyDuration(time.Since(start)) p.logger.WithFields(log.Fields{ "t1": time1, @@ -876,10 +902,6 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject, newIn "t6": time6, "t7": time7, "t8": time8, - "t9": time9, - "t10": time10, - "t11": time11, - "t12": time12, }).Debug("times during state processor apply") return logs, nil } @@ -923,12 +945,12 @@ func (p *StateProcessor) GetVMConfig() *vm.Config { // State returns a new mutable state based on the current HEAD block. func (p *StateProcessor) State() (*state.StateDB, error) { - return p.StateAt(p.hc.GetBlockByHash(p.hc.CurrentHeader().Hash()).EVMRoot(), p.hc.GetBlockByHash(p.hc.CurrentHeader().Hash()).UTXORoot()) + return p.StateAt(p.hc.CurrentHeader().EVMRoot(), p.hc.CurrentHeader().UTXORoot(), p.hc.CurrentHeader().EtxSetRoot()) } // StateAt returns a new mutable state based on a particular point in time. -func (p *StateProcessor) StateAt(root common.Hash, utxoRoot common.Hash) (*state.StateDB, error) { - return state.New(root, utxoRoot, p.stateCache, p.utxoCache, p.snaps, p.hc.NodeLocation()) +func (p *StateProcessor) StateAt(root, utxoRoot, etxRoot common.Hash) (*state.StateDB, error) { + return state.New(root, utxoRoot, etxRoot, p.stateCache, p.utxoCache, p.etxCache, p.snaps, p.hc.NodeLocation()) } // StateCache returns the caching database underpinning the blockchain instance. @@ -1027,6 +1049,7 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba current *types.WorkObject database state.Database utxoDatabase state.Database + etxDatabase state.Database report = true nodeLocation = p.hc.NodeLocation() nodeCtx = p.hc.NodeCtx() @@ -1034,7 +1057,7 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba ) // Check the live database first if we have the state fully available, use that. if checkLive { - statedb, err = p.StateAt(block.EVMRoot(), block.UTXORoot()) + statedb, err = p.StateAt(block.EVMRoot(), block.UTXORoot(), block.EtxSetRoot()) if err == nil { return statedb, nil } @@ -1043,7 +1066,7 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba var newHeads []*types.WorkObject if base != nil { // The optional base statedb is given, mark the start point as parent block - statedb, database, utxoDatabase, report = base, base.Database(), base.UTXODatabase(), false + statedb, database, utxoDatabase, etxDatabase, report = base, base.Database(), base.UTXODatabase(), base.ETXDatabase(), false current = p.hc.GetHeaderOrCandidate(block.ParentHash(nodeCtx), block.NumberU64(nodeCtx)-1) } else { // Otherwise try to reexec blocks until we find a state or reach our limit @@ -1055,12 +1078,15 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba // Create an ephemeral trie.Database for isolating the live one. Otherwise // the internal junks created by tracing will be persisted into the disk. utxoDatabase = state.NewDatabaseWithConfig(p.hc.headerDb, &trie.Config{Cache: 16}) + // Create an ephemeral trie.Database for isolating the live one. Otherwise + // the internal junks created by tracing will be persisted into the disk. + etxDatabase = state.NewDatabaseWithConfig(p.hc.headerDb, &trie.Config{Cache: 16}) // If we didn't check the dirty database, do check the clean one, otherwise // we would rewind past a persisted block (specific corner case is chain // tracing from the genesis). if !checkLive { - statedb, err = state.New(current.EVMRoot(), current.UTXORoot(), database, utxoDatabase, nil, nodeLocation) + statedb, err = state.New(current.EVMRoot(), current.UTXORoot(), current.EtxSetRoot(), database, utxoDatabase, etxDatabase, nil, nodeLocation) if err == nil { return statedb, nil } @@ -1077,7 +1103,7 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba } current = types.CopyWorkObject(parent) - statedb, err = state.New(current.EVMRoot(), current.UTXORoot(), database, utxoDatabase, nil, nodeLocation) + statedb, err = state.New(current.EVMRoot(), current.UTXORoot(), current.EtxSetRoot(), database, utxoDatabase, etxDatabase, nil, nodeLocation) if err == nil { break } @@ -1110,27 +1136,11 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba }).Info("Regenerating historical state") logged = time.Now() } - - parentHash := current.ParentHash(nodeCtx) - parentNumber := current.NumberU64(nodeCtx) - 1 - if p.hc.IsGenesisHash(parentHash) { - parent := p.hc.GetHeaderByHash(parentHash) - parentNumber = parent.NumberU64(nodeCtx) - } - etxSet := rawdb.ReadEtxSet(p.hc.bc.db, parentHash, parentNumber) - if etxSet == nil { - return nil, errors.New("etxSet set is nil in StateProcessor") - } - inboundEtxs := rawdb.ReadInboundEtxs(p.hc.bc.db, current.Hash()) - etxSet.Update(inboundEtxs, nodeLocation, func(hash common.Hash, etx *types.Transaction) { - rawdb.WriteETX(rawdb.NewMemoryDatabase(), hash, etx) - }) - currentBlock := rawdb.ReadWorkObject(p.hc.bc.db, current.Hash(), types.BlockObject) if currentBlock == nil { return nil, errors.New("detached block found trying to regenerate state") } - _, _, _, _, _, err := p.Process(currentBlock, etxSet) + _, _, _, _, _, err := p.Process(currentBlock) if err != nil { return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(nodeCtx), err) } @@ -1145,7 +1155,12 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba return nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w", current.NumberU64(nodeCtx), current.EVMRoot().Hex(), err) } - statedb, err = state.New(root, utxoRoot, database, utxoDatabase, nil, nodeLocation) + etxRoot, err := statedb.CommitETXs() + if err != nil { + return nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w", + current.NumberU64(nodeCtx), current.EVMRoot().Hex(), err) + } + statedb, err = state.New(root, utxoRoot, etxRoot, database, utxoDatabase, etxDatabase, nil, nodeLocation) if err != nil { return nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(nodeCtx), err) } @@ -1221,6 +1236,10 @@ func (p *StateProcessor) Stop() { utxoTrieDB := p.utxoCache.TrieDB() utxoTrieDB.SaveCache(p.cacheConfig.UTXOTrieCleanJournal) } + if p.cacheConfig.ETXTrieCleanJournal != "" { + etxTrieDB := p.etxCache.TrieDB() + etxTrieDB.SaveCache(p.cacheConfig.ETXTrieCleanJournal) + } close(p.quit) p.logger.Info("State Processor stopped") } diff --git a/core/tx_pool.go b/core/tx_pool.go index 1b46b24f45..f71067e95e 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -144,7 +144,7 @@ const ( type blockChain interface { CurrentBlock() *types.WorkObject GetBlock(hash common.Hash, number uint64) *types.WorkObject - StateAt(root common.Hash, utxoRoot common.Hash) (*state.StateDB, error) + StateAt(root, utxoRoot, etxRoot common.Hash) (*state.StateDB, error) SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription IsGenesisHash(hash common.Hash) bool CheckIfEtxIsEligible(hash common.Hash, location common.Location) bool @@ -1612,11 +1612,13 @@ func (pool *TxPool) reset(oldHead, newHead *types.WorkObject) { evmRoot := newHead.EVMRoot() utxoRoot := newHead.UTXORoot() + etxRoot := newHead.EtxSetRoot() if pool.chain.IsGenesisHash(newHead.Hash()) { evmRoot = types.EmptyRootHash utxoRoot = types.EmptyRootHash + etxRoot = types.EmptyRootHash } - statedb, err := pool.chain.StateAt(evmRoot, utxoRoot) + statedb, err := pool.chain.StateAt(evmRoot, utxoRoot, etxRoot) if err != nil { pool.logger.WithField("err", err).Error("Failed to reset txpool state") return diff --git a/core/types.go b/core/types.go index 32520c3337..dc1a07391a 100644 --- a/core/types.go +++ b/core/types.go @@ -31,7 +31,7 @@ type Validator interface { // ValidateState validates the given statedb and optionally the receipts and // gas used. - ValidateState(block *types.WorkObject, state *state.StateDB, receipts types.Receipts, utxoEtxs []*types.Transaction, etxSet *types.EtxSet, usedGas uint64) error + ValidateState(block *types.WorkObject, state *state.StateDB, receipts types.Receipts, utxoEtxs []*types.Transaction, usedGas uint64) error } // Prefetcher is an interface for pre-caching transaction signatures and state. diff --git a/core/types/block.go b/core/types/block.go index ac98437a02..687ed3633f 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -33,20 +33,18 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/common/hexutil" - "github.com/dominant-strategies/go-quai/crypto" "github.com/dominant-strategies/go-quai/log" "github.com/dominant-strategies/go-quai/rlp" ) var ( - EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") - EmptyUncleHash = RlpHash([]*Header(nil)) - EmptyBodyHash = common.HexToHash("51e1b9c1426a03bf73da3d98d9f384a49ded6a4d705dcdf25433915c3306826c") - EmptyEtxSetHash = crypto.Keccak256Hash([]byte{}) - EmptyHash = common.Hash{} - big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil) // 2^256 - hasher = blake3.New(32, nil) - hasherMu sync.RWMutex + EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + EmptyUncleHash = RlpHash([]*Header(nil)) + EmptyBodyHash = common.HexToHash("51e1b9c1426a03bf73da3d98d9f384a49ded6a4d705dcdf25433915c3306826c") + EmptyHash = common.Hash{} + big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil) // 2^256 + hasher = blake3.New(32, nil) + hasherMu sync.RWMutex ) // A BlockNonce is a 64-bit hash which proves (combined with the @@ -97,7 +95,7 @@ type Header struct { utxoRoot common.Hash `json:"utxoRoot" gencodec:"required"` txHash common.Hash `json:"transactionsRoot" gencodec:"required"` etxHash common.Hash `json:"extTransactionsRoot" gencodec:"required"` - etxSetHash common.Hash `json:"etxSetHash" gencodec:"required"` + etxSetRoot common.Hash `json:"etxSetRoot" gencodec:"required"` etxRollupHash common.Hash `json:"extRollupRoot" gencodec:"required"` manifestHash []common.Hash `json:"manifestHash" gencodec:"required"` receiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` @@ -158,7 +156,7 @@ func EmptyHeader(nodeCtx int) *WorkObject { h.utxoRoot = EmptyRootHash h.txHash = EmptyRootHash h.etxHash = EmptyRootHash - h.etxSetHash = EmptyEtxSetHash + h.etxSetRoot = EmptyRootHash h.etxRollupHash = EmptyRootHash h.uncleHash = EmptyUncleHash h.baseFee = big.NewInt(0) @@ -207,7 +205,7 @@ func (h *Header) ProtoEncode() (*ProtoHeader, error) { utxoRoot := common.ProtoHash{Value: h.UTXORoot().Bytes()} txHash := common.ProtoHash{Value: h.TxHash().Bytes()} etxhash := common.ProtoHash{Value: h.EtxHash().Bytes()} - etxSetHash := common.ProtoHash{Value: h.EtxSetHash().Bytes()} + etxSetRoot := common.ProtoHash{Value: h.EtxSetRoot().Bytes()} etxRollupHash := common.ProtoHash{Value: h.EtxRollupHash().Bytes()} receiptHash := common.ProtoHash{Value: h.ReceiptHash().Bytes()} mixHash := common.ProtoHash{Value: h.MixHash().Bytes()} @@ -227,7 +225,7 @@ func (h *Header) ProtoEncode() (*ProtoHeader, error) { UtxoRoot: &utxoRoot, TxHash: &txHash, EtxHash: &etxhash, - EtxSetHash: &etxSetHash, + EtxSetRoot: &etxSetRoot, EtxRollupHash: &etxRollupHash, ReceiptHash: &receiptHash, PrimeTerminus: &primeTerminus, @@ -289,8 +287,8 @@ func (h *Header) ProtoDecode(protoHeader *ProtoHeader, location common.Location) if protoHeader.EtxHash == nil { return errors.New("missing required field 'EtxHash' in Header") } - if protoHeader.EtxSetHash == nil { - return errors.New("missing required field 'EtxSetHash' in Header") + if protoHeader.EtxSetRoot == nil { + return errors.New("missing required field 'EtxSetRoot' in Header") } if protoHeader.EtxRollupHash == nil { return errors.New("missing required field 'EtxRollupHash' in Header") @@ -367,7 +365,7 @@ func (h *Header) ProtoDecode(protoHeader *ProtoHeader, location common.Location) h.SetTxHash(common.BytesToHash(protoHeader.GetTxHash().GetValue())) h.SetReceiptHash(common.BytesToHash(protoHeader.GetReceiptHash().GetValue())) h.SetEtxHash(common.BytesToHash(protoHeader.GetEtxHash().GetValue())) - h.SetEtxSetHash(common.BytesToHash(protoHeader.GetEtxSetHash().GetValue())) + h.SetEtxSetRoot(common.BytesToHash(protoHeader.GetEtxSetRoot().GetValue())) h.SetEtxRollupHash(common.BytesToHash(protoHeader.GetEtxRollupHash().GetValue())) h.SetPrimeTerminus(common.BytesToHash(protoHeader.GetPrimeTerminus().GetValue())) h.SetInterlinkRootHash(common.BytesToHash(protoHeader.GetInterlinkRootHash().GetValue())) @@ -407,7 +405,7 @@ func (h *Header) RPCMarshalHeader() map[string]interface{} { "transactionsRoot": h.TxHash(), "receiptsRoot": h.ReceiptHash(), "extTransactionsRoot": h.EtxHash(), - "etxSetHash": h.EtxSetHash(), + "etxSetRoot": h.EtxSetRoot(), "extRollupRoot": h.EtxRollupHash(), "primeTerminus": h.PrimeTerminus(), "interlinkRootHash": h.InterlinkRootHash(), @@ -470,8 +468,8 @@ func (h *Header) TxHash() common.Hash { func (h *Header) EtxHash() common.Hash { return h.etxHash } -func (h *Header) EtxSetHash() common.Hash { - return h.etxSetHash +func (h *Header) EtxSetRoot() common.Hash { + return h.etxSetRoot } func (h *Header) EtxRollupHash() common.Hash { return h.etxRollupHash @@ -563,10 +561,10 @@ func (h *Header) SetEtxHash(val common.Hash) { h.sealHash = atomic.Value{} // clear sealHash cache h.etxHash = val } -func (h *Header) SetEtxSetHash(val common.Hash) { +func (h *Header) SetEtxSetRoot(val common.Hash) { h.hash = atomic.Value{} // clear hash cache h.sealHash = atomic.Value{} // clear sealHash cache - h.etxSetHash = val + h.etxSetRoot = val } func (h *Header) SetEtxRollupHash(val common.Hash) { h.hash = atomic.Value{} // clear hash cache @@ -679,7 +677,7 @@ func (h *Header) SealEncode() *ProtoHeader { utxoRoot := common.ProtoHash{Value: h.UTXORoot().Bytes()} txHash := common.ProtoHash{Value: h.TxHash().Bytes()} etxhash := common.ProtoHash{Value: h.EtxHash().Bytes()} - etxSetHash := common.ProtoHash{Value: h.EtxSetHash().Bytes()} + etxSetRoot := common.ProtoHash{Value: h.EtxSetRoot().Bytes()} etxRollupHash := common.ProtoHash{Value: h.EtxRollupHash().Bytes()} receiptHash := common.ProtoHash{Value: h.ReceiptHash().Bytes()} etxEligibleSlices := common.ProtoHash{Value: h.EtxEligibleSlices().Bytes()} @@ -698,7 +696,7 @@ func (h *Header) SealEncode() *ProtoHeader { UtxoRoot: &utxoRoot, TxHash: &txHash, EtxHash: &etxhash, - EtxSetHash: &etxSetHash, + EtxSetRoot: &etxSetRoot, EtxRollupHash: &etxRollupHash, ReceiptHash: &receiptHash, GasLimit: &gasLimit, @@ -871,7 +869,7 @@ func CopyHeader(h *Header) *Header { cpy.SetUTXORoot(h.UTXORoot()) cpy.SetTxHash(h.TxHash()) cpy.SetEtxHash(h.EtxHash()) - cpy.SetEtxSetHash(h.EtxSetHash()) + cpy.SetEtxSetRoot(h.EtxSetRoot()) cpy.SetEtxRollupHash(h.EtxRollupHash()) cpy.SetReceiptHash(h.ReceiptHash()) cpy.SetPrimeTerminus(h.PrimeTerminus()) diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 6286beeae6..00988b034f 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -24,7 +24,7 @@ func (h Header) MarshalJSON() ([]byte, error) { TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` EtxHash common.Hash `json:"extTransactionsRoot" gencodec:"required"` - EtxSetHash common.Hash `json:"etxSetHash" gencodec:"required"` + EtxSetRoot common.Hash `json:"etxSetRoot" gencodec:"required"` EtxRollupHash common.Hash `json:"extRollupRoot" gencodec:"required"` ManifestHash []common.Hash `json:"manifestHash" gencodec:"required"` PrimeTerminus common.Hash `json:"primeTerminus" gencodec:"required"` @@ -67,7 +67,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.UTXORoot = h.UTXORoot() enc.TxHash = h.TxHash() enc.EtxHash = h.EtxHash() - enc.EtxSetHash = h.EtxSetHash() + enc.EtxSetRoot = h.EtxSetRoot() enc.EtxRollupHash = h.EtxRollupHash() enc.ReceiptHash = h.ReceiptHash() enc.PrimeTerminus = h.PrimeTerminus() @@ -96,7 +96,7 @@ func (h *Header) UnmarshalJSON(input []byte) error { TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` EtxHash *common.Hash `json:"extTransactionsRoot" gencodec:"required"` - EtxSetHash *common.Hash `json:"etxSetHash" gencodec:"required"` + EtxSetRoot *common.Hash `json:"etxSetRoot" gencodec:"required"` EtxRollupHash *common.Hash `json:"extRollupRoot" gencodec:"required"` ManifestHash []common.Hash `json:"manifestHash" gencodec:"required"` PrimeTerminus *common.Hash `json:"primeTerminus" gencodec:"required"` @@ -140,8 +140,8 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.EtxHash == nil { return errors.New("missing required field 'extTransactionsRoot' for Header") } - if dec.EtxSetHash == nil { - return errors.New("missing required field 'etxSetHash' for Header") + if dec.EtxSetRoot == nil { + return errors.New("missing required field 'etxSetRoot' for Header") } if dec.EtxRollupHash == nil { return errors.New("missing required field 'extRollupRoot' for Header") @@ -234,7 +234,7 @@ func (h *Header) UnmarshalJSON(input []byte) error { h.SetTxHash(*dec.TxHash) h.SetReceiptHash(*dec.ReceiptHash) h.SetEtxHash(*dec.EtxHash) - h.SetEtxSetHash(*dec.EtxSetHash) + h.SetEtxSetRoot(*dec.EtxSetRoot) h.SetEtxRollupHash(*dec.EtxRollupHash) h.SetPrimeTerminus(*dec.PrimeTerminus) h.SetInterlinkRootHash(*dec.InterlinkRootHash) diff --git a/core/types/proto_block.pb.go b/core/types/proto_block.pb.go index 3668d9216f..8bcfe5804a 100644 --- a/core/types/proto_block.pb.go +++ b/core/types/proto_block.pb.go @@ -49,7 +49,7 @@ type ProtoHeader struct { MixHash *common.ProtoHash `protobuf:"bytes,21,opt,name=mix_hash,json=mixHash,proto3,oneof" json:"mix_hash,omitempty"` Nonce *uint64 `protobuf:"varint,22,opt,name=nonce,proto3,oneof" json:"nonce,omitempty"` UtxoRoot *common.ProtoHash `protobuf:"bytes,23,opt,name=utxo_root,json=utxoRoot,proto3,oneof" json:"utxo_root,omitempty"` - EtxSetHash *common.ProtoHash `protobuf:"bytes,24,opt,name=etx_set_hash,json=etxSetHash,proto3,oneof" json:"etx_set_hash,omitempty"` + EtxSetRoot *common.ProtoHash `protobuf:"bytes,24,opt,name=etx_set_root,json=etxSetRoot,proto3,oneof" json:"etx_set_root,omitempty"` EfficiencyScore *uint64 `protobuf:"varint,25,opt,name=efficiency_score,json=efficiencyScore,proto3,oneof" json:"efficiency_score,omitempty"` ThresholdCount *uint64 `protobuf:"varint,26,opt,name=threshold_count,json=thresholdCount,proto3,oneof" json:"threshold_count,omitempty"` ExpansionNumber *uint64 `protobuf:"varint,27,opt,name=expansion_number,json=expansionNumber,proto3,oneof" json:"expansion_number,omitempty"` @@ -251,9 +251,9 @@ func (x *ProtoHeader) GetUtxoRoot() *common.ProtoHash { return nil } -func (x *ProtoHeader) GetEtxSetHash() *common.ProtoHash { +func (x *ProtoHeader) GetEtxSetRoot() *common.ProtoHash { if x != nil { - return x.EtxSetHash + return x.EtxSetRoot } return nil } @@ -1946,10 +1946,10 @@ var file_core_types_proto_block_proto_rawDesc = []byte{ 0x09, 0x75, 0x74, 0x78, 0x6f, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x48, 0x10, 0x52, 0x08, 0x75, 0x74, 0x78, 0x6f, 0x52, 0x6f, 0x6f, 0x74, 0x88, - 0x01, 0x01, 0x12, 0x38, 0x0a, 0x0c, 0x65, 0x74, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x01, 0x01, 0x12, 0x38, 0x0a, 0x0c, 0x65, 0x74, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x6f, + 0x6f, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x48, 0x11, 0x52, 0x0a, 0x65, - 0x74, 0x78, 0x53, 0x65, 0x74, 0x48, 0x61, 0x73, 0x68, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, + 0x74, 0x78, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x04, 0x48, 0x12, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x0f, @@ -1986,8 +1986,8 @@ var file_core_types_proto_block_proto_rawDesc = []byte{ 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x69, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x5f, 0x72, 0x6f, 0x6f, 0x74, - 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x65, 0x74, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, + 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x65, 0x74, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x6f, + 0x74, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x42, @@ -2339,7 +2339,7 @@ var file_core_types_proto_block_proto_depIdxs = []int32{ 27, // 8: block.ProtoHeader.location:type_name -> common.ProtoLocation 26, // 9: block.ProtoHeader.mix_hash:type_name -> common.ProtoHash 26, // 10: block.ProtoHeader.utxo_root:type_name -> common.ProtoHash - 26, // 11: block.ProtoHeader.etx_set_hash:type_name -> common.ProtoHash + 26, // 11: block.ProtoHeader.etx_set_root:type_name -> common.ProtoHash 26, // 12: block.ProtoHeader.etx_eligible_slices:type_name -> common.ProtoHash 26, // 13: block.ProtoHeader.prime_terminus:type_name -> common.ProtoHash 26, // 14: block.ProtoHeader.interlink_root_hash:type_name -> common.ProtoHash diff --git a/core/types/proto_block.proto b/core/types/proto_block.proto index 430b72f2e5..b8f07f6f91 100644 --- a/core/types/proto_block.proto +++ b/core/types/proto_block.proto @@ -29,7 +29,7 @@ message ProtoHeader { optional common.ProtoHash mix_hash = 21; optional uint64 nonce = 22; optional common.ProtoHash utxo_root = 23; - optional common.ProtoHash etx_set_hash = 24; + optional common.ProtoHash etx_set_root = 24; optional uint64 efficiency_score = 25; optional uint64 threshold_count = 26; optional uint64 expansion_number = 27; diff --git a/core/types/wo.go b/core/types/wo.go index d0e972619b..6280d9fcf8 100644 --- a/core/types/wo.go +++ b/core/types/wo.go @@ -202,8 +202,8 @@ func (wo *WorkObject) EtxRollupHash() common.Hash { return wo.Header().EtxRollupHash() } -func (wo *WorkObject) EtxSetHash() common.Hash { - return wo.Header().EtxSetHash() +func (wo *WorkObject) EtxSetRoot() common.Hash { + return wo.Header().EtxSetRoot() } func (wo *WorkObject) BaseFee() *big.Int { @@ -323,7 +323,7 @@ func (wo *WorkObject) QiTransactionsWithoutCoinbase() []*Transaction { func (wo *WorkObject) QuaiTransactionsWithoutCoinbase() []*Transaction { quaiTxs := make([]*Transaction, 0) for i, t := range wo.Transactions() { - if i == 0 && IsCoinBaseTx(t, wo.woHeader.parentHash, wo.woHeader.location) || t.Type() == QiTxType || (t.Type() == ExternalTxType && t.ETXSender().Location().Equal(*t.To().Location())) { + if i == 0 && IsCoinBaseTx(t, wo.woHeader.parentHash, wo.woHeader.location) || t.Type() == QiTxType || (t.Type() == ExternalTxType && t.To().IsInQiLedgerScope()) { // ignore the Quai coinbase tx and Quai->Qi to comply with prior functionality as it is not a normal transaction continue } @@ -334,6 +334,16 @@ func (wo *WorkObject) QuaiTransactionsWithoutCoinbase() []*Transaction { return quaiTxs } +func (wo *WorkObject) QuaiTransactionsWithFees() []*Transaction { + quaiTxs := make([]*Transaction, 0) + for _, t := range wo.Transactions() { + if t.Type() == QuaiTxType { // QuaiTxType is the only type that gives Quai fees to the miner + quaiTxs = append(quaiTxs, t) + } + } + return quaiTxs +} + func (wo *WorkObject) NumberArray() []*big.Int { numArray := make([]*big.Int, common.HierarchyDepth) for i := 0; i < common.HierarchyDepth; i++ { diff --git a/core/worker.go b/core/worker.go index 2694295672..3c6e169afd 100644 --- a/core/worker.go +++ b/core/worker.go @@ -559,7 +559,7 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool) (*typ w.adjustGasLimit(work, block) work.utxoFees = big.NewInt(0) start := time.Now() - etxSet := w.fillTransactions(interrupt, work, block, fill) + w.fillTransactions(interrupt, work, block, fill) if fill { w.fillTransactionsRollingAverage.Add(time.Since(start)) w.logger.WithFields(log.Fields{ @@ -568,12 +568,6 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool) (*typ "average": common.PrettyDuration(w.fillTransactionsRollingAverage.Average()), }).Info("Filled and sorted pending transactions") } - // Set the etx set commitment in the header - if etxSet != nil { - work.wo.Header().SetEtxSetHash(etxSet.Hash()) - } else { - work.wo.Header().SetEtxSetHash(types.EmptyEtxSetHash) - } if coinbase.IsInQiLedgerScope() { coinbaseTx, err := createQiCoinbaseTxWithFees(work.wo, work.utxoFees, work.state, work.signer, w.ephemeralKey) if err != nil { @@ -617,7 +611,7 @@ func (w *worker) printPendingHeaderInfo(work *environment, block *types.WorkObje "fees": totalFees(block, work.receipts), "elapsed": common.PrettyDuration(time.Since(start)), "utxoRoot": block.UTXORoot(), - "etxSetHash": block.EtxSetHash(), + "etxSetRoot": block.EtxSetRoot(), }).Info("Commit new sealing work") } else { w.logger.WithFields(log.Fields{ @@ -631,7 +625,7 @@ func (w *worker) printPendingHeaderInfo(work *environment, block *types.WorkObje "fees": totalFees(block, work.receipts), "elapsed": common.PrettyDuration(time.Since(start)), "utxoRoot": block.UTXORoot(), - "etxSetHash": block.EtxSetHash(), + "etxSetRoot": block.EtxSetRoot(), }).Debug("Commit new sealing work") } work.uncleMu.RUnlock() @@ -660,11 +654,13 @@ func (w *worker) makeEnv(parent *types.WorkObject, proposedWo *types.WorkObject, // the miner to speed block sealing up a bit. evmRoot := parent.EVMRoot() utxoRoot := parent.UTXORoot() + etxRoot := parent.EtxSetRoot() if w.hc.IsGenesisHash(parent.Hash()) { evmRoot = types.EmptyRootHash utxoRoot = types.EmptyRootHash + etxRoot = types.EmptyRootHash } - state, err := w.hc.bc.processor.StateAt(evmRoot, utxoRoot) + state, err := w.hc.bc.processor.StateAt(evmRoot, utxoRoot, etxRoot) if err != nil { return nil, err } @@ -813,14 +809,19 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t return nil, errors.New("error finding transaction") } -func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, etxs []*types.Transaction, txs *types.TransactionsByPriceAndNonce, etxSet *types.EtxSet, interrupt *int32) bool { +func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, txs *types.TransactionsByPriceAndNonce, interrupt *int32) bool { gasLimit := env.wo.GasLimit if env.gasPool == nil { env.gasPool = new(types.GasPool).AddGas(gasLimit()) } var coalescedLogs []*types.Log minEtxGas := gasLimit() / params.MinimumEtxGasDivisor - for _, tx := range etxs { + oldestIndex, err := env.state.GetOldestIndex() + if err != nil { + w.logger.WithField("err", err).Error("Failed to get oldest index") + return true + } + for { if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { return atomic.LoadInt32(interrupt) == commitInterruptNewHead } @@ -839,17 +840,21 @@ func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, log.Global.WithField("Gas Used", env.wo.GasUsed()).Error("Block uses more gas than maximum ETX gas") return true } - hash := etxSet.Pop() - if hash != tx.Hash() { // sanity check, this should never happen - log.Global.Errorf("ETX hash from set %032x does not match transaction hash %032x", hash, tx.Hash()) + etx, err := env.state.PopETX() + if err != nil { + w.logger.WithField("err", err).Error("Failed to read ETX") return true } - env.state.Prepare(tx.Hash(), env.tcount) - logs, err := w.commitTransaction(env, parent, tx) + if etx == nil { + break + } + env.state.Prepare(etx.Hash(), env.tcount) + logs, err := w.commitTransaction(env, parent, etx) if err == nil { coalescedLogs = append(coalescedLogs, logs...) env.tcount++ } + oldestIndex.Add(oldestIndex, big.NewInt(1)) } for { // In the following three cases, we will interrupt the execution of the transaction. @@ -1227,54 +1232,51 @@ func (w *worker) prepareWork(genParams *generateParams, wo *types.WorkObject) (* // fillTransactions retrieves the pending transactions from the txpool and fills them // into the given sealing block. The transaction selection and ordering strategy can // be customized with the plugin in the future. -func (w *worker) fillTransactions(interrupt *int32, env *environment, block *types.WorkObject, fill bool) *types.EtxSet { +func (w *worker) fillTransactions(interrupt *int32, env *environment, block *types.WorkObject, fill bool) bool { // Split the pending transactions into locals and remotes // Fill the block with all available pending transactions. - etxs := make([]*types.Transaction, 0) - etxSet := rawdb.ReadEtxSet(w.hc.bc.db, block.Hash(), block.NumberU64(w.hc.NodeCtx())) - if etxSet != nil { - etxs = make([]*types.Transaction, 0, len(etxSet.ETXHashes)/common.HashLength) - maxEtxGas := (env.wo.GasLimit() / params.MinimumEtxGasDivisor) * params.MaximumEtxGasMultiplier - totalGasEstimate := uint64(0) - index := 0 - for { - hash := etxSet.GetHashAtIndex(index) - if (hash == common.Hash{}) { // no more ETXs - break - } - entry := rawdb.ReadETX(w.hc.bc.db, hash) - if entry == nil { - log.Global.Errorf("ETX %s not found in the database!", hash.String()) - break - } - etxs = append(etxs, entry) - if totalGasEstimate += entry.Gas(); totalGasEstimate > maxEtxGas { // We don't need to load any more ETXs after this limit - break - } - index++ + etxs := false + newInboundEtxs := rawdb.ReadInboundEtxs(w.workerDb, block.Hash()) + if len(newInboundEtxs) > 0 { + etxs = true + env.state.PushETXs(newInboundEtxs) // apply the inbound ETXs from the previous block to the ETX set state + } else { + oldestIndex, err := env.state.GetOldestIndex() + if err != nil { + w.logger.WithField("err", err).Error("Failed to get oldest index") + return false + } + // Check if there is at least one ETX in the set + etx, err := env.state.ReadETX(oldestIndex) + if err != nil { + w.logger.WithField("err", err).Error("Failed to read ETX") + return false + } + if etx != nil { + etxs = true } } + if !fill { - if len(etxs) > 0 { - w.commitTransactions(env, block, etxs, &types.TransactionsByPriceAndNonce{}, etxSet, interrupt) + if etxs { + return w.commitTransactions(env, block, &types.TransactionsByPriceAndNonce{}, interrupt) } - return etxSet + return false } pending, err := w.txPool.TxPoolPending(true) if err != nil { - return nil + w.logger.WithField("err", err).Error("Failed to get pending transactions") + return false } pendingQiTxs := w.txPool.QiPoolPending() - if len(pending) > 0 || len(pendingQiTxs) > 0 || len(etxs) > 0 { + if len(pending) > 0 || len(pendingQiTxs) > 0 || etxs { txs := types.NewTransactionsByPriceAndNonce(env.signer, pendingQiTxs, pending, env.wo.BaseFee(), true) - if w.commitTransactions(env, block, etxs, txs, etxSet, interrupt) { - return etxSet - } + return w.commitTransactions(env, block, txs, interrupt) } - return etxSet + return false } // fillTransactions retrieves the pending transactions from the txpool and fills them diff --git a/quai/api.go b/quai/api.go index 5841b59cc9..c99ab8205d 100644 --- a/quai/api.go +++ b/quai/api.go @@ -260,7 +260,7 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error if block == nil { return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) } - stateDb, err := api.quai.core.StateAt(block.EVMRoot(), block.UTXORoot()) + stateDb, err := api.quai.core.StateAt(block.EVMRoot(), block.UTXORoot(), block.EtxSetRoot()) if err != nil { return state.Dump{}, err } @@ -311,7 +311,7 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta if block == nil { return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) } - stateDb, err = api.quai.core.StateAt(block.EVMRoot(), block.UTXORoot()) + stateDb, err = api.quai.core.StateAt(block.EVMRoot(), block.UTXORoot(), block.EtxSetRoot()) if err != nil { return state.IteratorDump{}, err } @@ -321,7 +321,7 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta if block == nil { return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex()) } - stateDb, err = api.quai.core.StateAt(block.EVMRoot(), block.UTXORoot()) + stateDb, err = api.quai.core.StateAt(block.EVMRoot(), block.UTXORoot(), block.EtxSetRoot()) if err != nil { return state.IteratorDump{}, err } diff --git a/quai/api_backend.go b/quai/api_backend.go index ded98db698..a6a6daac54 100644 --- a/quai/api_backend.go +++ b/quai/api_backend.go @@ -187,7 +187,7 @@ func (b *QuaiAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc. if header == nil { return nil, nil, errors.New("header not found") } - stateDb, err := b.quai.Core().StateAt(header.EVMRoot(), header.UTXORoot()) + stateDb, err := b.quai.Core().StateAt(header.EVMRoot(), header.UTXORoot(), header.EtxSetRoot()) return stateDb, header, err } @@ -210,7 +210,7 @@ func (b *QuaiAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, block if blockNrOrHash.RequireCanonical && b.quai.core.GetCanonicalHash(header.NumberU64(b.NodeCtx())) != hash { return nil, nil, errors.New("hash is not currently canonical") } - stateDb, err := b.quai.Core().StateAt(header.EVMRoot(), header.UTXORoot()) + stateDb, err := b.quai.Core().StateAt(header.EVMRoot(), header.UTXORoot(), header.EtxSetRoot()) return stateDb, header, err } return nil, nil, errors.New("invalid arguments; neither block nor hash specified") diff --git a/quai/backend.go b/quai/backend.go index f517fe44e2..c19d27410b 100644 --- a/quai/backend.go +++ b/quai/backend.go @@ -228,6 +228,7 @@ func New(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx TrieCleanLimit: config.TrieCleanCache, TrieCleanJournal: stack.ResolvePath(config.TrieCleanCacheJournal), UTXOTrieCleanJournal: stack.ResolvePath(config.UTXOTrieCleanCacheJournal), + ETXTrieCleanJournal: stack.ResolvePath(config.ETXTrieCleanCacheJournal), TrieCleanRejournal: config.TrieCleanCacheRejournal, TrieCleanNoPrefetch: config.NoPrefetch, TrieDirtyLimit: config.TrieDirtyCache, diff --git a/quai/quaiconfig/config.go b/quai/quaiconfig/config.go index 28ce03ee5a..b2c0a11b0f 100644 --- a/quai/quaiconfig/config.go +++ b/quai/quaiconfig/config.go @@ -74,6 +74,7 @@ var Defaults = Config{ TrieCleanCache: 154, TrieCleanCacheJournal: "triecache", UTXOTrieCleanCacheJournal: "utxotriecache", + ETXTrieCleanCacheJournal: "etxtriecache", TrieCleanCacheRejournal: 60 * time.Minute, TrieDirtyCache: 256, TrieTimeout: 60 * time.Minute, @@ -124,6 +125,7 @@ type Config struct { TrieCleanCache int TrieCleanCacheJournal string `toml:",omitempty"` // Disk journal directory for trie cache to survive node restarts UTXOTrieCleanCacheJournal string `toml:",omitempty"` // Disk journal directory for trie cache to survive node restarts + ETXTrieCleanCacheJournal string `toml:",omitempty"` // Disk journal directory for trie cache to survive node restarts TrieCleanCacheRejournal time.Duration `toml:",omitempty"` // Time interval to regenerate the journal for clean cache TrieDirtyCache int TrieTimeout time.Duration