diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82aeb61df4..eb3d731d8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,7 +118,7 @@ jobs: - name: lint run: make lint-github-action - build-tools: + build: runs-on: ${{ matrix.os }} needs: filter-changes if: ${{ needs.filter-changes.outputs.nondocchanges == 'true' }} @@ -160,6 +160,7 @@ jobs: with: check-latest: true go-version: ${{ env.go-version }} + cache: ${{ runner.arch != 'arm64' }} - name: setup env run: make install - name: build merge-nodes @@ -168,52 +169,6 @@ jobs: run: make gen-p2p-identity - name: build bootstrapper run: make bootstrapper - - build: - runs-on: ${{ matrix.os }} - needs: filter-changes - if: ${{ needs.filter-changes.outputs.nondocchanges == 'true' }} - strategy: - fail-fast: true - matrix: - os: - - ubuntu-22.04 - - ubuntu-latest-arm-8-cores - - macos-13 - - [self-hosted, macOS, ARM64, go-spacemesh] - - windows-2022 - steps: - - name: Add OpenCL support - Ubuntu - if: ${{ matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-latest-arm-8-cores' }} - run: sudo apt-get update -q && sudo apt-get install -qy ocl-icd-opencl-dev libpocl2 - - name: disable Windows Defender - Windows - if: ${{ matrix.os == 'windows-2022' }} - run: | - Set-MpPreference -DisableRealtimeMonitoring $true - - name: Set new git config - Windows - if: ${{ matrix.os == 'windows-2022' }} - run: | - git config --global pack.window 1 - git config --global core.compression 0 - git config --global http.postBuffer 524288000 - - name: checkout - uses: actions/checkout@v4 - with: - ssh-key: ${{ secrets.GH_ACTION_PRIVATE_KEY }} - - uses: extractions/netrc@v2 - with: - machine: github.com - username: ${{ secrets.GH_ACTION_TOKEN_USER }} - password: ${{ secrets.GH_ACTION_TOKEN }} - if: vars.GOPRIVATE - - name: set up go - uses: actions/setup-go@v5 - with: - check-latest: true - go-version: ${{ env.go-version }} - cache: ${{ runner.arch != 'arm64' }} - - name: setup env - run: make install - name: build timeout-minutes: 5 run: make build @@ -307,7 +262,6 @@ jobs: - filter-changes - quicktests - lint - - build-tools - build - unittests runs-on: ubuntu-22.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index 04bc503248..462b887df9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -708,7 +708,7 @@ and permanent ineligibility for rewards. * [#5470](https://github.com/spacemeshos/go-spacemesh/pull/5470) Fixed a bug in event reporting where the node reports a disconnection from the PoST service as a "PoST failed" event. - Disconnections cannot be avoided completely and do not interrupt the PoST proofing process. As long as the PoST + Disconnections cannot be avoided completely and do not interrupt the PoST proving process. As long as the PoST service reconnects within a reasonable time, the node will continue to operate normally without reporting any errors via the event API. diff --git a/Makefile b/Makefile index c1853c7560..777c1d6007 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ clear-test-cache: .PHONY: clear-test-cache test: get-libs - @$(ULIMIT) CGO_LDFLAGS="$(CGO_TEST_LDFLAGS)" gotestsum -- -race -timeout 8m -p 1 $(UNIT_TESTS) + @$(ULIMIT) CGO_LDFLAGS="$(CGO_TEST_LDFLAGS)" gotestsum -- -race -timeout 8m $(UNIT_TESTS) .PHONY: test generate: get-libs @@ -145,7 +145,7 @@ lint-github-action: get-libs .PHONY: lint-github-action cover: get-libs - @$(ULIMIT) CGO_LDFLAGS="$(CGO_TEST_LDFLAGS)" go test -coverprofile=cover.out -timeout 0 -p 1 -coverpkg=./... $(UNIT_TESTS) + @$(ULIMIT) CGO_LDFLAGS="$(CGO_TEST_LDFLAGS)" go test -coverprofile=cover.out -timeout 30m -coverpkg=./... $(UNIT_TESTS) .PHONY: cover list-versions: diff --git a/activation/activation.go b/activation/activation.go index 1903d14a97..567332e36e 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -443,6 +443,7 @@ func (b *Builder) run(ctx context.Context, sig *signing.EdSigner) { b.logger.Warn("failed to publish atx", zap.Error(err)) + poetErr := &PoetSvcUnstableError{} switch { case errors.Is(err, ErrATXChallengeExpired): b.logger.Debug("retrying with new challenge after waiting for a layer") @@ -459,8 +460,11 @@ func (b *Builder) run(ctx context.Context, sig *signing.EdSigner) { return case <-b.layerClock.AwaitLayer(currentLayer.Add(1)): } - case errors.Is(err, ErrPoetServiceUnstable): - b.logger.Warn("retrying after poet retry interval", zap.Duration("interval", b.poetRetryInterval)) + case errors.As(err, &poetErr): + b.logger.Warn("retrying after poet retry interval", + zap.Duration("interval", b.poetRetryInterval), + zap.Error(poetErr.source), + ) select { case <-ctx.Done(): return diff --git a/activation/activation_errors.go b/activation/activation_errors.go index 601541e83c..63027b0c81 100644 --- a/activation/activation_errors.go +++ b/activation/activation_errors.go @@ -8,8 +8,6 @@ import ( var ( // ErrATXChallengeExpired is returned when atx missed its publication window and needs to be regenerated. ErrATXChallengeExpired = errors.New("builder: atx expired") - // ErrPoetServiceUnstable is returned when poet quality of service is low. - ErrPoetServiceUnstable = &PoetSvcUnstableError{} // ErrPoetProofNotReceived is returned when no poet proof was received. ErrPoetProofNotReceived = errors.New("builder: didn't receive any poet proof") ) @@ -28,8 +26,3 @@ func (e *PoetSvcUnstableError) Error() string { } func (e *PoetSvcUnstableError) Unwrap() error { return e.source } - -func (e *PoetSvcUnstableError) Is(target error) bool { - _, ok := target.(*PoetSvcUnstableError) - return ok -} diff --git a/activation/activation_test.go b/activation/activation_test.go index a3c696cb24..ba9acfa0b7 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -1076,7 +1076,7 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { tries++ t.Logf("try %d: %s", tries, now) if tries < expectedTries { - return nil, ErrPoetServiceUnstable + return nil, &PoetSvcUnstableError{} } close(builderConfirmation) return newNIPostWithPoet(t, []byte("66666")), nil diff --git a/activation/handler_v2.go b/activation/handler_v2.go index d68bfff844..c8bc78f02e 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -629,9 +629,7 @@ func (h *HandlerV2) syntacticallyValidateDeps( zap.Int("index", invalidIdx.Index), ) // TODO(mafa): finish proof - proof := &wire.ATXProof{ - ProofType: wire.InvalidPost, - } + var proof wire.Proof if err := h.malPublisher.Publish(ctx, id, proof); err != nil { return nil, fmt.Errorf("publishing malfeasance proof for invalid post: %w", err) } @@ -721,7 +719,7 @@ func (h *HandlerV2) checkDoubleMarry( // found an identity that is already married var blob sql.Blob - if _, err := atxs.LoadBlob(ctx, tx, mAtxID[:], &blob); err != nil { + if _, err := atxs.LoadBlob(ctx, tx, mAtxID.Bytes(), &blob); err != nil { return true, fmt.Errorf("get atx blob %s: %w", mAtxID.ShortString(), err) } mAtx := &wire.ActivationTxV2{} @@ -730,12 +728,7 @@ func (h *HandlerV2) checkDoubleMarry( if err != nil { return true, fmt.Errorf("creating double marry proof: %w", err) } - atxProof := &wire.ATXProof{ - Version: 0, - ProofType: wire.DoubleMarry, - Proof: codec.MustEncode(proof), - } - return true, h.malPublisher.Publish(ctx, m.id, atxProof) + return true, h.malPublisher.Publish(ctx, m.id, proof) } return false, nil } @@ -764,9 +757,7 @@ func (h *HandlerV2) checkDoublePost( zap.Uint32("epoch", atx.PublishEpoch.Uint32()), ) // TODO(mafa): finish proof - proof := &wire.ATXProof{ - ProofType: wire.DoublePublish, - } + var proof wire.Proof return true, h.malPublisher.Publish(ctx, id, proof) } return false, nil @@ -793,10 +784,7 @@ func (h *HandlerV2) checkDoubleMerge(ctx context.Context, tx *sql.Tx, watx *wire zap.Stringer("smesher_id", watx.SmesherID), ) - // TODO(mafa): finish proof - proof := &wire.ATXProof{ - ProofType: wire.DoubleMerge, - } + var proof wire.Proof return true, h.malPublisher.Publish(ctx, watx.SmesherID, proof) } diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index 62ff24377a..a2e260d317 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -17,7 +17,6 @@ import ( "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/datastore" "github.com/spacemeshos/go-spacemesh/fetch" @@ -1562,17 +1561,7 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { gomock.Any(), ). Return(verifying.ErrInvalidIndex{Index: 7}) - atxHandler.mMalPublish.EXPECT().Publish( - gomock.Any(), - sig.NodeID(), - gomock.Cond(func(data any) bool { - proof, ok := data.(*wire.ATXProof) - if !ok { - return false - } - return proof.ProofType == wire.InvalidPost - }), - ) + atxHandler.mMalPublish.EXPECT().Publish(gomock.Any(), sig.NodeID(), gomock.Any()) _, err := atxHandler.syntacticallyValidateDeps(context.Background(), atx) vErr := &verifying.ErrInvalidIndex{} require.ErrorAs(t, err, vErr) @@ -1716,18 +1705,20 @@ func Test_Marriages(t *testing.T) { } atx2.Sign(sig) atxHandler.expectAtxV2(atx2) - atxHandler.mMalPublish.EXPECT().Publish(gomock.Any(), sig.NodeID(), gomock.Any()). - DoAndReturn(func(ctx context.Context, id types.NodeID, proof *wire.ATXProof) error { - require.Equal(t, wire.ProofVersion(0), proof.Version) - require.Equal(t, wire.DoubleMarry, proof.ProofType) - doubleMarryProof := &wire.ProofDoubleMarry{} - codec.MustDecode(proof.Proof, doubleMarryProof) - - nodeID, err := doubleMarryProof.Valid(atxHandler.edVerifier) - require.NoError(t, err) - require.Equal(t, sig.NodeID(), nodeID) - return nil - }) + atxHandler.mMalPublish.EXPECT().Publish( + gomock.Any(), + sig.NodeID(), + gomock.Cond(func(data any) bool { + _, ok := data.(*wire.ProofDoubleMarry) + return ok + }), + ).DoAndReturn(func(ctx context.Context, id types.NodeID, proof wire.Proof) error { + malProof := proof.(*wire.ProofDoubleMarry) + nId, err := malProof.Valid(atxHandler.edVerifier) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nId) + return nil + }) err = atxHandler.processATX(context.Background(), "", atx2, time.Now()) require.NoError(t, err) diff --git a/activation/interface.go b/activation/interface.go index 1cd3b4609b..218dbbaccf 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -101,7 +101,7 @@ type syncer interface { // Additionally the publisher will only gossip proofs when the node is in sync, otherwise it will only store them. // and mark the associated identity as malfeasant. type atxMalfeasancePublisher interface { - Publish(ctx context.Context, id types.NodeID, proof *wire.ATXProof) error + Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error } type malfeasancePublisher interface { diff --git a/activation/malfeasance2_handler.go b/activation/malfeasance2_handler.go index ae55d82911..1a8ecbe413 100644 --- a/activation/malfeasance2_handler.go +++ b/activation/malfeasance2_handler.go @@ -50,20 +50,20 @@ func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) ([]ty var proof wire.Proof switch decoded.ProofType { case wire.DoublePublish: - var p wire.ProofDoublePublish - if err := codec.Decode(decoded.Proof, &p); err != nil { + p := &wire.ProofDoublePublish{} + if err := codec.Decode(decoded.Proof, p); err != nil { return nil, fmt.Errorf("decoding ATX double publish proof: %w", err) } proof = p case wire.DoubleMarry: - var p wire.ProofDoubleMarry - if err := codec.Decode(decoded.Proof, &p); err != nil { + p := &wire.ProofDoubleMarry{} + if err := codec.Decode(decoded.Proof, p); err != nil { return nil, fmt.Errorf("decoding ATX double marry proof: %w", err) } proof = p case wire.InvalidPost: - var p wire.ProofInvalidPost - if err := codec.Decode(decoded.Proof, &p); err != nil { + p := &wire.ProofInvalidPost{} + if err := codec.Decode(decoded.Proof, p); err != nil { return nil, fmt.Errorf("decoding ATX invalid post proof: %w", err) } default: diff --git a/activation/malfeasance2_publisher.go b/activation/malfeasance2_publisher.go index 0aa5d55674..b71d18a289 100644 --- a/activation/malfeasance2_publisher.go +++ b/activation/malfeasance2_publisher.go @@ -10,7 +10,7 @@ import ( // ATXMalfeasancePublisher is the publisher for ATX proofs. type ATXMalfeasancePublisher struct{} -func (p *ATXMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof *wire.ATXProof) error { +func (p *ATXMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { // TODO(mafa): implement me return nil } diff --git a/activation/mocks.go b/activation/mocks.go index dd1c6f637d..38a1a47206 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1109,7 +1109,7 @@ func (m *MockmalfeasancePublisher) EXPECT() *MockmalfeasancePublisherMockRecorde } // Publish mocks base method. -func (m *MockmalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof *wire.ATXProof) error { +func (m *MockmalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Publish", ctx, id, proof) ret0, _ := ret[0].(error) @@ -1135,13 +1135,13 @@ func (c *MockmalfeasancePublisherPublishCall) Return(arg0 error) *Mockmalfeasanc } // Do rewrite *gomock.Call.Do -func (c *MockmalfeasancePublisherPublishCall) Do(f func(context.Context, types.NodeID, *wire.ATXProof) error) *MockmalfeasancePublisherPublishCall { +func (c *MockmalfeasancePublisherPublishCall) Do(f func(context.Context, types.NodeID, wire.Proof) error) *MockmalfeasancePublisherPublishCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockmalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, types.NodeID, *wire.ATXProof) error) *MockmalfeasancePublisherPublishCall { +func (c *MockmalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, types.NodeID, wire.Proof) error) *MockmalfeasancePublisherPublishCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/nipost_test.go b/activation/nipost_test.go index a351345d9f..95c3cf8a60 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -214,7 +214,7 @@ func Test_NIPost_PostClientHandling(t *testing.T) { }) t.Run("connect, disconnect, then cancel before reconnect", func(t *testing.T) { - // post client connects, starts post, disconnects in between and proofing is canceled before reconnection + // post client connects, starts post, disconnects in between and proving is canceled before reconnection sig, err := signing.NewEdSigner() require.NoError(t, err) @@ -713,9 +713,14 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { ) require.NoError(t, err) - nipst, err := nb.BuildNIPost(context.Background(), sig, challenge, - &types.NIPostChallenge{PublishEpoch: postGenesisEpoch + 2}) - require.ErrorIs(t, err, ErrPoetServiceUnstable) + nipst, err := nb.BuildNIPost( + context.Background(), + sig, + challenge, + &types.NIPostChallenge{PublishEpoch: postGenesisEpoch + 2}, + ) + poetErr := &PoetSvcUnstableError{} + require.ErrorAs(t, err, &poetErr) require.Nil(t, nipst) }) t.Run("Submit hangs", func(t *testing.T) { @@ -750,9 +755,14 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { ) require.NoError(t, err) - nipst, err := nb.BuildNIPost(context.Background(), sig, challenge, - &types.NIPostChallenge{PublishEpoch: postGenesisEpoch + 2}) - require.ErrorIs(t, err, ErrPoetServiceUnstable) + nipst, err := nb.BuildNIPost( + context.Background(), + sig, + challenge, + &types.NIPostChallenge{PublishEpoch: postGenesisEpoch + 2}, + ) + poetErr := &PoetSvcUnstableError{} + require.ErrorAs(t, err, &poetErr) require.Nil(t, nipst) }) t.Run("GetProof fails", func(t *testing.T) { diff --git a/activation/wire/malfeasance.go b/activation/wire/malfeasance.go index cf1a1f926d..08d226dfea 100644 --- a/activation/wire/malfeasance.go +++ b/activation/wire/malfeasance.go @@ -1,6 +1,8 @@ package wire import ( + "github.com/spacemeshos/go-scale" + "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/signing" ) @@ -41,10 +43,15 @@ const ( type ProofType byte const ( - DoublePublish ProofType = iota + 1 - DoubleMarry - DoubleMerge - InvalidPost + // TODO(mafa): legacy types for future migration to new malfeasance proofs. + LegacyDoublePublish ProofType = 0x00 + LegacyInvalidPost ProofType = 0x01 + LegacyInvalidPrevATX ProofType = 0x02 + + DoublePublish ProofType = 0x10 + DoubleMarry ProofType = 0x11 + DoubleMerge ProofType = 0x12 + InvalidPost ProofType = 0x13 ) // ProofVersion is an identifier for the version of the proof that is encoded in the ATXProof. @@ -60,7 +67,9 @@ type ATXProof struct { } // Proof is an interface for all types of proofs that can be provided in an ATXProof. -// Generally the proof should be able to validate itself. +// Generally the proof should be able to validate itself and be scale encoded. type Proof interface { + scale.Encodable + Valid(edVerifier *signing.EdVerifier) (types.NodeID, error) } diff --git a/activation/wire/malfeasance_double_marry.go b/activation/wire/malfeasance_double_marry.go index eb02f8d6c9..ea946b29ea 100644 --- a/activation/wire/malfeasance_double_marry.go +++ b/activation/wire/malfeasance_double_marry.go @@ -17,14 +17,17 @@ import ( // ProofDoubleMarry is a proof that two distinct ATXs contain a marriage certificate signed by the same identity. // -// We are proofing the following: +// We are proving the following: // 1. The ATXs have different IDs. // 2. Both ATXs have a valid signature. // 3. Both ATXs contain a marriage certificate created by the same identity. // 4. Both marriage certificates have valid signatures. // -// HINT: this only works if the identity that publishes the ATX with the certificates marries itself. +// HINT: this works if the identity that publishes the marriage ATX marries themselves. type ProofDoubleMarry struct { + // NodeID is the node ID that married twice. + NodeID types.NodeID + Proofs [2]MarryProof } @@ -46,6 +49,7 @@ func NewDoubleMarryProof(db sql.Executor, atx1, atx2 *ActivationTxV2, nodeID typ } proof := &ProofDoubleMarry{ + NodeID: nodeID, Proofs: [2]MarryProof{proof1, proof2}, } return proof, nil @@ -77,8 +81,7 @@ func createMarryProof(db sql.Executor, atx *ActivationTxV2, nodeID types.NodeID) } proof := MarryProof{ - ATXID: atx.ID(), - NodeID: nodeID, + ATXID: atx.ID(), MarriageRoot: types.Hash32(atx.Marriages.Root()), MarriageProof: marriageProof, @@ -134,24 +137,19 @@ func (p ProofDoubleMarry) Valid(edVerifier *signing.EdVerifier) (types.NodeID, e if p.Proofs[0].ATXID == p.Proofs[1].ATXID { return types.EmptyNodeID, errors.New("proofs have the same ATX ID") } - if p.Proofs[0].NodeID != p.Proofs[1].NodeID { - return types.EmptyNodeID, errors.New("proofs have different node IDs") - } - if err := p.Proofs[0].Valid(edVerifier); err != nil { + if err := p.Proofs[0].Valid(edVerifier, p.NodeID); err != nil { return types.EmptyNodeID, fmt.Errorf("proof 1 is invalid: %w", err) } - if err := p.Proofs[1].Valid(edVerifier); err != nil { + if err := p.Proofs[1].Valid(edVerifier, p.NodeID); err != nil { return types.EmptyNodeID, fmt.Errorf("proof 2 is invalid: %w", err) } - return p.Proofs[0].NodeID, nil + return p.NodeID, nil } type MarryProof struct { // ATXID is the ID of the ATX being proven. ATXID types.ATXID - // NodeID is the node ID that married twice. - NodeID types.NodeID // MarriageRoot and its proof that it is contained in the ATX. MarriageRoot types.Hash32 @@ -170,12 +168,12 @@ type MarryProof struct { Signature types.EdSignature } -func (p MarryProof) Valid(edVerifier *signing.EdVerifier) error { +func (p MarryProof) Valid(edVerifier *signing.EdVerifier, nodeID types.NodeID) error { if !edVerifier.Verify(signing.ATX, p.SmesherID, p.ATXID.Bytes(), p.Signature) { return errors.New("invalid ATX signature") } - if !edVerifier.Verify(signing.MARRIAGE, p.NodeID, p.SmesherID.Bytes(), p.CertificateSignature) { + if !edVerifier.Verify(signing.MARRIAGE, nodeID, p.SmesherID.Bytes(), p.CertificateSignature) { return errors.New("invalid certificate signature") } diff --git a/activation/wire/malfeasance_double_marry_scale.go b/activation/wire/malfeasance_double_marry_scale.go index 7f5caa171d..03f70c95fa 100644 --- a/activation/wire/malfeasance_double_marry_scale.go +++ b/activation/wire/malfeasance_double_marry_scale.go @@ -9,6 +9,13 @@ import ( ) func (t *ProofDoubleMarry) EncodeScale(enc *scale.Encoder) (total int, err error) { + { + n, err := scale.EncodeByteArray(enc, t.NodeID[:]) + if err != nil { + return total, err + } + total += n + } { n, err := scale.EncodeStructArray(enc, t.Proofs[:]) if err != nil { @@ -21,25 +28,25 @@ func (t *ProofDoubleMarry) EncodeScale(enc *scale.Encoder) (total int, err error func (t *ProofDoubleMarry) DecodeScale(dec *scale.Decoder) (total int, err error) { { - n, err := scale.DecodeStructArray(dec, t.Proofs[:]) + n, err := scale.DecodeByteArray(dec, t.NodeID[:]) if err != nil { return total, err } total += n } - return total, nil -} - -func (t *MarryProof) EncodeScale(enc *scale.Encoder) (total int, err error) { { - n, err := scale.EncodeByteArray(enc, t.ATXID[:]) + n, err := scale.DecodeStructArray(dec, t.Proofs[:]) if err != nil { return total, err } total += n } + return total, nil +} + +func (t *MarryProof) EncodeScale(enc *scale.Encoder) (total int, err error) { { - n, err := scale.EncodeByteArray(enc, t.NodeID[:]) + n, err := scale.EncodeByteArray(enc, t.ATXID[:]) if err != nil { return total, err } @@ -112,13 +119,6 @@ func (t *MarryProof) DecodeScale(dec *scale.Decoder) (total int, err error) { } total += n } - { - n, err := scale.DecodeByteArray(dec, t.NodeID[:]) - if err != nil { - return total, err - } - total += n - } { n, err := scale.DecodeByteArray(dec, t.MarriageRoot[:]) if err != nil { diff --git a/activation/wire/malfeasance_double_marry_test.go b/activation/wire/malfeasance_double_marry_test.go index ebc3b7630e..351aa265ed 100644 --- a/activation/wire/malfeasance_double_marry_test.go +++ b/activation/wire/malfeasance_double_marry_test.go @@ -28,14 +28,14 @@ func Test_DoubleMarryProof(t *testing.T) { require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) atx1 := newActivationTxV2( - WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) atx2 := newActivationTxV2( - WithMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), - WithMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), + withMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), + withMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), ) atx2.Sign(otherSig) @@ -53,12 +53,12 @@ func Test_DoubleMarryProof(t *testing.T) { db := sql.InMemory() atx1 := newActivationTxV2( - WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), ) atx1.Sign(sig) atx2 := newActivationTxV2( - WithMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), + withMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), ) atx2.Sign(otherSig) @@ -73,25 +73,6 @@ func Test_DoubleMarryProof(t *testing.T) { "proof for atx2: does not contain a marriage certificate signed by %s", sig.NodeID().ShortString(), )) require.Nil(t, proof) - - // manually construct an invalid proof - proof = &ProofDoubleMarry{ - Proofs: [2]MarryProof{ - { - ATXID: atx1.ID(), - NodeID: sig.NodeID(), - }, - { - ATXID: atx2.ID(), - NodeID: otherSig.NodeID(), - }, - }, - } - - verifier := signing.NewEdVerifier() - id, err := proof.Valid(verifier) - require.ErrorContains(t, err, "proofs have different node IDs") - require.Equal(t, types.EmptyNodeID, id) }) t.Run("same ATX ID", func(t *testing.T) { @@ -129,14 +110,14 @@ func Test_DoubleMarryProof(t *testing.T) { require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) atx1 := newActivationTxV2( - WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) atx2 := newActivationTxV2( - WithMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), - WithMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), + withMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), + withMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), ) atx2.Sign(otherSig) @@ -147,6 +128,7 @@ func Test_DoubleMarryProof(t *testing.T) { require.NoError(t, err) proof := &ProofDoubleMarry{ + NodeID: otherSig.NodeID(), Proofs: [2]MarryProof{ proof1, proof2, }, @@ -175,14 +157,14 @@ func Test_DoubleMarryProof(t *testing.T) { require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) atx1 := newActivationTxV2( - WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) atx2 := newActivationTxV2( - WithMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), - WithMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), + withMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), + withMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), ) atx2.Sign(otherSig) @@ -193,6 +175,7 @@ func Test_DoubleMarryProof(t *testing.T) { require.NoError(t, err) proof := &ProofDoubleMarry{ + NodeID: otherSig.NodeID(), Proofs: [2]MarryProof{ proof1, proof2, }, @@ -221,14 +204,14 @@ func Test_DoubleMarryProof(t *testing.T) { require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) atx1 := newActivationTxV2( - WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) atx2 := newActivationTxV2( - WithMarriageCertificate(otherSig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(sig, atx1.ID(), sig.NodeID()), + withMarriageCertificate(otherSig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(sig, atx1.ID(), sig.NodeID()), ) atx2.Sign(otherSig) @@ -257,14 +240,14 @@ func Test_DoubleMarryProof(t *testing.T) { require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) atx1 := newActivationTxV2( - WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) atx2 := newActivationTxV2( - WithMarriageCertificate(otherSig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(sig, atx1.ID(), sig.NodeID()), + withMarriageCertificate(otherSig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(sig, atx1.ID(), sig.NodeID()), ) atx2.Sign(otherSig) @@ -289,14 +272,14 @@ func Test_DoubleMarryProof(t *testing.T) { db := sql.InMemory() atx1 := newActivationTxV2( - WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(otherSig, types.RandomATXID(), sig.NodeID()), // unknown reference ATX + withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(otherSig, types.RandomATXID(), sig.NodeID()), // unknown reference ATX ) atx1.Sign(sig) atx2 := newActivationTxV2( - WithMarriageCertificate(otherSig, types.EmptyATXID, sig.NodeID()), - WithMarriageCertificate(sig, atx1.ID(), sig.NodeID()), + withMarriageCertificate(otherSig, types.EmptyATXID, sig.NodeID()), + withMarriageCertificate(sig, atx1.ID(), sig.NodeID()), ) atx2.Sign(otherSig) diff --git a/activation/wire/malfeasance_double_publish_test.go b/activation/wire/malfeasance_double_publish_test.go index d41cf1afe8..d66a98895f 100644 --- a/activation/wire/malfeasance_double_publish_test.go +++ b/activation/wire/malfeasance_double_publish_test.go @@ -15,10 +15,10 @@ func Test_DoublePublishProof(t *testing.T) { require.NoError(t, err) t.Run("valid", func(t *testing.T) { - atx1 := newActivationTxV2(WithPublishEpoch(10)) + atx1 := newActivationTxV2(withPublishEpoch(10)) atx1.Sign(sig) - atx2 := newActivationTxV2(WithPublishEpoch(10)) + atx2 := newActivationTxV2(withPublishEpoch(10)) atx2.Sign(sig) proof, err := NewDoublePublishProof(atx1, atx2) @@ -31,10 +31,10 @@ func Test_DoublePublishProof(t *testing.T) { }) t.Run("not same epoch", func(t *testing.T) { - atx1 := newActivationTxV2(WithPublishEpoch(10)) + atx1 := newActivationTxV2(withPublishEpoch(10)) atx1.Sign(sig) - atx2 := newActivationTxV2(WithPublishEpoch(11)) + atx2 := newActivationTxV2(withPublishEpoch(11)) atx2.Sign(sig) proof, err := NewDoublePublishProof(atx1, atx2) @@ -75,12 +75,12 @@ func Test_DoublePublishProof(t *testing.T) { t.Run("not same smesher", func(t *testing.T) { sig1 := sig - atx1 := newActivationTxV2(WithPublishEpoch(10)) + atx1 := newActivationTxV2(withPublishEpoch(10)) atx1.Sign(sig1) sig2, err := signing.NewEdSigner() require.NoError(t, err) - atx2 := newActivationTxV2(WithPublishEpoch(10)) + atx2 := newActivationTxV2(withPublishEpoch(10)) atx2.Sign(sig2) proof, err := NewDoublePublishProof(atx1, atx2) @@ -120,7 +120,7 @@ func Test_DoublePublishProof(t *testing.T) { }) t.Run("same ATX ID", func(t *testing.T) { - atx1 := newActivationTxV2(WithPublishEpoch(10)) + atx1 := newActivationTxV2(withPublishEpoch(10)) atx1.Sign(sig) proof, err := NewDoublePublishProof(atx1, atx1) @@ -157,10 +157,10 @@ func Test_DoublePublishProof(t *testing.T) { }) t.Run("invalid proof", func(t *testing.T) { - atx1 := newActivationTxV2(WithPublishEpoch(10)) + atx1 := newActivationTxV2(withPublishEpoch(10)) atx1.Sign(sig) - atx2 := newActivationTxV2(WithPublishEpoch(10)) + atx2 := newActivationTxV2(withPublishEpoch(10)) atx2.Sign(sig) // manually construct an invalid proof @@ -203,10 +203,10 @@ func Test_DoublePublishProof(t *testing.T) { }) t.Run("invalid signature", func(t *testing.T) { - atx1 := newActivationTxV2(WithPublishEpoch(10)) + atx1 := newActivationTxV2(withPublishEpoch(10)) atx1.Sign(sig) - atx2 := newActivationTxV2(WithPublishEpoch(10)) + atx2 := newActivationTxV2(withPublishEpoch(10)) atx2.Sign(sig) proof, err := NewDoublePublishProof(atx1, atx2) diff --git a/activation/wire/malfeasance_invalid_post.go b/activation/wire/malfeasance_invalid_post.go index 1637cf9ca0..70a95bdc27 100644 --- a/activation/wire/malfeasance_invalid_post.go +++ b/activation/wire/malfeasance_invalid_post.go @@ -12,7 +12,7 @@ import ( //go:generate scalegen -// InvalidPostProof is a proof that an ATXs with an invalid Post was published by a smesher. +// ProofInvalidPost is a proof that an ATXs with an invalid Post was published by a smesher. // // We are proofing the following: // 1. The provided Post is invalid for the given SmesherID. @@ -21,23 +21,23 @@ import ( // For this we need additional information: // 1. The initial ATX of the smesher for the Commitment ATX // 2. The marriage ATX of the smesher in the case the smesher is part of an equivocation set. -type InvalidPostProof struct { +type ProofInvalidPost struct { InvalidPost InvalidATXPostProof Commitment CommitmentProof // TODO(mafa): add marriage ATX proof } -var _ Proof = &InvalidPostProof{} +var _ Proof = &ProofInvalidPost{} -func NewInvalidPostProof(atx, initialAtx *ActivationTxV2) (*InvalidPostProof, error) { +func NewInvalidPostProof(atx, initialAtx *ActivationTxV2) (*ProofInvalidPost, error) { // TODO(mafa): implement return nil, nil } // Valid returns true if the proof is valid. It verifies that the two proofs have the same publish epoch, smesher ID, // and a valid signature but different ATX IDs as well as that the provided merkle proofs are valid. -func (p InvalidPostProof) Valid(edVerifier *signing.EdVerifier) (types.NodeID, error) { +func (p ProofInvalidPost) Valid(edVerifier *signing.EdVerifier) (types.NodeID, error) { // TODO(mafa): implement return types.EmptyNodeID, nil } diff --git a/activation/wire/malfeasance_invalid_post_scale.go b/activation/wire/malfeasance_invalid_post_scale.go index f6dc4a2d08..12750e8c50 100644 --- a/activation/wire/malfeasance_invalid_post_scale.go +++ b/activation/wire/malfeasance_invalid_post_scale.go @@ -8,7 +8,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" ) -func (t *InvalidPostProof) EncodeScale(enc *scale.Encoder) (total int, err error) { +func (t *ProofInvalidPost) EncodeScale(enc *scale.Encoder) (total int, err error) { { n, err := t.InvalidPost.EncodeScale(enc) if err != nil { @@ -26,7 +26,7 @@ func (t *InvalidPostProof) EncodeScale(enc *scale.Encoder) (total int, err error return total, nil } -func (t *InvalidPostProof) DecodeScale(dec *scale.Decoder) (total int, err error) { +func (t *ProofInvalidPost) DecodeScale(dec *scale.Decoder) (total int, err error) { { n, err := t.InvalidPost.DecodeScale(dec) if err != nil { diff --git a/activation/wire/wire_v2_test.go b/activation/wire/wire_v2_test.go index 32d54bcabe..9c99facfc1 100644 --- a/activation/wire/wire_v2_test.go +++ b/activation/wire/wire_v2_test.go @@ -13,13 +13,13 @@ import ( type testAtxV2Opt func(*ActivationTxV2) -func WithPublishEpoch(epoch types.EpochID) testAtxV2Opt { +func withPublishEpoch(epoch types.EpochID) testAtxV2Opt { return func(atx *ActivationTxV2) { atx.PublishEpoch = epoch } } -func WithMarriageCertificate(sig *signing.EdSigner, refAtx types.ATXID, atxPublisher types.NodeID) testAtxV2Opt { +func withMarriageCertificate(sig *signing.EdSigner, refAtx types.ATXID, atxPublisher types.NodeID) testAtxV2Opt { return func(atx *ActivationTxV2) { certificate := MarriageCertificate{ ReferenceAtx: refAtx, diff --git a/hare3/hare_test.go b/hare3/hare_test.go index ae26bca4f0..eff16db6cf 100644 --- a/hare3/hare_test.go +++ b/hare3/hare_test.go @@ -148,8 +148,8 @@ func (n *node) reuseSigner(signer *signing.EdSigner) *node { return n } -func (n *node) withDb() *node { - n.db = sql.InMemory() +func (n *node) withDb(tb testing.TB) *node { + n.db = sql.InMemoryTest(tb) n.atxsdata = atxsdata.New() n.proposals = store.New() return n @@ -342,7 +342,7 @@ func (cl *lockstepCluster) addActive(n int) *lockstepCluster { for i := last; i < last+n; i++ { cl.addNode((&node{t: cl.t, i: i}). withController().withSyncer().withPublisher(). - withClock().withDb().withSigner().withAtx(cl.units.min, cl.units.max). + withClock().withDb(cl.t).withSigner().withAtx(cl.units.min, cl.units.max). withOracle().withHare()) } return cl @@ -353,7 +353,7 @@ func (cl *lockstepCluster) addInactive(n int) *lockstepCluster { for i := last; i < last+n; i++ { cl.addNode((&node{t: cl.t, i: i}). withController().withSyncer().withPublisher(). - withClock().withDb().withSigner(). + withClock().withDb(cl.t).withSigner(). withOracle().withHare()) } return cl @@ -366,7 +366,7 @@ func (cl *lockstepCluster) addEquivocators(n int) *lockstepCluster { cl.addNode((&node{t: cl.t, i: i}). reuseSigner(cl.nodes[i-last].signer). withController().withSyncer().withPublisher(). - withClock().withDb().withAtx(cl.units.min, cl.units.max). + withClock().withDb(cl.t).withAtx(cl.units.min, cl.units.max). withOracle().withHare()) } return cl diff --git a/hare3/malfeasance_test.go b/hare3/malfeasance_test.go index 0f2a0f1491..7ff1cf52b1 100644 --- a/hare3/malfeasance_test.go +++ b/hare3/malfeasance_test.go @@ -26,7 +26,7 @@ type testMalfeasanceHandler struct { } func newTestMalfeasanceHandler(tb testing.TB) *testMalfeasanceHandler { - db := sql.InMemory() + db := sql.InMemoryTest(tb) observer, observedLogs := observer.New(zapcore.WarnLevel) logger := zaptest.NewLogger(tb, zaptest.WrapOptions(zap.WrapCore( func(core zapcore.Core) zapcore.Core { diff --git a/hare4/eligibility/oracle_test.go b/hare4/eligibility/oracle_test.go index d6ae2e4a58..5dfdf877b6 100644 --- a/hare4/eligibility/oracle_test.go +++ b/hare4/eligibility/oracle_test.go @@ -53,7 +53,7 @@ type testOracle struct { } func defaultOracle(tb testing.TB) *testOracle { - db := sql.InMemory() + db := sql.InMemoryTest(tb) atxsdata := atxsdata.New() ctrl := gomock.NewController(tb) diff --git a/hare4/hare.go b/hare4/hare.go index 7789c6c723..55eb6ddf4a 100644 --- a/hare4/hare.go +++ b/hare4/hare.go @@ -193,7 +193,7 @@ type nodeclock interface { func New( nodeclock nodeclock, - pubsub pubsub.PublishSubsciber, + pubsub pubsub.PublishSubscriber, db *sql.Database, atxsdata *atxsdata.Data, proposals *store.Store, @@ -262,7 +262,7 @@ type Hare struct { // dependencies nodeclock nodeclock - pubsub pubsub.PublishSubsciber + pubsub pubsub.PublishSubscriber db *sql.Database atxsdata *atxsdata.Data proposals *store.Store diff --git a/hare4/hare_test.go b/hare4/hare_test.go index 70f363a8f6..a793b35b7c 100644 --- a/hare4/hare_test.go +++ b/hare4/hare_test.go @@ -131,7 +131,7 @@ type node struct { proposals *store.Store ctrl *gomock.Controller - mpublisher *pmocks.MockPublishSubsciber + mpublisher *pmocks.MockPublishSubscriber msyncer *smocks.MockSyncStateProvider mverifier *hmock.Mockverifier mockStreamRequester *hmock.MockstreamRequester @@ -159,8 +159,8 @@ func (n *node) reuseSigner(signer *signing.EdSigner) *node { return n } -func (n *node) withDb() *node { - n.db = sql.InMemory() +func (n *node) withDb(tb testing.TB) *node { + n.db = sql.InMemoryTest(tb) n.atxsdata = atxsdata.New() n.proposals = store.New() return n @@ -220,7 +220,7 @@ func (n *node) withOracle() *node { } func (n *node) withPublisher() *node { - n.mpublisher = pmocks.NewMockPublishSubsciber(n.ctrl) + n.mpublisher = pmocks.NewMockPublishSubscriber(n.ctrl) n.mpublisher.EXPECT().Register(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() return n } @@ -391,7 +391,7 @@ func (cl *lockstepCluster) addActive(n int) *lockstepCluster { for i := last; i < last+n; i++ { nn := (&node{t: cl.t, i: i}). withController().withSyncer().withPublisher(). - withClock().withDb().withSigner().withAtx(cl.units.min, cl.units.max). + withClock().withDb(cl.t).withSigner().withAtx(cl.units.min, cl.units.max). withStreamRequester().withOracle().withHare() if cl.mockVerify { nn = nn.withVerifier() @@ -406,7 +406,7 @@ func (cl *lockstepCluster) addInactive(n int) *lockstepCluster { for i := last; i < last+n; i++ { cl.addNode((&node{t: cl.t, i: i}). withController().withSyncer().withPublisher(). - withClock().withDb().withSigner(). + withClock().withDb(cl.t).withSigner(). withStreamRequester().withOracle().withHare()) } return cl @@ -419,7 +419,7 @@ func (cl *lockstepCluster) addEquivocators(n int) *lockstepCluster { cl.addNode((&node{t: cl.t, i: i}). reuseSigner(cl.nodes[i-last].signer). withController().withSyncer().withPublisher(). - withClock().withDb().withAtx(cl.units.min, cl.units.max). + withClock().withDb(cl.t).withAtx(cl.units.min, cl.units.max). withStreamRequester().withOracle().withHare()) } return cl diff --git a/sql/database.go b/sql/database.go index 5f86f0bda5..e20a393b63 100644 --- a/sql/database.go +++ b/sql/database.go @@ -11,6 +11,7 @@ import ( "strings" "sync" "sync/atomic" + "testing" "time" sqlite "github.com/go-llsqlite/crawshaw" @@ -176,6 +177,7 @@ func WithQueryCacheSizes(sizes map[QueryCacheKind]int) Opt { type Opt func(c *conf) // InMemory database for testing. +// Please use InMemoryTest for automatic closing of the returned db during `tb.Cleanup`. func InMemory(opts ...Opt) *Database { opts = append(opts, WithConnections(1)) db, err := Open("file::memory:?mode=memory", opts...) @@ -185,6 +187,17 @@ func InMemory(opts ...Opt) *Database { return db } +// InMemoryTest returns an in-mem database for testing and ensures database is closed during `tb.Cleanup`. +func InMemoryTest(tb testing.TB, opts ...Opt) *Database { + opts = append(opts, WithConnections(1)) + db, err := Open("file::memory:?mode=memory", opts...) + if err != nil { + panic(err) + } + tb.Cleanup(func() { db.Close() }) + return db +} + // Open database with options. // // Database is opened in WAL mode and pragma synchronous=normal. diff --git a/systest/Makefile b/systest/Makefile index 705f9b230e..b1f4d07f11 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -36,8 +36,8 @@ ifeq ($(configname),$(test_job_name)) run_deps = config endif -command := tests -test.v -test.count=$(count) -test.timeout=0 -test.run=$(test_name) -test.parallel=$(clusters) \ - -clusters=$(clusters) -level=$(level) -configname=$(configname) -test.failfast=$(failfast) +command := tests -test.v -test.count=$(count) -test.timeout=60m -test.run=$(test_name) -test.parallel=$(clusters) \ + -test.failfast=$(failfast) -clusters=$(clusters) -level=$(level) -configname=$(configname) .PHONY: docker docker: