Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/rs-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ derive_more = { version = "1.0", features = ["from"] }
lru = { version = "0.16.3" }
bip37-bloom-filter = { git = "https://github.com/dashpay/rs-bip37-bloom-filter", branch = "develop" }
zeroize = { version = "1.8", features = ["derive"] }
# Monotonic `Instant` that also works on wasm32 (`performance.now()`).
web-time = "1.1"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.40", features = ["macros", "time", "rt-multi-thread"] }
Expand Down
73 changes: 39 additions & 34 deletions packages/rs-sdk/src/platform/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,41 +186,46 @@ where
let rich = &owned_rich;
let wire = &owned_wire;

let fut = |settings: RequestSettings| async move {
let ExecutionResponse {
address,
retries,
inner: response,
} = wire
.clone()
.execute(sdk, settings)
.await
.map_err(|execution_error| execution_error.inner_into())?;

let object_type = std::any::type_name::<Self>().to_string();
tracing::trace!(request = ?wire, response = ?response, ?address, retries, object_type, "fetched object from platform");

let (object, response_metadata, proof): (Option<Self>, ResponseMetadata, Proof) = sdk
.parse_proof_with_metadata_and_proof::<<Self as Fetch>::Query, Self>(
rich.clone(),
response,
wire.method_name(),
)
.await
.map_err(|e| ExecutionError {
inner: e,
address: Some(address.clone()),
// A proof signed by a quorum the context provider has not seen yet
// refreshes the provider's quorum keys and asks again; see
// `SdkBuilder::with_quorum_refresher`.
let fut = |settings: RequestSettings| {
sdk.with_quorum_refresh(move || async move {
let ExecutionResponse {
address,
retries,
})?;

match object {
Some(item) => Ok((item.into(), response_metadata, proof)),
None => Ok((None, response_metadata, proof)),
}
.map(|x| ExecutionResponse {
inner: x,
address,
retries,
inner: response,
} = wire
.clone()
.execute(sdk, settings)
.await
.map_err(|execution_error| execution_error.inner_into())?;

let object_type = std::any::type_name::<Self>().to_string();
tracing::trace!(request = ?wire, response = ?response, ?address, retries, object_type, "fetched object from platform");

let (object, response_metadata, proof): (Option<Self>, ResponseMetadata, Proof) = sdk
.parse_proof_with_metadata_and_proof::<<Self as Fetch>::Query, Self>(
rich.clone(),
response,
wire.method_name(),
)
.await
.map_err(|e| ExecutionError {
inner: e,
address: Some(address.clone()),
retries,
})?;

match object {
Some(item) => Ok((item.into(), response_metadata, proof)),
None => Ok((None, response_metadata, proof)),
}
.map(|x| ExecutionResponse {
inner: x,
address,
retries,
})
})
};

Expand Down
75 changes: 40 additions & 35 deletions packages/rs-sdk/src/platform/fetch_many.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,42 +224,47 @@ where
let rich = &owned_rich;
let wire = &owned_wire;

let fut = |settings: RequestSettings| async move {
let ExecutionResponse {
address,
retries,
inner: response,
} = wire
.clone()
.execute(sdk, settings)
// A proof signed by a quorum the context provider has not seen yet
// refreshes the provider's quorum keys and asks again; see
// `SdkBuilder::with_quorum_refresher`.
let fut = |settings: RequestSettings| {
sdk.with_quorum_refresh(move || async move {
let ExecutionResponse {
address,
retries,
inner: response,
} = wire
.clone()
.execute(sdk, settings)
.await
.map_err(|e| e.inner_into())?;

let object_type = std::any::type_name::<Self>().to_string();
tracing::trace!(
request = ?wire,
response = ?response,
?address,
retries,
object_type,
"fetched objects from platform"
);

sdk.parse_proof_with_metadata_and_proof::<<Self as FetchMany<K, O>>::Query, O>(
rich.clone(),
response,
wire.method_name(),
)
.await
.map_err(|e| e.inner_into())?;

let object_type = std::any::type_name::<Self>().to_string();
tracing::trace!(
request = ?wire,
response = ?response,
?address,
retries,
object_type,
"fetched objects from platform"
);

sdk.parse_proof_with_metadata_and_proof::<<Self as FetchMany<K, O>>::Query, O>(
rich.clone(),
response,
wire.method_name(),
)
.await
.map_err(|e| ExecutionError {
inner: e,
address: Some(address.clone()),
retries,
})
.map(|(o, metadata, proof)| ExecutionResponse {
inner: (o.unwrap_or_default(), metadata, proof),
retries,
address: address.clone(),
.map_err(|e| ExecutionError {
inner: e,
address: Some(address.clone()),
retries,
})
.map(|(o, metadata, proof)| ExecutionResponse {
inner: (o.unwrap_or_default(), metadata, proof),
retries,
address: address.clone(),
})
})
};

Expand Down
181 changes: 93 additions & 88 deletions packages/rs-sdk/src/platform/transition/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,103 +454,108 @@ impl WaitForOutcome for StateTransition {
};

// prepare a factory that will generate closure which executes actual code
let factory = |request_settings: RequestSettings| async move {
trace!("wait: creating request");
let request = self
.wait_for_state_transition_result_request()
.map_err(|e| ExecutionError {
inner: e,
address: None,
retries: 0,
})?;

trace!("wait: executing request");
let response = request.execute(sdk, request_settings).await.inner_into()?;
trace!("wait: received response");

let grpc_response: &WaitForStateTransitionResultResponse = &response.inner;

// We use match here to have a compilation error if a new version of the response is introduced
let state_transition_broadcast_error = match &grpc_response.version {
Some(wait_for_state_transition_result_response::Version::V0(result)) => {
match &result.result {
Some(wait_for_state_transition_result_response_v0::Result::Error(e)) => {
Some(e)
// A proof signed by a quorum the context provider has not seen yet
// refreshes the provider's quorum keys and asks again; see
// `SdkBuilder::with_quorum_refresher`.
let factory = |request_settings: RequestSettings| {
sdk.with_quorum_refresh(move || async move {
trace!("wait: creating request");
let request = self
.wait_for_state_transition_result_request()
.map_err(|e| ExecutionError {
inner: e,
address: None,
retries: 0,
})?;

trace!("wait: executing request");
let response = request.execute(sdk, request_settings).await.inner_into()?;
trace!("wait: received response");

let grpc_response: &WaitForStateTransitionResultResponse = &response.inner;

// We use match here to have a compilation error if a new version of the response is introduced
let state_transition_broadcast_error = match &grpc_response.version {
Some(wait_for_state_transition_result_response::Version::V0(result)) => {
match &result.result {
Some(wait_for_state_transition_result_response_v0::Result::Error(
e,
)) => Some(e),
_ => None,
}
_ => None,
}
}
None => None,
};

if let Some(e) = state_transition_broadcast_error {
warn!(error=?e, "wait: state transition broadcast error detected");
let state_transition_broadcast_error: StateTransitionBroadcastError =
StateTransitionBroadcastError::try_from(e.clone())
.wrap_to_execution_result(&response)?
.inner;

return Err(Error::from(state_transition_broadcast_error))
.wrap_to_execution_result(&response);
}
None => None,
};

let context_provider = sdk.context_provider().ok_or(ExecutionError {
inner: Error::from(ContextProviderError::Config(
"Context provider not initialized".to_string(),
)),
address: Some(response.address.clone()),
retries: response.retries,
})?;

// Verify through the `FromProof` impl: it runs the GroveDB structural check AND
// `verify_tenderdash_proof` (the quorum BLS signature gate) that authenticates
// `metadata`. The request must be reconstructed to feed that verifier.
let request: BroadcastStateTransitionRequest = self
.broadcast_request_for_state_transition()
.wrap_to_execution_result(&response)?
.inner;
if let Some(e) = state_transition_broadcast_error {
warn!(error=?e, "wait: state transition broadcast error detected");
let state_transition_broadcast_error: StateTransitionBroadcastError =
StateTransitionBroadcastError::try_from(e.clone())
.wrap_to_execution_result(&response)?
.inner;

trace!("wait: verifying proof and quorum signature");
let (maybe_outcome, metadata, _proof) = <StateTransitionProofOutcome as FromProof<
BroadcastStateTransitionRequest,
>>::maybe_from_proof_with_metadata(
request,
grpc_response.clone(),
sdk.network,
sdk.version(),
&context_provider,
)
.map_err(Error::from)
.wrap_to_execution_result(&response)?
.inner;

// The current `FromProof` impl always yields `Some`; this guards only a future
// impl change, so it stays a typed error rather than an unwrap.
let outcome: StateTransitionProofOutcome = maybe_outcome
.ok_or_else(|| {
Error::InvalidProvedResponse(
"state transition result missing from verified proof".to_string(),
)
})
.wrap_to_execution_result(&response)?
.inner;
return Err(Error::from(state_transition_broadcast_error))
.wrap_to_execution_result(&response);
}

// `metadata` is quorum-authenticated only after the verification above, so the
// protocol-version ratchet must run here, never before. A `StaleNode` error is
// retryable and prompts another server.
let _: () = sdk
.verify_response_metadata("wait_for_state_transition_result", &metadata)
let context_provider = sdk.context_provider().ok_or(ExecutionError {
inner: Error::from(ContextProviderError::Config(
"Context provider not initialized".to_string(),
)),
address: Some(response.address.clone()),
retries: response.retries,
})?;

// Verify through the `FromProof` impl: it runs the GroveDB structural check AND
// `verify_tenderdash_proof` (the quorum BLS signature gate) that authenticates
// `metadata`. The request must be reconstructed to feed that verifier.
let request: BroadcastStateTransitionRequest = self
.broadcast_request_for_state_transition()
.wrap_to_execution_result(&response)?
.inner;

trace!("wait: verifying proof and quorum signature");
let (maybe_outcome, metadata, _proof) = <StateTransitionProofOutcome as FromProof<
BroadcastStateTransitionRequest,
>>::maybe_from_proof_with_metadata(
request,
grpc_response.clone(),
sdk.network,
sdk.version(),
&context_provider,
)
.map_err(Error::from)
.wrap_to_execution_result(&response)?
.inner;

trace!("wait: proof verification successful");
trace!(
result_variant = %outcome.result().to_string(),
execution_proved = outcome.is_execution_proved(),
"wait: result variant"
);
// The current `FromProof` impl always yields `Some`; this guards only a future
// impl change, so it stays a typed error rather than an unwrap.
let outcome: StateTransitionProofOutcome = maybe_outcome
.ok_or_else(|| {
Error::InvalidProvedResponse(
"state transition result missing from verified proof".to_string(),
)
})
.wrap_to_execution_result(&response)?
.inner;

// `metadata` is quorum-authenticated only after the verification above, so the
// protocol-version ratchet must run here, never before. A `StaleNode` error is
// retryable and prompts another server.
let _: () = sdk
.verify_response_metadata("wait_for_state_transition_result", &metadata)
.wrap_to_execution_result(&response)?
.inner;

trace!("wait: proof verification successful");
trace!(
result_variant = %outcome.result().to_string(),
execution_proved = outcome.is_execution_proved(),
"wait: result variant"
);

Ok::<_, Error>((outcome, metadata)).wrap_to_execution_result(&response)
Ok::<_, Error>((outcome, metadata)).wrap_to_execution_result(&response)
})
};

let future = retry(sdk.address_list(), retry_settings, factory);
Expand Down
Loading
Loading