From 48d3db1da08933efbecbeb285333b64c49cedfd6 Mon Sep 17 00:00:00 2001 From: Ben Marx Date: Wed, 9 Sep 2026 15:25:14 -0700 Subject: [PATCH] solana: pin builder-stake's bytes, for both networks Neither checksum file listed `builder-stake`, and `shasum -c` checks only the files it names, so `make verify-checksums` passed on every change while pinning nothing about this program. The other two programs were covered from the day they landed; this one was added and never added to the list. That is not theoretical. The bytes first deployed to Solana devnet came from a local toolchain and were 9kb larger than the reproducible build, and nothing would have said so. Devnet now runs the artifact: its first 184768 bytes match this checksum exactly, and the rest is the zero padding an upgrade leaves behind when it does not shrink the data account. Verified by flipping one byte of the artifact and watching the check fail. --- CHANGELOG.md | 1 + solana/programs/sha256sums_development.txt | 1 + solana/programs/sha256sums_mainnet_beta.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42fc442f21..ba94abf95e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to this project will be documented in this file. - `sdk/shreds/go` carries the feed subscription program's `FeedDistribution` account: how much USDC one feed collected for one calendar month. The program is a second program alongside shred subscription and had nothing in this SDK, so each consumer decoded the account at fixed byte offsets itself, lake included. The account is a bytemuck Pod read here field by field, which agrees with the Pod bytes only because the field order leaves no interior padding; `TestStructSizes` pins the 120-byte total and a new test pins every field against a real mainnet account. `Client` is built around one program ID and so gains no fetch method, and `DeserializeFeedDistribution` is exported for a caller that makes its own `getProgramAccounts` call. `make sdk-test` never ran `./sdk/shreds/go/...`, so this package's layout pins have never run in CI; it runs them now. (#4216) - The TypeScript and Python `Feed` deserializers read the RFC-28 tail and synthesize `Active` for an account that carries no status byte, matching the Rust program. New `feed_legacy` fixture covers that path alongside the updated `feed` fixture. - Solana programs (`solana/`) + - `builder-stake` is covered by the reproducible-build checksums, for both networks. It was absent from both files since it was added, and `shasum -c` checks only the files it names, so the verification passed while pinning nothing about this program's bytes. - `builder-stake` carries its own instruction builders in `instruction::builders`, moved out of the test harness now that a caller outside the crate needs them. Each one fixes the account order the processor expects, so a change there has one place to update rather than one per caller. - `builder-stake` exposes its `processor` module and `try_process_instruction` under the existing `entrypoint` feature, so a test in another crate can load the program natively through `processor!` rather than building it to BPF first. `doublezero-serviceability` already exposes its own for the same reason. The crate's own tests still run against the `.so`, and nothing is exposed to a consumer that does not ask for the feature. - `builder-stake` holds a bond for six months and returns the excess after. The first bond starts the hold and a later one does not restart it, so a repricing that forces a top-up cannot push a builder's withdrawal date out. `Withdraw` returns anything above the stake's requirement to a token account the builder names, and refuses both before the hold elapses and below the requirement, which is what stops a builder walking its bond out from under a live feed. The hold is 180 days rather than calendar months, because a month has no fixed length and the alternative is calendar arithmetic onchain to move a one-off boundary by at most three days. A stake that has never been bonded has no hold, and a zero expiry means the hold has not started rather than that it ended in 1970. Every instruction taking a stake checks the account is at the address its own fields derive, which the zero-copy reader does not do. A `SetHoldExpiry` instruction lets a devnet demo show a withdrawal without waiting: it exists in every build and refuses outside a `development` one, rather than sitting behind a `#[cfg]` that would give the two binaries different instruction encodings for the same bytes. diff --git a/solana/programs/sha256sums_development.txt b/solana/programs/sha256sums_development.txt index 445a301b50..b89f45a6c5 100644 --- a/solana/programs/sha256sums_development.txt +++ b/solana/programs/sha256sums_development.txt @@ -1,2 +1,3 @@ +98faedb2f761d02a2e1577c7c940baa48129f93c59cf63f3bc7a148346edcc1e artifacts-development/doublezero_builder_stake.so ebfa651fc897ab8b5f9d4aa135fdd9e88db94dea08803e233ddb367e9314307c artifacts-development/doublezero_passport.so ec0407be6c5fa00ced5963d7d03a7a8e0f4e75fed6330261b318997112e8cb8c artifacts-development/doublezero_revenue_distribution.so diff --git a/solana/programs/sha256sums_mainnet_beta.txt b/solana/programs/sha256sums_mainnet_beta.txt index 1ba1dd93ef..460182d919 100644 --- a/solana/programs/sha256sums_mainnet_beta.txt +++ b/solana/programs/sha256sums_mainnet_beta.txt @@ -1,2 +1,3 @@ +84d63121129a8a6b85fb4b9cf4f6409e25c1bf812879e76037afc3490453b06e artifacts-mainnet-beta/doublezero_builder_stake.so d7c9e08b0c50970a9ffdaca384969758ec974a4768c98208f83c0f32de3dd016 artifacts-mainnet-beta/doublezero_passport.so 99d4b7fd904adcd0ef028cbdc99f6b1e693d8f8f4121b4d75fe101a6fbde5c1a artifacts-mainnet-beta/doublezero_revenue_distribution.so