Skip to content

Implement approval store#384

Open
yacovm wants to merge 3 commits into
mainfrom
reconfig-6
Open

Implement approval store#384
yacovm wants to merge 3 commits into
mainfrom
reconfig-6

Conversation

@yacovm
Copy link
Copy Markdown
Collaborator

@yacovm yacovm commented May 18, 2026

Also self-sign during aggregation

  • Introduces ApprovalStore (msm/approvals.go) — an in-memory store of validator approvals for epoch transitions, keyed by (NodeID, PChainHeight). It verifies BLS signatures on
    ingest, deduplicates by timestamp (newer wins, older is dropped), and prunes per-node entries to at most len(validators) by evicting the oldest timestamp.
  • Adds Timestamp to ValidatorSetApproval (msm/encoding.go) so the store can order/evict approvals deterministically.
  • computeNewApprovals (msm/msm.go) now optimistically self-signs the next epoch's P-chain reference height each round and appends its own ValidatorSetApproval to the peer set since the store deduplicates it later.

@yacovm yacovm force-pushed the reconfig-6 branch 3 times, most recently from bbd66e3 to 2c54008 Compare May 19, 2026 21:27
yacovm added 3 commits May 21, 2026 19:02
Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Also self-sign during aggregation

- Introduces ApprovalStore (msm/approvals.go) — an in-memory store of validator approvals for epoch transitions, keyed by (NodeID, PChainHeight). It verifies BLS signatures on
  ingest, deduplicates by timestamp (newer wins, older is dropped), and prunes per-node entries to at most len(validators) by evicting the oldest timestamp.
- Adds Timestamp to ValidatorSetApproval (msm/encoding.go) so the store can order/evict approvals deterministically.
- computeNewApprovals (msm/msm.go) now optimistically self-signs the next epoch's P-chain reference height each round and appends its own ValidatorSetApproval to the peer set since the store deduplicates it later.

Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Comment thread msm/msm.go

// SignatureVerifier verifies a cryptographic signature against a message and public key.
// Used to verify Approvals from validators for epoch transitions.
type SignatureVerifier interface {
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.

i just realized we have this interface already defined in simplex.api. thoughts on removing this and just using the simplex interface(we already do this for the Signer interface)?

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.

But the interface we have there takes in a signer []NodeID where here we take a public key instead.

Comment thread msm/msm.go
WaitForPendingBlock(ctx context.Context)
}

type verificationInput 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.

this is unused

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.

Comment thread msm/util_test.go
bs := make(blockStore)
bs[0] = &outerBlock{block: genesisBlock}

var myNodeID nodeID
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.

is there any benefit of making this random?

Comment thread msm/msm_test.go
Comment on lines +1196 to +1199
var myID nodeID
copy(myID[:], sm.MyNodeID)
validators := NodeBLSMappings{
{NodeID: myID, BLSKey: []byte{1}, Weight: 1},
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.

nit

validators := NodeBLSMappings{
{NodeID: nodeID(sm.MyNodeID), BLSKey: []byte{1}, Weight: 1}

also did github remove the suggestions option on pr review? can't seem to find it

Comment thread msm/approvals.go

type approvalsByPChainHeight map[uint64]*ValidatorSetApproval

type ApprovalStore 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.

Is this supposed to be passed into the MSM somewhere? I don't see it connected anywhere outside of tests

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.

Yeah, this is what implements:

// ApprovalsRetriever retrieves the approvals from validators of the next epoch for the epoch change.
type ApprovalsRetriever interface {
	Approvals() ValidatorSetApprovals
}

Comment thread msm/approvals.go
}
}

func (as *ApprovalStore) checkApprovalSignature(approval *ValidatorSetApproval, pk []byte) error {
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.

hmm is it not a problem that we verify the signature is valid over just the pChainHeight here, but in the MSM code we verify it with the context + asn1 encoding? shouldn't there be one canonical way of verifying/signing that is shared for both of them?

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.

Yeah, because when I implemented this, I still haven't done that domain separation in the MSM.

Will fix 👍

Comment thread msm/approvals.go
return approvals
}

func (as *ApprovalStore) HandleApproval(approval *ValidatorSetApproval) error {
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.

where is HandleApproval going to be called? Do we need to make this handling method thread safe?

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.

I will connect it somehow once I build the orchestration layer.

For now, I just implemented this logic without paying attention to thread safety as it's not connected to anything yet

Comment thread msm/approvals.go
binary.BigEndian.PutUint64(pChainHeightBuff, pChainHeight)

// We check if the signature is valid before we store the approval.
return as.signatureVerifier.VerifySignature(approval.Signature, pChainHeightBuff, pk)
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 dont we also check the signature is correct over the AuxInfoSeqDigest? I guess what is stopping nodes from agreeing on the pchain signature, but having different hashes in AuxInfoSeqDigest

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.

That's a good question.

So, it's true that in the spec I wrote that we check the signature on both the P-chain height and the AuxInfoDigest.

However, since you asked to remove the code that handles the AuxInfo from the MSM PR and I have yet to introduce it back, I am signing for now only on the P-chain height.

Comment thread msm/approvals_test.go
)

func makeNodeID(seed byte) nodeID {
var n nodeID
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.

i'm just realizing this now but nodeID will get very annoying imo. we already have simplex.NodeID and now we are adding nodeID which is different. Maybe in a separate pr can we change nodeID to something like avagoNodeID and merge that pr in before this one?

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.

Sure we can align the NodeID definitions.

But why do that before merging this PR? I think it'll be easier just doing it afterwards, no?

We anyway will need to do this before the repo is swallowed by avalanchego

Comment thread msm/approvals_test.go
require.Equal(t, 0, as.storedCount)
}

func TestApprovalStoreHandleApprovalInvalidSignature(t *testing.T) {
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.

thoughts on #392

feel free to close/merge/implement

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