[NOD-237] Implement transaction mass (#355)

* [NOD-237] Implemented transaction mass.

* [NOD-237] Added transaction mass validation to the mempool.

* [NOD-237] Made blockMaxMassMax not rely on MaxBlockPayload.

* [NOD-237] Added comments describing the new constants in validate.go.

* [NOD-237] Changed the default blockmaxmass to 10,000,000.

* [NOD-237] Fixed a comment that erroneously didn't refer to mass.

* [NOD-237] Added comments to ValidateTxMass and CalcTxMass.

* [NOD-237] Renamed "size" to "byte". Made validateBlockMass exit early if validation fails. Fixed unit names in comments. In CalcTxMass, moved summing of mass to the bottom of the function.

* [NOD-237] Instead of ErrMassTooHigh, renamed ErrBlockTooBig and ErrTxTooBig. Replaced wire.MaxBlockPayload with MaxMassPerBlock.

* [NOD-237] Fixed sanity checks related to block size in commands.

* [NOD-237] To use up less memory during testing, made the mass in the "too big" test come from pkScripts rather than input bytes.

* [NOD-237] Added an overflow check to validateBlockMass.
This commit is contained in:
stasatdaglabs 2019-08-05 16:04:24 +03:00 committed by Svarog
parent 54b681460d
commit bfdf7a2cf2
25 changed files with 218 additions and 132 deletions

View File

@ -37,9 +37,9 @@ const (
// exists.
ErrDuplicateBlock ErrorCode = iota
// ErrBlockTooBig indicates the serialized block size exceeds the
// maximum allowed size.
ErrBlockTooBig
// ErrBlockMassTooHigh indicates the mass of a block exceeds the maximum
// allowed limits.
ErrBlockMassTooHigh
// ErrBlockVersionTooOld indicates the block version is too old and is
// no longer accepted since the majority of the network has upgraded
@ -105,9 +105,9 @@ const (
// valid transaction must have at least one input.
ErrNoTxInputs
// ErrTxTooBig indicates a transaction exceeds the maximum allowed size
// when serialized.
ErrTxTooBig
// ErrTxMassTooHigh indicates the mass of a transaction exceeds the maximum
// allowed limits.
ErrTxMassTooHigh
// ErrBadTxOutValue indicates an output value for a transaction is
// invalid in some way such as being out of range.
@ -227,7 +227,7 @@ const (
// Map of ErrorCode values back to their constant names for pretty printing.
var errorCodeStrings = map[ErrorCode]string{
ErrDuplicateBlock: "ErrDuplicateBlock",
ErrBlockTooBig: "ErrBlockTooBig",
ErrBlockMassTooHigh: "ErrBlockMassTooHigh",
ErrBlockVersionTooOld: "ErrBlockVersionTooOld",
ErrInvalidTime: "ErrInvalidTime",
ErrTimeTooOld: "ErrTimeTooOld",
@ -242,7 +242,7 @@ var errorCodeStrings = map[ErrorCode]string{
ErrFinalityPointTimeTooOld: "ErrFinalityPointTimeTooOld",
ErrNoTransactions: "ErrNoTransactions",
ErrNoTxInputs: "ErrNoTxInputs",
ErrTxTooBig: "ErrTxTooBig",
ErrTxMassTooHigh: "ErrTxMassTooHigh",
ErrBadTxOutValue: "ErrBadTxOutValue",
ErrDuplicateTxInputs: "ErrDuplicateTxInputs",
ErrBadTxInput: "ErrBadTxInput",

View File

@ -16,7 +16,7 @@ func TestErrorCodeStringer(t *testing.T) {
want string
}{
{ErrDuplicateBlock, "ErrDuplicateBlock"},
{ErrBlockTooBig, "ErrBlockTooBig"},
{ErrBlockMassTooHigh, "ErrBlockMassTooHigh"},
{ErrBlockVersionTooOld, "ErrBlockVersionTooOld"},
{ErrInvalidTime, "ErrInvalidTime"},
{ErrTimeTooOld, "ErrTimeTooOld"},
@ -31,7 +31,7 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrFinalityPointTimeTooOld, "ErrFinalityPointTimeTooOld"},
{ErrNoTransactions, "ErrNoTransactions"},
{ErrNoTxInputs, "ErrNoTxInputs"},
{ErrTxTooBig, "ErrTxTooBig"},
{ErrTxMassTooHigh, "ErrTxMassTooHigh"},
{ErrBadTxOutValue, "ErrBadTxOutValue"},
{ErrDuplicateTxInputs, "ErrDuplicateTxInputs"},
{ErrBadTxInput, "ErrBadTxInput"},

View File

@ -1106,7 +1106,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
replaceSpendScript(sizePadScript)(b)
})
g.assertTipBlockSize(maxBlockSize + 1)
rejected(blockdag.ErrBlockTooBig)
rejected(blockdag.ErrBlockMassTooHigh)
// Parent was rejected, so this block must either be an orphan or
// outright rejected due to an invalid parent.

View File

@ -20,8 +20,8 @@ import (
const (
// MaxSigOpsPerBlock is the maximum number of signature operations
// allowed for a block. It is a fraction of the max block payload size.
MaxSigOpsPerBlock = wire.MaxBlockPayload / 50
// allowed for a block. It is a fraction of the max block transaction mass.
MaxSigOpsPerBlock = wire.MaxMassPerBlock / 50
// MaxCoinbasePayloadLen is the maximum length a coinbase payload can be.
MaxCoinbasePayloadLen = 150
@ -29,6 +29,11 @@ const (
// baseSubsidy is the starting subsidy amount for mined blocks. This
// value is halved every SubsidyHalvingInterval blocks.
baseSubsidy = 50 * util.SatoshiPerBitcoin
// the following are used when calculating a transaction's mass
massPerTxByte = 1
massPerPKScriptByte = 10
massPerSigOp = 10000
)
// isNullOutpoint determines whether or not a previous transaction outpoint
@ -121,13 +126,13 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID
return ruleError(ErrNoTxInputs, "transaction has no inputs")
}
// A transaction must not exceed the maximum allowed block payload when
// A transaction must not exceed the maximum allowed block mass when
// serialized.
serializedTxSize := msgTx.SerializeSize()
if serializedTxSize > wire.MaxBlockPayload {
if serializedTxSize > wire.MaxMassPerBlock {
str := fmt.Sprintf("serialized transaction is too big - got "+
"%d, max %d", serializedTxSize, wire.MaxBlockPayload)
return ruleError(ErrTxTooBig, str)
"%d, max %d", serializedTxSize, wire.MaxMassPerBlock)
return ruleError(ErrTxMassTooHigh, str)
}
// Ensure the transaction amounts are in range. Each transaction
@ -361,6 +366,86 @@ func CountP2SHSigOps(tx *util.Tx, isCoinbase bool, utxoSet UTXOSet) (int, error)
return totalSigOps, nil
}
// ValidateTxMass makes sure that the given transaction's mass does not exceed
// the maximum allowed limit. Currently, it is equivalent to the block mass limit.
// See CalcTxMass for further details.
func ValidateTxMass(tx *util.Tx, utxoSet UTXOSet) error {
txMass, err := CalcTxMass(tx, utxoSet)
if err != nil {
return err
}
if txMass > wire.MaxMassPerBlock {
str := fmt.Sprintf("tx %s has mass %d, which is above the "+
"allowed limit of %d", tx.ID(), txMass, wire.MaxMassPerBlock)
return ruleError(ErrTxMassTooHigh, str)
}
return nil
}
func validateBlockMass(pastUTXO UTXOSet, transactions []*util.Tx) error {
totalMass := uint64(0)
for _, tx := range transactions {
txMass, err := CalcTxMass(tx, pastUTXO)
if err != nil {
return err
}
totalMass += txMass
// We could potentially overflow the accumulator so check for
// overflow as well.
if totalMass < txMass || totalMass > wire.MaxMassPerBlock {
str := fmt.Sprintf("block has total mass %d, which is "+
"above the allowed limit of %d", totalMass, wire.MaxMassPerBlock)
return ruleError(ErrBlockMassTooHigh, str)
}
}
return nil
}
// CalcTxMass sums up and returns the "mass" of a transaction. This number
// is an approximation of how many resources (CPU, RAM, etc.) it would take
// to process the transaction.
// The following properties are considered in the calculation:
// * The transaction length in bytes
// * The length of all output scripts in bytes
// * The count of all input sigOps
func CalcTxMass(tx *util.Tx, utxoSet UTXOSet) (uint64, error) {
txSize := tx.MsgTx().SerializeSize()
if tx.IsCoinBase() {
return uint64(txSize * massPerTxByte), nil
}
pkScriptSize := 0
for _, txOut := range tx.MsgTx().TxOut {
pkScriptSize += len(txOut.PkScript)
}
sigOpsCount := 0
for txInIndex, txIn := range tx.MsgTx().TxIn {
// Ensure the referenced input transaction is available.
entry, ok := utxoSet.Get(txIn.PreviousOutpoint)
if !ok {
str := fmt.Sprintf("output %s referenced from "+
"transaction %s:%d either does not exist or "+
"has already been spent", txIn.PreviousOutpoint,
tx.ID(), txInIndex)
return 0, ruleError(ErrMissingTxOut, str)
}
// Count the precise number of signature operations in the
// referenced public key script.
pkScript := entry.PkScript()
sigScript := txIn.SignatureScript
isP2SH := txscript.IsPayToScriptHash(pkScript)
sigOpsCount += txscript.GetPreciseSigOpCount(sigScript, pkScript, isP2SH)
}
return uint64(txSize*massPerTxByte +
pkScriptSize*massPerPKScriptByte +
sigOpsCount*massPerSigOp), nil
}
// checkBlockHeaderSanity performs some preliminary checks on a block header to
// ensure it is sane before continuing with processing. These checks are
// context free.
@ -444,21 +529,12 @@ func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) (t
"any transactions")
}
// A block must not have more transactions than the max block payload or
// else it is certainly over the block size limit.
if numTx > wire.MaxBlockPayload {
// A block must not have more transactions than the max block mass or
// else it is certainly over the block mass limit.
if numTx > wire.MaxMassPerBlock {
str := fmt.Sprintf("block contains too many transactions - "+
"got %d, max %d", numTx, wire.MaxBlockPayload)
return 0, ruleError(ErrBlockTooBig, str)
}
// A block must not exceed the maximum allowed block payload when
// serialized.
serializedSize := msgBlock.SerializeSize()
if serializedSize > wire.MaxBlockPayload {
str := fmt.Sprintf("serialized block is too big - got %d, "+
"max %d", serializedSize, wire.MaxBlockPayload)
return 0, ruleError(ErrBlockTooBig, str)
"got %d, max %d", numTx, wire.MaxMassPerBlock)
return 0, ruleError(ErrBlockMassTooHigh, str)
}
// The first transaction in a block must be a coinbase.
@ -861,6 +937,10 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
if err := validateSigopsCount(pastUTXO, transactions); err != nil {
return nil, err
}
if err := validateBlockMass(pastUTXO, transactions); err != nil {
return nil, err
}
}
// Perform several checks on the inputs for each transaction. Also

View File

@ -633,7 +633,7 @@ func TestCheckTransactionSanity(t *testing.T) {
{"good one", 1, 1, 1, *subnetworkid.SubnetworkIDNative, nil, nil, nil},
{"no inputs", 0, 1, 1, *subnetworkid.SubnetworkIDNative, nil, nil, ruleError(ErrNoTxInputs, "")},
{"no outputs", 1, 0, 1, *subnetworkid.SubnetworkIDNative, nil, nil, nil},
{"too big", 100000, 1, 1, *subnetworkid.SubnetworkIDNative, nil, nil, ruleError(ErrTxTooBig, "")},
{"too massive", 1, 1000000, 1, *subnetworkid.SubnetworkIDNative, nil, nil, ruleError(ErrTxMassTooHigh, "")},
{"too much satoshi in one output", 1, 1, util.MaxSatoshi + 1,
*subnetworkid.SubnetworkIDNative,
nil,

View File

@ -222,10 +222,10 @@ type TemplateRequest struct {
// Optional long polling.
LongPollID string `json:"longPollId,omitempty"`
// Optional template tweaking. SigOpLimit and SizeLimit can be int64
// Optional template tweaking. SigOpLimit and MassLimit can be int64
// or bool.
SigOpLimit interface{} `json:"sigOpLimit,omitempty"`
SizeLimit interface{} `json:"sizeLimit,omitempty"`
MassLimit interface{} `json:"massLimit,omitempty"`
MaxVersion uint32 `json:"maxVersion,omitempty"`
// Basic pool extension from BIP 0023.
@ -257,7 +257,7 @@ func convertTemplateRequestField(fieldName string, iface interface{}) (interface
}
// UnmarshalJSON provides a custom Unmarshal method for TemplateRequest. This
// is necessary because the SigOpLimit and SizeLimit fields can only be specific
// is necessary because the SigOpLimit and MassLimit fields can only be specific
// types.
func (t *TemplateRequest) UnmarshalJSON(data []byte) error {
type templateRequest TemplateRequest
@ -274,12 +274,12 @@ func (t *TemplateRequest) UnmarshalJSON(data []byte) error {
}
request.SigOpLimit = val
// The SizeLimit field can only be nil, bool, or int64.
val, err = convertTemplateRequestField("sizeLimit", request.SizeLimit)
// The MassLimit field can only be nil, bool, or int64.
val, err = convertTemplateRequestField("massLimit", request.MassLimit)
if err != nil {
return err
}
request.SizeLimit = val
request.MassLimit = val
return nil
}

View File

@ -271,25 +271,25 @@ func TestDAGSvrCmds(t *testing.T) {
{
name: "getBlockTemplate optional - template request with tweaks",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getBlockTemplate", `{"mode":"template","capabilities":["longPoll","coinbaseTxn"],"sigOpLimit":500,"sizeLimit":100000000,"maxVersion":1}`)
return btcjson.NewCmd("getBlockTemplate", `{"mode":"template","capabilities":["longPoll","coinbaseTxn"],"sigOpLimit":500,"massLimit":100000000,"maxVersion":1}`)
},
staticCmd: func() interface{} {
template := btcjson.TemplateRequest{
Mode: "template",
Capabilities: []string{"longPoll", "coinbaseTxn"},
SigOpLimit: 500,
SizeLimit: 100000000,
MassLimit: 100000000,
MaxVersion: 1,
}
return btcjson.NewGetBlockTemplateCmd(&template)
},
marshalled: `{"jsonrpc":"1.0","method":"getBlockTemplate","params":[{"mode":"template","capabilities":["longPoll","coinbaseTxn"],"sigOpLimit":500,"sizeLimit":100000000,"maxVersion":1}],"id":1}`,
marshalled: `{"jsonrpc":"1.0","method":"getBlockTemplate","params":[{"mode":"template","capabilities":["longPoll","coinbaseTxn"],"sigOpLimit":500,"massLimit":100000000,"maxVersion":1}],"id":1}`,
unmarshalled: &btcjson.GetBlockTemplateCmd{
Request: &btcjson.TemplateRequest{
Mode: "template",
Capabilities: []string{"longPoll", "coinbaseTxn"},
SigOpLimit: int64(500),
SizeLimit: int64(100000000),
MassLimit: int64(100000000),
MaxVersion: 1,
},
},
@ -297,25 +297,25 @@ func TestDAGSvrCmds(t *testing.T) {
{
name: "getBlockTemplate optional - template request with tweaks 2",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getBlockTemplate", `{"mode":"template","capabilities":["longPoll","coinbaseTxn"],"sigOpLimit":true,"sizeLimit":100000000,"maxVersion":1}`)
return btcjson.NewCmd("getBlockTemplate", `{"mode":"template","capabilities":["longPoll","coinbaseTxn"],"sigOpLimit":true,"massLimit":100000000,"maxVersion":1}`)
},
staticCmd: func() interface{} {
template := btcjson.TemplateRequest{
Mode: "template",
Capabilities: []string{"longPoll", "coinbaseTxn"},
SigOpLimit: true,
SizeLimit: 100000000,
MassLimit: 100000000,
MaxVersion: 1,
}
return btcjson.NewGetBlockTemplateCmd(&template)
},
marshalled: `{"jsonrpc":"1.0","method":"getBlockTemplate","params":[{"mode":"template","capabilities":["longPoll","coinbaseTxn"],"sigOpLimit":true,"sizeLimit":100000000,"maxVersion":1}],"id":1}`,
marshalled: `{"jsonrpc":"1.0","method":"getBlockTemplate","params":[{"mode":"template","capabilities":["longPoll","coinbaseTxn"],"sigOpLimit":true,"massLimit":100000000,"maxVersion":1}],"id":1}`,
unmarshalled: &btcjson.GetBlockTemplateCmd{
Request: &btcjson.TemplateRequest{
Mode: "template",
Capabilities: []string{"longPoll", "coinbaseTxn"},
SigOpLimit: true,
SizeLimit: int64(100000000),
MassLimit: int64(100000000),
MaxVersion: 1,
},
},
@ -1152,9 +1152,9 @@ func TestDAGSvrCmdErrors(t *testing.T) {
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
},
{
name: "invalid template request sizelimit field",
name: "invalid template request masslimit field",
result: &btcjson.TemplateRequest{},
marshalled: `{"sizelimit":"invalid"}`,
marshalled: `{"masslimit":"invalid"}`,
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
},
}

View File

@ -121,6 +121,7 @@ type GetBlockTemplateResultTx struct {
Data string `json:"data"`
ID string `json:"id"`
Depends []int64 `json:"depends"`
Mass uint64 `json:"mass"`
Fee uint64 `json:"fee"`
SigOps int64 `json:"sigOps"`
}
@ -141,7 +142,7 @@ type GetBlockTemplateResult struct {
Height uint64 `json:"height"`
ParentHashes []string `json:"parentHashes"`
SigOpLimit int64 `json:"sigOpLimit,omitempty"`
SizeLimit int64 `json:"sizeLimit,omitempty"`
MassLimit int64 `json:"massLimit,omitempty"`
Transactions []GetBlockTemplateResultTx `json:"transactions"`
AcceptedIDMerkleRoot string `json:"acceptedIdMerkleRoot"`
UTXOCommitment string `json:"utxoCommitment"`

View File

@ -69,10 +69,10 @@ func (bi *blockImporter) readBlock() ([]byte, error) {
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
return nil, err
}
if blockLen > wire.MaxBlockPayload {
if blockLen > wire.MaxMessagePayload {
return nil, fmt.Errorf("block payload of %d bytes is larger "+
"than the max allowed %d bytes", blockLen,
wire.MaxBlockPayload)
wire.MaxMessagePayload)
}
serializedBlock := make([]byte, blockLen)

View File

@ -28,7 +28,6 @@ import (
"github.com/daglabs/btcd/util/network"
"github.com/daglabs/btcd/util/subnetworkid"
"github.com/daglabs/btcd/version"
"github.com/daglabs/btcd/wire"
"github.com/jessevdk/go-flags"
)
@ -47,10 +46,9 @@ const (
defaultMaxRPCWebsockets = 25
defaultMaxRPCConcurrentReqs = 20
defaultDbType = "ffldb"
defaultBlockMinSize = 0
defaultBlockMaxSize = 750000
blockMaxSizeMin = 1000
blockMaxSizeMax = wire.MaxBlockPayload - 1000
defaultBlockMaxMass = 10000000
blockMaxMassMin = 1000
blockMaxMassMax = 10000000
defaultGenerate = false
defaultMaxOrphanTransactions = 100
//DefaultMaxOrphanTxSize is the default maximum size for an orphan transaction
@ -148,8 +146,7 @@ type configFlags struct {
MaxOrphanTxs int `long:"maxorphantx" description:"Max number of orphan transactions to keep in memory"`
Generate bool `long:"generate" description:"Generate (mine) bitcoins using the CPU"`
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"`
BlockMaxMass uint64 `long:"blockmaxmass" description:"Maximum transaction mass to be used 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"`
EnableCFilters bool `long:"enablecfilters" description:"Enable committed filtering (CF) support"`
@ -312,8 +309,7 @@ func loadConfig() (*Config, []string, error) {
DbType: defaultDbType,
RPCKey: defaultRPCKeyFile,
RPCCert: defaultRPCCertFile,
BlockMinSize: defaultBlockMinSize,
BlockMaxSize: defaultBlockMaxSize,
BlockMaxMass: defaultBlockMaxMass,
MaxOrphanTxs: defaultMaxOrphanTransactions,
SigCacheMaxSize: defaultSigCacheMaxSize,
Generate: defaultGenerate,
@ -659,14 +655,14 @@ func loadConfig() (*Config, []string, error) {
return nil, nil, err
}
// Limit the max block size to a sane value.
if cfg.BlockMaxSize < blockMaxSizeMin || cfg.BlockMaxSize >
blockMaxSizeMax {
// Limit the max block mass to a sane value.
if cfg.BlockMaxMass < blockMaxMassMin || cfg.BlockMaxMass >
blockMaxMassMax {
str := "%s: The blockmaxsize option must be in between %d " +
str := "%s: The blockmaxmass option must be in between %d " +
"and %d -- parsed [%d]"
err := fmt.Errorf(str, funcName, blockMaxSizeMin,
blockMaxSizeMax, cfg.BlockMaxSize)
err := fmt.Errorf(str, funcName, blockMaxMassMin,
blockMaxMassMax, cfg.BlockMaxMass)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
@ -682,9 +678,6 @@ func loadConfig() (*Config, []string, error) {
return nil, nil, err
}
// Limit minimum block sizes to max block size.
cfg.BlockMinSize = minUint32(cfg.BlockMinSize, cfg.BlockMaxSize)
// Look for illegal characters in the user agent comments.
for _, uaComment := range cfg.UserAgentComments {
if strings.ContainsAny(uaComment, "/:()") {

View File

@ -81,10 +81,10 @@ func (bi *blockImporter) readBlock() ([]byte, error) {
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
return nil, err
}
if blockLen > wire.MaxBlockPayload {
if blockLen > wire.MaxMessagePayload {
return nil, fmt.Errorf("block payload of %d bytes is larger "+
"than the max allowed %d bytes", blockLen,
wire.MaxBlockPayload)
wire.MaxMessagePayload)
}
serializedBlock := make([]byte, blockLen)

View File

@ -938,6 +938,16 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rejectDupOrphans bo
"transaction's sequence locks on inputs not met")
}
// Don't allow transactions that exceed the maximum allowed
// transaction mass.
err = blockdag.ValidateTxMass(tx, mp.mpUTXOSet)
if err != nil {
if ruleError, ok := err.(blockdag.RuleError); ok {
return nil, nil, dagRuleError(ruleError)
}
return nil, nil, err
}
// Perform several checks on the transaction inputs using the invariant
// rules in blockchain for what transactions are allowed into blocks.
// Also returns the fees associated with the transaction which will be

View File

@ -162,6 +162,10 @@ type BlockTemplate struct {
// requirement.
Block *wire.MsgBlock
// TxMasses contains the mass of each transaction in the generated
// template performs.
TxMasses []uint64
// 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
@ -279,7 +283,7 @@ func NewBlkTmplGenerator(policy *Policy, params *dagconfig.Params,
// 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
// Any transactions which would cause the block to exceed the BlockMaxMass
// policy setting, exceed the maximum allowed signature operations per block, or
// otherwise cause the block to be invalid are skipped.
//
@ -294,7 +298,7 @@ func NewBlkTmplGenerator(policy *Policy, params *dagconfig.Params,
// |-----------------------------------| | --
// | | |
// | | |
// | | |--- policy.BlockMaxSize
// | | |--- policy.BlockMaxMass
// | Transactions prioritized by fee | |
// | until <= policy.TxMinFreeFee | |
// | | |
@ -310,6 +314,8 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
defer g.dag.RUnlock()
nextBlockBlueScore := g.dag.VirtualBlueScore()
nextBlockUTXO := g.dag.UTXOSet()
coinbasePayloadPkScript, err := txscript.PayToAddrScript(payToAddress)
if err != nil {
return nil, err
@ -328,6 +334,10 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
if err != nil {
return nil, err
}
coinbaseTxMass, err := blockdag.CalcTxMass(coinbaseTx, nextBlockUTXO)
if err != nil {
return nil, err
}
numCoinbaseSigOps := int64(blockdag.CountSigOps(coinbaseTx))
// Get the current source transactions and create a priority queue to
@ -346,21 +356,20 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
blockTxns := make([]*util.Tx, 0, len(sourceTxns)+1)
blockTxns = append(blockTxns, coinbaseTx)
// 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())
blockMass := coinbaseTxMass
blockSigOps := numCoinbaseSigOps
totalFees := uint64(0)
// 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.
// Create slices to hold the mass, 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.
txMasses := make([]uint64, 0, len(sourceTxns)+1)
txFees := make([]uint64, 0, len(sourceTxns)+1)
txSigOpCounts := make([]int64, 0, len(sourceTxns)+1)
txMasses = append(txMasses, coinbaseTxMass)
txFees = append(txFees, 0) // For coinbase tx
txSigOpCounts = append(txSigOpCounts, numCoinbaseSigOps)
@ -423,18 +432,22 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
gasUsageMap[subnetworkID] = gasUsage + txGas
}
// Enforce maximum block size. Also check for overflow.
txSize := uint32(tx.MsgTx().SerializeSize())
blockPlusTxSize := blockSize + txSize
if blockPlusTxSize < blockSize ||
blockPlusTxSize >= g.policy.BlockMaxSize {
// Enforce maximum transaction mass per block. Also check
// for overflow.
txMass, err := blockdag.CalcTxMass(tx, g.dag.UTXOSet())
if err != nil {
log.Tracef("Skipping tx %s due to error in "+
"CalcTxMass: %s", tx.ID(), err)
continue
}
if blockMass+txMass < blockMass ||
blockMass >= g.policy.BlockMaxMass {
log.Tracef("Skipping tx %s because it would exceed "+
"the max block size", tx.ID())
"the max block mass", tx.ID())
continue
}
// Enforce maximum signature operations per block. Also check
// Enforce maximum signature operations per block. Also check
// for overflow.
numSigOps := int64(blockdag.CountSigOps(tx))
if blockSigOps+numSigOps < blockSigOps ||
@ -476,12 +489,13 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
}
// Add the transaction to the block, increment counters, and
// save the fees and signature operation counts to the block
// save the masses, fees, and signature operation counts to the block
// template.
blockTxns = append(blockTxns, tx)
blockSize += txSize
blockSigOps += int64(numSigOps)
blockMass += txMass
blockSigOps += numSigOps
totalFees += prioItem.fee
txMasses = append(txMasses, txMass)
txFees = append(txFees, prioItem.fee)
txSigOpCounts = append(txSigOpCounts, numSigOps)
@ -489,12 +503,6 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
prioItem.tx.ID(), prioItem.feePerKB)
}
// 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.
blockSize -= wire.MaxVarIntPayload -
uint32(wire.VarIntSerializeSize(uint64(len(blockTxns))))
// 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.
@ -553,12 +561,13 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
}
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,
"%d signature operations, %d mass, target difficulty %064x)",
len(msgBlock.Transactions), totalFees, blockSigOps, blockMass,
util.CompactToBig(msgBlock.Header.Bits))
return &BlockTemplate{
Block: &msgBlock,
TxMasses: txMasses,
Fees: txFees,
SigOpCounts: txSigOpCounts,
ValidPayAddress: payToAddress != nil,

View File

@ -89,7 +89,7 @@ func TestNewBlockTemplate(t *testing.T) {
}
policy := Policy{
BlockMaxSize: 50000,
BlockMaxMass: 50000,
}
// First we create a block to have coinbase funds for the rest of the test.

View File

@ -8,11 +8,7 @@ package mining
// 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 to be used when generating
// a block template.
BlockMinSize uint32
// BlockMaxSize is the maximum block size to be used when generating a
// BlockMaxMass is the maximum block mass to be used when generating a
// block template.
BlockMaxSize uint32
BlockMaxMass uint64
}

View File

@ -46,7 +46,7 @@ func PrepareBlockForTest(dag *blockdag.BlockDAG, params *dagconfig.Params, paren
oldVirtual := blockdag.SetVirtualForTest(dag, newVirtual)
defer blockdag.SetVirtualForTest(dag, oldVirtual)
policy := Policy{
BlockMaxSize: 50000,
BlockMaxMass: 50000,
}
txSource := &fakeTxSource{

View File

@ -1761,6 +1761,7 @@ func (state *gbtWorkState) blockTemplateResult(dag *blockdag.BlockDAG, useCoinba
Data: hex.EncodeToString(txBuf.Bytes()),
ID: txID.String(),
Depends: depends,
Mass: template.TxMasses[i],
Fee: template.Fees[i],
SigOps: template.SigOpCounts[i],
}
@ -1779,7 +1780,7 @@ func (state *gbtWorkState) blockTemplateResult(dag *blockdag.BlockDAG, useCoinba
Height: template.Height,
ParentHashes: daghash.Strings(header.ParentHashes),
SigOpLimit: blockdag.MaxSigOpsPerBlock,
SizeLimit: wire.MaxBlockPayload,
MassLimit: wire.MaxMassPerBlock,
Transactions: transactions,
AcceptedIDMerkleRoot: header.AcceptedIDMerkleRoot.String(),
UTXOCommitment: header.UTXOCommitment.String(),
@ -1822,6 +1823,7 @@ func (state *gbtWorkState) blockTemplateResult(dag *blockdag.BlockDAG, useCoinba
Data: hex.EncodeToString(txBuf.Bytes()),
ID: tx.TxID().String(),
Depends: []int64{},
Mass: template.TxMasses[0],
Fee: template.Fees[0],
SigOps: template.SigOpCounts[0],
}
@ -2028,8 +2030,8 @@ func chainErrToGBTErrString(err error) string {
switch ruleErr.ErrorCode {
case blockdag.ErrDuplicateBlock:
return "duplicate"
case blockdag.ErrBlockTooBig:
return "bad-blk-length"
case blockdag.ErrBlockMassTooHigh:
return "bad-blk-mass"
case blockdag.ErrBlockVersionTooOld:
return "bad-version"
case blockdag.ErrInvalidTime:
@ -2054,8 +2056,8 @@ func chainErrToGBTErrString(err error) string {
return "bad-txns-none"
case blockdag.ErrNoTxInputs:
return "bad-txns-noinputs"
case blockdag.ErrTxTooBig:
return "bad-txns-size"
case blockdag.ErrTxMassTooHigh:
return "bad-txns-mass"
case blockdag.ErrBadTxOutValue:
return "bad-txns-outputvalue"
case blockdag.ErrDuplicateTxInputs:

View File

@ -283,7 +283,7 @@ var helpDescsEnUS = map[string]string{
"templateRequest-capabilities": "List of capabilities",
"templateRequest-longPollId": "The long poll ID of a job to monitor for expiration; required and valid only for long poll requests ",
"templateRequest-sigOpLimit": "Number of signature operations allowed in blocks (this parameter is ignored)",
"templateRequest-sizeLimit": "Number of bytes allowed in blocks (this parameter is ignored)",
"templateRequest-massLimit": "Max transaction mass allowed in blocks (this parameter is ignored)",
"templateRequest-maxVersion": "Highest supported block version number (this parameter is ignored)",
"templateRequest-target": "The desired target for the block template (this parameter is ignored)",
"templateRequest-data": "Hex-encoded block data (only for mode=proposal)",
@ -294,6 +294,7 @@ var helpDescsEnUS = map[string]string{
"getBlockTemplateResultTx-hash": "Hex-encoded transaction hash (little endian if treated as a 256-bit number)",
"getBlockTemplateResultTx-id": "Hex-encoded transaction ID (little endian if treated as a 256-bit number)",
"getBlockTemplateResultTx-depends": "Other transactions before this one (by 1-based index in the 'transactions' list) that must be present in the final block if this one is",
"getBlockTemplateResultTx-mass": "Total mass of all transactions in the block",
"getBlockTemplateResultTx-fee": "Difference in value between transaction inputs and outputs (in Satoshi)",
"getBlockTemplateResultTx-sigOps": "Total number of signature operations as counted for purposes of block limits",
@ -305,8 +306,8 @@ var helpDescsEnUS = map[string]string{
"getBlockTemplateResult-curTime": "Current time as seen by the server (recommended for block time); must fall within mintime/maxtime rules",
"getBlockTemplateResult-height": "Height of the block to be solved",
"getBlockTemplateResult-parentHashes": "Hex-encoded big-endian hashes of the parent blocks",
"getBlockTemplateResult-sigOpLimit": "Number of sigops allowed in blocks ",
"getBlockTemplateResult-sizeLimit": "Number of bytes allowed in blocks",
"getBlockTemplateResult-sigOpLimit": "Number of sigops allowed in blocks",
"getBlockTemplateResult-massLimit": "Max transaction mass allowed in blocks",
"getBlockTemplateResult-transactions": "Array of transactions as JSON objects",
"getBlockTemplateResult-acceptedIdMerkleRoot": "The root of the merkle tree of transaction IDs accepted by this block",
"getBlockTemplateResult-utxoCommitment": "An ECMH UTXO commitment of this block",

View File

@ -101,8 +101,7 @@ func NewServer(listenAddrs []string, db database.DB, dagParams *dagconfig.Params
// 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{
BlockMinSize: cfg.BlockMinSize,
BlockMaxSize: cfg.BlockMaxSize,
BlockMaxMass: cfg.BlockMaxMass,
}
blockTemplateGenerator := mining.NewBlkTmplGenerator(&policy,
s.p2pServer.DAGParams, s.p2pServer.TxMemPool, s.p2pServer.DAG, s.p2pServer.TimeSource, s.p2pServer.SigCache)

View File

@ -21,12 +21,12 @@ import (
// backing array multiple times.
const defaultTransactionAlloc = 2048
// MaxBlockPayload is the maximum bytes a block message can be in bytes.
const MaxBlockPayload = 1000000
// maxMassPerBlock is the maximum total transaction mass a block may contain.
const MaxMassPerBlock = 10000000
// maxTxPerBlock is the maximum number of transactions that could
// possibly fit into a block.
const maxTxPerBlock = (MaxBlockPayload / minTxPayload) + 1
const maxTxPerBlock = (MaxMassPerBlock / minTxPayload) + 1
// TxLoc holds locator data for the offset and length of where a transaction is
// located within a MsgBlock data buffer.
@ -217,10 +217,7 @@ func (msg *MsgBlock) Command() string {
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgBlock) MaxPayloadLength(pver uint32) uint32 {
// Block header at 80 bytes + transaction count + max transactions
// which can vary up to the MaxBlockPayload (including the block header
// and transaction count).
return MaxBlockPayload
return MaxMessagePayload
}
// BlockHash computes the block identifier hash for this block.

View File

@ -40,8 +40,7 @@ func TestBlock(t *testing.T) {
}
// Ensure max payload is expected value for latest protocol version.
// Num addresses (varInt) + max allowed addresses.
wantPayload := uint32(1000000)
wantPayload := uint32(1024 * 1024 * 32)
maxPayload := msg.MaxPayloadLength(pver)
if maxPayload != wantPayload {
t.Errorf("MaxPayloadLength: wrong max payload length for "+

View File

@ -133,7 +133,7 @@ func (msg *MsgMerkleBlock) Command() string {
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) MaxPayloadLength(pver uint32) uint32 {
return MaxBlockPayload
return MaxMessagePayload
}
// NewMsgMerkleBlock returns a new bitcoin merkleblock message that conforms to

View File

@ -38,8 +38,7 @@ func TestMerkleBlock(t *testing.T) {
}
// Ensure max payload is expected value for latest protocol version.
// Num addresses (varInt) + max allowed addresses.
wantPayload := uint32(1000000)
wantPayload := uint32(1024 * 1024 * 32)
maxPayload := msg.MaxPayloadLength(pver)
if maxPayload != wantPayload {
t.Errorf("MaxPayloadLength: wrong max payload length for "+

View File

@ -765,7 +765,7 @@ func (msg *MsgTx) Command() string {
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgTx) MaxPayloadLength(pver uint32) uint32 {
return MaxBlockPayload
return MaxMessagePayload
}
// PkScriptLocs returns a slice containing the start of each public key script

View File

@ -37,7 +37,7 @@ func TestTx(t *testing.T) {
}
// Ensure max payload is expected value for latest protocol version.
wantPayload := uint32(1000 * 1000)
wantPayload := uint32(1024 * 1024 * 32)
maxPayload := msg.MaxPayloadLength(pver)
if maxPayload != wantPayload {
t.Errorf("MaxPayloadLength: wrong max payload length for "+