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

ENG-771: Add upgrade-info json file to determine when upgrades are applied + rpc + cli #793

Closed
wants to merge 13 commits into from
3 changes: 3 additions & 0 deletions fendermint/app/config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ builtin_actors_bundle = "bundle.car"
custom_actors_bundle = "custom_actors_bundle.car"
# Where to reach CometBFT for queries or broadcasting transactions.
tendermint_rpc_url = "http://127.0.0.1:26657"
# The upgrade_info JSON file contains mapping from block height to upgrade info which is
# used to determine when to run an upgrade migration while progressing through the chain.
upgrade_info = "upgrade_info.json"

# Secp256k1 private key used for signing transactions. Leave empty if not validating,
# or if it's not needed to sign and broadcast transactions as a validator.
Expand Down
5 changes: 4 additions & 1 deletion fendermint/app/options/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing_subscriber::EnvFilter;

use self::{
eth::EthArgs, genesis::GenesisArgs, key::KeyArgs, materializer::MaterializerArgs, rpc::RpcArgs,
run::RunArgs,
run::RunArgs, upgrade::UpgradeArgs,
};

pub mod eth;
Expand All @@ -18,6 +18,7 @@ pub mod key;
pub mod materializer;
pub mod rpc;
pub mod run;
pub mod upgrade;

mod log;
mod parse;
Expand Down Expand Up @@ -160,6 +161,8 @@ pub enum Commands {
/// Subcommands related to the Testnet Materializer.
#[clap(aliases = &["mat", "matr", "mate"])]
Materializer(MaterializerArgs),
// Subcommands related to managing the upgrade_info.json file
Upgrade(UpgradeArgs),
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions fendermint/app/options/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ pub enum RpcQueryCommands {
},
/// Get the slowly changing state parameters.
StateParams,
/// Query the current upgrade schedule
UpgradeSchedule,
Comment on lines +95 to +96
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not exactly sure what an upgrade schedule is; maybe the docstring could explain a bit more.

For example I understand it can be the contents of the JSON file, but I was wondering if it could also include the versions that the node is capable of executing.

/// Get the current node state
NodeState,
}

#[derive(Subcommand, Debug, Clone)]
Expand Down
43 changes: 43 additions & 0 deletions fendermint/app/options/src/upgrade.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2022-2024 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT

use std::path::PathBuf;

use clap::{Args, Subcommand};

#[derive(Args, Debug)]
pub struct UpgradeArgs {
/// Path to the upgrade_info JSON file.
#[arg(long)]
pub upgrade_file: PathBuf,

#[command(subcommand)]
pub command: UpgradeCommands,
}

#[derive(Subcommand, Debug)]
pub enum UpgradeCommands {
AddUpgrade(AddUpgrade),
}

#[derive(Args, Debug)]
pub struct AddUpgrade {
/// the height at which to schedule the upgrade
#[arg(long)]
pub height: u64,

/// the application version the upgrade will update to
#[arg(long)]
pub new_app_version: u64,

/// the required cometbft version for the upgrade
#[arg(long)]
pub cometbft_version: String,

/// whether this is a required upgrade or not. A required upgrade
/// will cause the node to freeze and not process any more blocks
/// if it does not have the corresponding Upgrade defined to
/// migrate to that version
#[arg(long)]
pub required: bool,
Comment on lines +37 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking when an upgrade wouldn't be required. I know we talked about upgrades where you can download the new software and replace the existing one with it now, because the binary is backwards compatible, and it will apply the upgrade when the time comes. Why the upgrade list would know this though..

OTOH if some nodes update the app_version then everyone must do it, because it's part of the ledger. So an optional upgrade must not update the app version.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not sure how an optional upgrade would be handled if the node with the upgrade is started later than the height. Nothing will trigger that upgrade, so we don't know if it's been applied or not?

}
5 changes: 4 additions & 1 deletion fendermint/app/settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ pub struct Settings {
builtin_actors_bundle: PathBuf,
/// Custom actors CAR file.
custom_actors_bundle: PathBuf,
/// upgrade_info JSON file.
upgrade_info: PathBuf,

/// Where to reach CometBFT for queries or broadcasting transactions.
tendermint_rpc_url: Url,
Expand All @@ -229,7 +231,8 @@ impl Settings {
snapshots_dir,
contracts_dir,
builtin_actors_bundle,
custom_actors_bundle
custom_actors_bundle,
upgrade_info
);

/// Load the default configuration from a directory,
Expand Down
2 changes: 2 additions & 0 deletions fendermint/app/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod key;
pub mod materializer;
pub mod rpc;
pub mod run;
pub mod upgrade;

#[async_trait]
pub trait Cmd {
Expand Down Expand Up @@ -66,6 +67,7 @@ pub async fn exec(opts: &Options) -> anyhow::Result<()> {
Commands::Rpc(args) => args.exec(()).await,
Commands::Eth(args) => args.exec(settings(opts)?.eth).await,
Commands::Materializer(args) => args.exec(()).await,
Commands::Upgrade(args) => args.exec(()).await,
}
}

Expand Down
10 changes: 10 additions & 0 deletions fendermint/app/src/cmd/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ async fn query(
let json = json!({ "response": res });
print_json(&json)?;
}
RpcQueryCommands::UpgradeSchedule => {
let res = client.upgrade_schedule(height).await?;
let json = json!({ "response": res });
print_json(&json)?;
}
RpcQueryCommands::NodeState => {
let res = client.node_state(height).await?;
let json = json!({ "response": res });
print_json(&json)?;
}
};
Ok(())
}
Expand Down
12 changes: 10 additions & 2 deletions fendermint/app/src/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use fendermint_crypto::SecretKey;
use fendermint_rocksdb::{blockstore::NamespaceBlockstore, namespaces, RocksDb, RocksDbConfig};
use fendermint_vm_actor_interface::eam::EthAddress;
use fendermint_vm_interpreter::chain::ChainEnv;
use fendermint_vm_interpreter::fvm::upgrades::UpgradeScheduler;
use fendermint_vm_interpreter::fvm::upgrades::{UpgradeSchedule, Upgrades};
use fendermint_vm_interpreter::{
bytes::{BytesMessageInterpreter, ProposalPrepareMode},
chain::{ChainMessageInterpreter, CheckpointPool},
Expand Down Expand Up @@ -107,14 +107,22 @@ async fn run(settings: Settings) -> anyhow::Result<()> {
ValidatorContext::new(sk, broadcaster)
});

let upgrade_schedule = UpgradeSchedule::get_or_create(settings.upgrade_info())?;
tracing::info!(
path = settings.upgrade_info().to_string_lossy().into_owned(),
schedule = ?upgrade_schedule,
"loaded upgrade_schedule"
);

let interpreter = FvmMessageInterpreter::<NamespaceBlockstore, _>::new(
tendermint_client.clone(),
validator_ctx,
settings.contracts_dir(),
settings.fvm.gas_overestimation_rate,
settings.fvm.gas_search_step,
settings.fvm.exec_in_check,
UpgradeScheduler::new(),
upgrade_schedule,
Upgrades::new(),
);
let interpreter = SignedMessageInterpreter::new(interpreter);
let interpreter = ChainMessageInterpreter::<_, NamespaceBlockstore>::new(interpreter);
Expand Down
37 changes: 37 additions & 0 deletions fendermint/app/src/cmd/upgrade.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2022-2024 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT

use std::path::PathBuf;

use fendermint_app_options::upgrade::AddUpgrade;
use fendermint_app_options::upgrade::UpgradeArgs;
use fendermint_app_options::upgrade::UpgradeCommands;
use fendermint_vm_interpreter::fvm::upgrades::UpgradeSchedule;
use fendermint_vm_message::ipc::UpgradeInfo;

use crate::cmd;

cmd! {
UpgradeArgs(self) {
let upgrade_file = self.upgrade_file.clone();
match &self.command {
UpgradeCommands::AddUpgrade(args) => args.exec(upgrade_file).await,
}
}
}

cmd! {
AddUpgrade(self, upgrade_file: PathBuf) {
let mut us = UpgradeSchedule::get_or_create(&upgrade_file)?;
us.add(UpgradeInfo::new(
self.height.try_into().unwrap(),
self.new_app_version,
self.cometbft_version.clone(),
self.required,
))?;

us.export_file(upgrade_file)?;

Ok(())
}
}
10 changes: 10 additions & 0 deletions fendermint/app/src/tmconv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ pub fn to_query(ret: FvmQueryRet, block_height: BlockHeight) -> anyhow::Result<r
FvmQueryRet::Call(_) | FvmQueryRet::EstimateGas(_) => ExitCode::OK,
FvmQueryRet::StateParams(_) => ExitCode::OK,
FvmQueryRet::BuiltinActors(_) => ExitCode::OK,
FvmQueryRet::UpgradeSchedule(_) => ExitCode::OK,
FvmQueryRet::NodeState(_) => ExitCode::OK,
};

// The return value has a `key` field which is supposed to be set to the data matched.
Expand Down Expand Up @@ -302,6 +304,14 @@ pub fn to_query(ret: FvmQueryRet, block_height: BlockHeight) -> anyhow::Result<r
let v = ipld_encode!(ba);
(Vec::new(), v)
}
FvmQueryRet::UpgradeSchedule(us) => {
let v = ipld_encode!(us);
(Vec::new(), v)
}
FvmQueryRet::NodeState(ns) => {
let v = ipld_encode!(ns);
(Vec::new(), v)
}
};

// The height here is the height of the block that was committed, not in which the app hash appeared.
Expand Down
3 changes: 3 additions & 0 deletions fendermint/docker/runner.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ ENV FM_ETH__LISTEN__HOST=0.0.0.0
RUN mkdir /fendermint/logs
RUN chmod 777 /fendermint/logs

RUN echo "[]" > /fendermint/upgrade_info.json
RUN chmod 777 /fendermint/upgrade_info.json

COPY fendermint/docker/.artifacts/bundle.car $FM_HOME_DIR/bundle.car
COPY fendermint/docker/.artifacts/custom_actors_bundle.car $FM_HOME_DIR/custom_actors_bundle.car
COPY fendermint/docker/.artifacts/contracts $FM_HOME_DIR/contracts
Expand Down
26 changes: 25 additions & 1 deletion fendermint/rpc/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use anyhow::{anyhow, Context};
use async_trait::async_trait;
use fendermint_vm_message::ipc::UpgradeInfo;
use fvm_ipld_encoding::serde::Serialize;
use fvm_shared::message::Message;
use prost::Message as ProstMessage;
Expand All @@ -16,7 +17,7 @@ use fvm_shared::ActorID;
use fvm_shared::{address::Address, error::ExitCode};

use fendermint_vm_message::query::{
ActorState, BuiltinActors, FvmQuery, FvmQueryHeight, GasEstimate, StateParams,
ActorState, BuiltinActors, FvmQuery, FvmQueryHeight, GasEstimate, NodeState, StateParams,
};

use crate::response::encode_data;
Expand Down Expand Up @@ -114,6 +115,29 @@ pub trait QueryClient: Sync {
Ok(QueryResponse { height, value })
}

async fn upgrade_schedule(
&self,
height: FvmQueryHeight,
) -> anyhow::Result<QueryResponse<Vec<UpgradeInfo>>> {
let res = self.perform(FvmQuery::UpgradeSchedule, height).await?;
let height = res.height;
let value = extract(res, |res| {
fvm_ipld_encoding::from_slice(&res.value)
.context("failed to decode UpgradeSchedule from query")
})?;
Ok(QueryResponse { height, value })
}

async fn node_state(&self, height: FvmQueryHeight) -> anyhow::Result<QueryResponse<NodeState>> {
let res = self.perform(FvmQuery::NodeState, height).await?;
let height = res.height;
let value = extract(res, |res| {
fvm_ipld_encoding::from_slice(&res.value)
.context("failed to decode NodeState from query")
})?;
Ok(QueryResponse { height, value })
}

/// Run an ABCI query.
async fn perform(&self, query: FvmQuery, height: FvmQueryHeight) -> anyhow::Result<AbciQuery>;
}
Expand Down
5 changes: 3 additions & 2 deletions fendermint/testing/contract-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use anyhow::{anyhow, Context, Result};
use byteorder::{BigEndian, WriteBytesExt};
use cid::Cid;
use fendermint_vm_core::Timestamp;
use fendermint_vm_interpreter::fvm::upgrades::{UpgradeSchedule, Upgrades};
use fendermint_vm_interpreter::fvm::PowerUpdates;
use fvm_shared::{bigint::Zero, clock::ChainEpoch, econ::TokenAmount, version::NetworkVersion};
use std::{future::Future, sync::Arc};
Expand All @@ -15,7 +16,6 @@ use fendermint_vm_interpreter::{
bundle::{bundle_path, contracts_path, custom_actors_bundle_path},
state::{FvmExecState, FvmGenesisState, FvmStateParams, FvmUpdatableParams},
store::memory::MemoryBlockstore,
upgrades::UpgradeScheduler,
FvmApplyRet, FvmGenesisOutput, FvmMessage, FvmMessageInterpreter,
},
ExecInterpreter, GenesisInterpreter,
Expand Down Expand Up @@ -56,7 +56,8 @@ pub async fn init_exec_state(
1.05,
1.05,
false,
UpgradeScheduler::new(),
UpgradeSchedule::new(),
Upgrades::new(),
);

let (state, out) = interpreter
Expand Down
Loading
Loading