Skip to content

Commit

Permalink
feat: load env privkey in config (#2)
Browse files Browse the repository at this point in the history
* feat: load env privkey in config

* fix: key as arg

* feat: LocalOrAws

* fix: impl TxSigner
  • Loading branch information
prestwich authored May 1, 2024
1 parent ff2c2ca commit 27a0069
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 50 deletions.
1 change: 1 addition & 0 deletions crates/builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ serde_json = "1.0"
thiserror = "1.0.58"
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] }
tracing-subscriber = "0.3.18"
async-trait = "0.1.80"
39 changes: 0 additions & 39 deletions crates/builder/src/config.rs

This file was deleted.

139 changes: 139 additions & 0 deletions crates/builder/src/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use alloy_consensus::SignableTransaction;
use alloy_primitives::{Address, ChainId, B256};
use alloy_signer::Signature;
use alloy_signer_aws::{AwsSigner, AwsSignerError};
use alloy_signer_wallet::LocalWallet;
use aws_config::BehaviorVersion;
use std::{env, num};

/// Abstraction over local signer or
#[derive(Debug, Clone)]
pub enum LocalOrAws {
Local(LocalWallet),
Aws(AwsSigner),
}

impl LocalOrAws {
/// Load a privkey or AWS signer from environment variables.
pub async fn load(key: &str, chain_id: Option<u64>) -> Result<Self, ConfigError> {
if let Ok(wallet) = load_wallet(key) {
Ok(LocalOrAws::Local(wallet))
} else {
let signer = load_aws_signer(key, chain_id).await?;
Ok(LocalOrAws::Aws(signer))
}
}
}

#[async_trait::async_trait]
impl alloy_network::TxSigner<Signature> for LocalOrAws {
fn address(&self) -> Address {
match self {
LocalOrAws::Local(signer) => signer.address(),
LocalOrAws::Aws(signer) => signer.address(),
}
}

async fn sign_transaction(
&self,
tx: &mut dyn SignableTransaction<Signature>,
) -> alloy_signer::Result<Signature> {
match self {
LocalOrAws::Local(signer) => signer.sign_transaction(tx).await,
LocalOrAws::Aws(signer) => signer.sign_transaction(tx).await,
}
}
}

#[async_trait::async_trait]
impl alloy_signer::Signer<Signature> for LocalOrAws {
/// Signs the given hash.
async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result<Signature> {
match self {
LocalOrAws::Local(signer) => signer.sign_hash(hash).await,
LocalOrAws::Aws(signer) => signer.sign_hash(hash).await,
}
}

/// Returns the signer's Ethereum Address.
fn address(&self) -> Address {
match self {
LocalOrAws::Local(signer) => signer.address(),
LocalOrAws::Aws(signer) => signer.address(),
}
}

/// Returns the signer's chain ID.
fn chain_id(&self) -> Option<ChainId> {
match self {
LocalOrAws::Local(signer) => signer.chain_id(),
LocalOrAws::Aws(signer) => signer.chain_id(),
}
}

/// Sets the signer's chain ID.
fn set_chain_id(&mut self, chain_id: Option<ChainId>) {
match self {
LocalOrAws::Local(signer) => signer.set_chain_id(chain_id),
LocalOrAws::Aws(signer) => signer.set_chain_id(chain_id),
}
}
}

#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
/// Error loading from environment variable
#[error("missing environment variable: {0}")]
Var(#[from] env::VarError),
/// Error parsing environment variable
#[error("failed to parse environment variable: {0}")]
Parse(#[from] num::ParseIntError),
/// Error during [`AwsSigner`] instantiation
#[error("failed to connect AWS signer: {0}")]
AwsSigner(#[from] AwsSignerError),
/// Error parsing hex
#[error("failed to parse hex: {0}")]
Hex(#[from] hex::FromHexError),
/// Error loading the private key
#[error("failed to load private key: {0}")]
Wallet(#[from] alloy_signer_wallet::WalletError),
}

/// Load the private key from environment variables.
pub fn load_privkey(key: &str) -> Result<String, ConfigError> {
env::var(key).map_err(Into::into)
}

/// Load the wallet from environment variables.
///
/// # Panics
///
/// Panics if the env var contents is not a valid secp256k1 private key.
pub fn load_wallet(key: &str) -> Result<LocalWallet, ConfigError> {
let key = load_privkey(key)?;
let bytes = hex::decode(key.strip_prefix("0x").unwrap_or(&key))?;
Ok(LocalWallet::from_slice(&bytes).unwrap())
}

/// Load the AWS signer from environment variables./s
pub async fn load_aws_signer(key: &str, chain_id: Option<u64>) -> Result<AwsSigner, ConfigError> {
let config = aws_config::load_defaults(BehaviorVersion::latest()).await;
let client = aws_sdk_kms::Client::new(&config);

let key_id = load_key_id(key)?;

AwsSigner::new(client, key_id, chain_id)
.await
.map_err(Into::into)
}

pub fn load_key_id(key: &str) -> Result<String, ConfigError> {
env::var(key).map_err(Into::into)
}

/// Load the server port from environment variables.
pub fn load_port() -> Result<u16, ConfigError> {
let port = env::var("ZENITH_PORT")?;
let port = port.parse::<u16>()?;
Ok(port)
}
32 changes: 21 additions & 11 deletions crates/builder/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(dead_code)]

mod config;
use config::load_aws_signer;
mod env;
use env::LocalOrAws;

mod bindings;
use bindings::ZenithContract;
Expand All @@ -28,13 +28,14 @@ pub struct ChainConfig {
pub confirmation_buffer: u64,
/// address of the Zenith contract
pub zenith: Address,
/// URL for Quincey server to sign blocks
/// URL for Quincey server to sign blocks. This prop is disregarded if a
/// local_sequencer_signer is configured via the "SEQUENCER_KEY" env var.
pub quincey_url: Cow<'static, str>,
/// URL for RPC node
pub rpc_url: Cow<'static, str>,

/// Wallet for signing blocks locally.
pub local_sequencer_signer: Option<alloy_signer_wallet::LocalWallet>,
pub local_sequencer_signer: Option<LocalOrAws>,

/// Whether to use calldata or blob for transactions
pub use_calldata: bool,
Expand All @@ -55,23 +56,32 @@ const HOLESKY: ChainConfig = ChainConfig {
async fn main() -> eyre::Result<()> {
tracing_subscriber::fmt::try_init().unwrap();

let span = tracing::span!(tracing::Level::INFO, "zenith-builder");
let span = tracing::info_span!("zenith-builder");

// finish app config by loading key from env
let config = ChainConfig {
local_sequencer_signer: LocalOrAws::load("SEQUENCER_KEY", None).await.ok(),
..HOLESKY
};

// Load builder key from env
let wallet = LocalOrAws::load("BUILDER_KEY_ID", Some(config.host_chain_id)).await?;
let builder_rewards = wallet.address();

let wallet = load_aws_signer("BUILDER_KEY_ID", Some(HOLESKY.host_chain_id)).await?;
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.signer(EthereumSigner::from(wallet.clone()))
.on_builtin(&HOLESKY.rpc_url)
.signer(EthereumSigner::from(wallet))
.on_builtin(&config.rpc_url)
.await?;
let zenith = ZenithContract::new(HOLESKY.zenith, provider.clone());
let zenith = ZenithContract::new(config.zenith, provider.clone());

let build = tasks::block::BlockBuilder { wait_secs: 5 };
let submit = tasks::submit::SubmitTask {
provider,
zenith,
client: reqwest::Client::new(),
config: HOLESKY,
builder_rewards: wallet.address(),
config,
builder_rewards,
gas_limit: 30_000_000,
};

Expand Down
2 changes: 2 additions & 0 deletions crates/builder/src/tasks/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ where
async fn handle_inbound(&self, in_progress: &InProgressBlock) -> eyre::Result<()> {
let sig_request = self.construct_sig_request(in_progress).await?;

// If configured with a local signer, we use it. Otherwise, we ask
// quincey (politely)
let signature = if let Some(signer) = &self.config.local_sequencer_signer {
signer.sign_hash(&sig_request.signing_hash()).await?
} else {
Expand Down

0 comments on commit 27a0069

Please sign in to comment.