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

Conversation

dantaik
Copy link
Contributor

@dantaik dantaik commented Feb 11, 2024

This PR shows the idea of improving the SignalService to achieve the following objectives:

  • Caching remote chain's state root or its signal service's storage root locally, inside the local signal service.
  • The proofs for multi-hop bridging can be full Merkle proofs, storage proof, or a mix of these two.
  • The transactor can specify exactly which chain/hop's signalRoot or stateRoot should be cached locally.
  • Caching a signalRoot/stateRoot is the same operation as relay the same root data to downstream chains, there is only one SSTORE, not two.

@dantaik dantaik changed the title LibSignals feat(protocol): try another idea to cache Feb 11, 2024
@dantaik dantaik changed the base branch from main to remove_code February 11, 2024 02:40
Base automatically changed from remove_code to main February 11, 2024 03:58
@adaki2004
Copy link
Contributor

adaki2004 commented Feb 11, 2024

2 general recommendations:

  • Still this will make A7 and A6 incompatible - better not to remove snippets from TaikoL2 just rename to snippets__deprecated, otherwise no upgrade possible.
  • Since it is an improvement in caching, better to base this on this branch since it has the MerkleProof verification working. Otherwise it will create again, lot of merge conflicts - especially because hop proof (structure) changed again - and it is not a straighforward , small change.

@dantaik
Copy link
Contributor Author

dantaik commented Feb 11, 2024

2 general recommendations:

  • Still this will make A7 and A6 incompatible - better not to remove snippets from TaikoL2 just rename to snippets__deprecated, otherwise no upgrade possible.

A7 won't be compatible with A6 also because of other changes such as #15716 , b46269c, and ea33e65

  • Since it is an improvement in caching, better to base this on this branch since it has the MerkleProof verification working. Otherwise it will create again, lot of merge conflicts - especially because hop proof (structure) changed again - and it is not a straighforward , small change.
    Will wait for your's PR to merge first.

@dantaik dantaik changed the title feat(protocol): try another idea to cache feat(protocol)!: try another idea to cache Feb 11, 2024
@adaki2004
Copy link
Contributor

A7 won't be compatible with A6 also because of other changes such as #15716 , b46269c, and ea33e65

Oh yeah i see, tho 15716 is not causing incompatibility, but sure the rest does.
Anyway best to minimize the changes - if we dont want to run another public testnet - because we need to be sure there are no such changes where mass (public), stress testing could reveal any findings.

@dantaik dantaik changed the title feat(protocol)!: try another idea to cache feat(protocol)!: cache cross-chain data in the signal service as signals Feb 11, 2024
@dantaik
Copy link
Contributor Author

dantaik commented Feb 11, 2024

A7 won't be compatible with A6 also because of other changes such as #15716 , b46269c, and ea33e65

Oh yeah i see, tho 15716 is not causing incompatibility, but sure the rest does. Anyway best to minimize the changes - if we dont want to run another public testnet - because we need to be sure there are no such changes where mass (public), stress testing could reveal any findings.

We'll talk about it in our sync up.

Copy link
Contributor

@Brechtpd Brechtpd left a comment

Choose a reason for hiding this comment

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

I have to look more into it to understand what is happening.

The transactor can specify exactly which chain/hop's signalRoot or stateRoot should be cached locally

Should it be an option though?

Comment on lines -152 to -157
snippets[l1Height] = ICrossChainSync.Snippet({
remoteBlockId: l1Height,
syncedInBlock: uint64(block.number),
blockHash: l1BlockHash,
stateRoot: l1StateRoot
});
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.

@dong77
Copy link
Contributor

dong77 commented Feb 12, 2024

I have to look more into it to understand what is happening.

The transactor can specify exactly which chain/hop's signalRoot or stateRoot should be cached locally

Should it be an option though?

I think so. As you said previously, if most the messages are processed by a relayer, the relayer should be smart enough to selectively cache things to lower his own cost; but normal users shall not pay extra fee to benefit others.

Comment on lines +166 to +168
(_signal,) = isFullProof
? _relayStateRoot( _chainId, hop.proof.rootHash)
: _relaySignalRoot(_chainId, hop.proof.rootHash);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we ever cache the state root? Regardless if a full proof is provided or not, I think we should always just cache the signal root. Because I don't think the state root is anything useful here, we just store it from the rollup contract now because that's all that we have available, but otherwise I think it doesn't really have a purpose (at least not from the signal service perspective). Because whenever we have state root of a chain, the first thing we have to do is proof the signal root for that state root, because proving state inside the signal contract is all we care about.

And so if we cache the signal root here directly then great because we immediately have the signal root for that chain! If we cache the state root for the chain, then if somebody wants to use it, the first thing they would have to do is to still prove the signal root for that state root.


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.

Comment on lines +280 to +307
function _relayStateRoot(
uint64 chainId,
bytes32 stateRoot
)
internal
returns (bytes32 signal, bytes32 slot)
{
if (chainId == block.chainid) revert SS_INVALID_PARAMS();
signal = signalForStateRoot(chainId, stateRoot);

console2.log("send signa:", msg.sender, uint256(signal));

slot = _sendSignal(resolve("taiko",false), signal);
emit StateRootRelayed(chainId, stateRoot, signal);
}

function _relaySignalRoot(
uint64 chainId,
bytes32 signalRoot
)
internal
returns (bytes32 signal, bytes32 slot)
{
if (chainId == block.chainid) revert SS_INVALID_PARAMS();
signal = signalForSignalRoot(chainId, signalRoot);
slot = _sendSignal(resolve("taiko", false), signal);
emit SignalRootRelayed(chainId, signalRoot, signal);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

So the purpose of these functions is to allow logging that these values are cached? Seems reasonable if so, I think otherwise it could go through the standard sendSignal API if I'm not mistaken.

@@ -133,45 +152,70 @@ contract SignalService is EssentialContract, ISignalService {
// 2. using the verified hop stateRoot to verify that the source app on chainA has sent a
// signal using its own signal service.
// We always verify the proofs in the reversed order (top to bottom).
Copy link
Contributor

Choose a reason for hiding this comment

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

// We always verify the proofs in the reversed order (top to bottom).

Should be bottom to top now right? Start at the chain that stores the signal and then go "upwards" towards a verified state root/signal root.

Comment on lines -152 to -157
snippets[l1Height] = ICrossChainSync.Snippet({
remoteBlockId: l1Height,
syncedInBlock: uint64(block.number),
blockHash: l1BlockHash,
stateRoot: l1StateRoot
});
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.

@Brechtpd
Copy link
Contributor

I have to look more into it to understand what is happening.

The transactor can specify exactly which chain/hop's signalRoot or stateRoot should be cached locally

Should it be an option though?

I think so. As you said previously, if most the messages are processed by a relayer, the relayer should be smart enough to selectively cache things to lower his own cost; but normal users shall not pay extra fee to benefit others.

Yep I don't think in practice it will matter much, but mostly just thinking if we should have it as an option if it is indeed a niche use case. But it's a reasonably small code difference, so seems okay.

Caching a signalRoot/stateRoot is the same operation as relay the same root data to downstream chains, there is only one SSTORE, not two.

Could you give some more information about this because I'm not sure I understand? The approach in this PR seems similar to the one in Dani's PR but I could very well miss something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants