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

[DRAFT] MpcContract using yields #1133

Draft
wants to merge 20 commits into
base: master
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions examples/mpc-contract/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[target.wasm32-unknown-unknown]
rustflags = ["-C", "link-arg=-s"]

[build]
target-dir = "../../target"
20 changes: 20 additions & 0 deletions examples/mpc-contract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "mpc-contract"
version = "0.1.0"
authors = ["Near Inc <[email protected]>"]
edition = "2021"

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

[dependencies]
near-sdk = { path = "../../near-sdk" }
hex = "0.4"

[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
lto = true
debug = false
panic = "abort"
9 changes: 9 additions & 0 deletions examples/mpc-contract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Status Message

Records the status messages of the accounts that call this contract.

## Testing
To test run:
```bash
cargo test --package status-message -- --nocapture
```
7 changes: 7 additions & 0 deletions examples/mpc-contract/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
TARGET="${CARGO_TARGET_DIR:-../../target}"
set -e
cd "$(dirname $0)"
cargo build --target wasm32-unknown-unknown --release
cp $TARGET/wasm32-unknown-unknown/release/mpc_contract.wasm ./res/
#wasm-opt -Oz --output ./res/mpc_contract.wasm ./res/status_message.wasm
5 changes: 5 additions & 0 deletions examples/mpc-contract/res/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Ignore all files
*

# Except this to keep the directory
!.gitignore
130 changes: 130 additions & 0 deletions examples/mpc-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use near_sdk::borsh::{BorshDeserialize, BorshSerialize};
use near_sdk::collections::TreeMap;
use near_sdk::{
env, log, near_bindgen, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, GasWeight,
NearToken, PromiseError,
};

#[derive(BorshSerialize, BorshStorageKey)]
#[borsh(crate = "near_sdk::borsh")]
struct RecordsKey;

#[derive(BorshDeserialize, BorshSerialize)]
#[borsh(crate = "near_sdk::borsh")]
pub struct SignatureRequest {
data_id: CryptoHash,
account_id: AccountId,
message: String,
}

#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
#[borsh(crate = "near_sdk::borsh")]
pub struct MpcContract {
requests: TreeMap<u64, SignatureRequest>,
next_available_request_index: u64,
}

impl Default for MpcContract {
fn default() -> Self {
Self { requests: TreeMap::new(RecordsKey), next_available_request_index: 0u64 }
}
}

// Register used to receive data id from `promise_await_data`.
const DATA_ID_REGISTER: u64 = 0;

// Prepaid gas for a `sign_on_finish` call
const SIGN_ON_FINISH_CALL_GAS: Gas = Gas::from_tgas(5);

// Prepaid gas for a `do_something` call
const CHAINED_CALL_GAS: Gas = Gas::from_tgas(5);

#[near_bindgen]
impl MpcContract {
/// User-facing API: accepts some message and returns a signature
pub fn sign(&mut self, message_to_be_signed: String) {
let index = self.next_available_request_index;
self.next_available_request_index += 1;

let yield_promise = env::promise_yield_create(
"sign_on_finish",
&serde_json::to_vec(&(index,)).unwrap(),
SIGN_ON_FINISH_CALL_GAS,
GasWeight(0),
DATA_ID_REGISTER,
);

// Store the request in the contract's local state
let data_id: CryptoHash =
env::read_register(DATA_ID_REGISTER).expect("").try_into().expect("");
self.requests.insert(
&index,
&SignatureRequest {
data_id,
account_id: env::signer_account_id(),
message: message_to_be_signed,
},
);

// The yield promise is composable with the usual promise API features. We can choose to
// chain another function call and it will receive the output of the `sign_on_finish`
// callback. Note that this chained promise can be a cross-contract call.
env::promise_then(
yield_promise,
env::current_account_id(),
"do_something",
&[],
NearToken::from_near(0),
CHAINED_CALL_GAS,
);

// The return value for this function call will be the value
// returned by the `sign_on_finish` callback.
env::promise_return(yield_promise);
}

/// Called by MPC participants to submit a signature
pub fn sign_respond(&mut self, data_id: String, signature: String) {
let mut data_id_buf = [0u8; 32];
hex::decode_to_slice(data_id, &mut data_id_buf).expect("");
let data_id = data_id_buf;

// check that caller is allowed to respond, signature is valid, etc.
// ...

log!("submitting response {} for data id {:?}", &signature, &data_id);
env::promise_yield_resume(&data_id, &serde_json::to_vec(&signature).unwrap());
}

/// Callback receiving the externally submitted data (or a PromiseError)
pub fn sign_on_finish(
&mut self,
request_index: u64,
#[callback_result] signature: Result<String, PromiseError>,
) -> String {
// Clean up the local state
self.requests.remove(&request_index);

match signature {
Ok(signature) => "signature received: ".to_owned() + &signature,
Err(_) => "signature request timed out".to_string(),
}
}

pub fn do_something(#[callback_unwrap] signature_result: String) {
log!("fn do_something invoked with result '{}'", &signature_result);
}

/// Helper for local testing; prints all pending requests
pub fn log_pending_requests(&self) {
for (_, request) in self.requests.iter() {
log!(
"{}: account_id={} payload={}",
hex::encode(request.data_id),
request.account_id,
request.message
);
}
}
}
34 changes: 33 additions & 1 deletion near-sdk/src/environment/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::promise::Allowance;
use crate::types::{
AccountId, BlockHeight, Gas, NearToken, PromiseIndex, PromiseResult, PublicKey, StorageUsage,
};
use crate::{GasWeight, PromiseError};
use crate::{CryptoHash, GasWeight, PromiseError};
use near_sys as sys;

const REGISTER_EXPECTED_ERR: &str =
Expand Down Expand Up @@ -491,6 +491,38 @@ pub fn alt_bn128_pairing_check(value: &[u8]) -> bool {
// ################
// # Promises API #
// ################
pub fn promise_yield_create(
function_name: &str,
arguments: &[u8],
gas: Gas,
weight: GasWeight,
register_id: u64,
) -> PromiseIndex {
unsafe {
PromiseIndex(sys::promise_yield_create(
function_name.len() as _,
function_name.as_ptr() as _,
arguments.len() as _,
arguments.as_ptr() as _,
gas.as_gas(),
weight.0,
register_id as _,
))
}
}

// TODO: return some kind of success/failure result
pub fn promise_yield_resume(data_id: &CryptoHash, data: &[u8]) -> bool {
unsafe {
sys::promise_yield_resume(
data_id.len() as _,
data_id.as_ptr() as _,
data.len() as _,
data.as_ptr() as _,
) != 0
}
}

/// Creates a promise that will execute a method on account with given arguments and attaches
/// the given amount and gas.
pub fn promise_create(
Expand Down
18 changes: 18 additions & 0 deletions near-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,24 @@ extern "C" {
beneficiary_id_len: u64,
beneficiary_id_ptr: u64,
);
// ########################
// # Promise yield/resume #
// ########################
pub fn promise_yield_create(
function_name_len: u64,
function_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
gas: u64,
gas_weight: u64,
register_id: u64,
) -> u64;
pub fn promise_yield_resume(
data_id_len: u64,
data_id_ptr: u64,
payload_len: u64,
payload_ptr: u64,
) -> u32;
// #######################
// # Promise API results #
// #######################
Expand Down
Loading