From fee8db867f86a955189b9756c0dab743b5870a55 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 2 Nov 2023 11:00:13 +0000 Subject: [PATCH 1/5] Overwrite values in storage from previous heights --- apps/src/lib/node/ledger/storage/rocksdb.rs | 54 +++++++++++++++++++++ core/src/ledger/storage/mockdb.rs | 11 +++++ core/src/ledger/storage/mod.rs | 10 ++++ 3 files changed, 75 insertions(+) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 0ce9ba1fe1..56754f30cb 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1432,6 +1432,60 @@ impl DB for RocksDB { Ok(()) } + + #[inline] + fn overwrite_entry( + &mut self, + batch: &mut Self::WriteBatch, + height: Option, + key: &Key, + new_value: impl AsRef<[u8]>, + ) -> Result<()> { + let last_height: BlockHeight = { + let state_cf = self.get_column_family(STATE_CF)?; + + types::decode( + self.0 + .get_cf(state_cf, "height") + .map_err(|e| Error::DBError(e.to_string()))? + .ok_or_else(|| { + Error::DBError("No block height found".to_string()) + })?, + ) + .map_err(|e| { + Error::DBError(format!("Unable to decode block height: {e}")) + })? + }; + let desired_height = height.unwrap_or(last_height); + + if desired_height != last_height { + todo!( + "Overwriting values at heights different than the last \ + committed height hast yet to be implemented" + ); + } + // NB: the following code only updates values + // written to at the last committed height + + let val = new_value.as_ref(); + + // update subspace value + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; + let subspace_key = key.to_string(); + + batch.0.put_cf(subspace_cf, subspace_key, val); + + // update value stored in diffs + let diffs_cf = self.get_column_family(DIFFS_CF)?; + let diffs_key = Key::from(last_height.to_db_key()) + .with_segment("new".to_owned()) + .join(key) + .to_string(); + + batch.0.put_cf(diffs_cf, diffs_key, val); + + Ok(()) + } } impl<'iter> DBIter<'iter> for RocksDB { diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index 11154ca5ce..50077ad565 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -590,6 +590,17 @@ impl DB for MockDB { Ok(()) } + + #[cold] + fn overwrite_entry( + &mut self, + _batch: &mut Self::WriteBatch, + _height: Option, + _key: &Key, + _new_value: impl AsRef<[u8]>, + ) -> Result<()> { + unimplemented!() + } } impl<'iter> DBIter<'iter> for MockDB { diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 4f506a61be..2ccc4eeb13 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -372,6 +372,16 @@ pub trait DB: std::fmt::Debug { batch: &mut Self::WriteBatch, key: &Key, ) -> Result<()>; + + /// Overwrite a new value in storage, taking into + /// account values stored at a previous height + fn overwrite_entry( + &mut self, + batch: &mut Self::WriteBatch, + height: Option, + key: &Key, + new_value: impl AsRef<[u8]>, + ) -> Result<()>; } /// A database prefix iterator. From 3e93349bbca3457c4be69c02f67a620b564b2ae9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 31 Oct 2023 18:51:37 +0000 Subject: [PATCH 2/5] Modify the funds of some account/token pair in rocksdb --- apps/src/bin/namada-node/cli.rs | 5 ++ apps/src/lib/cli.rs | 72 ++++++++++++++++++ apps/src/lib/node/ledger/mod.rs | 84 +++++++++++++++++++++ apps/src/lib/node/ledger/tendermint_node.rs | 17 +++++ 4 files changed, 178 insertions(+) diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index 6499a34e9e..434838aa85 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -2,6 +2,7 @@ use eyre::{Context, Result}; use namada::types::time::{DateTimeUtc, Utc}; +use namada_apps::cli::args::CliToSdk; use namada_apps::cli::{self, cmds}; use namada_apps::node::ledger; @@ -28,6 +29,10 @@ pub fn main() -> Result<()> { cmds::Ledger::DumpDb(cmds::LedgerDumpDb(args)) => { ledger::dump_db(ctx.config.ledger, args); } + cmds::Ledger::SetFunds(cmds::LedgerSetFunds(args)) => { + let args = args.to_sdk(&mut ctx); + ledger::set_funds(ctx.config.ledger, args); + } cmds::Ledger::RollBack(_) => { ledger::rollback(ctx.config.ledger) .wrap_err("Failed to rollback the Namada node")?; diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 51e3b03a73..ccc318ab36 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -945,6 +945,7 @@ pub mod cmds { RunUntil(LedgerRunUntil), Reset(LedgerReset), DumpDb(LedgerDumpDb), + SetFunds(LedgerSetFunds), RollBack(LedgerRollBack), } @@ -956,10 +957,12 @@ 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 set_funds = SubCmd::parse(matches).map(Self::SetFunds); let rollback = SubCmd::parse(matches).map(Self::RollBack); let run_until = SubCmd::parse(matches).map(Self::RunUntil); run.or(reset) .or(dump_db) + .or(set_funds) .or(rollback) .or(run_until) // The `run` command is the default if no sub-command given @@ -979,6 +982,7 @@ pub mod cmds { .subcommand(LedgerRunUntil::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) + .subcommand(LedgerSetFunds::def()) .subcommand(LedgerRollBack::def()) } } @@ -1061,6 +1065,29 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct LedgerSetFunds(pub args::LedgerSetFunds); + + impl SubCmd for LedgerSetFunds { + const CMD: &'static str = "set-funds"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::LedgerSetFunds::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Change the funds of an account in-place. Use with \ + caution, as this modifies state in storage without going \ + through the consensus protocol.", + ) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct LedgerRollBack; @@ -2964,6 +2991,51 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct LedgerSetFunds { + pub account: C::Address, + pub token: C::Address, + pub amount: token::Amount, + } + + impl CliToSdk> for LedgerSetFunds { + fn to_sdk(self, ctx: &mut Context) -> LedgerSetFunds { + LedgerSetFunds { + account: ctx.get(&self.account), + token: ctx.get(&self.token), + amount: self.amount, + } + } + } + + impl Args for LedgerSetFunds { + fn parse(matches: &ArgMatches) -> Self { + let account = ADDRESS.parse(matches); + let token = TOKEN.parse(matches); + let amount = AMOUNT.parse(matches).into(); + + Self { + account, + token, + amount, + } + } + + fn def(app: App) -> App { + app.arg( + ADDRESS + .def() + .help("The target account whose funds will be modified."), + ) + .arg( + AMOUNT.def().help( + "The amount of tokens to set for the target account.", + ), + ) + .arg(TOKEN.def().help("The asset to be changed in storage.")) + } + } + /// Convert CLI args to SDK args, with contextual data. pub trait CliToSdk: Args { /// Convert CLI args to SDK args, with contextual data. diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 3e1dbe0b46..34ee7cb68b 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -229,6 +229,90 @@ pub fn dump_db( db.dump_block(out_file_path, historic, block_height); } +/// Change the funds of an account in-place. Use with +/// caution, as this modifies state in storage without +/// going through the consensus protocol. +pub fn set_funds( + config: config::Ledger, + args::LedgerSetFunds { + account, + token, + amount, + }: args::LedgerSetFunds, +) { + use namada::ledger::storage::types::{decode, encode}; + use namada::ledger::storage::DB; + use namada::types::token; + + let cometbft_path = config.cometbft_dir(); + 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 mut batch = Default::default(); + + let bal_key = token::balance_key(&token, &account); + let minted_key = token::minted_balance_key(&token); + + tracing::debug!( + %bal_key, + %minted_key, + %token, + %account, + ?amount, + "Changing balance keys" + ); + + let previous_acc_funds = { + let value: token::Amount = db + .read_subspace_val(&bal_key) + .expect("Failed to read from storage") + .map(|amt| decode(amt).expect("Failed to decode amount")) + .unwrap_or_default(); + value + }; + let previous_minted_funds = { + let value: token::Amount = db + .read_subspace_val(&minted_key) + .expect("Failed to read from storage") + .map(|amt| decode(amt).expect("Failed to decode amount")) + .unwrap_or_default(); + value + }; + + tracing::debug!( + ?previous_acc_funds, + ?previous_minted_funds, + "Previous funds in storage" + ); + + let diff = amount.change() - previous_acc_funds.change(); + let new_minted_funds = + token::Amount::from_change(previous_minted_funds.change() + diff); + + db.overwrite_entry(&mut batch, None, &bal_key, encode(&amount)) + .expect("Failed to overwrite funds in storage"); + db.overwrite_entry( + &mut batch, + None, + &minted_key, + encode(&new_minted_funds), + ) + .expect("Failed to overwrite funds in storage"); + + db.exec_batch(batch).expect("Failed to execute write batch"); + + // reset CometBFT's state, such that we can resume with a different app hash + tendermint_node::reset_state(cometbft_path) + .expect("Failed to reset CometBFT state"); + + tracing::debug!( + new_acc_funds = ?amount, + ?new_minted_funds, + "New funds in storage" + ); +} + /// Roll Namada state back to the previous height pub fn rollback(config: config::Ledger) -> Result<(), shell::Error> { shell::rollback(config) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 0833f7c3a7..974f5f092e 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -191,6 +191,23 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { Ok(()) } +/// Reset CometBFT to genesis state. +pub fn reset_state(tendermint_dir: impl AsRef) -> Result<()> { + let tendermint_path = from_env_or_default()?; + let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); + std::process::Command::new(tendermint_path) + .args([ + "unsafe-reset-all", + // NOTE: log config: https://docs.tendermint.com/master/nodes/logging.html#configuring-log-levels + // "--log-level=\"*debug\"", + "--home", + &tendermint_dir, + ]) + .output() + .expect("Failed to reset tendermint node's data"); + Ok(()) +} + pub fn rollback(tendermint_dir: impl AsRef) -> Result { let tendermint_path = from_env_or_default()?; let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); From ca97a9a4ac292b5eb566661f7033753e5c1b72be Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 4 Nov 2023 11:28:23 +0100 Subject: [PATCH 3/5] State migration checks --- apps/src/lib/node/ledger/mod.rs | 9 ++++++++- .../lib/node/ledger/shell/finalize_block.rs | 19 +++++++++++++++---- apps/src/lib/node/ledger/shell/init_chain.rs | 13 +++++++++++++ apps/src/lib/node/ledger/shell/mod.rs | 8 ++++++++ apps/src/lib/node/ledger/tendermint_node.rs | 6 ++++++ 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 34ee7cb68b..866eef1a7f 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -16,7 +16,7 @@ use byte_unit::Byte; use futures::future::TryFutureExt; use namada::core::ledger::governance::storage::keys as governance_storage; use namada::eth_bridge::ethers::providers::{Http, Provider}; -use namada::types::storage::Key; +use namada::types::storage::{BlockHeight, Key}; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; use tokio::sync::mpsc; @@ -168,6 +168,13 @@ impl Shell { } } +/// Determine if the ledger is migrating state. +pub fn migrating_state() -> Option { + const ENV_INITIAL_HEIGHT: &str = "NAMADA_INITIAL_HEIGHT"; + let height = std::env::var(ENV_INITIAL_HEIGHT).ok()?; + height.parse::().ok().map(BlockHeight) +} + /// Run the ledger with an async runtime pub fn run(config: config::Ledger, wasm_dir: PathBuf) { let logical_cores = num_cpus::get(); diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 4fef1fc7ef..d4e184359d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -120,10 +120,21 @@ where )?; } - // Invariant: Has to be applied before `record_slashes_from_evidence` - // because it potentially needs to be able to read validator state from - // previous epoch and jailing validator removes the historical state - self.log_block_rewards(&req.votes, height, current_epoch, new_epoch)?; + // NOTE: this condition is required for hard-forks + // TODO: load block signatures from external sources for + // hard-forks, so we can still distribute PoS rewards + if hints::likely(!req.votes.is_empty()) { + // Invariant: Has to be applied before + // `record_slashes_from_evidence` because it potentially + // needs to be able to read validator state from + // previous epoch and jailing validator removes the historical state + self.log_block_rewards( + &req.votes, + height, + current_epoch, + new_epoch, + )?; + } if new_epoch { self.apply_inflation(current_epoch)?; } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 2554349680..f1132e48c3 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -21,6 +21,7 @@ use namada_sdk::eth_bridge::EthBridgeStatus; use super::*; use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tower_abci::{request, response}; +use crate::node::ledger; use crate::wasm_loader; impl Shell @@ -48,6 +49,18 @@ where current_chain_id, init.chain_id ))); } + if ledger::migrating_state().is_some() { + let rsp = response::InitChain { + validators: self + .get_abci_validator_updates(true) + .expect("Must be able to set genesis validator set"), + app_hash: self.wl_storage.storage.merkle_root().0.to_vec(), + ..Default::default() + }; + debug_assert!(!rsp.validators.is_empty()); + debug_assert!(!rsp.app_hash.iter().all(|&b| b == 0)); + return Ok(rsp); + } #[cfg(not(any(test, feature = "dev")))] let genesis = genesis::genesis(&self.base_dir, &self.wl_storage.storage.chain_id); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e647d1eebb..ed8cdc0692 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -82,6 +82,7 @@ use crate::facade::tendermint_proto::abci::{ use crate::facade::tendermint_proto::crypto::public_key; use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::facade::tower_abci::{request, response}; +use crate::node::ledger; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; @@ -572,6 +573,13 @@ where /// Load the Merkle root hash and the height of the last committed block, if /// any. This is returned when ABCI sends an `info` request. pub fn last_state(&mut self) -> response::Info { + if ledger::migrating_state().is_some() { + // when migrating state, return a height of 0, such + // that CometBFT calls InitChain and subsequently + // updates the apphash in its state + return response::Info::default(); + } + let mut response = response::Info::default(); let result = self.wl_storage.storage.get_state(); diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 974f5f092e..72b5c896d9 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -422,6 +422,12 @@ async fn write_tm_genesis( genesis.genesis_time = genesis_time .try_into() .expect("Couldn't convert DateTimeUtc to Tendermint Time"); + if let Some(height) = super::migrating_state() { + genesis.initial_height = height + .0 + .try_into() + .expect("Failed to convert initial genesis height"); + } let size = block::Size { // maximum size of a serialized Tendermint block // cannot go over 100 MiB From d4abfa7a744eea50d68e894323eee8336bc68abc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 4 Nov 2023 15:58:53 +0100 Subject: [PATCH 4/5] End-to-end test for set-funds --- tests/src/e2e/ledger_tests.rs | 166 +++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 2ee44c62ae..463e962f68 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -19,7 +19,7 @@ use std::time::{Duration, Instant}; use borsh_ext::BorshSerializeExt; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; -use namada::types::address::Address; +use namada::types::address::{nam, Address}; use namada::types::storage::Epoch; use namada::types::token; use namada_apps::config::ethereum_bridge; @@ -39,8 +39,8 @@ use setup::constants::*; use setup::Test; use super::helpers::{ - epochs_per_year_from_min_duration, get_height, wait_for_block_height, - wait_for_wasm_pre_compile, + epochs_per_year_from_min_duration, get_height, rpc_client_do, + wait_for_block_height, wait_for_wasm_pre_compile, }; use super::setup::{get_all_wasms_hashes, set_ethereum_bridge_mode, NamadaCmd}; use crate::e2e::helpers::{ @@ -50,6 +50,48 @@ use crate::e2e::helpers::{ use crate::e2e::setup::{self, default_port_offset, sleep, Bin, Who}; use crate::{run, run_as}; +fn kill_namada_ledger(mut node: NamadaCmd) -> Result<()> { + node.interrupt()?; + node.exp_string("Namada ledger node has shut down.")?; + node.exp_eof()?; + Ok(()) +} + +fn run_namada_ledger_node_until( + test: &Test, + idx: Option, + timeout_sec: Option, + until_height: u64, +) -> Result { + let who = match idx { + Some(idx) => Who::Validator(idx), + _ => Who::NonValidator, + }; + let mut node = run_as!( + test, + who.clone(), + Bin::Node, + &[ + "ledger", + "run-until", + "--block-height", + &format!("{until_height}"), + "--suspend" + ], + timeout_sec + )?; + node.exp_string("Namada ledger node started")?; + if let Who::Validator(_) = who { + node.exp_string("This node is a validator")?; + } else { + node.exp_string("This node is not a validator")?; + } + node.exp_string(&format!( + "Reached block height {until_height}, suspending." + ))?; + Ok(node) +} + fn start_namada_ledger_node( test: &Test, idx: Option, @@ -116,6 +158,124 @@ fn run_ledger() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_hardfork_set_funds() -> Result<()> { + const HARDFORK_HEIGHT: u64 = 3; + const ACCOUNT_ALIAS: &str = "garfo"; + + let test = setup::single_node_net()?; + + // Generate a new key for an implicit account + let account_address = { + let mut cmd = run!( + test, + Bin::Wallet, + &[ + "key", + "gen", + "--alias", + ACCOUNT_ALIAS, + "--unsafe-dont-encrypt" + ], + Some(20), + )?; + cmd.assert_success(); + let mut cmd = run!( + test, + Bin::Wallet, + &["address", "find", "--alias", ACCOUNT_ALIAS], + Some(20), + )?; + let (_, matched) = cmd.exp_regex(r"Found address Implicit: .*\n")?; + let (_, account_address) = matched + .trim() + .split_once("Found address Implicit: ") + .unwrap(); + Address::from_str(account_address).unwrap() + }; + + // Run a validator and a full node until `HARDFORK_HEIGHT` + let validator = run_namada_ledger_node_until( + &test, + Some(0), + Some(120), + HARDFORK_HEIGHT, + )? + .background(); + let fullnode = + run_namada_ledger_node_until(&test, None, Some(120), HARDFORK_HEIGHT)? + .background(); + + let validator_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let fullnode_rpc = get_actor_rpc(&test, &Who::NonValidator); + + // Assert that `account_address` has a balance of 0 + let bal = rpc_client_do( + &validator_rpc, + &account_address, + |rpc, client, addr| async move { + rpc.vp().token().balance(&client, &nam(), addr).await + }, + ) + .await?; + assert!(bal.is_zero()); + + // Wait until we reach the desired height on each node + let mut validator_ready = false; + let mut fullnode_ready = false; + let start = Instant::now(); + let loop_timeout = Duration::new(20, 0); + loop { + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timeout waiting for block height"); + } + if !validator_ready { + let last_block = rpc_client_do( + &validator_rpc, + (), + |rpc, client, ()| async move { + rpc.shell().last_block(&client).await + }, + ) + .await?; + + if let Some(block) = last_block { + if block.height.0 == HARDFORK_HEIGHT { + validator_ready = true; + } + } + } + if !fullnode_ready { + let last_block = rpc_client_do( + &fullnode_rpc, + (), + |rpc, client, ()| async move { + rpc.shell().last_block(&client).await + }, + ) + .await?; + + if let Some(block) = last_block { + if block.height.0 == HARDFORK_HEIGHT { + fullnode_ready = true; + } + } + } + if fullnode_ready && validator_ready { + break; + } + } + + // Kill the nodes + kill_namada_ledger(validator.foreground())?; + kill_namada_ledger(fullnode.foreground())?; + + // TODO: call `set-funds`, and check that the balances were + // updated on each node + + Ok(()) +} + /// In this test we: /// 1. Run 2 genesis validator ledger nodes and 1 non-validator node /// 2. Cross over epoch to check for consensus with multiple nodes From d00968005a363a35ff0f18b373f9cb731d334347 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 7 Nov 2023 14:03:10 +0100 Subject: [PATCH 5/5] fixup! Modify the funds of some account/token pair in rocksdb --- apps/src/lib/node/ledger/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 866eef1a7f..333fce0aff 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -308,6 +308,7 @@ pub fn set_funds( .expect("Failed to overwrite funds in storage"); db.exec_batch(batch).expect("Failed to execute write batch"); + db.flush(true).expect("Failed to flush data to disk"); // reset CometBFT's state, such that we can resume with a different app hash tendermint_node::reset_state(cometbft_path)