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 |
* @asaj @mattiecnvr @nambrot @tkporter @webbhorn @yorhodes @zmanian |
||||||
rust/ @prestwich @emberian @ltchang |
|
||||||
|
@ -1,5 +1,4 @@ |
|||||||
target |
target |
||||||
processordb |
|
||||||
validatordb |
validatordb |
||||||
relayerdb |
relayerdb |
||||||
kathydb |
kathydb |
@ -1,6 +1,5 @@ |
|||||||
{ |
{ |
||||||
"files.exclude": { |
"files.exclude": { |
||||||
"target": true, |
"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