188 Commits

Author SHA1 Message Date
Whisky f8be768676 Change runner to ubuntu-latest for all jobs 2026-03-05 18:30:17 -07:00
Whisky fcfe94856c Remove self-hosted runner specification from CI jobs
Removed 'runs-on: self-hosted' from multiple jobs in CI workflow.
2026-03-05 18:25:30 -07:00
Matt Hess 071f1e7f74 Update README 2026-03-05 17:38:46 +00:00
Matt Hess ed0ccad87f license updates 2026-03-05 17:03:39 +00:00
Matt Hess fe589acc6a update ignore paths and add manual option to CI 2026-03-05 17:00:52 +00:00
Matt Hess 504ea3ccd5 use self-hosted CI runner 2026-03-05 16:44:43 +00:00
Matt Hess 2951b06606 Fix multi-language mnemonic restore: Unicode case folding and UTF-8 prefix handling 2026-03-05 16:27:32 +00:00
Matt Hess 8abed301da salvium_wallet_scan_mempool is now exported as a C FFI symbol 2026-03-03 16:58:17 +00:00
Matt Hess a13c1d6451 Salvium 2 layers 2-5: validation, builder, CLI, FFI, RPC
- Fix builder version selection to emit v5 for CREATE_TOKEN/ROLLUP
  - Add consensus validation for tx types 9-10 (HF11 gating, asset rules)
  - Wire wallet token module (validate_create_token_params, CREATE_TOKEN_COST)
  - Add CreateToken CLI command with full build/sign/submit pipeline
  - Add salvium_wallet_create_token FFI endpoint with token metadata support
  - Add create_token wallet RPC types and method
  - Fix CLI mempool scan skipped when wallet already at chain tip
  - Show pool TXs in history by default, display "pool" instead of height 0
  - Normalize FFI input param structs to camelCase (#[serde(rename_all)])
  - Update tx_type_name for types 9 (CREATE_TOKEN) and 10 (ROLLUP)
  - Add integration testing docs (docs/integration-testing.md)
2026-03-03 15:50:54 +00:00
Matt Hess 0459b3d0f9 Add mempool TX scanning: show pending transfers immediately after sync and broadcast 2026-03-02 16:34:59 +00:00
Matt Hess 7f8a711276 Fix sweep sanity_check_failed on mainnet: add cross-input ring member deduplication
The daemon's tx_sanity_check requires ≥80% unique ring member indices
  across all inputs (when ≥10000 outputs exist). Our DecoySelector picked
  decoys independently per input, causing heavy overlap from the gamma
  distribution favoring recent outputs. Sweeps with many inputs failed
  on mainnet but passed on testnet (which has <10000 outputs, skipping
  the check entirely).

  Add build_ring_excluding() that avoids indices already used by prior
  inputs in the same transaction, with soft fallback after many attempts
  to prevent exhaustion on small output pools.
2026-03-02 01:00:50 +00:00
Matt Hess b340151c7c correct vout for sweep 2026-03-02 00:34:40 +00:00
Matt Hess 166882ad30 reveal mode detail on daemon reject/failure 2026-03-01 23:46:20 +00:00
Matt Hess 4d2fae88fc format fix 2026-03-01 22:25:53 +00:00
Matt Hess 0d2887175a Fix sweep fee_too_low: replace single rebuild with convergence loop
The rebuild step could produce a TX with slightly different weight than
  the first build (signing randomness), leaving the fee short. Now loops
  until fee >= weight * fee_per_byte, converging in 1-2 iterations.
2026-03-01 22:10:08 +00:00
Matt Hess d61834be1f Remove hardcoded "SAL" asset types; auto-detect native asset from HF version
- Add native_asset field to CLI FeeContext, derived from hard_fork_info() RPC
  - Add --asset CLI arg to 14 command variants (default: auto-detect)
  - Thread asset_override through all 12+ transfer commands in transfers.rs
  - Update multisig transfer_multisig() with same dynamic asset pattern
  - FFI: extend detect_fork_params() to return native asset, auto-detect
    in do_transfer/do_stake/do_sweep when caller omits asset_type
  - Fix asset-type-specific index conversion in CLI tx_common fetch_decoys
    (was using global indices, matching prior FFI fix)
  - Version bump to 1.0.7-r015
2026-03-01 19:25:56 +00:00
Matt Hess efd0a76b8e Return fee diagnostic in FFI JSON responses
Add weight/estimated_weight/fee_per_byte to the JSON result from
  walletTransfer, walletStake, and walletSweep so Dart can dlog() them.
  On daemon rejection, the error string now includes the diagnostic
  numbers (est_weight, actual_weight, fee, fee_needed, fpb) so fee
  issues are immediately visible without a Rust log callback.
2026-03-01 12:39:52 +00:00
Matt Hess e320731600 Fix three FFI transaction bugs: secret_key_y, unlock_time, fee estimate
The FFI path (build_sign_maybe_broadcast) had three bugs not present in
  the integration test code which builds transactions directly:

  1. Legacy CryptoNote outputs always got secret_key_y=None, but TCLSAG
     (rct_type >= SALVIUM_ONE) requires secret_key_y=Some([0u8;32]) even
     for non-CARROT inputs. C++ sets carrot_ctkey.y = rct::zero(). This
     caused "input N requires secret_key_y for TCLSAG but has None" on
     sweeps.

  2. STAKE transactions never set unlock_time (defaulted to 0). The daemon
     requires unlock_time = current_height + stake_lock_period, causing
     "Failed (unknown)" rejections. do_stake() now queries daemon height
     and uses network_config().stake_lock_period.

  3. estimate_tx_size() underestimated by ~86 bytes for CARROT transactions.
     Missing: return_address_list (32*n_outputs), return_address_change_mask
     (n_outputs), per-output ephemeral keys in extra (2+32*n_outputs).
     Old flat estimates (24+40=64 bytes) replaced with accurate per-field
     accounting (~150 bytes for 2-output CARROT TX).

  Also adds FEE DIAGNOSTIC log line in FFI that prints estimated vs actual
  serialized weight after signing, making fee issues immediately visible.
2026-03-01 12:33:56 +00:00
Matt Hess 650cb4b1f3 Fix fee calculation to match C++ wallet2 2021-scaling behavior
The fee pipeline had three critical bugs causing fee_too_low rejections:

  1. Missing fees[] array — daemon returns [Fl,Fn,Fm,Fh] tiers via
     get_fee_estimate RPC but we only captured fee (= fees[0]), losing
     all priority tier data.

  2. Wrong priority handling — we applied priority.multiplier() (1x/5x/25x/
     1000x) on top of fees[0]. C++ wallet2 uses fees[priority_index] directly
     with tiers already baked in (1x/4x/custom/custom).

  3. Silent fallback to FEE_PER_BYTE=30 — real fee is ~500-1200/byte, so
     the fallback guaranteed rejection. Now propagates errors instead.

  Changes: add fees[] to FeeEstimate, add tier_index() to FeePriority,
  remove priority multiplier from estimate_tx_fee (fee_per_byte now
  includes priority), add calculate_fee_from_weight matching C++ exactly,
  update all callers across CLI/FFI/tests.
2026-03-01 01:56:15 +00:00
Matt Hess 07c76e3a0c Fix fee_too_low: implement correct 2021-scaling fee formula
The dynamic fee calculation used an inverted formula
  (constant/base_reward) instead of the daemon's actual check_fee
  formula (base_reward * 3000 / median²). This caused fee_too_low
  rejections on all transaction types.

  - Add get_dynamic_base_fee() matching C++ Blockchain::get_dynamic_base_fee()
  - Add get_dynamic_base_fee_estimate_2021_scaling() for 4-tier fee tiers
  - Add round_money_up() matching C++ cryptonote::round_money_up()
  - Update calculate_required_fee/validate_fee to use median_block_weight
  - CLI/FFI wallet: use daemon get_fee_estimate RPC (matches C++ wallet)
  - Add SCALING_2021_FEE_ROUNDING_PLACES and REWARD_BLOCKS_WINDOW constants
2026-02-28 22:49:29 +00:00
Matt Hess c10a347103 fix liniting issues 2026-02-28 18:50:26 +00:00
Matt Hess c1a6c25e80 Fix fee quantization formula to match C++ get_fee_quantization_mask()
fee_quantization_mask() was computing 10^PER_KB_FEE_QUANTIZATION_DECIMALS - 1
  (= 10^8 - 1 = 99,999,999), rounding every fee UP to the nearest 100,000,000
  atomic units — a minimum of 1 whole SAL per transaction. The C++ formula is
  10^(DISPLAY_DECIMAL_POINT - PER_KB_FEE_QUANTIZATION_DECIMALS) = 10^(8-8) = 1,
  meaning no quantization. Also fixed the rounding formula to match C++
  calculate_fee_from_weight(). Added sanity tests that assert fees stay well
  under 1 SAL for standard transactions.
2026-02-28 18:36:28 +00:00
Matt Hess 68e2dc2a41 Replicate C++ dynamic fee calculation locally to fix fee_too_low rejections
The daemon rejects transactions with fee_too_low because the wallet was
  using a hardcoded FEE_PER_BYTE=30, while the daemon dynamically computes
  its minimum from the block reward. This replicates the C++ formula
  (blockchain.cpp get_dynamic_base_fee_estimate) as a pure local function
  and applies fee quantization matching the daemon's rounding.

  - Add dynamic_fee_per_byte(base_reward, hf_version) and
    fee_quantization_mask() to salvium-tx/fee.rs
  - Update estimate_tx_fee() to accept explicit fee_per_byte with
    quantization (round up to 10^8 boundary)
  - Replace adjust_priority() with resolve_fee_context() in both CLI
    (tx_common.rs) and FFI (transfer.rs), combining priority adjustment
    and fee rate computation in a single RPC roundtrip using
    BlockHeader.reward and BlockHeader.major_version
  - Add fee_per_byte field to TxPipeline
  - Thread fee_per_byte through all 13 CLI transfer commands, multisig
    transfer, and 5 FFI entry points (transfer, stake, stake_dry_run,
    sweep, transfer_dry_run)
  - Update all test files to pass explicit fee_per_byte values
2026-02-28 15:58:25 +00:00
Matt Hess 272fbfffde Implement adjust_priority to match Salvium C++ wallet2 behavior
Automatically downgrade fee priority from Normal (5x) to Low (1x)
  when the network isn't busy, saving users fees. Matches the C++
  wallet2::adjust_priority logic: check mempool backlog and recent
  block fullness (>80% threshold) before deciding.

  - Add FeePriority::Default variant that resolves at runtime
  - Add adjust_priority() in both FFI and CLI paths
  - Refactor CLI TxPipeline to use shared NodePool from AppContext
  - Wire adjustment into all transfer commands (FFI and CLI)
2026-02-28 14:34:25 +00:00
Matt Hess 0331a99283 Fix stake double-counting and improve daemon rejection errors
Stake transactions were adding the stake amount as both a destination
  and amount_burnt, causing the builder to require 2x the intended
  balance. Remove the destination — amount_burnt alone handles it.

  Daemon rejection errors now report boolean flags (double_spend,
  fee_too_low, invalid_input, etc.) instead of the often-empty reason
  string.
2026-02-28 12:23:11 +00:00
Matt Hess 6914f7a6fc Fix CARROT commitment storage, add spend-time fallback, fix sync height race
1. Pipeline refactor regression: store_found_output_row passed outputs: vec![]
     so commitment and per-output unlock_time were never stored. Add both fields
     to FoundOutputInfo and forward from parsed TxOutput to storage.

  2. Spend-time fallback: recompute commitment from pedersen_commit(amount, mask)
     when not stored, matching C++ wallet2 approach. Existing wallets work without
     resync.

  3. Progress callback reported batch_end (dispatched to store worker) while
     sync_height() read from DB (committed by store worker), causing the app to
     see height jump backwards. Now both report committed_height.
2026-02-28 03:48:00 +00:00
Matt Hess df783121d1 added tar to container and fix cdylib make 2026-02-28 00:49:16 +00:00
Matt Hess f746f183f3 added cmake to container 2026-02-28 00:34:16 +00:00
Matt Hess a1b1f0c665 fix linting 2026-02-28 00:29:56 +00:00
Matt Hess c3cf5993ac Eliminate per-tx DB queries in detect_spent_outputs with in-memory caches
Store phase was still the bottleneck (~700-1800ms/batch) despite the
  pipeline, because detect_spent_outputs issued ~51k individual DB queries
  per batch (key image + ring member lookups for every on-chain tx).

  Load all owned key images and global indices into HashSets at batch start,
  check in-memory first, and only hit the DB on the rare cache hit (~0.01%).
  Also adds PRAGMA synchronous=NORMAL and hoists get_stakes out of the
  per-block loop.

  Result: store times drop from 700-1800ms to 11-167ms, overall sync
  throughput improves from ~490 to ~2073 blocks/s (4x).
2026-02-28 00:27:08 +00:00
Matt Hess 5a4f7be4ed fix linting 2026-02-27 21:46:11 +00:00
Matt Hess 97fed0ebfa Pipeline DB store onto dedicated thread, overlap with fetch+parse
The store phase (~65% of batch time) now runs on a std::thread while
  the sync loop fetches and parses the next batch concurrently. Bounded
  channel (capacity 2) provides backpressure. Block hash cache avoids
  DB lock contention during reorg checks. scan_ctx updates (cn_subaddr
  entries from STAKE/CONVERT/AUDIT) flow back via StoreResultMsg and
  are merged before cloning for the next parse.
2026-02-27 21:40:25 +00:00
Matt Hess f5e945a23b change CI pipeline to alpine based, improve sync parallelism 2026-02-27 19:59:57 +00:00
Matt Hess 9bf50947dd restructure CI into preflight 2026-02-27 19:22:34 +00:00
Matt Hess 098f8a3ab9 format fixes 2026-02-27 19:18:06 +00:00
Matt Hess aa30b213d2 Add distributed multi-node fetch and explorer FFI exports
Distribute block fetch requests across up to 4 fastest nodes in the
  NodePool, selected by latency-weighted racing. All configured nodes
  are probed but only the top 4 are used for parallel fetching.

  NodePool (salvium-rpc):
  - Add max_fetch_nodes to PoolConfig (default 4)
  - Add force_race() to probe all nodes on demand
  - Add fetch_batch_distributed() with latency-weighted range splitting
  - Add compute_assignments() with 6 unit tests
  - Add DistributedBatchResult type

  Sync engine (salvium-wallet):
  - Call force_race() at sync start to populate latency data
  - Replace all 3 fetch sites with fetch_batch_distributed
  - Simplify PrefetchResult to use DistributedBatchResult

  Explorer FFI (salvium-ffi):
  - Add salvium_daemon_get_blocks_by_height (JSON heights → blocks)
  - Add salvium_daemon_get_transactions (JSON hashes → tx hex)
  - Add salvium_daemon_add_nodes (batch add from JSON array)
  - Add salvium_daemon_force_race (probe all nodes)

  CLI & bench:
  - Add --nodes flag to salvium-wallet-cli and salvium-sync-bench
  - Wire extra nodes into NodePool for sync commands

  Docs:
  - Document multi-node setup in wallet-sync-spec.md
  - Document FFI block/tx fetching in explorer-spec.md
2026-02-27 18:56:31 +00:00
Matt Hess 8c02a452e8 Store Pedersen commitment (outPk) for owned outputs during sync
CARROT outputs need the commitment for spend key derivation, but store_found_outputs was hardcoding commitment: None. Now looks up the commitment from the parsed TxOutput by index. Existing wallets need a rescan to
  populate the field.
2026-02-27 05:45:02 +00:00
Matt Hess 415f2cd6db Add salvium_version() and salvium_rs_version() FFI functions
Exposes two version queries: salvium_version() returns the Salvium protocol version (e.g. "1.0.7"), salvium_rs_version() returns the library version derived from the rXXX tag (e.g. "v0.1.2"). Both are auto-derived from
  Cargo.toml at compile time.
2026-02-27 05:11:28 +00:00
Matt Hess 9996b116a7 Fix get_outs HTTP 500 by using global index space for decoy selection
The transfer pipeline was passing asset-type-specific asset_type to get_output_distribution and get_outs but using global output indices from the wallet DB, causing an index space mismatch that made the daemon return HTTP
   500. Switched to empty asset_type (global index space) for both calls, matching the existing salvium-cli pattern. Removed unused is_global_out field from OutputRequest. Bumped version to r012.
2026-02-27 03:19:45 +00:00
Matt Hess 401a6c2013 Fix UTXO selection: require caller-specified asset_type, remove CARROT filter 2026-02-27 01:56:37 +00:00
Matt Hess 136f56976a Fix UTXO selection: require caller-specified asset_type, remove CARROT filter
Transfer/stake/sweep failed with "no suitable outputs" because the
  default asset_type was "SAL" but HF10+ outputs are stored as "SAL1".
  Remove hardcoded default, require the caller to specify asset_type
  explicitly, and use select_outputs (not select_carrot_outputs) so
  pre-fork UTXOs remain spendable. This also supports future token
  types without hardcoding asset strings.
2026-02-27 01:41:23 +00:00
Matt Hess fad6aca9c6 Fix doc inaccuracies across wallet-sync-spec, explorer-spec, ffi-scanner
Correct camelCase field names in FFI source comments, fix build script
  target description, bigint→number types for u64 params, address format
  values (legacy not CryptoNote), verify_rct binary return format, and
  stale line number references.
2026-02-27 01:19:38 +00:00
Matt Hess e55119aba5 Fix transaction display bug: sent transactions showing 0.00 values
Change is_incoming logic in build_transaction_row so received change
  outputs from our own spends don't mark the transaction as incoming.
  Previously nearly every outgoing tx had is_incoming=true because
  change outputs were present, causing the app to display 0.00 amounts.
2026-02-26 23:06:58 +00:00
Matt Hess 2fd9da6275 Add salvium_wallet_stake_dry_run FFI for pre-broadcast fee estimation 2026-02-26 20:43:44 +00:00
Matt Hess bada091402 Add rustfmt.toml with use_small_heuristics = Max, reformat workspace, big reformat needs commit to avoid change blame 2026-02-26 16:00:34 +00:00
Matt Hess 21e2294285 lint fix 2026-02-26 15:56:33 +00:00
Matt Hess 5db3afca48 Add handle lifecycle safety, wallet_close and daemon_close now wait for active sync to finish before dropping, preventing use-after-free on wallet switch 2026-02-26 15:46:25 +00:00
Matt Hess e177fec00e Fix mutex deadlock in create_subaddress/create_account — read chain_tip while holding existing db lock instead of re-locking in derive_subaddress 2026-02-26 12:53:22 +00:00
Matt Hess eac1b04912 Fix subaddress creation to use chain tip for CARROT detection, add CARROT subaddress derivation 2026-02-26 12:00:29 +00:00
Matt Hess a662f7da2e Fix subaddress creation to use chain tip for CARROT detection, add CARROT subaddress derivation 2026-02-26 11:56:42 +00:00