Skip to content

Commit e72e9fb

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 df0f8a6 commit e72e9fb

File tree

8 files changed

+67
-3
lines changed

8 files changed

+67
-3
lines changed

src/consensus/params.h

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

src/deploymentinfo.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
2323
/*.name =*/ "anyprevout",
2424
/*.gbt_force =*/ true,
2525
},
26+
{
27+
/*.name =*/ "64bytetx",
28+
/*.gbt_force =*/ true,
29+
},
2630
};
2731

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

src/kernel/chainparams.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ class CRegTestParams : public CChainParams
461461
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = SetupDeployment{.start = 0, .timeout = Consensus::HereticalDeployment::NO_TIMEOUT, .activate = 0x30000000, .abandon = 0x50000000};
462462
consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .always = true};
463463
consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.activate = 0x60007600, .abandon = 0x40007600, .always = true};
464+
consensus.vDeployments[Consensus::DEPLOYMENT_64BYTETX] = SetupDeployment{.activate = 0x60000001, .abandon = 0x40000001, .always = true}; // needs BIN assigned
464465

465466
consensus.nMinimumChainWork = uint256{};
466467
consensus.defaultAssumeValid = uint256{};

src/rpc/blockchain.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager&
13391339
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT);
13401340
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY);
13411341
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_ANYPREVOUT);
1342+
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_64BYTETX);
13421343
return softforks;
13431344
}
13441345
} // anon namespace

src/validation.cpp

+36-1
Original file line numberDiff line numberDiff line change
@@ -2020,6 +2020,7 @@ unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Chainstat
20202020
return flags;
20212021
}
20222022

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

20242025
static SteadyClock::duration time_check{};
20252026
static SteadyClock::duration time_forks{};
@@ -2046,6 +2047,10 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
20462047
const auto time_start{SteadyClock::now()};
20472048
const CChainParams& params{m_chainman.GetParams()};
20482049

2050+
if (!ContextualBlockPreCheck(block, state, m_chainman, pindex->pprev)) {
2051+
return error("%s: Consensus::ContextualBlockPreCheck: %s", __func__, state.ToString());
2052+
}
2053+
20492054
// Check it again in case a previous version let a bad block in
20502055
// NOTE: We don't currently (re-)invoke ContextualCheckBlock() or
20512056
// ContextualCheckBlockHeader() here. This means that if we add a new
@@ -3605,6 +3610,28 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
36053610
return true;
36063611
}
36073612

3613+
/**
3614+
* We want to enforce certain rules (specifically the 64-byte transaction check)
3615+
* before we call CheckBlock to check the merkle root. This allows us to enforce
3616+
* malleability checks which may interact with other CheckBlock checks.
3617+
* This is currently called both in AcceptBlock prior to writing the block to
3618+
* disk and in ConnectBlock.
3619+
* Note that as this is called before merkle-tree checks so must never return a
3620+
* non-malleable error condition.
3621+
*/
3622+
static bool ContextualBlockPreCheck(const CBlock& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev)
3623+
{
3624+
if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_64BYTETX)) {
3625+
for (const auto& tx : block.vtx) {
3626+
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) == 64) {
3627+
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "64-byte-transaction", strprintf("size of tx %s without witness is 64 bytes", tx->GetHash().ToString()));
3628+
}
3629+
}
3630+
}
3631+
3632+
return true;
3633+
}
3634+
36083635
/** NOTE: This function is not currently invoked by ConnectBlock(), so we
36093636
* should consider upgrade issues if we change which consensus rules are
36103637
* enforced in this function (eg by adding a new consensus rule). See comment
@@ -3910,7 +3937,8 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
39103937

39113938
const CChainParams& params{m_chainman.GetParams()};
39123939

3913-
if (!CheckBlock(block, state, params.GetConsensus()) ||
3940+
if (!ContextualBlockPreCheck(block, state, m_chainman, pindex->pprev) ||
3941+
!CheckBlock(block, state, params.GetConsensus()) ||
39143942
!ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) {
39153943
if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
39163944
pindex->nStatus |= BLOCK_FAILED_VALID;
@@ -4018,6 +4046,8 @@ bool TestBlockValidity(BlockValidationState& state,
40184046
// NOTE: CheckBlockHeader is called by CheckBlock
40194047
if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, pindexPrev, adjusted_time_callback()))
40204048
return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, state.ToString());
4049+
if (!ContextualBlockPreCheck(block, state, chainstate.m_chainman, pindexPrev))
4050+
return error("%s: Consensus::ContextualBlockPreCheck: %s", __func__, state.ToString());
40214051
if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot))
40224052
return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString());
40234053
if (!ContextualCheckBlock(block, state, chainstate.m_chainman, pindexPrev))
@@ -4140,6 +4170,11 @@ VerifyDBResult CVerifyDB::VerifyDB(
41404170
return VerifyDBResult::CORRUPTED_BLOCK_DB;
41414171
}
41424172
// check level 1: verify block validity
4173+
if (nCheckLevel >= 1 && !ContextualBlockPreCheck(block, state, chainstate.m_chainman, pindex->pprev)) {
4174+
LogPrintf("Verification error: found bad block at %d due to soft-fork, hash=%s (%s)\n",
4175+
pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
4176+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
4177+
}
41434178
if (nCheckLevel >= 1 && !CheckBlock(block, state, consensus_params)) {
41444179
LogPrintf("Verification error: found bad block at %d, hash=%s (%s)\n",
41454180
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
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
@@ -250,6 +250,21 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash):
250250
'active': True,
251251
'height': 0,
252252
},
253+
'64bytetx': {
254+
'type': 'heretical',
255+
'heretical': {
256+
'bip': 0,
257+
'bip_version': 1,
258+
'start_time': -1,
259+
'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value
260+
'period': 144,
261+
'status': 'active',
262+
'status_next': 'active',
263+
'since': 0,
264+
},
265+
'active': True,
266+
'height': 0,
267+
},
253268
}
254269
})
255270

0 commit comments

Comments
 (0)