Skip to content

Commit

Permalink
Permissioned L1 stake table (#2325)
Browse files Browse the repository at this point in the history
* Initial draft for minimal L1 stake table

* Add Schnorr verifying key

* fix solhint error: named mapping keys and values

* WIP: contract stake table types to rust types

Missing conversion from G2 Affine to BLSPubKey.

* rename: SimpleStakeTable -> PermissionedStakeTable

* PermissionedStakeTable contract: add DA flag

- Update bindings.
- Add tests for event emission.

* Allow setting an initial stake table on deployment

- The stake table isn't useful without stakers I think it makes sense to
  require it to be provided on deployment.

* deploy: load initial stake table from toml file

* Use serde instead of unsafe rust

We will add some more ergonomic code for conversion to jellyfish at some
point. This code can be used as a stop gap until then.

* Update utils/src/deployer.rs

Co-authored-by: Alysia Tech <[email protected]>

* WIP: load initial stake table from file

* Move solidity <-> jf code to contract adapter

* Load initial stake table from file

* remove unused imports

* remove unused module

* dev-node: fix arguments to deploy function

* fix clippy

* fix Cargo.toml for bindings generation

breaks if we use: `serde.workspace = true`

* combine adding and removing into single event

* update comment to match implementation (#2282)

* align comment with implementation

* added more clarity

* comment format

* clearer comment

* update comment to make it clear that there may be outdated elements in the state history

* update query-service

* db max connections = 25 for dev node tests

* update query-service

* Fix no-storage decides

* Branches -> tags

* 2368 stake table registration with fixed stake (#2365)

* add stake table tests

* remove stake types

* verify token allowance, balance and reprioritize verification order on registration

* set the fixed stake amount, added related tests, updated data types

* add more verification checks to the withdraw function

* updated errror types

* added TODO statements in comments to be explicit about outdated functions that need to be updated to the new spec

* fix env var for permissioned_stake_table

* Add test for jf / contract types conversions

* cleanup: pass rng

* load stake table from toml file (#2377)

* change lc proxy addr

* fix deploy-sequencer-contracts in docker compose

* test for stake table from toml file

* initial stake table toml file

* exclude toml file in typos

* remove custom deser

* remove unnecessary rand dependency

---------

Co-authored-by: Alysia Tech <[email protected]>
Co-authored-by: tbro <[email protected]>
Co-authored-by: imabdulbasit <[email protected]>
Co-authored-by: Artemii Gerasimovich <[email protected]>
Co-authored-by: Abdul Basit <[email protected]>
  • Loading branch information
6 people authored Dec 10, 2024
1 parent bc5b7f1 commit 979cfb3
Show file tree
Hide file tree
Showing 24 changed files with 1,880 additions and 5 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ ESPRESSO_BUILDER_ETH_ACCOUNT_INDEX=8
ESPRESSO_DEPLOYER_ACCOUNT_INDEX=9

# Contracts
ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS=0x0c8e79f3534b00d9a3d4a856b665bf4ebc22f2ba
ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS=0xf7cd8fa9b94db2aa972023b379c7f72c65e4de9d
ESPRESSO_SEQUENCER_LIGHTCLIENT_ADDRESS=$ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS
ESPRESSO_SEQUENCER_PERMISSIONED_PROVER=0x14dc79964da2c08b23698b3d3cc7ca32193d9955
SPRESSO_SEQUENCER_PERMISSIONED_STAKE_TABLE_ADDRESS=0x8ce361602b935680e8dec218b820ff5056beb7af

# Example sequencer demo private keys
ESPRESSO_DEMO_SEQUENCER_STAKING_PRIVATE_KEY_0=BLS_SIGNING_KEY~lNDh4Pn-pTAyzyprOAFdXHwhrKhEwqwtMtkD3CZF4x3o
Expand Down
2 changes: 2 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[files]
extend-exclude = [
"data/initial_stake_table.toml",
".env",
"*.json",
"**/*.pdf",
"doc/*.svg",
"doc/*.puml",
"contracts/lib",
Expand Down
9 changes: 9 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 contract-bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod light_client;
pub mod light_client_mock;
pub mod light_client_state_update_vk;
pub mod light_client_state_update_vk_mock;
pub mod permissioned_stake_table;
pub mod plonk_verifier;
pub mod plonk_verifier_2;
pub mod shared_types;
1,169 changes: 1,169 additions & 0 deletions contract-bindings/src/permissioned_stake_table.rs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions contracts/rust/adapter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ edition = { workspace = true }
[dependencies]
anyhow = { workspace = true }
ark-bn254 = { workspace = true }
ark-ec = { workspace = true }
ark-ed-on-bn254 = { workspace = true }
ark-ff = { workspace = true }
ark-poly = { workspace = true }
ark-serialize = { workspace = true }
ark-std = { workspace = true }
contract-bindings = { path = "../../../contract-bindings" }
derive_more = { workspace = true }
diff-test-bn254 = { git = "https://github.com/EspressoSystems/solidity-bn254.git" }
ethers = { version = "2.0.4" }
hotshot-types = { workspace = true }
Expand All @@ -22,6 +25,7 @@ jf-utils = { workspace = true }
libp2p = { workspace = true, features = ["serde"] }
num-bigint = { version = "0.4", default-features = false }
num-traits = { version = "0.2", default-features = false }
serde = { workspace = true }

[[bin]]
name = "eval-domain"
Expand Down
1 change: 1 addition & 0 deletions contracts/rust/adapter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod jellyfish;
pub mod light_client;
pub mod stake_table;

// Archived, legacy helpers and tests, to be removed soon. not included, reference/read only
// mod archived
224 changes: 224 additions & 0 deletions contracts/rust/adapter/src/stake_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
use crate::jellyfish::u256_to_field;
use ark_ec::{
short_weierstrass,
twisted_edwards::{self, Affine, TECurveConfig},
AffineRepr,
};
use ark_ed_on_bn254::EdwardsConfig;
use ark_ff::{BigInteger, PrimeField};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use contract_bindings::permissioned_stake_table::{self, EdOnBN254Point, NodeInfo};
use diff_test_bn254::ParsedG2Point;
use ethers::{
abi::AbiDecode,
prelude::{AbiError, EthAbiCodec, EthAbiType},
types::U256,
};
use hotshot_types::{light_client::StateVerKey, network::PeerConfigKeys, signature_key::BLSPubKey};
use serde::{Deserialize, Serialize};
use std::str::FromStr;

// TODO: (alex) maybe move these commonly shared util to a crate
/// convert a field element to U256, panic if field size is larger than 256 bit
pub fn field_to_u256<F: PrimeField>(f: F) -> U256 {
if F::MODULUS_BIT_SIZE > 256 {
panic!("Shouldn't convert a >256-bit field to U256");
}
U256::from_little_endian(&f.into_bigint().to_bytes_le())
}

/// an intermediate representation of `EdOnBN254Point` in solidity.
#[derive(Clone, PartialEq, Eq, Debug, EthAbiType, EthAbiCodec)]
pub struct ParsedEdOnBN254Point {
/// x coordinate of affine repr
pub x: U256,
/// y coordinate of affine repr
pub y: U256,
}

// this is convention from BN256 precompile
impl Default for ParsedEdOnBN254Point {
fn default() -> Self {
Self {
x: U256::from(0),
y: U256::from(0),
}
}
}

impl From<ParsedEdOnBN254Point> for EdOnBN254Point {
fn from(value: ParsedEdOnBN254Point) -> Self {
Self {
x: value.x,
y: value.y,
}
}
}

impl From<EdOnBN254Point> for ParsedEdOnBN254Point {
fn from(value: EdOnBN254Point) -> Self {
let EdOnBN254Point { x, y } = value;
Self { x, y }
}
}

impl FromStr for ParsedEdOnBN254Point {
type Err = AbiError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parsed: (Self,) = AbiDecode::decode_hex(s)?;
Ok(parsed.0)
}
}

impl<P: TECurveConfig> From<Affine<P>> for ParsedEdOnBN254Point
where
P::BaseField: PrimeField,
{
fn from(p: Affine<P>) -> Self {
if p.is_zero() {
// this convention is from the BN precompile
Self {
x: U256::from(0),
y: U256::from(0),
}
} else {
Self {
x: field_to_u256::<P::BaseField>(*p.x().unwrap()),
y: field_to_u256::<P::BaseField>(*p.y().unwrap()),
}
}
}
}

impl<P: TECurveConfig> From<ParsedEdOnBN254Point> for Affine<P>
where
P::BaseField: PrimeField,
{
fn from(p: ParsedEdOnBN254Point) -> Self {
if p == ParsedEdOnBN254Point::default() {
Self::default()
} else {
Self::new_unchecked(
u256_to_field::<P::BaseField>(p.x),
u256_to_field::<P::BaseField>(p.y),
)
}
}
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct NodeInfoJf {
pub stake_table_key: BLSPubKey,
pub state_ver_key: StateVerKey,
pub da: bool,
}

impl From<NodeInfoJf> for NodeInfo {
fn from(value: NodeInfoJf) -> Self {
let NodeInfoJf {
stake_table_key,
state_ver_key,
da,
} = value;
let ParsedG2Point { x0, x1, y0, y1 } = stake_table_key.to_affine().into();
let schnorr: ParsedEdOnBN254Point = state_ver_key.to_affine().into();
Self {
bls_vk: permissioned_stake_table::G2Point {
x_0: x0,
x_1: x1,
y_0: y0,
y_1: y1,
},
schnorr_vk: schnorr.into(),
is_da: da,
}
}
}

impl From<NodeInfo> for NodeInfoJf {
fn from(value: NodeInfo) -> Self {
let NodeInfo {
bls_vk,
schnorr_vk,
is_da,
} = value;
let stake_table_key = {
let g2 = diff_test_bn254::ParsedG2Point {
x0: bls_vk.x_0,
x1: bls_vk.x_1,
y0: bls_vk.y_0,
y1: bls_vk.y_1,
};
let g2_affine = short_weierstrass::Affine::<ark_bn254::g2::Config>::from(g2);
let mut bytes = vec![];
// TODO: remove serde round-trip once jellyfin provides a way to
// convert from Affine representation to VerKey.
//
// Serialization and de-serialization shouldn't fail.
g2_affine
.into_group()
.serialize_compressed(&mut bytes)
.unwrap();
BLSPubKey::deserialize_compressed(&bytes[..]).unwrap()
};
let state_ver_key = {
let g1_point: ParsedEdOnBN254Point = schnorr_vk.into();
let state_sk_affine = twisted_edwards::Affine::<EdwardsConfig>::from(g1_point);
StateVerKey::from(state_sk_affine)
};
Self {
stake_table_key,
state_ver_key,
da: is_da,
}
}
}

impl From<PeerConfigKeys<BLSPubKey>> for NodeInfoJf {
fn from(value: PeerConfigKeys<BLSPubKey>) -> Self {
let PeerConfigKeys {
stake_table_key,
state_ver_key,
da,
..
} = value;
Self {
stake_table_key,
state_ver_key,
da,
}
}
}

#[cfg(test)]
mod test {
use super::*;
use ark_std::rand::{Rng, RngCore};
use hotshot_types::{light_client::StateKeyPair, traits::signature_key::BuilderSignatureKey};

impl NodeInfoJf {
fn random(rng: &mut impl RngCore) -> Self {
let mut seed = [0u8; 32];
rng.fill_bytes(&mut seed);

let (stake_table_key, _) = BLSPubKey::generated_from_seed_indexed(seed, 0);
let state_key_pair = StateKeyPair::generate_from_seed_indexed(seed, 0);
Self {
stake_table_key,
state_ver_key: state_key_pair.ver_key(),
da: rng.gen(),
}
}
}

#[test]
fn test_node_info_round_trip() {
let mut rng = ark_std::rand::thread_rng();
for _ in 0..20 {
let jf = NodeInfoJf::random(&mut rng);
let sol: NodeInfo = jf.clone().into();
let jf2: NodeInfoJf = sol.into();
assert_eq!(jf2, jf);
}
}
}
Loading

0 comments on commit 979cfb3

Please sign in to comment.