Skip to content

Commit

Permalink
Global db nonces in InterchainDB (#2068)
Browse files Browse the repository at this point in the history
* Use dbNonce in `InterchainEntry` struct

* Update InterchainDB: source chain logic

* Update InterchainDB: destination chain logic

* Update InterchainDB tests

* Use `dbNonce` in InterchainClient

* Update the rest of the tests
  • Loading branch information
ChiTimesChi authored Feb 20, 2024
1 parent 207e894 commit b344693
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 426 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 {
uint64 nonce,
bytes options,
bytes32 indexed transactionId,
uint256 dbWriterNonce
uint256 dbNonce
);

// @notice Emitted when an interchain transaction is executed.
Expand All @@ -71,7 +71,7 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 {
uint64 nonce,
bytes options,
bytes32 indexed transactionId,
uint256 dbWriterNonce
uint256 dbNonce
);

/**
Expand All @@ -86,7 +86,7 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 {
uint64 nonce;
bytes options;
bytes32 transactionId;
uint256 dbWriterNonce;
uint256 dbNonce;
}

function _generateTransactionId(
Expand Down Expand Up @@ -131,15 +131,15 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 {
nonce: clientNonce,
options: options,
transactionId: 0,
dbWriterNonce: 0
dbNonce: 0
});

bytes32 transactionId = _generateTransactionId(
icTx.srcSender, icTx.srcChainId, icTx.dstReceiver, icTx.dstChainId, icTx.message, icTx.nonce, icTx.options
);
icTx.transactionId = transactionId;

icTx.dbWriterNonce = IInterchainDB(interchainDB).writeEntryWithVerification{value: verificationFees}(
icTx.dbNonce = IInterchainDB(interchainDB).writeEntryWithVerification{value: verificationFees}(
icTx.dstChainId, icTx.transactionId, srcModules
);
IExecutionService(srcExecutionService).requestExecution({
Expand All @@ -160,7 +160,7 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 {
icTx.nonce,
icTx.options,
icTx.transactionId,
icTx.dbWriterNonce
icTx.dbNonce
);
// Increment nonce for next message
clientNonce++;
Expand Down Expand Up @@ -192,8 +192,8 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 {
// Construct expected entry based on icTransaction data
InterchainEntry memory icEntry = InterchainEntry({
srcChainId: icTx.srcChainId,
dbNonce: icTx.dbNonce,
srcWriter: linkedClients[icTx.srcChainId],
writerNonce: icTx.dbWriterNonce,
dataHash: icTx.transactionId
});

Expand Down Expand Up @@ -280,7 +280,7 @@ contract InterchainClientV1 is Ownable, IInterchainClientV1 {
icTx.nonce,
icTx.options,
icTx.transactionId,
icTx.dbWriterNonce
icTx.dbNonce
);
}
}
65 changes: 32 additions & 33 deletions packages/contracts-communication/contracts/InterchainDB.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {InterchainEntry, InterchainEntryLib} from "./libs/InterchainEntry.sol";
import {TypeCasts} from "./libs/TypeCasts.sol";

contract InterchainDB is InterchainDBEvents, IInterchainDB {
mapping(address writer => bytes32[] dataHashes) internal _entries;
mapping(bytes32 entryId => mapping(address module => RemoteEntry entry)) internal _remoteEntries;
LocalEntry[] internal _entries;
mapping(address module => mapping(bytes32 entryKey => RemoteEntry entry)) internal _remoteEntries;

modifier onlyRemoteChainId(uint256 chainId) {
if (chainId == block.chainid) {
Expand All @@ -22,22 +22,21 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB {
// ═══════════════════════════════════════════════ WRITER-FACING ═══════════════════════════════════════════════════

/// @inheritdoc IInterchainDB
function writeEntry(bytes32 dataHash) external returns (uint256 writerNonce) {
function writeEntry(bytes32 dataHash) external returns (uint256 dbNonce) {
return _writeEntry(dataHash);
}

/// @inheritdoc IInterchainDB
function requestVerification(
uint256 destChainId,
address writer,
uint256 writerNonce,
uint256 dbNonce,
address[] calldata srcModules
)
external
payable
onlyRemoteChainId(destChainId)
{
InterchainEntry memory entry = getEntry(writer, writerNonce);
InterchainEntry memory entry = getEntry(dbNonce);
_requestVerification(destChainId, entry, srcModules);
}

Expand All @@ -50,31 +49,30 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB {
external
payable
onlyRemoteChainId(destChainId)
returns (uint256 writerNonce)
returns (uint256 dbNonce)
{
writerNonce = _writeEntry(dataHash);
InterchainEntry memory entry = InterchainEntryLib.constructLocalEntry(msg.sender, writerNonce, dataHash);
dbNonce = _writeEntry(dataHash);
InterchainEntry memory entry = InterchainEntryLib.constructLocalEntry(dbNonce, msg.sender, dataHash);
_requestVerification(destChainId, entry, srcModules);
}

// ═══════════════════════════════════════════════ MODULE-FACING ═══════════════════════════════════════════════════

/// @inheritdoc IInterchainDB
function verifyEntry(InterchainEntry memory entry) external onlyRemoteChainId(entry.srcChainId) {
bytes32 entryId = InterchainEntryLib.entryId(entry);
RemoteEntry memory existingEntry = _remoteEntries[entryId][msg.sender];
bytes32 entryKey = InterchainEntryLib.entryKey(entry);
bytes32 entryValue = InterchainEntryLib.entryValue(entry);
RemoteEntry memory existingEntry = _remoteEntries[msg.sender][entryKey];
// Check if that's the first time module verifies the entry
if (existingEntry.verifiedAt == 0) {
_remoteEntries[entryId][msg.sender] = RemoteEntry({verifiedAt: block.timestamp, dataHash: entry.dataHash});
emit InterchainEntryVerified(
msg.sender, entry.srcChainId, entry.srcWriter, entry.writerNonce, entry.dataHash
);
_remoteEntries[msg.sender][entryKey] = RemoteEntry({verifiedAt: block.timestamp, entryValue: entryValue});
emit InterchainEntryVerified(msg.sender, entry.srcChainId, entry.dbNonce, entry.srcWriter, entry.dataHash);
} else {
// If the module has already verified the entry, check that the data hash is the same
if (existingEntry.dataHash != entry.dataHash) {
revert InterchainDB__ConflictingEntries(existingEntry.dataHash, entry);
// If the module has already verified the entry, check that the entry value is the same
if (existingEntry.entryValue != entryValue) {
revert InterchainDB__ConflictingEntries(existingEntry.entryValue, entry);
}
// No-op if the data hash is the same
// No-op if the entry value is the same
}
}

Expand All @@ -90,9 +88,10 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB {
onlyRemoteChainId(entry.srcChainId)
returns (uint256 moduleVerifiedAt)
{
RemoteEntry memory remoteEntry = _remoteEntries[InterchainEntryLib.entryId(entry)][dstModule];
// Check data against the one verified by the module
return remoteEntry.dataHash == entry.dataHash ? remoteEntry.verifiedAt : 0;
RemoteEntry memory remoteEntry = _remoteEntries[dstModule][InterchainEntryLib.entryKey(entry)];
bytes32 entryValue = InterchainEntryLib.entryValue(entry);
// Check entry value against the one verified by the module
return remoteEntry.entryValue == entryValue ? remoteEntry.verifiedAt : 0;
}

/// @inheritdoc IInterchainDB
Expand All @@ -101,25 +100,25 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB {
}

/// @inheritdoc IInterchainDB
function getEntry(address writer, uint256 writerNonce) public view returns (InterchainEntry memory) {
if (getWriterNonce(writer) <= writerNonce) {
revert InterchainDB__EntryDoesNotExist(writer, writerNonce);
function getEntry(uint256 dbNonce) public view returns (InterchainEntry memory) {
if (getDBNonce() <= dbNonce) {
revert InterchainDB__EntryDoesNotExist(dbNonce);
}
return InterchainEntryLib.constructLocalEntry(writer, writerNonce, _entries[writer][writerNonce]);
return InterchainEntryLib.constructLocalEntry(dbNonce, _entries[dbNonce].writer, _entries[dbNonce].dataHash);
}

/// @inheritdoc IInterchainDB
function getWriterNonce(address writer) public view returns (uint256) {
return _entries[writer].length;
function getDBNonce() public view returns (uint256) {
return _entries.length;
}

// ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════

/// @dev Write the entry to the database and emit the event.
function _writeEntry(bytes32 dataHash) internal returns (uint256 writerNonce) {
writerNonce = _entries[msg.sender].length;
_entries[msg.sender].push(dataHash);
emit InterchainEntryWritten(block.chainid, TypeCasts.addressToBytes32(msg.sender), writerNonce, dataHash);
function _writeEntry(bytes32 dataHash) internal returns (uint256 dbNonce) {
dbNonce = _entries.length;
_entries.push(LocalEntry(msg.sender, dataHash));
emit InterchainEntryWritten(block.chainid, dbNonce, TypeCasts.addressToBytes32(msg.sender), dataHash);
}

/// @dev Request the verification of the entry by the modules, and emit the event.
Expand All @@ -139,7 +138,7 @@ contract InterchainDB is InterchainDBEvents, IInterchainDB {
for (uint256 i = 0; i < len; ++i) {
IInterchainModule(srcModules[i]).requestVerification{value: fees[i]}(destChainId, entry);
}
emit InterchainVerificationRequested(destChainId, entry.srcWriter, entry.writerNonce, srcModules);
emit InterchainVerificationRequested(destChainId, entry.dbNonce, srcModules);
}

// ══════════════════════════════════════════════ INTERNAL VIEWS ═══════════════════════════════════════════════════
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ pragma solidity ^0.8.0;

abstract contract InterchainDBEvents {
// TODO: figure out indexing
event InterchainEntryWritten(uint256 srcChainId, bytes32 srcWriter, uint256 writerNonce, bytes32 dataHash);
event InterchainEntryWritten(uint256 srcChainId, uint256 dbNonce, bytes32 srcWriter, bytes32 dataHash);
event InterchainEntryVerified(
address module, uint256 srcChainId, bytes32 srcWriter, uint256 writerNonce, bytes32 dataHash
address module, uint256 srcChainId, uint256 dbNonce, bytes32 srcWriter, bytes32 dataHash
);

event InterchainVerificationRequested(
uint256 destChainId, bytes32 srcWriter, uint256 writerNonce, address[] srcModules
);
event InterchainVerificationRequested(uint256 destChainId, uint256 dbNonce, address[] srcModules);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@ pragma solidity ^0.8.0;
import {InterchainEntry} from "../libs/InterchainEntry.sol";

interface IInterchainDB {
/// @notice Struct representing an entry from the local Interchain DataBase.
/// @param writer The address of the writer on the local chain
/// @param dataHash The hash of the data written on the local chain
struct LocalEntry {
address writer;
bytes32 dataHash;
}

/// @notice Struct representing an entry from the remote Interchain DataBase verified by the Interchain Module
/// @param verifiedAt The block timestamp at which the entry was verified by the module
/// @param dataHash The hash of the data written on the source chain
/// @param entryValue The value of the entry: writer + dataHash hashed together
struct RemoteEntry {
uint256 verifiedAt;
bytes32 dataHash;
bytes32 entryValue;
}

error InterchainDB__ConflictingEntries(bytes32 existingDataHash, InterchainEntry newEntry);
error InterchainDB__EntryDoesNotExist(address writer, uint256 writerNonce);
error InterchainDB__ConflictingEntries(bytes32 existingEntryValue, InterchainEntry newEntry);
error InterchainDB__EntryDoesNotExist(uint256 dbNonce);
error InterchainDB__IncorrectFeeAmount(uint256 actualFee, uint256 expectedFee);
error InterchainDB__NoModulesSpecified();
error InterchainDB__SameChainId();
Expand All @@ -22,45 +30,38 @@ interface IInterchainDB {
/// Note: there are no guarantees that this entry will be available for reading on any of the remote chains.
/// Use `verifyEntry` to ensure that the entry is available for reading on the destination chain.
/// @param dataHash The hash of the data to be written to the Interchain DataBase as a new entry
/// @return writerNonce The writer-specific nonce of the written entry
function writeEntry(bytes32 dataHash) external returns (uint256 writerNonce);
/// @return dbNonce The database nonce of the written entry on this chain
function writeEntry(bytes32 dataHash) external returns (uint256 dbNonce);

/// @notice Request the given Interchain Modules to verify the already written entry on the destination chain.
/// Note: every module has a separate fee paid in the native gas token of the source chain,
/// and `msg.value` must be equal to the sum of all fees.
/// Note: this method is permissionless, and anyone can request verification for any entry.
/// @dev Will revert if the entry with the given nonce does not exist.
/// @param destChainId The chain id of the destination chain
/// @param writer The address of the writer on the source chain
/// @param writerNonce The nonce of the writer on the source chain
/// @param dbNonce The database nonce of the written entry on this chain
/// @param srcModules The source chain addresses of the Interchain Modules to use for verification
function requestVerification(
uint256 destChainId,
address writer,
uint256 writerNonce,
address[] memory srcModules
)
external
payable;
function requestVerification(uint256 destChainId, uint256 dbNonce, address[] memory srcModules) external payable;

/// @notice Write data to the Interchain DataBase,
/// and request the given Interchain Modules to verify it on the destination chain.
/// Note: every module has a separate fee paid in the native gas token of the source chain,
/// and `msg.value` must be equal to the sum of all fees.
/// Note: additional verification for the same entry could be later done using `requestVerification`.
/// Note: additional verification for the same entry could be later done using `requestVerification`
/// by providing the returned `dbNonce`.
/// @dev Will revert if the empty array of modules is provided.
/// @param destChainId The chain id of the destination chain
/// @param dataHash The hash of the data to be written to the Interchain DataBase as a new entry
/// @param srcModules The source chain addresses of the Interchain Modules to use for verification
/// @return writerNonce The writer-specific nonce of the written entry
/// @return dbNonce The database nonce of the written entry on this chain
function writeEntryWithVerification(
uint256 destChainId,
bytes32 dataHash,
address[] memory srcModules
)
external
payable
returns (uint256 writerNonce);
returns (uint256 dbNonce);

/// @notice Allows the Interchain Module to verify the entry coming from a remote source chain.
/// @param entry The Interchain Entry to confirm
Expand All @@ -75,13 +76,11 @@ interface IInterchainDB {

/// @notice Get the Interchain Entry by the writer and the writer nonce.
/// @dev Will revert if the entry with the given nonce does not exist.
/// @param writer The address of the writer on this chain
/// @param writerNonce The nonce of the writer's entry on this chain
function getEntry(address writer, uint256 writerNonce) external view returns (InterchainEntry memory);
/// @param dbNonce The database nonce of the written entry on this chain
function getEntry(uint256 dbNonce) external view returns (InterchainEntry memory);

/// @notice Get the nonce of the writer on this chain.
/// @param writer The address of the writer on this chain
function getWriterNonce(address writer) external view returns (uint256);
/// @notice Get the nonce of the database.
function getDBNonce() external view returns (uint256);

/// @notice Read the data written on specific source chain by a specific writer,
/// and verify it on the destination chain using the provided Interchain Module.
Expand Down
30 changes: 19 additions & 11 deletions packages/contracts-communication/contracts/libs/InterchainEntry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,30 @@ pragma solidity ^0.8.0;

import {TypeCasts} from "./TypeCasts.sol";

/// @notice Struct representing an entry in the Interchain DataBase
/// @notice Struct representing an entry in the Interchain DataBase.
/// Entry has a globally unique identifier (key) and a value.
/// - key: srcChainId + dbNonce
/// - value: srcWriter + dataHash
/// @param srcChainId The chain id of the source chain
/// @param dbNonce The database nonce of the entry on the source chain
/// @param srcWriter The address of the writer on the source chain
/// @param writerNonce The nonce of the writer on the source chain
/// @param dataHash The hash of the data written on the source chain
struct InterchainEntry {
uint256 srcChainId;
uint256 dbNonce;
bytes32 srcWriter;
uint256 writerNonce;
bytes32 dataHash;
}

library InterchainEntryLib {
/// @notice Constructs an InterchainEntry struct to be written on the local chain
/// @param srcWriter The address of the writer on the local chain
/// @param writerNonce The nonce of the writer on the local chain
/// @param dbNonce The database nonce of the entry on the source chain
/// @param writer The address of the writer on the local chain
/// @param dataHash The hash of the data written on the local chain
/// @return entry The constructed InterchainEntry struct
function constructLocalEntry(
address srcWriter,
uint256 writerNonce,
uint256 dbNonce,
address writer,
bytes32 dataHash
)
internal
Expand All @@ -32,14 +35,19 @@ library InterchainEntryLib {
{
return InterchainEntry({
srcChainId: block.chainid,
srcWriter: TypeCasts.addressToBytes32(srcWriter),
writerNonce: writerNonce,
dbNonce: dbNonce,
srcWriter: TypeCasts.addressToBytes32(writer),
dataHash: dataHash
});
}

/// @notice Returns the globally unique identifier of the entry
function entryId(InterchainEntry memory entry) internal pure returns (bytes32) {
return keccak256(abi.encode(entry.srcChainId, entry.srcWriter, entry.writerNonce));
function entryKey(InterchainEntry memory entry) internal pure returns (bytes32) {
return keccak256(abi.encode(entry.srcChainId, entry.dbNonce));
}

/// @notice Returns the value of the entry: writer + dataHash hashed together
function entryValue(InterchainEntry memory entry) internal pure returns (bytes32) {
return keccak256(abi.encode(entry.srcWriter, entry.dataHash));
}
}
Loading

0 comments on commit b344693

Please sign in to comment.