mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-22 10:57:42 +08:00
* Delegated Routing. Implementation of Reframe specs (https://github.com/ipfs/specs/blob/master/REFRAME.md) using go-delegated-routing library. * Requested changes. * Init using op string * Separate possible ContentRouters for TopicDiscovery. If we don't do this, we have a ciclic dependency creating TieredRouter. Now we can create first all possible content routers, and after that, create Routers. * Set dht default routing type * Add tests and remove uneeded code * Add documentation. * docs: Routing.Routers * Requested changes. Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com> * Add some documentation on new fx functions. * Add changelog entry and integration tests * test: sharness for 'dht' in 'routing' commands Since 'routing' is currently the same as 'dht' (minus query command) we need to test both, that way we won't have unnoticed divergence in the default behavior. * test(sharness): delegated routing via reframe URL * Add more tests for delegated routing. * If any put operation fails, the tiered router will fail. * refactor: Routing.Routers: Parameters.Endpoint As agreed in https://github.com/ipfs/kubo/pull/8997#issuecomment-1175684716 * Try to improve CHANGELOG entry. * chore: update reframe spec link * Update go-delegated-routing dependency * Fix config error test * use new changelog format * Remove port conflict * go mod tidy * ProviderManyWrapper to ProviderMany * Update docs/changelogs/v0.14.md Co-authored-by: Adin Schmahmann <adin.schmahmann@gmail.com> Co-authored-by: Marcin Rataj <lidel@lidel.org> Co-authored-by: Adin Schmahmann <adin.schmahmann@gmail.com>
314 lines
9.7 KiB
Go
314 lines
9.7 KiB
Go
package core
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"net/http/httptest"
|
|
"path"
|
|
"testing"
|
|
"time"
|
|
|
|
context "context"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/ipfs/go-delegated-routing/client"
|
|
"github.com/ipfs/go-ipns"
|
|
"github.com/ipfs/kubo/core/node/libp2p"
|
|
"github.com/ipfs/kubo/repo"
|
|
"github.com/libp2p/go-libp2p-core/crypto"
|
|
peer "github.com/libp2p/go-libp2p-core/peer"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
datastore "github.com/ipfs/go-datastore"
|
|
syncds "github.com/ipfs/go-datastore/sync"
|
|
drs "github.com/ipfs/go-delegated-routing/server"
|
|
config "github.com/ipfs/kubo/config"
|
|
)
|
|
|
|
func TestInitialization(t *testing.T) {
|
|
ctx := context.Background()
|
|
id := testIdentity
|
|
|
|
good := []*config.Config{
|
|
{
|
|
Identity: id,
|
|
Addresses: config.Addresses{
|
|
Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic"},
|
|
API: []string{"/ip4/127.0.0.1/tcp/8000"},
|
|
},
|
|
},
|
|
|
|
{
|
|
Identity: id,
|
|
Addresses: config.Addresses{
|
|
Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic"},
|
|
API: []string{"/ip4/127.0.0.1/tcp/8000"},
|
|
},
|
|
},
|
|
}
|
|
|
|
bad := []*config.Config{
|
|
{},
|
|
}
|
|
|
|
for i, c := range good {
|
|
r := &repo.Mock{
|
|
C: *c,
|
|
D: syncds.MutexWrap(datastore.NewMapDatastore()),
|
|
}
|
|
n, err := NewNode(ctx, &BuildCfg{Repo: r})
|
|
if n == nil || err != nil {
|
|
t.Error("Should have constructed.", i, err)
|
|
}
|
|
}
|
|
|
|
for i, c := range bad {
|
|
r := &repo.Mock{
|
|
C: *c,
|
|
D: syncds.MutexWrap(datastore.NewMapDatastore()),
|
|
}
|
|
n, err := NewNode(ctx, &BuildCfg{Repo: r})
|
|
if n != nil || err == nil {
|
|
t.Error("Should have failed to construct.", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
var testIdentity = config.Identity{
|
|
PeerID: "QmNgdzLieYi8tgfo2WfTUzNVH5hQK9oAYGVf6dxN12NrHt",
|
|
PrivKey: "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",
|
|
}
|
|
|
|
var errNotSupported = errors.New("method not supported")
|
|
|
|
func TestDelegatedRoutingSingle(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
pId1, priv1, err := GeneratePeerID()
|
|
require.NoError(err)
|
|
|
|
pId2, _, err := GeneratePeerID()
|
|
require.NoError(err)
|
|
|
|
theID := path.Join("/ipns", string(pId1))
|
|
theErrorID := path.Join("/ipns", string(pId2))
|
|
|
|
d := &delegatedRoutingService{
|
|
goodPeerID: pId1,
|
|
badPeerID: pId2,
|
|
pk1: priv1,
|
|
}
|
|
|
|
url := StartRoutingServer(t, d)
|
|
n := GetNode(t, url)
|
|
|
|
ctx := context.Background()
|
|
|
|
v, err := n.Routing.GetValue(ctx, theID)
|
|
require.NoError(err)
|
|
require.NotNil(v)
|
|
require.Contains(string(v), "RECORD FROM SERVICE 0")
|
|
|
|
v, err = n.Routing.GetValue(ctx, theErrorID)
|
|
require.Nil(v)
|
|
require.Error(err)
|
|
|
|
err = n.Routing.PutValue(ctx, theID, v)
|
|
require.NoError(err)
|
|
|
|
err = n.Routing.PutValue(ctx, theErrorID, v)
|
|
require.Error(err)
|
|
}
|
|
|
|
func TestDelegatedRoutingMulti(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
pId1, priv1, err := GeneratePeerID()
|
|
require.NoError(err)
|
|
|
|
pId2, priv2, err := GeneratePeerID()
|
|
require.NoError(err)
|
|
|
|
theID1 := path.Join("/ipns", string(pId1))
|
|
theID2 := path.Join("/ipns", string(pId2))
|
|
|
|
d1 := &delegatedRoutingService{
|
|
goodPeerID: pId1,
|
|
badPeerID: pId2,
|
|
pk1: priv1,
|
|
serviceID: 1,
|
|
}
|
|
|
|
url1 := StartRoutingServer(t, d1)
|
|
|
|
d2 := &delegatedRoutingService{
|
|
goodPeerID: pId2,
|
|
badPeerID: pId1,
|
|
pk1: priv2,
|
|
serviceID: 2,
|
|
}
|
|
|
|
url2 := StartRoutingServer(t, d2)
|
|
|
|
n := GetNode(t, url1, url2)
|
|
|
|
ctx := context.Background()
|
|
|
|
v, err := n.Routing.GetValue(ctx, theID1)
|
|
require.NoError(err)
|
|
require.NotNil(v)
|
|
require.Contains(string(v), "RECORD FROM SERVICE 1")
|
|
|
|
v, err = n.Routing.GetValue(ctx, theID2)
|
|
require.NoError(err)
|
|
require.NotNil(v)
|
|
require.Contains(string(v), "RECORD FROM SERVICE 2")
|
|
|
|
err = n.Routing.PutValue(ctx, theID1, v)
|
|
require.Error(err)
|
|
|
|
err = n.Routing.PutValue(ctx, theID2, v)
|
|
require.Error(err)
|
|
}
|
|
|
|
func StartRoutingServer(t *testing.T, d drs.DelegatedRoutingService) string {
|
|
t.Helper()
|
|
|
|
f := drs.DelegatedRoutingAsyncHandler(d)
|
|
svr := httptest.NewServer(f)
|
|
t.Cleanup(func() {
|
|
svr.Close()
|
|
})
|
|
|
|
return svr.URL
|
|
}
|
|
|
|
func GetNode(t *testing.T, reframeURLs ...string) *IpfsNode {
|
|
t.Helper()
|
|
|
|
routers := make(map[string]config.Router)
|
|
for i, ru := range reframeURLs {
|
|
routers[fmt.Sprintf("reframe-%d", i)] = config.Router{
|
|
Type: string(config.RouterTypeReframe),
|
|
Parameters: map[string]string{
|
|
string(config.RouterParamEndpoint): ru,
|
|
},
|
|
}
|
|
}
|
|
|
|
cfg := config.Config{
|
|
Identity: testIdentity,
|
|
Addresses: config.Addresses{
|
|
Swarm: []string{"/ip4/0.0.0.0/tcp/0", "/ip4/0.0.0.0/udp/0/quic"},
|
|
API: []string{"/ip4/127.0.0.1/tcp/0"},
|
|
},
|
|
Routing: config.Routing{
|
|
Type: config.NewOptionalString("none"),
|
|
Routers: routers,
|
|
},
|
|
}
|
|
|
|
r := &repo.Mock{
|
|
C: cfg,
|
|
D: syncds.MutexWrap(datastore.NewMapDatastore()),
|
|
}
|
|
|
|
n, err := NewNode(context.Background(), &BuildCfg{Repo: r, Online: true, Routing: libp2p.NilRouterOption})
|
|
require.NoError(t, err)
|
|
|
|
return n
|
|
}
|
|
|
|
func GeneratePeerID() (peer.ID, crypto.PrivKey, error) {
|
|
priv, pk, err := crypto.GenerateEd25519Key(rand.Reader)
|
|
if err != nil {
|
|
return peer.ID(""), nil, err
|
|
}
|
|
|
|
pid, err := peer.IDFromPublicKey(pk)
|
|
return pid, priv, err
|
|
}
|
|
|
|
type delegatedRoutingService struct {
|
|
goodPeerID, badPeerID peer.ID
|
|
pk1 crypto.PrivKey
|
|
serviceID int
|
|
}
|
|
|
|
func (drs *delegatedRoutingService) FindProviders(ctx context.Context, key cid.Cid) (<-chan client.FindProvidersAsyncResult, error) {
|
|
return nil, errNotSupported
|
|
}
|
|
|
|
func (drs *delegatedRoutingService) GetIPNS(ctx context.Context, id []byte) (<-chan client.GetIPNSAsyncResult, error) {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
ch := make(chan client.GetIPNSAsyncResult)
|
|
go func() {
|
|
defer close(ch)
|
|
defer cancel()
|
|
|
|
var out client.GetIPNSAsyncResult
|
|
switch peer.ID(id) {
|
|
case drs.goodPeerID:
|
|
ie, err := ipns.Create(drs.pk1, []byte(fmt.Sprintf("RECORD FROM SERVICE %d", drs.serviceID)), 0, time.Now().Add(10*time.Hour), 100*time.Hour)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
ieb, err := ie.Marshal()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
out = client.GetIPNSAsyncResult{
|
|
Record: ieb,
|
|
Err: nil,
|
|
}
|
|
case drs.badPeerID:
|
|
out = client.GetIPNSAsyncResult{
|
|
Record: nil,
|
|
Err: errors.New("THE ERROR"),
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case ch <- out:
|
|
}
|
|
}()
|
|
|
|
return ch, nil
|
|
|
|
}
|
|
|
|
func (drs *delegatedRoutingService) PutIPNS(ctx context.Context, id []byte, record []byte) (<-chan client.PutIPNSAsyncResult, error) {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
ch := make(chan client.PutIPNSAsyncResult)
|
|
go func() {
|
|
defer close(ch)
|
|
defer cancel()
|
|
|
|
var out client.PutIPNSAsyncResult
|
|
switch peer.ID(id) {
|
|
case drs.goodPeerID:
|
|
out = client.PutIPNSAsyncResult{}
|
|
case drs.badPeerID:
|
|
out = client.PutIPNSAsyncResult{
|
|
Err: fmt.Errorf("THE ERROR %d", drs.serviceID),
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case ch <- out:
|
|
}
|
|
}()
|
|
|
|
return ch, nil
|
|
}
|