Skip to content
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

[Merged by Bors] - Add workaround for PoET round 4 #5031

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Support for old certificate sync protocol is dropped. This update is incompatibl

### Features

* [#5031](https://github.com/spacemeshos/go-spacemesh/pull/5031) Nodes will also fetch from PoET 112 for round 4 if they were able to register to PoET 110.

### Improvements

* [#4998](https://github.com/spacemeshos/go-spacemesh/pull/4998) First phase of state size reduction.
Expand Down
66 changes: 43 additions & 23 deletions activation/nipost.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,57 +407,77 @@
return 0, fmt.Errorf("challenge is not a member of the proof")
}

// TODO(mafa): remove after epoch 4 ends; https://github.com/spacemeshos/go-spacemesh/issues/4968
func (nb *NIPostBuilder) addPoet111ForPubEpoch4(ctx context.Context) error {
// because PoET 111 had a hardware issue when challenges for round 3 were submitted, no node could submit to it
// 111 was recovered with the PoET 110 DB, so all successful submissions to 110 should be able to be fetched from there as well
func (nb *NIPostBuilder) addPoETMitigation(ctx context.Context, from, to string, pubEpoch types.EpochID) error {
clientTo, ok := nb.poetProvers[to]
if !ok {
// Target PoET is not in the list, no action necessary
return nil
}

client111, ok := nb.poetProvers["https://poet-111.spacemesh.network"]
clientFrom, ok := nb.poetProvers[from]
if !ok {
// poet 111 is not in the list, no action necessary
// Source PoET is not in the list, cannot apply mitigation

Check warning on line 419 in activation/nipost.go

View check run for this annotation

Codecov / codecov/patch

activation/nipost.go#L419

Added line #L419 was not covered by tests
return nil
}

nb.log.Info("pub epoch 4 mitigation: PoET 111 is in the list of PoETs, adding it to the state as well")
client110 := nb.poetProvers["https://poet-110.spacemesh.network"]
nb.log.With().Info("poet mitigation: target and source are in the list of PoETs, applying mitigation",
log.String("state_from", from),
log.String("target_poet", to),
log.Uint32("pub_epoch", pubEpoch.Uint32()),
)

ID110, err := client110.PoetServiceID(ctx)
idFrom, err := clientFrom.PoetServiceID(ctx)
if err != nil {
return fmt.Errorf("failed to get PoET 110 id: %w", err)
return fmt.Errorf("failed to get id for PoET %s: %w", from, err)

Check warning on line 431 in activation/nipost.go

View check run for this annotation

Codecov / codecov/patch

activation/nipost.go#L431

Added line #L431 was not covered by tests
}

ID111, err := client111.PoetServiceID(ctx)
idTo, err := clientTo.PoetServiceID(ctx)
if err != nil {
return fmt.Errorf("failed to get PoET 111 id: %w", err)
return fmt.Errorf("failed to get id for PoET %s: %w", to, err)

Check warning on line 436 in activation/nipost.go

View check run for this annotation

Codecov / codecov/patch

activation/nipost.go#L436

Added line #L436 was not covered by tests
}

if slices.IndexFunc(nb.state.PoetRequests, func(r types.PoetRequest) bool { return bytes.Equal(r.PoetServiceID.ServiceID, ID111.ServiceID) }) != -1 {
if slices.IndexFunc(nb.state.PoetRequests, func(r types.PoetRequest) bool { return bytes.Equal(r.PoetServiceID.ServiceID, idTo.ServiceID) }) != -1 {
nb.log.Info("PoET 111 is already in the state, no action necessary")
return nil
}

poet110 := slices.IndexFunc(nb.state.PoetRequests, func(r types.PoetRequest) bool {
return bytes.Equal(r.PoetServiceID.ServiceID, ID110.ServiceID)
poetFromIdx := slices.IndexFunc(nb.state.PoetRequests, func(r types.PoetRequest) bool {
return bytes.Equal(r.PoetServiceID.ServiceID, idFrom.ServiceID)
})
if poet110 == -1 {
if poetFromIdx == -1 {
return fmt.Errorf("poet 110 is not in the state, cannot add poet 111")
}

poet111 := nb.state.PoetRequests[poet110]
poet111.PoetServiceID.ServiceID = ID111.ServiceID
nb.state.PoetRequests = append(nb.state.PoetRequests, poet111)
poetToReq := nb.state.PoetRequests[poetFromIdx]
poetToReq.PoetServiceID.ServiceID = idTo.ServiceID
nb.state.PoetRequests = append(nb.state.PoetRequests, poetToReq)
nb.persistState()
nb.log.Info("pub epoch 4 mitigation: PoET 111 added to the state")
nb.log.With().Info("poet mitigation: target PoET added to the state",
log.String("target_poet", to),
log.Uint32("pub_epoch", pubEpoch.Uint32()),
)
return nil
}

func (nb *NIPostBuilder) getBestProof(ctx context.Context, challenge types.Hash32, publishEpoch types.EpochID) (types.PoetProofRef, *types.MerkleProof, error) {
// TODO(mafa): remove after next PoET round; https://github.com/spacemeshos/go-spacemesh/issues/4968
if publishEpoch == 4 {
err := nb.addPoet111ForPubEpoch4(ctx)
switch publishEpoch {
case 4:
// because PoET 111 had a hardware issue when challenges for round 3 were submitted, no node could submit to it
// 111 was recovered with the PoET 110 DB, so all successful submissions to 110 should be able to be fetched from there as well
// TODO(mafa): remove after next PoET round; https://github.com/spacemeshos/go-spacemesh/issues/4968
err := nb.addPoETMitigation(ctx, "https://poet-110.spacemesh.network", "https://poet-111.spacemesh.network", 4)
if err != nil {
nb.log.With().Error("pub epoch 4 mitigation: failed to add PoET 111 to state for pub epoch 4", log.Err(err))
}
case 5:
// PoET 112 came online after the registration window for round 4 ended, so no node could submit to it
// 112 was initialized with the PoET 110 DB, so all successful submissions to 110 should be able to be fetched from there as well
// TODO(mafa): remove after next PoET round; https://github.com/spacemeshos/go-spacemesh/issues/5030
err := nb.addPoETMitigation(ctx, "https://poet-110.spacemesh.network", "https://poet-112.spacemesh.network", 5)
if err != nil {
nb.log.With().Error("pub epoch 5 mitigation: failed to add PoET 112 to state for pub epoch 5", log.Err(err))
}

Check warning on line 479 in activation/nipost.go

View check run for this annotation

Codecov / codecov/patch

activation/nipost.go#L478-L479

Added lines #L478 - L479 were not covered by tests
default:
}

type poetProof struct {
Expand Down
180 changes: 104 additions & 76 deletions activation/nipost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1039,88 +1039,116 @@ func TestConstructingMerkleProof(t *testing.T) {
})
}

// TODO(mafa): remove after epoch 4 end; https://github.com/spacemeshos/go-spacemesh/issues/4968
func TestNIPostBuilder_Mainnet_PoetRound3_Workaround(t *testing.T) {
t.Parallel()

challenge := types.NIPostChallenge{
PublishEpoch: 4,
}

ctrl := gomock.NewController(t)
postProvider := NewMockpostSetupProvider(ctrl)
postProvider.EXPECT().Status().Return(&PostSetupStatus{State: PostSetupStateComplete})
postProvider.EXPECT().CommitmentAtx().Return(types.EmptyATXID, nil).AnyTimes()
postProvider.EXPECT().LastOpts().Return(&PostSetupOpts{}).AnyTimes()
postProvider.EXPECT().GenerateProof(gomock.Any(), gomock.Any(), gomock.Any())

nipostValidator := NewMocknipostValidator(ctrl)
nipostValidator.EXPECT().Post(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil)

poets := make([]PoetProvingServiceClient, 0, 2)
{
poetProvider := NewMockPoetProvingServiceClient(ctrl)
poetProvider.EXPECT().Address().Return("https://poet-110.spacemesh.network")
poetProvider.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{ServiceID: []byte("poet-110")}, nil).AnyTimes()
poetProvider.EXPECT().PowParams(gomock.Any()).AnyTimes().Return(&PoetPowParams{}, nil)

// PoET 110 succeeds to submit
poetProvider.EXPECT().Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&types.PoetRound{}, nil)

// proof is fetched from PoET 110
poetProvider.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProofMessage{
PoetProof: types.PoetProof{},
}, []types.Member{types.Member(challenge.Hash())}, nil)
poets = append(poets, poetProvider)
}

{
// PoET 111 fails submission
poetProvider := NewMockPoetProvingServiceClient(ctrl)
poetProvider.EXPECT().Address().Return("https://poet-111.spacemesh.network")
poetProvider.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{}, errors.New("failed submission"))

// proof is still fetched from PoET 111
poetProvider.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{ServiceID: []byte("poet-111")}, nil).AnyTimes()
poetProvider.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProofMessage{
PoetProof: types.PoetProof{},
}, []types.Member{types.Member(challenge.Hash())}, nil)

poets = append(poets, poetProvider)
}

poetDb := NewMockpoetDbAPI(ctrl)
poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil).Times(2)
mclock := NewMocklayerClock(ctrl)
genesis := time.Now().Add(-time.Duration(1) * layerDuration)
mclock.EXPECT().LayerToTime(gomock.Any()).AnyTimes().DoAndReturn(
func(got types.LayerID) time.Time {
return genesis.Add(layerDuration * time.Duration(got))
tt := []struct {
name string
from string
to string
epoch types.EpochID
}{
{
// TODO(mafa): remove after epoch 4 end; https://github.com/spacemeshos/go-spacemesh/issues/4968
name: "epoch 4: PoET 111 restore with PoET 110",
from: "https://poet-110.spacemesh.network",
to: "https://poet-111.spacemesh.network",
epoch: 4,
},
)
{
// TODO(mafa): remove after epoch 4 end; https://github.com/spacemeshos/go-spacemesh/issues/5030
name: "epoch 5: PoET 112 restore with PoET 110",
from: "https://poet-110.spacemesh.network",
to: "https://poet-112.spacemesh.network",
epoch: 5,
},
}

sig, err := signing.NewEdSigner()
require.NoError(t, err)
poetCfg := PoetConfig{
PhaseShift: layerDuration * layersPerEpoch / 2,
for _, tc := range tt {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

challenge := types.NIPostChallenge{
PublishEpoch: tc.epoch,
}

ctrl := gomock.NewController(t)
postProvider := NewMockpostSetupProvider(ctrl)
postProvider.EXPECT().Status().Return(&PostSetupStatus{State: PostSetupStateComplete})
postProvider.EXPECT().CommitmentAtx().Return(types.EmptyATXID, nil).AnyTimes()
postProvider.EXPECT().LastOpts().Return(&PostSetupOpts{}).AnyTimes()
postProvider.EXPECT().GenerateProof(gomock.Any(), gomock.Any(), gomock.Any())

nipostValidator := NewMocknipostValidator(ctrl)
nipostValidator.EXPECT().Post(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil)

poets := make([]PoetProvingServiceClient, 0, 2)
{
poetProvider := NewMockPoetProvingServiceClient(ctrl)
poetProvider.EXPECT().Address().Return(tc.from)
poetProvider.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{ServiceID: []byte("poet-from")}, nil).AnyTimes()
poetProvider.EXPECT().PowParams(gomock.Any()).AnyTimes().Return(&PoetPowParams{}, nil)

// PoET succeeds to submit
poetProvider.EXPECT().Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&types.PoetRound{}, nil)

// proof is fetched from PoET
poetProvider.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProofMessage{
PoetProof: types.PoetProof{},
}, []types.Member{types.Member(challenge.Hash())}, nil)
poets = append(poets, poetProvider)
}

{
// PoET fails submission
poetProvider := NewMockPoetProvingServiceClient(ctrl)
poetProvider.EXPECT().Address().Return(tc.to)
poetProvider.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{}, errors.New("failed submission"))

// proof is still fetched from PoET
poetProvider.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{ServiceID: []byte("poet-to")}, nil).AnyTimes()
poetProvider.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProofMessage{
PoetProof: types.PoetProof{},
}, []types.Member{types.Member(challenge.Hash())}, nil)

poets = append(poets, poetProvider)
}

poetDb := NewMockpoetDbAPI(ctrl)
poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil).Times(2)
mclock := NewMocklayerClock(ctrl)
genesis := time.Now().Add(-time.Duration(1) * layerDuration)
mclock.EXPECT().LayerToTime(gomock.Any()).AnyTimes().DoAndReturn(
func(got types.LayerID) time.Time {
return genesis.Add(layerDuration * time.Duration(got))
},
)

sig, err := signing.NewEdSigner()
require.NoError(t, err)
poetCfg := PoetConfig{
PhaseShift: layerDuration * layersPerEpoch / 2,
}
nb, err := NewNIPostBuilder(
types.NodeID{1},
postProvider,
poetDb,
[]string{},
t.TempDir(),
logtest.New(t, zapcore.DebugLevel),
sig,
poetCfg,
mclock,
WithNipostValidator(nipostValidator),
withPoetClients(poets),
)
require.NoError(t, err)
nipost, err := nb.BuildNIPost(context.Background(), &challenge)
require.NoError(t, err)
require.NotNil(t, nipost)
})
}
nb, err := NewNIPostBuilder(
types.NodeID{1},
postProvider,
poetDb,
[]string{},
t.TempDir(),
logtest.New(t, zapcore.DebugLevel),
sig,
poetCfg,
mclock,
WithNipostValidator(nipostValidator),
withPoetClients(poets),
)
require.NoError(t, err)
nipost, err := nb.BuildNIPost(context.Background(), &challenge)
require.NoError(t, err)
require.NotNil(t, nipost)
}

func FuzzBuilderStateConsistency(f *testing.F) {
Expand Down