Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: workspaces::sandbox() can connect to user provided sandbox node #220

Merged
merged 3 commits into from
Apr 25, 2023
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: 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