Skip to content
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
9 changes: 7 additions & 2 deletions apps/src/bin/namada-client/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use color_eyre::eyre::Result;
use namada_apps::cli;
use namada_apps::cli::cmds::*;
use namada_apps::client::eth_bridge::bridge_pool;
use namada_apps::client::eth_bridge::{bridge_pool, validator_set};
use namada_apps::client::{rpc, tx, utils};

pub async fn main() -> Result<()> {
Expand Down Expand Up @@ -49,10 +49,15 @@ pub async fn main() -> Result<()> {
Sub::Withdraw(Withdraw(args)) => {
tx::submit_withdraw(ctx, args).await;
}
// Eth bridge pool
// Eth bridge
Sub::AddToEthBridgePool(args) => {
bridge_pool::add_to_eth_bridge_pool(ctx, args.0).await;
}
Sub::SubmitValidatorSetUpdate(SubmitValidatorSetUpdate(
args,
)) => {
validator_set::submit_validator_set_update(ctx, args).await;
}
// Ledger queries
Sub::QueryEpoch(QueryEpoch(args)) => {
rpc::query_and_print_epoch(args).await;
Expand Down
51 changes: 50 additions & 1 deletion apps/src/lib/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,9 @@ pub mod cmds {
.subcommand(Bond::def().display_order(2))
.subcommand(Unbond::def().display_order(2))
.subcommand(Withdraw::def().display_order(2))
// Ethereum bridge pool
// Ethereum bridge
.subcommand(AddToEthBridgePool::def().display_order(3))
.subcommand(SubmitValidatorSetUpdate::def().display_order(3))
// Queries
.subcommand(QueryEpoch::def().display_order(4))
.subcommand(QueryTransfers::def().display_order(4))
Expand Down Expand Up @@ -279,6 +280,8 @@ pub mod cmds {
Self::parse_with_ctx(matches, QueryProtocolParameters);
let add_to_eth_bridge_pool =
Self::parse_with_ctx(matches, AddToEthBridgePool);
let submit_validator_set_update =
Self::parse_with_ctx(matches, SubmitValidatorSetUpdate);
let utils = SubCmd::parse(matches).map(Self::WithoutContext);
tx_custom
.or(tx_transfer)
Expand All @@ -293,6 +296,7 @@ pub mod cmds {
.or(unbond)
.or(withdraw)
.or(add_to_eth_bridge_pool)
.or(submit_validator_set_update)
.or(query_epoch)
.or(query_transfers)
.or(query_conversions)
Expand Down Expand Up @@ -357,6 +361,7 @@ pub mod cmds {
Unbond(Unbond),
Withdraw(Withdraw),
AddToEthBridgePool(AddToEthBridgePool),
SubmitValidatorSetUpdate(SubmitValidatorSetUpdate),
QueryEpoch(QueryEpoch),
QueryTransfers(QueryTransfers),
QueryConversions(QueryConversions),
Expand Down Expand Up @@ -1715,6 +1720,25 @@ pub mod cmds {
}
}

#[derive(Clone, Debug)]
pub struct SubmitValidatorSetUpdate(pub args::SubmitValidatorSetUpdate);

impl SubCmd for SubmitValidatorSetUpdate {
const CMD: &'static str = "validator-set-update";

fn parse(matches: &ArgMatches) -> Option<Self> {
matches.subcommand_matches(Self::CMD).map(|matches| {
Self(args::SubmitValidatorSetUpdate::parse(matches))
})
}

fn def() -> App {
App::new(Self::CMD)
.about("Submit a validator set update protocol tx.")
.add_args::<args::SubmitValidatorSetUpdate>()
}
}

#[derive(Clone, Debug)]
pub struct ConstructProof(pub args::BridgePoolProof);

Expand Down Expand Up @@ -2334,6 +2358,31 @@ pub mod args {
}
}

/// A transfer to be added to the Ethereum bridge pool.
#[derive(Clone, Debug)]
pub struct SubmitValidatorSetUpdate {
/// The query parameters.
pub query: Query,
/// The epoch of the validator set to relay.
pub epoch: Option<Epoch>,
}

impl Args for SubmitValidatorSetUpdate {
fn parse(matches: &ArgMatches) -> Self {
let epoch = EPOCH.parse(matches);
let query = Query::parse(matches);
Self { epoch, query }
}

fn def(app: App) -> App {
app.add_args::<Query>().arg(
EPOCH
.def()
.about("The epoch of the validator set to relay."),
)
}
}

#[derive(Debug, Clone)]
pub struct RecommendBatch {
/// The query parameters.
Expand Down
2 changes: 1 addition & 1 deletion apps/src/lib/client/eth_bridge/bridge_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ mod recommendations {
let voting_powers = RPC
.shell()
.eth_bridge()
.eth_voting_powers(&client, &height)
.voting_powers_at_height(&client, &height)
.await
.unwrap();
let valset_size = voting_powers.len() as u64;
Expand Down
92 changes: 90 additions & 2 deletions apps/src/lib/client/eth_bridge/validator_set.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,109 @@
use std::cmp::Ordering;
use std::sync::Arc;

use borsh::BorshSerialize;
use data_encoding::HEXLOWER;
use ethbridge_governance_contract::Governance;
use futures::future::FutureExt;
use namada::core::types::storage::Epoch;
use namada::core::types::vote_extensions::validator_set_update;
use namada::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable};
use namada::eth_bridge::ethers::core::types::TransactionReceipt;
use namada::eth_bridge::ethers::providers::{Http, Provider};
use namada::eth_bridge::structs::{Signature, ValidatorSetArgs};
use namada::ledger::queries::RPC;
use namada::proto::Tx;
use namada::types::key::RefTo;
use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType};
use namada::types::transaction::TxType;
use tokio::time::{Duration, Instant};

use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit};
use crate::cli::{args, safe_exit};
use crate::cli::{args, safe_exit, Context};
use crate::client::eth_bridge::BlockOnEthSync;
use crate::facade::tendermint_rpc::HttpClient;
use crate::facade::tendermint_rpc::{Client, HttpClient};

/// Submit a validator set update protocol tx to the network.
pub async fn submit_validator_set_update(
mut ctx: Context,
args: args::SubmitValidatorSetUpdate,
) {
let maybe_validator_data = ctx.wallet.take_validator_data();
let Some(validator_data) = maybe_validator_data else {
println!("No validator keys found in the Namada directory.");
safe_exit(1);
};

let args::SubmitValidatorSetUpdate {
query,
epoch: maybe_epoch,
} = args;

let client = HttpClient::new(query.ledger_address).unwrap();

let epoch = if let Some(epoch) = maybe_epoch {
epoch
} else {
RPC.shell().epoch(&client).await.unwrap().next()
};

if epoch.0 == 0 {
println!(
"Validator set update proofs should only be requested from epoch \
1 onwards"
);
safe_exit(1);
}

let voting_powers = match RPC
.shell()
.eth_bridge()
.voting_powers_at_epoch(&client, &epoch)
.await
{
Ok(voting_powers) => voting_powers,
Err(e) => {
println!("Failed to get voting powers: {e}");
safe_exit(1);
}
};
let protocol_tx = ProtocolTxType::ValSetUpdateVext(
validator_set_update::Vext {
voting_powers,
signing_epoch: epoch - 1,
validator_addr: validator_data.address,
}
.sign(&validator_data.keys.eth_bridge_keypair),
);
let tx = Tx::new(
vec![],
Some(
TxType::Protocol(ProtocolTx {
pk: validator_data.keys.protocol_keypair.ref_to(),
tx: protocol_tx,
})
.try_to_vec()
.expect("Could not serialize ProtocolTx"),
),
)
.sign(&validator_data.keys.protocol_keypair);

let response = match client.broadcast_tx_sync(tx.to_bytes().into()).await {
Ok(response) => response,
Err(e) => {
println!("Failed to broadcast protocol tx: {e}");
safe_exit(1);
}
};

if response.code == 0.into() {
println!("Transaction added to mempool: {:?}", response);
} else {
let err = serde_json::to_string(&response).unwrap();
eprintln!("Encountered error while broadcasting transaction: {err}");
safe_exit(1);
}
}

/// Query an ABI encoding of the validator set to be installed
/// at the given epoch, and its associated proof.
Expand Down
2 changes: 1 addition & 1 deletion apps/src/lib/node/ledger/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ where
.unwrap(),
);
wallet
.take_validator_data()
.into_validator_data()
.map(|data| ShellMode::Validator {
data,
broadcast_sender,
Expand Down
19 changes: 16 additions & 3 deletions apps/src/lib/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub enum FindKeyError {
KeyNotFound,
#[error("{0}")]
KeyDecryptionError(keys::DecryptionError),
#[error("Expected a Secp256k1 key, but found another key kind")]
NotSecp256k1Error,
}

impl Wallet {
Expand Down Expand Up @@ -168,6 +170,12 @@ impl Wallet {
protocol_pk: Option<common::PublicKey>,
protocol_key_scheme: SchemeType,
) -> Result<ValidatorKeys, FindKeyError> {
match &eth_bridge_pk {
Some(common::PublicKey::Secp256k1(_)) | None => {}
_ => {
return Err(FindKeyError::NotSecp256k1Error);
}
}
let protocol_keypair = self
.find_secret_key(protocol_pk, |data| data.keys.protocol_keypair)?;
let eth_bridge_keypair = self
Expand Down Expand Up @@ -215,15 +223,20 @@ impl Wallet {
self.store.add_validator_data(address, keys);
}

/// Returns the validator data, if it exists.
/// Returns a reference to the validator data, if it exists.
pub fn get_validator_data(&self) -> Option<&ValidatorData> {
self.store.get_validator_data()
}

/// Take the validator data, if it exists.
pub fn take_validator_data(&mut self) -> Option<ValidatorData> {
self.store.validator_data.take()
}

/// Returns the validator data, if it exists.
/// [`Wallet::save`] cannot be called after using this
/// method as it involves a partial move
pub fn take_validator_data(self) -> Option<ValidatorData> {
/// method as it involves a partial move.
pub fn into_validator_data(self) -> Option<ValidatorData> {
self.store.validator_data()
}

Expand Down
51 changes: 42 additions & 9 deletions shared/src/ledger/queries/shell/eth_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ router! {ETH_BRIDGE,
// Iterates over all ethereum events and returns the amount of
// voting power backing each `TransferToEthereum` event.
( "pool" / "transfer_to_eth_progress" )
-> HashMap<TransferToEthereum, FractionalVotingPower> = transfer_to_ethereum_progress,
-> HashMap<TransferToEthereum, FractionalVotingPower>
= transfer_to_ethereum_progress,

// Request a proof of a validator set signed off for
// the given epoch.
Expand Down Expand Up @@ -95,11 +96,19 @@ router! {ETH_BRIDGE,
( "contracts" / "native_erc20" )
-> EthAddress = read_native_erc20_contract,

// Read the voting powers map for the requested validator set.
( "eth_voting_powers" / [height: BlockHeight]) -> VotingPowersMap = eth_voting_powers,
// Read the voting powers map for the requested validator set
// at the given block height.
( "voting_powers" / "height" / [height: BlockHeight] )
-> VotingPowersMap = voting_powers_at_height,

// Read the voting powers map for the requested validator set
// at the given block height.
( "voting_powers" / "epoch" / [epoch: Epoch] )
-> VotingPowersMap = voting_powers_at_epoch,

// Read the total supply of some wrapped ERC20 token in Namada.
( "erc20" / "supply" / [asset: EthAddress] ) -> Option<Amount> = read_erc20_supply,
( "erc20" / "supply" / [asset: EthAddress] )
-> Option<Amount> = read_erc20_supply,
}

/// Read the total supply of some wrapped ERC20 token in Namada.
Expand Down Expand Up @@ -499,21 +508,45 @@ where
}
}

/// The validator set in order with corresponding
/// voting powers.
fn eth_voting_powers<D, H>(
/// Retrieve the consensus validator voting powers at the
/// given [`BlockHeight`].
fn voting_powers_at_height<D, H>(
ctx: RequestCtx<'_, D, H>,
height: BlockHeight,
) -> storage_api::Result<VotingPowersMap>
where
D: 'static + DB + for<'iter> DBIter<'iter> + Sync,
H: 'static + StorageHasher + Sync,
{
let epoch = ctx.wl_storage.pos_queries().get_epoch(height);
let maybe_epoch = ctx.wl_storage.pos_queries().get_epoch(height);
let Some(epoch) = maybe_epoch else {
return Err(storage_api::Error::SimpleMessage(
"The epoch of the requested height does not exist",
));
};
voting_powers_at_epoch(ctx, epoch)
}

/// Retrieve the consensus validator voting powers at the
/// given [`Epoch`].
fn voting_powers_at_epoch<D, H>(
ctx: RequestCtx<'_, D, H>,
epoch: Epoch,
) -> storage_api::Result<VotingPowersMap>
where
D: 'static + DB + for<'iter> DBIter<'iter> + Sync,
H: 'static + StorageHasher + Sync,
{
let current_epoch = ctx.wl_storage.storage.get_current_epoch().0;
if epoch > current_epoch + 1u64 {
return Err(storage_api::Error::SimpleMessage(
"The requested epoch cannot be queried",
));
}
let (_, voting_powers) = ctx
.wl_storage
.ethbridge_queries()
.get_validator_set_args(epoch);
.get_validator_set_args(Some(epoch));
Ok(voting_powers)
}

Expand Down