feat: Delayed signature verification during block proposals#15813
feat: Delayed signature verification during block proposals#15813spalladino merged 22 commits intonextfrom
Conversation
12719d4 to
fe70dd3
Compare
LHerskind
left a comment
There was a problem hiding this comment.
Overall think the direction is good, added some comments that are mostly minor or suggestions for future work.
The main important one is the missing invalidation case 👀
| Epoch epoch = STFLib.getEpochForBlock(_endBlockNumber); | ||
|
|
||
| // Only verify attestations if they are not empty (for testing compatibility) | ||
| if (!_attestations.isEmpty()) { |
There was a problem hiding this comment.
Assuming that the if is to be removed and always run later?
There was a problem hiding this comment.
Yep, removed, verify already takes care of skipping the check if the committee is empty.
| ); | ||
|
|
||
| // Get the stored block data | ||
| TempBlockLog memory blockLog = STFLib.getTempBlockLog(_blockNumber); |
There was a problem hiding this comment.
You don't need to load the entire thing into memory, a bunch of the values wont be used, you could use TempBlockLog storage blockLog.
There was a problem hiding this comment.
Good catch. I added an additional function to STFLib to get the block log as storage directly.
| } | ||
|
|
||
| // Calculate required threshold (2/3 + 1) | ||
| uint256 requiredSignatures = (committeeSize << 1) / 3 + 1; |
There was a problem hiding this comment.
Would probably keep the *2 just for readability here 😆 Think we can live with the 2 gas more.
There was a problem hiding this comment.
Just copy-pasta from verify :-P
LHerskind
left a comment
There was a problem hiding this comment.
You also want to update the preheatHeaders function in STFLib.sol to not pay a penalty.
294c5ce to
b060765
Compare
5448803 to
4e0a2bc
Compare
621950f to
72f9d52
Compare
LHerskind
left a comment
There was a problem hiding this comment.
Think I found a way to have honest committees and provers but still prune due to the changes in verify 👀.
| Slot slot = _args.header.slotNumber; | ||
| Epoch epoch = slot.epochFromSlot(); | ||
| ValidatorSelectionLib.verify(slot, epoch, _attestations, _args.digest); | ||
| ValidatorSelectionLib.verifyProposer(slot, epoch, _attestations, _signers, _args.digest); |
There was a problem hiding this comment.
Is the proposer not already validated during the verify in here, so why the separate call to verifyProposer?
There was a problem hiding this comment.
Removed the proposer verification from verify above (which I renamed to verifyAttestations)
| require( | ||
| stack.proposerVerified || proposer == msg.sender, | ||
| Errors.ValidatorSelection__InvalidProposer(proposer, msg.sender) | ||
| stack.proposerVerified, Errors.ValidatorSelection__InvalidProposer(proposer, address(0)) |
There was a problem hiding this comment.
This introduce a very strange edge case, or maybe I am misunderstanding again 😬.
Because the verify is applied at the time of proof submission, but the verifyProposer is done at proposer. It is possible to pass 33 attestations that do not include the attester, but have the attester transmit the message. It would pass the verifyProposer check in propose BUT it would fail the verify. At the same time, it cannot be invalidated.
I think this check should actually be removed. We don't care for the validity if the proposer actually attested just that there are enough. We do however care at the time of propose that he controlled it (so that check is good).
There was a problem hiding this comment.
Removing this check
|
|
||
| // Reset the pending block number to remove this block and all subsequent blocks | ||
| RollupStore storage rollupStore = STFLib.getStorage(); | ||
| rollupStore.tips = rollupStore.tips.updatePendingBlockNumber(_blockNumber - 1); |
There was a problem hiding this comment.
You are doing this write twice, first here and then in the _invalidateBlock below.
l1-contracts/test/Rollup.t.sol
Outdated
| end: _end, | ||
| args: args, | ||
| fees: fees, | ||
| attestations: CommitteeAttestations({signatureIndices: "", signaturesOrAddresses: ""}), // TODO(palla): Add unit tests with non-empty attestations |
There was a problem hiding this comment.
Just realized I never pushed the commit Add test for submitEpochProof
| // a block if you submit one with no signatures. This was a change from prior behavior where we had had | ||
| // that if there were zero validators in a rollup, anyone could build a block | ||
|
|
||
| // TODO(palla): What should we do in this scenario? Block the proposal, or allow invalidating later? |
There was a problem hiding this comment.
The committee is still the same (why it was revert=true before), so as long as that committee is the active committee you kinda gotta wait. As it could also just be a partial move, you are in this weird state where the chain is degraded.
I believe you will also have a very similar thing going on at the new rollup, because of the delayed reads (2 epochs) the first two epochs after attesters were added there won't be much to do 👀
There was a problem hiding this comment.
Not necessarily for this PR, but we gotta clean those up and split it better, can be hard to follow in here. Some of the extra things for invalidation make it a bit harder as well.
There was a problem hiding this comment.
Agree, there's also a lot of duplicate code across test helpers
87f2b1f to
2b10c89
Compare
just-mitch
left a comment
There was a problem hiding this comment.
Looks good to me code-wise! We definitely need to update the design to just have the cleaned up flows with their proper names.
| } | ||
|
|
||
| /** | ||
| * @notice Propose a pending block from the point-of-view of sequencer selection. Will: |
There was a problem hiding this comment.
Comment should really be updated.
There was a problem hiding this comment.
validateHeader can be restricted to view now the linter is telling me 🎉
There was a problem hiding this comment.
Good point! We no longer sample validators in there
| } | ||
|
|
||
| function validateHeader(ValidateHeaderArgs calldata _args) external { | ||
| function validateHeaderWithAttestations( |
There was a problem hiding this comment.
It would be good to document the overall flows here of who calls what, when. Perhaps the design doc should be updated with the implementation and expected flows.
2b10c89 to
c35f762
Compare
LHerskind
left a comment
There was a problem hiding this comment.
Thanks for addressing it 👍
|
|
||
| // @note: not view as sampling validators uses tstore | ||
| function validateHeader(ValidateHeaderArgs memory _args) internal { | ||
| function validateHeader(ValidateHeaderArgs memory _args) internal view { |
Archiver now checks committee attestations and refuses to sync a block if it does not pass validation. Note that this addresses scenarios where the proposer is malicious, but does not handle cases where the entire committee is and produces signatures for a block with an unattested parent. That'll be left for a future PR. Builds on #15813
Implements AztecProtocol/engineering-designs#69
Median
proposegas cost is321250according tohappy.t.sol#test_100_val