-
Notifications
You must be signed in to change notification settings - Fork 4
Move approval store into MSM #429
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
Open
yacovm
wants to merge
3
commits into
main
Choose a base branch
from
approvalStoreInMSM
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -222,6 +222,172 @@ func TestApprovalStoreHandleApproval(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| func TestApprovalStorePutApprovals(t *testing.T) { | ||
| // findApproval locates the approval for a given (NodeID, PChainHeight) in a slice returned by | ||
| // Approvals(). It is used to assert which of two competing approvals (source vs. destination) | ||
| // survived a merge, since Approvals() does not carry the timestamp but does carry the Signature, | ||
| // and signApproval produces a distinct signature per call. | ||
| findApproval := func(got ValidatorSetApprovals, node nodeID, height uint64) (ValidatorSetApproval, bool) { | ||
| for _, a := range got { | ||
| if a.NodeID == node && a.PChainHeight == height { | ||
| return a, true | ||
| } | ||
| } | ||
| return ValidatorSetApproval{}, false | ||
| } | ||
|
|
||
| for _, tc := range []struct { | ||
| name string | ||
| // srcValidators/dstValidators size the two stores via makeValidators; NodeIDs are makeNodeID(i+1). | ||
| srcValidators int | ||
| dstValidators int | ||
| // srcApprovals are loaded into the source store; dstApprovals are pre-loaded into the destination | ||
| // store before PutApprovals is called. | ||
| srcApprovals []approvalAndTimestamp | ||
| dstApprovals []approvalAndTimestamp | ||
| // verify asserts on the destination (and source) store after src.PutApprovals(dst). | ||
| verify func(t *testing.T, dst, src *ApprovalStore, srcSent, dstSent []approvalAndTimestamp) | ||
| }{ | ||
| { | ||
| // Copies every source approval into an empty destination that shares the same validator set, | ||
| // and leaves the source store untouched (PutApprovals copies, it does not move). | ||
| name: "copies all approvals into empty destination", | ||
| srcValidators: 3, | ||
| dstValidators: 3, | ||
| srcApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100}, | ||
| {ValidatorSetApproval{NodeID: makeNodeID(2), PChainHeight: 8, Signature: signApproval(8, [32]byte{})}, 100}, | ||
| }, | ||
| verify: func(t *testing.T, dst, src *ApprovalStore, srcSent, _ []approvalAndTimestamp) { | ||
| got := dst.Approvals() | ||
| require.Len(t, got, 2) | ||
| require.Equal(t, 2, dst.storedCount) | ||
| require.ElementsMatch(t, []ValidatorSetApproval{srcSent[0].ValidatorSetApproval, srcSent[1].ValidatorSetApproval}, got) | ||
| // The source store is unchanged. | ||
| require.Len(t, src.Approvals(), 2) | ||
| require.Equal(t, 2, src.storedCount) | ||
| }, | ||
| }, | ||
| { | ||
| // Approvals from nodes absent from the destination's (smaller) validator set are dropped on | ||
| // carry-over. This is the epoch-change case: a validator that is not part of the new set does | ||
| // not have its approval carried into the new store. | ||
| name: "drops approvals from nodes not in destination validator set", | ||
| srcValidators: 3, | ||
| dstValidators: 2, | ||
| srcApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 10}, | ||
| {ValidatorSetApproval{NodeID: makeNodeID(2), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 10}, | ||
| {ValidatorSetApproval{NodeID: makeNodeID(3), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 10}, | ||
| }, | ||
| verify: func(t *testing.T, dst, _ *ApprovalStore, _, _ []approvalAndTimestamp) { | ||
| got := dst.Approvals() | ||
| require.Len(t, got, 2, "only approvals from nodes in the destination set carry over") | ||
| require.Equal(t, 2, dst.storedCount) | ||
| _, ok := findApproval(got, makeNodeID(3), 1) | ||
| require.False(t, ok, "node 3 is not in the destination validator set") | ||
| }, | ||
| }, | ||
| { | ||
| // Merges into a non-empty destination: a pre-existing destination approval with a newer | ||
| // timestamp is kept over an older one carried from the source, while a brand-new node's | ||
| // approval from the source is added. | ||
| name: "keeps newer destination approval and adds new node", | ||
| srcValidators: 2, | ||
| dstValidators: 2, | ||
| dstApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 200}, // newer, already present | ||
| }, | ||
| srcApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100}, // older, must not overwrite | ||
| {ValidatorSetApproval{NodeID: makeNodeID(2), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100}, // new node, added | ||
| }, | ||
| verify: func(t *testing.T, dst, _ *ApprovalStore, _, dstSent []approvalAndTimestamp) { | ||
| got := dst.Approvals() | ||
| require.Len(t, got, 2) | ||
| require.Equal(t, 2, dst.storedCount) | ||
| kept, ok := findApproval(got, makeNodeID(1), 7) | ||
| require.True(t, ok) | ||
| require.Equal(t, dstSent[0].ValidatorSetApproval, kept, "the newer destination approval is retained") | ||
| _, ok = findApproval(got, makeNodeID(2), 7) | ||
| require.True(t, ok, "the new node's approval is carried over") | ||
| }, | ||
| }, | ||
| { | ||
| // A source approval with a newer timestamp replaces the stale destination approval at the | ||
| // same (NodeID, PChainHeight). | ||
| name: "newer source approval replaces stale destination approval", | ||
| srcValidators: 2, | ||
| dstValidators: 2, | ||
| dstApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100}, // older, present | ||
| }, | ||
| srcApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 200}, // newer, replaces | ||
| }, | ||
| verify: func(t *testing.T, dst, _ *ApprovalStore, srcSent, _ []approvalAndTimestamp) { | ||
| got := dst.Approvals() | ||
| require.Len(t, got, 1) | ||
| require.Equal(t, 1, dst.storedCount) | ||
| require.Equal(t, srcSent[0].ValidatorSetApproval, got[0], "the newer source approval replaces the stale one") | ||
| }, | ||
| }, | ||
| { | ||
| // An empty source store is a no-op: the destination is left exactly as it was. | ||
| name: "empty source is a no-op", | ||
| srcValidators: 2, | ||
| dstValidators: 2, | ||
| dstApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 10}, | ||
| }, | ||
| verify: func(t *testing.T, dst, _ *ApprovalStore, _, dstSent []approvalAndTimestamp) { | ||
| got := dst.Approvals() | ||
| require.Len(t, got, 1) | ||
| require.Equal(t, 1, dst.storedCount) | ||
| require.Equal(t, dstSent[0].ValidatorSetApproval, got[0]) | ||
| }, | ||
| }, | ||
| { | ||
| // A source approval with the same timestamp as an existing destination approval at the same | ||
| // (NodeID, PChainHeight) does not overwrite it: the >= tie in approvalExistsAndUpToDate goes | ||
| // to the current destination. | ||
| name: "equal-timestamp source does not overwrite destination", | ||
| srcValidators: 2, | ||
| dstValidators: 2, | ||
| dstApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100}, | ||
| }, | ||
| srcApprovals: []approvalAndTimestamp{ | ||
| {ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100}, | ||
| }, | ||
| verify: func(t *testing.T, dst, _ *ApprovalStore, _, dstSent []approvalAndTimestamp) { | ||
| got := dst.Approvals() | ||
| require.Len(t, got, 1) | ||
| require.Equal(t, 1, dst.storedCount) | ||
| require.Equal(t, dstSent[0].ValidatorSetApproval, got[0], "the destination approval is retained on a timestamp tie") | ||
| }, | ||
| }, | ||
| } { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| src := NewApprovalStore(&signatureVerifier{}, makeValidators(tc.srcValidators), testutil.MakeLogger(t)) | ||
| dst := NewApprovalStore(&signatureVerifier{}, makeValidators(tc.dstValidators), testutil.MakeLogger(t)) | ||
|
|
||
| for _, a := range tc.srcApprovals { | ||
| require.NoError(t, src.HandleApproval(&a.ValidatorSetApproval, a.Timestamp)) | ||
| } | ||
| for _, a := range tc.dstApprovals { | ||
| require.NoError(t, dst.HandleApproval(&a.ValidatorSetApproval, a.Timestamp)) | ||
| } | ||
|
|
||
| src.PutApprovals(dst) | ||
|
|
||
| // storedCount must always stay in sync with the number of retrievable approvals. | ||
| require.Len(t, dst.Approvals(), dst.storedCount) | ||
| tc.verify(t, dst, src, tc.srcApprovals, tc.dstApprovals) | ||
| }) | ||
| } | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we add one where the timestamp is equal {
// A source approval with the same timestamp as an existing destination approval at the same
// (NodeID, PChainHeight) does not overwrite it: the >= tie in approvalExistsAndUpToDate goes
// to the incumbent.
name: "equal-timestamp source does not overwrite destination",
srcValidators: 2,
dstValidators: 2,
dstApprovals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100},
},
srcApprovals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100},
},
verify: func(t *testing.T, dst, _ *ApprovalStore, _, dstSent []approvalAndTimestamp) {
got := dst.Approvals()
require.Len(t, got, 1)
require.Equal(t, 1, dst.storedCount)
require.Equal(t, dstSent[0].ValidatorSetApproval, got[0], "the destination approval is retained on a timestamp tie")
},
}, |
||
|
|
||
| func TestApprovalStoreHandleApprovalStoredCountStaysConsistent(t *testing.T) { | ||
| // Runs a mixed workload (insert, duplicate, replace, new height, prune) | ||
| // and asserts that storedCount equals len(Approvals()) after every step. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
why do we need the
storedCountfield? can't we just calculate it by summing the lengths inapprovalsByNodes?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.
nvm i guess it would be inefficient to iterate and find exactly how many entries there are, and probably hard to get an accurate estimate
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.
It's so we can allocate the exact size in
Approvals()and avoid memory copying