Skip to content

[release-v2.1] main: Use backported wire updates.#3658

Merged
davecgh merged 16 commits intodecred:release-v2.1from
davecgh:rel21_wire_backports
Apr 6, 2026
Merged

[release-v2.1] main: Use backported wire updates.#3658
davecgh merged 16 commits intodecred:release-v2.1from
davecgh:rel21_wire_backports

Conversation

@davecgh
Copy link
Copy Markdown
Member

@davecgh davecgh commented Apr 6, 2026

This updates the 2.1 release branch to use the latest version of the wire module which includes various optimizations, test improvements, hardening against potential misuse, and a tightening of the protocol to reject messages with trailing bytes.

In particular, the following updated module version is used:

- github.com/decred/dcrd/wire@v1.7.5

Note that it also cherry picks all of the relevant commits included in updates to the wire module to ensure they are also included in the release branch even though it is not strictly necessary since go.mod has been updated to require the new release and thus will pull in the new code. However, from past experience, not having code backported to modules available in the release branch too leads to headaches for devs building from source in their local workspace with overrides such as
those in go.work.

The following PRs are included:

davecgh added 16 commits April 6, 2026 13:04
This allows WriteHeaderN to avoid two unnecessary allocations by using the
same buffer to encode both the message header and the encoded message payload.
The payload is appended to zeroed-out header bytes in the buffer, before
calculating the payload checksum and serializing the header to the bytes in
the beginning of the buffer.

This change also results in one less Write to the network connection.
When ReadMessage/ReadMessageN error due to the message having an invalid
network magic or invalid encoding of the command string, error immediately
instead of reading the remaining byte count reported in the message header.
There is no good reason to do so, and the explanation given by the
discardInput comment is bogus.

Unlike message serialization for generic purposes (via the BtcEncode method or
the Message interface), ReadMessage{,N} is intended to only be used for
network connections which have already negotiated a protocol version.  Upon
any obvious error, the socket should just be closed (as dcrd's peer and
dcrwallet's p2p package both do) without reading any more input.

The previous behavior of discarding this input to read the next wire protocol
message is not documented by ReadMessage{,N}.  Changing this is considered to
be a bug fix rather than a major API break.
This special cases writes to bytes.Buffer, which is always the writer type
written to by WriteMessageN.  There are several optimizations that can be
implemented by special casing this type:

First, pulling temporary short buffers from binary freelist can be skipped
entirely, and instead the binary encoding of integers can be appended directly
to its existing capacity.  This avoids the synchronization cost to add and
remove buffers from the free list, and for applications which only ever write
wire messages with WriteMessageN, the allocation and ongoing garbage
collection scanning cost to for these buffers can be completely skipped.

Second, special casing the buffer type in WriteVarString saves us from
creating a temporary heap copy of a string, as the buffer's WriteString method
can be used instead.

Third, special casing the buffer allows WriteMessageN to calculate the
serialize size and grow its buffer so all remaining appends for writing block
and transactions will not have to reallocate the buffer's backing allocation.
This same optimization can be applied to other messages in the future.
This adds two additional type cases to the optimized writes for BLAKE-256
(used by transactions and the original PoW algorithm) and BLAKE-3 (used by the
current PoW algorithm).  It also updates both the block header and transaction
code to use these hashers during the calculation of block and transaction
hashes.
A previous commit which intended to remove the no longer used *[4]byte special
case (for message header checksums) from writeElement inadvertently removed
this from readElement instead.  Remove it from the intended place, and restore
the readElement case to avoid hitting the slow path.
This adds a couple of tests to ensure a couple of the newer error codes
produce the expected human-readable output that were missed when adding
them.

It also adds a new define to the enum to count how many there are along
with a test to detect missing entries.
This modifies the code that deals with serializing and deserializing the
version message int64s to be more restrictive and reject any values that
result in times that require special handling for comparisons.

This clamping behavior is not strictly required since code that deals
with the timestamps later is careful to avoid bad timestamps in general,
but it safer to just reject them at the protocol level so even if code
elsewhere is not taking extra precautions there would still not be any
potential issues.
This commit contains several performance and memory optimizations to improve
message deserialization, both when generically called through the BtcDecode
method of the Message interface and when reading wire protocol using
ReadMessageN.

Special cases have been added for the bytes.Buffer and bytes.Reader reader
types, avoiding the allocation and synchronization costs associated with using
temporary buffers from the binary freelist and avoiding the heap allocations
required due to the reader interface leaking the slice parameter.

ReadMessageN has reduced the number of allocations it must incur by avoiding
the readElements helper function.

Finally, a new internal buffer type is introduced that is used exclusively by
ReadMessageN.  Since the lifetime and memory of this buffer is under the
control of the wire package, and the buffer is never reset or truncated, this
allows transaction script deserialization to slice the internal bytes of this
buffer rather than pulling temporary buffers from the scriptPool freelist and
copying these into a new contiguous allocation.
The code that handles deserializing transaction scripts by way of the
free list cleans up in the event of error by returning the non-nil
scripts to the free list to avoid leaking them.

This is making an implicit (and undocumented) assumption that it is only
deserializing into empty instances and therefore any non-nil scripts in
the inputs and outputs in the event of a failed deserialization came
from the free list.  The consequence of that is that it is possible that
any slices that were set by the caller prior to a failed deserialization
could incorrectly be returned to the free list and ultimately get
clobbered later.

While this is not an issue for dcrd since it never deserializes into
non-empty instances, there is no guarantee that is true for all callers.

In order to ensure safety for all callers, this nils the input and
output slices prior to deserializing anything in order to ensure the
aforementioned assumption is always satisfied.
This avoids needing the fragile ErrUnexpectedEOF handling and prevents read
errors when the reader is buffered and next chunk of bytes is only partially
available.

Discovered by a failing wallet unit test which passed in a reader created by
hex.NewDecoder.
This adds some additional tests for short reads.

- Adds cases from some missing paths in the tests for readElement
- Adds another scenario when testing readElement that reader that only
  provides at most one byte per Read
- Adds a new test name TestShortReads that exercise each of the
  individual readX funcs with the various supported readers as well as a
  couple of readers for the default path including a one byte reader

Together these tests help ensure all of the new short read paths are
fully exercised.
This retracts that 1.7.3 wire release, updates the wire module copyright
year in the files modified since the previous wire/v1.7.2 release and
serves as a base for wire/v1.7.4.
This updates the tests which exercise various read message error paths
to improve their accuracy, modernize the error detection, and make them
more consistent with the newer formatting practices.
Currently, message decoding correctly reads exactly the amount of bytes
that are needed and silently ignores any remaining bytes.  This is
correct and expected behavior, however, the entire raw buffer is also
passed through to rest of the application code unaltered (aka with the
extra trailing bytes) for use in some very specific cases.

While there are no serious consequences to this behavior currently, it
is not ideal and could potentially lead to unexpected consequences in
the future.

With that in mind, this adds an additional safety check to reject any
messages that are not fully consumed while decoding to prevent them
immediately at the protocol level rather than leaving it to code at
higher layers to deal with.
This updates the 2.1 release branch to use the latest version of the
wire module which includes various optimizations, test improvements,
hardening against potential misuse, and a tightening of the protocol to
reject messages with trailing bytes.

In particular, the following updated module version is used:

- github.com/decred/dcrd/wire@v1.7.5
@davecgh davecgh added this to the 2.1.4 milestone Apr 6, 2026
@davecgh davecgh changed the title main: Use backported wire updates. [release-2.1] main: Use backported wire updates. Apr 6, 2026
@davecgh davecgh changed the title [release-2.1] main: Use backported wire updates. [release-v2.1] main: Use backported wire updates. Apr 6, 2026
@davecgh davecgh merged commit 6bdfdcf into decred:release-v2.1 Apr 6, 2026
34 checks passed
@davecgh davecgh deleted the rel21_wire_backports branch April 6, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants