Skip to content

Commit

Permalink
explorer: stake control and implement public tally
Browse files Browse the repository at this point in the history
this could re-use more code from the ledger, but it's not that easy to
ignore validations and also I think this is simple enough for now. The
implementation is somewhat isolated, so it's not that hard to change
things after some more thought.
  • Loading branch information
ecioppettini committed Oct 15, 2021
1 parent 8a65598 commit 3e14b75
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 17 deletions.
8 changes: 4 additions & 4 deletions explorer/src/api/graphql/mod.rs
Expand Up @@ -497,14 +497,14 @@ impl Block {
async fn fetch_explorer_block(&self, db: &ExplorerDb) -> FieldResult<Arc<ExplorerBlock>> {
let mut contents = self.contents.lock().await;
if let Some(block) = &*contents {
return Ok(Arc::clone(&block));
Ok(Arc::clone(&block))
} else {
let block = db.get_block(&self.hash).await.ok_or_else(|| {
ApiError::InternalError("Couldn't find block's contents in explorer".to_owned())
})?;

*contents = Some(Arc::clone(&block));
return Ok(block);
Ok(block)
}
}

Expand Down Expand Up @@ -1373,14 +1373,14 @@ impl VotePlanStatus {
tally: proposal.tally.map(|tally| match tally {
ExplorerVoteTally::Public { results, options } => {
TallyStatus::Public(TallyPublicStatus {
results: results.into_iter().map(Into::into).collect(),
results: results.iter().map(Into::into).collect(),
options: options.into(),
})
}
ExplorerVoteTally::Private { results, options } => {
TallyStatus::Private(TallyPrivateStatus {
results: results
.map(|res| res.into_iter().map(Into::into).collect()),
.map(|res| res.iter().map(Into::into).collect()),
options: options.into(),
})
}
Expand Down
6 changes: 3 additions & 3 deletions explorer/src/api/graphql/scalars.rs
Expand Up @@ -388,9 +388,9 @@ impl From<chain_impl_mockchain::certificate::ExternalProposalId> for ExternalPro
}
}

impl From<vote::Weight> for Weight {
fn from(w: vote::Weight) -> Self {
Self(format!("{}", w))
impl<T: std::borrow::Borrow<vote::Weight>> From<T> for Weight {
fn from(w: T) -> Self {
Self(format!("{}", w.borrow()))
}
}

Expand Down
15 changes: 14 additions & 1 deletion explorer/src/db/indexing.rs
Expand Up @@ -3,6 +3,7 @@ use cardano_legacy_address::Addr as OldAddress;
use chain_addr::{Address, Discrimination};
use chain_core::property::{Block as _, Fragment as _};
use chain_impl_mockchain::{
account::Identifier,
block::{Block, Proof},
certificate::{
Certificate, ExternalProposalId, PoolId, PoolRegistration, PoolRetirement, VotePlanId,
Expand Down Expand Up @@ -131,7 +132,7 @@ pub struct ExplorerVoteProposal {
#[derive(Clone)]
pub enum ExplorerVoteTally {
Public {
results: Vec<Weight>,
results: Box<[Weight]>,
options: Options,
},
Private {
Expand Down Expand Up @@ -446,3 +447,15 @@ impl ExplorerTransaction {
&self.outputs
}
}

impl ExplorerAddress {
pub fn to_single_account(&self) -> Option<Identifier> {
match self {
ExplorerAddress::New(address) => match address.kind() {
chain_addr::Kind::Single(key) => Some(key.clone().into()),
_ => None,
},
ExplorerAddress::Old(_) => None,
}
}
}
100 changes: 91 additions & 9 deletions explorer/src/db/mod.rs
Expand Up @@ -2,7 +2,10 @@ pub mod error;
pub mod indexing;
pub mod multiverse;
pub mod persistent_sequence;
mod tally;

use crate::db::tally::compute_public_tally;
use crate::db::tally::compute_private_tally;
use self::error::{ExplorerError as Error, Result};
use self::indexing::{
Addresses, Blocks, ChainLengths, EpochData, Epochs, ExplorerAddress, ExplorerBlock,
Expand All @@ -12,17 +15,20 @@ use self::indexing::{
use self::persistent_sequence::PersistentSequence;
use chain_core::property::Block as _;
pub use multiverse::Ref;

use chain_addr::Discrimination;
use chain_impl_mockchain::fee::LinearFee;
use chain_impl_mockchain::{block::HeaderId as HeaderHash, certificate::VotePlanId};
use chain_impl_mockchain::{
block::HeaderId as HeaderHash,
certificate::VotePlanId,
stake::{Stake, StakeControl},
};
use chain_impl_mockchain::{
block::{Block, ChainLength, Epoch},
certificate::{Certificate, PoolId},
chaintypes::ConsensusVersion,
config::ConfigParam,
fragment::{ConfigParams, Fragment, FragmentId},
};
use chain_impl_mockchain::{fee::LinearFee, vote::PayloadType};
use futures::prelude::*;
use multiverse::Multiverse;
use std::convert::Infallible;
Expand Down Expand Up @@ -83,6 +89,7 @@ pub struct State {
stake_pool_data: StakePool,
stake_pool_blocks: StakePoolBlocks,
vote_plans: VotePlans,
stake_control: StakeControl,
}

#[derive(Clone)]
Expand Down Expand Up @@ -123,7 +130,8 @@ impl ExplorerDb {
let addresses = apply_block_to_addresses(Addresses::new(), &block);
let (stake_pool_data, stake_pool_blocks) =
apply_block_to_stake_pools(StakePool::new(), StakePoolBlocks::new(), &block);
let vote_plans = apply_block_to_vote_plans(VotePlans::new(), &block);
let stake_control = apply_block_to_stake_control(StakeControl::new(), &block);
let vote_plans = apply_block_to_vote_plans(VotePlans::new(), &block, &stake_control);

let initial_state = State {
transactions,
Expand All @@ -134,6 +142,7 @@ impl ExplorerDb {
stake_pool_data,
stake_pool_blocks,
vote_plans,
stake_control,
};

let block0_id = block0.id();
Expand Down Expand Up @@ -181,6 +190,7 @@ impl ExplorerDb {
stake_pool_data,
stake_pool_blocks,
vote_plans,
stake_control,
} = previous_state.state().clone();

let explorer_block = ExplorerBlock::resolve_from(
Expand All @@ -194,6 +204,8 @@ impl ExplorerDb {
let (stake_pool_data, stake_pool_blocks) =
apply_block_to_stake_pools(stake_pool_data, stake_pool_blocks, &explorer_block);

let stake_control = apply_block_to_stake_control(stake_control, &explorer_block);

let state_ref = multiverse
.insert(
chain_length,
Expand All @@ -207,7 +219,12 @@ impl ExplorerDb {
chain_lengths: apply_block_to_chain_lengths(chain_lengths, &explorer_block)?,
stake_pool_data,
stake_pool_blocks,
vote_plans: apply_block_to_vote_plans(vote_plans, &explorer_block),
vote_plans: apply_block_to_vote_plans(
vote_plans,
&explorer_block,
&stake_control,
),
stake_control,
},
)
.await;
Expand Down Expand Up @@ -568,7 +585,11 @@ fn apply_block_to_stake_pools(
(data, blocks)
}

fn apply_block_to_vote_plans(mut vote_plans: VotePlans, block: &ExplorerBlock) -> VotePlans {
fn apply_block_to_vote_plans(
mut vote_plans: VotePlans,
block: &ExplorerBlock,
stake: &StakeControl,
) -> VotePlans {
for tx in block.transactions.values() {
if let Some(cert) = &tx.certificate {
vote_plans = match cert {
Expand Down Expand Up @@ -655,9 +676,27 @@ fn apply_block_to_vote_plans(mut vote_plans: VotePlans, block: &ExplorerBlock) -
.unwrap(),
}
}
Certificate::VoteTally(_vote_tally) => {
unimplemented!("this may require access to the node's Tip");
}
Certificate::VoteTally(vote_tally) => vote_plans
.update(vote_tally.id(), |vote_plan| {
let proposals = vote_plan
.proposals
.clone()
.into_iter()
.map(|mut proposal| {
proposal.tally = Some(match vote_tally.tally_type() {
PayloadType::Public => compute_public_tally(&proposal, stake),
PayloadType::Private => compute_private_tally(&proposal),
});
proposal
})
.collect();
let vote_plan = ExplorerVotePlan {
proposals,
..(**vote_plan).clone()
};
Ok::<_, std::convert::Infallible>(Some(Arc::new(vote_plan)))
})
.unwrap(),
_ => vote_plans,
}
}
Expand All @@ -666,6 +705,49 @@ fn apply_block_to_vote_plans(mut vote_plans: VotePlans, block: &ExplorerBlock) -
vote_plans
}

fn apply_block_to_stake_control(
mut stake_control: StakeControl,
block: &ExplorerBlock,
) -> StakeControl {
for (_id, tx) in block.transactions.iter() {
// TODO: there is a bit of code duplication here (maybe?)

for input in tx.inputs() {
let indexing::ExplorerInput { address, value } = input;
let address = match address {
ExplorerAddress::Old(_) => continue,
ExplorerAddress::New(address) => address,
};

match address.kind() {
chain_addr::Kind::Group(_, id) | chain_addr::Kind::Account(id) => {
stake_control =
stake_control.remove_from(id.clone().into(), Stake::from_value(*value));
}
_ => continue,
}
}

for output in tx.outputs() {
let indexing::ExplorerOutput { address, value } = output;
let address = match address {
ExplorerAddress::Old(_) => continue,
ExplorerAddress::New(address) => address,
};

match address.kind() {
chain_addr::Kind::Group(_, id) | chain_addr::Kind::Account(id) => {
stake_control =
stake_control.add_to(id.clone().into(), Stake::from_value(*value));
}
_ => continue,
}
}
}

stake_control
}

impl BlockchainConfig {
fn from_config_params(params: &ConfigParams) -> BlockchainConfig {
let mut discrimination: Option<Discrimination> = None;
Expand Down
44 changes: 44 additions & 0 deletions explorer/src/db/tally.rs
@@ -0,0 +1,44 @@
use chain_impl_mockchain::stake::StakeControl;

use super::indexing::ExplorerVoteProposal;
use crate::db::indexing::{ExplorerVote, ExplorerVoteTally};

pub fn compute_private_tally(proposal: &ExplorerVoteProposal) -> ExplorerVoteTally {
tracing::warn!("private tally triggered but it is not implemented");

ExplorerVoteTally::Private {
results: None,
options: proposal.options.clone(),
}
}

pub fn compute_public_tally(
proposal: &ExplorerVoteProposal,
stake: &StakeControl,
) -> ExplorerVoteTally {
let mut results = vec![0u64; proposal.options.choice_range().end as usize];

for (address, vote) in proposal.votes.iter() {
if let Some(account_id) = address.to_single_account() {
if let Some(stake) = stake.by(&account_id) {
match vote.as_ref() {
ExplorerVote::Public(choice) => {
let index = choice.as_byte() as usize;
results[index] = results[index].saturating_add(stake.into());
}
ExplorerVote::Private {
proof: _,
encrypted_vote: _,
} => {
unreachable!("internal error: found private vote when computing tally for public proposal")
}
}
}
}
}

ExplorerVoteTally::Public {
results: results.drain(..).map(u64::into).collect(),
options: proposal.options.clone(),
}
}

0 comments on commit 3e14b75

Please sign in to comment.