Commit Graph
248 Commits
Author SHA1 Message Date
jwinterm 370cd4fae7 Contracts: make the invoice proof reader fully fallible
InvoiceProofBin is a normal Readable, so a malformed encoding must come
back as a serialization error. The two secp keys, the ed25519 sender
address and the memo were unwrapped or copied without a length check.
Map them all to grin_ser errors, and take the proof type with a cast
that cannot lose data rather than a fallible conversion.
2026-07-28 11:52:31 -04:00
jwinterm 6d6e19832c Contracts: wire up the contract view command
contract view was in the CLI help but the command returned Not
implemented, and there was no owner API entry point for the existing
libwallet implementation. Add owner::contract_view through to the CLI
and the owner RPC, with a display for the result.

The slate is taken from --input or prompted for, as receive does.

is_executed was hardcoded to false. Derive it from our own tx log entry
for the slate, which is confirmed once the contract has executed.
2026-07-28 11:13:53 -04:00
jwinterm 9539d342cf Contracts: cancel every tx log entry for a slate in one batch
cancel_tx committed each entry separately, so a failure part way through
left some entries cancelled and the rest untouched, and a retry then
stopped on the entry that was already cancelled. Collect the entries and
their outputs first and cancel them in a single commit, so the partial
state cannot arise.

Cancelling an already cancelled transaction still reports an error, as
db_wallet_tx_rollback expects.
2026-07-27 18:17:42 -04:00
jwinterm 949066ea92 Contracts: reject an inconsistent participant list and slate state
add_keys matched a participant on the public excess alone, so an entry
carrying our excess with a nonce that is not ours counted as ours and the
full-slate guard was skipped. add_participant_info only replaces an entry
when both keys match, so that entry was kept and ours appended, leaving a
two party slate with three participants. pub_nonce_sum and pub_blind_sum
fold in every entry, so the partial signature is then made over an
aggregate that includes the spurious one. Match on the excess and nonce
pair, the same test add_participant_info and fill_round_2 already apply,
and reject a list carrying our excess with a foreign nonce rather than
treating that entry as ours.

transition_state turned every other state, including Unknown and an
already completed slate, into a successful Standard3. That happens after
signing and before the wallet state is persisted, so accept only the four
transitions the contract flows use and report anything else.
2026-07-27 17:38:33 -04:00
jwinterm 78aa41c77e Contracts: return errors for the remaining reachable panics
- Context::get_net_change unwrapped setup_args, which is None for the
  standard flows that share Context, so a non-contract context reaching
  a contract path terminated the wallet. It returns a Result now.
- A recomputed fee that no longer matches the stored one was an
  assert_eq, which aborts; report both values instead.
- Selection used abs(), which panics on i64::MIN, and unchecked addition
  for the amount equation.
- build_output_amount_list subtracted the custom output sum from the
  input sum as u64, before the checked conversion that was meant to
  guard it. A receiver without a payjoin contributes no inputs while
  --make-outputs is still allowed, so that underflows. The balance is
  evaluated in checked i64 now, with a test for the no-payjoin case.
2026-07-27 08:49:44 -04:00
jwinterm 43d14ebbf9 Contracts: sign and store the same payment proof timestamp
add_payment_proof read the clock again after generate_invoice_signature
had already signed over its own reading. The promise signature binds the
timestamp, so a tick between the two reads produced a proof that failed
verification with 'Invalid recipient signature'.

Carry over the timestamp that was signed.
2026-07-27 08:12:25 -04:00
jwinterm f594bb0c45 Slate: match a participant on both keys and validate the count
find_index_matching_context accepted an entry matching either our excess
or our nonce, while fill_round_2 requires both, so the two could disagree
on which entry is ours and signing could return success without adding a
signature. Both also indexed participant_data by position up to
num_participants, which a counterparty can send short.

Match on both keys and iterate the entries the slate carries. Require the
participant count to be exact before signing a contract, and check for two
participants in the contract tx log before pairing them with 'xor 1'.
2026-07-27 08:12:25 -04:00
jwinterm 80e1c29739 Contracts: emit the lowest slate version that fits the payment proof
A legacy send proof uses no V5 field: V4 carries the sender and receiver
addresses and the signature, and only the timestamp and memo need V5.
Sending those as V5 stopped V4-only wallets from reading the slate.

Add SlateVersion::lowest_for and use it from create_slatepack and from
the contract owner API methods, which returned V4 unconditionally and so
dropped the timestamp and memo that an early proof's promise signature
binds. contract_sign keeps the version the counterparty sent when the
slate does not need more.
2026-07-26 14:24:32 -04:00
jwinterm 5397cef9b0 Slate: validate the declared version when deserializing
VersionedSlate and VersionedBinSlate are untagged and try V5 first, so
without a version check a V4 slate parses as V5. Only visible without a
payment proof; with one the V4 and V5 proof shapes differ enough for
serde to pick the right variant by chance.

Reject a mismatched version in the V4 and V5 deserializers, for both the
JSON and binary encodings, and set the declared version from the
conversion so it always matches the structure emitted. Add V4/V5 round
trip tests over both encodings, with and without a proof.

The V3 API examples showed V4 slates declaring version 5; they now
declare 4.
2026-07-26 13:51:36 -04:00
jwinterm 1f035e3ab8 Contracts: replace the deprecated chrono calls
NaiveDateTime::from_timestamp_opt and DateTime::from_utc are deprecated
in favour of DateTime::from_timestamp.

Two call sites in contract::proofs converted a timestamp to a
NaiveDateTime and immediately back to the same i64; those now use the
value directly, which also removes an unwrap.
2026-07-26 08:39:20 -04:00
jwinterm 6c7026604a Contracts: clear the warnings introduced by this PR
- Remove unused imports in api_impl::owner, backend, contract::utils,
  v5_bin and the contract tests.
- Remove unused test bindings, and document the shared slate test
  fixtures module.
- Drop the commented-out assertion block in the mwixnet contract test,
  along with the height bookkeeping and import that only it used.
2026-07-26 08:18:43 -04:00
jwinterm fb10280165 Contracts: apply rustfmt to the contract module
cargo fmt --all -- --check is now clean across the workspace.
2026-07-26 07:50:25 -04:00
jwinterm 894023a863 Merge upstream staging into contracts
Brings the branch current with staging for review.

- ed25519-dalek 1.0.0-pre.4 to 2: PublicKey is now VerifyingKey, Keypair
  is replaced by SigningKey, and from_bytes takes a fixed-size array.
  Ported the contract proof code and the V5 slate serializers, following
  the pattern staging already uses in v4_bin.
- owner_single_use and foreign_single_use now take the wallet instance
  and a config path rather than an optional API handle. Updated the
  contract call sites in command.rs and the contract tests.
- update_tx_slate_state moved to api_impl::types; dropped the local copy.
- Took staging's fixed-size-array form of the slate signature guards; it
  is equivalent to 25a5434 and keeps the file in sync with upstream.
- The grin node is now a submodule, so a build needs
  git submodule update --init.
2026-07-26 07:14:23 -04:00
d2fe2b90f6 Global config instance (#769)
* config: global instance

* fix: cli tests

* fix: owner api tests

* config: save tor config

* fix: owner rpc tests

* fix: set config file path on init and owner api call

* tor: store launched services, get global config at command loop, remove unused listen args, check for empty global config to set default, do not save empty tor config at owner

* api: update config in memory after successful file write

* arti: restart proxy in loop

* fix: comment for tor config save

* config: set or init, update without error

* config: scoped tor config

* build: update lock file

* config: ability to store multiple configurations at memory from config path

* tor: restart service on config change

* fix: owner api test

* foreign: pass tor config from args, stop service, error handling

* arti: move runtime wrapper to single file

* config: read existing config from file at create_config

* config: handle error on read

* arti: single dir for cache

* foreign: register config listener before launch

* config: save temp

* config: remove unused error type

* config: make path and members not optional, return an error on config creation if it exists, pass default tor config to send if not set

* arti: non public runtime wrapper

* config: migrate comments

* fix: rpc tests

* config: return from cache if exists

* config: save disabled tor config when null provided at owner and tor section was not set

* config: update tor with args at listener

* config: pass pathbuf directly on creation

* config: unique tmp file name, setting permissions and providing config version

* fix global config handling

* fix config persistence

* fix tor service restart

* fix config synchronization

* require wallet close before changing data directory

* unify global config updates

---------

Co-authored-by: wiesche <wiesche89@googlemail.com>
2026-07-24 22:15:19 +02:00
e9e27f4feb Update deps (#768)
* build: update grin submodule to last staging

* build: update grin submodule

* build: remove remove_dir_all dependency

* build: update rpassword

* build: update semver

* build: remove unused deps

* build: update ring

* build: update lock file deps

* build: update rustyline

* build: update ed25519-dalek

* build: update curve25519-dalek

* build: update age

* build: update uuid

* fix: uuid test

* build: update x25519-dalek

* fix: x25519-dalek build

* build: update arti

* build: update grin submodule

* build: update hyper

* fix: initialize rustls provider for tests

* build: update grin submodule

* fix: rustls install provider

* tor: share getting private key

* fix: show an error on node version parse

* cli: renamed prompt password method

* fix: replace into_boxed_slice with try_from

* fix: dalek tests for onion

* config: comment expanded secret key

* fix: import api body for controller

* password: handle prompt error

* wallet: parse node error message

* tests: do not use random values for sign messages

* build: make hook executable

* build: cargo format all

* rustls: do not install provider at tests and globally

* build: update grin submodule

* build: fix api channel from grin submodule

* args: use read_password for piped prompt

* build: remove unused dep

* test: fixed signature

* preserve piped pw input

---------

Co-authored-by: wiesche <wiesche89@googlemail.com>
2026-07-16 20:24:09 +02:00
ardocrat a1d52879cb Merge branch 'staging' into pay_tor_result 2026-06-23 22:53:30 +03:00
ardocrat 91da4e7875 Merge branch 'master' into staging_master
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	api/Cargo.toml
#	api/src/foreign.rs
#	api/src/owner.rs
#	config/Cargo.toml
#	controller/Cargo.toml
#	controller/tests/common/mod.rs
#	impls/Cargo.toml
#	libwallet/Cargo.toml
#	util/Cargo.toml
2026-06-23 13:10:57 +03:00
ardocrat f92a2d6b43 slatepack: return concrete error on sync workflow instead of none, optimize sending requirement detection 2026-06-23 00:37:00 +03:00
ardocratandGitHub 411bcff003 Integrated Tor arti Client (#750)
* tor: integrated client for listener, update tokio to last version

* tor: send transaction with integrated client

* build: code format

* tor: optimize bootstrap percent check

* tor: handle config build error

* tor: do not show slatepack address if tor not launched

* tor: handle keystore errors

* tor: keep service launched

* tor: bootstrap timeout, clean cache before client launch

* tor: post request timeout

* fix: post timeout

* build: update deps after merge

* tor: store arti runtime

* tor: do not send over tor if `manual` arg presents or `skip_send_attempt` set at config

* fix: arg name

* tor: handle arti runtime creation error

* tor: handle connection error

* tor: handle bootstrap error

* tor: handle start service name error

* tor: handle service config creation error

* tor: service proxy errors

* tor: handle service key creation emptiness

* build: format code

* build: update arti to 0.43, hyper to 0.10.1

* api: start foreign listener before tor connection

* tor: handle json parse errors

* tor: prevent to create several client runtimes, ability to recreate

* tor: return bootstrap error

* tor: handle host parse error

* tor: restart proxy on error

* tor: do not use integrated client when config value use_integrated is missing, add logging

* build: update lock file
2026-06-21 14:20:34 +02:00
ardocratandGitHub ca5686aa04 Node submodules (#758)
* build: use node submodule as dependency

* ci: checkout submodules

* docs: add contributing and build instruction

* ci: update release build

* git: remove old submodule

* git: grin staging submodule

* git: add submodule dir
2026-06-20 22:42:41 +02:00
David Burkett 5c20635a24 Bump version to 5.4.1 2026-06-10 22:00:53 -04:00
David Burkett d4867d5cd6 Removing some panics 2026-06-10 20:34:32 -04:00
ardocratandDavid Burkett 84019630ed Update rust edition to 2021 (#749)
* build: update rust edition 2021

* cargo: format all

* build: fix tests and warnings
2026-06-10 20:33:35 -04:00
jwinterm 178deac700 Contracts: remove the stale contract_setup RPC stubs and tidy the README
The separate contract_setup API was dropped in favor of contract_new/contract_sign.
Remove the two commented-out contract_setup stubs left in the owner RPC trait and
impl, and drop the README action-list items they (and earlier fixes) resolved: the
'remove setup API/CLI' item, the make_outputs-nanogrins item (now done), and the
move-test-utilities item (the per-scenario contract_*.rs tests already cover it).

Addresses PR #754 review (owner_rpc.rs, README.md).
2026-06-10 17:55:56 -04:00
jwinterm 2106c08625 Slate: document that the proof-downgrade placeholder key is inert
When downgrading a payment proof with no sender address (current -> V5/V4) we emit
an all-zero placeholder key. Replace the speculative TODOs with the invariant: it
can never be accepted as a real sender because verification binds the sender
address into the signed message (contract::proofs::verify_promise_signature and
the legacy verify_payment_proof via payment_proof_message), so a proof only
validates against the address the verifier supplies.

Addresses PR #754 review (slate.rs).
2026-06-10 17:10:13 -04:00
jwinterm a9cf8efbce Contracts: validate the slate in contract view
contract view derived its result without checking the slate. Reject an Unknown
slate state and an out-of-range num_participants (mirroring context::create), so
a malformed or tampered slate can't feed a bogus view to a caller/UI. Also fix
num_sigs, which counted participants still missing a signature rather than the
signatures present.

Addresses PR #754 review (view.rs).
2026-06-10 17:10:13 -04:00
jwinterm 55a0d43d57 Contracts: deterministic self-spend id so an interrupted revoke resumes
revoke builds the self-spend via new(), which used a random slate id, so a crash
between new() and sign() left an orphaned self-spend and a retry created another
one. Derive the self-spend slate id deterministically from the revoked slate
(blake2b, already a dependency) and let contract::new take an optional slate id;
a retry then reuses the in-progress context via get_or_create instead of
orphaning a fresh self-spend. The other new() caller passes None (random id).

Adds a unit test that the derived id is deterministic and distinct.
2026-06-10 16:34:52 -04:00
jwinterm c7acc8ef4a Contracts: make revoke idempotent and resumable
revoke cancels the original tx (unlocking its inputs) before the replacement
self-spend is built, and the steps commit separately, so a crash in between left
the inputs Unspent with no self-spend while a retry silently did nothing.

- Find the contributed inputs by tx id whether they are still Locked (original tx
  active) or already Unspent (a prior revoke cancelled them but the self-spend did
  not finish), instead of Locked-only.
- Cancel only when the tx is still in a cancellable state, so a resumed or repeated
  revoke skips the cancel and re-spends the Unspent inputs.
- Once the self-spend completes the inputs reference its tx id, so a later revoke
  is a no-op.

Adds a regression test that interrupts a revoke after the cancel and confirms the
next revoke resumes and produces the self-spend.

Addresses PR #754 review (revoke.rs).
2026-06-10 16:14:35 -04:00
jwinterm a134158170 Contracts: store the signed tx after the wallet-state commit
save_step wrote the tx file (store_tx) before the atomic batch commit, so a DB
failure could leave the stored tx out of sync with wallet state. Move store_tx
to after batch.commit() (and after the post-commit signing-context check),
matching the core convention in internal::selection. #755 keeps stored txs as
files for all flows, so this reorder closes the desync window without diverging
from core.

Addresses PR #754 review (utils.rs).
2026-06-10 14:57:09 -04:00
jwinterm 25a54340e8 Slate: length-check ed25519/uuid fields in the JSON serde path
The serde deserialize helpers copied fixed-length byte arrays
(b.copy_from_slice(&bytes[0..N])) without checking the decoded length, so a
slatepack with a short pubkey/signature/uuid field panicked the wallet during
deserialization. The binary path was hardened in 25dab9f; do the same for the
JSON modules: reject any field whose decoded length is not exactly N. Adds a
regression test that exercised the panic.
2026-06-10 14:45:37 -04:00
jwinterm 148987417d Contracts: replace reachable panics with errors
De-panic the contract paths flagged in review plus several found alongside them:
- new: use unsigned_abs (abs() panics on i64::MIN).
- proofs: surface InvoiceProof serialization errors instead of expect(); bounds-
  check the participant index before indexing participant_data.
- utils/slate: propagate the error when a context input is missing from the db
  instead of unwrapping.
- revoke: return an error when a locked input has no cached commitment.
- selection: checked i64/u64 conversions so large amounts can't wrap into a
  wrong (possibly negative) change.
- types: checked sum of output amounts.

Addresses PR #754 review (new.rs, proofs.rs, utils.rs, selection.rs).
2026-06-10 14:28:35 -04:00
jwinterm f13ea7f01f Contracts: make_outputs takes nanogrin amounts (Vec<u64>), not a string
OutputSelectionArgs.make_outputs was a comma-separated grin string parsed inside
libwallet via amount_from_hr_string. Move that parsing to the CLI boundary and
carry explicit nanogrin u64 amounts through the API and Context instead:
- OutputSelectionArgs.make_outputs: Option<String> -> Option<Vec<u64>>;
  output_amounts() becomes a plain accessor, num_custom_outputs() uses len().
- The CLI (wallet_args) parses grin -> nanogrin per amount; ContractNewArgs and
  ContractSetupArgs carry Vec<u64> and copy it through.
- Update the two tests that supplied make_outputs as a string.

Addresses PR #754 review (types.rs, wallet_args.rs).
2026-06-10 14:18:50 -04:00
jwinterm 3fba1319fb Merge upstream staging (#755 multi-LMDB backend) into contracts
Reconcile the contract feature with #755, which replaced the WalletBackend
trait with a concrete WalletBackend<C,K> struct (libwallet/src/backend.rs) and
removed impls/src/backends/lmdb.rs.

- Port the contract module and contract API entry points from the trait form
  (T: WalletBackend<'a,C,K>) to the concrete struct.
- Re-home next_child_for and a parent_id+log_id get_tx_log_entry onto backend.rs.
- Re-apply the self-spend tx-log filters, cancel-loop, v5 payment-proof fields,
  and Context contract fields onto staging's rewritten files.
- Adapt to fallible iter()/tx_log_iter() and self-consuming batch commit.

Full suite green (53 binaries / 191 tests).
2026-06-10 13:48:34 -04:00
06ab92a619 Update lmdb (#755)
* wallet: update lmdb from store crate, migrate single backend from trait to struct, update tests cargo deps and design doc

* backend: do not panic on iter and directory creation

* return  iterator errors

* build: update to last node revision

* fix: height selection for scan, remove unused mutability for wallet

* wallet: remove unused mutability conversion

* tx: store slate state at database

* build: cargo fmt

* fix: do not return error when slate not found on tx slate state update

* lmdb: do not collect all tx log records at iterator, show read errors at log

* fix: save slate state, return error if slate not found

* tx: specify amount of bad txs on slate state update error

---------

Co-authored-by: Joerg <wiesche89@googlemail.com>
2026-06-09 13:54:51 +02:00
jwinterm db52b2e58c Contracts: resolve remaining review TODOs
Comment-only follow-ups to the review:
- selection: a negative change output is already rejected; drop the stale TODO.
- utils: note the per-participant kernel fee is rounded up (overpay bounded to
  under one unit per participant), and that add_outputs/parent_key_id are handled
  by the sign step and the stored Context.
- types: drop the "custom outputs imply payjoin" TODO; make_outputs is valid
  without a payjoin (e.g. a receiver splitting its output), so the check would be
  incorrect.

Addresses PR #754 review (selection.rs, utils.rs, types.rs).
2026-06-07 20:15:13 -04:00
jwinterm 18167be1e5 Contracts: tidy review TODOs, document the owner API, simplify get_net_change
Comment/doc cleanups from review; the only behavior-adjacent change is the
get_net_change signature.
- get_net_change takes the slate id instead of the whole slate.
- replace the owner-API contract methods' `/// TODO` placeholders with real docs.
- narrow or remove now-resolved TODOs (slate proof fns, context, utils stored_tx,
  a doc-comment that should be //, wallet_args parse notes); reword the rest as
  concrete FUTURE notes.
- prune the contract actions README of items already fixed.

Addresses PR #754 review (comment hygiene).
2026-06-07 19:50:52 -04:00
jwinterm 1b3d773fab Contracts: delete the canceled context on revoke
revoke left the canceled slate's private context in place when the contract
had not been signed (signing already deletes it). Look up the revoked slate id
from its tx log entry and delete the context after cancelling, if one still
exists. Resolves the stale revoke TODOs about the cancelled context.

Addresses PR #754 review (revoke.rs).
2026-06-07 19:15:22 -04:00
jwinterm 1d89ec2682 Contracts: report agreed_net_change in contract view
contract view hardcoded agreed_net_change to None. When a local context exists
we've agreed on the net change, so read it from the context and report it
(None when there's no context); other errors propagate.

Addresses PR #754 review (view.rs).
2026-06-07 19:10:22 -04:00
jwinterm bcea98be2a Contracts: remove the commented-out contract_setup
The separate contract_setup step is superseded by contract_sign, which runs
setup internally. Remove the dead commented-out function (libwallet, controller,
api), its CLI subcommand (grin-wallet.yml), and its dispatch arm (wallet_args).
The contract_sign path and the parse_contract_setup_args parser are unaffected.

Addresses PR #754 review (command.rs, owner.rs).
2026-06-07 19:07:24 -04:00
jwinterm 756664e654 Contracts: remove the unused target_slate_version field
ContractNewArgsAPI and ContractView carried a target_slate_version that was only
ever None and never read; the emitted slatepack version is chosen from the slate
content. Remove it. The legacy InitTxArgs/IssueInvoiceTxArgs fields are unrelated
and untouched.

Addresses PR #754 review (types.rs).
2026-06-07 18:43:48 -04:00
jwinterm 5569d38df2 Contracts: test selection-args consistency and fee splitting
Adds the two regression tests requested in review:
- verify_selection_consistency: same/default args are accepted; changed
  non-default args after setup are rejected.
- my_fee_contribution: a single participant pays the whole kernel cost, two
  participants each pay ceil(kernel/2), and the shares cover the full cost.

Also resolves the related TODOs (selection.rs "test that this works",
utils.rs fee-encoding compatibility note).

Addresses PR #754 review (selection.rs, utils.rs).
2026-06-07 17:16:44 -04:00
jwinterm 7982f17926 Contracts: reject a slate that already has all participants
add_keys added our participant info without checking the slate wasn't already
full, so a tampered slate could exceed num_participants. Block adding when the
participant set is already full and we aren't already in it (re-adding our own
info stays idempotent).

Addresses PR #754 review (slate.rs).
2026-06-07 17:00:50 -04:00
jwinterm 9bf11fbc8d Contracts: derive the proof address under the contract's account
generate_invoice_signature derived the receiver's proof address from the active
account's parent key. Like the output-derivation fix, use context.parent_key_id
so the proof address belongs to the contract's account, not whatever account is
active.

Addresses PR #754 review (proofs.rs, utils.rs).
2026-06-07 16:45:53 -04:00
jwinterm 62fc9421f1 Contracts: use Error::Fee for the fee-overshoot error
The committed-fee-exceeded check returned a GenericError. Use the existing
Error::Fee variant and fold the "cancel and retry" hint into the message.

Addresses PR #754 review (selection.rs).
2026-06-07 16:42:16 -04:00
jwinterm b3f75ee2b5 Contracts: use checked conversions for amount -> i64
A large u64 amount could wrap when cast `as i64`. Convert with i64::try_from in
the CLI net-change derivation and in contract view, returning an error instead.
get_net_change / to_api_args now return Result (the two arg panics become
errors too), and the to_api_args call moves out of the owner-session closure.

Addresses PR #754 review (command.rs, view.rs).
2026-06-07 16:37:54 -04:00
jwinterm 5cc1683a1b Contracts: validate num_participants before fee math
num_participants is divided into the kernel cost, so 0 caused a divide-by-zero
(inf -> garbage fee). Validate it in context::create, the common setup path for
every party: the initiator's value comes from args and the signer's from the
slate, so this also rejects a tampered slate. Only 1 (self-spend) and 2
(standard) are supported today; lift the upper bound when multi-party contracts
are implemented.

Addresses PR #754 review (wallet_args.rs:1039).
2026-06-07 16:33:59 -04:00
jwinterm d4d5f97bd8 Contracts: validate net_change in the foreign sign path
The foreign contract_sign API unwrapped the optional net_change field, so an
incomplete request could panic the wallet. Return an error instead, matching
the foreign contract_new path.

Addresses PR #754 review (foreign.rs).
2026-06-07 16:14:02 -04:00
jwinterm 502d2ae336 Contracts: revoke under the inputs' account, not the active one
revoke() cancelled the original tx and built the self-spend using the wallet's
active account, but the locked inputs belong to the account that created the
contract. Revoking while a different account was active would fail to find and
cancel the original tx and would recover funds into the wrong account.

- derive the account from the locked input's root_key_id; use it for cancel_tx
  and as the self-spend's src_acct_name.
- test: revoke a contract while a different account is active.

Addresses PR #754 review (revoke.rs).
2026-06-04 20:41:11 -04:00
jwinterm 62ede419dd Contracts: emit the lowest slatepack version needed
create_slatepack always serialized to V5, so every slatepack went out V5 and
V4-only peers could not read it. Emit V4 unless the slate carries a payment
proof: only the (opt-in) proof needs the V5-only fields (timestamp/memo) that
V4 cannot represent. The slate's declared version is set to match the emitted
structure so readers select the right variant.

- packer: V5 when payment_proof is present, else V4.
- test: a proof slate round-trips as V5 (memo preserved); a no-proof slate as V4.

Addresses PR #754 review (packer.rs).
2026-06-04 20:14:52 -04:00
jwinterm 15cbbc96e8 Contracts: validate net_change instead of unwrapping
contract `new` and the foreign `contract_new` API unwrapped the optional
net_change field, so a request omitting it panicked the wallet. Return an
error instead.

- new::compute and foreign::contract_new: net_change.ok_or_else(..).
- test: contract_new with no net change errors rather than panicking.

Addresses PR #754 review (new.rs, foreign.rs).
2026-06-04 19:34:21 -04:00