diff --git a/activation/activation.go b/activation/activation.go index 79d075ee141..11aa48898c6 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -17,6 +17,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/metrics" "github.com/spacemeshos/go-spacemesh/activation/wire" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/events" @@ -72,6 +73,7 @@ type Builder struct { coinbaseAccount types.Address conf Config db sql.Executor + atxsdata *atxsdata.Data localDB *localsql.Database publisher pubsub.Publisher nipostBuilder nipostBuilder @@ -152,6 +154,7 @@ func WithPostStates(ps PostStates) BuilderOption { func NewBuilder( conf Config, db sql.Executor, + atxsdata *atxsdata.Data, localDB *localsql.Database, publisher pubsub.Publisher, nipostBuilder nipostBuilder, @@ -165,6 +168,7 @@ func NewBuilder( signers: make(map[types.NodeID]*signing.EdSigner), conf: conf, db: db, + atxsdata: atxsdata, localDB: localDB, publisher: publisher, nipostBuilder: nipostBuilder, @@ -507,12 +511,6 @@ func (b *Builder) BuildNIPostChallenge(ctx context.Context, nodeID types.NodeID) } } - posAtx, err := b.getPositioningAtx(ctx, nodeID, publish) - if err != nil { - return nil, fmt.Errorf("failed to get positioning ATX: %w", err) - } - logger.Info("selected positioning atx", log.ZShortStringer("atx_id", posAtx)) - prevAtx, err = b.GetPrevAtx(nodeID) switch { case errors.Is(err, sql.ErrNotFound): @@ -538,6 +536,10 @@ func (b *Builder) BuildNIPostChallenge(ctx context.Context, nodeID types.NodeID) } return nil, fmt.Errorf("initial POST is invalid: %w", err) } + posAtx, err := b.getPositioningAtx(ctx, nodeID, publish, nil) + if err != nil { + return nil, fmt.Errorf("failed to get positioning ATX: %w", err) + } challenge = &types.NIPostChallenge{ PublishEpoch: publish, Sequence: 0, @@ -554,6 +556,10 @@ func (b *Builder) BuildNIPostChallenge(ctx context.Context, nodeID types.NodeID) return nil, fmt.Errorf("get last ATX: %w", err) default: // regular ATX challenge + posAtx, err := b.getPositioningAtx(ctx, nodeID, publish, prevAtx) + if err != nil { + return nil, fmt.Errorf("failed to get positioning ATX: %w", err) + } challenge = &types.NIPostChallenge{ PublishEpoch: publish, Sequence: prevAtx.Sequence + 1, @@ -692,6 +698,12 @@ func (b *Builder) createAtx( break } if nipostState.VRFNonce != oldNonce { + b.log.Info( + "attaching a new VRF nonce in ATX", + log.ZShortStringer("smesherID", sig.NodeID()), + zap.Uint64("new nonce", uint64(nipostState.VRFNonce)), + zap.Uint64("old nonce", uint64(oldNonce)), + ) nonce = &nipostState.VRFNonce } } @@ -723,9 +735,9 @@ func (b *Builder) broadcast(ctx context.Context, atx scale.Encodable) (int, erro return len(buf), nil } -// getPositioningAtx returns atx id with the highest tick height. +// searchPositioningAtx returns atx id with the highest tick height. // publish epoch is used for caching the positioning atx. -func (b *Builder) getPositioningAtx( +func (b *Builder) searchPositioningAtx( ctx context.Context, nodeID types.NodeID, publish types.EpochID, @@ -738,11 +750,17 @@ func (b *Builder) getPositioningAtx( return found.id, nil } - logger.Info("searching for positioning atx") + latestPublished, err := atxs.LatestEpoch(b.db) + if err != nil { + return types.EmptyATXID, fmt.Errorf("get latest epoch: %w", err) + } + logger.Info("searching for positioning atx", zap.Uint32("latest_epoch", latestPublished.Uint32())) + // positioning ATX publish epoch must be lower than the publish epoch of built ATX + positioningAtxPublished := min(latestPublished, publish-1) id, err := findFullyValidHighTickAtx( ctx, - b.db, - nodeID, + b.atxsdata, + positioningAtxPublished, b.conf.GoldenATXID, b.validator, logger, @@ -750,22 +768,47 @@ func (b *Builder) getPositioningAtx( VerifyChainOpts.WithTrustedID(nodeID), VerifyChainOpts.WithLogger(b.log), ) - switch { - case err == nil: - b.posAtxFinder.found = &struct { - id types.ATXID - forPublish types.EpochID - }{id, publish} - return id, nil - case errors.Is(err, sql.ErrNotFound): - logger.Info("using golden atx as positioning atx") - b.posAtxFinder.found = &struct { - id types.ATXID - forPublish types.EpochID - }{b.conf.GoldenATXID, publish} - return b.conf.GoldenATXID, nil + if err != nil { + logger.Info("search failed - using golden atx as positioning atx", zap.Error(err)) + id = b.conf.GoldenATXID + } + b.posAtxFinder.found = &struct { + id types.ATXID + forPublish types.EpochID + }{id, publish} + + return id, nil +} + +// getPositioningAtx returns the positioning ATX. +// The provided previous ATX is picked if it has a greater or equal +// tick count as the ATX selected in `searchPositioningAtx`. +func (b *Builder) getPositioningAtx( + ctx context.Context, + nodeID types.NodeID, + publish types.EpochID, + previous *types.ActivationTx, +) (types.ATXID, error) { + id, err := b.searchPositioningAtx(ctx, nodeID, publish) + if err != nil { + return types.EmptyATXID, err + } + + if previous != nil { + switch { + case id == b.conf.GoldenATXID: + id = previous.ID() + case id != b.conf.GoldenATXID: + if candidate, err := atxs.Get(b.db, id); err == nil { + if previous.TickHeight() >= candidate.TickHeight() { + id = previous.ID() + } + } + } } - return id, err + + b.log.Info("selected positioning atx", log.ZShortStringer("id", id), log.ZShortStringer("smesherID", nodeID)) + return id, nil } func (b *Builder) Regossip(ctx context.Context, nodeID types.NodeID) error { @@ -797,35 +840,26 @@ func buildNipostChallengeStartDeadline(roundStart time.Time, gracePeriod time.Du func findFullyValidHighTickAtx( ctx context.Context, - db sql.Executor, - prefNodeID types.NodeID, + atxdata *atxsdata.Data, + publish types.EpochID, goldenATXID types.ATXID, validator nipostValidator, logger *zap.Logger, opts ...VerifyChainOption, ) (types.ATXID, error) { - rejectedAtxs := make(map[types.ATXID]struct{}) - filter := func(id types.ATXID) bool { - _, ok := rejectedAtxs[id] - return !ok - } - - for { - select { - case <-ctx.Done(): - return types.ATXID{}, ctx.Err() - default: - } - id, err := atxs.GetIDWithMaxHeight(db, prefNodeID, filter) - if err != nil { - return types.ATXID{}, err - } - logger.Info("found candidate for high-tick atx, verifying its chain", log.ZShortStringer("atx_id", id)) + var found *types.ATXID + atxdata.IterateHighTicksInEpoch(publish+1, func(id types.ATXID) bool { + logger.Info("found candidate for high-tick atx", log.ZShortStringer("id", id)) if err := validator.VerifyChain(ctx, id, goldenATXID, opts...); err != nil { - logger.Info("rejecting candidate for high-tick atx", zap.Error(err), log.ZShortStringer("atx_id", id)) - rejectedAtxs[id] = struct{}{} - } else { - return id, nil + logger.Info("rejecting candidate for high-tick atx", zap.Error(err), log.ZShortStringer("id", id)) + return true } + found = &id + return false + }) + + if found != nil { + return *found, nil } + return types.ATXID{}, ErrNotFound } diff --git a/activation/activation_test.go b/activation/activation_test.go index 6d9d800dfcb..e6131e3b528 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -22,6 +22,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/spacemeshos/go-spacemesh/activation/wire" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/events" @@ -103,6 +104,7 @@ func newTestBuilder(tb testing.TB, numSigners int, opts ...BuilderOption) *testA b := NewBuilder( cfg, tab.db, + atxsdata.New(), tab.localDb, tab.mpub, tab.mnipost, @@ -174,6 +176,7 @@ func publishAtx( built = new(wire.ActivationTxV1) codec.MustDecode(got, built) require.NoError(tb, atxs.Add(tab.db, toAtx(tb, built))) + tab.atxsdata.AddFromAtx(toAtx(tb, built), false) return nil }) @@ -335,6 +338,7 @@ func TestBuilder_PublishActivationTx_HappyFlow(t *testing.T) { prevAtx := newInitialATXv1(t, tab.goldenATXID) prevAtx.Sign(sig) require.NoError(t, atxs.Add(tab.db, toAtx(t, prevAtx))) + tab.atxsdata.AddFromAtx(toAtx(t, prevAtx), false) // create and publish ATX tab.mclock.EXPECT().CurrentLayer().Return(currLayer).Times(4) @@ -342,16 +346,18 @@ func TestBuilder_PublishActivationTx_HappyFlow(t *testing.T) { atx1, err := publishAtx(t, tab, sig.NodeID(), posEpoch, &currLayer, layersPerEpoch) require.NoError(t, err) require.NotNil(t, atx1) + require.Equal(t, prevAtx.ID(), atx1.PositioningATXID) // create and publish another ATX currLayer = (posEpoch + 1).FirstLayer() tab.mclock.EXPECT().CurrentLayer().Return(currLayer).Times(4) - tab.mValidator.EXPECT().VerifyChain(gomock.Any(), prevAtx.ID(), tab.goldenATXID, gomock.Any()) + tab.mValidator.EXPECT().VerifyChain(gomock.Any(), atx1.ID(), tab.goldenATXID, gomock.Any()) atx2, err := publishAtx(t, tab, sig.NodeID(), atx1.PublishEpoch, &currLayer, layersPerEpoch) require.NoError(t, err) require.NotNil(t, atx2) require.NotEqual(t, atx1, atx2) require.Equal(t, atx1.PublishEpoch+1, atx2.PublishEpoch) + require.Equal(t, atx1.ID(), atx2.PositioningATXID) // state is cleaned up _, err = nipost.Challenge(tab.localDB, sig.NodeID()) @@ -370,6 +376,7 @@ func TestBuilder_Loop_WaitsOnStaleChallenge(t *testing.T) { prevAtx := newInitialATXv1(t, tab.goldenATXID) prevAtx.Sign(sig) require.NoError(t, atxs.Add(tab.db, toAtx(t, prevAtx))) + tab.atxsdata.AddFromAtx(toAtx(t, prevAtx), false) tab.mclock.EXPECT().CurrentLayer().Return(currLayer).AnyTimes() tab.mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( @@ -418,6 +425,7 @@ func TestBuilder_PublishActivationTx_FaultyNet(t *testing.T) { prevAtx := newInitialATXv1(t, tab.goldenATXID) prevAtx.Sign(sig) require.NoError(t, atxs.Add(tab.db, toAtx(t, prevAtx))) + tab.atxsdata.AddFromAtx(toAtx(t, prevAtx), false) publishEpoch := posEpoch + 1 tab.mclock.EXPECT().CurrentLayer().DoAndReturn(func() types.LayerID { return currLayer }).AnyTimes() @@ -491,6 +499,7 @@ func TestBuilder_PublishActivationTx_UsesExistingChallengeOnLatePublish(t *testi prevAtx.Sign(sig) vPrevAtx := toAtx(t, prevAtx) require.NoError(t, atxs.Add(tab.db, vPrevAtx)) + tab.atxsdata.AddFromAtx(toAtx(t, prevAtx), false) publishEpoch := currLayer.GetEpoch() tab.mclock.EXPECT().CurrentLayer().DoAndReturn(func() types.LayerID { return currLayer }).AnyTimes() @@ -566,6 +575,7 @@ func TestBuilder_PublishActivationTx_RebuildNIPostWhenTargetEpochPassed(t *testi prevAtx.Sign(sig) vPrevAtx := toAtx(t, prevAtx) require.NoError(t, atxs.Add(tab.db, vPrevAtx)) + tab.atxsdata.AddFromAtx(toAtx(t, prevAtx), false) publishEpoch := posEpoch + 1 tab.mclock.EXPECT().CurrentLayer().DoAndReturn( @@ -625,6 +635,7 @@ func TestBuilder_PublishActivationTx_RebuildNIPostWhenTargetEpochPassed(t *testi posAtx := newInitialATXv1(t, tab.goldenATXID, func(atx *wire.ActivationTxV1) { atx.PublishEpoch = posEpoch }) posAtx.Sign(sig) require.NoError(t, atxs.Add(tab.db, toAtx(t, posAtx))) + tab.atxsdata.AddFromAtx(toAtx(t, posAtx), false) tab.mclock.EXPECT().CurrentLayer().DoAndReturn(func() types.LayerID { return currLayer }).AnyTimes() tab.mnipost.EXPECT().ResetState(sig.NodeID()).Return(nil) tab.mValidator.EXPECT().VerifyChain(gomock.Any(), posAtx.ID(), tab.goldenATXID, gomock.Any()) @@ -774,6 +785,7 @@ func TestBuilder_PublishActivationTx_PrevATXWithoutPrevATX(t *testing.T) { vPosAtx := toAtx(t, posAtx) vPosAtx.TickCount = 100 r.NoError(atxs.Add(tab.db, vPosAtx)) + tab.atxsdata.AddFromAtx(vPosAtx, false) nonce := types.VRFPostIndex(123) prevAtx := newInitialATXv1(t, tab.goldenATXID, func(atx *wire.ActivationTxV1) { @@ -782,6 +794,7 @@ func TestBuilder_PublishActivationTx_PrevATXWithoutPrevATX(t *testing.T) { prevAtx.Sign(sig) vPrevAtx := toAtx(t, prevAtx) r.NoError(atxs.Add(tab.db, vPrevAtx)) + tab.atxsdata.AddFromAtx(vPrevAtx, false) // Act tab.msync.EXPECT().RegisterForATXSynced().DoAndReturn(closedChan).AnyTimes() @@ -862,6 +875,7 @@ func TestBuilder_PublishActivationTx_TargetsEpochBasedOnPosAtx(t *testing.T) { posAtx := newInitialATXv1(t, tab.goldenATXID) posAtx.Sign(otherSigner) r.NoError(atxs.Add(tab.db, toAtx(t, posAtx))) + tab.atxsdata.AddFromAtx(toAtx(t, posAtx), false) // Act & Assert tab.msync.EXPECT().RegisterForATXSynced().DoAndReturn(closedChan).AnyTimes() @@ -955,6 +969,7 @@ func TestBuilder_PublishActivationTx_FailsWhenNIPostBuilderFails(t *testing.T) { prevAtx := newInitialATXv1(t, tab.goldenATXID) prevAtx.Sign(sig) require.NoError(t, atxs.Add(tab.db, toAtx(t, prevAtx))) + tab.atxsdata.AddFromAtx(toAtx(t, prevAtx), false) tab.mclock.EXPECT().CurrentLayer().Return(posEpoch.FirstLayer()).AnyTimes() tab.mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( @@ -1010,6 +1025,7 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { prevAtx := newInitialATXv1(t, tab.goldenATXID) prevAtx.Sign(sig) require.NoError(t, atxs.Add(tab.db, toAtx(t, prevAtx))) + tab.atxsdata.AddFromAtx(toAtx(t, prevAtx), false) publishEpoch := prevAtx.PublishEpoch + 1 currLayer := prevAtx.PublishEpoch.FirstLayer() @@ -1360,6 +1376,7 @@ func TestGetPositioningAtxPicksAtxWithValidChain(t *testing.T) { vInvalidAtx.TickCount = 100 require.NoError(t, err) require.NoError(t, atxs.Add(tab.db, vInvalidAtx)) + tab.atxsdata.AddFromAtx(vInvalidAtx, false) // Valid chain with lower height sigValid, err := signing.NewEdSigner() @@ -1369,6 +1386,7 @@ func TestGetPositioningAtxPicksAtxWithValidChain(t *testing.T) { validAtx.Sign(sigValid) vValidAtx := toAtx(t, validAtx) require.NoError(t, atxs.Add(tab.db, vValidAtx)) + tab.atxsdata.AddFromAtx(vValidAtx, false) tab.mValidator.EXPECT(). VerifyChain(gomock.Any(), invalidAtx.ID(), tab.goldenATXID, gomock.Any()). @@ -1376,12 +1394,12 @@ func TestGetPositioningAtxPicksAtxWithValidChain(t *testing.T) { tab.mValidator.EXPECT(). VerifyChain(gomock.Any(), validAtx.ID(), tab.goldenATXID, gomock.Any()) - posAtxID, err := tab.getPositioningAtx(context.Background(), sig.NodeID(), 77) + posAtxID, err := tab.getPositioningAtx(context.Background(), sig.NodeID(), 77, nil) require.NoError(t, err) require.Equal(t, posAtxID, vValidAtx.ID()) // should use the cached positioning ATX when asked for the same publish epoch - posAtxID, err = tab.getPositioningAtx(context.Background(), sig.NodeID(), 77) + posAtxID, err = tab.getPositioningAtx(context.Background(), sig.NodeID(), 77, nil) require.NoError(t, err) require.Equal(t, posAtxID, vValidAtx.ID()) @@ -1392,21 +1410,88 @@ func TestGetPositioningAtxPicksAtxWithValidChain(t *testing.T) { tab.mValidator.EXPECT(). VerifyChain(gomock.Any(), validAtx.ID(), tab.goldenATXID, gomock.Any()) - posAtxID, err = tab.getPositioningAtx(context.Background(), sig.NodeID(), 99) + posAtxID, err = tab.getPositioningAtx(context.Background(), sig.NodeID(), 99, nil) require.NoError(t, err) require.Equal(t, posAtxID, vValidAtx.ID()) } -func TestGetPositioningAtxDbFailed(t *testing.T) { - tab := newTestBuilder(t, 1) - sig := maps.Values(tab.signers)[0] +func TestGetPositioningAtx(t *testing.T) { + t.Parallel() + t.Run("db failed", func(t *testing.T) { + t.Parallel() + tab := newTestBuilder(t, 1) - db := sqlmocks.NewMockExecutor(gomock.NewController(t)) - tab.Builder.db = db - expected := errors.New("db error") - db.EXPECT().Exec(gomock.Any(), gomock.Any(), gomock.Any()).Return(0, expected) + db := sqlmocks.NewMockExecutor(gomock.NewController(t)) + tab.Builder.db = db + expected := errors.New("db error") + db.EXPECT().Exec(gomock.Any(), gomock.Any(), gomock.Any()).Return(0, expected) - none, err := tab.getPositioningAtx(context.Background(), sig.NodeID(), 99) - require.ErrorIs(t, err, expected) - require.Equal(t, types.ATXID{}, none) + none, err := tab.getPositioningAtx(context.Background(), types.EmptyNodeID, 99, nil) + require.ErrorIs(t, err, expected) + require.Equal(t, types.ATXID{}, none) + }) + t.Run("picks golden if no ATXs", func(t *testing.T) { + tab := newTestBuilder(t, 1) + atx, err := tab.getPositioningAtx(context.Background(), types.EmptyNodeID, 99, nil) + require.NoError(t, err) + require.Equal(t, tab.goldenATXID, atx) + }) + t.Run("prefers own previous to golden", func(t *testing.T) { + prev := &types.ActivationTx{} + prev.SetID(types.RandomATXID()) + tab := newTestBuilder(t, 1) + atx, err := tab.getPositioningAtx(context.Background(), types.EmptyNodeID, 99, prev) + require.NoError(t, err) + require.Equal(t, prev.ID(), atx) + }) + t.Run("prefers own previous when it has GTE ticks", func(t *testing.T) { + tab := newTestBuilder(t, 1) + + atxInDb := &types.ActivationTx{TickCount: 10} + atxInDb.SetID(types.RandomATXID()) + require.NoError(t, atxs.Add(tab.db, atxInDb)) + tab.atxsdata.AddFromAtx(atxInDb, false) + + prev := &types.ActivationTx{TickCount: 100} + prev.SetID(types.RandomATXID()) + + tab.mValidator.EXPECT().VerifyChain(gomock.Any(), atxInDb.ID(), tab.goldenATXID, gomock.Any()) + found, err := tab.searchPositioningAtx(context.Background(), types.EmptyNodeID, 99) + require.NoError(t, err) + require.Equal(t, atxInDb.ID(), found) + + // prev.Height > found.Height + selected, err := tab.getPositioningAtx(context.Background(), types.EmptyNodeID, 99, prev) + require.NoError(t, err) + require.Equal(t, prev.ID(), selected) + + // prev.Height == found.Height + prev.TickCount = atxInDb.TickCount + selected, err = tab.getPositioningAtx(context.Background(), types.EmptyNodeID, 99, prev) + require.NoError(t, err) + require.Equal(t, prev.ID(), selected) + }) +} + +func TestFindFullyValidHighTickAtx(t *testing.T) { + t.Parallel() + golden := types.RandomATXID() + + t.Run("skips malicious ATXs", func(t *testing.T) { + data := atxsdata.New() + atxMal := &types.ActivationTx{TickCount: 100, SmesherID: types.RandomNodeID()} + atxMal.SetID(types.RandomATXID()) + data.AddFromAtx(atxMal, true) + + atxLower := &types.ActivationTx{TickCount: 10, SmesherID: types.RandomNodeID()} + atxLower.SetID(types.RandomATXID()) + data.AddFromAtx(atxLower, false) + + mValidator := NewMocknipostValidator(gomock.NewController(t)) + mValidator.EXPECT().VerifyChain(gomock.Any(), atxLower.ID(), golden, gomock.Any()) + + found, err := findFullyValidHighTickAtx(context.Background(), data, 0, golden, mValidator, zaptest.NewLogger(t)) + require.NoError(t, err) + require.Equal(t, atxLower.ID(), found) + }) } diff --git a/activation/e2e/activation_test.go b/activation/e2e/activation_test.go index 7e6c4a84a23..352516517f0 100644 --- a/activation/e2e/activation_test.go +++ b/activation/e2e/activation_test.go @@ -3,6 +3,7 @@ package activation_test import ( "context" "sync" + "sync/atomic" "testing" "time" @@ -10,20 +11,22 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "go.uber.org/zap" "go.uber.org/zap/zaptest" "golang.org/x/sync/errgroup" "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/api/grpcserver" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql" "github.com/spacemeshos/go-spacemesh/timesync" ) @@ -31,7 +34,10 @@ import ( func Test_BuilderWithMultipleClients(t *testing.T) { ctrl := gomock.NewController(t) - numSigners := 3 + const numEpochs = 3 + const numSigners = 3 + const totalAtxs = numEpochs * numSigners + signers := make(map[types.NodeID]*signing.EdSigner, numSigners) for range numSigners { sig, err := signing.NewEdSigner() @@ -44,7 +50,6 @@ func Test_BuilderWithMultipleClients(t *testing.T) { goldenATX := types.ATXID{2, 3, 4} cfg := activation.DefaultPostConfig() db := sql.InMemory() - cdb := datastore.NewCachedDB(db, log.NewFromLog(logger)) syncer := activation.NewMocksyncer(ctrl) syncer.EXPECT().RegisterForATXSynced().DoAndReturn(func() <-chan struct{} { @@ -71,7 +76,7 @@ func Test_BuilderWithMultipleClients(t *testing.T) { i += 1 eg.Go(func() error { validator := activation.NewMocknipostValidator(ctrl) - mgr, err := activation.NewPostSetupManager(cfg, logger, cdb, goldenATX, syncer, validator) + mgr, err := activation.NewPostSetupManager(cfg, logger, db, atxsdata.New(), goldenATX, syncer, validator) require.NoError(t, err) initPost(t, mgr, opts, sig.NodeID()) @@ -123,7 +128,10 @@ func Test_BuilderWithMultipleClients(t *testing.T) { localDB, poetDb, svc, - []types.PoetServer{{Pubkey: types.NewBase64Enc([]byte("foobar")), Address: poetProver.RestURL().String()}}, + []types.PoetServer{{ + Pubkey: types.NewBase64Enc(poetProver.Service.PublicKey()), + Address: poetProver.RestURL().String(), + }}, logger.Named("nipostBuilder"), poetCfg, clock, @@ -136,8 +144,10 @@ func Test_BuilderWithMultipleClients(t *testing.T) { RegossipInterval: 0, } + data := atxsdata.New() + var atxsPublished atomic.Uint32 var atxMtx sync.Mutex - atxs := make(map[types.NodeID]wire.ActivationTxV1) + gotAtxs := make(map[types.NodeID][]wire.ActivationTxV1) endChan := make(chan struct{}) mpub := mocks.NewMockPublisher(ctrl) mpub.EXPECT().Publish(gomock.Any(), pubsub.AtxProtocol, gomock.Any()).DoAndReturn( @@ -147,21 +157,31 @@ func Test_BuilderWithMultipleClients(t *testing.T) { var gotAtx wire.ActivationTxV1 codec.MustDecode(got, &gotAtx) - atxs[gotAtx.SmesherID] = gotAtx - if len(atxs) == numSigners { + gotAtxs[gotAtx.SmesherID] = append(gotAtxs[gotAtx.SmesherID], gotAtx) + atx := wire.ActivationTxFromWireV1(&gotAtx) + if gotAtx.VRFNonce == nil { + atx.VRFNonce, err = atxs.NonceByID(db, gotAtx.PrevATXID) + require.NoError(t, err) + } + logger.Debug("persisting ATX", zap.Inline(atx)) + require.NoError(t, atxs.Add(db, atx)) + data.AddFromAtx(atx, false) + + if atxsPublished.Add(1) == totalAtxs { close(endChan) } return nil }, - ).Times(numSigners) + ).Times(totalAtxs) verifier, err := activation.NewPostVerifier(cfg, logger.Named("verifier")) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, verifier.Close()) }) - v := activation.NewValidator(nil, poetDb, cfg, opts.Scrypt, verifier) + v := activation.NewValidator(db, poetDb, cfg, opts.Scrypt, verifier) tab := activation.NewBuilder( conf, - cdb, + db, + data, localDB, mpub, nb, @@ -179,7 +199,13 @@ func Test_BuilderWithMultipleClients(t *testing.T) { // initial proof postStates.EXPECT().Set(sig.NodeID(), types.PostStateProving), postStates.EXPECT().Set(sig.NodeID(), types.PostStateIdle), - // post proof + // post proof - 1st epoch + postStates.EXPECT().Set(sig.NodeID(), types.PostStateProving), + postStates.EXPECT().Set(sig.NodeID(), types.PostStateIdle), + // 2nd epoch + postStates.EXPECT().Set(sig.NodeID(), types.PostStateProving), + postStates.EXPECT().Set(sig.NodeID(), types.PostStateIdle), + // 3rd epoch postStates.EXPECT().Set(sig.NodeID(), types.PostStateProving), postStates.EXPECT().Set(sig.NodeID(), types.PostStateIdle), ) @@ -191,34 +217,44 @@ func Test_BuilderWithMultipleClients(t *testing.T) { require.NoError(t, tab.StopSmeshing(false)) for _, sig := range signers { - atx := atxs[sig.NodeID()] - - _, err = v.NIPost( - context.Background(), - sig.NodeID(), - *atx.CommitmentATXID, - wire.NiPostFromWireV1(atx.NIPost), - atx.NIPostChallengeV1.Hash(), - atx.NumUnits, - ) - require.NoError(t, err) + var commitment types.ATXID + var previous types.ATXID - require.NotNil(t, atx.VRFNonce) - err := v.VRFNonce( - sig.NodeID(), - *atx.CommitmentATXID, - uint64(*atx.VRFNonce), - atx.NIPost.PostMetadata.LabelsPerUnit, - atx.NumUnits, - ) - require.NoError(t, err) + for seq, atx := range gotAtxs[sig.NodeID()] { + logger.Debug("checking ATX", zap.Inline(&atx), zap.Uint64("seq", uint64(seq))) + if seq == 0 { + commitment = *atx.CommitmentATXID + require.Equal(t, sig.NodeID(), *atx.NodeID) + require.Equal(t, goldenATX, atx.PositioningATXID) + require.NotNil(t, atx.VRFNonce) + err := v.VRFNonce( + sig.NodeID(), + commitment, + uint64(*atx.VRFNonce), + atx.NIPost.PostMetadata.LabelsPerUnit, + atx.NumUnits, + ) + require.NoError(t, err) + } else { + require.Nil(t, atx.VRFNonce) + require.Equal(t, previous, atx.PositioningATXID) + } + _, err = v.NIPost( + context.Background(), + sig.NodeID(), + commitment, + wire.NiPostFromWireV1(atx.NIPost), + atx.NIPostChallengeV1.Hash(), + atx.NumUnits, + ) + require.NoError(t, err) - require.Equal(t, postGenesisEpoch, atx.PublishEpoch+1) - require.Equal(t, types.EmptyATXID, atx.PrevATXID) - require.Equal(t, goldenATX, atx.PositioningATXID) - require.Equal(t, uint64(0), atx.Sequence) + require.Equal(t, previous, atx.PrevATXID) + require.Equal(t, postGenesisEpoch.Add(uint32(seq)), atx.PublishEpoch+1) + require.Equal(t, uint64(seq), atx.Sequence) + require.Equal(t, types.Address{}, atx.Coinbase) - require.Equal(t, types.Address{}, atx.Coinbase) - require.Equal(t, sig.NodeID(), *atx.NodeID) + previous = atx.ID() + } } } diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 872a88fc0e6..78f7adf8093 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -19,6 +19,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/api/grpcserver" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/log" @@ -129,7 +130,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { }) validator := activation.NewMocknipostValidator(ctrl) - mgr, err := activation.NewPostSetupManager(cfg, logger, cdb, goldenATX, syncer, validator) + mgr, err := activation.NewPostSetupManager(cfg, logger, cdb, atxsdata.New(), goldenATX, syncer, validator) require.NoError(t, err) opts := activation.DefaultPostSetupOpts() @@ -263,7 +264,6 @@ func TestNewNIPostBuilderNotInitialized(t *testing.T) { goldenATX := types.ATXID{2, 3, 4} cfg := activation.DefaultPostConfig() db := sql.InMemory() - cdb := datastore.NewCachedDB(db, log.NewFromLog(logger)) syncer := activation.NewMocksyncer(ctrl) syncer.EXPECT().RegisterForATXSynced().AnyTimes().DoAndReturn(func() <-chan struct{} { @@ -273,7 +273,7 @@ func TestNewNIPostBuilderNotInitialized(t *testing.T) { }) validator := activation.NewMocknipostValidator(ctrl) - mgr, err := activation.NewPostSetupManager(cfg, logger, cdb, goldenATX, syncer, validator) + mgr, err := activation.NewPostSetupManager(cfg, logger, db, atxsdata.New(), goldenATX, syncer, validator) require.NoError(t, err) // ensure that genesis aligns with layer timings @@ -368,7 +368,6 @@ func Test_NIPostBuilderWithMultipleClients(t *testing.T) { goldenATX := types.ATXID{2, 3, 4} cfg := activation.DefaultPostConfig() db := sql.InMemory() - cdb := datastore.NewCachedDB(db, log.NewFromLog(logger)) syncer := activation.NewMocksyncer(ctrl) syncer.EXPECT().RegisterForATXSynced().AnyTimes().DoAndReturn(func() <-chan struct{} { @@ -391,7 +390,7 @@ func Test_NIPostBuilderWithMultipleClients(t *testing.T) { for _, sig := range signers { opts := opts eg.Go(func() error { - mgr, err := activation.NewPostSetupManager(cfg, logger, cdb, goldenATX, syncer, validator) + mgr, err := activation.NewPostSetupManager(cfg, logger, db, atxsdata.New(), goldenATX, syncer, validator) require.NoError(t, err) opts.DataDir = t.TempDir() diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index dbfafe3adf3..95524899490 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -14,8 +14,8 @@ import ( "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/api/grpcserver" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" @@ -31,7 +31,7 @@ func TestValidator_Validate(t *testing.T) { logger := zaptest.NewLogger(t) goldenATX := types.ATXID{2, 3, 4} cfg := activation.DefaultPostConfig() - cdb := datastore.NewCachedDB(sql.InMemory(), log.NewFromLog(logger)) + db := sql.InMemory() validator := activation.NewMocknipostValidator(gomock.NewController(t)) syncer := activation.NewMocksyncer(gomock.NewController(t)) @@ -41,7 +41,7 @@ func TestValidator_Validate(t *testing.T) { return synced }) - mgr, err := activation.NewPostSetupManager(cfg, logger, cdb, goldenATX, syncer, validator) + mgr, err := activation.NewPostSetupManager(cfg, logger, db, atxsdata.New(), goldenATX, syncer, validator) require.NoError(t, err) opts := activation.DefaultPostSetupOpts() @@ -109,7 +109,7 @@ func TestValidator_Validate(t *testing.T) { nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.NoError(t, err) - v := activation.NewValidator(cdb, poetDb, cfg, opts.Scrypt, verifier) + v := activation.NewValidator(db, poetDb, cfg, opts.Scrypt, verifier) _, err = v.NIPost(context.Background(), sig.NodeID(), goldenATX, nipost.NIPost, challenge, nipost.NumUnits) require.NoError(t, err) @@ -130,7 +130,7 @@ func TestValidator_Validate(t *testing.T) { newPostCfg := cfg newPostCfg.MinNumUnits = nipost.NumUnits + 1 - v = activation.NewValidator(cdb, poetDb, newPostCfg, opts.Scrypt, nil) + v = activation.NewValidator(db, poetDb, newPostCfg, opts.Scrypt, nil) _, err = v.NIPost(context.Background(), sig.NodeID(), goldenATX, nipost.NIPost, challenge, nipost.NumUnits) require.EqualError( t, @@ -140,7 +140,7 @@ func TestValidator_Validate(t *testing.T) { newPostCfg = cfg newPostCfg.MaxNumUnits = nipost.NumUnits - 1 - v = activation.NewValidator(cdb, poetDb, newPostCfg, opts.Scrypt, nil) + v = activation.NewValidator(db, poetDb, newPostCfg, opts.Scrypt, nil) _, err = v.NIPost(context.Background(), sig.NodeID(), goldenATX, nipost.NIPost, challenge, nipost.NumUnits) require.EqualError( t, @@ -150,7 +150,7 @@ func TestValidator_Validate(t *testing.T) { newPostCfg = cfg newPostCfg.LabelsPerUnit = nipost.PostMetadata.LabelsPerUnit + 1 - v = activation.NewValidator(cdb, poetDb, newPostCfg, opts.Scrypt, nil) + v = activation.NewValidator(db, poetDb, newPostCfg, opts.Scrypt, nil) _, err = v.NIPost(context.Background(), sig.NodeID(), goldenATX, nipost.NIPost, challenge, nipost.NumUnits) require.EqualError( t, diff --git a/activation/poet.go b/activation/poet.go index dd00bcb7a69..9a938cd3a61 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -358,10 +358,12 @@ func (c *PoetClient) Proof(ctx context.Context, roundID string) (*types.PoetProo defer c.gettingProof.Unlock() if members, ok := c.proofMembers[roundID]; ok { - if proof, err := c.db.ProofForRound(c.id, roundID); err == nil { + proof, err := c.db.ProofForRound(c.id, roundID) + if err == nil { c.logger.Debug("returning cached proof", zap.String("round_id", roundID)) return proof, members, nil } + c.logger.Warn("cached members found but proof not found in db", zap.String("round_id", roundID), zap.Error(err)) } proof, members, err := c.client.Proof(getProofsCtx, roundID) diff --git a/activation/post.go b/activation/post.go index 9b8cdce06b0..08f083592a4 100644 --- a/activation/post.go +++ b/activation/post.go @@ -12,8 +12,8 @@ import ( "github.com/spacemeshos/post/initialization" "go.uber.org/zap" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/metrics/public" "github.com/spacemeshos/go-spacemesh/sql" @@ -180,7 +180,8 @@ type PostSetupManager struct { cfg PostConfig logger *zap.Logger - db *datastore.CachedDB + db sql.Executor + atxsdata *atxsdata.Data goldenATXID types.ATXID validator nipostValidator @@ -207,7 +208,8 @@ func PostValidityDelay(delay time.Duration) PostSetupManagerOpt { func NewPostSetupManager( cfg PostConfig, logger *zap.Logger, - db *datastore.CachedDB, + db sql.Executor, + atxsdata *atxsdata.Data, goldenATXID types.ATXID, syncer syncer, validator nipostValidator, @@ -217,6 +219,7 @@ func NewPostSetupManager( cfg: cfg, logger: logger, db: db, + atxsdata: atxsdata, goldenATXID: goldenATXID, state: PostSetupStateNotStarted, syncer: syncer, @@ -397,10 +400,15 @@ func (mgr *PostSetupManager) findCommitmentAtx(ctx context.Context) (types.ATXID mgr.logger.Info("ATXs synced - selecting commitment ATX") } + latest, err := atxs.LatestEpoch(mgr.db) + if err != nil { + return types.EmptyATXID, fmt.Errorf("get latest epoch: %w", err) + } + atx, err := findFullyValidHighTickAtx( context.Background(), - mgr.db, - types.EmptyNodeID, + mgr.atxsdata, + latest, mgr.goldenATXID, mgr.validator, mgr.logger, @@ -408,7 +416,7 @@ func (mgr *PostSetupManager) findCommitmentAtx(ctx context.Context) (types.ATXID VerifyChainOpts.WithLogger(mgr.logger), ) switch { - case errors.Is(err, sql.ErrNotFound): + case errors.Is(err, ErrNotFound): mgr.logger.Info("using golden atx as commitment atx") return mgr.goldenATXID, nil case err != nil: diff --git a/activation/post_supervisor_test.go b/activation/post_supervisor_test.go index deacdb67c4d..f26021a3645 100644 --- a/activation/post_supervisor_test.go +++ b/activation/post_supervisor_test.go @@ -21,9 +21,8 @@ import ( "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/log/logtest" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" ) @@ -57,8 +56,9 @@ func newPostManager(t *testing.T, cfg PostConfig, opts PostSetupOpts) *PostSetup close(ch) return ch }) - cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(t)) - mgr, err := NewPostSetupManager(cfg, zaptest.NewLogger(t), cdb, types.RandomATXID(), syncer, validator) + db := sql.InMemory() + atxsdata := atxsdata.New() + mgr, err := NewPostSetupManager(cfg, zaptest.NewLogger(t), db, atxsdata, types.RandomATXID(), syncer, validator) require.NoError(t, err) return mgr } diff --git a/activation/post_test.go b/activation/post_test.go index fd64ee43bd5..b3a8e7c233c 100644 --- a/activation/post_test.go +++ b/activation/post_test.go @@ -13,6 +13,7 @@ import ( "go.uber.org/zap/zaptest" "golang.org/x/sync/errgroup" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/log/logtest" @@ -283,6 +284,7 @@ func TestPostSetupManager_findCommitmentAtx_UsesLatestAtx(t *testing.T) { atx.TickCount = 1 require.NoError(t, err) require.NoError(t, atxs.Add(mgr.db, atx)) + mgr.atxsdata.AddFromAtx(atx, false) commitmentAtx, err := mgr.findCommitmentAtx(context.Background()) require.NoError(t, err) @@ -364,7 +366,8 @@ func newTestPostManager(tb testing.TB) *testPostManager { syncer.EXPECT().RegisterForATXSynced().AnyTimes().Return(synced) cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(tb)) - mgr, err := NewPostSetupManager(DefaultPostConfig(), zaptest.NewLogger(tb), cdb, goldenATXID, syncer, validator) + logger := zaptest.NewLogger(tb) + mgr, err := NewPostSetupManager(DefaultPostConfig(), logger, cdb, atxsdata.New(), goldenATXID, syncer, validator) require.NoError(tb, err) return &testPostManager{ diff --git a/api/grpcserver/post_service_test.go b/api/grpcserver/post_service_test.go index 63c73b59dd5..f3fddd506b0 100644 --- a/api/grpcserver/post_service_test.go +++ b/api/grpcserver/post_service_test.go @@ -20,9 +20,8 @@ import ( "google.golang.org/grpc/status" "github.com/spacemeshos/go-spacemesh/activation" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/log/logtest" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" ) @@ -59,8 +58,9 @@ func launchPostSupervisor( close(ch) return ch }) - cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(tb)) - mgr, err := activation.NewPostSetupManager(postCfg, log.Named("post manager"), cdb, goldenATXID, syncer, validator) + db := sql.InMemory() + logger := log.Named("post manager") + mgr, err := activation.NewPostSetupManager(postCfg, logger, db, atxsdata.New(), goldenATXID, syncer, validator) require.NoError(tb, err) // start post supervisor @@ -102,8 +102,9 @@ func launchPostSupervisorTLS( close(ch) return ch }) - cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(tb)) - mgr, err := activation.NewPostSetupManager(postCfg, log.Named("post manager"), cdb, goldenATXID, syncer, validator) + db := sql.InMemory() + logger := log.Named("post supervisor") + mgr, err := activation.NewPostSetupManager(postCfg, logger, db, atxsdata.New(), goldenATXID, syncer, validator) require.NoError(tb, err) // start post supervisor diff --git a/atxsdata/data.go b/atxsdata/data.go index fb41bd3c2c0..f8eae4794c3 100644 --- a/atxsdata/data.go +++ b/atxsdata/data.go @@ -1,6 +1,7 @@ package atxsdata import ( + "slices" "sync" "sync/atomic" @@ -169,6 +170,16 @@ func (d *Data) Get(epoch types.EpochID, atx types.ATXID) *ATX { return data } +func (d *Data) Size(target types.EpochID) int { + d.mu.RLock() + defer d.mu.RUnlock() + ecache, exists := d.epochs[target] + if !exists { + return 0 + } + return len(ecache.index) +} + type lockGuard struct{} // AtxFilter is a function that filters atxs. @@ -204,6 +215,33 @@ func (d *Data) IterateInEpoch(epoch types.EpochID, fn func(types.ATXID, *ATX), f } } +func (d *Data) IterateHighTicksInEpoch(target types.EpochID, fn func(types.ATXID) bool) { + type candidate struct { + id types.ATXID + *ATX + } + candidates := make([]candidate, 0, d.Size(target)) + d.IterateInEpoch(target, func(id types.ATXID, atx *ATX) { + candidates = append(candidates, candidate{id: id, ATX: atx}) + }, NotMalicious) + + slices.SortFunc(candidates, func(a, b candidate) int { + switch { + case a.Height < b.Height: + return 1 + case a.Height > b.Height: + return -1 + } + return 0 + }) + + for _, c := range candidates { + if cont := fn(c.id); !cont { + return + } + } +} + func (d *Data) MissingInEpoch(epoch types.EpochID, atxs []types.ATXID) []types.ATXID { d.mu.RLock() defer d.mu.RUnlock() diff --git a/node/node.go b/node/node.go index 507b7c35d86..ae0db0d4c3e 100644 --- a/node/node.go +++ b/node/node.go @@ -985,7 +985,8 @@ func (app *App) initServices(ctx context.Context) error { postSetupMgr, err := activation.NewPostSetupManager( app.Config.POST, app.addLogger(PostLogger, lg).Zap(), - app.cachedDB, + app.db, + app.atxsdata, goldenATXID, newSyncer, app.validator, @@ -1020,7 +1021,8 @@ func (app *App) initServices(ctx context.Context) error { } atxBuilder := activation.NewBuilder( builderConfig, - app.cachedDB, + app.db, + app.atxsdata, app.localDB, app.host, nipostBuilder, diff --git a/node/node_test.go b/node/node_test.go index c192bf22b09..67cbf23c4d8 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -1102,7 +1102,8 @@ func TestAdminEvents_MultiSmesher(t *testing.T) { mgr, err := activation.NewPostSetupManager( cfg.POST, logger.Zap(), - app.cachedDB, + app.db, + app.atxsdata, types.ATXID(app.Config.Genesis.GoldenATX()), app.syncer, app.validator, diff --git a/systest/tests/distributed_post_verification_test.go b/systest/tests/distributed_post_verification_test.go index 5588609e5a0..615a4cc3b06 100644 --- a/systest/tests/distributed_post_verification_test.go +++ b/systest/tests/distributed_post_verification_test.go @@ -22,6 +22,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/api/grpcserver" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" @@ -115,6 +116,7 @@ func TestPostMalfeasanceProof(t *testing.T) { cfg.POST, logger.Named("post"), datastore.NewCachedDB(sql.InMemory(), log.NewNop()), + atxsdata.New(), cl.GoldenATX(), syncer, activation.NewMocknipostValidator(ctrl),