This updates the new RPC websocket work notification that is sent when a new template is generated and a websocket client requests updated via notifywork to include the reason the template was generated. In order to accomplish this, it modifies the background template generator subscription notifications, the RPC websockets notification manager, and the JSON-RPC work notification to include the reason and updates all of the intermediate plumbing code accordingly. It also updates the JSON-RPC documentation to include the new field and enumerate the supported reasons.
3231 lines
118 KiB
Go
3231 lines
118 KiB
Go
// Copyright (c) 2014-2016 The btcsuite developers
|
|
// Copyright (c) 2015-2019 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"container/heap"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/blockchain/stake/v2"
|
|
"github.com/decred/dcrd/blockchain/standalone"
|
|
"github.com/decred/dcrd/blockchain/v2"
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/chaincfg/v2"
|
|
"github.com/decred/dcrd/dcrutil/v2"
|
|
"github.com/decred/dcrd/gcs/v2/blockcf2"
|
|
"github.com/decred/dcrd/lru"
|
|
"github.com/decred/dcrd/mining/v2"
|
|
"github.com/decred/dcrd/txscript/v2"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
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 = 7
|
|
|
|
// generatedBlockVersionTest is the version of the block being generated
|
|
// for networks other than the main and simulation networks.
|
|
generatedBlockVersionTest = 8
|
|
|
|
// 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
|
|
|
|
// minVotesTimeoutDuration is the duration that must elapse after a new tip
|
|
// block has been received before other variants that also extend the same
|
|
// parent and received later are considered for the base of new templates.
|
|
minVotesTimeoutDuration = time.Second * 3
|
|
|
|
// maxVoteTimeoutDuration is the duration elapsed after the minimum number
|
|
// of votes for a new tip block has been received that a new template with
|
|
// less than the maximum number of votes will be generated.
|
|
maxVoteTimeoutDuration = time.Millisecond * 2500 // 2.5 seconds
|
|
|
|
// templateRegenSecs is the required number of seconds elapsed with
|
|
// incoming non vote transactions before template regeneration
|
|
// is required.
|
|
templateRegenSecs = 30
|
|
|
|
// merkleRootPairSize is the size in bytes of the merkle root + stake root
|
|
// of a block.
|
|
merkleRootPairSize = 64
|
|
)
|
|
|
|
// txPrioItem houses a transaction along with extra information that allows the
|
|
// transaction to be prioritized and track dependencies on other transactions
|
|
// which have not been mined into a block yet.
|
|
type txPrioItem struct {
|
|
tx *dcrutil.Tx
|
|
txType stake.TxType
|
|
fee int64
|
|
priority float64
|
|
feePerKB float64
|
|
|
|
// dependsOn holds a map of transaction hashes which this one depends
|
|
// on. It will only be set when the transaction references other
|
|
// transactions in the source pool and hence must come after them in
|
|
// a block.
|
|
dependsOn map[chainhash.Hash]struct{}
|
|
}
|
|
|
|
// txPriorityQueueLessFunc describes a function that can be used as a compare
|
|
// function for a transaction priority queue (txPriorityQueue).
|
|
type txPriorityQueueLessFunc func(*txPriorityQueue, int, int) bool
|
|
|
|
// txPriorityQueue implements a priority queue of txPrioItem elements that
|
|
// supports an arbitrary compare function as defined by txPriorityQueueLessFunc.
|
|
type txPriorityQueue struct {
|
|
lessFunc txPriorityQueueLessFunc
|
|
items []*txPrioItem
|
|
}
|
|
|
|
// Len returns the number of items in the priority queue. It is part of the
|
|
// heap.Interface implementation.
|
|
func (pq *txPriorityQueue) Len() int {
|
|
return len(pq.items)
|
|
}
|
|
|
|
// Less returns whether the item in the priority queue with index i should sort
|
|
// before the item with index j by deferring to the assigned less function. It
|
|
// is part of the heap.Interface implementation.
|
|
func (pq *txPriorityQueue) Less(i, j int) bool {
|
|
return pq.lessFunc(pq, i, j)
|
|
}
|
|
|
|
// Swap swaps the items at the passed indices in the priority queue. It is
|
|
// part of the heap.Interface implementation.
|
|
func (pq *txPriorityQueue) Swap(i, j int) {
|
|
pq.items[i], pq.items[j] = pq.items[j], pq.items[i]
|
|
}
|
|
|
|
// Push pushes the passed item onto the priority queue. It is part of the
|
|
// heap.Interface implementation.
|
|
func (pq *txPriorityQueue) Push(x interface{}) {
|
|
pq.items = append(pq.items, x.(*txPrioItem))
|
|
}
|
|
|
|
// Pop removes the highest priority item (according to Less) from the priority
|
|
// queue and returns it. It is part of the heap.Interface implementation.
|
|
func (pq *txPriorityQueue) Pop() interface{} {
|
|
n := len(pq.items)
|
|
item := pq.items[n-1]
|
|
pq.items[n-1] = nil
|
|
pq.items = pq.items[0 : n-1]
|
|
return item
|
|
}
|
|
|
|
// SetLessFunc sets the compare function for the priority queue to the provided
|
|
// function. It also invokes heap.Init on the priority queue using the new
|
|
// function so it can immediately be used with heap.Push/Pop.
|
|
func (pq *txPriorityQueue) SetLessFunc(lessFunc txPriorityQueueLessFunc) {
|
|
pq.lessFunc = lessFunc
|
|
heap.Init(pq)
|
|
}
|
|
|
|
// stakePriority is an integer that is used to sort stake transactions
|
|
// by importance when they enter the min heap for block construction.
|
|
// 2 is for votes (highest), followed by 1 for tickets (2nd highest),
|
|
// followed by 0 for regular transactions and revocations (lowest).
|
|
type stakePriority int
|
|
|
|
const (
|
|
regOrRevocPriority stakePriority = iota
|
|
ticketPriority
|
|
votePriority
|
|
)
|
|
|
|
// stakePriority assigns a stake priority based on a transaction type.
|
|
func txStakePriority(txType stake.TxType) stakePriority {
|
|
prio := regOrRevocPriority
|
|
switch txType {
|
|
case stake.TxTypeSSGen:
|
|
prio = votePriority
|
|
case stake.TxTypeSStx:
|
|
prio = ticketPriority
|
|
}
|
|
|
|
return prio
|
|
}
|
|
|
|
// compareStakePriority compares the stake priority of two transactions.
|
|
// It uses votes > tickets > regular transactions or revocations. It
|
|
// returns 1 if i > j, 0 if i == j, and -1 if i < j in terms of stake
|
|
// priority.
|
|
func compareStakePriority(i, j *txPrioItem) int {
|
|
iStakePriority := txStakePriority(i.txType)
|
|
jStakePriority := txStakePriority(j.txType)
|
|
|
|
if iStakePriority > jStakePriority {
|
|
return 1
|
|
}
|
|
if iStakePriority < jStakePriority {
|
|
return -1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// txPQByStakeAndFee sorts a txPriorityQueue by stake priority, followed by
|
|
// fees per kilobyte, and then transaction priority.
|
|
func txPQByStakeAndFee(pq *txPriorityQueue, i, j int) bool {
|
|
// Sort by stake priority, continue if they're the same stake priority.
|
|
cmp := compareStakePriority(pq.items[i], pq.items[j])
|
|
if cmp == 1 {
|
|
return true
|
|
}
|
|
if cmp == -1 {
|
|
return false
|
|
}
|
|
|
|
// Using > here so that pop gives the highest fee item as opposed
|
|
// to the lowest. Sort by fee first, then priority.
|
|
if pq.items[i].feePerKB == pq.items[j].feePerKB {
|
|
return pq.items[i].priority > pq.items[j].priority
|
|
}
|
|
|
|
// The stake priorities are equal, so return based on fees
|
|
// per KB.
|
|
return pq.items[i].feePerKB > pq.items[j].feePerKB
|
|
}
|
|
|
|
// txPQByStakeAndFeeAndThenPriority sorts a txPriorityQueue by stake priority,
|
|
// followed by fees per kilobyte, and then if the transaction type is regular
|
|
// or a revocation it sorts it by priority.
|
|
func txPQByStakeAndFeeAndThenPriority(pq *txPriorityQueue, i, j int) bool {
|
|
// Sort by stake priority, continue if they're the same stake priority.
|
|
cmp := compareStakePriority(pq.items[i], pq.items[j])
|
|
if cmp == 1 {
|
|
return true
|
|
}
|
|
if cmp == -1 {
|
|
return false
|
|
}
|
|
|
|
bothAreLowStakePriority :=
|
|
txStakePriority(pq.items[i].txType) == regOrRevocPriority &&
|
|
txStakePriority(pq.items[j].txType) == regOrRevocPriority
|
|
|
|
// Use fees per KB on high stake priority transactions.
|
|
if !bothAreLowStakePriority {
|
|
return pq.items[i].feePerKB > pq.items[j].feePerKB
|
|
}
|
|
|
|
// Both transactions are of low stake importance. Use > here so that
|
|
// pop gives the highest priority item as opposed to the lowest.
|
|
// Sort by priority first, then fee.
|
|
if pq.items[i].priority == pq.items[j].priority {
|
|
return pq.items[i].feePerKB > pq.items[j].feePerKB
|
|
}
|
|
|
|
return pq.items[i].priority > pq.items[j].priority
|
|
}
|
|
|
|
// newTxPriorityQueue returns a new transaction priority queue that reserves the
|
|
// passed amount of space for the elements. The new priority queue uses the
|
|
// less than function lessFunc to sort the items in the min heap. The priority
|
|
// queue can grow larger than the reserved space, but extra copies of the
|
|
// underlying array can be avoided by reserving a sane value.
|
|
func newTxPriorityQueue(reserve int, lessFunc func(*txPriorityQueue, int, int) bool) *txPriorityQueue {
|
|
pq := &txPriorityQueue{
|
|
items: make([]*txPrioItem, 0, reserve),
|
|
}
|
|
pq.SetLessFunc(lessFunc)
|
|
return pq
|
|
}
|
|
|
|
// 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 mining.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})
|
|
numTopBlockVotes := uint16(len(curVoteMetadata))
|
|
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 view 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 fully spent.
|
|
func mergeUtxoView(viewA *blockchain.UtxoViewpoint, viewB *blockchain.UtxoViewpoint) {
|
|
viewAEntries := viewA.Entries()
|
|
for hash, entryB := range viewB.Entries() {
|
|
if entryA, exists := viewAEntries[hash]; !exists ||
|
|
entryA == nil || entryA.IsFullySpent() {
|
|
viewAEntries[hash] = 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 to use as extranonces. The OP_RETURN pushes 32 bytes.
|
|
func standardCoinbaseOpReturn(height uint32, extraNonce uint64) ([]byte, error) {
|
|
enData := make([]byte, 12)
|
|
binary.LittleEndian.PutUint32(enData[0:4], height)
|
|
binary.LittleEndian.PutUint64(enData[4:12], extraNonce)
|
|
extraNonceScript, err := txscript.GenerateProvablyPruneableOut(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 dcrutil.Address, voters uint16, params *chaincfg.Params) (*dcrutil.Tx, error) {
|
|
tx := wire.NewMsgTx()
|
|
tx.AddTxIn(&wire.TxIn{
|
|
// Coinbase 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: coinbaseScript,
|
|
})
|
|
|
|
// Block one is a special block that might pay out tokens to a ledger.
|
|
if nextBlockHeight == 1 && len(params.BlockOneLedger) != 0 {
|
|
for _, payout := range params.BlockOneLedger {
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: payout.Amount,
|
|
Version: payout.ScriptVersion,
|
|
PkScript: payout.Script,
|
|
})
|
|
}
|
|
|
|
tx.TxIn[0].ValueIn = params.BlockOneSubsidy()
|
|
|
|
return dcrutil.NewTx(tx), nil
|
|
}
|
|
|
|
// Create a coinbase with correct block subsidy and extranonce.
|
|
workSubsidy := subsidyCache.CalcWorkSubsidy(nextBlockHeight, voters)
|
|
treasurySubsidy := subsidyCache.CalcTreasurySubsidy(nextBlockHeight, voters)
|
|
|
|
// Treasury output.
|
|
if params.BlockTaxProportion > 0 {
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: treasurySubsidy,
|
|
PkScript: params.OrganizationPkScript,
|
|
})
|
|
} else {
|
|
// Treasury disabled.
|
|
scriptBuilder := txscript.NewScriptBuilder()
|
|
trueScript, err := scriptBuilder.AddOp(txscript.OP_TRUE).Script()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: treasurySubsidy,
|
|
PkScript: trueScript,
|
|
})
|
|
}
|
|
// Extranonce.
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: 0,
|
|
PkScript: opReturnPkScript,
|
|
})
|
|
// ValueIn.
|
|
tx.TxIn[0].ValueIn = workSubsidy + treasurySubsidy
|
|
|
|
// 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.
|
|
var pksSubsidy []byte
|
|
if addr != nil {
|
|
var err error
|
|
pksSubsidy, err = txscript.PayToAddrScript(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
var err error
|
|
scriptBuilder := txscript.NewScriptBuilder()
|
|
pksSubsidy, err = scriptBuilder.AddOp(txscript.OP_TRUE).Script()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// Subsidy paid to miner.
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: workSubsidy,
|
|
PkScript: pksSubsidy,
|
|
})
|
|
|
|
return dcrutil.NewTx(tx), 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) {
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
originIndex := txIn.PreviousOutPoint.Index
|
|
entry := utxoView.LookupEntry(originHash)
|
|
if entry != nil {
|
|
entry.SpendOutput(originIndex)
|
|
}
|
|
}
|
|
|
|
utxoView.AddTxOuts(tx, height, wire.NullBlockIndex)
|
|
}
|
|
|
|
// 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 map[chainhash.Hash]*txPrioItem) {
|
|
if deps == nil {
|
|
return
|
|
}
|
|
|
|
for _, item := range deps {
|
|
minrLog.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 medianAdjustedTime(best *blockchain.BestState, timeSource blockchain.MedianTimeSource) 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.
|
|
newTimestamp := 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(-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 maybeInsertStakeTx(bm *blockManager, stx *dcrutil.Tx, treeValid bool) bool {
|
|
missingInput := false
|
|
|
|
view, err := bm.cfg.Chain.FetchUtxoView(stx, treeValid)
|
|
if err != nil {
|
|
minrLog.Warnf("Unable to fetch transaction store for "+
|
|
"stx %s: %v", stx.Hash(), err)
|
|
return false
|
|
}
|
|
mstx := stx.MsgTx()
|
|
isSSGen := stake.IsSSGen(mstx)
|
|
for i, txIn := range mstx.TxIn {
|
|
// Evaluate if this is a stakebase input or not. If it
|
|
// is, continue without evaluation of the input.
|
|
// if isStakeBase
|
|
if isSSGen && (i == 0) {
|
|
txIn.BlockHeight = wire.NullBlockHeight
|
|
txIn.BlockIndex = wire.NullBlockIndex
|
|
|
|
continue
|
|
}
|
|
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
utxIn := view.LookupEntry(originHash)
|
|
if utxIn == nil {
|
|
missingInput = true
|
|
break
|
|
} else {
|
|
originIdx := txIn.PreviousOutPoint.Index
|
|
txIn.ValueIn = utxIn.AmountByIndex(originIdx)
|
|
txIn.BlockHeight = uint32(utxIn.BlockHeight())
|
|
txIn.BlockIndex = utxIn.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 handleTooFewVoters(subsidyCache *standalone.SubsidyCache, nextHeight int64, miningAddress dcrutil.Address, bm *blockManager) (*BlockTemplate, error) {
|
|
timeSource := bm.cfg.TimeSource
|
|
stakeValidationHeight := bm.cfg.ChainParams.StakeValidationHeight
|
|
|
|
// Handle not enough voters being present if we're set to mine aggressively
|
|
// (default behavior).
|
|
best := bm.cfg.Chain.BestSnapshot()
|
|
if nextHeight >= stakeValidationHeight && bm.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 := bm.cfg.Chain.BlockByHash(&best.Hash)
|
|
if err != nil {
|
|
str := fmt.Sprintf("unable to get tip block %s", best.PrevHash)
|
|
return nil, miningRuleError(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.
|
|
rand, err := wire.RandomUint64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
coinbaseScript := make([]byte, len(coinbaseFlags)+2)
|
|
copy(coinbaseScript[2:], coinbaseFlags)
|
|
opReturnPkScript, err := standardCoinbaseOpReturn(tipHeader.Height, rand)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
coinbaseTx, err := createCoinbaseTx(subsidyCache, coinbaseScript,
|
|
opReturnPkScript, topBlock.Height(), miningAddress,
|
|
tipHeader.Voters, bm.cfg.ChainParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block.AddTransaction(coinbaseTx.MsgTx())
|
|
|
|
// Copy all of the stake transactions over.
|
|
for _, stx := range topBlock.STransactions() {
|
|
block.AddSTransaction(stx.MsgTx())
|
|
}
|
|
|
|
// Set a fresh timestamp.
|
|
ts := medianAdjustedTime(best, timeSource)
|
|
block.Header.Timestamp = ts
|
|
|
|
// If we're on testnet, the time since this last block listed as the
|
|
// parent must be taken into consideration.
|
|
if bm.cfg.ChainParams.ReduceMinDifficulty {
|
|
parentHash := topBlock.MsgBlock().Header.PrevBlock
|
|
|
|
requiredDifficulty, err :=
|
|
bm.CalcNextRequiredDiffNode(&parentHash, ts)
|
|
if err != nil {
|
|
return nil, miningRuleError(ErrGettingDifficulty,
|
|
err.Error())
|
|
}
|
|
|
|
block.Header.Bits = requiredDifficulty
|
|
}
|
|
|
|
// 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 := bm.cfg.Chain.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 := bm.cfg.Chain.FetchUtxoViewParentTemplate(&block)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to fetch inputs when making new "+
|
|
"block template: %v", err)
|
|
return nil, miningRuleError(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, miningRuleError(ErrCalcCommitmentRoot, str)
|
|
}
|
|
} else {
|
|
cmtRoot = standalone.CalcTxTreeMerkleRoot(block.STransactions)
|
|
}
|
|
header.StakeRoot = cmtRoot
|
|
|
|
// Make sure the block validates.
|
|
btBlock := dcrutil.NewBlockDeepCopyCoinbase(&block)
|
|
err = bm.cfg.Chain.CheckConnectBlockTemplate(btBlock)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to check template: %v while "+
|
|
"constructing a new parent", err.Error())
|
|
return nil, miningRuleError(ErrCheckConnectBlock, str)
|
|
}
|
|
|
|
return bt, nil
|
|
}
|
|
|
|
bmgrLog.Debugf("Not enough voters on top block to generate " +
|
|
"new block template")
|
|
|
|
return nil, 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 {
|
|
policy *mining.Policy
|
|
txSource mining.TxSource
|
|
sigCache *txscript.SigCache
|
|
subsidyCache *standalone.SubsidyCache
|
|
chainParams *chaincfg.Params
|
|
chain *blockchain.BlockChain
|
|
blockManager *blockManager
|
|
timeSource blockchain.MedianTimeSource
|
|
}
|
|
|
|
// newBlkTmplGenerator returns a new block template generator for the given
|
|
// policy using transactions from the provided transaction source.
|
|
func newBlkTmplGenerator(policy *mining.Policy, txSource mining.TxSource,
|
|
timeSource blockchain.MedianTimeSource, sigCache *txscript.SigCache,
|
|
subsidyCache *standalone.SubsidyCache, chainParams *chaincfg.Params,
|
|
chain *blockchain.BlockChain,
|
|
blockManager *blockManager) *BlkTmplGenerator {
|
|
|
|
return &BlkTmplGenerator{
|
|
policy: policy,
|
|
txSource: txSource,
|
|
sigCache: sigCache,
|
|
subsidyCache: subsidyCache,
|
|
chainParams: chainParams,
|
|
chain: chain,
|
|
blockManager: blockManager,
|
|
timeSource: timeSource,
|
|
}
|
|
}
|
|
|
|
// 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 several
|
|
// factors. First, each transaction has a priority calculated based on its
|
|
// value, age of inputs, and size. Transactions which consist of larger
|
|
// amounts, older inputs, and small sizes have the highest priority. Second, a
|
|
// fee per kilobyte is calculated for each transaction. 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 either
|
|
// prioritizes based on the priority (then fee per kilobyte) or the fee per
|
|
// kilobyte (then priority) depending on whether or not the BlockPrioritySize
|
|
// policy setting allots space for high-priority transactions. 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.
|
|
//
|
|
// Once the high-priority area (if configured) has been filled with
|
|
// transactions, or the priority falls below what is considered high-priority,
|
|
// the priority queue is updated to prioritize by fees per kilobyte (then
|
|
// priority).
|
|
//
|
|
// When the fees per kilobyte drop below the TxMinFreeFee policy setting, the
|
|
// transaction will be skipped unless the BlockMinSize policy setting is
|
|
// nonzero, in which case the block will be filled with the low-fee/free
|
|
// transactions until the block size reaches that minimum size.
|
|
//
|
|
// 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.BlockPrioritySize
|
|
// | High-priority Transactions | | |
|
|
// | | | |
|
|
// |-----------------------------------| | --
|
|
// | | |
|
|
// | | |
|
|
// | | |--- (policy.BlockMaxSize) / 2
|
|
// | Transactions prioritized by fee | |
|
|
// | until <= policy.TxMinFreeFee | |
|
|
// | | |
|
|
// | | |
|
|
// | | |
|
|
// |-----------------------------------| |
|
|
// | Low-fee/Non high-priority (free) | |
|
|
// | transactions (while block size | |
|
|
// | <= policy.BlockMinSize) | |
|
|
// ----------------------------------- --
|
|
//
|
|
// Which also includes a stake tree that looks like the following:
|
|
//
|
|
// ----------------------------------- -- --
|
|
// | | | |
|
|
// | Votes | | | --- >= (chaincfg.TicketsPerBlock/2) + 1
|
|
// | | | |
|
|
// |-----------------------------------| | --
|
|
// | | | |
|
|
// | Tickets | | | --- <= chaincfg.MaxFreshStakePerBlock
|
|
// | | | |
|
|
// |-----------------------------------| | --
|
|
// | | |
|
|
// | Revocations | |
|
|
// | | |
|
|
// ----------------------------------- --
|
|
//
|
|
// This function returns nil, nil if there are not enough voters on any of
|
|
// the current top blocks to create a new block template.
|
|
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress dcrutil.Address) (*BlockTemplate, error) {
|
|
// All transaction scripts are verified using the more strict standard
|
|
// flags.
|
|
scriptFlags, err := standardScriptVerifyFlags(g.chain)
|
|
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.chain.BestSnapshot()
|
|
prevHash := best.Hash
|
|
nextBlockHeight := best.Height + 1
|
|
stakeValidationHeight := g.chainParams.StakeValidationHeight
|
|
|
|
if nextBlockHeight >= stakeValidationHeight {
|
|
// Obtain the entire generation of blocks stemming from this parent.
|
|
children, err := g.blockManager.TipGeneration()
|
|
if err != nil {
|
|
return nil, miningRuleError(ErrFailedToGetGeneration, err.Error())
|
|
}
|
|
|
|
// 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.txSource, prevHash, children,
|
|
g.chainParams)
|
|
if len(eligibleParents) == 0 {
|
|
minrLog.Debugf("Too few voters found on any HEAD block, " +
|
|
"recycling a parent block to mine on")
|
|
return handleTooFewVoters(g.subsidyCache, nextBlockHeight,
|
|
payToAddress, g.blockManager)
|
|
}
|
|
|
|
minrLog.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 we need
|
|
// to.
|
|
if eligibleParents[0] != prevHash {
|
|
for i := range eligibleParents {
|
|
newHead := &eligibleParents[i]
|
|
err := g.blockManager.ForceReorganization(prevHash, *newHead)
|
|
if err != nil {
|
|
minrLog.Errorf("failed to reorganize to new parent: %v", err)
|
|
continue
|
|
}
|
|
|
|
// Check to make sure we actually have the transactions
|
|
// (votes) we need in the mempool.
|
|
voteHashes := g.txSource.VoteHashesForBlock(newHead)
|
|
if len(voteHashes) == 0 {
|
|
return nil, fmt.Errorf("no vote metadata for block %v",
|
|
newHead)
|
|
}
|
|
|
|
if exist := g.txSource.HaveAllTransactions(voteHashes); !exist {
|
|
continue
|
|
} else {
|
|
prevHash = *newHead
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
sourceTxns := g.txSource.MiningDescs()
|
|
sortedByFee := g.policy.BlockPrioritySize == 0
|
|
lessFunc := txPQByStakeAndFeeAndThenPriority
|
|
if sortedByFee {
|
|
lessFunc = txPQByStakeAndFee
|
|
}
|
|
priorityQueue := newTxPriorityQueue(len(sourceTxns), lessFunc)
|
|
|
|
// 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 := blockchain.NewUtxoViewpoint()
|
|
|
|
// dependers is used to track transactions which depend on another
|
|
// transaction in the source pool. This, in conjunction with the
|
|
// dependsOn map kept with each dependent transaction helps quickly
|
|
// determine which dependent transactions are now eligible for inclusion
|
|
// in the block once each transaction has been included.
|
|
dependers := make(map[chainhash.Hash]map[chainhash.Hash]*txPrioItem)
|
|
|
|
// 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
|
|
|
|
minrLog.Debugf("Considering %d transactions for inclusion to new block",
|
|
len(sourceTxns))
|
|
knownDisapproved := g.txSource.IsRegTxTreeKnownDisapproved(&prevHash)
|
|
|
|
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) {
|
|
minrLog.Tracef("Skipping coinbase tx %s", tx.Hash())
|
|
continue
|
|
}
|
|
if !blockchain.IsFinalizedTransaction(tx, nextBlockHeight,
|
|
best.MedianTime) {
|
|
minrLog.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)) {
|
|
minrLog.Tracef("Skipping ssgen tx %s because it does "+
|
|
"not vote on the correct block", tx.Hash())
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Fetch all of the utxos referenced by the this transaction.
|
|
// NOTE: This intentionally does not fetch inputs from the
|
|
// mempool since a transaction which depends on other
|
|
// transactions in the mempool must come after those
|
|
utxos, err := g.chain.FetchUtxoView(tx, !knownDisapproved)
|
|
if err != nil {
|
|
minrLog.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{tx: txDesc.Tx, 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 isSSGen && (i == 0) {
|
|
continue
|
|
}
|
|
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
originIndex := txIn.PreviousOutPoint.Index
|
|
utxoEntry := utxos.LookupEntry(originHash)
|
|
if utxoEntry == nil || utxoEntry.IsOutputSpent(originIndex) {
|
|
if !g.txSource.HaveTransaction(originHash) {
|
|
minrLog.Tracef("Skipping tx %s because "+
|
|
"it references unspent output "+
|
|
"%s which is not available",
|
|
tx.Hash(), txIn.PreviousOutPoint)
|
|
continue mempoolLoop
|
|
}
|
|
|
|
// The transaction is referencing another
|
|
// transaction in the source pool, so setup an
|
|
// ordering dependency.
|
|
deps, exists := dependers[*originHash]
|
|
if !exists {
|
|
deps = make(map[chainhash.Hash]*txPrioItem)
|
|
dependers[*originHash] = deps
|
|
}
|
|
deps[*prioItem.tx.Hash()] = prioItem
|
|
if prioItem.dependsOn == nil {
|
|
prioItem.dependsOn = make(
|
|
map[chainhash.Hash]struct{})
|
|
}
|
|
prioItem.dependsOn[*originHash] = struct{}{}
|
|
|
|
// Skip the check below. We already know the
|
|
// referenced transaction is available.
|
|
continue
|
|
}
|
|
}
|
|
|
|
// 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 = mining.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.
|
|
txSize := tx.MsgTx().SerializeSize()
|
|
prioItem.feePerKB = (float64(txDesc.Fee) * float64(kilobyte)) /
|
|
float64(txSize)
|
|
prioItem.fee = txDesc.Fee
|
|
|
|
// Add the transaction to the priority queue to mark it ready
|
|
// for inclusion in the block unless it has dependencies.
|
|
if prioItem.dependsOn == nil {
|
|
heap.Push(priorityQueue, prioItem)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
minrLog.Tracef("Priority queue len %d, dependers len %d",
|
|
priorityQueue.Len(), len(dependers))
|
|
|
|
// 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
|
|
|
|
foundWinningTickets := make(map[chainhash.Hash]bool, len(best.NextWinningTickets))
|
|
for _, ticketHash := range best.NextWinningTickets {
|
|
foundWinningTickets[ticketHash] = false
|
|
}
|
|
|
|
// Choose which transactions make it into the block.
|
|
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.tx
|
|
|
|
// 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
|
|
|
|
// Grab the list of transactions which depend on this one (if any).
|
|
deps := dependers[*tx.Hash()]
|
|
|
|
// Skip if we already have too many SStx.
|
|
if isSStx && (numSStx >=
|
|
int(g.chainParams.MaxFreshStakePerBlock)) {
|
|
minrLog.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 all missed tickets that we've never heard of.
|
|
if isSSRtx {
|
|
ticketHash := &tx.MsgTx().TxIn[0].PreviousOutPoint.Hash
|
|
|
|
if !hashInSlice(*ticketHash, best.MissedTickets) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Enforce maximum block size. Also check for overflow.
|
|
txSize := uint32(tx.MsgTx().SerializeSize())
|
|
blockPlusTxSize := blockSize + txSize
|
|
if blockPlusTxSize < blockSize ||
|
|
blockPlusTxSize >= g.policy.BlockMaxSize {
|
|
minrLog.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)
|
|
continue
|
|
}
|
|
|
|
// Enforce maximum signature operations per block. Also check
|
|
// for overflow.
|
|
numSigOps := int64(blockchain.CountSigOps(tx, false, isSSGen))
|
|
if blockSigOps+numSigOps < blockSigOps ||
|
|
blockSigOps+numSigOps > blockchain.MaxSigOpsPerBlock {
|
|
minrLog.Tracef("Skipping tx %s because it would "+
|
|
"exceed the maximum sigops per block", tx.Hash())
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
|
|
// This isn't very expensive, but we do this check a number of times.
|
|
// Consider caching this in the mempool in the future. - Decred
|
|
numP2SHSigOps, err := blockchain.CountP2SHSigOps(tx, false,
|
|
isSSGen, blockUtxos)
|
|
if err != nil {
|
|
minrLog.Tracef("Skipping tx %s due to error in "+
|
|
"CountP2SHSigOps: %v", tx.Hash(), err)
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
numSigOps += int64(numP2SHSigOps)
|
|
if blockSigOps+numSigOps < blockSigOps ||
|
|
blockSigOps+numSigOps > blockchain.MaxSigOpsPerBlock {
|
|
minrLog.Tracef("Skipping tx %s because it would "+
|
|
"exceed the maximum sigops per block (p2sh)",
|
|
tx.Hash())
|
|
logSkippedDeps(tx, deps)
|
|
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 free transactions once the block is larger than the
|
|
// minimum block size, except for stake transactions.
|
|
if sortedByFee &&
|
|
(prioItem.feePerKB < float64(g.policy.TxMinFreeFee)) &&
|
|
(tx.Tree() != wire.TxTreeStake) &&
|
|
(blockPlusTxSize >= g.policy.BlockMinSize) {
|
|
|
|
minrLog.Tracef("Skipping tx %s with feePerKB %.2f "+
|
|
"< TxMinFreeFee %d and block size %d >= "+
|
|
"minBlockSize %d", tx.Hash(), prioItem.feePerKB,
|
|
g.policy.TxMinFreeFee, blockPlusTxSize,
|
|
g.policy.BlockMinSize)
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
|
|
// Prioritize by fee per kilobyte once the block is larger than
|
|
// the priority size or there are no more high-priority
|
|
// transactions.
|
|
if !sortedByFee && (blockPlusTxSize >= g.policy.BlockPrioritySize ||
|
|
prioItem.priority <= mining.MinHighPriority) {
|
|
|
|
minrLog.Tracef("Switching to sort by fees per "+
|
|
"kilobyte blockSize %d >= BlockPrioritySize "+
|
|
"%d || priority %.2f <= minHighPriority %.2f",
|
|
blockPlusTxSize, g.policy.BlockPrioritySize,
|
|
prioItem.priority, mining.MinHighPriority)
|
|
|
|
sortedByFee = true
|
|
priorityQueue.SetLessFunc(txPQByStakeAndFee)
|
|
|
|
// Put the transaction back into the priority queue and
|
|
// skip it so it is re-prioritized by fees if it won't
|
|
// fit into the high-priority section or the priority is
|
|
// too low. Otherwise this transaction will be the
|
|
// final one in the high-priority section, so just fall
|
|
// though to the code below so it is added now.
|
|
if blockPlusTxSize > g.policy.BlockPrioritySize ||
|
|
prioItem.priority < mining.MinHighPriority {
|
|
|
|
heap.Push(priorityQueue, prioItem)
|
|
continue
|
|
}
|
|
}
|
|
|
|
// 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 = blockchain.CheckTransactionInputs(g.subsidyCache, tx,
|
|
nextBlockHeight, blockUtxos, false, g.chainParams)
|
|
if err != nil {
|
|
minrLog.Tracef("Skipping tx %s due to error in "+
|
|
"CheckTransactionInputs: %v", tx.Hash(), err)
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
err = blockchain.ValidateTransactionScripts(tx, blockUtxos,
|
|
scriptFlags, g.sigCache)
|
|
if err != nil {
|
|
minrLog.Tracef("Skipping tx %s due to error in "+
|
|
"ValidateTransactionScripts: %v", tx.Hash(), err)
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
|
|
// 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, tx, nextBlockHeight)
|
|
|
|
// Add the transaction to the block, increment counters, and
|
|
// save the fees and signature operation counts to the block
|
|
// template.
|
|
blockTxns = append(blockTxns, tx)
|
|
blockSize += txSize
|
|
blockSigOps += numSigOps
|
|
|
|
// Accumulate the SStxs in the block, because only a certain number
|
|
// are allowed.
|
|
if isSStx {
|
|
numSStx++
|
|
}
|
|
if isSSGen {
|
|
foundWinningTickets[tx.MsgTx().TxIn[1].PreviousOutPoint.Hash] = true
|
|
}
|
|
|
|
txFeesMap[*tx.Hash()] = prioItem.fee
|
|
txSigOpCountsMap[*tx.Hash()] = numSigOps
|
|
|
|
minrLog.Tracef("Adding tx %s (priority %.2f, feePerKB %.2f)",
|
|
prioItem.tx.Hash(), prioItem.priority, prioItem.feePerKB)
|
|
|
|
// Add transactions which depend on this one (and also do not
|
|
// have any other unsatisfied dependencies) to the priority
|
|
// queue.
|
|
for _, item := range deps {
|
|
// Add the transaction to the priority queue if there
|
|
// are no more dependencies after this one.
|
|
delete(item.dependsOn, *tx.Hash())
|
|
if len(item.dependsOn) == 0 {
|
|
heap.Push(priorityQueue, item)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build tx list for stake tx.
|
|
blockTxnsStake := make([]*dcrutil.Tx, 0, len(blockTxns))
|
|
|
|
// Stake tx ordering in stake tree:
|
|
// 1. SSGen (votes).
|
|
// 2. SStx (fresh stake tickets).
|
|
// 3. SSRtx (revocations for missed tickets).
|
|
|
|
// Get the block votes (SSGen tx) and store them and their number.
|
|
voters := 0
|
|
var voteBitsVoters []uint16
|
|
|
|
// Have SSGen should be present after this height.
|
|
if nextBlockHeight >= stakeValidationHeight {
|
|
for _, tx := range blockTxns {
|
|
msgTx := tx.MsgTx()
|
|
|
|
if stake.IsSSGen(msgTx) {
|
|
txCopy := dcrutil.NewTxDeepTxIns(msgTx)
|
|
if maybeInsertStakeTx(g.blockManager, txCopy, !knownDisapproved) {
|
|
vb := stake.SSGenVoteBits(txCopy.MsgTx())
|
|
voteBitsVoters = append(voteBitsVoters, vb)
|
|
blockTxnsStake = append(blockTxnsStake, txCopy)
|
|
voters++
|
|
}
|
|
}
|
|
|
|
// Don't let this overflow, although probably it's impossible.
|
|
if voters >= math.MaxUint16 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
if votebits == uint16(0x0000) {
|
|
// In the event TxTreeRegular is disabled, we need to remove all tx
|
|
// in the current block that depend on tx from the TxTreeRegular of
|
|
// the previous block.
|
|
// DECRED WARNING: The ideal behaviour should also be that we re-add
|
|
// all tx that we just removed from the previous block into our
|
|
// current block template. Right now this code fails to do that;
|
|
// these tx will then be included in the next block, which isn't
|
|
// catastrophic but is kind of buggy.
|
|
|
|
// Retrieve the current top block, whose TxTreeRegular was voted
|
|
// out.
|
|
topBlock, err := g.chain.BlockByHash(&prevHash)
|
|
if err != nil {
|
|
str := fmt.Sprintf("unable to get tip block %s", prevHash)
|
|
return nil, miningRuleError(ErrGetTopBlock, str)
|
|
}
|
|
topBlockRegTx := topBlock.Transactions()
|
|
|
|
tempBlockTxns := make([]*dcrutil.Tx, 0, len(sourceTxns))
|
|
for _, tx := range blockTxns {
|
|
if tx.Tree() == wire.TxTreeRegular {
|
|
// Go through all the inputs and check to see if this mempool
|
|
// tx uses outputs from the parent block. This loop is
|
|
// probably very expensive.
|
|
isValid := true
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
for _, parentTx := range topBlockRegTx {
|
|
if txIn.PreviousOutPoint.Hash.IsEqual(
|
|
parentTx.Hash()) {
|
|
isValid = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if isValid {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx.MsgTx())
|
|
tempBlockTxns = append(tempBlockTxns, txCopy)
|
|
}
|
|
} else {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx.MsgTx())
|
|
tempBlockTxns = append(tempBlockTxns, txCopy)
|
|
}
|
|
}
|
|
|
|
// Replace blockTxns with the pruned list of valid mempool tx.
|
|
blockTxns = tempBlockTxns
|
|
}
|
|
}
|
|
|
|
// 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(msgTx)
|
|
if maybeInsertStakeTx(g.blockManager, txCopy, !knownDisapproved) {
|
|
blockTxnsStake = append(blockTxnsStake, txCopy)
|
|
freshStake++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't let this overflow.
|
|
if freshStake >= int(g.chainParams.MaxFreshStakePerBlock) {
|
|
break
|
|
}
|
|
}
|
|
|
|
// 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(msgTx)
|
|
if maybeInsertStakeTx(g.blockManager, txCopy, !knownDisapproved) {
|
|
blockTxnsStake = append(blockTxnsStake, txCopy)
|
|
revocations++
|
|
}
|
|
}
|
|
|
|
// Don't let this overflow.
|
|
if revocations >= math.MaxUint8 {
|
|
break
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
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.
|
|
rand, err := wire.RandomUint64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opReturnPkScript, err := standardCoinbaseOpReturn(uint32(nextBlockHeight),
|
|
rand)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
coinbaseTx, err := createCoinbaseTx(g.subsidyCache,
|
|
coinbaseScript,
|
|
opReturnPkScript,
|
|
nextBlockHeight,
|
|
payToAddress,
|
|
uint16(voters),
|
|
g.chainParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
coinbaseTx.SetTree(wire.TxTreeRegular) // Coinbase only in regular tx tree
|
|
|
|
numCoinbaseSigOps := int64(blockchain.CountSigOps(coinbaseTx, true, false))
|
|
blockSize += uint32(coinbaseTx.MsgTx().SerializeSize())
|
|
blockSigOps += numCoinbaseSigOps
|
|
txFeesMap[*coinbaseTx.Hash()] = 0
|
|
txSigOpCountsMap[*coinbaseTx.Hash()] = numCoinbaseSigOps
|
|
|
|
// 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 {
|
|
minrLog.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)
|
|
}
|
|
|
|
txSigOpCounts = append(txSigOpCounts, numCoinbaseSigOps)
|
|
|
|
// If we're greater than or equal to stake validation height, scale the
|
|
// fees according to the number of voters.
|
|
totalFees *= int64(voters)
|
|
totalFees /= int64(g.chainParams.TicketsPerBlock)
|
|
|
|
// 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))))
|
|
coinbaseTx.MsgTx().TxOut[2].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 := medianAdjustedTime(best, g.timeSource)
|
|
reqDifficulty, err := g.chain.CalcNextRequiredDifficulty(ts)
|
|
if err != nil {
|
|
return nil, miningRuleError(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.chainParams.TicketsPerBlock / 2) + 1)
|
|
if nextBlockHeight >= stakeValidationHeight &&
|
|
voters < minimumVotesRequired {
|
|
minrLog.Warnf("incongruent number of voters in mempool " +
|
|
"vs mempool.voters; not enough voters found")
|
|
return handleTooFewVoters(g.subsidyCache, nextBlockHeight, payToAddress,
|
|
g.blockManager)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
utxs, err := g.chain.FetchUtxoView(tx, !knownDisapproved)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to fetch input utxs for tx %v: %s",
|
|
tx.Hash(), err.Error())
|
|
return nil, miningRuleError(ErrFetchTxStore, str)
|
|
}
|
|
|
|
// Copy the transaction and swap the pointer.
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx.MsgTx())
|
|
blockTxnsRegular[i] = txCopy
|
|
tx = txCopy
|
|
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
utx := utxs.LookupEntry(originHash)
|
|
if utx == nil {
|
|
// Set a flag with the index so we can properly set
|
|
// the fraud proof below.
|
|
txIn.BlockIndex = wire.NullBlockIndex
|
|
} else {
|
|
originIdx := txIn.PreviousOutPoint.Index
|
|
txIn.ValueIn = utx.AmountByIndex(originIdx)
|
|
txIn.BlockHeight = uint32(utx.BlockHeight())
|
|
txIn.BlockIndex = utx.BlockIndex()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fill in locally referenced inputs.
|
|
for i, tx := range blockTxnsRegular {
|
|
// Skip coinbase.
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
|
|
// Copy the transaction and swap the pointer.
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx.MsgTx())
|
|
blockTxnsRegular[i] = txCopy
|
|
tx = txCopy
|
|
|
|
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.
|
|
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, miningRuleError(ErrFraudProofIndex, str)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Choose the block version to generate based on the network.
|
|
blockVersion := int32(generatedBlockVersion)
|
|
if g.chainParams.Net != wire.MainNet && g.chainParams.Net != wire.SimNet {
|
|
blockVersion = generatedBlockVersionTest
|
|
}
|
|
|
|
// Figure out stake version.
|
|
generatedStakeVersion, err := g.chain.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, miningRuleError(ErrTransactionAppend, err.Error())
|
|
}
|
|
}
|
|
|
|
for _, tx := range blockTxnsStake {
|
|
if err := msgBlock.AddSTransaction(tx.MsgTx()); err != nil {
|
|
return nil, miningRuleError(ErrTransactionAppend, err.Error())
|
|
}
|
|
}
|
|
|
|
// Calculate the merkle root depending on the result of the header
|
|
// commitments agenda vote.
|
|
hdrCmtActive, err := g.chain.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, miningRuleError(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.chain.CheckConnectBlockTemplate(block)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to do final check for check connect "+
|
|
"block when making new block template: %v",
|
|
err.Error())
|
|
return nil, miningRuleError(ErrCheckConnectBlock, str)
|
|
}
|
|
|
|
minrLog.Debugf("Created new block template (%d transactions, %d "+
|
|
"stake transactions, %d in fees, %d signature operations, "+
|
|
"%d bytes, target difficulty %064x, stake difficulty %v)",
|
|
len(msgBlock.Transactions), len(msgBlock.STransactions),
|
|
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 header of the passed block 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. Finally, it will update the target difficulty if needed
|
|
// based on the new time for the test networks since their target difficulty can
|
|
// change based upon time.
|
|
func (g *BlkTmplGenerator) UpdateBlockTime(header *wire.BlockHeader) error {
|
|
// 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 := medianAdjustedTime(g.chain.BestSnapshot(), g.timeSource)
|
|
header.Timestamp = newTimestamp
|
|
|
|
// If running on a network that requires recalculating the difficulty,
|
|
// do so now.
|
|
if activeNetParams.ReduceMinDifficulty {
|
|
difficulty, err := g.chain.CalcNextRequiredDifficulty(newTimestamp)
|
|
if err != nil {
|
|
return miningRuleError(ErrGettingDifficulty, err.Error())
|
|
}
|
|
header.Bits = difficulty
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// regenEventType represents the type of a template regeneration event message.
|
|
type regenEventType int
|
|
|
|
// Constants for the type of template regeneration event messages.
|
|
const (
|
|
// rtReorgStarted indicates a chain reorganization has been started.
|
|
rtReorgStarted regenEventType = iota
|
|
|
|
// rtReorgDone indicates a chain reorganization has completed.
|
|
rtReorgDone
|
|
|
|
// rtBlockAccepted indicates a new block has been accepted to the block
|
|
// chain which does not necessarily mean it was added to the main chain.
|
|
// That case is rtBlockConnected.
|
|
rtBlockAccepted
|
|
|
|
// rtBlockConnected indicates a new block has been connected to the main
|
|
// chain.
|
|
rtBlockConnected
|
|
|
|
// rtBlockDisconnected indicates the current tip block of the best chain has
|
|
// been disconnected.
|
|
rtBlockDisconnected
|
|
|
|
// rtVote indicates a new vote has been received. It applies to all votes
|
|
// and therefore may or may not be relevant.
|
|
rtVote
|
|
|
|
// rtTemplateUpdated indicates the current template associated with the
|
|
// generator has been updated.
|
|
rtTemplateUpdated
|
|
)
|
|
|
|
// TemplateUpdateReason represents the type of a reason why a template is
|
|
// being updated.
|
|
type TemplateUpdateReason int
|
|
|
|
// Constants for the type of template update reasons.
|
|
const (
|
|
// TURNewParent indicates the associated template has been updated because
|
|
// it builds on a new block as compared to the previous template.
|
|
TURNewParent TemplateUpdateReason = iota
|
|
|
|
// TURNewVotes indicates the associated template has been updated because a
|
|
// new vote for the block it builds on has been received.
|
|
TURNewVotes
|
|
|
|
// TURNewTxns indicates the associated template has been updated because new
|
|
// non-vote transactions are available and have potentially been included.
|
|
TURNewTxns
|
|
|
|
// turUnknown indicates the associated template has either been updated due
|
|
// to an error or cleared for a chain reorg. It is only used internally to
|
|
// the background template generator.
|
|
turUnknown
|
|
)
|
|
|
|
// TemplateNtfn represents a notification of a new template along with the
|
|
// reason it was generated. It is sent to subscribers on the channel obtained
|
|
// from the TemplateSubscription instance returned by Subscribe.
|
|
type TemplateNtfn struct {
|
|
Template *BlockTemplate
|
|
Reason TemplateUpdateReason
|
|
}
|
|
|
|
// templateUpdate defines a type which is used to signal the regen event handler
|
|
// that a new template and relevant error have been associated with the
|
|
// generator.
|
|
type templateUpdate struct {
|
|
template *BlockTemplate
|
|
err error
|
|
}
|
|
|
|
// regenEvent defines an event which will potentially result in regenerating a
|
|
// block template and consists of a regen event type as well as associated data
|
|
// that depends on the type as follows:
|
|
// - rtReorgStarted: nil
|
|
// - rtReorgDone: nil
|
|
// - rtBlockAccepted: *dcrutil.Block
|
|
// - rtBlockConnected: *dcrutil.Block
|
|
// - rtBlockDisconnected: *dcrutil.Block
|
|
// - rtVote: *dcrutil.Tx
|
|
// - rtTemplateUpdated: templateUpdate
|
|
type regenEvent struct {
|
|
reason regenEventType
|
|
value interface{}
|
|
}
|
|
|
|
// BgBlkTmplGenerator provides facilities for asynchronously generating block
|
|
// templates in response to various relevant events and allowing clients to
|
|
// subscribe for updates when new templates are generated as well as access the
|
|
// most recently-generated template in a concurrency-safe manner.
|
|
//
|
|
// An example of some of the events that trigger a new block template to be
|
|
// generated are modifications to the current best chain, receiving relevant
|
|
// votes, and periodic timeouts to allow inclusion of new transactions.
|
|
//
|
|
// The templates are generated based on a given block template generator
|
|
// instance which itself is based on a given mining policy and transaction
|
|
// source. See the NewBlockTemplate method for a detailed description of how
|
|
// the block template is generated.
|
|
//
|
|
// The background generation makes use of three main goroutines -- a regen event
|
|
// queue to allow asynchronous non-blocking signalling, a regen event handler to
|
|
// process the aforementioned queue and react accordingly, and a subscriber
|
|
// notification controller. In addition, the templates themselves are generated
|
|
// in their own goroutines with a cancellable context.
|
|
//
|
|
// A high level overview of the semantics are as follows:
|
|
// - Ignore all vote handling when prior to stake validation height
|
|
// - Generate templates building on the current tip at startup with a fall back
|
|
// to generate a template on its parent if the current tip does not receive
|
|
// enough votes within a timeout
|
|
// - Continue monitoring for votes on any blocks that extend said parent to
|
|
// potentially switch to them and generate a template building on them when
|
|
// possible
|
|
// - Generate new templates building on new best chain tip blocks once they have
|
|
// received the minimum votes after a timeout to provide the additional votes
|
|
// an opportunity to propagate, except when it is an intermediate block in a
|
|
// chain reorganization
|
|
// - In the event the current tip fails to receive the minimum number of
|
|
// required votes, monitor side chain blocks which are siblings of it for
|
|
// votes in order to potentially switch to them and generate a template
|
|
// building on them when possible
|
|
// - Generate new templates on blocks disconnected from the best chain tip,
|
|
// except when it is an intermediate block in a chain reorganization
|
|
// - Generate new templates periodically when there are new regular transactions
|
|
// to include
|
|
// - Bias templates towards building on the first seen block when possible in
|
|
// order to prevent PoW miners from being able to gain an advantage through
|
|
// vote withholding
|
|
// - Schedule retries in the rare event template generation fails
|
|
// - Allow clients to subscribe for updates every time a new template is
|
|
// successfully generated along with a reason why it was generated
|
|
// - Provide direct access to the most-recently generated template
|
|
// - Block while generating new templates that will make the current template
|
|
// stale (e.g. new parent or new votes)
|
|
type BgBlkTmplGenerator struct {
|
|
wg sync.WaitGroup
|
|
|
|
// These fields are provided by the caller when the generator is created and
|
|
// are either independently safe for concurrent access or do not change after
|
|
// initialization.
|
|
//
|
|
// chain is the blockchain instance that is used to build the block and
|
|
// validate the block templates.
|
|
//
|
|
// tg is a block template generator instance that is used to actually create
|
|
// the block templates the background block template generator stores.
|
|
//
|
|
// allowUnsyncedMining indicates block templates should be created even when
|
|
// the chain is not fully synced.
|
|
//
|
|
// maxVotesPerBlock is the maximum number of votes per block and comes from
|
|
// the chain parameters. It is defined separately for convenience.
|
|
//
|
|
// minVotesRequired is the minimum number of votes required for a block to
|
|
// be built on. It is derived from the chain parameters and is defined
|
|
// separately for convenience.
|
|
chain *blockchain.BlockChain
|
|
tg *BlkTmplGenerator
|
|
allowUnsyncedMining bool
|
|
miningAddrs []dcrutil.Address
|
|
maxVotesPerBlock uint16
|
|
minVotesRequired uint16
|
|
|
|
// These fields deal with providing a stream of template updates to
|
|
// subscribers.
|
|
//
|
|
// subscriptions tracks all template update subscriptions. It is protected
|
|
// for concurrent access by subscriptionMtx.
|
|
//
|
|
// notifySubscribers delivers template updates to the separate subscriber
|
|
// notification goroutine so it can in turn asynchronously deliver
|
|
// notifications to all subscribers.
|
|
subscriptionMtx sync.Mutex
|
|
subscriptions map[*TemplateSubscription]struct{}
|
|
notifySubscribers chan *TemplateNtfn
|
|
notifiedParents lru.Cache
|
|
|
|
// These fields deal with the template regeneration event queue. This is
|
|
// implemented as a concurrent queue with immediate passthrough when
|
|
// possible to ensure the order of events is maintained and the related
|
|
// callbacks never block.
|
|
//
|
|
// queueRegenEvent either immediately forwards regen events to the
|
|
// regenEventMsgs channel when it would not block or adds the event to a
|
|
// queue that is processed asynchronously as soon as the receiver becomes
|
|
// available.
|
|
//
|
|
// regenEventMsgs delivers relevant regen events to which the generator
|
|
// reacts to the separate regen goroutine so it can in turn asynchronously
|
|
// process the events and regenerate templates as needed.
|
|
queueRegenEvent chan regenEvent
|
|
regenEventMsgs chan regenEvent
|
|
|
|
// staleTemplateWg is used to allow template retrieval to block callers when
|
|
// a new template that will make the current template stale is being
|
|
// generated. Stale, in this context, means either the parent has changed
|
|
// or there are new votes available.
|
|
staleTemplateWg sync.WaitGroup
|
|
|
|
// These fields track the current best template and are protected by the
|
|
// template mutex. The template will be nil when there is a template error
|
|
// set.
|
|
templateMtx sync.Mutex
|
|
template *BlockTemplate
|
|
templateReason TemplateUpdateReason
|
|
templateErr error
|
|
|
|
// These fields are used to provide the ability to cancel a template that
|
|
// is in the process of being asynchronously generated in favor of
|
|
// generating a new one.
|
|
//
|
|
// cancelTemplate is a function which will cancel the current template that
|
|
// is in the process of being asynchronously generated. It will have no
|
|
// effect if no template generation is in progress. It is protected for
|
|
// concurrent access by cancelTemplateMtx.
|
|
cancelTemplateMtx sync.Mutex
|
|
cancelTemplate func()
|
|
}
|
|
|
|
// newBgBlkTmplGenerator initializes a background block template generator with
|
|
// the provided parameters. The returned instance must be started with the Run
|
|
// method to allowing processing.
|
|
func newBgBlkTmplGenerator(tg *BlkTmplGenerator, addrs []dcrutil.Address, allowUnsynced bool) *BgBlkTmplGenerator {
|
|
return &BgBlkTmplGenerator{
|
|
chain: tg.chain,
|
|
tg: tg,
|
|
allowUnsyncedMining: allowUnsynced,
|
|
miningAddrs: addrs,
|
|
maxVotesPerBlock: tg.chainParams.TicketsPerBlock,
|
|
minVotesRequired: (tg.chainParams.TicketsPerBlock / 2) + 1,
|
|
subscriptions: make(map[*TemplateSubscription]struct{}),
|
|
notifySubscribers: make(chan *TemplateNtfn),
|
|
notifiedParents: lru.NewCache(3),
|
|
queueRegenEvent: make(chan regenEvent),
|
|
regenEventMsgs: make(chan regenEvent),
|
|
cancelTemplate: func() {},
|
|
}
|
|
}
|
|
|
|
// setCurrentTemplate sets the current template and error associated with the
|
|
// background block template generator and notifies the regen event handler
|
|
// about the update.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (g *BgBlkTmplGenerator) setCurrentTemplate(template *BlockTemplate, reason TemplateUpdateReason, err error) {
|
|
g.templateMtx.Lock()
|
|
g.template, g.templateReason, g.templateErr = template, reason, err
|
|
g.templateMtx.Unlock()
|
|
|
|
tplUpdate := templateUpdate{template: template, err: err}
|
|
g.queueRegenEvent <- regenEvent{rtTemplateUpdated, tplUpdate}
|
|
}
|
|
|
|
// currentTemplate returns the current template associated with the background
|
|
// template generator along with the associated reason and error.
|
|
//
|
|
// NOTE: The returned template and block that it contains MUST be treated as
|
|
// immutable since they are shared by all callers.
|
|
//
|
|
// NOTE: The returned template might be nil even if there is no error. It is
|
|
// the responsibility of the caller to properly handle nil templates.
|
|
//
|
|
// This function differs from the exported version in that it also returns the
|
|
// reason associated with the template that is used in notifications.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (g *BgBlkTmplGenerator) currentTemplate() (*BlockTemplate, TemplateUpdateReason, error) {
|
|
g.staleTemplateWg.Wait()
|
|
g.templateMtx.Lock()
|
|
template, reason, err := g.template, g.templateReason, g.templateErr
|
|
g.templateMtx.Unlock()
|
|
return template, reason, err
|
|
}
|
|
|
|
// CurrentTemplate returns the current template associated with the background
|
|
// template generator along with any associated error.
|
|
//
|
|
// NOTE: The returned template and block that it contains MUST be treated as
|
|
// immutable since they are shared by all callers.
|
|
//
|
|
// NOTE: The returned template might be nil even if there is no error. It is
|
|
// the responsibility of the caller to properly handle nil templates.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (g *BgBlkTmplGenerator) CurrentTemplate() (*BlockTemplate, error) {
|
|
template, _, err := g.currentTemplate()
|
|
return template, err
|
|
}
|
|
|
|
// TemplateSubscription defines a subscription to receive block template updates
|
|
// from the background block template generator. The caller must call Stop on
|
|
// the subscription when it is no longer needed to free resources.
|
|
//
|
|
// NOTE: Notifications are dropped to make up for slow receivers to ensure
|
|
// notifications to other subscribers, as well as senders, are not blocked
|
|
// indefinitely. Since templates are typically only generated infrequently and
|
|
// receives must fall several templates behind before new ones are dropped, this
|
|
// should not affect callers in practice, however, if a caller wishes to
|
|
// guarantee that no templates are being dropped, they will need to ensure the
|
|
// channel is always processed quickly.
|
|
type TemplateSubscription struct {
|
|
g *BgBlkTmplGenerator
|
|
privC chan *TemplateNtfn
|
|
}
|
|
|
|
// C returns a channel that produces a stream of block templates as each new
|
|
// template is generated. Successive calls to C return the same channel.
|
|
//
|
|
// NOTE: Notifications are dropped to make up for slow receivers. See the
|
|
// template subscription type documentation for more details.
|
|
func (s *TemplateSubscription) C() <-chan *TemplateNtfn {
|
|
return s.privC
|
|
}
|
|
|
|
// Stop prevents any future template updates from being delivered and
|
|
// unsubscribes the associated subscription.
|
|
//
|
|
// NOTE: The channel is not closed to prevent a read from the channel succeeding
|
|
// incorrectly.
|
|
func (s *TemplateSubscription) Stop() {
|
|
s.g.subscriptionMtx.Lock()
|
|
delete(s.g.subscriptions, s)
|
|
s.g.subscriptionMtx.Unlock()
|
|
}
|
|
|
|
// publishTemplateNtfn sends the provided template notification on the channel
|
|
// associated with the subscription.
|
|
func (s *TemplateSubscription) publishTemplateNtfn(templateNtfn *TemplateNtfn) {
|
|
// Make use of a non-blocking send along with the buffered channel to allow
|
|
// notifications to be dropped to make up for slow receivers.
|
|
select {
|
|
case s.privC <- templateNtfn:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// notifySubscribersHandler updates subscribers with newly created block
|
|
// templates.
|
|
//
|
|
// This must be run as a goroutine.
|
|
func (g *BgBlkTmplGenerator) notifySubscribersHandler(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case templateNtfn := <-g.notifySubscribers:
|
|
if r := g.tg.blockManager.cfg.RpcServer(); r != nil {
|
|
r.ntfnMgr.NotifyWork(templateNtfn)
|
|
}
|
|
|
|
g.subscriptionMtx.Lock()
|
|
for subscription := range g.subscriptions {
|
|
subscription.publishTemplateNtfn(templateNtfn)
|
|
}
|
|
g.subscriptionMtx.Unlock()
|
|
|
|
case <-ctx.Done():
|
|
g.wg.Done()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Subscribe subscribes a client for block template updates. The returned
|
|
// template subscription contains functions to retrieve a channel that produces
|
|
// the stream of block templates and to stop the stream when the caller no
|
|
// longer wishes to receive new templates.
|
|
//
|
|
// The current template associated with the background block template generator,
|
|
// if any, is immediately sent to the returned subscription stream.
|
|
func (g *BgBlkTmplGenerator) Subscribe() *TemplateSubscription {
|
|
// Create the subscription with a buffered channel that is large enough to
|
|
// handle twice the number of templates that can be induced due votes in
|
|
// order to provide a reasonable amount of buffering before dropping
|
|
// notifications due to a slow receiver.
|
|
maxVoteInducedRegens := g.maxVotesPerBlock - g.minVotesRequired + 1
|
|
c := make(chan *TemplateNtfn, maxVoteInducedRegens*2)
|
|
subscription := &TemplateSubscription{
|
|
g: g,
|
|
privC: c,
|
|
}
|
|
g.subscriptionMtx.Lock()
|
|
g.subscriptions[subscription] = struct{}{}
|
|
g.subscriptionMtx.Unlock()
|
|
|
|
// Send existing valid template immediately.
|
|
template, reason, err := g.currentTemplate()
|
|
if err == nil && template != nil {
|
|
subscription.publishTemplateNtfn(&TemplateNtfn{template, reason})
|
|
}
|
|
|
|
return subscription
|
|
}
|
|
|
|
// regenQueueHandler immediately forwards items from the regen event queue
|
|
// channel to the regen event messages channel when it would not block or adds
|
|
// the event to an internal queue to be processed as soon as the receiver
|
|
// becomes available. This ensures that queueing regen events never blocks
|
|
// despite how busy the regen handler might become during a burst of events.
|
|
//
|
|
// This must be run as a goroutine.
|
|
func (g *BgBlkTmplGenerator) regenQueueHandler(ctx context.Context) {
|
|
var q []regenEvent
|
|
var out, dequeue chan<- regenEvent = g.regenEventMsgs, nil
|
|
skipQueue := out
|
|
var next regenEvent
|
|
for {
|
|
select {
|
|
case n := <-g.queueRegenEvent:
|
|
// Either send to destination channel immediately when skipQueue is
|
|
// non-nil (queue is empty) and reader is ready, or append to the
|
|
// queue and send later.
|
|
select {
|
|
case skipQueue <- n:
|
|
default:
|
|
q = append(q, n)
|
|
dequeue = out
|
|
skipQueue = nil
|
|
next = q[0]
|
|
}
|
|
|
|
case dequeue <- next:
|
|
copy(q, q[1:])
|
|
q = q[:len(q)-1]
|
|
if len(q) == 0 {
|
|
dequeue = nil
|
|
skipQueue = out
|
|
} else {
|
|
next = q[0]
|
|
}
|
|
|
|
case <-ctx.Done():
|
|
g.wg.Done()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// regenHandlerState houses the state used in the regen event handler goroutine.
|
|
// It is separated from the background template generator to ensure it is only
|
|
// available within the scope of the goroutine.
|
|
type regenHandlerState struct {
|
|
// isReorganizing indicates the chain is currently undergoing a
|
|
// reorganization and therefore the generator should not attempt to create
|
|
// new templates until the reorganization has completed.
|
|
isReorganizing bool
|
|
|
|
// These fields are used to implement a periodic regeneration timeout that
|
|
// can be reset at any time without needing to create a new one and the
|
|
// associated extra garbage.
|
|
//
|
|
// regenTimer is an underlying timer that is used to implement the timeout.
|
|
//
|
|
// regenChanDrained indicates whether or not the channel for the regen timer
|
|
// has already been read and is used when resetting the timer to ensure the
|
|
// channel is drained when the timer is stopped as described in the timer
|
|
// documentation.
|
|
//
|
|
// lastGeneratedTime specifies the timestamp the current template was
|
|
// generated.
|
|
regenTimer *time.Timer
|
|
regenChanDrained bool
|
|
lastGeneratedTime int64
|
|
|
|
// These fields are used to control the various generation states when a new
|
|
// block that requires votes has been received.
|
|
//
|
|
// awaitingMinVotesHash is selectively set when a new tip block has been
|
|
// received that requires votes until the minimum number of required votes
|
|
// has been received.
|
|
//
|
|
// maxVotesTimeout is selectively enabled once the minimum number of
|
|
// required votes for the current tip block has been received and is
|
|
// disabled once the maximum number of votes has been received. This
|
|
// effectively sets a timeout to give the remaining votes an opportunity to
|
|
// propagate prior to forcing a template with less than the maximum number
|
|
// of votes.
|
|
awaitingMinVotesHash *chainhash.Hash
|
|
maxVotesTimeout <-chan time.Time
|
|
|
|
// These fields are used to handle detection of side chain votes and
|
|
// potentially reorganizing the chain to a variant of the current tip when
|
|
// it is unable to obtain the minimum required votes.
|
|
//
|
|
// awaitingSideChainMinVotes houses the known blocks that build from the
|
|
// same parent as the current tip and will only be selectively populated
|
|
// when none of the current possible tips have the minimum number of
|
|
// required votes.
|
|
//
|
|
// trackSideChainsTimeout is selectively enabled when a new tip block has
|
|
// been received in order to give the minimum number of required votes
|
|
// needed to build a block template on it an opportunity to propagate before
|
|
// attempting to find any other variants that extend the same parent as the
|
|
// current tip with enough votes to force a reorganization. This ensures the
|
|
// first block that is seen is chosen to build templates on so long as it
|
|
// receives the minimum required votes in order to prevent PoW miners from
|
|
// being able to gain an advantage through vote withholding. It is disabled
|
|
// if the minimum number of votes is received prior to the timeout.
|
|
awaitingSideChainMinVotes map[chainhash.Hash]struct{}
|
|
trackSideChainsTimeout <-chan time.Time
|
|
|
|
// failedGenRetryTimeout is selectively enabled in the rare case a template
|
|
// fails to generate so it can be regenerated again after a delay. A
|
|
// template should never fail to generate in practice, however, future code
|
|
// changes might break that assumption and thus it is important to handle
|
|
// the case properly.
|
|
failedGenRetryTimeout <-chan time.Time
|
|
|
|
// These fields track the block and height that the next template to be
|
|
// generated will build on. This may not be the same as the current tip in
|
|
// the case it has not yet received the minimum number of required votes
|
|
// needed to build a template on it.
|
|
//
|
|
// baseBlockHash is the hash of the block the next template to be generated
|
|
// will build on.
|
|
//
|
|
// baseBlockHeight is the height of the block identified by the base block
|
|
// hash.
|
|
baseBlockHash chainhash.Hash
|
|
baseBlockHeight uint32
|
|
}
|
|
|
|
// makeRegenHandlerState returns a regen handler state that is ready to use.
|
|
func makeRegenHandlerState() regenHandlerState {
|
|
regenTimer := time.NewTimer(math.MaxInt64)
|
|
regenTimer.Stop()
|
|
return regenHandlerState{
|
|
regenTimer: regenTimer,
|
|
regenChanDrained: true,
|
|
awaitingSideChainMinVotes: make(map[chainhash.Hash]struct{}),
|
|
}
|
|
}
|
|
|
|
// stopRegenTimer stops the regen timer while ensuring to read from the timer's
|
|
// channel in the case the timer already expired which can happen due to the
|
|
// fact the stop happens in between channel reads. This behavior is well
|
|
// documented in the Timer docs.
|
|
//
|
|
// NOTE: This function must not be called concurrent with any other receives on
|
|
// the timer's channel.
|
|
func (state *regenHandlerState) stopRegenTimer() {
|
|
t := state.regenTimer
|
|
if !t.Stop() && !state.regenChanDrained {
|
|
<-t.C
|
|
}
|
|
state.regenChanDrained = true
|
|
}
|
|
|
|
// resetRegenTimer resets the regen timer to the given duration while ensuring
|
|
// to read from the timer's channel in the case the timer already expired which
|
|
// can happen due to the fact the reset happens in between channel reads. This
|
|
// behavior is well documented in the Timer docs.
|
|
//
|
|
// NOTE: This function must not be called concurrent with any other receives on
|
|
// the timer's channel.
|
|
func (state *regenHandlerState) resetRegenTimer(d time.Duration) {
|
|
state.stopRegenTimer()
|
|
state.regenTimer.Reset(d)
|
|
state.regenChanDrained = false
|
|
}
|
|
|
|
// clearSideChainTracking removes all tracking for minimum required votes on
|
|
// side chain blocks as well as clears the associated timeout that must
|
|
// transpire before said tracking is enabled.
|
|
func (state *regenHandlerState) clearSideChainTracking() {
|
|
for hash := range state.awaitingSideChainMinVotes {
|
|
delete(state.awaitingSideChainMinVotes, hash)
|
|
}
|
|
state.trackSideChainsTimeout = nil
|
|
}
|
|
|
|
// genTemplateAsync cancels any asynchronous block template that is already
|
|
// currently being generated and launches a new goroutine to asynchronously
|
|
// generate a new one with the provided reason. It also handles updating the
|
|
// current template and error associated with the generator with the results in
|
|
// a concurrent safe fashion and, in the case a successful template is
|
|
// generated, notifies the subscription handler goroutine with the new template.
|
|
func (g *BgBlkTmplGenerator) genTemplateAsync(ctx context.Context, reason TemplateUpdateReason) {
|
|
// Cancel any other templates that might currently be in the process of
|
|
// being generated and create a new context that can be cancelled for the
|
|
// new template that is about to be generated.
|
|
g.cancelTemplateMtx.Lock()
|
|
g.cancelTemplate()
|
|
ctx, g.cancelTemplate = context.WithCancel(ctx)
|
|
g.cancelTemplateMtx.Unlock()
|
|
|
|
// Ensure that attempts to retrieve the current template block until the
|
|
// new template is generated when it is because the parent has changed or
|
|
// new votes are available in order to avoid handing out a template that
|
|
// is guaranteed to be stale soon after.
|
|
blockRetrieval := reason == TURNewParent || reason == TURNewVotes
|
|
if blockRetrieval {
|
|
g.staleTemplateWg.Add(1)
|
|
}
|
|
go func(ctx context.Context, reason TemplateUpdateReason, blockRetrieval bool) {
|
|
if blockRetrieval {
|
|
defer g.staleTemplateWg.Done()
|
|
}
|
|
|
|
// Pick a mining address at random and generate a block template that
|
|
// pays to it.
|
|
prng := rand.New(rand.NewSource(time.Now().Unix()))
|
|
payToAddr := g.miningAddrs[prng.Intn(len(g.miningAddrs))]
|
|
template, err := g.tg.NewBlockTemplate(payToAddr)
|
|
// NOTE: err is handled below.
|
|
|
|
// Don't update the state or notify subscribers when the template
|
|
// generation was cancelled.
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
|
|
// Update the current template state with the results and notify
|
|
// subscribed clients of the new template so long as it's valid.
|
|
if err != nil {
|
|
reason = turUnknown
|
|
}
|
|
g.setCurrentTemplate(template, reason, err)
|
|
if err == nil && template != nil {
|
|
// It is possible for a new vote to show up while the template for
|
|
// a new parent is still being generated which causes that template
|
|
// to be canceled in favor of the the new one with the vote. So,
|
|
// ensure the first notification sent for a new parent has that
|
|
// reason.
|
|
header := &template.Block.Header
|
|
if reason == TURNewVotes {
|
|
if !g.notifiedParents.Contains(header.PrevBlock) {
|
|
reason = TURNewParent
|
|
}
|
|
}
|
|
if reason == TURNewParent {
|
|
g.notifiedParents.Add(header.PrevBlock)
|
|
}
|
|
|
|
// Ensure the goroutine exits cleanly during shutdown.
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
|
|
case g.notifySubscribers <- &TemplateNtfn{template, reason}:
|
|
}
|
|
}
|
|
}(ctx, reason, blockRetrieval)
|
|
}
|
|
|
|
// curTplHasNumVotes returns whether or not the current template is valid,
|
|
// builds on the provided hash, and contains the specified number of votes.
|
|
func (g *BgBlkTmplGenerator) curTplHasNumVotes(votedOnHash *chainhash.Hash, numVotes uint16) bool {
|
|
g.templateMtx.Lock()
|
|
template, err := g.template, g.templateErr
|
|
g.templateMtx.Unlock()
|
|
if template == nil || err != nil {
|
|
return false
|
|
}
|
|
if template.Block.Header.PrevBlock != *votedOnHash {
|
|
return false
|
|
}
|
|
return template.Block.Header.Voters == numVotes
|
|
}
|
|
|
|
// numVotesForBlock returns the number of votes on the provided block hash that
|
|
// are known.
|
|
func (g *BgBlkTmplGenerator) numVotesForBlock(votedOnBlock *chainhash.Hash) uint16 {
|
|
return uint16(len(g.tg.txSource.VoteHashesForBlock(votedOnBlock)))
|
|
}
|
|
|
|
// handleBlockConnected handles the rtBlockConnected event by either immediately
|
|
// generating a new template building on the block when it will still be prior
|
|
// to stake validation height or selectively setting up timeouts to give the
|
|
// votes a chance to propagate once the template will be at or after stake
|
|
// validation height.
|
|
//
|
|
// This function is only intended for use by the regen handler goroutine.
|
|
func (g *BgBlkTmplGenerator) handleBlockConnected(ctx context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) {
|
|
// Clear all vote tracking when the current chain tip changes.
|
|
state.awaitingMinVotesHash = nil
|
|
state.clearSideChainTracking()
|
|
|
|
// Nothing more to do if the connected block is not the current chain tip.
|
|
// This can happen in rare cases such as if more than one new block shows up
|
|
// while generating a template. Due to the requirement for votes later in
|
|
// the chain, it should almost never happen in practice once the chain has
|
|
// progressed that far, however, it is required for correctness. It is also
|
|
// worth noting that it happens more frequently earlier in the chain before
|
|
// voting starts, particularly in simulation networks with low difficulty.
|
|
blockHeight := block.MsgBlock().Header.Height
|
|
blockHash := block.Hash()
|
|
if int64(blockHeight) != chainTip.Height || *blockHash != chainTip.Hash {
|
|
return
|
|
}
|
|
|
|
// Generate a new template immediately when it will be prior to stake
|
|
// validation height which means no votes are required.
|
|
newTemplateHeight := blockHeight + 1
|
|
if newTemplateHeight < uint32(g.tg.chainParams.StakeValidationHeight) {
|
|
state.stopRegenTimer()
|
|
state.failedGenRetryTimeout = nil
|
|
state.baseBlockHash = *blockHash
|
|
state.baseBlockHeight = blockHeight
|
|
g.genTemplateAsync(ctx, TURNewParent)
|
|
return
|
|
}
|
|
|
|
// At this point the template will be at or after stake validation height,
|
|
// and therefore requires the inclusion of votes on the previous block to be
|
|
// valid.
|
|
|
|
// Generate a new template immediately when the maximum number of votes
|
|
// for the block are already known.
|
|
numVotes := g.numVotesForBlock(blockHash)
|
|
if numVotes >= g.maxVotesPerBlock {
|
|
state.stopRegenTimer()
|
|
state.failedGenRetryTimeout = nil
|
|
state.baseBlockHash = *blockHash
|
|
state.baseBlockHeight = blockHeight
|
|
g.genTemplateAsync(ctx, TURNewParent)
|
|
return
|
|
}
|
|
|
|
// Update the state so the next template generated will build on the block
|
|
// and set a timeout to give the remaining votes an opportunity to propagate
|
|
// when the minimum number of required votes for the block are already
|
|
// known. This provides a balance between preferring to generate block
|
|
// templates with max votes and not waiting too long before starting work on
|
|
// the next block.
|
|
if numVotes >= g.minVotesRequired {
|
|
state.stopRegenTimer()
|
|
state.failedGenRetryTimeout = nil
|
|
state.baseBlockHash = *blockHash
|
|
state.baseBlockHeight = blockHeight
|
|
state.maxVotesTimeout = time.After(maxVoteTimeoutDuration)
|
|
return
|
|
}
|
|
|
|
// Mark the state as waiting for the minimum number of required votes needed
|
|
// to build a template on the block to be received and set a timeout to give
|
|
// them an opportunity to propagate before attempting to find any other
|
|
// variants that extend the same parent with enough votes to force a
|
|
// reorganization. This ensures the first block that is seen is chosen to
|
|
// build templates on so long as it receives the minimum required votes in
|
|
// order to prevent PoW miners from being able to gain an advantage through
|
|
// vote withholding.
|
|
//
|
|
// Also, the regen timer for the current template is stopped since chances
|
|
// are high that the votes will be received and it is ideal to avoid
|
|
// regenerating a template that will likely be stale shortly. The regen
|
|
// timer is reset after the timeout if needed.
|
|
state.stopRegenTimer()
|
|
state.awaitingMinVotesHash = blockHash
|
|
state.trackSideChainsTimeout = time.After(minVotesTimeoutDuration)
|
|
}
|
|
|
|
// handleBlockDisconnected handles the rtBlockDisconnected event by immediately
|
|
// generating a new template based on the new tip since votes for it are
|
|
// either necessarily already known due to being included in the block being
|
|
// disconnected or not required due to moving before stake validation height.
|
|
//
|
|
// This function is only intended for use by the regen handler goroutine.
|
|
func (g *BgBlkTmplGenerator) handleBlockDisconnected(ctx context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) {
|
|
// Clear all vote tracking when the current chain tip changes.
|
|
state.awaitingMinVotesHash = nil
|
|
state.clearSideChainTracking()
|
|
|
|
// Nothing more to do if the current chain tip is not the block prior to the
|
|
// block that was disconnected. This can happen in rare cases such as when
|
|
// forcing disconnects via block invalidation. In practice, disconnects
|
|
// happen as a result of chain reorganizations and thus this code will not
|
|
// be executed, however, it is required for correctness.
|
|
prevHeight := block.MsgBlock().Header.Height - 1
|
|
prevHash := &block.MsgBlock().Header.PrevBlock
|
|
if int64(prevHeight) != chainTip.Height || *prevHash != chainTip.Hash {
|
|
return
|
|
}
|
|
|
|
// NOTE: The block being disconnected necessarily has votes for the block
|
|
// that is becoming the new tip and they should ideally be extracted here to
|
|
// ensure they are available for use when building the template. However,
|
|
// the underlying template generator currently relies on pulling the votes
|
|
// out of the mempool and performs this task itself. In the future, the
|
|
// template generator should ideally accept the votes to include directly.
|
|
|
|
// Generate a new template building on the new tip.
|
|
state.stopRegenTimer()
|
|
state.failedGenRetryTimeout = nil
|
|
state.baseBlockHash = *prevHash
|
|
state.baseBlockHeight = prevHeight
|
|
g.genTemplateAsync(ctx, TURNewParent)
|
|
}
|
|
|
|
// handleBlockAccepted handles the rtBlockAccepted event by establishing vote
|
|
// tracking for the block when it is a variant that extends the same parent as
|
|
// the current tip, the current tip does not have the minimum number of required
|
|
// votes, and the initial timeout to provide them an opportunity to propagate
|
|
// has already expired.
|
|
//
|
|
// This function is only intended for use by the regen handler goroutine.
|
|
func (g *BgBlkTmplGenerator) handleBlockAccepted(ctx context.Context, state *regenHandlerState, block *dcrutil.Block, chainTip *blockchain.BestState) {
|
|
// Ignore side chain blocks while still waiting for the side chain tracking
|
|
// timeout to expire. This provides a bias towards the first block that is
|
|
// seen in order to prevent PoW miners from being able to gain an advantage
|
|
// through vote withholding.
|
|
if state.trackSideChainsTimeout != nil {
|
|
return
|
|
}
|
|
|
|
// Ignore side chain blocks when building on it would produce a block prior
|
|
// to stake validation height which means no votes are required and
|
|
// therefore no additional handling is necessary.
|
|
blockHeight := block.MsgBlock().Header.Height
|
|
newTemplateHeight := blockHeight + 1
|
|
if newTemplateHeight < uint32(g.tg.chainParams.StakeValidationHeight) {
|
|
return
|
|
}
|
|
|
|
// Ignore side chain blocks when the current tip already has enough votes
|
|
// for a template to be built on it. This ensures the first block that is
|
|
// seen is chosen to build templates on so long as it receives the minimum
|
|
// required votes in order to prevent PoW miners from being able to gain an
|
|
// advantage through vote withholding.
|
|
if state.awaitingMinVotesHash == nil {
|
|
return
|
|
}
|
|
|
|
// Ignore blocks that are prior to the current tip.
|
|
if blockHeight < uint32(chainTip.Height) {
|
|
return
|
|
}
|
|
|
|
// Ignore main chain tip block since it is handled by the connect path.
|
|
blockHash := block.Hash()
|
|
if *blockHash == chainTip.Hash {
|
|
return
|
|
}
|
|
|
|
// Ignore side chain blocks when the current template is already building on
|
|
// the current tip or the accepted block is not a sibling of the current
|
|
// best chain tip.
|
|
alreadyBuildingOnCurTip := state.baseBlockHash == chainTip.Hash
|
|
acceptedPrevHash := &block.MsgBlock().Header.PrevBlock
|
|
if alreadyBuildingOnCurTip || *acceptedPrevHash != chainTip.PrevHash {
|
|
return
|
|
}
|
|
|
|
// Setup tracking for votes on the block.
|
|
state.awaitingSideChainMinVotes[*blockHash] = struct{}{}
|
|
}
|
|
|
|
// handleVote handles the rtVote event by determining if the vote is for a block
|
|
// the current state is monitoring and reacting accordingly. At a high level,
|
|
// this entails either establishing a timeout once the minimum number of
|
|
// required votes for the current tip have been received to provide the
|
|
// remaining votes an opportunity to propagate, regenerating the current
|
|
// template as a result of the vote, or potentially reorganizing the chain to a
|
|
// new tip that has enough votes in the case the current tip is unable to obtain
|
|
// the required votes.
|
|
//
|
|
// This function is only intended for use by the regen handler goroutine.
|
|
func (g *BgBlkTmplGenerator) handleVote(ctx context.Context, state *regenHandlerState, voteTx *dcrutil.Tx, chainTip *blockchain.BestState) {
|
|
votedOnHash, _ := stake.SSGenBlockVotedOn(voteTx.MsgTx())
|
|
|
|
// The awaiting min votes hash is selectively set once a block is connected
|
|
// such that a new template that builds on it will be at or after stake
|
|
// validation height until the minimum number of votes required to build a
|
|
// template are received.
|
|
//
|
|
// Update the state so the next template generated will build on the current
|
|
// tip once at least the minimum number of required votes for it has been
|
|
// received and either set a timeout to give the remaining votes an
|
|
// opportunity to propagate if the maximum number of votes is not already
|
|
// known or generate a new template immediately when they are. This
|
|
// provides a balance between preferring to generate block templates with
|
|
// max votes and not waiting too long before starting work on the next
|
|
// block.
|
|
minVotesHash := state.awaitingMinVotesHash
|
|
if minVotesHash != nil && votedOnHash == *minVotesHash {
|
|
numVotes := g.numVotesForBlock(minVotesHash)
|
|
minrLog.Debugf("Received vote %s for tip block %s (%d total)",
|
|
voteTx.Hash(), minVotesHash, numVotes)
|
|
if numVotes >= g.minVotesRequired {
|
|
// Ensure the next template generated builds on the tip and clear
|
|
// all vote tracking to lock the current tip in now that it
|
|
// has the minimum required votes.
|
|
state.stopRegenTimer()
|
|
state.failedGenRetryTimeout = nil
|
|
state.baseBlockHash = *minVotesHash
|
|
state.baseBlockHeight = uint32(chainTip.Height)
|
|
state.awaitingMinVotesHash = nil
|
|
state.clearSideChainTracking()
|
|
|
|
// Generate a new template immediately when the maximum number of
|
|
// votes for the block are already known.
|
|
if numVotes >= g.maxVotesPerBlock {
|
|
g.genTemplateAsync(ctx, TURNewParent)
|
|
return
|
|
}
|
|
|
|
// Set a timeout to give the remaining votes an opportunity to
|
|
// propagate.
|
|
state.maxVotesTimeout = time.After(maxVoteTimeoutDuration)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Generate a template on new votes for the block the current state is
|
|
// configured to build the next block template on when either the maximum
|
|
// number of votes is received for it or once the minimum number of required
|
|
// votes has been received and the propagation delay timeout that is started
|
|
// upon receipt of said minimum votes has expired.
|
|
//
|
|
// Note that the base block hash is only updated to the current tip once it
|
|
// has received the minimum number of required votes, so this will continue
|
|
// to detect votes for the parent of the current tip prior to the point the
|
|
// new tip has received enough votes.
|
|
//
|
|
// This ensures new templates that include the new votes are generated
|
|
// immediately upon receiving the maximum number of votes as well as any
|
|
// additional votes that arrive after the initial timeout.
|
|
if votedOnHash == state.baseBlockHash {
|
|
// Avoid regenerating the current template if it is already building on
|
|
// the expected block and already has the maximum number of votes.
|
|
if g.curTplHasNumVotes(&votedOnHash, g.maxVotesPerBlock) {
|
|
state.maxVotesTimeout = nil
|
|
return
|
|
}
|
|
|
|
numVotes := g.numVotesForBlock(&votedOnHash)
|
|
minrLog.Debugf("Received vote %s for current template %s (%d total)",
|
|
voteTx.Hash(), votedOnHash, numVotes)
|
|
if numVotes >= g.maxVotesPerBlock || state.maxVotesTimeout == nil {
|
|
// The template needs to be updated due to a new parent the first
|
|
// time it is generated and due to new votes on subsequent votes.
|
|
// The max votes timeout is only non-nil before the first time it is
|
|
// generated.
|
|
tplUpdateReason := TURNewVotes
|
|
if state.maxVotesTimeout != nil {
|
|
tplUpdateReason = TURNewParent
|
|
}
|
|
|
|
// Cancel the max votes timeout (if set).
|
|
state.maxVotesTimeout = nil
|
|
|
|
state.stopRegenTimer()
|
|
state.failedGenRetryTimeout = nil
|
|
g.genTemplateAsync(ctx, tplUpdateReason)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Reorganize to an alternative chain tip when it receives at least the
|
|
// minimum required number of votes in the case the current chain tip does
|
|
// not receive the minimum number of required votes within an initial
|
|
// timeout period.
|
|
//
|
|
// Note that the potential side chain blocks to consider are only populated
|
|
// in the aforementioned case.
|
|
if _, ok := state.awaitingSideChainMinVotes[votedOnHash]; ok {
|
|
numVotes := g.numVotesForBlock(&votedOnHash)
|
|
minrLog.Debugf("Received vote %s for side chain block %s (%d total)",
|
|
voteTx.Hash(), votedOnHash, numVotes)
|
|
if numVotes >= g.minVotesRequired {
|
|
err := g.chain.ForceHeadReorganization(chainTip.Hash, votedOnHash)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Prevent votes on other tip candidates from causing reorg again
|
|
// since the new chain tip has enough votes.
|
|
state.clearSideChainTracking()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// handleTemplateUpdate handles the rtTemplateUpdate event by updating the state
|
|
// accordingly.
|
|
//
|
|
// This function is only intended for use by the regen handler goroutine.
|
|
func (g *BgBlkTmplGenerator) handleTemplateUpdate(state *regenHandlerState, tplUpdate templateUpdate) {
|
|
// Schedule a template regen if it failed to generate for some reason. This
|
|
// should be exceedingly rare in practice.
|
|
if tplUpdate.err != nil && state.failedGenRetryTimeout == nil {
|
|
state.failedGenRetryTimeout = time.After(time.Second)
|
|
return
|
|
}
|
|
if tplUpdate.template == nil {
|
|
return
|
|
}
|
|
|
|
// Ensure the base block details match the template.
|
|
state.baseBlockHash = tplUpdate.template.Block.Header.PrevBlock
|
|
state.baseBlockHeight = tplUpdate.template.Block.Header.Height - 1
|
|
|
|
// Update the state related to template regeneration due to new regular
|
|
// transactions.
|
|
state.lastGeneratedTime = time.Now().Unix()
|
|
state.resetRegenTimer(templateRegenSecs * time.Second)
|
|
}
|
|
|
|
// handleRegenEvent handles all regen events by determining the event reason and
|
|
// reacting accordingly. For example, it calls the appropriate associated event
|
|
// handler for the events that have one and prevents templates from being
|
|
// generating in the middle of reorgs.
|
|
//
|
|
// This function is only intended for use by the regen handler goroutine.
|
|
func (g *BgBlkTmplGenerator) handleRegenEvent(ctx context.Context, state *regenHandlerState, event regenEvent) {
|
|
// Handle chain reorg messages up front since all of the following logic
|
|
// only applies when not in the middle of reorganizing.
|
|
switch event.reason {
|
|
case rtReorgStarted:
|
|
// Ensure that attempts to retrieve the current template block until the
|
|
// new template after the reorg is generated.
|
|
g.staleTemplateWg.Add(1)
|
|
|
|
// Mark the state as reorganizing.
|
|
state.isReorganizing = true
|
|
|
|
// Stop all timeouts and clear all vote tracking.
|
|
state.stopRegenTimer()
|
|
state.failedGenRetryTimeout = nil
|
|
state.awaitingMinVotesHash = nil
|
|
state.maxVotesTimeout = nil
|
|
state.clearSideChainTracking()
|
|
|
|
// Clear the current template and associated base block for the next
|
|
// generated template.
|
|
g.setCurrentTemplate(nil, turUnknown, nil)
|
|
state.baseBlockHash = zeroHash
|
|
state.baseBlockHeight = 0
|
|
return
|
|
|
|
case rtReorgDone:
|
|
state.isReorganizing = false
|
|
|
|
// Treat the tip block as if it was just connected when a reorganize
|
|
// finishes so the existing code paths are run.
|
|
//
|
|
// An error should be impossible here since the request is for the block
|
|
// the chain believes is the current tip which means it must exist.
|
|
chainTip := g.chain.BestSnapshot()
|
|
tipBlock, err := g.chain.BlockByHash(&chainTip.Hash)
|
|
if err != nil {
|
|
g.setCurrentTemplate(nil, turUnknown, err)
|
|
} else {
|
|
g.handleBlockConnected(ctx, state, tipBlock, chainTip)
|
|
}
|
|
|
|
g.staleTemplateWg.Done()
|
|
return
|
|
}
|
|
|
|
// Do not generate block templates when the chain is in the middle of
|
|
// reorganizing.
|
|
if state.isReorganizing {
|
|
return
|
|
}
|
|
|
|
// Do not generate block templates when the chain is not synced unless
|
|
// specifically requested to.
|
|
if !g.allowUnsyncedMining && !g.tg.blockManager.IsCurrent() {
|
|
return
|
|
}
|
|
|
|
chainTip := g.chain.BestSnapshot()
|
|
switch event.reason {
|
|
case rtBlockConnected:
|
|
block := event.value.(*dcrutil.Block)
|
|
g.handleBlockConnected(ctx, state, block, chainTip)
|
|
|
|
case rtBlockDisconnected:
|
|
block := event.value.(*dcrutil.Block)
|
|
g.handleBlockDisconnected(ctx, state, block, chainTip)
|
|
|
|
case rtBlockAccepted:
|
|
block := event.value.(*dcrutil.Block)
|
|
g.handleBlockAccepted(ctx, state, block, chainTip)
|
|
|
|
case rtVote:
|
|
voteTx := event.value.(*dcrutil.Tx)
|
|
g.handleVote(ctx, state, voteTx, chainTip)
|
|
|
|
case rtTemplateUpdated:
|
|
tplUpdate := event.value.(templateUpdate)
|
|
g.handleTemplateUpdate(state, tplUpdate)
|
|
}
|
|
}
|
|
|
|
// tipSiblingsSortedByVotes returns all blocks other than the current tip block
|
|
// that also extend its parent sorted by the number of votes each has in
|
|
// descending order.
|
|
func (g *BgBlkTmplGenerator) tipSiblingsSortedByVotes(state *regenHandlerState) []*blockWithNumVotes {
|
|
// Obtain all of the current blocks that extend the same parent as the
|
|
// current tip. The error is ignored here because it is deprecated.
|
|
generation, _ := g.chain.TipGeneration()
|
|
|
|
// Nothing else to consider if there is only a single block which will be
|
|
// the current tip itself.
|
|
if len(generation) <= 1 {
|
|
return nil
|
|
}
|
|
|
|
siblings := make([]*blockWithNumVotes, 0, len(generation)-1)
|
|
for i := range generation {
|
|
hash := &generation[i]
|
|
if *hash == *state.awaitingMinVotesHash {
|
|
continue
|
|
}
|
|
|
|
numVotes := g.numVotesForBlock(hash)
|
|
siblings = append(siblings, &blockWithNumVotes{
|
|
Hash: *hash,
|
|
NumVotes: numVotes,
|
|
})
|
|
}
|
|
sort.Sort(sort.Reverse(byNumberOfVotes(siblings)))
|
|
return siblings
|
|
}
|
|
|
|
// handleTrackSideChainsTimeout handles potentially reorganizing the chain to a
|
|
// side chain block with the most votes in the case the minimum number of
|
|
// votes needed to build a block template on the current tip have not been
|
|
// received within a certain timeout.
|
|
//
|
|
// It also doubles to reset the regen timer for the current template in the case
|
|
// no validate candidates are found since it is disabled when setting up this
|
|
// timeout to prevent creating new templates that would very likely be stale
|
|
// soon after.
|
|
//
|
|
// This function is only intended for use by the regen handler goroutine.
|
|
func (g *BgBlkTmplGenerator) handleTrackSideChainsTimeout(ctx context.Context, state *regenHandlerState) {
|
|
// Don't allow side chain variants to override the current tip when it
|
|
// already has the minimum required votes.
|
|
if state.awaitingMinVotesHash == nil {
|
|
return
|
|
}
|
|
|
|
// Reorganize the chain to a valid sibling of the current tip that has at
|
|
// least the minimum number of required votes while preferring the most
|
|
// votes.
|
|
//
|
|
// Also, while looping, add each tip the map of side chain blocks to monitor
|
|
// for votes in the event there are not currently any eligible candidates
|
|
// since they may become eligible as votes arrive.
|
|
sortedSiblings := g.tipSiblingsSortedByVotes(state)
|
|
for _, sibling := range sortedSiblings {
|
|
if sibling.NumVotes >= g.minVotesRequired {
|
|
err := g.chain.ForceHeadReorganization(*state.awaitingMinVotesHash,
|
|
sibling.Hash)
|
|
if err != nil {
|
|
// Try the next block in the case of failure to reorg.
|
|
continue
|
|
}
|
|
|
|
// Prevent votes on other tip candidates from causing reorg again
|
|
// since the new chain tip has enough votes. The reorg event clears
|
|
// the state, but, since there is a backing queue for the events,
|
|
// and the reorg itself might haven taken a bit of time, it could
|
|
// allow new side chain blocks or votes on existing ones in before
|
|
// the reorg events are processed. Thus, update the state to
|
|
// indicate the next template is to be built on the new tip to
|
|
// prevent any possible logic races.
|
|
state.awaitingMinVotesHash = nil
|
|
state.clearSideChainTracking()
|
|
state.stopRegenTimer()
|
|
state.failedGenRetryTimeout = nil
|
|
state.baseBlockHash = sibling.Hash
|
|
return
|
|
}
|
|
|
|
state.awaitingSideChainMinVotes[sibling.Hash] = struct{}{}
|
|
}
|
|
|
|
// Generate a new template building on the parent of the current tip when
|
|
// there is not already an existing template and the initial timeout has
|
|
// elapsed upon receiving the new tip without receiving votes for it. There
|
|
// will typically only not be an existing template when the generator is
|
|
// first instantiated and after a chain reorganization.
|
|
if state.baseBlockHash == zeroHash {
|
|
chainTip := g.chain.BestSnapshot()
|
|
state.failedGenRetryTimeout = nil
|
|
state.baseBlockHash = chainTip.PrevHash
|
|
state.baseBlockHeight = uint32(chainTip.Height - 1)
|
|
g.genTemplateAsync(ctx, TURNewParent)
|
|
return
|
|
}
|
|
|
|
// At this point, no viable candidates to change the current template were
|
|
// found, so reset the regen timer for the current template.
|
|
state.resetRegenTimer(templateRegenSecs * time.Second)
|
|
}
|
|
|
|
// regenHandler is the main workhorse for generating new templates in response
|
|
// to regen events and also handles generating a new template during initial
|
|
// startup.
|
|
//
|
|
// This must be run as a goroutine.
|
|
func (g *BgBlkTmplGenerator) regenHandler(ctx context.Context) {
|
|
// Treat the tip block as if it was just connected when starting up so the
|
|
// existing code paths are run.
|
|
tipBlock, err := g.chain.BlockByHash(&g.chain.BestSnapshot().Hash)
|
|
if err != nil {
|
|
g.setCurrentTemplate(nil, turUnknown, err)
|
|
} else {
|
|
g.queueRegenEvent <- regenEvent{rtBlockConnected, tipBlock}
|
|
}
|
|
|
|
state := makeRegenHandlerState()
|
|
for {
|
|
select {
|
|
case event := <-g.regenEventMsgs:
|
|
g.handleRegenEvent(ctx, &state, event)
|
|
|
|
// This timeout is selectively enabled once the minimum number of
|
|
// required votes has been received in order to give the remaining votes
|
|
// an opportunity to propagate. It is disabled if the remaining votes
|
|
// are received prior to the timeout.
|
|
case <-state.maxVotesTimeout:
|
|
state.maxVotesTimeout = nil
|
|
g.genTemplateAsync(ctx, TURNewParent)
|
|
|
|
// This timeout is selectively enabled when a new block is connected in
|
|
// order to give the minimum number of required votes needed to build a
|
|
// block template on it an opportunity to propagate before attempting to
|
|
// find any other variants that extend the same parent as the current
|
|
// tip with enough votes to force a reorganization. This ensures the
|
|
// first block that is seen is chosen to build templates on so long as
|
|
// it receives the minimum required votes in order to prevent PoW miners
|
|
// from being able to gain an advantage through vote withholding. It is
|
|
// disabled if the minimum number of votes is received prior to the
|
|
// timeout.
|
|
case <-state.trackSideChainsTimeout:
|
|
state.trackSideChainsTimeout = nil
|
|
g.handleTrackSideChainsTimeout(ctx, &state)
|
|
|
|
// This timeout is selectively enabled once a template has been
|
|
// generated in order to allow the template to be periodically
|
|
// regenerated with new transactions. Note that votes have special
|
|
// handling as described above.
|
|
case <-state.regenTimer.C:
|
|
// Mark the timer's channel as having been drained so the timer can
|
|
// safely be reset.
|
|
state.regenChanDrained = true
|
|
|
|
// Generate a new template when there are new transactions
|
|
// available.
|
|
if g.tg.txSource.LastUpdated().Unix() > state.lastGeneratedTime {
|
|
state.failedGenRetryTimeout = nil
|
|
g.genTemplateAsync(ctx, TURNewTxns)
|
|
continue
|
|
}
|
|
|
|
// There are no new transactions to include and the initial timeout
|
|
// has been triggered, so reset the timer to check again in one
|
|
// second.
|
|
state.resetRegenTimer(time.Second)
|
|
|
|
// This timeout is selectively enabled in the rare case a template fails
|
|
// to generate and disabled prior to attempts at generating a new one.
|
|
case <-state.failedGenRetryTimeout:
|
|
state.failedGenRetryTimeout = nil
|
|
g.genTemplateAsync(ctx, TURNewParent)
|
|
|
|
case <-ctx.Done():
|
|
g.wg.Done()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// ChainReorgStarted informs the background block template generator that a
|
|
// chain reorganization has started. It is caller's responsibility to ensure
|
|
// this is only invoked as described.
|
|
func (g *BgBlkTmplGenerator) ChainReorgStarted() {
|
|
g.queueRegenEvent <- regenEvent{rtReorgStarted, nil}
|
|
}
|
|
|
|
// ChainReorgDone informs the background block template generator that a chain
|
|
// reorganization has completed. It is caller's responsibility to ensure this
|
|
// is only invoked as described.
|
|
func (g *BgBlkTmplGenerator) ChainReorgDone() {
|
|
g.queueRegenEvent <- regenEvent{rtReorgDone, nil}
|
|
}
|
|
|
|
// BlockAccepted informs the background block template generator that a block
|
|
// has been accepted to the block chain. It is caller's responsibility to
|
|
// ensure this is only invoked as described.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (g *BgBlkTmplGenerator) BlockAccepted(block *dcrutil.Block) {
|
|
g.queueRegenEvent <- regenEvent{rtBlockAccepted, block}
|
|
}
|
|
|
|
// BlockConnected informs the background block template generator that a block
|
|
// has been connected to the main chain. It is caller's responsibility to
|
|
// ensure this is only invoked as described.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (g *BgBlkTmplGenerator) BlockConnected(block *dcrutil.Block) {
|
|
g.queueRegenEvent <- regenEvent{rtBlockConnected, block}
|
|
}
|
|
|
|
// BlockDisconnected informs the background block template generator that a
|
|
// block has been disconnected from the main chain. It is caller's
|
|
// responsibility to ensure this is only invoked as described.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (g *BgBlkTmplGenerator) BlockDisconnected(block *dcrutil.Block) {
|
|
g.queueRegenEvent <- regenEvent{rtBlockDisconnected, block}
|
|
}
|
|
|
|
// VoteReceived informs the background block template generator that a new vote
|
|
// has been received. It is the caller's responsibility to ensure this is only
|
|
// invoked with valid votes.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (g *BgBlkTmplGenerator) VoteReceived(tx *dcrutil.Tx) {
|
|
g.queueRegenEvent <- regenEvent{rtVote, tx}
|
|
}
|
|
|
|
// Run starts the background block template generator and all other goroutines
|
|
// necessary for it to function properly and blocks until the provided context
|
|
// is cancelled.
|
|
func (g *BgBlkTmplGenerator) Run(ctx context.Context) {
|
|
g.wg.Add(3)
|
|
go g.regenQueueHandler(ctx)
|
|
go g.regenHandler(ctx)
|
|
go g.notifySubscribersHandler(ctx)
|
|
g.wg.Wait()
|
|
}
|