What changed, and why it matters
This commit narrows a long-standing special-case workaround for Monero block 202612. Previously, any new blockchain fork would have inherited a hard-coded proof-of-work hash at that exact height, which could stop nodes from syncing or miners from producing valid blocks if the network's difficulty was higher than the fixed value. The change now applies the workaround only to the known historical mainnet, testnet, and stagenet blocks, so new or private chains compute proof of work normally.
Review whether any other network-specific hard-coded workarounds in the consensus path are similarly over-broad. Ensure downstream forks and private testnets are aware that block 202612 now requires the exact historical hashing blob to receive the legacy PoW hash. Run the new unit tests on all supported build configurations.
Security signals we found
Hard-coded consensus bypass scoped to explicit historical block hashes
Potential chain-split/sync failure for new networks or private testnets at height 202612
Workaround originally intended for a historical block-202612 bug now restricted
Added unit tests assert both preservation of legacy hash and rejection of arbitrary blobs
Evidence from the diff
The patch replaces the unconditional if (height == 202612) hard-coded PoW hash in get_block_longhash() with a helper that first hashes the supplied block_hashing_blob and checks it against three known object hashes for mainnet, testnet, and stagenet. Only if the blob matches one of those historical blobs is the fixed PoW hash returned; otherwise normal RandomX/CryptoNight hashing proceeds. Unit tests and binary block fixtures for all three networks are added to verify both that the historical blocks still produce the legacy hash and that arbitrary blobs at height 202612 do not.
Changed components
src/cryptonote_basic/cryptonote_format_utils.cpptests/unit_tests/cryptonote_format_utils.cpptests/data/blocks/block_202612_mainnet.bintests/data/blocks/block_202612_testnet.bintests/data/blocks/block_202612_stagenet.binInspect captured patch +141 / −6
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index 5447584..ce0982e 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -1598,6 +1598,32 @@ namespace cryptonote
return get_tx_tree_hash(txs_ids);
}
//---------------------------------------------------------------
+ static bool get_block_longhash_202612(const blobdata_ref block_hashing_blob, crypto::hash &res)
+ {
+ static constexpr const char longhash_202612[] = "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000";
+ // Object hashes of get_block_hashing_blob(block). The mainnet value differs
+ // from the public block id because calculate_block_hash() replaces that id.
+ static constexpr const char * const block_hashing_blob_ids_202612[] =
+ {
+ "426d16cff04c71f8b16340b722dc4010a2dd3831c22041431f772547ba6e331a", // mainnet
+ "248fde4b96b829c4ddbd00e3f76d35b03d01257898bc1b5578bc9e04b379a676", // testnet
+ "f3449e658b5f880c4b0e69007ed5d092c9c883ac3a518166fa652d5cc505e7b1", // stagenet
+ };
+
+ crypto::hash block_id;
+ const blobdata block_hashing_blob_copy(block_hashing_blob.data(), block_hashing_blob.size());
+ get_object_hash(block_hashing_blob_copy, block_id);
+ const std::string block_id_hex = string_tools::pod_to_hex(block_id);
+ for (const char * const expected_block_id: block_hashing_blob_ids_202612)
+ {
+ if (block_id_hex == expected_block_id)
+ {
+ return epee::string_tools::hex_to_pod(longhash_202612, res);
+ }
+ }
+ return false;
+ }
+ //---------------------------------------------------------------
crypto::hash get_block_longhash(const blobdata_ref block_hashing_blob,
const uint64_t height,
const uint8_t major_version,
@@ -1605,12 +1631,10 @@ namespace cryptonote
{
crypto::hash res;
- if (height == 202612) // block 202612 bug workaround
- {
- static const std::string longhash_202612 = "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000";
- epee::string_tools::hex_to_pod(longhash_202612, res);
- }
- else if (major_version >= RX_BLOCK_VERSION) // RandomX
+ if (height == 202612 && get_block_longhash_202612(block_hashing_blob, res))
+ return res;
+
+ if (major_version >= RX_BLOCK_VERSION) // RandomX
{
crypto::rx_slow_hash(seed_hash.data, block_hashing_blob.data(), block_hashing_blob.size(), res.data);
}
diff --git a/tests/data/blocks/block_202612_mainnet.bin b/tests/data/blocks/block_202612_mainnet.bin
new file mode 100644
index 0000000..b2e81a3
Binary files /dev/null and b/tests/data/blocks/block_202612_mainnet.bin differ
diff --git a/tests/data/blocks/block_202612_stagenet.bin b/tests/data/blocks/block_202612_stagenet.bin
new file mode 100644
index 0000000..a282cc7
Binary files /dev/null and b/tests/data/blocks/block_202612_stagenet.bin differ
diff --git a/tests/data/blocks/block_202612_testnet.bin b/tests/data/blocks/block_202612_testnet.bin
new file mode 100644
index 0000000..f974597
Binary files /dev/null and b/tests/data/blocks/block_202612_testnet.bin differ
diff --git a/tests/unit_tests/cryptonote_format_utils.cpp b/tests/unit_tests/cryptonote_format_utils.cpp
index 1b15305..55bc147 100644
--- a/tests/unit_tests/cryptonote_format_utils.cpp
+++ b/tests/unit_tests/cryptonote_format_utils.cpp
@@ -30,8 +30,17 @@
#include "crypto/generators.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
+#include "file_io_utils.h"
#include "serialization/binary_utils.h"
#include "serialization/string.h"
+#include "string_tools.h"
+#include "unit_tests_utils.h"
+
+namespace
+{
+static constexpr const char EXISTING_BLOCK_POW_HASH_202612[] =
+ "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000";
+} //anonymous namespace
TEST(cn_format_utils, add_extra_nonce_to_tx_extra)
{
@@ -160,6 +169,108 @@ TEST(cn_format_utils, payment_id_tx_extra_nonce_rejects_wrong_tag_or_size)
EXPECT_FALSE(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(wrong_size, parsed_encrypted_payment_id));
}
+TEST(cn_format_utils, block_longhash_202612_arbitrary_blob)
+{
+ const cryptonote::blobdata blob = "not the historical block 202612 hashing blob";
+ const crypto::hash pow_hash = cryptonote::get_block_longhash(blob, 202612, 1, crypto::null_hash);
+
+ ASSERT_NE(EXISTING_BLOCK_POW_HASH_202612, epee::string_tools::pod_to_hex(pow_hash));
+}
+
+TEST(cn_format_utils, block_longhash_202612_mainnet_blob)
+{
+ // load existing mainnet block 202612 from file
+ const auto block_202612_blob_path = unit_test::data_dir / "blocks" / "block_202612_mainnet.bin";
+ cryptonote::blobdata block_blob;
+ ASSERT_TRUE(epee::file_io_utils::load_file_to_string(block_202612_blob_path.string(), block_blob));
+
+ // parse and validate existing mainnet block 202612 and check block ID (you can see bbd604d2... on an explorer)
+ cryptonote::block block;
+ crypto::hash block_id = crypto::null_hash;
+ ASSERT_TRUE(cryptonote::parse_and_validate_block_from_blob(block_blob, block, &block_id));
+ ASSERT_EQ("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698", epee::string_tools::pod_to_hex(block_id));
+
+ // get hashing blob for 202612 and check raw hash (you can see 426d16cf... in cryptonote::get_block_longhash() impl)
+ const cryptonote::blobdata hashing_blob = cryptonote::get_block_hashing_blob(block);
+ ASSERT_TRUE(cryptonote::get_object_hash(hashing_blob, block_id));
+ ASSERT_EQ("426d16cff04c71f8b16340b722dc4010a2dd3831c22041431f772547ba6e331a", epee::string_tools::pod_to_hex(block_id));
+
+ // call get_block_longhash() and check that we get the existing PoW hash
+ const crypto::hash pow_hash = cryptonote::get_block_longhash(hashing_blob, 202612, 1, crypto::null_hash);
+ ASSERT_EQ(EXISTING_BLOCK_POW_HASH_202612, epee::string_tools::pod_to_hex(pow_hash));
+}
+
+TEST(cn_format_utils, block_longhash_202612_testnet_blob)
+{
+ // load existing testnet block 202612 from file
+ const auto block_202612_blob_path = unit_test::data_dir / "blocks" / "block_202612_testnet.bin";
+ cryptonote::blobdata block_blob;
+ ASSERT_TRUE(epee::file_io_utils::load_file_to_string(block_202612_blob_path.string(), block_blob));
+
+ // parse and validate existing testnet block 202612 and check block ID (you can see 248fde4b... on an explorer)
+ cryptonote::block block;
+ crypto::hash block_id;
+ ASSERT_TRUE(cryptonote::parse_and_validate_block_from_blob(block_blob, block, block_id));
+ ASSERT_EQ("248fde4b96b829c4ddbd00e3f76d35b03d01257898bc1b5578bc9e04b379a676", epee::string_tools::pod_to_hex(block_id));
+
+ // get hashing blob for 202612 and check raw hash (you can see 248fde4b... in cryptonote::get_block_longhash() impl)
+ const cryptonote::blobdata hashing_blob = cryptonote::get_block_hashing_blob(block);
+ ASSERT_TRUE(cryptonote::get_object_hash(hashing_blob, block_id));
+ ASSERT_EQ("248fde4b96b829c4ddbd00e3f76d35b03d01257898bc1b5578bc9e04b379a676", epee::string_tools::pod_to_hex(block_id));
+
+ // call get_block_longhash() and check that we get the existing PoW hash
+ const crypto::hash pow_hash = cryptonote::get_block_longhash(hashing_blob, 202612, 9, crypto::null_hash);
+ ASSERT_EQ(EXISTING_BLOCK_POW_HASH_202612, epee::string_tools::pod_to_hex(pow_hash));
+}
+
+TEST(cn_format_utils, block_longhash_202612_stagenet_blob)
+{
+ // load existing stagenet block 202612 from file
+ const auto block_202612_blob_path = unit_test::data_dir / "blocks" / "block_202612_stagenet.bin";
+ cryptonote::blobdata block_blob;
+ ASSERT_TRUE(epee::file_io_utils::load_file_to_string(block_202612_blob_path.string(), block_blob));
+
+ // parse and validate existing stagenet block 202612 and check block ID (you can see f3449e65... on an explorer)
+ cryptonote::block block;
+ crypto::hash block_id;
+ ASSERT_TRUE(cryptonote::parse_and_validate_block_from_blob(block_blob, block, block_id));
+ ASSERT_EQ("f3449e658b5f880c4b0e69007ed5d092c9c883ac3a518166fa652d5cc505e7b1", epee::string_tools::pod_to_hex(block_id));
+
+ // get hashing blob for 202612 and check raw hash (you can see f3449e65... in cryptonote::get_block_longhash() impl)
+ const cryptonote::blobdata hashing_blob = cryptonote::get_block_hashing_blob(block);
+ ASSERT_TRUE(cryptonote::get_object_hash(hashing_blob, block_id));
+ ASSERT_EQ("f3449e658b5f880c4b0e69007ed5d092c9c883ac3a518166fa652d5cc505e7b1", epee::string_tools::pod_to_hex(block_id));
+
+ // call get_block_longhash() and check that we get the existing PoW hash
+ const crypto::hash pow_hash = cryptonote::get_block_longhash(hashing_blob, 202612, 9, crypto::null_hash);
+ ASSERT_EQ(EXISTING_BLOCK_POW_HASH_202612, epee::string_tools::pod_to_hex(pow_hash));
+}
+
+TEST(cn_format_utils, block_longhash_reconstruct_existing_202612)
+{
+ // load existing mainnet block 202612 from file
+ const auto block_202612_blob_path = unit_test::data_dir / "blocks" / "block_202612_mainnet.bin";
+ cryptonote::blobdata block_blob;
+ ASSERT_TRUE(epee::file_io_utils::load_file_to_string(block_202612_blob_path.string(), block_blob));
+
+ // parse and validate existing mainnet block 202612
+ cryptonote::block block;
+ ASSERT_TRUE(cryptonote::parse_and_validate_block_from_blob(block_blob, block));
+ ASSERT_EQ(513, block.tx_hashes.size());
+
+ // modify block and hashing blob s.t. it matches buggy behavior of tree hash before counting fix
+ block.tx_hashes.pop_back();
+ block.tx_hashes.pop_back();
+ cryptonote::blobdata hashing_blob = cryptonote::get_block_hashing_blob(block);
+ ASSERT_EQ(77, hashing_blob.size());
+ hashing_blob.at(75) = (char) -126; // mangle total tx count from 512 to 514
+
+ // check that we can reconstruct the existing PoW hash
+ crypto::hash pow_hash;
+ crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), pow_hash, /*variant=*/0, /*height=*/202612);
+ ASSERT_EQ(EXISTING_BLOCK_POW_HASH_202612, epee::string_tools::pod_to_hex(pow_hash));
+}
+
TEST(cn_format_utils, add_mm_merkle_root_to_tx_extra)
{
const std::vector<std::uint64_t> depths{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 63, 64, 127, 128, 16383, 16384};
Why this scored 60/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.