Skip to content

Contract creation with history enabled fails proof verification in Evo SDK #2842

@thephez

Description

@thephez

Expected Behavior

When creating a data contract with keeps_history: true using the evo-sdk, the contract should be successfully created and the SDK should receive and verify the proof of creation without errors.

Current Behavior

Contract creation with history enabled succeeds on the network (the state transition is applied and the contract is stored), but the SDK throws a proof verification error:

Something went wrong:
 incorrect proof error: proof did not contain contract with id 9G2kkdnRC3VTBRUUj65mY4XCreXDENNRGpvik1xZ4v7s expected to exist because of state transition (create)

The contract can be seen on platform explorer, proving it exists on the network. Only the client-side proof verification fails.

Possible Solution

The bug is caused by a mismatch between the proof query path and the contract storage path for history-enabled contracts:

Root Cause

1. Proof Generation Bug (packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs:48-49)

The proof generation for DataContractCreate state transitions always uses the non-historical path query:

StateTransition::DataContractCreate(st) => {
    contract_ids_to_non_historical_path_query(&st.modified_data_ids())
}

This should check if the contract keeps history and use the appropriate query:

  • If st.data_contract().config().keeps_history() is true: use contract_ids_to_historical_path_query()
  • Otherwise: use contract_ids_to_non_historical_path_query()

2. Verification Logic Issue (packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs:69-79)

The verification passes None for contract_known_keeps_history:

let (root_hash, contract) = Drive::verify_contract(
    proof,
    None,  // ← Should pass Some(keeps_history) from the state transition
    false,
    true,
    data_contract_create.data_contract().id().into_buffer(),
    platform_version,
)?;

This should be changed to:

Some(data_contract_create.data_contract().config().keeps_history())

Why This Happens

  • Contracts with history are stored at contract_keeping_history_root_path() with time-encoded keys
  • Contracts without history are stored at contract_root_path() with key [0]
  • The proof is generated with the wrong path (always non-historical)
  • The verifier defaults to non-historical when contract_known_keeps_history is None
  • Result: The proof doesn't contain the contract data at the expected path

Proposed Fix

File 1: packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs

Change lines 48-49 from:

StateTransition::DataContractCreate(st) => {
    contract_ids_to_non_historical_path_query(&st.modified_data_ids())
}

To:

StateTransition::DataContractCreate(st) => {
    if st.data_contract().config().keeps_history() {
        contract_ids_to_historical_path_query(&st.modified_data_ids())
    } else {
        contract_ids_to_non_historical_path_query(&st.modified_data_ids())
    }
}

File 2: packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs

Change the Drive::verify_contract call (around line 69-79) from:

let (root_hash, contract) = Drive::verify_contract(
    proof,
    None,
    false,
    true,
    data_contract_create.data_contract().id().into_buffer(),
    platform_version,
)?;

To:

let (root_hash, contract) = Drive::verify_contract(
    proof,
    Some(data_contract_create.data_contract().config().keeps_history()),
    false,
    true,
    data_contract_create.data_contract().id().into_buffer(),
    platform_version,
)?;

Steps to Reproduce

  1. Create a data contract configuration with keeps_history: true in the config
  2. Use evo-sdk or wasm-sdk to create the contract
  3. Call sdk.contracts.create(contract) or equivalent
  4. Observe the proof verification error

Example contract config:

{
  "$format_version": "0",
  "id": "...",
  "ownerId": "...",
  "config": {
    "keepsHistory": true,  // ← This triggers the bug
  },
  "documentSchemas": { ... }
}

Context

This bug prevents the creation of data contracts with history enabled through the SDK, which is a critical feature for contracts that need to track changes over time. While the contracts do get created successfully on the network, the SDK reports an error which breaks the user's workflow and prevents them from proceeding with document operations.

The bug affects:

  • packages/js-evo-sdk - JavaScript/TypeScript SDK
  • packages/wasm-sdk - WebAssembly SDK
  • packages/rs-sdk - Rust SDK (underlying implementation)

All SDK layers are affected because they rely on the proof verification logic in rs-drive.

Your Environment

  • Version used: Latest (2.1.3)
  • Component: rs-drive proof generation and verification
  • Affected SDKs: js-evo-sdk, wasm-sdk, rs-sdk
  • Network: Testnet (and likely mainnet if history-enabled contracts are created)

Related Code Locations

Key files involved:

  1. Proof Generation (Bug): packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs:48-49
  2. Proof Verification (Bug): packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs:69-79
  3. Contract Verification Logic: packages/rs-drive/src/verify/contract/verify_contract/v0/mod.rs:45-88
  4. Contract Storage (Different paths): packages/rs-drive/src/drive/contract/insert/add_contract_to_storage/v0/mod.rs:43-141
  5. Example of Correct Logic: packages/rs-drive/src/drive/contract/prove/prove_contract/v0/mod.rs:30-57 (shows how to detect history-enabled contracts using element.is_basic_tree())

Additional Notes

The existing prove_contract logic in rs-drive already has the correct pattern for detecting history-enabled contracts by checking if the element is a basic tree. This same logic should be applied to the state transition proof generation.

Contracts without history enabled (keepsHistory: false or omitted) work correctly - this bug only affects contracts with keepsHistory: true.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions