Skip to content

Commit

Permalink
Add workaround for PoET round 4 (#5031)
Browse files Browse the repository at this point in the history
## Motivation
Closes #5029 

## Changes
- For PoET round 4 / publish epoch 5 PoET 112 will be added to the local state to fetch proofs from if the node registered to PoET 110 for that round.

## Test Plan
Added tests to ensure workaround works for the correct epoch / round

## TODO
<!-- This section should be removed when all items are complete -->
- [x] Explain motivation or link existing issue(s)
- [x] Test changes and document test plan
- [x] Update documentation as needed
- [x] Update [changelog](../CHANGELOG.md) as needed
  • Loading branch information
fasmat committed Sep 20, 2023
1 parent 83e2242 commit 2a0bb6d
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 99 deletions.
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 @@ func membersContainChallenge(members []types.Member, challenge types.Hash32) (ui
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
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)
}

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)
}

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))
}
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 5 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

0 comments on commit 2a0bb6d

Please sign in to comment.