Skip to content
Merged
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
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,5 @@ jobs:
run: ../scripts/examples.sh 2>&1 | tee e2e_examples.log

- name: "Detect Inconsistent Results"
# TODO XC-292: Should no longer fail once rounding is in
continue-on-error: true
working-directory: canister/ci
run: cat e2e_examples.log | grep -e Inconsistent
run: cat e2e_examples.log | grep -q -e Inconsistent && exit 1 || exit 0
53 changes: 22 additions & 31 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ assert_matches = "1.5.0"
async-trait = "0.1.88"
candid = "0.10.13"
candid_parser = "0.1.4"
canhttp = { git = "https://github.com/dfinity/evm-rpc-canister", rev = "b976abe57379be7d2649f6a31522d0bb25c5f455" }
canhttp = { git = "https://github.com/dfinity/evm-rpc-canister", ref = "d7261024d16df7fadd1ee60e4cd70798f3cf5577" }
ciborium = "0.2.2"
# Transitive dependency of ic-ed25519
# See https://forum.dfinity.org/t/module-imports-function-wbindgen-describe-from-wbindgen-placeholder-that-is-not-exported-by-the-runtime/11545/8
const_format = "0.2.34"
derive_more = { version = "2.0.1", features = ["from"] }
derive_more = { version = "2.0.1", features = ["from", "into"] }
futures = "0.3.31"
# Transitive dependency of ic-ed25519
# See https://forum.dfinity.org/t/module-imports-function-wbindgen-describe-from-wbindgen-placeholder-that-is-not-exported-by-the-runtime/11545/8
getrandom = { version = "*", default-features = false, features = ["custom"] }
hex = "0.4.3"
http = "1.2.0"
Expand Down
1 change: 0 additions & 1 deletion canister/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ hex = { workspace = true }
http = { workspace = true }
ic-cdk = { workspace = true }
ic-metrics-encoder = { workspace = true }
ic-sha3 = { workspace = true }
ic-stable-structures = { workspace = true }
maplit = { workspace = true }
minicbor = { workspace = true }
Expand Down
27 changes: 26 additions & 1 deletion canister/sol_rpc_canister.did
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ type RpcConfig = record {
responseConsensus : opt ConsensusStrategy;
};


// Rounding error for fetching the current slot from Solana using the JSON-RPC interface, meaning slots will be rounded
// down to the nearest multiple of this error when being fetched.
//
// Solana slot time (around 400ms) is faster than the latency of an HTTPs outcall (which involves every node in the
// subnet making an HTTP request), which is typically around a couple of seconds. It is therefore extremely likely that
// the nodes will receive different results and will fail to reach consensus.
//
// Rounding down the slot received by each node artificially increases the slot time observed by each node and therefore
// increases the probability of reaching consensus. In other words, the higher the rounding error, the more likely it is
// that consensus will be reached (which is required for the HTTPs outcall to be successful), but the older the
// resulting slot will be. Certain use cases, such as sending transactions, require a relatively recent block hash (less
// than 150 blocks old) so that too a large rounding error is not advisable.
//
// The default value of 20 has been experimentally shown to likely achieve consensus while still resulting in a slot
// whose corresponding block is "recent enough" to be used in a Solana transaction.
type RoundingError = nat64;

// Configures how to perform `getSlot` RPC HTTP calls.
type GetSlotRpcConfig = record {
responseSizeEstimate : opt nat64;
responseConsensus : opt ConsensusStrategy;
roundingError : opt RoundingError;
};

// Defines a consensus strategy for combining responses from different providers.
type ConsensusStrategy = variant {
Equality;
Expand Down Expand Up @@ -213,7 +238,7 @@ service : (InstallArgs,) -> {
updateApiKeys : (vec record { SupportedProvider; opt text }) -> ();

// Call the Solana `getSlot` RPC method and return the resulting slot.
getSlot : (RpcSources, opt RpcConfig, opt GetSlotParams) -> (MultiGetSlotResult);
getSlot : (RpcSources, opt GetSlotRpcConfig, opt GetSlotParams) -> (MultiGetSlotResult);

// Make a generic RPC request that sends the given json_rpc_payload.
request : (RpcSources, opt RpcConfig, json_rpc_paylod: text) -> (MultiRequestResult)
Expand Down
11 changes: 10 additions & 1 deletion canister/src/candid_rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
metrics::RpcMethod,
providers::get_provider,
rpc_client::{ReducedResult, SolRpcClient},
types::RoundingError,
util::hostname_from_url,
};
use canhttp::multi::ReductionError;
Expand Down Expand Up @@ -58,8 +59,16 @@ pub struct CandidRpcClient {

impl CandidRpcClient {
pub fn new(source: RpcSources, config: Option<RpcConfig>) -> RpcResult<Self> {
Self::new_with_rounding_error(source, config, None)
}

pub fn new_with_rounding_error(
source: RpcSources,
config: Option<RpcConfig>,
rounding_error: Option<RoundingError>,
) -> RpcResult<Self> {
Ok(Self {
client: SolRpcClient::new(source, config)?,
client: SolRpcClient::new(source, config, rounding_error)?,
})
}

Expand Down
3 changes: 1 addition & 2 deletions canister/src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use crate::{
constants::{COLLATERAL_CYCLES_PER_NODE, CONTENT_TYPE_VALUE},
http::errors::HttpClientError,
logs::Priority,
memory::next_request_id,
memory::{read_state, State},
memory::{next_request_id, read_state, State},
metrics::{MetricRpcHost, MetricRpcMethod},
};
use canhttp::{
Expand Down
15 changes: 12 additions & 3 deletions canister/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ use sol_rpc_canister::{
memory::{mutate_state, read_state},
metrics::encode_metrics,
providers::{get_provider, PROVIDERS},
types::RoundingError,
};
use sol_rpc_types::{
GetSlotParams, MultiRpcResult, RpcAccess, RpcConfig, RpcError, RpcSources,
GetSlotParams, GetSlotRpcConfig, MultiRpcResult, RpcAccess, RpcConfig, RpcError, RpcSources,
SupportedRpcProvider, SupportedRpcProviderId,
};
use solana_clock::Slot;
Expand Down Expand Up @@ -78,10 +79,18 @@ async fn update_api_keys(api_keys: Vec<(SupportedRpcProviderId, Option<String>)>
#[candid_method(rename = "getSlot")]
async fn get_slot(
source: RpcSources,
config: Option<RpcConfig>,
config: Option<GetSlotRpcConfig>,
params: Option<GetSlotParams>,
) -> MultiRpcResult<Slot> {
match CandidRpcClient::new(source, config) {
let rounding_error = config
.as_ref()
.and_then(|c| c.rounding_error)
.map(RoundingError::from);
match CandidRpcClient::new_with_rounding_error(
source,
config.map(RpcConfig::from),
rounding_error,
) {
Ok(client) => client.get_slot(params.unwrap_or_default()).await,
Err(err) => Err(err).into(),
}
Expand Down
6 changes: 4 additions & 2 deletions canister/src/memory/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#[cfg(test)]
mod tests;

use crate::metrics::Metrics;
use crate::types::{ApiKey, OverrideProvider};
use crate::{
metrics::Metrics,
types::{ApiKey, OverrideProvider},
};
use candid::{Deserialize, Principal};
use canhttp::http::json::Id;
use canlog::LogFilter;
Expand Down
12 changes: 10 additions & 2 deletions canister/src/rpc_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
metrics::MetricRpcMethod,
providers::{request_builder, resolve_rpc_provider, Providers},
rpc_client::sol_rpc::{ResponseSizeEstimate, ResponseTransform, HEADER_SIZE_LIMIT},
types::RoundingError,
};
use canhttp::{
http::json::JsonRpcRequest,
Expand All @@ -30,15 +31,22 @@ use tower::ServiceExt;
pub struct SolRpcClient {
providers: Providers,
config: RpcConfig,
rounding_error: RoundingError,
}

impl SolRpcClient {
pub fn new(source: RpcSources, config: Option<RpcConfig>) -> Result<Self, ProviderError> {
pub fn new(
source: RpcSources,
config: Option<RpcConfig>,
rounding_error: Option<RoundingError>,
) -> Result<Self, ProviderError> {
let config = config.unwrap_or_default();
let rounding_error = rounding_error.unwrap_or_default();
let strategy = config.response_consensus.clone().unwrap_or_default();
Ok(Self {
providers: Providers::new(source, strategy)?,
config,
rounding_error,
})
}

Expand Down Expand Up @@ -143,7 +151,7 @@ impl SolRpcClient {
"getSlot",
vec![params],
self.response_size_estimate(1024 + HEADER_SIZE_LIMIT),
&Some(ResponseTransform::GetSlot),
&Some(ResponseTransform::GetSlot(self.rounding_error)),
)
.await
.reduce(self.reduction_strategy())
Expand Down
Loading