Skip to content

Commit

Permalink
Syntactical validation of merged ATX
Browse files Browse the repository at this point in the history
  • Loading branch information
poszu committed Jun 11, 2024
1 parent e960aca commit 38d3bd4
Show file tree
Hide file tree
Showing 9 changed files with 1,030 additions and 42 deletions.
434 changes: 434 additions & 0 deletions activation/e2e/atx_merge_test.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions activation/e2e/certifier_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func spawnTestCertifier(
postVerifier, err := activation.NewPostVerifier(
cfg,
zaptest.NewLogger(t),
activation.WithVerifyingOpts(activation.DefaultTestPostVerifyingOpts()),
)
require.NoError(t, err)
var eg errgroup.Group
Expand Down
128 changes: 104 additions & 24 deletions activation/handler_v2.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package activation

import (
"bytes"
"context"
"errors"
"fmt"
"math"
"sort"
"time"

"github.com/spacemeshos/post/shared"
Expand Down Expand Up @@ -146,8 +148,6 @@ func (h *HandlerV2) processATX(
}

// Syntactically validate an ATX.
// TODOs:
// 2. support merged ATXs.
func (h *HandlerV2) syntacticallyValidate(ctx context.Context, atx *wire.ActivationTxV2) error {
if !h.edVerifier.Verify(signing.ATX, atx.SmesherID, atx.SignedBytes(), atx.Signature) {
return fmt.Errorf("invalid atx signature: %w", errMalformedData)
Expand Down Expand Up @@ -219,8 +219,9 @@ func (h *HandlerV2) syntacticallyValidate(ctx context.Context, atx *wire.Activat
if len(atx.Marriages) != 0 {
return errors.New("merged atx cannot have marriages")
}
// TODO: support merged ATXs
return errors.New("atx merge is not supported")
if err := h.verifyIncludedIDsUniqueness(atx); err != nil {
return err
}
default:
// Solo chained (non-initial) ATX
if len(atx.PreviousATXs) != 1 {
Expand Down Expand Up @@ -353,13 +354,28 @@ func (h *HandlerV2) validatePreviousAtx(id types.NodeID, post *wire.SubPostV2, p
}
return min(prev.NumUnits, post.NumUnits), nil
case *wire.ActivationTxV2:
// TODO: support previous merged-ATX

// previous is solo ATX
if prev.SmesherID == id {
return min(prev.NiPosts[0].Posts[0].NumUnits, post.NumUnits), nil
if prev.MarriageATX != nil {
// Previous is a merged ATX
// need to find out if the given ID was present in the previous ATX
_, idx, err := identities.MarriageInfo(h.cdb, id)
if err != nil {
return 0, fmt.Errorf("fetching marriage info for ID %s: %w", id, err)

Check warning on line 362 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L360-L362

Added lines #L360 - L362 were not covered by tests
}
for _, nipost := range prev.NiPosts {
for _, post := range nipost.Posts {
if post.MarriageIndex == uint32(idx) {
return min(post.NumUnits, post.NumUnits), nil

Check warning on line 367 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L364-L367

Added lines #L364 - L367 were not covered by tests
}
}
}
} else {
// Previous is a solo ATX
if prev.SmesherID == id {
return min(prev.NiPosts[0].Posts[0].NumUnits, post.NumUnits), nil
}
}
return 0, fmt.Errorf("previous solo ATX V2 has different owner: %s (expected %s)", prev.SmesherID, id)

return 0, fmt.Errorf("previous ATX V2 doesn't contain %s", id)
}
return 0, fmt.Errorf("unexpected previous ATX type: %T", prev)

Check warning on line 380 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L380

Added line #L380 was not covered by tests
}
Expand Down Expand Up @@ -398,11 +414,56 @@ func (h *HandlerV2) validatePositioningAtx(publish types.EpochID, golden, positi
return posAtx.TickHeight(), nil
}

// Validate marriage ATX and return the full equivocation set.
func (h *HandlerV2) validateMarriages(atx *wire.ActivationTxV2) ([]types.NodeID, error) {
if atx.MarriageATX == nil {
return []types.NodeID{atx.SmesherID}, nil
}
marriageAtxID, _, err := identities.MarriageInfo(h.cdb, atx.SmesherID)
switch {
case errors.Is(err, sql.ErrNotFound) || marriageAtxID == nil:
return nil, errors.New("smesher is not married")
case err != nil:
return nil, fmt.Errorf("fetching smesher's marriage atx ID: %w", err)

Check warning on line 427 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L426-L427

Added lines #L426 - L427 were not covered by tests
}

if *atx.MarriageATX != *marriageAtxID {
return nil, fmt.Errorf("smesher's marriage ATX ID mismatch: %s != %s", *atx.MarriageATX, *marriageAtxID)
}

marriageAtx, err := atxs.Get(h.cdb, *atx.MarriageATX)
if err != nil {
return nil, fmt.Errorf("fetching marriage atx: %w", err)

Check warning on line 436 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L436

Added line #L436 was not covered by tests
}
if !(marriageAtx.PublishEpoch <= atx.PublishEpoch-2) {
return nil, fmt.Errorf(
"marriage atx must be published at least 2 epochs before %v (is %v)",
atx.PublishEpoch,
marriageAtx.PublishEpoch,
)
}

return identities.EquivocationSetByMarriageATX(h.cdb, *atx.MarriageATX)
}

type atxParts struct {
leaves uint64
effectiveUnits uint32
}

func (h *HandlerV2) verifyIncludedIDsUniqueness(atx *wire.ActivationTxV2) error {
seen := make(map[uint32]struct{})
for _, niposts := range atx.NiPosts {
for _, post := range niposts.Posts {
if _, ok := seen[post.MarriageIndex]; ok {
return fmt.Errorf("ID present twice (duplicated marriage index): %d", post.MarriageIndex)
}
seen[post.MarriageIndex] = struct{}{}
}
}
return nil
}

// Syntactically validate the ATX with its dependencies.
func (h *HandlerV2) syntacticallyValidateDeps(
ctx context.Context,
Expand All @@ -427,33 +488,42 @@ func (h *HandlerV2) syntacticallyValidateDeps(
previousAtxs[i] = prevAtx
}

// validate all niposts
// TODO: support merged ATXs
// For a merged ATX we need to fetch the equivocation this smesher is part of.
equivocationSet := []types.NodeID{atx.SmesherID}
equivocationSet, err := h.validateMarriages(atx)
if err != nil {
return nil, nil, fmt.Errorf("validating marriages: %w", err)

Check warning on line 493 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L493

Added line #L493 was not covered by tests
}

// validate previous ATXs
var totalEffectiveNumUnits uint32
var minLeaves uint64 = math.MaxUint64
var smesherCommitment *types.ATXID
for _, niposts := range atx.NiPosts {
// verify PoET memberships in a single go
var poetChallenges [][]byte

for _, post := range niposts.Posts {
if post.MarriageIndex >= uint32(len(equivocationSet)) {
err := fmt.Errorf("marriage index out of bounds: %d > %d", post.MarriageIndex, len(equivocationSet)-1)
return nil, nil, err
}

id := equivocationSet[post.MarriageIndex]
effectiveNumUnits := post.NumUnits
if atx.Initial == nil {
var err error
effectiveNumUnits, err = h.validatePreviousAtx(id, &post, previousAtxs)
if err != nil {
return nil, nil, fmt.Errorf("validating previous atx for ID %s: %w", id, err)
return nil, nil, fmt.Errorf("validating previous atx: %w", err)
}
}
totalEffectiveNumUnits += effectiveNumUnits
}
}

// validate all niposts
var minLeaves uint64 = math.MaxUint64
var smesherCommitment *types.ATXID
for _, niposts := range atx.NiPosts {
// verify PoET memberships in a single go
var poetChallenges [][]byte

for _, post := range niposts.Posts {
id := equivocationSet[post.MarriageIndex]
var commitment types.ATXID
if atx.Initial != nil {
commitment = atx.Initial.CommitmentATX
Expand All @@ -463,7 +533,7 @@ func (h *HandlerV2) syntacticallyValidateDeps(
if err != nil {
return nil, nil, fmt.Errorf("commitment atx not found for ID %s: %w", id, err)
}
if smesherCommitment == nil {
if id == atx.SmesherID {
smesherCommitment = &commitment
}
}
Expand Down Expand Up @@ -506,6 +576,9 @@ func (h *HandlerV2) syntacticallyValidateDeps(
Nodes: niposts.Membership.Nodes,
LeafIndices: niposts.Membership.LeafIndices,
}
sort.Slice(poetChallenges, func(i, j int) bool {
return bytes.Compare(poetChallenges[i], poetChallenges[j]) < 0
})
leaves, err := h.nipostValidator.PoetMembership(ctx, &membership, niposts.Challenge, poetChallenges)
if err != nil {
return nil, nil, fmt.Errorf("invalid poet membership: %w", err)
Expand All @@ -519,6 +592,9 @@ func (h *HandlerV2) syntacticallyValidateDeps(
}

if atx.Initial == nil {
if smesherCommitment == nil {
return nil, nil, errors.New("ATX signer not present in merged ATX")
}
err := h.nipostValidator.VRFNonceV2(atx.SmesherID, *smesherCommitment, atx.VRFNonce, atx.TotalNumUnits())
if err != nil {
return nil, nil, fmt.Errorf("validating VRF nonce: %w", err)
Expand Down Expand Up @@ -563,6 +639,10 @@ func (h *HandlerV2) checkDoubleMarry(
tx *sql.Tx,
watx *wire.ActivationTxV2,
) (*mwire.MalfeasanceProof, error) {
if len(watx.Marriages) == 0 {
// not trying to marry
return nil, nil
}
checkMarried := func(tx *sql.Tx, id types.NodeID) (*mwire.MalfeasanceProof, error) {
married, err := identities.Married(tx, id)
if err != nil {
Expand Down Expand Up @@ -611,12 +691,12 @@ func (h *HandlerV2) storeAtx(
}

if len(watx.Marriages) != 0 {
for _, m := range watx.Marriages {
if err := identities.SetMarriage(tx, m.ID, atx.ID()); err != nil {
for i, m := range watx.Marriages {
if err := identities.SetMarriage(tx, m.ID, atx.ID(), i+1); err != nil {
return err
}
}
if err := identities.SetMarriage(tx, atx.SmesherID, atx.ID()); err != nil {
if err := identities.SetMarriage(tx, atx.SmesherID, atx.ID(), 0); err != nil {
return err
}
if !malicious && proof == nil {
Expand Down
Loading

0 comments on commit 38d3bd4

Please sign in to comment.