Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
835bd80
docs(rs-platform-wallet/e2e): add ID-007 spec entry — identity-auth a…
lklimek May 5, 2026
8325a26
test(rs-platform-wallet/e2e): add ID-007 — pin contract that identity…
lklimek May 5, 2026
318d83b
feat(dashmate): default-on the BIP158 compact-filter index across all…
QuantumExplorer May 5, 2026
37b0f76
test(rs-platform-wallet/e2e): re-enable SPV runtime in framework setup
lklimek May 5, 2026
578a6da
fix(rs-platform-wallet/e2e): bump REGISTRATION_HEADROOM to 150M for c…
lklimek May 5, 2026
75619fd
feat(rs-platform-wallet/e2e): implement CR-003 — BankWallet::send_cor…
lklimek May 5, 2026
75ba17b
docs(rs-platform-wallet/e2e): mark ID-007 status FRAMEWORK-READY
lklimek May 5, 2026
458ee80
refactor(rs-platform-wallet/e2e): factor core_send free fn + prominen…
lklimek May 5, 2026
9df5493
feat(rs-platform-wallet/e2e): wire Core-side cleanup sweep to bank
lklimek May 5, 2026
b856fa8
feat(rs-platform-wallet/e2e): add setup_with_core_funded_test_wallet …
lklimek May 5, 2026
726cee3
test(rs-platform-wallet/e2e): implement CR-003 — asset-lock-funded id…
lklimek May 5, 2026
3bf4275
docs(rs-platform-wallet/e2e): mark CR-003 status STUB — implementatio…
lklimek May 5, 2026
fcb1ac3
feat(rs-platform-wallet): add birth_height_override to wallet creatio…
lklimek May 5, 2026
cbc4302
fix(rs-platform-wallet/e2e): use birth_height=0 for bank wallet so hi…
lklimek May 5, 2026
aead0cc
feat(rs-platform-wallet/e2e): surface bank birth_height in init log
lklimek May 5, 2026
d573cf8
ci: drop '!path' negation patterns from JS package filter (#3592)
QuantumExplorer May 5, 2026
26cec33
fix(swift-example-app): hold one PlatformWalletManager per network (#…
QuantumExplorer May 5, 2026
fc8bce1
feat(swift-example-app): debounced live validation of faucet RPC pass…
QuantumExplorer May 5, 2026
0cacadb
fix(swift-example-app): point regtest+docker SPV at dashmate seed por…
QuantumExplorer May 5, 2026
b9a9293
feat(rs-platform-wallet/e2e): add wait_for_bank_funded framework gate
lklimek May 5, 2026
7760040
feat(rs-platform-wallet/e2e): gate bank operations on cold-cache filt…
lklimek May 5, 2026
717e6c1
docs(rs-platform-wallet/e2e): document cold-cache scan time on ID-007…
lklimek May 5, 2026
d507193
ci: skip @dashevo/wasm-dpp tests on pull_request (nightly-only) (#3593)
QuantumExplorer May 5, 2026
4735964
feat(rs-platform-wallet/e2e): surface dash-spv ManagerError early in …
lklimek May 5, 2026
9c62fd8
docs(rs-platform-wallet/e2e): document mn-list QRInfo stall known issue
lklimek May 5, 2026
32ee2cd
Merge origin/v3.1-dev into feat/rs-platform-wallet-id-007-stub
lklimek May 5, 2026
9df3aa4
docs(rs-platform-wallet/e2e): flip ID-007 to Pass after testnet pass
lklimek May 5, 2026
f3416dc
fix(rs-platform-wallet/e2e): wait_for_core_balance now requires confi…
lklimek May 5, 2026
409d088
docs(rs-platform-wallet/e2e): drop stale BLOCKED parenthetical from I…
lklimek May 5, 2026
1a03112
test(rs-platform-wallet/e2e): invert ID-007 — assert correct behavior…
lklimek May 5, 2026
d368436
docs(rs-platform-wallet/e2e): update ID-007 spec status to FAILING-by…
lklimek May 5, 2026
69ff154
fix(rs-platform-wallet/e2e): CR-003 POST-pin uses credits, not duffs
lklimek May 5, 2026
fa55e64
chore(rs-platform-wallet/e2e): wait_for_core_balance logs which path …
lklimek May 5, 2026
dc186fd
docs(rs-platform-wallet/e2e): flip CR-003 to Pass after units fix ver…
lklimek May 5, 2026
aace4d9
test(rs-platform-wallet/e2e): restore ID-007 — pin intentional not-mo…
lklimek May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions .github/package-filters/js-packages-no-workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,18 @@
- packages/rs-platform-version/**
- packages/rs-platform-versioning/**
- packages/rs-dpp/**
# Exclude Rust test files — they don't affect WASM builds
- '!packages/rs-dpp/**/tests.rs'
- '!packages/rs-dpp/**/tests/**'
- '!packages/rs-dpp/**/test_helpers/**'
- '!packages/rs-dpp/**/test_utils.rs'
- '!packages/rs-dpp/**/test_utils/**'
# NOTE: do not add `!packages/rs-dpp/**/tests.rs` style negation
# patterns here. The dispatcher in `tests.yml` runs these filters
# via `dorny/paths-filter@v3` with the default
# `predicate-quantifier: some`, under which each pattern (including
# `!`-prefixed ones) is OR'd independently. A `!` pattern then
# "matches" every file that doesn't match the negated path — i.e.
# virtually every file in the repo — which trips this filter on
# Swift-only / Rust-only changes and cascades through every other
# filter that aliases `*wasm-dpp` (dapi, dapi-client, wallet-lib,
# dash, dashmate, platform-test-suite, …). Cheaper to over-trigger
# the WASM tests on rs-dpp test-only edits than to mis-trigger
# half the JS test matrix on every PR.

'@dashevo/wasm-dpp2': &wasm-dpp2
- packages/wasm-dpp2/**
Expand Down Expand Up @@ -96,13 +102,10 @@ dashmate:
- packages/rs-platform-version/**
- packages/rs-dash-platform-macros/**
- packages/dapi-grpc/**
# Exclude Rust test files — they don't affect WASM builds
- '!packages/rs-drive-proof-verifier/**/tests.rs'
- '!packages/rs-drive-proof-verifier/**/tests/**'
- '!packages/rs-sdk/**/tests.rs'
- '!packages/rs-sdk/**/tests/**'
- '!packages/rs-dapi-client/**/tests.rs'
- '!packages/rs-dapi-client/**/tests/**'
# NOTE: do not add `!path` negation patterns here — see the long
# explanation on `@dashevo/wasm-dpp` above. Same `dorny/paths-filter@v3`
# + `predicate-quantifier: some` interaction trips this filter on
# unrelated changes and cascades into every consumer (`*wasm-sdk`).

'@dashevo/evo-sdk': &evo-sdk
- packages/js-evo-sdk/**
Expand Down
31 changes: 30 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
outputs:
js-packages: ${{ steps.override.outputs.js-packages || steps.filter-js.outputs.changes }}
js-packages: ${{ steps.override.outputs.js-packages || steps.prune-pr-matrix.outputs.js-packages || steps.filter-js.outputs.changes }}
js-packages-direct: ${{ steps.override.outputs.js-packages-direct || steps.filter-js-direct.outputs.changes }}
rs-packages: ${{ steps.override.outputs.rs-packages || steps.filter-rs.outputs.changes }}
rs-workflows-changed: ${{ steps.filter-rs-workflows.outputs.rs-workflows }}
Expand Down Expand Up @@ -124,6 +124,35 @@ jobs:
- packages/rs-sdk/**
- packages/rs-sdk-ffi/**

# Drop @dashevo/wasm-dpp from the JS test matrix on
# `pull_request` events so the heaviest entry in the matrix
# only runs on the nightly schedule + manual
# `workflow_dispatch`. @dashevo/wasm-dpp2 stays on the PR
# path — it's a separate, lighter package that should be
# exercised on every PR. Cascading consumers
# (`dapi-client`, `wallet-lib`, `dash`, `dashmate`,
# `platform-test-suite`, `evo-sdk`) also keep running on PRs:
# their JS test code exercises behavior on top of wasm-dpp's
# already-built artifact, and `tests-build-js.yml` runs
# `yarn build` across the whole workspace so the wasm-dpp
# output is still compiled and linkable for them — just not
# test-driven on the PR critical path. To force wasm-dpp
# tests on a specific PR, use the `Run workflow`
# (workflow_dispatch) button on the Actions tab — that path
# goes through the `override` step below and runs every JS
# package.
- name: Skip wasm-dpp tests on pull_request (nightly-only)
id: prune-pr-matrix
if: ${{ github.event_name == 'pull_request' }}
run: |
set -eo pipefail
raw='${{ steps.filter-js.outputs.changes }}'
pruned=$(echo "$raw" | jq -c 'map(select(. != "@dashevo/wasm-dpp"))')
echo "js-packages=$pruned" >> "$GITHUB_OUTPUT"
echo "Pruned wasm-dpp from PR matrix:"
echo " before: $raw"
echo " after: $pruned"

- name: Override all outputs for workflow_dispatch
id: override
if: ${{ github.event_name == 'workflow_dispatch' }}
Expand Down
7 changes: 7 additions & 0 deletions packages/dashmate/configs/defaults/getBaseConfigFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ export default function getBaseConfigFactory() {
},
},
indexes: [],
// BIP158 cfilter index + NODE_COMPACT_FILTERS service bit.
// Default-on across every preset so dashmate-managed nodes
// are BIP157 SPV-friendly out of the box. Operators who
// can't spare the cfilter index disk overhead (~10% of
// chain size on mainnet) can flip this off via
// `dashmate config set core.compactFilters false`.
compactFilters: true,
},
platform: {
quorumList: {
Expand Down
8 changes: 8 additions & 0 deletions packages/dashmate/configs/defaults/getLocalConfigFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export default function getLocalConfigFactory(getBaseConfig) {
zmq: {
port: 49998,
},
// Mirrors the `core.compactFilters: true` set on the base
// config; restated explicitly here because the local
// preset is the canonical surface where dev BIP157 SPV
// clients (e.g. the swift-sdk iOS example app pointed at
// `local_seed`) need cfilter sync to work, and we want
// that requirement to survive any future flip of the base
// default.
compactFilters: true,
},
dashmate: {
helper: {
Expand Down
14 changes: 14 additions & 0 deletions packages/dashmate/configs/getConfigFileMigrationsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,20 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs)
const isLocal = options.network === NETWORK_LOCAL || name === 'local';
const isTestnet = options.network === NETWORK_TESTNET || name === 'testnet';

// Flip `core.compactFilters` to true for every config —
// pre-3.1.0 configs predate the field entirely (template
// emitted nothing, dashcore left the cfilter index off),
// so a missing-or-false value here always means
// "inherited the old implicit-off default" rather than
// "user explicitly opted out". The base config now
// ships with this flag on; this backfill brings every
// already-set-up cluster up to that line so the iOS
// BIP157 SPV flow against `local_seed` (and any other
// dashmate node) works without manual editing.
if (options.core) {
options.core.compactFilters = true;
}

if (options.platform?.drive?.tenderdash?.docker
&& defaultConfig.has('platform.drive.tenderdash.docker.image')) {
options.platform.drive.tenderdash.docker.image = defaultConfig
Expand Down
8 changes: 8 additions & 0 deletions packages/dashmate/src/config/configJsonSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,14 @@ export default {
description: 'List of core indexes to enable. `platform.enable`, '
+ ' `core.masternode.enable`, and `core.insight.enabled` add indexes dynamically',
},
compactFilters: {
type: 'boolean',
description: 'Build the BIP158 cfilter index and advertise '
+ 'NODE_COMPACT_FILTERS to peers, so BIP157 SPV clients can sync '
+ 'filter headers + filters from this node. Defaults to true on '
+ 'every preset; flip to false to skip the cfilter index '
+ '(~10% chain-size disk overhead on mainnet).',
},
},
required: ['docker', 'p2p', 'rpc', 'zmq', 'spork', 'masternode', 'miner', 'devnet', 'log',
'indexes', 'insight'],
Expand Down
11 changes: 11 additions & 0 deletions packages/dashmate/templates/core/dash.conf.dot
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ spentindex=1
{{~}}
{{?}}

# BIP157/158 compact block filters. Building the cfilter index plus
# advertising NODE_COMPACT_FILTERS lets BIP157 SPV clients sync
# headers + filters from this node. Off by default (mainnet/testnet
# usage doesn't justify the disk + CPU overhead); enabled on the
# `local` preset where the chain is tiny and the iOS dev flow needs
# filter sync against dashmate's `local_seed` to test the wallet.
{{? it.core.compactFilters }}
blockfilterindex=basic
peerblockfilters=1
{{?}}

# ZeroMQ notifications
zmqpubrawtx=tcp://0.0.0.0:{{=it.core.zmq.port}}
zmqpubrawtxlock=tcp://0.0.0.0:{{=it.core.zmq.port}}
Expand Down
9 changes: 7 additions & 2 deletions packages/rs-platform-wallet-ffi/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed(
};

let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| {
runtime().block_on(manager.create_wallet_from_seed_bytes(network, seed, accounts))
runtime().block_on(manager.create_wallet_from_seed_bytes(network, seed, accounts, None))
});
let result = unwrap_option_or_return!(option);
let wallet = unwrap_result_or_return!(result);
Expand Down Expand Up @@ -139,7 +139,12 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic(
};

let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| {
runtime().block_on(manager.create_wallet_from_mnemonic(mnemonic_str, network, accounts))
runtime().block_on(manager.create_wallet_from_mnemonic(
mnemonic_str,
network,
accounts,
None,
))
});
let result = unwrap_option_or_return!(option);
let wallet = unwrap_result_or_return!(result);
Expand Down
1 change: 1 addition & 0 deletions packages/rs-platform-wallet/examples/basic_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Network::Testnet,
seed_bytes,
WalletAccountCreationOptions::Default,
None,
)
.await?;

Expand Down
62 changes: 48 additions & 14 deletions packages/rs-platform-wallet/src/manager/wallet_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,23 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> {
/// [`parse_mnemonic_any_language`]). For passphrase-only flows or
/// out-of-band seed material, derive the seed externally and use
/// [`Self::create_wallet_from_seed_bytes`].
///
/// `birth_height_override` controls SPV's compact-filter scan
/// window for the new wallet. `None` (the default for fresh
/// wallets) seeds the birth height to SPV's current confirmed
/// header tip, so the scan window is `[H_now, ∞)` — anything
/// funded before init is invisible. `Some(0)` requests a full
/// historical scan from genesis (use sparingly — expensive on
/// long-lived chains, but required when an address may have
/// received funds before the wallet was first registered).
/// `Some(h)` pins the scan start to a specific block height,
/// useful when a known funding block is on record.
pub async fn create_wallet_from_mnemonic(
&self,
mnemonic_phrase: &str,
network: Network,
accounts: WalletAccountCreationOptions,
birth_height_override: Option<u32>,
) -> Result<Arc<PlatformWallet>, PlatformWalletError> {
let mnemonic = parse_mnemonic_any_language(mnemonic_phrase)
.map_err(|e| PlatformWalletError::WalletCreation(format!("Invalid mnemonic: {}", e)))?;
Expand All @@ -70,35 +82,64 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> {
e
))
})?;
self.register_wallet(wallet).await
self.register_wallet(wallet, birth_height_override).await
}

/// Create a PlatformWallet from raw seed bytes, initialize persisted
/// state, register it with the manager and return an `Arc` handle.
///
/// See [`Self::create_wallet_from_mnemonic`] for the
/// `birth_height_override` semantics. `None` keeps the
/// pre-existing behaviour (scan from current SPV tip forward);
/// `Some(h)` is for callers that need to see funding deposited
/// before the wallet was registered (e.g. a long-lived bank
/// address pre-funded with testnet duffs).
pub async fn create_wallet_from_seed_bytes(
&self,
network: Network,
seed_bytes: [u8; 64],
accounts: WalletAccountCreationOptions,
birth_height_override: Option<u32>,
) -> Result<Arc<PlatformWallet>, PlatformWalletError> {
let wallet = Wallet::from_seed_bytes(seed_bytes, network, accounts).map_err(|e| {
PlatformWalletError::WalletCreation(format!(
"Failed to create wallet from seed bytes: {}",
e
))
})?;
self.register_wallet(wallet).await
self.register_wallet(wallet, birth_height_override).await
}

/// Register a pre-built `Wallet` with the manager: insert into the
/// `WalletManager`, build a `PlatformWallet` handle, load persisted
/// state, and return an `Arc` to the managed wallet.
///
/// `birth_height_override` flows through to both the in-memory
/// `ManagedWalletInfo` sync checkpoint and the persisted
/// `WalletMetadataEntry` so the SPV scan window is consistent
/// across restarts. See [`Self::create_wallet_from_mnemonic`] for
/// the contract.
#[allow(clippy::type_complexity)]
async fn register_wallet(
&self,
wallet: Wallet,
birth_height_override: Option<u32>,
) -> Result<Arc<PlatformWallet>, PlatformWalletError> {
let wallet_info = ManagedWalletInfo::from_wallet(&wallet, 0);
// Birth height resolution: explicit override wins; otherwise
// fall back to SPV's confirmed header tip (default for fresh
// wallets — they only need to see funding from now on); 0 if
// SPV isn't running yet.
let birth_height: u32 = match birth_height_override {
Some(h) => h,
None => self
.spv_manager
.sync_progress()
.await
.and_then(|p| p.headers().ok().map(|h| h.tip_height()))
.unwrap_or(0),
};

let wallet_info = ManagedWalletInfo::from_wallet(&wallet, birth_height);

let balance = Arc::new(WalletBalance::new());

Expand Down Expand Up @@ -192,17 +233,10 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> {
// the persister is a best-effort channel, not a source of
// truth in steady state.

// Birth height = SPV's confirmed header tip if SPV is running,
// otherwise 0 (caller can bump it later when SPV catches up).
// 0 means "scan from genesis", which is safe-correct for
// fresh wallets.
let birth_height: u32 = self
.spv_manager
.sync_progress()
.await
.and_then(|p| p.headers().ok().map(|h| h.tip_height()))
.unwrap_or(0);

// `birth_height` was resolved at the top of `register_wallet`
// and seeded into `ManagedWalletInfo`; reuse it here so the
// persisted `WalletMetadataEntry` agrees with the in-memory
// sync checkpoint.
let mut registration_changeset = PlatformWalletChangeSet {
wallet_metadata: Some(WalletMetadataEntry {
network: self.sdk.network,
Expand Down
11 changes: 11 additions & 0 deletions packages/rs-platform-wallet/src/spv/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,17 @@ impl SpvRuntime {
Some(client.sync_progress().await)
}

/// The [`PlatformEventManager`] this runtime dispatches SPV events
/// through. Exposed so consumers (e.g. the e2e framework) can
/// register additional [`crate::events::PlatformEventHandler`]s
/// after construction — for example, to observe
/// `SyncEvent::ManagerError` while waiting for mn-list sync so
/// hard-stalls surface immediately instead of burning the full
/// timeout.
pub fn event_manager(&self) -> &Arc<PlatformEventManager> {
&self.event_manager
}

/// Clear all persisted SPV storage (headers, filters, state).
///
/// The SPV client must be running to perform this operation.
Expand Down
Loading
Loading