diff --git a/core/state_processor.go b/core/state_processor.go index 8f7515130..83e27f353 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -108,20 +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 - minFee, maxFee, avgFee *big.Int + 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 @@ -774,7 +774,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } time5 := common.PrettyDuration(time.Since(start)) - calcRollingFeeInfo(p.minFee, p.maxFee, p.avgFee, blockMinFee, blockMaxFee, blockAvgFee, numTxsProcessed) + calcRollingFeeInfo(p.minFee, p.maxFee, p.avgFee, p.numElements, blockMinFee, blockMaxFee, blockAvgFee, numTxsProcessed) p.logger.WithFields(log.Fields{ "signing time": common.PrettyDuration(timeSign), @@ -1876,16 +1876,16 @@ func (p *StateProcessor) StateAtTransaction(block *types.WorkObject, txIndex int } func calcQiTxStats(blockMinFee, blockMaxFee, blockAvgFee, qiTxFee, numTxsProcessed *big.Int) (newBlockMinFee, newBlockMaxFee, newBlockAvgFee *big.Int) { - numTxsProcessed = numTxsProcessed.Add(numTxsProcessed, common.Big1) - val := numTxsProcessed.Cmp(common.Big0) - if val == 1 { + if numTxsProcessed.Cmp(common.Big0) == 0 { + numTxsProcessed.Add(numTxsProcessed, common.Big1) blockMinFee = new(big.Int).Set(qiTxFee) blockMaxFee = new(big.Int).Set(qiTxFee) blockAvgFee = new(big.Int).Set(qiTxFee) return blockMinFee, blockMaxFee, blockAvgFee } + numTxsProcessed = numTxsProcessed.Add(numTxsProcessed, common.Big1) blockMinFee = bigMath.BigMin(qiTxFee, blockMinFee) blockMaxFee = bigMath.BigMax(qiTxFee, blockMaxFee) intermediateAvg := new(big.Int).Add(blockAvgFee, qiTxFee) @@ -1894,34 +1894,44 @@ func calcQiTxStats(blockMinFee, blockMaxFee, blockAvgFee, qiTxFee, numTxsProcess return blockMinFee, blockMaxFee, blockAvgFee } -func calcRollingFeeInfo(rollingMinFee, rollingMaxFee, rollingAvgFee, blockMinFee, blockMaxFee, blockAvgFee, numTxsProcessed *big.Int) (min, max, avg *big.Int) { +func calcRollingFeeInfo(rollingMinFee, rollingMaxFee, rollingAvgFee, rollingNumElements, blockMinFee, blockMaxFee, blockAvgFee, numTxsProcessed *big.Int) (min, max, avg *big.Int) { decay := func(fee *big.Int) { - fee = fee.Mul(fee, big.NewInt(99)) - fee.Set(fee.Div(fee, big.NewInt(100))) + fee.Mul(fee, big.NewInt(99)) + fee.Div(fee, big.NewInt(100)) } // 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 + } if rollingMinFee == nil || blockMinFee.Cmp(rollingMinFee) < 0 { // If the new minimum is less than the old minimum, overwrite it. - rollingMinFee = blockMinFee + rollingMinFee = new(big.Int).Set(blockMinFee) } else { // If not, decay the old minimum by 1%. decay(rollingMinFee) } if rollingMaxFee == nil || blockMaxFee.Cmp(rollingMaxFee) > 0 { - rollingMaxFee = blockMaxFee + rollingMaxFee = new(big.Int).Set(blockMaxFee) } else { decay(rollingMaxFee) } // Implement running average if rollingAvgFee == nil { - rollingAvgFee = common.Big1 + rollingAvgFee = big.NewInt(1) + rollingNumElements = big.NewInt(0) + } + + if numTxsProcessed.Cmp(common.Big0) > 0 { + intermediateVal := new(big.Int).Mul(rollingNumElements, rollingAvgFee) + intermediateVal = intermediateVal.Add(intermediateVal, blockAvgFee) + + rollingAvgFee = intermediateVal.Div(intermediateVal, numTxsProcessed) + rollingNumElements.Add(rollingNumElements, common.Big1) } - intermediateVal := new(big.Int).Mul(new(big.Int).Sub(numTxsProcessed, common.Big1), rollingAvgFee) - intermediateVal = intermediateVal.Add(intermediateVal, blockAvgFee) - rollingAvgFee = intermediateVal.Div(intermediateVal, numTxsProcessed) return rollingMinFee, rollingMaxFee, rollingAvgFee } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 4883309ea..f6c5eacee 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -5,68 +5,92 @@ import ( "math/rand" "testing" - "github.com/dominant-strategies/go-quai/common" "github.com/stretchr/testify/require" ) -func generateRandomNumbers(min, max, count int) (simpleNums []int, bigNums []*big.Int) { - simpleNums = make([]int, count) - bigNums = make([]*big.Int, count) +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) - for i := 0; i < count; i++ { - randNum := rand.Intn(max-min+1) + min - simpleNums[i] = randNum - bigNums[i] = big.NewInt(int64(randNum)) + 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 simpleNums, bigNums + return simpleBlockFees, bigBlockFees } -func TestSingleBlockTxStats(t *testing.T) { - const testLength = 1000 - simpleFees, bigFees := generateRandomNumbers(1000, 1000000000, testLength) +func TestMultiBlockTxStats(t *testing.T) { + simpleBlockFees, bigBlockFees := generateRandomBlockFees(1000, 10000000000, 1, 100) - numTxsProcessed := 0 - var expectedMin int - var expectedMax int - var sum int - for _, fee := range simpleFees { - if numTxsProcessed == 0 { - expectedMin = fee - expectedMax = fee - sum = 0 - } else { - if fee < expectedMin { - expectedMin = fee + 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 { - expectedMin = expectedMin * 99 / 100 + if fee < blockMin { + blockMin = fee + } + + if fee > blockMax { + blockMax = fee + } } + sum += fee + } - if fee > expectedMax { - expectedMax = fee + if blockNum == 0 { + expectedRollingMin = blockMin + expectedRollingMax = blockMax + if len(blockFees) != 0 { + expectedRollingAvg = sum / len(blockFees) + } + } else { + if blockMin < expectedRollingMin { + expectedRollingMin = blockMin } else { - expectedMax = expectedMax * 99 / 100 + expectedRollingMin = expectedRollingMin * 99 / 100 } + if blockMax > expectedRollingMax { + expectedRollingMax = blockMax + } else { + expectedRollingMax = expectedRollingMax * 99 / 100 + } + if len(blockFees) != 0 { + // Calculate a running average + expectedRollingAvg = (len(blockFees)*expectedRollingAvg + sum) / len(blockFees) + } } - - sum += fee - numTxsProcessed += 1 } - expectedAvg := sum / numTxsProcessed - bigTxsProcessed := new(big.Int).Set(common.Big0) var blockMin, blockMax, blockAvg *big.Int - var actualMin *big.Int - var actualMax *big.Int - var actualAvg *big.Int - for _, fee := range bigFees { - blockMin, blockMax, blockAvg := calcQiTxStats(blockMin, blockMax, blockAvg, fee, bigTxsProcessed) - actualMin, actualMax, actualAvg = calcRollingFeeInfo(actualMin, actualMax, actualAvg, blockMin, blockMax, blockAvg, bigTxsProcessed) + var actualRollingMin, actualRollingMax, rollingAvg, rollingNumElements *big.Int + for _, blockFees := range bigBlockFees { + bigTxsProcessed := big.NewInt(0) + for _, fee := range blockFees { + blockMin, blockMax, blockAvg = calcQiTxStats(blockMin, blockMax, blockAvg, fee, bigTxsProcessed) + } + + actualRollingMin, actualRollingMax, rollingAvg = calcRollingFeeInfo(actualRollingMin, actualRollingMax, rollingAvg, rollingNumElements, blockMin, blockMax, blockAvg, bigTxsProcessed) } - require.Equal(t, uint64(expectedMin), actualMin.Uint64(), "Expected min not equal") - require.Equal(t, uint64(expectedMax), actualMax.Uint64(), "Expected max not equal") - // Account for maxmium error of 1 unit of gas due to float math for average - require.InEpsilon(t, uint64(expectedAvg), actualAvg.Uint64(), float64(1), "Expected average not equal") + 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") + } }