Repository navigation
Add an underwriter role to the batch operator plugin - #671
Conversation
The relay signed every depot action with the first WIRE K1 signature provider on the node, declaring `<operator>@active`. A node that holds a second WIRE key, such as an underwriter's, would sign with whichever came first. At startup, once synced, pick the one operator-configured WIRE provider, of any key type, whose key alone satisfies the operator's active authority on chain; check that its signatures recover to its key and that the authority may declare every action the relay pushes; and stop the node otherwise. In chain_plugin, `key_alone_satisfies` replaces `provider_can_authorize_active_alone` and asks the chain's own authority checker, so a key reached through a delegated account permission counts and an owner key no longer passes for active; `permission_satisfies_link` checks a permission against an action's `linkauth`.
Bonding a `sysio.synd` request is what releases its syndications. `batch-underwriter-account` (`account` or `account@permission`) enables an underwriter that any depot account can run, with or without the relay; `batch-underwriter-max-exposure` caps what it may have bonded per shadow token. Each poll it confirms each outpost's accepted envelope chain against the outpost's own latest envelope: the same epoch must carry the same bytes (sha256 against `outpcons.winning_checksum`), or the outpost's next envelope must chain to the accepted digest. `sysio.msgch` accepts only envelopes that continue the accepted chain, so a confirmed tip confirms every envelope before it. It bonds each OPEN `sysio.synd` request whose epoch is confirmed, oldest first and in full, within the cap and the account's balance; cranks `sysio.synd`; approves its requests after the challenge window and claims them once approved or ruled VALID; and prunes. It bonds nothing while the `sysio.andon` cord is pulled, after one of its bonds is ruled INVALID until the node is restarted, or on a pass whose table reads failed. A chain it cannot confirm or cannot serve holds back only that chain's requests, and a contradiction is logged as an error with both hashes. The node exempts the accounts it signs as from subjective CPU billing. The decisions are pure functions with unit tests; a contract test pins the on-chain flow the underwriter drives, from a real delivery to the bond coming back.
| if (!is_envelope_request(r) || r.state != request_state::INVALID) continue; | ||
| const auto bond = bonds.find(r.id); | ||
| if (bond != bonds.end() && !bond->second.paid) out.push_back(r.id); |
There was a problem hiding this comment.
[P2] Detect invalid bonds even after their yield is claimed
If an existing bond is ruled INVALID and has accrued yield, anyone can call the permissionless sysio.bond::claim(request_id, underwriter) before the next poll. That settles the forfeited yield and sets the bond's paid flag; it does not return the forfeited principal. This filter then hides the invalid request from note_forfeits, so the node continues bonding instead of halting after the loss.
Detect newly invalid bonds independently of their paid flag. A focused reproduction with an INVALID request and its paid bond returns no forfeiture from this function.
There was a problem hiding this comment.
Fixed in ec19393. A forfeit is now any request the underwriter bonded that is ruled INVALID, whatever paid says. The node remembers the requests behind its bond rows, so a claim followed by a prune of the row is caught too (a_forfeit_is_reported_after_anyone_claims_or_prunes_the_bond).
| case request_state::APPROVED: | ||
| case request_state::VALID: | ||
| if (!in.frozen && ours && !bond->second.paid) out.claims.push_back(r->id); |
There was a problem hiding this comment.
[P2] Retry claims when a paid bond still has outstanding yield
sysio.bond::claim can return the principal and set paid=true while retaining yield.owed_wire when the yield pool cannot cover the full payout. The existing contract test unbacked_yield_survives_pruning_and_never_repays_principal demonstrates this state and the subsequent claim needed to settle it. This condition permanently skips that bond, leaving yield uncollected even after funding becomes available and preventing its rows from being pruned.
Decode the remaining yield into bond_position and schedule claims while either principal or yield remains owed. A focused reproduction with an APPROVED request and a bond containing paid=true, yield.owed_wire=1 currently produces no claim.
There was a problem hiding this comment.
Fixed in ec19393. bond_position decodes yield.owed_wire, and an APPROVED or VALID bond that is paid with yield still owed is claimed again each poll until the yield is paid (owed_yield_is_claimed_after_the_principal). Forfeited bonds are still never claimed.
valthon
left a comment
There was a problem hiding this comment.
The daemon should only underwrite statements it has explicitly verified to be true.
Currently, verification records a confirmed epoch for each outpost, and the planner accepts requests at or before that epoch. Although it decodes the statement’s digest, it never checks that digest against the verified evidence. This relies on sysio.synd constructing the correct statement.
Can we make the verification result identify the actual statements it proves—binding the chain, epoch, and envelope digest—and require that evidence before scheduling accept? Verification through a chained tip is fine, provided the proof is explicitly tied to the statement being underwritten.
Please add a negative test showing that a request with the right chain and epoch but an unverified or mismatched digest is not underwritten.
The hex constructor takes a `std::string_view`, so a caller holding a view no longer copies it into a string first. It already parsed through `fc::from_hex(std::string_view, ...)`; `std::string` callers convert implicitly.
`push_action` runs on cron threads but read the ABI, the head and the chain id straight from the controller, racing block application, and `read_write::push_transaction` resolved ABIs on the same thread. The transaction is now built in a read window and submitted from the main thread; the signature, which a remote signer can make slow, is still made on the cron thread. The relay and the underwriter share the fix.
…velope Both outposts keep a write-once record of every envelope they emit: Ethereum's `OPP.outboundEnvelopes(epoch)` and Solana's outbound `EnvelopeLog`, each holding the keccak256 of the canonical encoded envelope, which is the protocol's epoch digest. `outpost_client::read_emitted_envelope_digest(epoch)` reads it at finality on both chains and returns nothing when the outpost holds no record for the epoch. The Ethereum view is wired only when the loaded OPP ABI declares it, so an older ABI still serves the relay, and its read accepts exactly one record. Solana clients built for the new `underwriter` role check at boot that the IDL declares the `EnvelopeLog` the reader decodes.
The underwriter confirmed the depot's latest accepted envelope per outpost, then bonded any `sysio.synd` request up to that epoch without checking the statement's digest, relying on `sysio.synd` to build each statement from what `sysio.msgch` accepted. It now reads, for each OPEN request, the outpost's own record of the envelope it emitted for the statement's epoch, and bonds only when that record carries the statement's digest. A statement whose digest differs is never bonded and is logged as an error; one with no record yet waits. The depot-tip comparison is gone. A forfeit is detected on any request the underwriter bonded that is ruled INVALID, even after anyone claims the forfeited bond's yield (which marks it paid) or prunes its row; the bonded requests are known from their bond rows, since an accept this node took can still fail where it lands. A claim that returned the principal but left yield owed is claimed again until the yield is paid. A row behind one of its bonds that does not decode stops the pass, so no bond drops out of the exposure cap. It reads requests from the oldest one it still needs, cranks `sysio.synd` every pass only while a request is in play or an outcome is unacknowledged, judges challenge windows by chain time, and stops pushing when its key no longer satisfies its permission. It no longer spaces its own logs or pushes, nor the relay's `sysio.chains` read warning: each condition is logged on every poll it holds, leaving deduplication to log tooling. Amounts are `chain::asset` in their token's symbol (from `sysio.liq`'s stat, or WIRE), so the cap and balance checks are symbol-checked; a bond takes its request's symbol when decoded, so a bond whose request row did not decode stops the pass there. Request ids are `request_id_t`, durations throughout the plugin are `fc::microseconds` (milliseconds only where an option or the cron schedule takes them), outpost clients are keyed by their chain's slug, and ordinary variables name their types.
|
Changed in ec19393. Verification no longer goes through the depot. For each OPEN request the underwriter reads, at finality, the outpost's own write-once record of the envelope it emitted for the statement's epoch (Ethereum |
| FC_ASSERT(info->owner == _program_id, | ||
| "outpost_solana_client[{}]: the outbound envelope log is owned by {} instead of program {}", to_string(), | ||
| info->owner.to_string(fc::yield_function_t{}), _program_id.to_string(fc::yield_function_t{})); | ||
| return outpost_solana_client_detail::decode_envelope_log_digest(*_program_client, info->data, epoch_index); |
There was a problem hiding this comment.
[P2] Keep emitted records for the full verification window
This lookup searches only the current Solana EnvelopeLog, but the current wire-solana origin/next program hard-caps that log at 10 records (opp_states.rs:116, with eviction before age pruning at lines 864-872), even though the configured retention is 128 epochs. Its append_caps_at_ten_dropping_oldest test confirms that emitting record 11 removes epoch 1. If an OPEN request waits while ten newer envelopes are emitted, this reader returns no digest forever; because syndication processes a pair oldest-first, that request also blocks later automatic progress for the pair. Raise/migrate the on-chain cap to cover the retention window, or add a historical proof/recovery path and a cross-repository test.
There was a problem hiding this comment.
Confirmed: MAX_TRACKED_ENVELOPES = 10 evicts the oldest record before the 128-epoch retention applies, while Ethereum keeps 200 epochs. For launch we are documenting this rather than adding state: the README now says a Solana request must be verified within about 10 epochs, after which sysio rules it. The underwriter verifies and bonds a request within a poll of its issue, so this applies only when one waits that long (the node down, a cap, the balance, a pulled cord).
| try { | ||
| const std::optional<std::vector<outpost_descriptor>> active = read_active_outposts(); | ||
| if (!active) return; | ||
| drop_stale(underwriter_clients, *active, [](fc::slug_name chain_code, std::string_view why) { |
There was a problem hiding this comment.
[P2] Preserve verification for requests from the previous deployment
When setoutpost changes an address, drop_stale discards the old client and all outstanding requests are subsequently checked only against the new deployment. The bonded statement contains chain, epoch, digest, and token, but no deployment identity, and sysio.chains::setoutpost permits replacing an active deployment without first draining unresolved requests. A valid OPEN request from the old deployment will therefore read as missing or contradictory on the new one and can stall its oldest-first pair until manual adjudication. Gate redeployment on draining those requests, enforce record migration, or retain versioned readers keyed by deployment.
There was a problem hiding this comment.
Confirmed. Replacing an active outpost deployment is a rare governance step, so we are documenting the procedure rather than keeping versioned readers: before setoutpost replaces a deployment, let that chain's OPEN requests settle. It is in the README.
| std::optional<std::map<uw::request_id_t, uw::bond_position>> | ||
| read_own_bonds(const std::vector<uw::request>& requests) { | ||
| const std::optional<fc::variants> rows = | ||
| read_all_rows(uw::bond::account, uw::bond::account, uw::bond::table_bonds); |
There was a problem hiding this comment.
[P2] Avoid scanning every account's bonds on each poll
read_all_rows fetches and ABI-decodes the entire global sysio.bond::bonds table every 15 seconds, then filters locally for this underwriter. The table has no underwriter index, and this all_rows path deliberately disables the row limit and read deadline, so unrelated accounts can grow the work and memory for every underwriter node without bound while holding a controller read window. Add an underwriter secondary index or maintain a bounded, paginated cursor/cache instead of doing a global full-table read per pass.
There was a problem hiding this comment.
Changed in cd148fb: the bonds read now starts at the scan window, like the requests read. The table is keyed by request id first, and the window holds every request the underwriter has a bond on, so the read covers only requests still in play instead of the whole table. No contract index needed.
There was a problem hiding this comment.
[P2] The lower bound still leaves the suffix unbounded
The bound skips rows before from, but the primary key is (request_id, underwriter), so all_rows still copies and ABI-decodes every account's bond at every later request ID. A single OPEN envelope can pin underwriter_scan_from indefinitely (including the documented case where its outpost record has expired) while later global bond rows keep accumulating; all_rows also ignores limits and deadlines. Each 15-second pass can therefore grow without bound and monopolize a controller read window. Query an underwriter index or exact (request_id, underwriter) keys, or use bounded pagination/state.
There was a problem hiding this comment.
Agreed on the mechanism: both reads walk every row from the cursor on, and all_rows has no limit or deadline. Leaving it for launch. The cursor is the oldest envelope request still in play, so the window is about one challenge window of requests: roughly 30 per pair, so about 70 request rows and 70 bond rows per pass. It grows only while a pair is stuck on a request sysio has not ruled, or while someone pays RAM for requests inside the window, and the cost is a slower pass on the underwriter node, never a wrong bond. The bounded form is to find requests through sysio.synd::envelopes and read sysio.bond by exact key; that is a follow-up for when third parties underwrite.
| void underwriter_prune(const std::map<fc::slug_name, liq_token>& tokens) { | ||
| underwriter_push(uw::bond::account, uw::bond::action_prune, | ||
| fc::mutable_variant_object() | ||
| (uw::bond::field::from_id, 0) |
There was a problem hiding this comment.
[P2] Advance pruning past skipped requests
Every housekeeping pass restarts sysio.bond::prune at request 0. The contract increments its limit counter only for a request it deletes or one with paid bond rows; OPEN/HELD, retained, unresolved-token, and unpaid terminal requests consume no budget. A sufficiently large early unprunable prefix is therefore rescanned without bound on every ten-minute transaction, eventually exhausting CPU before later paid rows can be reached. Persist and wrap a scan cursor, and/or change the contract limit to count examined requests as well as mutations.
There was a problem hiding this comment.
Measured on master's sysio.bond: a prune(0, 64) walk reaches the 150 ms transaction limit at about 7,000 ruled requests still inside their 7-day retention on JIT (about 15,000 on OC; Debug build). sysio.synd issues at most one request per token per active epoch, about 600 to 1,700 per token across that retention, so launch volume leaves roughly 2 to 5 times headroom. Bounding the walk needs the contract to count the requests it examines; that change is deferred and tracked for when tokens are added or epochs shorten.
| /// says `sysio.synd` has an envelope request in play or an outcome to acknowledge. | ||
| void push_plan(const uw::plan& plan, bool synd_busy, fc::time_point now) { | ||
| for (const uw::accept_action& accept : plan.accepts) { | ||
| // Accepted by this node only: it can still fail where it lands, so the bond is known from its row. |
There was a problem hiding this comment.
[P2] Remember a landed accept before its row can disappear
A successful submission is not added to underwriter_bonded; the ID is remembered only if a later irreversible-state poll observes its bond row. If the accept lands, then the system rules the request INVALID and a caller invokes the permissionless claim and prune actions before that poll, the paid bond row disappears while the retained request remains. This node never learns that it owned the forfeited bond and continues bonding despite the promised halt-on-loss behavior. Track submitted accepts as pending and reconcile them with irreversible transaction evidence, or retain the underwriter association on chain through the request-retention period; add a test for this ordering.
There was a problem hiding this comment.
Leaving this as is. Missing the forfeit needs all of this inside one poll gap after our accept lands (about 15 seconds of irreversible state): sysio rules the request INVALID, a yield distribution lets the forfeited bond earn something, a claim marks it paid, and a prune erases it. A ruling normally follows a challenge and a hold. The worst case is a halt one forfeit late: balances and the cap come from chain state, so they stay right, and the next forfeit whose row survives a poll stops bonding. Counting a pushed accept as ours instead would halt a healthy node whenever an accept that lost a race to another underwriter was for a request later ruled INVALID.
Each pass read the whole `sysio.bond::bonds` table, every underwriter's bonds, in one `all_rows` walk. The table is keyed by request id first, and the scan window holds every request the underwriter has a bond on, so the read now starts at the window, as the requests read does. The README now says Solana's outbound log keeps only its last 10 records, so a Solana request must be verified within about 10 epochs, and that a `setoutpost` redeploy should wait until the chain's OPEN requests settle.
| the outpost can no longer be verified and waits until `sysio` rules it. Ethereum keeps | ||
| 200 epochs by default, but Solana keeps only its last 10 records, so a Solana request | ||
| must be verified within about 10 epochs. Before `sysio.chains::setoutpost` replaces an | ||
| active outpost's deployment, let that chain's OPEN requests settle: the underwriter |
There was a problem hiding this comment.
[P2] Drain pre-request envelopes before redeploying
Waiting for OPEN bond requests is insufficient because an accepted sysio.synd::envelopes row can still be OPEN or WAITING with request_id == 0. After setoutpost, the queue can issue that row's request with the old deployment's digest, while the underwriter has already rebuilt its client for the new deployment; it can no longer verify the request, and the pair stalls until manual adjudication. Require the procedure to clear the chain's pre-request OPEN/WAITING envelope rows too, or retain/version the old deployment reader.
There was a problem hiding this comment.
Right: an envelope accepted before setoutpost can still be OPEN or WAITING with no request, and its request then states the old deployment's digest. Fixed in 077c47f: the README now says the chain's sysio.synd envelopes must drain first, those still waiting for a request included.
There was a problem hiding this comment.
[P2] Drain the ingress pipeline before redeploying
sysio.synd can be empty while an old-deployment envelope is still finalizing remotely, waiting for sysio.msgch quorum, or under dispute. Its sysio.synd row is created only when consensus later dispatches the attestations. Because setoutpost does not fence old deliveries and relay jobs switch only on refresh, this procedure can drain sysio.synd, redeploy, then have an old digest materialize that the new client cannot verify. Quiesce the old deployment, wait for remote finality and all pending sysio.msgch delivery/consensus/dispute work, and only then drain sysio.synd; or retain/version the old reader.
There was a problem hiding this comment.
Is active redeployment intended to be prohibited for launch?
If it is prohibited, the smallest safe pre-launch resolution is to enforce that in setoutpost: reject changing the deployment of an already-active chain, and document live redeployment as unsupported. The current runbook instruction alone still leaves the unsafe transition callable.
If active redeployment is supported, please version deployments by epoch instead of overwriting the address in place: schedule a cutover for a future epoch, have the relay and underwriter select the deployment for the request's epoch_index, and retain old deployment metadata/readers until requests from its epoch range are terminal and pruned. Since the statement already includes epoch_index, allowing only one deployment per epoch should avoid a statement-format change. If same-epoch emergency replacement must be supported, carry an explicit deployment generation through delivery, consensus, syndication, and the signed statement.
There was a problem hiding this comment.
Not prohibited. Replacing an active outpost's deployment stays a privileged governance action for an emergency, run with the drain the README describes. The underwriter fails closed across a redeploy: a statement from the old deployment is never bonded, its request waits, and sysio rules it with sysio.bond::rslvvalid or rslvinvalid.
| std::optional<std::map<uw::request_id_t, uw::bond_position>> | ||
| read_own_bonds(uw::request_id_t from, const std::vector<uw::request>& requests) { | ||
| const std::optional<fc::variants> rows = | ||
| read_all_rows(uw::bond::account, uw::bond::account, uw::bond::table_bonds, |
There was a problem hiding this comment.
[P2] Keep discovering bonds created below the cursor
next_scan_start advances past an OPEN request when it is not a sysio.synd envelope and no bond for it has been observed yet. The configured account can later call accept on that still-OPEN request through another permission or client; because this read starts at the newer cursor, the new bond is never discovered. It then never counts toward batch-underwriter-max-exposure, so this node may bond beyond the configured cap. Read through an underwriter index (which also bounds the global scan), or enforce and validate exclusive accept activity for this account.
There was a problem hiding this comment.
Correct, for a request that is not a sysio.synd envelope request: the node never bonds those, so a bond made on one by hand after the cursor has passed it is not counted. Keeping every OPEN request in the window would let anyone pin the cursor with one unbonded request, and a periodic read from 0 is the full scan again, so 077c47f documents it instead: one underwriter node per account, and no bonding from that account by other means, since a cap counts only the bonds its node makes. The account's sysio.liq balance still limits what it can bond.
…counts An envelope sysio.synd accepted before sysio.chains::setoutpost can still be waiting for its request, which then states the old deployment's digest. The README named only OPEN requests; it now names the chain's sysio.synd envelopes, those still waiting for a request included. A cap counts only the bonds the node makes: a bond made by hand from the underwriter's account, on a request the node's scan has already passed, is not seen. The README now says not to bond from that account by other means.
| out.claims.push_back(r->id); | ||
| } | ||
| break; | ||
| case request_state::INVALID: |
There was a problem hiding this comment.
[P2] Settle accrued yield before leaving INVALID
A forfeited shadow bond can still owe the yield it accrued before resolved_index. sysio.bond::claim(request_id, underwriter) settles that yield into sysio.liq without returning the forfeited principal and marks the bond paid; fully_paid otherwise prevents pruning it. This branch never schedules that claim, so without an unrelated external caller the request and bond can persist forever and pin underwriter_scan_from. Queue and retry the settlement claim for our INVALID bonds while continuing to treat the principal as forfeited. Do not gate the first attempt only on owed_wire, because that amount is computed when claim settles the yield index.
There was a problem hiding this comment.
Fixed in 8567999. Every 10 minutes, with its prunes, the underwriter now pushes sysio.bond::claim for its INVALID bonds that are unpaid or still owed WIRE; the principal stays forfeited, and the attempt is not gated on owed_wire. It rides the housekeeping rather than every pass because a forfeited bond that earned nothing has nothing to claim, so that claim is refused until sysio.bond prunes the request 7 days after the ruling.
…ping A bond ruled INVALID was never claimed. If it earned yield before the ruling, sysio.bond does not prune its request until that yield is claimed, so the rows stayed and the underwriter's scan stayed pinned at the request. Every 10 minutes, with its prunes, the underwriter now pushes sysio.bond::claim for its INVALID bonds that are unpaid or still owed WIRE. The claim returns no principal: it pays what the bond earned into sysio.liq and marks the row paid. It rides the housekeeping rather than every pass because a bond that earned nothing has nothing to claim, and the row does not say which it is before a claim settles it. That claim is refused until sysio.bond prunes the request, 7 days after the ruling.
…return dispatch test #670 added min_desyndicate to sysio.synd::setconfig, and every configuration now supplies it. The dispatch test this branch adds, a_bond_releases_the_envelope_and_comes_back_after_the_window, configured its pair without it, so the action was refused once master was merged in. It now passes 1, as #670's own calls with no desyndication bucket do.
huangminghuang
left a comment
There was a problem hiding this comment.
Reviewed current head 63c23b5. No new blocking findings; the setoutpost explanation is accepted. Focused local validation passed, and all completed build/test CI jobs are green.
Summary
Bonding a
sysio.syndrequest is what releases its deposits. This adds an underwriter tobatch_operator_pluginthat any depot account can run, enabled bybatch-underwriter-account, with or without the relay. The team runs one at launch, at zero fees; others can run their own, more so once fees are on.What it does each poll
OPP.outboundEnvelopes, Solana's outboundEnvelopeLog). The underwriter reads that record at finality and bonds only when it carries the statement's digest, so whether a statement is true is checked against the outpost alone (amounts, states and windows still come from the depot's tables). A request can be verified only while its outpost still holds the record: 200 epochs on Ethereum by default, but only the last 10 records on Solana, so a Solana request must be verified within about 10 epochs orsysiorules it. Beforesetoutpostreplaces an active outpost deployment, that chain's OPEN requests should settle.sysio.syndrequest, oldest first, for its whole remainder, within the token's exposure cap (batch-underwriter-max-exposure, counting bonds not yet claimed) and the account's balance.sysio.synd, which releases what bonding allows and issues the next request: every poll while an envelope request is in play or has an outcomesysio.syndhas not acknowledged, otherwise every 5 minutes.sysio.liqso the request can be pruned, and prunessysio.bond/sysio.synd.When it waits
sysio.andoncord is pulledEach is logged on every poll it holds; deduplicating those lines is left to log tooling. Pushes are built in a read window and submitted from the main thread; only the signature is made on the polling thread.
Signing
Each role signs with the one operator-configured WIRE signature provider, of any key type, whose key alone satisfies its authority on chain:
<operator>@activefor the relay, the configured permission for the underwriter. At startup the node also checks that the key's signatures recover to it and that the authority may declare every action the role pushes. The relay previously signed with the first WIRE K1 provider on the node, which would be ambiguous once the underwriter's key is there too. With a dedicated permission, link it tosysio.bond::accept,approve,claim,pruneandsysio.synd::crank,pruneenvonly.The node exempts the accounts it signs as from subjective CPU billing: both roles push on a schedule that loses some pushes, and billed failures would soon have the node refuse every transaction the account signs. An operator node must therefore not expose its transaction push API publicly.
Signers are chosen once, at startup. Each poll checks that each role's key still satisfies its authorization; after a rotation the underwriter stops pushing and the relay logs until the node is restarted with the new key.
Tests
underwriter_tests: statement and row decoding, and the plan: bonding only a statement whose digest the outpost recorded, never one it recorded differently, waiting without a record (or with one for another epoch or chain); remainder, oldest first, the cap across unclaimed bonds and the same pass, balance, missing cap, malformed or mismatched statements, other issuers, approval at the window's end, claims and owed-yield claims, held, blocked and forfeited requests (including a bond since claimed or pruned), the freeze and the halt.read_emitted_envelope_digest_reads_the_outpost_record(Ethereum) anddecode_envelope_log_digest_finds_the_epochs_record/envelope_log_shape_is_checked_for_the_underwriter(Solana): the record read, an absent record, a record for another epoch, malformed or oversized data, an ABI without the view, and a drifted IDL.role_config_tests: option parsing and the one-provider rule.chain_plugin_tests:key_alone_satisfiesagainst single, owner, delegated and weighted authorities, andpermission_satisfies_linkagainst linked, unlinked andsysio.anyactions.sysio_dispatch_tests/a_bond_releases_the_envelope_and_comes_back_after_the_window: a real delivery opens a request whose statement names the accepted digest, which is also the keccak an outpost records for the envelope (the canonical oracle); one account bonds it, a crank releases the deposit, approval is refused until the window passes, and the claim returns the whole bond.Related PRs: Wire-Network/wire-tools-ts#120 runs
flow-syndication-underwritingagainst the underwriter.