diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..2d2c3db58 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +target +.git +Dockerfile +.dockerignore diff --git a/bolt-boost/Cargo.lock b/bolt-boost/Cargo.lock index 26a114203..4ea87e87c 100644 --- a/bolt-boost/Cargo.lock +++ b/bolt-boost/Cargo.lock @@ -1060,6 +1060,7 @@ name = "bolt-boost" version = "0.1.0" dependencies = [ "alloy", + "alloy-rlp", "async-trait", "axum", "axum-extra", diff --git a/bolt-boost/Cargo.toml b/bolt-boost/Cargo.toml index fd02c0d07..36987f0e2 100644 --- a/bolt-boost/Cargo.toml +++ b/bolt-boost/Cargo.toml @@ -38,6 +38,7 @@ alloy = { version = "0.3.6", features = [ "rpc-types-beacon", "rpc-types-engine", ] } +alloy-rlp = "0.3.8" # commit-boost cb-common = { git = "https://github.com/commit-boost/commit-boost-client", tag = "v0.3.0" } diff --git a/bolt-boost/src/proofs.rs b/bolt-boost/src/proofs.rs index 432b4d527..d28284232 100644 --- a/bolt-boost/src/proofs.rs +++ b/bolt-boost/src/proofs.rs @@ -41,8 +41,8 @@ pub fn verify_multiproofs( // Get all the leaves from the saved constraints let mut leaves = Vec::with_capacity(proofs.total_leaves()); - // NOTE: Get the leaves from the constraints cache by matching the saved hashes. We need the leaves - // in order to verify the multiproof. + // NOTE: Get the leaves from the constraints cache by matching the saved hashes. We need the + // leaves in order to verify the multiproof. for hash in &proofs.transaction_hashes { let mut found = false; for constraint in constraints { diff --git a/bolt-boost/src/server.rs b/bolt-boost/src/server.rs index a0022b551..332e24efd 100644 --- a/bolt-boost/src/server.rs +++ b/bolt-boost/src/server.rs @@ -543,13 +543,9 @@ where Ok(response) => { let url = response.url().clone(); let status = response.status(); - let body = response.text().await.ok(); if status != StatusCode::OK { - error!( - %status, - %url, - "Failed to POST to relay: {body:?}" - ) + let body = response.text().await.ok(); + error!(%status, %url, "Failed to POST to relay: {body:?}"); } else { debug!(%url, "Successfully sent POST request to relay"); success = true; diff --git a/bolt-boost/src/types.rs b/bolt-boost/src/types.rs index 4bd62ccd3..8a5b355d6 100644 --- a/bolt-boost/src/types.rs +++ b/bolt-boost/src/types.rs @@ -1,15 +1,17 @@ use alloy::{ - consensus::TxEnvelope, - eips::eip2718::{Decodable2718, Eip2718Error}, + consensus::{TxEip4844Variant, TxEnvelope}, + eips::eip2718::{Decodable2718, Eip2718Error, Eip2718Result}, primitives::{Bytes, TxHash, B256}, rpc::types::beacon::{BlsPublicKey, BlsSignature}, signers::k256::sha2::{Digest, Sha256}, }; +use alloy_rlp::{BufMut, Encodable}; use axum::http::HeaderMap; use reqwest::Url; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use std::ops::Deref; +use tracing::error; use cb_common::{ constants::COMMIT_BOOST_DOMAIN, @@ -44,8 +46,15 @@ impl SignedConstraints { #[allow(unused)] pub fn verify_signature(&self, chain: Chain, pubkey: &BlsPublicKey) -> bool { let domain = compute_domain(chain, COMMIT_BOOST_DOMAIN); - let signing_root = compute_signing_root(self.message.digest(), domain); + let digest = match self.message.digest() { + Ok(digest) => digest, + Err(e) => { + error!(err = ?e, "Failed to compute digest"); + return false; + } + }; + let signing_root = compute_signing_root(digest, domain); verify_bls_signature(pubkey, &signing_root, &self.signature).is_ok() } } @@ -60,19 +69,18 @@ pub struct ConstraintsMessage { impl ConstraintsMessage { /// Returns the digest of this message. - pub fn digest(&self) -> [u8; 32] { + pub fn digest(&self) -> Eip2718Result<[u8; 32]> { let mut hasher = Sha256::new(); hasher.update(self.pubkey); hasher.update(self.slot.to_le_bytes()); hasher.update((self.top as u8).to_le_bytes()); for bytes in &self.transactions { - let tx = TxEnvelope::decode_2718(&mut bytes.as_ref()).expect("valid transaction"); - + let tx = TxEnvelope::decode_2718(&mut bytes.as_ref())?; hasher.update(tx.tx_hash()); } - hasher.finalize().into() + Ok(hasher.finalize().into()) } } @@ -92,14 +100,10 @@ impl TryFrom for ConstraintsWithProofData { .transactions .iter() .map(|tx| { - let tx_hash = *TxEnvelope::decode_2718(&mut tx.as_ref())?.tx_hash(); - - let tx_root = - tree_hash::TreeHash::tree_hash_root(&Transaction::< - ::MaxBytesPerTransaction, - >::from(tx.to_vec())); + let envelope = TxEnvelope::decode_2718(&mut tx.as_ref())?; + let tx_hash_tree_root = calculate_tx_hash_tree_root(&envelope, tx)?; - Ok((tx_hash, tx_root)) + Ok((*envelope.tx_hash(), tx_hash_tree_root)) }) .collect::, Eip2718Error>>()?; @@ -107,6 +111,42 @@ impl TryFrom for ConstraintsWithProofData { } } +/// Calculate the SSZ hash tree root of a transaction, starting from its enveloped form. +/// For type 3 transactions, the hash tree root of the inner transaction is taken (without blobs). +fn calculate_tx_hash_tree_root( + envelope: &TxEnvelope, + raw_tx: &Bytes, +) -> Result { + match envelope { + // For type 3 txs, take the hash tree root of the inner tx (EIP-4844) + TxEnvelope::Eip4844(tx) => match tx.tx() { + TxEip4844Variant::TxEip4844(tx) => { + let mut out = Vec::new(); + out.put_u8(0x03); + tx.encode(&mut out); + + Ok(tree_hash::TreeHash::tree_hash_root(&Transaction::< + ::MaxBytesPerTransaction, + >::from(out))) + } + TxEip4844Variant::TxEip4844WithSidecar(tx) => { + use alloy_rlp::Encodable; + let mut out = Vec::new(); + out.put_u8(0x03); + tx.tx.encode(&mut out); + + Ok(tree_hash::TreeHash::tree_hash_root(&Transaction::< + ::MaxBytesPerTransaction, + >::from(out))) + } + }, + // For other transaction types, take the hash tree root of the whole tx + _ => Ok(tree_hash::TreeHash::tree_hash_root(&Transaction::< + ::MaxBytesPerTransaction, + >::from(raw_tx.to_vec()))), + } +} + #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct SignedDelegation { pub message: DelegationMessage, @@ -115,6 +155,7 @@ pub struct SignedDelegation { #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct DelegationMessage { + action: u8, pub validator_pubkey: BlsPublicKey, pub delegatee_pubkey: BlsPublicKey, } @@ -127,6 +168,7 @@ pub struct SignedRevocation { #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct RevocationMessage { + action: u8, pub validator_pubkey: BlsPublicKey, pub delegatee_pubkey: BlsPublicKey, } @@ -173,3 +215,53 @@ pub struct RequestConfig { pub timeout_ms: u64, pub headers: HeaderMap, } + +#[cfg(test)] +mod tests { + use alloy::{hex::FromHex, primitives::Bytes}; + + use super::ConstraintsWithProofData; + use crate::types::SignedConstraints; + + #[test] + fn decode_constraints_test() { + let raw = r#"{ + "message": { + "pubkey": "0xa695ad325dfc7e1191fbc9f186f58eff42a634029731b18380ff89bf42c464a42cb8ca55b200f051f57f1e1893c68759", + "slot": 32, + "top": true, + "transactions": [ + "0x02f86c870c72dd9d5e883e4d0183408f2382520894d2e2adf7177b7a8afddbc12d1634cf23ea1a71020180c001a08556dcfea479b34675db3fe08e29486fe719c2b22f6b0c1741ecbbdce4575cc6a01cd48009ccafd6b9f1290bbe2ceea268f94101d1d322c787018423ebcbc87ab4" + ] + }, + "signature": "0xb8d50ee0d4b269db3d4658c1dac784d273a4160d769e16dce723a9684c390afe5865348416b3bf0f1a4f47098bec9024135d0d95f08bed18eb577a3d8a67f5dc78b13cc62515e280786a73fb267d35dfb7ab46a25ac29bf5bc2fa5b07b3e07a6" + }"#; + + let mut c = serde_json::from_str::(raw).unwrap(); + let pd = ConstraintsWithProofData::try_from(c.message.clone()).unwrap().proof_data[0]; + + assert_eq!( + pd.0.to_string(), + "0x385b9f1ba5dbbe419dcbbbbf0840b76b941f3c216d383ec9deb9b1a323ee0cea".to_string() + ); + + assert_eq!( + pd.1.to_string(), + "0x02e383af0c34516ef38e13391d917d5b61b6f69e17d5234f77cb8cc3a1ae932e".to_string() + ); + + c.message.transactions[0] = Bytes::from_hex("0x03f9029c01830299f184b2d05e008507aef40a00832dc6c09468d30f47f19c07bccef4ac7fae2dc12fca3e0dc980b90204ef16e845000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000633b68f5d8d3a86593ebb815b4663bcbe0302e31382e302d64657600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004109de8da2a97e37f2e6dc9f7d50a408f9344d7aa1a925ae53daf7fbef43491a571960d76c0cb926190a9da10df7209fb1ba93cd98b1565a3a2368749d505f90c81c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0843b9aca00e1a00141e3a338e30c49ed0501e315bcc45e4edefebed43ab1368a1505461d9cf64901a01e8511e06b17683d89eb57b9869b96b8b611f969f7f56cbc0adc2df7c88a2a07a00910deacf91bba0d74e368d285d311dc5884e7cfe219d85aea5741b2b6e3a2fe").unwrap(); + + let pd = ConstraintsWithProofData::try_from(c.message).unwrap().proof_data[0]; + + assert_eq!( + pd.0.to_string(), + "0x15bd881daa1408b33f67fa4bdeb8acfb0a2289d9b4c6f81eef9bb2bb2e52e780".to_string() + ); + + assert_eq!( + pd.1.to_string(), + "0x0a637924b9f9b28a413b01cb543bcd688850b8964f77576fc71219448f7b4ab9".to_string() + ); + } +} diff --git a/bolt-kurtosis-client/src/main.rs b/bolt-kurtosis-client/src/main.rs index 5c2c925fd..dae9b84b6 100644 --- a/bolt-kurtosis-client/src/main.rs +++ b/bolt-kurtosis-client/src/main.rs @@ -84,7 +84,7 @@ async fn main() -> Result<()> { ); info!("Transaction hash: {}", tx_hash); - info!("body: {}", serde_json::to_string(&request)?); + info!("body: {}", trim_zeroes(serde_json::to_string(&request)?)); let client = reqwest::Client::new(); let response = client @@ -95,8 +95,13 @@ async fn main() -> Result<()> { .send() .await?; - info!("Response: {:?}", response.text().await?); + let res = trim_zeroes(response.text().await?); + info!("Response: {:?}", res); } Ok(()) } + +fn trim_zeroes(s: impl Into) -> String { + s.into().replace(&"0".repeat(32), ".").replace(&".".repeat(4), "") +} diff --git a/bolt-kurtosis-client/src/utils.rs b/bolt-kurtosis-client/src/utils.rs index 89edf80bc..1d9f2a7a1 100644 --- a/bolt-kurtosis-client/src/utils.rs +++ b/bolt-kurtosis-client/src/utils.rs @@ -37,7 +37,7 @@ pub fn generate_random_blob_tx() -> TransactionRequest { .with_value(U256::from(100)) .with_max_fee_per_blob_gas(100u128) .max_fee_per_gas(NOICE_GAS_PRICE) - .max_priority_fee_per_gas(NOICE_GAS_PRICE / 10) + .max_priority_fee_per_gas(NOICE_GAS_PRICE) .with_gas_limit(42_000u128) .with_blob_sidecar(sidecar) .with_input(random_bytes) diff --git a/bolt-sidecar/Cargo.lock b/bolt-sidecar/Cargo.lock index d1cd4db45..e3b816772 100644 --- a/bolt-sidecar/Cargo.lock +++ b/bolt-sidecar/Cargo.lock @@ -2,6 +2,26 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "account_utils" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "directory", + "eth2_keystore 0.1.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", + "eth2_wallet", + "filesystem", + "rand 0.8.5", + "regex", + "rpassword", + "serde", + "serde_yaml 0.9.33", + "slog", + "types", + "validator_dir", + "zeroize", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -17,6 +37,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + [[package]] name = "aes" version = "0.7.5" @@ -24,12 +65,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", - "ctr", + "ctr 0.8.0", "opaque-debug", ] +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" +dependencies = [ + "aead", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.7.0", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.11" @@ -851,7 +917,7 @@ dependencies = [ "alloy-transport 0.2.1", "futures", "http 1.1.0", - "rustls", + "rustls 0.23.12", "serde_json", "tokio", "tokio-tungstenite 0.23.1", @@ -954,6 +1020,21 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "archery" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a8da9bc4c4053ee067669762bcaeea6e241841295a2b6c948312dad6ef4cc02" +dependencies = [ + "static_assertions", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -1090,6 +1171,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "asn1_der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" + [[package]] name = "async-channel" version = "1.9.0" @@ -1310,11 +1397,17 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base16ct" version = "0.1.1" @@ -1432,17 +1525,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" +dependencies = [ + "funty 1.1.0", + "radium 0.6.2", + "tap", + "wyz 0.2.0", +] + [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "funty", - "radium", + "funty 2.0.0", + "radium 0.7.0", "serde", "tap", - "wyz", + "wyz 0.5.1", ] [[package]] @@ -1451,6 +1556,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ + "block-padding", "generic-array", ] @@ -1463,6 +1569,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bls" version = "0.2.0" @@ -1470,7 +1582,25 @@ source = "git+https://github.com/sigp/lighthouse?rev=9e12c21f268c80a3f002ae0ca27 dependencies = [ "arbitrary", "blst", - "ethereum-types", + "ethereum-types 0.14.1", + "ethereum_hashing 0.6.0", + "ethereum_serde_utils", + "ethereum_ssz", + "hex", + "rand 0.8.5", + "serde", + "tree_hash 0.6.0", + "zeroize", +] + +[[package]] +name = "bls" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "arbitrary", + "blst", + "ethereum-types 0.14.1", "ethereum_hashing 0.6.0", "ethereum_serde_utils", "ethereum_ssz", @@ -1510,18 +1640,21 @@ dependencies = [ name = "bolt-sidecar" version = "0.2.1-alpha" dependencies = [ + "account_utils", "alloy", "alloy-node-bindings", "async-trait", "axum", "axum-extra", "beacon-api-client", + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", "blst", "bytes", "cb-common", "clap", "commit-boost", "dotenvy", + "eth2_keystore 0.1.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", "ethereum-consensus", "ethereum_ssz", "ethereum_ssz_derive", @@ -1531,7 +1664,7 @@ dependencies = [ "lru", "metrics", "metrics-exporter-prometheus", - "parking_lot", + "parking_lot 0.12.3", "partial-mpt", "rand 0.8.5", "regex", @@ -1542,13 +1675,14 @@ dependencies = [ "serde", "serde_json", "ssz_rs 0.9.0 (git+https://github.com/ralexstokes/ssz-rs)", + "tempfile", "thiserror", "tokio", "tower-http", "tracing", "tracing-subscriber", "tree_hash 0.5.2", - "tree_hash_derive", + "tree_hash_derive 0.5.2", "warp", ] @@ -1558,6 +1692,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -1591,6 +1734,27 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "c-kzg" version = "1.0.3" @@ -1606,6 +1770,20 @@ dependencies = [ "serde", ] +[[package]] +name = "cached_tree_hash" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "ethereum-types 0.14.1", + "ethereum_hashing 0.6.0", + "ethereum_ssz", + "ethereum_ssz_derive", + "smallvec", + "ssz_types 0.6.0", + "tree_hash 0.6.0", +] + [[package]] name = "cb-cli" version = "0.1.0" @@ -1632,8 +1810,8 @@ dependencies = [ "bimap", "blst", "derive_more 1.0.0", - "eth2_keystore", - "ethereum-types", + "eth2_keystore 0.1.0 (git+https://github.com/sigp/lighthouse?rev=9e12c21f268c80a3f002ae0ca27477f9f512eb6f)", + "ethereum-types 0.14.1", "ethereum_serde_utils", "ethereum_ssz", "ethereum_ssz_derive", @@ -1643,7 +1821,7 @@ dependencies = [ "reqwest 0.12.7", "serde", "serde_json", - "ssz_types", + "ssz_types 0.5.4", "thiserror", "tokio", "toml 0.8.19", @@ -1651,7 +1829,7 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "tree_hash 0.5.2", - "tree_hash_derive", + "tree_hash_derive 0.5.2", "url", ] @@ -1714,7 +1892,7 @@ dependencies = [ "tokio", "tracing", "tree_hash 0.5.2", - "tree_hash_derive", + "tree_hash_derive 0.5.2", "uuid 1.10.0", ] @@ -1772,6 +1950,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -1803,6 +1991,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim 0.11.1", + "terminal_size", ] [[package]] @@ -1823,6 +2012,23 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +[[package]] +name = "clap_utils" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "clap", + "dirs", + "eth2_network_config", + "ethereum-types 0.14.1", + "ethereum_ssz", + "hex", + "serde", + "serde_json", + "serde_yaml 0.9.33", + "types", +] + [[package]] name = "cmake" version = "0.1.51" @@ -1890,7 +2096,24 @@ dependencies = [ "eyre", "tokio", "tree_hash 0.5.2", - "tree_hash_derive", + "tree_hash_derive 0.5.2", +] + +[[package]] +name = "compare_fields" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "itertools 0.10.5", +] + +[[package]] +name = "compare_fields_derive" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "quote", + "syn 1.0.109", ] [[package]] @@ -1921,6 +2144,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "convert_case" version = "0.4.0" @@ -1985,6 +2214,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -2059,6 +2297,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "crypto-mac" version = "0.11.0" @@ -2069,13 +2317,49 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" +dependencies = [ + "cipher 0.3.0", +] + [[package]] name = "ctr" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ - "cipher", + "cipher 0.3.0", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", ] [[package]] @@ -2148,6 +2432,12 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "dary_heap" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7762d17f1241643615821a8455a0b2c3e803784b058693d990b11f2dce25a0ca" + [[package]] name = "dashmap" version = "5.5.3" @@ -2158,7 +2448,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.10", ] [[package]] @@ -2167,6 +2457,51 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "data-encoding-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +dependencies = [ + "data-encoding", + "syn 1.0.109", +] + +[[package]] +name = "delay_map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4355c25cbf99edcb6b4a0e906f6bdc6956eda149e84455bea49696429b2f8e8" +dependencies = [ + "futures", + "tokio-util", +] + +[[package]] +name = "deposit_contract" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "ethabi 16.0.0", + "ethereum_ssz", + "hex", + "reqwest 0.11.27", + "serde_json", + "sha2 0.9.9", + "tree_hash 0.6.0", + "types", +] + [[package]] name = "der" version = "0.6.1" @@ -2307,28 +2642,110 @@ dependencies = [ ] [[package]] -name = "docker-compose-types" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9213a368b9c0767c81ef9ced0f712cfd99d27d7de2a22a60e7ac9b1342c8a395" +name = "directory" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" dependencies = [ - "derive_builder", - "indexmap 2.4.0", - "serde", - "serde_yaml 0.9.33", + "clap", + "clap_utils", + "eth2_network_config", ] [[package]] -name = "doctest-file" -version = "1.0.0" +name = "dirs" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] [[package]] -name = "dotenvy" -version = "0.15.7" +name = "dirs-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "discv5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac33cb3f99889a57e56a8c6ccb77aaf0cfc7787602b7af09783f736d77314e1" +dependencies = [ + "aes 0.7.5", + "aes-gcm", + "arrayvec", + "delay_map", + "enr 0.10.0", + "fnv", + "futures", + "hashlink", + "hex", + "hkdf", + "lazy_static", + "libp2p", + "lru", + "more-asserts", + "parking_lot 0.11.2", + "rand 0.8.5", + "rlp", + "smallvec", + "socket2 0.4.10", + "tokio", + "tracing", + "uint", + "zeroize", +] + +[[package]] +name = "docker-compose-types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9213a368b9c0767c81ef9ced0f712cfd99d27d7de2a22a60e7ac9b1342c8a395" +dependencies = [ + "derive_builder", + "indexmap 2.4.0", + "serde", + "serde_yaml 0.9.33", +] + +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dunce" @@ -2368,6 +2785,31 @@ dependencies = [ "spki 0.7.3", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.13.0" @@ -2429,7 +2871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26fa0a0be8915790626d5759eb51fe47435a8eac92c2f212bd2da9aa7f30ea56" dependencies = [ "base64 0.13.1", - "bs58", + "bs58 0.4.0", "bytes", "hex", "k256 0.11.6", @@ -2437,7 +2879,26 @@ dependencies = [ "rand 0.8.5", "rlp", "serde", - "sha3", + "sha3 0.10.8", + "zeroize", +] + +[[package]] +name = "enr" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4" +dependencies = [ + "base64 0.21.7", + "bytes", + "ed25519-dalek", + "hex", + "k256 0.13.3", + "log", + "rand 0.8.5", + "rlp", + "serde", + "sha3 0.10.8", "zeroize", ] @@ -2458,6 +2919,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "erased-serde" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +dependencies = [ + "serde", +] + [[package]] name = "errno" version = "0.3.9" @@ -2468,12 +2938,47 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "eth2_config" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "paste", + "types", +] + +[[package]] +name = "eth2_interop_keypairs" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", + "ethereum_hashing 0.6.0", + "hex", + "lazy_static", + "num-bigint", + "serde", + "serde_yaml 0.9.33", +] + [[package]] name = "eth2_key_derivation" version = "0.1.0" source = "git+https://github.com/sigp/lighthouse?rev=9e12c21f268c80a3f002ae0ca27477f9f512eb6f#9e12c21f268c80a3f002ae0ca27477f9f512eb6f" dependencies = [ - "bls", + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?rev=9e12c21f268c80a3f002ae0ca27477f9f512eb6f)", + "num-bigint-dig", + "ring 0.16.20", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "eth2_key_derivation" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", "num-bigint-dig", "ring 0.16.20", "sha2 0.9.9", @@ -2485,12 +2990,34 @@ name = "eth2_keystore" version = "0.1.0" source = "git+https://github.com/sigp/lighthouse?rev=9e12c21f268c80a3f002ae0ca27477f9f512eb6f#9e12c21f268c80a3f002ae0ca27477f9f512eb6f" dependencies = [ - "aes", - "bls", - "eth2_key_derivation", + "aes 0.7.5", + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?rev=9e12c21f268c80a3f002ae0ca27477f9f512eb6f)", + "eth2_key_derivation 0.1.0 (git+https://github.com/sigp/lighthouse?rev=9e12c21f268c80a3f002ae0ca27477f9f512eb6f)", + "hex", + "hmac 0.11.0", + "pbkdf2 0.8.0", + "rand 0.8.5", + "scrypt", + "serde", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "unicode-normalization", + "uuid 0.8.2", + "zeroize", +] + +[[package]] +name = "eth2_keystore" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "aes 0.7.5", + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", + "eth2_key_derivation 0.1.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", "hex", "hmac 0.11.0", - "pbkdf2", + "pbkdf2 0.8.0", "rand 0.8.5", "scrypt", "serde", @@ -2502,23 +3029,86 @@ dependencies = [ "zeroize", ] +[[package]] +name = "eth2_network_config" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "bytes", + "discv5", + "eth2_config", + "logging", + "pretty_reqwest_error", + "reqwest 0.11.27", + "sensitive_url", + "serde_yaml 0.9.33", + "sha2 0.9.9", + "slog", + "types", + "url", + "zip", +] + +[[package]] +name = "eth2_wallet" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "eth2_key_derivation 0.1.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", + "eth2_keystore 0.1.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", + "rand 0.8.5", + "serde", + "serde_json", + "serde_repr", + "tiny-bip39", + "uuid 0.8.2", +] + +[[package]] +name = "ethabi" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c98847055d934070b90e806e12d3936b787d0a115068981c1d8dfd5dfef5a5" +dependencies = [ + "ethereum-types 0.12.1", + "hex", + "serde", + "serde_json", + "sha3 0.9.1", + "thiserror", + "uint", +] + [[package]] name = "ethabi" version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" dependencies = [ - "ethereum-types", + "ethereum-types 0.14.1", "hex", "once_cell", "regex", "serde", "serde_json", - "sha3", + "sha3 0.10.8", "thiserror", "uint", ] +[[package]] +name = "ethbloom" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8" +dependencies = [ + "crunchy", + "fixed-hash 0.7.0", + "impl-rlp", + "impl-serde 0.3.2", + "tiny-keccak", +] + [[package]] name = "ethbloom" version = "0.13.0" @@ -2526,10 +3116,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", - "fixed-hash", - "impl-codec", + "fixed-hash 0.8.0", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "scale-info", "tiny-keccak", ] @@ -2540,13 +3130,13 @@ version = "0.1.1" source = "git+https://github.com/ralexstokes/ethereum-consensus?rev=cf3c404#cf3c404043230559660810bc0c9d6d5a8498d819" dependencies = [ "blst", - "bs58", + "bs58 0.4.0", "c-kzg", - "enr", + "enr 0.6.2", "hex", "integer-sqrt", - "multiaddr", - "multihash", + "multiaddr 0.14.0", + "multihash 0.16.3", "rand 0.8.5", "serde", "serde_json", @@ -2558,18 +3148,32 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "ethereum-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05136f7057fe789f06e6d41d07b34e6f70d8c86e5693b60f97aaa6553553bdaf" +dependencies = [ + "ethbloom 0.11.1", + "fixed-hash 0.7.0", + "impl-rlp", + "impl-serde 0.3.2", + "primitive-types 0.10.1", + "uint", +] + [[package]] name = "ethereum-types" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", + "ethbloom 0.13.0", + "fixed-hash 0.8.0", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", - "primitive-types", + "impl-serde 0.4.0", + "primitive-types 0.12.2", "scale-info", "uint", ] @@ -2604,7 +3208,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de4d5951468846963c24e8744c133d44f39dff2cd3a233f6be22b370d08a524f" dependencies = [ - "ethereum-types", + "ethereum-types 0.14.1", "hex", "serde", "serde_derive", @@ -2617,7 +3221,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d3627f83d8b87b432a5fad9934b4565260722a141a2c40f371f8080adec9425" dependencies = [ - "ethereum-types", + "ethereum-types 0.14.1", "itertools 0.10.5", "smallvec", ] @@ -2645,7 +3249,7 @@ dependencies = [ "chrono", "const-hex", "elliptic-curve 0.13.8", - "ethabi", + "ethabi 18.0.0", "generic-array", "k256 0.13.3", "num_enum", @@ -2677,6 +3281,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "1.9.0" @@ -2719,23 +3335,71 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "bitvec", + "bitvec 1.0.1", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version 0.4.1", +] + +[[package]] +name = "filesystem" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "winapi", + "windows-acl", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixed-hash" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ + "arbitrary", "byteorder", "rand 0.8.5", "rustc-hex", "static_assertions", ] +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2766,12 +3430,28 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "funty" version = "2.0.0" @@ -2818,6 +3498,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -2864,6 +3545,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.30" @@ -2923,6 +3610,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.1" @@ -3012,6 +3709,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "headers" version = "0.3.9" @@ -3078,6 +3784,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -3093,13 +3805,32 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac", + "crypto-mac 0.11.0", "digest 0.9.0", ] @@ -3112,6 +3843,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + [[package]] name = "home" version = "0.5.9" @@ -3226,7 +3968,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -3254,6 +3996,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.2" @@ -3265,11 +4021,11 @@ dependencies = [ "hyper 1.4.1", "hyper-util", "log", - "rustls", + "rustls 0.23.12", "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", ] @@ -3315,7 +4071,7 @@ dependencies = [ "http-body 1.0.1", "hyper 1.4.1", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower", "tower-service", @@ -3361,13 +4117,22 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443" +dependencies = [ + "parity-scale-codec 2.3.1", +] + [[package]] name = "impl-codec" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 3.6.12", ] [[package]] @@ -3379,6 +4144,15 @@ dependencies = [ "rlp", ] +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + [[package]] name = "impl-serde" version = "0.4.0" @@ -3433,6 +4207,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -3442,6 +4225,14 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "int_to_bytes" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "bytes", +] + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -3462,7 +4253,7 @@ dependencies = [ "libc", "recvmsg", "tokio", - "widestring", + "widestring 1.1.0", "windows-sys 0.52.0", ] @@ -3472,6 +4263,17 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -3570,11 +4372,11 @@ dependencies = [ "base64 0.22.1", "http-body 1.0.1", "hyper 1.4.1", - "hyper-rustls", + "hyper-rustls 0.27.2", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls", + "rustls 0.23.12", "rustls-platform-verifier", "serde", "serde_json", @@ -3657,6 +4459,23 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "kzg" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "arbitrary", + "c-kzg", + "derivative", + "ethereum_hashing 0.6.0", + "ethereum_serde_utils", + "ethereum_ssz", + "ethereum_ssz_derive", + "hex", + "serde", + "tree_hash 0.6.0", +] + [[package]] name = "kzg-rs" version = "0.1.0" @@ -3693,6 +4512,30 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libflate" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" +dependencies = [ + "adler32", + "core2", + "crc32fast", + "dary_heap", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" +dependencies = [ + "core2", + "hashbrown 0.14.5", + "rle-decode-fast", +] + [[package]] name = "libloading" version = "0.8.5" @@ -3700,7 +4543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3709,6 +4552,200 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libp2p" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681fb3f183edfbedd7a57d32ebe5dcdc0b9f94061185acf3c30249349cc6fc99" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom 0.2.15", + "instant", + "libp2p-allow-block-list", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "multiaddr 0.18.2", + "pin-project", + "rw-stream-sink", + "thiserror", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107b238b794cb83ab53b74ad5dcf7cca3200899b72fe662840cfb52f5b0a32e6" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cd50a78ccfada14de94cbacd3ce4b0138157f376870f13d3a8422cd075b4fd" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-core" +version = "0.41.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a8920cbd8540059a01950c1e5c96ea8d89eb50c51cd366fc18bdf540a6e48f" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr 0.18.2", + "multihash 0.19.1", + "multistream-select", + "once_cell", + "parking_lot 0.12.3", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "smallvec", + "thiserror", + "tracing", + "unsigned-varint 0.8.0", + "void", + "web-time", +] + +[[package]] +name = "libp2p-identity" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" +dependencies = [ + "asn1_der", + "bs58 0.5.1", + "ed25519-dalek", + "hkdf", + "libsecp256k1", + "multihash 0.19.1", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.8", + "thiserror", + "tracing", + "zeroize", +] + +[[package]] +name = "libp2p-swarm" +version = "0.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80cae6cb75f89dbca53862f9ebe0b9f463aa7b302762fcfaafb9e51dcc9b0f7e" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "lru", + "multistream-select", + "once_cell", + "rand 0.8.5", + "smallvec", + "tracing", + "void", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lighthouse_metrics" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "prometheus", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3731,12 +4768,43 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfile" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "fs2", +] + [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "logging" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "chrono", + "lazy_static", + "lighthouse_metrics", + "parking_lot 0.12.3", + "serde", + "serde_json", + "slog", + "slog-term", + "sloggers", + "take_mut", + "tokio", + "tracing", + "tracing-appender", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.12.4" @@ -3746,6 +4814,12 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "matchers" version = "0.1.0" @@ -3767,6 +4841,49 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merkle_proof" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "ethereum-types 0.14.1", + "ethereum_hashing 0.6.0", + "lazy_static", + "safe_arith", +] + +[[package]] +name = "metastruct" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00a5ba4a0f3453c31c397b214e1675d95b697c33763aa58add57ea833424384" +dependencies = [ + "metastruct_macro", +] + +[[package]] +name = "metastruct_macro" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a991d4536c933306e52f0e8ab303757185ec13a09d1f3e1cbde5a0d8410bf" +dependencies = [ + "darling 0.13.4", + "itertools 0.10.5", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + [[package]] name = "metrics" version = "0.23.0" @@ -3786,7 +4903,7 @@ dependencies = [ "base64 0.22.1", "http-body-util", "hyper 1.4.1", - "hyper-rustls", + "hyper-rustls 0.27.2", "hyper-util", "indexmap 2.4.0", "ipnet", @@ -3832,6 +4949,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "milhouse" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3826d3602a3674b07e080ce1982350e454ec253d73f156bd927ac1b652293f4d" +dependencies = [ + "arbitrary", + "derivative", + "ethereum-types 0.14.1", + "ethereum_hashing 0.6.0", + "ethereum_ssz", + "ethereum_ssz_derive", + "itertools 0.10.5", + "parking_lot 0.12.3", + "rayon", + "serde", + "smallvec", + "tree_hash 0.6.0", + "triomphe", + "typenum", + "vec_map", +] + [[package]] name = "mime" version = "0.3.17" @@ -3863,13 +5003,22 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", @@ -3902,6 +5051,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "more-asserts" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" + [[package]] name = "multer" version = "2.1.0" @@ -3927,17 +5082,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" dependencies = [ "arrayref", - "bs58", + "bs58 0.4.0", + "byteorder", + "data-encoding", + "multihash 0.16.3", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint 0.7.2", + "url", +] + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", "byteorder", "data-encoding", - "multihash", + "libp2p-identity", + "multibase", + "multihash 0.19.1", "percent-encoding", "serde", "static_assertions", - "unsigned-varint", + "unsigned-varint 0.8.0", "url", ] +[[package]] +name = "multibase" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] + [[package]] name = "multihash" version = "0.16.3" @@ -3948,7 +5133,17 @@ dependencies = [ "digest 0.10.7", "multihash-derive", "sha2 0.10.8", - "unsigned-varint", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "multihash" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" +dependencies = [ + "core2", + "unsigned-varint 0.7.2", ] [[package]] @@ -3965,6 +5160,20 @@ dependencies = [ "synstructure", ] +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint 0.7.2", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -4072,7 +5281,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -4140,7 +5349,7 @@ dependencies = [ "arrayvec", "auto_impl", "bytes", - "ethereum-types", + "ethereum-types 0.14.1", "open-fastrlp-derive", ] @@ -4186,7 +5395,16 @@ dependencies = [ name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "300.3.2+3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +dependencies = [ + "cc", +] [[package]] name = "openssl-sys" @@ -4196,6 +5414,7 @@ checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -4221,6 +5440,20 @@ dependencies = [ "group 0.13.0", ] +[[package]] +name = "parity-scale-codec" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" +dependencies = [ + "arrayvec", + "bitvec 0.20.4", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive 2.3.1", + "serde", +] + [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -4228,13 +5461,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec", - "bitvec", + "bitvec 1.0.1", "byte-slice-cast", "impl-trait-for-tuples", - "parity-scale-codec-derive", + "parity-scale-codec-derive 3.6.12", "serde", ] +[[package]] +name = "parity-scale-codec-derive" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "parity-scale-codec-derive" version = "3.6.12" @@ -4253,6 +5498,17 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -4260,7 +5516,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -4271,7 +5541,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] @@ -4292,6 +5562,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -4304,7 +5585,19 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ - "crypto-mac", + "crypto-mac 0.11.0", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", + "password-hash", + "sha2 0.10.8", ] [[package]] @@ -4402,6 +5695,18 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.7.0" @@ -4423,6 +5728,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_reqwest_error" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "reqwest 0.11.27", + "sensitive_url", +] + [[package]] name = "prettyplease" version = "0.2.20" @@ -4433,16 +5747,29 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "primitive-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" +dependencies = [ + "fixed-hash 0.7.0", + "impl-codec 0.5.1", + "impl-rlp", + "impl-serde 0.3.2", + "uint", +] + [[package]] name = "primitive-types" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ - "fixed-hash", - "impl-codec", + "fixed-hash 0.8.0", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "scale-info", "uint", ] @@ -4509,7 +5836,7 @@ dependencies = [ "fnv", "lazy_static", "memchr", - "parking_lot", + "parking_lot 0.12.3", "protobuf", "thiserror", ] @@ -4561,6 +5888,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + [[package]] name = "quote" version = "1.0.37" @@ -4570,6 +5906,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "radium" version = "0.7.0" @@ -4691,6 +6033,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.3" @@ -4700,6 +6051,17 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.6" @@ -4759,6 +6121,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.30", + "hyper-rustls 0.24.2", "hyper-tls 0.5.0", "ipnet", "js-sys", @@ -4768,6 +6131,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.12", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -4776,6 +6140,7 @@ dependencies = [ "system-configuration 0.5.1", "tokio", "tokio-native-tls", + "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", @@ -4783,6 +6148,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots 0.25.4", "winreg", ] @@ -4802,7 +6168,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.4.1", - "hyper-rustls", + "hyper-rustls 0.27.2", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -4901,7 +6267,7 @@ dependencies = [ "serde", "tempfile", "thiserror", - "zstd", + "zstd 0.13.2", ] [[package]] @@ -4979,7 +6345,7 @@ dependencies = [ "alloy-primitives", "auto_impl", "bitflags 2.6.0", - "bitvec", + "bitvec 1.0.1", "c-kzg", "cfg-if", "derive_more 0.99.18", @@ -5043,6 +6409,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + [[package]] name = "rlp" version = "0.5.2" @@ -5075,6 +6447,25 @@ dependencies = [ "byteorder", ] +[[package]] +name = "rpassword" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "rpds" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ef5140bcb576bfd6d56cd2de709a7d17851ac1f3805e67fe9d99e42a11821f" +dependencies = [ + "archery", +] + [[package]] name = "ruint" version = "1.12.3" @@ -5089,8 +6480,8 @@ dependencies = [ "fastrlp", "num-bigint", "num-traits", - "parity-scale-codec", - "primitive-types", + "parity-scale-codec 3.6.12", + "primitive-types 0.12.2", "proptest", "rand 0.8.5", "rlp", @@ -5106,6 +6497,20 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rusqlite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +dependencies = [ + "bitflags 1.3.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -5150,9 +6555,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -5161,6 +6566,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.12" @@ -5172,7 +6589,7 @@ dependencies = [ "once_cell", "ring 0.17.8", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.7", "subtle", "zeroize", ] @@ -5226,13 +6643,13 @@ dependencies = [ "jni", "log", "once_cell", - "rustls", + "rustls 0.23.12", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki", + "rustls-webpki 0.102.7", "security-framework", "security-framework-sys", - "webpki-roots", + "webpki-roots 0.26.3", "winapi", ] @@ -5242,6 +6659,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + [[package]] name = "rustls-webpki" version = "0.102.7" @@ -5272,19 +6699,35 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "safe_arith" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" + [[package]] name = "salsa20" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] @@ -5304,7 +6747,7 @@ checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "cfg-if", "derive_more 0.99.18", - "parity-scale-codec", + "parity-scale-codec 3.6.12", "scale-info-derive", ] @@ -5348,11 +6791,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879588d8f90906e73302547e20fffefdd240eb3e0e744e142321f5d49dea0518" dependencies = [ "hmac 0.11.0", - "pbkdf2", + "pbkdf2 0.8.0", "salsa20", "sha2 0.9.9", ] +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + [[package]] name = "sec1" version = "0.3.0" @@ -5454,6 +6907,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +[[package]] +name = "sensitive_url" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "serde", + "url", +] + [[package]] name = "serde" version = "1.0.209" @@ -5629,6 +7091,18 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + [[package]] name = "sha3" version = "0.10.8" @@ -5706,18 +7180,120 @@ dependencies = [ ] [[package]] -name = "sketches-ddsketch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" - -[[package]] -name = "slab" -version = "0.4.9" +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" +dependencies = [ + "erased-serde", +] + +[[package]] +name = "slog-async" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84" +dependencies = [ + "crossbeam-channel", + "slog", + "take_mut", + "thread_local", +] + +[[package]] +name = "slog-json" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1e53f61af1e3c8b852eef0a9dee29008f55d6dd63794f3f12cef786cf0f219" +dependencies = [ + "serde", + "serde_json", + "slog", + "time", +] + +[[package]] +name = "slog-kvfilter" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae939ed7d169eed9699f4f5cd440f046f5dc5dfc27c19e3cd311619594c175e0" +dependencies = [ + "regex", + "slog", +] + +[[package]] +name = "slog-scope" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" +dependencies = [ + "arc-swap", + "lazy_static", + "slog", +] + +[[package]] +name = "slog-stdlog" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" +dependencies = [ + "log", + "slog", + "slog-scope", +] + +[[package]] +name = "slog-term" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" +dependencies = [ + "is-terminal", + "slog", + "term", + "thread_local", + "time", +] + +[[package]] +name = "sloggers" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "75062c2738b82cd45ae633623caae3393f43eb00aada1dc2d3ebe88db6b0db9b" dependencies = [ - "autocfg", + "chrono", + "libc", + "libflate", + "once_cell", + "regex", + "serde", + "slog", + "slog-async", + "slog-json", + "slog-kvfilter", + "slog-scope", + "slog-stdlog", + "slog-term", + "trackable", + "winapi", + "windows-acl", ] [[package]] @@ -5729,6 +7305,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.7" @@ -5777,7 +7363,7 @@ version = "0.9.0" source = "git+https://github.com/ralexstokes/ssz-rs?rev=84ef2b71aa004f6767420badb42c902ad56b8b72#84ef2b71aa004f6767420badb42c902ad56b8b72" dependencies = [ "alloy-primitives", - "bitvec", + "bitvec 1.0.1", "serde", "sha2 0.9.9", "ssz_rs_derive 0.9.0 (git+https://github.com/ralexstokes/ssz-rs?rev=84ef2b71aa004f6767420badb42c902ad56b8b72)", @@ -5789,7 +7375,7 @@ version = "0.9.0" source = "git+https://github.com/ralexstokes/ssz-rs#84ef2b71aa004f6767420badb42c902ad56b8b72" dependencies = [ "alloy-primitives", - "bitvec", + "bitvec 1.0.1", "serde", "sha2 0.9.9", "ssz_rs_derive 0.9.0 (git+https://github.com/ralexstokes/ssz-rs)", @@ -5832,6 +7418,30 @@ dependencies = [ "typenum", ] +[[package]] +name = "ssz_types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625b20de2d4b3891e6972f4ce5061cb11bd52b3479270c4b177c134b571194a9" +dependencies = [ + "arbitrary", + "derivative", + "ethereum_serde_utils", + "ethereum_ssz", + "itertools 0.10.5", + "serde", + "serde_derive", + "smallvec", + "tree_hash 0.6.0", + "typenum", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -5878,6 +7488,29 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "superstruct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f4e1f478a7728f8855d7e620e9a152cf8932c6614f86564c886f9b8141f3201" +dependencies = [ + "darling 0.13.4", + "itertools 0.10.5", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "swap_or_not_shuffle" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "ethereum-types 0.14.1", + "ethereum_hashing 0.6.0", +] + [[package]] name = "syn" version = "1.0.109" @@ -5981,6 +7614,12 @@ dependencies = [ "libc", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + [[package]] name = "tap" version = "1.0.1" @@ -5989,9 +7628,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand 2.1.1", @@ -6000,6 +7639,36 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "test_random_derive" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "thiserror" version = "1.0.63" @@ -6090,6 +7759,25 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-bip39" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" +dependencies = [ + "anyhow", + "hmac 0.12.1", + "once_cell", + "pbkdf2 0.11.0", + "rand 0.8.5", + "rustc-hash 1.1.0", + "sha2 0.10.8", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -6124,10 +7812,10 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.52.0", ] @@ -6153,13 +7841,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls", + "rustls 0.23.12", "rustls-pki-types", "tokio", ] @@ -6196,12 +7894,12 @@ checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.23.12", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tungstenite 0.23.0", - "webpki-roots", + "webpki-roots 0.26.3", ] [[package]] @@ -6214,6 +7912,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project-lite", + "slab", "tokio", ] @@ -6402,13 +8101,32 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "trackable" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" +dependencies = [ + "trackable_derive", +] + +[[package]] +name = "trackable_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "tree_hash" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c998ac5fe2b07c025444bdd522e6258110b63861c6698eedc610c071980238d" dependencies = [ - "ethereum-types", + "ethereum-types 0.14.1", "ethereum_hashing 1.0.0-beta.2", "smallvec", ] @@ -6419,7 +8137,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "134d6b24a5b829f30b5ee7de05ba7384557f5f6b00e29409cdf2392f93201bfa" dependencies = [ - "ethereum-types", + "ethereum-types 0.14.1", "ethereum_hashing 0.6.0", "smallvec", ] @@ -6435,6 +8153,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tree_hash_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce7bccc538359a213436af7bc95804bdbf1c2a21d80e22953cbe9e096837ff1" +dependencies = [ + "darling 0.13.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "serde", + "stable_deref_trait", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6473,7 +8212,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "rustls", + "rustls 0.23.12", "rustls-pki-types", "sha1", "thiserror", @@ -6486,6 +8225,55 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "types" +version = "0.2.1" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "arbitrary", + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", + "cached_tree_hash", + "compare_fields", + "compare_fields_derive", + "derivative", + "eth2_interop_keypairs", + "ethereum-types 0.14.1", + "ethereum_hashing 0.6.0", + "ethereum_serde_utils", + "ethereum_ssz", + "ethereum_ssz_derive", + "hex", + "int_to_bytes", + "itertools 0.10.5", + "kzg", + "lazy_static", + "log", + "maplit", + "merkle_proof", + "metastruct", + "milhouse", + "parking_lot 0.12.3", + "rand 0.8.5", + "rand_xorshift", + "rayon", + "regex", + "rpds", + "rusqlite", + "safe_arith", + "serde", + "serde_json", + "serde_yaml 0.9.33", + "slog", + "smallvec", + "ssz_types 0.6.0", + "superstruct", + "swap_or_not_shuffle", + "tempfile", + "test_random_derive", + "tree_hash 0.6.0", + "tree_hash_derive 0.6.0", +] + [[package]] name = "ucd-trie" version = "0.1.6" @@ -6498,6 +8286,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ + "arbitrary", "byteorder", "crunchy", "hex", @@ -6552,6 +8341,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +[[package]] +name = "universal-hash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -6564,6 +8363,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + [[package]] name = "untrusted" version = "0.7.1" @@ -6621,6 +8426,24 @@ dependencies = [ "serde", ] +[[package]] +name = "validator_dir" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?rev=a87f19d#a87f19d801a57b1d6ff101750840294c210ff956" +dependencies = [ + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", + "deposit_contract", + "derivative", + "directory", + "eth2_keystore 0.1.0 (git+https://github.com/sigp/lighthouse?rev=a87f19d)", + "filesystem", + "hex", + "lockfile", + "rand 0.8.5", + "tree_hash 0.6.0", + "types", +] + [[package]] name = "valuable" version = "0.1.0" @@ -6633,12 +8456,24 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -6804,6 +8639,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "webpki-roots" version = "0.26.3" @@ -6825,6 +8676,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + [[package]] name = "widestring" version = "1.1.0" @@ -6862,6 +8719,18 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-acl" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177b1723986bcb4c606058e77f6e8614b51c7f9ad2face6f6fd63dd5c8b3cec3" +dependencies = [ + "field-offset", + "libc", + "widestring 0.4.3", + "winapi", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -7087,6 +8956,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "wyz" version = "0.5.1" @@ -7146,13 +9021,52 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes 0.8.4", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac 0.12.1", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + [[package]] name = "zstd" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ - "zstd-safe", + "zstd-safe 7.2.1", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", ] [[package]] diff --git a/bolt-sidecar/Cargo.toml b/bolt-sidecar/Cargo.toml index ea0162e0c..0aa4535c7 100644 --- a/bolt-sidecar/Cargo.toml +++ b/bolt-sidecar/Cargo.toml @@ -25,10 +25,10 @@ ethereum_ssz_derive = "0.5" # alloy alloy = { version = "0.2.0", features = [ - "full", - "provider-trace-api", - "rpc-types-beacon", - "rpc-types-engine", + "full", + "provider-trace-api", + "rpc-types-beacon", + "rpc-types-engine", ] } # reth @@ -40,6 +40,11 @@ reqwest = "0.12" ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus", rev = "cf3c404" } beacon-api-client = { git = "https://github.com/ralexstokes/ethereum-consensus", rev = "cf3c404" } +# lighthouse +lighthouse_account_utils = { package = "account_utils", git = "https://github.com/sigp/lighthouse", rev = "a87f19d" } +lighthouse_eth2_keystore = { package = "eth2_keystore", git = "https://github.com/sigp/lighthouse", rev = "a87f19d" } +lighthouse_bls = { package = "bls", git = "https://github.com/sigp/lighthouse", rev = "a87f19d" } + # types partial-mpt = { git = "https://github.com/chainbound/partial-mpt", branch = "feat/alloy" } serde = { version = "1.0.197", features = ["derive"] } @@ -65,15 +70,16 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "fmt"] } # telemetry metrics = "0.23" metrics-exporter-prometheus = { version = "0.15.3", features = [ - "http-listener", + "http-listener", ] } # commit-boost -commit-boost = { git = "https://github.com/Commit-Boost/commit-boost-client", rev = "45ce8f1"} +commit-boost = { git = "https://github.com/Commit-Boost/commit-boost-client", rev = "45ce8f1" } cb-common = { git = "https://github.com/Commit-Boost/commit-boost-client", rev = "45ce8f1" } [dev-dependencies] alloy-node-bindings = "0.2.0" +tempfile = "3.13.0" [[bin]] diff --git a/bolt-sidecar/Dockerfile b/bolt-sidecar/Dockerfile index 5b2075b99..99c0f839c 100644 --- a/bolt-sidecar/Dockerfile +++ b/bolt-sidecar/Dockerfile @@ -1,23 +1,57 @@ -FROM rust:1.81.0-slim-bullseye AS compiler +# Stage 1: Base compiler image with necessary dependencies +FROM rust:1.81.0-slim-bullseye AS base +# Install cargo-chef for dependency caching RUN cargo install cargo-chef +# Set the working directory to /app WORKDIR /app -FROM compiler AS planner -COPY . . +# Stage 2: Planner (generating the recipe) +FROM base AS planner + +# Copy only Cargo files to cache dependencies +COPY Cargo.toml Cargo.lock ./ + +# Prepare the recipe for caching dependencies (Cargo.toml/Cargo.lock) RUN cargo chef prepare --recipe-path recipe.json -FROM compiler AS builder +# Stage 3: Builder with necessary dependencies for OpenSSL +FROM base AS builder + +# Install required dependencies for building Rust projects (OpenSSL, pkg-config) +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + build-essential \ + perl \ + gcc \ + make + +# Copy the generated recipe from the planner stage COPY --from=planner /app/recipe.json recipe.json -RUN apt-get update && apt-get install pkg-config libssl-dev -y + +# Cache the dependencies using the cargo-chef recipe RUN cargo chef cook --release --recipe-path recipe.json + +# Copy the source code and build the project COPY . . RUN cargo build --release -FROM debian:bullseye-slim +# Stage 4: Final runtime image (lean image) +FROM debian:bullseye-slim AS runtime + +# Set the working directory for the final container WORKDIR /usr/local/bin -COPY --from=builder /app/target/release/bolt-sidecar / -RUN apt-get update && apt-get install -y libssl-dev ca-certificates && rm -rf /var/lib/apt/lists/* -ENTRYPOINT ["/bolt-sidecar"] +# Install necessary runtime dependencies (OpenSSL and CA certificates) +RUN apt-get update && apt-get install -y \ + libssl-dev \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Copy the compiled binary from the builder stage +COPY --from=builder /app/target/release/bolt-sidecar /usr/local/bin/bolt-sidecar + +# Define the entrypoint for the container +ENTRYPOINT ["/usr/local/bin/bolt-sidecar"] diff --git a/bolt-sidecar/bin/sidecar.rs b/bolt-sidecar/bin/sidecar.rs index 3082f3a6c..909d4e402 100644 --- a/bolt-sidecar/bin/sidecar.rs +++ b/bolt-sidecar/bin/sidecar.rs @@ -1,28 +1,36 @@ -use bolt_sidecar::{telemetry::init_telemetry_stack, Config, SidecarDriver}; +use bolt_sidecar::{telemetry::init_telemetry_stack, Opts, SidecarDriver}; +use clap::Parser; use eyre::{bail, Result}; use tracing::info; #[tokio::main] async fn main() -> Result<()> { - let config = match Config::parse_from_cli() { - Ok(config) => config, - Err(err) => bail!("Failed to parse CLI arguments: {:?}", err), - }; + let opts = Opts::parse(); - let metrics_port = if !config.disable_metrics { Some(config.metrics_port) } else { None }; + let metrics_port = + if !opts.telemetry.disable_metrics { Some(opts.telemetry.metrics_port) } else { None }; if let Err(err) = init_telemetry_stack(metrics_port) { bail!("Failed to initialize telemetry stack: {:?}", err) } - info!(chain = config.chain.name(), "Starting Bolt sidecar"); + info!(chain = opts.chain.name(), "Starting Bolt sidecar"); - if config.private_key.is_some() { - match SidecarDriver::with_local_signer(config).await { + if opts.signing.private_key.is_some() { + match SidecarDriver::with_local_signer(&opts).await { Ok(driver) => driver.run_forever().await, - Err(err) => bail!("Failed to initialize the sidecar driver: {:?}", err), + Err(err) => { + bail!("Failed to initialize the sidecar driver with local signer: {:?}", err) + } + } + } else if opts.signing.keystore_password.is_some() { + match SidecarDriver::with_keystore_signer(&opts).await { + Ok(driver) => driver.run_forever().await, + Err(err) => { + bail!("Failed to initialize the sidecar driver with keystore signer: {:?}", err) + } } } else { - match SidecarDriver::with_commit_boost_signer(config).await { + match SidecarDriver::with_commit_boost_signer(&opts).await { Ok(driver) => driver.run_forever().await, Err(err) => { bail!("Failed to initialize the sidecar driver with commit boost: {:?}", err) diff --git a/bolt-sidecar/keys/.gitignore b/bolt-sidecar/keys/.gitignore new file mode 100644 index 000000000..7c9d611b5 --- /dev/null +++ b/bolt-sidecar/keys/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!README.md diff --git a/bolt-sidecar/keys/README.md b/bolt-sidecar/keys/README.md new file mode 100644 index 000000000..1a539d703 --- /dev/null +++ b/bolt-sidecar/keys/README.md @@ -0,0 +1,7 @@ +### About + +This directory is intended to be used as the folder to place ERC-2335 keystores +files. + +It is assumed that each keystore file has a `.json` extension and is is placed +in a directory named after the public key it corresponds to. diff --git a/bolt-sidecar/src/builder/mod.rs b/bolt-sidecar/src/builder/mod.rs index 88f59b3d3..1ea89bb76 100644 --- a/bolt-sidecar/src/builder/mod.rs +++ b/bolt-sidecar/src/builder/mod.rs @@ -1,6 +1,5 @@ use alloy::primitives::U256; use beacon_api_client::mainnet::Client as BeaconClient; -use blst::min_pk::SecretKey; use ethereum_consensus::{ crypto::{KzgCommitment, PublicKey}, deneb::mainnet::ExecutionPayloadHeader, @@ -10,10 +9,11 @@ use payload_builder::FallbackPayloadBuilder; use signature::sign_builder_message; use crate::{ + common::BlsSecretKeyWrapper, primitives::{ BuilderBid, GetPayloadResponse, PayloadAndBid, PayloadAndBlobs, SignedBuilderBid, }, - ChainConfig, Config, + ChainConfig, Opts, }; /// Basic block template handler that can keep track of @@ -65,7 +65,7 @@ pub enum BuilderError { pub struct LocalBuilder { /// BLS credentials for the local builder. We use this to sign the /// payload bid submissions built by the sidecar. - secret_key: SecretKey, + secret_key: BlsSecretKeyWrapper, /// Chain configuration /// (necessary for signing messages with the correct domain) chain: ChainConfig, @@ -78,12 +78,12 @@ pub struct LocalBuilder { impl LocalBuilder { /// Create a new local builder with the given secret key. - pub fn new(config: &Config, beacon_api_client: BeaconClient, genesis_time: u64) -> Self { + pub fn new(opts: &Opts, beacon_api_client: BeaconClient, genesis_time: u64) -> Self { Self { payload_and_bid: None, - fallback_builder: FallbackPayloadBuilder::new(config, beacon_api_client, genesis_time), - secret_key: config.builder_private_key.clone(), - chain: config.chain, + fallback_builder: FallbackPayloadBuilder::new(opts, beacon_api_client, genesis_time), + secret_key: opts.builder_private_key.clone(), + chain: opts.chain, } } diff --git a/bolt-sidecar/src/builder/payload_builder.rs b/bolt-sidecar/src/builder/payload_builder.rs index 72dede40f..1dfe2e9ff 100644 --- a/bolt-sidecar/src/builder/payload_builder.rs +++ b/bolt-sidecar/src/builder/payload_builder.rs @@ -21,7 +21,7 @@ use super::{ compat::{to_alloy_execution_payload, to_reth_withdrawal}, BuilderError, }; -use crate::{BeaconClient, Config, RpcClient}; +use crate::{BeaconClient, Opts, RpcClient}; /// Extra-data payload field used for locally built blocks, decoded in UTF-8. /// @@ -54,7 +54,7 @@ pub struct FallbackPayloadBuilder { impl FallbackPayloadBuilder { /// Create a new fallback payload builder - pub fn new(config: &Config, beacon_api_client: BeaconClient, genesis_time: u64) -> Self { + pub fn new(config: &Opts, beacon_api_client: BeaconClient, genesis_time: u64) -> Self { let engine_hinter = EngineHinter { client: reqwest::Client::new(), jwt_hex: config.jwt_hex.to_string(), @@ -449,7 +449,7 @@ mod tests { let wallet = EthereumWallet::from(signer); let addy = Address::from_private_key(&sk); - let tx = default_test_transaction(addy, Some(1)).with_chain_id(1); + let tx = default_test_transaction(addy, Some(3)).with_chain_id(1); let tx_signed = tx.build(&wallet).await?; let raw_encoded = tx_signed.encoded_2718(); let tx_signed_reth = TransactionSigned::decode_enveloped(&mut raw_encoded.as_slice())?; diff --git a/bolt-sidecar/src/builder/signature.rs b/bolt-sidecar/src/builder/signature.rs index 107326fa0..ee1d0e57c 100644 --- a/bolt-sidecar/src/builder/signature.rs +++ b/bolt-sidecar/src/builder/signature.rs @@ -23,7 +23,7 @@ pub fn sign_builder_message( sk: &SecretKey, msg: &T, ) -> Result { - let domain = chain.builder_domain(); + let domain = chain.application_builder_domain(); let object_root = msg.hash_tree_root()?.0; let signing_root = compute_signing_root(object_root, domain); @@ -42,7 +42,7 @@ pub fn verify_signed_builder_message( msg: &T, signature: &BlsSignature, ) -> Result<(), ethereum_consensus::Error> { - let domain = chain.builder_domain(); + let domain = chain.application_builder_domain(); let object_root = msg.hash_tree_root()?.0; let signing_root = compute_signing_root(object_root, domain); @@ -121,18 +121,27 @@ mod tests { #[test] fn test_compute_builder_domain() { let mainnet = ChainConfig::mainnet(); - assert_eq!(compute_builder_domain(mainnet.fork_version(), None), mainnet.builder_domain()); + assert_eq!( + compute_builder_domain(mainnet.fork_version(), None), + mainnet.application_builder_domain() + ); let holesky = ChainConfig::holesky(); - assert_eq!(compute_builder_domain(holesky.fork_version(), None), holesky.builder_domain()); + assert_eq!( + compute_builder_domain(holesky.fork_version(), None), + holesky.application_builder_domain() + ); let kurtosis = ChainConfig::kurtosis(0, 0); assert_eq!( compute_builder_domain(kurtosis.fork_version(), None), - kurtosis.builder_domain() + kurtosis.application_builder_domain() ); let helder = ChainConfig::helder(); - assert_eq!(compute_builder_domain(helder.fork_version(), None), helder.builder_domain()); + assert_eq!( + compute_builder_domain(helder.fork_version(), None), + helder.application_builder_domain() + ); } } diff --git a/bolt-sidecar/src/client/mod.rs b/bolt-sidecar/src/client/mod.rs index 2235dbe09..c09795fc5 100644 --- a/bolt-sidecar/src/client/mod.rs +++ b/bolt-sidecar/src/client/mod.rs @@ -1,4 +1,3 @@ -pub mod commit_boost; pub mod constraints_client; pub mod pubsub; pub mod rpc; diff --git a/bolt-sidecar/src/common.rs b/bolt-sidecar/src/common.rs index 0563a0296..b6366792c 100644 --- a/bolt-sidecar/src/common.rs +++ b/bolt-sidecar/src/common.rs @@ -1,4 +1,13 @@ +use std::{ + fmt::{self, Display}, + fs::read_to_string, + ops::Deref, + path::Path, +}; + use alloy::primitives::U256; +use blst::min_pk::SecretKey; +use rand::{Rng, RngCore}; use reth_primitives::PooledTransactionsElement; use crate::{ @@ -86,6 +95,82 @@ pub fn validate_transaction( Ok(()) } +#[derive(Clone, Debug)] +pub struct BlsSecretKeyWrapper(pub SecretKey); + +impl BlsSecretKeyWrapper { + pub fn random() -> Self { + let mut rng = rand::thread_rng(); + let mut ikm = [0u8; 32]; + rng.fill_bytes(&mut ikm); + Self(SecretKey::key_gen(&ikm, &[]).unwrap()) + } +} + +impl From<&str> for BlsSecretKeyWrapper { + fn from(sk: &str) -> Self { + let hex_sk = sk.strip_prefix("0x").unwrap_or(sk); + let sk = SecretKey::from_bytes(&hex::decode(hex_sk).expect("valid hex")).expect("valid sk"); + BlsSecretKeyWrapper(sk) + } +} + +impl Deref for BlsSecretKeyWrapper { + type Target = SecretKey; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for BlsSecretKeyWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{}", hex::encode(self.0.to_bytes())) + } +} + +#[derive(Debug, Clone)] +pub struct JwtSecretConfig(pub String); + +impl Default for JwtSecretConfig { + fn default() -> Self { + let random_bytes: [u8; 32] = rand::thread_rng().gen(); + let secret = hex::encode(random_bytes); + Self(secret) + } +} + +impl From<&str> for JwtSecretConfig { + fn from(jwt: &str) -> Self { + let jwt = if jwt.starts_with("0x") { + jwt.trim_start_matches("0x").to_string() + } else if Path::new(&jwt).exists() { + read_to_string(jwt) + .unwrap_or_else(|_| panic!("Failed reading JWT secret file: {:?}", jwt)) + .trim_start_matches("0x") + .to_string() + } else { + jwt.to_string() + }; + + assert!(jwt.len() == 64, "Engine JWT secret must be a 32 byte hex string"); + + Self(jwt) + } +} + +impl Deref for JwtSecretConfig { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Display for JwtSecretConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{}", self.0) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/bolt-sidecar/src/config/chain.rs b/bolt-sidecar/src/config/chain.rs index 0c5dda74e..f5c4769b5 100644 --- a/bolt-sidecar/src/config/chain.rs +++ b/bolt-sidecar/src/config/chain.rs @@ -92,7 +92,7 @@ impl ChainConfig { } /// Get the domain for signing application-builder messages on the given chain. - pub fn builder_domain(&self) -> [u8; 32] { + pub fn application_builder_domain(&self) -> [u8; 32] { self.compute_domain_from_mask(APPLICATION_BUILDER_DOMAIN_MASK) } @@ -174,15 +174,15 @@ mod tests { use super::ChainConfig; let mainnet = ChainConfig::mainnet(); - assert_eq!(mainnet.builder_domain(), BUILDER_DOMAIN_MAINNET); + assert_eq!(mainnet.application_builder_domain(), BUILDER_DOMAIN_MAINNET); let holesky = ChainConfig::holesky(); - assert_eq!(holesky.builder_domain(), BUILDER_DOMAIN_HOLESKY); + assert_eq!(holesky.application_builder_domain(), BUILDER_DOMAIN_HOLESKY); let helder = ChainConfig::helder(); - assert_eq!(helder.builder_domain(), BUILDER_DOMAIN_HELDER); + assert_eq!(helder.application_builder_domain(), BUILDER_DOMAIN_HELDER); let kurtosis = ChainConfig::kurtosis(0, 0); - assert_eq!(kurtosis.builder_domain(), BUILDER_DOMAIN_KURTOSIS); + assert_eq!(kurtosis.application_builder_domain(), BUILDER_DOMAIN_KURTOSIS); } } diff --git a/bolt-sidecar/src/config/limits.rs b/bolt-sidecar/src/config/limits.rs new file mode 100644 index 000000000..6bf02c47a --- /dev/null +++ b/bolt-sidecar/src/config/limits.rs @@ -0,0 +1,36 @@ +use clap::Parser; +use std::num::NonZero; + +// Default limit values +pub const DEFAULT_MAX_COMMITMENTS: usize = 128; +pub const DEFAULT_MAX_COMMITTED_GAS: u64 = 10_000_000; +pub const DEFAULT_MIN_PRIORITY_FEE: u128 = 1_000_000_000; // 1 Gwei + +/// Limits for the sidecar. +#[derive(Debug, Parser, Clone, Copy)] +pub struct LimitsOpts { + /// Max number of commitments to accept per block + #[clap(long, env = "BOLT_SIDECAR_MAX_COMMITMENTS", + default_value_t = LimitsOpts::default().max_commitments_per_slot)] + pub max_commitments_per_slot: NonZero, + /// Max committed gas per slot + #[clap(long, env = "BOLT_SIDECAR_MAX_COMMITTED_GAS", + default_value_t = LimitsOpts::default().max_committed_gas_per_slot)] + pub max_committed_gas_per_slot: NonZero, + /// Min priority fee to accept for a commitment + #[clap(long, env = "BOLT_SIDECAR_MIN_PRIORITY_FEE", + default_value_t = LimitsOpts::default().min_priority_fee)] + pub min_priority_fee: NonZero, +} + +impl Default for LimitsOpts { + fn default() -> Self { + Self { + max_commitments_per_slot: NonZero::new(DEFAULT_MAX_COMMITMENTS) + .expect("Valid non-zero"), + max_committed_gas_per_slot: NonZero::new(DEFAULT_MAX_COMMITTED_GAS) + .expect("Valid non-zero"), + min_priority_fee: NonZero::new(DEFAULT_MIN_PRIORITY_FEE).expect("Valid non-zero"), + } + } +} diff --git a/bolt-sidecar/src/config/mod.rs b/bolt-sidecar/src/config/mod.rs index 6a3012185..7110d7aca 100644 --- a/bolt-sidecar/src/config/mod.rs +++ b/bolt-sidecar/src/config/mod.rs @@ -1,14 +1,6 @@ -use std::{fs::read_to_string, path::Path, str::FromStr}; - use alloy::primitives::Address; -use blst::min_pk::SecretKey; use clap::Parser; -use eyre::{bail, eyre, Report, Result}; use reqwest::Url; -use std::num::NonZero; -use tracing::info; - -use crate::crypto::bls::random_bls_secret; pub mod validator_indexes; pub use validator_indexes::ValidatorIndexes; @@ -22,255 +14,80 @@ pub use signing::SigningOpts; pub mod telemetry; use telemetry::TelemetryOpts; +pub mod limits; +use limits::LimitsOpts; + +use crate::common::{BlsSecretKeyWrapper, JwtSecretConfig}; + /// Default port for the JSON-RPC server exposed by the sidecar. pub const DEFAULT_RPC_PORT: u16 = 8000; /// Default port for the Constraints proxy server. pub const DEFAULT_CONSTRAINTS_PROXY_PORT: u16 = 18551; -// Default limit values -pub const DEFAULT_MAX_COMMITMENTS: usize = 128; -pub const DEFAULT_MAX_COMMITTED_GAS: u64 = 10_000_000; -pub const DEFAULT_MIN_PRIORITY_FEE: u128 = 1_000_000_000; // 1 Gwei - /// Command-line options for the Bolt sidecar -#[derive(Parser, Debug)] +#[derive(Debug, Parser)] +#[clap(trailing_var_arg = true)] pub struct Opts { /// Port to listen on for incoming JSON-RPC requests - #[clap(long, env = "BOLT_SIDECAR_PORT")] - pub(super) port: Option, + #[clap(long, env = "BOLT_SIDECAR_PORT", + default_value_t = DEFAULT_RPC_PORT)] + pub port: u16, /// URL for the beacon client - #[clap(long, env = "BOLT_SIDECAR_BEACON_API_URL")] - pub(super) beacon_api_url: String, + #[clap(long, env = "BOLT_SIDECAR_BEACON_API_URL", default_value = "http://localhost:5052")] + pub beacon_api_url: Url, /// URL for the Constraint sidecar client to use - #[clap(long, env = "BOLT_SIDECAR_CONSTRAINTS_URL")] - pub(super) constraints_url: String, + #[clap(long, env = "BOLT_SIDECAR_CONSTRAINTS_URL", default_value = "http://localhost:3030")] + pub constraints_url: Url, /// Execution client API URL - #[clap(long, env = "BOLT_SIDECAR_EXECUTION_API_URL")] - pub(super) execution_api_url: String, + #[clap(long, env = "BOLT_SIDECAR_EXECUTION_API_URL", default_value = "http://localhost:8545")] + pub execution_api_url: Url, /// Execution client Engine API URL - #[clap(long, env = "BOLT_SIDECAR_ENGINE_API_URL")] - pub(super) engine_api_url: String, + #[clap(long, env = "BOLT_SIDECAR_ENGINE_API_URL", default_value = "http://localhost:8551")] + pub engine_api_url: Url, /// Constraint proxy server port to use - #[clap(long, env = "BOLT_SIDECAR_CONSTRAINTS_PROXY_PORT")] - pub(super) constraints_proxy_port: u16, - /// Max number of commitments to accept per block - #[clap(long, env = "BOLT_SIDECAR_MAX_COMMITMENTS")] - pub(super) max_commitments: Option>, - /// Max committed gas per slot - #[clap(long, env = "BOLT_SIDECAR_MAX_COMMITTED_GAS")] - pub(super) max_committed_gas: Option>, - /// Min priority fee to accept for a commitment - #[clap(long, env = "BOLT_SIDECAR_MIN_PRIORITY_FEE")] - pub(super) min_priority_fee: Option>, + #[clap(long, env = "BOLT_SIDECAR_CONSTRAINTS_PROXY_PORT", + default_value_t = DEFAULT_CONSTRAINTS_PROXY_PORT)] + pub constraints_proxy_port: u16, /// Validator indexes of connected validators that the sidecar /// should accept commitments on behalf of. Accepted values: /// - a comma-separated list of indexes (e.g. "1,2,3,4") /// - a contiguous range of indexes (e.g. "1..4") /// - a mix of the above (e.g. "1,2..4,6..8") - #[clap(long, value_parser = ValidatorIndexes::from_str, env = "BOLT_SIDECAR_VALIDATOR_INDEXES")] - pub(super) validator_indexes: ValidatorIndexes, + #[clap(long, env = "BOLT_SIDECAR_VALIDATOR_INDEXES", default_value_t)] + pub validator_indexes: ValidatorIndexes, /// The JWT secret token to authenticate calls to the engine API. /// /// It can either be a hex-encoded string or a file path to a file /// containing the hex-encoded secret. - #[clap(long, env = "BOLT_SIDECAR_JWT_HEX")] - pub(super) jwt_hex: String, + #[clap(long, env = "BOLT_SIDECAR_JWT_HEX", default_value_t)] + pub jwt_hex: JwtSecretConfig, /// The fee recipient address for fallback blocks - #[clap(long, env = "BOLT_SIDECAR_FEE_RECIPIENT")] - pub(super) fee_recipient: Address, + #[clap(long, env = "BOLT_SIDECAR_FEE_RECIPIENT", + default_value_t = Address::ZERO)] + pub fee_recipient: Address, /// Secret BLS key to sign fallback payloads with /// (If not provided, a random key will be used) - #[clap(long, env = "BOLT_SIDECAR_BUILDER_PRIVATE_KEY")] - pub(super) builder_private_key: Option, + #[clap(long, env = "BOLT_SIDECAR_BUILDER_PRIVATE_KEY", + default_value_t = BlsSecretKeyWrapper::random())] + pub builder_private_key: BlsSecretKeyWrapper, + /// Operating limits for the sidecar + #[clap(flatten)] + pub limits: LimitsOpts, /// Chain config for the chain on which the sidecar is running #[clap(flatten)] - pub(super) chain: ChainConfig, + pub chain: ChainConfig, /// Commitment signing options. #[clap(flatten)] - pub(super) signing: SigningOpts, + pub signing: SigningOpts, /// Telemetry options #[clap(flatten)] - pub(super) telemetry: TelemetryOpts, -} - -/// Configuration options for the sidecar. These are parsed from -/// command-line options in the form of [`Opts`]. -#[derive(Debug, Clone)] -pub struct Config { - /// Port to listen on for incoming JSON-RPC requests - pub rpc_port: u16, - /// The Constraints client proxy server port to listen on - pub constraints_proxy_port: u16, - /// URL for the Constraints sidecar client to use - pub constraints_url: Url, - /// URL for the beacon client API URL - pub beacon_api_url: Url, - /// The execution API url - pub execution_api_url: Url, - /// The engine API url - pub engine_api_url: Url, - /// URL for the commit-boost sidecar - pub commit_boost_url: Option, - /// The JWT secret token to authenticate calls to the commit-boost - pub commit_boost_jwt_hex: Option, - /// Private key to use for signing preconfirmation requests - pub private_key: Option, - /// The jwt.hex secret to authenticate calls to the engine API - pub jwt_hex: String, - /// The fee recipient address for fallback blocks - pub fee_recipient: Address, - /// Operating limits for the sidecar - pub limits: Limits, - /// Validator indexes of connected validators that the - /// sidecar should accept commitments on behalf of - pub validator_indexes: ValidatorIndexes, - /// Local bulider private key for signing fallback payloads. - /// If not provided, a random key will be used. - pub builder_private_key: SecretKey, - /// The chain on which the sidecar is running - pub chain: ChainConfig, - /// Metrics port - pub metrics_port: u16, - /// Toggle for metrics - pub disable_metrics: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - rpc_port: DEFAULT_RPC_PORT, - constraints_proxy_port: DEFAULT_CONSTRAINTS_PROXY_PORT, - commit_boost_url: None, - commit_boost_jwt_hex: None, - constraints_url: "http://localhost:3030".parse().expect("Valid URL"), - beacon_api_url: "http://localhost:5052".parse().expect("Valid URL"), - execution_api_url: "http://localhost:8545".parse().expect("Valid URL"), - engine_api_url: "http://localhost:8551".parse().expect("Valid URL"), - private_key: Some(random_bls_secret()), - jwt_hex: String::new(), - fee_recipient: Address::ZERO, - builder_private_key: random_bls_secret(), - limits: Limits::default(), - validator_indexes: ValidatorIndexes::default(), - chain: ChainConfig::default(), - metrics_port: 0, - disable_metrics: false, - } - } -} - -/// Limits for the sidecar. -#[derive(Debug, Clone, Copy)] -pub struct Limits { - /// Maximum number of commitments to accept per block - pub max_commitments_per_slot: NonZero, - pub max_committed_gas_per_slot: NonZero, - - /// Minimum priority fee to accept for a commitment in wei - pub min_priority_fee: NonZero, -} - -impl Default for Limits { - fn default() -> Self { - Self { - max_commitments_per_slot: NonZero::new(DEFAULT_MAX_COMMITMENTS) - .expect("Valid non-zero"), - max_committed_gas_per_slot: NonZero::new(DEFAULT_MAX_COMMITTED_GAS) - .expect("Valid non-zero"), - min_priority_fee: NonZero::new(DEFAULT_MIN_PRIORITY_FEE).expect("Valid non-zero"), - } - } -} - -impl Config { - /// Parse the command-line options and return a new [`Config`] instance - pub fn parse_from_cli() -> Result { - let opts = Opts::parse(); - Self::try_from(opts) - } -} - -impl TryFrom for Config { - type Error = Report; - - fn try_from(opts: Opts) -> Result { - let mut config = Config::default(); - - if let Some(port) = opts.port { - config.rpc_port = port; - } - - if let Some(max_commitments) = opts.max_commitments { - config.limits.max_commitments_per_slot = max_commitments; - } - - if let Some(max_committed_gas) = opts.max_committed_gas { - config.limits.max_committed_gas_per_slot = max_committed_gas; - } - - if let Some(min_priority_fee) = opts.min_priority_fee { - config.limits.min_priority_fee = min_priority_fee; - } - - if let Some(commit_boost_url) = &opts.signing.commit_boost_url { - if let Ok(url) = Url::parse(commit_boost_url) { - if let Ok(socket_addrs) = url.socket_addrs(|| None) { - config.commit_boost_url = Some(socket_addrs[0].to_string()); - } - } - } - - config.private_key = if let Some(sk) = opts.signing.private_key { - let hex_sk = sk.strip_prefix("0x").unwrap_or(&sk); - let sk = SecretKey::from_bytes(&hex::decode(hex_sk)?) - .map_err(|e| eyre!("Failed decoding BLS signer secret key: {:?}", e))?; - Some(sk) - } else { - None - }; - - if let Some(builder_sk) = opts.builder_private_key { - let hex_sk = builder_sk.strip_prefix("0x").unwrap_or(&builder_sk); - let sk = SecretKey::from_bytes(&hex::decode(hex_sk)?) - .map_err(|e| eyre!("Failed decoding BLS builder secret key: {:?}", e))?; - config.builder_private_key = sk; - } - - config.jwt_hex = if opts.jwt_hex.starts_with("0x") { - opts.jwt_hex.trim_start_matches("0x").to_string() - } else if Path::new(&opts.jwt_hex).exists() { - read_to_string(opts.jwt_hex) - .map_err(|e| eyre!("Failed reading JWT secret file: {:?}", e))? - .trim_start_matches("0x") - .to_string() - } else { - opts.jwt_hex - }; - - // Validate the JWT secret - if config.jwt_hex.len() != 64 { - bail!("Engine JWT secret must be a 32 byte hex string"); - } else { - info!("Engine JWT secret loaded successfully"); - } - - config.constraints_proxy_port = opts.constraints_proxy_port; - config.engine_api_url = opts.engine_api_url.parse()?; - config.execution_api_url = opts.execution_api_url.parse()?; - config.beacon_api_url = opts.beacon_api_url.parse()?; - config.constraints_url = opts.constraints_url.parse()?; - - config.fee_recipient = opts.fee_recipient; - - config.validator_indexes = opts.validator_indexes; - - config.chain = opts.chain; - config.metrics_port = opts.telemetry.metrics_port; - config.disable_metrics = opts.telemetry.disable_metrics; - - Ok(config) - } + pub telemetry: TelemetryOpts, + /// Additional unrecognized arguments. Useful for CI and testing + /// to avoid issues on potential extra flags provided (e.g. "--exact" from cargo nextest). + #[cfg(test)] + #[clap(allow_hyphen_values = true)] + pub extra_args: Vec, } #[cfg(test)] diff --git a/bolt-sidecar/src/config/signing.rs b/bolt-sidecar/src/config/signing.rs index c592b1cf6..d4d91494d 100644 --- a/bolt-sidecar/src/config/signing.rs +++ b/bolt-sidecar/src/config/signing.rs @@ -1,29 +1,57 @@ +use crate::common::{BlsSecretKeyWrapper, JwtSecretConfig}; +use std::{ + fmt::{self}, + net::SocketAddr, +}; + use clap::{ArgGroup, Args}; +use lighthouse_account_utils::ZeroizeString; /// Command-line options for signing -#[derive(Debug, Clone, Args)] +#[derive(Args)] #[clap( group = ArgGroup::new("signing-opts").required(true) - .args(&["private_key", "commit_boost_url", "commit_boost_jwt_hex"]) + .args(&["private_key", "commit_boost_address", "commit_boost_jwt_hex", "keystore_password"]) )] pub struct SigningOpts { /// Private key to use for signing preconfirmation requests - #[clap(long, env = "BOLT_SIDECAR_PRIVATE_KEY", conflicts_with("commit_boost_url"))] - pub(super) private_key: Option, - /// URL for the commit-boost sidecar - #[clap( - long, - env = "BOLT_SIDECAR_CB_SIGNER_URL", - conflicts_with("private_key"), - requires("commit_boost_jwt_hex") - )] - pub(super) commit_boost_url: Option, + #[clap(long, env = "BOLT_SIDECAR_PRIVATE_KEY")] + pub private_key: Option, + /// Socket address for the commit-boost sidecar + #[clap(long, env = "BOLT_SIDECAR_CB_SIGNER_URL", requires("commit_boost_jwt_hex"))] + pub commit_boost_address: Option, /// JWT in hexadecimal format for authenticating with the commit-boost service - #[clap( - long, - env = "BOLT_SIDECAR_CB_JWT_HEX", - conflicts_with("private_key"), - requires("commit_boost_url") - )] - pub(super) commit_boost_jwt_hex: Option, + #[clap(long, env = "BOLT_SIDECAR_CB_JWT_HEX", requires("commit_boost_address"))] + pub commit_boost_jwt_hex: Option, + /// The password for the ERC-2335 keystore. + /// Reference: https://eips.ethereum.org/EIPS/eip-2335 + #[clap(long, env = "BOLT_SIDECAR_KEYSTORE_PASSWORD")] + pub keystore_password: Option, + /// Path to the keystores folder. If not provided, the default path is used. + #[clap(long, env = "BOLT_SIDECAR_KEYSTORE_PATH", requires("keystore_password"))] + pub keystore_path: Option, +} + +// Implement Debug manually to hide the keystore_password field +impl fmt::Debug for SigningOpts { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SigningOpts") + .field("private_key", &self.private_key) + .field("commit_boost_url", &self.commit_boost_address) + .field("commit_boost_jwt_hex", &self.commit_boost_jwt_hex) + .field("keystore_password", &"********") // Hides the actual password + .finish() + } +} + +impl Default for SigningOpts { + fn default() -> Self { + Self { + private_key: Some(BlsSecretKeyWrapper::random()), + commit_boost_address: None, + commit_boost_jwt_hex: None, + keystore_password: None, + keystore_path: None, + } + } } diff --git a/bolt-sidecar/src/config/validator_indexes.rs b/bolt-sidecar/src/config/validator_indexes.rs index 54321f907..48e8cca0a 100644 --- a/bolt-sidecar/src/config/validator_indexes.rs +++ b/bolt-sidecar/src/config/validator_indexes.rs @@ -1,4 +1,7 @@ -use std::str::FromStr; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; #[derive(Debug, Clone, Default)] pub struct ValidatorIndexes(Vec); @@ -24,6 +27,10 @@ impl FromStr for ValidatorIndexes { let s = s.trim(); let mut vec = Vec::new(); + if s.is_empty() { + return Ok(Self(vec)); + } + for comma_separated_part in s.split(',') { if comma_separated_part.contains("..") { let mut parts = comma_separated_part.split(".."); @@ -51,6 +58,12 @@ impl From> for ValidatorIndexes { } } +impl Display for ValidatorIndexes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.iter().map(|index| index.to_string()).collect::>().join(",")) + } +} + #[cfg(test)] mod tests { #[test] diff --git a/bolt-sidecar/src/crypto/bls.rs b/bolt-sidecar/src/crypto/bls.rs index 56179aba5..2f5687b14 100644 --- a/bolt-sidecar/src/crypto/bls.rs +++ b/bolt-sidecar/src/crypto/bls.rs @@ -1,15 +1,9 @@ -use std::fmt::Debug; +use alloy::{primitives::FixedBytes, rpc::types::beacon::constants::BLS_PUBLIC_KEY_BYTES_LEN}; +use ethereum_consensus::crypto::PublicKey as BlsPublicKey; -use alloy::primitives::FixedBytes; -use blst::{min_pk::Signature, BLST_ERROR}; -use ethereum_consensus::deneb::compute_signing_root; -use rand::RngCore; - -pub use blst::min_pk::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}; +pub use blst::min_pk::{PublicKey, SecretKey as BlsSecretKey}; pub use ethereum_consensus::deneb::BlsSignature; -use crate::ChainConfig; - /// The BLS Domain Separator used in Ethereum 2.0. pub const BLS_DST_PREFIX: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; @@ -23,143 +17,7 @@ pub trait SignableBLS { fn digest(&self) -> [u8; 32]; } -/// A generic signing trait to generate BLS signatures. -/// -/// Note: we keep this async to allow remote signer implementations. -#[async_trait::async_trait] -pub trait SignerBLS: Send + Debug { - /// Sign the given data and return the signature. - async fn sign_commit_boost_root(&self, data: &[u8; 32]) -> eyre::Result; -} - -/// A BLS signer that can sign any type that implements the [`SignableBLS`] trait. -#[derive(Clone)] -pub struct Signer { - chain: ChainConfig, - key: BlsSecretKey, -} - -impl Debug for Signer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Signer") - .field("pubkey", &self.pubkey()) - .field("chain", &self.chain.name()) - .finish() - } -} - -impl Signer { - /// Create a new signer with the given BLS secret key. - pub fn new(key: BlsSecretKey, chain: ChainConfig) -> Self { - Self { key, chain } - } - - /// Create a signer with a random BLS key configured for Mainnet for testing. - #[cfg(test)] - pub fn random() -> Self { - Self { key: random_bls_secret(), chain: ChainConfig::mainnet() } - } - - /// Get the public key of the signer. - pub fn pubkey(&self) -> BlsPublicKey { - self.key.sk_to_pk() - } - - /// Sign an SSZ object root with the Application Builder domain. - pub fn sign_application_builder_root(&self, root: [u8; 32]) -> eyre::Result { - self.sign_root(root, self.chain.builder_domain()) - } - - /// Sign an SSZ object root with the Commit Boost domain. - pub fn sign_commit_boost_root(&self, root: [u8; 32]) -> eyre::Result { - self.sign_root(root, self.chain.commit_boost_domain()) - } - - /// Sign an SSZ object root with the given domain. - pub fn sign_root(&self, root: [u8; 32], domain: [u8; 32]) -> eyre::Result { - let signing_root = compute_signing_root(&root, domain)?; - let sig = self.key.sign(signing_root.as_slice(), BLS_DST_PREFIX, &[]); - Ok(BLSSig::from_slice(&sig.to_bytes())) - } - - /// Verify the signature with the public key of the signer using the Application Builder domain. - pub fn verify_application_builder_root( - &self, - root: [u8; 32], - signature: &Signature, - ) -> eyre::Result<()> { - self.verify_root(root, signature, &self.pubkey(), self.chain.builder_domain()) - } - - /// Verify the signature with the public key of the signer using the Commit Boost domain. - pub fn verify_commit_boost_root( - &self, - root: [u8; 32], - signature: &Signature, - ) -> eyre::Result<()> { - self.verify_root(root, signature, &self.pubkey(), self.chain.commit_boost_domain()) - } - - /// Verify the signature of the object with the given public key. - pub fn verify_root( - &self, - root: [u8; 32], - signature: &Signature, - pubkey: &BlsPublicKey, - domain: [u8; 32], - ) -> eyre::Result<()> { - let signing_root = compute_signing_root(&root, domain)?; - - let res = signature.verify(true, signing_root.as_ref(), BLS_DST_PREFIX, &[], pubkey, true); - if res == BLST_ERROR::BLST_SUCCESS { - Ok(()) - } else { - eyre::bail!(format!("Invalid signature: {:?}", res)) - } - } -} - -#[async_trait::async_trait] -impl SignerBLS for Signer { - async fn sign_commit_boost_root(&self, data: &[u8; 32]) -> eyre::Result { - self.sign_commit_boost_root(*data) - } -} - -/// Compatibility between ethereum_consensus and blst -pub fn from_bls_signature_to_consensus_signature(sig_bytes: impl AsRef<[u8]>) -> BlsSignature { - BlsSignature::try_from(sig_bytes.as_ref()).unwrap() -} - -/// Generate a random BLS secret key. -pub fn random_bls_secret() -> BlsSecretKey { - let mut rng = rand::thread_rng(); - let mut ikm = [0u8; 32]; - rng.fill_bytes(&mut ikm); - BlsSecretKey::key_gen(&ikm, &[]).unwrap() -} - -#[cfg(test)] -mod tests { - use crate::{ - crypto::bls::{SignableBLS, Signer}, - test_util::TestSignableData, - }; - - use rand::Rng; - - #[tokio::test] - async fn test_bls_signer() { - let signer = Signer::random(); - - // Generate random data for the test - let mut rng = rand::thread_rng(); - let mut data = [0u8; 32]; - rng.fill(&mut data); - let msg = TestSignableData { data }; - - let signature = signer.sign_commit_boost_root(msg.digest()).unwrap(); - let sig = blst::min_pk::Signature::from_bytes(signature.as_ref()).unwrap(); - assert!(signer.verify_commit_boost_root(msg.digest(), &sig).is_ok()); - } +/// Convert a BLS public key from Consensus Types to a byte array. +pub fn cl_public_key_to_arr(pubkey: BlsPublicKey) -> [u8; BLS_PUBLIC_KEY_BYTES_LEN] { + pubkey.as_ref().try_into().expect("BLS keys are 48 bytes") } diff --git a/bolt-sidecar/src/crypto/mod.rs b/bolt-sidecar/src/crypto/mod.rs index 2cabada59..330d3fdde 100644 --- a/bolt-sidecar/src/crypto/mod.rs +++ b/bolt-sidecar/src/crypto/mod.rs @@ -1,6 +1,6 @@ /// BLS12_381 signatures and verification functions. pub mod bls; -pub use bls::{SignableBLS, SignerBLS}; +pub use bls::SignableBLS; /// ECDSA signatures and verification functions. pub mod ecdsa; diff --git a/bolt-sidecar/src/driver.rs b/bolt-sidecar/src/driver.rs index bee3969ed..ed365244f 100644 --- a/bolt-sidecar/src/driver.rs +++ b/bolt-sidecar/src/driver.rs @@ -16,23 +16,25 @@ use crate::{ server::{CommitmentsApiServer, Event as CommitmentEvent}, spec::Error as CommitmentError, }, - crypto::{bls::Signer as BlsSigner, SignableBLS, SignerBLS, SignerECDSA}, + crypto::{bls::cl_public_key_to_arr, SignableBLS, SignerECDSA}, primitives::{ CommitmentRequest, ConstraintsMessage, FetchPayloadRequest, LocalPayloadFetcher, SignedConstraints, TransactionExt, }, + signer::{keystore::KeystoreSigner, local::LocalSigner}, start_builder_proxy_server, state::{fetcher::StateFetcher, ConsensusState, ExecutionState, HeadTracker, StateClient}, telemetry::ApiMetrics, - BuilderProxyConfig, CommitBoostSigner, Config, ConstraintsApi, ConstraintsClient, LocalBuilder, + BuilderProxyConfig, CommitBoostSigner, ConstraintsApi, ConstraintsClient, LocalBuilder, Opts, + SignerBLS, }; /// The driver for the sidecar, responsible for managing the main event loop. -pub struct SidecarDriver { +pub struct SidecarDriver { head_tracker: HeadTracker, execution: ExecutionState, consensus: ConsensusState, - constraint_signer: BLS, + constraint_signer: SignerBLS, commitment_signer: ECDSA, local_builder: LocalBuilder, constraints_client: ConstraintsClient, @@ -42,7 +44,7 @@ pub struct SidecarDriver { slot_stream: SlotStream, } -impl fmt::Debug for SidecarDriver { +impl fmt::Debug for SidecarDriver { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SidecarDriver") .field("head_tracker", &self.head_tracker) @@ -58,76 +60,100 @@ impl fmt::Debug for SidecarDriver { - /// Create a new sidecar driver with the given [Config] and private key signer. - pub async fn with_local_signer(cfg: Config) -> eyre::Result { +impl SidecarDriver { + /// Create a new sidecar driver with the given [Opts] and private key signer. + pub async fn with_local_signer(opts: &Opts) -> eyre::Result { // The default state client simply uses the execution API URL to fetch state updates. - let state_client = StateClient::new(cfg.execution_api_url.clone()); + let state_client = StateClient::new(opts.execution_api_url.clone()); // Constraints are signed with a BLS private key - let constraint_signing_key = cfg.private_key.clone().expect("Private key must be provided"); - let constraint_signer = BlsSigner::new(constraint_signing_key, cfg.chain); + let constraint_signer = SignerBLS::Local(LocalSigner::new( + opts.signing.private_key.clone().expect("local signer").0, + opts.chain, + )); // Commitment responses are signed with a regular Ethereum wallet private key. // This is now generated randomly because slashing is not yet implemented. let commitment_signer = PrivateKeySigner::random(); - Self::from_components(cfg, constraint_signer, commitment_signer, state_client).await + Self::from_components(opts, constraint_signer, commitment_signer, state_client).await } } -impl SidecarDriver { - /// Create a new sidecar driver with the given [Config] and commit-boost signer. - pub async fn with_commit_boost_signer(cfg: Config) -> eyre::Result { +impl SidecarDriver { + /// Create a new sidecar driver with the given [Opts] and keystore signer. + pub async fn with_keystore_signer(opts: &Opts) -> eyre::Result { // The default state client simply uses the execution API URL to fetch state updates. - let state_client = StateClient::new(cfg.execution_api_url.clone()); + let state_client = StateClient::new(opts.execution_api_url.clone()); + + let keystore_signer = SignerBLS::Keystore(KeystoreSigner::new( + opts.signing.keystore_path.as_deref(), + opts.signing.keystore_password.as_ref().expect("keystore password").as_ref(), + opts.chain, + )?); + + // Commitment responses are signed with a regular Ethereum wallet private key. + // This is now generated randomly because slashing is not yet implemented. + let commitment_signer = PrivateKeySigner::random(); + + Self::from_components(opts, keystore_signer, commitment_signer, state_client).await + } +} + +impl SidecarDriver { + /// Create a new sidecar driver with the given [Opts] and commit-boost signer. + pub async fn with_commit_boost_signer(opts: &Opts) -> eyre::Result { + // The default state client simply uses the execution API URL to fetch state updates. + let state_client = StateClient::new(opts.execution_api_url.clone()); let commit_boost_signer = CommitBoostSigner::new( - cfg.commit_boost_url.clone().expect("CommitBoost URL must be provided"), - &cfg.commit_boost_jwt_hex.clone().expect("CommitBoost JWT must be provided"), + opts.signing + .commit_boost_address + .expect("CommitBoost URL must be provided") + .to_string(), + &opts.signing.commit_boost_jwt_hex.clone().expect("CommitBoost JWT must be provided"), ) .await?; - // Constraints are signed with commit-boost signer - let constraint_signer = commit_boost_signer.clone(); + let cb_bls_signer = SignerBLS::CommitBoost(commit_boost_signer.clone()); // Commitment responses are signed with commit-boost signer let commitment_signer = commit_boost_signer.clone(); - Self::from_components(cfg, constraint_signer, commitment_signer, state_client).await + Self::from_components(opts, cb_bls_signer, commitment_signer, state_client).await } } -impl SidecarDriver { +impl SidecarDriver { /// Create a new sidecar driver with the given components pub async fn from_components( - cfg: Config, - constraint_signer: BLS, + opts: &Opts, + constraint_signer: SignerBLS, commitment_signer: ECDSA, fetcher: C, ) -> eyre::Result { - let constraints_client = ConstraintsClient::new(cfg.constraints_url.clone()); - let beacon_client = BeaconClient::new(cfg.beacon_api_url.clone()); - let execution = ExecutionState::new(fetcher, cfg.limits).await?; + let constraints_client = ConstraintsClient::new(opts.constraints_url.clone()); + let beacon_client = BeaconClient::new(opts.beacon_api_url.clone()); + let execution = ExecutionState::new(fetcher, opts.limits).await?; let genesis_time = beacon_client.get_genesis_details().await?.genesis_time; let slot_stream = - clock::from_system_time(genesis_time, cfg.chain.slot_time(), SLOTS_PER_EPOCH) + clock::from_system_time(genesis_time, opts.chain.slot_time(), SLOTS_PER_EPOCH) .into_stream(); - let local_builder = LocalBuilder::new(&cfg, beacon_client.clone(), genesis_time); + let local_builder = LocalBuilder::new(opts, beacon_client.clone(), genesis_time); let head_tracker = HeadTracker::start(beacon_client.clone()); let consensus = ConsensusState::new( beacon_client, - cfg.validator_indexes.clone(), - cfg.chain.commitment_deadline(), + opts.validator_indexes.clone(), + opts.chain.commitment_deadline(), ); let (payload_requests_tx, payload_requests_rx) = mpsc::channel(16); let builder_proxy_cfg = BuilderProxyConfig { - constraints_url: cfg.constraints_url.clone(), - server_port: cfg.constraints_proxy_port, + constraints_url: opts.constraints_url.clone(), + server_port: opts.constraints_proxy_port, }; // start the builder api proxy server @@ -139,7 +165,7 @@ impl SidecarDriver SidecarDriver signer.pubkey(), + SignerBLS::CommitBoost(ref signer) => signer.pubkey(), + SignerBLS::Keystore(_) => validator_pubkey.clone(), + }; + // NOTE: we iterate over the transactions in the request and generate a signed constraint // for each one. This is because the transactions in the commitment request are not // supposed to be treated as a relative-ordering bundle, but a batch // with no ordering guarantees. for tx in inclusion_request.txs { let tx_type = tx.tx_type(); - let message = ConstraintsMessage::from_transaction(validator_pubkey.clone(), slot, tx); - - let signed_constraints = - match self.constraint_signer.sign_commit_boost_root(&message.digest()).await { - Ok(signature) => SignedConstraints { message, signature }, - Err(err) => { - error!(?err, "Failed to sign constraints"); - let _ = response.send(Err(CommitmentError::Internal)); - return; - } - }; + let message = ConstraintsMessage::from_transaction(pubkey.clone(), slot, tx); + let digest = message.digest(); + + let signature = match self.constraint_signer { + SignerBLS::Local(ref signer) => signer.sign_commit_boost_root(digest), + SignerBLS::CommitBoost(ref signer) => signer.sign_commit_boost_root(digest).await, + SignerBLS::Keystore(ref signer) => { + signer.sign_commit_boost_root(digest, cl_public_key_to_arr(pubkey.clone())) + } + }; + + let signed_constraints = match signature { + Ok(signature) => SignedConstraints { message, signature }, + Err(e) => { + error!(?e, "Failed to sign constraints"); + let _ = response.send(Err(CommitmentError::Internal)); + return; + } + }; ApiMetrics::increment_transactions_preconfirmed(tx_type); self.execution.add_constraint(slot, signed_constraints); diff --git a/bolt-sidecar/src/lib.rs b/bolt-sidecar/src/lib.rs index 219cd9e47..53180f6c5 100644 --- a/bolt-sidecar/src/lib.rs +++ b/bolt-sidecar/src/lib.rs @@ -12,10 +12,7 @@ pub use api::{ }; mod client; -pub use client::{ - commit_boost::CommitBoostSigner, constraints_client::ConstraintsClient, rpc::RpcClient, - BeaconClient, -}; +pub use client::{constraints_client::ConstraintsClient, rpc::RpcClient, BeaconClient}; /// Telemetry and metrics utilities pub mod telemetry; @@ -36,7 +33,7 @@ pub use builder::LocalBuilder; /// Configuration and command-line argument parsing mod config; -pub use config::{ChainConfig, Config, Opts}; +pub use config::{ChainConfig, Opts}; /// Crypto utilities, including BLS and ECDSA pub mod crypto; @@ -47,6 +44,10 @@ pub mod primitives; /// State management and fetching for EVM simulation pub mod state; +/// The signers available to the sidecar +mod signer; +pub use signer::{commit_boost::CommitBoostSigner, SignerBLS}; + /// Utilities for testing #[cfg(test)] mod test_util; diff --git a/bolt-sidecar/src/primitives/constraint.rs b/bolt-sidecar/src/primitives/constraint.rs index a52a460b2..344bf7807 100644 --- a/bolt-sidecar/src/primitives/constraint.rs +++ b/bolt-sidecar/src/primitives/constraint.rs @@ -16,7 +16,7 @@ pub type BatchedSignedConstraints = Vec; /// A container for a list of constraints and the signature of the proposer sidecar. /// /// Reference: https://chainbound.github.io/bolt-docs/api/builder#constraints -#[derive(Serialize, Default, Debug, Clone, PartialEq)] +#[derive(Serialize, Default, Debug, Clone, PartialEq, Eq)] pub struct SignedConstraints { /// The constraints that need to be signed. pub message: ConstraintsMessage, @@ -27,7 +27,7 @@ pub struct SignedConstraints { /// A message that contains the constraints that need to be signed by the proposer sidecar. /// /// Reference: https://chainbound.github.io/bolt-docs/api/builder#constraints -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Eq)] pub struct ConstraintsMessage { /// The validator pubkey of the proposer sidecar. pub pubkey: BlsPublicKey, @@ -72,7 +72,11 @@ impl SignableBLS for ConstraintsMessage { #[cfg(test)] mod tests { + use crate::signer::local::LocalSigner; + use super::*; + use alloy::primitives::bytes; + use blst::min_pk::Signature as BlsSignature; use rand::{rngs::ThreadRng, Rng}; fn random_u64(rng: &mut ThreadRng) -> u64 { @@ -131,4 +135,22 @@ mod tests { // Verify that the deserialized message is equal to the original message assert_eq!(message, deserialized_message); } + + #[test] + fn test_constraints_signature_roundtrip() { + let signer = LocalSigner::random(); + + let tx_bytes = bytes!("f8678085019dc6838082520894deaddeaddeaddeaddeaddeaddeaddeaddeaddead38808360306ca06664c078fa60bd3ece050903dd295949908dd9686ec8871fa558f868e031cd39a00ed4f0b122b32b73f19230fabe6a726e2d07f84eda5beaa42a1ae1271bdee39f").to_vec(); + let tx = FullTransaction::decode_enveloped(tx_bytes.as_slice()).unwrap(); + + let constraint = ConstraintsMessage::from_transaction(signer.pubkey(), 165, tx); + + let digest = constraint.digest(); + let signature = signer.sign_commit_boost_root(digest).unwrap(); + let signed_constraints = SignedConstraints { message: constraint, signature }; + + // verify the signature + let blst_sig = BlsSignature::from_bytes(signed_constraints.signature.as_ref()).unwrap(); + assert!(signer.verify_commit_boost_root(digest, &blst_sig).is_ok()); + } } diff --git a/bolt-sidecar/src/primitives/mod.rs b/bolt-sidecar/src/primitives/mod.rs index c4cbc5dee..5a9b04f6b 100644 --- a/bolt-sidecar/src/primitives/mod.rs +++ b/bolt-sidecar/src/primitives/mod.rs @@ -379,6 +379,12 @@ impl std::ops::DerefMut for FullTransaction { } impl FullTransaction { + /// Convenience method to parse a raw transaction into a `FullTransaction`. + pub fn decode_enveloped(data: impl AsRef<[u8]>) -> eyre::Result { + let tx = PooledTransactionsElement::decode_enveloped(&mut data.as_ref())?; + Ok(Self { tx, sender: None }) + } + pub fn into_inner(self) -> PooledTransactionsElement { self.tx } @@ -446,21 +452,41 @@ where #[error("Invalid signature")] pub struct SignatureError; -#[derive(Debug, Clone, Serialize)] +/// Event types that can be emitted by the validator pubkey to +/// signal some action on the Bolt protocol. +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +enum SignedMessageAction { + /// Signal delegation of a validator pubkey to a delegatee pubkey. + Delegation, + /// Signal revocation of a previously delegated pubkey. + Revocation, +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct SignedDelegation { pub message: DelegationMessage, pub signature: BlsSignature, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct DelegationMessage { + action: u8, pub validator_pubkey: BlsPublicKey, pub delegatee_pubkey: BlsPublicKey, } +impl DelegationMessage { + /// Create a new delegation message. + pub fn new(validator_pubkey: BlsPublicKey, delegatee_pubkey: BlsPublicKey) -> Self { + Self { action: SignedMessageAction::Delegation as u8, validator_pubkey, delegatee_pubkey } + } +} + impl SignableBLS for DelegationMessage { fn digest(&self) -> [u8; 32] { let mut hasher = Sha256::new(); + hasher.update([self.action]); hasher.update(self.validator_pubkey.to_vec()); hasher.update(self.delegatee_pubkey.to_vec()); @@ -468,21 +494,30 @@ impl SignableBLS for DelegationMessage { } } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct SignedRevocation { pub message: RevocationMessage, pub signature: BlsSignature, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct RevocationMessage { + action: u8, pub validator_pubkey: BlsPublicKey, pub delegatee_pubkey: BlsPublicKey, } +impl RevocationMessage { + /// Create a new revocation message. + pub fn new(validator_pubkey: BlsPublicKey, delegatee_pubkey: BlsPublicKey) -> Self { + Self { action: SignedMessageAction::Revocation as u8, validator_pubkey, delegatee_pubkey } + } +} + impl SignableBLS for RevocationMessage { fn digest(&self) -> [u8; 32] { let mut hasher = Sha256::new(); + hasher.update([self.action]); hasher.update(self.validator_pubkey.to_vec()); hasher.update(self.delegatee_pubkey.to_vec()); diff --git a/bolt-sidecar/src/client/commit_boost.rs b/bolt-sidecar/src/signer/commit_boost.rs similarity index 77% rename from bolt-sidecar/src/client/commit_boost.rs rename to bolt-sidecar/src/signer/commit_boost.rs index ad5931727..1757e8071 100644 --- a/bolt-sidecar/src/client/commit_boost.rs +++ b/bolt-sidecar/src/signer/commit_boost.rs @@ -2,23 +2,22 @@ use std::{str::FromStr, sync::Arc}; use alloy::{rpc::types::beacon::BlsSignature, signers::Signature}; use cb_common::{ - commit::{client::SignerClient, request::SignConsensusRequest}, - signer::{BlsPublicKey, EcdsaPublicKey}, + commit::{client::SignerClient, error::SignerClientError, request::SignConsensusRequest}, + signer::EcdsaPublicKey, }; use commit_boost::prelude::SignProxyRequest; -use eyre::ErrReport; +use ethereum_consensus::crypto::bls::PublicKey as BlsPublicKey; use parking_lot::RwLock; use thiserror::Error; use tracing::{debug, error, info}; use crate::{ - crypto::{ - bls::{SignerBLS, BLS_DST_PREFIX}, - ecdsa::SignerECDSA, - }, + crypto::{bls::BLS_DST_PREFIX, ecdsa::SignerECDSA}, primitives::commitment::ECDSASignatureExt, }; +use super::SignerResult; + /// A client for interacting with CommitBoost. #[derive(Debug, Clone)] pub struct CommitBoostSigner { @@ -33,14 +32,17 @@ pub enum CommitBoostError { #[error("failed to sign constraint: {0}")] NoSignature(String), #[error("failed to create signer client: {0}")] - SignerClientError(#[from] ErrReport), + SignerClientError(#[from] SignerClientError), + #[error("error in commit boost signer: {0}")] + Other(#[from] eyre::Report), } #[allow(unused)] impl CommitBoostSigner { /// Create a new [CommitBoostSigner] instance - pub async fn new(signer_server_address: String, jwt: &str) -> Result { - let signer_client = SignerClient::new(signer_server_address, jwt)?; + pub async fn new(signer_server_address: String, jwt: &str) -> SignerResult { + let signer_client = + SignerClient::new(signer_server_address, jwt).map_err(CommitBoostError::Other)?; let client = Self { signer_client, @@ -60,7 +62,11 @@ impl CommitBoostSigner { ); let mut pubkeys_lock = this.pubkeys.write(); let mut proxy_ecdsa_lock = this.proxy_ecdsa.write(); - *pubkeys_lock = pubkeys.consensus; + *pubkeys_lock = pubkeys + .consensus + .into_iter() + .map(|k| BlsPublicKey::try_from(k.as_ref()).unwrap()) + .collect(); *proxy_ecdsa_lock = pubkeys.proxy_ecdsa; } Err(e) => { @@ -74,7 +80,8 @@ impl CommitBoostSigner { /// Get the consensus public key from the Commit-Boost signer. pub fn get_consensus_pubkey(&self) -> BlsPublicKey { - *self.pubkeys.read().first().expect("consensus pubkey loaded") + let pk = self.pubkeys.read().first().expect("consensus pubkey loaded").clone(); + BlsPublicKey::try_from(pk.as_ref()).expect("consensus pubkey is valid") } /// Get the proxy ECDSA public key from the Commit-Boost signer. @@ -107,17 +114,28 @@ impl CommitBoostSigner { } } -#[async_trait::async_trait] -impl SignerBLS for CommitBoostSigner { - async fn sign_commit_boost_root(&self, data: &[u8; 32]) -> eyre::Result { - let request = SignConsensusRequest { - pubkey: *self.pubkeys.read().first().expect("consensus pubkey loaded"), - object_root: *data, - }; +impl CommitBoostSigner { + /// Get the public key of the signer. + pub fn pubkey(&self) -> BlsPublicKey { + self.get_consensus_pubkey() + } + + /// Sign an object root with the Commit Boost domain. + pub async fn sign_commit_boost_root(&self, data: [u8; 32]) -> SignerResult { + // convert the pubkey from ethereum_consensus to commit-boost format + let pubkey = cb_common::signer::BlsPublicKey::from( + alloy::rpc::types::beacon::BlsPublicKey::from_slice(self.pubkey().as_ref()), + ); + + let request = SignConsensusRequest { pubkey, object_root: data }; debug!(?request, "Requesting signature from commit_boost"); - Ok(self.signer_client.request_consensus_signature(request).await?) + Ok(self + .signer_client + .request_consensus_signature(request) + .await + .map_err(CommitBoostError::SignerClientError)?) } } @@ -167,7 +185,7 @@ mod test { let mut data = [0u8; 32]; rng.fill(&mut data); - let signature = signer.sign_commit_boost_root(&data).await.unwrap(); + let signature = signer.sign_commit_boost_root(data).await.unwrap(); let sig = blst::min_pk::Signature::from_bytes(signature.as_ref()).unwrap(); let pubkey = signer.get_consensus_pubkey(); let bls_pubkey = blst::min_pk::PublicKey::from_bytes(pubkey.as_ref()).unwrap(); diff --git a/bolt-sidecar/src/signer/keystore.rs b/bolt-sidecar/src/signer/keystore.rs new file mode 100644 index 000000000..f0dda8a6d --- /dev/null +++ b/bolt-sidecar/src/signer/keystore.rs @@ -0,0 +1,305 @@ +//! An ERC-2335 keystore signer. + +use std::{ + ffi::OsString, + fmt::Debug, + fs::{self, DirEntry, ReadDir}, + io, + path::{Path, PathBuf}, +}; + +use alloy::rpc::types::beacon::constants::BLS_PUBLIC_KEY_BYTES_LEN; + +use lighthouse_bls::Keypair; +use lighthouse_eth2_keystore::Keystore; +use ssz::Encode; + +use crate::{builder::signature::compute_signing_root, crypto::bls::BLSSig, ChainConfig}; + +use super::SignerResult; + +pub const KEYSTORES_DEFAULT_PATH: &str = "keys"; + +#[derive(Debug, thiserror::Error)] +pub enum KeystoreError { + #[error("failed to read keystore directory: {0}")] + ReadFromDirectory(#[from] std::io::Error), + #[error("failed to read keystore from JSON file {0}: {1}")] + ReadFromJSON(PathBuf, String), + #[error("failed to decrypt keypair from JSON file {0} with the provided password: {1}")] + KeypairDecryption(PathBuf, String), + #[error("could not find private key associated to public key {0}")] + UnknownPublicKey(String), + #[error("invalid signature key length -- signature: {0} -- message: {1}")] + SignatureLength(String, String), +} + +#[derive(Clone)] +pub struct KeystoreSigner { + keypairs: Vec, + chain: ChainConfig, +} + +impl KeystoreSigner { + pub fn new(keys_path: Option<&str>, password: &[u8], chain: ChainConfig) -> SignerResult { + let keystores_paths = keystore_paths(keys_path)?; + let mut keypairs = Vec::with_capacity(keystores_paths.len()); + + for path in keystores_paths { + let keypair = Keystore::from_json_file(path.clone()); + let keypair = keypair + .map_err(|e| KeystoreError::ReadFromJSON(path.clone(), format!("{e:?}")))? + .decrypt_keypair(password) + .map_err(|e| KeystoreError::KeypairDecryption(path.clone(), format!("{e:?}")))?; + keypairs.push(keypair); + } + + Ok(Self { keypairs, chain }) + } + + pub fn sign_commit_boost_root( + &self, + root: [u8; 32], + public_key: [u8; BLS_PUBLIC_KEY_BYTES_LEN], + ) -> SignerResult { + self.sign_root(root, public_key, self.chain.commit_boost_domain()) + } + + fn sign_root( + &self, + root: [u8; 32], + public_key: [u8; BLS_PUBLIC_KEY_BYTES_LEN], + domain: [u8; 32], + ) -> SignerResult { + let sk = self + .keypairs + .iter() + // `as_ssz_bytes` returns the raw bytes we need + .find(|kp| kp.pk.as_ssz_bytes() == public_key.as_ref()) + .ok_or(KeystoreError::UnknownPublicKey(hex::encode(public_key)))?; + + let signing_root = compute_signing_root(root, domain); + + let sig = sk.sk.sign(signing_root.into()).as_ssz_bytes(); + let sig = BLSSig::try_from(sig.as_slice()) + .map_err(|e| KeystoreError::SignatureLength(hex::encode(sig), format!("{e:?}")))?; + + Ok(sig) + } +} + +impl Debug for KeystoreSigner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Signer") + .field( + "pubkeys", + &self.keypairs.iter().map(|kp| kp.pk.as_hex_string()).collect::>(), + ) + .finish() + } +} + +/// Returns the paths of all the keystore files provided an optional `keys_path`, which defaults to +/// `keys`. `keys_path` is a relative path from the root of this cargo project +/// We're expecting a directory structure like: +/// ${keys_path}/ +/// -- 0x1234.../validator.json +/// -- 0x5678.../validator.json +/// -- ... +fn keystore_paths(keys_path: Option<&str>) -> SignerResult> { + // Create the path to the keystore directory, starting from the root of the project + let keys_path = if let Some(keys_path) = keys_path { + Path::new(&keys_path).to_path_buf() + } else { + let project_root = env!("CARGO_MANIFEST_DIR"); + Path::new(project_root).join(keys_path.unwrap_or(KEYSTORES_DEFAULT_PATH)) + }; + + let json_extension = OsString::from("json"); + + let mut keystores_paths = vec![]; + // Iter over the `keys` directory + for entry in read_dir(keys_path)? { + let path = read_path(entry)?; + if path.is_dir() { + for entry in read_dir(path)? { + let path = read_path(entry)?; + if path.is_file() && path.extension() == Some(&json_extension) { + keystores_paths.push(path); + } + } + } + } + + Ok(keystores_paths) +} + +fn read_dir(path: PathBuf) -> SignerResult { + Ok(fs::read_dir(path).map_err(KeystoreError::ReadFromDirectory)?) +} + +fn read_path(entry: std::result::Result) -> SignerResult { + Ok(entry.map_err(KeystoreError::ReadFromDirectory)?.path()) +} + +#[cfg(test)] +mod tests { + use std::{fs::File, io::Write}; + + use blst::min_pk::SecretKey; + + use crate::{signer::local::LocalSigner, ChainConfig}; + + use super::{KeystoreSigner, KEYSTORES_DEFAULT_PATH}; + /// The str path of the root of the project + pub const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); + + #[test] + fn test_keystore_signer() { + // 0. Test data setup + + // Reference: https://eips.ethereum.org/EIPS/eip-2335#test-cases + let tests_keystore_json = [ + r#" + { + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "d2217fe5f3e9a1e34581ef8a78f7c9928e436d36dacc5e846690a5581e8ea484" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "06ae90d55fe0a6e9c5c3bc5b170827b2e5cce3929ed3f116c2811e6366dfe20f" + } + }, + "description": "This is a test keystore that uses scrypt to secure the secret.", + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/3141592653/589793238", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "version": 4 + } + "#, + r#" + { + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 32, + "c": 262144, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "8a9f5d9912ed7e75ea794bc5a89bca5f193721d30868ade6f73043c6ea6febf1" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "cee03fde2af33149775b7223e7845e4fb2c8ae1792e5f99fe9ecf474cc8c16ad" + } + }, + "description": "This is a test keystore that uses PBKDF2 to secure the secret.", + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + } + "#, + ]; + + // Reference: https://eips.ethereum.org/EIPS/eip-2335#test-cases + let keystore_password = r#"𝔱𝔢𝔰𝔱𝔭𝔞𝔰𝔰𝔴𝔬𝔯𝔡🔑"#; + let keystore_public_key = "0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07"; + let keystore_publlc_key_bytes: [u8; 48] = [ + 0x96, 0x12, 0xd7, 0xa7, 0x27, 0xc9, 0xd0, 0xa2, 0x2e, 0x18, 0x5a, 0x1c, 0x76, 0x84, + 0x78, 0xdf, 0xe9, 0x19, 0xca, 0xda, 0x92, 0x66, 0x98, 0x8c, 0xb3, 0x23, 0x59, 0xc1, + 0x1f, 0x2b, 0x7b, 0x27, 0xf4, 0xae, 0x40, 0x40, 0x90, 0x23, 0x82, 0xae, 0x29, 0x10, + 0xc1, 0x5e, 0x2b, 0x42, 0x0d, 0x07, + ]; + let keystore_secret_key = + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; + let chain_config = ChainConfig::mainnet(); + + // 1. Create a temp directory with the keystore and create a signer from it + + let path_str = format!("{}/{}", CARGO_MANIFEST_DIR, KEYSTORES_DEFAULT_PATH); + + for test_keystore_json in tests_keystore_json { + let tmp_dir = tempfile::TempDir::with_prefix_in( + "0xdeadbeefdeadbeefdeadbeefdeadbeef", + path_str.clone(), + ) + .expect("to create temp dir"); + + // NOTE: it is sufficient to create a temp dir, then we can create a file as usual and + // it will be dropped correctly + let mut tmp_file = File::create_new(tmp_dir.path().join("voting-keystore.json")) + .expect("to create new file"); + + tmp_file.write_all(test_keystore_json.as_bytes()).expect("to write to temp file"); + + for entry in tmp_dir.path().read_dir().expect("to read tmp dir") { + let mut path = entry.expect("to read entry").path(); + println!("inside loop: {:?}", path); + let extenstion = path + .extension() + .expect("to get extension") + .to_str() + .expect("to convert to str"); + + if extenstion.contains("tmp") { + path.set_extension("json"); + println!("path: {:?}", path); + break; + } + } + + let keystore_signer = + KeystoreSigner::new(None, keystore_password.as_bytes(), chain_config) + .expect("to create keystore signer"); + + assert_eq!(keystore_signer.keypairs.len(), 1); + assert_eq!( + keystore_signer.keypairs.first().expect("to get keypair").pk.to_string(), + keystore_public_key + ); + + // 2. Sign a message with the signer and check the signature + + let keystore_sk_bls = SecretKey::from_bytes( + hex::decode(keystore_secret_key).expect("to decode secret key").as_slice(), + ) + .expect("to create secret key"); + + let local_signer = LocalSigner::new(keystore_sk_bls, chain_config); + + let sig_local = local_signer.sign_commit_boost_root([0; 32]).expect("to sign message"); + let sig_keystore = keystore_signer + .sign_commit_boost_root([0; 32], keystore_publlc_key_bytes) + .expect("to sign message"); + assert_eq!(sig_local, sig_keystore); + } + } +} diff --git a/bolt-sidecar/src/signer/local.rs b/bolt-sidecar/src/signer/local.rs new file mode 100644 index 000000000..2ce970594 --- /dev/null +++ b/bolt-sidecar/src/signer/local.rs @@ -0,0 +1,138 @@ +use std::fmt::Debug; + +use blst::{min_pk::Signature, BLST_ERROR}; +use ethereum_consensus::{crypto::PublicKey as ClPublicKey, deneb::compute_signing_root}; + +use crate::{crypto::bls::BLSSig, ChainConfig}; +pub use blst::min_pk::SecretKey; + +use super::SignerResult; + +/// The BLS Domain Separator used in Ethereum 2.0. +pub const BLS_DST_PREFIX: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + +#[derive(Debug, thiserror::Error)] +pub enum LocalSignerError { + #[error("failed to compute signing root: {0}")] + SigningRootComputation(#[from] ethereum_consensus::error::Error), + #[error("invalid signature: {0}")] + InvalidSignature(String), +} + +/// A BLS signer that can sign any type that implements the [`SignableBLS`] trait. +#[derive(Clone)] +pub struct LocalSigner { + chain: ChainConfig, + key: SecretKey, +} + +impl Debug for LocalSigner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Signer") + .field("pubkey", &self.pubkey()) + .field("chain", &self.chain.name()) + .finish() + } +} + +impl LocalSigner { + /// Create a new signer with the given BLS secret key. + pub fn new(key: SecretKey, chain: ChainConfig) -> Self { + Self { key, chain } + } + + /// Get the public key of the signer. + pub fn pubkey(&self) -> ClPublicKey { + let pk = self.key.sk_to_pk(); + ClPublicKey::try_from(pk.to_bytes().as_ref()).unwrap() + } + + /// Sign an SSZ object root with the Application Builder domain. + pub fn sign_application_builder_root(&self, root: [u8; 32]) -> SignerResult { + self.sign_root(root, self.chain.application_builder_domain()) + } + + /// Sign an SSZ object root with the Commit Boost domain. + pub fn sign_commit_boost_root(&self, root: [u8; 32]) -> SignerResult { + self.sign_root(root, self.chain.commit_boost_domain()) + } + + /// Sign an SSZ object root with the given domain. + pub fn sign_root(&self, root: [u8; 32], domain: [u8; 32]) -> SignerResult { + let signing_root = compute_signing_root(&root, domain) + .map_err(LocalSignerError::SigningRootComputation)?; + let sig = self.key.sign(signing_root.as_slice(), BLS_DST_PREFIX, &[]); + Ok(BLSSig::from_slice(&sig.to_bytes())) + } + + /// Verify the signature with the public key of the signer using the Application Builder domain. + pub fn verify_application_builder_root( + &self, + root: [u8; 32], + signature: &Signature, + ) -> SignerResult<()> { + self.verify_root(root, signature, self.chain.application_builder_domain()) + } + + /// Verify the signature with the public key of the signer using the Commit Boost domain. + pub fn verify_commit_boost_root( + &self, + root: [u8; 32], + signature: &Signature, + ) -> SignerResult<()> { + self.verify_root(root, signature, self.chain.commit_boost_domain()) + } + + /// Verify the signature of the object with the given public key. + pub fn verify_root( + &self, + root: [u8; 32], + signature: &Signature, + domain: [u8; 32], + ) -> SignerResult<()> { + let signing_root = compute_signing_root(&root, domain) + .map_err(LocalSignerError::SigningRootComputation)?; + let pk = blst::min_pk::PublicKey::from_bytes(self.pubkey().as_ref()).unwrap(); + + let res = signature.verify(true, signing_root.as_ref(), BLS_DST_PREFIX, &[], &pk, true); + if res == BLST_ERROR::BLST_SUCCESS { + Ok(()) + } else { + Err(LocalSignerError::InvalidSignature(format!("{res:?}")))? + } + } +} + +#[cfg(test)] +impl LocalSigner { + /// Create a signer with a random BLS key configured for Mainnet for testing. + pub fn random() -> Self { + use crate::common::BlsSecretKeyWrapper; + + Self { key: BlsSecretKeyWrapper::random().0, chain: ChainConfig::mainnet() } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + crypto::bls::SignableBLS, signer::local::LocalSigner, test_util::TestSignableData, + }; + + use rand::Rng; + + #[tokio::test] + async fn test_bls_signer() { + let signer = LocalSigner::random(); + + // Generate random data for the test + let mut rng = rand::thread_rng(); + let mut data = [0u8; 32]; + rng.fill(&mut data); + let msg = TestSignableData { data }; + + let signature = signer.sign_commit_boost_root(msg.digest()).unwrap(); + let sig = blst::min_pk::Signature::from_bytes(signature.as_ref()).unwrap(); + assert!(signer.verify_commit_boost_root(msg.digest(), &sig).is_ok()); + } +} diff --git a/bolt-sidecar/src/signer/mod.rs b/bolt-sidecar/src/signer/mod.rs new file mode 100644 index 000000000..3d74c2629 --- /dev/null +++ b/bolt-sidecar/src/signer/mod.rs @@ -0,0 +1,32 @@ +pub mod commit_boost; +use commit_boost::CommitBoostSigner; + +pub mod keystore; +use keystore::KeystoreSigner; + +pub mod local; +use local::LocalSigner; + +/// Signer for BLS signatures. +#[derive(Debug, Clone)] +pub enum SignerBLS { + /// Local signer with a BLS secret key. + Local(LocalSigner), + /// Signer from Commit-Boost. + CommitBoost(CommitBoostSigner), + /// Signer consisting of multiple keypairs loaded from ERC-2335 keystores files. + Keystore(KeystoreSigner), +} + +/// Error in the signer. +#[derive(Debug, thiserror::Error)] +pub enum SignerError { + #[error("local signer error: {0}")] + LocalSigner(#[from] local::LocalSignerError), + #[error("commit boost signer error: {0}")] + CommitBoost(#[from] commit_boost::CommitBoostError), + #[error("keystore signer error: {0}")] + Keystore(#[from] keystore::KeystoreError), +} + +pub type SignerResult = std::result::Result; diff --git a/bolt-sidecar/src/state/execution.rs b/bolt-sidecar/src/state/execution.rs index a0dbc4ae6..29073c299 100644 --- a/bolt-sidecar/src/state/execution.rs +++ b/bolt-sidecar/src/state/execution.rs @@ -13,7 +13,7 @@ use tracing::{debug, trace}; use crate::{ builder::BlockTemplate, common::{calculate_max_basefee, max_transaction_cost, validate_transaction}, - config::Limits, + config::limits::LimitsOpts, primitives::{AccountState, CommitmentRequest, SignedConstraints, Slot}, }; @@ -154,7 +154,7 @@ pub struct ExecutionState { /// The chain ID of the chain (constant). chain_id: u64, /// The limits set for the sidecar. - limits: Limits, + limits: LimitsOpts, /// The KZG settings for validating blobs. kzg_settings: EnvKzgSettings, /// The state fetcher client. @@ -184,7 +184,7 @@ impl Default for ValidationParams { impl ExecutionState { /// Creates a new state with the given client, initializing the /// basefee and head block number. - pub async fn new(client: C, limits: Limits) -> Result { + pub async fn new(client: C, limits: LimitsOpts) -> Result { let (basefee, blob_basefee, block_number, chain_id) = tokio::try_join!( client.get_basefee(None), client.get_blob_basefee(None), @@ -527,7 +527,7 @@ pub struct StateUpdate { #[cfg(test)] mod tests { - use crate::builder::template::StateDiff; + use crate::{builder::template::StateDiff, signer::local::LocalSigner}; use std::{num::NonZero, str::FromStr, time::Duration}; use alloy::{ @@ -542,7 +542,7 @@ mod tests { use reth_primitives::constants::GWEI_TO_WEI; use crate::{ - crypto::{bls::Signer, SignableBLS}, + crypto::SignableBLS, primitives::{ConstraintsMessage, SignedConstraints}, state::fetcher, test_util::{create_signed_commitment_request, default_test_transaction, launch_anvil}, @@ -557,7 +557,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); @@ -582,7 +582,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); @@ -620,7 +620,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); @@ -669,7 +669,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); @@ -699,11 +699,11 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); - let signer = Signer::random(); + let signer = LocalSigner::random(); // initialize the state by updating the head once let slot = client.get_head().await?; @@ -761,7 +761,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let limits = Limits { + let limits = LimitsOpts { max_commitments_per_slot: NonZero::new(10).unwrap(), max_committed_gas_per_slot: NonZero::new(5_000_000).unwrap(), min_priority_fee: NonZero::new(200000000).unwrap(), // 0.2 gwei @@ -800,7 +800,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let limits = Limits { + let limits = LimitsOpts { max_commitments_per_slot: NonZero::new(10).unwrap(), max_committed_gas_per_slot: NonZero::new(5_000_000).unwrap(), min_priority_fee: NonZero::new(2000000000).unwrap(), @@ -831,7 +831,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let limits = Limits { + let limits = LimitsOpts { max_commitments_per_slot: NonZero::new(10).unwrap(), max_committed_gas_per_slot: NonZero::new(5_000_000).unwrap(), min_priority_fee: NonZero::new(2 * GWEI_TO_WEI as u128).unwrap(), @@ -873,7 +873,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let limits = Limits { + let limits = LimitsOpts { max_commitments_per_slot: NonZero::new(10).unwrap(), max_committed_gas_per_slot: NonZero::new(5_000_000).unwrap(), min_priority_fee: NonZero::new(2 * GWEI_TO_WEI as u128).unwrap(), @@ -920,7 +920,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let limits = Limits { + let limits = LimitsOpts { max_commitments_per_slot: NonZero::new(10).unwrap(), max_committed_gas_per_slot: NonZero::new(5_000_000).unwrap(), min_priority_fee: NonZero::new(2 * GWEI_TO_WEI as u128).unwrap(), @@ -963,7 +963,7 @@ mod tests { let client = StateClient::new(anvil.endpoint_url()); let provider = ProviderBuilder::new().on_http(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); @@ -985,7 +985,7 @@ mod tests { assert!(state.validate_request(&mut request).await.is_ok()); - let bls_signer = Signer::random(); + let bls_signer = LocalSigner::random(); let message = ConstraintsMessage::build(Default::default(), inclusion_request); let signature = bls_signer.sign_commit_boost_root(message.digest()).unwrap(); let signed_constraints = SignedConstraints { message, signature }; @@ -1016,7 +1016,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); @@ -1033,7 +1033,7 @@ mod tests { assert!(state.validate_request(&mut request).await.is_ok()); - let bls_signer = Signer::random(); + let bls_signer = LocalSigner::random(); let message = ConstraintsMessage::build(Default::default(), inclusion_request); let signature = bls_signer.sign_commit_boost_root(message.digest()).unwrap(); let signed_constraints = SignedConstraints { message, signature }; @@ -1058,7 +1058,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let limits: Limits = Limits { + let limits: LimitsOpts = LimitsOpts { max_commitments_per_slot: NonZero::new(10).unwrap(), max_committed_gas_per_slot: NonZero::new(5_000_000).unwrap(), min_priority_fee: NonZero::new(1000000000).unwrap(), @@ -1080,7 +1080,7 @@ mod tests { assert!(state.validate_request(&mut request).await.is_ok()); - let bls_signer = Signer::random(); + let bls_signer = LocalSigner::random(); let message = ConstraintsMessage::build(Default::default(), inclusion_request); let signature = bls_signer.sign_commit_boost_root(message.digest()).unwrap(); let signed_constraints = SignedConstraints { message, signature }; @@ -1109,7 +1109,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); @@ -1136,7 +1136,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); @@ -1166,7 +1166,7 @@ mod tests { let anvil = launch_anvil(); let client = StateClient::new(anvil.endpoint_url()); - let mut state = ExecutionState::new(client.clone(), Limits::default()).await?; + let mut state = ExecutionState::new(client.clone(), LimitsOpts::default()).await?; let sender = anvil.addresses().first().unwrap(); let sender_pk = anvil.keys().first().unwrap(); diff --git a/bolt-sidecar/src/test_util.rs b/bolt-sidecar/src/test_util.rs index d62429434..b4dbf40c3 100644 --- a/bolt-sidecar/src/test_util.rs +++ b/bolt-sidecar/src/test_util.rs @@ -11,19 +11,21 @@ use alloy::{ }; use alloy_node_bindings::{Anvil, AnvilInstance}; use blst::min_pk::SecretKey; +use clap::Parser; use ethereum_consensus::crypto::{PublicKey, Signature}; use rand::Rng; -use reth_primitives::PooledTransactionsElement; use secp256k1::Message; use tracing::warn; use crate::{ - crypto::{bls::Signer as BlsSigner, ecdsa::SignableECDSA, SignableBLS}, + common::{BlsSecretKeyWrapper, JwtSecretConfig}, + crypto::{ecdsa::SignableECDSA, SignableBLS}, primitives::{ CommitmentRequest, ConstraintsMessage, DelegationMessage, FullTransaction, InclusionRequest, RevocationMessage, SignedConstraints, SignedDelegation, SignedRevocation, }, - Config, + signer::local::LocalSigner, + ChainConfig, Opts, }; /// The URL of the test execution client HTTP API. @@ -76,25 +78,30 @@ pub(crate) async fn try_get_beacon_api_url() -> Option<&'static str> { /// - The default values for the remaining configuration fields. /// /// If any of the above values can't be found, the function will return `None`. -pub(crate) async fn get_test_config() -> Option { +pub(crate) async fn get_test_config() -> Option { + std::env::set_var("BOLT_SIDECAR_PRIVATE_KEY", BlsSecretKeyWrapper::random().to_string()); + let _ = dotenvy::dotenv(); + let mut opts = Opts::parse(); + let Some(jwt) = std::env::var("ENGINE_JWT").ok() else { warn!("ENGINE_JWT not found in environment variables"); return None; }; - let execution = try_get_execution_api_url().await?; - let beacon = try_get_beacon_api_url().await?; - let engine = try_get_engine_api_url().await?; - - Some(Config { - execution_api_url: execution.parse().ok()?, - engine_api_url: engine.parse().ok()?, - beacon_api_url: beacon.parse().ok()?, - jwt_hex: jwt, - ..Default::default() - }) + if let Some(url) = try_get_execution_api_url().await { + opts.execution_api_url = url.parse().expect("valid URL"); + } + if let Some(url) = try_get_beacon_api_url().await { + opts.beacon_api_url = url.parse().expect("valid URL"); + } + if let Some(url) = try_get_engine_api_url().await { + opts.engine_api_url = url.parse().expect("valid URL"); + } + opts.jwt_hex = JwtSecretConfig(jwt); + + Some(opts) } /// Launch a local instance of the Anvil test chain. @@ -157,8 +164,8 @@ pub(crate) async fn create_signed_commitment_request( for tx in txs { let tx_signed = tx.clone().build(&wallet).await?; let raw_encoded = tx_signed.encoded_2718(); - let tx_pooled = PooledTransactionsElement::decode_enveloped(&mut raw_encoded.as_slice())?; - full_txs.push(FullTransaction::from(tx_pooled)); + let full_tx = FullTransaction::decode_enveloped(raw_encoded.as_slice())?; + full_txs.push(full_tx); } let mut request = InclusionRequest { txs: full_txs, slot, signature: None, signer: None }; @@ -188,11 +195,11 @@ fn random_constraints(count: usize) -> Vec { } #[tokio::test] -async fn generate_test_data() { - let signer = BlsSigner::random(); +async fn generate_test_data_kurtosis() { + let signer = LocalSigner::new(BlsSecretKeyWrapper::random().0, ChainConfig::kurtosis(0, 0)); let pk = signer.pubkey(); - println!("Validator Public Key: {}", hex::encode(pk.to_bytes())); + println!("Validator Public Key: {}", hex::encode(pk.as_ref())); // Generate a delegatee's BLS secret key and public key let delegatee_ikm: [u8; 32] = rand::thread_rng().gen(); @@ -201,35 +208,36 @@ async fn generate_test_data() { let delegatee_pk = delegatee_sk.sk_to_pk(); // Prepare a Delegation message - let delegation_msg = DelegationMessage { - validator_pubkey: PublicKey::try_from(pk.to_bytes().as_slice()) - .expect("Failed to convert validator public key"), - delegatee_pubkey: PublicKey::try_from(delegatee_pk.to_bytes().as_slice()) + let delegation_msg = DelegationMessage::new( + pk.clone(), + PublicKey::try_from(delegatee_pk.to_bytes().as_slice()) .expect("Failed to convert delegatee public key"), - }; + ); let digest = SignableBLS::digest(&delegation_msg); // Sign the Delegation message let delegation_signature = signer.sign_commit_boost_root(digest).unwrap(); + let blst_sig = blst::min_pk::Signature::from_bytes(delegation_signature.as_ref()) + .expect("Failed to convert delegation signature"); + let consensus_sig = Signature::try_from(delegation_signature.as_ref()) + .expect("Failed to convert delegation signature"); + + // Sanity check: verify the signature + assert!(signer.verify_commit_boost_root(digest, &blst_sig).is_ok()); // Create SignedDelegation - let signed_delegation = SignedDelegation { - message: delegation_msg, - signature: Signature::try_from(delegation_signature.as_ref()) - .expect("Failed to convert delegation signature"), - }; + let signed_delegation = SignedDelegation { message: delegation_msg, signature: consensus_sig }; // Output SignedDelegation println!("{}", serde_json::to_string_pretty(&signed_delegation).unwrap()); // Prepare a revocation message - let revocation_msg = RevocationMessage { - validator_pubkey: PublicKey::try_from(pk.to_bytes().as_slice()) - .expect("Failed to convert validator public key"), - delegatee_pubkey: PublicKey::try_from(delegatee_pk.to_bytes().as_slice()) + let revocation_msg = RevocationMessage::new( + pk.clone(), + PublicKey::try_from(delegatee_pk.to_bytes().as_slice()) .expect("Failed to convert delegatee public key"), - }; + ); let digest = SignableBLS::digest(&revocation_msg); @@ -249,13 +257,7 @@ async fn generate_test_data() { let transactions = random_constraints(1); // Prepare a ConstraintsMessage - let constraints_msg = ConstraintsMessage { - pubkey: PublicKey::try_from(pk.to_bytes().as_slice()) - .expect("Failed to convert validator public key"), - slot: 32, - top: true, - transactions, - }; + let constraints_msg = ConstraintsMessage { pubkey: pk, slot: 32, top: true, transactions }; let digest = SignableBLS::digest(&constraints_msg); diff --git a/builder/builder/builder.go b/builder/builder/builder.go index 65c65f144..e765b4bad 100644 --- a/builder/builder/builder.go +++ b/builder/builder/builder.go @@ -11,6 +11,7 @@ import ( "math/big" "net/http" _ "os" + "slices" "strings" "sync" "time" @@ -362,32 +363,19 @@ func (b *Builder) subscribeToRelayForConstraints(relayBaseEndpoint string) error } for _, constraint := range constraintsSigned { - // TODO: re-enable this once testing the devnet has ended - // oneValidSignature := false - // Check if the signature is valid against any of the authorized pubkeys - // for _, pubkey := range b.slotConstraintsPubkeys { - // valid, err := constraint.VerifySignature(pubkey, b.GetConstraintsDomain()) - // if err != nil || !valid { - // log.Error("Failed to verify constraint signature", "err", err) - // continue - // } - // - // oneValidSignature = true - // } - - // TODO: remove this once testing the devnet has ended, we should check for authorized keys + // Check that the constraints pubkey is authorized to sign constraints + if !slices.Contains(b.slotConstraintsPubkeys, constraint.Message.Pubkey) { + log.Warn("Received constraint from unauthorized pubkey", "pubkey", constraint.Message.Pubkey) + continue + } + + // Verify the signature of the constraints message valid, err := constraint.VerifySignature(constraint.Message.Pubkey, b.GetConstraintsDomain()) if err != nil || !valid { log.Error("Failed to verify constraint signature", "err", err) continue } - // TODO: re-enable this once testing the devnet has ended - // If there is no valid signature, continue with the next constraint - // if !oneValidSignature { - // continue - // } - decodedConstraints, err := DecodeConstraints(constraint) if err != nil { log.Error("Failed to decode constraint: ", err) diff --git a/builder/builder/builder_test.go b/builder/builder/builder_test.go index c4aa35868..07d7d1fc4 100644 --- a/builder/builder/builder_test.go +++ b/builder/builder/builder_test.go @@ -465,6 +465,41 @@ func TestSubscribeProposerConstraints(t *testing.T) { } } +func TestDeserializeConstraints(t *testing.T) { + jsonStr := `[ + { + "message": { + "pubkey": "0xa695ad325dfc7e1191fbc9f186f58eff42a634029731b18380ff89bf42c464a42cb8ca55b200f051f57f1e1893c68759", + "slot": 32, + "top": true, + "transactions": [ + "0x02f86c870c72dd9d5e883e4d0183408f2382520894d2e2adf7177b7a8afddbc12d1634cf23ea1a71020180c001a08556dcfea479b34675db3fe08e29486fe719c2b22f6b0c1741ecbbdce4575cc6a01cd48009ccafd6b9f1290bbe2ceea268f94101d1d322c787018423ebcbc87ab4" + ] + }, + "signature": "0xb8d50ee0d4b269db3d4658c1dac784d273a4160d769e16dce723a9684c390afe5865348416b3bf0f1a4f47098bec9024135d0d95f08bed18eb577a3d8a67f5dc78b13cc62515e280786a73fb267d35dfb7ab46a25ac29bf5bc2fa5b07b3e07a6" + } + ]` + + var constraints types.SignedConstraintsList + err := json.Unmarshal([]byte(jsonStr), &constraints) + require.NoError(t, err) + + jsonStr = `{ + "message": { + "pubkey":"0xb3cd9c9e59730c210bf9b76959bf11e20bb05cf47cfefdcaab74bc17c369d6daefe1219c2b94d743ffd27988edf24b90", + "slot":183, + "top":false, + "transactions": [ + "0xf8678085019dc6838082520894deaddeaddeaddeaddeaddeaddeaddeaddeaddead04808360306ca0fde9bdf8f1a9fefef7538490242afb21a0160cf19f1686c7b9bddb45de973b62a0318411f2c959d3e6a25434f99850a0eaa6beb617f7a2dbc9683dccded7bd4b10" + ] + }, + "signature": "0xaa8a47c6398d5862b56d1bbb308352c65e57e62b0bfdda39a36db7fff3a256c3c7066b219a15a013aae5303f42b6f07b025f34ed6d899e6172fec20d40c4ffebeb50f5d0b75a303c1cc916574c3e0f29d53b2211d28234f430fffce62b4ee554" + }` + + err = json.Unmarshal([]byte(jsonStr), &constraints) + require.NoError(t, err) +} + func sseConstraintsHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") diff --git a/builder/core/types/constraints.go b/builder/core/types/constraints.go index 39c7caeca..ea16c6128 100644 --- a/builder/core/types/constraints.go +++ b/builder/core/types/constraints.go @@ -13,6 +13,7 @@ import ( capellaSpec "github.com/attestantio/go-eth2-client/spec/capella" denebSpec "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-builder-client/api/deneb" v1 "github.com/attestantio/go-builder-client/api/v1" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ethereum/go-ethereum/common" @@ -147,10 +148,12 @@ func (v *VersionedSubmitBlockRequestWithProofs) MarshalJSON() ([]byte, error) { ExecutionPayload *denebSpec.ExecutionPayload `json:"execution_payload"` Signature phase0.BLSSignature `json:"signature"` Proofs *InclusionProof `json:"proofs"` + BlobsBundle *deneb.BlobsBundle `json:"blobs_bundle"` }{ Message: v.Deneb.Message, ExecutionPayload: v.Deneb.ExecutionPayload, Signature: v.Deneb.Signature, + BlobsBundle: v.Deneb.BlobsBundle, Proofs: v.Proofs, }) } diff --git a/builder/miner/worker.go b/builder/miner/worker.go index 98f02a9e8..2a69317c4 100644 --- a/builder/miner/worker.go +++ b/builder/miner/worker.go @@ -1176,7 +1176,7 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac } } else { // No more pool tx left, we can add the unindexed ones if available - if len(constraintsOrderedByNonceAndHashDesc) == 0 { + if len(constraintsRecoveredOrderedByNonceAndHashDesc) == 0 { // To recap, this means: // 1. there are no more pool tx left // 2. there are no more constraints diff --git a/justfile b/justfile index bd18c1b75..54827ebcb 100644 --- a/justfile +++ b/justfile @@ -43,61 +43,61 @@ _restart-sidecar: inspect: kurtosis enclave inspect bolt-devnet +bash service: + @id=$(docker ps -n 100 | grep {{ service }} | awk -F' ' '{print $1}') && \ + docker exec -it $id bash + +log service: + @id=$(docker ps -n 100 | grep {{ service }} | awk -F' ' '{print $1}') && \ + docker logs -f $id + +dump service: + @id=$(docker ps -n 100 | grep {{ service }} | awk -F' ' '{print $1}') && \ + docker logs $id 2>&1 | tee {{ service }}_dump.log + # show the logs for the bolt devnet relay relay-logs: - @id=$(docker ps -n 100 | grep helix-relay | awk -F' ' '{print $1}') && \ - docker logs -f $id + @just log helix-relay # show the logs for the bolt devnet builder builder-logs: - @id=$(docker ps -n 100 | grep bolt-builder | awk -F' ' '{print $1}') && \ - docker logs -f $id + @just log bolt-builder # show the logs for the bolt devnet bolt-boost sidecar boost-logs: - @id=$(docker ps -n 100 | grep bolt-boost | awk -F' ' '{print $1}') && \ - docker logs -f $id + @just log bolt-boost # show the logs for the bolt devnet mev-boost sidecar mev-boost-logs: - @id=$(docker ps -n 100 | grep bolt-mev-boost | awk -F' ' '{print $1}') && \ - docker logs -f $id + @just log bolt-mev-boost # show the logs for the bolt devnet bolt-sidecar sidecar-logs: - @id=$(docker ps -n 100 | grep sidecar | awk -F' ' '{print $1}') && \ - docker logs -f $id + @just log sidecar # show the logs for the bolt devnet for beacon node beacon-logs: - @id=$(docker ps -n 100 | grep 'cl-1-lighthouse-geth' | awk -F' ' '{print $1}') && \ - docker logs -f $id + @just log 'cl-1-lighthouse-geth' # show the logs for the bolt devnet for beacon node beacon-dump: - @id=$(docker ps -n 100 | grep 'cl-1-lighthouse-geth' | awk -F' ' '{print $1}') && \ - docker logs $id 2>&1 | tee beacon_dump.log + @just dump 'cl-1-lighthouse-geth' # show the logs for the bolt devnet relay relay-dump: - @id=$(docker ps -n 100 | grep mev-relay-api | awk -F' ' '{print $1}') && \ - docker logs $id 2>&1 | tee relay_dump.log + @just dump mev-relay-api # show the logs for the bolt devnet builder builder-dump: - @id=$(docker ps -n 100 | grep bolt-builder | awk -F' ' '{print $1}') && \ - docker logs $id 2>&1 | tee builder_dump.log + @just dump bolt-builder # show the logs for the bolt devnet mev-boost sidecar boost-dump: - @id=$(docker ps -n 100 | grep bolt-mev-boost | awk -F' ' '{print $1}') && \ - docker logs $id 2>&1 | tee boost_dump.log + @just dump bolt-mev-boost # show the logs for the bolt devnet bolt-sidecar sidecar-dump: - @id=$(docker ps -n 100 | grep sidecar | awk -F' ' '{print $1}') && \ - docker logs $id 2>&1 | tee sidecar_dump.log - + @just dump sidecar # show the logs for the bolt devnet builder kill-builder: @@ -153,23 +153,23 @@ build-images: # build the docker image for the bolt builder _build-builder: - cd builder && docker buildx build -t ghcr.io/chainbound/bolt-builder:0.1.0 . --load + cd builder && docker build -t ghcr.io/chainbound/bolt-builder:0.1.0 . --load # build the docker image for the bolt relay _build-relay: - cd mev-boost-relay && docker buildx build -t ghcr.io/chainbound/bolt-relay:0.1.0 . --load + cd mev-boost-relay && docker build -t ghcr.io/chainbound/bolt-relay:0.1.0 . --load # build the docker image for the bolt sidecar _build-sidecar: - cd bolt-sidecar && docker buildx build -t ghcr.io/chainbound/bolt-sidecar:0.1.0 . --load + cd bolt-sidecar && docker build -t ghcr.io/chainbound/bolt-sidecar:0.1.0 . --load # build the docker image for the bolt mev-boost sidecar _build-mevboost: - cd mev-boost && docker buildx build -t ghcr.io/chainbound/bolt-mev-boost:0.1.0 . --load + cd mev-boost && docker build -t ghcr.io/chainbound/bolt-mev-boost:0.1.0 . --load # build the docker image for bolt-boost _build-bolt-boost: - cd bolt-boost && docker buildx build -t ghcr.io/chainbound/bolt-boost:0.1.0 . --load + cd bolt-boost && docker build -t ghcr.io/chainbound/bolt-boost:0.1.0 . --load # deploy the bolt sidecar to the dev server deploy-sidecar-dev: diff --git a/mev-boost/server/backend.go b/mev-boost/server/backend.go index 631a587a4..8e4979dee 100644 --- a/mev-boost/server/backend.go +++ b/mev-boost/server/backend.go @@ -4,11 +4,18 @@ const ( // Router paths pathStatus = "/eth/v1/builder/status" pathRegisterValidator = "/eth/v1/builder/validators" - pathSubmitConstraint = "/eth/v1/builder/constraints" pathGetHeader = "/eth/v1/builder/header/{slot:[0-9]+}/{parent_hash:0x[a-fA-F0-9]+}/{pubkey:0x[a-fA-F0-9]+}" pathGetHeaderWithProofs = "/eth/v1/builder/header_with_proofs/{slot:[0-9]+}/{parent_hash:0x[a-fA-F0-9]+}/{pubkey:0x[a-fA-F0-9]+}" pathGetPayload = "/eth/v1/builder/blinded_blocks" + // Constraints namespace paths + // Ref: https://docs.boltprotocol.xyz/api/builder#constraints + pathSubmitConstraint = "/constraints/v1/builder/constraints" + // Ref: https://docs.boltprotocol.xyz/api/builder#delegate + pathDelegate = "/constraints/v1/builder/delegate" + // Ref: https://docs.boltprotocol.xyz/api/builder#revoke + pathRevoke = "/constraints/v1/builder/revoke" + // // Relay Monitor paths // pathAuctionTranscript = "/monitor/v1/transcript" ) diff --git a/mev-boost/server/constraints.go b/mev-boost/server/constraints.go index 4f5914aa1..817ab1692 100644 --- a/mev-boost/server/constraints.go +++ b/mev-boost/server/constraints.go @@ -7,22 +7,25 @@ import ( lru "github.com/hashicorp/golang-lru/v2" ) -type BatchedSignedConstraints = []*SignedConstraints +type ( + BatchedSignedConstraints = []*SignedConstraints + HashToTransactionDecoded = map[gethCommon.Hash]*types.Transaction +) +// SignedConstraints represents the signed constraints. +// Reference: https://docs.boltprotocol.xyz/api/builder type SignedConstraints struct { Message ConstraintsMessage `json:"message"` Signature phase0.BLSSignature `json:"signature"` } +// ConstraintsMessage represents the constraints message. +// Reference: https://docs.boltprotocol.xyz/api/builder type ConstraintsMessage struct { - ValidatorIndex uint64 `json:"validator_index"` - Slot uint64 `json:"slot"` - Constraints []*Constraint `json:"constraints"` -} - -type Constraint struct { - Tx Transaction `json:"tx"` - Index *uint64 `json:"index"` + Pubkey phase0.BLSPubKey `json:"pubkey"` + Slot uint64 `json:"slot"` + Top bool `json:"top"` + Transactions []Transaction `json:"transactions"` } func (s *SignedConstraints) String() string { @@ -33,29 +36,25 @@ func (m *ConstraintsMessage) String() string { return JSONStringify(m) } -func (c *Constraint) String() string { - return JSONStringify(c) -} - -// ConstraintCache is a cache for constraints. -type ConstraintCache struct { +// ConstraintsCache is a cache for constraints. +type ConstraintsCache struct { // map of slots to all constraints for that slot - constraints *lru.Cache[uint64, map[gethCommon.Hash]*Constraint] + constraints *lru.Cache[uint64, map[gethCommon.Hash]*Transaction] } -// NewConstraintCache creates a new constraint cache. +// NewConstraintsCache creates a new constraint cache. // cap is the maximum number of slots to store constraints for. -func NewConstraintCache(cap int) *ConstraintCache { - constraints, _ := lru.New[uint64, map[gethCommon.Hash]*Constraint](cap) - return &ConstraintCache{ +func NewConstraintsCache(cap int) *ConstraintsCache { + constraints, _ := lru.New[uint64, map[gethCommon.Hash]*Transaction](cap) + return &ConstraintsCache{ constraints: constraints, } } // AddInclusionConstraint adds an inclusion constraint to the cache at the given slot for the given transaction. -func (c *ConstraintCache) AddInclusionConstraint(slot uint64, tx Transaction, index *uint64) error { +func (c *ConstraintsCache) AddInclusionConstraint(slot uint64, tx Transaction, index *uint64) error { if _, exists := c.constraints.Get(slot); !exists { - c.constraints.Add(slot, make(map[gethCommon.Hash]*Constraint)) + c.constraints.Add(slot, make(map[gethCommon.Hash]*Transaction)) } // parse transaction to get its hash and store it in the cache @@ -67,52 +66,73 @@ func (c *ConstraintCache) AddInclusionConstraint(slot uint64, tx Transaction, in } m, _ := c.constraints.Get(slot) - m[parsedTx.Hash()] = &Constraint{ - Tx: tx, - Index: index, - } + m[parsedTx.Hash()] = &tx return nil } // AddInclusionConstraints adds multiple inclusion constraints to the cache at the given slot -func (c *ConstraintCache) AddInclusionConstraints(slot uint64, constraints []*Constraint) error { +func (c *ConstraintsCache) AddInclusionConstraints(slot uint64, transactions []Transaction) error { if _, exists := c.constraints.Get(slot); !exists { - c.constraints.Add(slot, make(map[gethCommon.Hash]*Constraint)) + c.constraints.Add(slot, make(map[gethCommon.Hash]*Transaction)) } m, _ := c.constraints.Get(slot) - for _, constraint := range constraints { + for _, tx := range transactions { parsedTx := new(types.Transaction) - err := parsedTx.UnmarshalBinary(constraint.Tx) + err := parsedTx.UnmarshalBinary(tx) if err != nil { return err } - m[parsedTx.Hash()] = constraint + m[parsedTx.Hash()] = &tx } return nil } // Get gets the constraints at the given slot. -func (c *ConstraintCache) Get(slot uint64) (map[gethCommon.Hash]*Constraint, bool) { +func (c *ConstraintsCache) Get(slot uint64) (map[gethCommon.Hash]*Transaction, bool) { return c.constraints.Get(slot) } // FindTransactionByHash finds the constraint for the given transaction hash and returns it. -func (c *ConstraintCache) FindTransactionByHash(txHash gethCommon.Hash) (*Constraint, bool) { - for _, hashToConstraint := range c.constraints.Values() { - if constraint, exists := hashToConstraint[txHash]; exists { - return constraint, true +func (c *ConstraintsCache) FindTransactionByHash(txHash gethCommon.Hash) (*Transaction, bool) { + for _, hashToTx := range c.constraints.Values() { + if tx, exists := hashToTx[txHash]; exists { + return tx, true } } return nil, false } -type ( - HashToConstraintDecoded = map[gethCommon.Hash]*ConstraintDecoded - ConstraintDecoded struct { - Index *uint64 - Tx *types.Transaction - } -) +// SignedDelegation represents the delegation signed by the proposer pubkey to +// authorize the delegatee pubkey to submit constraints on their behalf. +// +// Specs: https://docs.boltprotocol.xyz/api/builder#delegate +type SignedDelegation struct { + Message Delegation `json:"message"` + Signature phase0.BLSSignature `json:"signature"` +} + +// Delegation as from Specs: https://docs.boltprotocol.xyz/api/builder#delegate +type Delegation struct { + Action uint8 `json:"action"` + ValidatorPubkey phase0.BLSPubKey `json:"validator_pubkey"` + DelegateePubkey phase0.BLSPubKey `json:"delegatee_pubkey"` +} + +// SignedRevocation represents the revocation signed by the proposer pubkey to +// revoke the delegatee pubkey's ability to submit constraints on their behalf. +// +// Specs: https://docs.boltprotocol.xyz/api/builder#revoke +type SignedRevocation struct { + Message Revocation `json:"message"` + Signature phase0.BLSSignature `json:"signature"` +} + +// Revocation as from Specs: https://docs.boltprotocol.xyz/api/builder#revoke +type Revocation struct { + Action uint8 `json:"action"` + ValidatorPubkey phase0.BLSPubKey `json:"validator_pubkey"` + DelegateePubkey phase0.BLSPubKey `json:"delegatee_pubkey"` +} diff --git a/mev-boost/server/mock_relay.go b/mev-boost/server/mock_relay.go index fc0695f60..07fee42aa 100644 --- a/mev-boost/server/mock_relay.go +++ b/mev-boost/server/mock_relay.go @@ -65,7 +65,7 @@ type mockRelay struct { // Default responses placeholders, used if overrider does not exist GetHeaderResponse *builderSpec.VersionedSignedBuilderBid - GetHeaderWithProofsResponse *BidWithInclusionProofs + GetHeaderWithProofsResponse *VersionedSignedBuilderBidWithProofs GetPayloadResponse *builderApi.VersionedSubmitBlindedBlockResponse // Server section @@ -123,6 +123,8 @@ func (m *mockRelay) getRouter() http.Handler { r.HandleFunc(pathGetHeader, m.handleGetHeader).Methods(http.MethodGet) r.HandleFunc(pathGetHeaderWithProofs, m.handleGetHeaderWithProofs).Methods(http.MethodGet) r.HandleFunc(pathSubmitConstraint, m.handleSubmitConstraint).Methods(http.MethodPost) + r.HandleFunc(pathDelegate, m.handleDelegate).Methods(http.MethodPost) + r.HandleFunc(pathRevoke, m.handleRevoke).Methods(http.MethodPost) r.HandleFunc(pathGetPayload, m.handleGetPayload).Methods(http.MethodPost) return m.newTestMiddleware(r) @@ -172,6 +174,28 @@ func (m *mockRelay) defaultHandleRegisterValidator(w http.ResponseWriter, req *h w.WriteHeader(http.StatusOK) } +func (m *mockRelay) handleDelegate(w http.ResponseWriter, req *http.Request) { + payload := SignedDelegation{} + if err := DecodeJSON(req.Body, &payload); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) +} + +func (m *mockRelay) handleRevoke(w http.ResponseWriter, req *http.Request) { + payload := SignedRevocation{} + if err := DecodeJSON(req.Body, &payload); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) +} + func (m *mockRelay) handleSubmitConstraint(w http.ResponseWriter, req *http.Request) { m.mu.Lock() defer m.mu.Unlock() @@ -197,7 +221,7 @@ func (m *mockRelay) MakeGetHeaderWithConstraintsResponse(value uint64, blockHash tx Transaction hash phase0.Hash32 }, -) *BidWithInclusionProofs { +) *VersionedSignedBuilderBidWithProofs { transactions := new(utilbellatrix.ExecutionPayloadTransactions) for _, con := range constraints { @@ -288,7 +312,7 @@ func (m *mockRelay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, p // MakeGetHeaderWithProofsResponseWithTxsRoot is used to create the default or can be used to create a custom response to the getHeaderWithProofs // method -func (m *mockRelay) MakeGetHeaderWithProofsResponseWithTxsRoot(value uint64, blockHash, parentHash, publicKey string, version spec.DataVersion, txsRoot phase0.Root) *BidWithInclusionProofs { +func (m *mockRelay) MakeGetHeaderWithProofsResponseWithTxsRoot(value uint64, blockHash, parentHash, publicKey string, version spec.DataVersion, txsRoot phase0.Root) *VersionedSignedBuilderBidWithProofs { switch version { case spec.DataVersionCapella: // Fill the payload with custom values. @@ -307,8 +331,8 @@ func (m *mockRelay) MakeGetHeaderWithProofsResponseWithTxsRoot(value uint64, blo signature, err := ssz.SignMessage(message, ssz.DomainBuilder, m.secretKey) require.NoError(m.t, err) - return &BidWithInclusionProofs{ - Bid: &builderSpec.VersionedSignedBuilderBid{ + return &VersionedSignedBuilderBidWithProofs{ + VersionedSignedBuilderBid: &builderSpec.VersionedSignedBuilderBid{ Version: spec.DataVersionCapella, Capella: &builderApiCapella.SignedBuilderBid{ Message: message, @@ -335,8 +359,8 @@ func (m *mockRelay) MakeGetHeaderWithProofsResponseWithTxsRoot(value uint64, blo signature, err := ssz.SignMessage(message, ssz.DomainBuilder, m.secretKey) require.NoError(m.t, err) - return &BidWithInclusionProofs{ - Bid: &builderSpec.VersionedSignedBuilderBid{ + return &VersionedSignedBuilderBidWithProofs{ + VersionedSignedBuilderBid: &builderSpec.VersionedSignedBuilderBid{ Version: spec.DataVersionDeneb, Deneb: &builderApiDeneb.SignedBuilderBid{ Message: message, @@ -411,7 +435,7 @@ func (m *mockRelay) defaultHandleGetHeaderWithProofs(w http.ResponseWriter) { "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - spec.DataVersionCapella, + spec.DataVersionDeneb, nil, ) diff --git a/mev-boost/server/proofs.go b/mev-boost/server/proofs.go index 72ccd626e..5ba5f40ba 100644 --- a/mev-boost/server/proofs.go +++ b/mev-boost/server/proofs.go @@ -10,23 +10,130 @@ import ( fastSsz "github.com/ferranbt/fastssz" + builderApiBellatrix "github.com/attestantio/go-builder-client/api/bellatrix" + builderApiCapella "github.com/attestantio/go-builder-client/api/capella" + builderApiDeneb "github.com/attestantio/go-builder-client/api/deneb" builderSpec "github.com/attestantio/go-builder-client/spec" + consensusSpec "github.com/attestantio/go-eth2-client/spec" + "github.com/attestantio/go-eth2-client/spec/phase0" ) -type BidWithInclusionProofs struct { - // The block bid - Bid *builderSpec.VersionedSignedBuilderBid `json:"bid"` - // The inclusion proofs - Proofs *InclusionProof `json:"proofs"` +// A wrapper struct over `builderSpec.VersionedSignedBuilderBid` +// to include constraint inclusion proofs +type VersionedSignedBuilderBidWithProofs struct { + Proofs *InclusionProof + *builderSpec.VersionedSignedBuilderBid } -func (b *BidWithInclusionProofs) String() string { - out, err := json.Marshal(b) - if err != nil { - return err.Error() +// this is necessary, because the mev-boost-relay deserialization doesn't expect a "Version" and "Data" wrapper object +// for deserialization. Instead, it tries to decode the object into the "Deneb" version first and if that fails, it tries +// the "Capella" version. This is a workaround to make the deserialization work. +// +// NOTE(bolt): struct embedding of the VersionedSignedBuilderBid is not possible for some reason because it causes the json +// encoding to omit the `proofs` field. Embedding all of the fields directly does the job. +func (v *VersionedSignedBuilderBidWithProofs) MarshalJSON() ([]byte, error) { + switch v.Version { + case consensusSpec.DataVersionBellatrix: + return json.Marshal(struct { + Message *builderApiBellatrix.BuilderBid `json:"message"` + Signature phase0.BLSSignature `json:"signature"` + Proofs *InclusionProof `json:"proofs"` + }{ + Message: v.Bellatrix.Message, + Signature: v.Bellatrix.Signature, + Proofs: v.Proofs, + }) + case consensusSpec.DataVersionCapella: + return json.Marshal(struct { + Message *builderApiCapella.BuilderBid `json:"message"` + Signature phase0.BLSSignature `json:"signature"` + Proofs *InclusionProof `json:"proofs"` + }{ + Message: v.Capella.Message, + Signature: v.Capella.Signature, + Proofs: v.Proofs, + }) + case consensusSpec.DataVersionDeneb: + return json.Marshal(struct { + Message *builderApiDeneb.BuilderBid `json:"message"` + Signature phase0.BLSSignature `json:"signature"` + Proofs *InclusionProof `json:"proofs"` + }{ + Message: v.Deneb.Message, + Signature: v.Deneb.Signature, + Proofs: v.Proofs, + }) + } + + return nil, fmt.Errorf("unknown data version %d", v.Version) +} + +func (v *VersionedSignedBuilderBidWithProofs) UnmarshalJSON(data []byte) error { + var err error + + // Try Deneb + var deneb struct { + Message *builderApiDeneb.BuilderBid `json:"message"` + Signature phase0.BLSSignature `json:"signature"` + Proofs *InclusionProof `json:"proofs"` + } + err = json.Unmarshal(data, &deneb) + if err == nil && deneb.Message != nil { + v.Proofs = deneb.Proofs + + v.VersionedSignedBuilderBid = &builderSpec.VersionedSignedBuilderBid{} + v.Deneb = &builderApiDeneb.SignedBuilderBid{ + Message: deneb.Message, + Signature: deneb.Signature, + } + v.Version = consensusSpec.DataVersionDeneb + return nil } - return string(out) + + // Try Capella + var capella struct { + Message *builderApiCapella.BuilderBid `json:"message"` + Signature phase0.BLSSignature `json:"signature"` + Proofs *InclusionProof `json:"proofs"` + } + err = json.Unmarshal(data, &capella) + if err == nil && capella.Message != nil { + v.Proofs = capella.Proofs + + v.VersionedSignedBuilderBid = &builderSpec.VersionedSignedBuilderBid{} + v.Capella = &builderApiCapella.SignedBuilderBid{ + Message: capella.Message, + Signature: capella.Signature, + } + v.Version = consensusSpec.DataVersionCapella + return nil + } + + // Try Bellatrix + var bellatrix struct { + Message *builderApiBellatrix.BuilderBid `json:"message"` + Signature phase0.BLSSignature `json:"signature"` + Proofs *InclusionProof `json:"proofs"` + } + err = json.Unmarshal(data, &bellatrix) + if err == nil && bellatrix.Message != nil { + v.Proofs = bellatrix.Proofs + + v.VersionedSignedBuilderBid = &builderSpec.VersionedSignedBuilderBid{} + v.Bellatrix = &builderApiBellatrix.SignedBuilderBid{ + Message: bellatrix.Message, + Signature: bellatrix.Signature, + } + v.Version = consensusSpec.DataVersionBellatrix + return nil + } + + return fmt.Errorf("failed to unmarshal VersionedSignedBuilderBidWithProofs: %v", err) +} + +func (v *VersionedSignedBuilderBidWithProofs) String() string { + return JSONStringify(v) } func (p *InclusionProof) String() string { diff --git a/mev-boost/server/service.go b/mev-boost/server/service.go index 36a501792..746566c54 100644 --- a/mev-boost/server/service.go +++ b/mev-boost/server/service.go @@ -112,6 +112,8 @@ type BoostService struct { httpClientGetPayload http.Client httpClientRegVal http.Client httpClientSubmitConstraint http.Client + httpClientDelegate http.Client + httpClientRevoke http.Client requestMaxRetries int bids map[bidRespKey]bidResp // keeping track of bids, to log the originating relay on withholding @@ -121,7 +123,7 @@ type BoostService struct { slotUIDLock sync.Mutex // BOLT: constraint cache - constraints *ConstraintCache + constraints *ConstraintsCache } // NewBoostService created a new BoostService @@ -163,10 +165,20 @@ func NewBoostService(opts BoostServiceOpts) (*BoostService, error) { Timeout: opts.RequestTimeoutSubmitConstraint, CheckRedirect: httpClientDisallowRedirects, }, + httpClientDelegate: http.Client{ + // NOTE: using the same timeout as registerValidator + Timeout: opts.RequestTimeoutRegVal, + CheckRedirect: httpClientDisallowRedirects, + }, + httpClientRevoke: http.Client{ + // NOTE: using the same timeout as registerValidator + Timeout: opts.RequestTimeoutRegVal, + CheckRedirect: httpClientDisallowRedirects, + }, requestMaxRetries: opts.RequestMaxRetries, // BOLT: Initialize the constraint cache - constraints: NewConstraintCache(64), + constraints: NewConstraintsCache(64), }, nil } @@ -196,13 +208,14 @@ func (m *BoostService) getRouter() http.Handler { r.HandleFunc(pathStatus, m.handleStatus).Methods(http.MethodGet) r.HandleFunc(pathRegisterValidator, m.handleRegisterValidator).Methods(http.MethodPost) r.HandleFunc(pathSubmitConstraint, m.handleSubmitConstraint).Methods(http.MethodPost) - // TODO: manage the switch between the endpoint with and without proofs - // with the bolt sidecar proxy instead of using the same response here. - // TODO: revert this to m.handleGetHeader - r.HandleFunc(pathGetHeader, m.handleGetHeaderWithProofs).Methods(http.MethodGet) + + r.HandleFunc(pathGetHeader, m.handleGetHeader).Methods(http.MethodGet) r.HandleFunc(pathGetHeaderWithProofs, m.handleGetHeaderWithProofs).Methods(http.MethodGet) r.HandleFunc(pathGetPayload, m.handleGetPayload).Methods(http.MethodPost) + r.HandleFunc(pathDelegate, m.handleDelegate).Methods(http.MethodPost) + r.HandleFunc(pathRevoke, m.handleRevoke).Methods(http.MethodPost) + r.Use(mux.CORSMethodMiddleware(r)) loggedRouter := httplogger.LoggingMiddlewareLogrus(m.log, r) return loggedRouter @@ -341,6 +354,101 @@ func (m *BoostService) handleRegisterValidator(w http.ResponseWriter, req *http. m.respondError(w, http.StatusBadGateway, errNoSuccessfulRelayResponse.Error()) } +func (m *BoostService) handleDelegate(w http.ResponseWriter, req *http.Request) { + log := m.log.WithField("method", "delegate") + log.Debug("delegate:", req.Body) + + payload := SignedDelegation{} + if err := DecodeJSON(req.Body, &payload); err != nil { + m.respondError(w, http.StatusBadRequest, err.Error()) + return + } + if payload.Message.Action != 0 { + m.respondError(w, http.StatusBadRequest, "invalid action, expected 0 for delegate") + return + } + + ua := UserAgent(req.Header.Get("User-Agent")) + log = log.WithFields(logrus.Fields{ + "validatorPubkey": payload.Message.ValidatorPubkey.String(), + "delegateePubkey": payload.Message.DelegateePubkey.String(), + "ua": ua, + }) + + relayRespCh := make(chan error, len(m.relays)) + + for _, relay := range m.relays { + go func(relay RelayEntry) { + url := relay.GetURI(pathDelegate) + log := log.WithField("url", url) + + _, err := SendHTTPRequest(context.Background(), m.httpClientDelegate, http.MethodPost, url, ua, nil, payload, nil) + relayRespCh <- err + if err != nil { + log.WithError(err).Warn("error calling delegate on relay") + return + } + }(relay) + } + + for i := 0; i < len(m.relays); i++ { + respErr := <-relayRespCh + if respErr == nil { + m.respondOK(w, nilResponse) + return + } + } + + m.respondError(w, http.StatusBadGateway, errNoSuccessfulRelayResponse.Error()) +} + +func (m *BoostService) handleRevoke(w http.ResponseWriter, req *http.Request) { + log := m.log.WithField("method", "revoke") + log.Debug("revoke:", req.Body) + + payload := SignedRevocation{} + if err := DecodeJSON(req.Body, &payload); err != nil { + m.respondError(w, http.StatusBadRequest, err.Error()) + return + } + if payload.Message.Action != 1 { + m.respondError(w, http.StatusBadRequest, "invalid action, expected 1 for revoke") + } + + ua := UserAgent(req.Header.Get("User-Agent")) + log = log.WithFields(logrus.Fields{ + "validatorPubkey": payload.Message.ValidatorPubkey.String(), + "delegateePubkey": payload.Message.DelegateePubkey.String(), + "ua": ua, + }) + + relayRespCh := make(chan error, len(m.relays)) + + for _, relay := range m.relays { + go func(relay RelayEntry) { + url := relay.GetURI(pathRevoke) + log := log.WithField("url", url) + + _, err := SendHTTPRequest(context.Background(), m.httpClientRevoke, http.MethodPost, url, ua, nil, payload, nil) + relayRespCh <- err + if err != nil { + log.WithError(err).Warn("error calling revoke on relay") + return + } + }(relay) + } + + for i := 0; i < len(m.relays); i++ { + respErr := <-relayRespCh + if respErr == nil { + m.respondOK(w, nilResponse) + return + } + } + + m.respondError(w, http.StatusBadGateway, errNoSuccessfulRelayResponse.Error()) +} + // verifyInclusionProof verifies the proofs against the constraints, and returns an error if the proofs are invalid. func (m *BoostService) verifyInclusionProof(transactionsRoot phase0.Root, proof *InclusionProof, slot uint64) error { log := m.log.WithFields(logrus.Fields{}) @@ -368,38 +476,35 @@ func (m *BoostService) verifyInclusionProof(transactionsRoot phase0.Root, proof // Decode the constraints, and sort them according to the utility function used // TODO: this should be done before verification ideally - hashToConstraint := make(HashToConstraintDecoded) - for hash, constraint := range inclusionConstraints { + hashToTransaction := make(HashToTransactionDecoded) + for hash, tx := range inclusionConstraints { transaction := new(gethTypes.Transaction) - err := transaction.UnmarshalBinary(constraint.Tx) + err := transaction.UnmarshalBinary(*tx) if err != nil { log.WithError(err).Error("error unmarshalling transaction while verifying proofs") return err } - hashToConstraint[hash] = &ConstraintDecoded{ - Tx: transaction.WithoutBlobTxSidecar(), - Index: constraint.Index, - } + hashToTransaction[hash] = transaction.WithoutBlobTxSidecar() } leaves := make([][]byte, len(inclusionConstraints)) indexes := make([]int, len(proof.GeneralizedIndexes)) for i, hash := range proof.TransactionHashes { - constraint, ok := hashToConstraint[gethCommon.Hash(hash)] - if constraint == nil || !ok { + tx, ok := hashToTransaction[gethCommon.Hash(hash)] + if tx == nil || !ok { return errNilConstraint } // Compute the hash tree root for the raw preconfirmed transaction // and use it as "Leaf" in the proof to be verified against - encoded, err := constraint.Tx.MarshalBinary() + encoded, err := tx.MarshalBinary() if err != nil { log.WithError(err).Error("error marshalling transaction without blob tx sidecar") return err } - tx := Transaction(encoded) - txHashTreeRoot, err := tx.HashTreeRoot() + txBytes := Transaction(encoded) + txHashTreeRoot, err := txBytes.HashTreeRoot() if err != nil { return errInvalidRoot } @@ -424,19 +529,9 @@ func (m *BoostService) verifyInclusionProof(transactionsRoot phase0.Root, proof if !ok { log.Error("[BOLT]: proof verification failed") - - // BOLT: send event to web demo - message := fmt.Sprintf("failed to verify merkle proof for slot %d", slot) - EmitBoltDemoEvent(message) - return errInvalidProofs } else { log.Info(fmt.Sprintf("[BOLT]: merkle proof verified in %s", elapsed)) - - // BOLT: send event to web demo - // verified merkle proof for tx: %s in %v", proof.TxHash.String(), elapsed) - message := fmt.Sprintf("verified merkle proof for slot %d in %v", slot, elapsed) - EmitBoltDemoEvent(message) } return nil @@ -451,8 +546,6 @@ func (m *BoostService) handleSubmitConstraint(w http.ResponseWriter, req *http.R "ua": ua, }) - path := req.URL.Path - log.Info("submitConstraint") payload := BatchedSignedConstraints{} @@ -464,25 +557,23 @@ func (m *BoostService) handleSubmitConstraint(w http.ResponseWriter, req *http.R // Add all constraints to the cache for _, signedConstraints := range payload { - constraintMessage := signedConstraints.Message + constraintsMessage := signedConstraints.Message - log.Infof("[BOLT]: adding inclusion constraints to cache. slot = %d, validatorIndex = %d, number of relays = %d", constraintMessage.Slot, constraintMessage.ValidatorIndex, len(m.relays)) + log.Infof("[BOLT]: adding inclusion constraints to cache. slot = %d, validatorPubkey = %s, number of relays = %d", constraintsMessage.Slot, constraintsMessage.Pubkey.String(), len(m.relays)) // Add the constraints to the cache. // They will be cleared when we receive a payload for the slot in `handleGetPayload` - err := m.constraints.AddInclusionConstraints(constraintMessage.Slot, constraintMessage.Constraints) + err := m.constraints.AddInclusionConstraints(constraintsMessage.Slot, constraintsMessage.Transactions) if err != nil { log.WithError(err).Errorf("error adding inclusion constraints to cache") continue } - log.Infof("[BOLT]: added inclusion constraints to cache. slot = %d, validatorIndex = %d, number of relays = %d", constraintMessage.Slot, constraintMessage.ValidatorIndex, len(m.relays)) + log.Infof("[BOLT]: added inclusion constraints to cache. slot = %d, validatorPubkey = %s, number of relays = %d", constraintsMessage.Slot, constraintsMessage.Pubkey.String(), len(m.relays)) } relayRespCh := make(chan error, len(m.relays)) - EmitBoltDemoEvent(fmt.Sprintf("received %d constraints, forwarding to Bolt relays... (path: %s)", len(payload), path)) - for _, relay := range m.relays { go func(relay RelayEntry) { url := relay.GetURI(pathSubmitConstraint) @@ -768,7 +859,7 @@ func (m *BoostService) handleGetHeaderWithProofs(w http.ResponseWriter, req *htt "genesisTime": m.genesisTime, "slotTimeSec": config.SlotTimeSec, "msIntoSlot": msIntoSlot, - }).Infof("getHeader request start - %d milliseconds into slot %d", msIntoSlot, slotUint) + }).Infof("getHeaderWithProof request start - %d milliseconds into slot %d", msIntoSlot, slotUint) // Add request headers headers := map[string]string{ @@ -789,7 +880,7 @@ func (m *BoostService) handleGetHeaderWithProofs(w http.ResponseWriter, req *htt path := fmt.Sprintf("/eth/v1/builder/header_with_proofs/%s/%s/%s", slot, parentHashHex, pubkey) url := relay.GetURI(path) log := log.WithField("url", url) - responsePayload := new(BidWithInclusionProofs) + responsePayload := new(VersionedSignedBuilderBidWithProofs) code, err := SendHTTPRequest(context.Background(), m.httpClientGetHeader, http.MethodGet, url, ua, headers, nil, responsePayload) if err != nil { log.WithError(err).Warn("error making request to relay") @@ -805,19 +896,19 @@ func (m *BoostService) handleGetHeaderWithProofs(w http.ResponseWriter, req *htt return } - if responsePayload.Bid == nil { + if responsePayload.VersionedSignedBuilderBid == nil { log.Warn("Bid in response is nil") return } // Skip if payload is empty - if responsePayload.Bid.IsEmpty() { + if responsePayload.IsEmpty() { log.Warn("Bid is empty") return } // Getting the bid info will check if there are missing fields in the response - bidInfo, err := parseBidInfo(responsePayload.Bid) + bidInfo, err := parseBidInfo(responsePayload.VersionedSignedBuilderBid) if err != nil { log.WithError(err).Warn("error parsing bid info") return @@ -843,7 +934,7 @@ func (m *BoostService) handleGetHeaderWithProofs(w http.ResponseWriter, req *htt // Verify the relay signature in the relay response if !config.SkipRelaySignatureCheck { - ok, err := checkRelaySignature(responsePayload.Bid, m.builderSigningDomain, relay.PublicKey) + ok, err := checkRelaySignature(responsePayload.VersionedSignedBuilderBid, m.builderSigningDomain, relay.PublicKey) if err != nil { log.WithError(err).Error("error verifying relay signature") return @@ -877,10 +968,10 @@ func (m *BoostService) handleGetHeaderWithProofs(w http.ResponseWriter, req *htt return } - // BOLT: verify preconfirmation inclusion proofs. If they don't match, we don't consider the bid to be valid. + // BOLT: verify inclusion proofs. If they don't match, we don't consider the bid to be valid. if responsePayload.Proofs != nil { // BOLT: verify the proofs against the constraints. If they don't match, we don't consider the bid to be valid. - transactionsRoot, err := responsePayload.Bid.TransactionsRoot() + transactionsRoot, err := responsePayload.TransactionsRoot() if err != nil { log.WithError(err).Error("[BOLT]: error getting transaction root") return @@ -912,7 +1003,7 @@ func (m *BoostService) handleGetHeaderWithProofs(w http.ResponseWriter, req *htt // Use this relay's response as mev-boost response because it's most profitable log.Infof("new best bid. Has proofs: %v", responsePayload.Proofs != nil) - result.response = *responsePayload.Bid + result.response = *responsePayload.VersionedSignedBuilderBid result.bidInfo = bidInfo result.t = time.Now() }(relay) diff --git a/mev-boost/server/service_test.go b/mev-boost/server/service_test.go index 4446f23e8..c4212951d 100644 --- a/mev-boost/server/service_test.go +++ b/mev-boost/server/service_test.go @@ -311,28 +311,85 @@ func TestRegisterValidator(t *testing.T) { }) } +func TestDelegate(t *testing.T) { + path := pathDelegate + delegate := SignedDelegation{ + Message: Delegation{ + Action: 0, + ValidatorPubkey: _HexToPubkey("0xa695ad325dfc7e1191fbc9f186f58eff42a634029731b18380ff89bf42c464a42cb8ca55b200f051f57f1e1893c68759"), + DelegateePubkey: _HexToPubkey("0xb8ba260170b9cda2ad54c321d9a8d77e4ca34517106f587eb5ec184bf78f8a0ce4fb55658301b0dc6b129d10adf62391"), + }, + Signature: _HexToSignature("0x8790321eacadd5b869838bc01db2338b0bd88a802d768bff8ddbe12aeff67ebc003af8ecc3bafedfef98d2946e869974075006f22367f77c58ca1f1ba20f0d90bf323d243063db16c631ce4ff89bc4f3f239e0879cc4eb492b9906a16fab6f16"), + } + payload := delegate + + t.Run("Normal function", func(t *testing.T) { + backend := newTestBackend(t, 1, time.Second) + rr := backend.request(t, http.MethodPost, path, payload) + require.Equal(t, http.StatusOK, rr.Code) + require.Equal(t, 1, backend.relays[0].GetRequestCount(path)) + }) +} + +func TestRevoke(t *testing.T) { + path := pathRevoke + revoke := SignedRevocation{ + Message: Revocation{ + Action: 1, + ValidatorPubkey: _HexToPubkey("0xa695ad325dfc7e1191fbc9f186f58eff42a634029731b18380ff89bf42c464a42cb8ca55b200f051f57f1e1893c68759"), + DelegateePubkey: _HexToPubkey("0xb8ba260170b9cda2ad54c321d9a8d77e4ca34517106f587eb5ec184bf78f8a0ce4fb55658301b0dc6b129d10adf62391"), + }, + Signature: _HexToSignature("0x8790321eacadd5b869838bc01db2338b0bd88a802d768bff8ddbe12aeff67ebc003af8ecc3bafedfef98d2946e869974075006f22367f77c58ca1f1ba20f0d90bf323d243063db16c631ce4ff89bc4f3f239e0879cc4eb492b9906a16fab6f16"), + } + payload := revoke + + t.Run("Normal function", func(t *testing.T) { + backend := newTestBackend(t, 1, time.Second) + rr := backend.request(t, http.MethodPost, path, payload) + require.Equal(t, http.StatusOK, rr.Code) + require.Equal(t, 1, backend.relays[0].GetRequestCount(path)) + }) +} + +func TestParseSignedDelegation(t *testing.T) { + jsonStr := `{ + "message": { + "validator_pubkey": "0xa695ad325dfc7e1191fbc9f186f58eff42a634029731b18380ff89bf42c464a42cb8ca55b200f051f57f1e1893c68759", + "delegatee_pubkey": "0xb8ba260170b9cda2ad54c321d9a8d77e4ca34517106f587eb5ec184bf78f8a0ce4fb55658301b0dc6b129d10adf62391" + }, + "signature": "0x8790321eacadd5b869838bc01db2338b0bd88a802d768bff8ddbe12aeff67ebc003af8ecc3bafedfef98d2946e869974075006f22367f77c58ca1f1ba20f0d90bf323d243063db16c631ce4ff89bc4f3f239e0879cc4eb492b9906a16fab6f16" + }` + + delegation := SignedDelegation{} + err := json.Unmarshal([]byte(jsonStr), &delegation) + require.NoError(t, err) + require.Equal(t, phase0.BLSPubKey(_HexToBytes("0xa695ad325dfc7e1191fbc9f186f58eff42a634029731b18380ff89bf42c464a42cb8ca55b200f051f57f1e1893c68759")), delegation.Message.ValidatorPubkey) + require.Equal(t, phase0.BLSPubKey(_HexToBytes("0xb8ba260170b9cda2ad54c321d9a8d77e4ca34517106f587eb5ec184bf78f8a0ce4fb55658301b0dc6b129d10adf62391")), delegation.Message.DelegateePubkey) + require.Equal(t, phase0.BLSSignature(_HexToBytes("0x8790321eacadd5b869838bc01db2338b0bd88a802d768bff8ddbe12aeff67ebc003af8ecc3bafedfef98d2946e869974075006f22367f77c58ca1f1ba20f0d90bf323d243063db16c631ce4ff89bc4f3f239e0879cc4eb492b9906a16fab6f16")), delegation.Signature) +} + func TestParseConstraints(t *testing.T) { - jsonStr := `[{ + jsonStr := `[ + { "message": { - "validator_index": 12345, - "slot": 8978583, - "constraints": [{ - "tx": "0x02f871018304a5758085025ff11caf82565f94388c818ca8b9251b393131c08a736a67ccb1929787a41bb7ee22b41380c001a0c8630f734aba7acb4275a8f3b0ce831cf0c7c487fd49ee7bcca26ac622a28939a04c3745096fa0130a188fa249289fd9e60f9d6360854820dba22ae779ea6f573f", - "index": null - }] + "pubkey": "0xa695ad325dfc7e1191fbc9f186f58eff42a634029731b18380ff89bf42c464a42cb8ca55b200f051f57f1e1893c68759", + "slot": 32, + "top": true, + "transactions": [ + "0x02f86c870c72dd9d5e883e4d0183408f2382520894d2e2adf7177b7a8afddbc12d1634cf23ea1a71020180c001a08556dcfea479b34675db3fe08e29486fe719c2b22f6b0c1741ecbbdce4575cc6a01cd48009ccafd6b9f1290bbe2ceea268f94101d1d322c787018423ebcbc87ab4" + ] }, - "signature": "0x81510b571e22f89d1697545aac01c9ad0c1e7a3e778b3078bef524efae14990e58a6e960a152abd49de2e18d7fd3081c15d5c25867ccfad3d47beef6b39ac24b6b9fbf2cfa91c88f67aff750438a6841ec9e4a06a94ae41410c4f97b75ab284c" - }]` + "signature": "0xb8d50ee0d4b269db3d4658c1dac784d273a4160d769e16dce723a9684c390afe5865348416b3bf0f1a4f47098bec9024135d0d95f08bed18eb577a3d8a67f5dc78b13cc62515e280786a73fb267d35dfb7ab46a25ac29bf5bc2fa5b07b3e07a6" + } + ]` constraints := BatchedSignedConstraints{} err := json.Unmarshal([]byte(jsonStr), &constraints) require.NoError(t, err) require.Len(t, constraints, 1) - require.Equal(t, uint64(12345), constraints[0].Message.ValidatorIndex) - require.Equal(t, uint64(8978583), constraints[0].Message.Slot) - require.Len(t, constraints[0].Message.Constraints, 1) - require.Equal(t, constraints[0].Message.Constraints[0].Tx, Transaction(_HexToBytes("0x02f871018304a5758085025ff11caf82565f94388c818ca8b9251b393131c08a736a67ccb1929787a41bb7ee22b41380c001a0c8630f734aba7acb4275a8f3b0ce831cf0c7c487fd49ee7bcca26ac622a28939a04c3745096fa0130a188fa249289fd9e60f9d6360854820dba22ae779ea6f573f"))) - require.Nil(t, constraints[0].Message.Constraints[0].Index) + require.Equal(t, phase0.BLSPubKey(_HexToBytes("0xa695ad325dfc7e1191fbc9f186f58eff42a634029731b18380ff89bf42c464a42cb8ca55b200f051f57f1e1893c68759")), constraints[0].Message.Pubkey) + require.Equal(t, uint64(32), constraints[0].Message.Slot) + require.Equal(t, constraints[0].Message.Transactions[0], Transaction(_HexToBytes("0x02f86c870c72dd9d5e883e4d0183408f2382520894d2e2adf7177b7a8afddbc12d1634cf23ea1a71020180c001a08556dcfea479b34675db3fe08e29486fe719c2b22f6b0c1741ecbbdce4575cc6a01cd48009ccafd6b9f1290bbe2ceea268f94101d1d322c787018423ebcbc87ab4"))) } func TestConstraintsAndProofs(t *testing.T) { @@ -344,9 +401,10 @@ func TestConstraintsAndProofs(t *testing.T) { payload := BatchedSignedConstraints{&SignedConstraints{ Message: ConstraintsMessage{ - ValidatorIndex: 12345, - Slot: slot, - Constraints: []*Constraint{{Transaction(rawTx), nil}}, + Pubkey: phase0.BLSPubKey(_HexToBytes("0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249")), + Slot: slot, + Top: false, + Transactions: []Transaction{rawTx}, }, Signature: phase0.BLSSignature(_HexToBytes( "0x81510b571e22f89d1697545aac01c9ad0c1e7a3e778b3078bef524efae14990e58a6e960a152abd49de2e18d7fd3081c15d5c25867ccfad3d47beef6b39ac24b6b9fbf2cfa91c88f67aff750438a6841ec9e4a06a94ae41410c4f97b75ab284c")), @@ -356,7 +414,7 @@ func TestConstraintsAndProofs(t *testing.T) { hash := _HexToHash("0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7") pubkey := _HexToPubkey( "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249") - getHeaderPath := getHeaderWithProofsPath(slot, hash, pubkey) + getHeaderWithProofsPath := getHeaderWithProofsPath(slot, hash, pubkey) t.Run("Normal function", func(t *testing.T) { backend := newTestBackend(t, 1, time.Second) @@ -364,10 +422,9 @@ func TestConstraintsAndProofs(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) require.Equal(t, 1, backend.relays[0].GetRequestCount(path)) - got, ok := backend.boost.constraints.FindTransactionByHash(common.HexToHash(txHash.String())) + tx, ok := backend.boost.constraints.FindTransactionByHash(common.HexToHash(txHash.String())) require.True(t, ok) - require.Equal(t, Transaction(rawTx), got.Tx) - require.Nil(t, got.Index) + require.Equal(t, Transaction(rawTx), *tx) }) t.Run("Normal function with constraints", func(t *testing.T) { @@ -389,9 +446,9 @@ func TestConstraintsAndProofs(t *testing.T) { ) backend.relays[0].GetHeaderWithProofsResponse = resp - rr := backend.request(t, http.MethodGet, getHeaderPath, nil) + rr := backend.request(t, http.MethodGet, getHeaderWithProofsPath, nil) require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) - require.Equal(t, 1, backend.relays[0].GetRequestCount(getHeaderPath)) + require.Equal(t, 1, backend.relays[0].GetRequestCount(getHeaderWithProofsPath)) }) t.Run("No proofs given", func(t *testing.T) { @@ -409,11 +466,11 @@ func TestConstraintsAndProofs(t *testing.T) { ) backend.relays[0].GetHeaderResponse = resp - rr := backend.request(t, http.MethodGet, getHeaderPath, nil) + rr := backend.request(t, http.MethodGet, getHeaderWithProofsPath, nil) // When we have constraints registered, but the relay does not return any proofs, we should return no content. // This will force a locally built block. require.Equal(t, http.StatusNoContent, rr.Code, rr.Body.String()) - require.Equal(t, 1, backend.relays[0].GetRequestCount(getHeaderPath)) + require.Equal(t, 1, backend.relays[0].GetRequestCount(getHeaderWithProofsPath)) }) } diff --git a/mev-boost/server/utils.go b/mev-boost/server/utils.go index a7ec11cef..9e51b0e47 100644 --- a/mev-boost/server/utils.go +++ b/mev-boost/server/utils.go @@ -273,19 +273,6 @@ func getPayloadResponseIsEmpty(payload *builderApi.VersionedSubmitBlindedBlockRe return false } -// EmitBoltDemoEvent sends a message to the web demo backend to log an event. -// This is only used for demo purposes and should be removed in production. -func EmitBoltDemoEvent(message string) { - event := strings.NewReader(fmt.Sprintf("{ \"message\": \"BOLT-MEV-BOOST: %s\"}", message)) - eventRes, err := http.Post("http://host.docker.internal:3001/events", "application/json", event) - if err != nil { - fmt.Printf("Failed to send web demo event: %v", err) - } - if eventRes != nil { - defer eventRes.Body.Close() - } -} - func Map[T any, U any](slice []*T, mapper func(el *T) *U) []*U { result := make([]*U, len(slice)) for i, el := range slice {