consensus tests

License: MIT
Signed-off-by: Hector Sanjuan <hector@protocol.ai>
This commit is contained in:
Hector Sanjuan 2016-12-14 15:31:50 +01:00
parent 98dc9f2289
commit 0f31995bd6
17 changed files with 249 additions and 69 deletions

4
api.go
View File

@ -7,9 +7,9 @@ import (
"net"
"net/http"
peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
peer "github.com/libp2p/go-libp2p-peer"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
mux "github.com/gorilla/mux"
)

View File

@ -8,20 +8,17 @@ import (
"net/http"
"testing"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
cid "github.com/ipfs/go-cid"
peer "github.com/libp2p/go-libp2p-peer"
)
var (
apiHost = "http://127.0.0.1:5000"
apiHost = "http://127.0.0.1:10002" // should match testingConfig()
)
func testClusterApi(t *testing.T) *ClusterHTTPAPI {
//logging.SetDebugLogging()
cfg := &ClusterConfig{
ClusterAPIListenAddr: "127.0.0.1",
ClusterAPIListenPort: 5000,
}
cfg := testingConfig()
api, err := NewHTTPClusterAPI(cfg)
// No keep alive! Otherwise tests hang with
// connections re-used from previous tests
@ -36,16 +33,6 @@ func testClusterApi(t *testing.T) *ClusterHTTPAPI {
return api
}
func simulateAnswer(ch <-chan ClusterRPC, answer interface{}, err error) {
go func() {
req := <-ch
req.ResponseCh() <- RPCResponse{
Data: answer,
Error: err,
}
}()
}
func processResp(t *testing.T, httpResp *http.Response, err error, resp interface{}) {
if err != nil {
t.Fatal("error making get request: ", err)

View File

@ -7,15 +7,15 @@ import (
"fmt"
"strings"
host "gx/ipfs/QmPTGbC34bPKaUm9wTxBo7zSCac7pDuG42ZmnXC718CKZZ/go-libp2p-host"
multiaddr "gx/ipfs/QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH/go-multiaddr"
swarm "gx/ipfs/QmWfxnAiQ5TnnCgiX9ikVUKFNHRgGhbgKdx5DoKPELD7P4/go-libp2p-swarm"
basichost "gx/ipfs/QmbzCT1CwxVZ2ednptC9RavuJe7Bv8DDi2Ne89qUrA37XM/go-libp2p/p2p/host/basic"
peerstore "gx/ipfs/QmeXj9VAjmYQZxpmVz7VzccbJrpmr8qkCDSjfVNsPTWTYU/go-libp2p-peerstore"
peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
crypto "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
crypto "github.com/libp2p/go-libp2p-crypto"
host "github.com/libp2p/go-libp2p-host"
peer "github.com/libp2p/go-libp2p-peer"
peerstore "github.com/libp2p/go-libp2p-peerstore"
swarm "github.com/libp2p/go-libp2p-swarm"
basichost "github.com/libp2p/go-libp2p/p2p/host/basic"
multiaddr "github.com/multiformats/go-multiaddr"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
)
// Cluster is the main IPFS cluster component. It provides

17
config_test.go Normal file
View File

@ -0,0 +1,17 @@
package ipfscluster
func testingConfig() *ClusterConfig {
cfg := &ClusterConfig{
ConsensusListenPort: 10000,
ConsensusListenAddr: "127.0.0.1",
ID: "QmUfSFm12eYCaRdypg48m8RqkXfLW7A2ZeGZb2skeHHDGA",
PrivateKey: "CAASqAkwggSkAgEAAoIBAQDpT16IRF6bb9tHsCbQ7M+nb2aI8sz8xyt8PoAWM42ki+SNoESIxKb4UhFxixKvtEdGxNE6aUUVc8kFk6wTStJ/X3IGiMetwkXiFiUxabUF/8A6SyvnSVDm+wFuavugpVrZikjLcfrf2xOVgnG3deQQvd/qbAv14jTwMFl+T+8d/cXBo8Mn/leLZCQun/EJEnkXP5MjgNI8XcWUE4NnH3E0ESSm6Pkm8MhMDZ2fmzNgqEyJ0GVinNgSml3Pyha3PBSj5LRczLip/ie4QkKx5OHvX2L3sNv/JIUHse5HSbjZ1c/4oGCYMVTYCykWiczrxBUOlcr8RwnZLOm4n2bCt5ZhAgMBAAECggEAVkePwfzmr7zR7tTpxeGNeXHtDUAdJm3RWwUSASPXgb5qKyXVsm5nAPX4lXDE3E1i/nzSkzNS5PgIoxNVU10cMxZs6JW0okFx7oYaAwgAddN6lxQtjD7EuGaixN6zZ1k/G6vT98iS6i3uNCAlRZ9HVBmjsOF8GtYolZqLvfZ5izEVFlLVq/BCs7Y5OrDrbGmn3XupfitVWYExV0BrHpobDjsx2fYdTZkmPpSSvXNcm4Iq2AXVQzoqAfGo7+qsuLCZtVlyTfVKQjMvE2ffzN1dQunxixOvev/fz4WSjGnRpC6QLn6Oqps9+VxQKqKuXXqUJC+U45DuvA94Of9MvZfAAQKBgQD7xmXueXRBMr2+0WftybAV024ap0cXFrCAu+KWC1SUddCfkiV7e5w+kRJx6RH1cg4cyyCL8yhHZ99Z5V0Mxa/b/usuHMadXPyX5szVI7dOGgIC9q8IijN7B7GMFAXc8+qC7kivehJzjQghpRRAqvRzjDls4gmbNPhbH1jUiU124QKBgQDtOaW5/fOEtOq0yWbDLkLdjImct6oKMLhENL6yeIKjMYgifzHb2adk7rWG3qcMrdgaFtDVfqv8UmMEkzk7bSkovMVj3SkLzMz84ii1SkSfyaCXgt/UOzDkqAUYB0cXMppYA7jxHa2OY8oEHdBgmyJXdLdzJxCp851AoTlRUSePgQKBgQCQgKgUHOUaXnMEx88sbOuBO14gMg3dNIqM+Ejt8QbURmI8k3arzqA4UK8Tbb9+7b0nzXWanS5q/TT1tWyYXgW28DIuvxlHTA01aaP6WItmagrphIelERzG6f1+9ib/T4czKmvROvDIHROjq8lZ7ERs5Pg4g+sbh2VbdzxWj49EQQKBgFEna36ZVfmMOs7mJ3WWGeHY9ira2hzqVd9fe+1qNKbHhx7mDJR9fTqWPxuIh/Vac5dZPtAKqaOEO8OQ6f9edLou+ggT3LrgsS/B3tNGOPvA6mNqrk/Yf/15TWTO+I8DDLIXc+lokbsogC+wU1z5NWJd13RZZOX/JUi63vTmonYBAoGBAIpglLCH2sPXfmguO6p8QcQcv4RjAU1c0GP4P5PNN3Wzo0ItydVd2LHJb6MdmL6ypeiwNklzPFwTeRlKTPmVxJ+QPg1ct/3tAURN/D40GYw9ojDhqmdSl4HW4d6gHS2lYzSFeU5jkG49y5nirOOoEgHy95wghkh6BfpwHujYJGw4",
IPFSAPIListenAddr: "127.0.0.1",
IPFSAPIListenPort: 10001,
ClusterAPIListenAddr: "127.0.0.1",
ClusterAPIListenPort: 10002,
RaftFolder: "./raftFolderFromTests",
}
return cfg
}

View File

@ -5,12 +5,12 @@ import (
"errors"
"time"
host "gx/ipfs/QmPTGbC34bPKaUm9wTxBo7zSCac7pDuG42ZmnXC718CKZZ/go-libp2p-host"
consensus "gx/ipfs/QmZ88KbrvZMJpXaNwAGffswcYKz8EbeafzAFGMCA6MEZKt/go-libp2p-consensus"
libp2praft "gx/ipfs/QmdHo2LQKmGQ6rDAWFxnzNuW3z8b6Xmw3wEFsMQaj9Rsqj/go-libp2p-raft"
peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
consensus "github.com/libp2p/go-libp2p-consensus"
host "github.com/libp2p/go-libp2p-host"
peer "github.com/libp2p/go-libp2p-peer"
libp2praft "github.com/libp2p/go-libp2p-raft"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
)
const (
@ -40,7 +40,7 @@ type clusterLogOp struct {
}
// ApplyTo applies the operation to the ClusterState
func (op clusterLogOp) ApplyTo(cstate consensus.State) (consensus.State, error) {
func (op *clusterLogOp) ApplyTo(cstate consensus.State) (consensus.State, error) {
state, ok := cstate.(ClusterState)
var err error
if !ok {
@ -99,7 +99,7 @@ type ClusterConsensus struct {
consensus consensus.OpLogConsensus
actor consensus.Actor
baseOp clusterLogOp
baseOp *clusterLogOp
rpcCh chan ClusterRPC
p2pRaft *libp2pRaftWrap
@ -112,7 +112,7 @@ func NewClusterConsensus(cfg *ClusterConfig, host host.Host, state ClusterState)
logger.Info("Starting Consensus component")
ctx := context.Background()
rpcCh := make(chan ClusterRPC, RPCMaxQueue)
op := clusterLogOp{
op := &clusterLogOp{
ctx: context.Background(),
rpcCh: rpcCh,
}
@ -154,7 +154,8 @@ func NewClusterConsensus(cfg *ClusterConfig, host host.Host, state ClusterState)
// shutdown, along with the libp2p transport.
func (cc *ClusterConsensus) Shutdown() error {
logger.Info("Stopping Consensus component")
defer cc.p2pRaft.transport.Close()
defer cc.p2pRaft.boltdb.Close() // important!
// When we take snapshot, we make sure that
// we re-start from the previous state, and that
// we don't replay the log. This includes
@ -163,7 +164,6 @@ func (cc *ClusterConsensus) Shutdown() error {
_ = f.Error()
f = cc.p2pRaft.raft.Shutdown()
err := f.Error()
cc.p2pRaft.transport.Close()
if err != nil {
return err
}
@ -176,8 +176,8 @@ func (cc *ClusterConsensus) RpcChan() <-chan ClusterRPC {
return cc.rpcCh
}
func (cc *ClusterConsensus) op(c *cid.Cid, t clusterLogOpType) clusterLogOp {
return clusterLogOp{
func (cc *ClusterConsensus) op(c *cid.Cid, t clusterLogOpType) *clusterLogOp {
return &clusterLogOp{
Cid: c.String(),
Type: t,
}
@ -228,6 +228,7 @@ func (cc *ClusterConsensus) Leader() peer.ID {
return raftactor.Leader()
}
// TODO
func (cc *ClusterConsensus) Rollback(state ClusterState) error {
return cc.consensus.Rollback(state)
}

164
consensus_test.go Normal file
View File

@ -0,0 +1,164 @@
package ipfscluster
import (
"context"
"os"
"testing"
"time"
cid "github.com/ipfs/go-cid"
)
func TestApplyToPin(t *testing.T) {
op := &clusterLogOp{
Cid: testCid,
Type: LogOpPin,
ctx: context.Background(),
rpcCh: make(chan ClusterRPC, 1),
}
st := NewMapState()
op.ApplyTo(st)
pins := st.ListPins()
if len(pins) != 1 || pins[0].String() != testCid {
t.Error("the state was not modified correctly")
}
}
func TestApplyToUnpin(t *testing.T) {
op := &clusterLogOp{
Cid: testCid,
Type: LogOpUnpin,
ctx: context.Background(),
rpcCh: make(chan ClusterRPC, 1),
}
st := NewMapState()
c, _ := cid.Decode(testCid)
st.AddPin(c)
op.ApplyTo(st)
pins := st.ListPins()
if len(pins) != 0 {
t.Error("the state was not modified correctly")
}
}
func TestApplyToBadState(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("should have recovered an error")
}
}()
op := &clusterLogOp{
Cid: testCid,
Type: LogOpUnpin,
ctx: context.Background(),
rpcCh: make(chan ClusterRPC, 1),
}
var st interface{}
op.ApplyTo(st)
}
func TestApplyToBadCid(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("should have recovered an error")
}
}()
op := &clusterLogOp{
Cid: "agadfaegf",
Type: LogOpPin,
ctx: context.Background(),
rpcCh: make(chan ClusterRPC, 1),
}
st := NewMapState()
op.ApplyTo(st)
}
func cleanRaft() {
os.RemoveAll(testingConfig().RaftFolder)
}
func testingClusterConsensus(t *testing.T) *ClusterConsensus {
//logging.SetDebugLogging()
cfg := testingConfig()
ctx := context.Background()
h, err := makeHost(ctx, cfg)
if err != nil {
t.Fatal("cannot create host:", err)
}
st := NewMapState()
cc, err := NewClusterConsensus(cfg, h, st)
if err != nil {
t.Fatal("cannot create ClusterConsensus:", err)
}
// Oxygen for Raft to declare leader
time.Sleep(1 * time.Second)
return cc
}
func TestShutdownClusterConsensus(t *testing.T) {
// Bring it up twice to make sure shutdown cleans up properly
// but also to make sure raft comes up ok when re-initialized
defer cleanRaft()
cc := testingClusterConsensus(t)
err := cc.Shutdown()
if err != nil {
t.Fatal("ClusterConsensus cannot shutdown:", err)
}
cc = testingClusterConsensus(t)
err = cc.Shutdown()
if err != nil {
t.Fatal("ClusterConsensus cannot shutdown:", err)
}
}
func TestConsensusPin(t *testing.T) {
cc := testingClusterConsensus(t)
defer cleanRaft() // Remember defer runs in LIFO order
defer cc.Shutdown()
c, _ := cid.Decode(testCid)
err := cc.AddPin(c)
if err != nil {
t.Error("the operation did not make it to the log:", err)
}
time.Sleep(250 * time.Millisecond)
st, err := cc.State()
if err != nil {
t.Error("error getting state:", err)
}
pins := st.ListPins()
if len(pins) != 1 || pins[0].String() != testCid {
t.Error("the added pin should be in the state")
}
}
func TestConsensusUnpin(t *testing.T) {
cc := testingClusterConsensus(t)
defer cleanRaft()
defer cc.Shutdown()
c, _ := cid.Decode(testCid2)
err := cc.RmPin(c)
if err != nil {
t.Error("the operation did not make it to the log:", err)
}
}
func TestConsensusLeader(t *testing.T) {
cc := testingClusterConsensus(t)
cfg := testingConfig()
pId := cfg.ID
defer cleanRaft()
defer cc.Shutdown()
if l := cc.Leader().Pretty(); l != pId {
t.Errorf("expected %s but the leader appears as %s", pId, l)
}
}

View File

@ -7,7 +7,7 @@ import (
"os/user"
"path/filepath"
logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
logging "github.com/ipfs/go-log"
ipfscluster "github.com/ipfs/ipfs-cluster"
)

View File

@ -10,12 +10,11 @@ package ipfscluster
import (
"context"
"errors"
logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
"time"
peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log"
peer "github.com/libp2p/go-libp2p-peer"
)
var logger = logging.Logger("ipfs-cluster")

View File

@ -6,7 +6,7 @@ import (
"testing"
"time"
peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
peer "github.com/libp2p/go-libp2p-peer"
)
var (
@ -58,3 +58,13 @@ func TestMakeRPC(t *testing.T) {
time.Sleep(2 * time.Second)
cancel()
}
func simulateAnswer(ch <-chan ClusterRPC, answer interface{}, err error) {
go func() {
req := <-ch
req.ResponseCh() <- RPCResponse{
Data: answer,
Error: err,
}
}()
}

View File

@ -11,7 +11,7 @@ import (
"net/http"
"strings"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
)
// IPFSHTTPConnector implements the IPFSConnector interface

View File

@ -9,7 +9,7 @@ import (
"strings"
"testing"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
)
func testServer(t *testing.T) *httptest.Server {
@ -48,12 +48,10 @@ func testIPFSConnectorConfig(ts *httptest.Server) *ClusterConfig {
h := strings.Split(url.Host, ":")
i, _ := strconv.Atoi(h[1])
return &ClusterConfig{
IPFSHost: h[0],
IPFSPort: i,
IPFSAPIListenAddr: "127.0.0.1",
IPFSAPIListenPort: 5000,
}
cfg := testingConfig()
cfg.IPFSHost = h[0]
cfg.IPFSPort = i
return cfg
}
func ipfsConnector(t *testing.T) (*IPFSHTTPConnector, *httptest.Server) {
@ -140,7 +138,8 @@ func TestProxy(t *testing.T) {
defer ts.Close()
defer ipfs.Shutdown()
res, err := http.Get("http://127.0.0.1:5000/api/v0/add?arg=" + testCid)
// Address comes from testingConfig()
res, err := http.Get("http://127.0.0.1:10001/api/v0/add?arg=" + testCid)
if err != nil {
t.Fatal("should forward requests to ipfs host: ", err)
}

View File

@ -21,7 +21,7 @@
},
{
"author": "hsanjuan",
"hash": "QmdHo2LQKmGQ6rDAWFxnzNuW3z8b6Xmw3wEFsMQaj9Rsqj",
"hash": "QmaofA6ApgPQm8yRojC77dQbVUatYMihdyQjB7VsAqrks1",
"name": "go-libp2p-raft",
"version": "0.1.1"
},

23
raft.go
View File

@ -1,13 +1,12 @@
package ipfscluster
import (
"os"
"path/filepath"
host "gx/ipfs/QmPTGbC34bPKaUm9wTxBo7zSCac7pDuG42ZmnXC718CKZZ/go-libp2p-host"
libp2praft "gx/ipfs/QmdHo2LQKmGQ6rDAWFxnzNuW3z8b6Xmw3wEFsMQaj9Rsqj/go-libp2p-raft"
host "github.com/libp2p/go-libp2p-host"
libp2praft "github.com/libp2p/go-libp2p-raft"
peer "gx/ipfs/QmfMmLGoKzCHDN7cGgk64PJr4iipzidDRME8HABSJqvmhC/go-libp2p-peer"
peer "github.com/libp2p/go-libp2p-peer"
hashiraft "github.com/hashicorp/raft"
raftboltdb "github.com/hashicorp/raft-boltdb"
@ -22,17 +21,20 @@ type libp2pRaftWrap struct {
logStore hashiraft.LogStore
stableStore hashiraft.StableStore
peerstore *libp2praft.Peerstore
boltdb *raftboltdb.BoltStore
}
// This function does all heavy the work which is specifically related to
// hashicorp's Raft. Other places should just rely on the Consensus interface.
func makeLibp2pRaft(cfg *ClusterConfig, host host.Host, state ClusterState, op clusterLogOp) (*libp2praft.Consensus, *libp2praft.Actor, *libp2pRaftWrap, error) {
func makeLibp2pRaft(cfg *ClusterConfig, host host.Host, state ClusterState, op *clusterLogOp) (*libp2praft.Consensus, *libp2praft.Actor, *libp2pRaftWrap, error) {
logger.Debug("creating libp2p Raft transport")
transport, err := libp2praft.NewLibp2pTransportWithHost(host)
if err != nil {
logger.Error("creating libp2p-raft transport: ", err)
return nil, nil, nil, err
}
logger.Debug("opening connections")
transport.OpenConns()
pstore := &libp2praft.Peerstore{}
@ -43,23 +45,27 @@ func makeLibp2pRaft(cfg *ClusterConfig, host host.Host, state ClusterState, op c
}
pstore.SetPeers(strPeers)
logger.Debug("creating OpLog")
cons := libp2praft.NewOpLog(state, op)
raftCfg := hashiraft.DefaultConfig()
raftCfg.EnableSingleNode = raftSingleMode
logger.Debug("creating file snapshot store")
snapshots, err := hashiraft.NewFileSnapshotStore(cfg.RaftFolder, maxSnapshots, nil)
if err != nil {
logger.Error("creating file snapshot store: ", err)
return nil, nil, nil, err
}
logger.Debug("creating BoltDB log store")
logStore, err := raftboltdb.NewBoltStore(filepath.Join(cfg.RaftFolder, "raft.db"))
if err != nil {
logger.Error("creating bolt store: ", err)
return nil, nil, nil, err
}
r, err := hashiraft.NewRaft(raftCfg, cons, logStore, logStore, snapshots, pstore, transport)
logger.Debug("creating Raft")
r, err := hashiraft.NewRaft(raftCfg, cons.FSM(), logStore, logStore, snapshots, pstore, transport)
if err != nil {
logger.Error("initializing raft: ", err)
return nil, nil, nil, err
@ -72,9 +78,6 @@ func makeLibp2pRaft(cfg *ClusterConfig, host host.Host, state ClusterState, op c
logStore: logStore,
stableStore: logStore,
peerstore: pstore,
boltdb: logStore,
}, nil
}
func cleanRaft(cfg *ClusterConfig) {
os.RemoveAll(cfg.RaftFolder)
}

2
rpc.go
View File

@ -1,6 +1,6 @@
package ipfscluster
import cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
import cid "github.com/ipfs/go-cid"
// ClusterRPC supported operations.
const (

View File

@ -3,7 +3,7 @@ package ipfscluster
import (
"testing"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
)
func TestRPC(t *testing.T) {

View File

@ -3,7 +3,7 @@ package ipfscluster
import (
"sync"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
)
// MapState is a very simple database to store

View File

@ -4,7 +4,7 @@ import (
"context"
"sync"
cid "gx/ipfs/QmcTcsTvfaeEBRFo1TkFgT8sRmgi1n1LTZpecfVP8fzpGD/go-cid"
cid "github.com/ipfs/go-cid"
)
const (