Skip to content

Commit 6deeaf0

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 <git@bluematt.me>
1 parent 56610cb commit 6deeaf0

File tree

8 files changed

+70
-3
lines changed

8 files changed

+70
-3
lines changed

src/consensus/params.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ enum DeploymentPos : uint16_t {
3535
DEPLOYMENT_CHECKTEMPLATEVERIFY, // Deployment of CTV (BIP 119)
3636
DEPLOYMENT_ANYPREVOUT,
3737
DEPLOYMENT_OP_CAT,
38+
DEPLOYMENT_64BYTETX,
3839
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
3940
MAX_VERSION_BITS_DEPLOYMENTS
4041
};

src/deploymentinfo.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
2727
/*.name =*/ "opcat",
2828
/*.gbt_force =*/ true,
2929
},
30+
{
31+
/*.name =*/ "64bytetx",
32+
/*.gbt_force =*/ true,
33+
},
3034
};
3135

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

src/kernel/chainparams.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ class CRegTestParams : public CChainParams
584584
consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .always = true};
585585
consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.activate = 0x60007600, .abandon = 0x40007600, .always = true};
586586
consensus.vDeployments[Consensus::DEPLOYMENT_OP_CAT] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .always = true};
587+
consensus.vDeployments[Consensus::DEPLOYMENT_64BYTETX] = SetupDeployment{.activate = 0x60000001, .abandon = 0x40000001, .always = true}; // needs BIN assigned
588+
587589
consensus.nMinimumChainWork = uint256{};
588590
consensus.defaultAssumeValid = uint256{};
589591

src/rpc/blockchain.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager&
13961396
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY);
13971397
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_ANYPREVOUT);
13981398
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_OP_CAT);
1399+
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_64BYTETX);
13991400
return softforks;
14001401
}
14011402
} // anon namespace

src/validation.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,7 @@ unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Chainstat
24012401
return flags;
24022402
}
24032403

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

24052406
/** Apply the effects of this block (with given index) on the UTXO set represented by coins.
24062407
* Validity checks that depend on the UTXO set are also done; ConnectBlock()
@@ -2418,6 +2419,11 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
24182419
const auto time_start{SteadyClock::now()};
24192420
const CChainParams& params{m_chainman.GetParams()};
24202421

2422+
if (!ContextualBlockPreCheck(block, state, m_chainman, pindex->pprev)) {
2423+
LogError("%s: Consensus::ContextualBlockPreCheck: %s", __func__, state.ToString());
2424+
return false;
2425+
}
2426+
24212427
// Check it again in case a previous version let a bad block in
24222428
// NOTE: We don't currently (re-)invoke ContextualCheckBlock() or
24232429
// ContextualCheckBlockHeader() here. This means that if we add a new
@@ -4196,6 +4202,28 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
41964202
return true;
41974203
}
41984204

4205+
/**
4206+
* We want to enforce certain rules (specifically the 64-byte transaction check)
4207+
* before we call CheckBlock to check the merkle root. This allows us to enforce
4208+
* malleability checks which may interact with other CheckBlock checks.
4209+
* This is currently called both in AcceptBlock prior to writing the block to
4210+
* disk and in ConnectBlock.
4211+
* Note that as this is called before merkle-tree checks so must never return a
4212+
* non-malleable error condition.
4213+
*/
4214+
static bool ContextualBlockPreCheck(const CBlock& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev)
4215+
{
4216+
if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_64BYTETX)) {
4217+
for (const auto& tx : block.vtx) {
4218+
if (::GetSerializeSize(TX_NO_WITNESS(tx)) == 64) {
4219+
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "64-byte-transaction", strprintf("size of tx %s without witness is 64 bytes", tx->GetHash().ToString()));
4220+
}
4221+
}
4222+
}
4223+
4224+
return true;
4225+
}
4226+
41994227
/** NOTE: This function is not currently invoked by ConnectBlock(), so we
42004228
* should consider upgrade issues if we change which consensus rules are
42014229
* enforced in this function (eg by adding a new consensus rule). See comment
@@ -4478,7 +4506,8 @@ bool ChainstateManager::AcceptBlock(const std::shared_ptr<const CBlock>& pblock,
44784506

44794507
const CChainParams& params{GetParams()};
44804508

4481-
if (!CheckBlock(block, state, params.GetConsensus()) ||
4509+
if (!ContextualBlockPreCheck(block, state, *this, pindex->pprev) ||
4510+
!CheckBlock(block, state, params.GetConsensus()) ||
44824511
!ContextualCheckBlock(block, state, *this, pindex->pprev)) {
44834512
if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
44844513
pindex->nStatus |= BLOCK_FAILED_VALID;
@@ -4613,6 +4642,10 @@ bool TestBlockValidity(BlockValidationState& state,
46134642
LogError("%s: Consensus::ContextualCheckBlockHeader: %s\n", __func__, state.ToString());
46144643
return false;
46154644
}
4645+
if (!ContextualBlockPreCheck(block, state, chainstate.m_chainman, pindexPrev)) {
4646+
LogError("%s: Consensus::ContextualBlockPreCheck: %s", __func__, state.ToString());
4647+
return false;
4648+
}
46164649
if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) {
46174650
LogError("%s: Consensus::CheckBlock: %s\n", __func__, state.ToString());
46184651
return false;
@@ -4733,6 +4766,11 @@ VerifyDBResult CVerifyDB::VerifyDB(
47334766
return VerifyDBResult::CORRUPTED_BLOCK_DB;
47344767
}
47354768
// check level 1: verify block validity
4769+
if (nCheckLevel >= 1 && !ContextualBlockPreCheck(block, state, chainstate.m_chainman, pindex->pprev)) {
4770+
LogPrintf("Verification error: found bad block at %d due to soft-fork, hash=%s (%s)\n",
4771+
pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
4772+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
4773+
}
47364774
if (nCheckLevel >= 1 && !CheckBlock(block, state, consensus_params)) {
47374775
LogPrintf("Verification error: found bad block at %d, hash=%s (%s)\n",
47384776
pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());

test/functional/data/invalid_txs.py

Lines changed: 8 additions & 1 deletion
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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def run_test(self):
159159
blockname = f"for_invalid.{TxTemplate.__name__}"
160160
self.next_block(blockname)
161161
badtx = template.get_tx()
162-
if TxTemplate != invalid_txs.InputMissing:
162+
if template.wants_signature:
163163
self.sign_tx(badtx, attempt_spend_tx)
164164
badtx.rehash()
165165
badblock = self.update_block(blockname, [badtx])

test/functional/rpc_blockchain.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,20 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash):
261261
'height': 0,
262262
'active': True,
263263
},
264+
'64bytetx': {
265+
'type': 'heretical',
266+
'heretical': {
267+
'binana-id': 'BIN-2016-0000-001',
268+
'start_time': -1,
269+
'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value
270+
'period': 144,
271+
'status': 'active',
272+
'status_next': 'active',
273+
'since': 0,
274+
},
275+
'active': True,
276+
'height': 0,
277+
},
264278
}
265279
})
266280

0 commit comments

Comments
 (0)