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

feat(ethexe): introduce Router.commitBatch(), add timestamp for CodeCommitment #4476

Merged
merged 11 commits into from
Jan 30, 2025
Merged
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,
StackOverflowExcept1on marked this conversation as resolved.
Show resolved Hide resolved
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>,
}
StackOverflowExcept1on marked this conversation as resolved.
Show resolved Hide resolved

#[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