kaspad/domain/miningmanager/mempool/check_transaction_standard.go
Svarog c13a4d90ed
Mempool redesign (#1752)
* Added model and stubs for all main methods

* Add constructors to all main objects

* Implement BlockCandidateTransactions

* implement expireOldTransactions and expireOrphanTransactions

* Rename isHighPriority to neverExpires

* Add stub for checkDoubleSpends

* Revert "Rename isHighPriority to neverExpires"

This reverts commit b2da9a4a00c02fb380d2518cf54fa16257bd8423.

* Imeplement transactionsOrderedByFeeRate

* Orphan maps should be idToOrphan

* Add error.go to mempool

* Invert the condition for banning when mempool rejects a transaction

* Move all model objects to model package

* Implement getParentsInPool

* Implemented mempoolUTXOSet.addTransaction

* Implement removeTransaction, remove sanity checks

* Implemented mempoolUTXOSet.checkDoubleSpends

* Implemented removeOrphan

* Implement removeOrphan

* Implement maybeAddOrphan and AddOrphan

* Implemented processOrphansAfterAcceptedTransaction

* Implement transactionsPool.addTransaction

* Implement RemoveTransaction

* If a transaction was removed from the mempool - update it's redeemers in orphan pool as well

* Use maximumOrphanTransactionCount

* Add allowOrphans to ValidateAndInsertTransaction stub

* Implement validateTransaction functions

* Implement fillInputs

* Implement ValidateAndInsertTransaction

* Implement HandleNewBlockTransactions

* Implement missing mempool interface methods

* Add comments to exported functions

* Call ValidateTransactionInIsolation where needed

* Implement RevalidateHighPriorityTransactions

* Rewire kaspad to use new mempool, and fix compilation errors

* Update rebroadcast logic to use new structure

* Handle non-standard transaction errors properly

* Add mutex to mempool

* bugfix: GetTransaction panics when ok is false

* properly calculate targetBlocksPerSecond in config.go

* Fix various lint errors and tests

* Fix expected text in test for duplicate transactions

* Skip the coinbase transaction in HandleNewBlockTransactions

* Unorphan the correct transactions

* Call ValidateTransactionAndPopulateWithConsensusData on unorphanTransaction

* Re-apply policy_test as check_transactions_standard_test

* removeTransaction: Remove redeemers in orphan pool as well

* Remove redundant check for uint64 < 0

* Export and rename isDust -> IsTransactionOutputDust to allow usage by rothschild

* Add allowOrphan to SubmitTransaction RPC request

* Remove all implementation from mempool.go

* tidy go mod

* Don't pass acceptedOrphans to handleNewBlockTransactions

* Use t.Errorf in stead of t.Fatalf

* Remove minimum relay fee from TestDust, as it's no longer configurable

* Add separate VirtualDAASCore method for faster retrieval where it's repeated multiple times

* Broadcast all transactions that were accepted

* Don't re-use GetVirtualDAAScore in GetVirtualInfo - this causes a deadlock

* Use real transaction count, and not Orphan

* Get mempool config from outside, incorporating values received from cli

* Use MinRelayFee and MaxOrphanTxs from global kaspad config

* Add explanation for the seemingly redundant check for transaction version in checkTransactionStandard

* Update some comment

* Convert creation of acceptedTransactions to a single line

* Move mempoolUTXOSet out of checkDoubleSpends

* Add test for attempt to insert double spend into mempool

* fillInputs: Skip check for coinbase - it's always false in mempool

* Clarify comment about removeRedeemers when removing random orphan

* Don't remove high-priority transactions in limitTransactionCount

* Use mempool.removeTransaction in limitTransactionCount

* Add mutex comment to handleNewBlockTransactions

* Return error from limitTransactionCount

* Pluralize the map types

* mempoolUTXOSet.removeTransaction: Don't restore utxo if it was not created in mempool

* Don't evacuate from orphanPool high-priority transactions

* Disallow double-spends in orphan pool

* Don't use exported (and locking) methods from inside mempool

* Check for double spends in mempool during revalidateTransaction

* Add checkOrphanDuplicate

* Add orphan to acceptedOrphans, not current

* Add TestHighPriorityTransactions

* Fix off-by-one error in limitTransactionCount

* Add TestRevalidateHighPriorityTransactions

* Remove checkDoubleSpends from revalidateTransaction

* Fix TestRevalidateHighPriorityTransactions

* Move check for MaximumOrphanCount to beggining of maybeAddOrphan

* Rename all map type to singulateToSingularMap

* limitOrphanPool only after the orphan was added

* TestDoubleSpendInMempool: use createChildTxWhenParentTxWasAddedByConsensus instead of createTransactionWithUTXOEntry

* Fix some comments

* Have separate min/max transaction versions for mempool

* Add comment on defaultMaximumOrphanTransactionCount to keep it small as long as we have recursion

* Fix comment

* Rename: createChildTxWhenParentTxWasAddedByConsensus -> createChildTxWhereParentTxWasAddedByConsensus

* Handle error from createChildTxWhereParentTxWasAddedByConsensus

* Rename createChildTxWhereParentTxWasAddedByConsensus -> createChildAndParentTxsAndAddParentToConsensus

* Convert all MaximumXXX constants to uint64

* Add comment

* remove mutex comments
2021-06-23 15:49:20 +03:00

212 lines
9.4 KiB
Go

package mempool
import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/util"
)
const (
// maxStandardP2SHSigOps is the maximum number of signature operations
// that are considered standard in a pay-to-script-hash script.
maxStandardP2SHSigOps = 15
// maximumStandardSignatureScriptSize is the maximum size allowed for a
// transaction input signature script to be considered standard. This
// value allows for a 15-of-15 CHECKMULTISIG pay-to-script-hash with
// compressed keys.
//
// The form of the overall script is: OP_0 <15 signatures> OP_PUSHDATA2
// <2 bytes len> [OP_15 <15 pubkeys> OP_15 OP_CHECKMULTISIG]
//
// For the p2sh script portion, each of the 15 compressed pubkeys are
// 33 bytes (plus one for the OP_DATA_33 opcode), and the thus it totals
// to (15*34)+3 = 513 bytes. Next, each of the 15 signatures is a max
// of 73 bytes (plus one for the OP_DATA_73 opcode). Also, there is one
// extra byte for the initial extra OP_0 push and 3 bytes for the
// OP_PUSHDATA2 needed to specify the 513 bytes for the script push.
// That brings the total to 1+(15*74)+3+513 = 1627. This value also
// adds a few extra bytes to provide a little buffer.
// (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650
maximumStandardSignatureScriptSize = 1650
// maximumStandardTransactionSize is the maximum size allowed for transactions that
// are considered standard and will therefore be relayed and considered
// for mining.
maximumStandardTransactionSize = 100000
)
func (mp *mempool) checkTransactionStandardInIsolation(transaction *externalapi.DomainTransaction) error {
// The transaction must be a currently supported version.
//
// This check is currently mirrored in consensus.
// However, in a later version of Kaspa the consensus-valid transaction version range might diverge from the
// standard transaction version range, and thus the validation should happen in both levels.
if transaction.Version > mp.config.MaximumStandardTransactionVersion ||
transaction.Version < mp.config.MinimumStandardTransactionVersion {
str := fmt.Sprintf("transaction version %d is not in the valid range of %d-%d", transaction.Version,
mp.config.MinimumStandardTransactionVersion, mp.config.MaximumStandardTransactionVersion)
return transactionRuleError(RejectNonstandard, str)
}
// Since extremely large transactions with a lot of inputs can cost
// almost as much to process as the sender fees, limit the maximum
// size of a transaction. This also helps mitigate CPU exhaustion
// attacks.
serializedLength := estimatedsize.TransactionEstimatedSerializedSize(transaction)
if serializedLength > maximumStandardTransactionSize {
str := fmt.Sprintf("transaction size of %d is larger than max allowed size of %d",
serializedLength, maximumStandardTransactionSize)
return transactionRuleError(RejectNonstandard, str)
}
for i, input := range transaction.Inputs {
// Each transaction input signature script must not exceed the
// maximum size allowed for a standard transaction. See
// the comment on maximumStandardSignatureScriptSize for more details.
signatureScriptLen := len(input.SignatureScript)
if signatureScriptLen > maximumStandardSignatureScriptSize {
str := fmt.Sprintf("transaction input %d: signature script size of %d bytes is larger than the "+
"maximum allowed size of %d bytes", i, signatureScriptLen, maximumStandardSignatureScriptSize)
return transactionRuleError(RejectNonstandard, str)
}
}
// None of the output public key scripts can be a non-standard script or be "dust".
for i, output := range transaction.Outputs {
if output.ScriptPublicKey.Version > constants.MaxScriptPublicKeyVersion {
return transactionRuleError(RejectNonstandard, "The version of the scriptPublicKey is higher than the known version.")
}
scriptClass := txscript.GetScriptClass(output.ScriptPublicKey.Script)
if scriptClass == txscript.NonStandardTy {
str := fmt.Sprintf("transaction output %d: non-standard script form", i)
return transactionRuleError(RejectNonstandard, str)
}
if mp.IsTransactionOutputDust(output) {
str := fmt.Sprintf("transaction output %d: payment "+
"of %d is dust", i, output.Value)
return transactionRuleError(RejectDust, str)
}
}
return nil
}
// IsTransactionOutputDust returns whether or not the passed transaction output amount
// is considered dust or not based on the configured minimum transaction relay fee.
// Dust is defined in terms of the minimum transaction relay fee. In
// particular, if the cost to the network to spend coins is more than 1/3 of the
// minimum transaction relay fee, it is considered dust.
//
// It is exported for use by transaction generators and wallets
func (mp *mempool) IsTransactionOutputDust(output *externalapi.DomainTransactionOutput) bool {
// Unspendable outputs are considered dust.
if txscript.IsUnspendable(output.ScriptPublicKey.Script) {
return true
}
// The total serialized size consists of the output and the associated
// input script to redeem it. Since there is no input script
// to redeem it yet, use the minimum size of a typical input script.
//
// Pay-to-pubkey bytes breakdown:
//
// Output to pubkey (43 bytes):
// 8 value, 1 script len, 34 script [1 OP_DATA_32,
// 32 pubkey, 1 OP_CHECKSIG]
//
// Input (105 bytes):
// 36 prev outpoint, 1 script len, 64 script [1 OP_DATA_64,
// 64 sig], 4 sequence
//
// The most common scripts are pay-to-pubkey, and as per the above
// breakdown, the minimum size of a p2pk input script is 148 bytes. So
// that figure is used.
totalSerializedSize := estimatedsize.TransactionOutputEstimatedSerializedSize(output) + 148
// The output is considered dust if the cost to the network to spend the
// coins is more than 1/3 of the minimum free transaction relay fee.
// mp.config.MinimumRelayTransactionFee is in sompi/KB, so multiply
// by 1000 to convert to bytes.
//
// Using the typical values for a pay-to-pubkey transaction from
// the breakdown above and the default minimum free transaction relay
// fee of 1000, this equates to values less than 546 sompi being
// considered dust.
//
// The following is equivalent to (value/totalSerializedSize) * (1/3) * 1000
// without needing to do floating point math.
return output.Value*1000/(3*totalSerializedSize) < uint64(mp.config.MinimumRelayTransactionFee)
}
// checkTransactionStandardInContext performs a series of checks on a transaction's
// inputs to ensure they are "standard". A standard transaction input within the
// context of this function is one whose referenced public key script is of a
// standard form and, for pay-to-script-hash, does not have more than
// maxStandardP2SHSigOps signature operations.
// In addition, makes sure that the transaction's fee is above the minimum for acceptance
// into the mempool and relay
func (mp *mempool) checkTransactionStandardInContext(transaction *externalapi.DomainTransaction) error {
for i, input := range transaction.Inputs {
// It is safe to elide existence and index checks here since
// they have already been checked prior to calling this
// function.
utxoEntry := input.UTXOEntry
originScriptPubKey := utxoEntry.ScriptPublicKey()
switch txscript.GetScriptClass(originScriptPubKey.Script) {
case txscript.ScriptHashTy:
numSigOps := txscript.GetPreciseSigOpCount(
input.SignatureScript, originScriptPubKey, true)
if numSigOps > maxStandardP2SHSigOps {
str := fmt.Sprintf("transaction input #%d has %d signature operations which is more "+
"than the allowed max amount of %d", i, numSigOps, maxStandardP2SHSigOps)
return transactionRuleError(RejectNonstandard, str)
}
case txscript.NonStandardTy:
str := fmt.Sprintf("transaction input #%d has a non-standard script form", i)
return transactionRuleError(RejectNonstandard, str)
}
}
serializedSize := estimatedsize.TransactionEstimatedSerializedSize(transaction)
minimumFee := mp.minimumRequiredTransactionRelayFee(serializedSize)
if transaction.Fee < minimumFee {
str := fmt.Sprintf("transaction %s has %d fees which is under the required amount of %d",
consensushashing.TransactionID(transaction), transaction.Fee, minimumFee)
return transactionRuleError(RejectInsufficientFee, str)
}
return nil
}
// minimumRequiredTransactionRelayFee returns the minimum transaction fee required for a
// transaction with the passed serialized size to be accepted into the memory
// pool and relayed.
func (mp *mempool) minimumRequiredTransactionRelayFee(serializedSize uint64) uint64 {
// Calculate the minimum fee for a transaction to be allowed into the
// mempool and relayed by scaling the base fee. MinimumRelayTransactionFee is in
// sompi/kB so multiply by serializedSize (which is in bytes) and
// divide by 1000 to get minimum sompis.
minimumFee := (serializedSize * uint64(mp.config.MinimumRelayTransactionFee)) / 1000
if minimumFee == 0 && mp.config.MinimumRelayTransactionFee > 0 {
minimumFee = uint64(mp.config.MinimumRelayTransactionFee)
}
// Set the minimum fee to the maximum possible value if the calculated
// fee is not in the valid range for monetary amounts.
if minimumFee > util.MaxSompi {
minimumFee = util.MaxSompi
}
return minimumFee
}