Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

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

83 changes: 62 additions & 21 deletions ethexe/common/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::{
Announce, Digest, HashOf, ToDigest,
Address, Announce, Digest, HashOf, ProtocolTimelines, ToDigest,
ecdsa::{ContractSignature, VerifiedData},
gear::BatchCommitment,
validators::ValidatorsVec,
Expand All @@ -43,22 +43,39 @@ pub type VerifiedValidationReply = VerifiedData<BatchCommitmentValidationReply>;

// TODO #4553: temporary implementation, should be improved
/// Returns block producer for time slot. Next slot is the next validator in the list.
pub const fn block_producer_index(validators_amount: usize, slot: u64) -> usize {
pub const fn block_producer_index_for_slot(validators_amount: usize, slot: u64) -> usize {
(slot % validators_amount as u64) as usize
}

/// Calculates the producer address for a given slot based on the validators and timestamp.
pub fn block_producer_for(
validators: &ValidatorsVec,
timestamp: u64,
slot_duration: u64,
) -> crate::Address {
let slot = timestamp / slot_duration;
let index = block_producer_index(validators.len(), slot);
validators
.get(index)
.cloned()
.unwrap_or_else(|| unreachable!("index must be valid"))
impl ProtocolTimelines {
/// Calculates the producer address for a given timestamp.
///
/// # Arguments
/// * `validators` - A non-empty vector of validator addresses.
/// * `timestamp` - The timestamp for which to calculate the block producer.
///
/// # Panics
/// Panics if timestamp is before genesis.
pub fn block_producer_at(&self, validators: &ValidatorsVec, timestamp: u64) -> Address {
let block_producer_index = self.block_producer_index_at(validators.len(), timestamp);
validators
.get(block_producer_index)
.cloned()
.unwrap_or_else(|| unreachable!("index must be valid"))
}

/// Calculates the block producer index for a given timestamp.
///
/// # Arguments
/// * `validators_amount` - The number of validators in the protocol.
/// * `timestamp` - The timestamp for which to calculate the block producer index.
///
/// # Panics
/// Panics if timestamp is before genesis or if validators_amount is zero.
pub fn block_producer_index_at(&self, validators_amount: usize, timestamp: u64) -> usize {
let slot = self.slot_from_ts(timestamp);
block_producer_index_for_slot(validators_amount, slot)
}
}

/// Represents a request for validating a batch commitment.
Expand Down Expand Up @@ -144,24 +161,48 @@ mod tests {
let validators_amount = 5;
let slot = 7;

let index = block_producer_index(validators_amount, slot);
let index = block_producer_index_for_slot(validators_amount, slot);

assert_eq!(index, 2);
}

#[test]
fn block_producer_for_calculates_correct_producer() {
let validators = vec![
crate::Address::from([1; 20]),
crate::Address::from([2; 20]),
crate::Address::from([3; 20]),
Address::from([1; 20]),
Address::from([2; 20]),
Address::from([3; 20]),
]
.try_into()
.unwrap();

let producer = ProtocolTimelines {
slot: 1,
genesis_ts: 0,
..Default::default()
}
.block_producer_at(&validators, 10);

assert_eq!(producer, Address::from([2; 20]));
}

#[test]
fn block_producer_for_calculates_correct_producer_with_genesis_timestamp() {
let validators = vec![
Address::from([1; 20]),
Address::from([2; 20]),
Address::from([3; 20]),
]
.try_into()
.unwrap();
let timestamp = 10;

let producer = block_producer_for(&validators, timestamp, 1);
let producer = ProtocolTimelines {
slot: 2,
genesis_ts: 6,
..Default::default()
}
.block_producer_at(&validators, 16);

assert_eq!(producer, validators[timestamp as usize % validators.len()]);
assert_eq!(producer, Address::from([3; 20]));
}
}
8 changes: 8 additions & 0 deletions ethexe/common/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ pub struct ProtocolTimelines {
pub slot: u64,
}

// TODO: #5290 remove panics here
impl ProtocolTimelines {
/// Returns the era index for the given timestamp. Eras starts from 0.
/// If given `ts` less than `genesis_ts` function returns `0`;
Expand All @@ -276,6 +277,13 @@ impl ProtocolTimelines {
pub fn era_election_start_ts(&self, era_index: u64) -> u64 {
self.era_start_ts(era_index + 1) - self.election
}

#[inline(always)]
pub fn slot_from_ts(&self, ts: u64) -> u64 {
ts.checked_sub(self.genesis_ts)
.expect("timestamp must be >= genesis_ts")
/ self.slot
}
}

/// RemoveFromMailbox key; (msgs sources program (mailbox and queue provider), destination user id)
Expand Down
18 changes: 6 additions & 12 deletions ethexe/consensus/src/connect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::{
use anyhow::{Result, anyhow};
use ethexe_common::{
Address, Announce, HashOf, PromisePolicy, ProtocolTimelines, SimpleBlockData,
consensus::{VerifiedAnnounce, VerifiedValidationRequest, block_producer_for},
consensus::{VerifiedAnnounce, VerifiedValidationRequest},
db::{ConfigStorageRO, OnChainStorageRO},
injected::{Promise, SignedInjectedTransaction},
network::{AnnouncesRequest, AnnouncesResponse},
Expand All @@ -42,7 +42,6 @@ use std::{
num::NonZeroUsize,
pin::Pin,
task::{Context, Poll},
time::Duration,
};

/// Maximum number of pending announces to store
Expand Down Expand Up @@ -100,7 +99,6 @@ enum State {
#[derive(derive_more::Debug)]
pub struct ConnectService {
db: Database,
slot_duration: Duration,
commitment_delay_limit: u32,
timelines: ProtocolTimelines,

Expand All @@ -114,14 +112,12 @@ impl ConnectService {
///
/// # Parameters
/// - `db`: Database instance.
/// - `slot_duration`: Duration of each slot in the consensus protocol.
/// - `commitment_delay_limit`: Maximum allowed delay for announce to be committed.
pub fn new(db: Database, slot_duration: Duration, commitment_delay_limit: u32) -> Self {
pub fn new(db: Database, commitment_delay_limit: u32) -> Self {
let timelines = db.config().timelines;

Self {
db,
slot_duration,
commitment_delay_limit,
timelines,
state: State::WaitingForBlock,
Expand Down Expand Up @@ -193,11 +189,9 @@ impl ConsensusService for ConnectService {
let validators = self.db.validators(block_era).ok_or(anyhow!(
"validators not found for synced block({block_hash})"
))?;
let producer = block_producer_for(
&validators,
block.header.timestamp,
self.slot_duration.as_secs(),
);
let producer = self
.timelines
.block_producer_at(&validators, block.header.timestamp);

self.state = State::WaitingForPreparedBlock {
block: *block,
Expand Down Expand Up @@ -383,7 +377,7 @@ mod tests {
let db = Database::memory();
let chain = BlockChain::mock((10, validators)).setup(&db);

let mut service = ConnectService::new(db, Duration::from_secs(12), 10);
let mut service = ConnectService::new(db, 10);
service
.receive_new_chain_head(chain.blocks[10].to_simple())
.unwrap();
Expand Down
2 changes: 0 additions & 2 deletions ethexe/consensus/src/validator/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ use tokio::sync::RwLock;

#[derive(derive_more::Debug)]
pub struct ValidatorCore {
pub slot_duration: Duration,
pub signatures_threshold: u64,
pub router_address: Address,
pub pub_key: PublicKey,
Expand Down Expand Up @@ -69,7 +68,6 @@ pub struct ValidatorCore {
impl Clone for ValidatorCore {
fn clone(&self) -> Self {
Self {
slot_duration: self.slot_duration,
signatures_threshold: self.signatures_threshold,
router_address: self.router_address,
pub_key: self.pub_key,
Expand Down
12 changes: 5 additions & 7 deletions ethexe/consensus/src/validator/initial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use anyhow::{Result, anyhow};
use derive_more::{Debug, Display};
use ethexe_common::{
SimpleBlockData,
consensus::block_producer_for,
db::OnChainStorageRO,
network::{AnnouncesRequest, AnnouncesResponse},
};
Expand Down Expand Up @@ -228,11 +227,10 @@ impl ValidatorContext {
.validators(era_index)
.ok_or(anyhow!("validators not found for era {era_index}"))?;

let producer = block_producer_for(
&validators,
block.header.timestamp,
self.core.slot_duration.as_secs(),
);
let producer = self
.core
.timelines
.block_producer_at(&validators, block.header.timestamp);
let my_address = self.core.pub_key.to_address();

if my_address == producer {
Expand Down Expand Up @@ -287,9 +285,9 @@ mod tests {

let (mut ctx, keys, _) = mock_validator_context();
let validators: ValidatorsVec = nonempty![
ctx.core.pub_key.to_address(),
keys[0].to_address(),
keys[1].to_address(),
ctx.core.pub_key.to_address(),
]
.into();

Expand Down
3 changes: 1 addition & 2 deletions ethexe/consensus/src/validator/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,14 @@ pub fn mock_validator_context() -> (ValidatorContext, Vec<PublicKey>, MockEthere
let (signer, _, mut keys) = crate::mock::init_signer_with_keys(10);
let ethereum = MockEthereum::default();
let db = Database::memory();
let timelines = ProtocolTimelines::mock(());
let timelines = ProtocolTimelines::mock(()).tap_mut(|tl| tl.slot = 1);

let limits = BatchLimits::default();
let middleware = MiddlewareWrapper::from_inner(ethereum.clone());
let batch_manager = BatchCommitmentManager::new(limits, db.clone(), middleware);

let ctx = ValidatorContext {
core: ValidatorCore {
slot_duration: Duration::from_secs(1),
signatures_threshold: 1,
router_address: 12345.into(),
pub_key: keys.pop().unwrap(),
Expand Down
3 changes: 0 additions & 3 deletions ethexe/consensus/src/validator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ pub struct ValidatorConfig {
/// ECDSA multi-signature threshold
// TODO #4637: threshold should be a ratio (and maybe also a block dependent value)
pub signatures_threshold: u64,
/// Duration of ethexe slot (only to identify producer for the incoming blocks)
pub slot_duration: Duration,
/// Block gas limit for producer to create announces
pub block_gas_limit: u64,
/// Delay limit for commitment
Expand Down Expand Up @@ -149,7 +147,6 @@ impl ValidatorService {

let ctx = ValidatorContext {
core: ValidatorCore {
slot_duration: config.slot_duration,
signatures_threshold: config.signatures_threshold,
router_address: config.router_address,
pub_key: config.pub_key,
Expand Down
7 changes: 1 addition & 6 deletions ethexe/rpc/src/apis/injected.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use anyhow::Result;
use dashmap::DashMap;
use ethexe_common::{
Address, HashOf,
consensus::block_producer_for,
injected::{
AddressedInjectedTransaction, InjectedTransaction, InjectedTransactionAcceptance,
SignedPromise,
Expand Down Expand Up @@ -308,11 +307,7 @@ mod utils {
.validators(era)
.ok_or_else(|| anyhow::anyhow!("validators not found for era={era}"))?;

Ok(block_producer_for(
&validators,
target_timestamp,
timelines.slot,
))
Ok(timelines.block_producer_at(&validators, target_timestamp))
}

/// Returns the current time since [SystemTime::UNIX_EPOCH].
Expand Down
1 change: 1 addition & 0 deletions ethexe/service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ jsonrpsee = { workspace = true, features = ["client"] }
async-broadcast.workspace = true
wat.workspace = true
tempfile.workspace = true
chrono = "0.4"

demo-ping = { workspace = true, features = ["debug", "ethexe"] }
demo-value-sender-ethexe = { workspace = true, features = ["debug", "ethexe"] }
Expand Down
7 changes: 1 addition & 6 deletions ethexe/service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ impl Service {
ValidatorConfig {
pub_key,
signatures_threshold: threshold,
slot_duration: config.ethereum.block_time,
block_gas_limit: config.node.block_gas_limit,
// TODO: #4942 commitment_delay_limit is a protocol specific constant
// which better to be configurable by router contract
Expand All @@ -341,11 +340,7 @@ impl Service {
},
)?)
} else {
Box::pin(ConnectService::new(
db.clone(),
config.ethereum.block_time,
3,
))
Box::pin(ConnectService::new(db.clone(), 3))
}
};

Expand Down
15 changes: 7 additions & 8 deletions ethexe/service/src/tests/utils/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ use ethexe_blob_loader::{BlobLoader, BlobLoaderService, ConsensusLayerConfig};
use ethexe_common::{
Address, COMMITMENT_DELAY_LIMIT, CodeAndId, DEFAULT_BLOCK_GAS_LIMIT, SimpleBlockData, ToDigest,
ValidatorsVec,
consensus::{DEFAULT_BATCH_SIZE_LIMIT, DEFAULT_CHAIN_DEEPNESS_THRESHOLD, block_producer_index},
consensus::{DEFAULT_BATCH_SIZE_LIMIT, DEFAULT_CHAIN_DEEPNESS_THRESHOLD},
db::ConfigStorageRO,
ecdsa::{PrivateKey, PublicKey, SignedData},
events::{
BlockEvent, MirrorEvent, RouterEvent,
Expand Down Expand Up @@ -636,11 +637,11 @@ impl TestEnv {
/// that can produce blocks for the same rpc node,
/// then the return may be outdated.
pub async fn next_block_producer_index(&self) -> usize {
let timestamp = self.latest_block().await.header.timestamp;
block_producer_index(
self.validators.len(),
(timestamp + self.block_time.as_secs()) / self.block_time.as_secs(),
)
let timestamp = self.latest_block().await.header.timestamp + self.block_time.as_secs();
self.db
.config()
.timelines
.block_producer_index_at(self.validators.len(), timestamp)
}

/// Waits until the next block producer index becomes equal to `index`.
Expand Down Expand Up @@ -996,7 +997,6 @@ impl Node {
ethexe_consensus::ValidatorConfig {
pub_key: config.public_key,
signatures_threshold: self.threshold,
slot_duration: self.block_time,
block_gas_limit: DEFAULT_BLOCK_GAS_LIMIT,
commitment_delay_limit: self.commitment_delay_limit,
producer_delay: self.block_time / 6,
Expand All @@ -1010,7 +1010,6 @@ impl Node {
} else {
Box::pin(ConnectService::new(
self.db.clone(),
self.block_time,
self.commitment_delay_limit,
))
}
Expand Down
Loading