Skip to content

Commit

Permalink
feat!: remove all Provide* functions
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Aug 2, 2023
1 parent 043fb2f commit 9ce5527
Show file tree
Hide file tree
Showing 10 changed files with 5 additions and 669 deletions.
98 changes: 1 addition & 97 deletions routing/http/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@ import (
"mime"
"net/http"
"strings"
"time"

"github.com/benbjohnson/clock"
ipns "github.com/ipfs/boxo/ipns"
"github.com/ipfs/boxo/routing/http/contentrouter"
"github.com/ipfs/boxo/routing/http/internal/drjson"
"github.com/ipfs/boxo/routing/http/server"
"github.com/ipfs/boxo/routing/http/types"
"github.com/ipfs/boxo/routing/http/types/iter"
jsontypes "github.com/ipfs/boxo/routing/http/types/json"
Expand Down Expand Up @@ -58,10 +55,6 @@ type client struct {
peerID peer.ID
addrs []types.Multiaddr
identity crypto.PrivKey

// called immeidately after signing a provide req
// used for testing, e.g. testing the server with a mangled signature
afterSignCallback func(req *types.WriteBitswapProviderRecord)
}

// defaultUserAgent is used as a fallback to inform HTTP server which library
Expand Down Expand Up @@ -168,7 +161,7 @@ func (c *client) FindProviders(ctx context.Context, key cid.Cid) (provs iter.Res
// TODO test measurements
m := newMeasurement("FindProviders")

url := c.baseURL + server.ProvidePath + key.String()
url := c.baseURL + "/routing/v1/providers/" + key.String()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
Expand Down Expand Up @@ -238,95 +231,6 @@ func (c *client) FindProviders(ctx context.Context, key cid.Cid) (provs iter.Res
return &measuringIter[iter.Result[types.ProviderResponse]]{Iter: it, ctx: ctx, m: m}, nil
}

func (c *client) ProvideBitswap(ctx context.Context, keys []cid.Cid, ttl time.Duration) (time.Duration, error) {
if c.identity == nil {
return 0, errors.New("cannot provide Bitswap records without an identity")
}
if c.peerID.Size() == 0 {
return 0, errors.New("cannot provide Bitswap records without a peer ID")
}

ks := make([]types.CID, len(keys))
for i, c := range keys {
ks[i] = types.CID{Cid: c}
}

now := c.clock.Now()

req := types.WriteBitswapProviderRecord{
Protocol: "transport-bitswap",
Schema: types.SchemaBitswap,
Payload: types.BitswapPayload{
Keys: ks,
AdvisoryTTL: &types.Duration{Duration: ttl},
Timestamp: &types.Time{Time: now},
ID: &c.peerID,
Addrs: c.addrs,
},
}
err := req.Sign(c.peerID, c.identity)
if err != nil {
return 0, err
}

if c.afterSignCallback != nil {
c.afterSignCallback(&req)
}

advisoryTTL, err := c.provideSignedBitswapRecord(ctx, &req)
if err != nil {
return 0, err
}

return advisoryTTL, err
}

// ProvideAsync makes a provide request to a delegated router
func (c *client) provideSignedBitswapRecord(ctx context.Context, bswp *types.WriteBitswapProviderRecord) (time.Duration, error) {
req := jsontypes.WriteProvidersRequest{Providers: []types.WriteProviderRecord{bswp}}

url := c.baseURL + server.ProvidePath

b, err := drjson.MarshalJSONBytes(req)
if err != nil {
return 0, err
}

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

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

if resp.StatusCode != http.StatusOK {
return 0, httpError(resp.StatusCode, resp.Body)
}
var provideResult jsontypes.WriteProvidersResponse
err = json.NewDecoder(resp.Body).Decode(&provideResult)
if err != nil {
return 0, err
}
if len(provideResult.ProvideResults) != 1 {
return 0, fmt.Errorf("expected 1 result but got %d", len(provideResult.ProvideResults))
}

v, ok := provideResult.ProvideResults[0].(*types.WriteBitswapProviderRecordResponse)
if !ok {
return 0, fmt.Errorf("expected AdvisoryTTL field")
}

if v.AdvisoryTTL != nil {
return v.AdvisoryTTL.Duration, nil
}

return 0, nil
}

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

Expand Down
143 changes: 0 additions & 143 deletions routing/http/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"testing"
"time"

"github.com/benbjohnson/clock"
"github.com/ipfs/boxo/coreiface/path"
ipns "github.com/ipfs/boxo/ipns"
ipfspath "github.com/ipfs/boxo/path"
Expand All @@ -22,7 +21,6 @@ import (
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
"github.com/multiformats/go-multibase"
"github.com/multiformats/go-multihash"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
Expand All @@ -36,16 +34,6 @@ func (m *mockContentRouter) FindProviders(ctx context.Context, key cid.Cid, limi
return args.Get(0).(iter.ResultIter[types.ProviderResponse]), args.Error(1)
}

func (m *mockContentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) {
args := m.Called(ctx, req)
return args.Get(0).(time.Duration), args.Error(1)
}

func (m *mockContentRouter) Provide(ctx context.Context, req *server.WriteProvideRequest) (types.ProviderResponse, error) {
args := m.Called(ctx, req)
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)
Expand Down Expand Up @@ -151,13 +139,6 @@ func addrsToDRAddrs(addrs []multiaddr.Multiaddr) (drmas []types.Multiaddr) {
return
}

func drAddrsToAddrs(drmas []types.Multiaddr) (addrs []multiaddr.Multiaddr) {
for _, a := range drmas {
addrs = append(addrs, a.Multiaddr)
}
return
}

func makeBSReadProviderResp() types.ReadBitswapProviderRecord {
peerID, addrs, _ := makeProviderAndIdentity()
return types.ReadBitswapProviderRecord{
Expand Down Expand Up @@ -333,130 +314,6 @@ func TestClient_FindProviders(t *testing.T) {
}
}

func TestClient_Provide(t *testing.T) {
cases := []struct {
name string
manglePath bool
mangleSignature bool
stopServer bool
noProviderInfo bool
noIdentity bool

cids []cid.Cid
ttl time.Duration

routerAdvisoryTTL time.Duration
routerErr error

expErrContains string
expWinErrContains string

expAdvisoryTTL time.Duration
}{
{
name: "happy case",
cids: []cid.Cid{makeCID()},
ttl: 1 * time.Hour,
routerAdvisoryTTL: 1 * time.Minute,

expAdvisoryTTL: 1 * time.Minute,
},
{
name: "should return a 403 if the payload signature verification fails",
cids: []cid.Cid{},
mangleSignature: true,
expErrContains: "HTTP error with StatusCode=403",
},
{
name: "should return error if identity is not provided",
noIdentity: true,
expErrContains: "cannot provide Bitswap records without an identity",
},
{
name: "should return error if provider is not provided",
noProviderInfo: true,
expErrContains: "cannot provide Bitswap records without a peer ID",
},
{
name: "returns an error if there's a non-200 response",
manglePath: true,
expErrContains: "HTTP error with StatusCode=404: 404 page not found",
},
{
name: "returns an error if the HTTP client returns a non-HTTP error",
stopServer: true,
expErrContains: "connect: connection refused",
expWinErrContains: "connectex: No connection could be made because the target machine actively refused it.",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
deps := makeTestDeps(t, nil, nil)
client := deps.client
router := deps.router

if c.noIdentity {
client.identity = nil
}
if c.noProviderInfo {
client.peerID = ""
client.addrs = nil
}

clock := clock.NewMock()
clock.Set(time.Now())
client.clock = clock

ctx := context.Background()

if c.manglePath {
client.baseURL += "/foo"
}
if c.stopServer {
deps.server.Close()
}
if c.mangleSignature {
client.afterSignCallback = func(req *types.WriteBitswapProviderRecord) {
mh, err := multihash.Encode([]byte("boom"), multihash.SHA2_256)
require.NoError(t, err)
mb, err := multibase.Encode(multibase.Base64, mh)
require.NoError(t, err)

req.Signature = mb
}
}

expectedProvReq := &server.BitswapWriteProvideRequest{
Keys: c.cids,
Timestamp: clock.Now().Truncate(time.Millisecond),
AdvisoryTTL: c.ttl,
Addrs: drAddrsToAddrs(client.addrs),
ID: client.peerID,
}

router.On("ProvideBitswap", mock.Anything, expectedProvReq).
Return(c.routerAdvisoryTTL, c.routerErr)

advisoryTTL, err := client.ProvideBitswap(ctx, c.cids, c.ttl)

var errorString string
if runtime.GOOS == "windows" && c.expWinErrContains != "" {
errorString = c.expWinErrContains
} else {
errorString = c.expErrContains
}

if errorString != "" {
require.ErrorContains(t, err, errorString)
} else {
require.NoError(t, err)
}

assert.Equal(t, c.expAdvisoryTTL, advisoryTTL)
})
}
}

func makeName(t *testing.T) (crypto.PrivKey, ipns.Name) {
sk, _, err := crypto.GenerateEd25519Key(rand.Reader)
require.NoError(t, err)
Expand Down
39 changes: 1 addition & 38 deletions routing/http/contentrouter/contentrouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,20 @@ import (
"reflect"
"time"

"github.com/ipfs/boxo/routing/http/internal"
"github.com/ipfs/boxo/routing/http/types"
"github.com/ipfs/boxo/routing/http/types/iter"
"github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/routing"
"github.com/multiformats/go-multiaddr"
"github.com/multiformats/go-multihash"
)

var logger = logging.Logger("service/contentrouting")

const ttl = 24 * time.Hour

type Client interface {
ProvideBitswap(ctx context.Context, keys []cid.Cid, ttl time.Duration) (time.Duration, error)
FindProviders(ctx context.Context, key cid.Cid) (iter.ResultIter[types.ProviderResponse], error)
}

Expand Down Expand Up @@ -60,41 +57,7 @@ func NewContentRoutingClient(c Client, opts ...option) *contentRouter {
}

func (c *contentRouter) Provide(ctx context.Context, key cid.Cid, announce bool) error {
// If 'true' is
// passed, it also announces it, otherwise it is just kept in the local
// accounting of which objects are being provided.
if !announce {
return nil
}

_, err := c.client.ProvideBitswap(ctx, []cid.Cid{key}, ttl)
return err
}

// ProvideMany provides a set of keys to the remote delegate.
// Large sets of keys are chunked into multiple requests and sent concurrently, according to the concurrency configuration.
// TODO: implement retries through transient errors
func (c *contentRouter) ProvideMany(ctx context.Context, mhKeys []multihash.Multihash) error {
keys := make([]cid.Cid, 0, len(mhKeys))
for _, m := range mhKeys {
keys = append(keys, cid.NewCidV1(cid.Raw, m))
}

if len(keys) <= c.maxProvideBatchSize {
_, err := c.client.ProvideBitswap(ctx, keys, ttl)
return err
}

return internal.DoBatch(
ctx,
c.maxProvideBatchSize,
c.maxProvideConcurrency,
keys,
func(ctx context.Context, batch []cid.Cid) error {
_, err := c.client.ProvideBitswap(ctx, batch, ttl)
return err
},
)
return routing.ErrNotSupported
}

// Ready is part of the existing `ProvideMany` interface.
Expand Down
Loading

0 comments on commit 9ce5527

Please sign in to comment.