diff --git a/headertest/dummy_header.go b/headertest/dummy_header.go index 64d21f22..e8480dde 100644 --- a/headertest/dummy_header.go +++ b/headertest/dummy_header.go @@ -27,6 +27,9 @@ type DummyHeader struct { // VerifyFailure allows for testing scenarios where a header would fail // verification. When set to true, it forces a failure. VerifyFailure bool + // SoftFailure allows for testing scenarios where a header would fail + // verification with SoftFailure set to true + SoftFailure bool } func RandDummyHeader(t *testing.T) *DummyHeader { @@ -96,11 +99,10 @@ func (d *DummyHeader) IsExpired(period time.Duration) bool { return expirationTime.Before(time.Now()) } -func (d *DummyHeader) Verify(header *DummyHeader) error { - if header.VerifyFailure { - return ErrDummyVerify +func (d *DummyHeader) Verify(hdr *DummyHeader) error { + if hdr.VerifyFailure { + return &header.VerifyError{Reason: ErrDummyVerify, SoftFailure: hdr.SoftFailure} } - return nil } diff --git a/p2p/exchange.go b/p2p/exchange.go index c4dd8289..af0bf100 100644 --- a/p2p/exchange.go +++ b/p2p/exchange.go @@ -3,6 +3,7 @@ package p2p import ( "bytes" "context" + "errors" "fmt" "math/rand" "sort" @@ -155,12 +156,20 @@ func (ex *Exchange[H]) Head(ctx context.Context, opts ...header.HeadOption[H]) ( if useTrackedPeers { err = reqParams.TrustedHead.Verify(headers[0]) if err != nil { - log.Errorw("verifying head received from tracked peer", "tracked peer", from, - "height", headers[0].Height(), "err", err) + var verErr *header.VerifyError + if errors.As(err, &verErr) && verErr.SoftFailure { + log.Debugw("received head from tracked peer that soft-failed verification", + "tracked peer", from, "err", err) + headerRespCh <- headers[0] + return + } // bad head was given, block peer ex.peerTracker.blockPeer(from, fmt.Errorf("returned bad head: %w", err)) + log.Errorw("verifying head received from tracked peer", "tracked peer", from, + "height", headers[0].Height(), "err", err) headerRespCh <- zero return + } } // request ensures that the result slice will have at least one Header diff --git a/p2p/exchange_test.go b/p2p/exchange_test.go index 6b81f529..35d70d72 100644 --- a/p2p/exchange_test.go +++ b/p2p/exchange_test.go @@ -88,6 +88,50 @@ func TestExchange_RequestHead(t *testing.T) { } } +// TestExchange_RequestHead_SoftFailure tests that the exchange still processes +// a Head response that has a SoftFailure. +func TestExchange_RequestHead_SoftFailure(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + hosts := createMocknet(t, 3) + exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) + + // create a tracked peer + suite := headertest.NewTestSuite(t) + trackedStore := headertest.NewStore[*headertest.DummyHeader](t, suite, 50) + // create a header that will SoftFail verification and append it to tracked + // peer's store + hdr := suite.GenDummyHeaders(1)[0] + hdr.VerifyFailure = true + hdr.SoftFailure = true + err := trackedStore.Append(ctx, hdr) + require.NoError(t, err) + // start the tracked peer's server + serverSideEx, err := NewExchangeServer[*headertest.DummyHeader](hosts[2], trackedStore, + WithNetworkID[ServerParameters](networkID), + ) + require.NoError(t, err) + err = serverSideEx.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + err = serverSideEx.Stop(ctx) + require.NoError(t, err) + }) + + // get first subjective head from trusted peer to initialize the + // exchange's store + var head header.Header[*headertest.DummyHeader] + head, err = exchg.Head(ctx) + require.NoError(t, err) + + // now use that trusted head to request a new head from the exchange + // from the tracked peer + softFailHead, err := exchg.Head(ctx, header.WithTrustedHead(head)) + require.NoError(t, err) + assert.Equal(t, trackedStore.HeadHeight, softFailHead.Height()) +} + func TestExchange_RequestHead_UnresponsivePeer(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel)