Go 1.22 is introducing a change to the way the range statement works that is not entirely backward compatible. In particular, the loop variable is changing from one instance per loop to one instance per iteration. The existing semantics are well known and in order to ensure correct behavior and avoid potential races that would otherwise result, the current code typically opts for using ranges with an index and creating a local that points into the array when the range variable needs to be used as a pointer. In addition, there are a few remaining cases that use the common alternative approach of capturing the range variable through self assignment. Both approaches will work correctly when compiled with Go 1.22 as well as older version of Go so long as the module versions are not bumped. However, once the modules are updated to allow support of all features introduced by go 1.22, the second approach of capturing the range variable would no longer be needed and as a result would very likely need to be removed to avoid vet/linter issues. Unfortunately, the consequence of that change would mean building the code with older versions of Go would start to produce incorrect code. The first approach of using a range index that is predominantly used throughout the code does not have that potential pitfall as it works equally well for all of the aforementioned cases. Thus, in order to avoid any potential issues before they ever even have a chance to arise, this updates the few remaining instances of the second approach to the first one or to otherwise rework the code to avoid the need altogether.
2360 lines
82 KiB
Go
2360 lines
82 KiB
Go
// Copyright (c) 2014-2016 The btcsuite developers
|
|
// Copyright (c) 2015-2023 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package mining
|
|
|
|
import (
|
|
"container/heap"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/blockchain/stake/v5"
|
|
"github.com/decred/dcrd/blockchain/standalone/v2"
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/chaincfg/v3"
|
|
"github.com/decred/dcrd/dcrutil/v4"
|
|
"github.com/decred/dcrd/gcs/v4/blockcf2"
|
|
"github.com/decred/dcrd/internal/blockchain"
|
|
"github.com/decred/dcrd/txscript/v4"
|
|
"github.com/decred/dcrd/txscript/v4/stdaddr"
|
|
"github.com/decred/dcrd/txscript/v4/stdscript"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
var (
|
|
// zeroHash is the zero value hash (all zeros). It is defined as a
|
|
// convenience.
|
|
zeroHash chainhash.Hash
|
|
|
|
// opTrueScript is a simple public key script that contains the OP_TRUE
|
|
// opcode. It is defined here to reduce garbage creation.
|
|
opTrueScript = []byte{txscript.OP_TRUE}
|
|
)
|
|
|
|
// Config is a descriptor containing the mining configuration.
|
|
type Config struct {
|
|
// Policy houses the policy (configuration parameters) which is used to control
|
|
// the generation of block templates.
|
|
Policy *Policy
|
|
|
|
// TxSource represents a source of transactions to consider for inclusion in
|
|
// new blocks.
|
|
TxSource TxSource
|
|
|
|
// TimeSource defines the median time source which is used to retrieve the
|
|
// current time adjusted by the median time offset. This is used when setting
|
|
// the timestamp in the header of new blocks.
|
|
TimeSource blockchain.MedianTimeSource
|
|
|
|
// SubsidyCache defines a subsidy cache to use when calculating and validating
|
|
// block and vote subsidies.
|
|
SubsidyCache *standalone.SubsidyCache
|
|
|
|
// ChainParams identifies which chain parameters should be used while
|
|
// generating block templates.
|
|
ChainParams *chaincfg.Params
|
|
|
|
// MiningTimeOffset defines the number of seconds to offset the mining
|
|
// timestamp of a block by (positive values are in the past).
|
|
MiningTimeOffset int
|
|
|
|
// BestSnapshot defines the function to use to access information about the
|
|
// current best block. The returned instance should be treated as immutable.
|
|
BestSnapshot func() *blockchain.BestState
|
|
|
|
// BlockByHash defines the function to use to search the internal chain block
|
|
// stores and the database in an attempt to find the requested block and return
|
|
// it. This function should return blocks regardless of whether or not they
|
|
// are part of the main chain.
|
|
BlockByHash func(hash *chainhash.Hash) (*dcrutil.Block, error)
|
|
|
|
// CalcNextRequiredDifficulty defines the function to use to calculate the
|
|
// required difficulty for the block after the given block based on the
|
|
// difficulty retarget rules.
|
|
CalcNextRequiredDifficulty func(hash *chainhash.Hash, timestamp time.Time) (uint32, error)
|
|
|
|
// CalcStakeVersionByHash defines the function to use to calculate the expected
|
|
// stake version for the block AFTER the provided block hash.
|
|
CalcStakeVersionByHash func(hash *chainhash.Hash) (uint32, error)
|
|
|
|
// CheckConnectBlockTemplate defines the function to use to fully validate that
|
|
// connecting the passed block to either the tip of the main chain or its
|
|
// parent does not violate any consensus rules, aside from the proof of work
|
|
// requirement.
|
|
CheckConnectBlockTemplate func(block *dcrutil.Block) error
|
|
|
|
// CheckTicketExhaustion defines the function to use to ensure that extending
|
|
// the block associated with the provided hash with a block that contains the
|
|
// specified number of ticket purchases will not result in a chain that is
|
|
// unrecoverable due to inevitable ticket exhaustion. This scenario happens
|
|
// when the number of live tickets drops below the number of tickets that is
|
|
// needed to reach the next block at which any outstanding immature ticket
|
|
// purchases that would provide the necessary live tickets mature.
|
|
CheckTicketExhaustion func(hash *chainhash.Hash, ticketPurchases uint8) error
|
|
|
|
// CheckTransactionInputs defines the function to use to perform a series of
|
|
// checks on the inputs to a transaction to ensure they are valid.
|
|
CheckTransactionInputs func(tx *dcrutil.Tx, txHeight int64,
|
|
view *blockchain.UtxoViewpoint, checkFraudProof bool,
|
|
prevHeader *wire.BlockHeader, isTreasuryEnabled,
|
|
isAutoRevocationsEnabled bool,
|
|
subsidySplitVariant standalone.SubsidySplitVariant) (int64, error)
|
|
|
|
// CheckTSpendHasVotes defines the function to use to check whether the given
|
|
// tspend has enough votes to be included in a block AFTER the specified block.
|
|
CheckTSpendHasVotes func(prevHash chainhash.Hash, tspend *dcrutil.Tx) error
|
|
|
|
// CountSigOps defines the function to use to count the number of signature
|
|
// operations for all transaction input and output scripts in the provided
|
|
// transaction.
|
|
CountSigOps func(tx *dcrutil.Tx, isCoinBaseTx bool, isSSGen bool, isTreasuryEnabled bool) int
|
|
|
|
// FetchUtxoEntry defines the function to use to load and return the requested
|
|
// unspent transaction output from the point of view of the main chain tip.
|
|
//
|
|
// NOTE: Requesting an output for which there is no data will NOT return an
|
|
// error. Instead both the entry and the error will be nil. This is done to
|
|
// allow pruning of spent transaction outputs. In practice this means the
|
|
// caller must check if the returned entry is nil before invoking methods on
|
|
// it.
|
|
//
|
|
// This function is safe for concurrent access however the returned entry (if
|
|
// any) is NOT.
|
|
FetchUtxoEntry func(outpoint wire.OutPoint) (*blockchain.UtxoEntry, error)
|
|
|
|
// FetchUtxoView defines the function to use to fetch unspent transaction
|
|
// output information. The returned instance should be treated as immutable.
|
|
FetchUtxoView func(tx *dcrutil.Tx, includeRegularTxns bool) (*blockchain.UtxoViewpoint, error)
|
|
|
|
// FetchUtxoViewParentTemplate defines the function to use to fetch unspent
|
|
// transaction output information from the point of view of just having
|
|
// connected the given block, which must be a block template that connects to
|
|
// the parent of the tip of the main chain. In other words, the given block
|
|
// must be a sibling of the current tip of the main chain.
|
|
//
|
|
// This should typically only be used by mining code when it is unable to
|
|
// generate a template that extends the current tip due to being unable to
|
|
// acquire the minimum required number of votes to extend it.
|
|
//
|
|
// The returned instance should be treated as immutable.
|
|
FetchUtxoViewParentTemplate func(block *wire.MsgBlock) (*blockchain.UtxoViewpoint, error)
|
|
|
|
// ForceHeadReorganization defines the function to use to force a
|
|
// reorganization of the block chain to the block hash requested, so long as
|
|
// it matches up with the current organization of the best chain.
|
|
ForceHeadReorganization func(formerBest chainhash.Hash, newBest chainhash.Hash) error
|
|
|
|
// HeaderByHash returns the block header identified by the given hash or an
|
|
// error if it doesn't exist. Note that this will return headers from both
|
|
// the main chain and any side chains.
|
|
HeaderByHash func(hash *chainhash.Hash) (wire.BlockHeader, error)
|
|
|
|
// IsFinalizedTransaction defines the function to use to determine whether or
|
|
// not a transaction is finalized.
|
|
IsFinalizedTransaction func(tx *dcrutil.Tx, blockHeight int64, blockTime time.Time) bool
|
|
|
|
// IsHeaderCommitmentsAgendaActive defines the function to use to determine
|
|
// whether or not the header commitments agenda is active or not for the block
|
|
// AFTER the given block.
|
|
IsHeaderCommitmentsAgendaActive func(prevHash *chainhash.Hash) (bool, error)
|
|
|
|
// IsTreasuryAgendaActive defines the function to use to determine if the
|
|
// treasury agenda is active or not for the block AFTER the given block.
|
|
IsTreasuryAgendaActive func(prevHash *chainhash.Hash) (bool, error)
|
|
|
|
// IsAutoRevocationsAgendaActive defines the function to use to determine if
|
|
// the automatic ticket revocations agenda is active or not for the block
|
|
// AFTER the given block.
|
|
IsAutoRevocationsAgendaActive func(prevHash *chainhash.Hash) (bool, error)
|
|
|
|
// IsSubsidySplitAgendaActive defines the function to use to determine if
|
|
// the modified subsidy split agenda is active or not for the block AFTER
|
|
// the given block.
|
|
IsSubsidySplitAgendaActive func(prevHash *chainhash.Hash) (bool, error)
|
|
|
|
// IsSubsidySplitR2AgendaActive defines the function to use to determine if
|
|
// the modified subsidy split round 2 agenda is active or not for the block
|
|
// AFTER the given block.
|
|
IsSubsidySplitR2AgendaActive func(prevHash *chainhash.Hash) (bool, error)
|
|
|
|
// MaxTreasuryExpenditure defines the function to use to get the maximum amount
|
|
// of funds that can be spent from the treasury by a set of TSpends for a block
|
|
// that extends the given block hash. The function should return 0 if it is
|
|
// called on an invalid TVI.
|
|
MaxTreasuryExpenditure func(preTVIBlock *chainhash.Hash) (int64, error)
|
|
|
|
// NewUtxoViewpoint defines the function to use to create a new empty unspent
|
|
// transaction output view.
|
|
NewUtxoViewpoint func() *blockchain.UtxoViewpoint
|
|
|
|
// TipGeneration defines the function to use to get the entire generation of
|
|
// blocks stemming from the parent of the current tip.
|
|
TipGeneration func() []chainhash.Hash
|
|
|
|
// ValidateTransactionScripts defines the function to use to validate the
|
|
// scripts for the passed transaction.
|
|
ValidateTransactionScripts func(tx *dcrutil.Tx,
|
|
utxoView *blockchain.UtxoViewpoint, flags txscript.ScriptFlags,
|
|
isAutoRevocationsEnabled bool) error
|
|
}
|
|
|
|
// TxDesc is a descriptor about a transaction in a transaction source along with
|
|
// additional metadata.
|
|
type TxDesc struct {
|
|
// Tx is the transaction associated with the entry.
|
|
Tx *dcrutil.Tx
|
|
|
|
// Type is the type of the transaction associated with the entry.
|
|
Type stake.TxType
|
|
|
|
// Added is the time when the entry was added to the source pool.
|
|
Added time.Time
|
|
|
|
// Height is the block height when the entry was added to the source
|
|
// pool.
|
|
Height int64
|
|
|
|
// Fee is the total fee the transaction associated with the entry pays.
|
|
Fee int64
|
|
|
|
// TotalSigOps is the total signature operations for this transaction.
|
|
TotalSigOps int
|
|
|
|
// TxSize is the size of the transaction.
|
|
TxSize int64
|
|
}
|
|
|
|
// TxAncestorStats is a descriptor that stores aggregated statistics for the
|
|
// unconfirmed ancestors of a transaction.
|
|
type TxAncestorStats struct {
|
|
// Fees is the sum of all fees of unconfirmed ancestors.
|
|
Fees int64
|
|
|
|
// SizeBytes is the total size of all unconfirmed ancestors.
|
|
SizeBytes int64
|
|
|
|
// TotalSigOps is the total number of signature operations of all ancestors.
|
|
TotalSigOps int
|
|
|
|
// NumAncestors is the total number of ancestors for a given transaction.
|
|
NumAncestors int
|
|
|
|
// NumDescendants is the total number of descendants that have ancestor
|
|
// statistics tracked for a given transaction.
|
|
NumDescendants int
|
|
}
|
|
|
|
// VoteDesc is a descriptor about a vote transaction in a transaction source
|
|
// along with additional metadata.
|
|
type VoteDesc struct {
|
|
VoteHash chainhash.Hash
|
|
TicketHash chainhash.Hash
|
|
ApprovesParent bool
|
|
}
|
|
|
|
const (
|
|
// generatedBlockVersion is the version of the block being generated for
|
|
// the main network. It is defined as a constant here rather than using
|
|
// the wire.BlockVersion constant since a change in the block version
|
|
// will require changes to the generated block. Using the wire constant
|
|
// for generated block version could allow creation of invalid blocks
|
|
// for the updated version.
|
|
generatedBlockVersion = 10
|
|
|
|
// generatedBlockVersionTest is the version of the block being generated
|
|
// for networks other than the main network.
|
|
generatedBlockVersionTest = 11
|
|
|
|
// blockHeaderOverhead is the max number of bytes it takes to serialize
|
|
// a block header and max possible transaction count.
|
|
blockHeaderOverhead = wire.MaxBlockHeaderPayload + wire.MaxVarIntPayload
|
|
|
|
// coinbaseFlags is some extra data appended to the coinbase script
|
|
// sig.
|
|
coinbaseFlags = "/dcrd/"
|
|
|
|
// kilobyte is the size of a kilobyte.
|
|
kilobyte = 1000
|
|
)
|
|
|
|
// containsTx is a helper function that checks to see if a list of transactions
|
|
// contains any of the TxIns of some transaction.
|
|
func containsTxIns(txs []*dcrutil.Tx, tx *dcrutil.Tx) bool {
|
|
for _, txToCheck := range txs {
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
if txIn.PreviousOutPoint.Hash.IsEqual(txToCheck.Hash()) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// blockWithNumVotes is a block with the number of votes currently present
|
|
// for that block. Just used for sorting.
|
|
type blockWithNumVotes struct {
|
|
Hash chainhash.Hash
|
|
NumVotes uint16
|
|
}
|
|
|
|
// byNumberOfVotes implements sort.Interface to sort a slice of blocks by their
|
|
// number of votes.
|
|
type byNumberOfVotes []*blockWithNumVotes
|
|
|
|
// Len returns the number of elements in the slice. It is part of the
|
|
// sort.Interface implementation.
|
|
func (b byNumberOfVotes) Len() int {
|
|
return len(b)
|
|
}
|
|
|
|
// Swap swaps the elements at the passed indices. It is part of the
|
|
// sort.Interface implementation.
|
|
func (b byNumberOfVotes) Swap(i, j int) {
|
|
b[i], b[j] = b[j], b[i]
|
|
}
|
|
|
|
// Less returns whether the block with index i should sort before the block with
|
|
// index j. It is part of the sort.Interface implementation.
|
|
func (b byNumberOfVotes) Less(i, j int) bool {
|
|
return b[i].NumVotes < b[j].NumVotes
|
|
}
|
|
|
|
// SortParentsByVotes takes a list of block header hashes and sorts them
|
|
// by the number of votes currently available for them in the votes map of
|
|
// mempool. It then returns all blocks that are eligible to be used (have
|
|
// at least a majority number of votes) sorted by number of votes, descending.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func SortParentsByVotes(txSource TxSource, currentTopBlock chainhash.Hash, blocks []chainhash.Hash, params *chaincfg.Params) []chainhash.Hash {
|
|
// Return now when no blocks were provided.
|
|
lenBlocks := len(blocks)
|
|
if lenBlocks == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Fetch the vote metadata for the provided block hashes from the
|
|
// mempool and filter out any blocks that do not have the minimum
|
|
// required number of votes.
|
|
minVotesRequired := (params.TicketsPerBlock / 2) + 1
|
|
voteMetadata := txSource.VotesForBlocks(blocks)
|
|
filtered := make([]*blockWithNumVotes, 0, lenBlocks)
|
|
for i := range blocks {
|
|
numVotes := uint16(len(voteMetadata[i]))
|
|
if numVotes >= minVotesRequired {
|
|
filtered = append(filtered, &blockWithNumVotes{
|
|
Hash: blocks[i],
|
|
NumVotes: numVotes,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Return now if there are no blocks with enough votes to be eligible to
|
|
// build on top of.
|
|
if len(filtered) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Blocks with the most votes appear at the top of the list.
|
|
sort.Sort(sort.Reverse(byNumberOfVotes(filtered)))
|
|
sortedUsefulBlocks := make([]chainhash.Hash, 0, len(filtered))
|
|
for _, bwnv := range filtered {
|
|
sortedUsefulBlocks = append(sortedUsefulBlocks, bwnv.Hash)
|
|
}
|
|
|
|
// Make sure we don't reorganize the chain needlessly if the top block has
|
|
// the same amount of votes as the current leader after the sort. After this
|
|
// point, all blocks listed in sortedUsefulBlocks definitely also have the
|
|
// minimum number of votes required.
|
|
curVoteMetadata := txSource.VotesForBlocks([]chainhash.Hash{currentTopBlock})
|
|
var numTopBlockVotes uint16
|
|
if len(curVoteMetadata) > 0 {
|
|
numTopBlockVotes = uint16(len(curVoteMetadata[0]))
|
|
}
|
|
if filtered[0].NumVotes == numTopBlockVotes && filtered[0].Hash !=
|
|
currentTopBlock {
|
|
|
|
// Attempt to find the position of the current block being built
|
|
// from in the list.
|
|
pos := 0
|
|
for i, bwnv := range filtered {
|
|
if bwnv.Hash == currentTopBlock {
|
|
pos = i
|
|
break
|
|
}
|
|
}
|
|
|
|
// Swap the top block into the first position. We directly access
|
|
// sortedUsefulBlocks useful blocks here with the assumption that
|
|
// since the values were accumulated from filtered, they should be
|
|
// in the same positions and we shouldn't be able to access anything
|
|
// out of bounds.
|
|
if pos != 0 {
|
|
sortedUsefulBlocks[0], sortedUsefulBlocks[pos] =
|
|
sortedUsefulBlocks[pos], sortedUsefulBlocks[0]
|
|
}
|
|
}
|
|
|
|
return sortedUsefulBlocks
|
|
}
|
|
|
|
// BlockTemplate houses a block that has yet to be solved along with additional
|
|
// details about the fees and the number of signature operations for each
|
|
// transaction in the block.
|
|
type BlockTemplate struct {
|
|
// Block is a block that is ready to be solved by miners. Thus, it is
|
|
// completely valid with the exception of satisfying the proof-of-work
|
|
// requirement.
|
|
Block *wire.MsgBlock
|
|
|
|
// Fees contains the amount of fees each transaction in the generated
|
|
// template pays in base units. Since the first transaction is the
|
|
// coinbase, the first entry (offset 0) will contain the negative of the
|
|
// sum of the fees of all other transactions.
|
|
Fees []int64
|
|
|
|
// SigOpCounts contains the number of signature operations each
|
|
// transaction in the generated template performs.
|
|
SigOpCounts []int64
|
|
|
|
// Height is the height at which the block template connects to the main
|
|
// chain.
|
|
Height int64
|
|
|
|
// ValidPayAddress indicates whether or not the template coinbase pays
|
|
// to an address or is redeemable by anyone. See the documentation on
|
|
// NewBlockTemplate for details on which this can be useful to generate
|
|
// templates without a coinbase payment address.
|
|
ValidPayAddress bool
|
|
}
|
|
|
|
// mergeUtxoView adds all of the entries in viewB to viewA. The result is that
|
|
// viewA will contain all of its original entries plus all of the entries
|
|
// in viewB. It will replace any entries in viewB which also exist in viewA
|
|
// if the entry in viewA is spent.
|
|
func mergeUtxoView(viewA *blockchain.UtxoViewpoint, viewB *blockchain.UtxoViewpoint) {
|
|
viewAEntries := viewA.Entries()
|
|
for outpoint, entryB := range viewB.Entries() {
|
|
if entryA, exists := viewAEntries[outpoint]; !exists ||
|
|
entryA == nil || entryA.IsSpent() {
|
|
viewAEntries[outpoint] = entryB
|
|
}
|
|
}
|
|
}
|
|
|
|
// hashExistsInList checks if a hash exists in a list of hash pointers.
|
|
func hashInSlice(h chainhash.Hash, list []chainhash.Hash) bool {
|
|
for i := range list {
|
|
if h == list[i] {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// txIndexFromTxList returns a transaction's index in a list, or -1 if it
|
|
// can not be found.
|
|
func txIndexFromTxList(hash chainhash.Hash, list []*dcrutil.Tx) int {
|
|
for i, tx := range list {
|
|
h := tx.Hash()
|
|
if hash == *h {
|
|
return i
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// standardCoinbaseOpReturn creates a standard OP_RETURN output to insert into
|
|
// coinbase. This function autogenerates the extranonce. The OP_RETURN pushes
|
|
// 12 bytes.
|
|
func standardCoinbaseOpReturn(height uint32) ([]byte, error) {
|
|
extraNonce, err := wire.RandomUint64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
enData := make([]byte, 12)
|
|
binary.LittleEndian.PutUint32(enData[0:4], height)
|
|
binary.LittleEndian.PutUint64(enData[4:12], extraNonce)
|
|
extraNonceScript, err := stdscript.ProvablyPruneableScriptV0(enData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return extraNonceScript, nil
|
|
}
|
|
|
|
// standardTreasurybaseOpReturn creates a standard OP_RETURN output to insert
|
|
// into a treasurybase. This function autogenerates the extranonce. The
|
|
// OP_RETURN pushes 12 bytes.
|
|
func standardTreasurybaseOpReturn(height uint32) ([]byte, error) {
|
|
extraNonce, err := wire.RandomUint64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
enData := make([]byte, 12)
|
|
binary.LittleEndian.PutUint32(enData[0:4], height)
|
|
binary.LittleEndian.PutUint64(enData[4:12], extraNonce)
|
|
extraNonceScript, err := stdscript.ProvablyPruneableScriptV0(enData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return extraNonceScript, nil
|
|
}
|
|
|
|
// calcBlockMerkleRoot calculates and returns a merkle root depending on the
|
|
// result of the header commitments agenda vote. In particular, before the
|
|
// agenda is active, it returns the merkle root of the regular transaction tree.
|
|
// Once the agenda is active, it returns the combined merkle root for the
|
|
// regular and stake transaction trees in accordance with DCP0005.
|
|
func calcBlockMerkleRoot(regularTxns, stakeTxns []*wire.MsgTx, hdrCmtActive bool) chainhash.Hash {
|
|
if !hdrCmtActive {
|
|
return standalone.CalcTxTreeMerkleRoot(regularTxns)
|
|
}
|
|
|
|
return standalone.CalcCombinedTxTreeMerkleRoot(regularTxns, stakeTxns)
|
|
}
|
|
|
|
// calcBlockCommitmentRootV1 calculates and returns the required v1 block and
|
|
// the previous output scripts it references as inputs.
|
|
func calcBlockCommitmentRootV1(block *wire.MsgBlock, prevScripts blockcf2.PrevScripter) (chainhash.Hash, error) {
|
|
filter, err := blockcf2.Regular(block, prevScripts)
|
|
if err != nil {
|
|
return chainhash.Hash{}, err
|
|
}
|
|
return blockchain.CalcCommitmentRootV1(filter.Hash()), nil
|
|
}
|
|
|
|
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
|
|
// based on the passed block height to the provided address. When the address
|
|
// is nil, the coinbase transaction will instead be redeemable by anyone.
|
|
//
|
|
// See the comment for NewBlockTemplate for more information about why the nil
|
|
// address handling is useful.
|
|
func createCoinbaseTx(subsidyCache *standalone.SubsidyCache,
|
|
coinbaseScript []byte, opReturnPkScript []byte, nextBlockHeight int64,
|
|
addr stdaddr.Address, voters uint16, params *chaincfg.Params,
|
|
isTreasuryEnabled bool,
|
|
subsidySplitVariant standalone.SubsidySplitVariant) *dcrutil.Tx {
|
|
|
|
// Coinbase transactions have no inputs, so previous outpoint is zero hash
|
|
// and max index.
|
|
coinbaseInput := &wire.TxIn{
|
|
PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
|
|
wire.MaxPrevOutIndex, wire.TxTreeRegular),
|
|
Sequence: wire.MaxTxInSequenceNum,
|
|
BlockHeight: wire.NullBlockHeight,
|
|
BlockIndex: wire.NullBlockIndex,
|
|
SignatureScript: coinbaseScript,
|
|
}
|
|
|
|
// Block one is a special block that might pay out tokens to a ledger.
|
|
if nextBlockHeight == 1 && len(params.BlockOneLedger) != 0 {
|
|
tx := wire.NewMsgTx()
|
|
tx.Version = 1
|
|
tx.AddTxIn(coinbaseInput)
|
|
tx.TxIn[0].ValueIn = params.BlockOneSubsidy()
|
|
|
|
for _, payout := range params.BlockOneLedger {
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: payout.Amount,
|
|
Version: payout.ScriptVersion,
|
|
PkScript: payout.Script,
|
|
})
|
|
}
|
|
|
|
return dcrutil.NewTx(tx)
|
|
}
|
|
|
|
// Prior to the decentralized treasury agenda, the transaction version must
|
|
// be 1 and there is an additional output that either pays to organization
|
|
// associated with the treasury or a provably pruneable zero-value output
|
|
// script when it is disabled.
|
|
//
|
|
// Once the decentralized treasury agenda is active, the transaction version
|
|
// must be the new expected version and there is no treasury output since it
|
|
// is included in the stake tree instead.
|
|
var txVersion = uint16(1)
|
|
var treasuryOutput *wire.TxOut
|
|
var treasurySubsidy int64
|
|
if !isTreasuryEnabled {
|
|
if params.BlockTaxProportion > 0 {
|
|
// Create the treasury output with the correct subsidy and public
|
|
// key script for the organization associated with the treasury.
|
|
treasurySubsidy = subsidyCache.CalcTreasurySubsidy(nextBlockHeight,
|
|
voters, isTreasuryEnabled)
|
|
treasuryOutput = &wire.TxOut{
|
|
Value: treasurySubsidy,
|
|
PkScript: params.OrganizationPkScript,
|
|
}
|
|
} else {
|
|
// Treasury disabled.
|
|
treasuryOutput = &wire.TxOut{
|
|
Value: 0,
|
|
PkScript: opTrueScript,
|
|
}
|
|
}
|
|
} else {
|
|
// Set the transaction version to the new version required by the
|
|
// decentralized treasury agenda.
|
|
txVersion = wire.TxVersionTreasury
|
|
}
|
|
|
|
// Create the script to pay to the provided payment address if one was
|
|
// specified. Otherwise create a script that allows the coinbase to be
|
|
// redeemable by anyone.
|
|
workSubsidyScriptVer := uint16(0)
|
|
workSubsidyScript := opTrueScript
|
|
if addr != nil {
|
|
workSubsidyScriptVer, workSubsidyScript = addr.PaymentScript()
|
|
}
|
|
|
|
// Create a coinbase with expected inputs and outputs.
|
|
//
|
|
// Inputs:
|
|
// - A single input with input value set to the total payout amount.
|
|
//
|
|
// Outputs:
|
|
// - Potential treasury output prior to the decentralized treasury agenda
|
|
// - Output that includes the block height and potential extra nonce used
|
|
// to ensure a unique hash
|
|
// - Output that pays the work subsidy to the miner
|
|
workSubsidy := subsidyCache.CalcWorkSubsidyV3(nextBlockHeight, voters,
|
|
subsidySplitVariant)
|
|
tx := wire.NewMsgTx()
|
|
tx.Version = txVersion
|
|
tx.AddTxIn(coinbaseInput)
|
|
tx.TxIn[0].ValueIn = workSubsidy + treasurySubsidy
|
|
if treasuryOutput != nil {
|
|
tx.AddTxOut(treasuryOutput)
|
|
}
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: 0,
|
|
PkScript: opReturnPkScript,
|
|
})
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: workSubsidy,
|
|
Version: workSubsidyScriptVer,
|
|
PkScript: workSubsidyScript,
|
|
})
|
|
return dcrutil.NewTx(tx)
|
|
}
|
|
|
|
// createTreasuryBaseTx returns a treasurybase transaction paying an appropriate
|
|
// subsidy based on the passed block height to the treasury.
|
|
func createTreasuryBaseTx(subsidyCache *standalone.SubsidyCache, nextBlockHeight int64, voters uint16) (*dcrutil.Tx, error) {
|
|
// Create provably pruneable script for the output that encodes the block
|
|
// height used to ensure a unique overall transaction hash. This is
|
|
// necessary because neither the input nor the output that adds to the
|
|
// treasury account balance are unique for a treasurybase.
|
|
opReturnTreasury, err := standardTreasurybaseOpReturn(uint32(nextBlockHeight))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create a treasurybase with expected inputs and outputs.
|
|
//
|
|
// Inputs:
|
|
// - A single input with input value set to the total payout amount.
|
|
//
|
|
// Outputs:
|
|
// - Treasury output that adds to the treasury account balance
|
|
// - Output that includes the block height to ensure a unique hash
|
|
//
|
|
// Note that all treasurybase transactions require TxVersionTreasury and
|
|
// they must be in the stake transaction tree.
|
|
const withTreasury = true
|
|
trsySubsidy := subsidyCache.CalcTreasurySubsidy(nextBlockHeight, voters,
|
|
withTreasury)
|
|
tx := wire.NewMsgTx()
|
|
tx.Version = wire.TxVersionTreasury
|
|
tx.AddTxIn(&wire.TxIn{
|
|
// Treasurybase transactions have no inputs, so previous outpoint
|
|
// is zero hash and max index.
|
|
PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
|
|
wire.MaxPrevOutIndex, wire.TxTreeRegular),
|
|
Sequence: wire.MaxTxInSequenceNum,
|
|
BlockHeight: wire.NullBlockHeight,
|
|
BlockIndex: wire.NullBlockIndex,
|
|
SignatureScript: nil, // Must be nil by consensus.
|
|
})
|
|
tx.TxIn[0].ValueIn = trsySubsidy
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: trsySubsidy,
|
|
Version: 0,
|
|
PkScript: []byte{txscript.OP_TADD},
|
|
})
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: 0,
|
|
PkScript: opReturnTreasury,
|
|
})
|
|
retTx := dcrutil.NewTx(tx)
|
|
retTx.SetTree(wire.TxTreeStake)
|
|
return retTx, nil
|
|
}
|
|
|
|
// spendTransaction updates the passed view by marking the inputs to the passed
|
|
// transaction as spent. It also adds all outputs in the passed transaction
|
|
// which are not provably unspendable as available unspent transaction outputs.
|
|
func spendTransaction(utxoView *blockchain.UtxoViewpoint, tx *dcrutil.Tx,
|
|
height int64, isTreasuryEnabled bool) {
|
|
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
entry := utxoView.LookupEntry(txIn.PreviousOutPoint)
|
|
if entry != nil {
|
|
entry.Spend()
|
|
}
|
|
}
|
|
|
|
utxoView.AddTxOuts(tx, height, wire.NullBlockIndex, isTreasuryEnabled)
|
|
}
|
|
|
|
// logSkippedDeps logs any dependencies which are also skipped as a result of
|
|
// skipping a transaction while generating a block template at the trace level.
|
|
func logSkippedDeps(tx *dcrutil.Tx, deps []*TxDesc) {
|
|
if deps == nil {
|
|
return
|
|
}
|
|
|
|
for _, item := range deps {
|
|
log.Tracef("Skipping tx %s since it depends on %s\n",
|
|
item.Tx.Hash(), tx.Hash())
|
|
}
|
|
}
|
|
|
|
// minimumMedianTime returns the minimum allowed timestamp for a block building
|
|
// on the end of the current best chain. In particular, it is one second after
|
|
// the median timestamp of the last several blocks per the chain consensus
|
|
// rules.
|
|
func minimumMedianTime(best *blockchain.BestState) time.Time {
|
|
return best.MedianTime.Add(time.Second)
|
|
}
|
|
|
|
// medianAdjustedTime returns the current time adjusted to ensure it is at least
|
|
// one second after the median timestamp of the last several blocks per the
|
|
// chain consensus rules.
|
|
func (g *BlkTmplGenerator) medianAdjustedTime() time.Time {
|
|
// The timestamp for the block must not be before the median timestamp
|
|
// of the last several blocks. Thus, choose the maximum between the
|
|
// current time and one second after the past median time. The current
|
|
// timestamp is truncated to a second boundary before comparison since a
|
|
// block timestamp does not support a precision greater than one second.
|
|
best := g.cfg.BestSnapshot()
|
|
newTimestamp := g.cfg.TimeSource.AdjustedTime()
|
|
minTimestamp := minimumMedianTime(best)
|
|
if newTimestamp.Before(minTimestamp) {
|
|
newTimestamp = minTimestamp
|
|
}
|
|
|
|
// Adjust by the amount requested from the command line argument.
|
|
newTimestamp = newTimestamp.Add(
|
|
time.Duration(-g.cfg.MiningTimeOffset) * time.Second)
|
|
|
|
return newTimestamp
|
|
}
|
|
|
|
// maybeInsertStakeTx checks to make sure that a stake tx is
|
|
// valid from the perspective of the mainchain (not necessarily
|
|
// the mempool or block) before inserting into a tx tree.
|
|
// If it fails the check, it returns false; otherwise true.
|
|
func (g *BlkTmplGenerator) maybeInsertStakeTx(stx *dcrutil.Tx, treeValid bool, isTreasuryEnabled bool) bool {
|
|
missingInput := false
|
|
|
|
view, err := g.cfg.FetchUtxoView(stx, treeValid)
|
|
if err != nil {
|
|
log.Warnf("Unable to fetch transaction store for "+
|
|
"stx %s: %v", stx.Hash(), err)
|
|
return false
|
|
}
|
|
mstx := stx.MsgTx()
|
|
isSSGen := stake.IsSSGen(mstx)
|
|
var isTSpend, isTreasuryBase bool
|
|
if isTreasuryEnabled {
|
|
isTSpend = stake.IsTSpend(mstx)
|
|
isTreasuryBase = stake.IsTreasuryBase(mstx)
|
|
}
|
|
for i, txIn := range mstx.TxIn {
|
|
// Evaluate if this is a stakebase or treasury base input or
|
|
// not. If it is, continue without evaluation of the input.
|
|
if (i == 0 && (isSSGen || isTreasuryBase)) || isTSpend {
|
|
txIn.BlockHeight = wire.NullBlockHeight
|
|
txIn.BlockIndex = wire.NullBlockIndex
|
|
continue
|
|
}
|
|
|
|
entry := view.LookupEntry(txIn.PreviousOutPoint)
|
|
if entry == nil {
|
|
missingInput = true
|
|
break
|
|
} else {
|
|
txIn.ValueIn = entry.Amount()
|
|
txIn.BlockHeight = uint32(entry.BlockHeight())
|
|
txIn.BlockIndex = entry.BlockIndex()
|
|
}
|
|
}
|
|
return !missingInput
|
|
}
|
|
|
|
// handleTooFewVoters handles the situation in which there are too few voters on
|
|
// of the blockchain. If there are too few voters and a cached parent template to
|
|
// work off of is present, it will return a copy of that template to pass to the
|
|
// miner.
|
|
// Safe for concurrent access.
|
|
func (g *BlkTmplGenerator) handleTooFewVoters(nextHeight int64,
|
|
miningAddress stdaddr.Address, isTreasuryEnabled bool,
|
|
subsidySplitVariant standalone.SubsidySplitVariant) (*BlockTemplate, error) {
|
|
|
|
stakeValidationHeight := g.cfg.ChainParams.StakeValidationHeight
|
|
|
|
// Handle not enough voters being present if we're set to mine aggressively
|
|
// (default behavior).
|
|
best := g.cfg.BestSnapshot()
|
|
if nextHeight >= stakeValidationHeight && g.cfg.Policy.AggressiveMining {
|
|
// Fetch the latest block and head and begin working off of it with an
|
|
// empty transaction tree regular and the contents of that stake tree.
|
|
// In the future we should have the option of reading some transactions
|
|
// from this block, too.
|
|
topBlock, err := g.cfg.BlockByHash(&best.Hash)
|
|
if err != nil {
|
|
str := fmt.Sprintf("unable to get tip block %s", best.PrevHash)
|
|
return nil, makeError(ErrGetTopBlock, str)
|
|
}
|
|
tipHeader := &topBlock.MsgBlock().Header
|
|
|
|
// Start with a copy of the tip block header.
|
|
var block wire.MsgBlock
|
|
block.Header = *tipHeader
|
|
|
|
// Create and populate a new coinbase.
|
|
coinbaseScript := make([]byte, len(coinbaseFlags)+2)
|
|
copy(coinbaseScript[2:], coinbaseFlags)
|
|
opReturnPkScript, err := standardCoinbaseOpReturn(tipHeader.Height)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
coinbaseTx := createCoinbaseTx(g.cfg.SubsidyCache, coinbaseScript,
|
|
opReturnPkScript, topBlock.Height(), miningAddress,
|
|
tipHeader.Voters, g.cfg.ChainParams, isTreasuryEnabled,
|
|
subsidySplitVariant)
|
|
block.AddTransaction(coinbaseTx.MsgTx())
|
|
|
|
if isTreasuryEnabled {
|
|
treasuryBase, err := createTreasuryBaseTx(g.cfg.SubsidyCache,
|
|
topBlock.Height(), tipHeader.Voters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block.AddSTransaction(treasuryBase.MsgTx())
|
|
}
|
|
|
|
// Copy all of the regular transactions over.
|
|
for i, tx := range topBlock.Transactions() {
|
|
if i == 0 {
|
|
// Skip copying coinbase.
|
|
continue
|
|
}
|
|
block.AddTransaction(tx.MsgTx())
|
|
}
|
|
|
|
// Copy all of the stake transactions over.
|
|
for i, stx := range topBlock.STransactions() {
|
|
if i == 0 && isTreasuryEnabled {
|
|
// Skip copying treasurybase.
|
|
continue
|
|
}
|
|
block.AddSTransaction(stx.MsgTx())
|
|
}
|
|
|
|
// Set a fresh timestamp.
|
|
ts := g.medianAdjustedTime()
|
|
block.Header.Timestamp = ts
|
|
|
|
// Recalculate the size.
|
|
block.Header.Size = uint32(block.SerializeSize())
|
|
|
|
bt := &BlockTemplate{
|
|
Block: &block,
|
|
Fees: []int64{0},
|
|
SigOpCounts: []int64{0},
|
|
Height: int64(tipHeader.Height),
|
|
ValidPayAddress: miningAddress != nil,
|
|
}
|
|
|
|
// Calculate the merkle root depending on the result of the header
|
|
// commitments agenda vote.
|
|
prevHash := &tipHeader.PrevBlock
|
|
hdrCmtActive, err := g.cfg.IsHeaderCommitmentsAgendaActive(prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
header := &block.Header
|
|
header.MerkleRoot = calcBlockMerkleRoot(block.Transactions, block.STransactions, hdrCmtActive)
|
|
|
|
// Calculate the stake root or commitment root depending on the result
|
|
// of the header commitments agenda vote.
|
|
var cmtRoot chainhash.Hash
|
|
if hdrCmtActive {
|
|
// Load all of the previous output scripts the block references as
|
|
// inputs since they are needed to create the filter commitment.
|
|
blockUtxos, err := g.cfg.FetchUtxoViewParentTemplate(&block)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to fetch inputs when making new "+
|
|
"block template: %v", err)
|
|
return nil, makeError(ErrFetchTxStore, str)
|
|
}
|
|
|
|
cmtRoot, err = calcBlockCommitmentRootV1(&block, blockUtxos)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to calculate commitment root for "+
|
|
"block when making new block template: %v", err)
|
|
return nil, makeError(ErrCalcCommitmentRoot, str)
|
|
}
|
|
} else {
|
|
cmtRoot = standalone.CalcTxTreeMerkleRoot(block.STransactions)
|
|
}
|
|
header.StakeRoot = cmtRoot
|
|
|
|
// Make sure the block validates.
|
|
btBlock := dcrutil.NewBlockDeepCopyCoinbase(&block)
|
|
err = g.cfg.CheckConnectBlockTemplate(btBlock)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to check template: %v while "+
|
|
"constructing a new parent", err.Error())
|
|
return nil, makeError(ErrCheckConnectBlock, str)
|
|
}
|
|
|
|
return bt, nil
|
|
}
|
|
|
|
log.Debugf("Not enough voters on top block to generate " +
|
|
"new block template")
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// createRevocationFromTicket creates a revocation transaction from the provided
|
|
// ticket hash. It also adds any utxos used to create the transaction to the
|
|
// provided block utxos viewpoint so that they are available for lookup.
|
|
//
|
|
// Note: This should only be used if the automatic ticket revocations agenda is
|
|
// active.
|
|
func (g *BlkTmplGenerator) createRevocationFromTicket(ticketHash *chainhash.Hash,
|
|
blockUtxos *blockchain.UtxoViewpoint, prevHeaderBytes []byte,
|
|
isTreasuryEnabled bool) (*TxDesc, error) {
|
|
|
|
// Fetch the utxo for the ticket submission to be revoked.
|
|
const ticketSubmissionOutput = 0
|
|
ticketSubmission := wire.OutPoint{
|
|
Hash: *ticketHash,
|
|
Index: ticketSubmissionOutput,
|
|
Tree: wire.TxTreeStake,
|
|
}
|
|
ticketUtxo, err := g.cfg.FetchUtxoEntry(ticketSubmission)
|
|
if err != nil {
|
|
return nil, makeError(ErrGetTicketInfo, err.Error())
|
|
}
|
|
if ticketUtxo == nil || ticketUtxo.IsSpent() {
|
|
str := fmt.Sprintf("ticket %v does not exist or is spent", ticketHash)
|
|
return nil, makeError(ErrGetTicketInfo, str)
|
|
}
|
|
|
|
// Add the ticket submission utxo to the block utxos view so that it is
|
|
// available for lookup later.
|
|
blockUtxos.Entries()[ticketSubmission] = ticketUtxo
|
|
|
|
// Get the minimal outputs for the ticket.
|
|
ticketMinOuts := ticketUtxo.TicketMinimalOutputs()
|
|
if ticketMinOuts == nil {
|
|
str := fmt.Sprintf("ticket %v missing minimal outputs", ticketHash)
|
|
return nil, makeError(ErrGetTicketInfo, str)
|
|
}
|
|
|
|
// Create a revocation transaction for the ticket.
|
|
const isAutoRevocationsEnabled = true
|
|
const revocationTxFee = dcrutil.Amount(0)
|
|
revocationMsgTx, err := stake.CreateRevocationFromTicket(ticketHash,
|
|
ticketMinOuts, revocationTxFee, stake.TxVersionAutoRevocations,
|
|
g.cfg.ChainParams, prevHeaderBytes, isAutoRevocationsEnabled)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
revocationTx := dcrutil.NewTx(revocationMsgTx)
|
|
revocationTx.SetTree(wire.TxTreeStake)
|
|
txDesc := &TxDesc{
|
|
Tx: revocationTx,
|
|
Type: stake.TxTypeSSRtx,
|
|
TotalSigOps: g.cfg.CountSigOps(revocationTx, false, false,
|
|
isTreasuryEnabled),
|
|
TxSize: int64(revocationMsgTx.SerializeSize()),
|
|
}
|
|
|
|
return txDesc, nil
|
|
}
|
|
|
|
// addAutoRevocationsToQueue is a helper function that creates revocations for
|
|
// all tickets that will become missed or expired as of the block being created.
|
|
// It inserts the created revocations into the provided priority queue and maps.
|
|
//
|
|
// Note: This should only be used if the automatic ticket revocations agenda is
|
|
// active.
|
|
func (g *BlkTmplGenerator) addAutoRevocationsToQueue(winningTickets map[chainhash.Hash]bool,
|
|
blockUtxos *blockchain.UtxoViewpoint, prevHeaderBytes []byte, numSSGen int,
|
|
isTreasuryEnabled bool, priorityQueue *txPriorityQueue,
|
|
prioritizedTxns map[chainhash.Hash]struct{},
|
|
prioItemMap map[chainhash.Hash]*txPrioItem) error {
|
|
|
|
// Return now if there are no tickets to revoke.
|
|
best := g.cfg.BestSnapshot()
|
|
missedCount := len(best.NextWinningTickets) - numSSGen
|
|
expiredCount := len(best.NextExpiringTickets)
|
|
if missedCount+expiredCount == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Create a slice of the ticket hashes that must be revoked due to
|
|
// becoming missed or expired this block.
|
|
revokeTickets := make([]chainhash.Hash, 0, missedCount+expiredCount)
|
|
|
|
// Add tickets that will be missed to the slice of ticket hashes to
|
|
// revoke.
|
|
for ticketHash, hasVote := range winningTickets {
|
|
// If a winning ticket does not have a vote in this block, it will be
|
|
// missed.
|
|
if !hasVote {
|
|
revokeTickets = append(revokeTickets, ticketHash)
|
|
}
|
|
}
|
|
|
|
// Add tickets that will be expired to the slice of ticket hashes to
|
|
// revoke.
|
|
revokeTickets = append(revokeTickets, best.NextExpiringTickets...)
|
|
|
|
// Create revocation transactions for all tickets that will become missed
|
|
// or expired as of this block.
|
|
for i := range revokeTickets {
|
|
// Create the revocation transaction.
|
|
ticketHash := &revokeTickets[i]
|
|
txDesc, err := g.createRevocationFromTicket(ticketHash, blockUtxos,
|
|
prevHeaderBytes, isTreasuryEnabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Add the revocation transaction to the priority queue.
|
|
prioItem := &txPrioItem{
|
|
txDesc: txDesc,
|
|
txType: txDesc.Type,
|
|
autoRevocation: true,
|
|
}
|
|
revocationTxHash := txDesc.Tx.Hash()
|
|
prioritizedTxns[*revocationTxHash] = struct{}{}
|
|
heap.Push(priorityQueue, prioItem)
|
|
prioItemMap[*revocationTxHash] = prioItem
|
|
}
|
|
|
|
log.Debugf("Generated %d automatic revocations for the new block (num "+
|
|
"missed this block: %d, num expired this block: %d)", len(revokeTickets),
|
|
missedCount, expiredCount)
|
|
|
|
return nil
|
|
}
|
|
|
|
// BlkTmplGenerator generates block templates based on a given mining policy
|
|
// and a transactions source. It also houses additional state required in
|
|
// order to ensure the templates adhere to the consensus rules and are built
|
|
// on top of the best chain tip or its parent if the best chain tip is
|
|
// unable to get enough votes.
|
|
//
|
|
// See the NewBlockTemplate method for a detailed description of how the block
|
|
// template is generated.
|
|
type BlkTmplGenerator struct {
|
|
cfg *Config
|
|
}
|
|
|
|
// NewBlkTmplGenerator returns a new block template generator for the given
|
|
// policy using transactions from the provided transaction source.
|
|
func NewBlkTmplGenerator(cfg *Config) *BlkTmplGenerator {
|
|
return &BlkTmplGenerator{cfg: cfg}
|
|
}
|
|
|
|
// calcFeePerKb returns an adjusted fee per kilobyte taking the provided
|
|
// transaction and its ancestors into account.
|
|
func calcFeePerKb(txDesc *TxDesc, ancestorStats *TxAncestorStats) float64 {
|
|
txSize := txDesc.Tx.MsgTx().SerializeSize()
|
|
if ancestorStats.Fees < 0 || ancestorStats.SizeBytes < 0 {
|
|
return (float64(txDesc.Fee) * float64(kilobyte)) / float64(txSize)
|
|
}
|
|
return (float64(txDesc.Fee+ancestorStats.Fees) * float64(kilobyte)) /
|
|
float64(int64(txSize)+ancestorStats.SizeBytes)
|
|
}
|
|
|
|
// NewBlockTemplate returns a new block template that is ready to be solved
|
|
// using the transactions from the passed transaction source pool and a coinbase
|
|
// that either pays to the passed address if it is not nil, or a coinbase that
|
|
// is redeemable by anyone if the passed address is nil. The nil address
|
|
// functionality is useful since there are cases such as the getblocktemplate
|
|
// RPC where external mining software is responsible for creating their own
|
|
// coinbase which will replace the one generated for the block template. Thus
|
|
// the need to have configured address can be avoided.
|
|
//
|
|
// The transactions selected and included are prioritized according to whether
|
|
// or not they are stake transactions and their fee per kilobyte. Transactions
|
|
// with a higher fee per kilobyte are preferred. Finally, the block generation
|
|
// related policy settings are all taken into account.
|
|
//
|
|
// Transactions which only spend outputs from other transactions already in the
|
|
// block chain are immediately added to a priority queue which prioritizes based
|
|
// the fee per kilobyte. Transactions which spend outputs from other
|
|
// transactions in the source pool are added to a dependency map so they can be
|
|
// added to the priority queue once the transactions they depend on have been
|
|
// included.
|
|
//
|
|
// When the fees per kilobyte drop below the TxMinFreeFee policy setting, the
|
|
// transaction will be skipped.
|
|
//
|
|
// Any transactions which would cause the block to exceed the BlockMaxSize
|
|
// policy setting, exceed the maximum allowed signature operations per block, or
|
|
// otherwise cause the block to be invalid are skipped.
|
|
//
|
|
// Given the above, a block generated by this function is of the following form:
|
|
//
|
|
// ----------------------------------- --
|
|
// | Coinbase Transaction | |
|
|
// |-----------------------------------| |
|
|
// | | |
|
|
// | | |--- (policy.BlockMaxSize) / 2
|
|
// | Transactions prioritized by fee | |
|
|
// | until <= policy.TxMinFreeFee | |
|
|
// | | |
|
|
// | | |
|
|
// ----------------------------------- --
|
|
//
|
|
// Which also includes a stake tree that looks like the following:
|
|
//
|
|
// ----------------------------------- -- --
|
|
// | | | |
|
|
// | Votes | | | --- >= (chaincfg.TicketsPerBlock/2) + 1
|
|
// | | | |
|
|
// |-----------------------------------| | --
|
|
// | | | |
|
|
// | Tickets | | | --- <= chaincfg.MaxFreshStakePerBlock
|
|
// | | | |
|
|
// |-----------------------------------| | --
|
|
// | | |
|
|
// | Revocations | |
|
|
// | | |
|
|
// ----------------------------------- --
|
|
//
|
|
// This function returns nil when there are not enough voters on any of the
|
|
// current top blocks to create a new block template.
|
|
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress stdaddr.Address) (*BlockTemplate, error) {
|
|
// All transaction scripts are verified using the more strict standard
|
|
// flags.
|
|
scriptFlags, err := g.cfg.Policy.StandardVerifyFlags()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Extend the most recently known best block.
|
|
// The most recently known best block is the top block that has the most
|
|
// ssgen votes for it. We only need this after the height in which stake voting
|
|
// has kicked in.
|
|
// To figure out which block has the most ssgen votes, we need to run the
|
|
// following algorithm:
|
|
// 1. Acquire the HEAD block and all of its orphans. Record their block header
|
|
// hashes.
|
|
// 2. Create a map of [blockHeaderHash] --> [mempoolTxnList].
|
|
// 3. for blockHeaderHash in candidateBlocks:
|
|
// if mempoolTx.StakeDesc == SSGen &&
|
|
// mempoolTx.SSGenParseBlockHeader() == blockHeaderHash:
|
|
// map[blockHeaderHash].append(mempoolTx)
|
|
// 4. Check len of each map entry and store.
|
|
// 5. Query the ticketdb and check how many eligible ticket holders there are
|
|
// for the given block you are voting on.
|
|
// 6. Divide #ofvotes (len(map entry)) / totalPossibleVotes --> penalty ratio
|
|
// 7. Store penalty ratios for all block candidates.
|
|
// 8. Select the one with the largest penalty ratio (highest block reward).
|
|
// This block is then selected to build upon instead of the others, because
|
|
// it yields the greater amount of rewards.
|
|
|
|
best := g.cfg.BestSnapshot()
|
|
prevHash := best.Hash
|
|
nextBlockHeight := best.Height + 1
|
|
stakeValidationHeight := g.cfg.ChainParams.StakeValidationHeight
|
|
|
|
isTreasuryEnabled, err := g.cfg.IsTreasuryAgendaActive(&prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
isAutoRevocationsEnabled, err := g.cfg.IsAutoRevocationsAgendaActive(&prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
isSubsidyEnabled, err := g.cfg.IsSubsidySplitAgendaActive(&prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
isSubsidyR2Enabled, err := g.cfg.IsSubsidySplitR2AgendaActive(&prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Determine which subsidy split variant to use depending on the active
|
|
// agendas.
|
|
subsidySplitVariant := standalone.SSVOriginal
|
|
switch {
|
|
case isSubsidyR2Enabled:
|
|
subsidySplitVariant = standalone.SSVDCP0012
|
|
case isSubsidyEnabled:
|
|
subsidySplitVariant = standalone.SSVDCP0010
|
|
}
|
|
|
|
var (
|
|
isTVI bool
|
|
maxTreasurySpend int64
|
|
)
|
|
if isTreasuryEnabled {
|
|
isTVI = standalone.IsTreasuryVoteInterval(uint64(nextBlockHeight),
|
|
g.cfg.ChainParams.TreasuryVoteInterval)
|
|
}
|
|
|
|
if nextBlockHeight >= stakeValidationHeight {
|
|
// Obtain the entire generation of blocks stemming from this parent.
|
|
children := g.cfg.TipGeneration()
|
|
|
|
// Get the list of blocks that we can actually build on top of. If we're
|
|
// not currently on the block that has the most votes, switch to that
|
|
// block.
|
|
eligibleParents := SortParentsByVotes(g.cfg.TxSource, prevHash, children,
|
|
g.cfg.ChainParams)
|
|
if len(eligibleParents) == 0 {
|
|
log.Debugf("Too few voters found on any HEAD block, " +
|
|
"recycling a parent block to mine on")
|
|
return g.handleTooFewVoters(nextBlockHeight, payToAddress,
|
|
isTreasuryEnabled, subsidySplitVariant)
|
|
}
|
|
|
|
log.Debugf("Found eligible parent %v with enough votes to build "+
|
|
"block on, proceeding to create a new block template",
|
|
eligibleParents[0])
|
|
|
|
// Force a reorganization to the parent with the most votes if needed.
|
|
for i := range eligibleParents {
|
|
newHead := &eligibleParents[i]
|
|
if *newHead == prevHash {
|
|
break
|
|
}
|
|
|
|
err := g.cfg.ForceHeadReorganization(prevHash, *newHead)
|
|
if err != nil {
|
|
log.Debugf("failed to reorganize to new parent: %v", err)
|
|
continue
|
|
}
|
|
|
|
// Ensure the needed votes are actually in the mempool.
|
|
voteHashes := g.cfg.TxSource.VoteHashesForBlock(newHead)
|
|
if len(voteHashes) == 0 {
|
|
return nil, fmt.Errorf("no vote metadata for block %v",
|
|
newHead)
|
|
}
|
|
haveAllVotes := g.cfg.TxSource.HaveAllTransactions(voteHashes)
|
|
if !haveAllVotes {
|
|
continue
|
|
}
|
|
|
|
prevHash = *newHead
|
|
break
|
|
}
|
|
|
|
// Obtain the maximum allowed treasury expenditure.
|
|
if isTreasuryEnabled && isTVI {
|
|
maxTreasurySpend, err = g.cfg.MaxTreasuryExpenditure(&prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the current source transactions and create a priority queue to
|
|
// hold the transactions which are ready for inclusion into a block
|
|
// along with some priority related and fee metadata. Reserve the same
|
|
// number of items that are available for the priority queue. Also,
|
|
// choose the initial sort order for the priority queue based on whether
|
|
// or not there is an area allocated for high-priority transactions.
|
|
miningView := g.cfg.TxSource.MiningView()
|
|
sourceTxns := miningView.TxDescs()
|
|
priorityQueue := newTxPriorityQueue(len(sourceTxns), txPQByStakeAndFee)
|
|
prioritizedTxns := make(map[chainhash.Hash]struct{}, len(sourceTxns))
|
|
|
|
// Create a slice to hold the transactions to be included in the
|
|
// generated block with reserved space. Also create a utxo view to
|
|
// house all of the input transactions so multiple lookups can be
|
|
// avoided.
|
|
blockTxns := make([]*dcrutil.Tx, 0, len(sourceTxns))
|
|
blockUtxos := g.cfg.NewUtxoViewpoint()
|
|
|
|
// Create slices to hold the fees and number of signature operations
|
|
// for each of the selected transactions and add an entry for the
|
|
// coinbase. This allows the code below to simply append details about
|
|
// a transaction as it is selected for inclusion in the final block.
|
|
// However, since the total fees aren't known yet, use a dummy value for
|
|
// the coinbase fee which will be updated later.
|
|
txFees := make([]int64, 0, len(sourceTxns))
|
|
txFeesMap := make(map[chainhash.Hash]int64)
|
|
txSigOpCounts := make([]int64, 0, len(sourceTxns))
|
|
txSigOpCountsMap := make(map[chainhash.Hash]int64)
|
|
txFees = append(txFees, -1) // Updated once known
|
|
|
|
log.Debugf("Considering %d transactions for inclusion to new block",
|
|
len(sourceTxns))
|
|
knownDisapproved := g.cfg.TxSource.IsRegTxTreeKnownDisapproved(&prevHash)
|
|
|
|
// Tracks the total number of transactions that depend on another
|
|
// from the tx source.
|
|
totalDescendantTxns := 0
|
|
prioItemMap := make(map[chainhash.Hash]*txPrioItem, len(sourceTxns))
|
|
|
|
mempoolLoop:
|
|
for _, txDesc := range sourceTxns {
|
|
// A block can't have more than one coinbase or contain
|
|
// non-finalized transactions.
|
|
tx := txDesc.Tx
|
|
msgTx := tx.MsgTx()
|
|
if standalone.IsCoinBaseTx(msgTx, isTreasuryEnabled) {
|
|
log.Tracef("Skipping coinbase tx %s", tx.Hash())
|
|
continue
|
|
}
|
|
if !g.cfg.IsFinalizedTransaction(tx, nextBlockHeight, best.MedianTime) {
|
|
log.Tracef("Skipping non-finalized tx %s", tx.Hash())
|
|
continue
|
|
}
|
|
|
|
// Need this for a check below for stake base input, and to check
|
|
// the ticket number.
|
|
isSSGen := txDesc.Type == stake.TxTypeSSGen
|
|
if isSSGen {
|
|
blockHash, blockHeight := stake.SSGenBlockVotedOn(msgTx)
|
|
if !((blockHash == prevHash) &&
|
|
(int64(blockHeight) == nextBlockHeight-1)) {
|
|
log.Tracef("Skipping ssgen tx %s because it does "+
|
|
"not vote on the correct block", tx.Hash())
|
|
continue
|
|
}
|
|
}
|
|
|
|
var isTSpend bool
|
|
if isTreasuryEnabled {
|
|
isTSpend = txDesc.Type == stake.TxTypeTSpend
|
|
}
|
|
|
|
// Fetch all of the utxos referenced by the this transaction.
|
|
utxos, err := g.cfg.FetchUtxoView(tx, !knownDisapproved)
|
|
if err != nil {
|
|
log.Warnf("Unable to fetch utxo view for tx %s: "+
|
|
"%v", tx.Hash(), err)
|
|
continue
|
|
}
|
|
|
|
// Setup dependencies for any transactions which reference
|
|
// other transactions in the mempool so they can be properly
|
|
// ordered below.
|
|
prioItem := &txPrioItem{txDesc: txDesc, txType: txDesc.Type}
|
|
for i, txIn := range tx.MsgTx().TxIn {
|
|
// Evaluate if this is a stakebase input or not. If it is, continue
|
|
// without evaluation of the input.
|
|
// if isStakeBase
|
|
if (i == 0 && isSSGen) || isTSpend {
|
|
continue
|
|
}
|
|
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
entry := utxos.LookupEntry(txIn.PreviousOutPoint)
|
|
if entry == nil || entry.IsSpent() {
|
|
if !g.cfg.TxSource.HaveTransaction(originHash) {
|
|
log.Tracef("Skipping tx %s because "+
|
|
"it references unspent output "+
|
|
"%s which is not available",
|
|
tx.Hash(), txIn.PreviousOutPoint)
|
|
continue mempoolLoop
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate the final transaction priority using the input
|
|
// value age sum as well as the adjusted transaction size. The
|
|
// formula is: sum(inputValue * inputAge) / adjustedTxSize
|
|
prioItem.priority = CalcPriority(tx.MsgTx(), utxos,
|
|
nextBlockHeight)
|
|
|
|
// Calculate the fee in Atoms/KB.
|
|
// NOTE: This is a more precise value than the one calculated
|
|
// during calcMinRelayFee which rounds up to the nearest full
|
|
// kilobyte boundary. This is beneficial since it provides an
|
|
// incentive to create smaller transactions.
|
|
ancestorStats, hasStats := miningView.AncestorStats(tx.Hash())
|
|
prioItem.feePerKB = calcFeePerKb(txDesc, ancestorStats)
|
|
prioItem.fee = txDesc.Fee + ancestorStats.Fees
|
|
prioItemMap[*tx.Hash()] = prioItem
|
|
hasParents := miningView.hasParents(tx.Hash())
|
|
|
|
if !hasParents || hasStats {
|
|
heap.Push(priorityQueue, prioItem)
|
|
prioritizedTxns[*tx.Hash()] = struct{}{}
|
|
blockUtxos.AddTxOuts(tx, nextBlockHeight, wire.NullBlockIndex,
|
|
isTreasuryEnabled)
|
|
}
|
|
|
|
if hasParents {
|
|
totalDescendantTxns++
|
|
}
|
|
|
|
// Merge the referenced outputs from the input transactions to
|
|
// this transaction into the block utxo view. This allows the
|
|
// code below to avoid a second lookup.
|
|
mergeUtxoView(blockUtxos, utxos)
|
|
}
|
|
|
|
log.Tracef("Priority queue len %d, dependers len %d",
|
|
priorityQueue.Len(), totalDescendantTxns)
|
|
|
|
// The starting block size is the size of the block header plus the max
|
|
// possible transaction count size, plus the size of the coinbase
|
|
// transaction.
|
|
blockSize := uint32(blockHeaderOverhead)
|
|
|
|
// Guesstimate for sigops based on valid txs in loop below. This number
|
|
// tends to overestimate sigops because of the way the loop below is
|
|
// coded and the fact that tx can sometimes be removed from the tx
|
|
// trees if they fail one of the stake checks below the priorityQueue
|
|
// pop loop. This is buggy, but not catastrophic behaviour. A future
|
|
// release should fix it. TODO
|
|
blockSigOps := int64(0)
|
|
totalFees := int64(0)
|
|
|
|
numSStx := 0
|
|
numSSGen := 0
|
|
numTAdds := 0
|
|
|
|
foundWinningTickets := make(map[chainhash.Hash]bool, len(best.NextWinningTickets))
|
|
for _, ticketHash := range best.NextWinningTickets {
|
|
foundWinningTickets[ticketHash] = false
|
|
}
|
|
|
|
// Create a map of the ticket hashes that will become expired as of the block.
|
|
numExpiring := len(best.NextExpiringTickets)
|
|
expiringTicketHashes := make(map[chainhash.Hash]struct{}, numExpiring)
|
|
for _, ticketHash := range best.NextExpiringTickets {
|
|
expiringTicketHashes[ticketHash] = struct{}{}
|
|
}
|
|
|
|
// Maintain lookup of transactions that have been included in the block
|
|
// template.
|
|
templateTxnMap := make(map[chainhash.Hash]struct{})
|
|
|
|
// Track if auto revocations have been added to the priority queue.
|
|
addedAutoRevocations := false
|
|
|
|
// Get the best block and header.
|
|
bestHeader, err := g.cfg.HeaderByHash(&best.Hash)
|
|
if err != nil {
|
|
str := fmt.Sprintf("unable to get tip block header %v", best.Hash)
|
|
return nil, makeError(ErrGetTopBlock, str)
|
|
}
|
|
bestHeaderBytes, err := bestHeader.Bytes()
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to serialize header for block %v: %v",
|
|
best.Hash, err)
|
|
return nil, makeError(ErrSerializeHeader, str)
|
|
}
|
|
|
|
// Choose which transactions make it into the block.
|
|
nextPriorityQueueItem:
|
|
for priorityQueue.Len() > 0 {
|
|
// Grab the highest priority (or highest fee per kilobyte
|
|
// depending on the sort order) transaction.
|
|
prioItem := heap.Pop(priorityQueue).(*txPrioItem)
|
|
tx := prioItem.txDesc.Tx
|
|
delete(prioritizedTxns, *tx.Hash())
|
|
|
|
if _, exist := templateTxnMap[*tx.Hash()]; exist {
|
|
continue
|
|
}
|
|
|
|
// Store if this is an SStx or not.
|
|
isSStx := prioItem.txType == stake.TxTypeSStx
|
|
|
|
// Store if this is an SSGen or not.
|
|
isSSGen := prioItem.txType == stake.TxTypeSSGen
|
|
|
|
// Store if this is an SSRtx or not.
|
|
isSSRtx := prioItem.txType == stake.TxTypeSSRtx
|
|
|
|
var isTSpend, isTAdd bool
|
|
if isTreasuryEnabled {
|
|
// Store if this is an TSpend or not.
|
|
isTSpend = prioItem.txType == stake.TxTypeTSpend
|
|
|
|
// Store if this is a TAdd or not
|
|
isTAdd = prioItem.txType == stake.TxTypeTAdd
|
|
}
|
|
|
|
// If the transaction is not a vote, then we are done adding votes since
|
|
// votes are the highest priority in the queue.
|
|
doneAddingVotes := !isSSGen
|
|
|
|
// If the automatic ticket revocations agenda is active, then the block MUST
|
|
// contain version 2 revocation transactions for all tickets that will
|
|
// become missed or expired as of this block.
|
|
//
|
|
// Create and insert these revocations into the priority queue AFTER votes
|
|
// are done being added to ensure that revocations are added for votes that
|
|
// fail validation checks or are otherwise not included into the block.
|
|
if isAutoRevocationsEnabled && !addedAutoRevocations && doneAddingVotes {
|
|
// Create revocations for all tickets that will become missed or expired
|
|
// as of the block being created and add them to the priority queue and
|
|
// maps.
|
|
err = g.addAutoRevocationsToQueue(foundWinningTickets, blockUtxos,
|
|
bestHeaderBytes, numSSGen, isTreasuryEnabled, priorityQueue,
|
|
prioritizedTxns, prioItemMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Set the flag that auto revocations have been added so that they are
|
|
// only added once.
|
|
addedAutoRevocations = true
|
|
|
|
// Requeue the current item now that the revocations have been added and
|
|
// continue.
|
|
heap.Push(priorityQueue, prioItem)
|
|
prioritizedTxns[*tx.Hash()] = struct{}{}
|
|
continue
|
|
}
|
|
|
|
// Grab the list of transactions which depend on this one (if any).
|
|
deps := miningView.children(tx.Hash())
|
|
|
|
// Skip TSpend if this block is not on a TVI or outside of the
|
|
// Tspend window.
|
|
if isTSpend {
|
|
if !isTVI {
|
|
log.Tracef("Skipping tspend %v because block "+
|
|
"is not on a TVI: %v", tx.Hash(),
|
|
nextBlockHeight)
|
|
continue
|
|
}
|
|
|
|
// We are on a TVI, make sure this tspend is within the
|
|
// window.
|
|
exp := tx.MsgTx().Expiry
|
|
if !standalone.InsideTSpendWindow(nextBlockHeight,
|
|
exp, g.cfg.ChainParams.TreasuryVoteInterval,
|
|
g.cfg.ChainParams.TreasuryVoteIntervalMultiplier) {
|
|
|
|
log.Tracef("Skipping treasury spend %v at height %d because it "+
|
|
"has an expiry of %d that is outside of the voting window",
|
|
tx.Hash(), nextBlockHeight, exp)
|
|
continue
|
|
}
|
|
|
|
// Ensure there are enough votes. Send in a fake block
|
|
// with the proper height.
|
|
err = g.cfg.CheckTSpendHasVotes(prevHash,
|
|
dcrutil.NewTx(tx.MsgTx()))
|
|
if err != nil {
|
|
log.Tracef("Skipping tspend %v because it doesn't have enough "+
|
|
"votes: height %v reason '%v'", tx.Hash(), nextBlockHeight, err)
|
|
continue
|
|
}
|
|
|
|
// Ensure this TSpend won't overspend from the
|
|
// treasury. This is currently considering between
|
|
// TSpends by tx priority order, which might not be
|
|
// ideal, but we don't expect this to be triggered for
|
|
// mainnet. This could be improved by sorting approved
|
|
// TSpends either by approval % or by expiry.
|
|
tspendAmount := tx.MsgTx().TxIn[0].ValueIn
|
|
if maxTreasurySpend-tspendAmount < 0 {
|
|
log.Tracef("Skipping tspend %v because it spends "+
|
|
"more than allowed: treasury %d tspend %d",
|
|
tx.Hash(), maxTreasurySpend, tspendAmount)
|
|
continue
|
|
}
|
|
maxTreasurySpend -= tspendAmount
|
|
}
|
|
|
|
// Skip if we already have too many TAdds.
|
|
if isTAdd && numTAdds >= blockchain.MaxTAddsPerBlock {
|
|
log.Tracef("Skipping tadd %s because it would exceed "+
|
|
"the max number of tadds allowed in a block",
|
|
tx.Hash())
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
|
|
// Skip if we already have too many SStx.
|
|
if isSStx && (numSStx >=
|
|
int(g.cfg.ChainParams.MaxFreshStakePerBlock)) {
|
|
log.Tracef("Skipping sstx %s because it would exceed "+
|
|
"the max number of sstx allowed in a block", tx.Hash())
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
|
|
// Skip if the SStx commit value is below the value required by the
|
|
// stake diff.
|
|
if isSStx && (tx.MsgTx().TxOut[0].Value < best.NextStakeDiff) {
|
|
continue
|
|
}
|
|
|
|
// Skip revocation transactions if they are spending tickets that are not
|
|
// eligible to be revoked.
|
|
if isSSRtx {
|
|
ticketHash := &tx.MsgTx().TxIn[0].PreviousOutPoint.Hash
|
|
|
|
// Determine if the ticket being revoked will become missed or expired as
|
|
// of this block.
|
|
voted, winning := foundWinningTickets[*ticketHash]
|
|
missedThisBlock := !voted && winning
|
|
_, expiringThisBlock := expiringTicketHashes[*ticketHash]
|
|
missedOrExpiredThisBlock := missedThisBlock || expiringThisBlock
|
|
|
|
// Ensure the ticket being spent is actually eligible to be revoked in
|
|
// this block. If the automatic ticket revocations agenda is active, then
|
|
// revocations are allowed if the ticket will become missed or expired as
|
|
// of this block.
|
|
eligible := (isAutoRevocationsEnabled && missedOrExpiredThisBlock) ||
|
|
hashInSlice(*ticketHash, best.MissedTickets)
|
|
|
|
if !eligible {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if miningView.isRejected(tx.Hash()) {
|
|
// If the transaction or any of its ancestors have been rejected,
|
|
// discard the transaction.
|
|
continue
|
|
}
|
|
|
|
ancestors := miningView.ancestors(tx.Hash())
|
|
ancestorStats, _ := miningView.AncestorStats(tx.Hash())
|
|
oldFee := prioItem.feePerKB
|
|
prioItem.feePerKB = calcFeePerKb(prioItem.txDesc, ancestorStats)
|
|
|
|
feeDecreased := oldFee > prioItem.feePerKB
|
|
if feeDecreased && ancestorStats.NumAncestors == 0 {
|
|
// If the fee decreased due to ancestors being included in the
|
|
// template and the transaction has no parents, then enqueue it one
|
|
// more time with an accurate feePerKb.
|
|
heap.Push(priorityQueue, prioItem)
|
|
prioritizedTxns[*tx.Hash()] = struct{}{}
|
|
continue
|
|
}
|
|
|
|
if feeDecreased {
|
|
// Skip the transaction if the total feePerKb decreased.
|
|
// This addresses a vulnerability that would allow a low-fee
|
|
// transaction to have an inflated and inaccurate feePerKb based on
|
|
// ancestors that have already been included in the block template.
|
|
// The transaction will be added back to the priority queue when all
|
|
// parent transactions are included in the template.
|
|
continue
|
|
}
|
|
|
|
// Enforce maximum block size. Also check for overflow.
|
|
txSize := uint32(tx.MsgTx().SerializeSize())
|
|
blockPlusTxSize := blockSize + txSize + uint32(ancestorStats.SizeBytes)
|
|
if blockPlusTxSize < blockSize ||
|
|
blockPlusTxSize >= g.cfg.Policy.BlockMaxSize {
|
|
log.Tracef("Skipping tx %s (size %v) because it "+
|
|
"would exceed the max block size; cur block "+
|
|
"size %v, cur num tx %v", tx.Hash(), txSize,
|
|
blockSize, len(blockTxns))
|
|
logSkippedDeps(tx, deps)
|
|
miningView.reject(tx.Hash())
|
|
continue
|
|
}
|
|
|
|
// Enforce maximum signature operations per block. Also check
|
|
// for overflow.
|
|
numSigOps := int64(prioItem.txDesc.TotalSigOps)
|
|
numSigOpsBundle := numSigOps + int64(ancestorStats.TotalSigOps)
|
|
if blockSigOps+numSigOpsBundle < blockSigOps ||
|
|
blockSigOps+numSigOpsBundle > blockchain.MaxSigOpsPerBlock {
|
|
log.Tracef("Skipping tx %s because it would "+
|
|
"exceed the maximum sigops per block", tx.Hash())
|
|
logSkippedDeps(tx, deps)
|
|
miningView.reject(tx.Hash())
|
|
continue
|
|
}
|
|
|
|
// Check to see if the SSGen tx actually uses a ticket that is
|
|
// valid for the next block.
|
|
if isSSGen {
|
|
if foundWinningTickets[tx.MsgTx().TxIn[1].PreviousOutPoint.Hash] {
|
|
continue
|
|
}
|
|
msgTx := tx.MsgTx()
|
|
isEligible := false
|
|
for _, sstxHash := range best.NextWinningTickets {
|
|
if sstxHash.IsEqual(&msgTx.TxIn[1].PreviousOutPoint.Hash) {
|
|
isEligible = true
|
|
}
|
|
}
|
|
|
|
if !isEligible {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Skip transactions that do not pay enough fee except for stake
|
|
// transactions.
|
|
if prioItem.feePerKB < float64(g.cfg.Policy.TxMinFreeFee) &&
|
|
tx.Tree() != wire.TxTreeStake {
|
|
|
|
log.Tracef("Skipping tx %s with feePerKB %.2f < TxMinFreeFee %d ",
|
|
tx.Hash(), prioItem.feePerKB, g.cfg.Policy.TxMinFreeFee)
|
|
logSkippedDeps(tx, deps)
|
|
miningView.reject(tx.Hash())
|
|
continue
|
|
}
|
|
|
|
txBundle := append(ancestors, prioItem.txDesc)
|
|
for _, bundledTx := range txBundle {
|
|
// Ensure the transaction inputs pass all of the necessary
|
|
// preconditions before allowing it to be added to the block.
|
|
// The fraud proof is not checked because it will be filled in
|
|
// by the miner.
|
|
_, err = g.cfg.CheckTransactionInputs(bundledTx.Tx, nextBlockHeight,
|
|
blockUtxos, false, &bestHeader, isTreasuryEnabled,
|
|
isAutoRevocationsEnabled, subsidySplitVariant)
|
|
if err != nil {
|
|
log.Tracef("Skipping tx %s due to error in "+
|
|
"CheckTransactionInputs: %v", bundledTx.Tx.Hash(), err)
|
|
logSkippedDeps(bundledTx.Tx, deps)
|
|
miningView.reject(bundledTx.Tx.Hash())
|
|
continue nextPriorityQueueItem
|
|
}
|
|
err = g.cfg.ValidateTransactionScripts(bundledTx.Tx, blockUtxos,
|
|
scriptFlags, isAutoRevocationsEnabled)
|
|
if err != nil {
|
|
log.Tracef("Skipping tx %s due to error in "+
|
|
"ValidateTransactionScripts: %v", bundledTx.Tx.Hash(), err)
|
|
logSkippedDeps(bundledTx.Tx, deps)
|
|
miningView.reject(bundledTx.Tx.Hash())
|
|
continue nextPriorityQueueItem
|
|
}
|
|
}
|
|
|
|
for _, bundledTxDesc := range txBundle {
|
|
bundledTx := bundledTxDesc.Tx
|
|
bundledTxHash := bundledTx.Hash()
|
|
|
|
// Spend the transaction inputs in the block utxo view and add
|
|
// an entry for it to ensure any transactions which reference
|
|
// this one have it available as an input and can ensure they
|
|
// aren't double spending.
|
|
spendTransaction(blockUtxos, bundledTxDesc.Tx, nextBlockHeight,
|
|
isTreasuryEnabled)
|
|
|
|
// Add the transaction to the block, increment counters, and
|
|
// save the fees and signature operation counts to the block
|
|
// template.
|
|
blockTxns = append(blockTxns, bundledTx)
|
|
blockSize += uint32(bundledTx.MsgTx().SerializeSize())
|
|
bundledTxSigOps := int64(bundledTxDesc.TotalSigOps)
|
|
blockSigOps += bundledTxSigOps
|
|
|
|
// Accumulate the SStxs in the block, because only a certain number
|
|
// are allowed.
|
|
if bundledTxDesc.Type == stake.TxTypeSStx {
|
|
numSStx++
|
|
}
|
|
if bundledTxDesc.Type == stake.TxTypeSSGen {
|
|
foundWinningTickets[bundledTx.MsgTx().TxIn[1].PreviousOutPoint.Hash] = true
|
|
numSSGen++
|
|
}
|
|
if isTreasuryEnabled && bundledTxDesc.Type == stake.TxTypeTAdd {
|
|
numTAdds++
|
|
}
|
|
|
|
templateTxnMap[*bundledTxHash] = struct{}{}
|
|
txFeesMap[*bundledTxHash] = bundledTxDesc.Fee
|
|
txSigOpCountsMap[*bundledTxHash] = bundledTxSigOps
|
|
|
|
bundledPrioItem := prioItemMap[*bundledTxHash]
|
|
log.Tracef("Adding tx %s (priority %.2f, feePerKB %.2f)",
|
|
bundledTxHash, bundledPrioItem.priority,
|
|
bundledPrioItem.feePerKB)
|
|
|
|
// Remove transaction from mining view since it's been added to the
|
|
// block template.
|
|
bundledTxDeps := miningView.children(bundledTxHash)
|
|
miningView.RemoveTransaction(bundledTxHash, false)
|
|
|
|
// Add transactions which depend on this one (and also do not
|
|
// have any other unsatisfied dependencies) to the priority
|
|
// queue.
|
|
for _, childTx := range bundledTxDeps {
|
|
childTxHash := childTx.Tx.Hash()
|
|
if _, exist := prioritizedTxns[*childTxHash]; exist {
|
|
continue
|
|
}
|
|
|
|
// Add the transaction to the priority queue if there are no
|
|
// more dependencies after this one and the priority item for it
|
|
// already exists.
|
|
if !miningView.hasParents(childTxHash) {
|
|
childPrioItem := prioItemMap[*childTxHash]
|
|
if childPrioItem != nil {
|
|
heap.Push(priorityQueue, childPrioItem)
|
|
prioritizedTxns[*childTxHash] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we did not already add auto revocations above, attempt to add them now.
|
|
// This case will be hit if the priority queue contained only vote
|
|
// transactions.
|
|
if isAutoRevocationsEnabled && !addedAutoRevocations {
|
|
// Create revocations for all tickets that will become missed or expired
|
|
// as of the block being created and add them to the priority queue and
|
|
// maps.
|
|
err = g.addAutoRevocationsToQueue(foundWinningTickets, blockUtxos,
|
|
bestHeaderBytes, numSSGen, isTreasuryEnabled, priorityQueue,
|
|
prioritizedTxns, prioItemMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Set the flag that auto revocations have been added so that they are
|
|
// only added once.
|
|
addedAutoRevocations = true
|
|
|
|
// Continue to dequeue from the priority queue now that the revocations have
|
|
// been added.
|
|
goto nextPriorityQueueItem
|
|
}
|
|
|
|
// Build tx list for stake tx.
|
|
blockTxnsStake := make([]*dcrutil.Tx, 0, len(blockTxns))
|
|
|
|
// Create a standard coinbase transaction paying to the provided
|
|
// address. NOTE: The coinbase value will be updated to include the
|
|
// fees from the selected transactions later after they have actually
|
|
// been selected. It is created here to detect any errors early
|
|
// before potentially doing a lot of work below. The extra nonce helps
|
|
// ensure the transaction is not a duplicate transaction (paying the
|
|
// same value to the same public key address would otherwise be an
|
|
// identical transaction for block version 1).
|
|
// Decred: We need to move this downwards because of the requirements
|
|
// to incorporate voters and potential voters.
|
|
//
|
|
// NOTE: we have to do this early to deal with stakebase.
|
|
coinbaseScript := []byte{0x00, 0x00}
|
|
coinbaseScript = append(coinbaseScript, []byte(coinbaseFlags)...)
|
|
|
|
// Add a random coinbase nonce to ensure that tx prefix hash
|
|
// so that our merkle root is unique for lookups needed for
|
|
// getwork, etc.
|
|
opReturnPkScript, err := standardCoinbaseOpReturn(uint32(nextBlockHeight))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stake tx ordering in stake tree:
|
|
// 1. Stakebase
|
|
// 2. SSGen (votes).
|
|
// 3. SStx (fresh stake tickets).
|
|
// 4. SSRtx (revocations for missed tickets).
|
|
// 5. Stuff treasury payout in stake tree.
|
|
|
|
// We have to figure out how many voters we have before adding the
|
|
// stakebase transaction.
|
|
//
|
|
// We should only find at most TicketsPerBlock votes per block, so
|
|
// initialize the slices using that as a hint to avoid reallocations.
|
|
voters := 0
|
|
voteBitsVoters := make([]uint16, 0, g.cfg.ChainParams.TicketsPerBlock)
|
|
votes := make([]*dcrutil.Tx, 0, g.cfg.ChainParams.TicketsPerBlock)
|
|
|
|
// After SVH, we should add SSGens (votes) to the stake tree. Since we
|
|
// need to figure out the number of voters before creating the
|
|
// treasuryBase, we add the votes to an aux slice to be processed
|
|
// later.
|
|
if nextBlockHeight >= stakeValidationHeight {
|
|
for _, tx := range blockTxns {
|
|
msgTx := tx.MsgTx()
|
|
|
|
if stake.IsSSGen(msgTx) {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx)
|
|
if g.maybeInsertStakeTx(txCopy, !knownDisapproved,
|
|
isTreasuryEnabled) {
|
|
|
|
vb := stake.SSGenVoteBits(txCopy.MsgTx())
|
|
voteBitsVoters = append(voteBitsVoters, vb)
|
|
votes = append(votes, txCopy)
|
|
voters++
|
|
}
|
|
}
|
|
|
|
// Don't let this overflow, although probably it's impossible.
|
|
if voters >= math.MaxUint16 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the treasury is enabled, it should be the first tx of the stake
|
|
// tree.
|
|
var treasuryBase *dcrutil.Tx
|
|
if isTreasuryEnabled {
|
|
treasuryBase, err = createTreasuryBaseTx(g.cfg.SubsidyCache,
|
|
nextBlockHeight, uint16(voters))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blockTxnsStake = append(blockTxnsStake, treasuryBase)
|
|
}
|
|
|
|
// Add the votes to blockTxnsStake since treasuryBase needs to come
|
|
// first.
|
|
blockTxnsStake = append(blockTxnsStake, votes...)
|
|
|
|
// Set votebits, which determines whether the TxTreeRegular of the previous
|
|
// block is valid or not.
|
|
var votebits uint16
|
|
if nextBlockHeight < stakeValidationHeight {
|
|
votebits = uint16(0x0001) // TxTreeRegular enabled pre-staking
|
|
} else {
|
|
// Otherwise, we need to check the votes to determine if the tx tree was
|
|
// validated or not.
|
|
voteYea := 0
|
|
totalVotes := 0
|
|
|
|
for _, vb := range voteBitsVoters {
|
|
if dcrutil.IsFlagSet16(vb, dcrutil.BlockValid) {
|
|
voteYea++
|
|
}
|
|
totalVotes++
|
|
}
|
|
|
|
if voteYea == 0 { // Handle zero case for div by zero error prevention.
|
|
votebits = uint16(0x0000) // TxTreeRegular disabled
|
|
} else if (totalVotes / voteYea) <= 1 {
|
|
votebits = uint16(0x0001) // TxTreeRegular enabled
|
|
} else {
|
|
votebits = uint16(0x0000) // TxTreeRegular disabled
|
|
}
|
|
}
|
|
|
|
// Get the newly purchased tickets (SStx tx) and store them and their
|
|
// number.
|
|
freshStake := 0
|
|
for _, tx := range blockTxns {
|
|
msgTx := tx.MsgTx()
|
|
if tx.Tree() == wire.TxTreeStake && stake.IsSStx(msgTx) {
|
|
// A ticket can not spend an input from TxTreeRegular,
|
|
// since it has not yet been validated.
|
|
if containsTxIns(blockTxns, tx) {
|
|
continue
|
|
}
|
|
|
|
// Quick check for difficulty here.
|
|
if msgTx.TxOut[0].Value >= best.NextStakeDiff {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx)
|
|
if g.maybeInsertStakeTx(txCopy, !knownDisapproved,
|
|
isTreasuryEnabled) {
|
|
|
|
blockTxnsStake = append(blockTxnsStake, txCopy)
|
|
freshStake++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't let this overflow.
|
|
if freshStake >= int(g.cfg.ChainParams.MaxFreshStakePerBlock) {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Ensure that mining the block would not cause the chain to become
|
|
// unrecoverable due to ticket exhaustion.
|
|
err = g.cfg.CheckTicketExhaustion(&best.Hash, uint8(freshStake))
|
|
if err != nil {
|
|
log.Debug(err)
|
|
return nil, makeError(ErrTicketExhaustion, err.Error())
|
|
}
|
|
|
|
// Get the ticket revocations (SSRtx tx) and store them and their
|
|
// number.
|
|
revocations := 0
|
|
for _, tx := range blockTxns {
|
|
if nextBlockHeight < stakeValidationHeight {
|
|
break // No SSRtx should be present before this height.
|
|
}
|
|
|
|
msgTx := tx.MsgTx()
|
|
if tx.Tree() == wire.TxTreeStake && stake.IsSSRtx(msgTx) {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx)
|
|
if g.maybeInsertStakeTx(txCopy, !knownDisapproved,
|
|
isTreasuryEnabled) {
|
|
|
|
blockTxnsStake = append(blockTxnsStake, txCopy)
|
|
revocations++
|
|
}
|
|
}
|
|
|
|
// Don't let this overflow.
|
|
if revocations >= math.MaxUint8 {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Insert TAdd/TSpend transactions.
|
|
if isTreasuryEnabled {
|
|
for _, tx := range blockTxns {
|
|
msgTx := tx.MsgTx()
|
|
if tx.Tree() == wire.TxTreeStake && stake.IsTAdd(msgTx) {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx)
|
|
if g.maybeInsertStakeTx(txCopy, !knownDisapproved,
|
|
isTreasuryEnabled) {
|
|
|
|
blockTxnsStake = append(blockTxnsStake, txCopy)
|
|
log.Tracef("maybeInsertStakeTx TADD %v ", tx.Hash())
|
|
}
|
|
} else if tx.Tree() == wire.TxTreeStake && stake.IsTSpend(msgTx) {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx)
|
|
if g.maybeInsertStakeTx(txCopy, !knownDisapproved,
|
|
isTreasuryEnabled) {
|
|
|
|
blockTxnsStake = append(blockTxnsStake, txCopy)
|
|
log.Tracef("maybeInsertStakeTx TSPEND %v ", tx.Hash())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
coinbaseTx := createCoinbaseTx(g.cfg.SubsidyCache, coinbaseScript,
|
|
opReturnPkScript, nextBlockHeight, payToAddress, uint16(voters),
|
|
g.cfg.ChainParams, isTreasuryEnabled, subsidySplitVariant)
|
|
coinbaseTx.SetTree(wire.TxTreeRegular)
|
|
|
|
numCoinbaseSigOps := int64(g.cfg.CountSigOps(coinbaseTx, true,
|
|
false, isTreasuryEnabled))
|
|
blockSize += uint32(coinbaseTx.MsgTx().SerializeSize())
|
|
blockSigOps += numCoinbaseSigOps
|
|
txFeesMap[*coinbaseTx.Hash()] = 0
|
|
txSigOpCountsMap[*coinbaseTx.Hash()] = numCoinbaseSigOps
|
|
if treasuryBase != nil {
|
|
txFeesMap[*treasuryBase.Hash()] = 0
|
|
n := int64(g.cfg.CountSigOps(treasuryBase, true, false, isTreasuryEnabled))
|
|
txSigOpCountsMap[*treasuryBase.Hash()] = n
|
|
}
|
|
|
|
// Build tx lists for regular tx.
|
|
blockTxnsRegular := make([]*dcrutil.Tx, 0, len(blockTxns)+1)
|
|
|
|
// Append coinbase.
|
|
blockTxnsRegular = append(blockTxnsRegular, coinbaseTx)
|
|
|
|
// Assemble the two transaction trees.
|
|
for _, tx := range blockTxns {
|
|
if tx.Tree() == wire.TxTreeRegular {
|
|
blockTxnsRegular = append(blockTxnsRegular, tx)
|
|
} else if tx.Tree() == wire.TxTreeStake {
|
|
continue
|
|
} else {
|
|
log.Tracef("Error adding tx %s to block; invalid tree", tx.Hash())
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, tx := range blockTxnsRegular {
|
|
fee, ok := txFeesMap[*tx.Hash()]
|
|
if !ok {
|
|
return nil, fmt.Errorf("couldn't find fee for tx %v",
|
|
*tx.Hash())
|
|
}
|
|
totalFees += fee
|
|
txFees = append(txFees, fee)
|
|
|
|
tsos, ok := txSigOpCountsMap[*tx.Hash()]
|
|
if !ok {
|
|
return nil, fmt.Errorf("couldn't find sig ops count for tx %v",
|
|
*tx.Hash())
|
|
}
|
|
txSigOpCounts = append(txSigOpCounts, tsos)
|
|
}
|
|
|
|
for _, tx := range blockTxnsStake {
|
|
fee, ok := txFeesMap[*tx.Hash()]
|
|
if !ok {
|
|
return nil, fmt.Errorf("couldn't find fee for stx %v",
|
|
*tx.Hash())
|
|
}
|
|
totalFees += fee
|
|
txFees = append(txFees, fee)
|
|
|
|
tsos, ok := txSigOpCountsMap[*tx.Hash()]
|
|
if !ok {
|
|
return nil, fmt.Errorf("couldn't find sig ops count for stx %v",
|
|
*tx.Hash())
|
|
}
|
|
txSigOpCounts = append(txSigOpCounts, tsos)
|
|
}
|
|
|
|
// Scale the fees according to the number of voters once stake validation
|
|
// height is reached.
|
|
if nextBlockHeight >= stakeValidationHeight {
|
|
totalFees *= int64(voters)
|
|
totalFees /= int64(g.cfg.ChainParams.TicketsPerBlock)
|
|
}
|
|
|
|
txSigOpCounts = append(txSigOpCounts, numCoinbaseSigOps)
|
|
|
|
// Now that the actual transactions have been selected, update the
|
|
// block size for the real transaction count and coinbase value with
|
|
// the total fees accordingly.
|
|
if nextBlockHeight > 1 {
|
|
blockSize -= wire.MaxVarIntPayload -
|
|
uint32(wire.VarIntSerializeSize(uint64(len(blockTxnsRegular))+
|
|
uint64(len(blockTxnsStake))))
|
|
powOutputIdx := 2
|
|
if isTreasuryEnabled {
|
|
powOutputIdx = 1
|
|
}
|
|
coinbaseTx.MsgTx().TxOut[powOutputIdx].Value += totalFees
|
|
txFees[0] = -totalFees
|
|
}
|
|
|
|
// Calculate the required difficulty for the block. The timestamp
|
|
// is potentially adjusted to ensure it comes after the median time of
|
|
// the last several blocks per the chain consensus rules.
|
|
ts := g.medianAdjustedTime()
|
|
reqDifficulty, err := g.cfg.CalcNextRequiredDifficulty(&prevHash, ts)
|
|
if err != nil {
|
|
return nil, makeError(ErrGettingDifficulty, err.Error())
|
|
}
|
|
|
|
// Return nil if we don't yet have enough voters; sometimes it takes a
|
|
// bit for the mempool to sync with the votes map and we end up down
|
|
// here despite having the relevant votes available in the votes map.
|
|
minimumVotesRequired :=
|
|
int((g.cfg.ChainParams.TicketsPerBlock / 2) + 1)
|
|
if nextBlockHeight >= stakeValidationHeight &&
|
|
voters < minimumVotesRequired {
|
|
log.Warnf("incongruent number of voters in mempool " +
|
|
"vs mempool.voters; not enough voters found")
|
|
return g.handleTooFewVoters(nextBlockHeight, payToAddress,
|
|
isTreasuryEnabled, subsidySplitVariant)
|
|
}
|
|
|
|
// Correct transaction index fraud proofs for any transactions that
|
|
// are chains. maybeInsertStakeTx fills this in for stake transactions
|
|
// already, so only do it for regular transactions.
|
|
for i, tx := range blockTxnsRegular {
|
|
// No need to check any of the transactions in the custom first
|
|
// block.
|
|
if nextBlockHeight == 1 {
|
|
break
|
|
}
|
|
|
|
// Skip coinbase.
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
|
|
view, err := g.cfg.FetchUtxoView(tx, !knownDisapproved)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to fetch utxo view for tx %v: %s",
|
|
tx.Hash(), err.Error())
|
|
return nil, makeError(ErrFetchTxStore, str)
|
|
}
|
|
|
|
// Copy the transaction and swap the pointer.
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx)
|
|
blockTxnsRegular[i] = txCopy
|
|
tx = txCopy
|
|
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
entry := view.LookupEntry(txIn.PreviousOutPoint)
|
|
if entry == nil {
|
|
// Set a flag with the index so we can properly set
|
|
// the fraud proof below.
|
|
txIn.BlockIndex = wire.NullBlockIndex
|
|
} else {
|
|
txIn.ValueIn = entry.Amount()
|
|
txIn.BlockHeight = uint32(entry.BlockHeight())
|
|
txIn.BlockIndex = entry.BlockIndex()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fill in locally referenced inputs.
|
|
for i, tx := range blockTxnsRegular {
|
|
// Skip coinbase.
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
// This tx was at some point 0-conf and now requires the correct block
|
|
// height and index. Set it here. It is safe to modify the transaction
|
|
// directly since it has already been copied above.
|
|
if txIn.BlockIndex == wire.NullBlockIndex {
|
|
idx := txIndexFromTxList(txIn.PreviousOutPoint.Hash,
|
|
blockTxnsRegular)
|
|
|
|
// The input is in the block, set it accordingly.
|
|
if idx != -1 {
|
|
originIdx := txIn.PreviousOutPoint.Index
|
|
amt := blockTxnsRegular[idx].MsgTx().TxOut[originIdx].Value
|
|
txIn.ValueIn = amt
|
|
txIn.BlockHeight = uint32(nextBlockHeight)
|
|
txIn.BlockIndex = uint32(idx)
|
|
} else {
|
|
str := fmt.Sprintf("failed find hash in tx list "+
|
|
"for fraud proof; tx in hash %v",
|
|
txIn.PreviousOutPoint.Hash)
|
|
return nil, makeError(ErrFraudProofIndex, str)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Choose the block version to generate based on the network.
|
|
blockVersion := int32(generatedBlockVersion)
|
|
if g.cfg.ChainParams.Net != wire.MainNet {
|
|
blockVersion = generatedBlockVersionTest
|
|
}
|
|
|
|
// Figure out stake version.
|
|
generatedStakeVersion, err := g.cfg.CalcStakeVersionByHash(&prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create a new block ready to be solved.
|
|
var msgBlock wire.MsgBlock
|
|
msgBlock.Header = wire.BlockHeader{
|
|
Version: blockVersion,
|
|
PrevBlock: prevHash,
|
|
// MerkleRoot and StakeRoot set below.
|
|
VoteBits: votebits,
|
|
FinalState: best.NextFinalState,
|
|
Voters: uint16(voters),
|
|
FreshStake: uint8(freshStake),
|
|
Revocations: uint8(revocations),
|
|
PoolSize: best.NextPoolSize,
|
|
Timestamp: ts,
|
|
SBits: best.NextStakeDiff,
|
|
Bits: reqDifficulty,
|
|
StakeVersion: generatedStakeVersion,
|
|
Height: uint32(nextBlockHeight),
|
|
// Size declared below
|
|
}
|
|
|
|
for _, tx := range blockTxnsRegular {
|
|
if err := msgBlock.AddTransaction(tx.MsgTx()); err != nil {
|
|
return nil, makeError(ErrTransactionAppend, err.Error())
|
|
}
|
|
}
|
|
|
|
totalTreasuryOps := 0
|
|
for _, tx := range blockTxnsStake {
|
|
if err := msgBlock.AddSTransaction(tx.MsgTx()); err != nil {
|
|
return nil, makeError(ErrTransactionAppend, err.Error())
|
|
}
|
|
// While in this loop count treasury operations.
|
|
if isTreasuryEnabled {
|
|
if stake.IsTAdd(tx.MsgTx()) {
|
|
totalTreasuryOps++
|
|
} else if stake.IsTSpend(tx.MsgTx()) {
|
|
totalTreasuryOps++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate the merkle root depending on the result of the header
|
|
// commitments agenda vote.
|
|
hdrCmtActive, err := g.cfg.IsHeaderCommitmentsAgendaActive(&prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
msgBlock.Header.MerkleRoot = calcBlockMerkleRoot(msgBlock.Transactions,
|
|
msgBlock.STransactions, hdrCmtActive)
|
|
|
|
// Calculate the stake root or commitment root depending on the result of
|
|
// the header commitments agenda vote.
|
|
var cmtRoot chainhash.Hash
|
|
if hdrCmtActive {
|
|
cmtRoot, err = calcBlockCommitmentRootV1(&msgBlock, blockUtxos)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to calculate commitment root for block "+
|
|
"when making new block template: %v", err)
|
|
return nil, makeError(ErrCalcCommitmentRoot, str)
|
|
}
|
|
} else {
|
|
cmtRoot = standalone.CalcTxTreeMerkleRoot(msgBlock.STransactions)
|
|
}
|
|
msgBlock.Header.StakeRoot = cmtRoot
|
|
|
|
msgBlock.Header.Size = uint32(msgBlock.SerializeSize())
|
|
|
|
// Finally, perform a full check on the created block against the chain
|
|
// consensus rules to ensure it properly connects to the current best
|
|
// chain with no issues.
|
|
block := dcrutil.NewBlockDeepCopyCoinbase(&msgBlock)
|
|
err = g.cfg.CheckConnectBlockTemplate(block)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to do final check for check connect "+
|
|
"block when making new block template: %v", err)
|
|
return nil, makeError(ErrCheckConnectBlock, str)
|
|
}
|
|
|
|
log.Debugf("Created new block template (%d transactions, %d stake "+
|
|
"transactions, %d treasury transactions, %d in fees, %d signature "+
|
|
"operations, %d bytes, target difficulty %064x, stake difficulty %v)",
|
|
len(msgBlock.Transactions), len(msgBlock.STransactions),
|
|
totalTreasuryOps, totalFees, blockSigOps, blockSize,
|
|
standalone.CompactToBig(msgBlock.Header.Bits),
|
|
dcrutil.Amount(msgBlock.Header.SBits).ToCoin())
|
|
|
|
blockTemplate := &BlockTemplate{
|
|
Block: &msgBlock,
|
|
Fees: txFees,
|
|
SigOpCounts: txSigOpCounts,
|
|
Height: nextBlockHeight,
|
|
ValidPayAddress: payToAddress != nil,
|
|
}
|
|
|
|
return blockTemplate, nil
|
|
}
|
|
|
|
// UpdateBlockTime updates the timestamp in the passed header to the current
|
|
// time while taking into account the median time of the last several blocks to
|
|
// ensure the new time is after that time per the chain consensus rules.
|
|
func (g *BlkTmplGenerator) UpdateBlockTime(header *wire.BlockHeader) {
|
|
// The new timestamp is potentially adjusted to ensure it comes after
|
|
// the median time of the last several blocks per the chain consensus
|
|
// rules.
|
|
newTimestamp := g.medianAdjustedTime()
|
|
header.Timestamp = newTimestamp
|
|
}
|