mining+config: modify GBT mining to limit by weight, add witness commitment

This commit modifies the existing block selection logic to limit
preferentially by weight instead of serialized block size, and also to
adhere to the new sig-op cost limits which are weighted according to
the witness discount.
This commit is contained in:
Olaoluwa Osuntokun 2016-10-19 09:57:23 -07:00 committed by Dave Collins
parent 8b130ec4ea
commit 1244c45b88
5 changed files with 172 additions and 72 deletions

View file

@ -1473,6 +1473,7 @@ func newBlockManager(s *server, indexManager blockchain.IndexManager) (*blockMan
Notifications: bm.handleNotifyMsg,
SigCache: s.sigCache,
IndexManager: indexManager,
HashCache: s.hashCache,
})
if err != nil {
return nil, err

View file

@ -20,13 +20,13 @@ import (
"strings"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr"
"github.com/btcsuite/btcd/database"
_ "github.com/btcsuite/btcd/database/ffldb"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/go-socks/socks"
flags "github.com/jessevdk/go-flags"
@ -49,8 +49,12 @@ const (
defaultFreeTxRelayLimit = 15.0
defaultBlockMinSize = 0
defaultBlockMaxSize = 750000
defaultBlockMinWeight = 0
defaultBlockMaxWeight = 3000000
blockMaxSizeMin = 1000
blockMaxSizeMax = wire.MaxBlockPayload - 1000
blockMaxSizeMax = blockchain.MaxBlockBaseSize - 1000
blockMaxWeightMin = 4000
blockMaxWeightMax = blockchain.MaxBlockWeight - 4000
defaultGenerate = false
defaultMaxOrphanTransactions = 100
defaultMaxOrphanTxSize = 100000
@ -140,6 +144,8 @@ type config struct {
MiningAddrs []string `long:"miningaddr" description:"Add the specified payment address to the list of addresses to use for generated blocks -- At least one address is required if the generate option is set"`
BlockMinSize uint32 `long:"blockminsize" description:"Mininum block size in bytes to be used when creating a block"`
BlockMaxSize uint32 `long:"blockmaxsize" description:"Maximum block size in bytes to be used when creating a block"`
BlockMinWeight uint32 `long:"blockminweight" description:"Mininum block weight to be used when creating a block"`
BlockMaxWeight uint32 `long:"blockmaxweight" description:"Maximum block weight to be used when creating a block"`
BlockPrioritySize uint32 `long:"blockprioritysize" description:"Size in bytes for high-priority/low-fee transactions when creating a block"`
UserAgentComments []string `long:"uacomment" description:"Comment to add to the user agent -- See BIP 14 for more information."`
NoPeerBloomFilters bool `long:"nopeerbloomfilters" description:"Disable bloom filtering support"`
@ -407,6 +413,8 @@ func loadConfig() (*config, []string, error) {
FreeTxRelayLimit: defaultFreeTxRelayLimit,
BlockMinSize: defaultBlockMinSize,
BlockMaxSize: defaultBlockMaxSize,
BlockMinWeight: defaultBlockMinWeight,
BlockMaxWeight: defaultBlockMaxWeight,
BlockPrioritySize: mempool.DefaultBlockPrioritySize,
MaxOrphanTxs: defaultMaxOrphanTransactions,
SigCacheMaxSize: defaultSigCacheMaxSize,
@ -531,8 +539,8 @@ func loadConfig() (*config, []string, error) {
cfg.DisableDNSSeed = true
}
if numNets > 1 {
str := "%s: The testnet, regtest, and simnet params can't be " +
"used together -- choose one of the three"
str := "%s: The testnet, regtest, segnet, and simnet params " +
"can't be used together -- choose one of the four"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
@ -723,7 +731,20 @@ func loadConfig() (*config, []string, error) {
return nil, nil, err
}
// Limit the max orphan count to a sane value.
// Limit the max block weight to a sane value.
if cfg.BlockMaxWeight < blockMaxWeightMin ||
cfg.BlockMaxWeight > blockMaxWeightMax {
str := "%s: The blockmaxweight option must be in between %d " +
"and %d -- parsed [%d]"
err := fmt.Errorf(str, funcName, blockMaxWeightMin,
blockMaxWeightMax, cfg.BlockMaxWeight)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
}
// Limit the max orphan count to a sane vlue.
if cfg.MaxOrphanTxs < 0 {
str := "%s: The maxorphantx option may not be less than 0 " +
"-- parsed [%d]"
@ -736,6 +757,24 @@ func loadConfig() (*config, []string, error) {
// Limit the block priority and minimum block sizes to max block size.
cfg.BlockPrioritySize = minUint32(cfg.BlockPrioritySize, cfg.BlockMaxSize)
cfg.BlockMinSize = minUint32(cfg.BlockMinSize, cfg.BlockMaxSize)
cfg.BlockMinWeight = minUint32(cfg.BlockMinWeight, cfg.BlockMaxWeight)
switch {
// If the max block size isn't set, but the max weight is, then we'll
// set the limit for the max block size to a safe limit so weight takes
// precedence.
case cfg.BlockMaxSize == defaultBlockMaxSize &&
cfg.BlockMaxWeight != defaultBlockMaxWeight:
cfg.BlockMaxSize = blockchain.MaxBlockBaseSize - 1000
// If the max block weight isn't set, but the block size is, then we'll
// scale the set weight accordingly based on the max block size value.
case cfg.BlockMaxSize != defaultBlockMaxSize &&
cfg.BlockMaxWeight == defaultBlockMaxWeight:
cfg.BlockMaxWeight = cfg.BlockMaxSize * blockchain.WitnessScaleFactor
}
// Look for illegal characters in the user agent comments.
for _, uaComment := range cfg.UserAgentComments {

View file

@ -197,9 +197,9 @@ type BlockTemplate struct {
// sum of the fees of all other transactions.
Fees []int64
// SigOpCounts contains the number of signature operations each
// SigOpCosts contains the number of signature operations each
// transaction in the generated template performs.
SigOpCounts []int64
SigOpCosts []int64
// Height is the height at which the block template connects to the main
// chain.
@ -210,6 +210,12 @@ type BlockTemplate struct {
// NewBlockTemplate for details on which this can be useful to generate
// templates without a coinbase payment address.
ValidPayAddress bool
// WitnessCommitment is a commitment to the witness data (if any)
// within the block. This field will only be populted once segregated
// witness has been activated, and the block contains a transaction
// which has witness data.
WitnessCommitment []byte
}
// mergeUtxoView adds all of the entries in view to viewA. The result is that
@ -347,6 +353,7 @@ type BlkTmplGenerator struct {
chain *blockchain.BlockChain
timeSource blockchain.MedianTimeSource
sigCache *txscript.SigCache
hashCache *txscript.HashCache
}
// NewBlkTmplGenerator returns a new block template generator for the given
@ -358,7 +365,8 @@ type BlkTmplGenerator struct {
func NewBlkTmplGenerator(policy *Policy, params *chaincfg.Params,
txSource TxSource, chain *blockchain.BlockChain,
timeSource blockchain.MedianTimeSource,
sigCache *txscript.SigCache) *BlkTmplGenerator {
sigCache *txscript.SigCache,
hashCache *txscript.HashCache) *BlkTmplGenerator {
return &BlkTmplGenerator{
policy: policy,
@ -367,6 +375,7 @@ func NewBlkTmplGenerator(policy *Policy, params *chaincfg.Params,
chain: chain,
timeSource: timeSource,
sigCache: sigCache,
hashCache: hashCache,
}
}
@ -451,12 +460,13 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress btcutil.Address) (*Bloc
if err != nil {
return nil, err
}
// TODO(roasbeef): add witnesss commitment output
coinbaseTx, err := createCoinbaseTx(g.chainParams, coinbaseScript,
nextBlockHeight, payToAddress)
if err != nil {
return nil, err
}
numCoinbaseSigOps := int64(blockchain.CountSigOps(coinbaseTx))
coinbaseSigOpCost := int64(blockchain.CountSigOps(coinbaseTx)) * blockchain.WitnessScaleFactor
// Get the current source transactions and create a priority queue to
// hold the transactions which are ready for inclusion into a block
@ -490,9 +500,9 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress btcutil.Address) (*Bloc
// 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))
txSigOpCounts := make([]int64, 0, len(sourceTxns))
txSigOpCosts := make([]int64, 0, len(sourceTxns))
txFees = append(txFees, -1) // Updated once known
txSigOpCounts = append(txSigOpCounts, numCoinbaseSigOps)
txSigOpCosts = append(txSigOpCosts, coinbaseSigOpCost)
log.Debugf("Considering %d transactions for inclusion to new block",
len(sourceTxns))
@ -570,6 +580,7 @@ mempoolLoop:
nextBlockHeight)
// Calculate the fee in Satoshi/kB.
// TODO(roasbeef): cost accounting by weight
prioItem.feePerKB = txDesc.FeePerKB
prioItem.fee = txDesc.Fee
@ -591,10 +602,14 @@ mempoolLoop:
// 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 := blockHeaderOverhead + uint32(coinbaseTx.MsgTx().SerializeSize())
blockSigOps := numCoinbaseSigOps
blockWeight := uint32((blockHeaderOverhead * (blockchain.WitnessScaleFactor - 1)) + blockchain.GetTransactionWeight(coinbaseTx))
blockSigOpCost := coinbaseSigOpCost
totalFees := int64(0)
// TODO(roasbeef): should be guarded by version bits state check
var witnessIncluded bool
includeWitness := true
// Choose which transactions make it into the block.
for priorityQueue.Len() > 0 {
// Grab the highest priority (or highest fee per kilobyte
@ -602,45 +617,73 @@ mempoolLoop:
prioItem := heap.Pop(priorityQueue).(*txPrioItem)
tx := prioItem.tx
switch {
// If segregated witness has not been activated yet, then we
// shouldn't include any witness transactions in the block.
case tx.HasWitness() && !segwitActive:
continue
// Otherwise, Keep track of if we've included a transaction
// with witness data or not. If so, then we'll need to include
// the witness commitment as the last output in the coinbase
// transaction.
case tx.HasWitness() && segwitActive:
// If we're about to include a transaction bearing
// witness data, then we'll also need to include a
// witness commitment in the coinbase transaction.
// Therefore, we account for the additional weight
// within the block.
if !witnessIncluded {
// First we account for the additional witness
// data in the witness nonce of the coinbaes
// transaction: 32-bytes of zeroes.
blockWeight += 2 + 32
// Next we account for the additional flag and
// marker bytes in the transaction
// serialization.
blockWeight += (1 + 1) * blockchain.WitnessScaleFactor
// Finally we account for the weight of the
// additional OP_RETURN output: 8-bytes (value)
// + 1-byte (var-int) + 38-bytes (pkScript),
// scaling up the weight as it's non-witness
// data.
blockWeight += (8 + 1 + 38) * blockchain.WitnessScaleFactor
}
witnessIncluded = true
}
// Grab any transactions which depend on this one.
deps := dependers[*tx.Hash()]
// Enforce maximum block size. Also check for overflow.
txSize := uint32(tx.MsgTx().SerializeSize())
blockPlusTxSize := blockSize + txSize
if blockPlusTxSize < blockSize ||
blockPlusTxSize >= g.policy.BlockMaxSize {
txWeight := uint32(blockchain.GetTransactionWeight(tx))
blockPlusTxWeight := uint32(blockWeight + txWeight)
if blockPlusTxWeight < blockWeight ||
blockPlusTxWeight >= g.policy.BlockMaxWeight {
log.Tracef("Skipping tx %s because it would exceed "+
"the max block size", tx.Hash())
"the max block weight", tx.Hash())
logSkippedDeps(tx, deps)
continue
}
// Enforce maximum signature operations per block. Also check
// for overflow.
numSigOps := int64(blockchain.CountSigOps(tx))
if blockSigOps+numSigOps < blockSigOps ||
blockSigOps+numSigOps > blockchain.MaxSigOpsPerBlock {
log.Tracef("Skipping tx %s because it would exceed "+
"the maximum sigops per block", tx.Hash())
logSkippedDeps(tx, deps)
continue
}
numP2SHSigOps, err := blockchain.CountP2SHSigOps(tx, false,
blockUtxos)
// Enforce maximum signature operation cost per block. Also
// check for overflow.
sigOpCost, err := blockchain.GetSigOpCost(tx, false,
blockUtxos, true, includeWitness)
if err != nil {
log.Tracef("Skipping tx %s due to error in "+
"CountP2SHSigOps: %v", tx.Hash(), err)
"GetSigOpCost: %v", tx.Hash(), err)
logSkippedDeps(tx, deps)
continue
}
numSigOps += int64(numP2SHSigOps)
if blockSigOps+numSigOps < blockSigOps ||
blockSigOps+numSigOps > blockchain.MaxSigOpsPerBlock {
log.Tracef("Skipping tx %s because it would exceed "+
"the maximum sigops per block (p2sh)",
tx.Hash())
if blockSigOpCost+int64(sigOpCost) < blockSigOpCost ||
blockSigOpCost+int64(sigOpCost) > blockchain.MaxBlockSigOpsCost {
log.Tracef("Skipping tx %s because it would "+
"exceed the maximum sigops per block", tx.Hash())
logSkippedDeps(tx, deps)
continue
}
@ -649,13 +692,13 @@ mempoolLoop:
// minimum block size.
if sortedByFee &&
prioItem.feePerKB < int64(g.policy.TxMinFreeFee) &&
blockPlusTxSize >= g.policy.BlockMinSize {
blockPlusTxWeight >= g.policy.BlockMinWeight {
log.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)
log.Tracef("Skipping tx %s with feePerKB %d "+
"< TxMinFreeFee %d and block weight %d >= "+
"minBlockWeight %d", tx.Hash(), prioItem.feePerKB,
g.policy.TxMinFreeFee, blockPlusTxWeight,
g.policy.BlockMinWeight)
logSkippedDeps(tx, deps)
continue
}
@ -663,13 +706,13 @@ mempoolLoop:
// 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 ||
if !sortedByFee && (blockPlusTxWeight >= g.policy.BlockPrioritySize ||
prioItem.priority <= MinHighPriority) {
log.Tracef("Switching to sort by fees per kilobyte "+
"blockSize %d >= BlockPrioritySize %d || "+
"priority %.2f <= minHighPriority %.2f",
blockPlusTxSize, g.policy.BlockPrioritySize,
log.Tracef("Switching to sort by fees per "+
"kilobyte blockSize %d >= BlockPrioritySize "+
"%d || priority %.2f <= minHighPriority %.2f",
blockPlusTxWeight, g.policy.BlockPrioritySize,
prioItem.priority, MinHighPriority)
sortedByFee = true
@ -677,11 +720,11 @@ mempoolLoop:
// 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
// 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 ||
if blockPlusTxWeight > g.policy.BlockPrioritySize ||
prioItem.priority < MinHighPriority {
heap.Push(priorityQueue, prioItem)
@ -700,11 +743,11 @@ mempoolLoop:
continue
}
err = blockchain.ValidateTransactionScripts(tx, blockUtxos,
txscript.StandardVerifyFlags, g.sigCache)
txscript.StandardVerifyFlags, g.sigCache,
g.hashCache)
if err != nil {
log.Tracef("Skipping tx %s due to error in "+
"ValidateTransactionScripts: %v", tx.Hash(),
err)
"ValidateTransactionScripts: %v", tx.Hash(), err)
logSkippedDeps(tx, deps)
continue
}
@ -719,11 +762,11 @@ mempoolLoop:
// save the fees and signature operation counts to the block
// template.
blockTxns = append(blockTxns, tx)
blockSize += txSize
blockSigOps += numSigOps
blockWeight += txWeight
blockSigOpCost += int64(sigOpCost)
totalFees += prioItem.fee
txFees = append(txFees, prioItem.fee)
txSigOpCounts = append(txSigOpCounts, numSigOps)
txSigOpCosts = append(txSigOpCosts, int64(sigOpCost))
log.Tracef("Adding tx %s (priority %.2f, feePerKB %.2f)",
prioItem.tx.Hash(), prioItem.priority, prioItem.feePerKB)
@ -742,13 +785,18 @@ mempoolLoop:
}
// Now that the actual transactions have been selected, update the
// block size for the real transaction count and coinbase value with
// block weight for the real transaction count and coinbase value with
// the total fees accordingly.
blockSize -= wire.MaxVarIntPayload -
uint32(wire.VarIntSerializeSize(uint64(len(blockTxns))))
blockWeight -= wire.MaxVarIntPayload -
(uint32(wire.VarIntSerializeSize(uint64(len(blockTxns)))) *
(blockchain.WitnessScaleFactor - 1))
coinbaseTx.MsgTx().TxOut[0].Value += totalFees
txFees[0] = -totalFees
// TODO(roasbeef): add witness commitment
if witnessIncluded {
}
// 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.
@ -766,7 +814,7 @@ mempoolLoop:
}
// Create a new block ready to be solved.
merkles := blockchain.BuildMerkleTreeStore(blockTxns)
merkles := blockchain.BuildMerkleTreeStore(blockTxns, false)
var msgBlock wire.MsgBlock
msgBlock.Header = wire.BlockHeader{
Version: nextBlockVersion,
@ -790,15 +838,15 @@ mempoolLoop:
return nil, err
}
log.Debugf("Created new block template (%d transactions, %d in fees, "+
"%d signature operations, %d bytes, target difficulty %064x)",
len(msgBlock.Transactions), totalFees, blockSigOps, blockSize,
blockchain.CompactToBig(msgBlock.Header.Bits))
log.Debugf("Created new block template (%d transactions, %d in "+
"fees, %d signature operations cost, %d weight, target difficulty "+
"%064x)", len(msgBlock.Transactions), totalFees, blockSigOpCost,
blockWeight, blockchain.CompactToBig(msgBlock.Header.Bits))
return &BlockTemplate{
Block: &msgBlock,
Fees: txFees,
SigOpCounts: txSigOpCounts,
SigOpCosts: txSigOpCosts,
Height: nextBlockHeight,
ValidPayAddress: payToAddress != nil,
}, nil
@ -852,7 +900,7 @@ func (g *BlkTmplGenerator) UpdateExtraNonce(msgBlock *wire.MsgBlock, blockHeight
// Recalculate the merkle root with the updated extra nonce.
block := btcutil.NewBlock(msgBlock)
merkles := blockchain.BuildMerkleTreeStore(block.Transactions())
merkles := blockchain.BuildMerkleTreeStore(block.Transactions(), false)
msgBlock.Header.MerkleRoot = *merkles[len(merkles)-1]
return nil
}

View file

@ -21,12 +21,20 @@ const (
// the generation of block templates. See the documentation for
// NewBlockTemplate for more details on each of these parameters are used.
type Policy struct {
// BlockMinSize is the minimum block size in bytes to be used when
// BlockMinWeight is the minimum block weight to be used when
// generating a block template.
BlockMinWeight uint32
// BlockMaxWeight is the maximum block weight to be used when
// generating a block template.
BlockMaxWeight uint32
// BlockMinWeight is the minimum block size to be used when generating
// a block template.
BlockMinSize uint32
// BlockMaxSize is the maximum block size in bytes to be used when
// generating a block template.
// BlockMaxSize is the maximum block size to be used when generating a
// block template.
BlockMaxSize uint32
// BlockPrioritySize is the size in bytes for high-priority / low-fee

View file

@ -173,6 +173,7 @@ type server struct {
addrManager *addrmgr.AddrManager
connManager *connmgr.ConnManager
sigCache *txscript.SigCache
hashCache *txscript.HashCache
rpcServer *rpcServer
blockManager *blockManager
txMemPool *mempool.TxPool
@ -2412,6 +2413,7 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
timeSource: blockchain.NewMedianTime(),
services: services,
sigCache: txscript.NewSigCache(cfg.SigCacheMaxSize),
hashCache: txscript.NewHashCache(cfg.SigCacheMaxSize),
}
// Create the transaction and address indexes if needed.
@ -2459,7 +2461,7 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
FreeTxRelayLimit: cfg.FreeTxRelayLimit,
MaxOrphanTxs: cfg.MaxOrphanTxs,
MaxOrphanTxSize: defaultMaxOrphanTxSize,
MaxSigOpsPerTx: blockchain.MaxSigOpsPerBlock / 5,
MaxSigOpCostPerTx: blockchain.MaxBlockSigOpsCost / 4,
MinRelayTxFee: cfg.minRelayTxFee,
MaxTxVersion: 2,
},
@ -2483,6 +2485,8 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
// NOTE: The CPU miner relies on the mempool, so the mempool has to be
// created before calling the function to create the CPU miner.
policy := mining.Policy{
BlockMinWeight: cfg.BlockMinWeight,
BlockMaxWeight: cfg.BlockMaxWeight,
BlockMinSize: cfg.BlockMinSize,
BlockMaxSize: cfg.BlockMaxSize,
BlockPrioritySize: cfg.BlockPrioritySize,
@ -2490,7 +2494,7 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
}
blockTemplateGenerator := mining.NewBlkTmplGenerator(&policy,
s.chainParams, s.txMemPool, s.blockManager.chain, s.timeSource,
s.sigCache)
s.sigCache, s.hashCache)
s.cpuMiner = cpuminer.New(&cpuminer.Config{
ChainParams: chainParams,
BlockTemplateGenerator: blockTemplateGenerator,