Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions msm/approvals.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
package metadata

import (
"encoding/asn1"
"encoding/binary"
"fmt"

"github.com/ava-labs/simplex/common"
"go.uber.org/zap"
)

type approvalsByPChainHeight map[uint64]*approvalAndTimestamp
type approvalKey struct {
pChainHeight uint64
auxInfoDigest [32]byte
}

type approvalsByPChainHeightAndAuxInfoDigest map[approvalKey]*approvalAndTimestamp

type approvalAndTimestamp struct {
ValidatorSetApproval
Expand All @@ -24,7 +27,7 @@ type ApprovalStore struct {
validators NodeBLSMappings
logger common.Logger
pkByNodeID map[nodeID][]byte
approvalsByNodes map[nodeID]approvalsByPChainHeight
approvalsByNodes map[nodeID]approvalsByPChainHeightAndAuxInfoDigest
storedCount int
}

Expand All @@ -34,9 +37,9 @@ func NewApprovalStore(signatureVerifier SignatureVerifier, validators NodeBLSMap
pkByNodeID[vdr.NodeID] = vdr.BLSKey
}

approvalsByNodes := make(map[nodeID]approvalsByPChainHeight, len(validators))
approvalsByNodes := make(map[nodeID]approvalsByPChainHeightAndAuxInfoDigest, len(validators))
for _, vdr := range validators {
approvalsByNodes[vdr.NodeID] = make(approvalsByPChainHeight)
approvalsByNodes[vdr.NodeID] = make(approvalsByPChainHeightAndAuxInfoDigest)
}

return &ApprovalStore{
Expand Down Expand Up @@ -82,9 +85,14 @@ func (as *ApprovalStore) HandleApproval(approval *ValidatorSetApproval, timestam
return nil
}

key := approvalKey{
pChainHeight: approval.PChainHeight,
auxInfoDigest: approval.AuxInfoDigest,
}

// Store the approval.
oldApproval := as.approvalsByNodes[approval.NodeID][approval.PChainHeight]
as.approvalsByNodes[approval.NodeID][approval.PChainHeight] = &approvalAndTimestamp{
oldApproval := as.approvalsByNodes[approval.NodeID][key]
as.approvalsByNodes[approval.NodeID][key] = &approvalAndTimestamp{
ValidatorSetApproval: *approval,
Timestamp: timestamp,
}
Expand Down Expand Up @@ -113,22 +121,22 @@ func (as *ApprovalStore) maybePruneOldApprovals(approval *ValidatorSetApproval)
}

if oldestApproval != nil {
key := approvalKey{
pChainHeight: oldestApproval.PChainHeight,
auxInfoDigest: oldestApproval.AuxInfoDigest,
}

as.logger.Debug("Deleting old approval from node",
zap.String("nodeID", fmt.Sprintf("%x", oldestApproval.NodeID)),
zap.String("oldestApprovalPChainHeight",
fmt.Sprintf("%d", oldestApproval.PChainHeight)), zap.Uint64("oldestApprovalTimestamp", oldestApproval.Timestamp))
delete(as.approvalsByNodes[approval.NodeID], oldestApproval.PChainHeight)
delete(as.approvalsByNodes[approval.NodeID], key)
as.storedCount--
}
}

func (as *ApprovalStore) checkApprovalSignature(approval *ValidatorSetApproval, pk []byte) error {
pChainHeight := approval.PChainHeight
pChainHeightBuff := make([]byte, 8)
binary.BigEndian.PutUint64(pChainHeightBuff, pChainHeight)

signedMsg := common.SignedMessage{Payload: pChainHeightBuff, Context: signatureContext}
toBeSigned, err := asn1.Marshal(signedMsg)
toBeSigned, err := assembleApprovalToBeSigned(approval.PChainHeight, approval.AuxInfoDigest)
if err != nil {
return err
}
Expand All @@ -146,7 +154,13 @@ func (as *ApprovalStore) approvalExistsAndUpToDate(approval *ValidatorSetApprova
if as.approvalsByNodes[approval.NodeID] == nil {
return false
}
existingApproval := as.approvalsByNodes[approval.NodeID][approval.PChainHeight]

key := approvalKey{
pChainHeight: approval.PChainHeight,
auxInfoDigest: approval.AuxInfoDigest,
}

existingApproval := as.approvalsByNodes[approval.NodeID][key]
if existingApproval == nil {
return false
}
Expand Down
67 changes: 43 additions & 24 deletions msm/approvals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestApprovalStoreHandleApproval(t *testing.T) {
validators: 3,
sigErr: errors.New("bad sig"),
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 1, Signature: []byte{0xAA}}, 1},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 1},
},
verify: func(t *testing.T, as *ApprovalStore, _ []approvalAndTimestamp) {
require.Empty(t, as.Approvals())
Expand All @@ -74,7 +74,7 @@ func TestApprovalStoreHandleApproval(t *testing.T) {
name: "valid approval is stored",
validators: 3,
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0x01}}, 100},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100},
},
verify: func(t *testing.T, as *ApprovalStore, sent []approvalAndTimestamp) {
got := as.Approvals()
Expand All @@ -89,8 +89,8 @@ func TestApprovalStoreHandleApproval(t *testing.T) {
name: "duplicate approval with same timestamp is a no-op",
validators: 3,
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0x01}}, 100},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0x01}}, 100},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100},
},
verify: func(t *testing.T, as *ApprovalStore, _ []approvalAndTimestamp) {
require.Len(t, as.Approvals(), 1)
Expand All @@ -103,8 +103,8 @@ func TestApprovalStoreHandleApproval(t *testing.T) {
name: "older timestamp is ignored",
validators: 3,
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0x02}}, 200}, // newer, stored first
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0x01}}, 100}, // older, dropped
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 200}, // newer, stored first
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100}, // older, dropped
},
verify: func(t *testing.T, as *ApprovalStore, sent []approvalAndTimestamp) {
got := as.Approvals()
Expand All @@ -119,8 +119,8 @@ func TestApprovalStoreHandleApproval(t *testing.T) {
name: "newer timestamp replaces",
validators: 3,
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0x01}}, 100}, // older, stored first
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0x02}}, 200}, // newer, replaces
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 100}, // older, stored first
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, 200}, // newer, replaces
},
verify: func(t *testing.T, as *ApprovalStore, sent []approvalAndTimestamp) {
got := as.Approvals()
Expand All @@ -136,12 +136,12 @@ func TestApprovalStoreHandleApproval(t *testing.T) {
validators: 3,
// 3 validators x 2 heights, with timestamp == i*10 + h and Signature == {i} per validator.
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 1, Signature: []byte{0}}, 1},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 2, Signature: []byte{0}}, 2},
{ValidatorSetApproval{NodeID: makeNodeID(2), PChainHeight: 1, Signature: []byte{1}}, 11},
{ValidatorSetApproval{NodeID: makeNodeID(2), PChainHeight: 2, Signature: []byte{1}}, 12},
{ValidatorSetApproval{NodeID: makeNodeID(3), PChainHeight: 1, Signature: []byte{2}}, 21},
{ValidatorSetApproval{NodeID: makeNodeID(3), PChainHeight: 2, Signature: []byte{2}}, 22},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 1},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 2, Signature: signApproval(2, [32]byte{})}, 2},
{ValidatorSetApproval{NodeID: makeNodeID(2), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 11},
{ValidatorSetApproval{NodeID: makeNodeID(2), PChainHeight: 2, Signature: signApproval(2, [32]byte{})}, 12},
{ValidatorSetApproval{NodeID: makeNodeID(3), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 21},
{ValidatorSetApproval{NodeID: makeNodeID(3), PChainHeight: 2, Signature: signApproval(2, [32]byte{})}, 22},
},
verify: func(t *testing.T, as *ApprovalStore, sent []approvalAndTimestamp) {
require.Len(t, as.Approvals(), len(sent))
Expand All @@ -154,9 +154,9 @@ func TestApprovalStoreHandleApproval(t *testing.T) {
name: "prunes oldest when over cap",
validators: 2,
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 1, Signature: []byte{1}}, 10},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 2, Signature: []byte{2}}, 20},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 3, Signature: []byte{3}}, 30},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 10},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 2, Signature: signApproval(2, [32]byte{})}, 20},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 3, Signature: signApproval(3, [32]byte{})}, 30},
},
verify: func(t *testing.T, as *ApprovalStore, _ []approvalAndTimestamp) {
got := as.Approvals()
Expand All @@ -174,15 +174,32 @@ func TestApprovalStoreHandleApproval(t *testing.T) {
require.True(t, heights[3])
},
},
{
// Verifies that the store keys on (NodeID, PChainHeight, AuxInfoDigest):
// two approvals from the same node at the same P-chain height but with different
// auxiliary info digests are kept as independent entries.
name: "same height different aux info digest coexist",
validators: 3,
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, AuxInfoDigest: [32]byte{0xAA}, Signature: signApproval(7, [32]byte{0xAA})}, 100},
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, AuxInfoDigest: [32]byte{0xBB}, Signature: signApproval(7, [32]byte{0xBB})}, 100},
},
verify: func(t *testing.T, as *ApprovalStore, sent []approvalAndTimestamp) {
got := as.Approvals()
require.Len(t, got, 2)
require.Equal(t, 2, as.storedCount)
require.ElementsMatch(t, []ValidatorSetApproval{sent[0].ValidatorSetApproval, sent[1].ValidatorSetApproval}, got)
},
},
{
// Verifies that an approval with the maximum uint64 timestamp is stored,
// and that a subsequent approval at the same (NodeID, PChainHeight) with any
// smaller timestamp is treated as older and does not replace it.
name: "max uint64 timestamp is kept over any smaller timestamp",
validators: 3,
approvals: []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0xFF}}, math.MaxUint64}, // maxTS
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: []byte{0x01}}, math.MaxUint64 - 1}, // older
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, math.MaxUint64}, // maxTS
{ValidatorSetApproval{NodeID: makeNodeID(1), PChainHeight: 7, Signature: signApproval(7, [32]byte{})}, math.MaxUint64 - 1}, // older
},
verify: func(t *testing.T, as *ApprovalStore, sent []approvalAndTimestamp) {
got := as.Approvals()
Expand Down Expand Up @@ -213,11 +230,11 @@ func TestApprovalStoreHandleApprovalStoredCountStaysConsistent(t *testing.T) {
node := vdrs[0].NodeID

for _, a := range []approvalAndTimestamp{
{ValidatorSetApproval{NodeID: node, PChainHeight: 1}, 10},
{ValidatorSetApproval{NodeID: node, PChainHeight: 1}, 10}, // duplicate
{ValidatorSetApproval{NodeID: node, PChainHeight: 1}, 20}, // replaces
{ValidatorSetApproval{NodeID: node, PChainHeight: 2}, 30}, // new height
{ValidatorSetApproval{NodeID: node, PChainHeight: 3}, 40}, // triggers prune
{ValidatorSetApproval{NodeID: node, PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 10},
{ValidatorSetApproval{NodeID: node, PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 10}, // duplicate
{ValidatorSetApproval{NodeID: node, PChainHeight: 1, Signature: signApproval(1, [32]byte{})}, 20}, // replaces
{ValidatorSetApproval{NodeID: node, PChainHeight: 2, Signature: signApproval(2, [32]byte{})}, 30}, // new height
{ValidatorSetApproval{NodeID: node, PChainHeight: 3, Signature: signApproval(3, [32]byte{})}, 40}, // triggers prune
} {
require.NoError(t, as.HandleApproval(&a.ValidatorSetApproval, a.Timestamp))
require.Len(t, as.Approvals(), as.storedCount)
Expand All @@ -235,12 +252,14 @@ func TestApprovalStoreHandleApprovalPruningIsPerNode(t *testing.T) {
require.NoError(t, as.HandleApproval(&ValidatorSetApproval{
NodeID: vdrs[1].NodeID,
PChainHeight: 1,
Signature: signApproval(1, [32]byte{}),
}, 100))

for h := uint64(1); h <= 10; h++ {
require.NoError(t, as.HandleApproval(&ValidatorSetApproval{
NodeID: vdrs[0].NodeID,
PChainHeight: h,
Signature: signApproval(h, [32]byte{}),
}, h))
}
require.Len(t, as.Approvals().UniqueByNodeID(), 2)
Expand Down
Loading
Loading