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
3 changes: 3 additions & 0 deletions apps/src/bin/namada-node/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub fn main() -> Result<()> {
cmds::Ledger::DumpDb(cmds::LedgerDumpDb(args)) => {
ledger::dump_db(ctx.config.ledger, args);
}
cmds::Ledger::DbDeleteValue(cmds::LedgerDbDeleteValue(args)) => {
ledger::db_delete_value(ctx.config.ledger, args);
}
},
cmds::NamadaNode::Config(sub) => match sub {
cmds::Config::Gen(cmds::ConfigGen) => {
Expand Down
50 changes: 49 additions & 1 deletion apps/src/lib/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,7 @@ pub mod cmds {
Run(LedgerRun),
Reset(LedgerReset),
DumpDb(LedgerDumpDb),
DbDeleteValue(LedgerDbDeleteValue),
}

impl SubCmd for Ledger {
Expand All @@ -837,8 +838,11 @@ pub mod cmds {
let run = SubCmd::parse(matches).map(Self::Run);
let reset = SubCmd::parse(matches).map(Self::Reset);
let dump_db = SubCmd::parse(matches).map(Self::DumpDb);
let db_delete_value =
SubCmd::parse(matches).map(Self::DbDeleteValue);
run.or(reset)
.or(dump_db)
.or(db_delete_value)
// The `run` command is the default if no sub-command given
.or(Some(Self::Run(LedgerRun(args::LedgerRun(None)))))
})
Expand All @@ -853,6 +857,7 @@ pub mod cmds {
.subcommand(LedgerRun::def())
.subcommand(LedgerReset::def())
.subcommand(LedgerDumpDb::def())
.subcommand(LedgerDbDeleteValue::def())
}
}

Expand Down Expand Up @@ -912,6 +917,29 @@ pub mod cmds {
}
}

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

impl SubCmd for LedgerDbDeleteValue {
const CMD: &'static str = "db-delete-value";

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

fn def() -> App {
App::new(Self::CMD)
.about(
"Delete a value from the ledger node's DB at the given \
key.",
)
.setting(AppSettings::ArgRequiredElseHelp)
.add_args::<args::LedgerDbDeleteValue>()
}
}

#[derive(Clone, Debug)]
pub enum Config {
Gen(ConfigGen),
Expand Down Expand Up @@ -2263,6 +2291,26 @@ pub mod args {
}
}

#[derive(Clone, Debug)]
pub struct LedgerDbDeleteValue {
pub storage_key: storage::Key,
}

impl Args for LedgerDbDeleteValue {
fn parse(matches: &ArgMatches) -> Self {
let storage_key = STORAGE_KEY.parse(matches);
Self { storage_key }
}

fn def(app: App) -> App {
app.arg(
STORAGE_KEY
.def()
.about("Storage key to delete a value from."),
)
}
}

/// Transaction associated results arguments
#[derive(Clone, Debug)]
pub struct QueryResult {
Expand Down Expand Up @@ -2359,7 +2407,7 @@ pub mod args {
}
}

/// A transfer to be added to the Ethereum bridge pool.
/// Submit a validator set update protocol tx.
#[derive(Clone, Debug)]
pub struct SubmitValidatorSetUpdate {
/// The query parameters.
Expand Down
59 changes: 59 additions & 0 deletions apps/src/lib/node/ledger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,65 @@ pub fn dump_db(
db.dump_last_block(out_file_path);
}

/// Delete a value from storage.
// TODO: recalculate merkle roots? maybe this should be
// a new argument
pub fn db_delete_value(
config: config::Ledger,
args: args::LedgerDbDeleteValue,
) {
use namada::ledger::storage::DB;

let chain_id = config.chain_id;
let db_path = config.shell.db_dir(&chain_id);

let mut db = storage::PersistentDB::open(db_path, None);
let latest_block = match db.read_last_block() {
Ok(Some(data)) => {
tracing::info!(
last_height = ?data.height,
"Read the last committed block's data."
);
data
}
Ok(None) => {
tracing::error!("No block has been committed yet.");
return;
}
Err(reason) => {
tracing::error!(%reason, "Failed to read the last block's data.");
return;
}
};

tracing::info!(
key = %args.storage_key,
last_height = ?latest_block.height,
"Deleting value from storage subspace key..."
);
if let Err(reason) =
db.delete_subspace_val(latest_block.height, &args.storage_key)
{
tracing::error!(
%reason,
key = %args.storage_key,
"Failed to delete value from database."
);
return;
}

tracing::debug!("Flushing changes...");
if let Err(reason) = db.flush(true) {
tracing::error!(%reason, "Failed to flush database changes.");
return;
}

tracing::info!(
key = %args.storage_key,
"Value successfully deleted from the database."
);
}

/// Runs and monitors a few concurrent tasks.
///
/// This includes:
Expand Down
166 changes: 166 additions & 0 deletions tests/src/e2e/eth_bridge_tests.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
mod helpers;

use std::num::NonZeroU64;
use std::ops::ControlFlow;
use std::str::FromStr;

use borsh::BorshDeserialize;
use color_eyre::eyre::{eyre, Result};
use namada::eth_bridge::oracle;
use namada::eth_bridge::storage::vote_tallies;
use namada::ledger::eth_bridge::{
ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations,
UpgradeableContract,
};
use namada::types::address::wnam;
use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS;
use namada::types::ethereum_events::EthAddress;
use namada::types::storage::Epoch;
use namada::types::{address, token};
use namada_apps::config::ethereum_bridge;
use namada_apps::control_flow::timeouts::SleepStrategy;
use namada_core::ledger::eth_bridge::ADDRESS as BRIDGE_ADDRESS;
use namada_core::types::address::Address;
use namada_core::types::ethereum_events::{
EthereumEvent, TransferToEthereum, TransferToNamada,
};
use namada_core::types::token::Amount;
use tokio::time::{Duration, Instant};

use super::setup::set_ethereum_bridge_mode;
use crate::e2e::eth_bridge_tests::helpers::{
Expand All @@ -30,6 +36,7 @@ use crate::e2e::eth_bridge_tests::helpers::{
};
use crate::e2e::helpers::{
find_address, find_balance, get_actor_rpc, init_established_account,
rpc_client_do, run_single_node_test_from,
};
use crate::e2e::setup;
use crate::e2e::setup::constants::{
Expand Down Expand Up @@ -1214,3 +1221,162 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> {

Ok(())
}

/// Test that manually submitting a validator set update protocol
/// transaction works.
#[tokio::test]
async fn test_submit_validator_set_udpate() -> Result<()> {
let (test, bg_ledger) = setup_single_validator_test()?;

let ledger_addr = get_actor_rpc(&test, &Who::Validator(0));
let rpc_addr = format!("http://{ledger_addr}");

// wait for epoch E > 1 to be installed
let instant = Instant::now() + Duration::from_secs(180);
SleepStrategy::Constant(Duration::from_millis(500))
.timeout(instant, || async {
match rpc_client_do(&rpc_addr, &(), |rpc, client, ()| async move {
rpc.shell().epoch(&client).await.ok()
})
.await
{
Some(epoch) if epoch.0 > 0 => ControlFlow::Break(()),
_ => ControlFlow::Continue(()),
}
})
.await?;

// check that we have a complete proof for E=1
let valset_upd_keys = vote_tallies::Keys::from(&Epoch(1));
let seen_key = valset_upd_keys.seen();
SleepStrategy::Constant(Duration::from_millis(500))
.timeout(instant, || async {
rpc_client_do(
&rpc_addr,
&seen_key,
|rpc, client, seen_key| async move {
rpc.shell()
.storage_value(&client, None, None, false, seen_key)
.await
.map_or_else(
|_| {
unreachable!(
"By the end of epoch 0, a proof should be \
available"
)
},
|rsp| {
let seen =
bool::try_from_slice(&rsp.data).unwrap();
assert!(
seen,
"No valset upd complete proof in storage"
);
ControlFlow::Break(())
},
)
},
)
.await
})
.await?;

// shut down ledger
let mut ledger = bg_ledger.foreground();
ledger.send_control('c')?;
ledger.exp_string("Namada ledger node has shut down.")?;
ledger.exp_eof()?;
drop(ledger);

// delete the valset upd proof for E=1 from storage
for key in &valset_upd_keys {
let key = key.to_string();
let delete_args =
vec!["ledger", "db-delete-value", "--storage-key", &key];
let mut delete_cmd =
run_as!(test, Who::Validator(0), Bin::Node, delete_args, Some(30))?;
delete_cmd
.exp_string("Value successfully deleted from the database.")?;
drop(delete_cmd);
}

// restart the ledger
let (test, _bg_ledger) = run_single_node_test_from(test)?;

// check that no complete proof is available for E=1 anymore
SleepStrategy::Constant(Duration::from_millis(500))
.timeout(instant, || async {
rpc_client_do(
&rpc_addr,
&seen_key,
|rpc, client, seen_key| async move {
rpc.shell()
.storage_value(&client, None, None, false, seen_key)
.await
.map_or_else(
|_| unreachable!("The RPC does not error out"),
|rsp| {
assert_eq!(
rsp.info,
format!(
"No value found for key: {seen_key}"
)
);
ControlFlow::Break(())
},
)
},
)
.await
})
.await?;

// submit valset upd vote extension protocol tx for E=1
let tx_args = vec![
"validator-set-update",
"--ledger-address",
&ledger_addr,
"--epoch",
"1",
];
let mut namadac_tx =
run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(30))?;
namadac_tx.exp_string("Transaction added to mempool")?;
drop(namadac_tx);

// check that a complete proof is once more available for E=1
SleepStrategy::Constant(Duration::from_millis(500))
.timeout(instant, || async {
rpc_client_do(
&rpc_addr,
&seen_key,
|rpc, client, seen_key| async move {
rpc.shell()
.storage_value(&client, None, None, false, seen_key)
.await
.map_or_else(
|_| ControlFlow::Continue(()),
|rsp| {
if rsp
.info
.starts_with("No value found for key")
{
return ControlFlow::Continue(());
}
let seen =
bool::try_from_slice(&rsp.data).unwrap();
assert!(
seen,
"No valset upd complete proof in storage"
);
ControlFlow::Break(())
},
)
},
)
.await
})
.await?;

Ok(())
}
2 changes: 1 addition & 1 deletion tests/src/e2e/eth_bridge_tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ pub async fn read_erc20_supply(
ledger_addr: &str,
asset: &EthAddress,
) -> Result<Option<token::Amount>> {
rpc_client_do(ledger_addr, |rpc, client| async move {
rpc_client_do(ledger_addr, &(), |rpc, client, ()| async move {
let amount = rpc
.shell()
.eth_bridge()
Expand Down
Loading