diff options
| author | Bitflash <btf01@proton.me> | 2026-08-02 11:04:00 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-02 11:04:00 -0300 |
| commit | fe04a2045c3080797c6f93bf7cd2e12192b8f68f (patch) | |
| tree | 4f38cde1114c45bc334d096e90fef94780a66733 | |
| parent | 89f6d045c8e3dacbfbb369a3ac89c0c33de22b4c (diff) | |
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.
| -rw-r--r-- | src/Makefile | 1 | ||||
| -rw-r--r-- | src/main.cpp | 2 | ||||
| -rw-r--r-- | src/main.h | 21 | ||||
| -rw-r--r-- | src/makefile.mingw | 1 | ||||
| -rw-r--r-- | src/selftest.cpp | 62 |
5 files changed, 85 insertions, 2 deletions
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 | |||
| 140 | ./bitflash-node -selftest=wallet-keypool | 140 | ./bitflash-node -selftest=wallet-keypool |
| 141 | ./bitflash-node -selftest=wallet-hd | 141 | ./bitflash-node -selftest=wallet-hd |
| 142 | ./bitflash-node -selftest=net-message | 142 | ./bitflash-node -selftest=net-message |
| 143 | ./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 | |||
| 1719 | // that can be verified before saving an orphan block. | 1719 | // that can be verified before saving an orphan block. |
| 1720 | 1720 | ||
| 1721 | // Size limits | 1721 | // Size limits |
| 1722 | if (vtx.empty() || vtx.size() > MAX_SIZE || ::GetSerializeSize(*this, SER_DISK) > MAX_SIZE) | 1722 | if (!CheckSizeLimits()) |
| 1723 | return error("CheckBlock() : size limits failed"); | 1723 | return error("CheckBlock() : size limits failed"); |
| 1724 | 1724 | ||
| 1725 | // Check timestamp | 1725 | // Check timestamp |
| @@ -29,6 +29,10 @@ static const unsigned int MAX_ORPHAN_BLOCKS = 200; | |||
| 29 | // so a block within the size limit could still hold millions of them and take | 29 | // so a block within the size limit could still hold millions of them and take |
| 30 | // the network minutes of CPU to reject. Same value Bitcoin settled on. | 30 | // the network minutes of CPU to reject. Same value Bitcoin settled on. |
| 31 | static const unsigned int MAX_BLOCK_SIGOPS = 20000; | 31 | static const unsigned int MAX_BLOCK_SIGOPS = 20000; |
| 32 | // Consensus block-size ceiling. MAX_SIZE is the serializer's generic safety | ||
| 33 | // limit (32 MB), not a block rule. Bitcoin 0.1.0 predated the later 1 MB block | ||
| 34 | // cap, so this tree inherited no real block-size consensus limit. | ||
| 35 | static const unsigned int MAX_BLOCK_SIZE = 1000000; | ||
| 32 | static const int64 COIN = 100000000; | 36 | static const int64 COIN = 100000000; |
| 33 | // Total Bitflash emission: identical to Bitcoin (21 million). | 37 | // Total Bitflash emission: identical to Bitcoin (21 million). |
| 34 | // MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in | 38 | // MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in |
| @@ -1053,6 +1057,23 @@ public: | |||
| 1053 | return RandomXPoWHash((const void*)BEGIN(nVersion), END(nNonce) - BEGIN(nVersion)); | 1057 | return RandomXPoWHash((const void*)BEGIN(nVersion), END(nNonce) - BEGIN(nVersion)); |
| 1054 | } | 1058 | } |
| 1055 | 1059 | ||
| 1060 | bool CheckSizeLimits() const | ||
| 1061 | { | ||
| 1062 | // The middle test compares a transaction *count* against a *byte* | ||
| 1063 | // limit. It is inherited from 0.1.0, where the same line compared | ||
| 1064 | // vtx.size() against MAX_SIZE, and it is inert: the smallest possible | ||
| 1065 | // transaction serializes to more than sixty bytes, so a block holding | ||
| 1066 | // over a million of them cannot also weigh under a megabyte. | ||
| 1067 | // | ||
| 1068 | // Kept rather than removed. This is consensus code, and a rewrite that | ||
| 1069 | // reads better is still a rewrite that can change what the network | ||
| 1070 | // accepts. Written down so the next reader does not have to derive it, | ||
| 1071 | // or "fix" it into something that does. | ||
| 1072 | return (!vtx.empty() && | ||
| 1073 | vtx.size() <= MAX_BLOCK_SIZE && | ||
| 1074 | ::GetSerializeSize(*this, SER_DISK) <= MAX_BLOCK_SIZE); | ||
| 1075 | } | ||
| 1076 | |||
| 1056 | 1077 | ||
| 1057 | uint256 BuildMerkleTree() const | 1078 | uint256 BuildMerkleTree() const |
| 1058 | { | 1079 | { |
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 | |||
| 112 | ./bitflash.exe -selftest=wallet-keypool | 112 | ./bitflash.exe -selftest=wallet-keypool |
| 113 | ./bitflash.exe -selftest=wallet-hd | 113 | ./bitflash.exe -selftest=wallet-hd |
| 114 | ./bitflash.exe -selftest=net-message | 114 | ./bitflash.exe -selftest=net-message |
| 115 | ./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() | |||
| 531 | return nFail == 0 ? 0 : 1; | 531 | return nFail == 0 ? 0 : 1; |
| 532 | } | 532 | } |
| 533 | 533 | ||
| 534 | static CBlock MakeSizedConsensusBlock(size_t nScriptBytes) | ||
| 535 | { | ||
| 536 | CTransaction tx; | ||
| 537 | tx.vin.push_back(CTxIn(COutPoint(), CScript() << 1 << 1)); | ||
| 538 | tx.vout.push_back(CTxOut(0, CScript())); | ||
| 539 | tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nScriptBytes, 0); | ||
| 540 | |||
| 541 | CBlock block; | ||
| 542 | block.vtx.push_back(tx); | ||
| 543 | block.hashMerkleRoot = block.BuildMerkleTree(); | ||
| 544 | block.nVersion = 1; | ||
| 545 | block.nTime = GetAdjustedTime(); | ||
| 546 | block.nBits = bnProofOfWorkLimit.GetCompact(); | ||
| 547 | block.nNonce = 0; | ||
| 548 | return block; | ||
| 549 | } | ||
| 550 | |||
| 551 | static int RunConsensusLimitsSelfTest() | ||
| 552 | { | ||
| 553 | fflush(stdout); | ||
| 554 | printf("consensus-limits self-test\n"); | ||
| 555 | |||
| 556 | int nFail = 0; | ||
| 557 | try | ||
| 558 | { | ||
| 559 | nFail += Check(MAX_BLOCK_SIZE == 1000000, | ||
| 560 | "the consensus block-size cap is 1 MB") ? 0 : 1; | ||
| 561 | nFail += Check(MAX_BLOCK_SIZE < MAX_SIZE, | ||
| 562 | "the block cap is tighter than the serializer cap") ? 0 : 1; | ||
| 563 | |||
| 564 | CBlock small = MakeSizedConsensusBlock(100); | ||
| 565 | nFail += Check(small.CheckSizeLimits(), | ||
| 566 | "a small block is within the consensus size limit") ? 0 : 1; | ||
| 567 | |||
| 568 | CBlock oversized = MakeSizedConsensusBlock(MAX_BLOCK_SIZE); | ||
| 569 | unsigned int nSerialized = ::GetSerializeSize(oversized, SER_DISK); | ||
| 570 | nFail += Check(nSerialized > MAX_BLOCK_SIZE && nSerialized <= MAX_SIZE, | ||
| 571 | "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1; | ||
| 572 | nFail += Check(!oversized.CheckSizeLimits(), | ||
| 573 | "a block above 1 MB is outside the consensus size limit") ? 0 : 1; | ||
| 574 | } | ||
| 575 | catch (const std::exception& e) | ||
| 576 | { | ||
| 577 | printf(" FAIL exception: %s\n", e.what()); | ||
| 578 | nFail++; | ||
| 579 | } | ||
| 580 | catch (...) | ||
| 581 | { | ||
| 582 | printf(" FAIL unknown exception\n"); | ||
| 583 | nFail++; | ||
| 584 | } | ||
| 585 | |||
| 586 | printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", | ||
| 587 | nFail, nFail == 1 ? "" : "s"); | ||
| 588 | fflush(stdout); | ||
| 589 | return nFail == 0 ? 0 : 1; | ||
| 590 | } | ||
| 591 | |||
| 534 | int RunSelfTest(const std::string& name) | 592 | int RunSelfTest(const std::string& name) |
| 535 | { | 593 | { |
| 536 | AttachTerminal(); | 594 | AttachTerminal(); |
| @@ -541,8 +599,10 @@ int RunSelfTest(const std::string& name) | |||
| 541 | return RunWalletHDSelfTest(); | 599 | return RunWalletHDSelfTest(); |
| 542 | if (name == "net-message") | 600 | if (name == "net-message") |
| 543 | return RunNetMessageSelfTest(); | 601 | return RunNetMessageSelfTest(); |
| 602 | if (name == "consensus-limits") | ||
| 603 | return RunConsensusLimitsSelfTest(); | ||
| 544 | 604 | ||
| 545 | printf("Unknown self-test '%s'\n", name.c_str()); | 605 | printf("Unknown self-test '%s'\n", name.c_str()); |
| 546 | printf("Known self-tests: wallet-keypool, wallet-hd, net-message\n"); | 606 | printf("Known self-tests: wallet-keypool, wallet-hd, net-message, consensus-limits\n"); |
| 547 | return 1; | 607 | return 1; |
| 548 | } | 608 | } |