diff --git a/blockchain/weight.go b/blockchain/weight.go new file mode 100644 index 00000000..f3fd2cfe --- /dev/null +++ b/blockchain/weight.go @@ -0,0 +1,109 @@ +// Copyright (c) 2013-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package blockchain + +import ( + "fmt" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" +) + +const ( + // MaxBlockWeight defines the maximum block weight, where "block + // weight" is interpreted as defined in BIP0141. A block's weight is + // calculated as the sum of the of bytes in the existing transactions + // and header, plus the weight of each byte within a transaction. The + // weight of a "base" byte is 4, while the weight of a witness byte is + // 1. As a result, for a block to be valid, the BlockWeight MUST be + // less than, or equal to MaxBlockWeight. + MaxBlockWeight = 4000000 + + // MaxBlockBaseSize is the maximum number of bytes within a block + // which can be allocated to non-witness data. + MaxBlockBaseSize = 1000000 + + // MaxBlockSigOpsCost is the maximum number of signature operations + // allowed for a block. It is calculated via a weighted algorithm which + // weights segragated witness sig ops lower than regular sig ops. + MaxBlockSigOpsCost = 80000 + + // WitnessScaleFactor determines the level of "discount" witness data + // receives compared to "base" data. A scale factor of 4, denotes that + // witness data is 1/4 as cheap as regular non-witness data. + WitnessScaleFactor = 4 +) + +// GetBlockWeight computes the value of the weight metric for a given block. +// Currently the weight metric is simply the sum of the block's serialized size +// without any witness data scaled proportionally by the WitnessScaleFactor, +// and the block's serialized size including any witness data. +func GetBlockWeight(blk *btcutil.Block) int64 { + msgBlock := blk.MsgBlock() + + baseSize := msgBlock.SerializeSizeStripped() + totalSize := msgBlock.SerializeSize() + + // (baseSize * 3) + totalSize + return int64((baseSize * (WitnessScaleFactor - 1)) + totalSize) +} + +// GetTransactionWeight computes the value of the weight metric for a given +// transaction. Currently the weight metric is simply the sum of the +// transactions's serialized size without any witness data scaled +// proportionally by the WitnessScaleFactor, and the transaction's serialized +// size including any witness data. +func GetTransactionWeight(tx *btcutil.Tx) int64 { + msgTx := tx.MsgTx() + + baseSize := msgTx.SerializeSizeStripped() + totalSize := msgTx.SerializeSize() + + // (baseSize * 3) + totalSize + return int64((baseSize * (WitnessScaleFactor - 1)) + totalSize) +} + +// GetSigOpCost returns the unified sig op cost for the passed transaction +// respecting current active soft-forks which modified sig op cost counting. +// The unified sig op cost for a transaction is computed as the sum of: the +// legacy sig op count scaled according to the WitnessScaleFactor, the sig op +// count for all p2sh inputs scaled by the WitnessScaleFactor, and finally the +// unscaled sig op count for any inputs spending witness programs. +func GetSigOpCost(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint, + bip16, segWit bool) (int, error) { + + numSigOps := CountSigOps(tx) * WitnessScaleFactor + if bip16 { + numP2SHSigOps, err := CountP2SHSigOps(tx, isCoinBaseTx, utxoView) + if err != nil { + return 0, nil + } + numSigOps += (numP2SHSigOps * WitnessScaleFactor) + } + + if segWit && !isCoinBaseTx { + msgTx := tx.MsgTx() + for txInIndex, txIn := range msgTx.TxIn { + // Ensure the referenced input transaction is available. + originTxHash := &txIn.PreviousOutPoint.Hash + originTxIndex := txIn.PreviousOutPoint.Index + txEntry := utxoView.LookupEntry(originTxHash) + if txEntry == nil || txEntry.IsOutputSpent(originTxIndex) { + str := fmt.Sprintf("unable to find unspent output "+ + "%v referenced from transaction %s:%d", + txIn.PreviousOutPoint, tx.Hash(), txInIndex) + return 0, ruleError(ErrMissingTx, str) + } + + witness := txIn.Witness + sigScript := txIn.SignatureScript + pkScript := txEntry.PkScriptByIndex(originTxIndex) + numSigOps += txscript.GetWitnessSigOpCount(sigScript, pkScript, witness) + } + + } + + return numSigOps, nil +} diff --git a/mempool/mempool.go b/mempool/mempool.go index 89ddee7f..99cb963d 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -805,7 +805,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit, rejec // which is more desirable. Therefore, as long as the size of the // transaction does not exceeed 1000 less than the reserved space for // high-priority transactions, don't require a fee for it. - serializedSize := int64(tx.MsgTx().SerializeSize()) + serializedSize := GetTxVirtualSize(tx) minFee := calcMinRequiredTxRelayFee(serializedSize, mp.cfg.Policy.MinRelayTxFee) if serializedSize >= (DefaultBlockPrioritySize-1000) && txFee < minFee { @@ -1150,6 +1150,7 @@ func (mp *TxPool) RawMempoolVerbose() map[string]*btcjson.GetRawMempoolVerboseRe mpd := &btcjson.GetRawMempoolVerboseResult{ Size: int32(tx.MsgTx().SerializeSize()), + Vsize: int32(GetTxVirtualSize(tx)), Fee: btcutil.Amount(desc.Fee).ToBTC(), Time: desc.Added.Unix(), Height: int64(desc.Height), diff --git a/mempool/policy.go b/mempool/policy.go index 0e5fb1ff..fc5c38ef 100644 --- a/mempool/policy.go +++ b/mempool/policy.go @@ -343,3 +343,16 @@ func checkTransactionStandard(tx *btcutil.Tx, height int32, return nil } + +// GetTxVirtualSize computes the virtual size of a given transaction. A +// transaction's virtual size is based off it's weight, creating a discount for +// any witness data it contains, proportional to the current +// blockchain.WitnessScaleFactor value. +func GetTxVirtualSize(tx *btcutil.Tx) int64 { + // vSize := (weight(tx) + 3) / 4 + // := (((baseSize * 3) + totalSize) + 3) / 4 + // We add 3 here as a way to compute the ceiling of the prior arithmetic + // to 4. The division by 4 creates a discount for wit witness data. + return (blockchain.GetTransactionWeight(tx) + (blockchain.WitnessScaleFactor - 1)) / + blockchain.WitnessScaleFactor +} diff --git a/rpcserver.go b/rpcserver.go index 0da988a0..3001bd68 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -720,6 +720,9 @@ func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, txReply := &btcjson.TxRawResult{ Hex: mtxHex, Txid: txHash, + Hash: mtx.WitnessHash().String(), + Size: int32(mtx.SerializeSize()), + Vsize: int32(mempool.GetTxVirtualSize(btcutil.NewTx(mtx))), Vin: createVinList(mtx), Vout: createVoutList(mtx, chainParams, nil), Version: mtx.Version,