Skip to content

Commit

Permalink
Merge pull request #109 from renaynay/soft-fail-head
Browse files Browse the repository at this point in the history
fix(p2p): Head requests respects SoftFailure during Verify check
  • Loading branch information
renaynay authored Sep 25, 2023
2 parents a6544e3 + 19ecd5b commit ab6c2f5
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 12 deletions.
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 {
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

0 comments on commit ab6c2f5

Please sign in to comment.