From fe04a2045c3080797c6f93bf7cd2e12192b8f68f Mon Sep 17 00:00:00 2001 From: Bitflash Date: Sun, 2 Aug 2026 11:04:00 -0300 Subject: Add consensus block size limit (#111, from #110) CheckBlock() validated block size against MAX_SIZE, the serializer's generic 32 MB guard rather than a block rule; MAX_BLOCK_SIZE did not exist in this tree at all, because Bitcoin's 1 MB cap arrived in 0.3.x, after the 0.1.0 this fork came from. A miner could produce a 32 MB block and every node would download, verify and store it. Original work by mtg0001 in #110, rebased over #109 with the self-test conflict resolved, plus a comment explaining why the inherited count-against-bytes test stays. Measured before choosing the number: 5709 blocks read from blk0001.dat give a largest block of 34,290 bytes and a median of 216, so 1 MB leaves 29x of headroom and rejects nothing that exists. A node built from this branch then synced genesis to 5706 on the live network with zero size-limit rejections and zero CheckBlock errors -- the same test that caught BIP30 rejecting a real block at height 859. --- src/Makefile | 1 + src/main.cpp | 2 +- src/main.h | 21 ++++++++++++++++++ src/makefile.mingw | 1 + src/selftest.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 85 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/Makefile b/src/Makefile index 88dc08f..82e7d06 100644 --- a/src/Makefile +++ b/src/Makefile @@ -140,3 +140,4 @@ tests: test_bip32 bitflash-node ./bitflash-node -selftest=wallet-keypool ./bitflash-node -selftest=wallet-hd ./bitflash-node -selftest=net-message + ./bitflash-node -selftest=consensus-limits diff --git a/src/main.cpp b/src/main.cpp index 9bb3c24..1b09a1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1719,7 +1719,7 @@ bool CBlock::CheckBlock() const // that can be verified before saving an orphan block. // Size limits - if (vtx.empty() || vtx.size() > MAX_SIZE || ::GetSerializeSize(*this, SER_DISK) > MAX_SIZE) + if (!CheckSizeLimits()) return error("CheckBlock() : size limits failed"); // Check timestamp diff --git a/src/main.h b/src/main.h index 1b0be59..768f1c7 100644 --- a/src/main.h +++ b/src/main.h @@ -29,6 +29,10 @@ static const unsigned int MAX_ORPHAN_BLOCKS = 200; // so a block within the size limit could still hold millions of them and take // the network minutes of CPU to reject. Same value Bitcoin settled on. static const unsigned int MAX_BLOCK_SIGOPS = 20000; +// Consensus block-size ceiling. MAX_SIZE is the serializer's generic safety +// limit (32 MB), not a block rule. Bitcoin 0.1.0 predated the later 1 MB block +// cap, so this tree inherited no real block-size consensus limit. +static const unsigned int MAX_BLOCK_SIZE = 1000000; static const int64 COIN = 100000000; // Total Bitflash emission: identical to Bitcoin (21 million). // MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in @@ -1053,6 +1057,23 @@ public: return RandomXPoWHash((const void*)BEGIN(nVersion), END(nNonce) - BEGIN(nVersion)); } + bool CheckSizeLimits() const + { + // The middle test compares a transaction *count* against a *byte* + // limit. It is inherited from 0.1.0, where the same line compared + // vtx.size() against MAX_SIZE, and it is inert: the smallest possible + // transaction serializes to more than sixty bytes, so a block holding + // over a million of them cannot also weigh under a megabyte. + // + // Kept rather than removed. This is consensus code, and a rewrite that + // reads better is still a rewrite that can change what the network + // accepts. Written down so the next reader does not have to derive it, + // or "fix" it into something that does. + return (!vtx.empty() && + vtx.size() <= MAX_BLOCK_SIZE && + ::GetSerializeSize(*this, SER_DISK) <= MAX_BLOCK_SIZE); + } + uint256 BuildMerkleTree() const { diff --git a/src/makefile.mingw b/src/makefile.mingw index 5025acb..9bbfef1 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -112,3 +112,4 @@ tests: test_bip32.exe bitflash.exe ./bitflash.exe -selftest=wallet-keypool ./bitflash.exe -selftest=wallet-hd ./bitflash.exe -selftest=net-message + ./bitflash.exe -selftest=consensus-limits diff --git a/src/selftest.cpp b/src/selftest.cpp index c04adf4..67f96f8 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp @@ -531,6 +531,64 @@ static int RunNetMessageSelfTest() return nFail == 0 ? 0 : 1; } +static CBlock MakeSizedConsensusBlock(size_t nScriptBytes) +{ + CTransaction tx; + tx.vin.push_back(CTxIn(COutPoint(), CScript() << 1 << 1)); + tx.vout.push_back(CTxOut(0, CScript())); + tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nScriptBytes, 0); + + CBlock block; + block.vtx.push_back(tx); + block.hashMerkleRoot = block.BuildMerkleTree(); + block.nVersion = 1; + block.nTime = GetAdjustedTime(); + block.nBits = bnProofOfWorkLimit.GetCompact(); + block.nNonce = 0; + return block; +} + +static int RunConsensusLimitsSelfTest() +{ + fflush(stdout); + printf("consensus-limits self-test\n"); + + int nFail = 0; + try + { + nFail += Check(MAX_BLOCK_SIZE == 1000000, + "the consensus block-size cap is 1 MB") ? 0 : 1; + nFail += Check(MAX_BLOCK_SIZE < MAX_SIZE, + "the block cap is tighter than the serializer cap") ? 0 : 1; + + CBlock small = MakeSizedConsensusBlock(100); + nFail += Check(small.CheckSizeLimits(), + "a small block is within the consensus size limit") ? 0 : 1; + + CBlock oversized = MakeSizedConsensusBlock(MAX_BLOCK_SIZE); + unsigned int nSerialized = ::GetSerializeSize(oversized, SER_DISK); + nFail += Check(nSerialized > MAX_BLOCK_SIZE && nSerialized <= MAX_SIZE, + "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1; + nFail += Check(!oversized.CheckSizeLimits(), + "a block above 1 MB is outside the consensus size limit") ? 0 : 1; + } + catch (const std::exception& e) + { + printf(" FAIL exception: %s\n", e.what()); + nFail++; + } + catch (...) + { + printf(" FAIL unknown exception\n"); + nFail++; + } + + printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", + nFail, nFail == 1 ? "" : "s"); + fflush(stdout); + return nFail == 0 ? 0 : 1; +} + int RunSelfTest(const std::string& name) { AttachTerminal(); @@ -541,8 +599,10 @@ int RunSelfTest(const std::string& name) return RunWalletHDSelfTest(); if (name == "net-message") return RunNetMessageSelfTest(); + if (name == "consensus-limits") + return RunConsensusLimitsSelfTest(); printf("Unknown self-test '%s'\n", name.c_str()); - printf("Known self-tests: wallet-keypool, wallet-hd, net-message\n"); + printf("Known self-tests: wallet-keypool, wallet-hd, net-message, consensus-limits\n"); return 1; } -- cgit v1.2.3