kaspad/domain/consensus/processes/transactionvalidator/transaction_in_isolation.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

181 lines
6.1 KiB
Go

package transactionvalidator
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
"github.com/pkg/errors"
)
// ValidateTransactionInIsolation validates the parts of the transaction that can be validated context-free
func (v *transactionValidator) ValidateTransactionInIsolation(tx *externalapi.DomainTransaction) error {
err := v.checkTransactionInputCount(tx)
if err != nil {
return err
}
err = v.checkTransactionAmountRanges(tx)
if err != nil {
return err
}
err = v.checkDuplicateTransactionInputs(tx)
if err != nil {
return err
}
err = v.checkCoinbaseLength(tx)
if err != nil {
return err
}
err = v.checkGasInBuiltInOrNativeTransactions(tx)
if err != nil {
return err
}
err = v.checkSubnetworkRegistryTransaction(tx)
if err != nil {
return err
}
err = v.checkNativeTransactionPayload(tx)
if err != nil {
return err
}
// TODO: fill it with the node's subnetwork id.
err = v.checkTransactionSubnetwork(tx, nil)
if err != nil {
return err
}
if tx.Version > constants.MaxTransactionVersion {
return errors.Wrapf(ruleerrors.ErrTransactionVersionIsUnknown, "validation failed: unknown transaction version. ")
}
return nil
}
func (v *transactionValidator) checkTransactionInputCount(tx *externalapi.DomainTransaction) error {
// A non-coinbase transaction must have at least one input.
if !transactionhelper.IsCoinBase(tx) && len(tx.Inputs) == 0 {
return errors.Wrapf(ruleerrors.ErrNoTxInputs, "transaction has no inputs")
}
return nil
}
func (v *transactionValidator) checkTransactionAmountRanges(tx *externalapi.DomainTransaction) error {
// Ensure the transaction amounts are in range. Each transaction
// output must not be negative or more than the max allowed per
// transaction. Also, the total of all outputs must abide by the same
// restrictions. All amounts in a transaction are in a unit value known
// as a sompi. One kaspa is a quantity of sompi as defined by the
// sompiPerKaspa constant.
var totalSompi uint64
for _, txOut := range tx.Outputs {
sompi := txOut.Value
if sompi == 0 {
return errors.Wrap(ruleerrors.ErrTxOutValueZero, "zero value outputs are forbidden")
}
if sompi > constants.MaxSompi {
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "transaction output value of %d is "+
"higher than max allowed value of %d", sompi, constants.MaxSompi)
}
// Binary arithmetic guarantees that any overflow is detected and reported.
// This is impossible for Kaspa, but perhaps possible if an alt increases
// the total money supply.
newTotalSompi := totalSompi + sompi
if newTotalSompi < totalSompi {
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "total value of all transaction "+
"outputs exceeds max allowed value of %d",
constants.MaxSompi)
}
totalSompi = newTotalSompi
if totalSompi > constants.MaxSompi {
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "total value of all transaction "+
"outputs is %d which is higher than max "+
"allowed value of %d", totalSompi,
constants.MaxSompi)
}
}
return nil
}
func (v *transactionValidator) checkDuplicateTransactionInputs(tx *externalapi.DomainTransaction) error {
existingTxOut := make(map[externalapi.DomainOutpoint]struct{})
for _, txIn := range tx.Inputs {
if _, exists := existingTxOut[txIn.PreviousOutpoint]; exists {
return errors.Wrapf(ruleerrors.ErrDuplicateTxInputs, "transaction "+
"contains duplicate inputs")
}
existingTxOut[txIn.PreviousOutpoint] = struct{}{}
}
return nil
}
func (v *transactionValidator) checkCoinbaseLength(tx *externalapi.DomainTransaction) error {
if !transactionhelper.IsCoinBase(tx) {
return nil
}
// Coinbase payload length must not exceed the max length.
payloadLen := len(tx.Payload)
if uint64(payloadLen) > v.maxCoinbasePayloadLength {
return errors.Wrapf(ruleerrors.ErrBadCoinbasePayloadLen, "coinbase transaction payload length "+
"of %d is out of range (max: %d)",
payloadLen, v.maxCoinbasePayloadLength)
}
return nil
}
func (v *transactionValidator) checkGasInBuiltInOrNativeTransactions(tx *externalapi.DomainTransaction) error {
// Transactions in native, registry and coinbase subnetworks must have Gas = 0
if subnetworks.IsBuiltInOrNative(tx.SubnetworkID) && tx.Gas > 0 {
return errors.Wrapf(ruleerrors.ErrInvalidGas, "transaction in the native or "+
"registry subnetworks has gas > 0 ")
}
return nil
}
func (v *transactionValidator) checkSubnetworkRegistryTransaction(tx *externalapi.DomainTransaction) error {
if tx.SubnetworkID != subnetworks.SubnetworkIDRegistry {
return nil
}
if len(tx.Payload) != 8 {
return errors.Wrapf(ruleerrors.ErrSubnetworkRegistry, "validation failed: subnetwork registry "+
"tx has an invalid payload")
}
return nil
}
func (v *transactionValidator) checkNativeTransactionPayload(tx *externalapi.DomainTransaction) error {
if tx.SubnetworkID == subnetworks.SubnetworkIDNative && len(tx.Payload) > 0 {
return errors.Wrapf(ruleerrors.ErrInvalidPayload, "transaction in the native subnetwork "+
"includes a payload")
}
return nil
}
func (v *transactionValidator) checkTransactionSubnetwork(tx *externalapi.DomainTransaction,
localNodeSubnetworkID *externalapi.DomainSubnetworkID) error {
if !v.enableNonNativeSubnetworks && tx.SubnetworkID != subnetworks.SubnetworkIDNative &&
tx.SubnetworkID != subnetworks.SubnetworkIDCoinbase {
return errors.Wrapf(ruleerrors.ErrSubnetworksDisabled, "transaction has non native or coinbase "+
"subnetwork ID")
}
// If we are a partial node, only transactions on built in subnetworks
// or our own subnetwork may have a payload
isLocalNodeFull := localNodeSubnetworkID == nil
shouldTxBeFull := subnetworks.IsBuiltIn(tx.SubnetworkID) || tx.SubnetworkID.Equal(localNodeSubnetworkID)
if !isLocalNodeFull && !shouldTxBeFull && len(tx.Payload) > 0 {
return errors.Wrapf(ruleerrors.ErrInvalidPayload,
"transaction that was expected to be partial has a payload "+
"with length > 0")
}
return nil
}