Skip to content

Move approval store into MSM#429

Open
yacovm wants to merge 3 commits into
mainfrom
approvalStoreInMSM
Open

Move approval store into MSM#429
yacovm wants to merge 3 commits into
mainfrom
approvalStoreInMSM

Conversation

@yacovm

@yacovm yacovm commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

This commit moves the approval store into the MSM, instead of being an external dependency.

The reason is that the approval store needs to be initialized with the validator set of the next epoch and not the current epoch, which can only be computed inside the MSM.

This commit moves the approval store into the MSM,
instead of being an external dependency.

The reason is that the approval store needs to be initialized with the validator set
of the *next* epoch and not the *current epoch*, which can only be computed inside the MSM.

Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Comment thread msm/approvals.go Outdated
@@ -27,8 +28,12 @@ type ApprovalStore struct {
validators NodeBLSMappings
logger common.Logger
pkByNodeID map[nodeID][]byte

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

unrelated but should we change this to nodeIDToPK. we already have validatorsToPKs map[string][]byte in epoch.go

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

not sure I follow, what is the problem with the field name?

Comment thread msm/approvals.go
// the block builder reads the accumulated approvals when building a block.
lock sync.RWMutex
approvalsByNodes map[nodeID]approvalsByPChainHeightAndAuxInfoDigest
storedCount int

Copy link
Copy Markdown
Collaborator

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 storedCount field? can't we just calculate it by summing the lengths in approvalsByNodes?

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator Author

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

Comment thread msm/approvals.go Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

unrelated: can we remove the getPKOfNode() function? it seems unnecessary

Comment thread msm/approvals.go Outdated
// Snapshot the approvals under our lock, then hand them to the destination
// store (which takes its own lock). Copying first avoids holding two store
// locks at once.
as.lock.Lock()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we can just grab the read part of the lock rather than the entire lock

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

yes, I can change it accordingly.

Though you know, when this is called, the approval store has a few micro seconds left to live 💀

Comment thread msm/approvals.go Outdated
// store (which takes its own lock). Copying first avoids holding two store
// locks at once.
as.lock.Lock()
type approvalWithTimestamp struct {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why do we create a new struct? can we use approvalAndTimestamp?

Comment thread msm/approvals_test.go
tc.verify(t, dst, src, tc.srcApprovals, tc.dstApprovals)
})
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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")
			},
		},

Comment thread msm/msm.go Outdated
errValidatorSetUnchanged, next.NextPChainReferenceHeight, prev.PChainReferenceHeight)
}

// Else, ! currentValidatorSet.Equal(newValidatorSet) || next.NextPChainReferenceHeight == 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Else, ! currentValidatorSet.Equal(newValidatorSet) || next.NextPChainReferenceHeight == 0
// Else, !currentValidatorSet.Equal(newValidatorSet) || next.NextPChainReferenceHeight == 0

Comment thread msm/msm.go
if decisionToBuildBlock.transitionEpoch && isSealingBlockFinalized {
sm.Logger.Debug("Transitioning epoch after building block", zap.Uint64("newPChainRefHeight", decisionToBuildBlock.pChainHeight))
newSimplexEpochInfo.NextPChainReferenceHeight = decisionToBuildBlock.pChainHeight
sm.maybeInitializeApprovalStore(decisionToBuildBlock.validatorSet)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

but if the sealing block is finalized, we must have already computed an approval store. furthermore, since this is for the next epoch aren't these validators not the right validators anyways? we want the validator set for the next, next epoch which we can't know since we are only transitioning to the next epoch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is the sealing block of the previous epoch, not this epoch.
The sealing block of this epoch hasn't been created yet.

since this is for the next epoch aren't these validators not the right validators anyways?

I changed shouldBuildBlock to return the validator set at decisionToBuildBlock.pChainHeight which is written into newSimplexEpochInfo.NextPChainReferenceHeight.

we want the validator set for the next, next epoch which we can't know since we are only transitioning to the next epoch.

This function determines the P-chain reference height for the next epoch, and that's the validator set we are seeding the approval store with.

yacovm and others added 2 commits July 2, 2026 22:33
Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
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.

2 participants