Skip to content

coordinator/tributary tests#783

Merged
kayabaNerve merged 91 commits intoserai-dex:nextfrom
rafael-xmr:next-tributary-tests
Apr 28, 2026
Merged

coordinator/tributary tests#783
kayabaNerve merged 91 commits intoserai-dex:nextfrom
rafael-xmr:next-tributary-tests

Conversation

@rafael-xmr
Copy link
Copy Markdown

@rafael-xmr rafael-xmr commented Apr 9, 2026

coordinator/tributary/src/tests/transaction.rs

1) [bug] Identified in impl ReadWrite for Transaction:

Replaced the usage of:

borsh::from_reader(reader)

for:

borsh::BorshDeserialize::deserialize_reader(reader)

And added the tests sequential_reads_from_shared_reader and borsh_from_reader_rejects_shared_reader_with_trailing_bytes to verify the behavior, as using the previous method would fail on the scenario of multiple transactions on reader.

coordinator/tributary/src/tests/db.rs

1) [feat] Added an assertion for the commented case:

Here:

// This function will only be called once for a (validator, topic) tuple due to how we handle
// nonces on transactions (deterministically to the topic)
assert!(
  txn.get(Accumulated::<D>::key(set, topic, validator)).is_none(),
  "accumulate called twice for the same (validator, topic) tuple: \
   the nonce system should have prevented this"
);

Not entirely necessary just defensive.

2) [bug] Bad usage of ::get(): replaced by ::key()

Found a bug which, given the nature of the accumulate function using a Generic type D for the data, when an Accumulate entry is added for a Preprocess ([u8; 64]) type, later a type of Share ([u8; 32]) would panic with a "Not all bytes read" error trying to deserialize the DB entry for the preceding topic's type. This is fixed by not trying to get the Accumulated entry with D as a type to deserialize, but rather getting its preceding topic key and using txn.get() instead.

From:

if Accumulated::<D>::get(txn, set, preceding_topic, validator).is_none() {

To:

if txn.get(Accumulated::<D>::key(set, preceding_topic, validator)).is_none() {

3) [bug] attempt * BASE_REATTEMPT_DELAY overflow:

From:

let blocks_till_reattempt = u64::from(attempt * BASE_REATTEMPT_DELAY);

To:

let blocks_till_reattempt = u64::from(attempt) * u64::from(BASE_REATTEMPT_DELAY);

Before, a too high attempt * BASE_REATTEMPT_DELAY could overflow the u32::max, now convert each value to u64 first, before doing the operation which avoids the u32 overflow.

coordinator/tributary/src/tests/lib.rs

1) [bug] panic on slash_report's added with a value of 0

By not filtering the 0 points reports, slash_report panics when expected to be less or equal than f

            let mut slash_report = vec![];
            for points in amortized_slash_report {
              // TODO: Natively store this as a `Slash`
              if points == u32::MAX {
                slash_report.push(Slash::Fatal);
              } else {
                slash_report.push(Slash::Points(points));
              }
            }
            assert!(slash_report.len() <= f);

The fix is:

              } else if points > 0 {
                slash_report.push(Slash::Points(points));

Developer Certificate of Origin
Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.


Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
No output from a Large Language Model (LLM) was included within any of the
contribution.

This reverts commit 4a1cf91.
@kayabaNerve
Copy link
Copy Markdown
Member

FYI, some of the above CI failures are because they're misconfigured. For anything on: push: branches: - next, the CI should pass and should continue to pass however. For the current HEAD of next, 970f86d, 22 were triggered and 22 passed (where some more may have triggered if the PR modified more files).

With this PR,

  • Lint/[fmt, clippy, machete] fail
  • Lint/vet fails because the Cargo.lock is dirty (and may independently fail even if it wasn't dirty)

I don't assume this PR is final, so no worries on the failures, I just wanted to clarify which lights are expected to be red and which lights should be green. If any CIs are being a problem, feel free to poke me :) For new crates, there's some basic definitions which have to be added for it to work as expected, and now, even for updates to/new dependencies, cargo vet will need to be poked (presumably by me).

@kayabaNerve
Copy link
Copy Markdown
Member

That's disgusting re: borsh::from_reader. It isn't even documented behavior. Can we add it to the clippy.toml's disallowed methods for the footgun of requiring encapsulation, please? Though I'll note we do have the opposite DoS concern, where someone submits valid data with arbitrary junk after it, and once we have read everything, we do need to check the reader is then empty ourselves. I don't think achieving that by adopting borsh::from_reader is the correct solution at this time as it's unreliable and we likely need a more encompassing solution. A reader over a slice that on drop, checks if it was empty or not, could be interesting if there was a way to handle an error on drop...

Defensive assertions are good. I have a note the processor really should be flush with them.

I have yet to review the PR itself, these are just a pair of comments on the summary :)

…cends from

This reverts a myriad of `cargo update`s.
A `u32`, when incremented by one, is technically feasible to overflow. This PR
changed it to return `None` for the next topic if it did overflow, but that'd
technically stop creating topics for ongoing protocols. This transformed a
panic (which would crash the entire `coordinator`) into a liveness failure for
this specific Tributary due to inaccurately claiming there was no next topic
(an unsound definition which would become a more localized liveness issue).

This uses a `u64` to ensure this state is unreachable when started from zero,
and incremented by one, unless so many years pass this is definitively
irrelevant.
Only pushing some elements meant that this vector no longer corresponded to the
list of validators. Since each element wasn't paired with a validator's ID, it
became meaningless. The `assert` which triggered was the underlying fault, and
it's been adjusted to only check the amount of non-zero elements is now less
than or equal to `f`.
While it currently _can_ be collapsed to the function `required_participation`,
the ability for per-topic thresholds is desired.

The calculation is also updated to be infallible.
Moving to `SigningProtocolRound` would be nice if all such transactions fit
that model. As shown, not all transactions do. Kludging them into
`SigningProtocolRound` isn't preferred, and it appears to have been improperly
done for `SigningProtocolRound` (causing a `clippy` lint which was allowed).
Some are made more flexible, removing the need for other approximate variants.
Test helpers which encoded a non-syntactic semantic context via their name have
been removed outright, especially as `substrate/primitives` shouldn't have
helpers for Coordinator-specific definitions which may not be universal or
applicable to the Substrate context.

Similarly, these should likely be moved to `tests/helpers` where we have more
flexibility. We want to publish `substrate/primitives` but I couldn't care less
about the idea of publishing these test helpers and committing to them under
our API in any way. While the `Cargo.toml` already has a comment on the
`test-helpers` feature, their own crate entirely avoid any contamination here.
…s` type aliases

I understand the intent. Unfortunately, these definitions were incorrect.

For a FROST protocol over Ristretto, the preprocess will be 64 bytes and the
share 32 bytes. This is used for the DKG confirmation, batches, where the
former hard-coded such a definition. For batches, the process is routed via
`VariantSignId` where the same processing occurs for transactions. Signing
transactions on external networks has a preprocess, share, of length variable
to the external network and its signing protocol. For Ethereum, it'd be a
66-byte preprocess and 32-byte share. For Bitcoin, it's a 64-byte preprocess
and 32-byte share _per input_ (concatenated into a single byte blob). For
Monero, it's a 160-byte preprocess and 32-byte share _per input_ (again so
concatenated).
The tests in `coordinator/tributary/src/tests/scan_tributary.rs` demonstrated
creating a tributary with one `NewSetInformation`, before reading from it with
another, which should be obviously invalid? `NewSetInformation::genesis` has
been added to make the genesis deterministic and binding to the
`NewSetInformation`, allowing the `ScanTributaryTask` to check its initialized
with values consistent to how the Tributary itself was initialized.

This updates the cited tests accordingly.
One has an unclear story. Others have a pattern of checking a single message
(less than the threshold) causes no events, but doesn't check all messages less
than the threshold cause no events.
Some of these fix behavior which was updated with prior commits, but for which
all the tests/cases had yet to be updated.
@kayabaNerve kayabaNerve merged commit 5d7761a into serai-dex:next Apr 28, 2026
18 of 22 checks passed
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