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(protocol)!: cache cross-chain data in the signal service as signals #15736

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a3c14b4
LibSignals
dantaik Feb 11, 2024
08038bd
more
dantaik Feb 11, 2024
163abdb
remove unused code
dantaik Feb 11, 2024
9af711b
Merge branch 'remove_code' into another_way_to_cache
dantaik Feb 11, 2024
80cdc1f
more
dantaik Feb 11, 2024
9c4dfd3
more
dantaik Feb 11, 2024
e9cdfb6
more
dantaik Feb 11, 2024
fe4eda7
more
dantaik Feb 11, 2024
216b565
more
dantaik Feb 11, 2024
8c54e20
fmt
dantaik Feb 11, 2024
4380fbe
Update SignalService.sol
dantaik Feb 11, 2024
6c9da20
Update SignalService.sol
dantaik Feb 11, 2024
7806d77
more
dantaik Feb 11, 2024
7517d7d
delete files
dantaik Feb 11, 2024
19b16a6
Update SignalService.sol
dantaik Feb 11, 2024
7640159
Update SignalService.sol
dantaik Feb 11, 2024
f85cc11
Update SignalService.sol
dantaik Feb 11, 2024
c462319
improve
dantaik Feb 11, 2024
1668e49
Merge branch 'main' into another_way_to_cache
dantaik Feb 11, 2024
40ddb74
Update SignalService.sol
dantaik Feb 11, 2024
731df4f
Update SignalService.sol
dantaik Feb 11, 2024
4d33026
Merge branch 'main' into another_way_to_cache
dantaik Feb 11, 2024
a600f92
test
dantaik Feb 11, 2024
c4326cf
Merge branch 'another_way_to_cache' of https://github.com/taikoxyz/ta…
dantaik Feb 11, 2024
244de26
Update Bridge.t.sol
dantaik Feb 11, 2024
f4ec267
test
dantaik Feb 11, 2024
37b9480
Merge branch 'main' into another_way_to_cache
dantaik Feb 11, 2024
62b6f3d
more
dantaik Feb 11, 2024
c4ca6cb
Merge branch 'main' into another_way_to_cache
dantaik Feb 12, 2024
5dc4d49
more
dantaik Feb 12, 2024
550ba67
Update SignalService.sol
dantaik Feb 12, 2024
b34cf02
more
dantaik Feb 12, 2024
c0de501
more
dantaik Feb 12, 2024
08610b3
Update SignalService.t.sol
dantaik Feb 12, 2024
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
22 changes: 1 addition & 21 deletions packages/protocol/contracts/L1/TaikoL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,7 @@ import "./TaikoEvents.sol";
/// layers"). The contract also handles the deposit and withdrawal of Taiko
/// tokens and Ether.
/// This contract doesn't hold any Ether. Ether deposited to L2 are held by the Bridge contract.
contract TaikoL1 is
EssentialContract,
ITaikoL1,
ICrossChainSync,
ITierProvider,
TaikoEvents,
TaikoErrors
{
contract TaikoL1 is EssentialContract, ITaikoL1, ITierProvider, TaikoEvents, TaikoErrors {
TaikoData.State public state;
uint256[100] private __gap;

Expand Down Expand Up @@ -163,19 +156,6 @@ contract TaikoL1 is
return LibUtils.getTransition(state, getConfig(), blockId, parentHash);
}

/// @inheritdoc ICrossChainSync
/// @notice Important: as this contract doesn't send each block's state root as a signal when
/// the block is verified, bridging developers should subscribe to CrossChainSynced events
/// to ensure all synced state roots are verifiable using merkle proofs.
function getSyncedSnippet(uint64 blockId)
public
view
override
returns (ICrossChainSync.Snippet memory)
{
return LibUtils.getSyncedSnippet(state, getConfig(), blockId);
}

/// @notice Gets the state variables of the TaikoL1 contract.
function getStateVariables()
public
Expand Down
29 changes: 0 additions & 29 deletions packages/protocol/contracts/L1/libs/LibUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

pragma solidity 0.8.24;

import "../../common/ICrossChainSync.sol";
import "../TaikoData.sol";

/// @title LibUtils
Expand Down Expand Up @@ -53,34 +52,6 @@ library LibUtils {
ts = state.transitions[slot][tid];
}

function getSyncedSnippet(
TaikoData.State storage state,
TaikoData.Config memory config,
uint64 blockId
)
external
view
returns (ICrossChainSync.Snippet memory)
{
uint64 _blockId = blockId == 0 ? state.slotB.lastVerifiedBlockId : blockId;
uint64 slot = _blockId % config.blockRingBufferSize;

TaikoData.Block storage blk = state.blocks[slot];

if (blk.blockId != _blockId) revert L1_BLOCK_MISMATCH();
if (blk.verifiedTransitionId == 0) revert L1_TRANSITION_NOT_FOUND();

TaikoData.TransitionState storage transition =
state.transitions[slot][blk.verifiedTransitionId];

return ICrossChainSync.Snippet({
remoteBlockId: blockId,
syncedInBlock: blk.proposedIn,
blockHash: transition.blockHash,
stateRoot: transition.stateRoot
});
}

/// @dev Retrieves a block based on its ID.
function getBlock(
TaikoData.State storage state,
Expand Down
10 changes: 2 additions & 8 deletions packages/protocol/contracts/L1/libs/LibVerifying.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ library LibVerifying {
uint8 contestations
);

event CrossChainSynced(
uint64 indexed syncedInBlock, uint64 indexed blockId, bytes32 blockHash, bytes32 stateRoot
);

// Warning: Any errors defined here must also be defined in TaikoErrors.sol.
error L1_BLOCK_MISMATCH();
error L1_INVALID_CONFIG();
Expand Down Expand Up @@ -243,10 +239,8 @@ library LibVerifying {
// This also means if we verified more than one block, only the last one's stateRoot
// is sent as a signal and verifiable with merkle proofs, all other blocks'
// stateRoot are not.
ISignalService(resolver.resolve("signal_service", false)).sendSignal(stateRoot);

emit CrossChainSynced(
uint64(block.number), lastVerifiedBlockId, blockHash, stateRoot
ISignalService(resolver.resolve("signal_service", false)).relayStateRoot(
config.chainId, stateRoot
);
}
}
Expand Down
48 changes: 17 additions & 31 deletions packages/protocol/contracts/L2/TaikoL2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ pragma solidity 0.8.24;
import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

import "../common/ICrossChainSync.sol";
import "../signal/ISignalService.sol";
import "../libs/LibAddress.sol";
import "../libs/LibMath.sol";
Expand All @@ -30,7 +29,7 @@ import "./CrossChainOwned.sol";
/// It is used to anchor the latest L1 block details to L2 for cross-layer
/// communication, manage EIP-1559 parameters for gas pricing, and store
/// verified L1 block information.
contract TaikoL2 is CrossChainOwned, ICrossChainSync {
contract TaikoL2 is CrossChainOwned {
using LibAddress for address;
using LibMath for uint256;
using SafeERC20 for IERC20;
Expand All @@ -46,14 +45,13 @@ contract TaikoL2 is CrossChainOwned, ICrossChainSync {
// Mapping from L2 block numbers to their block hashes.
// All L2 block hashes will be saved in this mapping.
mapping(uint256 blockId => bytes32 blockHash) public l2Hashes;
mapping(uint256 l1height => ICrossChainSync.Snippet) public snippets;

// A hash to check the integrity of public inputs.
bytes32 public publicInputHash; // slot 3
uint64 public gasExcess; // slot 4
bytes32 public publicInputHash; // slot 2
uint64 public gasExcess; // slot 3
uint64 public latestSyncedL1Height;

uint256[146] private __gap;
uint256[147] private __gap;

event Anchored(bytes32 parentHash, uint64 gasExcess);

Expand Down Expand Up @@ -137,24 +135,13 @@ contract TaikoL2 is CrossChainOwned, ICrossChainSync {
// Verify the base fee per gas is correct
uint256 basefee;
(basefee, gasExcess) = _calc1559BaseFee(config, l1Height, parentGasUsed);
if (!skipFeeCheck() && block.basefee != basefee) {
revert L2_BASEFEE_MISMATCH();
}

// Store the L1's state root as a signal to the local signal service to
// allow for multi-hop bridging.
ISignalService(resolve("signal_service", false)).sendSignal(l1StateRoot);
verifyBaseFee(basefee);

emit CrossChainSynced(uint64(block.number), l1Height, l1BlockHash, l1StateRoot);
relayStateRoot(ownerChainId, l1StateRoot);

// Update state variables
l2Hashes[parentId] = blockhash(parentId);
snippets[l1Height] = ICrossChainSync.Snippet({
remoteBlockId: l1Height,
syncedInBlock: uint64(block.number),
blockHash: l1BlockHash,
stateRoot: l1StateRoot
});
Comment on lines -152 to -157
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does now make it impossible for smart contracts on L2 to just fetch this data from a smart contract or fetch specific L1 state roots. The block number and block hash info is now lost and unavailable, only now possible to verify that some L1 state root existed, not which and also not the corresponding block hash.

The applications that can make use of this info is low (thinking of things like axiom for historical data or some other alternative bridging solution), so probably not a big deal, still it felt good to have it there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also realized the differences. I think it's fine as most apps shall not assuming these data are available otherwise, the app will not be deployable on other chains.

But I do think it may be benefit to add the latest cached data and its block number to TaikoL1/L2 contract, and the cost is very small (writing to the same slot).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed hard to predict how useful it will be.

But it all depends on what other L2s also make available, because the function to call to get the data will be different, but the dapp could depend on the data to be there easily by just pointing to the right contract for each chain. I haven't look too closely to what is available on optimism, but there at least the full block header of the last known L1 block is made available in a contract: https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L1Block.sol. That means that dapps that depend on this information can run on Optimism but would not be able to run on Taiko.

publicInputHash = publicInputHashNew;
latestSyncedL1Height = l1Height;

Expand All @@ -171,17 +158,6 @@ contract TaikoL2 is CrossChainOwned, ICrossChainSync {
}
}

/// @inheritdoc ICrossChainSync
function getSyncedSnippet(uint64 blockId)
public
view
override
returns (ICrossChainSync.Snippet memory)
{
uint256 id = blockId == 0 ? latestSyncedL1Height : blockId;
return snippets[id];
}

/// @notice Gets the basefee and gas excess using EIP-1559 configuration for
/// the given parameters.
/// @param l1Height The synced L1 height in the next Taiko block
Expand Down Expand Up @@ -220,10 +196,20 @@ contract TaikoL2 is CrossChainOwned, ICrossChainSync {

/// @notice Tells if we need to validate basefee (for simulation).
/// @return Returns true to skip checking basefee mismatch.
function skipFeeCheck() public pure virtual returns (bool) {
function skipFeeCheck() internal pure virtual returns (bool) {
return false;
}

function verifyBaseFee(uint256 basefee) internal virtual {
if (block.basefee != basefee) revert L2_BASEFEE_MISMATCH();
}

function relayStateRoot(uint64 chainId, bytes32 signalRoot) internal virtual {
// Store the L1's state root as a signal to the local signal service to
// allow for multi-hop bridging.
ISignalService(resolve("signal_service", false)).relayStateRoot(chainId, signalRoot);
}

function _calcPublicInputHash(uint256 blockId)
private
view
Expand Down
4 changes: 4 additions & 0 deletions packages/protocol/contracts/L2/TaikoL2EIP1559Configurable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ contract TaikoL2EIP1559Configurable is TaikoL2 {
function getConfig() public view override returns (Config memory) {
return _config;
}

function relayStateRoot(uint64 chainId, bytes32 signalRoot) internal pure override {
// Do nothing
}
}
46 changes: 0 additions & 46 deletions packages/protocol/contracts/common/ICrossChainSync.sol

This file was deleted.

40 changes: 23 additions & 17 deletions packages/protocol/contracts/libs/LibTrieProof.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,60 @@ pragma solidity ^0.8.24;

import { RLPReader } from "../thirdparty/optimism/rlp/RLPReader.sol";
import { SecureMerkleTrie } from "../thirdparty/optimism/trie/SecureMerkleTrie.sol";

import "forge-std/console2.sol";
/**
* @title LibTrieProof
*/

library LibTrieProof {
// The consensus format representing account is RLP encoded in the
// following order: nonce, balance, storageHash, codeHash.
uint256 private constant ACCOUNT_FIELD_INDEX_STORAGE_HASH = 2;

error LTP_INVALID_ACCOUNT_PROOF();
error LTP_INVALID_INCLUSION_PROOF();
error LTP_INVALID_STORAGE_PROOF();

/**
* Verifies that the value of a slot in the storage of an account is value.
*
* @param stateRoot The merkle root of state tree.
* @param rootHash The merkle root of state tree.
* @param addr The address of contract.
* @param slot The slot in the contract.
* @param value The value to be verified.
* @param mkproof The proof obtained by encoding storage proof.
* @param accountProof The account proof.
* @param storageProof The storage proof.
* @return storageRoot The storage root.
*/
function verifyFullMerkleProof(
bytes32 stateRoot,
function verifyMerkleProof(
bytes32 rootHash,
address addr,
bytes32 slot,
bytes memory value,
bytes memory mkproof
bytes[] memory accountProof,
bytes[] memory storageProof
)
internal
pure
returns (bytes32 storageRoot)
{
(bytes[] memory accountProof, bytes[] memory storageProof) =
abi.decode(mkproof, (bytes[], bytes[]));

bytes memory rlpAccount =
SecureMerkleTrie.get(abi.encodePacked(addr), accountProof, stateRoot);
if (accountProof.length == 0) {
storageRoot = rootHash;
} else {
bytes memory rlpAccount =
SecureMerkleTrie.get(abi.encodePacked(addr), accountProof, rootHash);

if (rlpAccount.length == 0) revert LTP_INVALID_ACCOUNT_PROOF();
if (rlpAccount.length == 0) revert LTP_INVALID_ACCOUNT_PROOF();
Copy link
Contributor

@Brechtpd Brechtpd Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (rlpAccount.length == 0) revert LTP_INVALID_ACCOUNT_PROOF();
if (rlpAccount.length < MIN_LENGTH_ACCOUNT_LEAF) revert LTP_INVALID_ACCOUNT_PROOF();

With uint256 public constant MIN_LENGTH_ACCOUNT_LEAF = 70; this a good check to additionally make sure that a storage root cannot be used as a state root.

For the storage value the maximum length is 33 so we can also add a check on that in the input.


RLPReader.RLPItem[] memory accountState = RLPReader.readList(rlpAccount);
RLPReader.RLPItem[] memory accountState = RLPReader.readList(rlpAccount);

bytes memory storageRoot =
RLPReader.readBytes(accountState[ACCOUNT_FIELD_INDEX_STORAGE_HASH]);
storageRoot =
bytes32(RLPReader.readBytes(accountState[ACCOUNT_FIELD_INDEX_STORAGE_HASH]));
}

bool verified = SecureMerkleTrie.verifyInclusionProof(
bytes.concat(slot), value, storageProof, bytes32(storageRoot)
);

if (!verified) revert LTP_INVALID_INCLUSION_PROOF();
if (!verified) revert LTP_INVALID_STORAGE_PROOF();
}
}
7 changes: 4 additions & 3 deletions packages/protocol/contracts/signal/ISignalService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ interface ISignalService {
/// @notice Send a signal (message) by setting the storage slot to a value
/// of 1.
/// @param signal The signal (message) to send.
/// @return storageSlot The location in storage where this signal is stored.
function sendSignal(bytes32 signal) external returns (bytes32 storageSlot);
/// @return slot The location in storage where this signal is stored.
function sendSignal(bytes32 signal) external returns (bytes32 slot);

/// @notice Verifies if a particular signal has already been sent.
/// @param app The address that initiated the signal.
/// @param signal The signal (message) to send.
/// @return True if the signal has been sent, otherwise false.
function isSignalSent(address app, bytes32 signal) external view returns (bool);

function relayStateRoot(uint64 chainId, bytes32 stateRoot) external returns (bytes32 slot);

/// @notice Verifies if a signal has been received on the target chain.
/// @param srcChainId The identifier for the source chain from which the
/// signal originated.
Expand All @@ -43,6 +45,5 @@ interface ISignalService {
bytes calldata proof
)
external
view
returns (bool);
}
Loading
Loading