Skip to content

Commit

Permalink
feat(ethexe): introduce Router.commitBatch(), add timestamp for `…
Browse files Browse the repository at this point in the history
…CodeCommitment` (#4476)
  • Loading branch information
StackOverflowExcept1on authored Jan 30, 2025
1 parent aefe5e3 commit caf74e7
Show file tree
Hide file tree
Showing 26 changed files with 297 additions and 83 deletions.
8 changes: 4 additions & 4 deletions ethexe/common/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ impl BlockHeader {
}

#[derive(Debug, Clone, Default, Encode, Decode)]
pub struct CodeUploadInfo {
pub origin: ActorId,
pub struct CodeInfo {
pub timestamp: u64,
pub tx_hash: H256,
}

Expand Down Expand Up @@ -120,8 +120,8 @@ pub trait CodesStorage: Send + Sync {
fn instrumented_code(&self, runtime_id: u32, code_id: CodeId) -> Option<InstrumentedCode>;
fn set_instrumented_code(&self, runtime_id: u32, code_id: CodeId, code: InstrumentedCode);

fn code_blob_tx(&self, code_id: CodeId) -> Option<H256>;
fn set_code_blob_tx(&self, code_id: CodeId, tx_hash: H256);
fn code_info(&self, code_id: CodeId) -> Option<CodeInfo>;
fn set_code_info(&self, code_id: CodeId, code_info: CodeInfo);

fn code_valid(&self, code_id: CodeId) -> Option<bool>;
fn set_code_valid(&self, code_id: CodeId, valid: bool);
Expand Down
14 changes: 11 additions & 3 deletions ethexe/common/src/events/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub enum Event {
},
CodeValidationRequested {
code_id: CodeId,
timestamp: u64,
tx_hash: H256,
},
ComputationSettingsChanged {
Expand All @@ -49,9 +50,15 @@ pub enum Event {
impl Event {
pub fn to_request(self) -> Option<RequestEvent> {
Some(match self {
Self::CodeValidationRequested { code_id, tx_hash } => {
RequestEvent::CodeValidationRequested { code_id, tx_hash }
}
Self::CodeValidationRequested {
code_id,
timestamp,
tx_hash,
} => RequestEvent::CodeValidationRequested {
code_id,
timestamp,
tx_hash,
},
Self::ComputationSettingsChanged {
threshold,
wvara_per_second,
Expand All @@ -76,6 +83,7 @@ impl Event {
pub enum RequestEvent {
CodeValidationRequested {
code_id: CodeId,
timestamp: u64,
// TODO (breathx): replace with `code: Vec<u8>`
tx_hash: H256,
},
Expand Down
8 changes: 8 additions & 0 deletions ethexe/common/src/gear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,17 @@ pub struct BlockCommitment {
#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct CodeCommitment {
pub id: CodeId,
/// represented as u48 in router contract.
pub timestamp: u64,
pub valid: bool,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct BatchCommitment {
pub code_commitments: Vec<CodeCommitment>,
pub block_commitments: Vec<BlockCommitment>,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct ValidatorsCommitment {
pub aggregated_public_key: AggregatedPublicKey,
Expand Down
9 changes: 8 additions & 1 deletion ethexe/contracts/src/IRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ interface IRouter {
/// @dev ProgramCreated Emitted on success.
function createProgramWithDecoder(address decoderImpl, bytes32 codeId, bytes32 salt) external returns (address);

// # Validators calls.
/// @dev CodeGotValidated Emitted for each code in commitment.
function commitCodes(
Gear.CodeCommitment[] calldata codeCommitments,
Expand All @@ -128,4 +127,12 @@ interface IRouter {
Gear.SignatureType signatureType,
bytes[] calldata signatures
) external;

/// @dev CodeGotValidated Emitted for each code in commitment.
/// @dev BlockCommitted Emitted on success. Triggers multiple events for each corresponding mirror.
function commitBatch(
Gear.BatchCommitment calldata batchCommitment,
Gear.SignatureType signatureType,
bytes[] calldata signatures
) external;
}
85 changes: 79 additions & 6 deletions ethexe/contracts/src/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,14 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
return mirror;
}

// # Validators calls.

/// @dev Set validators for the next era.
function commitValidators(
Gear.ValidatorsCommitment calldata _validatorsCommitment,
Gear.SignatureType _signatureType,
bytes[] calldata _signatures
) external {
Storage storage router = _router();
require(router.genesisBlock.hash != bytes32(0), "router genesis is zero; call `lookupGenesisHash()` first");

uint256 currentEraIndex = (block.timestamp - router.genesisBlock.timestamp) / router.timelines.era;

Expand Down Expand Up @@ -311,6 +310,7 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
Storage storage router = _router();
require(router.genesisBlock.hash != bytes32(0), "router genesis is zero; call `lookupGenesisHash()` first");

uint256 maxTimestamp = 0;
bytes memory codeCommitmentsHashes;

for (uint256 i = 0; i < _codeCommitments.length; i++) {
Expand All @@ -331,10 +331,15 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
emit CodeGotValidated(codeCommitment.id, codeCommitment.valid);

codeCommitmentsHashes = bytes.concat(codeCommitmentsHashes, Gear.codeCommitmentHash(codeCommitment));
if (codeCommitment.timestamp > maxTimestamp) {
maxTimestamp = codeCommitment.timestamp;
}
}

require(
Gear.validateSignatures(router, keccak256(codeCommitmentsHashes), _signatureType, _signatures),
Gear.validateSignaturesAt(
router, keccak256(codeCommitmentsHashes), _signatureType, _signatures, maxTimestamp
),
"signatures verification failed"
);
}
Expand All @@ -347,10 +352,8 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
Storage storage router = _router();
require(router.genesisBlock.hash != bytes32(0), "router genesis is zero; call `lookupGenesisHash()` first");

require(_blockCommitments.length > 0, "no block commitments to commit");

bytes memory blockCommitmentsHashes;
uint256 maxTimestamp = 0;
bytes memory blockCommitmentsHashes;

for (uint256 i = 0; i < _blockCommitments.length; i++) {
Gear.BlockCommitment calldata blockCommitment = _blockCommitments[i];
Expand All @@ -371,6 +374,76 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
);
}

function commitBatch(
Gear.BatchCommitment calldata _batchCommitment,
Gear.SignatureType _signatureType,
bytes[] calldata _signatures
) external nonReentrant {
Storage storage router = _router();
require(router.genesisBlock.hash != bytes32(0), "router genesis is zero; call `lookupGenesisHash()` first");

require(
_batchCommitment.codeCommitments.length > 0 && _batchCommitment.blockCommitments.length > 0,
"no commitments to commit"
);

uint256 maxTimestamp = 0;

/* Commit Codes */

bytes memory codeCommitmentsHashes;

for (uint256 i = 0; i < _batchCommitment.codeCommitments.length; i++) {
Gear.CodeCommitment calldata codeCommitment = _batchCommitment.codeCommitments[i];

require(
router.protocolData.codes[codeCommitment.id] == Gear.CodeState.ValidationRequested,
"code must be requested for validation to be committed"
);

if (codeCommitment.valid) {
router.protocolData.codes[codeCommitment.id] = Gear.CodeState.Validated;
router.protocolData.validatedCodesCount++;
} else {
delete router.protocolData.codes[codeCommitment.id];
}

emit CodeGotValidated(codeCommitment.id, codeCommitment.valid);

codeCommitmentsHashes = bytes.concat(codeCommitmentsHashes, Gear.codeCommitmentHash(codeCommitment));
if (codeCommitment.timestamp > maxTimestamp) {
maxTimestamp = codeCommitment.timestamp;
}
}

/* Commit Blocks */

bytes memory blockCommitmentsHashes;

for (uint256 i = 0; i < _batchCommitment.blockCommitments.length; i++) {
Gear.BlockCommitment calldata blockCommitment = _batchCommitment.blockCommitments[i];
blockCommitmentsHashes = bytes.concat(blockCommitmentsHashes, _commitBlock(router, blockCommitment));
if (blockCommitment.timestamp > maxTimestamp) {
maxTimestamp = blockCommitment.timestamp;
}
}

// NOTE: Use maxTimestamp to validate signatures for all commitments.
// This means that if at least one commitment is for block from current era,
// then all commitments should be checked with current era validators.

require(
Gear.validateSignaturesAt(
router,
keccak256(abi.encodePacked(blockCommitmentsHashes, codeCommitmentsHashes)),
_signatureType,
_signatures,
maxTimestamp
),
"signatures verification failed"
);
}

/* Helper private functions */

function _createProgram(bytes32 _codeId, bytes32 _salt) private returns (address) {
Expand Down
32 changes: 21 additions & 11 deletions ethexe/contracts/src/libraries/Gear.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,10 @@ library Gear {
address wrappedVara;
}

struct ValidatorsCommitment {
AggregatedPublicKey aggregatedPublicKey;
VerifyingShare[] verifyingShares;
address[] validators;
uint256 eraIndex;
struct CodeCommitment {
bytes32 id;
uint48 timestamp;
bool valid;
}

struct BlockCommitment {
Expand All @@ -60,9 +59,16 @@ library Gear {
StateTransition[] transitions;
}

struct CodeCommitment {
bytes32 id;
bool valid;
struct ValidatorsCommitment {
AggregatedPublicKey aggregatedPublicKey;
VerifyingShare[] verifyingShares;
address[] validators;
uint256 eraIndex;
}

struct BatchCommitment {
CodeCommitment[] codeCommitments;
BlockCommitment[] blockCommitments;
}

enum CodeState {
Expand Down Expand Up @@ -163,20 +169,24 @@ library Gear {
}

function blockIsPredecessor(bytes32 hash) internal view returns (bool) {
for (uint256 i = block.number - 1; i > 0; i--) {
for (uint256 i = block.number - 1; i > 0;) {
bytes32 ret = blockhash(i);
if (ret == hash) {
return true;
} else if (ret == 0) {
break;
}

unchecked {
i--;
}
}

return false;
}

function codeCommitmentHash(CodeCommitment calldata codeCommitment) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(codeCommitment.id, codeCommitment.valid));
function codeCommitmentHash(CodeCommitment memory codeCommitment) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(codeCommitment.id, codeCommitment.timestamp, codeCommitment.valid));
}

function defaultComputationSettings() internal pure returns (ComputationSettings memory) {
Expand Down
2 changes: 1 addition & 1 deletion ethexe/contracts/test/Base.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ contract Base is POCBaseTest {

for (uint256 i = 0; i < _commitments.length; i++) {
Gear.CodeCommitment memory _commitment = _commitments[i];
_codesBytes = bytes.concat(_codesBytes, keccak256(abi.encodePacked(_commitment.id, _commitment.valid)));
_codesBytes = bytes.concat(_codesBytes, Gear.codeCommitmentHash(_commitment));
}

router.commitCodes(_commitments, Gear.SignatureType.FROST, signBytes(_privateKeys, _codesBytes));
Expand Down
2 changes: 1 addition & 1 deletion ethexe/contracts/test/POC.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ contract POCTest is Base {
uint256[] memory _privateKeys = new uint256[](1);
_privateKeys[0] = signingKey.asScalar();

commitCode(_privateKeys, Gear.CodeCommitment(_codeId, true));
commitCode(_privateKeys, Gear.CodeCommitment(_codeId, 42, true));

address _ping = deployPing(_privateKeys, _codeId);
IMirror actor = IMirror(_ping);
Expand Down
10 changes: 5 additions & 5 deletions ethexe/db/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
CASDatabase, KVDatabase,
};
use ethexe_common::{
db::{BlockHeader, BlockMetaStorage, CodesStorage, Schedule},
db::{BlockHeader, BlockMetaStorage, CodeInfo, CodesStorage, Schedule},
events::BlockRequestEvent,
gear::StateTransition,
};
Expand Down Expand Up @@ -378,17 +378,17 @@ impl CodesStorage for Database {
);
}

fn code_blob_tx(&self, code_id: CodeId) -> Option<H256> {
fn code_info(&self, code_id: CodeId) -> Option<CodeInfo> {
self.kv
.get(&KeyPrefix::CodeUpload.one(code_id))
.map(|data| {
Decode::decode(&mut data.as_slice()).expect("Failed to decode data into `H256`")
Decode::decode(&mut data.as_slice()).expect("Failed to decode data into `CodeInfo`")
})
}

fn set_code_blob_tx(&self, code_id: CodeId, tx_hash: H256) {
fn set_code_info(&self, code_id: CodeId, code_info: CodeInfo) {
self.kv
.put(&KeyPrefix::CodeUpload.one(code_id), tx_hash.encode());
.put(&KeyPrefix::CodeUpload.one(code_id), code_info.encode());
}

fn code_valid(&self, code_id: CodeId) -> Option<bool> {
Expand Down
2 changes: 1 addition & 1 deletion ethexe/ethereum/Mirror.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ethexe/ethereum/MirrorProxy.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ethexe/ethereum/Router.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ethexe/ethereum/TransparentUpgradeableProxy.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ethexe/ethereum/WrappedVara.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions ethexe/ethereum/src/abi/gear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl From<CodeCommitment> for Gear::CodeCommitment {
fn from(value: CodeCommitment) -> Self {
Self {
id: code_id_to_bytes32(value.id),
timestamp: u64_to_uint48_lossy(value.timestamp),
valid: value.valid,
}
}
Expand All @@ -78,6 +79,19 @@ impl From<ValidatorsCommitment> for Gear::ValidatorsCommitment {
}
}

impl From<BatchCommitment> for Gear::BatchCommitment {
fn from(value: BatchCommitment) -> Self {
Self {
blockCommitments: value
.block_commitments
.into_iter()
.map(Into::into)
.collect(),
codeCommitments: value.code_commitments.into_iter().map(Into::into).collect(),
}
}
}

impl From<Message> for Gear::Message {
fn from(value: Message) -> Self {
Self {
Expand Down
4 changes: 4 additions & 0 deletions ethexe/ethereum/src/router/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,14 @@ pub fn try_extract_event(log: &Log) -> Result<Option<RouterEvent>> {
let tx_hash = log
.transaction_hash
.ok_or_else(|| anyhow!("Tx hash not found"))?;
let block_timestamp = log
.block_timestamp
.ok_or_else(|| anyhow!("Block timestamp not found"))?;
let event = decode_log::<IRouter::CodeValidationRequested>(log)?;

RouterEvent::CodeValidationRequested {
code_id: bytes32_to_code_id(event.codeId),
timestamp: block_timestamp,
tx_hash: bytes32_to_h256(tx_hash),
}
}
Expand Down
Loading

0 comments on commit caf74e7

Please sign in to comment.