serai-node which compiles and produces/finalizes blocks with --dev

This commit is contained in:
Luke Parker
2025-11-05 18:20:23 -05:00
parent 012b8fddae
commit 31874ceeae
7 changed files with 232 additions and 53 deletions

View File

@@ -98,6 +98,13 @@ pub mod pallet {
});
}
/// The code to run on genesis.
pub fn genesis() {
BlocksCommitmentMerkle::<T>::new_expecting_none();
BlockTransactionsCommitmentMerkle::<T>::new_expecting_none();
BlockEventsCommitmentMerkle::<T>::new_expecting_none();
}
/// The code to run when beginning execution of a transaction.
///
/// The caller MUST ensure two transactions aren't simultaneously started.
@@ -126,6 +133,7 @@ pub mod pallet {
/// Fetch all of Serai's events.
///
/// This MUST only be used for testing purposes.
#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
pub fn events() -> Vec<serai_abi::Event>
where
serai_abi::Event: TryFrom<T::RuntimeEvent>,
@@ -144,15 +152,13 @@ pub struct StartOfBlock<T: Config>(PhantomData<T>);
impl<T: Config> frame_support::traits::PreInherents for StartOfBlock<T> {
fn pre_inherents() {
use frame_support::pallet_prelude::Zero;
// `Pallet::genesis` is expected to be used for the genesis block
assert!(!frame_system::Pallet::<T>::block_number().is_zero());
if frame_system::Pallet::<T>::block_number().is_zero() {
BlocksCommitmentMerkle::<T>::new_expecting_none();
} else {
let parent_hash = frame_system::Pallet::<T>::parent_hash();
Blocks::<T>::set(parent_hash, Some(()));
let parent_hash: [u8; 32] = parent_hash.into();
BlocksCommitmentMerkle::<T>::append(&parent_hash);
}
let parent_hash = frame_system::Pallet::<T>::parent_hash();
Blocks::<T>::set(parent_hash, Some(()));
let parent_hash: [u8; 32] = parent_hash.into();
BlocksCommitmentMerkle::<T>::append(&parent_hash);
BlockTransactionsCommitmentMerkle::<T>::new_expecting_none();
BlockEventsCommitmentMerkle::<T>::new_expecting_none();

View File

@@ -1,12 +1,15 @@
#![expect(unused_imports, dead_code)]
use core::marker::PhantomData;
use sp_core::Pair as PairTrait;
use sc_service::ChainType;
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use rand_core::OsRng;
use zeroize::Zeroizing;
use ciphersuite::{
group::{ff::Field, GroupEncoding},
WrappedGroup, Ciphersuite,
};
use embedwards25519::Embedwards25519;
use secq256k1::Secq256k1;
@@ -25,15 +28,28 @@ fn insecure_account_from_name(name: &'static str) -> Public {
sp_core::sr25519::Pair::from_string(&format!("//{name}"), None).unwrap().public().into()
}
fn insecure_arbitrary_public_key_from_name<C: Ciphersuite>(name: &'static str) -> Vec<u8> {
let key = C::hash_to_F(name.as_bytes());
(C::generator() * key).to_bytes().as_ref().to_vec()
}
fn insecure_embedded_elliptic_curve_keys_from_name(
fn insecure_embedded_elliptic_curve_keys(
name: &'static str,
) -> Vec<SignedEmbeddedEllipticCurveKeys> {
vec![] // TODO
vec![
SignedEmbeddedEllipticCurveKeys::bitcoin(
&mut OsRng,
insecure_account_from_name(name),
&Zeroizing::new(<Embedwards25519 as WrappedGroup>::F::random(&mut OsRng)),
&Zeroizing::new(<Secq256k1 as WrappedGroup>::F::random(&mut OsRng)),
),
SignedEmbeddedEllipticCurveKeys::ethereum(
&mut OsRng,
insecure_account_from_name(name),
&Zeroizing::new(<Embedwards25519 as WrappedGroup>::F::random(&mut OsRng)),
&Zeroizing::new(<Secq256k1 as WrappedGroup>::F::random(&mut OsRng)),
),
SignedEmbeddedEllipticCurveKeys::monero(
&mut OsRng,
insecure_account_from_name(name),
&Zeroizing::new(<Embedwards25519 as WrappedGroup>::F::random(&mut OsRng)),
),
]
}
fn wasm_binary() -> Vec<u8> {
@@ -54,9 +70,7 @@ fn devnet_genesis(
) -> RuntimeGenesisConfig {
let validators = validators
.iter()
.map(|name| {
(insecure_account_from_name(name), insecure_embedded_elliptic_curve_keys_from_name(name))
})
.map(|name| (insecure_account_from_name(name), insecure_embedded_elliptic_curve_keys(name)))
.collect::<Vec<_>>();
RuntimeGenesisConfig {
@@ -80,20 +94,12 @@ fn devnet_genesis(
},
signals: SignalsConfig::default(),
babe: BabeConfig {
authorities: validators
.iter()
.map(|validator| (sp_core::sr25519::Public::from(validator.0).into(), 1))
.collect(),
// We leave this empty as `serai-validator-sets-pallet` initializes the authorities
authorities: vec![],
epoch_config: BABE_GENESIS_EPOCH_CONFIG,
_config: PhantomData,
},
grandpa: GrandpaConfig {
authorities: validators
.into_iter()
.map(|validator| (sp_core::sr25519::Public::from(validator.0).into(), 1))
.collect(),
_config: PhantomData,
},
grandpa: GrandpaConfig { authorities: vec![], _config: PhantomData },
}
}

View File

@@ -5,6 +5,12 @@ use sp_keystore::*;
pub struct Keystore(sr25519::Pair);
impl From<sr25519::Pair> for Keystore {
fn from(keypair: sr25519::Pair) -> Self {
Self(keypair)
}
}
impl Keystore {
pub fn from_env() -> Option<Self> {
let mut key_hex = serai_env::var("KEY")?;

View File

@@ -92,6 +92,11 @@ pub fn new_partial(
let keystore: Arc<dyn sp_keystore::Keystore> =
if let Some(keystore) = crate::keystore::Keystore::from_env() {
Arc::new(keystore)
} else if let Some(seed) = config.dev_key_seed.as_ref() {
Arc::new(crate::keystore::Keystore::from(
<sp_core::sr25519::Pair as sp_core::Pair>::from_string(seed, None)
.expect("dev key had invalid seed"),
))
} else {
keystore_container.keystore()
};
@@ -288,10 +293,6 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
let role = config.role;
let keystore = keystore_container;
if let Some(seed) = config.dev_key_seed.as_ref() {
let _ =
keystore.sr25519_generate_new(sp_core::crypto::key_types::AUTHORITY_DISCOVERY, Some(seed));
}
let prometheus_registry = config.prometheus_registry().cloned();
// TODO: Ensure we're considered as an authority is a validator of an external network

View File

@@ -16,6 +16,8 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true
[dependencies]
rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", features = ["derive"] }
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"] }
@@ -34,23 +36,26 @@ schnorrkel = { version = "0.11", default-features = false }
bech32 = { version = "0.11", default-features = false }
[dev-dependencies]
rand_core = { version = "0.6", default-features = false, features = ["std"] }
[features]
std = [
"rand_core/std",
"zeroize/std",
"borsh/std",
"bitvec/std",
"scale?/std",
"sp-core/std",
"ciphersuite/std",
"schnorr-signatures/std",
"dalek-ff-group/std",
"embedwards25519/std",
"secq256k1/std",
"dkg/std",
"schnorrkel/std",
"bech32/std"
]
serde = []

View File

@@ -1,10 +1,17 @@
use zeroize::Zeroize;
use core::ops::Deref;
use rand_core::{RngCore, CryptoRng};
use zeroize::{Zeroize, Zeroizing};
use borsh::{io, BorshSerialize, BorshDeserialize};
use sp_core::{ConstU32, bounded::BoundedVec};
use ciphersuite::{
group::{ff::FromUniformBytes, GroupEncoding},
group::{
ff::{Field, PrimeField, FromUniformBytes},
GroupEncoding,
},
WrappedGroup, GroupCanonicalEncoding,
};
use embedwards25519::Embedwards25519;
@@ -211,9 +218,7 @@ impl SignedEmbeddedEllipticCurveKeys {
}
}
/// Verify these key(s)' signature(s), returning the key(s) if valid.
pub fn verify(self, validator: Public) -> Option<EmbeddedEllipticCurveKeys> {
// Sample a unified challenge
fn transcript(&self, validator: Public) -> [u8; 64] {
let transcript = match &self {
Self::Bitcoin(e, s, e_sig, s_sig) => [
[ExternalNetworkId::Bitcoin as u8].as_slice(),
@@ -237,7 +242,12 @@ impl SignedEmbeddedEllipticCurveKeys {
[[ExternalNetworkId::Monero as u8].as_slice(), &validator.0, e, &e_sig[.. 32]].concat()
}
};
let challenge = sp_core::hashing::blake2_512(&transcript);
sp_core::hashing::blake2_512(&transcript)
}
/// Verify these key(s)' signature(s), returning the key(s) if valid.
pub fn verify(self, validator: Public) -> Option<EmbeddedEllipticCurveKeys> {
let challenge = self.transcript(validator);
// Verify the Schnorr signatures
match &self {
@@ -275,6 +285,137 @@ impl SignedEmbeddedEllipticCurveKeys {
Self::Monero(e, _) => EmbeddedEllipticCurveKeys::Monero(e),
})
}
#[doc(hidden)]
pub fn bitcoin(
rng: &mut (impl RngCore + CryptoRng),
validator: Public,
embedwards25519: &Zeroizing<<Embedwards25519 as WrappedGroup>::F>,
secq256k1: &Zeroizing<<Secq256k1 as WrappedGroup>::F>,
) -> Self {
let em_public_key =
(<Embedwards25519 as WrappedGroup>::generator() * embedwards25519.deref()).to_bytes();
let em_nonce = Zeroizing::new(<Embedwards25519 as WrappedGroup>::F::random(&mut *rng));
let em_nonce_commitment = <Embedwards25519 as WrappedGroup>::generator() * em_nonce.deref();
let mut em_sig = [0; 64];
em_sig[.. 32].copy_from_slice(em_nonce_commitment.to_bytes().as_ref());
let secq_public_key = (<Secq256k1 as WrappedGroup>::generator() * secq256k1.deref()).to_bytes();
let secq_nonce = Zeroizing::new(<Secq256k1 as WrappedGroup>::F::random(&mut *rng));
let secq_nonce_commitment = <Secq256k1 as WrappedGroup>::generator() * secq_nonce.deref();
let mut secq_sig = [0; 65];
secq_sig[.. 33].copy_from_slice(secq_nonce_commitment.to_bytes().as_ref());
let challenge =
SignedEmbeddedEllipticCurveKeys::Bitcoin(em_public_key, secq_public_key, em_sig, secq_sig)
.transcript(validator);
em_sig[32 ..].copy_from_slice(
SchnorrSignature::<Embedwards25519>::sign(
embedwards25519,
em_nonce,
<<Embedwards25519 as WrappedGroup>::F as FromUniformBytes<_>>::from_uniform_bytes(
&challenge,
),
)
.s
.to_repr()
.as_ref(),
);
secq_sig[33 ..].copy_from_slice(
SchnorrSignature::<Secq256k1>::sign(
secq256k1,
secq_nonce,
<<Secq256k1 as WrappedGroup>::F as FromUniformBytes<_>>::from_uniform_bytes(&challenge),
)
.s
.to_repr()
.as_ref(),
);
SignedEmbeddedEllipticCurveKeys::Bitcoin(em_public_key, secq_public_key, em_sig, secq_sig)
}
#[doc(hidden)]
pub fn ethereum(
rng: &mut (impl RngCore + CryptoRng),
validator: Public,
embedwards25519: &Zeroizing<<Embedwards25519 as WrappedGroup>::F>,
secq256k1: &Zeroizing<<Secq256k1 as WrappedGroup>::F>,
) -> Self {
let em_public_key =
(<Embedwards25519 as WrappedGroup>::generator() * embedwards25519.deref()).to_bytes();
let em_nonce = Zeroizing::new(<Embedwards25519 as WrappedGroup>::F::random(&mut *rng));
let em_nonce_commitment = <Embedwards25519 as WrappedGroup>::generator() * em_nonce.deref();
let mut em_sig = [0; 64];
em_sig[.. 32].copy_from_slice(em_nonce_commitment.to_bytes().as_ref());
let secq_public_key = (<Secq256k1 as WrappedGroup>::generator() * secq256k1.deref()).to_bytes();
let secq_nonce = Zeroizing::new(<Secq256k1 as WrappedGroup>::F::random(&mut *rng));
let secq_nonce_commitment = <Secq256k1 as WrappedGroup>::generator() * secq_nonce.deref();
let mut secq_sig = [0; 65];
secq_sig[.. 33].copy_from_slice(secq_nonce_commitment.to_bytes().as_ref());
let challenge =
SignedEmbeddedEllipticCurveKeys::Ethereum(em_public_key, secq_public_key, em_sig, secq_sig)
.transcript(validator);
em_sig[32 ..].copy_from_slice(
SchnorrSignature::<Embedwards25519>::sign(
embedwards25519,
em_nonce,
<<Embedwards25519 as WrappedGroup>::F as FromUniformBytes<_>>::from_uniform_bytes(
&challenge,
),
)
.s
.to_repr()
.as_ref(),
);
secq_sig[33 ..].copy_from_slice(
SchnorrSignature::<Secq256k1>::sign(
secq256k1,
secq_nonce,
<<Secq256k1 as WrappedGroup>::F as FromUniformBytes<_>>::from_uniform_bytes(&challenge),
)
.s
.to_repr()
.as_ref(),
);
SignedEmbeddedEllipticCurveKeys::Ethereum(em_public_key, secq_public_key, em_sig, secq_sig)
}
#[doc(hidden)]
pub fn monero(
rng: &mut (impl RngCore + CryptoRng),
validator: Public,
embedwards25519: &Zeroizing<<Embedwards25519 as WrappedGroup>::F>,
) -> Self {
let em_public_key =
(<Embedwards25519 as WrappedGroup>::generator() * embedwards25519.deref()).to_bytes();
let em_nonce = Zeroizing::new(<Embedwards25519 as WrappedGroup>::F::random(&mut *rng));
let em_nonce_commitment = <Embedwards25519 as WrappedGroup>::generator() * em_nonce.deref();
let mut em_sig = [0; 64];
em_sig[.. 32].copy_from_slice(em_nonce_commitment.to_bytes().as_ref());
let challenge =
SignedEmbeddedEllipticCurveKeys::Monero(em_public_key, em_sig).transcript(validator);
em_sig[32 ..].copy_from_slice(
SchnorrSignature::<Embedwards25519>::sign(
embedwards25519,
em_nonce,
<<Embedwards25519 as WrappedGroup>::F as FromUniformBytes<_>>::from_uniform_bytes(
&challenge,
),
)
.s
.to_repr()
.as_ref(),
);
SignedEmbeddedEllipticCurveKeys::Monero(em_public_key, em_sig)
}
}
impl BorshSerialize for SignedEmbeddedEllipticCurveKeys {

View File

@@ -57,12 +57,18 @@ frame_support::parameter_types! {
// TODO
pub BlockLength: frame_system::limits::BlockLength =
frame_system::limits::BlockLength::max_with_normal_ratio(0, Perbill::from_percent(0));
frame_system::limits::BlockLength::max_with_normal_ratio(
100 * 1024,
Perbill::from_percent(75),
);
// TODO
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::with_sensible_defaults(
Weight::from_parts(0, 0),
Perbill::from_percent(0),
Weight::from_parts(
2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND,
u64::MAX,
),
Perbill::from_percent(75),
);
}
@@ -161,7 +167,7 @@ impl serai_validator_sets_pallet::Config for Runtime {
}
impl serai_signals_pallet::Config for Runtime {
type RetirementValidityDuration = ConstU64<0>; // TODO
type RetirementLockInDuration = ConstU64<0>; // TODO
type RetirementLockInDuration = ConstU64<1>; // TODO
}
impl serai_coins_pallet::Config<LiquidityTokensInstance> for Runtime {
type AllowMint = serai_coins_pallet::AlwaysAllowMint;
@@ -177,7 +183,8 @@ impl serai_coins_pallet::Config<LiquidityTokensInstance> for Runtime {
impl pallet_timestamp::Config for Runtime {
type Moment = u64;
type OnTimestampSet = Babe;
type MinimumPeriod = ConstU64<0>; // TODO
// TODO
type MinimumPeriod = ConstU64<{ (6 * 1000) / 2 }>;
type WeightInfo = ();
}
@@ -197,9 +204,10 @@ impl pallet_session::Config for Runtime {
type MaxAuthorities =
ConstU32<{ serai_abi::primitives::validator_sets::KeyShares::MAX_PER_SET_U32 }>;
impl pallet_babe::Config for Runtime {
type EpochDuration = ConstU64<0>; // TODO
// TODO
type EpochDuration = ConstU64<{ (7 * 24 * 60 * 60 * 1000) / (6 * 1000) }>;
type ExpectedBlockTime = ConstU64<0>; // TODO
type ExpectedBlockTime = ConstU64<{ 6 * 1000 }>; // TODO
type EpochChangeTrigger = pallet_babe::ExternalTrigger;
type WeightInfo = ();
@@ -341,7 +349,13 @@ pub const BABE_GENESIS_EPOCH_CONFIG: sp_consensus_babe::BabeEpochConfiguration =
sp_api::impl_runtime_apis! {
impl crate::GenesisApi<Block> for Runtime {
fn build(genesis: RuntimeGenesisConfig) {
<RuntimeGenesisConfig as frame_support::traits::BuildGenesisConfig>::build(&genesis)
Core::genesis();
Core::start_transaction();
<RuntimeGenesisConfig as frame_support::traits::BuildGenesisConfig>::build(&genesis);
Core::end_transaction([0; 32]);
<
serai_core_pallet::EndOfBlock<Runtime> as frame_support::traits::PostTransactions
>::post_transactions();
}
}