From 26510f2f8386fab72dfc787d3a7242982d081f5d Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Thu, 28 Sep 2023 09:25:52 +0100 Subject: [PATCH] FM-283: Add validator account kind setting (#285) --- Cargo.lock | 4 +-- fendermint/app/config/default.toml | 8 ++++- fendermint/app/config/test.toml | 4 +++ fendermint/app/options/src/rpc.rs | 8 ++++- fendermint/app/settings/src/lib.rs | 32 ++++++++++++----- fendermint/app/src/cmd/rpc.rs | 13 ++++++- fendermint/app/src/cmd/run.rs | 35 +++++++++++++++---- fendermint/rpc/examples/simplecoin.rs | 3 +- fendermint/rpc/src/message.rs | 16 ++++++--- .../vm/interpreter/src/fvm/broadcast.rs | 7 ++-- 10 files changed, 98 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a5dd9cf..27d9997a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3045,7 +3045,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "tendermint", + "tendermint 0.31.1", ] [[package]] @@ -3081,7 +3081,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "tendermint", + "tendermint 0.31.1", "tendermint-rpc", "thiserror", "tokio", diff --git a/fendermint/app/config/default.toml b/fendermint/app/config/default.toml index eedc9c85..68a5321f 100644 --- a/fendermint/app/config/default.toml +++ b/fendermint/app/config/default.toml @@ -11,11 +11,17 @@ contracts_dir = "contracts" builtin_actors_bundle = "bundle.car" # Where to reach CometBFT for queries or broadcasting transactions. tendermint_rpc_url = "http://127.0.0.1:26657" + # 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. # Leaving empty by default so single node deployments don't fail to start because # this key is not copied into place. -validator_key = "" +# [validator_key] +# # Path to the secret key file in base64 format. +# path = + +# # The on-chain account kind (regular|ethereum) +# kind = [abci] # Number of concurrent requests allowed to reach the application. diff --git a/fendermint/app/config/test.toml b/fendermint/app/config/test.toml index 9b7019ce..be6e2317 100644 --- a/fendermint/app/config/test.toml +++ b/fendermint/app/config/test.toml @@ -1,6 +1,10 @@ # These setting are overlayed over `default.toml` in unit tests # to exercise parsing values that are not viable as defaults. +[validator_key] +path = "dummy.sk" +kind = "ethereum" + [resolver] subnet_id = "/r31415926" diff --git a/fendermint/app/options/src/rpc.rs b/fendermint/app/options/src/rpc.rs index 71b95612..b6ac7fcf 100644 --- a/fendermint/app/options/src/rpc.rs +++ b/fendermint/app/options/src/rpc.rs @@ -10,7 +10,10 @@ use fvm_ipld_encoding::RawBytes; use fvm_shared::{address::Address, econ::TokenAmount, MethodNum}; use tendermint_rpc::Url; -use crate::parse::{parse_address, parse_bytes, parse_cid, parse_full_fil, parse_token_amount}; +use crate::{ + genesis::AccountKind, + parse::{parse_address, parse_bytes, parse_cid, parse_full_fil, parse_token_amount}, +}; #[derive(Args, Debug)] pub struct RpcArgs { @@ -151,6 +154,9 @@ pub struct TransArgs { /// Path to the secret key of the sender to sign the transaction. #[arg(long, short)] pub secret_key: PathBuf, + /// Indicate whether its a regular or ethereum account. + #[arg(long, short, default_value = "regular")] + pub account_kind: AccountKind, /// Sender account nonce. #[arg(long, short = 'n')] pub sequence: u64, diff --git a/fendermint/app/settings/src/lib.rs b/fendermint/app/settings/src/lib.rs index 7dfe5d14..a9083ba7 100644 --- a/fendermint/app/settings/src/lib.rs +++ b/fendermint/app/settings/src/lib.rs @@ -48,6 +48,26 @@ impl std::net::ToSocketAddrs for SocketAddress { } } +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "lowercase")] +/// Indicate the FVM account kind for generating addresses from a key. +pub enum AccountKind { + /// Has an f1 address. + Regular, + /// Has an f410 address. + Ethereum, +} + +/// A Secp256k1 key used to sign transactions, +/// with the account kind showing if it's a regular or an ethereum key. +#[derive(Debug, Deserialize, Clone)] +pub struct SigningKey { + path: PathBuf, + pub kind: AccountKind, +} + +home_relative!(SigningKey { path }); + #[derive(Debug, Deserialize, Clone)] pub struct AbciSettings { pub listen: SocketAddress, @@ -99,8 +119,9 @@ pub struct Settings { builtin_actors_bundle: PathBuf, /// Where to reach CometBFT for queries or broadcasting transactions. tendermint_rpc_url: Url, - /// Secp256k1 private key used for signing transactions. Leave empty if not validating. - validator_key: PathBuf, + + /// Secp256k1 private key used for signing transactions sent in the validator's name. Leave empty if not validating. + pub validator_key: Option, pub abci: AbciSettings, pub db: DbSettings, @@ -133,12 +154,7 @@ macro_rules! home_relative { } impl Settings { - home_relative!( - data_dir, - contracts_dir, - builtin_actors_bundle, - validator_key - ); + home_relative!(data_dir, contracts_dir, builtin_actors_bundle); /// Load the default configuration from a directory, /// then potential overrides specific to the run mode, diff --git a/fendermint/app/src/cmd/rpc.rs b/fendermint/app/src/cmd/rpc.rs index cf99be58..e5c692b3 100644 --- a/fendermint/app/src/cmd/rpc.rs +++ b/fendermint/app/src/cmd/rpc.rs @@ -8,6 +8,8 @@ use std::pin::Pin; use anyhow::Context; use async_trait::async_trait; use bytes::Bytes; +use fendermint_app_options::genesis::AccountKind; +use fendermint_crypto::SecretKey; use fendermint_rpc::client::BoundFendermintClient; use fendermint_rpc::tx::{ AsyncResponse, BoundClient, CallClient, CommitResponse, SyncResponse, TxAsync, TxClient, @@ -329,8 +331,9 @@ struct TransClient { impl TransClient { pub fn new(client: FendermintClient, args: &TransArgs) -> anyhow::Result { let sk = read_secret_key(&args.secret_key)?; + let addr = to_address(&sk, &args.account_kind)?; let chain_id = chainid::from_str_hashed(&args.chain_name)?; - let mf = MessageFactory::new(sk, args.sequence, chain_id)?; + let mf = MessageFactory::new(sk, addr, args.sequence, chain_id); let client = client.bind(mf); let client = Self { inner: client, @@ -377,3 +380,11 @@ fn gas_params(args: &TransArgs) -> GasParams { gas_premium: args.gas_premium.clone(), } } + +fn to_address(sk: &SecretKey, kind: &AccountKind) -> anyhow::Result
{ + let pk = sk.public_key().serialize(); + match kind { + AccountKind::Regular => Ok(Address::new_secp256k1(&pk)?), + AccountKind::Ethereum => Ok(Address::new_delegated(eam::EAM_ACTOR_ID, &pk)?), + } +} diff --git a/fendermint/app/src/cmd/run.rs b/fendermint/app/src/cmd/run.rs index 20cdee8f..05686427 100644 --- a/fendermint/app/src/cmd/run.rs +++ b/fendermint/app/src/cmd/run.rs @@ -1,10 +1,13 @@ // Copyright 2022-2023 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use anyhow::{anyhow, Context}; +use anyhow::{anyhow, bail, Context}; use fendermint_abci::ApplicationService; use fendermint_app::{App, AppConfig, AppStore, BitswapBlockstore}; +use fendermint_app_settings::AccountKind; +use fendermint_crypto::SecretKey; use fendermint_rocksdb::{blockstore::NamespaceBlockstore, namespaces, RocksDb, RocksDbConfig}; +use fendermint_vm_actor_interface::eam; use fendermint_vm_interpreter::{ bytes::{BytesMessageInterpreter, ProposalPrepareMode}, chain::{ChainMessageInterpreter, CheckpointPool}, @@ -12,6 +15,7 @@ use fendermint_vm_interpreter::{ signed::SignedMessageInterpreter, }; use fendermint_vm_resolver::ipld::IpldResolver; +use fvm_shared::address::Address; use libp2p::identity::secp256k1; use libp2p::identity::Keypair; use tracing::info; @@ -33,20 +37,29 @@ async fn run(settings: Settings) -> anyhow::Result<()> { tendermint_rpc::HttpClient::new(settings.tendermint_rpc_url()?) .context("failed to create Tendermint client")?; - let validator_key = { - let sk = settings.validator_key(); - if sk.exists() && sk.is_file() { - Some(read_secret_key(&sk).context("failed to read validator key")?) - } else { + let validator = match settings.validator_key { + Some(ref key) => { + let sk = key.path(settings.home_dir()); + if sk.exists() && sk.is_file() { + let sk = read_secret_key(&sk).context("failed to read validator key")?; + let addr = to_address(&sk, &key.kind)?; + Some((sk, addr)) + } else { + bail!("validator key does not exist: {}", sk.to_string_lossy()); + } + } + None => { tracing::debug!("validator key not configured"); None } }; - let validator_ctx = validator_key.map(|sk| { + let validator_ctx = validator.map(|(sk, addr)| { // For now we are using the validator key for submitting transactions. + // This allows us to identify transactions coming from bonded validators, to give priority to protocol related transactions. let broadcaster = Broadcaster::new( client.clone(), + addr, sk.clone(), settings.fvm.gas_fee_cap.clone(), settings.fvm.gas_premium.clone(), @@ -237,3 +250,11 @@ fn to_resolver_config(settings: &Settings) -> anyhow::Result anyhow::Result
{ + let pk = sk.public_key().serialize(); + match kind { + AccountKind::Regular => Ok(Address::new_secp256k1(&pk)?), + AccountKind::Ethereum => Ok(Address::new_delegated(eam::EAM_ACTOR_ID, &pk)?), + } +} diff --git a/fendermint/rpc/examples/simplecoin.rs b/fendermint/rpc/examples/simplecoin.rs index a65d76f9..4e0ae5f2 100644 --- a/fendermint/rpc/examples/simplecoin.rs +++ b/fendermint/rpc/examples/simplecoin.rs @@ -127,8 +127,7 @@ async fn main() { .value .chain_id; - let mf = MessageFactory::new(sk, sn, ChainID::from(chain_id)) - .expect("failed to create message factor"); + let mf = MessageFactory::new_secp256k1(sk, sn, ChainID::from(chain_id)); let mut client = client.bind(mf); diff --git a/fendermint/rpc/src/message.rs b/fendermint/rpc/src/message.rs index bd62a932..0fb51f98 100644 --- a/fendermint/rpc/src/message.rs +++ b/fendermint/rpc/src/message.rs @@ -28,15 +28,21 @@ pub struct MessageFactory { } impl MessageFactory { - pub fn new(sk: SecretKey, sequence: u64, chain_id: ChainID) -> anyhow::Result { - let pk = sk.public_key(); - let addr = Address::new_secp256k1(&pk.serialize())?; - Ok(Self { + /// Create a factor from a secret key and its corresponding address, which could be a delegated one. + pub fn new(sk: SecretKey, addr: Address, sequence: u64, chain_id: ChainID) -> Self { + Self { sk, addr, sequence, chain_id, - }) + } + } + + /// Treat the secret key as an f1 type account. + pub fn new_secp256k1(sk: SecretKey, sequence: u64, chain_id: ChainID) -> Self { + let pk = sk.public_key(); + let addr = Address::new_secp256k1(&pk.serialize()).expect("public key is 65 bytes"); + Self::new(sk, addr, sequence, chain_id) } /// Convenience method to read the secret key from a file, expected to be in Base64 format. diff --git a/fendermint/vm/interpreter/src/fvm/broadcast.rs b/fendermint/vm/interpreter/src/fvm/broadcast.rs index 5b106ccc..bab5f8b3 100644 --- a/fendermint/vm/interpreter/src/fvm/broadcast.rs +++ b/fendermint/vm/interpreter/src/fvm/broadcast.rs @@ -37,14 +37,12 @@ where { pub fn new( client: C, + addr: Address, secret_key: SecretKey, gas_fee_cap: TokenAmount, gas_premium: TokenAmount, ) -> Self { let client = FendermintClient::new(client); - // TODO: We could use f410 addresses to send the transaction, but the `MessageFactory` assumes f1. - let addr = Address::new_secp256k1(&secret_key.public_key().serialize()) - .expect("public key is 65 bytes"); Self { client, secret_key, @@ -65,8 +63,7 @@ where .await .context("failed to get broadcaster sequence")?; - let factory = MessageFactory::new(self.secret_key.clone(), sequence, chain_id) - .context("failed to create MessageFactory")?; + let factory = MessageFactory::new(self.secret_key.clone(), self.addr, sequence, chain_id); // Using the bound client as a one-shot transaction sender. let mut client = self.client.clone().bind(factory);