Skip to content

Commit

Permalink
feat: workspaces::sandbox() can connect to user provided sandbox node (
Browse files Browse the repository at this point in the history
…#220)

Co-authored-by: Daniyar Itegulov <[email protected]>
  • Loading branch information
ChaoticTempest and itegulov authored Apr 25, 2023
1 parent b24cf3c commit 1591a38
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 47 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ async fn test_contract() -> anyhow::Result<()> {

For a full example, take a look at [workspaces/tests/deploy_project.rs](https://github.com/near/workspaces-rs/blob/main/workspaces/tests/deploy_project.rs).

### Other Features

Other features can be directly found in the `examples/` folder, with some documentation outlining how they can be used.

### Environment Variables

These environment variables will be useful if there was ever a snag hit:
Expand Down
28 changes: 28 additions & 0 deletions examples/manually-spawned-sandbox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Utilizing custom sandbox node

This example will show us how to spin up a sandbox node of our own choosing. Follow the guide in https://github.com/near/sandbox to download it. This is mainly needed if a user wants to manage their own node and/or not require each test to spin up a new node each time.

Then initialize the chain via `init` and run it:

```sh
near-sandbox --home ${MY_HOME_DIRECTORY} init
near-sandbox --home ${MY_HOME_DIRECTORY} run
```

This will launch the chain onto `localhost:3030` by default. The `${MY_HOME_DIRECTORY}` is a path of our choosing here and this will be needed when running the workspaces code later on. In the following example, we had it set to `/home/user/.near-sandbox-home`.

In workspaces, to connect to our manually launched node, all we have to do is add a few additional parameters to `workspaces::sandbox()`:

```rs
#[tokio::main]
fn main() {
let worker = workspaces::sandbox()
.rpc_addr("http://localhost:3030")
.home_dir("/home/user/.near-sandbox-home")
.await?;

Ok(())
}
```

Then afterwards, we can continue performing our tests as we normally would if workspaces has spawned its own sandbox process.
12 changes: 10 additions & 2 deletions workspaces/src/error/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,26 @@ impl std::error::Error for Error {
}

impl SandboxErrorCode {
pub(crate) fn message<T>(self, msg: T) -> Error
where
T: Into<Cow<'static, str>>,
{
Error::message(ErrorKind::Sandbox(self), msg)
}

pub(crate) fn custom<E>(self, error: E) -> Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Error::custom(ErrorKind::Sandbox(self), error)
}

pub(crate) fn message<T>(self, msg: T) -> Error
pub(crate) fn full<T, E>(self, msg: T, error: E) -> Error
where
T: Into<Cow<'static, str>>,
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Error::message(ErrorKind::Sandbox(self), msg)
Error::full(ErrorKind::Sandbox(self), msg, error)
}
}

Expand Down
84 changes: 84 additions & 0 deletions workspaces/src/network/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::future::{Future, IntoFuture};
use std::marker::PhantomData;
use std::path::PathBuf;

use crate::network::Sandbox;
use crate::{Network, Worker};

pub(crate) type BoxFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + Send + 'a>>;

/// This trait provides a way to construct Networks out of a single builder. Currently
/// not planned to offer this trait outside, since the custom networks can just construct
/// themselves however they want utilizing `Worker::new` like so:
/// ```ignore
/// Worker::new(CustomNetwork {
/// ... // fields
/// })
/// ```
#[async_trait::async_trait]
pub(crate) trait FromNetworkBuilder: Sized {
async fn from_builder<'a>(build: NetworkBuilder<'a, Self>) -> crate::result::Result<Self>;
}

/// Builder for Networks. Only usable with workspaces provided Networks.
// Note, this is currently the aggregated state for all network types you can have since
// I didn't want to add additional reading complexity with another trait that associates the
// Network state.
pub struct NetworkBuilder<'a, T> {
pub(crate) name: &'a str,
pub(crate) rpc_addr: Option<String>,
pub(crate) home_dir: Option<PathBuf>,
_network: PhantomData<T>,
}

impl<'a, T> IntoFuture for NetworkBuilder<'a, T>
where
T: FromNetworkBuilder + Network + Send + 'a,
{
type Output = crate::result::Result<Worker<T>>;
type IntoFuture = BoxFuture<'a, Self::Output>;

fn into_future(self) -> Self::IntoFuture {
let fut = async {
let network = FromNetworkBuilder::from_builder(self).await?;
Ok(Worker::new(network))
};
Box::pin(fut)
}
}

impl<'a, T> NetworkBuilder<'a, T> {
pub(crate) fn new(name: &'a str) -> Self {
Self {
name,
rpc_addr: None,
home_dir: None,
_network: PhantomData,
}
}

/// Sets the RPC addr for this network. Useful for setting the Url to a different RPC
/// node than the default one provided by near.org. This enables certain features that
/// the default node doesn't provide such as getting beyond the data cap when downloading
/// state from the network.
///
/// Note that, for sandbox, we are required to specify `home_dir` as well to connect to
/// a manually spawned sandbox node.
pub fn rpc_addr(mut self, addr: &str) -> Self {
self.rpc_addr = Some(addr.into());
self
}
}

// So far, only Sandbox makes use of home_dir.
impl NetworkBuilder<'_, Sandbox> {
/// Specify at which location the home_dir of the manually spawned sandbox node is at.
/// We are expected to init our own sandbox before running this builder. To learn more
/// about initalizing and starting our own sandbox, go to [near-sandbox](https://github.com/near/sandbox).
/// Also required to set the home directory where all the chain data lives. This is
/// the `my_home_folder` we passed into `near-sandbox --home {my_home_folder} init`.
pub fn home_dir(mut self, home_dir: impl AsRef<std::path::Path>) -> Self {
self.home_dir = Some(home_dir.as_ref().into());
self
}
}
1 change: 1 addition & 0 deletions workspaces/src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod sandbox;
mod server;
mod testnet;

pub(crate) mod builder;
pub(crate) mod variants;

pub(crate) use variants::DEV_ACCOUNT_SEED;
Expand Down
60 changes: 39 additions & 21 deletions workspaces/src/network/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use near_jsonrpc_client::methods::sandbox_fast_forward::RpcSandboxFastForwardReq
use near_jsonrpc_client::methods::sandbox_patch_state::RpcSandboxPatchStateRequest;
use near_primitives::state_record::StateRecord;

use super::builder::{FromNetworkBuilder, NetworkBuilder};
use super::{AllowDevAccountCreation, NetworkClient, NetworkInfo, TopLevelAccountCreator};
use crate::error::SandboxErrorCode;
use crate::network::server::SandboxServer;
Expand All @@ -26,19 +27,52 @@ const DEFAULT_DEPOSIT: Balance = 100 * NEAR_BASE;
///
/// [`workspaces::sandbox`]: crate::sandbox
pub struct Sandbox {
server: SandboxServer,
pub(crate) server: SandboxServer,
client: Client,
info: Info,
}

impl Sandbox {
pub(crate) fn root_signer(&self) -> Result<InMemorySigner> {
let path = self.server.home_dir.path().join("validator_key.json");
let path = self.server.home_dir.join("validator_key.json");
InMemorySigner::from_file(&path)
}
}

impl std::fmt::Debug for Sandbox {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Sandbox")
.field("root_id", &self.info.root_id)
.field("rpc_url", &self.info.rpc_url)
.field("rpc_port", &self.server.rpc_port())
.field("net_port", &self.server.net_port())
.finish()
}
}

#[async_trait]
impl FromNetworkBuilder for Sandbox {
async fn from_builder<'a>(build: NetworkBuilder<'a, Self>) -> Result<Self> {
// Check the conditions of the provided rpc_url and home_dir
let mut server = match (build.rpc_addr, build.home_dir) {
// Connect to a provided sandbox:
(Some(rpc_url), Some(home_dir)) => SandboxServer::connect(rpc_url, home_dir).await?,

// Spawn a new sandbox since rpc_url and home_dir weren't specified:
(None, None) => SandboxServer::run_new().await?,

// Missing inputted paramters for sandbox:
(Some(rpc_url), None) => {
return Err(SandboxErrorCode::InitFailure
.message(format!("Custom rpc_url={rpc_url} requires home_dir set.")));
}
(None, Some(home_dir)) => {
return Err(SandboxErrorCode::InitFailure.message(format!(
"Custom home_dir={home_dir:?} requires rpc_url set."
)));
}
};

pub(crate) async fn new() -> Result<Self> {
let mut server = SandboxServer::run_new().await?;
let client = Client::new(&server.rpc_addr());
client.wait_for_rpc().await?;

Expand All @@ -49,7 +83,7 @@ impl Sandbox {
server.unlock_lockfiles()?;

let info = Info {
name: "sandbox".to_string(),
name: build.name.into(),
root_id: AccountId::from_str("test.near").unwrap(),
keystore_path: PathBuf::from(".near-credentials/sandbox/"),
rpc_url: server.rpc_addr(),
Expand All @@ -61,22 +95,6 @@ impl Sandbox {
info,
})
}

/// Port being used by sandbox server for RPC requests.
pub(crate) fn rpc_port(&self) -> u16 {
self.server.rpc_port
}
}

impl std::fmt::Debug for Sandbox {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Sandbox")
.field("root_id", &self.info.root_id)
.field("rpc_url", &self.info.rpc_url)
.field("rpc_port", &self.server.rpc_port)
.field("net_port", &self.server.net_port)
.finish()
}
}

impl AllowDevAccountCreation for Sandbox {}
Expand Down
Loading

0 comments on commit 1591a38

Please sign in to comment.