Skip to content

type-chrono: Add CustomBuildField/CustomReadField overloads for std::chrono::time_point#303

Open
ryanofsky wants to merge 1 commit into
bitcoin-core:masterfrom
ryanofsky:pr/timepoint
Open

type-chrono: Add CustomBuildField/CustomReadField overloads for std::chrono::time_point#303
ryanofsky wants to merge 1 commit into
bitcoin-core:masterfrom
ryanofsky:pr/timepoint

Conversation

@ryanofsky

@ryanofsky ryanofsky commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Needed for bitcoin/bitcoin#10102 since bitcoin/bitcoin#34882 was merged, which uses NodeClock::time_point in the CNodeStats struct (m_last_send, m_last_recv, m_ping_start) returned by Node::getNodesStats.

…chrono::time_point

Needed by bitcoin/bitcoin#34882 which uses NodeClock::time_point in the
Node stats struct (m_last_send, m_last_recv, m_ping_start), requiring
IPC serialization support for time_point types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@DrahtBot

DrahtBot commented Jul 8, 2026

Copy link
Copy Markdown

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline and AI policy for information on the review process.

Type Reviewers
ACK maflcko

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

@maflcko

maflcko commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

lgtm ACK b37d1d7

@Sjors

Sjors commented Jul 9, 2026

Copy link
Copy Markdown
Member

Needed by bitcoin/bitcoin#34882

That's merged, by I assume you still need this?

@maflcko

maflcko commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Needed by bitcoin/bitcoin#34882

That's merged, by I assume you still need this?

There was no rebase of bitcoin/bitcoin#10102 in 4 months, so I don't think bitcoin/bitcoin#10102 sees the changes from 34882 yet.

@ryanofsky

Copy link
Copy Markdown
Collaborator Author

Sorry PR description was written in a confusing way, should hopefully be clearer now. In order for bitcoin/bitcoin#10102 to work after bitcoin/bitcoin#34882 it needs these ReadField / BuildField overloads. It could define them itself, but it makes sense for libmultiprocess to provide them since it already provides many similar ones including ones for std::chrono::duration

Comment thread include/mp/type-chrono.h
Value&& value, Output&& output)
{
using Rep = typename Duration::rep;
static_assert(std::numeric_limits<decltype(output.get())>::lowest() <= std::numeric_limits<Rep>::lowest(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The range checks in both CustomBuildField overloads have a signedness bug:

static_assert(std::numeric_limits<decltype(output.get())>::lowest() <= std::numeric_limits<Rep>::lowest(), ...);

When the capnp field type is unsigned (e.g. UInt64) and Rep is signed (e.g. int64_t for std::chrono::nanoseconds), the usual arithmetic conversions convert Rep::lowest() (INT64_MIN) to the unsigned type before comparing, turning it into a huge positive number. So 0 <= INT64_MIN silently evaluates to true, and the assert passes for a field that can't actually represent negative tick counts (e.g. any pre-epoch system_clock::time_point).

Concretely, this means a UInt64 field paired with a signed Duration::rep compiles today, and output.set(negative_count) wraps the value into UINT64_MAX. That's silently wrong for anything that reads the field as its declared (unsigned) type — a different language's capnp bindings, a JSON dump, or a raw comparison would see 18446744073709551615 instead of -1. A C++ round trip through the same chrono type can look fine, since the wrap is bit-for-bit invertible at equal width, which is what makes this easy to miss in testing.

Fix: use std::cmp_less_equal/std::cmp_greater_equal (C++20, ) instead of <=/>=, since they're designed to compare integers of differing signedness correctly:

static_assert(std::cmp_less_equal(std::numeric_limits<decltype(output.get())>::lowest(), std::numeric_limits<Rep>::lowest()),
              "capnp type does not have enough range to hold lowest std::chrono::time_point value");
static_assert(std::cmp_greater_equal(std::numeric_limits<decltype(output.get())>::max(), std::numeric_limits<Rep>::max()),
              "capnp type does not have enough range to hold highest std::chrono::time_point value");

I verified this by temporarily declaring a test field as UInt64 against a signed Rep: it compiled before the fix and correctly fails to compile after.

Happy to push chrono type tests as a follow-up PR if useful.

Note: I had AI help me write this up clearly, apologies if the phrasing feels more polished or sloppy than my usual comments.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #303 (comment)

Good catch! I think this makes sense and it looks like this same problem also exists other places: for durations above and also in type-number.h. I'll see if it's possible to fix them all here without breaking anything.

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.

5 participants