Skip to content

Commit

Permalink
Setup multiple network configurations and drand schedule (#915)
Browse files Browse the repository at this point in the history
* Base setup to allow supporting multiple networks

* Fork handling logic

* Update drand schedule

* Move genesis to networks
  • Loading branch information
austinabell authored Jan 7, 2021
1 parent fa82654 commit 3c536d0
Show file tree
Hide file tree
Showing 34 changed files with 390 additions and 188 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"utils/statediff",
"utils/hash_utils",
"types",
"types/networks",
"key_management",
]

Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ clean:
@cargo clean -p genesis
@cargo clean -p actor_interface
@cargo clean -p forest_hash_utils
@cargo clean -p networks
@echo "Done cleaning."

lint: license clean
Expand Down
115 changes: 76 additions & 39 deletions blockchain/beacon/src/drand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ use clock::ChainEpoch;
use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize};
use sha2::Digest;
use std::borrow::Cow;
use std::convert::TryFrom;
use std::error;
use std::sync::Arc;

/// Default endpoint for the drand beacon node.
// TODO this URL is only valid until smoke fork, should setup schedule for drand upgrade
pub const DEFAULT_DRAND_URL: &str = "https://pl-us.incentinet.drand.sh";

/// Enviromental Variable to ignore Drand. Lotus parallel is LOTUS_IGNORE_DRAND
pub const IGNORE_DRAND_VAR: &str = "IGNORE_DRAND";

Expand All @@ -35,32 +30,72 @@ impl DrandPublic {
}
}

pub struct Schedule<T>(pub Vec<BeaconPoint<T>>);
#[derive(Clone)]
pub struct DrandConfig<'a> {
pub server: &'static str,
pub chain_info: ChainInfo<'a>,
}

pub struct BeaconSchedule<T>(pub Vec<BeaconPoint<T>>);

impl<T> Schedule<T>
impl<T> BeaconSchedule<T>
where
T: Beacon,
{
pub fn beacon_for_epoch(&self, e: ChainEpoch) -> Result<&T, Box<dyn error::Error>> {
if let Some(beacon_point) = self
pub async fn beacon_entries_for_block(
&self,
epoch: ChainEpoch,
parent_epoch: ChainEpoch,
prev: &BeaconEntry,
) -> Result<Vec<BeaconEntry>, Box<dyn error::Error>> {
let (cb_epoch, curr_beacon) = self.beacon_for_epoch(epoch)?;
let (pb_epoch, _) = self.beacon_for_epoch(parent_epoch)?;
if cb_epoch != pb_epoch {
// Fork logic
let round = curr_beacon.max_beacon_round_for_epoch(epoch);
let mut entries = Vec::with_capacity(2);
entries.push(curr_beacon.entry(round - 1).await?);
entries.push(curr_beacon.entry(round).await?);
return Ok(entries);
}
let max_round = curr_beacon.max_beacon_round_for_epoch(epoch);
if max_round == prev.round() {
return Ok(vec![]);
}
// TODO: this is a sketchy way to handle the genesis block not having a beacon entry
let prev_round = if prev.round() == 0 {
max_round - 1
} else {
prev.round()
};

let mut cur = max_round;
let mut out = Vec::new();
while cur > prev_round {
let entry = curr_beacon.entry(cur).await?;
cur = entry.round() - 1;
out.push(entry);
}
out.reverse();
Ok(out)
}

pub fn beacon_for_epoch(
&self,
epoch: ChainEpoch,
) -> Result<(ChainEpoch, &T), Box<dyn error::Error>> {
Ok(self
.0
.iter()
.rev()
.find(|beacon| beacon.start == e)
.map(|s| &s.beacon)
{
Ok(&*beacon_point)
} else {
self.0
.first()
.map(|s| &*s.beacon)
.ok_or_else(|| Box::from("Could not get first_value of beacon in beacon_for_epoch"))
}
.find(|upgrade| epoch >= upgrade.height)
.map(|upgrade| (upgrade.height, upgrade.beacon.as_ref()))
.ok_or("Invalid beacon schedule, no valid beacon")?)
}
}

pub struct BeaconPoint<T> {
pub start: ChainEpoch,
pub height: ChainEpoch,
pub beacon: Arc<T>,
}

Expand All @@ -83,14 +118,14 @@ where
fn max_beacon_round_for_epoch(&self, fil_epoch: ChainEpoch) -> u64;
}

#[derive(SerdeDeserialize, SerdeSerialize, Debug, Clone)]
pub struct ChainInfo {
public_key: String,
period: i32,
genesis_time: i32,
hash: String,
#[derive(SerdeDeserialize, SerdeSerialize, Debug, Clone, PartialEq, Default)]
pub struct ChainInfo<'a> {
pub public_key: Cow<'a, str>,
pub period: i32,
pub genesis_time: i32,
pub hash: Cow<'a, str>,
#[serde(rename = "groupHash")]
group_hash: String,
pub group_hash: Cow<'a, str>,
}

#[derive(SerdeDeserialize, SerdeSerialize, Debug, Clone)]
Expand All @@ -102,7 +137,7 @@ pub struct BeaconEntryJson {
}

pub struct DrandBeacon {
url: Cow<'static, str>,
url: &'static str,

pub_key: DrandPublic,
/// Interval between beacons, in seconds.
Expand All @@ -118,26 +153,28 @@ pub struct DrandBeacon {
impl DrandBeacon {
/// Construct a new DrandBeacon.
pub async fn new(
url: impl Into<Cow<'static, str>>,
pub_key: DrandPublic,
genesis_ts: u64,
interval: u64,
config: &DrandConfig<'_>,
) -> Result<Self, Box<dyn error::Error>> {
if genesis_ts == 0 {
panic!("Genesis timestamp cannot be 0")
}
let url = url.into();
let chain_info: ChainInfo = surf::get(&format!("{}/info", &url)).recv_json().await?;
let remote_pub_key = hex::decode(chain_info.public_key)?;
if remote_pub_key != pub_key.coefficient {
return Err(Box::try_from(
"Drand pub key from config is different than one on drand servers",
)?);

let chain_info = &config.chain_info;

if cfg!(debug_assertions) {
let remote_chain_info: ChainInfo = surf::get(&format!("{}/info", &config.server))
.recv_json()
.await?;
debug_assert!(&remote_chain_info == chain_info);
}

Ok(Self {
url,
pub_key,
url: config.server,
pub_key: DrandPublic {
coefficient: hex::decode(chain_info.public_key.as_ref())?,
},
interval: chain_info.period as u64,
drand_gen_time: chain_info.genesis_time as u64,
fil_round_time: interval,
Expand Down
30 changes: 0 additions & 30 deletions blockchain/beacon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,3 @@ mod mock_beacon;
pub use beacon_entries::*;
pub use drand::*;
pub use mock_beacon::*;

use clock::ChainEpoch;
use std::error::Error;

pub async fn beacon_entries_for_block<B: Beacon>(
beacon: &B,
round: ChainEpoch,
prev: &BeaconEntry,
) -> Result<Vec<BeaconEntry>, Box<dyn Error>> {
let max_round = beacon.max_beacon_round_for_epoch(round);
if max_round == prev.round() {
return Ok(vec![]);
}
// TODO: this is a sketchy way to handle the genesis block not having a beacon entry
let prev_round = if prev.round() == 0 {
max_round - 1
} else {
prev.round()
};

let mut cur = max_round;
let mut out = Vec::new();
while cur > prev_round {
let entry = beacon.entry(cur).await?;
cur = entry.round() - 1;
out.push(entry);
}
out.reverse();
Ok(out)
}
26 changes: 16 additions & 10 deletions blockchain/beacon/tests/drand.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use beacon::{Beacon, DrandBeacon, DrandPublic, DEFAULT_DRAND_URL};
use beacon::{Beacon, ChainInfo, DrandBeacon, DrandConfig};
use serde::{Deserialize, Serialize};

async fn new_beacon() -> DrandBeacon {
// Current public parameters, subject to change.
let coeffs =
hex::decode("922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df").unwrap();
let dist_pub = DrandPublic {
coefficient: coeffs,
};
DrandBeacon::new(DEFAULT_DRAND_URL, dist_pub, 15904451751, 25)
.await
.unwrap()
DrandBeacon::new(
15904451751,
25,
// TODO this could maybe be referencing existing config
&DrandConfig {
server: "https://pl-us.incentinet.drand.sh",
chain_info: ChainInfo {
public_key: "922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df"
.into(),
..Default::default()
},
},
)
.await
.unwrap()
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down
32 changes: 27 additions & 5 deletions blockchain/blocks/src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use super::{ElectionProof, Error, Ticket, TipsetKeys};
use address::Address;
use beacon::{self, Beacon, BeaconEntry};
use beacon::{self, Beacon, BeaconEntry, BeaconSchedule};
use cid::{Cid, Code::Blake2b256};
use clock::ChainEpoch;
use crypto::Signature;
Expand Down Expand Up @@ -341,11 +341,33 @@ impl BlockHeader {
/// Validates if the current header's Beacon entries are valid to ensure randomness was generated correctly
pub async fn validate_block_drand<B: Beacon>(
&self,
beacon: &B,
b_schedule: &BeaconSchedule<B>,
_parent_epoch: ChainEpoch,
prev_entry: &BeaconEntry,
) -> Result<(), Error> {
// TODO validation may need to use the beacon schedule from `ChainSyncer`. Seems outdated
let max_round = beacon.max_beacon_round_for_epoch(self.epoch);
let (cb_epoch, curr_beacon) = b_schedule
.beacon_for_epoch(self.epoch)
.map_err(|e| Error::Validation(e.to_string()))?;
let (pb_epoch, _) = b_schedule
.beacon_for_epoch(self.epoch)
.map_err(|e| Error::Validation(e.to_string()))?;

if cb_epoch != pb_epoch {
// Fork logic
if self.beacon_entries.len() != 2 {
return Err(Error::Validation(format!(
"Expected two beacon entries at beacon fork, got {}",
self.beacon_entries.len()
)));
}

curr_beacon
.verify_entry(&self.beacon_entries[1], &self.beacon_entries[0])
.await
.map_err(|e| Error::Validation(e.to_string()))?;
}

let max_round = curr_beacon.max_beacon_round_for_epoch(self.epoch);
if max_round == prev_entry.round() {
if !self.beacon_entries.is_empty() {
return Err(Error::Validation(format!(
Expand All @@ -367,7 +389,7 @@ impl BlockHeader {

let mut prev = prev_entry;
for curr in &self.beacon_entries {
if !beacon
if !curr_beacon
.verify_entry(&curr, &prev)
.await
.map_err(|e| Error::Validation(e.to_string()))?
Expand Down
1 change: 1 addition & 0 deletions blockchain/chain_sync/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ smallvec = "1.1.0"
actor = { package = "actor_interface", path = "../../vm/actor_interface" }
interpreter = { path = "../../vm/interpreter/" }
message_pool = { path = "../message_pool" }
networks = { path = "../../types/networks" }

[dev-dependencies]
test_utils = { version = "0.1.0", path = "../../utils/test_utils/", features = ["test_constructors"] }
Expand Down
Loading

0 comments on commit 3c536d0

Please sign in to comment.