block: let a header own its AuxPoW proof - #392
Open
xanimo wants to merge 1 commit into
Open
Conversation
xanimo
force-pushed
the
0.1.5-dev-auxpow-payload
branch
from
August 4, 2026 00:35
1ba63b3 to
7cbf5ea
Compare
This was referenced Aug 4, 2026
edtubbs
approved these changes
Aug 5, 2026
edtubbs
left a comment
Member
There was a problem hiding this comment.
ACK, compact blocks give a reason to keep auxpow proofs around after validation, which is good for other uses too.
dogecoin_block_header carried an auxpow member holding only the validation hook -- check, ctx and is -- never the proof itself. The proof was built into a local dogecoin_auxpow_block inside dogecoin_block_header_deserialize and freed at cleanup, so by the time the caller had its header the parent coinbase, merkle branches and parent header were gone. dogecoin_block_header_copy made that worse by looking complete: it copied the three hook fields, so a copied merge-mined header was silently missing its proof with nothing to indicate it. Anything needing the proof after the parse therefore had to re-parse the wire bytes, or retain them separately. That is why BIP152 keeps a header_raw span on dogecoin_compact_block: not because a compact block wants raw bytes, but because the parsed header could not answer for itself. Add dogecoin_auxpow_payload, owned by the header. The deserializer moves the parsed fields onto it and nulls them on the scratch block, so ownership transfers rather than duplicating; _free releases it; _copy deep-copies it. The payload deliberately has no back-pointer to its header, unlike dogecoin_auxpow_block, which owns both its header and its parent_header and frees them. A header holding one of those would own the thing that owns it. The payload owns only parent_header, a plain 80-byte header whose own payload is NULL, so ownership terminates. auxpow.check / auxpow.ctx are untouched. That is a validation hook whose context the caller supplies at call time -- validation.c passes the block directly -- not a reference to this data. dogecoin_block_header_copy assigns the copied payload rather than freeing what dest held. Every other field there is a plain overwrite: the function treats dest as raw memory, and callers pass uninitialised stack headers to it -- net_tests.c does, through dogecoin_block_header_deserialize. Freeing dest->auxpow_payload dereferenced whatever the stack contained, which is a SEGV on eight platforms and the reason ASAN caught this and a local build did not: every local caller happened to use dogecoin_block_header_new, where the pointer is NULL. A dest that already owns a payload is the caller's to release, as with every other member. The test extends the height-371338 vector: the proof survives the parse, a copy owns an independent one, and freeing the source leaves the copy intact, which it can only do if nothing is shared. Disabling just the deep copy fails it at line 351. WITH_NET=ON with -DBUILD_SHARED_LIBS=1: 78/78. WITH_NET=OFF: 72/72. ASAN+UBSAN: 78/78, no leaks.
xanimo
force-pushed
the
0.1.5-dev-auxpow-payload
branch
from
August 5, 2026 21:04
7cbf5ea to
75f6348
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prerequisite for the AuxPoW follow-up sequence (parse/check split, then an
AuxPoW serializer). Independent of the BIP157/BIP152 stacks — this branches
straight off
0.1.5-dev.dogecoin_block_headercarried anauxpowmember holding only the validationhook —
check,ctx,is— never the proof. The proof was built into a localdogecoin_auxpow_blockinsidedogecoin_block_header_deserializeand freed atcleanup:, so by the time a caller had its header, the parent coinbase, merklebranches and parent header were gone.
dogecoin_block_header_copymade it worse by looking complete — it copied thethree hook fields, so a copied merge-mined header was silently missing its proof
with nothing to indicate it.
Anything needing the proof after the parse therefore had to re-parse the wire
bytes or retain them separately. That is why BIP152 keeps a
header_rawspanon
dogecoin_compact_block— not because a compact block wants raw bytes, butbecause the parsed header could not answer for itself.
What this adds
dogecoin_auxpow_payload, owned by the header. The deserializer moves theparsed fields onto it and nulls them on the scratch block, so ownership
transfers rather than duplicating;
_freereleases it;_copydeep-copies it.The payload deliberately has no back-pointer to its header, unlike
dogecoin_auxpow_block, which owns both itsheaderand itsparent_headerand frees them — a header holding one of those would own the thing that owns it.
The payload owns only
parent_header, a plain 80-byte header whose own payloadis NULL, so ownership terminates.
auxpow.check/auxpow.ctxare untouched. That is a validation hook whosecontext the caller supplies at call time —
validation.c:111passes the blockdirectly — not a reference to this data.
Test
Extends the height-371338 vector: the proof survives the parse, a copy owns an
independent one, and freeing the source leaves the copy intact — which it can
only do if nothing is shared.
Checked for bite, not just for passing: disabling only the deep copy in
_copyand leaving retention intact fails at line 351. A test that passedeither way would have proved nothing.
WITH_NET=ONwith-DBUILD_SHARED_LIBS=1: 78/78.WITH_NET=OFF: 72/72.What it unblocks
check_auxpowneeds the proof to still bearound after parsing, which it now is
is what makes one writable, and the encode side of
dogecoin_compact_block_serializeneeds it before libdogecoin can originate acmpctblockfor a block it assembled rather than receivedheader_rawbecomes an optimisation rather than a necessity