|
|
@@ -1,8 +1,5 @@
|
|
|
use {
|
|
|
- crate::{
|
|
|
- chain::reader::{BlockNumber, BlockStatus, PulseReader},
|
|
|
- state::HashChainState,
|
|
|
- },
|
|
|
+ crate::chain::reader::{BlockStatus, PulseReader},
|
|
|
anyhow::Result,
|
|
|
axum::{
|
|
|
body::Body,
|
|
|
@@ -21,14 +18,14 @@ use {
|
|
|
tokio::sync::RwLock,
|
|
|
url::Url,
|
|
|
};
|
|
|
-pub use {chain_ids::*, index::*, live::*, metrics::*, ready::*, price_updates::*};
|
|
|
+pub use {chain_ids::*, index::*, live::*, metrics::*, price_updates::*, ready::*};
|
|
|
|
|
|
mod chain_ids;
|
|
|
mod index;
|
|
|
mod live;
|
|
|
mod metrics;
|
|
|
-mod ready;
|
|
|
mod price_updates;
|
|
|
+mod ready;
|
|
|
|
|
|
pub type ChainId = String;
|
|
|
|
|
|
@@ -75,20 +72,15 @@ impl ApiState {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// The state of the randomness service for a single blockchain.
|
|
|
+/// The state of the price update service for a single blockchain.
|
|
|
#[derive(Clone)]
|
|
|
pub struct BlockchainState {
|
|
|
/// The chain id for this blockchain, useful for logging
|
|
|
pub id: ChainId,
|
|
|
- /// The hash chain(s) required to serve random numbers for this blockchain
|
|
|
- pub state: Arc<HashChainState>,
|
|
|
/// The contract that the server is fulfilling requests for.
|
|
|
pub contract: Arc<dyn PulseReader>,
|
|
|
/// The address of the provider that this server is operating for.
|
|
|
pub provider_address: Address,
|
|
|
- /// The server will wait for this many block confirmations of a request before revealing
|
|
|
- /// the random number.
|
|
|
- pub reveal_delay_blocks: BlockNumber,
|
|
|
/// The BlockStatus of the block that is considered to be confirmed on the blockchain.
|
|
|
/// For eg., Finalized, Safe
|
|
|
pub confirmed_block_status: BlockStatus,
|
|
|
@@ -99,14 +91,11 @@ pub enum RestError {
|
|
|
InvalidSequenceNumber,
|
|
|
/// The caller passed an unsupported chain id
|
|
|
InvalidChainId,
|
|
|
- /// The caller requested a random value that can't currently be revealed (because it
|
|
|
- /// hasn't been committed to on-chain)
|
|
|
+ /// The caller requested price updates that can't currently be provided (because they
|
|
|
+ /// haven't been committed to on-chain)
|
|
|
NoPendingRequest,
|
|
|
- /// The request exists, but the server is waiting for more confirmations (more blocks
|
|
|
- /// to be mined) before revealing the random number.
|
|
|
- PendingConfirmation,
|
|
|
/// The server cannot currently communicate with the blockchain, so is not able to verify
|
|
|
- /// which random values have been requested.
|
|
|
+ /// which price updates have been requested.
|
|
|
TemporarilyUnavailable,
|
|
|
/// A catch-all error for all other types of errors that could occur during processing.
|
|
|
Unknown,
|
|
|
@@ -125,13 +114,8 @@ impl IntoResponse for RestError {
|
|
|
}
|
|
|
RestError::NoPendingRequest => (
|
|
|
StatusCode::FORBIDDEN,
|
|
|
- "The request with the given sequence number has not been made yet, or the random value has already been revealed on chain.",
|
|
|
+ "The request with the given sequence number has not been made yet, or the price updates have already been provided on chain.",
|
|
|
).into_response(),
|
|
|
- RestError::PendingConfirmation => (
|
|
|
- StatusCode::FORBIDDEN,
|
|
|
- "The request needs additional confirmations before the random value can be retrieved. Try your request again later.",
|
|
|
- )
|
|
|
- .into_response(),
|
|
|
RestError::TemporarilyUnavailable => (
|
|
|
StatusCode::SERVICE_UNAVAILABLE,
|
|
|
"This service is temporarily unavailable",
|
|
|
@@ -154,8 +138,8 @@ pub fn routes(state: ApiState) -> Router<(), Body> {
|
|
|
.route("/ready", get(ready))
|
|
|
.route("/v1/chains", get(chain_ids))
|
|
|
.route(
|
|
|
- "/v1/chains/:chain_id/revelations/:sequence",
|
|
|
- get(revelation),
|
|
|
+ "/v1/chains/:chain_id/price-updates/:sequence",
|
|
|
+ get(price_update),
|
|
|
)
|
|
|
.with_state(state)
|
|
|
}
|
|
|
@@ -174,13 +158,12 @@ pub fn get_register_uri(base_uri: &str, chain_id: &str) -> Result<String> {
|
|
|
mod test {
|
|
|
use {
|
|
|
crate::{
|
|
|
- api::{self, ApiState, BinaryEncoding, Blob, BlockchainState, GetRandomValueResponse},
|
|
|
+ api::{self, ApiState, BlockchainState},
|
|
|
chain::reader::{mock::MockPulseReader, BlockStatus},
|
|
|
- state::{HashChainState, PebbleHashChain},
|
|
|
},
|
|
|
axum::http::StatusCode,
|
|
|
axum_test::{TestResponse, TestServer},
|
|
|
- ethers::prelude::Address,
|
|
|
+ ethers::prelude::{Address, U256},
|
|
|
lazy_static::lazy_static,
|
|
|
prometheus_client::registry::Registry,
|
|
|
std::{collections::HashMap, sync::Arc},
|
|
|
@@ -190,16 +173,6 @@ mod test {
|
|
|
const PROVIDER: Address = Address::zero();
|
|
|
lazy_static! {
|
|
|
static ref OTHER_PROVIDER: Address = Address::from_low_u64_be(1);
|
|
|
- // Note: these chains are immutable. They are wrapped in Arc because we need Arcs to
|
|
|
- // initialize the BlockchainStates below, but they aren't cloneable (nor do they need to be cloned).
|
|
|
- static ref ETH_CHAIN: Arc<HashChainState> = Arc::new(HashChainState::from_chain_at_offset(
|
|
|
- 0,
|
|
|
- PebbleHashChain::new([0u8; 32], 1000, 1),
|
|
|
- ));
|
|
|
- static ref AVAX_CHAIN: Arc<HashChainState> = Arc::new(HashChainState::from_chain_at_offset(
|
|
|
- 100,
|
|
|
- PebbleHashChain::new([1u8; 32], 1000, 1),
|
|
|
- ));
|
|
|
}
|
|
|
|
|
|
async fn test_server() -> (TestServer, Arc<MockPulseReader>, Arc<MockPulseReader>) {
|
|
|
@@ -207,10 +180,8 @@ mod test {
|
|
|
|
|
|
let eth_state = BlockchainState {
|
|
|
id: "ethereum".into(),
|
|
|
- state: ETH_CHAIN.clone(),
|
|
|
contract: eth_read.clone(),
|
|
|
provider_address: PROVIDER,
|
|
|
- reveal_delay_blocks: 1,
|
|
|
confirmed_block_status: BlockStatus::Latest,
|
|
|
};
|
|
|
|
|
|
@@ -220,10 +191,8 @@ mod test {
|
|
|
|
|
|
let avax_state = BlockchainState {
|
|
|
id: "avalanche".into(),
|
|
|
- state: AVAX_CHAIN.clone(),
|
|
|
contract: avax_read.clone(),
|
|
|
provider_address: PROVIDER,
|
|
|
- reveal_delay_blocks: 2,
|
|
|
confirmed_block_status: BlockStatus::Latest,
|
|
|
};
|
|
|
|
|
|
@@ -248,105 +217,145 @@ mod test {
|
|
|
}
|
|
|
|
|
|
#[tokio::test]
|
|
|
- async fn test_revelation() {
|
|
|
+ async fn test_price_updates() {
|
|
|
let (server, eth_contract, avax_contract) = test_server().await;
|
|
|
+ let empty_price_ids: Vec<[u8; 32]> = vec![];
|
|
|
+ let callback_gas_limit = U256::from(100000);
|
|
|
+ let publish_time = U256::from(1000);
|
|
|
|
|
|
- // Can't access a revelation if it hasn't been requested
|
|
|
+ // Can't access price updates if they haven't been requested
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/0",
|
|
|
+ "/v1/chains/ethereum/price-updates/0",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
|
|
|
- // Once someone requests the number, then it is accessible
|
|
|
- eth_contract.insert(PROVIDER, 0, 1, false);
|
|
|
- let response =
|
|
|
- get_and_assert_status(&server, "/v1/chains/ethereum/revelations/0", StatusCode::OK)
|
|
|
- .await;
|
|
|
- response.assert_json(&GetRandomValueResponse {
|
|
|
- value: Blob::new(BinaryEncoding::Hex, ETH_CHAIN.reveal(0).unwrap()),
|
|
|
- });
|
|
|
+ // Once someone requests the price updates, then they are accessible
|
|
|
+ eth_contract.insert(
|
|
|
+ PROVIDER,
|
|
|
+ 0,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+ get_and_assert_status(
|
|
|
+ &server,
|
|
|
+ "/v1/chains/ethereum/price-updates/0",
|
|
|
+ StatusCode::OK,
|
|
|
+ )
|
|
|
+ .await;
|
|
|
|
|
|
// Each chain and provider has its own set of requests
|
|
|
- eth_contract.insert(PROVIDER, 100, 1, false);
|
|
|
- eth_contract.insert(*OTHER_PROVIDER, 101, 1, false);
|
|
|
- eth_contract.insert(PROVIDER, 102, 1, false);
|
|
|
- avax_contract.insert(PROVIDER, 102, 1, false);
|
|
|
- avax_contract.insert(PROVIDER, 103, 1, false);
|
|
|
- avax_contract.insert(*OTHER_PROVIDER, 104, 1, false);
|
|
|
-
|
|
|
- let response = get_and_assert_status(
|
|
|
+ eth_contract.insert(
|
|
|
+ PROVIDER,
|
|
|
+ 100,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+ eth_contract.insert(
|
|
|
+ *OTHER_PROVIDER,
|
|
|
+ 101,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+ eth_contract.insert(
|
|
|
+ PROVIDER,
|
|
|
+ 102,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+ avax_contract.insert(
|
|
|
+ PROVIDER,
|
|
|
+ 102,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+ avax_contract.insert(
|
|
|
+ PROVIDER,
|
|
|
+ 103,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+ avax_contract.insert(
|
|
|
+ *OTHER_PROVIDER,
|
|
|
+ 104,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+
|
|
|
+ get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/100",
|
|
|
+ "/v1/chains/ethereum/price-updates/100",
|
|
|
StatusCode::OK,
|
|
|
)
|
|
|
.await;
|
|
|
- response.assert_json(&GetRandomValueResponse {
|
|
|
- value: Blob::new(BinaryEncoding::Hex, ETH_CHAIN.reveal(100).unwrap()),
|
|
|
- });
|
|
|
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/101",
|
|
|
+ "/v1/chains/ethereum/price-updates/101",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
- let response = get_and_assert_status(
|
|
|
+
|
|
|
+ get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/102",
|
|
|
+ "/v1/chains/ethereum/price-updates/102",
|
|
|
StatusCode::OK,
|
|
|
)
|
|
|
.await;
|
|
|
- response.assert_json(&GetRandomValueResponse {
|
|
|
- value: Blob::new(BinaryEncoding::Hex, ETH_CHAIN.reveal(102).unwrap()),
|
|
|
- });
|
|
|
+
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/103",
|
|
|
+ "/v1/chains/ethereum/price-updates/103",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
+
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/104",
|
|
|
+ "/v1/chains/ethereum/price-updates/104",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/100",
|
|
|
+ "/v1/chains/avalanche/price-updates/100",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
+
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/101",
|
|
|
+ "/v1/chains/avalanche/price-updates/101",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
- let response = get_and_assert_status(
|
|
|
+
|
|
|
+ get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/102",
|
|
|
+ "/v1/chains/avalanche/price-updates/102",
|
|
|
StatusCode::OK,
|
|
|
)
|
|
|
.await;
|
|
|
- response.assert_json(&GetRandomValueResponse {
|
|
|
- value: Blob::new(BinaryEncoding::Hex, AVAX_CHAIN.reveal(102).unwrap()),
|
|
|
- });
|
|
|
- let response = get_and_assert_status(
|
|
|
+
|
|
|
+ get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/103",
|
|
|
+ "/v1/chains/avalanche/price-updates/103",
|
|
|
StatusCode::OK,
|
|
|
)
|
|
|
.await;
|
|
|
- response.assert_json(&GetRandomValueResponse {
|
|
|
- value: Blob::new(BinaryEncoding::Hex, AVAX_CHAIN.reveal(103).unwrap()),
|
|
|
- });
|
|
|
+
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/104",
|
|
|
+ "/v1/chains/avalanche/price-updates/104",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
@@ -354,97 +363,87 @@ mod test {
|
|
|
// Bad chain ids fail
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/not_a_chain/revelations/0",
|
|
|
+ "/v1/chains/not_a_chain/price-updates/0",
|
|
|
StatusCode::BAD_REQUEST,
|
|
|
)
|
|
|
.await;
|
|
|
-
|
|
|
- // Requesting a number that has a request, but isn't in the HashChainState also fails.
|
|
|
- // (Note that this shouldn't happen in normal operation)
|
|
|
- get_and_assert_status(
|
|
|
- &server,
|
|
|
- "/v1/chains/avalanche/revelations/99",
|
|
|
- StatusCode::FORBIDDEN,
|
|
|
- )
|
|
|
- .await;
|
|
|
- avax_contract.insert(PROVIDER, 99, 1, false);
|
|
|
- get_and_assert_status(
|
|
|
- &server,
|
|
|
- "/v1/chains/avalanche/revelations/99",
|
|
|
- StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
- )
|
|
|
- .await;
|
|
|
}
|
|
|
|
|
|
#[tokio::test]
|
|
|
- async fn test_revelation_confirmation_delay() {
|
|
|
+ async fn test_price_update_confirmation_delay() {
|
|
|
let (server, eth_contract, avax_contract) = test_server().await;
|
|
|
+ let empty_price_ids: Vec<[u8; 32]> = vec![];
|
|
|
+ let callback_gas_limit = U256::from(100000);
|
|
|
+ let publish_time = U256::from(1000);
|
|
|
|
|
|
- eth_contract.insert(PROVIDER, 0, 10, false);
|
|
|
- eth_contract.insert(PROVIDER, 1, 11, false);
|
|
|
- eth_contract.insert(PROVIDER, 2, 12, false);
|
|
|
-
|
|
|
- avax_contract.insert(PROVIDER, 100, 10, false);
|
|
|
- avax_contract.insert(PROVIDER, 101, 11, false);
|
|
|
-
|
|
|
- eth_contract.set_block_number(10);
|
|
|
- avax_contract.set_block_number(10);
|
|
|
-
|
|
|
+ // No requests yet, so all requests should be forbidden
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/0",
|
|
|
+ "/v1/chains/ethereum/price-updates/0",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/100",
|
|
|
+ "/v1/chains/avalanche/price-updates/100",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
|
|
|
- eth_contract.set_block_number(11);
|
|
|
- avax_contract.set_block_number(11);
|
|
|
-
|
|
|
- get_and_assert_status(&server, "/v1/chains/ethereum/revelations/0", StatusCode::OK).await;
|
|
|
+ // Add requests - they should be immediately available
|
|
|
+ eth_contract.insert(
|
|
|
+ PROVIDER,
|
|
|
+ 0,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+ eth_contract.insert(
|
|
|
+ PROVIDER,
|
|
|
+ 1,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
+ avax_contract.insert(
|
|
|
+ PROVIDER,
|
|
|
+ 100,
|
|
|
+ callback_gas_limit,
|
|
|
+ empty_price_ids.clone(),
|
|
|
+ publish_time,
|
|
|
+ );
|
|
|
|
|
|
+ // All inserted requests should be immediately available
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/1",
|
|
|
- StatusCode::FORBIDDEN,
|
|
|
+ "/v1/chains/ethereum/price-updates/0",
|
|
|
+ StatusCode::OK,
|
|
|
)
|
|
|
.await;
|
|
|
-
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/100",
|
|
|
- StatusCode::FORBIDDEN,
|
|
|
+ "/v1/chains/ethereum/price-updates/1",
|
|
|
+ StatusCode::OK,
|
|
|
)
|
|
|
.await;
|
|
|
-
|
|
|
- eth_contract.set_block_number(12);
|
|
|
- avax_contract.set_block_number(12);
|
|
|
-
|
|
|
- get_and_assert_status(&server, "/v1/chains/ethereum/revelations/1", StatusCode::OK).await;
|
|
|
-
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/ethereum/revelations/2",
|
|
|
- StatusCode::FORBIDDEN,
|
|
|
+ "/v1/chains/avalanche/price-updates/100",
|
|
|
+ StatusCode::OK,
|
|
|
)
|
|
|
.await;
|
|
|
|
|
|
+ // Non-inserted requests should still be forbidden
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/100",
|
|
|
- StatusCode::OK,
|
|
|
+ "/v1/chains/ethereum/price-updates/2",
|
|
|
+ StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|
|
|
-
|
|
|
get_and_assert_status(
|
|
|
&server,
|
|
|
- "/v1/chains/avalanche/revelations/101",
|
|
|
+ "/v1/chains/avalanche/price-updates/101",
|
|
|
StatusCode::FORBIDDEN,
|
|
|
)
|
|
|
.await;
|