-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Conversation
2 general recommendations:
|
A7 won't be compatible with A6 also because of other changes such as #15716 , b46269c, and ea33e65
|
…iko-mono into another_way_to_cache
Oh yeah i see, tho 15716 is not causing incompatibility, but sure the rest does. |
We'll talk about it in our sync up. |
There was a problem hiding this 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?
snippets[l1Height] = ICrossChainSync.Snippet({ | ||
remoteBlockId: l1Height, | ||
syncedInBlock: uint64(block.number), | ||
blockHash: l1BlockHash, | ||
stateRoot: l1StateRoot | ||
}); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
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. |
(_signal,) = isFullProof | ||
? _relayStateRoot( _chainId, hop.proof.rootHash) | ||
: _relaySignalRoot(_chainId, hop.proof.rootHash); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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); | ||
} |
There was a problem hiding this comment.
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). |
There was a problem hiding this comment.
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.
snippets[l1Height] = ICrossChainSync.Snippet({ | ||
remoteBlockId: l1Height, | ||
syncedInBlock: uint64(block.number), | ||
blockHash: l1BlockHash, | ||
stateRoot: l1StateRoot | ||
}); |
There was a problem hiding this comment.
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.
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.
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. |
This PR shows the idea of improving the SignalService to achieve the following objectives: