diff --git a/common/proto_common.pb.go b/common/proto_common.pb.go index b070827015..30b8fc2958 100644 --- a/common/proto_common.pb.go +++ b/common/proto_common.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.3 +// protoc v4.25.2 // source: common/proto_common.proto package common diff --git a/consensus/blake3pow/consensus.go b/consensus/blake3pow/consensus.go index 76b899c839..f0e6672edc 100644 --- a/consensus/blake3pow/consensus.go +++ b/consensus/blake3pow/consensus.go @@ -628,6 +628,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 2e7da2c0ad..963c79b825 100644 --- a/consensus/progpow/consensus.go +++ b/consensus/progpow/consensus.go @@ -677,6 +677,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 36afb899ea..a80bfd0f8f 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -109,7 +109,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)) @@ -132,6 +132,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 @@ -148,19 +151,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 ff49b08d7b..5889be6c9a 100644 --- a/core/bodydb.go +++ b/core/bodydb.go @@ -91,7 +91,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() @@ -102,7 +102,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 248dfcebba..3b9ae47b97 100644 --- a/core/core.go +++ b/core/core.go @@ -1120,8 +1120,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 d591e996a1..4c197abc52 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, logger); 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, logger); 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) } @@ -432,7 +432,7 @@ func DefaultLocalGenesisBlock(consensusEngine string) *Genesis { Nonce: 66, ExtraData: hexutil.MustDecode("0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fb"), GasLimit: 5000000, - Difficulty: big.NewInt(100000), + Difficulty: big.NewInt(10000), } } return &Genesis{ diff --git a/core/headerchain.go b/core/headerchain.go index aaec7bbdca..b0a416f6fb 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -341,10 +341,10 @@ func (hc *HeaderChain) setStateProcessing() 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 } @@ -473,10 +473,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 { + + // Check if the state has been processed for this block + processedState := rawdb.ReadProcessedState(hc.headerDb, header.Hash()) + if processedState { break } current = types.CopyWorkObject(header) @@ -484,7 +484,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 } @@ -492,28 +496,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) @@ -1030,8 +1012,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.go b/core/rawdb/accessors_chain.go index 4aaa3dd8cd..d70d2d7f2c 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -132,6 +132,20 @@ func DeleteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash) { } } +func ReadProcessedState(db ethdb.KeyValueReader, hash common.Hash) bool { + data, _ := db.Get(processedStateKey(hash)) + if len(data) == 0 { + return false + } + return data[0] == 1 +} + +func WriteProcessedState(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Put(processedStateKey(hash), []byte{1}); err != nil { + db.Logger().WithField("err", err).Fatal("Failed to store processed state for block " + hash.String()) + } +} + // ReadHeadHeaderHash retrieves the hash of the current canonical head header. func ReadHeadHeaderHash(db ethdb.KeyValueReader) common.Hash { data, _ := db.Get(headHeaderKey) 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/rawdb/db.pb.go b/core/rawdb/db.pb.go index efb89f164a..0f2560b344 100644 --- a/core/rawdb/db.pb.go +++ b/core/rawdb/db.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.3 +// protoc v4.25.2 // source: core/rawdb/db.proto package rawdb diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index a040d22028..fa1dd034a3 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -108,6 +108,7 @@ var ( UtxoPrefix = []byte("ut") // outpointPrefix + hash -> types.Outpoint spentUTXOsPrefix = []byte("sutxo") // spentUTXOsPrefix + hash -> []types.SpentTxOut AddressUtxosPrefix = []byte("au") // addressUtxosPrefix + hash -> []types.UtxoEntry + processedStatePrefix = []byte("ps") // processedStatePrefix + hash -> boolean blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts @@ -268,6 +269,10 @@ func headerNumberKey(hash common.Hash) []byte { return append(headerNumberPrefix, hash.Bytes()...) } +func processedStateKey(hash common.Hash) []byte { + return append(processedStatePrefix, hash.Bytes()...) +} + // blockBodyKey = blockBodyPrefix + num (uint64 big endian) + hash func blockBodyKey(number uint64, hash common.Hash) []byte { return append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) diff --git a/core/slice.go b/core/slice.go index e1d3890f6b..76c934037a 100644 --- a/core/slice.go +++ b/core/slice.go @@ -343,8 +343,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) } @@ -458,6 +459,7 @@ func (sl *Slice) Append(header *types.WorkObject, domPendingHeader *types.WorkOb "gasLimit": block.GasLimit(), "evmRoot": block.EVMRoot(), "utxoRoot": block.UTXORoot(), + "etxSetRoot": block.EtxSetRoot(), "order": order, "location": block.Location(), "elapsed": common.PrettyDuration(time.Since(start)), @@ -1254,7 +1256,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) @@ -1376,10 +1377,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()) @@ -1437,7 +1439,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 9132fd506a..4902817ae3 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -26,6 +26,7 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/core/rawdb" @@ -45,7 +46,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 @@ -94,10 +98,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 logger *log.Logger @@ -156,7 +162,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, logger *log.Logger) (*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, logger *log.Logger) (*StateDB, error) { tr, err := db.OpenTrie(root) if err != nil { return nil, err @@ -165,11 +171,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, logger: logger, @@ -392,6 +404,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 { @@ -627,6 +643,148 @@ 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()) + } + protoTx, err := etx.ProtoEncode() + if err != nil { + return err + } + protoTxBytes, err := proto.Marshal(protoTx) + if err != nil { + return err + } + newestIndex, err := s.GetNewestIndex() + if err != nil { + return err + } + if err := s.etxTrie.TryUpdate(newestIndex.Bytes(), protoTxBytes); 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 { + protoTx, err := etx.ProtoEncode() + if err != nil { + return err + } + protoTxBytes, err := proto.Marshal(protoTx) + if err != nil { + return err + } + if err := s.etxTrie.TryUpdate(newestIndex.Bytes(), protoTxBytes); 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 + } + protoEtx := new(types.ProtoTransaction) + if err := proto.Unmarshal(enc, protoEtx); err != nil { + return nil, err + } + etx := new(types.Transaction) + if err := etx.ProtoDecode(protoEtx, s.nodeLocation); 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 + } + protoEtx := new(types.ProtoTransaction) + if err := proto.Unmarshal(enc, protoEtx); err != nil { + return nil, err + } + etx := new(types.Transaction) + if err := etx.ProtoDecode(protoEtx, s.nodeLocation); 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 29d92b3012..ff263a61bc 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, p.logger) + statedb, err := state.New(parentEvmRoot, parentUtxoRoot, parentEtxSetRoot, p.stateCache, p.utxoCache, p.etxCache, p.snaps, nodeLocation, p.logger) 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 @@ -318,19 +336,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 @@ -351,7 +375,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) @@ -360,7 +384,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 @@ -374,19 +398,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) @@ -492,8 +516,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) } @@ -818,28 +854,22 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, updateState bool, ch } // 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 } @@ -850,7 +880,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 } @@ -872,27 +902,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, @@ -904,11 +930,9 @@ 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") + // Indicate that we have processed the state of the block + rawdb.WriteProcessedState(batch, block.Hash()) return logs, nil } @@ -951,12 +975,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(), p.logger) +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(), p.logger) } // StateCache returns the caching database underpinning the blockchain instance. @@ -1055,6 +1079,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() @@ -1062,7 +1087,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 } @@ -1071,7 +1096,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 @@ -1083,12 +1108,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, p.logger) + statedb, err = state.New(current.EVMRoot(), current.UTXORoot(), current.EtxSetRoot(), database, utxoDatabase, etxDatabase, nil, nodeLocation, p.logger) if err == nil { return statedb, nil } @@ -1105,7 +1133,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, p.logger) + statedb, err = state.New(current.EVMRoot(), current.UTXORoot(), current.EtxSetRoot(), database, utxoDatabase, etxDatabase, nil, nodeLocation, p.logger) if err == nil { break } @@ -1138,27 +1166,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(p.logger), 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) } @@ -1173,7 +1185,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, p.logger) + 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, p.logger) if err != nil { return nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(nodeCtx), err) } @@ -1249,6 +1266,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 2d118e328d..638f81a540 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -146,7 +146,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 @@ -1742,11 +1742,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 d59282f01b..0429d45383 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -33,19 +33,17 @@ 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/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 @@ -96,7 +94,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"` @@ -157,7 +155,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) @@ -206,7 +204,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()} @@ -226,7 +224,7 @@ func (h *Header) ProtoEncode() (*ProtoHeader, error) { UtxoRoot: &utxoRoot, TxHash: &txHash, EtxHash: &etxhash, - EtxSetHash: &etxSetHash, + EtxSetRoot: &etxSetRoot, EtxRollupHash: &etxRollupHash, ReceiptHash: &receiptHash, PrimeTerminus: &primeTerminus, @@ -288,8 +286,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") @@ -366,7 +364,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())) @@ -406,7 +404,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(), @@ -469,8 +467,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 @@ -562,10 +560,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 @@ -678,7 +676,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()} @@ -697,7 +695,7 @@ func (h *Header) SealEncode() *ProtoHeader { UtxoRoot: &utxoRoot, TxHash: &txHash, EtxHash: &etxhash, - EtxSetHash: &etxSetHash, + EtxSetRoot: &etxSetRoot, EtxRollupHash: &etxRollupHash, ReceiptHash: &receiptHash, GasLimit: &gasLimit, @@ -872,7 +870,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/external_tx.go b/core/types/external_tx.go index 0a121b0bfb..8b0203aa77 100644 --- a/core/types/external_tx.go +++ b/core/types/external_tx.go @@ -189,5 +189,5 @@ func (tx *ExternalTx) setEcdsaSignatureValues(chainID, v, r, s *big.Int) { } func (tx *ExternalTx) setTo(to common.Address) { - panic("You should not set To on an external transaction") + tx.To = &to } 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 765c334c0a..9ec09a0bab 100644 --- a/core/types/proto_block.pb.go +++ b/core/types/proto_block.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.3 +// protoc v4.25.2 // source: core/types/proto_block.proto package types @@ -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 } @@ -2080,10 +2080,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, @@ -2120,8 +2120,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, @@ -2509,7 +2509,7 @@ var file_core_types_proto_block_proto_depIdxs = []int32{ 29, // 8: block.ProtoHeader.location:type_name -> common.ProtoLocation 28, // 9: block.ProtoHeader.mix_hash:type_name -> common.ProtoHash 28, // 10: block.ProtoHeader.utxo_root:type_name -> common.ProtoHash - 28, // 11: block.ProtoHeader.etx_set_hash:type_name -> common.ProtoHash + 28, // 11: block.ProtoHeader.etx_set_root:type_name -> common.ProtoHash 28, // 12: block.ProtoHeader.etx_eligible_slices:type_name -> common.ProtoHash 28, // 13: block.ProtoHeader.prime_terminus:type_name -> common.ProtoHash 28, // 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 4cdbce0b32..90071c4d39 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/transaction.go b/core/types/transaction.go index 642fea8e72..dc13171a92 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -106,7 +106,6 @@ type TxData interface { parentHash() *common.Hash mixHash() *common.Hash workNonce() *BlockNonce - // Schnorr segregated sigs getSchnorrSignature() *schnorr.Signature } @@ -605,7 +604,7 @@ func (tx *Transaction) To() *common.Address { return &cpy } -func (tx *Transaction) SetGas(addr common.Address) { +func (tx *Transaction) SetTo(addr common.Address) { tx.inner.setTo(addr) } diff --git a/core/types/wo.go b/core/types/wo.go index 7783f6eb29..578a33165f 100644 --- a/core/types/wo.go +++ b/core/types/wo.go @@ -213,8 +213,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 { @@ -334,7 +334,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 } @@ -345,6 +345,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++ { @@ -1074,7 +1084,6 @@ type WorkObjectHeaderView struct { func (wo *WorkObject) ConvertToHeaderView() *WorkObjectHeaderView { newWo := NewWorkObject(wo.woHeader, wo.woBody, wo.tx) - newWo.Body().SetExtTransactions(Transactions{}) newWo.Body().SetTransactions(Transactions{}) newWo.Body().SetManifest(BlockManifest{}) newWo.Body().SetInterlinkHashes(common.Hashes{}) diff --git a/core/worker.go b/core/worker.go index 53cdc01f9b..a925d37f87 100644 --- a/core/worker.go +++ b/core/worker.go @@ -566,7 +566,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{ @@ -575,12 +575,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 { @@ -624,7 +618,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{ @@ -638,7 +632,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() @@ -667,11 +661,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 } @@ -820,7 +816,7 @@ 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 { qiTxsToRemove := make([]*common.Hash, 0) gasLimit := env.wo.GasLimit if env.gasPool == nil { @@ -828,7 +824,12 @@ func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, } 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 } @@ -847,17 +848,21 @@ func (w *worker) commitTransactions(env *environment, parent *types.WorkObject, w.logger.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 - w.logger.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. @@ -1257,54 +1262,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 { - w.logger.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(false) 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/internal/quaiapi/quai_api.go b/internal/quaiapi/quai_api.go index bbee7ae0e1..643cfc0c1d 100644 --- a/internal/quaiapi/quai_api.go +++ b/internal/quaiapi/quai_api.go @@ -768,6 +768,9 @@ func (s *PublicBlockChainQuaiAPI) Append(ctx context.Context, raw json.RawMessag } body.Header.Header().SetCoinbase(common.BytesToAddress(body.Header.Coinbase().Bytes(), s.b.NodeLocation())) + for _, tx := range body.NewInboundEtxs { + tx.SetTo(common.BytesToAddress(tx.To().Bytes(), s.b.NodeLocation())) + } pendingEtxs, subReorg, setHead, err := s.b.Append(body.Header, body.Manifest, body.DomPendingHeader, body.DomTerminus, body.DomOrigin, body.NewInboundEtxs) if err != nil { return nil, err diff --git a/p2p/node/peerManager/peerdb/peer_info.pb.go b/p2p/node/peerManager/peerdb/peer_info.pb.go index cea9a3aa49..d20bc0c18a 100644 --- a/p2p/node/peerManager/peerdb/peer_info.pb.go +++ b/p2p/node/peerManager/peerdb/peer_info.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.3 +// protoc v4.25.2 // source: p2p/node/peerManager/peerdb/peer_info.proto package peerdb diff --git a/p2p/pb/quai_messages.pb.go b/p2p/pb/quai_messages.pb.go index 7a8dcc15fe..10b83255d3 100644 --- a/p2p/pb/quai_messages.pb.go +++ b/p2p/pb/quai_messages.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.3 +// protoc v4.25.2 // source: p2p/pb/quai_messages.proto package pb diff --git a/params/config.go b/params/config.go index bcccb96373..ac09690064 100644 --- a/params/config.go +++ b/params/config.go @@ -36,7 +36,7 @@ var ( Blake3PowColosseumGenesisHash = common.HexToHash("0x0ca6ff4426eab568de6773fb21d623d3fe6f2f0d2572c0b0b978384373e0895d") Blake3PowGardenGenesisHash = common.HexToHash("0x43af9e6e91fbf408b9c6fd423ed34e5ad0bd2154737cefc204d145ffc660fc9c") Blake3PowOrchardGenesisHash = common.HexToHash("0x43af9e6e91fbf408b9c6fd423ed34e5ad0bd2154737cefc204d145ffc660fc9c") - Blake3PowLocalGenesisHash = common.HexToHash("0xe145f05594415f1e7b7fdf261d41be71f9a9e53b9d07e4ec9d2658cefc4916ab") + Blake3PowLocalGenesisHash = common.HexToHash("0x3946bd52e33904f841d259cace4a22cce9a34f748f025bf8b1040a3f9300d201") Blake3PowLighthouseGenesisHash = common.HexToHash("0x43af9e6e91fbf408b9c6fd423ed34e5ad0bd2154737cefc204d145ffc660fc9c") ) 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 1e540a5ebc..8b67bc1433 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 1bd4a32c94..fe505dd8e8 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 diff --git a/trie/proto_trienode.pb.go b/trie/proto_trienode.pb.go index 67796299d9..c306fde3df 100644 --- a/trie/proto_trienode.pb.go +++ b/trie/proto_trienode.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.3 +// protoc v4.25.2 // source: trie/proto_trienode.proto package trie