Support Bitcoin Knots BLAKE2b header v2 blocks - #1333
Conversation
Bitcoin Knots v29.4.1 forks mainnet at height 961640 to a BLAKE2b
proof of work with a 164-byte header (version bit 31 set) and a new
block hash rule. rust-bitcoin's Header is fixed at 80 bytes, so
electrs died on the first headers message past the fork ("failed to
parse unsupported segwit version: 99") and restarted in a loop.
Replace bitcoin::block::Header with a knots::BlockHeader that decodes
and encodes both forms and hashes each the way CBlockHeader::GetHash()
does; the type and hash are adapted from Retropex's electrs fork (MIT).
Around it:
- p2p: parse "headers" entries as header + tx count instead of as an
empty Block, and check a received block's hash with the new type.
- chain: build on knots::BlockHeader; the genesis header converts.
- types/db: header rows are now 80 or 164 bytes. Existing databases
keep working: legacy rows decode as before and new rows append.
- index: walk a block by hand (header, count, transactions through the
bitcoin_slices visitor) since bsl::Block assumes an 80-byte header.
- electrum: unchanged code; block headers are served at their real
length, so blockchain.block.header returns 328 hex characters for a
BLAKE2b block.
Tests: the five header vectors from Knots' src/test/data/block_header_v2.json
(round trip and block hash), 2000 random v2 headers hashed by an
independent Python implementation (set KNOTS_RANDOM_VECTORS), and a
check that a legacy header still serializes and hashes exactly as
bitcoin::block::Header does. The existing suite passes unchanged.
|
Closing due to https://github.com/rust-bitcoin/rust-bitcoin#policy-on-altcoinsaltchains:
|
|
Not applicable, since this is for Bitcoin itself |
|
For anyone who lands here needing this: the branch is maintained at https://github.com/jasonsopko/electrs/tree/blake2b (0.10.10 plus header v2 parsing, hashing and storage). It has run on a Knots mainnet node since the fork block, 961640, and on testnet4. For pruned nodes, paulscode/electrs-pruned carries the same support. |
What
Support for Bitcoin Knots' BLAKE2b hardfork blocks: a header type that decodes both the legacy 80-byte header and the 164-byte header-v2 form (version bit 31), hashes each the way
CBlockHeader::GetHash()does, and the small changes around it so P2P sync, the header chain, the database rows, block indexing and the Electrum calls all accept either form.Why
Bitcoin Knots v29.4.1 forked mainnet at height 961640 to BLAKE2b proof of work with a longer header. rust-bitcoin's
Headeris fixed at 80 bytes, so an electrs following a Knots node dies on the firstheadersmessage past the fork ("failed to parse unsupported segwit version: 99") and restarts in a loop. Nothing changes for a node on the SHA256d chain: a legacy header decodes, encodes and hashes exactly as before, on-disk rows stay 80 bytes, and a set bit 31 cannot occur there because Core rejects those blocks asbad-version.The header type and its hash are adapted from Retropex's electrs fork (mempool/electrs lineage, MIT), credited in the module.
How I tested
cargo test: the existing suite passes unchanged, plus the five header-v2 vectors from Knots'src/test/data/block_header_v2.json(round trip and block hash), 2000 random v2 headers hashed by an independent Python implementation (setKNOTS_RANDOM_VECTORS), and a check that a legacy header still serializes and hashes byte for byte asbitcoin::block::Header.blockchain.headers.subscribereturns a 164-byte tip,blockchain.block.headerreturns 80 bytes for 961639 and 164 for 961640, ablock.headersrange spanning the fork concatenates correctly, andscripthash.get_historyfor a coinbase output mined at 961672 returns it with the right balance.Risk and rollback
No behavior change on a SHA256d chain; the v2 branch is unreachable there. Databases written by this version are readable by the previous one as long as no v2 block has been indexed. Revert the commit.
Still unsure about
Whether you want this behind a Cargo feature (
knots-blake2b, default off) to keep theblake2dependency out of the default build. The code splits cleanly that way; I left it unconditional because the legacy path is identical either way, but I will gate it if you prefer.