Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(p2p): Head requests respects SoftFailure during Verify check #109

Merged
merged 2 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions headertest/dummy_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down
4 changes: 2 additions & 2 deletions opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ type HeadOption[H Header[H]] func(opts *HeadParams[H])
type HeadParams[H Header[H]] struct {
// TrustedHead allows the caller of Head to specify a trusted header
// against which the underlying implementation of Head can verify against.
TrustedHead Header[H]
TrustedHead H
}

// WithTrustedHead sets the TrustedHead parameter to the given header.
func WithTrustedHead[H Header[H]](verified Header[H]) func(opts *HeadParams[H]) {
func WithTrustedHead[H Header[H]](verified H) func(opts *HeadParams[H]) {
return func(opts *HeadParams[H]) {
opts.TrustedHead = verified
}
Expand Down
17 changes: 13 additions & 4 deletions p2p/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package p2p
import (
"bytes"
"context"
"errors"
"fmt"
"math/rand"
"sort"
Expand Down Expand Up @@ -126,7 +127,7 @@ func (ex *Exchange[H]) Head(ctx context.Context, opts ...header.HeadOption[H]) (
// trusted peers for its Head request. If nil, trusted peers will
// be used. If non-nil, Exchange will ask several peers from its network for
// their Head and verify against the given trusted header.
useTrackedPeers := reqParams.TrustedHead != nil
useTrackedPeers := !reqParams.TrustedHead.IsZero()
if useTrackedPeers {
trackedPeers := ex.peerTracker.getPeers(maxUntrustedHeadRequests)
if len(trackedPeers) > 0 {
Expand All @@ -153,14 +154,22 @@ func (ex *Exchange[H]) Head(ctx context.Context, opts ...header.HeadOption[H]) (
}
// if tracked (untrusted) peers were requested, verify head
if useTrackedPeers {
err = reqParams.TrustedHead.Verify(headers[0])
err = header.Verify[H](reqParams.TrustedHead, headers[0], header.DefaultHeightThreshold)
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 {
renaynay marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
47 changes: 45 additions & 2 deletions p2p/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestExchange_RequestHead(t *testing.T) {

tests := []struct {
requestFromTrusted bool
lastHeader header.Header[*headertest.DummyHeader]
lastHeader *headertest.DummyHeader
expectedHeight uint64
expectedHash header.Hash
}{
Expand All @@ -76,7 +76,7 @@ func TestExchange_RequestHead(t *testing.T) {
t.Run(strconv.Itoa(i), func(t *testing.T) {
var opts []header.HeadOption[*headertest.DummyHeader]
if !tt.requestFromTrusted {
opts = append(opts, header.WithTrustedHead(tt.lastHeader))
opts = append(opts, header.WithTrustedHead[*headertest.DummyHeader](tt.lastHeader))
}

header, err := exchg.Head(ctx, opts...)
Expand All @@ -88,6 +88,49 @@ 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
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[*headertest.DummyHeader](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)
Expand Down