Knew from before that there are multiple places using these types of expressions involving WITNESS_SCALE_FACTOR. Here is a somewhat minimal diff that applies to those places. It's sad that we allow expressing negative transaction sizes and weights in many places even after this diff... but I agree with deferring fixing that.
<details><summary>Diff, maybe makes sense as a second commit?</summary>
diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h
index 71b5fe2468..bcfc1a0140 100644
--- a/src/consensus/consensus.h
+++ b/src/consensus/consensus.h
@@ -18,7 +18,7 @@ static const int64_t MAX_BLOCK_SIGOPS_COST = 80000;
/** Coinbase transaction outputs can only be spent after this number of new blocks (network rule) */
static const int COINBASE_MATURITY = 100;
-static const int WITNESS_SCALE_FACTOR = 4;
+constexpr unsigned int WITNESS_SCALE_FACTOR{4};
static const size_t MIN_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR * 60; // 60 is the lower bound for the size of a valid serialized CTransaction
static const size_t MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR * 10; // 10 is the lower bound for the size of a serialized CTransaction
diff --git a/src/consensus/validation.h b/src/consensus/validation.h
index 37a40e767e..5cf0b45c86 100644
--- a/src/consensus/validation.h
+++ b/src/consensus/validation.h
@@ -129,15 +129,15 @@ class BlockValidationState : public ValidationState<BlockValidationResult> {};
// using only serialization with and without witness data. As witness_size
// is equal to total_size - stripped_size, this formula is identical to:
// weight = (stripped_size * 3) + total_size.
-static inline int32_t GetTransactionWeight(const CTransaction& tx)
+static inline uint32_t GetTransactionWeight(const CTransaction& tx)
{
return ::GetSerializeSize(TX_NO_WITNESS(tx)) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(TX_WITH_WITNESS(tx));
}
-static inline int64_t GetBlockWeight(const CBlock& block)
+static inline uint64_t GetBlockWeight(const CBlock& block)
{
return ::GetSerializeSize(TX_NO_WITNESS(block)) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(TX_WITH_WITNESS(block));
}
-static inline int64_t GetTransactionInputWeight(const CTxIn& txin)
+static inline uint64_t GetTransactionInputWeight(const CTxIn& txin)
{
// scriptWitness size is added here because witnesses and txins are split up in segwit serialization.
return ::GetSerializeSize(TX_NO_WITNESS(txin)) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(TX_WITH_WITNESS(txin)) + ::GetSerializeSize(txin.scriptWitness.stack);
diff --git a/src/core_io.cpp b/src/core_io.cpp
index 7492e9ca50..fdc9e2cbe7 100644
--- a/src/core_io.cpp
+++ b/src/core_io.cpp
@@ -28,6 +28,7 @@
#include <undo.h>
#include <univalue.h>
#include <util/check.h>
+#include <util/overflow.h>
#include <util/result.h>
#include <util/strencodings.h>
#include <util/string.h>
@@ -435,7 +436,7 @@ void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
entry.pushKV("version", tx.version);
entry.pushKV("size", tx.ComputeTotalSize());
- entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
+ entry.pushKV("vsize", CeilDiv(GetTransactionWeight(tx), WITNESS_SCALE_FACTOR));
entry.pushKV("weight", GetTransactionWeight(tx));
entry.pushKV("locktime", tx.nLockTime);
diff --git a/src/kernel/mempool_entry.h b/src/kernel/mempool_entry.h
index 58824f7a80..cc3e64fa02 100644
--- a/src/kernel/mempool_entry.h
+++ b/src/kernel/mempool_entry.h
@@ -91,7 +91,7 @@ public:
: TxGraph::Ref(std::move(ref)),
tx{tx},
nFee{fee},
- nTxWeight{GetTransactionWeight(*tx)},
+ nTxWeight{static_cast<int32_t>(GetTransactionWeight(*tx))},
nUsageSize{RecursiveDynamicUsage(tx)},
nTime{time},
entry_sequence{entry_sequence},
diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp
index 46ec238c3b..4d20fe7c1e 100644
--- a/src/policy/policy.cpp
+++ b/src/policy/policy.cpp
@@ -373,22 +373,22 @@ bool SpendsNonAnchorWitnessProg(const CTransaction& tx, const CCoinsViewCache& p
return false;
}
-int64_t GetSigOpsAdjustedWeight(int64_t weight, int64_t sigop_cost, unsigned int bytes_per_sigop)
+uint64_t GetSigOpsAdjustedWeight(uint64_t weight, uint64_t sigop_cost, unsigned int bytes_per_sigop)
{
return std::max(weight, sigop_cost * bytes_per_sigop);
}
-int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop)
+uint64_t GetVirtualTransactionSize(uint64_t weight, uint64_t sigop_cost, unsigned int bytes_per_sigop)
{
- return (GetSigOpsAdjustedWeight(nWeight, nSigOpCost, bytes_per_sigop) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
+ return CeilDiv(GetSigOpsAdjustedWeight(weight, sigop_cost, bytes_per_sigop), WITNESS_SCALE_FACTOR);
}
-int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost, unsigned int bytes_per_sigop)
+uint64_t GetVirtualTransactionSize(const CTransaction& tx, uint64_t sigop_cost, unsigned int bytes_per_sigop)
{
- return GetVirtualTransactionSize(GetTransactionWeight(tx), nSigOpCost, bytes_per_sigop);
+ return GetVirtualTransactionSize(GetTransactionWeight(tx), sigop_cost, bytes_per_sigop);
}
-int64_t GetVirtualTransactionInputSize(const CTxIn& txin, int64_t nSigOpCost, unsigned int bytes_per_sigop)
+uint64_t GetVirtualTransactionInputSize(const CTxIn& txin, uint64_t sigop_cost, unsigned int bytes_per_sigop)
{
- return GetVirtualTransactionSize(GetTransactionInputWeight(txin), nSigOpCost, bytes_per_sigop);
+ return GetVirtualTransactionSize(GetTransactionInputWeight(txin), sigop_cost, bytes_per_sigop);
}
diff --git a/src/policy/policy.h b/src/policy/policy.h
index 35189d7133..5f7877ba75 100644
--- a/src/policy/policy.h
+++ b/src/policy/policy.h
@@ -12,6 +12,7 @@
#include <script/interpreter.h>
#include <script/solver.h>
#include <util/feefrac.h>
+#include <util/overflow.h>
#include <cstdint>
#include <string>
@@ -175,9 +176,9 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
bool SpendsNonAnchorWitnessProg(const CTransaction& tx, const CCoinsViewCache& prevouts);
/** Compute the virtual transaction size (weight reinterpreted as bytes). */
-int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop);
-int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost, unsigned int bytes_per_sigop);
-int64_t GetVirtualTransactionInputSize(const CTxIn& tx, int64_t nSigOpCost, unsigned int bytes_per_sigop);
+uint64_t GetVirtualTransactionSize(uint64_t weight, uint64_t sigop_cost, unsigned int bytes_per_sigop);
+uint64_t GetVirtualTransactionSize(const CTransaction& tx, uint64_t sigop_cost, unsigned int bytes_per_sigop);
+uint64_t GetVirtualTransactionInputSize(const CTxIn& tx, uint64_t sigop_cost, unsigned int bytes_per_sigop);
static inline int64_t GetVirtualTransactionSize(const CTransaction& tx)
{
@@ -189,8 +190,8 @@ static inline int64_t GetVirtualTransactionInputSize(const CTxIn& tx)
return GetVirtualTransactionInputSize(tx, 0, 0);
}
-int64_t GetSigOpsAdjustedWeight(int64_t weight, int64_t sigop_cost, unsigned int bytes_per_sigop);
+uint64_t GetSigOpsAdjustedWeight(uint64_t weight, uint64_t sigop_cost, unsigned int bytes_per_sigop);
-static inline FeePerVSize ToFeePerVSize(FeePerWeight feerate) { return {feerate.fee, (feerate.size + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR}; }
+static inline FeePerVSize ToFeePerVSize(FeePerWeight feerate) { return {feerate.fee, static_cast<int32_t>(CeilDiv(static_cast<uint32_t>(feerate.size), WITNESS_SCALE_FACTOR))}; }
#endif // BITCOIN_POLICY_POLICY_H
diff --git a/src/test/util/transaction_utils.cpp b/src/test/util/transaction_utils.cpp
index 7b1e2eb3ed..f69207774f 100644
--- a/src/test/util/transaction_utils.cpp
+++ b/src/test/util/transaction_utils.cpp
@@ -6,6 +6,7 @@
#include <consensus/validation.h>
#include <script/signingprovider.h>
#include <test/util/transaction_utils.h>
+#include <util/overflow.h>
CMutableTransaction BuildCreditingTransaction(const CScript& scriptPubKey, int nValue)
{
@@ -71,14 +72,14 @@ std::vector<CMutableTransaction> SetupDummyInputs(FillableSigningProvider& keyst
return dummyTransactions;
}
-void BulkTransaction(CMutableTransaction& tx, int32_t target_weight)
+void BulkTransaction(CMutableTransaction& tx, uint32_t target_weight)
{
tx.vout.emplace_back(0, CScript() << OP_RETURN);
auto unpadded_weight{GetTransactionWeight(CTransaction(tx))};
assert(target_weight >= unpadded_weight);
// determine number of needed padding bytes by converting weight difference to vbytes
- auto dummy_vbytes = (target_weight - unpadded_weight + (WITNESS_SCALE_FACTOR - 1)) / WITNESS_SCALE_FACTOR;
+ auto dummy_vbytes = CeilDiv(target_weight - unpadded_weight, WITNESS_SCALE_FACTOR);
// compensate for the increase of the compact-size encoded script length
// (note that the length encoding of the unpadded output script needs one byte)
dummy_vbytes -= GetSizeOfCompactSize(dummy_vbytes) - 1;
diff --git a/src/test/util/transaction_utils.h b/src/test/util/transaction_utils.h
index 867554f805..1b3df73e3d 100644
--- a/src/test/util/transaction_utils.h
+++ b/src/test/util/transaction_utils.h
@@ -29,7 +29,7 @@ std::vector<CMutableTransaction> SetupDummyInputs(FillableSigningProvider& keyst
// bulk transaction to reach a certain target weight,
// by appending a single output with padded output script
-void BulkTransaction(CMutableTransaction& tx, int32_t target_weight);
+void BulkTransaction(CMutableTransaction& tx, uint32_t target_weight);
/**
* Produce a satisfying script (scriptSig or witness).
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index edde923452..656f3e676d 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -167,7 +167,7 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
}
// It's ok to use 0 as the number of sigops since we never create any pathological transaction.
- return TxSize{GetVirtualTransactionSize(weight, 0, 0), weight};
+ return TxSize{static_cast<int64_t>(GetVirtualTransactionSize(weight, 0, 0)), weight};
}
TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control)
</details>
Also found:
https://github.com/bitcoin/bitcoin/blob/9cad97f6cdf1bd660732cd10e844a6a7e0771ea0/src/wallet/coinselection.cpp#L457
Which uses signed integers and I understand wanting to punt on it for now... although maybe providing a signed CeilDiv() overload could be an alternative?