From 287666fc323b3f3146ea24fbef70d01323c8df76 Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Tue, 16 Jan 2024 22:39:44 -0500 Subject: [PATCH 01/18] set up mpc-contract with promise_await_data call --- examples/mpc-contract/.cargo/config.toml | 5 ++ examples/mpc-contract/Cargo.toml | 19 ++++++++ examples/mpc-contract/README.md | 9 ++++ examples/mpc-contract/build.sh | 7 +++ examples/mpc-contract/create.sh | 2 + examples/mpc-contract/deploy.sh | 2 + examples/mpc-contract/res/.gitignore | 5 ++ examples/mpc-contract/src/lib.rs | 58 ++++++++++++++++++++++++ near-sdk/src/environment/env.rs | 18 ++++++++ near-sys/src/lib.rs | 10 ++++ 10 files changed, 135 insertions(+) create mode 100644 examples/mpc-contract/.cargo/config.toml create mode 100644 examples/mpc-contract/Cargo.toml create mode 100644 examples/mpc-contract/README.md create mode 100755 examples/mpc-contract/build.sh create mode 100755 examples/mpc-contract/create.sh create mode 100755 examples/mpc-contract/deploy.sh create mode 100644 examples/mpc-contract/res/.gitignore create mode 100644 examples/mpc-contract/src/lib.rs diff --git a/examples/mpc-contract/.cargo/config.toml b/examples/mpc-contract/.cargo/config.toml new file mode 100644 index 000000000..4a9f7c79c --- /dev/null +++ b/examples/mpc-contract/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.wasm32-unknown-unknown] +rustflags = ["-C", "link-arg=-s"] + +[build] +target-dir = "../../target" diff --git a/examples/mpc-contract/Cargo.toml b/examples/mpc-contract/Cargo.toml new file mode 100644 index 000000000..453db4d0c --- /dev/null +++ b/examples/mpc-contract/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mpc-contract" +version = "0.1.0" +authors = ["Near Inc "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = { path = "../../near-sdk" } + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "z" +lto = true +debug = false +panic = "abort" diff --git a/examples/mpc-contract/README.md b/examples/mpc-contract/README.md new file mode 100644 index 000000000..fac68a136 --- /dev/null +++ b/examples/mpc-contract/README.md @@ -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 +``` diff --git a/examples/mpc-contract/build.sh b/examples/mpc-contract/build.sh new file mode 100755 index 000000000..58703db28 --- /dev/null +++ b/examples/mpc-contract/build.sh @@ -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 diff --git a/examples/mpc-contract/create.sh b/examples/mpc-contract/create.sh new file mode 100755 index 000000000..67c9fc9cf --- /dev/null +++ b/examples/mpc-contract/create.sh @@ -0,0 +1,2 @@ +#!/bin/bash +env NEAR_ENV=localnet near create-account "$1.node0" --keyPath ~/.near/localnet/node0/validator_key.json --masterAccount node0 diff --git a/examples/mpc-contract/deploy.sh b/examples/mpc-contract/deploy.sh new file mode 100755 index 000000000..31a7285e4 --- /dev/null +++ b/examples/mpc-contract/deploy.sh @@ -0,0 +1,2 @@ +#!/bin/bash +env NEAR_ENV=localnet near deploy "$1.node0" res/mpc_contract.wasm diff --git a/examples/mpc-contract/res/.gitignore b/examples/mpc-contract/res/.gitignore new file mode 100644 index 000000000..fae4549b6 --- /dev/null +++ b/examples/mpc-contract/res/.gitignore @@ -0,0 +1,5 @@ +# Ignore all files +* + +# Except this to keep the directory +!.gitignore \ No newline at end of file diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs new file mode 100644 index 000000000..a31845ee7 --- /dev/null +++ b/examples/mpc-contract/src/lib.rs @@ -0,0 +1,58 @@ +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::collections::LookupMap; +use near_sdk::{env, log, near_bindgen, AccountId, BorshStorageKey, CryptoHash}; + +#[derive(BorshSerialize, BorshStorageKey)] +#[borsh(crate = "near_sdk::borsh")] +struct RecordsKey; + +#[derive(BorshDeserialize, BorshSerialize)] +#[borsh(crate = "near_sdk::borsh")] +pub struct SignatureRequest { + account_id: AccountId, + payload: String, +} + +#[near_bindgen] +#[derive(BorshDeserialize, BorshSerialize)] +#[borsh(crate = "near_sdk::borsh")] +pub struct MpcContract { + // Pending requests + requests: LookupMap, +} + +impl Default for MpcContract { + fn default() -> Self { + Self { requests: LookupMap::new(RecordsKey) } + } +} + +const YIELD_NUM_BLOCKS: u64 = 100; +const DATA_ID_REGISTER: u64 = 0; + +#[near_bindgen] +impl MpcContract { + #[payable] + pub fn sign(&mut self, payload: String) { + let account_id = env::signer_account_id(); + + let promise = + env::promise_await_data(account_id.clone(), YIELD_NUM_BLOCKS, DATA_ID_REGISTER); + + log!("Created data-awaiting promise with index {:?}", promise); + + let data_id: CryptoHash = + env::read_register(DATA_ID_REGISTER).expect("").try_into().expect(""); + + log!( + "request by account_id {} for payload {} is pending with data id {:?}", + account_id, + payload, + data_id + ); + + self.requests.insert(&data_id, &SignatureRequest { account_id, payload }); + + env::promise_return(promise); + } +} diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index 1e89fe9ad..61048078f 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -491,6 +491,24 @@ pub fn alt_bn128_pairing_check(value: &[u8]) -> bool { // ################ // # Promises API # // ################ +pub fn promise_await_data( + account_id: AccountId, + yield_num_blocks: u64, + register_id: u64, +) -> PromiseIndex { + let account_id = account_id.as_bytes(); + unsafe { + PromiseIndex(sys::promise_await_data( + account_id.len() as _, + account_id.as_ptr() as _, + yield_num_blocks as _, + register_id as _, + )) + } +} + +//pub fn promise_submit_data(data_id_ptr: u64, payload_len: u64, payload_ptr: u64); + /// Creates a promise that will execute a method on account with given arguments and attaches /// the given amount and gas. pub fn promise_create( diff --git a/near-sys/src/lib.rs b/near-sys/src/lib.rs index 786476308..ca1865623 100644 --- a/near-sys/src/lib.rs +++ b/near-sys/src/lib.rs @@ -147,6 +147,16 @@ extern "C" { beneficiary_id_ptr: u64, ); // ####################### + // # Promise API await/submit # + // ####################### + pub fn promise_await_data( + account_id_len: u64, + account_id_ptr: u64, + yield_num_blocks: u64, + register_id: u64, + ) -> u64; + pub fn promise_submit_data(data_id_ptr: u64, payload_len: u64, payload_ptr: u64); + // ####################### // # Promise API results # // ####################### pub fn promise_results_count() -> u64; From 5acd50ae0cbda2544deb37ca2960aa8eead9fb42 Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Wed, 17 Jan 2024 00:42:56 -0500 Subject: [PATCH 02/18] add sign_respond --- examples/mpc-contract/src/lib.rs | 27 +++++++++++++-------------- near-sdk/src/environment/env.rs | 11 +++++++---- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index a31845ee7..792334ac2 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -1,5 +1,5 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; -use near_sdk::collections::LookupMap; +use near_sdk::collections::TreeMap; use near_sdk::{env, log, near_bindgen, AccountId, BorshStorageKey, CryptoHash}; #[derive(BorshSerialize, BorshStorageKey)] @@ -18,12 +18,12 @@ pub struct SignatureRequest { #[borsh(crate = "near_sdk::borsh")] pub struct MpcContract { // Pending requests - requests: LookupMap, + requests: TreeMap, } impl Default for MpcContract { fn default() -> Self { - Self { requests: LookupMap::new(RecordsKey) } + Self { requests: TreeMap::new(RecordsKey) } } } @@ -36,23 +36,22 @@ impl MpcContract { pub fn sign(&mut self, payload: String) { let account_id = env::signer_account_id(); - let promise = - env::promise_await_data(account_id.clone(), YIELD_NUM_BLOCKS, DATA_ID_REGISTER); - - log!("Created data-awaiting promise with index {:?}", promise); + let promise = env::promise_await_data(&account_id, YIELD_NUM_BLOCKS, DATA_ID_REGISTER); let data_id: CryptoHash = env::read_register(DATA_ID_REGISTER).expect("").try_into().expect(""); - log!( - "request by account_id {} for payload {} is pending with data id {:?}", - account_id, - payload, - data_id - ); - self.requests.insert(&data_id, &SignatureRequest { account_id, payload }); env::promise_return(promise); } + + #[payable] + pub fn sign_respond(&mut self, signature: String) { + // TODO: really the signer should pass the data id, + // but for now just match this response to an arbitrary pending request + let data_id = self.requests.min().expect(""); + + env::promise_submit_data(&data_id, &signature.into_bytes()); + } } diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index 61048078f..ca55b844d 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -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 = @@ -492,11 +492,11 @@ pub fn alt_bn128_pairing_check(value: &[u8]) -> bool { // # Promises API # // ################ pub fn promise_await_data( - account_id: AccountId, + account_id: &AccountId, yield_num_blocks: u64, register_id: u64, ) -> PromiseIndex { - let account_id = account_id.as_bytes(); + let account_id: &str = account_id.as_ref(); unsafe { PromiseIndex(sys::promise_await_data( account_id.len() as _, @@ -507,7 +507,10 @@ pub fn promise_await_data( } } -//pub fn promise_submit_data(data_id_ptr: u64, payload_len: u64, payload_ptr: u64); +// TODO: return some kind of success/failure result +pub fn promise_submit_data(data_id: &CryptoHash, data: &[u8]) { + unsafe { sys::promise_submit_data(data_id.as_ptr() as _, data.len() as _, data.as_ptr() as _) } +} /// Creates a promise that will execute a method on account with given arguments and attaches /// the given amount and gas. From f94b6974517900664560474e7d49d05f9ab34591 Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Wed, 17 Jan 2024 12:05:43 -0500 Subject: [PATCH 03/18] update await_data host fn signature --- examples/mpc-contract/src/lib.rs | 14 +++++++++----- near-sdk/src/environment/env.rs | 16 ++-------------- near-sys/src/lib.rs | 7 +------ 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 792334ac2..ada4b3868 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -34,24 +34,28 @@ const DATA_ID_REGISTER: u64 = 0; impl MpcContract { #[payable] pub fn sign(&mut self, payload: String) { - let account_id = env::signer_account_id(); - - let promise = env::promise_await_data(&account_id, YIELD_NUM_BLOCKS, DATA_ID_REGISTER); + // Create the data-awaiting promise + let promise = env::promise_await_data(YIELD_NUM_BLOCKS, DATA_ID_REGISTER); + // Retrieve the generated data id let data_id: CryptoHash = env::read_register(DATA_ID_REGISTER).expect("").try_into().expect(""); - self.requests.insert(&data_id, &SignatureRequest { account_id, payload }); + // Record the pending request to be picked up by MPC indexers + self.requests + .insert(&data_id, &SignatureRequest { account_id: env::signer_account_id(), payload }); env::promise_return(promise); } #[payable] pub fn sign_respond(&mut self, signature: String) { - // TODO: really the signer should pass the data id, + // TODO: really the caller of this function should pass the data id for the // but for now just match this response to an arbitrary pending request let data_id = self.requests.min().expect(""); + log!("submitting response {} for data id {:?}", &signature, &data_id); + env::promise_submit_data(&data_id, &signature.into_bytes()); } } diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index ca55b844d..22b4e9db6 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -491,20 +491,8 @@ pub fn alt_bn128_pairing_check(value: &[u8]) -> bool { // ################ // # Promises API # // ################ -pub fn promise_await_data( - account_id: &AccountId, - yield_num_blocks: u64, - register_id: u64, -) -> PromiseIndex { - let account_id: &str = account_id.as_ref(); - unsafe { - PromiseIndex(sys::promise_await_data( - account_id.len() as _, - account_id.as_ptr() as _, - yield_num_blocks as _, - register_id as _, - )) - } +pub fn promise_await_data(yield_num_blocks: u64, register_id: u64) -> PromiseIndex { + unsafe { PromiseIndex(sys::promise_await_data(yield_num_blocks as _, register_id as _)) } } // TODO: return some kind of success/failure result diff --git a/near-sys/src/lib.rs b/near-sys/src/lib.rs index ca1865623..ce02abd1f 100644 --- a/near-sys/src/lib.rs +++ b/near-sys/src/lib.rs @@ -149,12 +149,7 @@ extern "C" { // ####################### // # Promise API await/submit # // ####################### - pub fn promise_await_data( - account_id_len: u64, - account_id_ptr: u64, - yield_num_blocks: u64, - register_id: u64, - ) -> u64; + pub fn promise_await_data(yield_num_blocks: u64, register_id: u64) -> u64; pub fn promise_submit_data(data_id_ptr: u64, payload_len: u64, payload_ptr: u64); // ####################### // # Promise API results # From 2d6a77b3d2177859a6750f91873f4787d1296988 Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Wed, 17 Jan 2024 15:24:02 -0500 Subject: [PATCH 04/18] chain a callback cleaning up the request --- examples/mpc-contract/src/lib.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index ada4b3868..9b0208f8a 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -1,6 +1,8 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::collections::TreeMap; -use near_sdk::{env, log, near_bindgen, AccountId, BorshStorageKey, CryptoHash}; +use near_sdk::{ + env, log, near_bindgen, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, NearToken, +}; #[derive(BorshSerialize, BorshStorageKey)] #[borsh(crate = "near_sdk::borsh")] @@ -35,17 +37,25 @@ impl MpcContract { #[payable] pub fn sign(&mut self, payload: String) { // Create the data-awaiting promise - let promise = env::promise_await_data(YIELD_NUM_BLOCKS, DATA_ID_REGISTER); + let data_promise = env::promise_await_data(YIELD_NUM_BLOCKS, DATA_ID_REGISTER); - // Retrieve the generated data id + // Record the pending request to be picked up by MPC indexers let data_id: CryptoHash = env::read_register(DATA_ID_REGISTER).expect("").try_into().expect(""); - - // Record the pending request to be picked up by MPC indexers self.requests .insert(&data_id, &SignatureRequest { account_id: env::signer_account_id(), payload }); - env::promise_return(promise); + // Add a callback for post-processing + let _callback_promise = env::promise_then( + data_promise, + env::current_account_id(), + "sign_on_finish", + &serde_json::to_vec(&(data_id,)).unwrap(), + NearToken::from_near(0), + Gas::from_tgas(10), + ); + + env::promise_return(data_promise); } #[payable] @@ -54,8 +64,14 @@ impl MpcContract { // but for now just match this response to an arbitrary pending request let data_id = self.requests.min().expect(""); - log!("submitting response {} for data id {:?}", &signature, &data_id); + // validate_signature(...) + + log!("received response {} for data id {:?}", &signature, &data_id); env::promise_submit_data(&data_id, &signature.into_bytes()); } + + pub fn sign_on_finish(&mut self, data_id: CryptoHash) { + self.requests.remove(&data_id); + } } From 1dc5e88b6ade6a3a0cbfdff6594d40077f59d00c Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Wed, 17 Jan 2024 15:44:14 -0500 Subject: [PATCH 05/18] use hex data ids to submit --- examples/mpc-contract/Cargo.toml | 1 + examples/mpc-contract/src/lib.rs | 33 ++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/examples/mpc-contract/Cargo.toml b/examples/mpc-contract/Cargo.toml index 453db4d0c..014dac594 100644 --- a/examples/mpc-contract/Cargo.toml +++ b/examples/mpc-contract/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib"] [dependencies] near-sdk = { path = "../../near-sdk" } +hex = "0.4" [profile.release] codegen-units = 1 diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 9b0208f8a..17925a9e7 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -1,7 +1,8 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::collections::TreeMap; use near_sdk::{ - env, log, near_bindgen, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, NearToken, + env, log, near_bindgen, require, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, + NearToken, }; #[derive(BorshSerialize, BorshStorageKey)] @@ -34,7 +35,7 @@ const DATA_ID_REGISTER: u64 = 0; #[near_bindgen] impl MpcContract { - #[payable] + /// User-facing API: accepts payload and returns signature pub fn sign(&mut self, payload: String) { // Create the data-awaiting promise let data_promise = env::promise_await_data(YIELD_NUM_BLOCKS, DATA_ID_REGISTER); @@ -58,20 +59,36 @@ impl MpcContract { env::promise_return(data_promise); } - #[payable] - pub fn sign_respond(&mut self, signature: String) { - // TODO: really the caller of this function should pass the data id for the - // but for now just match this response to an arbitrary pending request - let data_id = self.requests.min().expect(""); + /// Called by MPC participants to submit a signature + pub fn sign_respond(&mut self, data_id: String, signature: String) { + // For testing convenience, we accept a hexadecimal 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; + require!(self.requests.contains_key(&data_id)); - // validate_signature(...) + // check that caller is allowed to respond, signature is valid, etc. + // ... log!("received response {} for data id {:?}", &signature, &data_id); env::promise_submit_data(&data_id, &signature.into_bytes()); } + /// Callback used to clean up internal state pub fn sign_on_finish(&mut self, data_id: CryptoHash) { self.requests.remove(&data_id); } + + /// Helper for local testing + pub fn log_pending_requests(&self) { + for (data_id, request) in self.requests.iter() { + log!( + "{}: account_id={} payload={}", + hex::encode(data_id), + request.account_id, + request.payload + ); + } + } } From 84a9e01f1b7d181782905517c12f94524dece445 Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Wed, 17 Jan 2024 15:59:06 -0500 Subject: [PATCH 06/18] superficial --- examples/mpc-contract/src/.lib.rs.swp | Bin 0 -> 12288 bytes examples/mpc-contract/src/lib.rs | 16 ++++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 examples/mpc-contract/src/.lib.rs.swp diff --git a/examples/mpc-contract/src/.lib.rs.swp b/examples/mpc-contract/src/.lib.rs.swp new file mode 100644 index 0000000000000000000000000000000000000000..488d18597d7df8143629476949f5cc84f64e4bf4 GIT binary patch literal 12288 zcmeHNU2has7(Re}hyq6MypYo+X?AsYwty0xT1aWJthA+ci-{p*dUob)ckJxUc;-w$ zsMQ<)0gBOhM>O7Iq9&S%hD3gV@xnXfrPl^RqS5Ef$L{PDNF?46XOgG$bw1wrd7t+^ zXVYnoyxy3hFKZ(N$5VuuCqL)U-#D=IwGRmi*fN(L_2R)tGin#qW=_p%CTlrd6Q-+~ zUN_h6op`{y%bBPJ)^be*b-u>BzQcoh*EegX=Ssm$Sr3F+ckGrXg4__rZNWgnz{4;Q z%69GeK63QP;V9J)m4@guPhWVLk%~SA0|f&G0|f&G0|f&G0|f&G0|f(*CF@tr2MDfg8XTzyr<$$ABT=An+WpANXw_A>RR? z0zTjZU0@bC3jF;fA$Ng0z-{1b;41I|a1N*fF8~C%^#mb50$%~w01G$<>;=BqOUQL# z8E62vA1CBj;B8<7m;s&z2=L1uLcRsw0}Mb1>VO990RGxd$e+MX;4*L?*a!T+3-bWi z0SmYQEC9QKUBC}JF)r{8-~c1Q9$-7L4Y>IjAs+(_I0qaA?t=TDfiHp2fKPy{fQrdS zKym!P7}z8fNBnp2O5IgPi8^Hu!QTpbAT^(@J03DR`%Yb_@0n?Op(_pBs%~l1;cL3i zUDLC8*;>T$TpgJKoYzxi?SS6Eq#Tnp#w$wo3!+}qFd@iV($ni|(Pzg@ZWt95D zc4a`B6L~-=F9yCVquD8O=!zm##^@&x1z2q96YqV|G4m#!GZYU#Psx)b? z(+RQeOYaN|I$1*}U`UIZ%QOtQMWshghclOkKG=ZwE=&(3lbo_PZYfKRrFH;UJd+3L zmp7cdE4q%iT|3}L5VpFuG?X1ZiU>LI4nE<+TaB5B zC$y@xXR2zj!W>AJa%pOPQ^$@n0=sv1cy%CIAIf2Wp$Eq zRFT?jOwAhSW{p=HvyJ8%V`6-I+L#(|4%UNNeD=5CEpu1b+t8_@J_9U|{R%hB!@asusc50QDwto+bt?@fW3CcKxnYJva1_6O^~w(t#WZoYZ@SWpp~R|E z)<|9>lmiJTJKXKpI_Q4fvcPrPElM8W3uF!bRsJHHeI+Xfk&*CR_7n@5HPdXJkIOsV z$W&fEo?p4m#h_1VMW&mXC=DbDQ8T7TCym()Gsdaua}#HqRl3E)z5_6C0(7AKD)W>kYSB;#nX103 zzD71=R)K8JCd=Wy67&KhTx&B^jWj;jh!#@rBR!Kol)0+ps>BO2X=>vGSvupJ6Uo=H zSQ`&rRLNtsz{(PLFX6LYRB3s?G}P%73!qs1O;S%Tv7mf-1p0utbAtN}MIFx~oie&O zm`#h3j?+o0L_98|cod zY*6E4ck)3>XNu>UPt@95MUz!43Yl7@RFR>JNyv=892PM_ev8$_UIO`*ZZ34XAUL04 zzOK7`wH(VeILp^7$yNcI5{tK4=t%5UYA0yipG2XWj9)}t4u&n6cFQcU^!Eu>nos)^ zRidO8ND-QHiw;_zz}~|YO0Q$I6v+agR_l87qExvAtyo-OH7}o1O(!`-Xk$IS%&Rnd z(oFB8wmDtJJmoYU*T#Mw)#-S=v20*pnXxtRNhI9=)?kv*(vQ$69R9a+^e9Lvk)B{n zT*Vu?wHeoj*>w4e-ACE1s_jyhW+{tzw%C?nt?n&k)eJug=e-H4nLT1vWoVUPR~U~O j#c?BdZ+h4^nbJnW>w2Qn(OjFze_h{*;`@Mz!?S+?Wz0wd literal 0 HcmV?d00001 diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 17925a9e7..5a35dbf5a 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -20,7 +20,6 @@ pub struct SignatureRequest { #[derive(BorshDeserialize, BorshSerialize)] #[borsh(crate = "near_sdk::borsh")] pub struct MpcContract { - // Pending requests requests: TreeMap, } @@ -30,9 +29,15 @@ impl Default for MpcContract { } } -const YIELD_NUM_BLOCKS: u64 = 100; +// Register used to receive data id from `promise_await_data`. const DATA_ID_REGISTER: u64 = 0; +// Number of blocks for which `sign` will await a signature response. +const YIELD_NUM_BLOCKS: u64 = 100; + +// Prepaid gas for a `sign_on_finish` call +const SIGN_ON_FINISH_CALL_GAS: Gas = Gas::from_tgas(10); + #[near_bindgen] impl MpcContract { /// User-facing API: accepts payload and returns signature @@ -53,7 +58,7 @@ impl MpcContract { "sign_on_finish", &serde_json::to_vec(&(data_id,)).unwrap(), NearToken::from_near(0), - Gas::from_tgas(10), + SIGN_ON_FINISH_CALL_GAS, ); env::promise_return(data_promise); @@ -61,7 +66,6 @@ impl MpcContract { /// Called by MPC participants to submit a signature pub fn sign_respond(&mut self, data_id: String, signature: String) { - // For testing convenience, we accept a hexadecimal 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; @@ -75,12 +79,12 @@ impl MpcContract { env::promise_submit_data(&data_id, &signature.into_bytes()); } - /// Callback used to clean up internal state + /// Callback used to clean up internal state after a request is processed pub fn sign_on_finish(&mut self, data_id: CryptoHash) { self.requests.remove(&data_id); } - /// Helper for local testing + /// Helper for local testing; prints all pending requests pub fn log_pending_requests(&self) { for (data_id, request) in self.requests.iter() { log!( From 08c0860a2b18757876d6ff8e2a2380450c7a7d6d Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Wed, 17 Jan 2024 16:36:21 -0500 Subject: [PATCH 07/18] add post-processing step --- examples/mpc-contract/src/lib.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 5a35dbf5a..0c8a01693 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -2,7 +2,7 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::collections::TreeMap; use near_sdk::{ env, log, near_bindgen, require, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, - NearToken, + NearToken, PromiseResult, }; #[derive(BorshSerialize, BorshStorageKey)] @@ -52,7 +52,7 @@ impl MpcContract { .insert(&data_id, &SignatureRequest { account_id: env::signer_account_id(), payload }); // Add a callback for post-processing - let _callback_promise = env::promise_then( + let callback_promise = env::promise_then( data_promise, env::current_account_id(), "sign_on_finish", @@ -61,7 +61,7 @@ impl MpcContract { SIGN_ON_FINISH_CALL_GAS, ); - env::promise_return(data_promise); + env::promise_return(callback_promise); } /// Called by MPC participants to submit a signature @@ -69,19 +69,28 @@ impl MpcContract { let mut data_id_buf = [0u8; 32]; hex::decode_to_slice(data_id, &mut data_id_buf).expect(""); let data_id = data_id_buf; + require!(self.requests.contains_key(&data_id)); // check that caller is allowed to respond, signature is valid, etc. // ... - log!("received response {} for data id {:?}", &signature, &data_id); - + log!("submitting response {} for data id {:?}", &signature, &data_id); env::promise_submit_data(&data_id, &signature.into_bytes()); } /// Callback used to clean up internal state after a request is processed - pub fn sign_on_finish(&mut self, data_id: CryptoHash) { + pub fn sign_on_finish(&mut self, data_id: CryptoHash) -> Option { self.requests.remove(&data_id); + + require!(env::promise_results_count() == 1); + match env::promise_result(0) { + PromiseResult::Successful(x) => { + let signature = std::str::from_utf8(&x).unwrap().to_string(); + Some(signature + "_post") + } + _ => None, + } } /// Helper for local testing; prints all pending requests From e04b2762db72afdd767ba8045150038b62950cfe Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Wed, 17 Jan 2024 22:32:21 -0500 Subject: [PATCH 08/18] clean up --- examples/mpc-contract/create.sh | 2 -- examples/mpc-contract/deploy.sh | 2 -- examples/mpc-contract/src/.lib.rs.swp | Bin 12288 -> 0 bytes 3 files changed, 4 deletions(-) delete mode 100755 examples/mpc-contract/create.sh delete mode 100755 examples/mpc-contract/deploy.sh delete mode 100644 examples/mpc-contract/src/.lib.rs.swp diff --git a/examples/mpc-contract/create.sh b/examples/mpc-contract/create.sh deleted file mode 100755 index 67c9fc9cf..000000000 --- a/examples/mpc-contract/create.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -env NEAR_ENV=localnet near create-account "$1.node0" --keyPath ~/.near/localnet/node0/validator_key.json --masterAccount node0 diff --git a/examples/mpc-contract/deploy.sh b/examples/mpc-contract/deploy.sh deleted file mode 100755 index 31a7285e4..000000000 --- a/examples/mpc-contract/deploy.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -env NEAR_ENV=localnet near deploy "$1.node0" res/mpc_contract.wasm diff --git a/examples/mpc-contract/src/.lib.rs.swp b/examples/mpc-contract/src/.lib.rs.swp deleted file mode 100644 index 488d18597d7df8143629476949f5cc84f64e4bf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeHNU2has7(Re}hyq6MypYo+X?AsYwty0xT1aWJthA+ci-{p*dUob)ckJxUc;-w$ zsMQ<)0gBOhM>O7Iq9&S%hD3gV@xnXfrPl^RqS5Ef$L{PDNF?46XOgG$bw1wrd7t+^ zXVYnoyxy3hFKZ(N$5VuuCqL)U-#D=IwGRmi*fN(L_2R)tGin#qW=_p%CTlrd6Q-+~ zUN_h6op`{y%bBPJ)^be*b-u>BzQcoh*EegX=Ssm$Sr3F+ckGrXg4__rZNWgnz{4;Q z%69GeK63QP;V9J)m4@guPhWVLk%~SA0|f&G0|f&G0|f&G0|f&G0|f(*CF@tr2MDfg8XTzyr<$$ABT=An+WpANXw_A>RR? z0zTjZU0@bC3jF;fA$Ng0z-{1b;41I|a1N*fF8~C%^#mb50$%~w01G$<>;=BqOUQL# z8E62vA1CBj;B8<7m;s&z2=L1uLcRsw0}Mb1>VO990RGxd$e+MX;4*L?*a!T+3-bWi z0SmYQEC9QKUBC}JF)r{8-~c1Q9$-7L4Y>IjAs+(_I0qaA?t=TDfiHp2fKPy{fQrdS zKym!P7}z8fNBnp2O5IgPi8^Hu!QTpbAT^(@J03DR`%Yb_@0n?Op(_pBs%~l1;cL3i zUDLC8*;>T$TpgJKoYzxi?SS6Eq#Tnp#w$wo3!+}qFd@iV($ni|(Pzg@ZWt95D zc4a`B6L~-=F9yCVquD8O=!zm##^@&x1z2q96YqV|G4m#!GZYU#Psx)b? z(+RQeOYaN|I$1*}U`UIZ%QOtQMWshghclOkKG=ZwE=&(3lbo_PZYfKRrFH;UJd+3L zmp7cdE4q%iT|3}L5VpFuG?X1ZiU>LI4nE<+TaB5B zC$y@xXR2zj!W>AJa%pOPQ^$@n0=sv1cy%CIAIf2Wp$Eq zRFT?jOwAhSW{p=HvyJ8%V`6-I+L#(|4%UNNeD=5CEpu1b+t8_@J_9U|{R%hB!@asusc50QDwto+bt?@fW3CcKxnYJva1_6O^~w(t#WZoYZ@SWpp~R|E z)<|9>lmiJTJKXKpI_Q4fvcPrPElM8W3uF!bRsJHHeI+Xfk&*CR_7n@5HPdXJkIOsV z$W&fEo?p4m#h_1VMW&mXC=DbDQ8T7TCym()Gsdaua}#HqRl3E)z5_6C0(7AKD)W>kYSB;#nX103 zzD71=R)K8JCd=Wy67&KhTx&B^jWj;jh!#@rBR!Kol)0+ps>BO2X=>vGSvupJ6Uo=H zSQ`&rRLNtsz{(PLFX6LYRB3s?G}P%73!qs1O;S%Tv7mf-1p0utbAtN}MIFx~oie&O zm`#h3j?+o0L_98|cod zY*6E4ck)3>XNu>UPt@95MUz!43Yl7@RFR>JNyv=892PM_ev8$_UIO`*ZZ34XAUL04 zzOK7`wH(VeILp^7$yNcI5{tK4=t%5UYA0yipG2XWj9)}t4u&n6cFQcU^!Eu>nos)^ zRidO8ND-QHiw;_zz}~|YO0Q$I6v+agR_l87qExvAtyo-OH7}o1O(!`-Xk$IS%&Rnd z(oFB8wmDtJJmoYU*T#Mw)#-S=v20*pnXxtRNhI9=)?kv*(vQ$69R9a+^e9Lvk)B{n zT*Vu?wHeoj*>w4e-ACE1s_jyhW+{tzw%C?nt?n&k)eJug=e-H4nLT1vWoVUPR~U~O j#c?BdZ+h4^nbJnW>w2Qn(OjFze_h{*;`@Mz!?S+?Wz0wd From aabd53f955cc96d760e648606b398d5b974157bc Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Thu, 18 Jan 2024 12:51:22 -0500 Subject: [PATCH 09/18] update yield API --- examples/mpc-contract/src/lib.rs | 29 ++++++++++++----------------- near-sdk/src/environment/env.rs | 24 ++++++++++++++++++++---- near-sys/src/lib.rs | 12 ++++++++++-- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 0c8a01693..71aa70661 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -42,26 +42,21 @@ const SIGN_ON_FINISH_CALL_GAS: Gas = Gas::from_tgas(10); impl MpcContract { /// User-facing API: accepts payload and returns signature pub fn sign(&mut self, payload: String) { - // Create the data-awaiting promise - let data_promise = env::promise_await_data(YIELD_NUM_BLOCKS, DATA_ID_REGISTER); + let promise = env::promise_yield_create( + "sign_on_finish", + &[], + SIGN_ON_FINISH_CALL_GAS, + YIELD_NUM_BLOCKS, + DATA_ID_REGISTER, + ); - // Record the pending request to be picked up by MPC indexers + // Record the pending request let data_id: CryptoHash = env::read_register(DATA_ID_REGISTER).expect("").try_into().expect(""); self.requests .insert(&data_id, &SignatureRequest { account_id: env::signer_account_id(), payload }); - // Add a callback for post-processing - let callback_promise = env::promise_then( - data_promise, - env::current_account_id(), - "sign_on_finish", - &serde_json::to_vec(&(data_id,)).unwrap(), - NearToken::from_near(0), - SIGN_ON_FINISH_CALL_GAS, - ); - - env::promise_return(callback_promise); + env::promise_return(promise); } /// Called by MPC participants to submit a signature @@ -76,12 +71,12 @@ impl MpcContract { // ... log!("submitting response {} for data id {:?}", &signature, &data_id); - env::promise_submit_data(&data_id, &signature.into_bytes()); + env::promise_yield_resume(&data_id, &signature.into_bytes()); } /// Callback used to clean up internal state after a request is processed - pub fn sign_on_finish(&mut self, data_id: CryptoHash) -> Option { - self.requests.remove(&data_id); + pub fn sign_on_finish(&mut self) -> Option { + //self.requests.remove(&data_id); require!(env::promise_results_count() == 1); match env::promise_result(0) { diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index 22b4e9db6..0cb055630 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -491,13 +491,29 @@ pub fn alt_bn128_pairing_check(value: &[u8]) -> bool { // ################ // # Promises API # // ################ -pub fn promise_await_data(yield_num_blocks: u64, register_id: u64) -> PromiseIndex { - unsafe { PromiseIndex(sys::promise_await_data(yield_num_blocks as _, register_id as _)) } +pub fn promise_yield_create( + function_name: &str, + arguments: &[u8], + gas: Gas, + yield_num_blocks: u64, + 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(), + yield_num_blocks as _, + register_id as _, + )) + } } // TODO: return some kind of success/failure result -pub fn promise_submit_data(data_id: &CryptoHash, data: &[u8]) { - unsafe { sys::promise_submit_data(data_id.as_ptr() as _, data.len() as _, data.as_ptr() as _) } +pub fn promise_yield_resume(data_id: &CryptoHash, data: &[u8]) { + unsafe { sys::promise_yield_resume(data_id.as_ptr() as _, data.len() as _, data.as_ptr() as _) } } /// Creates a promise that will execute a method on account with given arguments and attaches diff --git a/near-sys/src/lib.rs b/near-sys/src/lib.rs index ce02abd1f..f8a6c9d2b 100644 --- a/near-sys/src/lib.rs +++ b/near-sys/src/lib.rs @@ -149,8 +149,16 @@ extern "C" { // ####################### // # Promise API await/submit # // ####################### - pub fn promise_await_data(yield_num_blocks: u64, register_id: u64) -> u64; - pub fn promise_submit_data(data_id_ptr: u64, payload_len: u64, payload_ptr: u64); + pub fn promise_yield_create( + function_name_len: u64, + function_name_ptr: u64, + arguments_len: u64, + arguments_ptr: u64, + gas: u64, + yield_num_blocks: u64, + register_id: u64, + ) -> u64; + pub fn promise_yield_resume(data_id_ptr: u64, payload_len: u64, payload_ptr: u64); // ####################### // # Promise API results # // ####################### From 418035cba05b863682003188b347e541716bac08 Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Thu, 18 Jan 2024 14:45:24 -0500 Subject: [PATCH 10/18] add cleanup callback --- examples/mpc-contract/src/lib.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 71aa70661..d2f96eacf 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -36,7 +36,10 @@ const DATA_ID_REGISTER: u64 = 0; const YIELD_NUM_BLOCKS: u64 = 100; // Prepaid gas for a `sign_on_finish` call -const SIGN_ON_FINISH_CALL_GAS: Gas = Gas::from_tgas(10); +const SIGN_ON_FINISH_CALL_GAS: Gas = Gas::from_tgas(5); + +// Prepaid gas for a `sign_on_finish` call +const REMOVE_REQUEST_CALL_GAS: Gas = Gas::from_tgas(5); #[near_bindgen] impl MpcContract { @@ -56,6 +59,16 @@ impl MpcContract { self.requests .insert(&data_id, &SignatureRequest { account_id: env::signer_account_id(), payload }); + // Schedule clean-up of self.requests after the yield execution completes + env::promise_then( + promise, + env::current_account_id(), + "remove_request", + &serde_json::to_vec(&(data_id,)).unwrap(), + NearToken::from_near(0), + REMOVE_REQUEST_CALL_GAS, + ); + env::promise_return(promise); } @@ -74,10 +87,8 @@ impl MpcContract { env::promise_yield_resume(&data_id, &signature.into_bytes()); } - /// Callback used to clean up internal state after a request is processed + /// Callback receiving the externally submitted data pub fn sign_on_finish(&mut self) -> Option { - //self.requests.remove(&data_id); - require!(env::promise_results_count() == 1); match env::promise_result(0) { PromiseResult::Successful(x) => { @@ -88,6 +99,11 @@ impl MpcContract { } } + /// Callback used to clean up the local state of the contract + pub fn remove_request(&mut self, data_id: CryptoHash) { + self.requests.remove(&data_id); + } + /// Helper for local testing; prints all pending requests pub fn log_pending_requests(&self) { for (data_id, request) in self.requests.iter() { From a476ff23e65636d18c3830db168c745a0f6471bf Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Fri, 19 Jan 2024 02:12:03 -0500 Subject: [PATCH 11/18] simplify sign_on_finish using callback_unwrap --- examples/mpc-contract/src/lib.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index d2f96eacf..365376673 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -2,7 +2,7 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::collections::TreeMap; use near_sdk::{ env, log, near_bindgen, require, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, - NearToken, PromiseResult, + NearToken, PromiseError, PromiseResult, }; #[derive(BorshSerialize, BorshStorageKey)] @@ -84,19 +84,12 @@ impl MpcContract { // ... log!("submitting response {} for data id {:?}", &signature, &data_id); - env::promise_yield_resume(&data_id, &signature.into_bytes()); + env::promise_yield_resume(&data_id, &serde_json::to_vec(&signature).unwrap()); } /// Callback receiving the externally submitted data - pub fn sign_on_finish(&mut self) -> Option { - require!(env::promise_results_count() == 1); - match env::promise_result(0) { - PromiseResult::Successful(x) => { - let signature = std::str::from_utf8(&x).unwrap().to_string(); - Some(signature + "_post") - } - _ => None, - } + pub fn sign_on_finish(#[callback_unwrap] signature: String) -> String { + signature + " post" } /// Callback used to clean up the local state of the contract From 5007a9735bedee9d159f626ce2e34358482e333a Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Tue, 30 Jan 2024 10:10:28 -0500 Subject: [PATCH 12/18] remove arg yield_num_blocks --- examples/mpc-contract/src/lib.rs | 6 +----- near-sdk/src/environment/env.rs | 2 -- near-sys/src/lib.rs | 1 - 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 365376673..7543a2203 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -2,7 +2,7 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::collections::TreeMap; use near_sdk::{ env, log, near_bindgen, require, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, - NearToken, PromiseError, PromiseResult, + NearToken, }; #[derive(BorshSerialize, BorshStorageKey)] @@ -32,9 +32,6 @@ impl Default for MpcContract { // Register used to receive data id from `promise_await_data`. const DATA_ID_REGISTER: u64 = 0; -// Number of blocks for which `sign` will await a signature response. -const YIELD_NUM_BLOCKS: u64 = 100; - // Prepaid gas for a `sign_on_finish` call const SIGN_ON_FINISH_CALL_GAS: Gas = Gas::from_tgas(5); @@ -49,7 +46,6 @@ impl MpcContract { "sign_on_finish", &[], SIGN_ON_FINISH_CALL_GAS, - YIELD_NUM_BLOCKS, DATA_ID_REGISTER, ); diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index 0cb055630..dd5c4254b 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -495,7 +495,6 @@ pub fn promise_yield_create( function_name: &str, arguments: &[u8], gas: Gas, - yield_num_blocks: u64, register_id: u64, ) -> PromiseIndex { unsafe { @@ -505,7 +504,6 @@ pub fn promise_yield_create( arguments.len() as _, arguments.as_ptr() as _, gas.as_gas(), - yield_num_blocks as _, register_id as _, )) } diff --git a/near-sys/src/lib.rs b/near-sys/src/lib.rs index f8a6c9d2b..04209b7e5 100644 --- a/near-sys/src/lib.rs +++ b/near-sys/src/lib.rs @@ -155,7 +155,6 @@ extern "C" { arguments_len: u64, arguments_ptr: u64, gas: u64, - yield_num_blocks: u64, register_id: u64, ) -> u64; pub fn promise_yield_resume(data_id_ptr: u64, payload_len: u64, payload_ptr: u64); From 3d123c1cb89353b9c82e84380f9502c4c550507a Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Tue, 13 Feb 2024 12:24:36 -0500 Subject: [PATCH 13/18] handle timeout case --- examples/mpc-contract/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 7543a2203..fe61054b2 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -2,7 +2,7 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::collections::TreeMap; use near_sdk::{ env, log, near_bindgen, require, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, - NearToken, + NearToken, PromiseError, }; #[derive(BorshSerialize, BorshStorageKey)] @@ -84,8 +84,13 @@ impl MpcContract { } /// Callback receiving the externally submitted data - pub fn sign_on_finish(#[callback_unwrap] signature: String) -> String { - signature + " post" + pub fn sign_on_finish( + #[callback_result] signature: Result, + ) -> Option { + match signature { + Ok(signature) => Some(signature + " post"), + Err(_) => Some("signature request timed out"), + } } /// Callback used to clean up the local state of the contract From 55bf0b6d62e04bfd04070b0f98c3da8bee18b49f Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Tue, 13 Feb 2024 12:47:45 -0500 Subject: [PATCH 14/18] update host function imports --- examples/mpc-contract/src/lib.rs | 5 +++-- near-sdk/src/environment/env.rs | 11 ++++++++++- near-sys/src/lib.rs | 8 +++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index fe61054b2..1b85947c9 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -2,7 +2,7 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::collections::TreeMap; use near_sdk::{ env, log, near_bindgen, require, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, - NearToken, PromiseError, + GasWeight, NearToken, PromiseError, }; #[derive(BorshSerialize, BorshStorageKey)] @@ -46,6 +46,7 @@ impl MpcContract { "sign_on_finish", &[], SIGN_ON_FINISH_CALL_GAS, + GasWeight(0), DATA_ID_REGISTER, ); @@ -89,7 +90,7 @@ impl MpcContract { ) -> Option { match signature { Ok(signature) => Some(signature + " post"), - Err(_) => Some("signature request timed out"), + Err(_) => Some("signature request timed out".to_string()), } } diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index dd5c4254b..b82f58bf4 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -495,6 +495,7 @@ pub fn promise_yield_create( function_name: &str, arguments: &[u8], gas: Gas, + weight: GasWeight, register_id: u64, ) -> PromiseIndex { unsafe { @@ -504,6 +505,7 @@ pub fn promise_yield_create( arguments.len() as _, arguments.as_ptr() as _, gas.as_gas(), + weight.0, register_id as _, )) } @@ -511,7 +513,14 @@ pub fn promise_yield_create( // TODO: return some kind of success/failure result pub fn promise_yield_resume(data_id: &CryptoHash, data: &[u8]) { - unsafe { sys::promise_yield_resume(data_id.as_ptr() as _, data.len() as _, data.as_ptr() as _) } + unsafe { + sys::promise_yield_resume( + data_id.len() as _, + data_id.as_ptr() as _, + data.len() as _, + data.as_ptr() as _, + ) + } } /// Creates a promise that will execute a method on account with given arguments and attaches diff --git a/near-sys/src/lib.rs b/near-sys/src/lib.rs index 04209b7e5..4f98ac1c1 100644 --- a/near-sys/src/lib.rs +++ b/near-sys/src/lib.rs @@ -155,9 +155,15 @@ extern "C" { arguments_len: u64, arguments_ptr: u64, gas: u64, + gas_weight: u64, register_id: u64, ) -> u64; - pub fn promise_yield_resume(data_id_ptr: u64, payload_len: u64, payload_ptr: u64); + pub fn promise_yield_resume( + data_id_len: u64, + data_id_ptr: u64, + payload_len: u64, + payload_ptr: u64, + ); // ####################### // # Promise API results # // ####################### From 623032888e4093650c47206b87b7d9ea4693df13 Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Thu, 15 Feb 2024 17:18:22 -0500 Subject: [PATCH 15/18] remove need for separate cleanup callback --- examples/mpc-contract/src/lib.rs | 52 ++++++++++++++------------------ 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index 1b85947c9..f6b31666c 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -1,8 +1,8 @@ use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; use near_sdk::collections::TreeMap; use near_sdk::{ - env, log, near_bindgen, require, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, - GasWeight, NearToken, PromiseError, + env, log, near_bindgen, serde_json, AccountId, BorshStorageKey, CryptoHash, Gas, GasWeight, + PromiseError, }; #[derive(BorshSerialize, BorshStorageKey)] @@ -12,6 +12,7 @@ struct RecordsKey; #[derive(BorshDeserialize, BorshSerialize)] #[borsh(crate = "near_sdk::borsh")] pub struct SignatureRequest { + data_id: CryptoHash, account_id: AccountId, payload: String, } @@ -20,12 +21,13 @@ pub struct SignatureRequest { #[derive(BorshDeserialize, BorshSerialize)] #[borsh(crate = "near_sdk::borsh")] pub struct MpcContract { - requests: TreeMap, + requests: TreeMap, + next_available_request_index: u64, } impl Default for MpcContract { fn default() -> Self { - Self { requests: TreeMap::new(RecordsKey) } + Self { requests: TreeMap::new(RecordsKey), next_available_request_index: 0u64 } } } @@ -35,16 +37,16 @@ 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 `sign_on_finish` call -const REMOVE_REQUEST_CALL_GAS: Gas = Gas::from_tgas(5); - #[near_bindgen] impl MpcContract { /// User-facing API: accepts payload and returns signature pub fn sign(&mut self, payload: String) { + let index = self.next_available_request_index; + self.next_available_request_index += 1; + let promise = env::promise_yield_create( "sign_on_finish", - &[], + &serde_json::to_vec(&(index,)).unwrap(), SIGN_ON_FINISH_CALL_GAS, GasWeight(0), DATA_ID_REGISTER, @@ -53,17 +55,9 @@ impl MpcContract { // Record the pending request let data_id: CryptoHash = env::read_register(DATA_ID_REGISTER).expect("").try_into().expect(""); - self.requests - .insert(&data_id, &SignatureRequest { account_id: env::signer_account_id(), payload }); - - // Schedule clean-up of self.requests after the yield execution completes - env::promise_then( - promise, - env::current_account_id(), - "remove_request", - &serde_json::to_vec(&(data_id,)).unwrap(), - NearToken::from_near(0), - REMOVE_REQUEST_CALL_GAS, + self.requests.insert( + &index, + &SignatureRequest { data_id, account_id: env::signer_account_id(), payload }, ); env::promise_return(promise); @@ -75,8 +69,6 @@ impl MpcContract { hex::decode_to_slice(data_id, &mut data_id_buf).expect(""); let data_id = data_id_buf; - require!(self.requests.contains_key(&data_id)); - // check that caller is allowed to respond, signature is valid, etc. // ... @@ -84,27 +76,27 @@ impl MpcContract { env::promise_yield_resume(&data_id, &serde_json::to_vec(&signature).unwrap()); } - /// Callback receiving the externally submitted data + /// Callback receiving the externally submitted data (or a PromiseError) pub fn sign_on_finish( + &mut self, + request_index: u64, #[callback_result] signature: Result, ) -> Option { + // clean up local state + self.requests.remove(&request_index); + match signature { - Ok(signature) => Some(signature + " post"), + Ok(signature) => Some("signature received: ".to_owned() + &signature), Err(_) => Some("signature request timed out".to_string()), } } - /// Callback used to clean up the local state of the contract - pub fn remove_request(&mut self, data_id: CryptoHash) { - self.requests.remove(&data_id); - } - /// Helper for local testing; prints all pending requests pub fn log_pending_requests(&self) { - for (data_id, request) in self.requests.iter() { + for (_, request) in self.requests.iter() { log!( "{}: account_id={} payload={}", - hex::encode(data_id), + hex::encode(request.data_id), request.account_id, request.payload ); From f3d26b698355f76e96fbc5af1752fac24bbde111 Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Thu, 15 Feb 2024 17:24:28 -0500 Subject: [PATCH 16/18] nits --- examples/mpc-contract/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index f6b31666c..da6e48ac2 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -52,7 +52,7 @@ impl MpcContract { DATA_ID_REGISTER, ); - // Record the pending request + // 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( @@ -60,6 +60,8 @@ impl MpcContract { &SignatureRequest { data_id, account_id: env::signer_account_id(), payload }, ); + // The return value for this function call will be the value + // returned by the `sign_on_finish` callback. env::promise_return(promise); } @@ -82,7 +84,7 @@ impl MpcContract { request_index: u64, #[callback_result] signature: Result, ) -> Option { - // clean up local state + // Clean up the local state self.requests.remove(&request_index); match signature { From 2b19c0a862a9eaadc381ab9529a13a5e1bb462bc Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Thu, 15 Feb 2024 18:04:02 -0500 Subject: [PATCH 17/18] add back promise_then example --- examples/mpc-contract/src/lib.rs | 45 ++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/examples/mpc-contract/src/lib.rs b/examples/mpc-contract/src/lib.rs index da6e48ac2..bae2fba8e 100644 --- a/examples/mpc-contract/src/lib.rs +++ b/examples/mpc-contract/src/lib.rs @@ -2,7 +2,7 @@ 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, - PromiseError, + NearToken, PromiseError, }; #[derive(BorshSerialize, BorshStorageKey)] @@ -14,7 +14,7 @@ struct RecordsKey; pub struct SignatureRequest { data_id: CryptoHash, account_id: AccountId, - payload: String, + message: String, } #[near_bindgen] @@ -37,14 +37,17 @@ 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 payload and returns signature - pub fn sign(&mut self, payload: String) { + /// 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 promise = env::promise_yield_create( + let yield_promise = env::promise_yield_create( "sign_on_finish", &serde_json::to_vec(&(index,)).unwrap(), SIGN_ON_FINISH_CALL_GAS, @@ -57,12 +60,28 @@ impl MpcContract { env::read_register(DATA_ID_REGISTER).expect("").try_into().expect(""); self.requests.insert( &index, - &SignatureRequest { data_id, account_id: env::signer_account_id(), payload }, + &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(promise); + env::promise_return(yield_promise); } /// Called by MPC participants to submit a signature @@ -83,16 +102,20 @@ impl MpcContract { &mut self, request_index: u64, #[callback_result] signature: Result, - ) -> Option { + ) -> String { // Clean up the local state self.requests.remove(&request_index); match signature { - Ok(signature) => Some("signature received: ".to_owned() + &signature), - Err(_) => Some("signature request timed out".to_string()), + 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() { @@ -100,7 +123,7 @@ impl MpcContract { "{}: account_id={} payload={}", hex::encode(request.data_id), request.account_id, - request.payload + request.message ); } } From 6b60b2f60b43c3e7226dd240cce192a64b80cfeb Mon Sep 17 00:00:00 2001 From: Saketh Are Date: Fri, 1 Mar 2024 11:48:51 -0500 Subject: [PATCH 18/18] update promise_yield_resume host fn signature --- near-sdk/src/environment/env.rs | 4 ++-- near-sys/src/lib.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/near-sdk/src/environment/env.rs b/near-sdk/src/environment/env.rs index b82f58bf4..26c4134b5 100644 --- a/near-sdk/src/environment/env.rs +++ b/near-sdk/src/environment/env.rs @@ -512,14 +512,14 @@ pub fn promise_yield_create( } // TODO: return some kind of success/failure result -pub fn promise_yield_resume(data_id: &CryptoHash, data: &[u8]) { +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 } } diff --git a/near-sys/src/lib.rs b/near-sys/src/lib.rs index 4f98ac1c1..c53ec1815 100644 --- a/near-sys/src/lib.rs +++ b/near-sys/src/lib.rs @@ -146,9 +146,9 @@ extern "C" { beneficiary_id_len: u64, beneficiary_id_ptr: u64, ); - // ####################### - // # Promise API await/submit # - // ####################### + // ######################## + // # Promise yield/resume # + // ######################## pub fn promise_yield_create( function_name_len: u64, function_name_ptr: u64, @@ -163,7 +163,7 @@ extern "C" { data_id_ptr: u64, payload_len: u64, payload_ptr: u64, - ); + ) -> u32; // ####################### // # Promise API results # // #######################