use std::convert::TryFrom;
use crate::admin::messages::{self, is_valid_circuit_id};
use crate::error::InvalidStateError;
use crate::protos::admin;
use crate::public_key::PublicKey;
use super::ProposedCircuit;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CircuitProposal {
proposal_type: ProposalType,
circuit_id: String,
circuit_hash: String,
circuit: ProposedCircuit,
votes: Vec<VoteRecord>,
requester: PublicKey,
requester_node_id: String,
}
impl CircuitProposal {
pub fn proposal_type(&self) -> &ProposalType {
&self.proposal_type
}
pub fn circuit_id(&self) -> &str {
&self.circuit_id
}
pub fn circuit_hash(&self) -> &str {
&self.circuit_hash
}
pub fn circuit(&self) -> &ProposedCircuit {
&self.circuit
}
pub fn votes(&self) -> &[VoteRecord] {
&self.votes
}
pub fn requester(&self) -> &PublicKey {
&self.requester
}
pub fn requester_node_id(&self) -> &str {
&self.requester_node_id
}
pub fn builder(&self) -> CircuitProposalBuilder {
CircuitProposalBuilder::new()
.with_proposal_type(self.proposal_type())
.with_circuit_id(self.circuit_id())
.with_circuit_hash(self.circuit_hash())
.with_circuit(self.circuit())
.with_votes(self.votes())
.with_requester(self.requester())
.with_requester_node_id(self.requester_node_id())
}
pub fn from_proto(mut proto: admin::CircuitProposal) -> Result<Self, InvalidStateError> {
let proposal_type = match proto.get_proposal_type() {
admin::CircuitProposal_ProposalType::CREATE => ProposalType::Create,
admin::CircuitProposal_ProposalType::UPDATE_ROSTER => ProposalType::UpdateRoster,
admin::CircuitProposal_ProposalType::ADD_NODE => ProposalType::AddNode,
admin::CircuitProposal_ProposalType::REMOVE_NODE => ProposalType::RemoveNode,
admin::CircuitProposal_ProposalType::DISBAND => ProposalType::Disband,
admin::CircuitProposal_ProposalType::UNSET_PROPOSAL_TYPE => {
return Err(InvalidStateError::with_message(
"unable to build, missing field: `proposal type`".to_string(),
));
}
};
let votes = proto
.take_votes()
.into_iter()
.map(VoteRecord::from_proto)
.collect::<Result<Vec<VoteRecord>, InvalidStateError>>()?;
Ok(Self {
proposal_type,
circuit_id: proto.take_circuit_id(),
circuit_hash: proto.take_circuit_hash(),
circuit: ProposedCircuit::from_proto(proto.take_circuit_proposal())?,
votes,
requester: PublicKey::from_bytes(proto.take_requester()),
requester_node_id: proto.take_requester_node_id(),
})
}
pub fn into_proto(self) -> admin::CircuitProposal {
let proposal_type = match self.proposal_type {
ProposalType::Create => admin::CircuitProposal_ProposalType::CREATE,
ProposalType::UpdateRoster => admin::CircuitProposal_ProposalType::UPDATE_ROSTER,
ProposalType::AddNode => admin::CircuitProposal_ProposalType::ADD_NODE,
ProposalType::RemoveNode => admin::CircuitProposal_ProposalType::REMOVE_NODE,
ProposalType::Disband => admin::CircuitProposal_ProposalType::DISBAND,
};
let votes = self
.votes
.into_iter()
.map(|vote| vote.into_proto())
.collect::<Vec<admin::CircuitProposal_VoteRecord>>();
let circuit = self.circuit.into_proto();
let mut proposal = admin::CircuitProposal::new();
proposal.set_proposal_type(proposal_type);
proposal.set_circuit_id(self.circuit_id.to_string());
proposal.set_circuit_hash(self.circuit_hash.to_string());
proposal.set_circuit_proposal(circuit);
proposal.set_votes(protobuf::RepeatedField::from_vec(votes));
proposal.set_requester(self.requester.into_bytes());
proposal.set_requester_node_id(self.requester_node_id);
proposal
}
}
#[derive(Clone, Default)]
pub struct CircuitProposalBuilder {
proposal_type: Option<ProposalType>,
circuit_id: Option<String>,
circuit_hash: Option<String>,
circuit: Option<ProposedCircuit>,
votes: Option<Vec<VoteRecord>>,
requester: Option<PublicKey>,
requester_node_id: Option<String>,
}
impl CircuitProposalBuilder {
pub fn new() -> Self {
CircuitProposalBuilder::default()
}
pub fn proposal_type(&self) -> Option<ProposalType> {
self.proposal_type.clone()
}
pub fn circuit_id(&self) -> Option<String> {
self.circuit_id.clone()
}
pub fn circuit_hash(&self) -> Option<String> {
self.circuit_hash.clone()
}
pub fn circuit(&self) -> Option<ProposedCircuit> {
self.circuit.clone()
}
pub fn votes(&self) -> Option<Vec<VoteRecord>> {
self.votes.clone()
}
pub fn requester(&self) -> Option<PublicKey> {
self.requester.clone()
}
pub fn requester_node_id(&self) -> Option<String> {
self.requester_node_id.clone()
}
pub fn with_proposal_type(mut self, proposal_type: &ProposalType) -> CircuitProposalBuilder {
self.proposal_type = Some(proposal_type.clone());
self
}
pub fn with_circuit_id(mut self, circuit_id: &str) -> CircuitProposalBuilder {
self.circuit_id = Some(circuit_id.to_string());
self
}
pub fn with_circuit_hash(mut self, circuit_hash: &str) -> CircuitProposalBuilder {
self.circuit_hash = Some(circuit_hash.to_string());
self
}
pub fn with_circuit(mut self, circuit: &ProposedCircuit) -> CircuitProposalBuilder {
self.circuit = Some(circuit.clone());
self
}
pub fn with_votes(mut self, votes: &[VoteRecord]) -> CircuitProposalBuilder {
self.votes = Some(votes.to_vec());
self
}
pub fn with_requester(mut self, requester: &PublicKey) -> CircuitProposalBuilder {
self.requester = Some(requester.clone());
self
}
pub fn with_requester_node_id(mut self, requester_node_id: &str) -> CircuitProposalBuilder {
self.requester_node_id = Some(requester_node_id.to_string());
self
}
pub fn build(self) -> Result<CircuitProposal, InvalidStateError> {
let circuit_id = match self.circuit_id {
Some(circuit_id) if is_valid_circuit_id(&circuit_id) => circuit_id,
Some(circuit_id) => {
return Err(InvalidStateError::with_message(format!(
"circuit_id is invalid ({}): must be an 11 character string composed of two, \
5 character base62 strings joined with a '-' (example: abcDE-F0123)",
circuit_id,
)))
}
None => {
return Err(InvalidStateError::with_message(
"unable to build, missing field: `circuit_id`".to_string(),
))
}
};
let proposal_type = self.proposal_type.ok_or_else(|| {
InvalidStateError::with_message(
"unable to build, missing field: `proposal_type`".to_string(),
)
})?;
let circuit_hash = self.circuit_hash.ok_or_else(|| {
InvalidStateError::with_message(
"unable to build, missing field: `circuit_hash`".to_string(),
)
})?;
let circuit = self.circuit.ok_or_else(|| {
InvalidStateError::with_message("unable to build, missing field: `circuit`".to_string())
})?;
let votes = self.votes.unwrap_or_default();
let requester = self.requester.ok_or_else(|| {
InvalidStateError::with_message(
"unable to build, missing field: `requester`".to_string(),
)
})?;
let requester_node_id = self.requester_node_id.ok_or_else(|| {
InvalidStateError::with_message(
"unable to build, missing field: `requester_node_id`".to_string(),
)
})?;
Ok(CircuitProposal {
proposal_type,
circuit_id,
circuit_hash,
circuit,
votes,
requester,
requester_node_id,
})
}
}
impl TryFrom<&messages::CircuitProposal> for CircuitProposal {
type Error = InvalidStateError;
fn try_from(admin_proposal: &messages::CircuitProposal) -> Result<Self, Self::Error> {
CircuitProposalBuilder::new()
.with_proposal_type(&ProposalType::from(&admin_proposal.proposal_type))
.with_circuit_id(&admin_proposal.circuit_id)
.with_circuit_hash(&admin_proposal.circuit_hash)
.with_circuit(&ProposedCircuit::try_from(&admin_proposal.circuit)?)
.with_votes(
&admin_proposal
.votes
.iter()
.map(VoteRecord::from)
.collect::<Vec<VoteRecord>>(),
)
.with_requester(&PublicKey::from_bytes(admin_proposal.requester.clone()))
.with_requester_node_id(&admin_proposal.requester_node_id)
.build()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VoteRecord {
public_key: PublicKey,
vote: Vote,
voter_node_id: String,
}
impl VoteRecord {
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
pub fn vote(&self) -> &Vote {
&self.vote
}
pub fn voter_node_id(&self) -> &str {
&self.voter_node_id
}
fn from_proto(mut proto: admin::CircuitProposal_VoteRecord) -> Result<Self, InvalidStateError> {
let vote = match proto.get_vote() {
admin::CircuitProposalVote_Vote::ACCEPT => Vote::Accept,
admin::CircuitProposalVote_Vote::REJECT => Vote::Reject,
admin::CircuitProposalVote_Vote::UNSET_VOTE => {
return Err(InvalidStateError::with_message(
"unable to build, missing field: `vote".to_string(),
));
}
};
Ok(Self {
public_key: PublicKey::from_bytes(proto.take_public_key()),
vote,
voter_node_id: proto.take_voter_node_id(),
})
}
fn into_proto(self) -> admin::CircuitProposal_VoteRecord {
let vote = match self.vote {
Vote::Accept => admin::CircuitProposalVote_Vote::ACCEPT,
Vote::Reject => admin::CircuitProposalVote_Vote::REJECT,
};
let mut vote_record = admin::CircuitProposal_VoteRecord::new();
vote_record.set_vote(vote);
vote_record.set_public_key(self.public_key.into_bytes());
vote_record.set_voter_node_id(self.voter_node_id);
vote_record
}
}
#[derive(Default)]
pub struct VoteRecordBuilder {
public_key: Option<PublicKey>,
vote: Option<Vote>,
voter_node_id: Option<String>,
}
impl VoteRecordBuilder {
pub fn new() -> Self {
VoteRecordBuilder::default()
}
pub fn public_key(&self) -> Option<PublicKey> {
self.public_key.clone()
}
pub fn vote(&self) -> Option<Vote> {
self.vote.clone()
}
pub fn voter_node_id(&self) -> Option<String> {
self.voter_node_id.clone()
}
pub fn with_public_key(mut self, public_key: &PublicKey) -> VoteRecordBuilder {
self.public_key = Some(public_key.clone());
self
}
pub fn with_vote(mut self, vote: &Vote) -> VoteRecordBuilder {
self.vote = Some(vote.clone());
self
}
pub fn with_voter_node_id(mut self, node_id: &str) -> VoteRecordBuilder {
self.voter_node_id = Some(node_id.to_string());
self
}
pub fn build(self) -> Result<VoteRecord, InvalidStateError> {
let public_key = self.public_key.ok_or_else(|| {
InvalidStateError::with_message(
"unable to build, missing field: `public_key`".to_string(),
)
})?;
let vote = self.vote.ok_or_else(|| {
InvalidStateError::with_message("unable to build, missing field: `vote`".to_string())
})?;
let voter_node_id = self.voter_node_id.ok_or_else(|| {
InvalidStateError::with_message("unable to build, missing field: `voter_`".to_string())
})?;
Ok(VoteRecord {
public_key,
vote,
voter_node_id,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Vote {
Accept,
Reject,
}
impl From<&messages::Vote> for Vote {
fn from(admin_vote: &messages::Vote) -> Self {
match *admin_vote {
messages::Vote::Accept => Vote::Accept,
messages::Vote::Reject => Vote::Reject,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProposalType {
Create,
UpdateRoster,
AddNode,
RemoveNode,
Disband,
}
impl From<&messages::ProposalType> for ProposalType {
fn from(admin_proposal_type: &messages::ProposalType) -> Self {
match *admin_proposal_type {
messages::ProposalType::Create => ProposalType::Create,
messages::ProposalType::UpdateRoster => ProposalType::UpdateRoster,
messages::ProposalType::AddNode => ProposalType::AddNode,
messages::ProposalType::RemoveNode => ProposalType::RemoveNode,
messages::ProposalType::Disband => ProposalType::Disband,
}
}
}
impl TryFrom<&admin::CircuitProposal_ProposalType> for ProposalType {
type Error = InvalidStateError;
fn try_from(
proto_proposal_type: &admin::CircuitProposal_ProposalType,
) -> Result<Self, Self::Error> {
match *proto_proposal_type {
admin::CircuitProposal_ProposalType::CREATE => Ok(ProposalType::Create),
admin::CircuitProposal_ProposalType::UPDATE_ROSTER => Ok(ProposalType::UpdateRoster),
admin::CircuitProposal_ProposalType::ADD_NODE => Ok(ProposalType::AddNode),
admin::CircuitProposal_ProposalType::REMOVE_NODE => Ok(ProposalType::RemoveNode),
admin::CircuitProposal_ProposalType::DISBAND => Ok(ProposalType::Disband),
admin::CircuitProposal_ProposalType::UNSET_PROPOSAL_TYPE => Err(
InvalidStateError::with_message("ProposalType is unset".to_string()),
),
}
}
}
impl From<&messages::VoteRecord> for VoteRecord {
fn from(admin_vote_record: &messages::VoteRecord) -> Self {
VoteRecord {
public_key: PublicKey::from_bytes(admin_vote_record.public_key.to_vec()),
vote: Vote::from(&admin_vote_record.vote),
voter_node_id: admin_vote_record.voter_node_id.to_string(),
}
}
}