Skip to content

chore: sync upstream solana-foundation/kora - #2

Open
patrislav wants to merge 51 commits into
mainfrom
sync/upstream-main
Open

patrislav wants to merge 51 commits into
mainfrom
sync/upstream-main

Conversation

@patrislav

Copy link
Copy Markdown
Member

Merges solana-foundation/kora:main (up to cbef22d) into our fork's main.

Resolved Cargo.lock / fuzz/Cargo.lock conflicts by taking upstream's resolution and reapplying our eMudhra CA bump (rustls-webpki 0.103.16, aws-lc-rs 1.18.2, aws-lc-sys 0.46.0) on top.

🤖 Generated with Claude Code

dev-jodee and others added 30 commits August 20, 2026 11:35
…#647)

The aws-sdk crate tree pulled in by the lockfile refresh in solana-foundation#627 declares
rust-version 1.94.1, so `cargo chef cook` aborted on the 1.91 builder image
and the edge Docker publish has failed on every push since. Max MSRV across
the dependency graph is 1.94.1; 1.97 is the latest stable with a bookworm
tag, which the runtime base still requires.

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
The plugin existed only to auto-provision a repomix MCP server for a
single slash command. A shallow clone plus grep covers the same ground,
so the marketplace and plugin manifests are no longer needed.

Fixes two bugs the old command carried. It enumerated upstream backends
by `from_*` method, which also matches key-format helpers such as
`from_bytes` and would scaffold them as phantom signers; it now
enumerates by `*SignerConfig` struct. It also hardcoded the existing
variants as Memory/Turnkey/Privy/Vault when there are twelve; the list
is now derived from the enum at runtime.

Scaffolding templates match the current adapter shape, and cover the
http_config arm in signer_validator.rs that the old command missed.

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
…ion#649)

Covers all 14 [validation.fee_payer_policy.spl_token] flags with the existing
DrainRole harness, so the SPL Token policy matrix is machine-checked rather
than relying on hand-written one-flag-at-a-time cases.

One role per gated slot, matching what the validator actually reads: owner for
transfer, burn, close, approve, revoke, initialize-account, withdraw-excess-
lamports and unwrap-lamports; mint authority for mint-to and initialize-mint;
freeze authority for freeze and thaw; current authority for set-authority; and
a signer for initialize-multisig.

allowed_programs() returns the SPL Token program id so the parsed instructions
route to the spl_token policy tree rather than token_2022.
…olana-foundation#651)

Covers all 10 [validation.fee_payer_policy.bpf_loader_upgradeable] flags with
the existing DrainRole harness, so the loader-v3 policy matrix is machine-checked
rather than relying on hand-written one-flag-at-a-time cases.

Fourteen roles for ten flags. DeployWithMaxDataLen, SetAuthority,
SetAuthorityChecked and ExtendProgramChecked each gate two slots, so each gets a
role per slot.

Close names the actor as recipient as well, because the unconditional drainage
guard rejects a fee-payer authority paired with a foreign recipient regardless of
the flag, which would otherwise fail the "flag on must pass" branch for an
unrelated reason. Upgrade only places the actor in the upgrade authority slot,
since spill is a lamport sink and deliberately ungated.

create_buffer and deploy_with_max_program_len each return the funding
create_account alongside the loader instruction; only the latter is under test.
…ion#650)

Covers all 7 [validation.fee_payer_policy.loader_v4] flags with the existing
DrainRole harness, so the loader-v4 policy matrix is machine-checked rather
than relying on hand-written one-flag-at-a-time cases.

Nine roles for seven flags: SetProgramLength and TransferAuthority each gate
two slots, so they get one role per slot. SetProgramLength's authority role
names the actor as recipient too, because the unconditional drainage guard
rejects a fee-payer authority paired with a foreign recipient regardless of
the flag, which would otherwise fail the "flag on must pass" branch for an
unrelated reason.
…foundation#654)

* fix(transaction): reject v1 transactions that cannot execute

A v1 message carries its resource limits in the transaction config, and a limit
the config leaves unset is zero rather than the runtime default legacy and v0
transactions fall back to. Zero compute units or zero loadable account bytes
cannot run any instruction, and the fee payer is charged for the failure anyway,
so decoding now rejects such a transaction before Kora simulates, signs, or pays
for it. A v1 message that references the ComputeBudget program logs a warning,
since the runtime ignores those instructions.

Appending a Lighthouse assertion now sanitizes the resulting message instead of
only checking its byte size. A v1 message accepts at most 64 addresses and 64
instructions and rejects duplicate addresses, so an appended account could
previously turn a valid transaction into one the network rejects.

Refs: TOO-527

* test(transaction): use fixed key material in v1 fixtures

The v1 resource-limit and Lighthouse address-limit tests built their keys,
hashes, and keypairs at runtime, so a failure could not be reproduced from the
test alone. None of them depend on the key values, so fixed byte arrays serve
just as well.

* refactor(tests): move v1 transaction fixtures to shared test utils

The v1 fixtures used by the decode and Lighthouse tests now live alongside the
other transaction mocks, so other modules can build the same transactions.

The warning about ComputeBudget instructions in a v1 transaction moves to the
transaction validator, next to the other instruction-level checks, and reads the
resolved instruction list instead of the message's account keys.
…ion#638)

* feat: add max_priority_fee_lamports validation config

Operators could block priority fees for legacy/v0 transactions by omitting
the ComputeBudget program from allowed_programs, but v1 transactions carry
the priority fee in the message config rather than an instruction, so the
program allowlist cannot gate it. Add an explicit cap enforced uniformly:
the v1 config field is read directly, legacy/v0 ComputeBudget instructions
are parsed (price times limit, rounded up, with the 1.4M CU maximum assumed
when a price is set without an explicit limit). Unset keeps today's
unlimited behavior; 0 disallows priority fees entirely.


---------

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
…a-foundation#655)

* docs: trim CLAUDE.md to repo-specific gotchas

---------

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
…a-foundation#657)

* docs: trim CLAUDE.md to repo-specific gotchas
* docs: slim skills to guides and push detail into references
* docs: adopt the CONTRIBUTING template

* chore: prune AI comment bloat and fix the openapi docs build
- `sdks/ts/examples/basic-usage.ts` called `transferTransaction`, removed
  from `KoraClient` and deprecated server-side in 2.2.0. Rewrites it on
  `getPaymentInstruction`. The example rotted unnoticed because
  `tsconfig.json` excludes `examples/`, so `tsconfig.examples.json` now
  type-checks it and `pnpm type-check` runs both projects.

---------

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
Tags and branches are mutable refs: an attacker who compromises an
action repo can repoint them at malicious code that runs with our
workflow secrets. Pin every third-party action to an immutable commit
SHA, keeping the version as a trailing comment so the refs stay
readable and dependabot can still bump them.

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
Replaces the hardcoded `allow_origin(Any)` CORS policy with a configurable `cors_allow_origins` field, allowing operators to restrict cross-origin access in production deployments instead of allowing all origins by default.

Note: this is a split-out of solana-foundation#559.
…foundation#664)

* ci: consolidate rust caches and drop unused stable toolchain

---------

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
…ana-foundation#665)

* ci: gate PRs on AI disclosure and flag unreviewed AI attribution

---------

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
…on#668)

Removes the 22 dependencies cargo-machete reports as unused across kora-lib,
kora-cli, tests and the devnet-deploy example, plus the workspace entries
nothing referenced anymore. solana-transaction is a false positive (it exists
to enable the wincode feature) and is listed in the machete ignore metadata.

reqwest loses native-tls: rustls with the platform verifier was already in the
tree, so this drops the second TLS stack (openssl, hyper-tls, tokio-native-tls)
without changing which root store is used.

mockall moves to dev-dependencies. The production PriceSource::Mock oracle is
now a plain PriceOracle impl instead of a mockall expectation object, and the
unused TokenInterface automock is removed. Cargo.lock shrinks from 883 to 847
packages.

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
…ation#671)

rustls 0.23.43 accepts TLS 1.3 handshake messages across encryption
level boundaries (CVSS 5.3). Upgrade to the patched 0.23.45, which also
pulls aws-lc-rs 1.18.1 and rustls-webpki 0.103.15.

Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
…ent failures (solana-foundation#614)

Adds resume capability and automatic buffer cleanup to kora-deploy, persisting local state so deployments can recover from transient failures instead of leaving orphaned on-chain buffers.

- Resume interrupted deployments with `--resume`, guarded by SHA-256 hash verification and chunk bounds checking
- Buffers are closed automatically on failure by default; pass `--no-cleanup-on-failure` to leave the buffer open for a later `--resume`
- Detect already-succeeded deploys after a timeout by checking ProgramData before resubmitting, skipping this check entirely on fresh (non-resume) deploys
- Force buffer cleanup when the initial state save fails, since the buffer can't be recovered any other way in that case
- Refactor deploy() into smaller functions with a shared, parametrized cleanup_buffer! macro
- Add `.kora-deploy-state.json` to `.gitignore` since it contains raw keypair bytes
- Add `share_mode(0)` on the non-Unix save path as a partial mitigation (concurrency guard only, not an ACL restriction full Windows permission support is out of scope here)
- Add unit tests for the state-invariant checks (hash mismatch, chunk-count overflow) via a standalone `check_state_invariants()` helper
- Document the resume/recovery flow and new flags in the README
…n#673)

When metrics and fee-payer balance metrics are both enabled,
`expiry_seconds = 0` reaches `tokio::time::interval(Duration::ZERO)`
in `start_background_tracking`, which panics and kills the spawned
metrics task instead of surfacing a startup error.

Validate the value in `ConfigValidator` and reject zero only while the
balance collector is active, so a zero in an inactive section keeps
loading as before. Add tests for the rejected and the allowed case.
The `unit-test` and `unit-test-ts` recipes prefix their commands with
just's `-` ignore-error marker, so a failing test does not fail
`just unit-test`, `just unit-test-ts`, the default recipe, or `just test`.

Drop the two prefixes so failures propagate to the invoking recipe. The
test commands themselves are unchanged. CI runs `cargo test` directly and
is not affected.
…lana-foundation#667)

* test(rpc): run the rpc integration phase on an embedded surfpool harness

The rpc phase no longer needs the external test_runner: the test binary boots
its own surfnet and kora node in-process, so `cargo test -p tests --test rpc`
works on its own.

What this adds:

- KoraHarness, which starts an embedded surfnet via surfpool-sdk, deploys the
  transfer-hook and lighthouse programs, spawns the prebuilt kora binary and
  waits on /liveness. Parameterized by KoraSpec (config, signers, whether to
  initialize payment ATAs), which is the same shape as a test_cases.toml entry,
  so the remaining phases can reuse it.
- Cheatcode-based account seeding, replacing RPC setup for the accounts the rpc
  phase needs: the USDC SPL and Token-2022 mints, the fee-payer-policy mints,
  three lookup tables and SOL balances. No solana-test-validator, no account
  cache to refresh.
- Block production in Clock mode at 400ms, so slots advance without the runner.
Migrates `api_key: Option<String>` to `api_keys: Option<Vec<String>>`, with backward-compatible deserialization for existing single-key configs. Compares keys via fixed-length SHA-256 hashes with constant-time comparison across all configured keys, avoiding any length-dependent timing signal.

Note: this is a split-out of solana-foundation#559.

- [x] AI tooling was used
- [ ] No AI tooling was used

Tool and extent: Gemini used for coding diffs based on my specifications; all changes were reviewed and verified by me before committing.
Bumps [rand](https://github.com/rust-random/rand) from 0.10.2 to 0.10.3.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](rust-random/rand@0.10.2...0.10.3)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.10.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…foundation#686)

* feat(deploy): send deployer and reaper transactions as tx v1

Deploy, upgrade and close transactions from kora-deploy, and the reaper's
close transaction, are now v1 messages. Their compute unit and loaded
accounts data size limits are set explicitly to the legacy defaults, since
v1 budgets zero for an unset limit.

The 4096-byte v1 size limit lets each buffer write carry 3800 bytes instead
of 900. The deploy state file now records its chunk size, and state files
written before this change resume at 900 bytes so their written chunk
offsets stay valid.
…tion#685)

Delete about 55 test mock and builder helpers with no callers, the
empty crates/lib/src/lib.rs, and the TS utils/transaction.ts that the
package exports field never exposed. Replace the get_value_by_priority
wrapper with AuthConfig::resolved_hmac_secret and point its tests at
resolve_secret.

Drop tests that only exercise derives, a byte-identical Token-2022
duplicate, a sig_verify copy the mock ignores, the Jito "mainnet" test
that hit testnet, and TS tests that asserted their own fixtures.

No public API change.
…on#689)

* ci: accept full issue URLs in the linked-issue check
solana-foundation#690)

The upgradeable loader rejects instruction data over PACKET_DATA_SIZE
regardless of transaction version, so the 3800-byte v1 write chunks failed
on devnet with "invalid instruction data" before a single chunk landed.

Chunks are now PACKET_DATA_SIZE minus the 16-byte Write header, and each
v1 transaction carries three writes, which keeps deploys at roughly a
quarter of the legacy transaction count. Deploy, upgrade and the smoke
harness share write_instruction_batches, which resumes at the recorded
chunk offset. A resume state file whose chunk_size is zero or above the
loader limit is rejected with an error instead of failing every write.
dev-jodee and others added 21 commits September 28, 2026 16:30
solana-foundation#693)

The inflated max_data_len probe hardcoded 1.7 MB, sized for rent of about
11.8 SOL. Devnet and mainnet now charge 8.64 SOL for that ProgramData, so
the probe fit under the 10 SOL max_allowed_lamports and Kora signed it as
the cap intends, which the suite reported as a capital-lock finding.

The probe now sizes max_data_len from the cluster's rent to 1.5x the cap.
…on#695)

Bundle execution is now serialized against concurrent writes upstream:
solana-foundation/surfpool#817 moved bundle execution onto the block-production
runloop, and #824 routed request_airdrop through the same lane instead of
writing the live VM inline. Both landed after the 1.6.0 release, so this pins a
commit rather than a version.
pnpm 11.13.0 is deprecated on npm as a broken release, so pnpm's
version switching refuses to run it.
…nter (solana-foundation#669)

Fixes solana-foundation#702

Adds a `RejectionReason` enum plumbed through every 401 path, and a `http_rejections_total` Prometheus counter labeled by method and reason.

Note: this is a split-out of solana-foundation#559
…tion (solana-foundation#675)

* feat(rpc): report whether signTransaction appended a Lighthouse assertion

With Lighthouse enabled, signTransaction appends a fee payer balance
assertion to the message before signing. The response did not say so.
A client that co-signs, or that checks the returned message against the
one it sent, had to diff the bytes to find out.

SignTransactionResponse now carries `lighthouse_assertion_added`. It is
true only when the assertion was appended; false when Lighthouse is
disabled, when Kora sends the transaction itself, and when the append was
skipped for size, which config alone cannot tell a client.

No change to validation, fee payer policy, fees, or signer handling.

* docs(rpc): describe lighthouse_assertion_added narrowly

The field reports whether a Lighthouse assertion was appended, nothing
more. signTransaction also replaces the recent blockhash for a request
with an empty signature array while the field stays false, so it must not
be read as proof that the returned message matches the request.
…by concern and general cleanup (solana-foundation#708)

* refactor(transaction): shrink the UiExtensionType mapping

Import both enums under short names so each variant maps on one line.
The match stays exhaustive and PermissionedBurn still rejects.


* refactor(transaction): derive SPL map keys from the parsed data

push_parsed_spl_instruction took the map key and the data separately,
so nothing stopped a key being paired with the wrong variant. The key is
now derived from the data variant.


* refactor(transaction): share instruction arms between token programs

spl-token and Token-2022 parsing repeated the same twenty instruction
arms. One macro now emits them against each program's own
TokenInstruction, and each program adds only its extra arms.


* refactor(transaction): trim System instruction reconstruction

Each arm now returns the instruction and its account indexes, and the
bincode serialization happens once.


* refactor(transaction): trim SPL instruction reconstruction

Field lookups go through small helpers, and a local pack! macro replaces
the spl-token versus Token-2022 branch repeated in every arm.


* refactor(transaction): compact the System, ALT and loader parsers

Index constants use per-arm aliases, optional accounts use
accounts.get, and map keys are derived from the data variant as for
SPL. The System-only parse macro is no longer needed.


* docs(transaction): correct the SplTokenUnknownExtension doc

It claimed the validator reads the recorded accounts. Nothing does;
fee-payer checks for these instructions run through
Token2022SecurityParser.


* refactor(transaction): split instruction_util into per-program modules

instruction_util.rs was one 2.7k-line logic block over a 3.4k-line test
module. Each program's parsed enums and parser now live in their own
file under transaction/instruction_util/, UI-instruction reconstruction
moves to reconstruct.rs, and the tests move unchanged to tests.rs.

This is a move only: no logic changes, and every public item keeps its
kora_lib::transaction path. Three private reconstruction helpers and the
batch discriminator become pub(super) so the sibling tests module can
reach them.


* refactor(validator): share one pubkey set parser

TransactionValidator::new parsed four config lists with four
hand-written map/collect chains, and BundleValidator had a private
parse_pubkey_set doing the same. Both now use one parse_pubkey_set in
validator/mod.rs.

The error for a malformed config pubkey now reads "Invalid public key
`<key>` in config: <reason>" for every list. Config validation already
rejects these at startup, so this only surfaces if the validator is
built from an unvalidated config.


* refactor(validator): check Assign and CreateAccount owners in one place

The Assign owner check was an inline copy of
validate_create_account_owner with a different message prefix. Both now
call validate_owner_program with the instruction name, which keeps the
two messages byte for byte.


* refactor(validator): drop the unused price source field

TransactionValidator stored config.price_source as _price_source and
never read it. The tests that imported PriceSource through the parent
module now import it directly.


* refactor(plugin): reuse from_config when validating plugin config

validate_config repeated from_config's dedupe loop and its plugin type
match. It now iterates the plugins from_config builds, so adding a
plugin type means touching one match instead of two.


* refactor(validator): replace the fee payer macros with deny_fee_payer

macros.rs had seven macros (validate_system, validate_spl,
validate_spl_multisig, validate_alt, validate_bpf_loader_upgradeable,
validate_loader_v4, validate_token2022) that all expanded to the same
loop: for each parsed instruction of a type, reject it when the fee
payer is used and the policy flag is off. The ALT and loader ones were
identical apart from the type enum, and validate_system's $extra arm
was never used.

deny_fee_payer! takes the instruction type path, the pattern, the
fee-payer condition, an optional `unless <flag>` and the message:

- `unless token_policy(policy, is_2022).flag` reads the same field from
  token_2022 or spl_token, so a call site can no longer pair
  mismatched SPL and Token-2022 flags;
- with no `unless` the use is always rejected, which now also expresses
  the two drainage guards that were hand-written loops.

fee_payer_signs replaces the repeated "authority or multisig signer"
check. Every error message is byte for byte the same.


* refactor(validator): split fee payer checks into per-program files

validate_fee_payer_usage was a single 430-line function. Its checks now
live in transaction_validator/{system,spl_token,token_2022,alt,
loader_v4,bpf_loader_upgradeable}.rs, one method per program, and
validate_fee_payer_usage calls them in the original order, parsing each
program's instructions exactly where it did before.

token_2022.rs also takes the transfer hook signing check and the
extension security check, which are Token-2022 fee payer checks too.

Apart from the wrappers and imports every line is moved unchanged. The
two "Validate <program> instructions" section comments were dropped
since the file names now say it. CLAUDE.md points at the new files.


* refactor(validator): build the bundle account configs once

The pre- and post-execution account configs were built by two identical
map/collect blocks. They are now built once and cloned.


* refactor(validator): build bundle outflow errors from one closure

validate_fee_payer_lamport_outflow spelled out the same
"Bundle simulation ... for signed transaction index N" error six times.
One closure now formats it. The checks run in the same order and every
message is unchanged.


* refactor(validator): check packed token accounts with one helper

validate_account_type repeated the same "length check, then
unpack_from_slice" block for SPL and Token-2022 mints and token
accounts. check_packed::<T: Pack> does it once. Messages are
unchanged.


* refactor(validator): drop redundant drops in the Redis probe

test_redis_connection dropped conn and pool explicitly just before
returning, which is what leaving scope already does, in the same order.


* refactor(config): trim api keys in one pass


* refactor(config): list enabled method names in one table

iter() reuses the IntoIterator list instead of repeating it.


* refactor(validator): check token 2022 extension names with initialize

validate_token2022_extensions repeated the parsing loops and messages of
Token2022Config::initialize.


* refactor(signer): validate signer env fields in one loop

Each signer type now lists its env fields; one loop checks them with the
same messages and order the twelve per-type functions used.


* refactor(validator): check configured accounts on chain in one loop


* refactor(validator): check program pubkey formats in one loop


* refactor(validator): drop a redundant guard and push loop in config checks


* refactor(validator): split config validation checks into per-topic files

validate_with_result_and_signers now calls one function per contiguous
block, in the original order, from config_validator/{server,transactions,
tokens,fee_payer_policy,onchain}.rs. Move only: messages and their order
are unchanged.


* refactor(token): price spl outflows with the shared lamports helper

calculate_spl_transfers_value_in_lamports repeated the body of
calculate_token_value_in_lamports_from_price inline. Call the helper
instead; the arithmetic, rounding and error messages are the same.


* refactor(token): fetch payment prices and decimals in one helper

calculate_spl_transfers_value_in_lamports and
calculate_payment_lamport_totals each fetched prices, checked staleness
and looked up decimals with the same block. Move it into
fetch_prices_and_decimals. Mints are visited in the same order as before.


* refactor(token): build the full payment extension check on the partial one

validate_token2022_extensions_for_payment repeated the mint and source
checks of validate_token2022_partial_for_ata_creation before checking
the destination. Call the partial check, then check the destination,
with one helper for the per-account extension check. The order of
checks and the error messages are unchanged.


* refactor(fee): sum the estimated fee with get_total_fee_lamports

estimate_transaction_fee repeated the checked sum, clamp and overflow
error that TotalFeeCalculation::get_total_fee_lamports already does.
Build the calculation, then fill in the total with that method.


* refactor(fee): override only the total in estimate_kora_fee

The Fixed strict and Margin arms rebuilt TotalFeeCalculation field by
field to replace the total. Use struct update syntax instead.


* refactor(fee): estimate a resolved transaction's fee via get_estimate_fee

get_estimate_fee_resolved matched on the message version exactly like
get_estimate_fee. Pass the transaction's message to get_estimate_fee.


* refactor(fee): share the lamport flow bookkeeping in fee payer outflow

calculate_fee_payer_outflow repeated the same checked add or subtract
with its overflow log nine times, and the ALT close and token account
close branches were the same rule written twice: count the closed
account's lamports when exactly one of authority and recipient is the
fee payer. Pull these into add_outflow, add_inflow and
add_closed_account_flow. Log lines and errors are unchanged.


* refactor(fee): charge extend program rent while scanning instructions

The ExtendProgram accounting collected byte sizes into a vector inside
a scope, then fetched rent for each in a second loop. Fetch the rent
directly in the scan. The rent requests happen in the same order.


* refactor(transaction): map token 2022 extension data errors in one place

Eleven extension parsers decoded their instruction data and built the
same "Failed to parse Token-2022 ... instruction" error inline. Pass
the decode result to extension_data instead, and import the extension
modules so the data types fit on one line. Messages are unchanged.


* refactor(token): split TokenUtil into per-topic files

Move only. token.rs mixed transfer hook checks, pricing, ATA parsing and
payment verification in one impl. Each topic now has its own file under
token/token/, each holding an impl TokenUtil block. token.rs keeps the
types, mint lookups and the tests. Public paths and signatures are
unchanged; three helpers shared between the new files become
pub(super).


* refactor(fee): move fee payer outflow accounting into its own file

Move only. calculate_fee_payer_outflow and its ATA rent and lamport
flow helpers are about 380 lines on one topic: what a transaction can
take from the fee payer. They now live in fee/fee/outflow.rs as an
impl FeeConfigUtil block. Signatures and visibility are unchanged.


* refactor(transaction): split the token 2022 security parser into files

Move only. token2022_security.rs held about 1,000 lines of parsing in
one impl. The token-metadata and token-group interface parsers move to
token2022_security/interface.rs and the per-extension parsers to
token2022_security/extensions.rs. The types, the parse dispatcher and
the shared field helpers stay in token2022_security.rs. The moved
parsers the dispatcher calls become pub(super); nothing else changes.


* refactor(signer): share fee payer signing between single and bundle paths

sign_transaction and BundleSigner::sign_transaction_for_bundle each
reserved the signer, ran sign_with_retry, reported the outcome to the
signer pool, and wrote the signature into the fee payer slot. Both now
call sign_with_signer_pool and set_signature_at. Log lines and error
messages are unchanged; the operation name still distinguishes them.


* refactor(bundle): fold sign_all_internal into sign_all

sign_all was the only caller and always passed validate_payment = true,
so the flag and the extra function go.


* refactor(rpc): run sign_and_send_bundle through the sign_bundle body

The two handlers repeated the same ~80 lines: bundle checks, processing,
sequential simulation, signing and merging. sign_and_send_bundle now
calls sign_bundle_transactions with its own plugin context and only adds
the Jito send. will_send still follows the method, so Lighthouse is
skipped on signAndSendBundle as before.


* refactor(transaction): drop the unreachable cache misses in get_or_parse

Each get_or_parse_* method filled its cache and then mapped a None it
had just ruled out to a SerializationError. Taking the cached value or
parsing, then inserting, returns the reference directly. Parse errors
still propagate and leave the cache empty.


* refactor(transaction): build resolved transactions through one constructor

from_transaction and from_kora_built_transaction each listed every field
of VersionedTransactionResolved, and from_transaction cloned the account
keys twice to fill a placeholder. Both now pass the keys and outer
instructions to new; inner instructions are still appended after
simulation.


* refactor(plugin): check deploy authority fee payer fields with one helper

Eight loader instruction arms repeated the same "X must be the fee
payer" comparison and error. They now call require_fee_payer with the
field label; every message is unchanged.


* refactor(bundle): parse simulateBundle account lists with one helper

parse_transaction_result had the same match for preExecutionAccounts and
postExecutionAccounts; parse_accounts takes the key and builds the same
error message.


* refactor(bundle): build mock simulation accounts with one helper

JitoMockClient built the pre and post execution account lists with two
copies of the same chain; mock_accounts takes the config list and index.


* refactor: remove repeated blocks in the server, cache, usage limiter and signer pool (solana-foundation#714)

* refactor(rpc): log handler responses through one helper

Each KoraRpc method repeated the same three lines to log and return the
handler result. They now call log_response with the label, and the log
text is unchanged.


* refactor(server): drop the duplicate method name from register_method_if_enabled

Every call passed the same identifier twice, once as the EnabledMethods
field and once as the KoraRpc method. The macro now takes it once and
uses it for both.


* refactor(auth): share the liveness bypass check

ApiKeyAuthService and HmacAuthService each spelled out the same nested
match to let liveness through. Both now call is_liveness_request and
forward the rebuilt request in one line.


* refactor(auth): derive Clone for KeyEntry

The manual impl cloned both fields field by field, which is what the
derive generates.


* refactor(auth): track the matched API key as an Option

is_valid was true exactly when matched_id had been set. The loop still
compares the provided hash against every key, and the last match still
wins.


* refactor(recaptcha): reuse auth_rejection_response for rejected tokens

RecaptchaConfig::validate built the same 401 response with an
AuthFailure extension twice. It now calls the helper the auth layers
already use, made pub(crate) for this.


* refactor(recaptcha): build the forwarded request once

RecaptchaService rebuilt the request on both the unprotected and the
protected path. It now decides up front whether a token is needed
(protected method, or no method found) and forwards a single request.


* refactor(middleware): reject disallowed methods with a plain is_err check

MethodValidationService matched on the result only to do nothing on Ok.


* refactor(cache): look up the Redis pool with one helper

get_account, get_or_fetch_latest_blockhash, get_or_fetch_token_prices
and get_multiple_accounts each checked is_cache_enabled and then
unwrapped CACHE_POOL by hand, get_account with two nested matches.
They now call pool(config) and fall back the same way when it returns
None.


* refactor(cache): map RPC account lookups without nested matches

get_account_from_rpc remaps AccountNotFound in a map_err, and the
multiple-account paths use ? and a zip instead of matching on Ok and
Err by hand. The first missing account is still the one reported.


* refactor(usage): read instruction rules straight from the rule list

UsageTracker kept a precomputed instruction_rule_indices list, then
rebuilt it into a HashSet and a running index to line the batched
instruction counts back up with the rules. check_and_record now filters
the instruction rules in order and takes the next count as it walks the
rules, so the field, the set and the increments vector go.


* refactor(usage): build multisig members once for both token programs

The spl-token and Token-2022 branches of
owner_signed_or_authorized_by_multisig differed only in the owner check
and the Multisig type they unpack. Each branch now unpacks to (m, n,
signers), and the member set and threshold check are written once.


* refactor(usage): propagate the source account lookup error with ?

The match only re-returned the error unchanged.


* refactor(usage): lock the in-memory usage store through one helper

All six InMemoryUsageStore methods repeated the same lock and
"Failed to lock usage store" mapping. They now call lock().


* refactor(signer): track an in-flight recovery probe by its start time alone

HealthState kept probe_in_flight next to probe_started_at, and every
write set or cleared the two together, so probe_in_flight was always
probe_started_at.is_some(). The flag goes, and with it the branch for
"in flight without a start time", which nothing could produce. The
tests that read the flag now check probe_started_at.
…ana-foundation#715)

* build(deps): bump solana-keychain to 2.0.0

BREAKING CHANGE: `type = "crossmint"` in signers.toml is no longer
accepted, and config loading fails with an unknown variant error.
Replace the entry with a supported signer backend.
…r `deploy()` (solana-foundation#710)

Fixes solana-foundation#700 

Mock infra (mockito for Kora HTTP, solana client's native mock RPC transport) + coverage for the untested `deploy()` orchestration paths from solana-foundation#614:

- `load_or_init_state`: fresh init success, forced cleanup on state save failure
- `write_chunks`: happy path, mid chunk failure -> `cleanup_buffer!`
- `finalize_deploy`: `is_already_live` skip, submit success, submit failure -> cleanup
- `validate_state`: `cleanup_buffer!` firing on hash mismatch vs skipped on chunk overflow (`cleanup_on_failure=false`)

Not covered: wallet: `Some(...)` branch in `finalize_deploy` (needs real tx construction, better suited to Surfpool). `deploy()`'s own function and `upgrade()` resume are out of scope - `deploy()` builds its own RpcClient internally so it isn't mock injectable without a refactor.

- [x] AI tooling was used
- [ ] No AI tooling was used

Tool and extent:Gemini used for coding diffs based on my specifications; all changes were reviewed and verified by me before committing.
…ry (solana-foundation#721)

Bumps the cargo group with 1 update in the /fuzz directory: [rustls](https://github.com/rustls/rustls).


Updates `rustls` from 0.23.43 to 0.23.45
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](rustls/rustls@v/0.23.43...v/0.23.45)

---
updated-dependencies:
- dependency-name: rustls
  dependency-version: 0.23.45
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
solana-foundation#722)

The HMAC freshness check used `(now - ts).abs()`, which accepted
timestamps up to `max_timestamp_age` in the future and doubled the
window a captured request stays valid. The subtraction also ran on the
raw client-supplied i64 before the MAC check, so `i64::MIN` overflowed:
a panic in debug builds and a wrapped value that passed the age check in
release builds.

Use `checked_sub`, keep `max_timestamp_age` for past timestamps and
allow only 30s of forward clock skew.
…olana-foundation#728)

TransactionPlugin::validate took &mut VersionedTransactionResolved only so
the get_or_parse_* getters could fill their caches. That also let a plugin
write to the transaction after validate_transaction ran, with the result
reaching the signed message unchecked and the parse caches left stale.

The parse caches are now OnceLock, the getters take &self, and plugins get
&VersionedTransactionResolved. The built-ins no longer clone parsed maps
to release the borrow.
…foundation#723)

The balance collector set signer_balance_lamports to 0 whenever
CacheUtil::get_account failed. solana-rpc-client reports every
get_account failure, including HTTP 500 and transport errors, as
AccountNotFound, so a provider outage looked like a drained fee payer
and paged balance-low alerts.

Fetch with getBalance instead, which returns 0 for a missing account and
an error only when the request itself fails. On failure the gauge keeps
its last value, the new signer_balance_fetch_errors_total counter is
incremented, and the real error is logged through sanitize_error!.

getBalance also bypasses the Redis account cache, which could otherwise
serve a balance up to account_ttl old and hide an outage.

track_all_signer_balances no longer takes the unused Config argument.

Refs: solana-foundation#660
…licy (solana-foundation#729)

SetAuthority only checked the current authority and InitializeMint only checked the mint
authority, so a transaction could hand the fee payer a mint, account or Token-2022 extension
authority (including PermanentDelegate) with every token flag off. Both now also deny the fee
payer in the new_authority and freeze_authority slots, under allow_set_authority and
allow_initialize_mint, matching the loader-v3 SetAuthority check.
…solana-foundation#727)

/verify returned isValid: true whenever Kora would sign the transaction,
and /settle broadcast anything Kora would sign. Kora's signing policy
protects the fee payer but never compares the payment to
paymentRequirements, so underpayments and transfers to the wrong
recipient verified and settled.

Run both endpoints through the @x402/svm exact scheme with a Kora-backed
FacilitatorSvmSigner, so amount, asset, recipient and instruction layout
are checked before Kora signs.
…ana-foundation#724)

Operators can now restrict which transaction message versions the node
accepts via `validation.allowed_transaction_versions`, using the
upstream solana_transaction TransactionVersion: "legacy", 0 and 1. The
default is all three, so existing configs keep their current behavior.

The check runs first in TransactionValidator::validate_transaction,
alongside the other transaction checks, so signTransaction,
signAndSendTransaction and the bundle methods reject a disallowed
version.

Config validation rejects an empty list and any version number other
than 0 or 1. The serde feature of solana-transaction is now enabled
explicitly instead of through feature unification. The field is exposed
through getConfig with the rest of the validation config, and the
OpenAPI spec is regenerated, which also picks up a stale
lighthouse_assertion_added description.

Refs: solana-foundation#637
…drain props (solana-foundation#725)

* fix(validator): make Token-2022 withdraw-excess and unwrap-lamports flags take effect

Bump spl-token-2022-interface to 3.1.1 (already in the lockfile via
solana-account-decoder) and spl-token-metadata-interface to 1.0.1 so
Token-2022 UnwrapLamports can be parsed. UnwrapLamports now goes through
the shared token arms, so the token_2022.allow_unwrap_lamports flag is
consulted.

Drop WithdrawExcessLamports from the Token-2022 unsupported-extension
list. It is gated on the authority by token_2022.allow_withdraw_excess_lamports,
matching the classic SPL path.

The bump makes three new instructions decodable, which used to fail to
parse and were rejected. Keep them fail closed: Batch is rejected by the
security parser, and PermissionedBurnExtension rejects the fee payer in
any account position.

OptionalNonZeroPubkey fields move to solana-nullable MaybeNull.

Fixes solana-foundation#717

* test(validator): add Token-2022 fee-payer drain property coverage

Add a Token-2022 DrainRole covering every gated token_2022 flag,
including Pause and Resume under the freeze and thaw flags, one role per
planted extension authority slot under allow_initialize_extension_authority,
and one role per extension update instruction under
allow_update_extension_authority. Reallocate is an unconditional denial
with no flag, so it has no role.

Fixes solana-foundation#643

* test(validator): cover WithSeed, Checked and multisig-signer fee-payer roles

System gains TransferWithSeed, AssignWithSeed, AllocateWithSeed,
CreateAccountWithSeed (payer, new account and base signer) and
CreateAccountAllowPrefund (funder and new account). SPL Token gains the
Checked forms, InitializeMint2 and InitializeAccount2/3. SPL Token and
Token-2022 gain roles that put the fee payer among the multisig signers
of an ordinary authority, which is the path fee_payer_signs checks.

Fixes solana-foundation#718

* fix(lib): reconstruct multisig authorities and Token-2022 metadata CPIs (solana-foundation#726)

* fix(lib): reconstruct multisig token authorities from parsed CPIs
Fixes solana-foundation#680
Fixes solana-foundation#681

Agave's jsonParsed output emits `authority` (or `owner`, `mintAuthority`,
`freezeAuthority`) only when an SPL Token instruction has no trailing
accounts. Otherwise it emits the `multisig*` variant plus `signers`, which
also covers transfer-hook mints whose extra accounts trail TransferChecked.
Reconstruction only read the single-authority field, so every such CPI
failed with "Missing field 'authority'" and the transaction was refused.

Rebuild the authority followed by the `signers` accounts for every arm
that uses Agave's parse_signers, so the reconstructed instruction matches
the original account layout and the trailing accounts still reach the fee
payer policy as multisig signers.
…, seed corpus (solana-foundation#731)

* test(validator): add fee-payer outflow cap property tests
* test(fuzz): add seed corpus for fuzz targets
* test(fuzz): add coverage-guided fuzz target over the validator
* test(fuzz): add validator vs LiteSVM differential fuzz target
…undation#735)

Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.7.9 to 4.7.10.
- [Release notes](https://github.com/handlebars-lang/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.10/release-notes.md)
- [Commits](handlebars-lang/handlebars.js@v4.7.9...v4.7.10)

---
updated-dependencies:
- dependency-name: handlebars
  dependency-version: 4.7.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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.

8 participants