diff --git a/.vscode/suggested_vscode_settings.json b/.vscode/suggested_vscode_settings.json new file mode 100644 index 000000000..c5a5db4e5 --- /dev/null +++ b/.vscode/suggested_vscode_settings.json @@ -0,0 +1,22 @@ +{ + "rust-analyzer.cargo.buildScripts.enable": false, + "rust-analyzer.restartServerOnConfigChange": true, + "rust-analyzer.debug.engine": "vadimcn.vscode-lldb", + "rust-analyzer.debug.openDebugPane": true, + "rust-analyzer.runnables.extraEnv": { + "DIEM_FORGE_NODE_BIN_PATH": "/root/.cargo/bin/diem-node" + }, + "rust-analyzer.server.extraEnv": { + "DIEM_FORGE_NODE_BIN_PATH": "/root/.cargo/bin/diem-node" + }, + "rust-analyzer.checkOnSave": false, + "rust-analyzer.check.noDefaultFeatures": false, + "rust-analyzer.cargo.buildScripts.useRustcWrapper": false, + "rust-analyzer.cargo.extraEnv": { + "RUSTC_WRAPPER": "sccache" + }, + "rust-analyzer.check.extraArgs": [ + "RUSTC_WRAPPER=sccache" + ], + "rust-analyzer.check.overrideCommand": "build" +} diff --git a/Cargo.toml b/Cargo.toml index a13420a29..77908db18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -357,11 +357,18 @@ strip = true # strip debug and symbols for size debug = true [profile.dev] -opt-level = 0 +opt-level = 1 debug = true split-debuginfo = "unpacked" -lto = false +lto = "off" +codegen-units = 256 # More parallel compilation units incremental = true [profile.test] inherits = "dev" +opt-level = 1 +debug = true +split-debuginfo = "unpacked" +lto = "off" +codegen-units = 256 # More parallel compilation units +incremental = true diff --git a/framework/libra-framework/sources/genesis.move b/framework/libra-framework/sources/genesis.move index ab419a90a..8ebbcef25 100644 --- a/framework/libra-framework/sources/genesis.move +++ b/framework/libra-framework/sources/genesis.move @@ -137,6 +137,12 @@ module diem_framework::genesis { chain_id::initialize(&diem_framework_account, chain_id); reconfiguration::initialize(&diem_framework_account); + // 0L: force testing environments to have short epochs + // TODO this should be solved at the SwarmBuilder level + // but requires change to vendors. + if (chain_id == 4) { + epoch_interval_microsecs = 1_000_000 * 30; + }; block::initialize(&diem_framework_account, epoch_interval_microsecs); state_storage::initialize(&diem_framework_account); randomness::initialize(&diem_framework_account); @@ -146,7 +152,6 @@ module diem_framework::genesis { validator_universe::initialize(&diem_framework_account); proof_of_fee::init_genesis_baseline_reward(&diem_framework_account); slow_wallet::initialize(&diem_framework_account); - // tower_state::initialize(&diem_framework_account); safe::initialize(&diem_framework_account); donor_voice::initialize(&diem_framework_account); epoch_boundary::initialize(&diem_framework_account); @@ -160,7 +165,7 @@ module diem_framework::genesis { let zero_x_two_sig = create_signer(@0x2); sacred_cows::init(&zero_x_two_sig); - // end 0L + //////// end 0L ///////// timestamp::set_time_has_started(&diem_framework_account); } @@ -181,8 +186,6 @@ module diem_framework::genesis { diem_framework: &signer, core_resources_auth_key: vector, ) { - // let (burn_cap, mint_cap) = diem_coin::initialize(diem_framework); - let core_resources = account::create_account(@core_resources); account::rotate_authentication_key_internal(&core_resources, core_resources_auth_key); diff --git a/framework/libra-framework/sources/ol_sources/secret_bid.move b/framework/libra-framework/sources/ol_sources/secret_bid.move index 105797288..ebeabbce7 100644 --- a/framework/libra-framework/sources/ol_sources/secret_bid.move +++ b/framework/libra-framework/sources/ol_sources/secret_bid.move @@ -10,21 +10,26 @@ module ol_framework::secret_bid { use std::hash; use std::signer; use std::vector; + use diem_std::debug::print; use diem_std::ed25519; use diem_std::comparator; use diem_framework::epoch_helper; use diem_framework::reconfiguration; use diem_framework::system_addresses; - use ol_framework::testnet; use ol_framework::address_utils; friend ol_framework::genesis; friend ol_framework::proof_of_fee; + #[test_only] + use diem_std::ed25519::SecretKey; #[test_only] use diem_framework::account; + #[test_only] + use diem_std::from_bcs; + #[test_only] friend ol_framework::test_boundary; @@ -36,11 +41,13 @@ module ol_framework::secret_bid { /// User bidding not initialized const ECOMMIT_BID_NOT_INITIALIZED: u64 = 1; /// Invalid signature on bid message - const EINVALID_SIGNATURE: u64 = 2; + const EINVALID_BID_SIGNATURE: u64 = 2; /// Must reveal bid in same epoch as committed const EMISMATCH_EPOCH: u64 = 3; - /// Must submit bid before reveal window opens, and reveal bid only after. - const ENOT_IN_REVEAL_WINDOW: u64 = 4; + /// Must commit a bid before reveal window opens. + const ETOO_LATE_TO_COMMIT: u64 = 4; + /// Can only reveal bid within reveal window. + const ECANT_REVEAL_YET: u64 = 4; /// Bad Alice, the reveal does not match the commit const ECOMMIT_DIGEST_NOT_EQUAL: u64 = 5; /// Bid is for different epoch, expired @@ -48,8 +55,10 @@ module ol_framework::secret_bid { struct CommittedBid has key { reveal_entry_fee: u64, + reveal_epoch: u64, entry_fee_history: vector, // keep previous 7 days bids commit_digest: vector, + // the epoch receiving commits commit_epoch: u64, } @@ -68,15 +77,15 @@ module ol_framework::secret_bid { /// Transaction entry function for committing bid public entry fun commit(user: &signer, digest: vector) acquires CommittedBid { - // don't allow commiting within reveal window - assert!(!in_reveal_window(), error::invalid_state(ENOT_IN_REVEAL_WINDOW)); + // don't allow committing within reveal window + assert!(!in_reveal_window(), error::invalid_state(ETOO_LATE_TO_COMMIT)); commit_entry_fee_impl(user, digest); } /// Transaction entry function for revealing bid public entry fun reveal(user: &signer, pk: vector, entry_fee: u64, signed_msg: vector) acquires CommittedBid { - // don't allow commiting within reveal window - assert!(in_reveal_window(), error::invalid_state(ENOT_IN_REVEAL_WINDOW)); + // don't allow committing within reveal window + assert!(in_reveal_window(), error::invalid_state(ECANT_REVEAL_YET)); reveal_entry_fee_impl(user, pk, entry_fee, signed_msg); } @@ -86,6 +95,7 @@ module ol_framework::secret_bid { if (!is_init(signer::address_of(user))) { move_to(user, CommittedBid { reveal_entry_fee: 0, + reveal_epoch: 0, entry_fee_history: vector::empty(), commit_digest: vector::empty(), commit_epoch: 0, @@ -130,7 +140,7 @@ module ol_framework::secret_bid { commitment } - /// user sends transaction which takes the committed signed message + /// user sends transaction which takes the committed signed message /// submits the public key used to sign message, which we compare to the authentication key. /// we use the epoch as the sequence number, so that messages are different on each submission. public fun reveal_entry_fee_impl(user: &signer, pk: vector, entry_fee: u64, signed_msg: vector) acquires CommittedBid { @@ -153,6 +163,7 @@ module ol_framework::secret_bid { assert!(comparator::is_equal(&comparator::compare(&commitment, &state.commit_digest)), error::invalid_argument(ECOMMIT_DIGEST_NOT_EQUAL)); state.reveal_entry_fee = entry_fee; + state.reveal_epoch = epoch; } fun check_signature(account_public_key_bytes: vector, signed_message_bytes: vector, bid_message: Bid) { @@ -160,7 +171,10 @@ module ol_framework::secret_bid { // confirm that the signature bytes belong to the message (the Bid struct) let sig = ed25519::new_signature_from_bytes(signed_message_bytes); - assert!(ed25519::signature_verify_strict_t(&sig, &pubkey, bid_message), error::invalid_argument(EINVALID_SIGNATURE)); + print(&@0x111); + print(&bid_message); + let bid_bytes = bcs::to_bytes(&bid_message); + assert!(ed25519::signature_verify_strict(&sig, &pubkey, bid_bytes), error::invalid_argument(EINVALID_BID_SIGNATURE)); ///////// // NOTE: previously we would check if the public key for the signed message @@ -188,9 +202,11 @@ module ol_framework::secret_bid { (addr, bids) } - ///////// GETTERS //////// - public(friend) fun get_bid_unchecked(user: address): u64 acquires CommittedBid { + + ///////// GETTERS //////// + #[view] + public fun get_bid_unchecked(user: address): u64 acquires CommittedBid { let state = borrow_global(user); state.reveal_entry_fee @@ -204,8 +220,8 @@ module ol_framework::secret_bid { // get the timestamp let remaining_secs = reconfiguration::get_remaining_epoch_secs(); let window = if (testnet::is_testnet()) { - // ten secs - 10 + // 15 secs, half of the short 30 sec epoch + 15 } else { // five mins 60*5 @@ -225,36 +241,29 @@ module ol_framework::secret_bid { #[view] /// get the current bid, and exclude bids that are stale public fun current_revealed_bid(user: address): u64 acquires CommittedBid { - // // if we are not in reveal window this information will be confusing. - // assert!(in_reveal_window(), error::invalid_state(ENOT_IN_REVEAL_WINDOW)); - - // let state = borrow_global(user); - // assert!(state.commit_epoch == epoch_helper::get_current_epoch(), error::invalid_state(EBID_EXPIRED)); - - // state.reveal_entry_fee + // if we are not in reveal window this information will be confusing. get_bid_unchecked(user) } - /// Find the most recent epoch the validator has placed a bid on. - public(friend) fun latest_epoch_bid(user: address): u64 acquires CommittedBid { + #[view] + /// Find the most recent epoch the validator has placed a bid on. + public fun latest_epoch_bid(user: address): u64 acquires CommittedBid { let state = borrow_global(user); state.commit_epoch } + #[view] + /// Find the most recent epoch the validator has revealed a bid for + public fun latest_epoch_revealed(user: address): u64 acquires CommittedBid { + let state = borrow_global(user); + state.reveal_epoch + } /// does the user have a current bid public(friend) fun has_valid_bid(user: address): bool acquires CommittedBid { let state = borrow_global(user); state.commit_epoch == epoch_helper::get_current_epoch() } - /// will abort if bid is not valid - fun assert_valid_bid(user: address) acquires CommittedBid { - // if we are not in reveal window this information will be confusing. - assert!(in_reveal_window(), error::invalid_state(ENOT_IN_REVEAL_WINDOW)); - - assert!(has_valid_bid(user), error::invalid_state(EBID_EXPIRED)); - } - #[view] /// get the current bid, and exclude bids that are stale public fun historical_bids(user: address): vector acquires CommittedBid { @@ -272,9 +281,11 @@ module ol_framework::secret_bid { if (!exists(user_addr)) { move_to(user, CommittedBid { reveal_entry_fee, + reveal_epoch: commit_epoch, entry_fee_history: vector[0], commit_digest: vector[0], commit_epoch, + }); } else { let state = borrow_global_mut(user_addr); @@ -284,104 +295,97 @@ module ol_framework::secret_bid { } - #[test] - fun test_sign_message() { - use diem_std::from_bcs; + #[test_only] + // helper to set get a signature for a Bid type + fun sign_bid_struct(sk: &SecretKey, bid: &Bid): vector { + let msg_bytes = bcs::to_bytes(bid); + let to_sig = ed25519::sign_arbitrary_bytes(sk, msg_bytes); + + ed25519::signature_to_bytes(&to_sig) + } + #[test] + fun test_sign_arbitrary_message() { let (new_sk, new_pk) = ed25519::generate_keys(); let new_pk_unvalidated = ed25519::public_key_to_unvalidated(&new_pk); - let new_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&new_pk_unvalidated); - let new_addr = from_bcs::to_address(new_auth_key); - let _alice = account::create_account_for_test(new_addr); - let message = Bid { + let bid = Bid { entry_fee: 0, epoch: 0, }; - let to_sig = ed25519::sign_struct(&new_sk, copy message); - let sig_bytes = ed25519::signature_to_bytes(&to_sig); - // end set-up - - // yes repetitive, but following the same workflow - let sig_again = ed25519::new_signature_from_bytes(sig_bytes); - - assert!(ed25519::signature_verify_strict_t(&sig_again, &new_pk_unvalidated, message), error::invalid_argument(EINVALID_SIGNATURE)); + let signed_message_bytes = sign_bid_struct(&new_sk, &bid); + let sig = ed25519::new_signature_from_bytes(signed_message_bytes); + // encoding directly should yield the same bytes as in signed_message_bytes + let encoded = bcs::to_bytes(&bid); + assert!(ed25519::signature_verify_strict(&sig, &new_pk_unvalidated, encoded), error::invalid_argument(EINVALID_BID_SIGNATURE)); } - #[test] - fun test_check_signature() { - use diem_std::from_bcs; - +#[test] +// sanity test the Signature type vs the bytes +fun test_round_trip_sig_type() { let (new_sk, new_pk) = ed25519::generate_keys(); let new_pk_unvalidated = ed25519::public_key_to_unvalidated(&new_pk); - let new_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&new_pk_unvalidated); - let new_addr = from_bcs::to_address(new_auth_key); - let _alice = account::create_account_for_test(new_addr); let message = Bid { entry_fee: 0, epoch: 0, }; + let msg_bytes = bcs::to_bytes(&message); - let to_sig = ed25519::sign_struct(&new_sk, copy message); + let to_sig = ed25519::sign_arbitrary_bytes(&new_sk, copy msg_bytes); let sig_bytes = ed25519::signature_to_bytes(&to_sig); - // end set-up + // should equal to_sig above + let sig_should_be_same = ed25519::new_signature_from_bytes(sig_bytes); + let res = comparator::compare(&to_sig, &sig_should_be_same); + assert!(comparator::is_equal(&res), 7357001); - // yes repetitive, but following the same workflow - let sig_again = ed25519::new_signature_from_bytes(sig_bytes); - - assert!(ed25519::signature_verify_strict_t(&sig_again, &new_pk_unvalidated, copy message), error::invalid_argument(EINVALID_SIGNATURE)); - - let pk_bytes = ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated); - - check_signature(pk_bytes, sig_bytes, message); - - } + assert!(ed25519::signature_verify_strict(&sig_should_be_same, &new_pk_unvalidated, copy msg_bytes), error::invalid_argument(EINVALID_BID_SIGNATURE)); +} #[test] - #[expected_failure(abort_code = 65538, location = Self)] - fun test_check_signature_sad() { - use diem_std::from_bcs; - + fun test_check_signature_happy() { let (new_sk, new_pk) = ed25519::generate_keys(); let new_pk_unvalidated = ed25519::public_key_to_unvalidated(&new_pk); - let new_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&new_pk_unvalidated); - let new_addr = from_bcs::to_address(new_auth_key); - let _alice = account::create_account_for_test(new_addr); + let pk_bytes = ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated); - let message = Bid { + let bid = Bid { entry_fee: 0, epoch: 0, }; - let to_sig = ed25519::sign_struct(&new_sk, copy message); - let sig_bytes = ed25519::signature_to_bytes(&to_sig); - // end set-up + let signed_message_bytes = sign_bid_struct(&new_sk, &bid); + + check_signature(pk_bytes, signed_message_bytes, bid); + } - // yes repetitive, but following the same workflow - let sig_again = ed25519::new_signature_from_bytes(sig_bytes); + #[test] + #[expected_failure(abort_code = 65538, location = Self)] + fun wrong_key_sad() { + let (_wrong_sk, wrong_pk) = ed25519::generate_keys(); + let wrong_pk_unvalidated = ed25519::public_key_to_unvalidated(&wrong_pk); + let wrong_pk_bytes = ed25519::unvalidated_public_key_to_bytes(&wrong_pk_unvalidated); - assert!(ed25519::signature_verify_strict_t(&sig_again, &new_pk_unvalidated, copy message), error::invalid_argument(EINVALID_SIGNATURE)); - let pk_bytes = ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated); + let (new_sk, new_pk) = ed25519::generate_keys(); + let new_pk_unvalidated = ed25519::public_key_to_unvalidated(&new_pk); + let _good_pk_bytes = ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated); - let message = Bid { - entry_fee: 2, // incorrect + let bid = Bid { + entry_fee: 0, epoch: 0, }; - check_signature(pk_bytes, sig_bytes, message); + let signed_message_bytes = sign_bid_struct(&new_sk, &bid); + check_signature(wrong_pk_bytes, signed_message_bytes, bid); } #[test(framework = @0x1)] fun test_commit_message(framework: &signer) acquires CommittedBid { - use diem_std::from_bcs; - let this_epoch = 1; epoch_helper::test_set_epoch(framework, this_epoch); @@ -390,16 +394,17 @@ module ol_framework::secret_bid { let new_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&new_pk_unvalidated); let new_addr = from_bcs::to_address(new_auth_key); let alice = account::create_account_for_test(new_addr); + let entry_fee = 5; let epoch = 0; - let message = Bid { + let bid = Bid { entry_fee, epoch, }; - let to_sig = ed25519::sign_struct(&new_sk, copy message); - let sig_bytes = ed25519::signature_to_bytes(&to_sig); + // let to_sig = ed25519::sign_arbitrary_bytes(&new_sk, copy message); + let sig_bytes = sign_bid_struct(&new_sk, &bid); let digest = make_hash(entry_fee, epoch, sig_bytes); // end set-up @@ -408,8 +413,6 @@ module ol_framework::secret_bid { #[test(framework = @0x1)] fun test_reveal(framework: &signer) acquires CommittedBid { - use diem_std::from_bcs; - let epoch = 1; epoch_helper::test_set_epoch(framework, epoch); @@ -419,13 +422,12 @@ module ol_framework::secret_bid { let new_addr = from_bcs::to_address(new_auth_key); let alice = account::create_account_for_test(new_addr); let entry_fee = 5; - let message = Bid { + let bid = Bid { entry_fee, epoch, }; - let to_sig = ed25519::sign_struct(&new_sk, copy message); - let sig_bytes = ed25519::signature_to_bytes(&to_sig); + let sig_bytes = sign_bid_struct(&new_sk, &bid); let digest = make_hash(entry_fee, epoch, sig_bytes); // end set-up @@ -433,15 +435,14 @@ module ol_framework::secret_bid { let pk_bytes = ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated); - check_signature(pk_bytes, sig_bytes, message); + check_signature(pk_bytes, sig_bytes, bid); reveal_entry_fee_impl(&alice, pk_bytes, 5, sig_bytes); } #[test(framework = @0x1)] #[expected_failure(abort_code = 65538, location = Self)] - fun test_reveal_sad_wrong_epoch(framework: &signer) acquires CommittedBid { - use diem_std::from_bcs; + fun test_reveal_bad_epoch(framework: &signer) acquires CommittedBid { let epoch = 1; epoch_helper::test_set_epoch(framework, epoch); @@ -451,15 +452,12 @@ module ol_framework::secret_bid { let new_addr = from_bcs::to_address(new_auth_key); let alice = account::create_account_for_test(new_addr); let entry_fee = 5; - let wrong_epoch = 100; - - let message = Bid { + let bid = Bid { entry_fee, - epoch: wrong_epoch, // wrong epoch, we are at 1 + epoch, }; - let to_sig = ed25519::sign_struct(&new_sk, copy message); - let sig_bytes = ed25519::signature_to_bytes(&to_sig); + let sig_bytes = sign_bid_struct(&new_sk, &bid); let digest = make_hash(entry_fee, epoch, sig_bytes); // end set-up @@ -467,9 +465,9 @@ module ol_framework::secret_bid { let pk_bytes = ed25519::unvalidated_public_key_to_bytes(&new_pk_unvalidated); - check_signature(pk_bytes, sig_bytes, message); + check_signature(pk_bytes, sig_bytes, bid); - reveal_entry_fee_impl(&alice, pk_bytes, 5, sig_bytes); + reveal_entry_fee_impl(&alice, pk_bytes, 10, sig_bytes); } #[test(framework = @0x1, alice = @0x10001, bob = @0x10002, carol = @0x10003)] diff --git a/tools/query/src/chain_queries.rs b/tools/query/src/chain_queries.rs index 12ee4b985..dfb925b11 100644 --- a/tools/query/src/chain_queries.rs +++ b/tools/query/src/chain_queries.rs @@ -95,3 +95,28 @@ pub async fn epoch_over_can_trigger(client: &Client) -> anyhow::Result { Ok(value[0]) } + +/// Retrieves if we are within the commit reveal window +pub async fn within_commit_reveal_window(client: &Client) -> anyhow::Result { + let res = get_view(client, "0x1::secret_bid::in_reveal_window", None, None).await?; + + let value: Vec = serde_json::from_value(res)?; + + Ok(value[0]) +} + +/// Time remaining in epoch +pub async fn secs_remaining_in_epoch(client: &Client) -> anyhow::Result { + let res = get_view( + client, + "0x1::reconfiguration::get_remaining_epoch_secs", + None, + None, + ) + .await?; + + let value: Vec = serde_json::from_value(res)?; + let secs = value.first().unwrap().parse::()?; + + Ok(secs) +} diff --git a/tools/txs/src/stream/bid_commit_reveal.rs b/tools/txs/src/stream/bid_commit_reveal.rs index 31a52d013..00fdeecef 100644 --- a/tools/txs/src/stream/bid_commit_reveal.rs +++ b/tools/txs/src/stream/bid_commit_reveal.rs @@ -1,8 +1,222 @@ -#[derive(clap::Args)] +use crate::submit_transaction::Sender as LibraSender; +use diem_logger::error; +use diem_logger::info; +use diem_sdk::crypto::ed25519::Ed25519Signature; +use diem_sdk::crypto::HashValue; +use diem_sdk::crypto::Signature; +use diem_sdk::crypto::SigningKey; +use diem_sdk::types::LocalAccount; +use diem_types::transaction::TransactionPayload; +use libra_cached_packages::libra_stdlib; +use libra_query::chain_queries; +use libra_query::query_view::get_view; +use libra_types::exports::AccountAddress; +use libra_types::exports::Client; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +#[derive(clap::Args, Debug)] pub struct PofBidArgs { #[clap(short, long)] + /// bid amount for validator net reward pub net_reward: u64, - #[clap(short, long)] - pub test_private_key: Option, + /// seconds to wait between retries + pub delay: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PofBidData { + entry_fee: u64, + epoch: u64, +} + +#[allow(dead_code)] +/// these are the bytes that will be encoded with BCS +/// and then signed with ed25519 key +/// See the move equivalent in secret_bid.move +impl PofBidData { + fn new(entry_fee: u64) -> Self { + PofBidData { + entry_fee, + epoch: 0, + } + } + fn to_bcs(&self) -> anyhow::Result> { + Ok(bcs::to_bytes(&self)?) + } + + fn from_bcs(bytes: &[u8]) -> anyhow::Result { + Ok(bcs::from_bytes(bytes)?) + } + + async fn update_epoch(&mut self, client: &Client) -> anyhow::Result<()> { + let num = chain_queries::get_epoch(client).await?; + self.epoch = num; + Ok(()) + } + + fn sign_bcs_bytes(&self, keys: &LocalAccount) -> anyhow::Result { + let bcs = self.to_bcs()?; + let signed = keys.private_key().sign_arbitrary_message(&bcs); + assert!( + signed.verify_arbitrary_msg(&bcs, keys.public_key()).is_ok(), + "cannot verify signed message" + ); + Ok(signed) + } + + fn create_hash_bytes(&self, keys: &LocalAccount) -> anyhow::Result> { + let sig = self.sign_bcs_bytes(keys).expect("could not sign bytes"); + + make_commit_hash(self.entry_fee, self.epoch, sig.to_bytes().to_vec()) + } + + fn encode_commit_tx_payload(&self, keys: &LocalAccount) -> anyhow::Result { + let digest = self.create_hash_bytes(keys)?; + let payload = libra_stdlib::secret_bid_commit(digest); + Ok(payload) + } + + // In the reveal we simply show the signature and + // open bid info. On the MoveVM side the hash is recreated + // so that it should match the commit + fn encode_reveal_tx_payload(&self, keys: &LocalAccount) -> anyhow::Result { + let sig = self.sign_bcs_bytes(keys)?; + let payload = libra_stdlib::secret_bid_reveal( + keys.public_key().to_bytes().to_vec(), + self.entry_fee, + sig.to_bytes().to_vec(), + ); + Ok(payload) + } +} + +pub async fn commit_reveal_poll( + sender: &mut LibraSender, + entry_fee: u64, + delay_secs: u64, +) -> anyhow::Result<()> { + println!("commit reveal bid: {}", entry_fee); + let mut bid = PofBidData::new(entry_fee); + let client = sender.client().clone(); + + loop { + // check what epoch we are in + let _ = bid.update_epoch(&client).await; + let reveal_window_open = chain_queries::within_commit_reveal_window(&client).await?; + let last_epoch_revealed = + validator_last_epoch_revealed(&client, sender.local_account.address()).await?; + let must_reveal = last_epoch_revealed != bid.epoch && reveal_window_open; + + let la = &sender.local_account; + if must_reveal { + info!("must reveal bid"); + + let payload = bid.encode_reveal_tx_payload(la)?; + // don't abort if we are trying too eager! + match sender.sign_submit_wait(payload).await { + Ok(_) => info!("successfully revealed bid"), + Err(e) => error!( + "could not reveal bid: message {}\ncontinuing...", + e.to_string() + ), + } + } else if !reveal_window_open { + info!("sending secret bid"); + let secs = chain_queries::secs_remaining_in_epoch(&client).await?; + info!("seconds remaining in epoch {secs}"); + + let payload = bid.encode_commit_tx_payload(la)?; + match sender.sign_submit_wait(payload).await { + Ok(_) => info!("successfully committed bid"), + Err(e) => error!( + "could not commit bid: message {}\ncontinuing...", + e.to_string() + ), + } + }; + // NOTE: it's possible the user sets a delay that is longer than the + // reveal window + tokio::time::sleep(Duration::from_secs(delay_secs)).await; + } +} + +/// produces a hash by combining the bid and the signed bid. +/// On the move side there is an equivalent function at: secret_bid.move +pub fn make_commit_hash( + entry_fee: u64, + epoch: u64, + mut signed_message: Vec, +) -> anyhow::Result> { + let bid = PofBidData { entry_fee, epoch }; + + let mut bid_bytes = bcs::to_bytes(&bid)?; + bid_bytes.append(&mut signed_message); + let commitment = HashValue::sha3_256_of(&bid_bytes); + + Ok(commitment.to_vec()) +} + +#[cfg(test)] +fn test_local_account() -> LocalAccount { + use diem_sdk::crypto::ed25519::Ed25519PrivateKey; + use diem_sdk::crypto::ValidCryptoMaterialStringExt; + use diem_sdk::types::AccountKey; + use libra_types::exports::AccountAddress; + + let pk = Ed25519PrivateKey::from_encoded_string( + "74f18da2b80b1820b58116197b1c41f8a36e1b37a15c7fb434bb42dd7bdaa66b", + ) + .unwrap(); + let account_key = AccountKey::from_private_key(pk); + LocalAccount::new( + AccountAddress::from_bytes(account_key.public_key().to_bytes()).unwrap(), + account_key, + 0, + ) +} + +#[test] +fn sign_bid() { + let bid = PofBidData::new(11); + let la = test_local_account(); + let sig = bid.sign_bcs_bytes(&la).expect("could not sign"); + let pubkey = la.public_key(); + let msg = bid.to_bcs().unwrap(); + + assert!(sig.verify_arbitrary_msg(&msg, pubkey).is_ok()); +} + +pub async fn validator_committed_bid(client: &Client, val: AccountAddress) -> anyhow::Result { + let res = get_view( + client, + "0x1::secret_bid::get_bid_unchecked", + None, + Some(val.to_hex_literal()), + ) + .await?; + + let value: Vec = serde_json::from_value(res)?; + let secs = value.first().unwrap().parse::()?; + + Ok(secs) +} + +pub async fn validator_last_epoch_revealed( + client: &Client, + val: AccountAddress, +) -> anyhow::Result { + let res = get_view( + client, + "0x1::secret_bid::latest_epoch_revealed", + None, + Some(val.to_hex_literal()), + ) + .await?; + + let value: Vec = serde_json::from_value(res)?; + let secs = value.first().unwrap().parse::()?; + + Ok(secs) } diff --git a/tools/txs/src/stream/channel.rs b/tools/txs/src/stream/channel.rs new file mode 100644 index 000000000..cd288a2e9 --- /dev/null +++ b/tools/txs/src/stream/channel.rs @@ -0,0 +1,52 @@ +use crate::submit_transaction::Sender as LibraSender; +use diem_logger::debug; +use diem_logger::prelude::{error, info}; +use diem_types::transaction::TransactionPayload; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, JoinHandle}; + +/// Creates a channel to receive an process for TransactionPayload +pub(crate) fn init_channel() -> (Sender, Receiver) { + mpsc::channel::() +} + +/// Start the listener on the channel which receives TransactionPayload +#[allow(unused_assignments)] +pub(crate) fn listen( + rx: Receiver, + send: Arc>, +) -> JoinHandle<()> { + thread::spawn(move || { + let mut busy = false; + + while !busy { + match rx.recv() { + Ok(payload) => { + info!("Received Channel Message\nTx: {:?}", payload); + if !busy { + busy = true; + match send + .lock() + .expect("could not access Sender client") + .sync_sign_submit_wait(payload) + { + Ok(r) => { + debug!("tx success: {:?}", r); + busy = false; + } + Err(e) => { + // DOES NOT abort on failed tx + error!("transaction failed: {:?}\ncontinuing...", &e); + } + }; + } + } + Err(_) => { + error!("could not parse channel message received, aborting"); + break; + } + }; + } + }) +} diff --git a/tools/txs/src/stream/epoch_tickle_poll.rs b/tools/txs/src/stream/epoch_tickle_poll.rs index 7ccd6f6c1..f3febe578 100644 --- a/tools/txs/src/stream/epoch_tickle_poll.rs +++ b/tools/txs/src/stream/epoch_tickle_poll.rs @@ -1,37 +1,25 @@ +use crate::submit_transaction::Sender as LibraSender; use diem_logger::info; -use diem_types::transaction::TransactionPayload; use libra_cached_packages::libra_stdlib; -use libra_types::exports::Client; -use std::borrow::BorrowMut; -use std::sync::mpsc::Sender; -use std::thread; use std::time::Duration; -pub fn epoch_tickle_poll(mut tx: Sender, client: Client, delay_secs: u64) { +pub async fn epoch_tickle_poll(sender: &mut LibraSender, delay_secs: u64) -> anyhow::Result<()> { println!("polling epoch boundary"); - let handle = thread::spawn(move || loop { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - // TODO: make the client borrow instead of clone - let res = rt.block_on(libra_query::chain_queries::epoch_over_can_trigger( - &client.clone(), - )); + let client = sender.client().clone(); + loop { + let res = libra_query::chain_queries::epoch_over_can_trigger(&client).await; match res { Ok(true) => { - let func = libra_stdlib::diem_governance_trigger_epoch(); + let payload = libra_stdlib::diem_governance_trigger_epoch(); - tx.borrow_mut().send(func).unwrap(); + sender.sign_submit_wait(payload).await?; } _ => { info!("Not ready to call epoch.") } } - thread::sleep(Duration::from_secs(delay_secs)); - }); - handle.join().expect("cannot poll for epoch boundary"); + tokio::time::sleep(Duration::from_secs(delay_secs)).await; + } } diff --git a/tools/txs/src/stream/mod.rs b/tools/txs/src/stream/mod.rs index 2cfb50065..d710d9379 100644 --- a/tools/txs/src/stream/mod.rs +++ b/tools/txs/src/stream/mod.rs @@ -1 +1,3 @@ +pub mod bid_commit_reveal; +// pub mod channel; pub mod epoch_tickle_poll; diff --git a/tools/txs/src/submit_transaction.rs b/tools/txs/src/submit_transaction.rs index 1651e1dac..f18794bee 100644 --- a/tools/txs/src/submit_transaction.rs +++ b/tools/txs/src/submit_transaction.rs @@ -212,7 +212,6 @@ impl Sender { println!("transaction sent"); self.response = Some(r.clone()); spin.finish_and_clear(); - // debug!("{:?}", &r); OLProgress::complete("transaction success"); Ok(r) } @@ -251,6 +250,7 @@ impl Sender { &mut self, signed_trans: &SignedTransaction, ) -> anyhow::Result { + debug!("signed tx payload: {:?}", &signed_trans.payload()); let pending_trans = self.client.submit(signed_trans).await?.into_inner(); info!("pending tx hash: {}", &pending_trans.hash.to_string()); diff --git a/tools/txs/src/txs_cli.rs b/tools/txs/src/txs_cli.rs index a0c94aab2..4691062ed 100644 --- a/tools/txs/src/txs_cli.rs +++ b/tools/txs/src/txs_cli.rs @@ -18,7 +18,6 @@ use libra_types::{ }; use libra_wallet::account_keys::{get_keys_from_mnem, get_keys_from_prompt}; use std::path::PathBuf; -use std::sync::{Arc, Mutex}; use url::Url; #[derive(Parser)] @@ -220,8 +219,8 @@ impl TxsCli { Some(TxsSub::User(user_txs)) => user_txs.run(&mut send).await, Some(TxsSub::Community(comm_txs)) => comm_txs.run(&mut send).await, Some(TxsSub::Stream(stream_txs)) => { - let arc_send = Arc::new(Mutex::new(send)); - stream_txs.start(arc_send); + // let arc_send = Arc::new(Mutex::new(send)); + stream_txs.start(&mut send).await?; Ok(()) } _ => { diff --git a/tools/txs/src/txs_cli_stream.rs b/tools/txs/src/txs_cli_stream.rs index 049fb5a16..6bc6a5fa6 100644 --- a/tools/txs/src/txs_cli_stream.rs +++ b/tools/txs/src/txs_cli_stream.rs @@ -1,12 +1,6 @@ -// use crate::bid_commit_reveal::PofBidArgs; -use crate::stream::epoch_tickle_poll::epoch_tickle_poll; +use crate::stream::bid_commit_reveal::commit_reveal_poll; +use crate::stream::{bid_commit_reveal::PofBidArgs, epoch_tickle_poll::epoch_tickle_poll}; use crate::submit_transaction::Sender as LibraSender; -use diem_logger::prelude::{error, info}; -use diem_types::transaction::TransactionPayload; -use std::process::exit; -use std::sync::mpsc::{self, Receiver, Sender}; -use std::sync::{Arc, Mutex}; -use std::thread::{self, JoinHandle}; #[derive(clap::Subcommand)] pub enum StreamTxs { @@ -17,67 +11,20 @@ pub enum StreamTxs { delay: Option, }, /// Submit secret PoF bids in background, and reveal when window opens - PofBid, + ValBid(PofBidArgs), } impl StreamTxs { - pub fn start(&self, send: Arc>) { - let (tx, rx) = init_channel(); - let client = send.lock().unwrap().client().clone(); - let stream_service = listen(rx, send); - + pub async fn start(&self, libra_sender: &mut LibraSender) -> anyhow::Result<()> { match &self { StreamTxs::EpochTickle { delay } => { println!("EpochTickle entry"); - epoch_tickle_poll(tx, client, delay.unwrap_or(60)); + epoch_tickle_poll(libra_sender, delay.unwrap_or(60)).await?; } - _ => { - println!("no service specified"); - exit(1); + StreamTxs::ValBid(args) => { + commit_reveal_poll(libra_sender, args.net_reward, args.delay.unwrap_or(60)).await?; } - }; - - stream_service - .join() - .expect("could not complete tasks in stream"); - } -} - -pub(crate) fn init_channel() -> (Sender, Receiver) { - mpsc::channel::() -} - -#[allow(unused_assignments)] -pub(crate) fn listen( - rx: Receiver, - send: Arc>, -) -> JoinHandle<()> { - thread::spawn(move || { - let mut busy = false; - - while !busy { - match rx.recv() { - Ok(payload) => { - info!("Tx: {:?}", payload); - if !busy { - busy = true; - match send - .lock() - .expect("could not access Sender client") - .sync_sign_submit_wait(payload) - { - Ok(_r) => { - busy = false; - } - Err(e) => { - error!("transaction failed: {:?}", &e); - break; - } - }; - } - } - Err(_) => break, - }; } - }) + Ok(()) + } } diff --git a/tools/txs/tests/commit_reveal.rs b/tools/txs/tests/commit_reveal.rs new file mode 100644 index 000000000..0cc2d268f --- /dev/null +++ b/tools/txs/tests/commit_reveal.rs @@ -0,0 +1,74 @@ +//! test commit reveal +use libra_query::query_view; +use libra_smoke_tests::libra_smoke::LibraSmoke; +use libra_txs::stream::bid_commit_reveal::{self, PofBidArgs}; +use libra_txs::{submit_transaction::Sender, txs_cli_stream::StreamTxs}; +use std::time::Duration; + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +/// Test triggering a new epoch +// Scenario: We want to trigger a new epoch using the TriggerEpoch command +// We will assume that triggering an epoch is an operation that we can test in a single node testnet +async fn background_commit_reveal() -> anyhow::Result<()> { + // create libra swarm and get app config for the validator + let mut ls = LibraSmoke::new(Some(1), None) + .await + .expect("could not start libra smoke"); + + let before_trigger_epoch_query_res = query_view::get_view( + &ls.client(), + "0x1::epoch_helper::get_current_epoch", + None, + None, + ) + .await + .expect("query failed: get epoch failed"); + + // TODO: why is it that smoke tests start on epoch 2? + assert_eq!( + &before_trigger_epoch_query_res.as_array().unwrap()[0], + "2", + "epoch should be 2" + ); + + let test_bid_amount = 1010; + + //////// TRIGGER THE EPOCH //////// + // The TriggerEpoch command does not require arguments, + // so we create it directly and attempt to run it. + let commit_reveal_cmd = StreamTxs::ValBid(PofBidArgs { + net_reward: test_bid_amount, + delay: Some(2), // Try to submit every 2 seconds in this test + }); + // create a Sender using the validator's app config + let val_app_cfg = ls.first_account_app_cfg()?; + let mut validator_sender = Sender::from_app_cfg(&val_app_cfg, None).await?; + let client = validator_sender.client().clone(); + let validator_addr = validator_sender.local_account.address(); + + // run the txs tool in background in stream mode + + let h = tokio::spawn(async move { + commit_reveal_cmd + .start(&mut validator_sender) + .await + .unwrap(); + }); + + // WAIT FOR END OF EPOCH chain_id==4 is 30 secs + // reveal window happens 15 seconds before end of epoch + std::thread::sleep(Duration::from_secs(20)); + h.abort(); + + match bid_commit_reveal::validator_committed_bid(&client, validator_addr).await { + Ok(onchain_bid) => { + dbg!(&onchain_bid); + assert!(test_bid_amount == onchain_bid, "incorrect bid found"); + println!("Validator committed bid successfully."); + return Ok(()); + } + Err(e) => { + panic!("Failed to reveal bid: {:?}", e); + } + } +} diff --git a/tools/txs/tests/trigger_epoch.rs b/tools/txs/tests/trigger_epoch.rs index 614de189a..4e2ee0576 100644 --- a/tools/txs/tests/trigger_epoch.rs +++ b/tools/txs/tests/trigger_epoch.rs @@ -1,6 +1,4 @@ //! test trigger epoch - -use std::sync::{Arc, Mutex}; use std::time::Duration; use diem_forge::Swarm; @@ -70,7 +68,7 @@ async fn sync_trigger_epoch() -> anyhow::Result<()> { Ok(()) } -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] /// Test triggering a new epoch // Scenario: We want to trigger a new epoch using the TriggerEpoch command // We will assume that triggering an epoch is an operation that we can test in a single node testnet @@ -102,11 +100,16 @@ async fn background_trigger_epoch() -> anyhow::Result<()> { let trigger_epoch_cmd = StreamTxs::EpochTickle { delay: Some(5) }; // create a Sender using the validator's app config let val_app_cfg = ls.first_account_app_cfg()?; - let validator_sender = Sender::from_app_cfg(&val_app_cfg, None).await?; - let wrapped_sender = Arc::new(Mutex::new(validator_sender)); + let mut validator_sender = Sender::from_app_cfg(&val_app_cfg, None).await?; - // run the txs tool in background in stream mode - std::thread::spawn(move || trigger_epoch_cmd.start(wrapped_sender)); + // run the txs tool in background in stream + + tokio::spawn(async move { + trigger_epoch_cmd + .start(&mut validator_sender) + .await + .unwrap(); + }); //////// FLIP BIT //////// std::thread::sleep(Duration::from_secs(10)); @@ -136,6 +139,8 @@ async fn background_trigger_epoch() -> anyhow::Result<()> { // helper for the testnet root to enable epoch boundary trigger async fn helper_set_enable_trigger(ls: &mut LibraSmoke) { + println!("enable trigger"); + let mut public_info = ls.swarm.diem_public_info(); let payload = public_info @@ -151,5 +156,5 @@ async fn helper_set_enable_trigger(ls: &mut LibraSmoke) { .submit_and_wait(&enable_trigger_tx) .await .expect("could not send demo tx"); - println!("testnet root account enables epoch trigger"); + println!("testnet root account sets epoch boundary trigger"); } diff --git a/tools/wallet/src/load_keys.rs b/tools/wallet/src/load_keys.rs index 71dd280c2..8e6ef3d24 100644 --- a/tools/wallet/src/load_keys.rs +++ b/tools/wallet/src/load_keys.rs @@ -61,7 +61,6 @@ pub fn get_account_from_prompt() -> (AuthenticationKey, AccountAddress, WalletLi #[test] fn wallet() { - // use diem_wallet::Mnemonic; let mut wallet = WalletLibrary::new(); let (auth_key, child_number) = wallet.new_address().expect("Could not generate address"); diff --git a/util/fixtures/mnemonic/alice.mnem b/util/fixtures/mnemonic/alice.mnem index a8d8b681b..cd2fbc932 100644 --- a/util/fixtures/mnemonic/alice.mnem +++ b/util/fixtures/mnemonic/alice.mnem @@ -1 +1 @@ -talent sunset lizard pill fame nuclear spy noodle basket okay critic grow sleep legend hurry pitch blanket clerk impose rough degree sock insane purse \ No newline at end of file +talent sunset lizard pill fame nuclear spy noodle basket okay critic grow sleep legend hurry pitch blanket clerk impose rough degree sock insane purse