Skip to content

Commit

Permalink
feat(snot): private key indirection parsing for topologies
Browse files Browse the repository at this point in the history
  • Loading branch information
Meshiest committed Mar 23, 2024
1 parent 944b3f5 commit 38f8f85
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 104 deletions.
85 changes: 0 additions & 85 deletions crates/snot-common/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,55 +47,6 @@ impl Display for PortConfig {
}
}

// // agent code
// impl AgentState {
// async fn reconcile(&self, target: AgentState) {
// // assert that we can actually move from self to target
// // if not, return ReconcileFailed

// if self.peers != target.peers {
// if self.online {
// self.turn_offline();
// }

// // make change to peers
// self.peers = target.peers;
// // make the change in snarkos

// // restore online state
// }

// // and do the rest of these fields

// // return StateReconciled(self)
// }
// }

// #[derive(Debug, Default, Clone, Serialize, Deserialize)]
// pub enum AgentState {
// Inventory,
// Node(ContextRequest, ConfigRequest),
// Cannon(/* config */),
// }

// /// Desired state for an agent's node.
// #[derive(Debug, Default, Clone, Serialize, Deserialize)]
// pub struct ContextRequest {
// pub id: usize,
// pub ty: NodeType,
// pub storage: StorageId,
// pub starting_height: Option<u32>,
// }

// #[derive(Debug, Default, Clone, Serialize, Deserialize)]
// pub struct ConfigRequest {
// pub id: usize,
// pub online: bool,
// pub peers: Vec<AgentPeer>,
// pub validators: Vec<AgentPeer>,
// pub next_height: Option<HeightRequest>,
// }

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub enum HeightRequest {
#[default]
Expand Down Expand Up @@ -130,42 +81,6 @@ impl AgentPeer {
}
}

// /// The state reported by an agent.
// #[derive(Debug, Default, Clone, Serialize, Deserialize, Hash)]
// pub struct ResolvedState {
// /// The timestamp of the last update.
// pub timestamp: i64, // TODO: chrono

// // pub online: bool,
// // pub config_ty: Option<NodeType>,

// pub current_state: State,

// pub genesis_hash: Option<String>,
// pub config_peers: Option<Vec<SocketAddr>>,
// pub config_validators: Option<Vec<SocketAddr>>,
// pub snarkos_peers: Option<Vec<SocketAddr>>,
// pub snarkos_validators: Option<Vec<SocketAddr>>,
// pub block_height: Option<u32>,
// pub block_timestamp: Option<i64>,
// }

// impl ConfigRequest {
// pub fn new() -> Self {
// Self::default()
// }

// pub fn with_online(mut self, online: bool) -> Self {
// self.online = online;
// self
// }

// pub fn with_type(mut self, ty: Option<NodeType>) -> Self {
// self.ty = ty;
// self
// }
// }

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct NodeKey {
pub ty: NodeType,
Expand Down
154 changes: 147 additions & 7 deletions crates/snot/src/schema/nodes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::net::SocketAddr;

use indexmap::IndexMap;
use serde::Deserialize;
use lazy_static::lazy_static;
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
use snot_common::state::{HeightRequest, NodeState, NodeType};

use super::{NodeKey, NodeTargets};
Expand Down Expand Up @@ -43,11 +44,8 @@ pub struct Node {
/// When specified, creates a group of nodes, all with the same
/// configuration.
pub replicas: Option<usize>,
/// The private key to start the node with. When unspecified, a random
/// private key is generated at runtime.
// TODO: turn this into an enum with options like `None`, `Additional(usize)`,
// `Committee(usize)`, `Named(String)`, `Literal(String)`
pub key: Option<String>,
/// The private key to start the node with.
pub key: Option<KeySource>,
/// Height of ledger to inherit.
///
/// * When null, a ledger is created when the node is started.
Expand All @@ -65,7 +63,7 @@ impl Node {
pub fn into_state(&self, ty: NodeType) -> NodeState {
NodeState {
ty,
private_key: self.key.clone(),
private_key: None,

// TODO
height: (0, HeightRequest::Top),
Expand All @@ -78,3 +76,145 @@ impl Node {
}
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum KeySource {
/// APrivateKey1zkp...
Literal(String),
/// committee.0 or committee.$ (for replicas)
Committee(Option<usize>),
/// accounts.0 or accounts.$ (for replicas)
Named(String, Option<usize>),
}

struct KeySourceVisitor;

impl<'de> Visitor<'de> for KeySourceVisitor {
type Value = KeySource;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string that represents an aleo private key, or a file from storage")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
// use KeySource::Literal(String) when the string is 59 characters long and starts with "APrivateKey1zkp"
// use KeySource::Commitee(Option<usize>) when the string is "committee.0" or "committee.$"
// use KeySource::Named(String, Option<usize>) when the string is "\w+.0" or "\w+.$"

// aleo private key
if v.len() == 59 && v.starts_with("APrivateKey1") {
return Ok(KeySource::Literal(v.to_string()));

// committee key
} else if let Some(index) = v.strip_prefix("committee.") {
if index == "$" {
return Ok(KeySource::Committee(None));
}
let replica = index
.parse()
.map_err(|_e| E::custom("committee index must be a positive number"))?;
return Ok(KeySource::Committee(Some(replica)));
}

// named key (using regex with capture groups)
lazy_static! {
static ref NAMED_KEYSOURCE_REGEX: regex::Regex =
regex::Regex::new(r"^(?P<name>\w+)\.(?P<idx>\d+|\$)$").unwrap();
}
let groups = NAMED_KEYSOURCE_REGEX
.captures(v)
.ok_or_else(|| E::custom("invalid key source"))?;
let name = groups.name("name").unwrap().as_str().to_string();
let idx = match groups.name("idx").unwrap().as_str() {
"$" => None,
idx => Some(
idx.parse()
.map_err(|_e| E::custom("index must be a positive number"))?,
),
};
Ok(KeySource::Named(name, idx))
}
}

impl<'de> Deserialize<'de> for KeySource {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(KeySourceVisitor)
}
}

impl Serialize for KeySource {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
KeySource::Literal(key) => serializer.serialize_str(key),
KeySource::Committee(None) => serializer.serialize_str("committee.$"),
KeySource::Committee(Some(idx)) => {
serializer.serialize_str(&format!("committee.{}", idx))
}
KeySource::Named(name, None) => serializer.serialize_str(&format!("{}.{}", name, "$")),
KeySource::Named(name, Some(idx)) => {
serializer.serialize_str(&format!("{}.{}", name, idx))
}
}
}
}

impl KeySource {
pub fn with_index(&self, idx: usize) -> Self {
match self {
KeySource::Committee(_) => KeySource::Committee(Some(idx)),
KeySource::Named(name, _) => KeySource::Named(name.clone(), Some(idx)),
_ => self.clone(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_key_source_deserialization() {
assert_eq!(
serde_yaml::from_str::<KeySource>("committee.0").expect("foo"),
KeySource::Committee(Some(0))
);
assert_eq!(
serde_yaml::from_str::<KeySource>("committee.100").expect("foo"),
KeySource::Committee(Some(100))
);
assert_eq!(
serde_yaml::from_str::<KeySource>("committee.$").expect("foo"),
KeySource::Committee(None)
);

assert_eq!(
serde_yaml::from_str::<KeySource>("accounts.0").expect("foo"),
KeySource::Named("accounts".to_string(), Some(0))
);
assert_eq!(
serde_yaml::from_str::<KeySource>("accounts.$").expect("foo"),
KeySource::Named("accounts".to_string(), None)
);

assert_eq!(
serde_yaml::from_str::<KeySource>(
"APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"
)
.expect("foo"),
KeySource::Literal(
"APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH".to_string()
)
);

assert!(serde_yaml::from_str::<KeySource>("committee.-100").is_err(),);
assert!(serde_yaml::from_str::<KeySource>("accounts.-100").is_err(),);
assert!(serde_yaml::from_str::<KeySource>("accounts._").is_err(),);
assert!(serde_yaml::from_str::<KeySource>("accounts.*").is_err(),);
}
}
23 changes: 13 additions & 10 deletions crates/snot/src/schema/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,23 @@ pub struct Transaction {
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct GenesisGeneration {
pub output: PathBuf,
pub committee: usize,
pub committee_balances: usize,
pub additional_accounts: usize,
pub additional_balances: usize,
}

impl Default for GenesisGeneration {
fn default() -> Self {
Self {
output: PathBuf::from("genesis.block"),
committee: 5,
committee_balances: 10_000_000_000_000,
additional_accounts: 5,
additional_balances: 100_000_000_000,
}
}
}
Expand Down Expand Up @@ -158,15 +167,9 @@ impl Document {
}

generation.genesis = GenesisGeneration {
output: base.join("genesis.block"),
output: base.join(generation.genesis.output),
..generation.genesis
};
// generation.genesis = snarkos_aot::genesis::Genesis {
// output: base.join("genesis.block"),
// ledger: None,
// additional_accounts_output: Some(base.join("accounts.json")),
// committee_output: Some(base.join("committee.json")),
// ..generation.genesis
// };

// generate the genesis block using the aot cli
let bin = std::env::var("AOT_BIN").map(PathBuf::from).unwrap_or(
Expand All @@ -180,11 +183,11 @@ impl Document {
.arg("--output")
.arg(&generation.genesis.output)
.arg("--committee-size")
.arg("5")
.arg(generation.genesis.committee.to_string())
.arg("--committee-output")
.arg(base.join("committee.json"))
.arg("--additional-accounts")
.arg("5")
.arg(generation.genesis.additional_accounts.to_string())
.arg("--additional-accounts-output")
.arg(base.join("accounts.json"))
.arg("--ledger")
Expand Down
1 change: 1 addition & 0 deletions crates/snot/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub async fn start(cli: Cli) -> Result<()> {
let app = Router::new()
.route("/agent", get(agent_ws_handler))
.nest("/api/v1", api::routes())
// /env/<id>/ledger/* - ledger query service reverse proxying /mainnet/latest/stateRoot
.nest("/content", content::init_routes(&state).await)
.with_state(Arc::new(state))
.layer(
Expand Down
9 changes: 7 additions & 2 deletions crates/snot/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tracing::{info, warn};

use crate::{
schema::{
nodes::{ExternalNode, Node},
nodes::{ExternalNode, KeySource, Node},
storage::FilenameString,
ItemDocument, NodeTargets,
},
Expand Down Expand Up @@ -100,7 +100,7 @@ impl Test {
// replace the key with a new one
let mut node = doc_node.to_owned();
if let Some(key) = node.key.take() {
node.key = Some(key.replace('$', &i.to_string()))
node.key = Some(key.with_index(i))
}
ent.insert(TestNode::Internal(node))
}
Expand Down Expand Up @@ -313,6 +313,11 @@ pub async fn initial_reconcile(state: &GlobalState) -> anyhow::Result<()> {

// resolve the peers and validators
let mut node_state = node.into_state(key.ty);
node_state.private_key = node.key.as_ref().map(|key| match key {
KeySource::Literal(pk) => pk.to_owned(),
KeySource::Committee(_i) => todo!(),
KeySource::Named(_, _) => todo!(),
});
node_state.peers = matching_nodes(key, &node.peers, false)?;
node_state.validators = matching_nodes(key, &node.validators, true)?;

Expand Down

0 comments on commit 38f8f85

Please sign in to comment.