Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
576a33f
fix(ci): stabilize platform e2e tests
shumkov Jul 23, 2026
9074263
fix(wallet-lib): initialize SPV chain before account sync
shumkov Jul 23, 2026
eddf6d5
Merge remote-tracking branch 'origin/v4.1-dev' into ci/e2e-tests-green
shumkov Jul 23, 2026
561da40
fix(dapi): preserve block header rejection errors
shumkov Jul 23, 2026
3bfa5d7
fix(spv): honor regtest no-retarget consensus
shumkov Jul 23, 2026
d2a574c
fix(test-suite): address remaining e2e failures
shumkov Jul 23, 2026
b4913ae
fix(e2e): prepare proof context and isolate keychains
shumkov Jul 23, 2026
3339d25
docs(e2e): qualify wallet bootstrap scope
shumkov Jul 23, 2026
7b79086
fix(swift): recover missing CI default keychain
shumkov Jul 23, 2026
ea337ae
fix(ci): allow cold E2E image pulls
shumkov Jul 23, 2026
86bc514
fix(wasm): refresh rotated quorum keys before proofs
shumkov Jul 23, 2026
2ec207a
fix(e2e): wait for propagated state in flaky suites
shumkov Jul 24, 2026
fa95a7f
fix(wallet): authenticate cold header sync from genesis
shumkov Jul 24, 2026
ccae976
fix(swift): recover unreadable CI keychain lists
shumkov Jul 24, 2026
c90eab2
fix(ci): allow cold Dashmate image pulls
shumkov Jul 24, 2026
fc8e1e8
fix(swift): pin CI keychain preference domain
shumkov Jul 24, 2026
259d3ba
fix(e2e): gate tests on observable readiness
shumkov Jul 24, 2026
77f70d1
docs: remove the E2E stabilization working spec
shumkov Jul 28, 2026
2b895b9
fix(test-suite): exclude network-derived creator id from DPNS compari…
shumkov Jul 28, 2026
10913fa
revert(swift): drop CI keychain recovery machinery
shumkov Jul 28, 2026
1214004
fix(wasm-sdk): keep broadcast results when a contract refresh fails
shumkov Jul 28, 2026
09dacd5
fix(test-suite): require an execution proof wherever one exists
shumkov Jul 28, 2026
a031579
fix(wallet-lib): surface and contain the header sync start change
shumkov Jul 28, 2026
2139520
fix(e2e): tighten header stream cancellation and test assertions
shumkov Jul 28, 2026
b8f5e69
fix(sdk): bound masternode discovery requests on wasm
shumkov Jul 28, 2026
576ab34
fix(test-suite): report WASM verifier errors as plain Errors
shumkov Jul 28, 2026
82f03b6
fix(wasm-sdk): fetch referenced contracts the SDK cannot resolve
shumkov Jul 28, 2026
6e33942
fix(wasm-sdk): scope the system contract import to the tests
shumkov Jul 28, 2026
95948cd
style(wasm-sdk): wrap an over-width line in the contract fetch test
shumkov Jul 28, 2026
88f65d3
fix(wasm-sdk): take the platform version from the inner SDK
shumkov Jul 28, 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
4 changes: 2 additions & 2 deletions .github/actions/local-network/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ runs:
${{ env.HOME }}/.dashmate
**/.env
dashmate_volumes_dump
key: local-network-volumes/${{ steps.dashmate-fingerprint.outputs.sha }}
key: local-network-volumes/${{ steps.dashmate-fingerprint.outputs.sha }}/${{ hashFiles('.github/actions/local-network/action.yaml', 'packages/dapi/.env.example', 'packages/rs-drive-abci/.env.local', 'scripts/setup_local_network.sh', 'scripts/configure_test_suite.sh', 'scripts/configure_dotenv.sh', 'scripts/dashmate/volumes/**') }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Restore dashmate volumes
run: ./scripts/dashmate/volumes/restore.sh
Expand Down Expand Up @@ -69,7 +69,7 @@ runs:
${{ env.HOME }}/.dashmate
**/.env
dashmate_volumes_dump
key: local-network-volumes/${{ steps.dashmate-fingerprint.outputs.sha }}
key: ${{ steps.local-network-data.outputs.cache-primary-key }}
if: steps.local-network-data.outputs.cache-hit != 'true'

- name: Configure pre-built docker images
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/tests-dashmate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ jobs:
dashmate-test:
name: Run ${{ inputs.name }} tests
runs-on: ubuntu-24.04
timeout-minutes: 15
# Cold helper-image pulls can consume most of 15 minutes before tests start.
timeout-minutes: 30
steps:
- name: Check out repo
uses: actions/checkout@v4
Expand Down Expand Up @@ -90,7 +91,7 @@ jobs:
${{ env.HOME }}/.dashmate
**/.env
dashmate_volumes_dump
key: local-network-volumes/${{ steps.dashmate-fingerprint.outputs.sha }}
key: local-network-volumes/${{ steps.dashmate-fingerprint.outputs.sha }}/${{ hashFiles('.github/actions/local-network/action.yaml', 'packages/dapi/.env.example', 'packages/rs-drive-abci/.env.local', 'scripts/setup_local_network.sh', 'scripts/configure_test_suite.sh', 'scripts/configure_dotenv.sh', 'scripts/dashmate/volumes/**') }}
if: inputs.restore_local_network_data == true

- name: Restore dashmate volumes
Expand All @@ -110,7 +111,7 @@ jobs:
env:
DEBUG: 1
DASHMATE_E2E_TESTS_SKIP_IMAGE_BUILD: true
if: steps.local-network-data.outputs.cache-hit == 'false'
if: steps.local-network-data.outputs.cache-hit != 'true'

- name: Show Docker logs
if: ${{ failure() }}
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/tests-packges-functional.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ jobs:
test-functional:
name: Run functional tests
runs-on: ubuntu-24.04
timeout-minutes: 15
# Cold helper-image pulls can consume most of 15 minutes before tests start.
timeout-minutes: 30
env:
ECR_HOST: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com
steps:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/tests-test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ jobs:
test-suite:
name: Run ${{ inputs.name }}
runs-on: ubuntu-24.04
timeout-minutes: 15
# Cold helper-image pulls can consume most of 15 minutes before tests start.
timeout-minutes: 30
env:
ECR_HOST: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com
steps:
Expand Down
52 changes: 44 additions & 8 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
doctests-changed: ${{ steps.override.outputs.doctests-changed || steps.filter-doctests.outputs.doctests-changed }}
swift-sdk-changed: ${{ steps.override.outputs.swift-sdk-changed || steps.filter-swift-sdk.outputs.swift-sdk-changed }}
version-changed: ${{ steps.override.outputs.version-changed || steps.filter-version.outputs.version-changed }}
e2e-tests-changed: ${{ steps.override.outputs.e2e-tests-changed || steps.filter-e2e.outputs.e2e-tests-changed }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -89,6 +90,37 @@ jobs:
- .github/workflows/tests.yml
- .github/scripts/check-wallet-closure.py

- uses: dorny/paths-filter@v4
id: filter-e2e
if: ${{ github.event_name != 'workflow_dispatch' }}
with:
filters: |
e2e-tests-changed:
- packages/platform-test-suite/**
- packages/dashmate/**
- packages/js-dapi-client/**
- packages/js-dash-sdk/**
- packages/wallet-lib/**
- packages/wasm-sdk/**
- packages/dapi/.env.example
- packages/rs-drive-abci/.env.local
- .github/actions/aws_ecr_login/**
- .github/actions/docker/**
- .github/actions/local-network/**
- .github/actions/nodejs/**
- .github/actions/rust/**
- .github/actions/sccache/**
- .github/workflows/tests.yml
- .github/workflows/tests-build-js.yml
- .github/workflows/tests-build-image.yml
- .github/workflows/tests-test-suite.yml
- .github/workflows/tests-packges-functional.yml
- .github/workflows/tests-dashmate.yml
- scripts/setup_local_network.sh
- scripts/configure_test_suite.sh
- scripts/configure_dotenv.sh
- scripts/dashmate/volumes/**

- name: Check for platform version change
id: filter-version
if: ${{ github.event_name != 'workflow_dispatch' }}
Expand Down Expand Up @@ -187,6 +219,7 @@ jobs:
with:
filters: |
swift-sdk-changed:
- .github/workflows/swift-sdk-build.yml
- packages/swift-sdk/**
- packages/dapi-grpc/**
- packages/dashpay-contract/**
Expand Down Expand Up @@ -265,12 +298,13 @@ jobs:
echo 'doctests-changed=true' >> "$GITHUB_OUTPUT"
echo 'swift-sdk-changed=true' >> "$GITHUB_OUTPUT"
echo 'version-changed=true' >> "$GITHUB_OUTPUT"
echo 'e2e-tests-changed=true' >> "$GITHUB_OUTPUT"

build-js:
name: Build JS packages
needs:
- changes
if: ${{ needs.changes.outputs.js-packages != '[]' || needs.changes.outputs.version-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
if: ${{ needs.changes.outputs.js-packages != '[]' || needs.changes.outputs.version-changed == 'true' || needs.changes.outputs.e2e-tests-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
secrets: inherit
uses: ./.github/workflows/tests-build-js.yml

Expand All @@ -279,10 +313,10 @@ jobs:
needs:
- check-secrets
- changes
# Build Docker images on version change, nightly schedule, or manual dispatch
# Build Docker images for platform E2E changes, version changes, nightly schedules, or manual dispatches
if: >-
needs.check-secrets.outputs.has_ecr == 'true' &&
(needs.changes.outputs.version-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
(needs.changes.outputs.version-changed == 'true' || needs.changes.outputs.e2e-tests-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
secrets: inherit
strategy:
fail-fast: false
Expand Down Expand Up @@ -374,8 +408,9 @@ jobs:
uses: ./.github/workflows/tests-js-package.yml
with:
package: ${{ matrix.js-package }}
test-command: ${{ matrix.js-package == 'dashmate' && 'test:unit' || 'test' }}
skip-tests: ${{ contains(matrix.js-package, 'platform-test-suite') }}
# The platform test suite's default command drives a live network, so it
# runs from the E2E jobs. Its unit tests need nothing and run here.
test-command: ${{ (matrix.js-package == 'dashmate' || contains(matrix.js-package, 'platform-test-suite')) && 'test:unit' || 'test' }}
direct-packages: ${{ needs.changes.outputs.js-packages-direct }}

js-deps-versions:
Expand Down Expand Up @@ -426,8 +461,9 @@ jobs:
if: >-
always() &&
needs.check-secrets.outputs.has_ecr == 'true' &&
needs.build-js.result == 'success' &&
needs.build-images.result == 'success' &&
(needs.changes.outputs.version-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
(needs.changes.outputs.version-changed == 'true' || needs.changes.outputs.e2e-tests-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test-suite:
name: Test Suite
Expand All @@ -442,7 +478,7 @@ jobs:
needs.check-secrets.outputs.has_ecr == 'true' &&
needs.build-js.result == 'success' &&
needs.build-images.result == 'success' &&
(needs.changes.outputs.version-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
(needs.changes.outputs.version-changed == 'true' || needs.changes.outputs.e2e-tests-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -479,5 +515,5 @@ jobs:
needs.check-secrets.outputs.has_ecr == 'true' &&
needs.build-js.result == 'success' &&
needs.build-images.result == 'success' &&
(needs.changes.outputs.version-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
(needs.changes.outputs.version-changed == 'true' || needs.changes.outputs.e2e-tests-changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
uses: ./.github/workflows/tests-packges-functional.yml
101 changes: 89 additions & 12 deletions packages/bench-suite/lib/client/createPlatformProofVerifier.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
const DAPIAddress = require('@dashevo/dapi-client/lib/dapiAddressProvider/DAPIAddress');

/**
* `WasmSdkError.name` for a transition family whose proof cannot bind the
* execution of one specific transition.
*
* @type {string}
*/
const EXECUTION_NOT_PROVED = 'ExecutionNotProved';

/**
* Read the error's kind without assuming it survives the WASM boundary.
*
* @param {*} error
* @returns {string|undefined}
*/
function readErrorName(error) {
try {
return error && error.name;
} catch (readError) {
// A WASM error object whose memory is already released throws on access.
return undefined;
}
}

/**
* Convert a WASM SDK error into a plain `Error`.
*
* These are wasm-bindgen class instances rather than `Error`s, and they carry
* their kind and message on prototype getters. Mocha runs the suite in
* parallel workers and serializes a failure by copying the error's own
* properties, so an unconverted one arrives with nothing in it and the run
* reports a test that failed for no stated reason.
*
* @param {*} error
* @returns {Error}
*/
function toReportableError(error) {
if (error instanceof Error) {
return error;
}

const name = readErrorName(error) || 'UnknownError';
let message;
try {
message = (error && error.message) || String(error);
} catch (readError) {
message = 'error details are unavailable';
}

const reportable = new Error(`${name}: ${message}`);
reportable.name = name;

return reportable;
}

/**
* Shared EvoSDK instance. One per process: the verifier is stateless and the
* underlying WASM SDK multiplexes concurrent requests.
Expand Down Expand Up @@ -134,17 +188,25 @@ async function getEvoSdkForNetwork(callNetwork) {

/**
* Create an `IPlatformProofVerifier` for `Dash.Client`, backed by the
* Rust/WASM SDK, which authenticates every result end to end: GroveDB proof
* verification plus the Tenderdash quorum signature over the root hash.
* Rust/WASM SDK. Its proved re-query authenticates an execution result or
* affected-state snapshot end to end: GroveDB proof verification plus the
* Tenderdash quorum signature over the root hash.
*
* Verification re-queries Platform through the WASM SDK's proved paths rather
* than re-checking the exact bytes the JS transport received: the returned
* data (and the absence of a consensus error) is quorum-authenticated, so the
* unverified DAPI response is never the source of truth.
* than re-checking the exact bytes the JS transport received. Execution proof
* is required wherever the transition family can produce one. The families
* that cannot fall back to a height-pinned snapshot of the affected state,
* which is not evidence that this exact transition executed; the caller
* handles consensus errors from the original response before invoking this
* verifier.
*
* @param {Object} [dependencies]
* @param {Function} [dependencies.getEvoSdkForNetwork]
* @returns {Object} IPlatformProofVerifier
*/
function createPlatformProofVerifier() {
function createPlatformProofVerifier({
getEvoSdkForNetwork: loadEvoSdkForNetwork = getEvoSdkForNetwork,
} = {}) {
return {
/**
* @param {Object} input
Expand All @@ -153,16 +215,31 @@ function createPlatformProofVerifier() {
* @returns {Promise<void>}
*/
async verifyStateTransitionResult({ serializedStateTransition, network }) {
const { evo, sdk } = await getEvoSdkForNetwork(network);
const { evo, sdk } = await loadEvoSdkForNetwork(network);

const stateTransition = evo.StateTransition.fromBytes(
new Uint8Array(serializedStateTransition),
);

// Waits on the proved endpoint and verifies the execution proof and
// quorum signature inside the Rust SDK; throws unless the transition
// was executed (or yielded a consensus error, which also throws).
await sdk.stateTransitions.waitForResponse(stateTransition);
try {
await sdk.stateTransitions.waitForResponse(stateTransition);
} catch (error) {
// Balance top-ups, credit transfers and withdrawals, address funds
// movements, shields and no-history token operations have no proof
// that binds one specific transition, so the SDK reports that the
// execution was not proved rather than that anything failed. Their
// authenticated affected-state snapshot is the strongest result
// available; every other family keeps failing closed here.
if (readErrorName(error) !== EXECUTION_NOT_PROVED) {
throw toReportableError(error);
}

try {
await sdk.stateTransitions.waitForAffectedState(stateTransition);
} catch (affectedStateError) {
throw toReportableError(affectedStateError);
}
}
},

/**
Expand All @@ -183,7 +260,7 @@ function createPlatformProofVerifier() {
);
}

const { sdk } = await getEvoSdkForNetwork(network);
const { sdk } = await loadEvoSdkForNetwork(network);

const history = await sdk.contracts.getHistory({
dataContractId: new Uint8Array(contractId),
Expand Down
6 changes: 6 additions & 0 deletions packages/dash-spv/lib/consensus.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ function isValidBlockHeader(newHeader, previousHeaders, network = 'mainnet') {
return false;
}

// Dash Core disables difficulty retargeting on regtest.
if (network === 'regtest' && normalizedPreviousHeaders.length > 0) {
const previousHeader = normalizedPreviousHeaders[normalizedPreviousHeaders.length - 1];
return normalizedHeader.bits === previousHeader.bits;
}

// A trusted checkpoint may contain fewer than a full DGW window. Proof of
// work, the network pow limit, and timestamp rules still apply immediately;
// exact difficulty validation begins as soon as the required history exists.
Expand Down
21 changes: 21 additions & 0 deletions packages/dash-spv/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,27 @@ describe('Block header consensus validation', () => {
consensus.isValidBlockHeader(nextHeader, wrongHistory, 'mainnet')
.should.equal(false);
});

it('should accept fast-mined regtest headers when Core retargeting is disabled', () => {
const previousHeaders = [new Blockchain('regtest').genesis];

while (previousHeaders.length < 24) {
previousHeaders.push(utils.createBlock(previousHeaders.at(-1), 0x207fffff));
}

const nextHeader = utils.createBlock(previousHeaders.at(-1), 0x207fffff);

consensus.isValidBlockHeader(nextHeader, previousHeaders, 'regtest')
.should.equal(true);
});

it('should reject a changed regtest target before the DGW history window', () => {
const { genesis } = new Blockchain('regtest');
const changedTargetHeader = utils.createBlock(genesis, 0x2070ffff);

consensus.isValidBlockHeader(changedTargetHeader, [genesis], 'regtest')
.should.equal(false);
});
});

describe('SPV-DASH (addHeaders) add many headers for testnet', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/dashmate/src/createDIContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import waitForConfirmations from './core/waitForConfirmations.js';
import generateBlsKeys from './core/generateBlsKeys.js';
import activateCoreSpork from './core/activateCoreSpork.js';
import waitForCorePeersConnected from './core/waitForCorePeersConnected.js';
import waitForNodesToHaveTheSameHeight from './core/waitForNodesToHaveTheSameHeight.js';

import createNewAddress from './core/wallet/createNewAddress.js';
import generateBlocks from './core/wallet/generateBlocks.js';
Expand Down Expand Up @@ -256,6 +257,7 @@ export default async function createDIContainer(options = {}) {
generateBlsKeys: asValue(generateBlsKeys),
activateCoreSpork: asValue(activateCoreSpork),
waitForCorePeersConnected: asValue(waitForCorePeersConnected),
waitForNodesToHaveTheSameHeight: asValue(waitForNodesToHaveTheSameHeight),
});

/**
Expand Down
Loading
Loading