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

Clique: get signers by block num #2610

Merged
merged 11 commits into from
Apr 10, 2023
Merged

Clique: get signers by block num #2610

merged 11 commits into from
Apr 10, 2023

Conversation

jochem-brouwer
Copy link
Member

@jochem-brouwer jochem-brouwer commented Mar 30, 2023

I added a test, which currently fails, it messes up the _cliqueLatestSignerStates somehow (after adding block 2, it adds a new signer to block 2, but also edits the signers of block 0?)

Note: we most likely have this problem with the "latest block signers", and the votes as well.

This PR currently allows to sync Goerli > 5476.

There is another problem with the current implementation: in case of a reorg, the active signers are all still remembered. However, if due to this reorg the signers at block X (where block X is now remembered of the previous change) change, this messes up the signer state internally. It should on reorg actually delete these signers above the block (need to investigate this claim though).

Note to self: local branch clique-by-blocknum-new

@codecov
Copy link

codecov bot commented Mar 30, 2023

Codecov Report

❗ No coverage uploaded for pull request base (develop-v7@fd2e4ea). Click here to learn what that means.
The diff coverage is n/a.

Additional details and impacted files

Impacted file tree graph

Flag Coverage Δ
block 90.44% <0.00%> (?)
blockchain 90.55% <0.00%> (?)
common 95.76% <0.00%> (?)
devp2p 91.72% <0.00%> (?)
evm 79.48% <0.00%> (?)
statemanager 89.58% <0.00%> (?)
trie 90.39% <0.00%> (?)
tx 94.45% <0.00%> (?)
util 83.54% <0.00%> (?)
vm 84.51% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@jochem-brouwer
Copy link
Member Author

Ok, I think I fixed the CI for blockchain.

The usage of cliqueActiveSigners(blockNum: bigint) are: if you input blockNum this will return whatever signers are active on that blockNum. In case a new signer gets activated/deactivated, this is referenced in the next block. This change of behavior is not a problem if there are no reorgs. However, if there are reorgs, this change should be taken into account.

Base automatically changed from devp2p-fixes to develop-v7 April 4, 2023 17:28
acolytec3
acolytec3 previously approved these changes Apr 10, 2023
Copy link
Contributor

@acolytec3 acolytec3 left a comment

Choose a reason for hiding this comment

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

This all looks great. Have a few clarifying questions but would love to merge this once final.

@@ -233,17 +244,24 @@ export class CliqueConsensus implements Consensus {
}
}

this._cliqueLatestSignerStates.sort((a, b) => (a[0] > b[0] ? 1 : -1))
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it likely that the clique signer states will get out of order and require us to potentially sort them twice in a single call to this update method?

Copy link
Member Author

Choose a reason for hiding this comment

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

I added this just to be sure, but looking at the code, it trims the cliqueLatestSignerStates after the first sort (so they are already ordered by block number) and thus removes latest signer states if they are below a certain threshold number (so the order should still be fine). I think we can remove.

i++
}
// eslint-disable-next-line no-empty
} catch (e) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the error that could occur here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I should have commented because now I don't remember. I think it was something when I tried to sync Goerli, and then for some reason signerState[0] was undefined, which throws the method. (If you want I can re-investigate)

const signers = this._cliqueLatestSignerStates
if (signers.length === 0) {
return []
}
return [...signers[signers.length - 1][1]]
for (let i = signers.length - 1; i >= 0; i--) {
if (signers[i][0] < blockNum) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just clarify my understanding here, we're just returning the most recent signer state, (which might be from a block older than the current chain head if no signer state change occurs on the current block, right?)

Copy link
Member Author

Choose a reason for hiding this comment

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

I think you got the idea here, but just for clarity:

  • This solves the reorg problem, because before it always returned whatever active signers there were at current chain head. (On client this could result in execution vs block import being out of sync which thus require different active signers if you verify them)
  • Note that _cliqueLatestSignerStates is sorted
  • We return the first candidate which has active signers < requested block num (so the state we want)
  • Note that signers always has 1 member

@@ -455,6 +478,7 @@ export class CliqueConsensus implements Consensus {
this._cliqueLatestSignerStates = this._cliqueLatestSignerStates.filter(
(s) => s[0] <= blockNumber
)
this._cliqueLatestSignerStates.sort((a, b) => (a[0] > b[0] ? 1 : -1))
Copy link
Contributor

Choose a reason for hiding this comment

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

How often to signer states get out of order? Seems like we do a lot of sorting.

Copy link
Member Author

Choose a reason for hiding this comment

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

On each reorg they will be out-of-order and will mess up the latest states (this fixes reorgs in case voting changes). However since this is in the delete method and the assumption is that the signer state is always sorted, this should only pop items from the end of the array, so might indeed remove.

for (let i = 1; i < blocks.length; i++) {
await blockchain.putBlock(blocks[i])
}
st.end()
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 add a simple check to validate the signer states at some of the historical blocks here or is that sufficiently covered by the reorg tests at the bottom?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh wow, the diff got completely messed up. Now it looks like I wrote a gigantic amount of new tests which is not the case. I must have accidentally moved some stuff around. I only added reorg tests and this specific test does not reorg. Note that if you putBlock and the state is wrong (either on reorg or not), then putBlock will throw. I will try to fix the diff.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nvm, I had to move some logic into methods to create the addNextBlockReorg function. This specific test, I think I added this to test things internally. I think for completeness, lets keep it, but I think if we run this on the old version (without reorg logic) this test still passes anyways.

'address C added to signers'
)
st.ok(
equalsBytes((await blockchain.getCanonicalHeadBlock()).hash(), headBlockUnforked.hash())
Copy link
Contributor

Choose a reason for hiding this comment

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

Complete nit but these equalsBytes calls can be replaced with st.deepEquals...

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh really? I did not know that :)

@jochem-brouwer
Copy link
Member Author

I will pick up these points and cleanup. I will also not fix this epoch reorg thing, but I will comment out the test and leave it there for educational purposes, and explicitly comment that we do not support certain reorg situations, like epoch reorg. (And possibly others but I will not research).

In case you linearly import a PoA chain now, this should be fine. However, at tip of chain if there are reorgs, failure might occur. (Note that this is still somewhat fixable: you can rewind the chain back to latest epoch change block, read clique state from there, and then just replay the chain, which should now fix the issue on the reorg). So it might be unstable at tip of PoA chains.

@acolytec3
Copy link
Contributor

Great, the couple of edits look good. Are we ready to merge?

@jochem-brouwer
Copy link
Member Author

Yup.

@jochem-brouwer jochem-brouwer marked this pull request as ready for review April 10, 2023 21:56
@jochem-brouwer
Copy link
Member Author

(sorry did not update the PR status, just did 😄 )

Copy link
Contributor

@acolytec3 acolytec3 left a comment

Choose a reason for hiding this comment

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

Lgtm

@acolytec3 acolytec3 merged commit a42c10e into develop-v7 Apr 10, 2023
@holgerd77 holgerd77 deleted the clique-by-blocknum branch April 11, 2023 15:19
g11tech pushed a commit that referenced this pull request Apr 13, 2023
* blockchain/clique: add support for clique signers by block num

* blockchain: clique sort clique latest signer states

* client: fix build

* blockchain/clique: fix tests and add test

* blockchain/clique: fix clique bugs

* blockchain/tests/clique: fix tests

* blockchain: clique: add reorg test

* blockchain/clique: add reorg over epoch test (fails) [no ci]

* replace equalsBytes with deepEquals

* blockchain/clique: address comments

* blockchain/clique: ensure tests are ran

---------

Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants