diff --git a/cmd/go-quai/start.go b/cmd/go-quai/start.go index 4f611cfeab..9ec235563d 100644 --- a/cmd/go-quai/start.go +++ b/cmd/go-quai/start.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/viper" "github.com/dominant-strategies/go-quai/cmd/utils" + "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/log" "github.com/dominant-strategies/go-quai/metrics_config" "github.com/dominant-strategies/go-quai/p2p/node" @@ -73,6 +74,7 @@ func runStart(cmd *cobra.Command, args []string) error { // create a quit channel for services to signal for a clean shutdown quitCh := make(chan struct{}) + common.SanityCheck(quitCh) // create a new p2p node node, err := node.NewNode(ctx, quitCh) if err != nil { @@ -104,8 +106,14 @@ func runStart(cmd *cobra.Command, args []string) error { // wait for a SIGINT or SIGTERM signal ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - <-ch - log.Global.Warn("Received 'stop' signal, shutting down gracefully...") + + select { + case <-ch: + log.Global.Warn("Received 'stop' signal, shutting down gracefully...") + case <-quitCh: + log.Global.Warn("Received 'quit' signal from child, shutting down...") + } + cancel() // stop the hierarchical co-ordinator hc.Stop() diff --git a/common/big.go b/common/big.go index 3626dd51a0..0e92ba4040 100644 --- a/common/big.go +++ b/common/big.go @@ -18,7 +18,9 @@ package common import ( "math/big" + "time" + "github.com/dominant-strategies/go-quai/log" "modernc.org/mathutil" ) @@ -33,11 +35,15 @@ var ( Big2 = big.NewInt(2) Big3 = big.NewInt(3) Big8 = big.NewInt(8) + Big10 = big.NewInt(10) Big32 = big.NewInt(32) + Big99 = big.NewInt(99) + Big100 = big.NewInt(100) + Big101 = big.NewInt(101) Big256 = big.NewInt(256) Big257 = big.NewInt(257) - Big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) Big2e64 = new(big.Int).Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) + Big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) ) func BigBitsToBits(original *big.Int) *big.Int { @@ -77,3 +83,47 @@ func LogBig(diff *big.Int) *big.Int { bigBits = new(big.Int).Add(bigBits, m) return bigBits } + +// Continously verify that the common values have not been overwritten. +func SanityCheck(quitCh chan struct{}) { + big0 := big.NewInt(0) + big1 := big.NewInt(1) + big2 := big.NewInt(2) + big3 := big.NewInt(3) + big8 := big.NewInt(8) + big10 := big.NewInt(10) + big32 := big.NewInt(32) + big99 := big.NewInt(99) + big100 := big.NewInt(100) + big101 := big.NewInt(101) + big256 := big.NewInt(256) + big257 := big.NewInt(257) + big2e64 := new(big.Int).Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) + big2e256 := new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) + + go func(quitCh chan struct{}) { + for { + time.Sleep(1 * time.Minute) + + // Verify that none of the values have mutated. + if Big0 == nil || big0.Cmp(Big0) != 0 || + Big1 == nil || big1.Cmp(Big1) != 0 || + Big2 == nil || big2.Cmp(Big2) != 0 || + Big3 == nil || big3.Cmp(Big3) != 0 || + Big8 == nil || big8.Cmp(Big8) != 0 || + Big10 == nil || big10.Cmp(Big10) != 0 || + Big32 == nil || big32.Cmp(Big32) != 0 || + Big99 == nil || big99.Cmp(Big99) != 0 || + Big100 == nil || big100.Cmp(Big100) != 0 || + Big101 == nil || big101.Cmp(Big101) != 0 || + Big256 == nil || big256.Cmp(Big256) != 0 || + Big257 == nil || big257.Cmp(Big257) != 0 || + Big2e64 == nil || big2e64.Cmp(Big2e64) != 0 || + Big2e256 == nil || big2e256.Cmp(Big2e256) != 0 { + // Send a message to quitCh to abort. + log.Global.Error("A common value has mutated, exiting now") + quitCh <- struct{}{} + } + } + }(quitCh) +} diff --git a/consensus/blake3pow/consensus.go b/consensus/blake3pow/consensus.go index b8c6aa4928..f75f00a9ef 100644 --- a/consensus/blake3pow/consensus.go +++ b/consensus/blake3pow/consensus.go @@ -29,25 +29,9 @@ import ( var ( allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks - ContextTimeFactor = big10 ZoneBlockReward = big.NewInt(5e+18) - RegionBlockReward = new(big.Int).Mul(ZoneBlockReward, big3) - PrimeBlockReward = new(big.Int).Mul(RegionBlockReward, big3) -) - -// Some useful constants to avoid constant memory allocs for them. -var ( - expDiffPeriod = big.NewInt(100000) - big0 = big.NewInt(0) - big1 = big.NewInt(1) - big2 = big.NewInt(2) - big3 = big.NewInt(3) - big8 = big.NewInt(8) - big9 = big.NewInt(9) - big10 = big.NewInt(10) - big32 = big.NewInt(32) - bigMinus99 = big.NewInt(-99) - big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) // 2^256 + RegionBlockReward = new(big.Int).Mul(ZoneBlockReward, common.Big3) + PrimeBlockReward = new(big.Int).Mul(RegionBlockReward, common.Big3) ) // Author implements consensus.Engine, returning the header's coinbase as the @@ -592,7 +576,7 @@ func (blake3pow *Blake3pow) verifySeal(header *types.WorkObjectHeader) error { return consensus.ErrInvalidDifficulty } - target := new(big.Int).Div(big2e256, header.Difficulty()) + target := new(big.Int).Div(common.Big2e256, header.Difficulty()) if new(big.Int).SetBytes(header.Hash().Bytes()).Cmp(target) > 0 { return consensus.ErrInvalidPoW } diff --git a/consensus/blake3pow/poem.go b/consensus/blake3pow/poem.go index 149ea94b24..626e3f04c1 100644 --- a/consensus/blake3pow/poem.go +++ b/consensus/blake3pow/poem.go @@ -23,7 +23,7 @@ func (blake3pow *Blake3pow) CalcOrder(chain consensus.BlockReader, header *types } nodeCtx := blake3pow.config.NodeLocation.Context() if header.NumberU64(nodeCtx) == 0 { - return big0, common.PRIME_CTX, nil + return big.NewInt(0), common.PRIME_CTX, nil } expansionNum := header.ExpansionNumber() @@ -31,7 +31,7 @@ func (blake3pow *Blake3pow) CalcOrder(chain consensus.BlockReader, header *types // Verify the seal and get the powHash for the given header err := blake3pow.verifySeal(header.WorkObjectHeader()) if err != nil { - return big0, -1, err + return big.NewInt(0), -1, err } // Get entropy reduction of this header @@ -59,7 +59,7 @@ func (blake3pow *Blake3pow) CalcOrder(chain consensus.BlockReader, header *types totalDeltaEntropyRegion := new(big.Int).Add(header.ParentDeltaEntropy(common.ZONE_CTX), intrinsicEntropy) regionDeltaEntropyTarget := new(big.Int).Mul(zoneThresholdEntropy, params.RegionEntropyTarget(expansionNum)) - regionDeltaEntropyTarget = new(big.Int).Div(regionDeltaEntropyTarget, big2) + regionDeltaEntropyTarget = new(big.Int).Div(regionDeltaEntropyTarget, common.Big2) regionBlockEntropyThreshold := new(big.Int).Add(zoneThresholdEntropy, common.BitsToBigBits(params.RegionEntropyTarget(expansionNum))) if intrinsicEntropy.Cmp(regionBlockEntropyThreshold) > 0 && totalDeltaEntropyRegion.Cmp(regionDeltaEntropyTarget) > 0 { @@ -75,7 +75,7 @@ func (blake3pow *Blake3pow) CalcOrder(chain consensus.BlockReader, header *types // IntrinsicLogEntropy returns the logarithm of the intrinsic entropy reduction of a PoW hash func (blake3pow *Blake3pow) IntrinsicLogEntropy(powHash common.Hash) *big.Int { x := new(big.Int).SetBytes(powHash.Bytes()) - d := new(big.Int).Div(big2e256, x) + d := new(big.Int).Div(common.Big2e256, x) c, m := mathutil.BinaryLog(d, consensus.MantBits) bigBits := new(big.Int).Mul(big.NewInt(int64(c)), new(big.Int).Exp(big.NewInt(2), big.NewInt(consensus.MantBits), nil)) bigBits = new(big.Int).Add(bigBits, m) diff --git a/consensus/consensus.go b/consensus/consensus.go index b546c1bc18..d376d9bb3e 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -24,8 +24,8 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/core/state" "github.com/dominant-strategies/go-quai/core/types" - "github.com/dominant-strategies/go-quai/ethdb" "github.com/dominant-strategies/go-quai/crypto/multiset" + "github.com/dominant-strategies/go-quai/ethdb" "github.com/dominant-strategies/go-quai/params" ) @@ -35,21 +35,6 @@ const ( MantBits = 64 ) -// Some useful constants to avoid constant memory allocs for them. -var ( - ExpDiffPeriod = big.NewInt(100000) - Big0 = big.NewInt(0) - Big1 = big.NewInt(1) - Big2 = big.NewInt(2) - Big3 = big.NewInt(3) - Big8 = big.NewInt(8) - Big9 = big.NewInt(9) - Big10 = big.NewInt(10) - Big32 = big.NewInt(32) - BigMinus99 = big.NewInt(-99) - Big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) // 2^256 -) - // Various error messages to mark blocks invalid. These should be private to // prevent engine specific errors from being referenced in the remainder of the // codebase, inherently breaking if the engine is swapped out. Please put common @@ -259,8 +244,8 @@ func CalcWorkShareThreshold(workShare *types.WorkObjectHeader, workShareThreshol return nil, ErrInvalidThresholdDiff } diff := workShare.Difficulty() - diffTarget := new(big.Int).Div(Big2e256, diff) - workShareTarget := new(big.Int).Exp(Big2, big.NewInt(int64(workShareThresholdDiff)), nil) + diffTarget := new(big.Int).Div(common.Big2e256, diff) + workShareTarget := new(big.Int).Exp(common.Big2, big.NewInt(int64(workShareThresholdDiff)), nil) return workShareTarget.Mul(diffTarget, workShareTarget), nil } diff --git a/consensus/progpow/consensus.go b/consensus/progpow/consensus.go index 7564469931..4f1ba58fd5 100644 --- a/consensus/progpow/consensus.go +++ b/consensus/progpow/consensus.go @@ -30,25 +30,9 @@ import ( var ( allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks - ContextTimeFactor = big10 ZoneBlockReward = big.NewInt(5e+18) - RegionBlockReward = new(big.Int).Mul(ZoneBlockReward, big3) - PrimeBlockReward = new(big.Int).Mul(RegionBlockReward, big3) -) - -// Some useful constants to avoid constant memory allocs for them. -var ( - expDiffPeriod = big.NewInt(100000) - big0 = big.NewInt(0) - big1 = big.NewInt(1) - big2 = big.NewInt(2) - big3 = big.NewInt(3) - big8 = big.NewInt(8) - big9 = big.NewInt(9) - big10 = big.NewInt(10) - big32 = big.NewInt(32) - bigMinus99 = big.NewInt(-99) - big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) // 2^256 + RegionBlockReward = new(big.Int).Mul(ZoneBlockReward, common.Big3) + PrimeBlockReward = new(big.Int).Mul(RegionBlockReward, common.Big3) ) // Author implements consensus.Engine, returning the header's coinbase as the @@ -636,7 +620,7 @@ func (progpow *Progpow) verifySeal(header *types.WorkObjectHeader) (common.Hash, if err != nil { return common.Hash{}, err } - target := new(big.Int).Div(big2e256, header.Difficulty()) + target := new(big.Int).Div(common.Big2e256, header.Difficulty()) if new(big.Int).SetBytes(powHash.Bytes()).Cmp(target) > 0 { return powHash, consensus.ErrInvalidPoW } diff --git a/consensus/progpow/poem.go b/consensus/progpow/poem.go index 6d535a2da7..ed7bbdcc41 100644 --- a/consensus/progpow/poem.go +++ b/consensus/progpow/poem.go @@ -23,14 +23,14 @@ func (progpow *Progpow) CalcOrder(chain consensus.BlockReader, header *types.Wor nodeCtx := progpow.config.NodeLocation.Context() // Except for the slice [0,0] have to check if the header hash is the genesis hash if header.NumberU64(nodeCtx) == 0 { - return big0, common.PRIME_CTX, nil + return big.NewInt(0), common.PRIME_CTX, nil } expansionNum := header.ExpansionNumber() // Verify the seal and get the powHash for the given header powHash, err := progpow.verifySeal(header.WorkObjectHeader()) if err != nil { - return big0, -1, err + return big.NewInt(0), -1, err } // Get entropy reduction of this header @@ -45,7 +45,7 @@ func (progpow *Progpow) CalcOrder(chain consensus.BlockReader, header *types.Wor totalDeltaEntropyPrime = new(big.Int).Add(totalDeltaEntropyPrime, intrinsicEntropy) primeDeltaEntropyTarget := new(big.Int).Mul(params.PrimeEntropyTarget(expansionNum), zoneThresholdEntropy) - primeDeltaEntropyTarget = new(big.Int).Div(primeDeltaEntropyTarget, big2) + primeDeltaEntropyTarget = new(big.Int).Div(primeDeltaEntropyTarget, common.Big2) primeBlockEntropyThreshold := new(big.Int).Add(zoneThresholdEntropy, common.BitsToBigBits(params.PrimeEntropyTarget(expansionNum))) if intrinsicEntropy.Cmp(primeBlockEntropyThreshold) > 0 && totalDeltaEntropyPrime.Cmp(primeDeltaEntropyTarget) > 0 { @@ -58,7 +58,7 @@ func (progpow *Progpow) CalcOrder(chain consensus.BlockReader, header *types.Wor totalDeltaSRegion := new(big.Int).Add(header.ParentDeltaEntropy(common.ZONE_CTX), intrinsicEntropy) regionDeltaSTarget := new(big.Int).Mul(zoneThresholdEntropy, params.RegionEntropyTarget(expansionNum)) - regionDeltaSTarget = new(big.Int).Div(regionDeltaSTarget, big2) + regionDeltaSTarget = new(big.Int).Div(regionDeltaSTarget, common.Big2) regionBlockEntropyThreshold := new(big.Int).Add(zoneThresholdEntropy, common.BitsToBigBits(params.RegionEntropyTarget(expansionNum))) if intrinsicEntropy.Cmp(regionBlockEntropyThreshold) > 0 && totalDeltaSRegion.Cmp(regionDeltaSTarget) > 0 { @@ -74,7 +74,7 @@ func (progpow *Progpow) CalcOrder(chain consensus.BlockReader, header *types.Wor // IntrinsicLogEntropy returns the logarithm of the intrinsic entropy reduction of a PoW hash func (progpow *Progpow) IntrinsicLogEntropy(powHash common.Hash) *big.Int { x := new(big.Int).SetBytes(powHash.Bytes()) - d := new(big.Int).Div(big2e256, x) + d := new(big.Int).Div(common.Big2e256, x) c, m := mathutil.BinaryLog(d, consensus.MantBits) bigBits := new(big.Int).Mul(big.NewInt(int64(c)), new(big.Int).Exp(big.NewInt(2), big.NewInt(consensus.MantBits), nil)) bigBits = new(big.Int).Add(bigBits, m) diff --git a/core/core.go b/core/core.go index fe2a1d5a8a..0787a1efbd 100644 --- a/core/core.go +++ b/core/core.go @@ -1365,6 +1365,10 @@ func (c *Core) SendTxToSharingClients(tx *types.Transaction) { c.sl.txPool.SendTxToSharingClients(tx) } +func (c *Core) GetRollingFeeInfo() (min, max, avg *big.Int) { + return c.Processor().GetRollingFeeInfo() +} + func (c *Core) SuggestFinalityDepth(qiValue *big.Int, correlatedRisk *big.Int) *big.Int { qiRewardPerBlock := misc.CalculateQiReward(c.CurrentHeader().WorkObjectHeader()) diff --git a/core/state_processor.go b/core/state_processor.go index 673baab942..baa8bc06d7 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -31,6 +31,7 @@ import ( lru "github.com/hashicorp/golang-lru/v2" "github.com/dominant-strategies/go-quai/common" + bigMath "github.com/dominant-strategies/go-quai/common/math" "github.com/dominant-strategies/go-quai/common/prque" "github.com/dominant-strategies/go-quai/consensus" "github.com/dominant-strategies/go-quai/consensus/misc" @@ -107,19 +108,20 @@ var defaultCacheConfig = &CacheConfig{ // // StateProcessor implements Processor. type StateProcessor struct { - config *params.ChainConfig // Chain configuration options - hc *HeaderChain // Canonical block chain - engine consensus.Engine // Consensus engine used for block rewards - logsFeed event.Feed - rmLogsFeed event.Feed - cacheConfig *CacheConfig // CacheConfig for StateProcessor - stateCache state.Database // State database to reuse between imports (contains state cache) - etxCache state.Database // ETX database to reuse between imports (contains ETX cache) - receiptsCache *lru.Cache[common.Hash, types.Receipts] // Cache for the most recent receipts per block - txLookupCache *lru.Cache[common.Hash, rawdb.LegacyTxLookupEntry] - validator Validator // Block and state validator interface - prefetcher Prefetcher - vmConfig vm.Config + config *params.ChainConfig // Chain configuration options + hc *HeaderChain // Canonical block chain + engine consensus.Engine // Consensus engine used for block rewards + logsFeed event.Feed + rmLogsFeed event.Feed + cacheConfig *CacheConfig // CacheConfig for StateProcessor + stateCache state.Database // State database to reuse between imports (contains state cache) + etxCache state.Database // ETX database to reuse between imports (contains ETX cache) + receiptsCache *lru.Cache[common.Hash, types.Receipts] // Cache for the most recent receipts per block + txLookupCache *lru.Cache[common.Hash, rawdb.LegacyTxLookupEntry] + validator Validator // Block and state validator interface + prefetcher Prefetcher + vmConfig vm.Config + minFee, maxFee, avgFee, numElements *big.Int scope event.SubscriptionScope wg sync.WaitGroup // chain processing wait group for shutting down @@ -221,17 +223,19 @@ type UtxosCreatedDeleted struct { // transactions failed to execute due to insufficient gas it will return an error. func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (types.Receipts, []*types.Transaction, []*types.Log, *state.StateDB, uint64, uint64, uint64, *multiset.MultiSet, []common.Unlock, error) { var ( - receipts types.Receipts - usedGas = new(uint64) - usedState = new(uint64) - header = types.CopyWorkObject(block) - blockHash = block.Hash() - nodeLocation = p.hc.NodeLocation() - nodeCtx = p.hc.NodeCtx() - blockNumber = block.Number(nodeCtx) - parentHash = block.ParentHash(nodeCtx) - allLogs []*types.Log - gp = new(types.GasPool).AddGas(block.GasLimit()) + receipts types.Receipts + usedGas = new(uint64) + usedState = new(uint64) + header = types.CopyWorkObject(block) + blockHash = block.Hash() + nodeLocation = p.hc.NodeLocation() + nodeCtx = p.hc.NodeCtx() + blockNumber = block.Number(nodeCtx) + parentHash = block.ParentHash(nodeCtx) + allLogs []*types.Log + gp = new(types.GasPool).AddGas(block.GasLimit()) + numTxsProcessed = big.NewInt(0) + blockMinFee, blockMaxFee, blockAvgFee *big.Int ) start := time.Now() parent := p.hc.GetBlock(block.ParentHash(nodeCtx), block.NumberU64(nodeCtx)-1) @@ -392,6 +396,8 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } } + calcTxStats(blockMinFee, blockMaxFee, blockAvgFee, qiTxFeeInQuai, numTxsProcessed) + totalEtxCoinbaseTime += time.Since(startEtxCoinbase) totalQiTime += time.Since(qiTimeBefore) totalQiProcessTimes["Sanity Checks"] += timing["Sanity Checks"] @@ -623,6 +629,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty minGasPrice = new(big.Int).Set(gasPrice) } } + calcTxStats(blockMinFee, blockMaxFee, blockAvgFee, fees, numTxsProcessed) } else { return nil, nil, nil, nil, 0, 0, 0, nil, nil, ErrTxTypeNotSupported @@ -768,6 +775,8 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } time5 := common.PrettyDuration(time.Since(start)) + calcRollingFeeInfo(p.minFee, p.maxFee, p.avgFee, p.numElements, blockMinFee, blockMaxFee, blockAvgFee, numTxsProcessed) + p.logger.WithFields(log.Fields{ "signing time": common.PrettyDuration(timeSign), "prepare state time": common.PrettyDuration(timePrepare), @@ -890,7 +899,7 @@ func RedeemLockedQuai(hc *HeaderChain, header *types.WorkObject, parent *types.W if types.IsConversionTx(etx) && etx.To().IsInQuaiLedgerScope() && conversionPeriodValid { internal, err := etx.To().InternalAddress() if err != nil { - fmt.Errorf("Error converting address to internal address: %v", err) + return fmt.Errorf("Error converting address to internal address: %v", err), nil } balance := etx.Value() if !statedb.Exist(internal) { @@ -1205,7 +1214,6 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir var elapsedTime time.Duration stepTimings := make(map[string]time.Duration) - qiTxFee := big.NewInt(0) // Start timing for sanity checks stepStart := time.Now() // Sanity checks @@ -1382,7 +1390,6 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir // the fee to pay the basefee/miner is the difference between inputs and outputs txFeeInQit := new(big.Int).Sub(totalQitIn, totalQitOut) - qiTxFee = new(big.Int).Set(txFeeInQit) // Check tx against required base fee and gas requiredGas := intrinsicGas + (uint64(len(etxs)) * (params.TxGas + params.ETXGas)) // Each ETX costs extra gas that is paid in the origin if requiredGas < intrinsicGas { @@ -1457,7 +1464,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir elapsedTime = time.Since(stepStart) stepTimings["Signature Check"] = elapsedTime - return qiTxFee, txFeeInQit, etxs, nil, stepTimings + return txFeeInQit, txFeeInQit, etxs, nil, stepTimings } // Go through all denominations largest to smallest, check if the input exists as the output, if not, convert it to the respective number of bills for the next smallest denomination, then repeat the check. Subtract the 'carry' when the outputs match the carry for that denomination. @@ -1501,10 +1508,12 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*t return nil, nil, err } if block.Hash() != blockHash { + err := errors.New("block hash changed after processing the block") p.logger.WithFields(log.Fields{ "oldHash": blockHash, "newHash": block.Hash(), - }).Warn("Block hash changed after Processing the block") + }).Error(err) + return nil, nil, err } time3 := common.PrettyDuration(time.Since(start)) err = p.validator.ValidateState(block, statedb, receipts, etxs, multiSet, usedGas, usedState) @@ -1865,6 +1874,70 @@ func (p *StateProcessor) StateAtTransaction(block *types.WorkObject, txIndex int return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } +func calcTxStats(blockMinFee, blockMaxFee, blockTotalFees, txFee, numTxsProcessed *big.Int) (newBlockMinFee, newBlockMaxFee, newBlockAvgFee *big.Int) { + + if numTxsProcessed.Cmp(common.Big0) == 0 { + numTxsProcessed.Add(numTxsProcessed, common.Big1) + blockMinFee = new(big.Int).Set(txFee) + blockMaxFee = new(big.Int).Set(txFee) + blockTotalFees = new(big.Int).Set(txFee) + return blockMinFee, blockMaxFee, blockTotalFees + } + + numTxsProcessed = numTxsProcessed.Add(numTxsProcessed, common.Big1) + blockMinFee = bigMath.BigMin(txFee, blockMinFee) + blockMaxFee = bigMath.BigMax(txFee, blockMaxFee) + blockTotalFees.Add(blockTotalFees, txFee) + + return blockMinFee, blockMaxFee, blockTotalFees +} + +func calcRollingFeeInfo(rollingMinFee, rollingMaxFee, rollingAvgFee, rollingNumElements, blockMinFee, blockMaxFee, blockTotalFees, numTxsProcessed *big.Int) (min, max, avg, num *big.Int) { + + // Implement peak/envelope filter + if numTxsProcessed.Cmp(common.Big0) == 0 { + // Block values will be nil, so don't compare or update. + return rollingMinFee, rollingMaxFee, rollingAvgFee, rollingNumElements + } + if rollingMinFee == nil || blockMinFee.Cmp(rollingMinFee) < 0 { + // If the new minimum is less than the old minimum, overwrite it. + rollingMinFee = new(big.Int).Set(blockMinFee) + } else { + // If not, increase the old minimum by 1%. + rollingMinFee.Mul(rollingMinFee, common.Big101) + rollingMinFee.Div(rollingMinFee, common.Big100) + } + + if rollingMaxFee == nil || blockMaxFee.Cmp(rollingMaxFee) > 0 { + rollingMaxFee = new(big.Int).Set(blockMaxFee) + } else { + // Decay the max fee by 1%. + rollingMaxFee.Mul(rollingMaxFee, common.Big99) + rollingMaxFee.Div(rollingMaxFee, common.Big100) + } + + // Implement running average + if rollingAvgFee == nil { + rollingAvgFee = big.NewInt(1) + rollingNumElements = big.NewInt(0) + } + + if numTxsProcessed.Cmp(common.Big0) > 0 { + blockAvgFee := blockTotalFees.Div(blockTotalFees, numTxsProcessed) + intermediateVal := new(big.Int).Mul(rollingNumElements, rollingAvgFee) + intermediateVal = intermediateVal.Add(intermediateVal, blockAvgFee) + + rollingNumElements.Add(rollingNumElements, common.Big1) + rollingAvgFee = intermediateVal.Div(intermediateVal, rollingNumElements) + } + + return rollingMinFee, rollingMaxFee, rollingAvgFee, rollingNumElements +} + +func (p *StateProcessor) GetRollingFeeInfo() (min, max, avg *big.Int) { + return p.minFee, p.maxFee, p.avgFee +} + func (p *StateProcessor) Stop() { // Ensure all live cached entries be saved into disk, so that we can skip // cache warmup when node restarts. diff --git a/core/state_processor_test.go b/core/state_processor_test.go new file mode 100644 index 0000000000..bd4518a6e3 --- /dev/null +++ b/core/state_processor_test.go @@ -0,0 +1,88 @@ +package core + +import ( + "math/big" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func generateRandomBlockFees(min, max, numBlocks, maxNumTxs int) (simpleBlockFees [][]int, bigBlockFees [][]*big.Int) { + // Generate the number of txs in this block first. + simpleBlockFees = make([][]int, numBlocks) + bigBlockFees = make([][]*big.Int, numBlocks) + for blockNum := 0; blockNum < numBlocks; blockNum++ { + txLen := rand.Intn(maxNumTxs) + + simpleBlockFees[blockNum] = make([]int, txLen) + bigBlockFees[blockNum] = make([]*big.Int, txLen) + + for i := 0; i < txLen; i++ { + randNum := rand.Intn(max-min+1) + min + simpleBlockFees[blockNum][i] = randNum + bigBlockFees[blockNum][i] = big.NewInt(int64(randNum)) + } + } + + return simpleBlockFees, bigBlockFees +} + +func TestMultiBlockTxStats(t *testing.T) { + simpleBlockFees, bigBlockFees := generateRandomBlockFees(0, 10000, 10000, 1000) + + var expectedRollingMin, expectedRollingMax, expectedRollingAvg int + for blockNum, blockFees := range simpleBlockFees { + var blockMin, blockMax, sum int + for txCount, fee := range blockFees { + if txCount == 0 { + blockMin = fee + blockMax = fee + sum = 0 + } else { + if fee < blockMin { + blockMin = fee + } + + if fee > blockMax { + blockMax = fee + } + } + sum += fee + } + + if len(blockFees) > 0 { + if blockMin < expectedRollingMin || blockNum == 0 { + expectedRollingMin = blockMin + } else { + expectedRollingMin = expectedRollingMin * 101 / 100 + } + + if blockMax > expectedRollingMax || blockNum == 0 { + expectedRollingMax = blockMax + } else { + expectedRollingMax = expectedRollingMax * 99 / 100 + } + // Calculate a running average + expectedRollingAvg = (blockNum*expectedRollingAvg + (sum / len(blockFees))) / (blockNum + 1) + } + } + + var blockMin, blockMax, blockTotal *big.Int + var actualRollingMin, actualRollingMax, rollingAvg, rollingNumElements *big.Int + for _, blockFees := range bigBlockFees { + bigTxsProcessed := big.NewInt(0) + for _, fee := range blockFees { + blockMin, blockMax, blockTotal = calcTxStats(blockMin, blockMax, blockTotal, fee, bigTxsProcessed) + } + + actualRollingMin, actualRollingMax, rollingAvg, rollingNumElements = calcRollingFeeInfo(actualRollingMin, actualRollingMax, rollingAvg, rollingNumElements, blockMin, blockMax, blockTotal, bigTxsProcessed) + } + + if expectedRollingMin != 0 || expectedRollingMax != 0 || expectedRollingAvg != 0 { + // If any one of these values is non-zero, then there were fees, so compare. + require.Equal(t, uint64(expectedRollingMin), actualRollingMin.Uint64(), "Expected min not equal") + require.Equal(t, uint64(expectedRollingMax), actualRollingMax.Uint64(), "Expected max not equal") + require.Equal(t, uint64(expectedRollingAvg), rollingAvg.Uint64(), "Expected average not equal") + } +} diff --git a/core/types/block.go b/core/types/block.go index 70457e654e..2945f2c8b3 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -44,7 +44,6 @@ var ( 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 ) diff --git a/core/types/transaction.go b/core/types/transaction.go index ed93ae672a..c15fda8831 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -60,7 +60,7 @@ const ( c_MaxTxForSorting = 1500 ) -// Transaction is a Quai transaction. +// Transaction can be a Quai, Qi, or External transaction. type Transaction struct { inner TxData // Consensus contents of a transaction time time.Time // Time first seen locally (spam avoidance) diff --git a/internal/quaiapi/api.go b/internal/quaiapi/api.go index 45f507cf67..f3f1a8b9e7 100644 --- a/internal/quaiapi/api.go +++ b/internal/quaiapi/api.go @@ -169,6 +169,15 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string { return content } +func (s *PublicTxPoolAPI) GetRollingFeeInfo() (min, max, avg *hexutil.Big) { + bigMin, bigMax, bigAvg := s.b.GetRollingFeeInfo() + min = (*hexutil.Big)(bigMin) + max = (*hexutil.Big)(bigMax) + avg = (*hexutil.Big)(bigAvg) + + return min, max, avg +} + // PublicBlockChainAPI provides an API to access the Quai blockchain. // It offers only methods that operate on public data that is freely available to anyone. type PublicBlockChainAPI struct { diff --git a/internal/quaiapi/backend.go b/internal/quaiapi/backend.go index 3c4be44f3e..59334320db 100644 --- a/internal/quaiapi/backend.go +++ b/internal/quaiapi/backend.go @@ -139,6 +139,7 @@ type Backend interface { GetMinGasPrice() *big.Int GetPoolGasPrice() *big.Int SendTxToSharingClients(tx *types.Transaction) + GetRollingFeeInfo() (min, max, avg *big.Int) // Filter API BloomStatus() (uint64, uint64) diff --git a/quai/api_backend.go b/quai/api_backend.go index 6041cc7a1d..80e0c7bac0 100644 --- a/quai/api_backend.go +++ b/quai/api_backend.go @@ -406,6 +406,10 @@ func (b *QuaiAPIBackend) SendTxToSharingClients(tx *types.Transaction) { b.quai.core.SendTxToSharingClients(tx) } +func (b *QuaiAPIBackend) GetRollingFeeInfo() (min, max, avg *big.Int) { + return b.quai.core.GetRollingFeeInfo() +} + func (b *QuaiAPIBackend) GetMinGasPrice() *big.Int { return b.quai.core.GetMinGasPrice() }