diff --git a/README.md b/README.md index cbf24058..747efd06 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ use workspaces::prelude::*; #[tokio::test] async fn test_deploy_and_view() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let contract = worker.dev_deploy(include_bytes!("path/to/file.wasm")) .await @@ -67,12 +67,12 @@ cargo run --example nft ```rust #[tokio::main] # or whatever runtime we want -async fn main() { +async fn main() -> anyhow::Result<()> { // Create a sandboxed environment. // NOTE: Each call will create a new sandboxed environment - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; // or for testnet: - let worker = workspaces::testnet(); + let worker = workspaces::testnet().await?; } ``` diff --git a/examples/src/fast_forward.rs b/examples/src/fast_forward.rs index e5075b5f..f25111d0 100644 --- a/examples/src/fast_forward.rs +++ b/examples/src/fast_forward.rs @@ -11,7 +11,7 @@ const SIMPLE_WASM_FILEPATH: &str = "./examples/res/simple_contract.wasm"; /// to be produced, which could take hours with the default genesis configuration. #[tokio::main] async fn main() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let contract = worker .dev_deploy(&std::fs::read(SIMPLE_WASM_FILEPATH)?) .await?; diff --git a/examples/src/nft.rs b/examples/src/nft.rs index 1a12a9d8..97b64023 100644 --- a/examples/src/nft.rs +++ b/examples/src/nft.rs @@ -6,7 +6,7 @@ const NFT_WASM_FILEPATH: &str = "./examples/res/non_fungible_token.wasm"; #[tokio::main] async fn main() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let wasm = std::fs::read(NFT_WASM_FILEPATH)?; let contract = worker.dev_deploy(&wasm).await?; diff --git a/examples/src/ref_finance.rs b/examples/src/ref_finance.rs index 405df5b3..7bf71136 100644 --- a/examples/src/ref_finance.rs +++ b/examples/src/ref_finance.rs @@ -18,7 +18,7 @@ const BLOCK_HEIGHT: BlockHeight = 50_000_000; /// Pull down the ref-finance contract and deploy it to the sandbox network, /// initializing it with all data required to run the tests. async fn create_ref(owner: &Account, worker: &Worker) -> anyhow::Result { - let mainnet = workspaces::mainnet_archival(); + let mainnet = workspaces::mainnet_archival().await?; let ref_finance_id: AccountId = REF_FINANCE_ACCOUNT_ID.parse()?; // This will pull down the relevant ref-finance contract from mainnet. We're going @@ -56,7 +56,7 @@ async fn create_ref(owner: &Account, worker: &Worker) -> anyhow::Result /// Pull down the WNear contract from mainnet and initilize it with our own metadata. async fn create_wnear(owner: &Account, worker: &Worker) -> anyhow::Result { - let mainnet = workspaces::mainnet_archival(); + let mainnet = workspaces::mainnet_archival().await?; let wnear_id: AccountId = "wrap.near".to_string().try_into()?; let wnear = worker .import_contract(&wnear_id, &mainnet) @@ -202,7 +202,7 @@ async fn create_custom_ft( #[tokio::main] async fn main() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let owner = worker.root_account(); /////////////////////////////////////////////////////////////////////////// diff --git a/examples/src/spooning.rs b/examples/src/spooning.rs index a3ceaf9f..4d895f0b 100644 --- a/examples/src/spooning.rs +++ b/examples/src/spooning.rs @@ -14,7 +14,7 @@ const STATUS_MSG_WASM_FILEPATH: &str = "./examples/res/status_message.wasm"; /// If you'd like a different account to deploy it to, run the following: /// ```norun /// async fn deploy_testnet() -> anyhow::Result<()> { -/// let worker = worspaces::testnet(); +/// let worker = worspaces::testnet().await?; /// /// let contract = deploy_status_contract(worker, "hello from testnet").await?; /// println!("{}", contract.id()); @@ -74,7 +74,7 @@ async fn main() -> anyhow::Result<()> { // Grab STATE from the testnet status_message contract. This contract contains the following data: // get_status(dev-20211013002148-59466083160385) => "hello from testnet" let (testnet_contract_id, status_msg) = { - let worker = workspaces::testnet(); + let worker = workspaces::testnet().await?; let contract_id: AccountId = TESTNET_PREDEPLOYED_CONTRACT_ID .parse() .map_err(anyhow::Error::msg)?; @@ -90,7 +90,7 @@ async fn main() -> anyhow::Result<()> { info!(target: "spooning", "Testnet: {:?}", status_msg); // Create our sandboxed environment and grab a worker to do stuff in it: - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; // Deploy with the following status_message state: sandbox_contract_id => "hello from sandbox" let sandbox_contract = deploy_status_contract(&worker, "hello from sandbox").await?; diff --git a/examples/src/status_message.rs b/examples/src/status_message.rs index 3fd3ca49..25ce234f 100644 --- a/examples/src/status_message.rs +++ b/examples/src/status_message.rs @@ -5,7 +5,7 @@ const STATUS_MSG_WASM_FILEPATH: &str = "./examples/res/status_message.wasm"; #[tokio::main] async fn main() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let wasm = std::fs::read(STATUS_MSG_WASM_FILEPATH)?; let contract = worker.dev_deploy(&wasm).await?; diff --git a/workspaces/src/network/mainnet.rs b/workspaces/src/network/mainnet.rs index 6920f2a2..23a65e36 100644 --- a/workspaces/src/network/mainnet.rs +++ b/workspaces/src/network/mainnet.rs @@ -11,28 +11,34 @@ pub struct Mainnet { } impl Mainnet { - pub(crate) fn new() -> Self { - Self { - client: Client::new(RPC_URL.into()), + pub(crate) async fn new() -> anyhow::Result { + let client = Client::new(RPC_URL.into()); + client.wait_for_rpc().await?; + + Ok(Self { + client, info: Info { name: "mainnet".into(), root_id: "near".parse().unwrap(), keystore_path: PathBuf::from(".near-credentials/mainnet/"), rpc_url: RPC_URL.into(), }, - } + }) } - pub(crate) fn archival() -> Self { - Self { - client: Client::new(ARCHIVAL_URL.into()), + pub(crate) async fn archival() -> anyhow::Result { + let client = Client::new(ARCHIVAL_URL.into()); + client.wait_for_rpc().await?; + + Ok(Self { + client, info: Info { name: "mainnet-archival".into(), root_id: "near".parse().unwrap(), keystore_path: PathBuf::from(".near-credentials/mainnet/"), rpc_url: ARCHIVAL_URL.into(), }, - } + }) } } diff --git a/workspaces/src/network/sandbox.rs b/workspaces/src/network/sandbox.rs index 827d4f16..a8f395c0 100644 --- a/workspaces/src/network/sandbox.rs +++ b/workspaces/src/network/sandbox.rs @@ -40,11 +40,12 @@ impl Sandbox { InMemorySigner::from_file(&path) } - pub(crate) fn new() -> Self { + pub(crate) async fn new() -> anyhow::Result { let mut server = SandboxServer::default(); - server.start().unwrap(); - + server.start()?; let client = Client::new(server.rpc_addr()); + client.wait_for_rpc().await?; + let info = Info { name: "sandbox".to_string(), root_id: AccountId::from_str("test.near").unwrap(), @@ -52,11 +53,11 @@ impl Sandbox { rpc_url: server.rpc_addr(), }; - Self { + Ok(Self { server, client, info, - } + }) } } diff --git a/workspaces/src/network/server.rs b/workspaces/src/network/server.rs index 914e56f8..e2230b12 100644 --- a/workspaces/src/network/server.rs +++ b/workspaces/src/network/server.rs @@ -1,6 +1,8 @@ +use std::process::Child; + use crate::network::Sandbox; + use portpicker::pick_unused_port; -use std::process::Child; use tracing::info; pub struct SandboxServer { diff --git a/workspaces/src/network/testnet.rs b/workspaces/src/network/testnet.rs index c5f9ec1c..d48a94f6 100644 --- a/workspaces/src/network/testnet.rs +++ b/workspaces/src/network/testnet.rs @@ -23,28 +23,34 @@ pub struct Testnet { } impl Testnet { - pub(crate) fn new() -> Self { - Self { - client: Client::new(RPC_URL.into()), + pub(crate) async fn new() -> anyhow::Result { + let client = Client::new(RPC_URL.into()); + client.wait_for_rpc().await?; + + Ok(Self { + client, info: Info { name: "testnet".into(), root_id: AccountId::from_str("testnet").unwrap(), keystore_path: PathBuf::from(".near-credentials/testnet/"), rpc_url: RPC_URL.into(), }, - } + }) } - pub(crate) fn archival() -> Self { - Self { - client: Client::new(ARCHIVAL_URL.into()), + pub(crate) async fn archival() -> anyhow::Result { + let client = Client::new(ARCHIVAL_URL.into()); + client.wait_for_rpc().await?; + + Ok(Self { + client, info: Info { name: "testnet-archival".into(), root_id: AccountId::from_str("testnet").unwrap(), keystore_path: PathBuf::from(".near-credentials/testnet/"), rpc_url: ARCHIVAL_URL.into(), }, - } + }) } } diff --git a/workspaces/src/rpc/client.rs b/workspaces/src/rpc/client.rs index beb5ab4a..4206f9b0 100644 --- a/workspaces/src/rpc/client.rs +++ b/workspaces/src/rpc/client.rs @@ -1,9 +1,12 @@ use std::collections::HashMap; use std::fmt::Debug; +use std::time::Duration; use tokio_retry::strategy::{jitter, ExponentialBackoff}; use tokio_retry::Retry; +use near_jsonrpc_client::errors::JsonRpcError; +use near_jsonrpc_client::methods::health::RpcStatusError; use near_jsonrpc_client::methods::query::RpcQueryRequest; use near_jsonrpc_client::{methods, JsonRpcClient, MethodCallResult}; use near_jsonrpc_primitives::types::query::QueryResponseKind; @@ -16,7 +19,7 @@ use near_primitives::transaction::{ use near_primitives::types::{Balance, BlockId, Finality, Gas, StoreKey}; use near_primitives::views::{ AccessKeyView, AccountView, BlockView, ContractCodeView, FinalExecutionOutcomeView, - QueryRequest, + QueryRequest, StatusResponse, }; use crate::result::ViewResultDetails; @@ -361,6 +364,33 @@ impl Client { ) .await } + + pub(crate) async fn status(&self) -> Result> { + let result = JsonRpcClient::connect(&self.rpc_addr) + .call(methods::status::RpcStatusRequest) + .await; + + tracing::debug!( + target: "workspaces", + "Querying RPC with RpcStatusRequest resulted in {:?}", + result, + ); + result + } + + pub(crate) async fn wait_for_rpc(&self) -> anyhow::Result<()> { + let retry_six_times = std::iter::repeat_with(|| Duration::from_millis(500)).take(6); + Retry::spawn(retry_six_times, || async { self.status().await }) + .await + .map_err(|e| { + anyhow::anyhow!( + "Failed to connect to RPC service {} within three seconds: {:?}", + self.rpc_addr, + e + ) + })?; + Ok(()) + } } pub(crate) async fn access_key( diff --git a/workspaces/src/worker/mod.rs b/workspaces/src/worker/mod.rs index e20a1ddc..36e4a81c 100644 --- a/workspaces/src/worker/mod.rs +++ b/workspaces/src/worker/mod.rs @@ -21,75 +21,75 @@ where } /// Spin up a new sandbox instance, and grab a [`Worker`] that interacts with it. -pub fn sandbox() -> Worker { - Worker::new(Sandbox::new()) +pub async fn sandbox() -> anyhow::Result> { + Ok(Worker::new(Sandbox::new().await?)) } /// Connect to the [testnet](https://explorer.testnet.near.org/) network, and grab /// a [`Worker`] that can interact with it. -pub fn testnet() -> Worker { - Worker::new(Testnet::new()) +pub async fn testnet() -> anyhow::Result> { + Ok(Worker::new(Testnet::new().await?)) } /// Connect to the [testnet archival](https://near-nodes.io/intro/node-types#archival-node) /// network, and grab a [`Worker`] that can interact with it. -pub fn testnet_archival() -> Worker { - Worker::new(Testnet::archival()) +pub async fn testnet_archival() -> anyhow::Result> { + Ok(Worker::new(Testnet::archival().await?)) } /// Connect to the [mainnet](https://explorer.near.org/) network, and grab /// a [`Worker`] that can interact with it. -pub fn mainnet() -> Worker { - Worker::new(Mainnet::new()) +pub async fn mainnet() -> anyhow::Result> { + Ok(Worker::new(Mainnet::new().await?)) } /// Connect to the [mainnet archival](https://near-nodes.io/intro/node-types#archival-node) /// network, and grab a [`Worker`] that can interact with it. -pub fn mainnet_archival() -> Worker { - Worker::new(Mainnet::archival()) +pub async fn mainnet_archival() -> anyhow::Result> { + Ok(Worker::new(Mainnet::archival().await?)) } /// Run a locally scoped task with a [`sandbox`] instanced [`Worker`] is supplied. -pub async fn with_sandbox(task: F) -> T::Output +pub async fn with_sandbox(task: F) -> anyhow::Result where F: Fn(Worker) -> T, T: core::future::Future, { - task(sandbox()).await + Ok(task(sandbox().await?).await) } /// Run a locally scoped task with a [`testnet`] instanced [`Worker`] is supplied. -pub async fn with_testnet(task: F) -> T::Output +pub async fn with_testnet(task: F) -> anyhow::Result where F: Fn(Worker) -> T, T: core::future::Future, { - task(testnet()).await + Ok(task(testnet().await?).await) } /// Run a locally scoped task with a [`testnet_archival`] instanced [`Worker`] is supplied. -pub async fn with_testnet_archival(task: F) -> T::Output +pub async fn with_testnet_archival(task: F) -> anyhow::Result where F: Fn(Worker) -> T, T: core::future::Future, { - task(testnet_archival()).await + Ok(task(testnet_archival().await?).await) } /// Run a locally scoped task with a [`mainnet`] instanced [`Worker`] is supplied. -pub async fn with_mainnet(task: F) -> T::Output +pub async fn with_mainnet(task: F) -> anyhow::Result where F: Fn(Worker) -> T, T: core::future::Future, { - task(mainnet()).await + Ok(task(mainnet().await?).await) } /// Run a locally scoped task with a [`mainnet_archival`] instanced [`Worker`] is supplied. -pub async fn with_mainnet_archival(task: F) -> T::Output +pub async fn with_mainnet_archival(task: F) -> anyhow::Result where F: Fn(Worker) -> T, T: core::future::Future, { - task(mainnet_archival()).await + Ok(task(mainnet_archival().await?).await) } diff --git a/workspaces/tests/batch_tx.rs b/workspaces/tests/batch_tx.rs index ce7fcbbd..dcb55411 100644 --- a/workspaces/tests/batch_tx.rs +++ b/workspaces/tests/batch_tx.rs @@ -5,7 +5,7 @@ use workspaces::prelude::*; #[test(tokio::test)] async fn test_batch_tx() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let contract = worker .dev_deploy(include_bytes!("../../examples/res/status_message.wasm")) .await?; diff --git a/workspaces/tests/create_account.rs b/workspaces/tests/create_account.rs index f9a31d04..a249e40f 100644 --- a/workspaces/tests/create_account.rs +++ b/workspaces/tests/create_account.rs @@ -4,7 +4,7 @@ use workspaces::prelude::*; #[test(tokio::test)] async fn test_subaccount_creation() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let account = worker.dev_create_account().await?; let sub = account diff --git a/workspaces/tests/deploy.rs b/workspaces/tests/deploy.rs index bc1f7b73..3ce7b87c 100644 --- a/workspaces/tests/deploy.rs +++ b/workspaces/tests/deploy.rs @@ -31,7 +31,7 @@ fn expected() -> NftMetadata { #[test(tokio::test)] async fn test_dev_deploy() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let wasm = std::fs::read(NFT_WASM_FILEPATH)?; let contract = worker.dev_deploy(&wasm).await?; diff --git a/workspaces/tests/deploy_project.rs b/workspaces/tests/deploy_project.rs index d23a284b..4a55231c 100644 --- a/workspaces/tests/deploy_project.rs +++ b/workspaces/tests/deploy_project.rs @@ -5,7 +5,7 @@ use workspaces::prelude::*; #[test(tokio::test)] async fn test_dev_deploy_project() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let wasm = workspaces::compile_project("./tests/test-contracts/status-message").await?; let contract = worker.dev_deploy(&wasm).await?; diff --git a/workspaces/tests/optional_args.rs b/workspaces/tests/optional_args.rs index 847660c7..47e85753 100644 --- a/workspaces/tests/optional_args.rs +++ b/workspaces/tests/optional_args.rs @@ -23,7 +23,7 @@ async fn init(worker: &Worker) -> anyhow::Result { #[test(tokio::test)] async fn test_empty_args_error() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let contract = init(&worker).await?; let res = contract @@ -39,7 +39,7 @@ async fn test_empty_args_error() -> anyhow::Result<()> { #[test(tokio::test)] async fn test_optional_args_present() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let contract = init(&worker).await?; let res = contract diff --git a/workspaces/tests/patch_state.rs b/workspaces/tests/patch_state.rs index 69cd3da6..9c0963eb 100644 --- a/workspaces/tests/patch_state.rs +++ b/workspaces/tests/patch_state.rs @@ -45,7 +45,7 @@ async fn view_status_state( #[test(tokio::test)] async fn test_view_state() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let (contract_id, status_msg) = view_status_state(worker).await?; assert_eq!( @@ -63,7 +63,7 @@ async fn test_view_state() -> anyhow::Result<()> { #[test(tokio::test)] async fn test_patch_state() -> anyhow::Result<()> { - let worker = workspaces::sandbox(); + let worker = workspaces::sandbox().await?; let (contract_id, mut status_msg) = view_status_state(worker.clone()).await?; status_msg.records.push(Record { k: "alice.near".to_string(),