mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 10:56:47 +00:00
Upstream commit a3fa066745.
Also updates all of the new references for the newly exported fields as
part of the merge commit.
2131 lines
71 KiB
Go
2131 lines
71 KiB
Go
// Copyright (c) 2014 The btcsuite developers
|
|
// Copyright (c) 2015-2016 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"
|
|
"container/list"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/blockchain"
|
|
"github.com/decred/dcrd/blockchain/stake"
|
|
"github.com/decred/dcrd/chaincfg"
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/database"
|
|
"github.com/decred/dcrd/mining"
|
|
"github.com/decred/dcrd/txscript"
|
|
"github.com/decred/dcrd/wire"
|
|
"github.com/decred/dcrutil"
|
|
)
|
|
|
|
const (
|
|
// minHighPriority is the minimum priority value that allows a
|
|
// transaction to be considered high priority.
|
|
minHighPriority = dcrutil.AtomsPerCoin * 144.0 / 250
|
|
|
|
// 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
|
|
)
|
|
|
|
// 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 transation 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
|
|
}
|
|
|
|
// txPQByPriority sorts a txPriorityQueue by transaction priority and then fees
|
|
// per kilobyte.
|
|
func txPQByPriority(pq *txPriorityQueue, i, j int) bool {
|
|
// Using > 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
|
|
}
|
|
|
|
// txPQByStakeAndPriority sorts a txPriorityQueue by stake priority then
|
|
// transaction priority, and then fees per kilobyte.
|
|
func txPQByStakeAndPriority(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 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
|
|
}
|
|
|
|
// txPQByFee sorts a txPriorityQueue by fees per kilobyte and then transaction
|
|
// priority.
|
|
func txPQByFee(pq *txPriorityQueue, i, j int) bool {
|
|
// 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
|
|
}
|
|
return pq.items[i].feePerKB > pq.items[j].feePerKB
|
|
}
|
|
|
|
// 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.Sha()) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// hashExistsInList checks if a hash exists in a list of hashes.
|
|
func hashExistsInList(hash *chainhash.Hash, list []chainhash.Hash) bool {
|
|
for _, h := range list {
|
|
if hash.IsEqual(&h) {
|
|
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.Sha()
|
|
if hash == *h {
|
|
return i
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// mergeTxStore adds all of the transactions in txStoreB to txStoreA. The
|
|
// result is that txStoreA will contain all of its original transactions plus
|
|
// all of the transactions in txStoreB.
|
|
func mergeTxStore(txStoreA blockchain.TxStore, txStoreB blockchain.TxStore) {
|
|
for hash, txDataB := range txStoreB {
|
|
if txDataA, exists := txStoreA[hash]; !exists ||
|
|
(txDataA.Err == database.ErrTxShaMissing && txDataB.Err !=
|
|
database.ErrTxShaMissing) {
|
|
|
|
txStoreA[hash] = txDataB
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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, extraNonces []uint64) ([]byte,
|
|
error) {
|
|
if len(extraNonces) != 4 {
|
|
return nil, fmt.Errorf("extranonces has wrong num uint64s")
|
|
}
|
|
|
|
enData := make([]byte, 36, 36)
|
|
binary.LittleEndian.PutUint32(enData[0:4], height)
|
|
binary.LittleEndian.PutUint64(enData[4:12], extraNonces[0])
|
|
binary.LittleEndian.PutUint64(enData[12:20], extraNonces[1])
|
|
binary.LittleEndian.PutUint64(enData[20:28], extraNonces[2])
|
|
binary.LittleEndian.PutUint64(enData[28:36], extraNonces[3])
|
|
extraNonceScript, err := txscript.GenerateProvablyPruneableOut(enData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return extraNonceScript, nil
|
|
}
|
|
|
|
// getCoinbaseExtranonce extracts the extranonce from a block template's
|
|
// coinbase transaction.
|
|
func (bt *BlockTemplate) getCoinbaseExtranonces() []uint64 {
|
|
if len(bt.Block.Transactions[0].TxOut) < 2 {
|
|
return []uint64{0, 0, 0, 0}
|
|
}
|
|
|
|
if len(bt.Block.Transactions[0].TxOut[1].PkScript) < 38 {
|
|
return []uint64{0, 0, 0, 0}
|
|
}
|
|
|
|
ens := make([]uint64, 4, 4) // 32-bytes
|
|
ens[0] = binary.LittleEndian.Uint64(
|
|
bt.Block.Transactions[0].TxOut[1].PkScript[6:14])
|
|
ens[1] = binary.LittleEndian.Uint64(
|
|
bt.Block.Transactions[0].TxOut[1].PkScript[14:22])
|
|
ens[2] = binary.LittleEndian.Uint64(
|
|
bt.Block.Transactions[0].TxOut[1].PkScript[22:30])
|
|
ens[3] = binary.LittleEndian.Uint64(
|
|
bt.Block.Transactions[0].TxOut[1].PkScript[30:38])
|
|
|
|
return ens
|
|
}
|
|
|
|
// getCoinbaseExtranonce extracts the extranonce from a block template's
|
|
// coinbase transaction.
|
|
func getCoinbaseExtranonces(msgBlock *wire.MsgBlock) []uint64 {
|
|
if len(msgBlock.Transactions[0].TxOut) < 2 {
|
|
return []uint64{0, 0, 0, 0}
|
|
}
|
|
|
|
if len(msgBlock.Transactions[0].TxOut[1].PkScript) < 38 {
|
|
return []uint64{0, 0, 0, 0}
|
|
}
|
|
|
|
ens := make([]uint64, 4, 4) // 32-bytes
|
|
ens[0] = binary.LittleEndian.Uint64(
|
|
msgBlock.Transactions[0].TxOut[1].PkScript[6:14])
|
|
ens[1] = binary.LittleEndian.Uint64(
|
|
msgBlock.Transactions[0].TxOut[1].PkScript[14:22])
|
|
ens[2] = binary.LittleEndian.Uint64(
|
|
msgBlock.Transactions[0].TxOut[1].PkScript[22:30])
|
|
ens[3] = binary.LittleEndian.Uint64(
|
|
msgBlock.Transactions[0].TxOut[1].PkScript[30:38])
|
|
|
|
return ens
|
|
}
|
|
|
|
// UpdateExtraNonce updates the extra nonce in the coinbase script of the passed
|
|
// block by regenerating the coinbase script with the passed value and block
|
|
// height. It also recalculates and updates the new merkle root that results
|
|
// from changing the coinbase script.
|
|
func UpdateExtraNonce(msgBlock *wire.MsgBlock, blockHeight int64,
|
|
extraNonces []uint64) error {
|
|
// First block has no extranonce.
|
|
if blockHeight == 1 {
|
|
return nil
|
|
}
|
|
if len(extraNonces) != 4 {
|
|
return fmt.Errorf("not enough nonce information passed")
|
|
}
|
|
|
|
coinbaseOpReturn, err := standardCoinbaseOpReturn(uint32(blockHeight),
|
|
extraNonces)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
msgBlock.Transactions[0].TxOut[1].PkScript = coinbaseOpReturn
|
|
|
|
// TODO(davec): A dcrutil.Block should use saved in the state to avoid
|
|
// recalculating all of the other transaction hashes.
|
|
// block.Transactions[0].InvalidateCache()
|
|
|
|
// Recalculate the merkle root with the updated extra nonce.
|
|
block := dcrutil.NewBlockDeepCopyCoinbase(msgBlock)
|
|
merkles := blockchain.BuildMerkleTreeStore(block.Transactions())
|
|
msgBlock.Header.MerkleRoot = *merkles[len(merkles)-1]
|
|
return 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(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, dcrutil.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 {
|
|
// Convert the addresses in the ledger into useable format.
|
|
addrs := make([]dcrutil.Address,
|
|
len(params.BlockOneLedger),
|
|
len(params.BlockOneLedger))
|
|
for i, payout := range params.BlockOneLedger {
|
|
addr, err := dcrutil.DecodeAddress(payout.Address, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addrs[i] = addr
|
|
}
|
|
|
|
for i, payout := range params.BlockOneLedger {
|
|
// Make payout to this address.
|
|
pks, err := txscript.PayToAddrScript(addrs[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: payout.Amount,
|
|
PkScript: pks,
|
|
})
|
|
}
|
|
|
|
tx.TxIn[0].ValueIn = params.BlockOneSubsidy()
|
|
|
|
return dcrutil.NewTx(tx), nil
|
|
}
|
|
|
|
// Create a coinbase with correct block subsidy and extranonce.
|
|
subsidy := blockchain.CalcBlockWorkSubsidy(nextBlockHeight,
|
|
voters,
|
|
activeNetParams.Params)
|
|
tax := blockchain.CalcBlockTaxSubsidy(nextBlockHeight,
|
|
voters,
|
|
activeNetParams.Params)
|
|
addrOrg, err := dcrutil.DecodeAddress(params.OrganizationAddress, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pksOrg, err := txscript.PayToAddrScript(addrOrg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Tax output.
|
|
if params.BlockTaxProportion > 0 {
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: tax,
|
|
PkScript: pksOrg,
|
|
})
|
|
} else {
|
|
// Tax disabled.
|
|
scriptBuilder := txscript.NewScriptBuilder()
|
|
trueScript, err := scriptBuilder.AddOp(txscript.OP_TRUE).Script()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: tax,
|
|
PkScript: trueScript,
|
|
})
|
|
}
|
|
// Extranonce.
|
|
tx.AddTxOut(&wire.TxOut{
|
|
Value: 0,
|
|
PkScript: opReturnPkScript,
|
|
})
|
|
// ValueIn.
|
|
tx.TxIn[0].ValueIn = subsidy + tax
|
|
|
|
// 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: subsidy,
|
|
PkScript: pksSubsidy,
|
|
})
|
|
|
|
return dcrutil.NewTx(tx), nil
|
|
}
|
|
|
|
// spendTransaction updates the passed transaction store by marking the inputs
|
|
// to the passed transaction as spent. It also adds the passed transaction to
|
|
// the store at the provided height.
|
|
func spendTransaction(txStore blockchain.TxStore, tx *dcrutil.Tx,
|
|
height int64) error {
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
originIndex := txIn.PreviousOutPoint.Index
|
|
if originTx, exists := txStore[*originHash]; exists {
|
|
originTx.Spent[originIndex] = true
|
|
}
|
|
}
|
|
|
|
txStore[*tx.Sha()] = &blockchain.TxData{
|
|
Tx: tx,
|
|
Hash: tx.Sha(),
|
|
BlockHeight: height,
|
|
BlockIndex: wire.NullBlockIndex,
|
|
Spent: make([]bool, len(tx.MsgTx().TxOut)),
|
|
Err: nil,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 *list.List) {
|
|
if deps == nil {
|
|
return
|
|
}
|
|
|
|
for e := deps.Front(); e != nil; e = e.Next() {
|
|
item := e.Value.(*txPrioItem)
|
|
minrLog.Tracef("Skipping tx %s since it depends on %s\n",
|
|
item.tx.Sha(), tx.Sha())
|
|
}
|
|
}
|
|
|
|
// 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(chainState *chainState) (time.Time, error) {
|
|
chainState.Lock()
|
|
defer chainState.Unlock()
|
|
if chainState.pastMedianTimeErr != nil {
|
|
return time.Time{}, chainState.pastMedianTimeErr
|
|
}
|
|
|
|
return chainState.pastMedianTime.Add(time.Second), nil
|
|
}
|
|
|
|
// 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(chainState *chainState,
|
|
timeSource blockchain.MedianTimeSource) (time.Time, error) {
|
|
chainState.Lock()
|
|
defer chainState.Unlock()
|
|
if chainState.pastMedianTimeErr != nil {
|
|
return time.Time{}, chainState.pastMedianTimeErr
|
|
}
|
|
|
|
// 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 supported a precision greater than one
|
|
// second.
|
|
newTimestamp := timeSource.AdjustedTime()
|
|
minTimestamp := chainState.pastMedianTime.Add(time.Second)
|
|
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, nil
|
|
}
|
|
|
|
// 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
|
|
|
|
txStore, err := bm.FetchTransactionStore(stx, treeValid)
|
|
if err != nil {
|
|
minrLog.Warnf("Unable to fetch transaction store for "+
|
|
"stx %s: %v", stx.Sha(), err)
|
|
return false
|
|
}
|
|
isSSGen, _ := stake.IsSSGen(stx)
|
|
for i, txIn := range stx.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) {
|
|
txIn.BlockHeight = wire.NullBlockHeight
|
|
txIn.BlockIndex = wire.NullBlockIndex
|
|
|
|
continue
|
|
}
|
|
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
txData, exists := txStore[*originHash]
|
|
if !exists || txData.Err != nil || txData.Tx == nil {
|
|
missingInput = true
|
|
break
|
|
} else {
|
|
originIdx := txIn.PreviousOutPoint.Index
|
|
txIn.ValueIn = txData.Tx.MsgTx().TxOut[originIdx].Value
|
|
txIn.BlockHeight = uint32(txData.BlockHeight)
|
|
txIn.BlockIndex = txData.BlockIndex
|
|
}
|
|
}
|
|
return !missingInput
|
|
}
|
|
|
|
// deepCopyBlockTemplate returns a deeply copied block template that copies all
|
|
// data except a block's references to transactions, which are kept as pointers
|
|
// in the block. This is considered safe because transaction data is generally
|
|
// immutable, with the exception of coinbases which we alternatively also
|
|
// deep copy.
|
|
func deepCopyBlockTemplate(blockTemplate *BlockTemplate) *BlockTemplate {
|
|
if blockTemplate == nil {
|
|
return nil
|
|
}
|
|
|
|
// Deep copy the header, which we hash on.
|
|
headerCopy := wire.BlockHeader{
|
|
Version: blockTemplate.Block.Header.Version,
|
|
PrevBlock: blockTemplate.Block.Header.PrevBlock,
|
|
MerkleRoot: blockTemplate.Block.Header.MerkleRoot,
|
|
StakeRoot: blockTemplate.Block.Header.StakeRoot,
|
|
VoteBits: blockTemplate.Block.Header.VoteBits,
|
|
FinalState: blockTemplate.Block.Header.FinalState,
|
|
Voters: blockTemplate.Block.Header.Voters,
|
|
FreshStake: blockTemplate.Block.Header.FreshStake,
|
|
Revocations: blockTemplate.Block.Header.Revocations,
|
|
PoolSize: blockTemplate.Block.Header.PoolSize,
|
|
Timestamp: blockTemplate.Block.Header.Timestamp,
|
|
Bits: blockTemplate.Block.Header.Bits,
|
|
SBits: blockTemplate.Block.Header.SBits,
|
|
Nonce: blockTemplate.Block.Header.Nonce,
|
|
Height: blockTemplate.Block.Header.Height,
|
|
Size: blockTemplate.Block.Header.Size,
|
|
}
|
|
|
|
// Copy transactions pointers. Duplicate the coinbase
|
|
// transaction, because it might update it by modifying
|
|
// the extra nonce.
|
|
transactionsCopy := make([]*wire.MsgTx, len(blockTemplate.Block.Transactions),
|
|
len(blockTemplate.Block.Transactions))
|
|
coinbaseCopy :=
|
|
dcrutil.NewTxDeep(blockTemplate.Block.Transactions[0])
|
|
for i, mtx := range blockTemplate.Block.Transactions {
|
|
if i == 0 {
|
|
transactionsCopy[i] = coinbaseCopy.MsgTx()
|
|
} else {
|
|
transactionsCopy[i] = mtx
|
|
}
|
|
}
|
|
|
|
sTransactionsCopy := make([]*wire.MsgTx,
|
|
len(blockTemplate.Block.STransactions),
|
|
len(blockTemplate.Block.STransactions))
|
|
for i, mtx := range blockTemplate.Block.STransactions {
|
|
sTransactionsCopy[i] = mtx
|
|
}
|
|
|
|
msgBlockCopy := &wire.MsgBlock{
|
|
Header: headerCopy,
|
|
Transactions: transactionsCopy,
|
|
STransactions: sTransactionsCopy,
|
|
}
|
|
|
|
fees := make([]int64, len(blockTemplate.Fees), len(blockTemplate.Fees))
|
|
for i, f := range blockTemplate.Fees {
|
|
fees[i] = f
|
|
}
|
|
|
|
sigOps := make([]int64, len(blockTemplate.SigOpCounts),
|
|
len(blockTemplate.SigOpCounts))
|
|
for i, s := range blockTemplate.SigOpCounts {
|
|
sigOps[i] = s
|
|
}
|
|
|
|
return &BlockTemplate{
|
|
Block: msgBlockCopy,
|
|
Fees: fees,
|
|
SigOpCounts: sigOps,
|
|
Height: blockTemplate.Height,
|
|
ValidPayAddress: blockTemplate.ValidPayAddress,
|
|
}
|
|
}
|
|
|
|
// 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(nextHeight int64,
|
|
miningAddress dcrutil.Address,
|
|
bm *blockManager) (*BlockTemplate, error) {
|
|
timeSource := bm.server.timeSource
|
|
chainState := &bm.chainState
|
|
stakeValidationHeight := bm.server.chainParams.StakeValidationHeight
|
|
curTemplate := bm.GetCurrentTemplate()
|
|
|
|
// Check to see if we've fallen off the chain, for example if a
|
|
// reorganization had recently occurred. If this is the case,
|
|
// nuke the templates.
|
|
bestHeader := chainState.GetTopBlockHeader()
|
|
if curTemplate != nil {
|
|
if !bestHeader.PrevBlock.IsEqual(
|
|
&curTemplate.Block.Header.PrevBlock) {
|
|
minrLog.Debugf("Cached mining templates are no longer current, " +
|
|
"resetting")
|
|
bm.SetCurrentTemplate(nil)
|
|
bm.SetParentTemplate(nil)
|
|
}
|
|
}
|
|
|
|
// Handle not enough voters being present if we're set to mine aggressively
|
|
// (default behaviour).
|
|
if nextHeight >= stakeValidationHeight {
|
|
if bm.AggressiveMining {
|
|
if curTemplate != nil {
|
|
cptCopy := deepCopyBlockTemplate(curTemplate)
|
|
|
|
// Update the timestamp of the old template.
|
|
ts, err := medianAdjustedTime(chainState, timeSource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cptCopy.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.server.chainParams.ResetMinDifficulty {
|
|
parentHash := cptCopy.Block.Header.PrevBlock
|
|
|
|
requiredDifficulty, err :=
|
|
bm.CalcNextRequiredDiffNode(&parentHash, ts)
|
|
if err != nil {
|
|
return nil, miningRuleError(ErrGettingDifficulty,
|
|
err.Error())
|
|
}
|
|
|
|
cptCopy.Block.Header.Bits = requiredDifficulty
|
|
}
|
|
|
|
// Choose a new extranonce value that is one greater
|
|
// than the previous extranonce, so we don't remine the
|
|
// same block and choose the same winners as before.
|
|
ens := cptCopy.getCoinbaseExtranonces()
|
|
ens[0]++
|
|
UpdateExtraNonce(cptCopy.Block, cptCopy.Height, ens)
|
|
|
|
// Update extranonce of the original template too, so
|
|
// we keep getting unique numbers.
|
|
UpdateExtraNonce(curTemplate.Block, curTemplate.Height, ens)
|
|
|
|
// Make sure the block validates.
|
|
block := dcrutil.NewBlockDeepCopyCoinbase(cptCopy.Block)
|
|
block.SetHeight(cptCopy.Height)
|
|
if err := blockchain.CheckWorklessBlockSanity(block,
|
|
bm.server.timeSource,
|
|
bm.server.chainParams); err != nil {
|
|
return nil, miningRuleError(ErrCheckConnectBlock,
|
|
err.Error())
|
|
}
|
|
|
|
if err := bm.CheckConnectBlock(block); err != nil {
|
|
minrLog.Errorf("failed to check template while "+
|
|
"duplicating a parent: %v", err.Error())
|
|
return nil, miningRuleError(ErrCheckConnectBlock,
|
|
err.Error())
|
|
}
|
|
|
|
return cptCopy, nil
|
|
}
|
|
|
|
// We may have just started mining and stored the current block
|
|
// template, so we don't have a parent.
|
|
if curTemplate == nil {
|
|
// 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 readding some
|
|
// transactions from this block, too.
|
|
topBlock, err :=
|
|
bm.GetTopBlockFromChain()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get top block from " +
|
|
"chain")
|
|
}
|
|
btMsgBlock := new(wire.MsgBlock)
|
|
rand, err := wire.RandomUint64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opReturnPkScript, err :=
|
|
standardCoinbaseOpReturn(topBlock.MsgBlock().Header.Height,
|
|
[]uint64{0, 0, 0, rand})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
coinbaseTx, err := createCoinbaseTx([]byte{0x01, 0x02},
|
|
opReturnPkScript,
|
|
topBlock.Height(),
|
|
miningAddress,
|
|
topBlock.MsgBlock().Header.Voters,
|
|
bm.server.chainParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
btMsgBlock.AddTransaction(coinbaseTx.MsgTx())
|
|
|
|
for _, stx := range topBlock.STransactions() {
|
|
btMsgBlock.AddSTransaction(stx.MsgTx())
|
|
}
|
|
|
|
// Copy the rest of the header.
|
|
btMsgBlock.Header.Version =
|
|
topBlock.MsgBlock().Header.Version
|
|
btMsgBlock.Header.PrevBlock =
|
|
topBlock.MsgBlock().Header.PrevBlock
|
|
btMsgBlock.Header.VoteBits =
|
|
topBlock.MsgBlock().Header.VoteBits
|
|
btMsgBlock.Header.FinalState =
|
|
topBlock.MsgBlock().Header.FinalState
|
|
btMsgBlock.Header.Voters =
|
|
topBlock.MsgBlock().Header.Voters
|
|
btMsgBlock.Header.FreshStake =
|
|
topBlock.MsgBlock().Header.FreshStake
|
|
btMsgBlock.Header.Revocations =
|
|
topBlock.MsgBlock().Header.Revocations
|
|
btMsgBlock.Header.PoolSize =
|
|
topBlock.MsgBlock().Header.PoolSize
|
|
btMsgBlock.Header.Bits =
|
|
topBlock.MsgBlock().Header.Bits
|
|
btMsgBlock.Header.SBits =
|
|
topBlock.MsgBlock().Header.SBits
|
|
btMsgBlock.Header.Height =
|
|
topBlock.MsgBlock().Header.Height
|
|
|
|
// Set a fresh timestamp.
|
|
ts, err := medianAdjustedTime(chainState, timeSource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
btMsgBlock.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.server.chainParams.ResetMinDifficulty {
|
|
parentHash := topBlock.MsgBlock().Header.PrevBlock
|
|
|
|
requiredDifficulty, err :=
|
|
bm.CalcNextRequiredDiffNode(&parentHash, ts)
|
|
if err != nil {
|
|
return nil, miningRuleError(ErrGettingDifficulty,
|
|
err.Error())
|
|
}
|
|
|
|
btMsgBlock.Header.Bits = requiredDifficulty
|
|
}
|
|
|
|
// Recalculate the size.
|
|
btMsgBlock.Header.Size = uint32(btMsgBlock.SerializeSize())
|
|
|
|
bt := &BlockTemplate{
|
|
Block: btMsgBlock,
|
|
Fees: []int64{0},
|
|
SigOpCounts: []int64{0},
|
|
Height: int64(topBlock.MsgBlock().Header.Height),
|
|
ValidPayAddress: miningAddress != nil,
|
|
}
|
|
|
|
// Recalculate the merkle roots. Use a temporary 'immutable'
|
|
// block object as we're changing the header contents.
|
|
btBlockTemp := dcrutil.NewBlockDeepCopyCoinbase(btMsgBlock)
|
|
merkles :=
|
|
blockchain.BuildMerkleTreeStore(btBlockTemp.Transactions())
|
|
merklesStake :=
|
|
blockchain.BuildMerkleTreeStore(btBlockTemp.STransactions())
|
|
btMsgBlock.Header.MerkleRoot = *merkles[len(merkles)-1]
|
|
btMsgBlock.Header.StakeRoot = *merklesStake[len(merklesStake)-1]
|
|
|
|
// Make sure the block validates.
|
|
btBlock := dcrutil.NewBlockDeepCopyCoinbase(btMsgBlock)
|
|
btBlock.SetHeight(bt.Height)
|
|
if err := blockchain.CheckWorklessBlockSanity(btBlock,
|
|
bm.server.timeSource,
|
|
bm.server.chainParams); err != nil {
|
|
str := fmt.Sprintf("failed to check sanity of template "+
|
|
"while constructing a new parent: %v",
|
|
err.Error())
|
|
return nil, miningRuleError(ErrCheckConnectBlock,
|
|
str)
|
|
}
|
|
|
|
if err := bm.CheckConnectBlock(btBlock); err != nil {
|
|
str := fmt.Sprintf("failed to check template: %v while "+
|
|
"constructing a new parent", err.Error())
|
|
return nil, miningRuleError(ErrCheckConnectBlock,
|
|
str)
|
|
}
|
|
|
|
// Make a copy to return.
|
|
cptCopy := deepCopyBlockTemplate(bt)
|
|
|
|
return cptCopy, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
bmgrLog.Debugf("Not enough voters on top block to generate " +
|
|
"new block template")
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// handleCreatedBlockTemplate stores a successfully created block template to
|
|
// the appropriate cache if needed, then returns the template to the miner to
|
|
// work on. The stored template is a copy of the template, to prevent races
|
|
// from occurring in case the template is mined on by the CPUminer.
|
|
func handleCreatedBlockTemplate(blockTemplate *BlockTemplate,
|
|
bm *blockManager) (*BlockTemplate, error) {
|
|
curTemplate := bm.GetCurrentTemplate()
|
|
|
|
nextBlockHeight := blockTemplate.Height
|
|
stakeValidationHeight := bm.server.chainParams.StakeValidationHeight
|
|
// This is where we begin storing block templates, when either the
|
|
// program is freshly started or the chain is matured to stake
|
|
// validation height.
|
|
if curTemplate == nil &&
|
|
nextBlockHeight >= stakeValidationHeight-2 {
|
|
bm.SetCurrentTemplate(blockTemplate)
|
|
}
|
|
|
|
// We're at the height where the next block needs to include SSGens,
|
|
// so we check to if CachedCurrentTemplate is out of date. If it is,
|
|
// we store it as the cached parent template, and store the new block
|
|
// template as the currenct template.
|
|
if curTemplate != nil &&
|
|
nextBlockHeight >= stakeValidationHeight-1 {
|
|
if curTemplate.Height < nextBlockHeight {
|
|
bm.SetParentTemplate(curTemplate)
|
|
bm.SetCurrentTemplate(blockTemplate)
|
|
}
|
|
}
|
|
|
|
// Overwrite the old cached block if it's out of date.
|
|
if curTemplate != nil {
|
|
if curTemplate.Height == nextBlockHeight {
|
|
bm.SetCurrentTemplate(blockTemplate)
|
|
}
|
|
}
|
|
|
|
return blockTemplate, nil
|
|
}
|
|
|
|
// 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) | |
|
|
// ----------------------------------- --
|
|
//
|
|
// TODO - DECRED
|
|
// We also need to include a stake tx tree that looks like the following:
|
|
//
|
|
// ----------------------------------- -- --
|
|
// | | | |
|
|
// | SSGen tx | | | ----- cfg.SSGenAllocatedSize ?
|
|
// | | | |
|
|
// |-----------------------------------| | --
|
|
// | | |
|
|
// | SStx tx | |--- (policy.BlockMaxSize) / 2
|
|
// | | |
|
|
// |-----------------------------------| |
|
|
// | | |
|
|
// | SSRtx tx | |
|
|
// | | |
|
|
// ----------------------------------- --
|
|
//
|
|
// 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 NewBlockTemplate(policy *mining.Policy, server *server,
|
|
payToAddress dcrutil.Address) (*BlockTemplate, error) {
|
|
|
|
// TODO: The mempool should be completely separated via the TxSource
|
|
// interface so this function is fully decoupled.
|
|
mempool := server.txMemPool
|
|
|
|
var txSource mining.TxSource = server.txMemPool
|
|
blockManager := server.blockManager
|
|
timeSource := server.timeSource
|
|
chainState := &blockManager.chainState
|
|
|
|
// 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.
|
|
chainState.Lock()
|
|
prevHash := chainState.newestHash
|
|
nextBlockHeight := chainState.newestHeight + 1
|
|
poolSize := chainState.nextPoolSize
|
|
requiredStakeDifficulty := chainState.nextStakeDifficulty
|
|
finalState := chainState.nextFinalState
|
|
winningTickets := make([]chainhash.Hash, len(chainState.winningTickets),
|
|
len(chainState.winningTickets))
|
|
for i, h := range chainState.winningTickets {
|
|
winningTickets[i] = h
|
|
}
|
|
missedTickets := make([]chainhash.Hash, len(chainState.missedTickets),
|
|
len(chainState.missedTickets))
|
|
for i, h := range chainState.missedTickets {
|
|
missedTickets[i] = h
|
|
}
|
|
chainState.Unlock()
|
|
|
|
// Calculate the stake enabled height.
|
|
stakeValidationHeight := server.chainParams.StakeValidationHeight
|
|
|
|
if nextBlockHeight >= stakeValidationHeight {
|
|
// Obtain the entire generation of blocks stemming from this parent.
|
|
children, err := blockManager.GetGeneration(*prevHash)
|
|
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, err := mempool.SortParentsByVotes(*prevHash, children)
|
|
if err != nil {
|
|
if err.(MiningRuleError).GetCode() == ErrNotEnoughVoters {
|
|
minrLog.Debugf("Too few voters found on any HEAD block, " +
|
|
"recycling a parent block to mine on")
|
|
return handleTooFewVoters(nextBlockHeight, payToAddress,
|
|
server.blockManager)
|
|
}
|
|
minrLog.Errorf("unexpected error while sorting eligible "+
|
|
"parents: %v", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
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].IsEqual(prevHash) {
|
|
for _, newHead := range eligibleParents {
|
|
err := 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, err := mempool.GetVoteHashesForBlock(newHead)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if exist := mempool.CheckIfTxsExist(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 := txSource.MiningDescs()
|
|
sortedByFee := 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 transaction
|
|
// store to house all of the input transactions so multiple lookups
|
|
// can be avoided.
|
|
blockTxns := make([]*dcrutil.Tx, 0, len(sourceTxns))
|
|
blockTxStore := make(blockchain.TxStore)
|
|
|
|
// 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]*list.List)
|
|
|
|
// 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))
|
|
treeValid := mempool.IsTxTreeValid(prevHash)
|
|
|
|
mempoolLoop:
|
|
for _, txDesc := range sourceTxns {
|
|
// A block can't have more than one coinbase or contain
|
|
// non-finalized transactions.
|
|
tx := txDesc.Tx
|
|
if blockchain.IsCoinBase(tx) {
|
|
minrLog.Tracef("Skipping coinbase tx %s", tx.Sha())
|
|
continue
|
|
}
|
|
if !blockchain.IsFinalizedTransaction(tx, nextBlockHeight,
|
|
timeSource.AdjustedTime()) {
|
|
|
|
minrLog.Tracef("Skipping non-finalized tx %s", tx.Sha())
|
|
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, err := stake.GetSSGenBlockVotedOn(tx)
|
|
if err != nil { // Should theoretically never fail.
|
|
minrLog.Tracef("Skipping ssgen tx %s because of failure "+
|
|
"to extract block voting data", tx.Sha())
|
|
continue
|
|
}
|
|
|
|
if !((blockHash == *prevHash) &&
|
|
(int64(blockHeight) == nextBlockHeight-1)) {
|
|
minrLog.Tracef("Skipping ssgen tx %s because it does "+
|
|
"not vote on the correct block", tx.Sha())
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Fetch all of the transactions referenced by the inputs to
|
|
// 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
|
|
// dependencies in the final generated block.
|
|
txStore, err := blockManager.FetchTransactionStore(tx, treeValid)
|
|
if err != nil {
|
|
minrLog.Warnf("Unable to fetch transaction store for "+
|
|
"tx %s: %v", tx.Sha(), 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
|
|
txData, exists := txStore[*originHash]
|
|
if !exists || txData.Err != nil || txData.Tx == nil {
|
|
if !txSource.HaveTransaction(originHash) {
|
|
minrLog.Tracef("Skipping tx %s because "+
|
|
"it references tx %s which is "+
|
|
"not available", tx.Sha,
|
|
originHash)
|
|
continue mempoolLoop
|
|
}
|
|
|
|
// The transaction is referencing another
|
|
// transaction in the source pool, so setup an
|
|
// ordering dependency.
|
|
depList, exists := dependers[*originHash]
|
|
if !exists {
|
|
depList = list.New()
|
|
dependers[*originHash] = depList
|
|
}
|
|
depList.PushBack(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
|
|
}
|
|
|
|
// Ensure the output index in the referenced transaction
|
|
// is available.
|
|
msgTx := txData.Tx.MsgTx()
|
|
if originIndex > uint32(len(msgTx.TxOut)) {
|
|
minrLog.Tracef("Skipping tx %s because "+
|
|
"it references output %d of tx %s "+
|
|
"which is out of bounds", tx.Sha,
|
|
originIndex, originHash)
|
|
continue mempoolLoop
|
|
}
|
|
}
|
|
|
|
// Calculate the final transaction priority using the input
|
|
// value age sum as well as the adjusted transaction size. The
|
|
// formula is: sum(inputValue * inputAge) / adjustedTxSize
|
|
prioItem.priority = calcPriority(tx.MsgTx(), txStore, 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 store which contains all of the input transactions
|
|
// for this transaction into the input transaction store. This
|
|
// allows the code below to avoid a second lookup.
|
|
mergeTxStore(blockTxStore, txStore)
|
|
}
|
|
|
|
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(winningTickets))
|
|
for _, ticketHash := range winningTickets {
|
|
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) and remove the entry for this transaction as it will
|
|
// either be included or skipped, but in either case the deps
|
|
// are no longer needed.
|
|
deps := dependers[*tx.Sha()]
|
|
delete(dependers, *tx.Sha())
|
|
|
|
// Skip if we already have too many SStx.
|
|
if isSStx && (numSStx >=
|
|
int(server.chainParams.MaxFreshStakePerBlock)) {
|
|
minrLog.Tracef("Skipping sstx %s because it would exceed "+
|
|
"the max number of sstx allowed in a block", tx.Sha())
|
|
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 < requiredStakeDifficulty) {
|
|
continue
|
|
}
|
|
|
|
// Skip all missed tickets that we've never heard of.
|
|
if isSSRtx {
|
|
ticketHash := &tx.MsgTx().TxIn[0].PreviousOutPoint.Hash
|
|
|
|
if !hashExistsInList(ticketHash, missedTickets) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Enforce maximum block size. Also check for overflow.
|
|
txSize := uint32(tx.MsgTx().SerializeSize())
|
|
blockPlusTxSize := blockSize + txSize
|
|
if blockPlusTxSize < blockSize || blockPlusTxSize >= 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.Sha(),
|
|
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.Sha())
|
|
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, blockTxStore)
|
|
if err != nil {
|
|
minrLog.Tracef("Skipping tx %s due to error in "+
|
|
"CountP2SHSigOps: %v", tx.Sha(), 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.Sha())
|
|
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 winningTickets {
|
|
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(policy.TxMinFreeFee)) &&
|
|
(tx.Tree() != dcrutil.TxTreeStake) &&
|
|
(blockPlusTxSize >= policy.BlockMinSize) {
|
|
|
|
minrLog.Tracef("Skipping tx %s with feePerKB %.2f "+
|
|
"< TxMinFreeFee %d and block size %d >= "+
|
|
"minBlockSize %d", tx.Sha(), prioItem.feePerKB,
|
|
policy.TxMinFreeFee, blockPlusTxSize,
|
|
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 >= policy.BlockPrioritySize ||
|
|
prioItem.priority <= minHighPriority) {
|
|
|
|
minrLog.Tracef("Switching to sort by fees per "+
|
|
"kilobyte blockSize %d >= BlockPrioritySize "+
|
|
"%d || priority %.2f <= minHighPriority %.2f",
|
|
blockPlusTxSize, policy.BlockPrioritySize,
|
|
prioItem.priority, minHighPriority)
|
|
|
|
sortedByFee = true
|
|
priorityQueue.SetLessFunc(txPQByStakeAndFee)
|
|
|
|
// Put the transaction back into the priority queue and
|
|
// skip it so it is re-priortized 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 > policy.BlockPrioritySize ||
|
|
prioItem.priority < 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.
|
|
_, err = blockchain.CheckTransactionInputs(tx,
|
|
nextBlockHeight,
|
|
blockTxStore,
|
|
false, // Don't check fraud proofs; missing ones are filled out below
|
|
server.chainParams)
|
|
if err != nil {
|
|
minrLog.Tracef("Skipping tx %s due to error in "+
|
|
"CheckTransactionInputs: %v", tx.Sha(), err)
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
err = blockchain.ValidateTransactionScripts(tx, blockTxStore,
|
|
txscript.StandardVerifyFlags, server.sigCache)
|
|
if err != nil {
|
|
minrLog.Tracef("Skipping tx %s due to error in "+
|
|
"ValidateTransactionScripts: %v", tx.Sha(), err)
|
|
logSkippedDeps(tx, deps)
|
|
continue
|
|
}
|
|
|
|
// Spend the transaction inputs in the block transaction store
|
|
// 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(blockTxStore, 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.Sha()] = prioItem.fee
|
|
txSigOpCountsMap[*tx.Sha()] = numSigOps
|
|
|
|
minrLog.Tracef("Adding tx %s (priority %.2f, feePerKB %.2f)",
|
|
prioItem.tx.Sha(), prioItem.priority, prioItem.feePerKB)
|
|
|
|
// Add transactions which depend on this one (and also do not
|
|
// have any other unsatisified dependencies) to the priority
|
|
// queue.
|
|
if deps != nil {
|
|
for e := deps.Front(); e != nil; e = e.Next() {
|
|
// Add the transaction to the priority queue if
|
|
// there are no more dependencies after this
|
|
// one.
|
|
item := e.Value.(*txPrioItem)
|
|
delete(item.dependsOn, *tx.Sha())
|
|
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
|
|
|
|
for _, tx := range blockTxns {
|
|
if nextBlockHeight < stakeValidationHeight {
|
|
break // No SSGen should be present before this height.
|
|
}
|
|
|
|
if isSSGen, _ := stake.IsSSGen(tx); isSSGen {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx.MsgTx())
|
|
if maybeInsertStakeTx(blockManager, txCopy, treeValid) {
|
|
vb := stake.GetSSGenVoteBits(txCopy)
|
|
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.
|
|
// Decred TODO: This is super inefficient, this block should be
|
|
// cached and stored somewhere.
|
|
topBlock, err := blockManager.GetTopBlockFromChain()
|
|
if err != nil {
|
|
return nil, miningRuleError(ErrGetTopBlock, "couldn't get "+
|
|
"top block")
|
|
}
|
|
topBlockRegTx := topBlock.Transactions()
|
|
|
|
tempBlockTxns := make([]*dcrutil.Tx, 0, len(sourceTxns))
|
|
for _, tx := range blockTxns {
|
|
if tx.Tree() == dcrutil.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.Sha()) {
|
|
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 {
|
|
isSStx, _ := stake.IsSStx(tx)
|
|
if tx.Tree() == dcrutil.TxTreeStake && isSStx {
|
|
// 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 tx.MsgTx().TxOut[0].Value >= requiredStakeDifficulty {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx.MsgTx())
|
|
if maybeInsertStakeTx(blockManager, txCopy, treeValid) {
|
|
blockTxnsStake = append(blockTxnsStake, txCopy)
|
|
freshStake++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't let this overflow.
|
|
if freshStake >= int(server.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.
|
|
}
|
|
|
|
isSSRtx, _ := stake.IsSSRtx(tx)
|
|
if tx.Tree() == dcrutil.TxTreeStake && isSSRtx {
|
|
txCopy := dcrutil.NewTxDeepTxIns(tx.MsgTx())
|
|
if maybeInsertStakeTx(blockManager, txCopy, treeValid) {
|
|
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),
|
|
[]uint64{0, 0, 0, rand})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
coinbaseTx, err := createCoinbaseTx(coinbaseScript,
|
|
opReturnPkScript,
|
|
nextBlockHeight,
|
|
payToAddress,
|
|
uint16(voters),
|
|
server.chainParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
coinbaseTx.SetTree(dcrutil.TxTreeRegular) // Coinbase only in regular tx tree
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
numCoinbaseSigOps := int64(blockchain.CountSigOps(coinbaseTx, true, false))
|
|
blockSize += uint32(coinbaseTx.MsgTx().SerializeSize())
|
|
blockSigOps += numCoinbaseSigOps
|
|
txFeesMap[*coinbaseTx.Sha()] = 0
|
|
txSigOpCountsMap[*coinbaseTx.Sha()] = 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() == dcrutil.TxTreeRegular {
|
|
blockTxnsRegular = append(blockTxnsRegular, tx)
|
|
} else if tx.Tree() == dcrutil.TxTreeStake {
|
|
continue
|
|
} else {
|
|
minrLog.Tracef("Error adding tx %s to block; invalid tree", tx.Sha())
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, tx := range blockTxnsRegular {
|
|
fee, ok := txFeesMap[*tx.Sha()]
|
|
if !ok {
|
|
return nil, fmt.Errorf("couldn't find fee for tx %v",
|
|
*tx.Sha())
|
|
}
|
|
totalFees += fee
|
|
txFees = append(txFees, fee)
|
|
|
|
tsos, ok := txSigOpCountsMap[*tx.Sha()]
|
|
if !ok {
|
|
return nil, fmt.Errorf("couldn't find sig ops count for tx %v",
|
|
*tx.Sha())
|
|
}
|
|
txSigOpCounts = append(txSigOpCounts, tsos)
|
|
}
|
|
|
|
for _, tx := range blockTxnsStake {
|
|
fee, ok := txFeesMap[*tx.Sha()]
|
|
if !ok {
|
|
return nil, fmt.Errorf("couldn't find fee for stx %v",
|
|
*tx.Sha())
|
|
}
|
|
totalFees += fee
|
|
txFees = append(txFees, fee)
|
|
|
|
tsos, ok := txSigOpCountsMap[*tx.Sha()]
|
|
if !ok {
|
|
return nil, fmt.Errorf("couldn't find sig ops count for stx %v",
|
|
*tx.Sha())
|
|
}
|
|
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(server.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, err := medianAdjustedTime(chainState, timeSource)
|
|
if err != nil {
|
|
return nil, miningRuleError(ErrGettingMedianTime, err.Error())
|
|
}
|
|
|
|
requiredDifficulty, err := blockManager.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((server.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(nextBlockHeight, payToAddress,
|
|
server.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 {
|
|
txs, err := blockManager.FetchTransactionStore(tx, treeValid)
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to fetch tx store for tx %v",
|
|
tx.Sha())
|
|
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
|
|
txData, exists := txs[*originHash]
|
|
if !exists || txData.Err != nil || txData.Tx == 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 = txData.Tx.MsgTx().TxOut[originIdx].Value
|
|
txIn.BlockHeight = uint32(txData.BlockHeight)
|
|
txIn.BlockIndex = txData.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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a new block ready to be solved.
|
|
merkles := blockchain.BuildMerkleTreeStore(blockTxnsRegular)
|
|
merklesStake := blockchain.BuildMerkleTreeStore(blockTxnsStake)
|
|
|
|
var msgBlock wire.MsgBlock
|
|
msgBlock.Header = wire.BlockHeader{
|
|
Version: server.chainParams.CurrentBlockVersion,
|
|
PrevBlock: *prevHash,
|
|
MerkleRoot: *merkles[len(merkles)-1],
|
|
StakeRoot: *merklesStake[len(merklesStake)-1],
|
|
VoteBits: votebits,
|
|
FinalState: finalState,
|
|
Voters: uint16(voters),
|
|
FreshStake: uint8(freshStake),
|
|
Revocations: uint8(revocations),
|
|
PoolSize: poolSize,
|
|
Timestamp: ts,
|
|
SBits: requiredStakeDifficulty,
|
|
Bits: requiredDifficulty,
|
|
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())
|
|
}
|
|
}
|
|
|
|
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)
|
|
block.SetHeight(nextBlockHeight)
|
|
|
|
if err := blockchain.CheckWorklessBlockSanity(block,
|
|
server.timeSource,
|
|
server.chainParams); err != nil {
|
|
str := fmt.Sprintf("failed to do final check for block workless "+
|
|
"sanity when making new block template: %v",
|
|
err.Error())
|
|
return nil, miningRuleError(ErrCheckConnectBlock, str)
|
|
}
|
|
|
|
if err := blockManager.CheckConnectBlock(block); 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,
|
|
blockchain.CompactToBig(msgBlock.Header.Bits),
|
|
dcrutil.Amount(msgBlock.Header.SBits).ToCoin())
|
|
|
|
blockTemplate := &BlockTemplate{
|
|
Block: &msgBlock,
|
|
Fees: txFees,
|
|
SigOpCounts: txSigOpCounts,
|
|
Height: nextBlockHeight,
|
|
ValidPayAddress: payToAddress != nil,
|
|
}
|
|
|
|
return handleCreatedBlockTemplate(blockTemplate, server.blockManager)
|
|
}
|
|
|
|
// 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 UpdateBlockTime(msgBlock *wire.MsgBlock, bManager *blockManager) 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, err := medianAdjustedTime(&bManager.chainState,
|
|
bManager.server.timeSource)
|
|
if err != nil {
|
|
return miningRuleError(ErrGettingMedianTime, err.Error())
|
|
}
|
|
msgBlock.Header.Timestamp = newTimestamp
|
|
|
|
// If running on a network that requires recalculating the difficulty,
|
|
// do so now.
|
|
if activeNetParams.ResetMinDifficulty {
|
|
difficulty, err := bManager.CalcNextRequiredDifficulty(newTimestamp)
|
|
if err != nil {
|
|
return miningRuleError(ErrGettingDifficulty, err.Error())
|
|
}
|
|
msgBlock.Header.Bits = difficulty
|
|
}
|
|
|
|
return nil
|
|
}
|