Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polygon POS consensus #60

Merged
merged 28 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dfc9c1c
polygon pos verifier
Wizdave97 Dec 21, 2023
c027210
implement some polygon-pos prover functions
Wizdave97 Dec 21, 2023
52544e8
ismp-polygon-pos
Wizdave97 Dec 22, 2023
f595aed
Merge branch 'main' of github.com:polytope-labs/hyperbridge into cliq…
Wizdave97 Dec 22, 2023
ec2d632
update longest chain finalization logic
Wizdave97 Dec 24, 2023
56ce399
nit
Wizdave97 Dec 24, 2023
e1ca58f
create verifier state
Wizdave97 Dec 24, 2023
8b9bef8
fix signature verification
Wizdave97 Dec 26, 2023
3de3a08
nit
Wizdave97 Dec 26, 2023
d128eeb
add polygon client to runtime
Wizdave97 Dec 26, 2023
31f8cf7
account for reorgs
Wizdave97 Dec 27, 2023
813db4c
update pos prover
Wizdave97 Dec 27, 2023
ce292d4
nit
Wizdave97 Dec 27, 2023
e105b4b
add some docs
Wizdave97 Dec 28, 2023
417d383
merge origin
Wizdave97 Dec 28, 2023
96dff03
fix authority set rotation
Wizdave97 Dec 29, 2023
63c10ba
nit
Wizdave97 Dec 29, 2023
526053e
doc
Wizdave97 Dec 31, 2023
1f93ff5
fix merge changes from origin
Wizdave97 Jan 1, 2024
8938586
use workspace deps
Wizdave97 Jan 1, 2024
59743fe
remove unused deps
Wizdave97 Jan 1, 2024
47232fc
fix ethers version
Wizdave97 Jan 1, 2024
c3eb36b
create geth primitives
Wizdave97 Jan 2, 2024
feb1fe2
handle forks with the same difficulty
Wizdave97 Jan 2, 2024
ad3b85e
ensure finality is sixteen minutes
Wizdave97 Jan 2, 2024
09bdef5
do not finalize when forks have the same difficulty
Wizdave97 Jan 2, 2024
adb2285
Merge branch 'main' of github.com:polytope-labs/hyperbridge into cliq…
Wizdave97 Jan 2, 2024
0035323
fix
Wizdave97 Jan 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
786 changes: 707 additions & 79 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"parachain/modules/ismp/core",
"parachain/modules/ismp/testsuite",
"parachain/modules/ismp/sync-committee",
"parachain/modules/ismp/polygon-pos",
"parachain/modules/ismp/casper-ffg",

# modules
Expand All @@ -25,6 +26,9 @@ members = [
"parachain/modules/consensus/sync-committee/prover",
"parachain/modules/consensus/sync-committee/verifier",
"parachain/modules/consensus/sync-committee/primitives",
"parachain/modules/consensus/polygon-pos/verifier",
"parachain/modules/consensus/polygon-pos/prover",


# cli-application
"client",
Expand Down
37 changes: 37 additions & 0 deletions parachain/modules/consensus/polygon-pos/prover/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "polygon-pos-prover"
version = "0.1.0"
edition = "2021"
Wizdave97 marked this conversation as resolved.
Show resolved Hide resolved

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
polygon-pos-verifier = { path= "../verifier" }
anyhow = "1.0.68"
tokio = { version = "1.32.0", features = ["sync"]}
tokio-stream = { version = "0.1.8" }
async-stream = { version = "0.3.3"}
env_logger = "0.10.0"
primitive-types = { version = "0.12.1", features = ["serde_no_std", "impl-codec"] }
log = "0.4.20"
hex = "0.4.3"
ethers = { version = "2.0.8", features = ["ws"] }


[dev-dependencies]
env_logger = "0.10.0"
polygon-pos-verifier = { path= "../verifier" }
ethers = { version = "2.0.8", features = ["ws"] }
tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"]}
parity-scale-codec = "3.2.2"
dotenv = "0.15.0"
ismp = { path = "../../../ismp/core" }
sp-core = {version = "27.0.0" }
sp-io = {version = "27.0.0" }
[features]
default = ["std"]
std = [
"anyhow/std",
]


92 changes: 92 additions & 0 deletions parachain/modules/consensus/polygon-pos/prover/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use anyhow::anyhow;
use ethers::{
prelude::{Provider, Ws},
providers::Middleware,
types::BlockId,
};
use polygon_pos_verifier::primitives::{parse_validators, CodecHeader, SPAN_LENGTH};
use primitive_types::H160;
use std::{collections::BTreeSet, fmt::Debug, sync::Arc};

#[cfg(test)]
mod test;

#[derive(Clone)]
pub struct PolygonPosProver {
/// Execution Rpc client
pub client: Arc<Provider<Ws>>,
}

impl PolygonPosProver {
pub fn new(client: Provider<Ws>) -> Self {
Self { client: Arc::new(client) }
}

pub async fn fetch_header<T: Into<BlockId> + Send + Sync + Debug + Copy>(
&self,
block: T,
) -> Result<CodecHeader, anyhow::Error> {
let block = self
.client
.get_block(block)
.await?
.ok_or_else(|| anyhow!("Header not found for {:?}", block))?;
let header = CodecHeader {
seunlanlege marked this conversation as resolved.
Show resolved Hide resolved
parent_hash: block.parent_hash,
uncle_hash: block.uncles_hash,
coinbase: block.author.unwrap_or_default(),
state_root: block.state_root,
transactions_root: block.transactions_root,
receipts_root: block.receipts_root,
logs_bloom: block.logs_bloom.unwrap_or_default(),
difficulty: block.difficulty,
number: block.number.unwrap_or_default().as_u64().into(),
gas_limit: block.gas_limit.low_u64(),
gas_used: block.gas_used.low_u64(),
timestamp: block.timestamp.low_u64(),
extra_data: block.extra_data.0.into(),
mix_hash: block.mix_hash.unwrap_or_default(),
nonce: block.nonce.unwrap_or_default(),
base_fee_per_gas: block.base_fee_per_gas,
withdrawals_hash: block.withdrawals_root,
excess_data_gas: block.excess_blob_gas,
};

Ok(header)
}

pub async fn latest_header(&self) -> Result<CodecHeader, anyhow::Error> {
let block_number = self.client.get_block_number().await?;
let header = self.fetch_header(block_number.as_u64()).await?;
Ok(header)
}

pub async fn fetch_finalized_state(
&self,
) -> Result<(CodecHeader, BTreeSet<H160>), anyhow::Error> {
let latest_header = self.latest_header().await?;
let finalized_block = latest_header.number.low_u64() - 250;
let span = finalized_block / SPAN_LENGTH;
let span_start = span * SPAN_LENGTH;
let span_begin_header = self.fetch_header(span_start - 1).await?;
let validators = parse_validators(&span_begin_header.extra_data)?
.ok_or_else(|| anyhow!("Validator set not found in span header"))?;
let finalized_header = self.fetch_header(finalized_block).await?;
Ok((finalized_header, validators))
}
}

/// Returns a vector of mandatory block numbers
pub fn should_sync(previous_finalized: u64, latest_header: u64) -> Vec<u64> {
let current_span = previous_finalized / SPAN_LENGTH;
let next_span = latest_header / SPAN_LENGTH;

((current_span + 1)..=next_span)
.into_iter()
.map(|span: u64| span * SPAN_LENGTH)
.collect()
}

pub fn is_span_start(block_number: u64) -> bool {
block_number % SPAN_LENGTH == 0
}
56 changes: 56 additions & 0 deletions parachain/modules/consensus/polygon-pos/prover/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::time::Duration;

use ethers::providers::{Provider, Ws};
use ismp::util::Keccak256;
use polygon_pos_verifier::{primitives::Header, verify_polygon_header};
use primitive_types::H256;
use tokio::time::interval;

use crate::PolygonPosProver;

pub struct Host;

impl Keccak256 for Host {
fn keccak256(bytes: &[u8]) -> primitive_types::H256
where
Self: Sized,
{
sp_core::keccak_256(bytes).into()
}
}

async fn setup_prover() -> PolygonPosProver {
dotenv::dotenv().ok();
let consensus_url = std::env::var("POLYGON_RPC").unwrap();
let provider = Provider::<Ws>::connect_with_reconnects(consensus_url, 1000).await.unwrap();

PolygonPosProver::new(provider)
}

#[tokio::test]
async fn verify_polygon_pos_headers() {
let prover = setup_prover().await;

let mut interval = interval(Duration::from_secs(128));
let (mut finalized_header, mut validators) = prover.fetch_finalized_state().await.unwrap();
loop {
interval.tick().await;
let latest_header = prover.latest_header().await.unwrap();
let mut parent_hash = Header::from(&finalized_header).hash::<Host>().unwrap();
for number in (finalized_header.number.low_u64() + 1)..=latest_header.number.low_u64() {
let header = prover.fetch_header(number).await.unwrap();
if parent_hash == header.parent_hash {
parent_hash = Header::from(&header).hash::<Host>().unwrap();
let result = verify_polygon_header::<Host>(&validators, header).unwrap();
finalized_header = result.header;
if let Some(next_validators) = result.next_validators {
validators = next_validators;
}
println!("Successfully verified header {:?}", finalized_header.number.low_u64());
} else {
println!("Header is not a descendant of known fork");
break
}
}
}
}
33 changes: 33 additions & 0 deletions parachain/modules/consensus/polygon-pos/verifier/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "polygon-pos-verifier"
version = "0.1.0"
edition = "2021"
authors = ["Polytope Labs"]

[dependencies]
log = { version = "0.4.17", default-features = false }
anyhow = { version = "1.0.75", default-features = false }
sp-core = {version = "27.0.0", default-features = false }
alloy-rlp = { version = "0.3.2", default-features = false }
alloy-primitives = { version = "0.5.3", default-features = false, features = ["rlp"] }
alloy-rlp-derive = "0.3.2"
codec = { package = "parity-scale-codec", version = "3.1.3", default-features = false }
ethabi = { version = "18.0.0", features = ["rlp", "parity-codec"], default-features = false }
ismp = { path = "../../../ismp/core", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
sp-io = { version = "27.0.0", default-features = false }

[features]
default = ["std"]
std = [
"log/std",
"anyhow/std",
"sp-core/std",
"codec/std",
"ismp/std",
"alloy-primitives/std",
"alloy-rlp/std",
"ethabi/std",
"scale-info/std",
"sp-io/std"
]
35 changes: 35 additions & 0 deletions parachain/modules/consensus/polygon-pos/verifier/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[warn(unused_imports)]
#[warn(unused_variables)]
use anyhow::anyhow;
use ismp::util::Keccak256;
use primitives::{get_signature, parse_validators, CodecHeader, Header};
use sp_core::{H160, H256};
pub mod primitives;
use alloc::collections::BTreeSet;

extern crate alloc;
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub hash: H256,
pub header: CodecHeader,
pub next_validators: Option<BTreeSet<H160>>,
}
/// This function simply verifies a polygon block header
pub fn verify_polygon_header<I: Keccak256>(
validators: &BTreeSet<H160>,
header: CodecHeader,
) -> Result<VerificationResult, anyhow::Error> {
let signature = get_signature(&header.extra_data)?;
let next_validators = parse_validators(&header.extra_data)?;
let rlp_header: Header = (&header).into();
let msg = rlp_header.clone().hash_without_sig::<I>()?;
let address = sp_io::crypto::secp256k1_ecdsa_recover(&signature, &msg.0)
.map_err(|_| anyhow!("Signature verification failed"))?;
let signer_hash = H160::from_slice(&I::keccak256(&address[..]).0[12..]);
if !validators.contains(&signer_hash) {
Err(anyhow!("Header is signed by unknown validator"))?
}
let hash = rlp_header.hash::<I>()?;
Ok(VerificationResult { hash, header, next_validators })
}
Loading
Loading