diff --git a/include/dogecoin/block.h b/include/dogecoin/block.h index af3a316aa..7fb651a19 100644 --- a/include/dogecoin/block.h +++ b/include/dogecoin/block.h @@ -48,6 +48,30 @@ typedef struct _auxpow { void *ctx; } auxpow; +/* The AuxPoW proof carried by a merge-mined header, owned by the header it + belongs to. + * + * This deliberately has no back-pointer to its owning header, unlike + * dogecoin_auxpow_block. That struct owns both its header and its parent_header + * and frees them, so a header could not hold one without the two owning each + * other. The payload owns only parent_header, which is a plain 80-byte header + * with no payload of its own, so ownership terminates. + * + * The auxpow.check / auxpow.ctx hook on dogecoin_block_header is unaffected: + * it is a validation hook whose context the caller supplies at call time, not a + * reference to this data. */ +typedef struct dogecoin_auxpow_payload_ { + dogecoin_tx* parent_coinbase; + uint256_t parent_hash; + uint8_t parent_merkle_count; + uint256_t* parent_coinbase_merkle; + uint32_t parent_merkle_index; + uint8_t aux_merkle_count; + uint256_t* aux_merkle_branch; + uint32_t aux_merkle_index; + struct dogecoin_block_header_* parent_header; +} dogecoin_auxpow_payload; + typedef struct dogecoin_block_header_ { int32_t version; uint256_t prev_block; @@ -56,8 +80,20 @@ typedef struct dogecoin_block_header_ { uint32_t bits; uint32_t nonce; auxpow auxpow[1]; + /** AuxPoW proof for a merge-mined header, NULL otherwise. Retained so the + header can reproduce the bytes it was parsed from: the deserializer used + to parse this into a local dogecoin_auxpow_block and free it, and + dogecoin_block_header_copy carried only the auxpow hook fields, so the + proof was discarded and anything needing it had to re-parse. */ + dogecoin_auxpow_payload* auxpow_payload; } dogecoin_block_header; +/** Free an AuxPoW payload and everything it owns. */ +LIBDOGECOIN_API void dogecoin_auxpow_payload_free(dogecoin_auxpow_payload* payload); + +/** Deep-copy an AuxPoW payload. Returns NULL if src is NULL. */ +LIBDOGECOIN_API dogecoin_auxpow_payload* dogecoin_auxpow_payload_copy(const dogecoin_auxpow_payload* src); + typedef struct dogecoin_auxpow_block_ { dogecoin_block_header* header; dogecoin_tx* parent_coinbase; @@ -75,9 +111,53 @@ LIBDOGECOIN_API dogecoin_block_header* dogecoin_block_header_new(); LIBDOGECOIN_API void dogecoin_block_header_free(dogecoin_block_header* header); LIBDOGECOIN_API dogecoin_auxpow_block* dogecoin_auxpow_block_new(); LIBDOGECOIN_API void dogecoin_auxpow_block_free(dogecoin_auxpow_block* block); +/** Parse a block header off the wire without validating it. + * + * Reads the 80 base fields and, when version bit 0x100 is set, the AuxPoW + * proof, which is retained on the header. Runs no proof-of-work check. + * + * check_auxpow is scrypt work over the parent chain. Doing it during parsing + * means every caller pays for it whether or not it wants the answer yet, and + * before any peer-level gating has happened. Callers that want the fields and + * will decide about validation later use this; callers that want the existing + * parse-and-validate behaviour keep using dogecoin_block_header_deserialize. + */ +LIBDOGECOIN_API int dogecoin_block_header_parse(dogecoin_block_header* header, struct const_buffer* buf, const dogecoin_chainparams *params); + +/** Validate a parsed header's AuxPoW and fill @p chainwork. + * + * Returns true immediately for a header with no AuxPoW: its proof of work is + * over the 80 base bytes and is the caller's to verify. Requires the header to + * still own its proof, so it must have come from dogecoin_block_header_parse + * or dogecoin_block_header_deserialize. + */ +LIBDOGECOIN_API int dogecoin_block_header_validate(dogecoin_block_header* header, const dogecoin_chainparams *params, arith_uint256* chainwork); + +/** Parse and validate. Unchanged in behaviour and signature: this is + * dogecoin_block_header_parse followed by dogecoin_block_header_validate. + * + * Deliberately kept as the name that validates. Making this the pure parse and + * adding a checked variant would silently stop verifying proof of work for any + * caller of the existing name, with no compile error to catch it. + */ LIBDOGECOIN_API int dogecoin_block_header_deserialize(dogecoin_block_header* header, struct const_buffer* buf, const dogecoin_chainparams *params, arith_uint256* chainwork); LIBDOGECOIN_API int deserialize_dogecoin_auxpow_block(dogecoin_auxpow_block* block, struct const_buffer* buffer, const dogecoin_chainparams *params, arith_uint256* chainwork); +/** Serialize the 80 base header fields. This is the pure header: it never + * emits AuxPoW, because its output is what the block hash, the scrypt proof of + * work and the fixed-width headers.db record are computed over. */ LIBDOGECOIN_API void dogecoin_block_header_serialize(cstring* s, const dogecoin_block_header* header); + +/** Serialize an AuxPoW proof in wire order. */ +LIBDOGECOIN_API void dogecoin_auxpow_payload_serialize(cstring* s, const dogecoin_auxpow_payload* payload); + +/** Serialize a header as it appears on the wire: the 80 base bytes, followed by + * the AuxPoW proof when the header carries one. + * + * This is Core's CBlockHeader to dogecoin_block_header_serialize's + * CPureBlockHeader. Messages that carry a whole header -- headers, block, + * cmpctblock -- want this one; anything hashing the header wants the pure form. + */ +LIBDOGECOIN_API void dogecoin_block_header_serialize_full(cstring* s, const dogecoin_block_header* header); LIBDOGECOIN_API void dogecoin_block_header_copy(dogecoin_block_header* dest, const dogecoin_block_header* src); LIBDOGECOIN_API dogecoin_bool dogecoin_block_header_hash(dogecoin_block_header* header, uint256_t hash); diff --git a/src/block.c b/src/block.c index b1b780727..709273edf 100644 --- a/src/block.c +++ b/src/block.c @@ -175,6 +175,7 @@ dogecoin_block_header* dogecoin_block_header_new() { header->auxpow->check = check; header->auxpow->ctx = header; header->auxpow->is = false; + header->auxpow_payload = NULL; return header; } @@ -207,8 +208,61 @@ dogecoin_auxpow_block* dogecoin_auxpow_block_new() { * * @return Nothing. */ +void dogecoin_auxpow_payload_free(dogecoin_auxpow_payload* payload) { + if (!payload) return; + dogecoin_tx_free(payload->parent_coinbase); + dogecoin_free(payload->parent_coinbase_merkle); + dogecoin_free(payload->aux_merkle_branch); + /* parent_header is a plain 80-byte header: its own auxpow_payload is NULL, + so this does not recurse. */ + dogecoin_block_header_free(payload->parent_header); + dogecoin_free(payload); + } + +dogecoin_auxpow_payload* dogecoin_auxpow_payload_copy(const dogecoin_auxpow_payload* src) { + if (!src) return NULL; + dogecoin_auxpow_payload* dst = dogecoin_calloc(1, sizeof(*dst)); + if (!dst) return NULL; + + memcpy_safe(dst->parent_hash, src->parent_hash, sizeof(uint256_t)); + dst->parent_merkle_count = src->parent_merkle_count; + dst->parent_merkle_index = src->parent_merkle_index; + dst->aux_merkle_count = src->aux_merkle_count; + dst->aux_merkle_index = src->aux_merkle_index; + + if (src->parent_coinbase) { + dst->parent_coinbase = dogecoin_tx_new(); + if (!dst->parent_coinbase) goto fail; + dogecoin_tx_copy(dst->parent_coinbase, src->parent_coinbase); + } + if (src->parent_merkle_count && src->parent_coinbase_merkle) { + size_t n = (size_t)src->parent_merkle_count * sizeof(uint256_t); + dst->parent_coinbase_merkle = dogecoin_malloc(n); + if (!dst->parent_coinbase_merkle) goto fail; + memcpy_safe(dst->parent_coinbase_merkle, src->parent_coinbase_merkle, n); + } + if (src->aux_merkle_count && src->aux_merkle_branch) { + size_t n = (size_t)src->aux_merkle_count * sizeof(uint256_t); + dst->aux_merkle_branch = dogecoin_malloc(n); + if (!dst->aux_merkle_branch) goto fail; + memcpy_safe(dst->aux_merkle_branch, src->aux_merkle_branch, n); + } + if (src->parent_header) { + dst->parent_header = dogecoin_block_header_new(); + if (!dst->parent_header) goto fail; + dogecoin_block_header_copy(dst->parent_header, src->parent_header); + } + return dst; + +fail: + dogecoin_auxpow_payload_free(dst); + return NULL; + } + void dogecoin_block_header_free(dogecoin_block_header* header) { if (!header) return; + dogecoin_auxpow_payload_free(header->auxpow_payload); + header->auxpow_payload = NULL; header->version = 0; dogecoin_mem_zero(&header->prev_block, DOGECOIN_HASH_LENGTH); dogecoin_mem_zero(&header->merkle_root, DOGECOIN_HASH_LENGTH); @@ -320,7 +374,9 @@ void print_block(dogecoin_auxpow_block* block) { * * @return 1 if deserialization was successful, 0 otherwise. */ -int dogecoin_block_header_deserialize(dogecoin_block_header* header, struct const_buffer* buf, const dogecoin_chainparams *params, arith_uint256* chainwork) { +static int parse_dogecoin_auxpow_fields(dogecoin_auxpow_block* block, struct const_buffer* buffer, const dogecoin_chainparams *params); + +int dogecoin_block_header_parse(dogecoin_block_header* header, struct const_buffer* buf, const dogecoin_chainparams *params) { dogecoin_auxpow_block* block = dogecoin_auxpow_block_new(); int ret = false; if (!deser_s32(&block->header->version, buf)) @@ -337,11 +393,34 @@ int dogecoin_block_header_deserialize(dogecoin_block_header* header, struct cons goto cleanup; dogecoin_block_header_copy(header, block->header); if ((block->header->version & 0x100) != 0 && buf->len) { - if (!deserialize_dogecoin_auxpow_block(block, buf, params, chainwork)) { + if (!parse_dogecoin_auxpow_fields(block, buf, params)) { printf("%s:%d:%s:%s\n", __FILE__, __LINE__, __func__, strerror(errno)); goto cleanup; } dogecoin_block_header_copy(header, block->header); + /* Move the proof onto the header instead of letting cleanup free it. + Ownership transfers: the fields are nulled on the block so + dogecoin_auxpow_block_free does not release what the header now owns. */ + dogecoin_auxpow_payload* payload = dogecoin_calloc(1, sizeof(*payload)); + if (!payload) goto cleanup; + payload->parent_coinbase = block->parent_coinbase; + memcpy_safe(payload->parent_hash, block->parent_hash, sizeof(uint256_t)); + payload->parent_merkle_count = block->parent_merkle_count; + payload->parent_coinbase_merkle = block->parent_coinbase_merkle; + payload->parent_merkle_index = block->parent_merkle_index; + payload->aux_merkle_count = block->aux_merkle_count; + payload->aux_merkle_branch = block->aux_merkle_branch; + payload->aux_merkle_index = block->aux_merkle_index; + payload->parent_header = block->parent_header; + block->parent_coinbase = NULL; + block->parent_coinbase_merkle = NULL; + block->aux_merkle_branch = NULL; + block->parent_header = NULL; + /* No free of a prior payload here: dogecoin_block_header_copy above has + already overwritten the pointer, and header may have arrived as an + uninitialised stack struct. Callers own dest's prior contents, the + same contract every other field in this function follows. */ + header->auxpow_payload = payload; } ret = true; cleanup: @@ -349,7 +428,49 @@ int dogecoin_block_header_deserialize(dogecoin_block_header* header, struct cons return ret; } -int deserialize_dogecoin_auxpow_block(dogecoin_auxpow_block* block, struct const_buffer* buffer, const dogecoin_chainparams *params, arith_uint256* chainwork) { +int dogecoin_block_header_validate(dogecoin_block_header* header, const dogecoin_chainparams *params, arith_uint256* chainwork) { + if (!header) return false; + /* Nothing to check for a header with no AuxPoW: its proof of work is over + the 80 base bytes, which is the caller's to verify -- headersdb_file.c + does exactly that for the non-AuxPoW case, and fills chainwork itself. */ + if (!header->auxpow_payload) return true; + + /* check_auxpow wants a dogecoin_auxpow_block. Build one that borrows from + the header and its payload rather than copying: it is never freed, so the + borrowed pointers are not released twice. dogecoin_auxpow_block_free + would take the header and parent_header with it, which is exactly the + ownership tangle the payload type exists to avoid. */ + dogecoin_auxpow_payload* p = header->auxpow_payload; + dogecoin_auxpow_block view; + dogecoin_mem_zero(&view, sizeof(view)); + view.header = header; + view.parent_coinbase = p->parent_coinbase; + memcpy_safe(view.parent_hash, p->parent_hash, sizeof(uint256_t)); + view.parent_merkle_count = p->parent_merkle_count; + view.parent_coinbase_merkle = p->parent_coinbase_merkle; + view.parent_merkle_index = p->parent_merkle_index; + view.aux_merkle_count = p->aux_merkle_count; + view.aux_merkle_branch = p->aux_merkle_branch; + view.aux_merkle_index = p->aux_merkle_index; + view.parent_header = p->parent_header; + + if (!check_auxpow(&view, (dogecoin_chainparams*)params, chainwork)) { + printf("check_auxpow failed!\n"); + return false; + } + return true; + } + +int dogecoin_block_header_deserialize(dogecoin_block_header* header, struct const_buffer* buf, const dogecoin_chainparams *params, arith_uint256* chainwork) { + if (!dogecoin_block_header_parse(header, buf, params)) return false; + return dogecoin_block_header_validate(header, params, chainwork); + } + +/* Parse the AuxPoW fields off the wire. No validation: check_auxpow is scrypt + work on the parent chain, and doing it here means every caller pays for it + during parsing whether or not it wants the answer yet. */ +static int parse_dogecoin_auxpow_fields(dogecoin_auxpow_block* block, struct const_buffer* buffer, const dogecoin_chainparams *params) { + (void)params; if (buffer->len > DOGECOIN_MAX_P2P_MSG_SIZE) { return printf("\ntransaction is invalid or to large.\n\n"); } @@ -465,11 +586,18 @@ int deserialize_dogecoin_auxpow_block(dogecoin_auxpow_block* block, struct const return false; } + return true; + } + +/* Unchanged behaviour and signature: parse, then validate. Callers that want + only the fields use parse_dogecoin_auxpow_fields via + dogecoin_block_header_parse. */ +int deserialize_dogecoin_auxpow_block(dogecoin_auxpow_block* block, struct const_buffer* buffer, const dogecoin_chainparams *params, arith_uint256* chainwork) { + if (!parse_dogecoin_auxpow_fields(block, buffer, params)) return false; if (!check_auxpow(block, (dogecoin_chainparams*)params, chainwork)) { printf("check_auxpow failed!\n"); return false; } - return true; } @@ -482,6 +610,36 @@ int deserialize_dogecoin_auxpow_block(dogecoin_auxpow_block* block, struct const * * @return Nothing. */ +void dogecoin_auxpow_payload_serialize(cstring* s, const dogecoin_auxpow_payload* payload) { + if (!s || !payload) return; + /* Wire order mirrors parse_dogecoin_auxpow_fields exactly. */ + dogecoin_tx_serialize(s, payload->parent_coinbase); + ser_u256(s, payload->parent_hash); + ser_varlen(s, payload->parent_merkle_count); + uint8_t i; + for (i = 0; i < payload->parent_merkle_count; i++) + ser_u256(s, payload->parent_coinbase_merkle[i]); + ser_u32(s, payload->parent_merkle_index); + ser_varlen(s, payload->aux_merkle_count); + for (i = 0; i < payload->aux_merkle_count; i++) + ser_u256(s, payload->aux_merkle_branch[i]); + ser_u32(s, payload->aux_merkle_index); + /* Parent header is a pure 80-byte header: it carries no AuxPoW of its own. */ + dogecoin_block_header_serialize(s, payload->parent_header); + } + +void dogecoin_block_header_serialize_full(cstring* s, const dogecoin_block_header* header) { + if (!s || !header) return; + dogecoin_block_header_serialize(s, header); + /* Core's CBlockHeader::SerializationOp appends the AuxPoW whenever + nVersion & VERSION_AUXPOW is set. Mirror that, driven by whether the + proof is actually present rather than by the bit alone, so a header + carrying the bit but no proof serializes as the 80 bytes it really has + instead of emitting a truncated blob. */ + if (header->auxpow_payload) + dogecoin_auxpow_payload_serialize(s, header->auxpow_payload); + } + void dogecoin_block_header_serialize(cstring* s, const dogecoin_block_header* header) { ser_s32(s, header->version); ser_u256(s, header->prev_block); @@ -510,6 +668,17 @@ void dogecoin_block_header_copy(dogecoin_block_header* dest, const dogecoin_bloc dest->auxpow->check = src->auxpow->check; dest->auxpow->ctx = src->auxpow->ctx; dest->auxpow->is = src->auxpow->is; + /* Deep-copy the proof. Carrying only the hook fields is what discarded it + before, so a copied merge-mined header could not be re-serialized or + re-validated without going back to the wire bytes. + + Assign, do not free what dest held. Every other field here is a plain + overwrite: this function treats dest as raw memory, and callers pass + uninitialised stack headers to it -- net_tests.c does, via + dogecoin_block_header_deserialize. Freeing dest->auxpow_payload would + dereference whatever the stack happened to contain. A dest that already + owns a payload is the caller's to release, as with every other member. */ + dest->auxpow_payload = dogecoin_auxpow_payload_copy(src->auxpow_payload); } /** diff --git a/test/block_tests.c b/test/block_tests.c index 3d27c9e76..b96520fb1 100644 --- a/test/block_tests.c +++ b/test/block_tests.c @@ -333,7 +333,91 @@ void test_auxpow_deserialize_real_vector() { u_assert_uint32_eq(header->bits, 456184976); u_assert_uint32_eq(header->nonce, 0); + /* The AuxPoW proof must survive the parse. It used to be built into a local + dogecoin_auxpow_block and freed at cleanup, so a caller holding the header + could not re-serialize or re-validate it without going back to the wire. */ + u_assert_not_null(header->auxpow_payload); + u_assert_not_null(header->auxpow_payload->parent_coinbase); + u_assert_not_null(header->auxpow_payload->parent_header); + u_assert_int_eq(header->auxpow_payload->parent_merkle_count > 0, 1); + u_assert_not_null(header->auxpow_payload->parent_coinbase_merkle); + + /* And it must copy deeply. dogecoin_block_header_copy carried only the + auxpow hook fields, so a copied merge-mined header silently lost its + proof; now the copy owns its own, and freeing one must not disturb the + other. */ + dogecoin_block_header* dup = dogecoin_block_header_new(); + dogecoin_block_header_copy(dup, header); + u_assert_not_null(dup->auxpow_payload); + u_assert_int_eq(dup->auxpow_payload != header->auxpow_payload, 1); + u_assert_int_eq(dup->auxpow_payload->parent_coinbase + != header->auxpow_payload->parent_coinbase, 1); + u_assert_int_eq(dup->auxpow_payload->parent_merkle_count + == header->auxpow_payload->parent_merkle_count, 1); + u_assert_mem_eq(dup->auxpow_payload->parent_hash, + header->auxpow_payload->parent_hash, DOGECOIN_HASH_LENGTH); + + /* Free the source first: the copy must still be intact, which it can only + be if nothing is shared. */ dogecoin_block_header_free(header); + u_assert_not_null(dup->auxpow_payload); + u_assert_not_null(dup->auxpow_payload->parent_header); + dogecoin_block_header_free(dup); + + /* The parse/check split: dogecoin_block_header_parse must produce the same + header and the same retained proof, without running check_auxpow. */ + dogecoin_block_header* parsed = dogecoin_block_header_new(); + struct const_buffer cb2 = { buf, blen }; + u_assert_int_eq(dogecoin_block_header_parse(parsed, &cb2, &dogecoin_chainparams_main), 1); + u_assert_uint32_eq((uint32_t)parsed->version, 0x00620102); + u_assert_uint32_eq(parsed->timestamp, 1410464609); + u_assert_not_null(parsed->auxpow_payload); + u_assert_not_null(parsed->auxpow_payload->parent_header); + + /* Validation deferred to a separate call, and it agrees with what the + parse-and-validate path computed. */ + arith_uint256 deferred_chainwork; + u_assert_int_eq(dogecoin_block_header_validate(parsed, &dogecoin_chainparams_main, + &deferred_chainwork), 1); + u_assert_mem_eq(utils_uint8_to_hex(arith_to_uint256(&deferred_chainwork), DOGECOIN_HASH_LENGTH), + utils_uint8_to_hex(arith_to_uint256(&chainwork), DOGECOIN_HASH_LENGTH), 64); + dogecoin_block_header_free(parsed); + + /* A header with no AuxPoW validates trivially: its proof of work is over + the 80 base bytes and belongs to the caller, which is why + headersdb_file.c runs check_pow itself for that case. */ + dogecoin_block_header* plain = dogecoin_block_header_new(); + plain->version = 1; + u_assert_int_eq(dogecoin_block_header_validate(plain, &dogecoin_chainparams_main, NULL), 1); + dogecoin_block_header_free(plain); + + /* Round-trip: a parsed merge-mined header must serialize back to exactly the + bytes it came from. This is the check that makes the serializer worth + anything -- emitting something well-formed but different would still let + short IDs and block hashes diverge from Core. */ + dogecoin_block_header* rt = dogecoin_block_header_new(); + struct const_buffer cb3 = { buf, blen }; + u_assert_int_eq(dogecoin_block_header_parse(rt, &cb3, &dogecoin_chainparams_main), 1); + size_t hdr_span = blen - cb3.len; + u_assert_int_eq(hdr_span > 80, 1); + + cstring* full = cstr_new_sz(hdr_span + 16); + dogecoin_block_header_serialize_full(full, rt); + u_assert_int_eq(full->len == hdr_span, 1); + u_assert_int_eq(memcmp(full->str, buf, hdr_span), 0); + + /* And the pure form stays 80 bytes: the block hash, the scrypt proof of + work and the headers.db record are all computed over it, so it must not + start emitting AuxPoW just because the header now retains some. */ + cstring* pure = cstr_new_sz(96); + dogecoin_block_header_serialize(pure, rt); + u_assert_int_eq(pure->len == 80, 1); + u_assert_int_eq(memcmp(pure->str, buf, 80), 0); + + cstr_free(full, true); + cstr_free(pure, true); + dogecoin_block_header_free(rt); + dogecoin_free(buf); }