kaspad/domain/miningmanager/mempool/transactions_pool.go
stasatdaglabs 3dbc42b4f7
Implement the new block subsidy function (#1830)
* Replace the old blockSubsidy parameters with the new ones.

* Return subsidyGenesisReward if blockHash is the genesis hash.

* Traverse a block's past for the subsidy calculation.

* Partially implement SubsidyStore.

* Refer to SubsidyStore from CoinbaseManager.

* Wrap calcBlockSubsidy in getBlockSubsidy, which first checks the database.

* Fix finalityStore not calling GenerateShardingID.

* Implement calculateAveragePastSubsidy.

* Implement calculateMergeSetSubsidySum.

* Implement calculateSubsidyRandomVariable.

* Implement calcBlockSubsidy.

* Add a TODO about floats.

* Update the calcBlockSubsidy TODO.

* Use binary.LittleEndian in calculateSubsidyRandomVariable.

* Fix bad range in calculateSubsidyRandomVariable.

* Replace float64 with big.Rat everywhere except for subsidyRandomVariable.

* Fix a nil dereference.

* Use a random walk to approximate the normal distribution.

* In order to avoid unsupported fractional results from powInt64, flip the numerator and the denominator manually.

* Set standardDeviation to 0.25, MaxSompi to 10_000_000_000 * SompiPerKaspa and defaultSubsidyGenesisReward to 1_000.

* Set the standard deviation to 0.2.

* Use a binomial distribution instead of trying to estimate the normal distribution.

* Change some values around.

* Clamp the block subsidy.

* Remove the fake duplicate constants in the util package.

* Reduce MaxSompi to only 100m Kaspa to avoid hitting the uint64 ceiling.

* Lower MaxSompi further to avoid new and exciting ways for the uint64 ceiling to be hit.

* Remove debug logs.

* Fix a couple of failing tests.

* Fix TestBlockWindow.

* Fix limitTransactionCount sometimes crashing on index-out-of-bounds.

* In TrustedDataDataDAABlock, replace BlockHeader with DomainBlock

* In calculateAveragePastSubsidy, use blockWindow instead of doing a BFS manually.

* Remove the reference to DAGTopologyManager in coinbaseManager.

* Add subsidy to the coinbase payload.

* Get rid of the subsidy store and extract subsidies out of coinbase transactions.

* Keep a blockWindow amount of blocks under the virtual for IBD purposes.

* Manually remove the virtual genesis from the merge set.

* Fix simnet genesis.

* Fix TestPruning.

* Fix TestCheckBlockIsNotPruned.

* Fix TestBlockWindow.

* Fix TestCalculateSignatureHashSchnorr.

* Fix TestCalculateSignatureHashECDSA.

* Fix serializing the wrong value into the coinbase payload.

* Rename coinbaseOutputForBlueBlock to coinbaseOutputAndSubsidyForBlueBlock.

* Add a TODO about optimizing trusted data DAA window blocks.

* Expand on a comment in TestCheckBlockIsNotPruned.

* In calcBlockSubsidy, divide the big.Int numerator by the big.Int denominator instead of converting to float64.

* Clarify a comment.

* Rename SubsidyMinGenesisReward to MinSubsidy.

* Properly handle trusted data blocks in calculateMergeSetSubsidySum.

* Use the first two bytes of the selected parent's hash for randomness instead of math/rand.

* Restore maxSompi to what it used to be.

* Fix TestPruning.

* Fix TestAmountCreation.

* Fix TestBlockWindow.

* Fix TestAmountUnitConversions.

* Increase the timeout in many-tips to 30 minutes.

* Check coinbase subsidy for every block

* Re-rename functions

* Use shift instead of powInt64 to determine subsidyRandom

Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-10-30 10:16:47 +03:00

218 lines
7.2 KiB
Go

package mempool
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
"time"
)
type transactionsPool struct {
mempool *mempool
allTransactions model.IDToTransactionMap
highPriorityTransactions model.IDToTransactionMap
chainedTransactionsByPreviousOutpoint model.OutpointToTransactionMap
transactionsOrderedByFeeRate model.TransactionsOrderedByFeeRate
lastExpireScanDAAScore uint64
lastExpireScanTime time.Time
}
func newTransactionsPool(mp *mempool) *transactionsPool {
return &transactionsPool{
mempool: mp,
allTransactions: model.IDToTransactionMap{},
highPriorityTransactions: model.IDToTransactionMap{},
chainedTransactionsByPreviousOutpoint: model.OutpointToTransactionMap{},
transactionsOrderedByFeeRate: model.TransactionsOrderedByFeeRate{},
lastExpireScanDAAScore: 0,
lastExpireScanTime: time.Now(),
}
}
func (tp *transactionsPool) addTransaction(transaction *externalapi.DomainTransaction,
parentTransactionsInPool model.OutpointToTransactionMap, isHighPriority bool) (*model.MempoolTransaction, error) {
virtualDAAScore, err := tp.mempool.consensusReference.Consensus().GetVirtualDAAScore()
if err != nil {
return nil, err
}
mempoolTransaction := model.NewMempoolTransaction(
transaction, parentTransactionsInPool, isHighPriority, virtualDAAScore)
err = tp.addMempoolTransaction(mempoolTransaction)
if err != nil {
return nil, err
}
return mempoolTransaction, nil
}
func (tp *transactionsPool) addMempoolTransaction(transaction *model.MempoolTransaction) error {
tp.allTransactions[*transaction.TransactionID()] = transaction
for outpoint, parentTransactionInPool := range transaction.ParentTransactionsInPool() {
tp.chainedTransactionsByPreviousOutpoint[outpoint] = parentTransactionInPool
}
tp.mempool.mempoolUTXOSet.addTransaction(transaction)
err := tp.transactionsOrderedByFeeRate.Push(transaction)
if err != nil {
return err
}
if transaction.IsHighPriority() {
tp.highPriorityTransactions[*transaction.TransactionID()] = transaction
}
return nil
}
func (tp *transactionsPool) removeTransaction(transaction *model.MempoolTransaction) error {
delete(tp.allTransactions, *transaction.TransactionID())
err := tp.transactionsOrderedByFeeRate.Remove(transaction)
if err != nil {
return err
}
delete(tp.highPriorityTransactions, *transaction.TransactionID())
for outpoint := range transaction.ParentTransactionsInPool() {
delete(tp.chainedTransactionsByPreviousOutpoint, outpoint)
}
return nil
}
func (tp *transactionsPool) expireOldTransactions() error {
virtualDAAScore, err := tp.mempool.consensusReference.Consensus().GetVirtualDAAScore()
if err != nil {
return err
}
if virtualDAAScore-tp.lastExpireScanDAAScore < tp.mempool.config.TransactionExpireScanIntervalDAAScore ||
time.Since(tp.lastExpireScanTime).Seconds() < float64(tp.mempool.config.TransactionExpireScanIntervalSeconds) {
return nil
}
for _, mempoolTransaction := range tp.allTransactions {
// Never expire high priority transactions
if mempoolTransaction.IsHighPriority() {
continue
}
// Remove all transactions whose addedAtDAAScore is older then TransactionExpireIntervalDAAScore
daaScoreSinceAdded := virtualDAAScore - mempoolTransaction.AddedAtDAAScore()
if daaScoreSinceAdded > tp.mempool.config.TransactionExpireIntervalDAAScore {
log.Debugf("Removing transaction %s, because it expired. DAAScore moved by %d, expire interval: %d",
mempoolTransaction.TransactionID(), daaScoreSinceAdded, tp.mempool.config.TransactionExpireIntervalDAAScore)
err = tp.mempool.removeTransaction(mempoolTransaction.TransactionID(), true)
if err != nil {
return err
}
}
}
tp.lastExpireScanDAAScore = virtualDAAScore
tp.lastExpireScanTime = time.Now()
return nil
}
func (tp *transactionsPool) allReadyTransactions() []*externalapi.DomainTransaction {
result := []*externalapi.DomainTransaction{}
for _, mempoolTransaction := range tp.allTransactions {
if len(mempoolTransaction.ParentTransactionsInPool()) == 0 {
result = append(result, mempoolTransaction.Transaction())
}
}
return result
}
func (tp *transactionsPool) getParentTransactionsInPool(
transaction *externalapi.DomainTransaction) model.OutpointToTransactionMap {
parentsTransactionsInPool := model.OutpointToTransactionMap{}
for _, input := range transaction.Inputs {
if transaction, ok := tp.allTransactions[input.PreviousOutpoint.TransactionID]; ok {
parentsTransactionsInPool[input.PreviousOutpoint] = transaction
}
}
return parentsTransactionsInPool
}
func (tp *transactionsPool) getRedeemers(transaction *model.MempoolTransaction) []*model.MempoolTransaction {
stack := []*model.MempoolTransaction{transaction}
redeemers := []*model.MempoolTransaction{}
for len(stack) > 0 {
var current *model.MempoolTransaction
last := len(stack) - 1
current, stack = stack[last], stack[:last]
outpoint := externalapi.DomainOutpoint{TransactionID: *current.TransactionID()}
for i := range current.Transaction().Outputs {
outpoint.Index = uint32(i)
if redeemerTransaction, ok := tp.chainedTransactionsByPreviousOutpoint[outpoint]; ok {
stack = append(stack, redeemerTransaction)
redeemers = append(redeemers, redeemerTransaction)
}
}
}
return redeemers
}
func (tp *transactionsPool) limitTransactionCount() error {
currentIndex := 0
for uint64(len(tp.allTransactions)) > tp.mempool.config.MaximumTransactionCount {
var transactionToRemove *model.MempoolTransaction
for {
transactionToRemove = tp.transactionsOrderedByFeeRate.GetByIndex(currentIndex)
if !transactionToRemove.IsHighPriority() {
break
}
currentIndex++
if currentIndex >= len(tp.allTransactions) {
log.Warnf(
"Number of high-priority transactions in mempool (%d) is higher than maximum allowed (%d)",
len(tp.allTransactions), tp.mempool.config.MaximumTransactionCount)
return nil
}
}
log.Debugf("Removing transaction %s, because mempoolTransaction count (%d) exceeded the limit (%d)",
transactionToRemove.TransactionID(), len(tp.allTransactions), tp.mempool.config.MaximumTransactionCount)
err := tp.mempool.removeTransaction(transactionToRemove.TransactionID(), true)
if err != nil {
return err
}
if currentIndex >= len(tp.allTransactions) {
break
}
}
return nil
}
func (tp *transactionsPool) getTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) {
if mempoolTransaction, ok := tp.allTransactions[*transactionID]; ok {
return mempoolTransaction.Transaction(), true
}
return nil, false
}
func (tp *transactionsPool) getAllTransactions() []*externalapi.DomainTransaction {
allTransactions := make([]*externalapi.DomainTransaction, 0, len(tp.allTransactions))
for _, mempoolTransaction := range tp.allTransactions {
allTransactions = append(allTransactions, mempoolTransaction.Transaction())
}
return allTransactions
}
func (tp *transactionsPool) transactionCount() int {
return len(tp.allTransactions)
}