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

Wasm -> near-vm-logic & near-vm-runner (Implements NEPS#0009) #1160

Merged
merged 13 commits into from
Aug 13, 2019
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ core/wasm/**/Cargo.lock
.env
*.pyc

# Node
**/node_modules/

# Integration tests
tmp/

Expand Down
1,794 changes: 909 additions & 885 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ members = [
"core/primitives",
"core/store",
"runtime/runtime",
"runtime/wasm",
"runtime/wasm/runtest",
"runtime/near-vm-logic",
"runtime/near-vm-runner",
"runtime/near-vm-runner-standalone",
"chain/chain",
"chain/pool",
"chain/client",
Expand All @@ -27,9 +28,6 @@ members = [
"test-utils/state-viewer",
"near/",
]
exclude = [
"runtime/wasm/runtest/generate-wasm/to-wasm",
]

[dev-dependencies]
actix = "0.8.2"
Expand All @@ -45,7 +43,6 @@ near-primitives = { path = "./core/primitives" }
near-store = { path = "./core/store" }

node-runtime = { path = "./runtime/runtime" }
wasm = { path = "./runtime/wasm" }

near-jsonrpc = { path = "./chain/jsonrpc" }
near-network = { path = "./chain/network" }
Expand Down
6 changes: 4 additions & 2 deletions core/store/src/trie/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ impl TrieUpdate {
let TrieUpdate { trie, root, committed, .. } = self;
trie.update(&root, committed.into_iter())
}

/// Returns Error if the underlying storage fails
pub fn iter(&self, prefix: &[u8]) -> Result<TrieUpdateIterator, Box<dyn std::error::Error>> {
TrieUpdateIterator::new(self, prefix, b"", None)
}
Expand Down Expand Up @@ -190,7 +192,7 @@ impl<'a> Iterator for TrieUpdateIterator<'a> {
let stop_cond = |key: &Vec<u8>, prefix: &Vec<u8>, end_offset: &Option<Vec<u8>>| {
!key.starts_with(prefix)
|| match end_offset {
Some(end) => key > end,
Some(end) => key >= end,
None => false,
}
};
Expand Down Expand Up @@ -364,7 +366,7 @@ mod tests {
let values: Vec<Vec<u8>> = trie_update.iter(b"dog").unwrap().collect();
assert_eq!(values, vec![b"dog".to_vec(), b"dog2".to_vec(), b"dog3".to_vec()]);

let values: Vec<Vec<u8>> = trie_update.range(b"do", b"g", b"g2").unwrap().collect();
let values: Vec<Vec<u8>> = trie_update.range(b"do", b"g", b"g21").unwrap().collect();
assert_eq!(values, vec![b"dog".to_vec(), b"dog2".to_vec()]);

let values: Vec<Vec<u8>> = trie_update.range(b"do", b"", b"xyz").unwrap().collect();
Expand Down
7 changes: 4 additions & 3 deletions near/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,10 @@ impl GenesisConfig {
pub fn legacy_test(seeds: Vec<&str>, num_validators: usize) -> Self {
let mut validators = vec![];
let mut records = vec![vec![]];
let default_test_contract =
include_bytes!("../../runtime/wasm/runtest/res/wasm_with_mem.wasm").as_ref();
let encoded_test_contract = to_base64(default_test_contract);
let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("../runtime/near-vm-runner/tests/res/test_contract_rs.wasm");
let default_test_contract = std::fs::read(path).unwrap();
let encoded_test_contract = to_base64(&default_test_contract);
let code_hash = hash(&default_test_contract);
for (i, account) in seeds.iter().enumerate() {
let signer = InMemorySigner::from_seed(account, account);
Expand Down
20 changes: 20 additions & 0 deletions runtime/near-vm-logic/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "near-vm-logic"
version = "0.2.0"
authors = ["Near Inc <hello@nearprotocol.com>"]
edition = "2018"

[dependencies]
bs58 = "0.2.2"
exonum_sodiumoxide = "0.0.20"
serde = { version = "1.0", features = ["derive"] }

[[test]]
name = "test_registers"
path = "tests/test_registers.rs"
required-features = ["mocks"]


[features]
# Mocks include some unsafe code to workaround lifetimes and therefore are optional.
mocks = []
7 changes: 7 additions & 0 deletions runtime/near-vm-logic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# near-vm-logic

This crate implements the specification of the interface that Near blockchain exposes to the smart contracts.
It is not dependent on the specific way the smart contract code is executed, e.g. through Wasmer or whatnot, and
therefore can be used for unit tests in smart contracts.

Note, this logic assumes the little endian byte ordering of the memory used by the smart contract.
68 changes: 68 additions & 0 deletions runtime/near-vm-logic/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::types::Gas;
use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
pub struct Config {
/// Gas cost of a growing memory by single page.
pub grow_mem_cost: u32,
/// Gas cost of a regular operation.
pub regular_op_cost: u32,
/// Max amount of gas that can be used, excluding gas attached to promises.
pub max_gas_burnt: Gas,

/// How tall the stack is allowed to grow?
///
/// See https://wiki.parity.io/WebAssembly-StackHeight to find out
/// how the stack frame cost is calculated.
pub max_stack_height: u32,
/// The initial number of memory pages.
pub initial_memory_pages: u32,
/// What is the maximal memory pages amount is allowed to have for
/// a contract.
pub max_memory_pages: u32,

/// Limit of memory used by registers.
pub registers_memory_limit: u64,
/// Maximum number of bytes that can be stored in a single register.
pub max_register_size: u64,
/// Maximum number of registers that can be used simultaneously.
pub max_number_registers: u64,

/// Maximum number of log entries.
pub max_number_logs: u64,
/// Maximum length of a single log, in bytes.
pub max_log_len: u64,
}

impl Default for Config {
fn default() -> Config {
Config {
grow_mem_cost: 1,
regular_op_cost: 1,
max_gas_burnt: 10u64.pow(9),
max_stack_height: 64 * 1024,
initial_memory_pages: 17,
max_memory_pages: 32,
// By default registers are limited by 1GiB of memory.
registers_memory_limit: 2u64.pow(30),
// By default each register is limited by 100MiB of memory.
max_register_size: 2u64.pow(20) * 100,
// By default there is at most 100 registers.
max_number_registers: 100,
max_number_logs: 100,
max_log_len: 500,
}
}
}

impl Config {
/// Computes non-cryptographically-proof hash. The computation is fast but not cryptographically
/// secure.
pub fn non_crypto_hash(&self) -> u64 {
let mut s = DefaultHasher::new();
self.hash(&mut s);
s.finish()
}
}
40 changes: 40 additions & 0 deletions runtime/near-vm-logic/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::types::{AccountId, Balance, BlockIndex, Gas, PublicKey};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone)]
/// Context for the contract execution.
pub struct VMContext {
/// The account id of the current contract that we are executing.
pub current_account_id: AccountId,
/// The account id of that signed the original transaction that led to this
/// execution.
pub signer_account_id: AccountId,
#[serde(with = "crate::serde_with::bytes_as_base58")]
/// The public key that was used to sign the original transaction that led to
/// this execution.
pub signer_account_pk: PublicKey,
/// If this execution is the result of cross-contract call or a callback then
/// predecessor is the account that called it.
/// If this execution is the result of direct execution of transaction then it
/// is equal to `signer_account_id`.
pub predecessor_account_id: AccountId,
#[serde(with = "crate::serde_with::bytes_as_str")]
/// The input to the contract call.
pub input: Vec<u8>,
/// The current block index.
pub block_index: BlockIndex,

/// The balance attached to the given account. Excludes the `attached_deposit` that was
/// attached to the transaction.
pub account_balance: Balance,
/// The balance that was attached to the call that will be immediately deposited before the
/// contract execution starts.
pub attached_deposit: Balance,
/// The gas attached to the call that can be used to pay for the gas fees.
pub prepaid_gas: Gas,
#[serde(with = "crate::serde_with::bytes_as_base58")]
/// Initial seed for randomness
pub random_seed: Vec<u8>,
/// Whether the execution should not charge any costs.
pub free_of_charge: bool,
}
85 changes: 85 additions & 0 deletions runtime/near-vm-logic/src/dependencies.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! External dependencies of the near-vm-logic.

use crate::types::{AccountId, Balance, Gas, IteratorIndex, ReceiptIndex, StorageUsage};

/// An abstraction over the memory of the smart contract.
pub trait MemoryLike {
MaksymZavershynskyi marked this conversation as resolved.
Show resolved Hide resolved
/// Returns whether the memory interval is completely inside the smart contract memory.
fn fits_memory(&self, offset: u64, len: u64) -> bool;

/// Reads the content of the given memory interval.
///
/// # Panics
///
/// If memory interval is outside the smart contract memory.
fn read_memory(&self, offset: u64, buffer: &mut [u8]);

/// Reads a single byte from the memory.
///
/// # Panics
///
/// If pointer is outside the smart contract memory.
fn read_memory_u8(&self, offset: u64) -> u8;

/// Writes the buffer into the smart contract memory.
///
/// # Panics
///
/// If `offset + buffer.len()` is outside the smart contract memory.
fn write_memory(&mut self, offset: u64, buffer: &[u8]);
}

#[derive(Debug, PartialEq, Clone)]
pub enum ExternalError {
InvalidReceiptIndex,
InvalidIteratorIndex,
InvalidAccountId,
InvalidMethodName,
}

pub type Result<T> = ::std::result::Result<T, ExternalError>;

pub trait External {
fn storage_set(&mut self, key: &[u8], value: &[u8]) -> Result<Option<Vec<u8>>>;

fn storage_get(&self, key: &[u8]) -> Result<Option<Vec<u8>>>;

fn storage_remove(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>>;

fn storage_has_key(&mut self, key: &[u8]) -> Result<bool>;

fn storage_iter(&mut self, prefix: &[u8]) -> Result<IteratorIndex>;

fn storage_iter_range(&mut self, start: &[u8], end: &[u8]) -> Result<IteratorIndex>;

fn storage_iter_next(
&mut self,
iterator_idx: IteratorIndex,
) -> Result<Option<(Vec<u8>, Vec<u8>)>>;

fn storage_iter_drop(&mut self, iterator_idx: IteratorIndex) -> Result<()>;

fn receipt_create(
MaksymZavershynskyi marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
receipt_indices: Vec<ReceiptIndex>,
receiver_id: AccountId,
method_name: Vec<u8>,
arguments: Vec<u8>,
attached_deposit: Balance,
prepaid_gas: Gas,
) -> Result<ReceiptIndex>;

fn storage_usage(&self) -> StorageUsage;
}

impl std::fmt::Display for ExternalError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
use ExternalError::*;
match &self {
InvalidReceiptIndex => write!(f, "VM Logic returned an invalid receipt index"),
InvalidIteratorIndex => write!(f, "VM Logic returned an invalid iterator index"),
InvalidAccountId => write!(f, "VM Logic returned an invalid account id"),
InvalidMethodName => write!(f, "VM Logic returned an invalid method name"),
}
}
}
52 changes: 52 additions & 0 deletions runtime/near-vm-logic/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::dependencies::ExternalError;

#[derive(Debug, Clone, PartialEq)]
pub enum HostError {
BadUTF16,
BadUTF8,
GasExceeded,
GasLimitExceeded,
EmptyMethodName,
External(ExternalError),
GuestPanic,
IntegerOverflow,
InvalidIteratorIndex,
InvalidPromiseIndex,
CannotReturnJointPromise,
InvalidPromiseResultIndex,
InvalidRegisterId,
IteratorWasInvalidated,
MemoryAccessViolation,
}

impl From<ExternalError> for HostError {
fn from(err: ExternalError) -> Self {
HostError::External(err)
}
}

impl std::fmt::Display for HostError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
use HostError::*;
match self {
BadUTF8 => write!(f, "String encoding is bad UTF-8 sequence."),
BadUTF16 => write!(f, "String encoding is bad UTF-16 sequence."),
GasExceeded => write!(f, "Exceeded the prepaid gas."),
GasLimitExceeded => write!(f, "Exceeded the maximum amount of gas allowed to burn per contract."),
EmptyMethodName => write!(f, "Tried to call an empty method name."),
External(ext) => {
write!(f, "External error: ")?;
ext.fmt(f)
},
GuestPanic => write!(f, "Smart contract has explicitly invoked `panic`."),
IntegerOverflow => write!(f, "Integer overflow."),
InvalidIteratorIndex => write!(f, "Invalid iterator index"),
InvalidPromiseIndex => write!(f, "Invalid promise index"),
CannotReturnJointPromise => write!(f, "Returning joint promise is currently prohibited."),
InvalidPromiseResultIndex => write!(f, "Accessed invalid promise result index."),
InvalidRegisterId => write!(f, "Accessed invalid register id"),
IteratorWasInvalidated => write!(f, "Iterator was invalidated after its creation by performing a mutable operation on trie"),
MemoryAccessViolation => write!(f, "Accessed memory outside the bounds."),
}
}
}
16 changes: 16 additions & 0 deletions runtime/near-vm-logic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
mod config;
mod context;
mod dependencies;
mod errors;
mod logic;
#[cfg(feature = "mocks")]
pub mod mocks;
pub mod serde_with;

pub mod types;
pub use config::Config;
pub use context::VMContext;
pub use dependencies::{External, ExternalError, MemoryLike};
pub use errors::HostError;
pub use logic::{VMLogic, VMOutcome};
pub use types::ReturnData;
Loading