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: add fast-forwarding #73

Merged
merged 7 commits into from
Apr 4, 2022
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 examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ path = "src/spooning.rs"
[[example]]
name = "ref_finance"
path = "src/ref_finance.rs"

[[example]]
name = "fast_forward"
path = "src/fast_forward.rs"
Binary file added examples/res/simple_contract.wasm
Binary file not shown.
19 changes: 19 additions & 0 deletions examples/simple-contract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "simple-contract"
version = "0.1.0"
publish = false
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
near-sdk = "4.0.0-pre.7"

[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
lto = true
debug = false
panic = "abort"
16 changes: 16 additions & 0 deletions examples/simple-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::{env, log, near_bindgen};

#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct Simple {}

#[near_bindgen]
impl Simple {
pub fn current_env_data() -> (u64, u64) {
let now = env::block_timestamp();
let eh = env::epoch_height();
log!("Timestamp: {}", now);
(now, eh)
}
}
44 changes: 44 additions & 0 deletions examples/src/fast_forward.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use workspaces::prelude::*;

/// Our simple contract. Has a function to called `current_env_data` to just grab
/// the current block_timestamp and epoch_height. Will be used to showcase what
/// our contracts can see pre-and-post fast forwarding.
const SIMPLE_WASM_FILEPATH: &str = "./examples/res/simple_contract.wasm";

/// This example will call into `fast_forward` to show us that our contracts are
/// are being fast forward in regards to the timestamp, block height and epoch height.
/// This saves us the time from having to wait a while for the same amount of blocks
/// to be produced, which could take hours with the default genesis configuration.
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let worker = workspaces::sandbox();
let contract = worker
.dev_deploy(&std::fs::read(SIMPLE_WASM_FILEPATH)?)
.await?;

let (timestamp, epoch_height): (u64, u64) = contract
.call(&worker, "current_env_data")
.view()
.await?
.json()?;
println!("timestamp = {}, epoch_height = {}", timestamp, epoch_height);

let block_info = worker.view_latest_block().await?;
println!("BlockInfo pre-fast_forward {:?}", block_info);

// Call into fast_forward. This will take a bit of time to invoke, but is
// faster than manually waiting for the same amounts of blocks to be produced
worker.fast_forward(10000).await?;

let (timestamp, epoch_height): (u64, u64) = contract
.call(&worker, "current_env_data")
.view()
.await?
.json()?;
println!("timestamp = {}, epoch_height = {}", timestamp, epoch_height);

let block_info = worker.view_latest_block().await?;
println!("BlockInfo post-fast_forward {:?}", block_info);

Ok(())
}
12 changes: 3 additions & 9 deletions examples/src/ref_finance.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::HashMap, convert::TryInto};

use near_units::{parse_gas, parse_near};
use workspaces::{prelude::*, BlockHeight, DevNetwork};
use workspaces::{prelude::*, BlockHeight, DevNetwork, Sandbox};
use workspaces::{Account, AccountId, Contract, Network, Worker};

const FT_CONTRACT_FILEPATH: &str = "./examples/res/fungible_token.wasm";
Expand All @@ -15,10 +15,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<impl Network + StatePatcher>,
) -> anyhow::Result<Contract> {
async fn create_ref(owner: &Account, worker: &Worker<Sandbox>) -> anyhow::Result<Contract> {
let mainnet = workspaces::mainnet_archival();
let ref_finance_id: AccountId = REF_FINANCE_ACCOUNT_ID.parse()?;

Expand Down Expand Up @@ -56,10 +53,7 @@ async fn create_ref(
}

/// Pull down the WNear contract from mainnet and initilize it with our own metadata.
async fn create_wnear(
owner: &Account,
worker: &Worker<impl Network + StatePatcher>,
) -> anyhow::Result<Contract> {
async fn create_wnear(owner: &Account, worker: &Worker<Sandbox>) -> anyhow::Result<Contract> {
let mainnet = workspaces::mainnet_archival();
let wnear_id: AccountId = "wrap.near".to_string().try_into()?;
let wnear = worker
Expand Down
1 change: 1 addition & 0 deletions workspaces/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod prelude;

pub use network::result;
pub use network::transaction::Function;
pub use network::Sandbox;
pub use network::{Account, AccountDetails, Block, Contract, DevNetwork, Network};
pub use types::{AccessKey, AccountId, BlockHeight, CryptoHash, InMemorySigner};
pub use worker::{
Expand Down
60 changes: 0 additions & 60 deletions workspaces/src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ pub mod transaction;

use async_trait::async_trait;

use near_jsonrpc_client::methods::sandbox_patch_state::RpcSandboxPatchStateRequest;
use near_primitives::state_record::StateRecord;

pub(crate) use crate::network::info::Info;
use crate::rpc::client::Client;
use crate::rpc::patch::ImportContractTransaction;
use crate::types::{AccountId, KeyType, SecretKey};
use crate::Worker;

pub use crate::network::account::{Account, AccountDetails, Contract};
pub use crate::network::block::Block;
Expand Down Expand Up @@ -88,61 +83,6 @@ where
}
}

pub trait AllowStatePatching {}

#[async_trait]
pub trait StatePatcher {
async fn patch_state(
&self,
contract_id: &AccountId,
key: &[u8],
value: &[u8],
) -> anyhow::Result<()>;

fn import_contract<'a, 'b>(
&'b self,
id: &AccountId,
worker: &'a Worker<impl Network>,
) -> ImportContractTransaction<'a, 'b>;
}

#[async_trait]
impl<T> StatePatcher for T
where
T: AllowStatePatching + NetworkClient + Send + Sync,
{
async fn patch_state(
&self,
contract_id: &AccountId,
key: &[u8],
value: &[u8],
) -> anyhow::Result<()> {
let state = StateRecord::Data {
account_id: contract_id.to_owned(),
data_key: key.to_vec(),
value: value.to_vec(),
};
let records = vec![state];

// NOTE: RpcSandboxPatchStateResponse is an empty struct with no fields, so don't do anything with it:
let _patch_resp = self
.client()
.query(&RpcSandboxPatchStateRequest { records })
.await
.map_err(|err| anyhow::anyhow!("Failed to patch state: {:?}", err))?;

Ok(())
}

fn import_contract<'a, 'b>(
&'b self,
id: &AccountId,
worker: &'a Worker<impl Network>,
) -> ImportContractTransaction<'a, 'b> {
ImportContractTransaction::new(id.to_owned(), worker.client(), self.client())
}
}

pub trait Network: NetworkInfo + NetworkClient + Send + Sync {}

impl<T> Network for T where T: NetworkInfo + NetworkClient + Send + Sync {}
Expand Down
55 changes: 51 additions & 4 deletions workspaces/src/network/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ use std::path::PathBuf;
use std::str::FromStr;

use async_trait::async_trait;
use near_jsonrpc_client::methods::sandbox_fast_forward::RpcSandboxFastForwardRequest;
use near_jsonrpc_client::methods::sandbox_patch_state::RpcSandboxPatchStateRequest;
use near_primitives::state_record::StateRecord;

use super::{
Account, AllowDevAccountCreation, AllowStatePatching, CallExecution, Contract, NetworkClient,
NetworkInfo, TopLevelAccountCreator,
Account, AllowDevAccountCreation, CallExecution, Contract, NetworkClient, NetworkInfo,
TopLevelAccountCreator,
};

use crate::network::server::SandboxServer;
use crate::network::Info;
use crate::rpc::client::Client;
use crate::rpc::patch::ImportContractTransaction;
use crate::types::{AccountId, Balance, InMemorySigner, SecretKey};
use crate::{Network, Worker};

// Constant taken from nearcore crate to avoid dependency
pub(crate) const NEAR_BASE: Balance = 1_000_000_000_000_000_000_000_000;
Expand Down Expand Up @@ -58,8 +63,6 @@ impl Sandbox {
}
}

impl AllowStatePatching for Sandbox {}

impl AllowDevAccountCreation for Sandbox {}

#[async_trait]
Expand Down Expand Up @@ -119,3 +122,47 @@ impl NetworkInfo for Sandbox {
&self.info
}
}

impl Sandbox {
pub(crate) fn import_contract<'a, 'b>(
&'b self,
id: &AccountId,
worker: &'a Worker<impl Network>,
) -> ImportContractTransaction<'a, 'b> {
ImportContractTransaction::new(id.to_owned(), worker.client(), self.client())
}

pub(crate) async fn patch_state(
&self,
contract_id: &AccountId,
key: &[u8],
value: &[u8],
) -> anyhow::Result<()> {
let state = StateRecord::Data {
account_id: contract_id.to_owned(),
data_key: key.to_vec(),
value: value.to_vec(),
};
let records = vec![state];

// NOTE: RpcSandboxPatchStateResponse is an empty struct with no fields, so don't do anything with it:
let _patch_resp = self
.client()
.query(&RpcSandboxPatchStateRequest { records })
.await
.map_err(|err| anyhow::anyhow!("Failed to patch state: {:?}", err))?;

Ok(())
}

pub(crate) async fn fast_forward(&self, delta_height: u64) -> anyhow::Result<()> {
// NOTE: RpcSandboxFastForwardResponse is an empty struct with no fields, so don't do anything with it:
self.client()
// TODO: replace this with the `query` variant when RpcSandboxFastForwardRequest impls Debug
.query_nolog(&RpcSandboxFastForwardRequest { delta_height })
.await
.map_err(|err| anyhow::anyhow!("Failed to fast forward: {:?}", err))?;

Ok(())
}
}
2 changes: 1 addition & 1 deletion workspaces/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pub use crate::network::{DevAccountDeployer, StatePatcher, TopLevelAccountCreator};
pub use crate::network::{DevAccountDeployer, TopLevelAccountCreator};
7 changes: 7 additions & 0 deletions workspaces/src/rpc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ impl Client {
.await
}

pub(crate) async fn query_nolog<M>(&self, method: &M) -> MethodCallResult<M::Response, M::Error>
where
M: methods::RpcMethod,
{
retry(|| async { JsonRpcClient::connect(&self.rpc_addr).call(method).await }).await
}

pub(crate) async fn query<M>(&self, method: &M) -> MethodCallResult<M::Response, M::Error>
where
M: methods::RpcMethod + Debug,
Expand Down
58 changes: 34 additions & 24 deletions workspaces/src/worker/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use near_primitives::types::{Balance, StoreKey};

use crate::network::{
Account, AllowDevAccountCreation, Block, CallExecution, CallExecutionDetails, Contract,
NetworkClient, NetworkInfo, StatePatcher, TopLevelAccountCreator, ViewResultDetails,
NetworkClient, NetworkInfo, TopLevelAccountCreator, ViewResultDetails,
};
use crate::network::{Info, Sandbox};
use crate::rpc::client::{Client, DEFAULT_CALL_DEPOSIT, DEFAULT_CALL_FN_GAS};
Expand Down Expand Up @@ -56,29 +56,6 @@ where
}
}

#[async_trait]
impl<T> StatePatcher for Worker<T>
where
T: StatePatcher + Send + Sync,
{
async fn patch_state(
&self,
contract_id: &AccountId,
key: &[u8],
value: &[u8],
) -> anyhow::Result<()> {
self.workspace.patch_state(contract_id, key, value).await
}

fn import_contract<'a, 'b>(
&'b self,
id: &AccountId,
worker: &'a Worker<impl Network>,
) -> ImportContractTransaction<'a, 'b> {
self.workspace.import_contract(id, worker)
}
}

impl<T> Worker<T>
where
T: NetworkClient,
Expand Down Expand Up @@ -186,4 +163,37 @@ impl Worker<Sandbox> {
let signer = self.workspace.root_signer();
Account::new(account_id, signer)
}

/// Import a contract from the the given network, and return us a [`ImportContractTransaction`]
/// which allows to specify further details, such as being able to import contract data and
/// how far back in time we wanna grab the contract.
pub fn import_contract<'a, 'b>(
&'b self,
id: &AccountId,
worker: &'a Worker<impl Network>,
) -> ImportContractTransaction<'a, 'b> {
self.workspace.import_contract(id, worker)
}

/// Patch state into the sandbox network, given a key and value. This will allow us to set
/// state that we have acquired in some manner. This allows us to test random cases that
/// are hard to come up naturally as state evolves.
pub async fn patch_state(
&self,
contract_id: &AccountId,
key: &[u8],
value: &[u8],
) -> anyhow::Result<()> {
self.workspace.patch_state(contract_id, key, value).await
}

/// Fast forward to a point in the future. The delta block height is supplied to tell the
/// network to advanced a certain amount of blocks. This comes with the advantage only having
/// to wait a fraction of the time it takes to produce the same number of blocks.
///
/// Estimate as to how long it takes: if our delta_height crosses `X` epochs, then it would
/// roughly take `X * 5` seconds for the fast forward request to be processed.
pub async fn fast_forward(&self, delta_height: u64) -> anyhow::Result<()> {
self.workspace.fast_forward(delta_height).await
}
}