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

feat(routing/http): delegated IPNS server and client, IPIP 379 #333

Merged
merged 3 commits into from
Aug 22, 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ The following emojis are used to highlight certain changes:

### Added

* The `routing/http` client and server now support Delegated IPNS at `/routing/v1`
as per [IPIP-379](https://specs.ipfs.tech/ipips/ipip-0379/).
* The `verifycid` package has been updated with the new Allowlist interface as part of
reducing globals efforts. Still, existing global accessor funcs are kept for
backwards-compatibility.
Expand Down
2 changes: 1 addition & 1 deletion examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/ipfs/go-cid v0.4.1
github.com/ipfs/go-datastore v0.6.0
github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33
github.com/ipld/go-ipld-prime v0.20.0
github.com/ipld/go-ipld-prime v0.21.0
github.com/libp2p/go-libp2p v0.26.3
github.com/libp2p/go-libp2p-routing-helpers v0.7.0
github.com/multiformats/go-multiaddr v0.8.0
Expand Down
8 changes: 4 additions & 4 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
Expand Down Expand Up @@ -332,8 +332,8 @@ github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33 h1:0OZwzSYWIuiKE
github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33/go.mod h1:sQEkXVM3csejlb1kCCb+vQ/pWBKX9QtvsrysMQjOgOg=
github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc=
github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s=
github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g=
github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M=
github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
Expand Down Expand Up @@ -625,7 +625,7 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U=
github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s=
github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/ipfs/go-unixfsnode v1.7.1
github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33
github.com/ipld/go-codec-dagpb v1.6.0
github.com/ipld/go-ipld-prime v0.20.0
github.com/ipld/go-ipld-prime v0.21.0
github.com/jbenet/goprocess v0.1.4
github.com/libp2p/go-buffer-pool v0.1.0
github.com/libp2p/go-doh-resolver v0.4.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
Expand Down Expand Up @@ -338,8 +338,8 @@ github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33 h1:0OZwzSYWIuiKE
github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33/go.mod h1:sQEkXVM3csejlb1kCCb+vQ/pWBKX9QtvsrysMQjOgOg=
github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc=
github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s=
github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g=
github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M=
github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
Expand Down Expand Up @@ -635,7 +635,7 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U=
github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s=
github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
Expand Down
71 changes: 69 additions & 2 deletions routing/http/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"encoding/json"
"errors"
"fmt"
"io"
"mime"
"net/http"
"strings"
Expand Down Expand Up @@ -41,8 +42,9 @@
)

const (
mediaTypeJSON = "application/json"
mediaTypeNDJSON = "application/x-ndjson"
mediaTypeJSON = "application/json"
mediaTypeNDJSON = "application/x-ndjson"
mediaTypeIPNSRecord = "application/vnd.ipfs.ipns-record"
)

type client struct {
Expand Down Expand Up @@ -324,3 +326,68 @@

return 0, nil
}

func (c *client) FindIPNSRecord(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
url := c.baseURL + "/routing/v1/ipns/" + name.String()

httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}

Check warning on line 336 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L335-L336

Added lines #L335 - L336 were not covered by tests
httpReq.Header.Set("Accept", mediaTypeIPNSRecord)

resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("making HTTP req to get IPNS record: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, httpError(resp.StatusCode, resp.Body)
}

Check warning on line 347 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L346-L347

Added lines #L346 - L347 were not covered by tests

// Limit the reader to the maximum record size.
rawRecord, err := io.ReadAll(io.LimitReader(resp.Body, int64(ipns.MaxRecordSize)))
if err != nil {
return nil, fmt.Errorf("making HTTP req to get IPNS record: %w", err)
}

Check warning on line 353 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L352-L353

Added lines #L352 - L353 were not covered by tests

record, err := ipns.UnmarshalRecord(rawRecord)
if err != nil {
return nil, fmt.Errorf("IPNS record from remote endpoint is not valid: %w", err)
}

Check warning on line 358 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L357-L358

Added lines #L357 - L358 were not covered by tests

err = ipns.ValidateWithName(record, name)
if err != nil {
return nil, fmt.Errorf("IPNS record from remote endpoint is not valid: %w", err)
}

return record, nil
}

func (c *client) ProvideIPNSRecord(ctx context.Context, name ipns.Name, record *ipns.Record) error {
url := c.baseURL + "/routing/v1/ipns/" + name.String()

rawRecord, err := ipns.MarshalRecord(record)
if err != nil {
return err
}

Check warning on line 374 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L373-L374

Added lines #L373 - L374 were not covered by tests

httpReq, err := http.NewRequestWithContext(ctx, http.MethodPut, url, bytes.NewReader(rawRecord))
if err != nil {
return err
}

Check warning on line 379 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L378-L379

Added lines #L378 - L379 were not covered by tests
httpReq.Header.Set("Content-Type", mediaTypeIPNSRecord)

resp, err := c.httpClient.Do(httpReq)
if err != nil {
return fmt.Errorf("making HTTP req to get IPNS record: %w", err)
}

Check warning on line 385 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L384-L385

Added lines #L384 - L385 were not covered by tests
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return httpError(resp.StatusCode, resp.Body)
}

Check warning on line 390 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L389-L390

Added lines #L389 - L390 were not covered by tests

return nil
}
112 changes: 112 additions & 0 deletions routing/http/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ package client
import (
"context"
"crypto/rand"
"errors"
"net/http"
"net/http/httptest"
"runtime"
"testing"
"time"

"github.com/benbjohnson/clock"
"github.com/ipfs/boxo/coreiface/path"
ipns "github.com/ipfs/boxo/ipns"
ipfspath "github.com/ipfs/boxo/path"
"github.com/ipfs/boxo/routing/http/server"
"github.com/ipfs/boxo/routing/http/types"
"github.com/ipfs/boxo/routing/http/types/iter"
Expand Down Expand Up @@ -42,6 +46,16 @@ func (m *mockContentRouter) Provide(ctx context.Context, req *server.WriteProvid
return args.Get(0).(types.ProviderResponse), args.Error(1)
}

func (m *mockContentRouter) FindIPNSRecord(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
args := m.Called(ctx, name)
return args.Get(0).(*ipns.Record), args.Error(1)
}

func (m *mockContentRouter) ProvideIPNSRecord(ctx context.Context, name ipns.Name, record *ipns.Record) error {
args := m.Called(ctx, name, record)
return args.Error(0)
}

type testDeps struct {
// recordingHandler records requests received on the server side
recordingHandler *recordingHandler
Expand Down Expand Up @@ -442,3 +456,101 @@ func TestClient_Provide(t *testing.T) {
})
}
}

func makeName(t *testing.T) (crypto.PrivKey, ipns.Name) {
sk, _, err := crypto.GenerateEd25519Key(rand.Reader)
require.NoError(t, err)

pid, err := peer.IDFromPrivateKey(sk)
require.NoError(t, err)

return sk, ipns.NameFromPeer(pid)
}

func makeIPNSRecord(t *testing.T, sk crypto.PrivKey, opts ...ipns.Option) (*ipns.Record, []byte) {
cid, err := cid.Decode("bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4")
require.NoError(t, err)

path := path.IpfsPath(cid)
eol := time.Now().Add(time.Hour * 48)
ttl := time.Second * 20

record, err := ipns.NewRecord(sk, ipfspath.FromString(path.String()), 1, eol, ttl, opts...)
require.NoError(t, err)

rawRecord, err := ipns.MarshalRecord(record)
require.NoError(t, err)

return record, rawRecord
}

func TestClient_IPNS(t *testing.T) {
t.Run("Find IPNS Record returns error if server errors", func(t *testing.T) {
_, name := makeName(t)

deps := makeTestDeps(t, nil, nil)
client := deps.client
router := deps.router

router.On("FindIPNSRecord", mock.Anything, name).Return(nil, errors.New("something wrong happened"))

receivedRecord, err := client.FindIPNSRecord(context.Background(), name)
require.Error(t, err)
require.Nil(t, receivedRecord)
})

runWithRecordOptions := func(t *testing.T, opts ...ipns.Option) {
t.Run("Find IPNS Record", func(t *testing.T) {
sk, name := makeName(t)
record, _ := makeIPNSRecord(t, sk, opts...)

deps := makeTestDeps(t, nil, nil)
client := deps.client
router := deps.router

router.On("FindIPNSRecord", mock.Anything, name).Return(record, nil)

receivedRecord, err := client.FindIPNSRecord(context.Background(), name)
require.NoError(t, err)
require.Equal(t, record, receivedRecord)
})

t.Run("Find IPNS Record returns error if server sends bad data", func(t *testing.T) {
sk, _ := makeName(t)
record, _ := makeIPNSRecord(t, sk, opts...)
_, name2 := makeName(t)

deps := makeTestDeps(t, nil, nil)
client := deps.client
router := deps.router

router.On("FindIPNSRecord", mock.Anything, name2).Return(record, nil)

receivedRecord, err := client.FindIPNSRecord(context.Background(), name2)
require.Error(t, err)
require.Nil(t, receivedRecord)
})

t.Run("Provide IPNS Record", func(t *testing.T) {
sk, name := makeName(t)
record, _ := makeIPNSRecord(t, sk, opts...)

deps := makeTestDeps(t, nil, nil)
client := deps.client
router := deps.router

router.On("ProvideIPNSRecord", mock.Anything, name, record).Return(nil)

err := client.ProvideIPNSRecord(context.Background(), name, record)
require.NoError(t, err)
})
}

t.Run("V1+V2 IPNS Records", func(t *testing.T) {
runWithRecordOptions(t, ipns.WithV1Compatibility(true))
})

t.Run("V2 IPNS Records", func(t *testing.T) {
runWithRecordOptions(t, ipns.WithV1Compatibility(false))
})
}
Loading