Skip to content

Commit e005744

Browse files
ajtownsTheBlueMatt
andcommitted
Make 64 byte txs consensus invalid
The 64-byte transaction check is executed in a new ContextualBlockPreCheck which must be run before CheckBlock (at least in the final checking before writing the block to disk). This function is a bit awkward but is seemingly the simplest way to implement the new check, with the caveat that, because the new function is called before CheckBlock, it can never return a non-CorruptionPossible error state. Co-Authored-By: Matt Corallo <[email protected]>
1 parent 6e09d0b commit e005744

File tree

8 files changed

+66
-3
lines changed

8 files changed

+66
-3
lines changed

src/chainparams.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ class CRegTestParams : public CChainParams {
463463
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = SetupDeployment{.start = 0, .timeout = Consensus::HereticalDeployment::NO_TIMEOUT, .activate = 0x30000000, .abandon = 0x50000000};
464464
consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.bip = 119, .bip_version = 0, .always = true};
465465
consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.bip = 118, .bip_version = 0, .always = true};
466+
consensus.vDeployments[Consensus::DEPLOYMENT_64BYTETX] = SetupDeployment{.bip = 0, .bip_version = 1, .always = true};
466467

467468
consensus.nMinimumChainWork = uint256{};
468469
consensus.defaultAssumeValid = uint256{};

src/consensus/params.h

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ enum DeploymentPos : uint16_t {
3333
DEPLOYMENT_TESTDUMMY,
3434
DEPLOYMENT_CHECKTEMPLATEVERIFY, // Deployment of CTV (BIP 119)
3535
DEPLOYMENT_ANYPREVOUT,
36+
DEPLOYMENT_64BYTETX,
3637
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
3738
MAX_VERSION_BITS_DEPLOYMENTS
3839
};

src/deploymentinfo.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
2121
/*.name =*/ "anyprevout",
2222
/*.gbt_force =*/ true,
2323
},
24+
{
25+
/*.name =*/ "64bytetx",
26+
/*.gbt_force =*/ true,
27+
},
2428
};
2529

2630
std::string DeploymentName(Consensus::BuriedDeployment dep)

src/rpc/blockchain.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager&
13221322
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT);
13231323
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY);
13241324
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_ANYPREVOUT);
1325+
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_64BYTETX);
13251326
return softforks;
13261327
}
13271328
} // anon namespace

src/validation.cpp

+35-1
Original file line numberDiff line numberDiff line change
@@ -1924,6 +1924,7 @@ unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Chainstat
19241924
return flags;
19251925
}
19261926

1927+
static bool ContextualBlockPreCheck(const CBlock& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev);
19271928

19281929
static int64_t nTimeCheck = 0;
19291930
static int64_t nTimeForks = 0;
@@ -1948,6 +1949,10 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
19481949

19491950
int64_t nTimeStart = GetTimeMicros();
19501951

1952+
if (!ContextualBlockPreCheck(block, state, m_chainman, pindex->pprev)) {
1953+
return error("%s: Consensus::ContextualBlockPreCheck: %s", __func__, state.ToString());
1954+
}
1955+
19511956
// Check it again in case a previous version let a bad block in
19521957
// NOTE: We don't currently (re-)invoke ContextualCheckBlock() or
19531958
// ContextualCheckBlockHeader() here. This means that if we add a new
@@ -3428,6 +3433,28 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
34283433
return true;
34293434
}
34303435

3436+
/**
3437+
* We want to enforce certain rules (specifically the 64-byte transaction check)
3438+
* before we call CheckBlock to check the merkle root. This allows us to enforce
3439+
* malleability checks which may interact with other CheckBlock checks.
3440+
* This is currently called both in AcceptBlock prior to writing the block to
3441+
* disk and in ConnectBlock.
3442+
* Note that as this is called before merkle-tree checks so must never return a
3443+
* non-malleable error condition.
3444+
*/
3445+
static bool ContextualBlockPreCheck(const CBlock& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev)
3446+
{
3447+
if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_64BYTETX)) {
3448+
for (const auto& tx : block.vtx) {
3449+
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) == 64) {
3450+
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "64-byte-transaction", strprintf("size of tx %s without witness is 64 bytes", tx->GetHash().ToString()));
3451+
}
3452+
}
3453+
}
3454+
3455+
return true;
3456+
}
3457+
34313458
/** NOTE: This function is not currently invoked by ConnectBlock(), so we
34323459
* should consider upgrade issues if we change which consensus rules are
34333460
* enforced in this function (eg by adding a new consensus rule). See comment
@@ -3714,7 +3741,8 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
37143741
if (pindex->nChainWork < nMinimumChainWork) return true;
37153742
}
37163743

3717-
if (!CheckBlock(block, state, m_params.GetConsensus()) ||
3744+
if (!ContextualBlockPreCheck(block, state, m_chainman, pindex->pprev) ||
3745+
!CheckBlock(block, state, m_params.GetConsensus()) ||
37183746
!ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) {
37193747
if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
37203748
pindex->nStatus |= BLOCK_FAILED_VALID;
@@ -3822,6 +3850,8 @@ bool TestBlockValidity(BlockValidationState& state,
38223850
// NOTE: CheckBlockHeader is called by CheckBlock
38233851
if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, pindexPrev, adjusted_time_callback()))
38243852
return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, state.ToString());
3853+
if (!ContextualBlockPreCheck(block, state, chainstate.m_chainman, pindexPrev))
3854+
return error("%s: Consensus::ContextualBlockPreCheck: %s", __func__, state.ToString());
38253855
if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot))
38263856
return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString());
38273857
if (!ContextualCheckBlock(block, state, chainstate.m_chainman, pindexPrev))
@@ -3940,6 +3970,10 @@ bool CVerifyDB::VerifyDB(
39403970
return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
39413971
}
39423972
// check level 1: verify block validity
3973+
if (nCheckLevel >= 1 && !ContextualBlockPreCheck(block, state, chainstate.m_chainman, pindex->pprev)) {
3974+
return error("%s: *** found bad block at %d due to soft-fork, hash=%s (%s)\n", __func__,
3975+
pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
3976+
}
39433977
if (nCheckLevel >= 1 && !CheckBlock(block, state, consensus_params)) {
39443978
return error("%s: *** found bad block at %d, hash=%s (%s)\n", __func__,
39453979
pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());

test/functional/data/invalid_txs.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ class BadTxTemplate:
7676
# the mempool (i.e. does it violate policy but not consensus)?
7777
valid_in_block = False
7878

79+
# Do we need a signature for this tx
80+
wants_signature = True
81+
7982
def __init__(self, *, spend_tx=None, spend_block=None):
8083
self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx
8184
self.spend_avail = sum(o.nValue for o in self.spend_tx.vout)
@@ -101,6 +104,7 @@ def get_tx(self):
101104
class InputMissing(BadTxTemplate):
102105
reject_reason = "bad-txns-vin-empty"
103106
expect_disconnect = True
107+
wants_signature = False
104108

105109
# We use a blank transaction here to make sure
106110
# it is interpreted as a non-witness transaction.
@@ -118,7 +122,9 @@ def get_tx(self):
118122
class SizeExactly64(BadTxTemplate):
119123
reject_reason = "tx-size-small"
120124
expect_disconnect = False
121-
valid_in_block = True
125+
valid_in_block = False
126+
wants_signature = False
127+
block_reject_reason = "64-byte-transaction"
122128

123129
def get_tx(self):
124130
tx = CTransaction()
@@ -133,6 +139,7 @@ class SizeSub64(BadTxTemplate):
133139
reject_reason = "tx-size-small"
134140
expect_disconnect = False
135141
valid_in_block = True
142+
wants_signature = False
136143

137144
def get_tx(self):
138145
tx = CTransaction()

test/functional/feature_block.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def run_test(self):
163163
blockname = f"for_invalid.{TxTemplate.__name__}"
164164
badblock = self.next_block(blockname)
165165
badtx = template.get_tx()
166-
if TxTemplate != invalid_txs.InputMissing:
166+
if template.wants_signature:
167167
self.sign_tx(badtx, attempt_spend_tx)
168168
badtx.rehash()
169169
badblock = self.update_block(blockname, [badtx])

test/functional/rpc_blockchain.py

+15
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,21 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash):
240240
'active': True,
241241
'height': 0,
242242
},
243+
'64bytetx': {
244+
'type': 'heretical',
245+
'heretical': {
246+
'bip': 0,
247+
'bip_version': 1,
248+
'start_time': -1,
249+
'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value
250+
'period': 144,
251+
'status': 'active',
252+
'status_next': 'active',
253+
'since': 0,
254+
},
255+
'active': True,
256+
'height': 0,
257+
},
243258
}
244259
})
245260

0 commit comments

Comments
 (0)