Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rust binary to update permissioned stake table #2410

Merged
merged 4 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sequencer/src/bin/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ struct Options {
/// stake_table_key = "BLS_VER_KEY~...",
/// state_ver_key = "SCHNORR_VER_KEY~...",
/// da = true,
/// stake = 1, # this value is ignored, but needs to be set
/// },
/// ]
#[clap(long, env = "ESPRESSO_SEQUENCER_INITIAL_PERMISSIONED_STAKE_TABLE_PATH")]
Expand Down
104 changes: 104 additions & 0 deletions sequencer/src/bin/update-permissioned-stake-table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use anyhow::Result;
use clap::Parser;
use espresso_types::parse_duration;
use ethers::types::Address;
use sequencer_utils::{logging, stake_table::{update_stake_table, PermissionedStakeTableUpdate}};
use std::{path::PathBuf, time::Duration};
use url::Url;

#[derive(Debug, Clone, Parser)]
struct Options {
/// RPC URL for the L1 provider.
#[clap(
short,
long,
env = "ESPRESSO_SEQUENCER_L1_PROVIDER",
default_value = "http://localhost:8545"
)]
rpc_url: Url,

/// Request rate when polling L1.
#[clap(
long,
env = "ESPRESSO_SEQUENCER_L1_POLLING_INTERVAL",
default_value = "7s",
value_parser = parse_duration,
)]
pub l1_polling_interval: Duration,

/// Mnemonic for an L1 wallet.
///
/// This wallet is used to deploy the contracts, so the account indicated by ACCOUNT_INDEX must
/// be funded with with ETH.
#[clap(
long,
name = "MNEMONIC",
env = "ESPRESSO_SEQUENCER_ETH_MNEMONIC",
default_value = "test test test test test test test test test test test junk"
)]
mnemonic: String,

/// Account index in the L1 wallet generated by MNEMONIC to use when deploying the contracts.
#[clap(
long,
name = "ACCOUNT_INDEX",
env = "ESPRESSO_DEPLOYER_ACCOUNT_INDEX",
default_value = "0"
)]
account_index: u32,

/// Permissioned stake table contract address.
#[clap(long, env = "ESPRESSO_SEQUENCER_PERMISSIONED_STAKE_TABLE_ADDRESS")]
contract_address: Address,

/// Path to the toml file containing the update information.
///
/// Schema of toml file:
/// ```toml
/// stakers_to_remove = [
/// {
/// stake_table_key = "BLS_VER_KEY~...",
/// state_ver_key = "SCHNORR_VER_KEY~...",
/// da = false,
/// stake = 1, # this value is ignored, but needs to be set
/// },
/// ]
///
/// new_stakers = [
/// {
/// stake_table_key = "BLS_VER_KEY~...",
/// state_ver_key = "SCHNORR_VER_KEY~...",
/// da = true,
/// stake = 1, # this value is ignored, but needs to be set
/// },
/// ]
/// ```
#[clap(
long,
env = "ESPRESSO_SEQUENCER_PERMISSIONED_STAKE_TABLE_UPDATE_TOML_PATH",
verbatim_doc_comment
)]
update_toml_path: PathBuf,
#[clap(flatten)]
logging: logging::Config,
}

#[tokio::main]
async fn main() -> Result<()> {
let opts = Options::parse();
opts.logging.init();
let update = PermissionedStakeTableUpdate::from_toml_file(&opts.update_toml_path)?;


update_stake_table(
opts.rpc_url,
opts.l1_polling_interval,
opts.mnemonic,
opts.account_index,
opts.contract_address,
update,
)
.await?;

Ok(())
}
87 changes: 85 additions & 2 deletions utils/src/stake_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
///
/// The initial stake table is passed to the permissioned stake table contract
/// on deployment.
use contract_bindings::permissioned_stake_table::NodeInfo;
use contract_bindings::permissioned_stake_table::{NodeInfo, PermissionedStakeTable};
use ethers::{
middleware::SignerMiddleware,
providers::{Http, Middleware as _, Provider},
signers::{coins_bip39::English, MnemonicBuilder, Signer as _},
types::Address,
};
use hotshot::types::BLSPubKey;
use hotshot_contract_adapter::stake_table::NodeInfoJf;
use hotshot_types::network::PeerConfigKeys;
use url::Url;

use std::{fs, path::Path};
use std::{fs, path::Path, sync::Arc, time::Duration};

/// A stake table config stored in a file
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
Expand Down Expand Up @@ -48,6 +55,82 @@ impl From<PermissionedStakeTableConfig> for Vec<NodeInfo> {
}
}

/// Information to add and remove stakers in the permissioned stake table contract.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(bound(deserialize = ""))]
pub struct PermissionedStakeTableUpdate {
#[serde(default)]
stakers_to_remove: Vec<PeerConfigKeys<BLSPubKey>>,
#[serde(default)]
new_stakers: Vec<PeerConfigKeys<BLSPubKey>>,
}

impl PermissionedStakeTableUpdate {
pub fn from_toml_file(path: &Path) -> anyhow::Result<Self> {
let config_file_as_string: String = fs::read_to_string(path)
.unwrap_or_else(|_| panic!("Could not read config file located at {}", path.display()));

Ok(
toml::from_str::<Self>(&config_file_as_string).unwrap_or_else(|err| {
panic!(
"Unable to convert config file {} to TOML: {err}",
path.display()
)
}),
)
}

fn stakers_to_remove(&self) -> Vec<NodeInfo> {
self.stakers_to_remove
.iter()
.map(|peer_config| {
let node_info: NodeInfoJf = peer_config.clone().into();
node_info.into()
})
.collect()
}

fn new_stakers(&self) -> Vec<NodeInfo> {
self.new_stakers
.iter()
.map(|peer_config| {
let node_info: NodeInfoJf = peer_config.clone().into();
node_info.into()
})
.collect()
}
}

pub async fn update_stake_table(
l1url: Url,
l1_interval: Duration,
mnemonic: String,
account_index: u32,
contract_address: Address,
update: PermissionedStakeTableUpdate,
) -> anyhow::Result<()> {
let provider = Provider::<Http>::try_from(l1url.to_string())?.interval(l1_interval);
let chain_id = provider.get_chainid().await?.as_u64();
let wallet = MnemonicBuilder::<English>::default()
.phrase(mnemonic.as_str())
.index(account_index)?
.build()?
.with_chain_id(chain_id);
let l1 = Arc::new(SignerMiddleware::new(provider.clone(), wallet));

let contract = PermissionedStakeTable::new(contract_address, l1);

tracing::info!("sending stake table update transaction");

let tx_receipt = contract
.update(update.stakers_to_remove(), update.new_stakers())
.send()
.await?
.await?;
tracing::info!("Transaction receipt: {:?}", tx_receipt);
Ok(())
}

#[cfg(test)]
mod test {
use crate::stake_table::PermissionedStakeTableConfig;
Expand Down
Loading