Misc. agent cleanup and dead code deletion (#470)
* Fix path to agent::Agent in comment. Otherwise `cargo doc` emits a rustdoc::broken_intra_doc_links warning. * Use automatic link in comment for rustdoc. Otherwise 'cargo doc' emits a warning. * Delete checkpointer agent. It is not used in current deployment. * Delete kms-cli. Not used in current deployment (no references). * Include "--all" in precommit cargo fmt -- --check invocation. * Delete unused and hidden src/bin programs. These appear to have been introduced over a year ago and appear to be dead code now. Unhide them from vscode workspace too -- since they were hidden, automated refactoring tools wouldn't find them until compiler failed. * Update github CODEOWNERS to current Abacus team. Drop optional authors field from Cargo.tomls. * Drop processordb from .gitignore. Processor crate has been deleted. * Delete balance-exporter crate. It is not used / no references. * Hoist abacus-cli into its own top-level crate. It is the only tool now, no reason to hide it inside of a tools directory. * Delete old relaese.sh, which only relates to Celo Optics. * Cleanup top-level rust README.md. - Architecture paragraphs outdated, delete. - Point to run-locally.sh instead of documentation. * Explain a bit more about crate deps in README. * Dockerfile fix after tools directory restructuring. We had previously hoisted abacus-cli out since it was the only remaining tool. * Fix path to agent::Agent in comment. Otherwise `cargo doc` emits a rustdoc::broken_intra_doc_links warning. * Use automatic link in coment for rustdoc. Otherwise 'cargo doc' emits a warning. * Delete checkpointer agent. It is not used in current deployment. * Delete kms-cli. Not used in current deployment (no references). * Include "--all" in precommit cargo fmt -- --check invocation. * Delete unused and hidden src/bin programs. These appear to have been introduced over a year ago and appear to be dead code now. Unhide them from vscode workspace too -- since they were hidden, automated refactoring tools wouldn't find them until compiler failed. * Update github CODEOWNERS to current Abacus team. Drop optional authors field from Cargo.tomls. * Drop processordb from .gitignore. Processor crate has been deleted. * Delete balance-exporter crate. It is not used / no references. * Hoist abacus-cli into its own top-level crate. It is the only tool now, no reason to hide it inside of a tools directory. * Delete old relaese.sh, which only relates to Celo Optics. * Cleanup top-level rust README.md. - Architecture paragraphs outdated, delete. - Point to run-locally.sh instead of documentation. * Explain a bit more about crate deps in README. * Dockerfile fix after tools directory restructuring. We had previously hoisted abacus-cli out since it was the only remaining tool.pull/498/head
parent
87ee40ec3d
commit
6b3822f664
@ -1,2 +1 @@ |
||||
* @prestwich @anna-carroll @erinhales |
||||
rust/ @prestwich @emberian @ltchang |
||||
* @asaj @mattiecnvr @nambrot @tkporter @webbhorn @yorhodes @zmanian |
||||
|
@ -1,5 +1,4 @@ |
||||
target |
||||
processordb |
||||
validatordb |
||||
relayerdb |
||||
kathydb |
@ -1,6 +1,5 @@ |
||||
{ |
||||
"files.exclude": { |
||||
"target": true, |
||||
"**/bin": true, |
||||
}, |
||||
} |
||||
|
@ -1,42 +0,0 @@ |
||||
use color_eyre::Result; |
||||
|
||||
use abacus_base::{Agent, Settings}; |
||||
|
||||
/// An example main function for any agent that implemented Default
|
||||
async fn _example_main<OA>(settings: Settings) -> Result<()> |
||||
where |
||||
OA: Agent<Settings = Settings> + Sized + 'static, |
||||
{ |
||||
// Instantiate an agent
|
||||
let oa = OA::from_settings(settings).await?; |
||||
oa.as_ref() |
||||
.settings |
||||
.tracing |
||||
.start_tracing(oa.metrics().span_duration())?; |
||||
|
||||
// Run the agent
|
||||
oa.run_all(vec![]).await? |
||||
} |
||||
|
||||
/// Read settings from the config file and set up reporting and logging based
|
||||
/// on the settings
|
||||
#[allow(dead_code)] |
||||
fn setup() -> Result<Settings> { |
||||
color_eyre::install()?; |
||||
|
||||
let settings = Settings::new()?; |
||||
|
||||
Ok(settings) |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
fn main() -> Result<()> { |
||||
let _settings = setup()?; |
||||
// tokio::runtime::Builder::new_current_thread()
|
||||
// .enable_all()
|
||||
// .build()
|
||||
// .unwrap()
|
||||
// .block_on(_example_main(settings))?;
|
||||
|
||||
Ok(()) |
||||
} |
@ -1,10 +0,0 @@ |
||||
#[cfg(feature = "output")] |
||||
use abacus_core::test_output::output_functions::*; |
||||
|
||||
fn main() { |
||||
#[cfg(feature = "output")] |
||||
{ |
||||
output_signed_checkpoints(); |
||||
output_message_and_leaf(); |
||||
} |
||||
} |
@ -1,9 +0,0 @@ |
||||
#[cfg(feature = "output")] |
||||
use abacus_core::test_output::output_functions::*; |
||||
|
||||
fn main() { |
||||
#[cfg(feature = "output")] |
||||
{ |
||||
output_merkle_proof(); |
||||
} |
||||
} |
@ -1,10 +0,0 @@ |
||||
#[cfg(feature = "output")] |
||||
use abacus_core::test_output::output_functions::*; |
||||
|
||||
fn main() { |
||||
#[cfg(feature = "output")] |
||||
{ |
||||
output_domain_hashes(); |
||||
output_destination_and_nonces(); |
||||
} |
||||
} |
@ -1,34 +0,0 @@ |
||||
[package] |
||||
name = "checkpointer" |
||||
version = "0.1.0" |
||||
authors = ["Abacus Team"] |
||||
edition = "2021" |
||||
|
||||
[dependencies] |
||||
tokio = { version = "1", features = ["rt", "macros"] } |
||||
config = "0.10" |
||||
serde = "1.0" |
||||
serde_json = { version = "1.0", default-features = false } |
||||
ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } |
||||
thiserror = { version = "1.0.22", default-features = false } |
||||
async-trait = { version = "0.1.42", default-features = false } |
||||
futures-util = "0.3.12" |
||||
eyre = "0.6" |
||||
color-eyre = { version = "0.6", optional = true } |
||||
tracing = "0.1" |
||||
tracing-futures = "0.2" |
||||
tracing-subscriber = "0.3" |
||||
rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb" } |
||||
|
||||
abacus-core = { path = "../../abacus-core" } |
||||
abacus-base = { path = "../../abacus-base" } |
||||
abacus-ethereum = { path = "../../chains/abacus-ethereum" } |
||||
|
||||
[dev-dependencies] |
||||
tokio-test = "0.4" |
||||
abacus-test = { path = "../../abacus-test" } |
||||
prometheus = "0.12" |
||||
|
||||
[features] |
||||
default = ["color-eyre"] |
||||
oneline-errors = ["abacus-base/oneline-eyre"] |
@ -1,74 +0,0 @@ |
||||
use async_trait::async_trait; |
||||
use eyre::Result; |
||||
use tokio::task::JoinHandle; |
||||
use tracing::instrument::Instrumented; |
||||
|
||||
use abacus_base::{AbacusAgentCore, Agent}; |
||||
|
||||
use crate::{settings::CheckpointerSettings as Settings, submit::CheckpointSubmitter}; |
||||
|
||||
/// A checkpointer agent
|
||||
#[derive(Debug)] |
||||
pub struct Checkpointer { |
||||
/// The polling interval (in seconds)
|
||||
polling_interval: u64, |
||||
/// The minimum period between created checkpoints (in seconds)
|
||||
creation_latency: u64, |
||||
pub(crate) core: AbacusAgentCore, |
||||
} |
||||
|
||||
impl AsRef<AbacusAgentCore> for Checkpointer { |
||||
fn as_ref(&self) -> &AbacusAgentCore { |
||||
&self.core |
||||
} |
||||
} |
||||
|
||||
impl Checkpointer { |
||||
/// Instantiate a new checkpointer
|
||||
pub fn new(polling_interval: u64, creation_latency: u64, core: AbacusAgentCore) -> Self { |
||||
Self { |
||||
polling_interval, |
||||
creation_latency, |
||||
core, |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl Agent for Checkpointer { |
||||
const AGENT_NAME: &'static str = "checkpointer"; |
||||
|
||||
type Settings = Settings; |
||||
|
||||
async fn from_settings(settings: Self::Settings) -> Result<Self> |
||||
where |
||||
Self: Sized, |
||||
{ |
||||
let polling_interval = settings |
||||
.pollinginterval |
||||
.parse() |
||||
.expect("invalid pollinginterval uint"); |
||||
let creation_latency = settings |
||||
.creationlatency |
||||
.parse() |
||||
.expect("invalid creationlatency uint"); |
||||
let core = settings |
||||
.as_ref() |
||||
.try_into_abacus_core(Self::AGENT_NAME) |
||||
.await?; |
||||
Ok(Self::new(polling_interval, creation_latency, core)) |
||||
} |
||||
} |
||||
|
||||
impl Checkpointer { |
||||
pub fn run(&self) -> Instrumented<JoinHandle<Result<()>>> { |
||||
let outbox = self.outbox(); |
||||
|
||||
let submit = CheckpointSubmitter::new(outbox, self.polling_interval, self.creation_latency); |
||||
|
||||
self.run_all(vec![submit.spawn()]) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod test {} |
@ -1,44 +0,0 @@ |
||||
//! The checkpointer observes the Outbox contract and calls checkpoint.
|
||||
|
||||
#![forbid(unsafe_code)] |
||||
#![warn(missing_docs)] |
||||
#![warn(unused_extern_crates)] |
||||
|
||||
use eyre::Result; |
||||
|
||||
use abacus_base::Agent; |
||||
|
||||
use crate::{checkpointer::Checkpointer, settings::CheckpointerSettings as Settings}; |
||||
|
||||
mod checkpointer; |
||||
mod settings; |
||||
mod submit; |
||||
|
||||
async fn _main() -> Result<()> { |
||||
#[cfg(feature = "oneline-errors")] |
||||
abacus_base::oneline_eyre::install()?; |
||||
#[cfg(not(feature = "oneline-errors"))] |
||||
color_eyre::install()?; |
||||
|
||||
let settings = Settings::new()?; |
||||
|
||||
let agent = Checkpointer::from_settings(settings).await?; |
||||
|
||||
agent |
||||
.as_ref() |
||||
.settings |
||||
.tracing |
||||
.start_tracing(agent.metrics().span_duration())?; |
||||
let _ = agent.metrics().run_http_server(); |
||||
|
||||
agent.run().await??; |
||||
Ok(()) |
||||
} |
||||
|
||||
fn main() -> Result<()> { |
||||
tokio::runtime::Builder::new_current_thread() |
||||
.enable_all() |
||||
.build() |
||||
.unwrap() |
||||
.block_on(_main()) |
||||
} |
@ -1,10 +0,0 @@ |
||||
//! Configuration
|
||||
|
||||
use abacus_base::decl_settings; |
||||
|
||||
decl_settings!(Checkpointer { |
||||
/// The polling interval (in seconds)
|
||||
pollinginterval: String, |
||||
/// The minimum period between submitted checkpoints (in seconds)
|
||||
creationlatency: String, |
||||
}); |
@ -1,65 +0,0 @@ |
||||
use std::{sync::Arc, time::Duration}; |
||||
|
||||
use eyre::Result; |
||||
use tokio::{task::JoinHandle, time::sleep}; |
||||
use tracing::{debug, info, info_span, instrument::Instrumented, Instrument}; |
||||
|
||||
use abacus_base::CachingOutbox; |
||||
use abacus_core::{AbacusCommon, Checkpoint, Outbox}; |
||||
|
||||
pub(crate) struct CheckpointSubmitter { |
||||
outbox: Arc<CachingOutbox>, |
||||
/// The polling interval
|
||||
polling_interval: Duration, |
||||
/// The minimum period between submitted checkpoints
|
||||
creation_latency: Duration, |
||||
} |
||||
|
||||
impl CheckpointSubmitter { |
||||
pub(crate) fn new( |
||||
outbox: Arc<CachingOutbox>, |
||||
polling_interval: u64, |
||||
creation_latency: u64, |
||||
) -> Self { |
||||
Self { |
||||
outbox, |
||||
polling_interval: Duration::from_secs(polling_interval), |
||||
creation_latency: Duration::from_secs(creation_latency), |
||||
} |
||||
} |
||||
|
||||
pub(crate) fn spawn(self) -> Instrumented<JoinHandle<Result<()>>> { |
||||
let span = info_span!("CheckpointSubmitter"); |
||||
|
||||
tokio::spawn(async move { |
||||
loop { |
||||
sleep(self.polling_interval).await; |
||||
|
||||
// Check the latest checkpointed index
|
||||
let Checkpoint { |
||||
index: latest_checkpoint_index, |
||||
.. |
||||
} = self.outbox.latest_checkpoint(None).await?; |
||||
// Get the current count of the tree
|
||||
let count = self.outbox.count().await?; |
||||
|
||||
info!( |
||||
latest_checkpoint_index=?latest_checkpoint_index, |
||||
count=?count, |
||||
"Got latest checkpoint and count" |
||||
); |
||||
// If there are any new messages, the count will be greater than
|
||||
// the latest checkpoint index + 1. Don't checkpoint for count < 2
|
||||
// since checkpoints with index 0 are disallowed
|
||||
if count > latest_checkpoint_index + 1 && count > 1 { |
||||
debug!("Creating checkpoint"); |
||||
self.outbox.create_checkpoint().await?; |
||||
// Sleep to ensure that another checkpoint isn't made until
|
||||
// creation_latency has passed
|
||||
sleep(self.creation_latency).await; |
||||
} |
||||
} |
||||
}) |
||||
.instrument(span) |
||||
} |
||||
} |
@ -1 +0,0 @@ |
||||
docker push gcr.io/clabs-optics/optics-agent:$1 |
@ -1,23 +0,0 @@ |
||||
[package] |
||||
name = "balance-exporter" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
description = "Polls chains for optics contract wallet balances and reports them in OpenMetrics format" |
||||
authors = ["ember arlynx <ember.arlynx@clabs.co>"] |
||||
license = "Apache-2.0" |
||||
|
||||
[dependencies] |
||||
tokio = "1" |
||||
futures = "0.3" |
||||
|
||||
metrics = "0.18" |
||||
serde_json = "1.0" |
||||
serde = "1.0" |
||||
color-eyre = "0.6" |
||||
clap = { version = "3.1", features = ["cargo", "derive"] } |
||||
human-panic = "1.0" |
||||
|
||||
abacus-base = { path = "../../abacus-base" } |
||||
|
||||
# SMELL: reaching into the implementation details. abstract eventually. |
||||
abacus-ethereum = { path = "../../chains/abacus-ethereum" } |
@ -1,161 +0,0 @@ |
||||
use std::{path::PathBuf, time::Duration}; |
||||
|
||||
use abacus_base::ChainSetup; |
||||
use clap::Arg; |
||||
use color_eyre::eyre::anyhow; |
||||
use futures::StreamExt; |
||||
use human_panic::setup_panic; |
||||
use tokio::time::Instant; |
||||
|
||||
#[derive(serde::Deserialize, Debug)] |
||||
struct Contract(String); |
||||
|
||||
#[derive(serde::Deserialize, Debug)] |
||||
struct Input { |
||||
contracts: Vec<ChainSetup<Contract>>, |
||||
} |
||||
|
||||
struct Sample { |
||||
balances: Vec<color_eyre::Result<String>>, |
||||
} |
||||
|
||||
async fn poll_once(input: &Input, timeout: Duration) -> Sample { |
||||
// does this open a new ws connection for each query? probably.
|
||||
let (send, mut recv) = tokio::sync::mpsc::unbounded_channel(); |
||||
|
||||
// moves `send` into the subtask executor. by the time it is complete,
|
||||
// `recv` will complete to exhaustion, due to `send` clones being dropped
|
||||
futures::stream::iter( |
||||
input |
||||
.contracts |
||||
.iter() |
||||
.enumerate() |
||||
.zip(std::iter::repeat(send)), |
||||
) |
||||
.for_each_concurrent(None, |((ix, _cs), send)| async move { |
||||
let sub_result = |
||||
tokio::time::timeout(timeout, std::future::ready(Err(anyhow!("WIP")))).await; |
||||
send.send((ix, sub_result)) |
||||
.expect("channel closed before we're done?"); |
||||
}) |
||||
.await; |
||||
|
||||
let mut balances: Vec<Option<color_eyre::Result<String>>> = |
||||
input.contracts.iter().map(|_| None).collect(); |
||||
|
||||
while let Some((ix, sub_result)) = recv.recv().await { |
||||
match sub_result { |
||||
Ok(Ok(v)) => balances[ix] = Some(Ok(v)), |
||||
Ok(Err(e)) => balances[ix] = Some(Err(e)), |
||||
Err(_) => balances[ix] = Some(Err(anyhow!("timeout expired"))), |
||||
} |
||||
} |
||||
|
||||
assert!(balances.iter().all(Option::is_some)); |
||||
|
||||
Sample { |
||||
balances: balances.into_iter().map(Option::unwrap).collect(), |
||||
} |
||||
} |
||||
|
||||
#[tokio::main] |
||||
async fn main() -> color_eyre::Result<()> { |
||||
setup_panic!(); |
||||
color_eyre::install()?; |
||||
|
||||
let args = clap::command!() |
||||
.arg( |
||||
Arg::new("polling-interval") |
||||
.validator(|s| { |
||||
str::parse::<u64>(s).map_err(|_| anyhow!("polling interval must be u64!")) |
||||
}) |
||||
.help("Minimum number of seconds to wait between poll attempts") |
||||
.default_value("120"), |
||||
) |
||||
.arg( |
||||
Arg::new("stdin") |
||||
.help("Read configuration JSON from stdin") |
||||
.required_unless_present("file"), |
||||
) |
||||
.arg( |
||||
Arg::new("file") |
||||
.takes_value(true) |
||||
.help("Path to configuration JSON file"), |
||||
) |
||||
.get_matches(); |
||||
|
||||
eprintln!("Hello, world!"); |
||||
|
||||
eprintln!("You asked me to do this: {:?}", args); |
||||
|
||||
eprintln!("Loading up the input..."); |
||||
|
||||
let setup: Input = if !args.is_present("stdin") { |
||||
serde_json::from_reader(std::fs::File::open(PathBuf::from( |
||||
args.value_of_os("file").expect("malformed --file"), |
||||
))?)? |
||||
} else { |
||||
serde_json::from_reader(std::io::stdin())? |
||||
}; |
||||
|
||||
let interval = Duration::from_secs( |
||||
args.value_of_t("polling-interval") |
||||
.expect("malformed --polling-interval"), |
||||
); |
||||
|
||||
println!("Going to start exporting these:"); |
||||
setup.contracts.iter().for_each(|s| println!("\t {:?}", s)); |
||||
|
||||
loop { |
||||
let start = Instant::now(); |
||||
let results = poll_once(&setup, interval).await; |
||||
|
||||
for (ix, res) in results.balances.into_iter().enumerate() { |
||||
let ChainSetup { |
||||
name: network, |
||||
addresses, |
||||
.. |
||||
} = &setup.contracts[ix]; |
||||
match res { |
||||
Ok(s) => { |
||||
// TODO: export metric
|
||||
println!("{} {} = {}", network, addresses.0, s); |
||||
} |
||||
Err(e) => { |
||||
eprintln!("Error while querying {:?}: {}", setup.contracts[ix], e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
tokio::time::sleep_until(start + interval).await; |
||||
} |
||||
} |
||||
|
||||
impl Sample { |
||||
#[allow(dead_code)] |
||||
fn record(_m: impl metrics::Recorder) {} |
||||
} |
||||
|
||||
#[tokio::test] |
||||
#[should_panic] |
||||
async fn mainnet_works() { |
||||
// query ethereum instance of AbacusConnectionManager and asserts the balance is nonzero.
|
||||
let sample = poll_once( |
||||
&Input { |
||||
contracts: vec![ChainSetup { |
||||
name: "ethereum".into(), |
||||
domain: "6648936".into(), |
||||
// i would love for this to just be ChainConf::ethereum()
|
||||
chain: abacus_base::chains::ChainConf::Ethereum(abacus_ethereum::Connection::Ws { |
||||
url: "wss://main-light.eth.linkpool.io/ws".into(), |
||||
}), |
||||
addresses: Contract("0xcEc158A719d11005Bd9339865965bed938BEafA3".into()), |
||||
disabled: None, |
||||
}], |
||||
}, |
||||
Duration::from_secs(120), |
||||
) |
||||
.await; |
||||
let only_balance = sample.balances[0].as_ref(); |
||||
assert!(only_balance.expect("failed to query chain!") != "0"); |
||||
} |
@ -1,18 +0,0 @@ |
||||
[package] |
||||
name = "kms-cli" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
||||
[dependencies] |
||||
clap = "3.0.0-beta.5" |
||||
color-eyre = "0.6" |
||||
ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } |
||||
ethers-signers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features = ["aws"] } |
||||
hex = "0.4.3" |
||||
once_cell = "1.8.0" |
||||
rusoto_core = "0.47.0" |
||||
rusoto_kms = "0.47.0" |
||||
tokio = "1" |
||||
serde_json = "1.0" |
@ -1,218 +0,0 @@ |
||||
use std::convert::TryFrom; |
||||
|
||||
use color_eyre::Result; |
||||
|
||||
use ethers::{ |
||||
core::types::{transaction::eip2718::TypedTransaction, Address, TransactionRequest, U256}, |
||||
providers::{Http, Middleware, Provider}, |
||||
signers::{AwsSigner, Signer}, |
||||
}; |
||||
use once_cell::sync::OnceCell; |
||||
use rusoto_core::{credential::EnvironmentProvider, HttpClient}; |
||||
use rusoto_kms::KmsClient; |
||||
|
||||
use clap::Parser; |
||||
|
||||
static KMS_CLIENT: OnceCell<KmsClient> = OnceCell::new(); |
||||
|
||||
fn init_kms(region: String) { |
||||
let client = |
||||
rusoto_core::Client::new_with(EnvironmentProvider::default(), HttpClient::new().unwrap()); |
||||
if KMS_CLIENT |
||||
.set(KmsClient::new_with_client( |
||||
client, |
||||
region.parse().expect("invalid region"), |
||||
)) |
||||
.is_err() |
||||
{ |
||||
panic!("couldn't set cell") |
||||
} |
||||
} |
||||
|
||||
#[derive(Parser)] |
||||
pub struct Tx { |
||||
// TX
|
||||
/// The TX value (in wei)
|
||||
#[clap(short, long)] |
||||
value: Option<String>, |
||||
/// The TX nonce (pulled from RPC if omitted)
|
||||
#[clap(long)] |
||||
nonce: Option<U256>, |
||||
/// The TX gas price (pulled from RPC if omitted)
|
||||
#[clap(long)] |
||||
gas_price: Option<U256>, |
||||
/// The TX gas limit (estimated from RPC if omitted)
|
||||
#[clap(long)] |
||||
gas: Option<U256>, |
||||
/// The TX data body (omit for simple sends)
|
||||
#[clap(short, long)] |
||||
data: Option<String>, |
||||
/// The recipient/contract address
|
||||
#[clap(short, long)] |
||||
to: Address, |
||||
/// The chain_id. see https://chainlist.org
|
||||
#[clap(short, long)] |
||||
chain_id: Option<u64>, |
||||
|
||||
// RPC
|
||||
/// RPC connection details
|
||||
#[clap(long)] |
||||
rpc: String, |
||||
} |
||||
|
||||
#[derive(Parser)] |
||||
pub struct Info {} |
||||
|
||||
#[derive(Parser)] |
||||
/// Subcommands
|
||||
#[allow(clippy::large_enum_variant)] |
||||
pub enum SubCommands { |
||||
/// Send a tx signed by the KMS key
|
||||
Transaction(Tx), |
||||
/// Print the key info (region, id, address)
|
||||
Info(Info), |
||||
} |
||||
|
||||
#[derive(Parser)] |
||||
#[clap(version = "0.1", author = "James Prestwich")] |
||||
pub struct Opts { |
||||
#[clap(subcommand)] |
||||
sub: SubCommands, |
||||
|
||||
// AWS
|
||||
/// AWS Key ID
|
||||
#[clap(short, long)] |
||||
key_id: String, |
||||
/// AWS Region string
|
||||
#[clap(long)] |
||||
region: String, |
||||
|
||||
// Behavior
|
||||
/// Print the tx req and signature instead of broadcasting
|
||||
#[clap(short, long)] |
||||
print_only: bool, |
||||
} |
||||
|
||||
macro_rules! apply_if { |
||||
($tx_req:ident, $method:ident, $prop:expr) => {{ |
||||
if let Some(prop) = $prop { |
||||
$tx_req.$method(prop) |
||||
} else { |
||||
$tx_req |
||||
} |
||||
}}; |
||||
|
||||
($tx_req:ident, $opts:ident.$prop:ident) => {{ |
||||
let prop = $opts.$prop; |
||||
apply_if!($tx_req, $prop, prop) |
||||
}}; |
||||
} |
||||
|
||||
fn prep_tx_request(opts: &Tx) -> TransactionRequest { |
||||
let tx_req = TransactionRequest::default().to(opts.to); |
||||
|
||||
// These swallow parse errors
|
||||
let tx_req = apply_if!( |
||||
tx_req, |
||||
data, |
||||
opts.data.clone().and_then(|data| hex::decode(&data).ok()) |
||||
); |
||||
let tx_req = apply_if!( |
||||
tx_req, |
||||
value, |
||||
opts.value |
||||
.clone() |
||||
.and_then(|value| U256::from_dec_str(&value).ok()) |
||||
); |
||||
|
||||
let tx_req = apply_if!(tx_req, opts.nonce); |
||||
let tx_req = apply_if!(tx_req, opts.gas); |
||||
|
||||
let data = opts |
||||
.data |
||||
.clone() |
||||
.and_then(|s| hex::decode(s).ok()) |
||||
.unwrap_or_default(); |
||||
|
||||
let tx_req = tx_req.data(data); |
||||
|
||||
apply_if!(tx_req, opts.gas_price) |
||||
} |
||||
|
||||
async fn _send_tx(signer: &AwsSigner<'_>, opts: &Opts) -> Result<()> { |
||||
let tx: &Tx = match opts.sub { |
||||
SubCommands::Transaction(ref tx) => tx, |
||||
SubCommands::Info(_) => unreachable!(), |
||||
}; |
||||
|
||||
let provider = Provider::<Http>::try_from(tx.rpc.clone())?; |
||||
|
||||
let tx_req = prep_tx_request(tx); |
||||
|
||||
let mut typed_tx: TypedTransaction = tx_req.clone().into(); |
||||
typed_tx.set_from(signer.address()); |
||||
typed_tx.set_nonce( |
||||
provider |
||||
.get_transaction_count(signer.address(), None) |
||||
.await?, |
||||
); |
||||
|
||||
// TODO: remove this these ethers is fixed
|
||||
typed_tx.set_gas(21000); |
||||
typed_tx.set_gas_price(20_000_000_000u64); // 20 gwei
|
||||
typed_tx.set_chain_id(signer.chain_id()); |
||||
|
||||
let sig = signer.sign_transaction(&typed_tx).await?; |
||||
|
||||
let rlp = typed_tx.rlp_signed(&sig); |
||||
println!( |
||||
"Tx request details:\n{}", |
||||
serde_json::to_string_pretty(&typed_tx)? |
||||
); |
||||
|
||||
println!("\nSigned Tx:\n 0x{}", hex::encode(&rlp)); |
||||
if !opts.print_only { |
||||
let res = provider.send_raw_transaction(rlp).await?; |
||||
println!("Broadcast tx with hash {:?}", *res); |
||||
println!("Awaiting confirmation. Ctrl+c to exit"); |
||||
dbg!(res.await?); |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
async fn _print_info(signer: &AwsSigner<'_>, opts: &Opts) -> Result<()> { |
||||
println!("Key ID: {}", opts.key_id); |
||||
println!("Region: {}", opts.region); |
||||
println!("Address: {}", signer.address()); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
async fn _main() -> Result<()> { |
||||
let opts: Opts = Opts::parse(); |
||||
init_kms(opts.region.to_owned()); |
||||
let chain_id = match opts.sub { |
||||
SubCommands::Transaction(ref tx) => tx.chain_id.unwrap_or(1), |
||||
SubCommands::Info(_) => 1, |
||||
}; |
||||
|
||||
let signer = AwsSigner::new(KMS_CLIENT.get().unwrap(), opts.key_id.clone(), 0) |
||||
.await? |
||||
.with_chain_id(chain_id); |
||||
|
||||
match opts.sub { |
||||
SubCommands::Transaction(_) => _send_tx(&signer, &opts).await, |
||||
SubCommands::Info(_) => _print_info(&signer, &opts).await, |
||||
} |
||||
} |
||||
|
||||
fn main() -> Result<()> { |
||||
color_eyre::install()?; |
||||
|
||||
tokio::runtime::Builder::new_current_thread() |
||||
.enable_all() |
||||
.build() |
||||
.unwrap() |
||||
.block_on(_main()) |
||||
} |
Loading…
Reference in new issue