diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f6d110041..678cc7b23 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -320,6 +320,11 @@ jobs: cache-all-crates: true cache-on-failure: true + - uses: actions/download-artifact@v4.1.2 + with: + name: framework-build + path: framework/ + - name: twin working-directory: ./testsuites/twin run: cargo test --no-fail-fast diff --git a/.gitignore b/.gitignore index 61f82acb9..e843caf07 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,6 @@ # will have compiled files and executables **/target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk @@ -13,8 +9,7 @@ Cargo.lock **/build **/doc **/framework_upgrade -# # upgrade fixtures -# **/framework/src/upgrade_fixtures/fixtures/upgrade* +**/head.mrb sccache.log diff --git a/framework/cached-packages/build.rs b/framework/cached-packages/build.rs index 01bdcadd4..0b7940bc4 100644 --- a/framework/cached-packages/build.rs +++ b/framework/cached-packages/build.rs @@ -18,6 +18,7 @@ fn main() { prev_dir.join("libra-framework").join("sources").display() ); + // TODO: make this run the libra binary if it is found in users $PATH ReleaseTarget::Head .create_release( false, diff --git a/framework/cached-packages/src/libra_framework_sdk_builder.rs b/framework/cached-packages/src/libra_framework_sdk_builder.rs index d2ffcbf2d..de66a3c72 100644 --- a/framework/cached-packages/src/libra_framework_sdk_builder.rs +++ b/framework/cached-packages/src/libra_framework_sdk_builder.rs @@ -13,6 +13,7 @@ #![allow(dead_code)] #![allow(unused_imports)] #![allow(clippy::too_many_arguments, clippy::doc_lazy_continuation)] + use diem_types::{ account_address::AccountAddress, transaction::{EntryFunction, TransactionPayload}, @@ -260,6 +261,10 @@ pub enum EntryFunctionCall { id: u64, }, + /// testnet helper to allow testnet root account to set flip the boundary bit + /// used for testing cli tools for polling and triggering + EpochBoundarySmokeEnableTrigger {}, + EpochBoundarySmokeTriggerEpoch {}, /// Only a Voucher of the validator can flip the unjail bit. @@ -545,7 +550,7 @@ pub enum EntryFunctionCall { friend_account: AccountAddress, }, - /// will only succesfully vouch if the two are not related by ancestry + /// will only successfully vouch if the two are not related by ancestry /// prevents spending a vouch that would not be counted. /// to add a vouch and ignore this check use insist_vouch VouchVouchFor { @@ -706,6 +711,7 @@ impl EntryFunctionCall { multisig_address, id, } => donor_voice_txs_vote_veto_tx(multisig_address, id), + EpochBoundarySmokeEnableTrigger {} => epoch_boundary_smoke_enable_trigger(), EpochBoundarySmokeTriggerEpoch {} => epoch_boundary_smoke_trigger_epoch(), JailUnjailByVoucher { addr } => jail_unjail_by_voucher(addr), LibraCoinClaimMintCapability {} => libra_coin_claim_mint_capability(), @@ -1519,6 +1525,23 @@ pub fn donor_voice_txs_vote_veto_tx( )) } +/// testnet helper to allow testnet root account to set flip the boundary bit +/// used for testing cli tools for polling and triggering +pub fn epoch_boundary_smoke_enable_trigger() -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("epoch_boundary").to_owned(), + ), + ident_str!("smoke_enable_trigger").to_owned(), + vec![], + vec![], + )) +} + pub fn epoch_boundary_smoke_trigger_epoch() -> TransactionPayload { TransactionPayload::EntryFunction(EntryFunction::new( ModuleId::new( @@ -2385,7 +2408,7 @@ pub fn vouch_revoke(friend_account: AccountAddress) -> TransactionPayload { )) } -/// will only succesfully vouch if the two are not related by ancestry +/// will only successfully vouch if the two are not related by ancestry /// prevents spending a vouch that would not be counted. /// to add a vouch and ignore this check use insist_vouch pub fn vouch_vouch_for(friend_account: AccountAddress) -> TransactionPayload { @@ -2762,6 +2785,16 @@ mod decoder { } } + pub fn epoch_boundary_smoke_enable_trigger( + payload: &TransactionPayload, + ) -> Option { + if let TransactionPayload::EntryFunction(_script) = payload { + Some(EntryFunctionCall::EpochBoundarySmokeEnableTrigger {}) + } else { + None + } + } + pub fn epoch_boundary_smoke_trigger_epoch( payload: &TransactionPayload, ) -> Option { @@ -3397,6 +3430,10 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy(@ol_framework); state.ready = false; @@ -253,16 +250,45 @@ module diem_framework::epoch_boundary { epoch_boundary(framework_signer, state.closing_epoch, 0); } + /// testnet helper to allow testnet root account to set flip the boundary bit + /// used for testing cli tools for polling and triggering + public entry fun smoke_enable_trigger(core_resource: &signer) + acquires BoundaryBit { + // cannot call this on mainnet + // only for smoke testing + assert!(testnet::is_not_mainnet(), ETRIGGER_EPOCH_UNAUTHORIZED); + // core_resource account is the the tesnet account with root permissions + system_addresses::assert_core_resource(core_resource); + let state = borrow_global_mut(@ol_framework); + state.closing_epoch = reconfiguration::get_current_epoch(); + state.ready = true; + } + + #[view] - /// check to see if the epoch BoundaryBit is true - public fun can_trigger(): bool acquires BoundaryBit { + /// API view for triggering which does not abort + public fun can_trigger():bool acquires BoundaryBit { + let state = borrow_global_mut(@ol_framework); + if (state.ready && + // greater than or equal because in case there is an epoch change due to an epoch bump in + // testnet Twin tools, or a rescue operation. + state.closing_epoch <= reconfiguration::get_current_epoch()){ + return true + }; + + false + } + + /// assert and abort with failure reason for trigger + fun assert_can_trigger(): bool acquires BoundaryBit { let state = borrow_global_mut(@ol_framework); assert!(state.ready, ETRIGGER_NOT_READY); - // greater than, in case there is an epoch change due to an epoch bump in + // greater than or equal because in case there is an epoch change due to an epoch bump in // testnet Twin tools, or a rescue operation. assert!(state.closing_epoch <= reconfiguration::get_current_epoch(), ENOT_SAME_EPOCH); - true + + can_trigger() } // This function handles the necessary migrations that occur at the epoch boundary @@ -272,9 +298,10 @@ module diem_framework::epoch_boundary { migrations::execute(framework); } - // Contains all of 0L's business logic for end of epoch. - // This removed business logic from reconfiguration.move - // and prevents dependency cycling. + /// Contains all of 0L's business logic for end of epoch. + /// This removed business logic from reconfiguration.move + /// and prevents dependency cycling. + // TODO: do we need closing_epoch if there is no depedency cycling with reconfiguration:: public(friend) fun epoch_boundary(root: &signer, closing_epoch: u64, epoch_round: u64) acquires BoundaryStatus { print(&string::utf8(b"EPOCH BOUNDARY BEGINS")); diff --git a/framework/releases/head.mrb b/framework/releases/head.mrb deleted file mode 100644 index 6bfd477db..000000000 Binary files a/framework/releases/head.mrb and /dev/null differ diff --git a/tools/query/src/chain_queries.rs b/tools/query/src/chain_queries.rs index c6c818a79..adb532797 100644 --- a/tools/query/src/chain_queries.rs +++ b/tools/query/src/chain_queries.rs @@ -2,8 +2,7 @@ use crate::query_view::{self, get_view}; use anyhow::Context; -use diem_sdk::rest_client::{diem_api_types::ViewRequest, Client}; -use libra_types::type_extensions::client_ext::entry_function_id; +use diem_sdk::rest_client::Client; /// Retrieves the current epoch from the blockchain. pub async fn get_epoch(client: &Client) -> anyhow::Result { @@ -21,29 +20,7 @@ pub async fn get_epoch(client: &Client) -> anyhow::Result { Ok(num) } -/// helper to get libra balance at a SlowWalletBalance type which shows -/// total balance and the unlocked balance. -pub async fn get_tower_difficulty(client: &Client) -> anyhow::Result<(u64, u64)> { - let slow_balance_id = entry_function_id("tower_state", "get_difficulty")?; - let request = ViewRequest { - function: slow_balance_id, - type_arguments: vec![], - arguments: vec![], - }; - - let res = client.view(&request, None).await?.into_inner(); - - // TODO: Gross. - let difficulty: u64 = - serde_json::from_value::(res.first().context("no difficulty returned")?.clone())? - .parse()?; - let security: u64 = serde_json::from_value::( - res.get(1).context("no security param returned")?.clone(), - )? - .parse()?; - - Ok((difficulty, security)) -} +// COMMIT NOTE: deprecated tower functions /// Retrieves the ID of the next governance proposal. pub async fn get_next_governance_proposal_id(client: &Client) -> anyhow::Result { @@ -115,3 +92,12 @@ pub async fn get_height(client: &Client) -> anyhow::Result { Ok(height) } + +/// Retrieves the current blockchain height. +pub async fn epoch_over_can_trigger(client: &Client) -> anyhow::Result { + let res = get_view(client, "0x1::epoch_boundary::can_trigger", None, None).await?; + + let value: Vec = serde_json::from_value(res)?; + + Ok(value[0]) +} diff --git a/tools/txs/src/lib.rs b/tools/txs/src/lib.rs index 67738fc9d..bfd0a2fbe 100644 --- a/tools/txs/src/lib.rs +++ b/tools/txs/src/lib.rs @@ -1,10 +1,12 @@ pub mod constants; pub mod generic_tx; pub mod publish; +pub mod stream; pub mod submit_transaction; pub mod transfer; pub mod txs_cli; pub mod txs_cli_community; pub mod txs_cli_governance; +pub mod txs_cli_stream; pub mod txs_cli_user; pub mod txs_cli_vals; diff --git a/tools/txs/src/stream/bid_commit_reveal.rs b/tools/txs/src/stream/bid_commit_reveal.rs new file mode 100644 index 000000000..31a52d013 --- /dev/null +++ b/tools/txs/src/stream/bid_commit_reveal.rs @@ -0,0 +1,8 @@ +#[derive(clap::Args)] +pub struct PofBidArgs { + #[clap(short, long)] + pub net_reward: u64, + + #[clap(short, long)] + pub test_private_key: Option, +} diff --git a/tools/txs/src/stream/epoch_tickle_poll.rs b/tools/txs/src/stream/epoch_tickle_poll.rs new file mode 100644 index 000000000..7ccd6f6c1 --- /dev/null +++ b/tools/txs/src/stream/epoch_tickle_poll.rs @@ -0,0 +1,37 @@ +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) { + 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(), + )); + + match res { + Ok(true) => { + let func = libra_stdlib::diem_governance_trigger_epoch(); + + tx.borrow_mut().send(func).unwrap(); + } + _ => { + info!("Not ready to call epoch.") + } + } + + thread::sleep(Duration::from_secs(delay_secs)); + }); + handle.join().expect("cannot poll for epoch boundary"); +} diff --git a/tools/txs/src/stream/mod.rs b/tools/txs/src/stream/mod.rs new file mode 100644 index 000000000..2cfb50065 --- /dev/null +++ b/tools/txs/src/stream/mod.rs @@ -0,0 +1 @@ +pub mod epoch_tickle_poll; diff --git a/tools/txs/src/submit_transaction.rs b/tools/txs/src/submit_transaction.rs index 49abef5f7..1651e1dac 100644 --- a/tools/txs/src/submit_transaction.rs +++ b/tools/txs/src/submit_transaction.rs @@ -212,11 +212,25 @@ impl Sender { println!("transaction sent"); self.response = Some(r.clone()); spin.finish_and_clear(); - debug!("{:?}", &r); + // debug!("{:?}", &r); OLProgress::complete("transaction success"); Ok(r) } + /// sync helper for sending tx + pub fn sync_sign_submit_wait( + &mut self, + payload: TransactionPayload, + ) -> anyhow::Result { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + // Call the asynchronous connect method using the runtime. + rt.block_on(self.sign_submit_wait(payload)) + } + /// Signs a transaction payload. pub fn sign_payload(&mut self, payload: TransactionPayload) -> SignedTransaction { let t = SystemTime::now() diff --git a/tools/txs/src/txs_cli.rs b/tools/txs/src/txs_cli.rs index 9712783c1..a0c94aab2 100644 --- a/tools/txs/src/txs_cli.rs +++ b/tools/txs/src/txs_cli.rs @@ -1,6 +1,7 @@ use crate::{ publish::encode_publish_payload, submit_transaction::Sender, txs_cli_community::CommunityTxs, - txs_cli_governance::GovernanceTxs, txs_cli_user::UserTxs, txs_cli_vals::ValidatorTxs, + txs_cli_governance::GovernanceTxs, txs_cli_stream::StreamTxs, txs_cli_user::UserTxs, + txs_cli_vals::ValidatorTxs, }; use anyhow::Result; use clap::Parser; @@ -17,6 +18,7 @@ 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)] @@ -91,6 +93,9 @@ pub enum TxsSub { #[clap(short, long)] amount: f64, }, + #[clap(subcommand, hide(true))] + /// Warn: Streaming transactions is experimental + Stream(StreamTxs), #[clap(hide(true))] /// Warn: Publishing contracts is for testing purposes only on Testnet Publish(MovePackageDir), @@ -214,6 +219,11 @@ impl TxsCli { Some(TxsSub::Governance(upgrade_txs)) => upgrade_txs.run(&mut send).await, 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); + Ok(()) + } _ => { println!( "\n\"I'm searching, though I don't succeed diff --git a/tools/txs/src/txs_cli_stream.rs b/tools/txs/src/txs_cli_stream.rs new file mode 100644 index 000000000..049fb5a16 --- /dev/null +++ b/tools/txs/src/txs_cli_stream.rs @@ -0,0 +1,83 @@ +// use crate::bid_commit_reveal::PofBidArgs; +use crate::stream::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 { + /// Trigger the epoch boundary when available + EpochTickle { + /// optional, seconds delay between attempts, defaults to 60s + #[clap(short, long)] + delay: Option, + }, + /// Submit secret PoF bids in background, and reveal when window opens + PofBid, +} + +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); + + match &self { + StreamTxs::EpochTickle { delay } => { + println!("EpochTickle entry"); + epoch_tickle_poll(tx, client, delay.unwrap_or(60)); + } + _ => { + println!("no service specified"); + exit(1); + } + }; + + 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, + }; + } + }) +} diff --git a/tools/txs/src/txs_cli_vals.rs b/tools/txs/src/txs_cli_vals.rs index 98d1ea749..58d3f4113 100644 --- a/tools/txs/src/txs_cli_vals.rs +++ b/tools/txs/src/txs_cli_vals.rs @@ -26,9 +26,9 @@ pub enum ValidatorTxs { /// validator set, with three decimal places: 1.234 is 123.4% bid_pct: Option, #[clap(short, long)] - /// Epoch until the bid is valid (will expire in `expiry` + 1). Max 30 epochs - expiry: u64, - #[clap(long)] + /// Epoch until the bid is valid (will expire in `expiry` + 1) + epoch_expiry: u64, + #[clap(short, long)] /// Eliminates the bid. There are only a limited amount of retractions that can happen in an epoch retract: bool, }, @@ -74,7 +74,7 @@ impl ValidatorTxs { ValidatorTxs::Pof { net_reward, bid_pct, - expiry, + epoch_expiry, retract, } => { if *retract { @@ -89,13 +89,13 @@ impl ValidatorTxs { } ProofOfFeePofUpdateBid { bid: scaled_bid, - epoch_expiry: *expiry, + epoch_expiry: *epoch_expiry, } } else { // Default path is to update based on the expected net reward ProofOfFeePofUpdateBidNetReward { net_reward: *net_reward, - epoch_expiry: *expiry, + epoch_expiry: *epoch_expiry, } } } diff --git a/tools/txs/tests/trigger_epoch.rs b/tools/txs/tests/trigger_epoch.rs index ba1450884..4b1d7067a 100644 --- a/tools/txs/tests/trigger_epoch.rs +++ b/tools/txs/tests/trigger_epoch.rs @@ -1,21 +1,27 @@ -// 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 +//! test trigger epoch + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use diem_forge::Swarm; +use libra_cached_packages::libra_stdlib; use libra_query::query_view; use libra_smoke_tests::libra_smoke::LibraSmoke; -use libra_txs::{submit_transaction::Sender, txs_cli_governance::GovernanceTxs}; -/// Test triggering a new epoch +use libra_txs::{ + submit_transaction::Sender, txs_cli_governance::GovernanceTxs, txs_cli_stream::StreamTxs, +}; + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] -#[ignore] // TODO -async fn trigger_epoch() -> anyhow::Result<()> { +/// 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 sync_trigger_epoch() -> 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 val_app_cfg = ls.first_account_app_cfg()?; - // create a Sender using the validator's app config - let mut validator_sender = Sender::from_app_cfg(&val_app_cfg, None).await?; - let before_trigger_epoch_query_res = query_view::get_view( &ls.client(), "0x1::reconfiguration::get_current_epoch", @@ -23,23 +29,95 @@ async fn trigger_epoch() -> anyhow::Result<()> { None, ) .await - .expect("Query failed: get epoch failed"); + .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 is not 2" + "epoch should be 2" ); + //////// FLIP BIT //////// + + helper_set_enable_trigger(&mut ls).await; + + //////// TRIGGER THE EPOCH //////// // The TriggerEpoch command does not require arguments, // so we create it directly and attempt to run it. let trigger_epoch_cmd = GovernanceTxs::EpochBoundary; + // create a Sender using the validator's app config + let mut validator_sender = Sender::from_app_cfg(&val_app_cfg, None).await?; // run the command and assert it succeeds let res = trigger_epoch_cmd.run(&mut validator_sender).await; assert!(res.is_ok(), "Failed to trigger new epoch: {:?}", res); - let _before_trigger_epoch_query_res = query_view::get_view( + let after_trigger_epoch_query_res = query_view::get_view( + &ls.client(), + "0x1::reconfiguration::get_current_epoch", + None, + None, + ) + .await + .expect("Query failed: get epoch failed"); + + assert!( + &after_trigger_epoch_query_res.as_array().unwrap()[0] == "3", + "epoch should be 3" + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +/// 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_trigger_epoch() -> 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::reconfiguration::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" + ); + + //////// TRIGGER THE EPOCH //////// + // The TriggerEpoch command does not require arguments, + // so we create it directly and attempt to run it. + 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)); + + // run the txs tool in background in stream mode + std::thread::spawn(move || trigger_epoch_cmd.start(wrapped_sender)); + + //////// FLIP BIT //////// + std::thread::sleep(Duration::from_secs(10)); + + helper_set_enable_trigger(&mut ls).await; + + std::thread::sleep(Duration::from_secs(20)); + + // now the backround service should succeed in triggering epoch. + + let after_trigger_epoch_query_res = query_view::get_view( &ls.client(), "0x1::reconfiguration::get_current_epoch", None, @@ -47,8 +125,31 @@ async fn trigger_epoch() -> anyhow::Result<()> { ) .await .expect("Query failed: get epoch failed"); - // TODO: find way to make epoch triggerable - //assert!(&before_trigger_epoch_query_res.as_array().unwrap()[0] == "3", "Epoch is not 3"); + + assert!( + &after_trigger_epoch_query_res.as_array().unwrap()[0] == "3", + "epoch should be 3" + ); Ok(()) } + +// helper for the testnet root to enable epoch boundary trigger +async fn helper_set_enable_trigger(ls: &mut LibraSmoke) { + let mut public_info = ls.swarm.diem_public_info(); + + let payload = public_info + .transaction_factory() + .payload(libra_stdlib::epoch_boundary_smoke_enable_trigger()); + + let enable_trigger_tx = public_info + .root_account() + .sign_with_transaction_builder(payload); + + public_info + .client() + .submit_and_wait(&enable_trigger_tx) + .await + .expect("could not send demo tx"); + println!("testnet root account enables epoch trigger"); +}