diff --git a/cmd/lassie/daemon.go b/cmd/lassie/daemon.go index 619fed14..03a024e9 100644 --- a/cmd/lassie/daemon.go +++ b/cmd/lassie/daemon.go @@ -2,7 +2,9 @@ package main import ( "fmt" + "time" + "github.com/filecoin-project/lassie/pkg/lassie" httpserver "github.com/filecoin-project/lassie/pkg/server/http" "github.com/urfave/cli/v2" ) @@ -39,7 +41,14 @@ var daemonCmd = &cli.Command{ func daemonCommand(cctx *cli.Context) error { address := cctx.String("address") port := cctx.Uint("port") - httpServer, err := httpserver.NewHttpServer(cctx.Context, address, port) + + // create a lassie instance + lassie, err := lassie.NewLassie(cctx.Context, lassie.WithProviderTimeout(20*time.Second)) + if err != nil { + return err + } + + httpServer, err := httpserver.NewHttpServer(cctx.Context, lassie, address, port) if err != nil { log.Errorw("failed to create http server", "err", err) return err diff --git a/cmd/lassie/fetch.go b/cmd/lassie/fetch.go index c63d9eca..553fe7a8 100644 --- a/cmd/lassie/fetch.go +++ b/cmd/lassie/fetch.go @@ -73,7 +73,7 @@ func Fetch(c *cli.Context) error { } timeout := c.Duration("timeout") - timeoutOpt := lassie.WithTimeout(timeout) + timeoutOpt := lassie.WithProviderTimeout(timeout) var opts = []lassie.LassieOption{timeoutOpt} if fetchProviderAddrInfo != nil { @@ -172,23 +172,23 @@ func (pp *progressPrinter) subscriber(event types.RetrievalEvent) { case types.IndexerPhase: fmt.Printf("\rQuerying indexer for %s...\n", ret.PayloadCid()) case types.QueryPhase: - fmt.Printf("\rQuerying [%s] (%s)...\n", ret.StorageProviderId(), ret.Code()) + fmt.Printf("\rQuerying [%s] (%s)...\n", types.Identifier(ret), ret.Code()) case types.RetrievalPhase: - fmt.Printf("\rRetrieving from [%s] (%s)...\n", ret.StorageProviderId(), ret.Code()) + fmt.Printf("\rRetrieving from [%s] (%s)...\n", types.Identifier(ret), ret.Code()) } case events.RetrievalEventConnected: switch ret.Phase() { case types.QueryPhase: - fmt.Printf("\rQuerying [%s] (%s)...\n", ret.StorageProviderId(), ret.Code()) + fmt.Printf("\rQuerying [%s] (%s)...\n", types.Identifier(ret), ret.Code()) case types.RetrievalPhase: - fmt.Printf("\rRetrieving from [%s] (%s)...\n", ret.StorageProviderId(), ret.Code()) + fmt.Printf("\rRetrieving from [%s] (%s)...\n", types.Identifier(ret), ret.Code()) } case events.RetrievalEventProposed: - fmt.Printf("\rRetrieving from [%s] (%s)...\n", ret.StorageProviderId(), ret.Code()) + fmt.Printf("\rRetrieving from [%s] (%s)...\n", types.Identifier(ret), ret.Code()) case events.RetrievalEventAccepted: - fmt.Printf("\rRetrieving from [%s] (%s)...\n", ret.StorageProviderId(), ret.Code()) + fmt.Printf("\rRetrieving from [%s] (%s)...\n", types.Identifier(ret), ret.Code()) case events.RetrievalEventFirstByte: - fmt.Printf("\rRetrieving from [%s] (%s)...\n", ret.StorageProviderId(), ret.Code()) + fmt.Printf("\rRetrieving from [%s] (%s)...\n", types.Identifier(ret), ret.Code()) case events.RetrievalEventCandidatesFound: pp.candidatesFound = len(ret.Candidates()) case events.RetrievalEventCandidatesFiltered: @@ -204,17 +204,17 @@ func (pp *progressPrinter) subscriber(event types.RetrievalEvent) { fmt.Printf("Using the explicitly specified storage provider, querying %s:\n", num) } for _, candidate := range ret.Candidates() { - fmt.Printf("\r\t%s\n", candidate.MinerPeer.ID) + fmt.Printf("\r\t%s, Protocols: %v\n", candidate.MinerPeer.ID, candidate.Metadata.Protocols()) } case events.RetrievalEventQueryAsked: - fmt.Printf("\rGot query response from [%s] (checking): size=%s, price-per-byte=%s, unseal-price=%s, message=%s\n", ret.StorageProviderId(), humanize.IBytes(ret.QueryResponse().Size), ret.QueryResponse().MinPricePerByte, ret.QueryResponse().UnsealPrice, ret.QueryResponse().Message) + fmt.Printf("\rGot query response from [%s] (checking): size=%s, price-per-byte=%s, unseal-price=%s, message=%s\n", types.Identifier(ret), humanize.IBytes(ret.QueryResponse().Size), ret.QueryResponse().MinPricePerByte, ret.QueryResponse().UnsealPrice, ret.QueryResponse().Message) case events.RetrievalEventQueryAskedFiltered: - fmt.Printf("\rGot query response from [%s] (filtered): size=%s, price-per-byte=%s, unseal-price=%s, message=%s\n", ret.StorageProviderId(), humanize.IBytes(ret.QueryResponse().Size), ret.QueryResponse().MinPricePerByte, ret.QueryResponse().UnsealPrice, ret.QueryResponse().Message) + fmt.Printf("\rGot query response from [%s] (filtered): size=%s, price-per-byte=%s, unseal-price=%s, message=%s\n", types.Identifier(ret), humanize.IBytes(ret.QueryResponse().Size), ret.QueryResponse().MinPricePerByte, ret.QueryResponse().UnsealPrice, ret.QueryResponse().Message) case events.RetrievalEventFailed: if ret.Phase() == types.IndexerPhase { fmt.Printf("\rRetrieval failure from indexer: %s\n", ret.ErrorMessage()) } else { - fmt.Printf("\rRetrieval failure for [%s]: %s\n", ret.StorageProviderId(), ret.ErrorMessage()) + fmt.Printf("\rRetrieval failure for [%s]: %s\n", types.Identifier(ret), ret.ErrorMessage()) } case events.RetrievalEventSuccess: // noop, handled at return from Retrieve() diff --git a/cmd/lassie/main.go b/cmd/lassie/main.go index 38172fa1..e0fd60f5 100644 --- a/cmd/lassie/main.go +++ b/cmd/lassie/main.go @@ -60,6 +60,8 @@ func before(cctx *cli.Context) error { subsystems := []string{ "lassie", "lassie/httpserver", + "indexerlookup", + "lassie/bitswap", } level := "WARN" diff --git a/go.mod b/go.mod index c24d97ff..4b79c91c 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/benbjohnson/clock v1.3.0 github.com/dustin/go-humanize v1.0.0 github.com/filecoin-project/go-address v1.1.0 - github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc2 + github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc2.0.20230214154556-9cac4a18a101 github.com/filecoin-project/go-fil-markets v1.25.3-0.20230107010325-143abaddd0f3 github.com/filecoin-project/go-state-types v0.9.9 github.com/filecoin-project/index-provider v0.9.2 @@ -18,12 +18,17 @@ require ( github.com/ipfs/go-cid v0.3.2 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-graphsync v0.14.0 + github.com/ipfs/go-ipfs-blockstore v1.2.0 + github.com/ipfs/go-ipfs-blocksutil v0.0.1 + github.com/ipfs/go-ipfs-delay v0.0.1 github.com/ipfs/go-ipld-format v0.4.0 + github.com/ipfs/go-libipfs v0.5.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipld/go-car/v2 v2.7.0 - github.com/ipld/go-ipld-prime v0.19.1-0.20230210001044-7b00b1490f0b + github.com/ipld/go-ipld-prime v0.20.0 github.com/ipni/storetheindex v0.5.4 - github.com/libp2p/go-libp2p v0.23.4 + github.com/libp2p/go-libp2p v0.25.1 + github.com/libp2p/go-libp2p-testing v0.12.0 github.com/multiformats/go-multiaddr v0.8.0 github.com/multiformats/go-multicodec v0.8.0 github.com/multiformats/go-multihash v0.2.1 @@ -38,13 +43,19 @@ require ( golang.org/x/net v0.5.0 ) +require ( + github.com/ipfs/go-ipfs-chunker v0.0.5 // indirect + github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect +) + require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bep/debounce v1.2.1 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/cgroups v1.0.4 // indirect - github.com/coreos/go-systemd/v22 v22.4.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cskr/pubsub v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect @@ -60,7 +71,6 @@ require ( github.com/filecoin-project/go-statestore v0.2.0 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.2.3 // indirect @@ -72,32 +82,35 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/hannahhoward/cbor-gen-for v0.0.0-20200817222906-ea96cece81f1 // indirect + github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect + github.com/hannahhoward/cbor-gen-for v0.0.0-20230214144701-5d17c9d5243c // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/huin/goupnp v1.0.3 // indirect github.com/ipfs/bbloom v0.0.4 // indirect + github.com/ipfs/go-bitfield v1.1.0 // indirect github.com/ipfs/go-block-format v0.1.1 // indirect - github.com/ipfs/go-blockservice v0.5.0 // indirect - github.com/ipfs/go-ipfs-blockstore v1.2.0 // indirect + github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect - github.com/ipfs/go-ipfs-exchange-interface v0.2.0 // indirect + github.com/ipfs/go-ipfs-exchange-interface v0.2.0 github.com/ipfs/go-ipfs-pq v0.0.2 // indirect github.com/ipfs/go-ipfs-util v0.0.2 // indirect github.com/ipfs/go-ipld-cbor v0.0.6 // indirect github.com/ipfs/go-ipld-legacy v0.1.1 // indirect - github.com/ipfs/go-libipfs v0.4.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-merkledag v0.9.0 // indirect github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipfs/go-peertaskqueue v0.8.0 // indirect + github.com/ipfs/go-unixfsnode v1.5.2 github.com/ipfs/go-verifcid v0.0.2 // indirect - github.com/ipld/go-codec-dagpb v1.5.0 // indirect + github.com/ipld/go-codec-dagpb v1.5.0 github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/jpillora/backoff v1.0.0 // indirect - github.com/klauspost/compress v1.15.10 // indirect + github.com/klauspost/compress v1.15.12 // indirect github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/koron/go-ssdp v0.0.3 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -106,19 +119,16 @@ require ( github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect - github.com/libp2p/go-msgio v0.2.0 // indirect + github.com/libp2p/go-libp2p-record v0.2.0 // indirect + github.com/libp2p/go-libp2p-routing-helpers v0.6.1 + github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect - github.com/libp2p/go-netroute v0.2.0 // indirect - github.com/libp2p/go-openssl v0.1.0 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-reuseport v0.2.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.0 // indirect - github.com/lucas-clemente/quic-go v0.29.1 // indirect - github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect - github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mattn/go-pointer v0.0.1 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.50 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -130,10 +140,9 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.1.1 // indirect - github.com/multiformats/go-multistream v0.3.3 // indirect + github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/nxadm/tail v1.4.8 // indirect - github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/ginkgo/v2 v2.5.1 // indirect github.com/opencontainers/runtime-spec v1.0.2 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -145,26 +154,35 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/statsd_exporter v0.22.7 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-18 v0.2.0 // indirect + github.com/quic-go/qtls-go1-19 v0.2.0 // indirect + github.com/quic-go/qtls-go1-20 v0.1.0 // indirect + github.com/quic-go/quic-go v0.32.0 // indirect + github.com/quic-go/webtransport-go v0.5.1 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + go.opentelemetry.io/otel/sdk v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect + go.uber.org/dig v1.15.0 // indirect + go.uber.org/fx v1.18.2 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.5.0 // indirect golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.4.0 // indirect - golang.org/x/tools v0.2.0 // indirect + golang.org/x/text v0.6.0 // indirect + golang.org/x/tools v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/protobuf v1.28.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.1.7 // indirect + nhooyr.io/websocket v1.8.7 // indirect ) diff --git a/go.sum b/go.sum index c85097db..5ffca618 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,9 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -76,8 +77,8 @@ github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.4.0 h1:y9YHcjnjynCd/DVbg5j9L/33jQM3MxJlbj/zWskzfGU= -github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= @@ -85,6 +86,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= +github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -128,8 +130,8 @@ github.com/filecoin-project/go-commp-utils/nonffi v0.0.0-20220905160352-62059082 github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMXdBnCiXjfCYx/hLqFxccPoqsSveQFxVLvNxy9bus= github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= github.com/filecoin-project/go-data-transfer v1.15.2 h1:PzqsFr2Q/onMGKrGh7TtRT0dKsJcVJrioJJnjnKmxlk= -github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc2 h1:O0SKeCU9eokmERkOFEzgW2vvFuBSQeLAywvUSao+Gh0= -github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc2/go.mod h1:DckrS1zxrtRNtIu3YdylWUKR4ZyJYNGHnox9n1eY/O4= +github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc2.0.20230214154556-9cac4a18a101 h1:z/keDQ6IJ2NjExxaWbKUgamjn607TYLPMOQmM/Kdhms= +github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc2.0.20230214154556-9cac4a18a101/go.mod h1:1WDoUgWYB2KvogfPTdDmoyBTa9cb9+oGwqKCCipnJeY= github.com/filecoin-project/go-ds-versioning v0.1.2 h1:to4pTadv3IeV1wvgbCbN6Vqd+fu+7tveXgv/rCEZy6w= github.com/filecoin-project/go-ds-versioning v0.1.2/go.mod h1:C9/l9PnB1+mwPa26BBVpCjG/XQCB0yj/q5CK2J8X1I4= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= @@ -171,10 +173,11 @@ github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -197,10 +200,23 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -274,6 +290,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -286,23 +304,25 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f h1:KMlcu9X58lhTA/KrfX8Bi1LQSO4pzoVjTiL3h4Jk+Zk= github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= -github.com/hannahhoward/cbor-gen-for v0.0.0-20200817222906-ea96cece81f1 h1:F9k+7wv5OIk1zcq23QpdiL0hfDuXPjuOmMNaC6fgQ0Q= -github.com/hannahhoward/cbor-gen-for v0.0.0-20200817222906-ea96cece81f1/go.mod h1:jvfsLIxk0fY/2BKSQ1xf2406AKA5dwMmKKv0ADcOfN8= +github.com/hannahhoward/cbor-gen-for v0.0.0-20230214144701-5d17c9d5243c h1:iiD+p+U0M6n/FsO6XIZuOgobnNa48FxtyYFfWwLttUQ= +github.com/hannahhoward/cbor-gen-for v0.0.0-20230214144701-5d17c9d5243c/go.mod h1:jvfsLIxk0fY/2BKSQ1xf2406AKA5dwMmKKv0ADcOfN8= github.com/hannahhoward/go-pubsub v1.0.0 h1:yONMbY9blu+FFlamGzRZVocoY6WHPJa08h3yX7nOGuA= github.com/hannahhoward/go-pubsub v1.0.0/go.mod h1:3lHsAt5uM7YFHauT5whoifwfgIgVwEX2fMDxPDrkpU4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= @@ -310,7 +330,8 @@ github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/go-bitfield v1.0.0 h1:y/XHm2GEmD9wKngheWNNCNL0pzrWXZwCdQGv1ikXknQ= +github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= +github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= @@ -345,9 +366,12 @@ github.com/ipfs/go-hamt-ipld v0.1.1/go.mod h1:1EZCr2v0jlCnhpa+aZ0JZYp8Tt2w16+JJO github.com/ipfs/go-ipfs-blockstore v1.2.0 h1:n3WTeJ4LdICWs/0VSfjHrlqpPpl6MZ+ySd3j8qz0ykw= github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= +github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8= +github.com/ipfs/go-ipfs-chunker v0.0.5/go.mod h1:jhgdF8vxRHycr00k13FM8Y0E+6BoalYeobXmUyTreP8= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= +github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= github.com/ipfs/go-ipfs-exchange-interface v0.2.0 h1:8lMSJmKogZYNo2jjhUs0izT+dck05pqUw4mWNW9Pw6Y= @@ -374,8 +398,8 @@ github.com/ipfs/go-ipld-format v0.4.0 h1:yqJSaJftjmjc9jEOFYlpkwOLVKv68OD27jFLlSg github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2cdcc= github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= -github.com/ipfs/go-libipfs v0.4.0 h1:TkUxJGjtPnSzAgkw7VjS0/DBay3MPjmTBa4dGdUQCDE= -github.com/ipfs/go-libipfs v0.4.0/go.mod h1:XsU2cP9jBhDrXoJDe0WxikB8XcVmD3k2MEZvB3dbYu8= +github.com/ipfs/go-libipfs v0.5.0 h1:gtvzeoTdUHPUN4B5izzyBS3Cxtpvi+l7hd2mmjN+teM= +github.com/ipfs/go-libipfs v0.5.0/go.mod h1:iZ9QyhzNr3AkxRXrbQYb//rv7iLyvZJX0GNuc3lJDiQ= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.0/go.mod h1:JO7RzlMK6rA+CIxFMLOuB6Wf5b81GDiKElL7UPSIKjA= github.com/ipfs/go-log v1.0.1/go.mod h1:HuWlQttfN6FWNHRhlY5yMk/lW7evQC0HHGOxEwMRR8I= @@ -394,19 +418,20 @@ github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fG github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-peertaskqueue v0.8.0 h1:JyNO144tfu9bx6Hpo119zvbEL9iQ760FHOiJYsUjqaU= github.com/ipfs/go-peertaskqueue v0.8.0/go.mod h1:cz8hEnnARq4Du5TGqiWKgMr/BOSQ5XOgMOh1K5YYKKM= -github.com/ipfs/go-unixfs v0.4.0 h1:qSyyxfB/OiDdWHYiSbyaqKC7zfSE/TFL0QdwkRjBm20= -github.com/ipfs/go-unixfsnode v1.5.1 h1:JcR3t5C2nM1V7PMzhJ/Qmo19NkoFIKweDSZyDx+CjkI= +github.com/ipfs/go-unixfs v0.4.3 h1:EdDc1sNZNFDUlo4UrVAvvAofVI5EwTnKu8Nv8mgXkWQ= +github.com/ipfs/go-unixfsnode v1.5.2 h1:CvsiTt58W2uR5dD8bqQv+aAY0c1qolmXmSyNbPHYiew= +github.com/ipfs/go-unixfsnode v1.5.2/go.mod h1:NlOebRwYx8lMCNMdhAhEspYPBD3obp7TE0LvBqHY+ks= github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs= github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU= -github.com/ipld/go-car v0.4.0 h1:U6W7F1aKF/OJMHovnOVdst2cpQE5GhmHibQkAixgNcQ= +github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8= github.com/ipld/go-car/v2 v2.7.0 h1:OFxJl6X6Ii7y7wYX4R+P8q9+vuz4vaY2Y9u1GHzfxbE= github.com/ipld/go-car/v2 v2.7.0/go.mod h1:qoqfgPnQYcaAYcfphctffdaNWJIWBR2QN4pjuKUtgao= github.com/ipld/go-codec-dagpb v1.5.0 h1:RspDRdsJpLfgCI0ONhTAnbHdySGD4t+LHSPK4X1+R0k= github.com/ipld/go-codec-dagpb v1.5.0/go.mod h1:0yRIutEFD8o1DGVqw4RSHh+BUTlJA9XWldxaaWR/o4g= github.com/ipld/go-ipld-adl-hamt v0.0.0-20220616142416-9004dbd839e0 h1:QAI/Ridj0+foHD6epbxmB4ugxz9B4vmNdYSmQLGa05E= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= -github.com/ipld/go-ipld-prime v0.19.1-0.20230210001044-7b00b1490f0b h1:YyDqJuZAGwCyGYHyBw6FEQgGT33RRGVRXcBjCDrXfHU= -github.com/ipld/go-ipld-prime v0.19.1-0.20230210001044-7b00b1490f0b/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= +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/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo= github.com/ipni/storetheindex v0.5.4 h1:K6kL8zy5LCf+JI8HRKrrdcxjk5xPmroYxWpQttJMC7U= github.com/ipni/storetheindex v0.5.4/go.mod h1:c/NS640Iu2NrCCIErnUhsUM5KVEyeXymgtNnx6eDwMU= @@ -416,6 +441,7 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c h1:uUx61FiAa1GI6ZmVd2wf2vULeQZIKG66eybjNXKYCz4= +github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c/go.mod h1:sdx1xVM9UuLw1tXnhJWN3piypTUO3vCIHYmG15KE/dU= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= @@ -426,8 +452,10 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0 github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -440,8 +468,9 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo= -github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= @@ -462,6 +491,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -469,48 +500,42 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= -github.com/libp2p/go-libp2p v0.23.4 h1:hWi9XHSOVFR1oDWRk7rigfyA4XNMuYL20INNybP9LP8= -github.com/libp2p/go-libp2p v0.23.4/go.mod h1:s9DEa5NLR4g+LZS+md5uGU4emjMWFiqkZr6hBTY8UxI= +github.com/libp2p/go-libp2p v0.25.1 h1:YK+YDCHpYyTvitKWVxa5PfElgIpOONU01X5UcLEwJGA= +github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-core v0.20.1 h1:fQz4BJyIFmSZAiTbKV8qoYhEH5Dtv/cVhZbG3Ib/+Cw= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= +github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= +github.com/libp2p/go-libp2p-routing-helpers v0.6.1 h1:tI3rHOf/FDQsxC2pHBaOZiqPJ0MZYyzGAf4V45xla4U= +github.com/libp2p/go-libp2p-routing-helpers v0.6.1/go.mod h1:R289GUxUMzRXIbWGSuUUTPrlVJZ3Y/pPz495+qgXJX8= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= -github.com/libp2p/go-msgio v0.2.0 h1:W6shmB+FeynDrUVl2dgFQvzfBZcXiyqY4VmpQLu9FqU= -github.com/libp2p/go-msgio v0.2.0/go.mod h1:dBVM1gW3Jk9XqHkU4eKdGvVHdLa51hoGfll6jMJMSlY= +github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= +github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= +github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= -github.com/libp2p/go-netroute v0.2.0 h1:0FpsbsvuSnAhXFnCY0VLFbJOzaK0VnP0r1QT/o4nWRE= -github.com/libp2p/go-netroute v0.2.0/go.mod h1:Vio7LTzZ+6hoT4CMZi5/6CpY3Snzh2vgZhWgxMNwlQI= -github.com/libp2p/go-openssl v0.1.0 h1:LBkKEcUv6vtZIQLVTegAil8jbNpJErQ9AnT+bWV+Ooo= -github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc= +github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= +github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/lucas-clemente/quic-go v0.29.1 h1:Z+WMJ++qMLhvpFkRZA+jl3BTxUjm415YBmWanXB8zP0= -github.com/lucas-clemente/quic-go v0.29.1/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= -github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= -github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= -github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU= -github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/marten-seemann/webtransport-go v0.1.1 h1:TnyKp3pEXcDooTaNn4s9dYpMJ7kMnTp7k5h+SgYP/mc= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= -github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= @@ -529,9 +554,11 @@ github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -567,8 +594,8 @@ github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= -github.com/multiformats/go-multistream v0.3.3 h1:d5PZpjwRgVlbwfdTDjife7XszfZd8KYWfROYFlGcR8o= -github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= +github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= +github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= @@ -578,16 +605,9 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= +github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= +github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -645,6 +665,18 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U= +github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= +github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk= +github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI= +github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA= +github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= +github.com/quic-go/webtransport-go v0.5.1 h1:1eVb7WDWCRoaeTtFHpFBJ6WDN1bSrPrRoW6tZgSw0Ow= +github.com/quic-go/webtransport-go v0.5.1/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= @@ -698,8 +730,6 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= -github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -721,6 +751,10 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cb github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.0.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= @@ -751,6 +785,7 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20210118024343-169e9d70c0c2/go.mod h1:f github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa h1:EyA027ZAkuaCLoxVX4r1TZMPy1d31fM6hbfQ4OU4I5o= github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= +github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/xlab/c-for-go v0.0.0-20200718154222-87b0065af829 h1:wb7xrDzfkLgPHsSEBm+VSx6aDdi64VtV0xvP0E6j8bk= github.com/xlab/c-for-go v0.0.0-20200718154222-87b0065af829/go.mod h1:h/1PEBwj7Ym/8kOuMWvO2ujZ6Lt+TMbySEXNhjjR87I= @@ -776,6 +811,7 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v1.12.0 h1:IgfC7kqQrRccIKuB7Cl+SRUmsKbEwSGPr0Eu+/ht1SQ= go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= +go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/trace v1.12.0 h1:p28in++7Kd0r2d8gSt931O57fdjUyWxkVbESuILAeUc= go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -784,6 +820,10 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= +go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= +go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= +go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -853,8 +893,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -885,7 +925,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -895,7 +934,6 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -925,8 +963,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= -golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -949,15 +987,13 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -976,20 +1012,17 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1007,6 +1040,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1060,13 +1094,12 @@ golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1171,10 +1204,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1210,6 +1240,8 @@ modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc= modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0 h1:7ccXrupWZIS3twbUGrtKmHS2DXY6xegFua+6O3xgAFU= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/eventrecorder/eventrecorder.go b/pkg/eventrecorder/eventrecorder.go index 432cf1ae..a2aa466b 100644 --- a/pkg/eventrecorder/eventrecorder.go +++ b/pkg/eventrecorder/eventrecorder.go @@ -12,7 +12,6 @@ import ( "github.com/filecoin-project/lassie/pkg/events" "github.com/filecoin-project/lassie/pkg/types" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/peer" ) var HttpTimeout = 5 * time.Second @@ -60,7 +59,7 @@ type eventReport struct { RetrievalId types.RetrievalID `json:"retrievalId"` InstanceId string `json:"instanceId"` Cid string `json:"cid"` - StorageProviderId peer.ID `json:"storageProviderId"` + StorageProviderId string `json:"storageProviderId"` Phase types.Phase `json:"phase"` PhaseStartTime time.Time `json:"phaseStartTime"` EventName types.EventCode `json:"eventName"` @@ -89,11 +88,16 @@ func (er *EventRecorder) RecordEvent(event types.RetrievalEvent) { return } + // TODO: We really need to change the schema here to include protocols + // For now, we double up the string here, which isn't great + // -- there are no peer ids for SPs so you just record the word + // "bitswap-peer" as the SP id + evt := eventReport{ RetrievalId: event.RetrievalId(), InstanceId: er.instanceId, Cid: event.PayloadCid().String(), - StorageProviderId: event.StorageProviderId(), + StorageProviderId: types.Identifier(event), Phase: event.Phase(), PhaseStartTime: event.PhaseStartTime(), EventName: event.Code(), diff --git a/pkg/eventrecorder/eventrecorder_test.go b/pkg/eventrecorder/eventrecorder_test.go index 2f23598c..79feb16e 100644 --- a/pkg/eventrecorder/eventrecorder_test.go +++ b/pkg/eventrecorder/eventrecorder_test.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/index-provider/metadata" "github.com/filecoin-project/lassie/pkg/eventrecorder" "github.com/filecoin-project/lassie/pkg/events" "github.com/filecoin-project/lassie/pkg/types" @@ -130,6 +131,40 @@ func TestEventRecorder(t *testing.T) { verifyIntNode(t, detailsNode, "durationMs", 4000) }, }, + { + name: "RetrievalSuccess, bitswap", + exec: func(t *testing.T, ctx context.Context, er *eventrecorder.EventRecorder, id types.RetrievalID, etime, ptime time.Time, spid peer.ID) { + er.RecordEvent(events.Success(id, ptime, types.NewRetrievalCandidate(peer.ID(""), testCid1, metadata.Bitswap{}), uint64(2020), 3030, 4*time.Second, big.Zero())) + + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case <-receivedChan: + } + + qt.Assert(t, req.Length(), qt.Equals, int64(1)) + eventList := verifyListNode(t, req, "events", 1) + event := verifyListElement(t, eventList, 0) + qt.Assert(t, event.Length(), qt.Equals, int64(9)) + verifyStringNode(t, event, "retrievalId", id.String()) + verifyStringNode(t, event, "instanceId", "test-instance") + verifyStringNode(t, event, "cid", testCid1.String()) + verifyStringNode(t, event, "storageProviderId", types.BitswapIndentifier) + verifyStringNode(t, event, "phase", "retrieval") + verifyStringNode(t, event, "phaseStartTime", ptime.Format(time.RFC3339Nano)) + verifyStringNode(t, event, "eventName", "success") + atime, err := time.Parse(time.RFC3339Nano, nodeToString(t, event, "eventTime")) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, etime.Sub(atime) < 50*time.Millisecond, qt.IsTrue) + + detailsNode, err := event.LookupByString("eventDetails") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, detailsNode.Length(), qt.Equals, int64(3)) + verifyIntNode(t, detailsNode, "receivedSize", 2020) + verifyIntNode(t, detailsNode, "receivedCids", 3030) + verifyIntNode(t, detailsNode, "durationMs", 4000) + }, + }, { name: "QueryFailure", exec: func(t *testing.T, ctx context.Context, er *eventrecorder.EventRecorder, id types.RetrievalID, etime, ptime time.Time, spid peer.ID) { @@ -193,6 +228,38 @@ func TestEventRecorder(t *testing.T) { verifyStringNode(t, detailsNode, "error", "ha ha no, silly silly") }, }, + { + name: "RetrievalFailure, bitswap", + exec: func(t *testing.T, ctx context.Context, er *eventrecorder.EventRecorder, id types.RetrievalID, etime, ptime time.Time, spid peer.ID) { + er.RecordEvent(events.Failed(id, ptime, types.RetrievalPhase, types.NewRetrievalCandidate(peer.ID(""), testCid1, metadata.Bitswap{}), "ha ha no, silly silly")) + + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case <-receivedChan: + } + + qt.Assert(t, req.Length(), qt.Equals, int64(1)) + eventList := verifyListNode(t, req, "events", 1) + event := verifyListElement(t, eventList, 0) + qt.Assert(t, event.Length(), qt.Equals, int64(9)) + verifyStringNode(t, event, "retrievalId", id.String()) + verifyStringNode(t, event, "instanceId", "test-instance") + verifyStringNode(t, event, "cid", testCid1.String()) + verifyStringNode(t, event, "storageProviderId", types.BitswapIndentifier) + verifyStringNode(t, event, "phase", "retrieval") + verifyStringNode(t, event, "phaseStartTime", ptime.Format(time.RFC3339Nano)) + verifyStringNode(t, event, "eventName", "failure") + atime, err := time.Parse(time.RFC3339Nano, nodeToString(t, event, "eventTime")) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, etime.Sub(atime) < 50*time.Millisecond, qt.IsTrue) + + detailsNode, err := event.LookupByString("eventDetails") + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, detailsNode.Length(), qt.Equals, int64(1)) + verifyStringNode(t, detailsNode, "error", "ha ha no, silly silly") + }, + }, { name: "QueryProgress", exec: func(t *testing.T, ctx context.Context, er *eventrecorder.EventRecorder, id types.RetrievalID, etime, ptime time.Time, spid peer.ID) { diff --git a/pkg/events/events.go b/pkg/events/events.go index 66a7443c..8729c973 100644 --- a/pkg/events/events.go +++ b/pkg/events/events.go @@ -9,6 +9,7 @@ import ( "github.com/filecoin-project/lassie/pkg/types" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multicodec" ) var ( @@ -37,12 +38,14 @@ type baseEvent struct { retrievalId types.RetrievalID phaseStartTime time.Time payloadCid cid.Cid + protocols []multicodec.Code } func (r baseEvent) Time() time.Time { return r.eventTime } func (r baseEvent) RetrievalId() types.RetrievalID { return r.retrievalId } func (r baseEvent) PhaseStartTime() time.Time { return r.phaseStartTime } func (r baseEvent) PayloadCid() cid.Cid { return r.payloadCid } +func (r baseEvent) Protocols() []multicodec.Code { return r.protocols } type indexerEvent struct { baseEvent @@ -56,10 +59,24 @@ type RetrievalEventCandidatesFound struct { indexerEvent } +func collectProtocols(candidates []types.RetrievalCandidate) []multicodec.Code { + allProtocols := make(map[multicodec.Code]struct{}) + for _, candidate := range candidates { + for _, protocol := range candidate.Metadata.Protocols() { + allProtocols[protocol] = struct{}{} + } + } + allProtocolsArr := make([]multicodec.Code, 0, len(allProtocols)) + for protocol := range allProtocols { + allProtocolsArr = append(allProtocolsArr, protocol) + } + return allProtocolsArr +} + func CandidatesFound(retrievalId types.RetrievalID, phaseStartTime time.Time, payloadCid cid.Cid, candidates []types.RetrievalCandidate) RetrievalEventCandidatesFound { c := make([]types.RetrievalCandidate, len(candidates)) copy(c, candidates) - return RetrievalEventCandidatesFound{indexerEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, payloadCid}, c}} + return RetrievalEventCandidatesFound{indexerEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, payloadCid, collectProtocols(candidates)}, c}} } type RetrievalEventCandidatesFiltered struct { @@ -69,7 +86,7 @@ type RetrievalEventCandidatesFiltered struct { func CandidatesFiltered(retrievalId types.RetrievalID, phaseStartTime time.Time, payloadCid cid.Cid, candidates []types.RetrievalCandidate) RetrievalEventCandidatesFiltered { c := make([]types.RetrievalCandidate, len(candidates)) copy(c, candidates) - return RetrievalEventCandidatesFiltered{indexerEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, payloadCid}, c}} + return RetrievalEventCandidatesFiltered{indexerEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, payloadCid, collectProtocols(candidates)}, c}} } type spBaseEvent struct { @@ -85,7 +102,8 @@ type RetrievalEventConnected struct { func (r spBaseEvent) StorageProviderId() peer.ID { return r.storageProviderId } func Connected(retrievalId types.RetrievalID, phaseStartTime time.Time, phase types.Phase, candidate types.RetrievalCandidate) RetrievalEventConnected { - return RetrievalEventConnected{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}, phase} + candidate.Metadata.Protocols() + return RetrievalEventConnected{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}, phase} } type RetrievalEventQueryAsked struct { @@ -94,7 +112,7 @@ type RetrievalEventQueryAsked struct { } func QueryAsked(retrievalId types.RetrievalID, phaseStartTime time.Time, candidate types.RetrievalCandidate, queryResponse retrievalmarket.QueryResponse) RetrievalEventQueryAsked { - return RetrievalEventQueryAsked{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}, queryResponse} + return RetrievalEventQueryAsked{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}, queryResponse} } type RetrievalEventQueryAskedFiltered struct { @@ -103,7 +121,7 @@ type RetrievalEventQueryAskedFiltered struct { } func QueryAskedFiltered(retrievalId types.RetrievalID, phaseStartTime time.Time, candidate types.RetrievalCandidate, queryResponse retrievalmarket.QueryResponse) RetrievalEventQueryAskedFiltered { - return RetrievalEventQueryAskedFiltered{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}, queryResponse} + return RetrievalEventQueryAskedFiltered{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}, queryResponse} } type RetrievalEventProposed struct { @@ -111,7 +129,7 @@ type RetrievalEventProposed struct { } func Proposed(retrievalId types.RetrievalID, phaseStartTime time.Time, candidate types.RetrievalCandidate) RetrievalEventProposed { - return RetrievalEventProposed{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}} + return RetrievalEventProposed{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}} } type RetrievalEventStarted struct { @@ -120,7 +138,7 @@ type RetrievalEventStarted struct { } func Started(retrievalId types.RetrievalID, phaseStartTime time.Time, phase types.Phase, candidate types.RetrievalCandidate) RetrievalEventStarted { - return RetrievalEventStarted{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}, phase} + return RetrievalEventStarted{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}, phase} } type RetrievalEventAccepted struct { @@ -128,7 +146,7 @@ type RetrievalEventAccepted struct { } func Accepted(retrievalId types.RetrievalID, phaseStartTime time.Time, candidate types.RetrievalCandidate) RetrievalEventAccepted { - return RetrievalEventAccepted{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}} + return RetrievalEventAccepted{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}} } type RetrievalEventFirstByte struct { @@ -136,7 +154,7 @@ type RetrievalEventFirstByte struct { } func FirstByte(retrievalId types.RetrievalID, phaseStartTime time.Time, candidate types.RetrievalCandidate) RetrievalEventFirstByte { - return RetrievalEventFirstByte{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}} + return RetrievalEventFirstByte{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}} } type RetrievalEventFailed struct { @@ -146,7 +164,7 @@ type RetrievalEventFailed struct { } func Failed(retrievalId types.RetrievalID, phaseStartTime time.Time, phase types.Phase, candidate types.RetrievalCandidate, errorMessage string) RetrievalEventFailed { - return RetrievalEventFailed{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}, phase, errorMessage} + return RetrievalEventFailed{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}, phase, errorMessage} } type RetrievalEventSuccess struct { @@ -158,28 +176,28 @@ type RetrievalEventSuccess struct { } func Success(retrievalId types.RetrievalID, phaseStartTime time.Time, candidate types.RetrievalCandidate, receivedSize uint64, receivedCids uint64, duration time.Duration, totalPayment big.Int) RetrievalEventSuccess { - return RetrievalEventSuccess{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid}, candidate.MinerPeer.ID}, receivedSize, receivedCids, duration, totalPayment} + return RetrievalEventSuccess{spBaseEvent{baseEvent{time.Now(), retrievalId, phaseStartTime, candidate.RootCid, candidate.Metadata.Protocols()}, candidate.MinerPeer.ID}, receivedSize, receivedCids, duration, totalPayment} } func (r RetrievalEventCandidatesFound) Code() types.EventCode { return types.CandidatesFoundCode } func (r RetrievalEventCandidatesFound) Phase() types.Phase { return types.IndexerPhase } func (r RetrievalEventCandidatesFound) String() string { - return fmt.Sprintf("CandidatesFoundEvent<%s, %s, %s, %d>", r.eventTime, r.retrievalId, r.payloadCid, len(r.candidates)) + return fmt.Sprintf("CandidatesFoundEvent<%s, %s, %s, %d, %v>", r.eventTime, r.retrievalId, r.payloadCid, len(r.candidates), r.protocols) } func (r RetrievalEventCandidatesFiltered) Code() types.EventCode { return types.CandidatesFilteredCode } func (r RetrievalEventCandidatesFiltered) Phase() types.Phase { return types.IndexerPhase } func (r RetrievalEventCandidatesFiltered) String() string { - return fmt.Sprintf("CandidatesFilteredEvent<%s, %s, %s, %d>", r.eventTime, r.retrievalId, r.payloadCid, len(r.candidates)) + return fmt.Sprintf("CandidatesFilteredEvent<%s, %s, %s, %d, %v>", r.eventTime, r.retrievalId, r.payloadCid, len(r.candidates), r.Protocols()) } func (r RetrievalEventStarted) Code() types.EventCode { return types.StartedCode } func (r RetrievalEventStarted) Phase() types.Phase { return r.phase } func (r RetrievalEventStarted) String() string { - return fmt.Sprintf("StartedEvent<%s, %s, %s, %s, %s>", r.phase, r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId) + return fmt.Sprintf("StartedEvent<%s, %s, %s, %s, %s, %v>", r.phase, r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols) } func (r RetrievalEventConnected) Code() types.EventCode { return types.ConnectedCode } func (r RetrievalEventConnected) Phase() types.Phase { return r.phase } func (r RetrievalEventConnected) String() string { - return fmt.Sprintf("ConnectedEvent<%s, %s, %s, %s, %s>", r.phase, r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId) + return fmt.Sprintf("ConnectedEvent<%s, %s, %s, %s, %s, %v>", r.phase, r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols) } func (r RetrievalEventQueryAsked) Code() types.EventCode { return types.QueryAskedCode } func (r RetrievalEventQueryAsked) Phase() types.Phase { return types.QueryPhase } @@ -187,7 +205,7 @@ func (r RetrievalEventQueryAsked) QueryResponse() retrievalmarket.QueryResponse return r.queryResponse } func (r RetrievalEventQueryAsked) String() string { - return fmt.Sprintf("QueryAsked<%s, %s, %s, %s, {%d, %d, %s, %d, %d}>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.queryResponse.Status, r.queryResponse.Size, r.queryResponse.MinPricePerByte, r.queryResponse.MaxPaymentInterval, r.queryResponse.MaxPaymentIntervalIncrease) + return fmt.Sprintf("QueryAsked<%s, %s, %s, %s, %v, {%d, %d, %s, %d, %d}>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols, r.queryResponse.Status, r.queryResponse.Size, r.queryResponse.MinPricePerByte, r.queryResponse.MaxPaymentInterval, r.queryResponse.MaxPaymentIntervalIncrease) } func (r RetrievalEventQueryAskedFiltered) Code() types.EventCode { return types.QueryAskedFilteredCode } func (r RetrievalEventQueryAskedFiltered) Phase() types.Phase { return types.QueryPhase } @@ -195,22 +213,22 @@ func (r RetrievalEventQueryAskedFiltered) QueryResponse() retrievalmarket.QueryR return r.queryResponse } // QueryResponse returns the response from a storage provider to a query-ask func (r RetrievalEventQueryAskedFiltered) String() string { - return fmt.Sprintf("QueryAskedFiltered<%s, %s, %s, %s, {%d, %d, %s, %d, %d}>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.queryResponse.Status, r.queryResponse.Size, r.queryResponse.MinPricePerByte, r.queryResponse.MaxPaymentInterval, r.queryResponse.MaxPaymentIntervalIncrease) + return fmt.Sprintf("QueryAskedFiltered<%s, %s, %s, %s, %v, {%d, %d, %s, %d, %d}>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols, r.queryResponse.Status, r.queryResponse.Size, r.queryResponse.MinPricePerByte, r.queryResponse.MaxPaymentInterval, r.queryResponse.MaxPaymentIntervalIncrease) } func (r RetrievalEventProposed) Code() types.EventCode { return types.ProposedCode } func (r RetrievalEventProposed) Phase() types.Phase { return types.RetrievalPhase } func (r RetrievalEventProposed) String() string { - return fmt.Sprintf("ProposedEvent<%s, %s, %s, %s>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId) + return fmt.Sprintf("ProposedEvent<%s, %s, %s, %s, %v>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols) } func (r RetrievalEventAccepted) Code() types.EventCode { return types.AcceptedCode } func (r RetrievalEventAccepted) Phase() types.Phase { return types.RetrievalPhase } func (r RetrievalEventAccepted) String() string { - return fmt.Sprintf("AcceptedEvent<%s, %s, %s, %s>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId) + return fmt.Sprintf("AcceptedEvent<%s, %s, %s, %s, %v>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols) } func (r RetrievalEventFirstByte) Code() types.EventCode { return types.FirstByteCode } func (r RetrievalEventFirstByte) Phase() types.Phase { return types.RetrievalPhase } func (r RetrievalEventFirstByte) String() string { - return fmt.Sprintf("FirstByteEvent<%s, %s, %s, %s>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId) + return fmt.Sprintf("FirstByteEvent<%s, %s, %s, %s, %v>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols) } func (r RetrievalEventFailed) Code() types.EventCode { return types.FailedCode } func (r RetrievalEventFailed) Phase() types.Phase { return r.phase } @@ -219,7 +237,7 @@ func (r RetrievalEventFailed) Phase() types.Phase { return r.phase } // failure func (r RetrievalEventFailed) ErrorMessage() string { return r.errorMessage } func (r RetrievalEventFailed) String() string { - return fmt.Sprintf("FailedEvent<%s, %s, %s, %s, %s>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.errorMessage) + return fmt.Sprintf("FailedEvent<%s, %s, %s, %s, %v, %s>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols, r.errorMessage) } func (r RetrievalEventSuccess) Code() types.EventCode { return types.SuccessCode } func (r RetrievalEventSuccess) Phase() types.Phase { return types.RetrievalPhase } @@ -234,5 +252,5 @@ func (r RetrievalEventSuccess) ReceivedSize() uint64 { return r.receivedSize } // equal the total number of blocks transferred func (r RetrievalEventSuccess) ReceivedCids() uint64 { return r.receivedCids } func (r RetrievalEventSuccess) String() string { - return fmt.Sprintf("SuccessEvent<%s, %s, %s, %s, { %s, %s, %d, %d }>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.duration, r.totalPayment, r.receivedSize, r.receivedCids) + return fmt.Sprintf("SuccessEvent<%s, %s, %s, %s, %v, { %s, %s, %d, %d }>", r.eventTime, r.retrievalId, r.payloadCid, r.storageProviderId, r.protocols, r.duration, r.totalPayment, r.receivedSize, r.receivedCids) } diff --git a/pkg/indexerlookup/candidatefinder.go b/pkg/indexerlookup/candidatefinder.go index fe329faf..a281fac9 100644 --- a/pkg/indexerlookup/candidatefinder.go +++ b/pkg/indexerlookup/candidatefinder.go @@ -43,7 +43,9 @@ func NewCandidateFinder(o ...Option) (*IndexerCandidateFinder, error) { func (idxf *IndexerCandidateFinder) sendJsonRequest(req *http.Request) (*model.FindResponse, error) { req.Header.Set("Accept", "application/json") + logger.Debugw("sending outgoing request", "url", req.URL, "accept", req.Header.Get("Accept")) resp, err := idxf.httpClient.Do(req) + if err != nil { logger.Debugw("Failed to perform json lookup", "err", err) return nil, err @@ -117,6 +119,7 @@ func (idxf *IndexerCandidateFinder) FindCandidatesAsync(ctx context.Context, c c return nil, err } req.Header.Set("Accept", "application/x-ndjson") + logger.Debugw("sending outgoing request", "url", req.URL, "accept", req.Header.Get("Accept")) resp, err := idxf.httpClient.Do(req) if err != nil { logger.Debugw("Failed to perform streaming lookup", "err", err) @@ -191,5 +194,5 @@ func (idxf *IndexerCandidateFinder) decodeProviderResultStream(ctx context.Conte func (idxf *IndexerCandidateFinder) findByMultihashEndpoint(mh multihash.Multihash) string { // TODO: Replace with URL.JoinPath once minimum go version in CI is updated to 1.19; like this: // return idxf.httpEndpoint.JoinPath("multihash", mh.B58String()).String() - return idxf.httpEndpoint.String() + path.Join("/multihash", mh.B58String()) + return idxf.httpEndpoint.String() + path.Join("/multihash", mh.B58String()) + "?cascade=ipfs-dht" } diff --git a/pkg/internal/itest/bitswapfetch_test.go b/pkg/internal/itest/bitswapfetch_test.go new file mode 100644 index 00000000..1e112dae --- /dev/null +++ b/pkg/internal/itest/bitswapfetch_test.go @@ -0,0 +1,174 @@ +package itest + +import ( + "bytes" + "context" + "crypto/rand" + "fmt" + "io" + "net/http" + "testing" + "time" + + "github.com/filecoin-project/index-provider/metadata" + "github.com/filecoin-project/lassie/pkg/internal/itest/testpeer" + "github.com/filecoin-project/lassie/pkg/internal/testutil" + "github.com/filecoin-project/lassie/pkg/lassie" + httpserver "github.com/filecoin-project/lassie/pkg/server/http" + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-graphsync/storeutil" + "github.com/ipfs/go-libipfs/bitswap/network" + "github.com/ipfs/go-libipfs/bitswap/server" + "github.com/ipfs/go-unixfsnode" + "github.com/ipfs/go-unixfsnode/data/builder" + "github.com/ipld/go-car/v2/storage" + dagpb "github.com/ipld/go-codec-dagpb" + "github.com/ipld/go-ipld-prime/linking" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/libp2p/go-libp2p/core/peer" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" +) + +func TestBitswapFetchTwoPeers(t *testing.T) { + req := require.New(t) + mn := mocknet.New() + ctx := context.Background() + testPeerGenerator := testpeer.NewTestPeerGenerator(ctx, t, mn, []network.NetOpt{}, []server.Option{}) + peers := testPeerGenerator.Peers(2) + + // build two files of 4MiB random bytes, packaged into unixfs DAGs (root1 & root2) + // and the original source data retained (srcData1, srcData2) + ls := storeutil.LinkSystemForBlockstore(peers[0].Blockstore()) + delimited := io.LimitReader(rand.Reader, 1<<22) + buf := new(bytes.Buffer) + delimited = io.TeeReader(delimited, buf) + root1, _, err := builder.BuildUnixFSFile(delimited, "size-256144", &ls) + srcData1 := buf.Bytes() + req.NoError(err) + rootCid1 := root1.(cidlink.Link).Cid + ls = storeutil.LinkSystemForBlockstore(peers[1].Blockstore()) + delimited = io.LimitReader(rand.Reader, 1<<22) + buf = new(bytes.Buffer) + delimited = io.TeeReader(delimited, buf) + root2, _, err := builder.BuildUnixFSFile(delimited, "size-256144", &ls) + srcData2 := buf.Bytes() + req.NoError(err) + rootCid2 := root2.(cidlink.Link).Cid + + finder := &testutil.MockCandidateFinder{ + Candidates: map[cid.Cid][]types.RetrievalCandidate{ + rootCid1: { + { + RootCid: rootCid1, + MinerPeer: peer.AddrInfo{ + ID: peers[0].ID, + Addrs: peers[0].Host.Addrs(), + }, + Metadata: metadata.Default.New(metadata.Bitswap{}), + }, + }, + rootCid2: { + { + RootCid: rootCid2, + MinerPeer: peer.AddrInfo{ + ID: peers[1].ID, + Addrs: peers[1].Host.Addrs(), + }, + Metadata: metadata.Default.New(metadata.Bitswap{}), + }, + }, + }, + } + self, err := mn.GenPeer() + req.NoError(err) + mn.LinkAll() + + lassie, err := lassie.NewLassie(ctx, lassie.WithFinder(finder), lassie.WithHost(self), lassie.WithGlobalTimeout(5*time.Second)) + req.NoError(err) + + httpServer, err := httpserver.NewHttpServer(ctx, lassie, "127.0.0.1", 8888) + req.NoError(err) + baseURL := httpServer.Addr() + serverError := make(chan error, 1) + go func() { + err := httpServer.Start() + serverError <- err + }() + // make two requests at the same time + resp1Chan := make(chan *http.Response, 1) + resp2Chan := make(chan *http.Response, 1) + go func() { + resp, err := http.DefaultClient.Get(fmt.Sprintf("http://%s/ipfs/%s?format=car", baseURL, rootCid1)) + req.NoError(err) + resp1Chan <- resp + }() + go func() { + resp, err := http.DefaultClient.Get(fmt.Sprintf("http://%s/ipfs/%s?format=car", baseURL, rootCid2)) + req.NoError(err) + resp2Chan <- resp + }() + var resp1, resp2 *http.Response + received := 0 + for received < 2 { + select { + case resp1 = <-resp1Chan: + received++ + case resp2 = <-resp2Chan: + received++ + case <-ctx.Done(): + req.FailNow("Did not receive responses") + } + } + // verify first response + req.Equal(200, resp1.StatusCode) + carData, err := io.ReadAll(resp1.Body) + req.NoError(err) + rCar, err := storage.OpenReadable(&byteReadAt{carData}) + req.NoError(err) + outLsys := cidlink.DefaultLinkSystem() + outLsys.SetReadStorage(rCar) + outLsys.NodeReifier = unixfsnode.Reify + nd, err := outLsys.Load(linking.LinkContext{Ctx: ctx}, root1, dagpb.Type.PBNode) + req.NoError(err) + destData, err := nd.AsBytes() + req.NoError(err) + req.Equal(srcData1, destData) + + // verify second response + req.Equal(200, resp2.StatusCode) + carData, err = io.ReadAll(resp2.Body) + req.NoError(err) + rCar, err = storage.OpenReadable(&byteReadAt{carData}) + req.NoError(err) + outLsys = cidlink.DefaultLinkSystem() + outLsys.SetReadStorage(rCar) + outLsys.NodeReifier = unixfsnode.Reify + nd, err = outLsys.Load(linking.LinkContext{Ctx: ctx}, root2, dagpb.Type.PBNode) + req.NoError(err) + destData, err = nd.AsBytes() + req.NoError(err) + req.Equal(srcData2, destData) + + err = httpServer.Close() + req.NoError(err) + select { + case <-ctx.Done(): + req.FailNow("server failed to shut down") + case err = <-serverError: + req.NoError(err) + } +} + +type byteReadAt struct { + data []byte +} + +func (bra *byteReadAt) ReadAt(p []byte, off int64) (n int, err error) { + if off >= int64(len(bra.data)) { + return 0, io.EOF + } + n = copy(p, bra.data[int64(off):]) + return +} diff --git a/pkg/internal/itest/testpeer/generator.go b/pkg/internal/itest/testpeer/generator.go new file mode 100644 index 00000000..31bfd6e5 --- /dev/null +++ b/pkg/internal/itest/testpeer/generator.go @@ -0,0 +1,142 @@ +package testpeer + +import ( + "context" + "testing" + "time" + + ds "github.com/ipfs/go-datastore" + delayed "github.com/ipfs/go-datastore/delayed" + ds_sync "github.com/ipfs/go-datastore/sync" + blockstore "github.com/ipfs/go-ipfs-blockstore" + delay "github.com/ipfs/go-ipfs-delay" + bsnet "github.com/ipfs/go-libipfs/bitswap/network" + "github.com/ipfs/go-libipfs/bitswap/server" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" + tnet "github.com/libp2p/go-libp2p-testing/net" + p2ptestutil "github.com/libp2p/go-libp2p-testing/netutil" + "github.com/libp2p/go-libp2p/core/host" + peer "github.com/libp2p/go-libp2p/core/peer" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" +) + +// NewTestPeerGenerator generates a new TestPeerGenerator for the given +// mocknet +func NewTestPeerGenerator(ctx context.Context, t *testing.T, mn mocknet.Mocknet, netOptions []bsnet.NetOpt, bsOptions []server.Option) TestPeerGenerator { + ctx, cancel := context.WithCancel(ctx) + return TestPeerGenerator{ + seq: 0, + t: t, + ctx: ctx, // TODO take ctx as param to Next, Instances + mn: mn, + cancel: cancel, + bsOptions: bsOptions, + netOptions: netOptions, + } +} + +// TestPeerGenerator generates new test peers bitswap+dependencies +// TODO: add graphsync/markets stack, make protocols choosable +type TestPeerGenerator struct { + seq int + t *testing.T + mn mocknet.Mocknet + ctx context.Context + cancel context.CancelFunc + bsOptions []server.Option + netOptions []bsnet.NetOpt +} + +// Close closes the clobal context, shutting down all test peers +func (g *TestPeerGenerator) Close() error { + g.cancel() + return nil // for Closer interface +} + +// Next generates a new test peer with bitswap + dependencies +func (g *TestPeerGenerator) Next() TestPeer { + g.seq++ + p, err := p2ptestutil.RandTestBogusIdentity() + require.NoError(g.t, err) + tp, err := NewTestPeer(g.ctx, g.mn, p, g.netOptions, g.bsOptions) + require.NoError(g.t, err) + return tp +} + +// Peers creates N test peers with bitswap + dependencies +func (g *TestPeerGenerator) Peers(n int) []TestPeer { + var instances []TestPeer + for j := 0; j < n; j++ { + inst := g.Next() + instances = append(instances, inst) + } + return instances +} + +// ConnectPeers connects the given peers to each other +func ConnectPeers(instances []TestPeer) { + for i, inst := range instances { + for j := i + 1; j < len(instances); j++ { + oinst := instances[j] + err := inst.Host.Connect(context.Background(), peer.AddrInfo{ID: oinst.ID}) + if err != nil { + panic(err.Error()) + } + } + } +} + +// TestPeer is a test instance of bitswap + dependencies for integration testing +type TestPeer struct { + ID peer.ID + BitswapServer *server.Server + blockstore blockstore.Blockstore + Host host.Host + blockstoreDelay delay.D +} + +// Blockstore returns the block store for this test instance +func (i *TestPeer) Blockstore() blockstore.Blockstore { + return i.blockstore +} + +// SetBlockstoreLatency customizes the artificial delay on receiving blocks +// from a blockstore test instance. +func (i *TestPeer) SetBlockstoreLatency(t time.Duration) time.Duration { + return i.blockstoreDelay.Set(t) +} + +// NewTestPeer creates a test peer instance. +// +// NB: It's easy make mistakes by providing the same peer ID to two different +// instances. To safeguard, use the InstanceGenerator to generate instances. It's +// just a much better idea. +func NewTestPeer(ctx context.Context, mn mocknet.Mocknet, p tnet.Identity, netOptions []bsnet.NetOpt, bsOptions []server.Option) (TestPeer, error) { + bsdelay := delay.Fixed(0) + + client, err := mn.AddPeer(p.PrivateKey(), p.Address()) + if err != nil { + panic(err.Error()) + } + bsNet := bsnet.NewFromIpfsHost(client, routinghelpers.Null{}, netOptions...) + + dstore := ds_sync.MutexWrap(delayed.New(ds.NewMapDatastore(), bsdelay)) + + bstore, err := blockstore.CachedBlockstore(ctx, + blockstore.NewBlockstore(ds_sync.MutexWrap(dstore)), + blockstore.DefaultCacheOpts()) + if err != nil { + return TestPeer{}, err + } + + bs := server.New(ctx, bsNet, bstore, bsOptions...) + bsNet.Start(bs) + return TestPeer{ + Host: client, + ID: p.ID(), + BitswapServer: bs, + blockstore: bstore, + blockstoreDelay: bsdelay, + }, nil +} diff --git a/pkg/internal/libp2p.go b/pkg/internal/libp2p.go index c65ee5c1..a9b54991 100644 --- a/pkg/internal/libp2p.go +++ b/pkg/internal/libp2p.go @@ -10,5 +10,5 @@ import ( ) func InitHost(ctx context.Context, listenAddrs ...multiaddr.Multiaddr) (host.Host, error) { - return libp2p.New(libp2p.ListenAddrs(listenAddrs...), libp2p.Identity(nil), libp2p.ResourceManager(network.NullResourceManager)) + return libp2p.New(libp2p.ListenAddrs(listenAddrs...), libp2p.Identity(nil), libp2p.ResourceManager(&network.NullResourceManager{})) } diff --git a/pkg/retriever/testutil/collectingeventlsubscriber.go b/pkg/internal/testutil/collectingeventlsubscriber.go similarity index 100% rename from pkg/retriever/testutil/collectingeventlsubscriber.go rename to pkg/internal/testutil/collectingeventlsubscriber.go diff --git a/pkg/internal/testutil/gen.go b/pkg/internal/testutil/gen.go new file mode 100644 index 00000000..331f203c --- /dev/null +++ b/pkg/internal/testutil/gen.go @@ -0,0 +1,89 @@ +package testutil + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-cid" + blocksutil "github.com/ipfs/go-ipfs-blocksutil" + "github.com/ipfs/go-libipfs/blocks" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +var blockGenerator = blocksutil.NewBlockGenerator() + +// var prioritySeq int +var seedSeq int64 + +// RandomBytes returns a byte array of the given size with random values. +func RandomBytes(n int64) []byte { + data := make([]byte, n) + src := rand.NewSource(seedSeq) + seedSeq++ + r := rand.New(src) + _, _ = r.Read(data) + return data +} + +// GenerateBlocksOfSize generates a series of blocks of the given byte size +func GenerateBlocksOfSize(n int, size int64) []blocks.Block { + generatedBlocks := make([]blocks.Block, 0, n) + for i := 0; i < n; i++ { + b := blocks.NewBlock(RandomBytes(size)) + generatedBlocks = append(generatedBlocks, b) + + } + return generatedBlocks +} + +// GenerateCid produces a content identifier. +func GenerateCid() cid.Cid { + return GenerateCids(1)[0] +} + +// GenerateCids produces n content identifiers. +func GenerateCids(n int) []cid.Cid { + cids := make([]cid.Cid, 0, n) + for i := 0; i < n; i++ { + c := blockGenerator.Next().Cid() + cids = append(cids, c) + } + return cids +} + +var peerSeq int + +// GeneratePeers creates n peer ids. +func GeneratePeers(n int) []peer.ID { + peerIds := make([]peer.ID, 0, n) + for i := 0; i < n; i++ { + peerSeq++ + p := peer.ID(fmt.Sprint(peerSeq)) + peerIds = append(peerIds, p) + } + return peerIds +} + +// GenerateRetrievalCandidates produces n retrieval candidates +func GenerateRetrievalCandidates(n int) []types.RetrievalCandidate { + candidates := make([]types.RetrievalCandidate, 0, n) + c := GenerateCid() + for i := 0; i < n; i++ { + peerSeq++ + candidates = append(candidates, types.NewRetrievalCandidate(peer.ID(fmt.Sprint(peerSeq)), c)) + } + return candidates +} + +func GenerateRetrievalIDs(t *testing.T, n int) []types.RetrievalID { + retrievalIDs := make([]types.RetrievalID, 0, n) + for i := 0; i < n; i++ { + id, err := types.NewRetrievalID() + require.NoError(t, err) + retrievalIDs = append(retrievalIDs, id) + } + return retrievalIDs +} diff --git a/pkg/retriever/testutil/mockcandidatefinder.go b/pkg/internal/testutil/mockcandidatefinder.go similarity index 100% rename from pkg/retriever/testutil/mockcandidatefinder.go rename to pkg/internal/testutil/mockcandidatefinder.go diff --git a/pkg/retriever/testutil/mockclient.go b/pkg/internal/testutil/mockclient.go similarity index 100% rename from pkg/retriever/testutil/mockclient.go rename to pkg/internal/testutil/mockclient.go diff --git a/pkg/lassie/lassie.go b/pkg/lassie/lassie.go index b2533324..518496d0 100644 --- a/pkg/lassie/lassie.go +++ b/pkg/lassie/lassie.go @@ -13,6 +13,7 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" "github.com/ipld/go-ipld-prime/linking" + "github.com/libp2p/go-libp2p/core/host" "github.com/multiformats/go-multiaddr" ) @@ -22,8 +23,10 @@ type Lassie struct { } type LassieConfig struct { - Finder retriever.CandidateFinder - Timeout time.Duration + Finder retriever.CandidateFinder + Host host.Host + ProviderTimeout time.Duration + GlobalTimeout time.Duration } type LassieOption func(cfg *LassieConfig) @@ -45,29 +48,35 @@ func NewLassieWithConfig(ctx context.Context, cfg *LassieConfig) (*Lassie, error } } - if cfg.Timeout == 0 { - cfg.Timeout = 20 * time.Second + if cfg.ProviderTimeout == 0 { + cfg.ProviderTimeout = 20 * time.Second } datastore := sync.MutexWrap(datastore.NewMapDatastore()) - host, err := internal.InitHost(ctx, multiaddr.StringCast("/ip4/0.0.0.0/tcp/6746")) - if err != nil { - return nil, err + if cfg.Host == nil { + var err error + cfg.Host, err = internal.InitHost(ctx, multiaddr.StringCast("/ip4/0.0.0.0/tcp/6746")) + if err != nil { + return nil, err + } } - retrievalClient, err := client.NewClient(datastore, host, nil) + retrievalClient, err := client.NewClient(datastore, cfg.Host, nil) if err != nil { return nil, err } + bitswapRetriever := retriever.NewBitswapRetrieverFromHost(ctx, cfg.Host, retriever.BitswapConfig{ + BlockTimeout: cfg.ProviderTimeout, + }) retrieverCfg := retriever.RetrieverConfig{ DefaultMinerConfig: retriever.MinerConfig{ - RetrievalTimeout: cfg.Timeout, + RetrievalTimeout: cfg.ProviderTimeout, }, } - retriever, err := retriever.NewRetriever(ctx, retrieverCfg, retrievalClient, cfg.Finder) + retriever, err := retriever.NewRetriever(ctx, retrieverCfg, retrievalClient, cfg.Finder, bitswapRetriever) if err != nil { return nil, err } @@ -87,13 +96,28 @@ func WithFinder(finder retriever.CandidateFinder) LassieOption { } } -func WithTimeout(timeout time.Duration) LassieOption { +func WithProviderTimeout(timeout time.Duration) LassieOption { return func(cfg *LassieConfig) { - cfg.Timeout = timeout + cfg.ProviderTimeout = timeout } } +func WithGlobalTimeout(timeout time.Duration) LassieOption { + return func(cfg *LassieConfig) { + cfg.GlobalTimeout = timeout + } +} +func WithHost(host host.Host) LassieOption { + return func(cfg *LassieConfig) { + cfg.Host = host + } +} func (l *Lassie) Retrieve(ctx context.Context, request types.RetrievalRequest) (*types.RetrievalStats, error) { + var cancel context.CancelFunc + if l.cfg.GlobalTimeout != time.Duration(0) { + ctx, cancel = context.WithTimeout(ctx, l.cfg.GlobalTimeout) + defer cancel() + } return l.retriever.Retrieve(ctx, request, func(types.RetrievalEvent) {}) } diff --git a/pkg/retriever/assignablecandidatefinder_test.go b/pkg/retriever/assignablecandidatefinder_test.go index 53514305..3cbeecd1 100644 --- a/pkg/retriever/assignablecandidatefinder_test.go +++ b/pkg/retriever/assignablecandidatefinder_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" + "github.com/filecoin-project/lassie/pkg/internal/testutil" "github.com/filecoin-project/lassie/pkg/retriever" - "github.com/filecoin-project/lassie/pkg/retriever/testutil" "github.com/filecoin-project/lassie/pkg/types" "github.com/ipfs/go-cid" cidlink "github.com/ipld/go-ipld-prime/linking/cid" diff --git a/pkg/retriever/bitswaphelpers/countinglinksystem.go b/pkg/retriever/bitswaphelpers/countinglinksystem.go new file mode 100644 index 00000000..a90176af --- /dev/null +++ b/pkg/retriever/bitswaphelpers/countinglinksystem.go @@ -0,0 +1,47 @@ +package bitswaphelpers + +import ( + "io" + + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/linking" +) + +type cumulativeCountWriter struct { + io.Writer + totalWritten uint64 + committer linking.BlockWriteCommitter + cb func(count uint64) +} + +func (ccw *cumulativeCountWriter) Write(p []byte) (n int, err error) { + written, err := ccw.Writer.Write(p) + if err != nil { + return written, err + } + ccw.totalWritten += uint64(written) + return written, nil +} + +func (ccw *cumulativeCountWriter) Commit(link datamodel.Link) error { + err := ccw.committer(link) + if err != nil { + return err + } + ccw.cb(ccw.totalWritten) + return nil +} + +func NewByteCountingLinkSystem(lsys *linking.LinkSystem, bytesWritten func(count uint64)) *linking.LinkSystem { + newLsys := *lsys // copy all values from old system + oldWriteOpener := lsys.StorageWriteOpener + newLsys.StorageWriteOpener = func(lctx linking.LinkContext) (io.Writer, linking.BlockWriteCommitter, error) { + w, committer, err := oldWriteOpener(lctx) + if err != nil { + return w, committer, err + } + ccw := &cumulativeCountWriter{w, 0, committer, bytesWritten} + return ccw, ccw.Commit, err + } + return &newLsys +} diff --git a/pkg/retriever/bitswaphelpers/indexerrouting.go b/pkg/retriever/bitswaphelpers/indexerrouting.go new file mode 100644 index 00000000..05a85d96 --- /dev/null +++ b/pkg/retriever/bitswaphelpers/indexerrouting.go @@ -0,0 +1,99 @@ +package bitswaphelpers + +import ( + "context" + "sync" + + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/routing" +) + +var log = logging.Logger("lassie/bitswap") + +var _ routing.Routing = (*IndexerRouting)(nil) + +// IndexerRouting provides an interface that satisfies routing.Routing but only returns +// provider records based on a preset set of providers read from the context key. +// Bitswap will potentially make multiple FindProvidersAsync requests, and the cid passed will not always be the root +// As a result, we have to rely on the retrieval id within a context key +// Also while there is a delegated routing client that talks to the indexer, we use this cause we run it on +// top of the processing we're doing at a higher level with multiprotocol filtering +type IndexerRouting struct { + routinghelpers.Null + providerSets map[types.RetrievalID][]types.RetrievalCandidate + providerSetsLk sync.Mutex + toRetrievalIDs func(cid.Cid) []types.RetrievalID +} + +// NewIndexerRouting makes a new indexer routing instance +func NewIndexerRouting(toRetrievalID func(cid.Cid) []types.RetrievalID) *IndexerRouting { + return &IndexerRouting{ + providerSets: make(map[types.RetrievalID][]types.RetrievalCandidate), + toRetrievalIDs: toRetrievalID, + } +} + +// RemoveProviders removes all provider records for a given retrieval id +func (ir *IndexerRouting) RemoveProviders(retrievalID types.RetrievalID) { + ir.providerSetsLk.Lock() + defer ir.providerSetsLk.Unlock() + delete(ir.providerSets, retrievalID) +} + +// AddProviders adds provider records to the total list for a given retrieval id +func (ir *IndexerRouting) AddProviders(retrievalID types.RetrievalID, providers []types.RetrievalCandidate) { + // dedup results to provide better answers + uniqueProvidersSet := make(map[string]struct{}, len(providers)) + uniqueProviders := make([]types.RetrievalCandidate, 0, len(providers)) + for _, p := range providers { + if _, ok := uniqueProvidersSet[p.MinerPeer.String()]; !ok { + uniqueProvidersSet[p.MinerPeer.String()] = struct{}{} + uniqueProviders = append(uniqueProviders, p) + } + } + ir.providerSetsLk.Lock() + defer ir.providerSetsLk.Unlock() + ir.providerSets[retrievalID] = append(ir.providerSets[retrievalID], uniqueProviders...) +} + +// FindProvidersAsync returns providers based on the retrieval id in a context key +// It returns a channel with up to `max` providers, keeping the others around for a future call +// TODO: there is a slight risk that go-bitswap, which dedups requests by CID across multiple sessions, +// could accidentally read the wrong retrieval id if two retrievals were running at the same time. Not sure how much +// of a risk this really is, cause when requests are deduped, both calls still receive the results. See go-bitswap +// ProviderQueryManager for more specifics +func (ir *IndexerRouting) FindProvidersAsync(ctx context.Context, c cid.Cid, max int) <-chan peer.AddrInfo { + resultsChan := make(chan peer.AddrInfo) + + go func() { + defer close(resultsChan) + + retrievalIDs := ir.toRetrievalIDs(c) + ir.providerSetsLk.Lock() + var providers []types.RetrievalCandidate + for _, retrievalID := range retrievalIDs { + providers = append(providers, ir.providerSets[retrievalID]...) + if len(providers) > max { + providers, ir.providerSets[retrievalID] = providers[:max], providers[max:] + break + } + if len(ir.providerSets) == 0 { + delete(ir.providerSets, retrievalID) + } + } + ir.providerSetsLk.Unlock() + log.Debugw("provider records requested from bitswap, sending back indexer results", "providerCount", len(providers)) + for _, p := range providers { + select { + case <-ctx.Done(): + return + case resultsChan <- p.MinerPeer: + } + } + }() + return resultsChan +} diff --git a/pkg/retriever/bitswaphelpers/indexerrouting_test.go b/pkg/retriever/bitswaphelpers/indexerrouting_test.go new file mode 100644 index 00000000..fb5bc3fe --- /dev/null +++ b/pkg/retriever/bitswaphelpers/indexerrouting_test.go @@ -0,0 +1,80 @@ +package bitswaphelpers_test + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/lassie/pkg/internal/testutil" + "github.com/filecoin-project/lassie/pkg/retriever/bitswaphelpers" + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +func TestIndexerRouting(t *testing.T) { + req := require.New(t) + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + cidToRetrievalIDs := make(map[cid.Cid][]types.RetrievalID) + toRetrievalID := func(c cid.Cid) []types.RetrievalID { + return cidToRetrievalIDs[c] + } + ir := bitswaphelpers.NewIndexerRouting(toRetrievalID) + id1, err := types.NewRetrievalID() + req.NoError(err) + id2, err := types.NewRetrievalID() + req.NoError(err) + id3, err := types.NewRetrievalID() + req.NoError(err) + cids := testutil.GenerateCids(4) + cidToRetrievalIDs[cids[0]] = []types.RetrievalID{id1} + cidToRetrievalIDs[cids[1]] = []types.RetrievalID{id2} + cidToRetrievalIDs[cids[2]] = []types.RetrievalID{id3} + cidToRetrievalIDs[cids[3]] = []types.RetrievalID{id1, id3} + // no candidates should be returned initially + verifyCandidates(ctx, t, nil, ir.FindProvidersAsync(ctx, cids[0], 5)) + verifyCandidates(ctx, t, nil, ir.FindProvidersAsync(ctx, cids[1], 5)) + verifyCandidates(ctx, t, nil, ir.FindProvidersAsync(ctx, cids[2], 5)) + candidates1 := testutil.GenerateRetrievalCandidates(10) + candidates2 := testutil.GenerateRetrievalCandidates(10) + candidates3 := testutil.GenerateRetrievalCandidates(10) + ir.AddProviders(id1, candidates1) + ir.AddProviders(id2, candidates2) + ir.AddProviders(id3, candidates3) + verifyCandidates(ctx, t, candidates1[:5], ir.FindProvidersAsync(ctx, cids[0], 5)) + verifyCandidates(ctx, t, candidates2[:5], ir.FindProvidersAsync(ctx, cids[1], 5)) + verifyCandidates(ctx, t, candidates3[:5], ir.FindProvidersAsync(ctx, cids[2], 5)) + // add more to one retrieval + extraCandidates := testutil.GenerateRetrievalCandidates(5) + ir.AddProviders(id1, extraCandidates) + // remove another retrieval + ir.RemoveProviders(id2) + // retrieval that had added candidates should include extra results across retrievals + verifyCandidates(ctx, t, append(append(candidates1[5:], extraCandidates...), candidates3[5:7]...), ir.FindProvidersAsync(ctx, cids[3], 12)) + // retrieval that had candidates removed should include no results + verifyCandidates(ctx, t, nil, ir.FindProvidersAsync(ctx, cids[1], 10)) +} + +func verifyCandidates(ctx context.Context, t *testing.T, expectedCandidates []types.RetrievalCandidate, incoming <-chan peer.AddrInfo) { + expectedAddrInfos := make([]peer.AddrInfo, 0, len(expectedCandidates)) + for _, candidate := range expectedCandidates { + expectedAddrInfos = append(expectedAddrInfos, candidate.MinerPeer) + } + receivedAddrInfos := make([]peer.AddrInfo, 0, len(expectedCandidates)) +addrInfosReceived: + for { + select { + case <-ctx.Done(): + require.FailNow(t, "candidate channel failed to close") + case next, ok := <-incoming: + if !ok { + break addrInfosReceived + } + receivedAddrInfos = append(receivedAddrInfos, next) + } + } + require.Equal(t, expectedAddrInfos, receivedAddrInfos) +} diff --git a/pkg/retriever/bitswaphelpers/inprogresscids.go b/pkg/retriever/bitswaphelpers/inprogresscids.go new file mode 100644 index 00000000..e243436f --- /dev/null +++ b/pkg/retriever/bitswaphelpers/inprogresscids.go @@ -0,0 +1,72 @@ +package bitswaphelpers + +import ( + "sync" + + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-cid" +) + +type InProgressCids struct { + inProgressCidsLk sync.RWMutex + inProgressCids map[cid.Cid]map[types.RetrievalID]uint64 +} + +func NewInProgressCids() *InProgressCids { + return &InProgressCids{ + inProgressCids: make(map[cid.Cid]map[types.RetrievalID]uint64), + } +} + +func (ipc *InProgressCids) Get(c cid.Cid) []types.RetrievalID { + ipc.inProgressCidsLk.RLock() + defer ipc.inProgressCidsLk.RUnlock() + retrievalIDMap, ok := ipc.inProgressCids[c] + if !ok { + return nil + } + retrievalIDs := make([]types.RetrievalID, 0, len(retrievalIDMap)) + for retrievalID := range retrievalIDMap { + retrievalIDs = append(retrievalIDs, retrievalID) + } + return retrievalIDs +} + +func (ipc *InProgressCids) Inc(c cid.Cid, retrievalID types.RetrievalID) { + ipc.inProgressCidsLk.Lock() + defer ipc.inProgressCidsLk.Unlock() + retrievalIDMap, ok := ipc.inProgressCids[c] + if !ok { + retrievalIDMap = retrievalMapPool.Get().(map[types.RetrievalID]uint64) + ipc.inProgressCids[c] = retrievalIDMap + } + retrievalIDMap[retrievalID]++ // will start at zero value and set if not present +} + +func (ipc *InProgressCids) Dec(c cid.Cid, retrievalID types.RetrievalID) { + ipc.inProgressCidsLk.Lock() + defer ipc.inProgressCidsLk.Unlock() + retrievalIDMap, ok := ipc.inProgressCids[c] + if !ok { + return + } + current, ok := retrievalIDMap[retrievalID] + if !ok { + return + } + if current <= 1 { + delete(retrievalIDMap, retrievalID) + if len(retrievalIDMap) == 0 { + delete(ipc.inProgressCids, c) + retrievalMapPool.Put(retrievalIDMap) + } + return + } + retrievalIDMap[retrievalID] = current - 1 +} + +var retrievalMapPool = sync.Pool{ + New: func() any { + return make(map[types.RetrievalID]uint64) + }, +} diff --git a/pkg/retriever/bitswaphelpers/inprogresscids_test.go b/pkg/retriever/bitswaphelpers/inprogresscids_test.go new file mode 100644 index 00000000..6be47dde --- /dev/null +++ b/pkg/retriever/bitswaphelpers/inprogresscids_test.go @@ -0,0 +1,69 @@ +package bitswaphelpers_test + +import ( + "testing" + + "github.com/filecoin-project/lassie/pkg/internal/testutil" + "github.com/filecoin-project/lassie/pkg/retriever/bitswaphelpers" + "github.com/filecoin-project/lassie/pkg/types" + "github.com/stretchr/testify/require" +) + +func TestInProgressCids(t *testing.T) { + req := require.New(t) + cids := testutil.GenerateCids(3) + retrievalIDs := testutil.GenerateRetrievalIDs(t, 3) + + inProgressCids := bitswaphelpers.NewInProgressCids() + // add some values + inProgressCids.Inc(cids[0], retrievalIDs[0]) + inProgressCids.Inc(cids[0], retrievalIDs[1]) + inProgressCids.Inc(cids[1], retrievalIDs[0]) + inProgressCids.Inc(cids[1], retrievalIDs[2]) + inProgressCids.Inc(cids[2], retrievalIDs[1]) + inProgressCids.Inc(cids[2], retrievalIDs[2]) + + req.ElementsMatch([]types.RetrievalID{retrievalIDs[0], retrievalIDs[1]}, inProgressCids.Get(cids[0])) + req.ElementsMatch([]types.RetrievalID{retrievalIDs[0], retrievalIDs[2]}, inProgressCids.Get(cids[1])) + req.ElementsMatch([]types.RetrievalID{retrievalIDs[1], retrievalIDs[2]}, inProgressCids.Get(cids[2])) + + // add some overlapping refs, then remove + inProgressCids.Inc(cids[0], retrievalIDs[0]) + inProgressCids.Inc(cids[1], retrievalIDs[0]) + inProgressCids.Inc(cids[1], retrievalIDs[2]) + inProgressCids.Inc(cids[2], retrievalIDs[2]) + inProgressCids.Dec(cids[0], retrievalIDs[0]) + inProgressCids.Dec(cids[0], retrievalIDs[1]) + inProgressCids.Dec(cids[1], retrievalIDs[0]) + inProgressCids.Dec(cids[1], retrievalIDs[2]) + inProgressCids.Dec(cids[2], retrievalIDs[1]) + inProgressCids.Dec(cids[2], retrievalIDs[2]) + + // only those with 0 refs should be gone + req.ElementsMatch([]types.RetrievalID{retrievalIDs[0]}, inProgressCids.Get(cids[0])) + req.ElementsMatch([]types.RetrievalID{retrievalIDs[0], retrievalIDs[2]}, inProgressCids.Get(cids[1])) + req.ElementsMatch([]types.RetrievalID{retrievalIDs[2]}, inProgressCids.Get(cids[2])) + + // wipe remaining + inProgressCids.Dec(cids[0], retrievalIDs[0]) + inProgressCids.Dec(cids[1], retrievalIDs[0]) + inProgressCids.Dec(cids[1], retrievalIDs[2]) + inProgressCids.Dec(cids[2], retrievalIDs[2]) + // everything should be empty now + req.ElementsMatch([]types.RetrievalID{}, inProgressCids.Get(cids[0])) + req.ElementsMatch([]types.RetrievalID{}, inProgressCids.Get(cids[1])) + req.ElementsMatch([]types.RetrievalID{}, inProgressCids.Get(cids[2])) + + // add back valus (but different cids) + inProgressCids.Inc(cids[2], retrievalIDs[0]) + inProgressCids.Inc(cids[2], retrievalIDs[1]) + inProgressCids.Inc(cids[1], retrievalIDs[0]) + inProgressCids.Inc(cids[1], retrievalIDs[2]) + inProgressCids.Inc(cids[0], retrievalIDs[1]) + inProgressCids.Inc(cids[0], retrievalIDs[2]) + + // verify matches + req.ElementsMatch([]types.RetrievalID{retrievalIDs[0], retrievalIDs[1]}, inProgressCids.Get(cids[2])) + req.ElementsMatch([]types.RetrievalID{retrievalIDs[0], retrievalIDs[2]}, inProgressCids.Get(cids[1])) + req.ElementsMatch([]types.RetrievalID{retrievalIDs[1], retrievalIDs[2]}, inProgressCids.Get(cids[0])) +} diff --git a/pkg/retriever/bitswaphelpers/multiblockstore.go b/pkg/retriever/bitswaphelpers/multiblockstore.go new file mode 100644 index 00000000..5f74d394 --- /dev/null +++ b/pkg/retriever/bitswaphelpers/multiblockstore.go @@ -0,0 +1,150 @@ +package bitswaphelpers + +import ( + "context" + "errors" + "io" + "sync" + + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" + "github.com/ipfs/go-libipfs/blocks" + "github.com/ipld/go-ipld-prime/linking" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" +) + +// ErrNotSupported indicates an operation not supported by the MultiBlockstore +var ErrNotSupported = errors.New("not supported") + +// ErrAlreadyRegistered means something has already been registered for a retrieval id +var ErrAlreadyRegisterd = errors.New("already registered") + +// ErrAlreadyRegistered means there is nothing registered for a retrieval id +var ErrNotRegistered = errors.New("not registered") + +// MultiBlockstore creates a blockstore based on one or more linkystems, extracting the target linksystem for each request +// from the retrieval id context key +type MultiBlockstore struct { + linkSystems map[types.RetrievalID]*linking.LinkSystem + linkSystemsLk sync.RWMutex +} + +// NewMultiblockstore returns a new MultiBlockstore +func NewMultiblockstore() *MultiBlockstore { + return &MultiBlockstore{ + linkSystems: make(map[types.RetrievalID]*linking.LinkSystem), + } +} + +// AddLinkSystem registers a linksystem to use for a given retrieval id +func (mbs *MultiBlockstore) AddLinkSystem(id types.RetrievalID, lsys *linking.LinkSystem) error { + mbs.linkSystemsLk.Lock() + defer mbs.linkSystemsLk.Unlock() + if _, ok := mbs.linkSystems[id]; ok { + return ErrAlreadyRegisterd + } + mbs.linkSystems[id] = lsys + return nil +} + +// RemoveLinkSystem unregisters the link system for a given retrieval id +func (mbs *MultiBlockstore) RemoveLinkSystem(id types.RetrievalID) { + mbs.linkSystemsLk.Lock() + defer mbs.linkSystemsLk.Unlock() + delete(mbs.linkSystems, id) +} + +// DeleteBlock is not supported +func (mbs *MultiBlockstore) DeleteBlock(ctx context.Context, c cid.Cid) error { + return ErrNotSupported +} + +// Has is not supported +func (mbs *MultiBlockstore) Has(ctx context.Context, c cid.Cid) (bool, error) { + return false, ErrNotSupported +} + +type byteReader interface { + Bytes() []byte +} + +// Get returns a block only if the given ctx contains a retrieval ID as a value that +// references a known linksystem. If it does, it uses that linksystem to load the block +func (mbs *MultiBlockstore) Get(ctx context.Context, c cid.Cid) (blocks.Block, error) { + id, err := types.RetrievalIDFromContext(ctx) + if err != nil { + return nil, err + } + mbs.linkSystemsLk.RLock() + lsys, ok := mbs.linkSystems[id] + mbs.linkSystemsLk.RUnlock() + if !ok { + return nil, ErrNotRegistered + } + r, err := lsys.StorageReadOpener(linking.LinkContext{Ctx: ctx}, cidlink.Link{Cid: c}) + if err != nil { + if nf, ok := err.(interface{ NotFound() bool }); ok && nf.NotFound() { + return nil, format.ErrNotFound{Cid: c} + } + return nil, err + } + if br, ok := r.(byteReader); ok { + return blocks.NewBlockWithCid(br.Bytes(), c) + } + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + return blocks.NewBlockWithCid(data, c) +} + +// GetSize is unsupported +func (mbs *MultiBlockstore) GetSize(ctx context.Context, c cid.Cid) (int, error) { + return 0, errors.New("not supported") +} + +// Put writes a block only if the given ctx contains a retrieval ID as a value that +// references a known linksystem. If it does, it uses that linksystem to save the block +func (mbs *MultiBlockstore) Put(ctx context.Context, blk blocks.Block) error { + return mbs.PutMany(ctx, []blocks.Block{blk}) +} + +// PutMany puts a slice of blocks at the same time, with the same rules as Put +func (mbs *MultiBlockstore) PutMany(ctx context.Context, blks []blocks.Block) error { + id, err := types.RetrievalIDFromContext(ctx) + if err != nil { + + return err + } + mbs.linkSystemsLk.RLock() + lsys, ok := mbs.linkSystems[id] + mbs.linkSystemsLk.RUnlock() + if !ok { + return ErrNotRegistered + } + for _, blk := range blks { + w, commit, err := lsys.StorageWriteOpener(linking.LinkContext{Ctx: ctx}) + if err != nil { + return err + } + _, err = w.Write(blk.RawData()) + if err != nil { + return err + } + err = commit(cidlink.Link{Cid: blk.Cid()}) + if err != nil { + return err + } + } + return nil +} + +// AllKeysChan is unsupported +func (mbs *MultiBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return nil, errors.New("not supported") +} + +// HashOnRead is unsupported +func (mbs *MultiBlockstore) HashOnRead(enabled bool) { +} diff --git a/pkg/retriever/bitswaphelpers/multiblockstore_test.go b/pkg/retriever/bitswaphelpers/multiblockstore_test.go new file mode 100644 index 00000000..7ec2d349 --- /dev/null +++ b/pkg/retriever/bitswaphelpers/multiblockstore_test.go @@ -0,0 +1,113 @@ +package bitswaphelpers_test + +import ( + "context" + "testing" + + "github.com/filecoin-project/lassie/pkg/internal/testutil" + "github.com/filecoin-project/lassie/pkg/retriever/bitswaphelpers" + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" + "github.com/ipfs/go-graphsync/storeutil" + blockstore "github.com/ipfs/go-ipfs-blockstore" + format "github.com/ipfs/go-ipld-format" + "github.com/stretchr/testify/require" +) + +func TestMultiblockstore(t *testing.T) { + req := require.New(t) + ctx := context.Background() + ds1 := datastore.NewMapDatastore() + dss1 := sync.MutexWrap(ds1) + bs1 := blockstore.NewBlockstore(dss1) + lsys1 := storeutil.LinkSystemForBlockstore(bs1) + ds2 := datastore.NewMapDatastore() + dss2 := sync.MutexWrap(ds2) + bs2 := blockstore.NewBlockstore(dss2) + lsys2 := storeutil.LinkSystemForBlockstore(bs2) + mbs := bitswaphelpers.NewMultiblockstore() + id1, err := types.NewRetrievalID() + req.NoError(err) + id2, err := types.NewRetrievalID() + req.NoError(err) + storage1Ctx := types.RegisterRetrievalIDToContext(ctx, id1) + storage2Ctx := types.RegisterRetrievalIDToContext(ctx, id2) + mbs.AddLinkSystem(id1, &lsys1) + mbs.AddLinkSystem(id2, &lsys2) + blks := testutil.GenerateBlocksOfSize(5, 1000) + cids := make([]cid.Cid, 0, 5) + for _, blk := range blks { + cids = append(cids, blk.Cid()) + } + // should start off with no blocks returning anything + for _, c := range cids { + _, err := mbs.Get(ctx, c) + req.EqualError(err, types.ErrMissingContextKey.Error()) + _, err = mbs.Get(storage1Ctx, c) + req.EqualError(err, format.ErrNotFound{Cid: c}.Error()) + _, err = mbs.Get(storage2Ctx, c) + req.EqualError(err, format.ErrNotFound{Cid: c}.Error()) + } + // put to root store is not supported + err = mbs.Put(ctx, blks[0]) + req.Equal(types.ErrMissingContextKey, err) + // put some blocks in each system + err = mbs.Put(storage1Ctx, blks[0]) + req.NoError(err) + err = mbs.Put(storage1Ctx, blks[1]) + req.NoError(err) + err = mbs.Put(storage1Ctx, blks[2]) + req.NoError(err) + err = mbs.PutMany(storage2Ctx, blks[2:]) + req.NoError(err) + // verify blocks retrievable on per context basis + for i, c := range cids { + // no blocks for root context + _, err := mbs.Get(ctx, c) + req.EqualError(err, types.ErrMissingContextKey.Error()) + // storage contexts only return blocks put with their key + blk, err := mbs.Get(storage1Ctx, c) + if i <= 2 { + req.NoError(err) + req.Equal(blks[i].RawData(), blk.RawData()) + } else { + req.True(format.IsNotFound(err)) + } + blk, err = mbs.Get(storage2Ctx, c) + if i >= 2 { + req.NoError(err) + req.Equal(blks[i].RawData(), blk.RawData()) + } else { + req.True(format.IsNotFound(err)) + } + } + // verify only registered link systems still return blocks + mbs.RemoveLinkSystem(id1) + for i, c := range cids { + // no blocks for root context + _, err := mbs.Get(ctx, c) + req.EqualError(err, types.ErrMissingContextKey.Error()) + // cancelled storage contexts return no blocks + _, err = mbs.Get(storage1Ctx, c) + req.EqualError(err, bitswaphelpers.ErrNotRegistered.Error()) + // storage contexts only return blocks put with their key + blk, err := mbs.Get(storage2Ctx, c) + if i >= 2 { + req.NoError(err) + req.Equal(blks[i].RawData(), blk.RawData()) + } else { + req.True(format.IsNotFound(err)) + } + } + // unsupported operations + _, err = mbs.Has(storage2Ctx, cids[2]) + req.Equal(bitswaphelpers.ErrNotSupported, err) + err = mbs.DeleteBlock(storage2Ctx, cids[2]) + req.Equal(bitswaphelpers.ErrNotSupported, err) + _, err = mbs.GetSize(storage2Ctx, cids[2]) + req.Equal(bitswaphelpers.ErrNotSupported, err) + _, err = mbs.AllKeysChan(storage2Ctx) + req.Equal(bitswaphelpers.ErrNotSupported, err) +} diff --git a/pkg/retriever/bitswapretriever.go b/pkg/retriever/bitswapretriever.go index 3b191f0f..1f04d442 100644 --- a/pkg/retriever/bitswapretriever.go +++ b/pkg/retriever/bitswapretriever.go @@ -1,29 +1,235 @@ package retriever import ( + "bytes" "context" - "errors" + "fmt" + "io" + "sync/atomic" + "time" + "github.com/benbjohnson/clock" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/index-provider/metadata" + "github.com/filecoin-project/lassie/pkg/events" + "github.com/filecoin-project/lassie/pkg/retriever/bitswaphelpers" "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-libipfs/bitswap/client" + "github.com/ipfs/go-libipfs/bitswap/network" + dagpb "github.com/ipld/go-codec-dagpb" + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/linking" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/node/basicnode" + "github.com/ipld/go-ipld-prime/traversal" + "github.com/ipld/go-ipld-prime/traversal/selector" + selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" ) -type BitswapRetriever struct{} +// IndexerRouting are the required methods to track indexer routing +type IndexerRouting interface { + AddProviders(types.RetrievalID, []types.RetrievalCandidate) + RemoveProviders(types.RetrievalID) +} + +// MultiBlockstore are the require methods to track linksystems +type MultiBlockstore interface { + AddLinkSystem(id types.RetrievalID, lsys *linking.LinkSystem) error + RemoveLinkSystem(id types.RetrievalID) +} + +type InProgressCids interface { + Inc(cid.Cid, types.RetrievalID) + Dec(cid.Cid, types.RetrievalID) +} + +// BitswapRetriever uses bitswap to retrieve data +// BitswapRetriever retieves using a combination of a go-bitswap client specially configured per retrieval, +// underneath a blockservice and a go-fetcher Fetcher. +// Selectors are used to travers the dag to make sure the CARs for bitswap match graphsync +// Note: this is a tradeoff over go-merkledag for traversal, cause selector execution is slow. But the solution +// is to improve selector execution, not introduce unpredictable encoding. +type BitswapRetriever struct { + bstore MultiBlockstore + inProgressCids InProgressCids + routing IndexerRouting + blockService blockservice.BlockService + clock clock.Clock + cfg BitswapConfig +} -func NewBitswapRetriever() *BitswapRetriever { - return &BitswapRetriever{} +const shortenedDelay = 4 * time.Millisecond + +// BitswapConfig contains configurable parameters for bitswap fetching +type BitswapConfig struct { + BlockTimeout time.Duration +} + +// NewBitswapRetrieverFromHost constructs a new bitswap retriever for the given libp2p host +func NewBitswapRetrieverFromHost(ctx context.Context, host host.Host, cfg BitswapConfig) *BitswapRetriever { + bstore := bitswaphelpers.NewMultiblockstore() + inProgressCids := bitswaphelpers.NewInProgressCids() + routing := bitswaphelpers.NewIndexerRouting(inProgressCids.Get) + bsnet := network.NewFromIpfsHost(host, routing) + bitswap := client.New(ctx, bsnet, bstore, client.ProviderSearchDelay(shortenedDelay)) + bsnet.Start(bitswap) + bsrv := blockservice.New(bstore, bitswap) + return NewBitswapRetrieverFromDeps(bsrv, routing, inProgressCids, bstore, cfg, clock.New()) } +// NewBitswapRetrieverFromDeps is primarily for testing, constructing behavior from direct dependencies +func NewBitswapRetrieverFromDeps(bsrv blockservice.BlockService, routing IndexerRouting, inProgressCids InProgressCids, bstore MultiBlockstore, cfg BitswapConfig, clock clock.Clock) *BitswapRetriever { + return &BitswapRetriever{ + bstore: bstore, + inProgressCids: inProgressCids, + routing: routing, + blockService: bsrv, + clock: clock, + cfg: cfg, + } +} + +// Retrieve initializes a new bitswap session func (br *BitswapRetriever) Retrieve(ctx context.Context, request types.RetrievalRequest, events func(types.RetrievalEvent)) types.CandidateRetrieval { - return &bitswapRetrieval{br, ctx, request, events} + return &bitswapRetrieval{br, blockservice.NewSession(ctx, br.blockService), ctx, request, events} } type bitswapRetrieval struct { *BitswapRetriever - ctx context.Context - request types.RetrievalRequest - events func(types.RetrievalEvent) + bsGetter blockservice.BlockGetter + ctx context.Context + request types.RetrievalRequest + events func(types.RetrievalEvent) } +// RetrieveFromCandidates retrieves via go-bitswap backed with the given candidates, under the auspices of a fetcher.Fetcher func (br *bitswapRetrieval) RetrieveFromCandidates(candidates []types.RetrievalCandidate) (*types.RetrievalStats, error) { - return nil, errors.New("not implemented") + phaseStartTime := br.clock.Now() + // this is a hack cause we aren't able to track bitswap fetches per peer for now, so instead we just create a single peer for all events + bitswapCandidate := types.NewRetrievalCandidate(peer.ID(""), br.request.Cid, metadata.Bitswap{}) + br.events(events.Started(br.request.RetrievalID, phaseStartTime, types.RetrievalPhase, bitswapCandidate)) + + // setup the linksystem to record bytes & blocks written -- since this isn't automatic w/o go-data-transfer + ctx, cancel := context.WithCancel(br.ctx) + defer cancel() + var lastBytesReceivedTimer *clock.Timer + if br.cfg.BlockTimeout != 0 { + lastBytesReceivedTimer = br.clock.AfterFunc(br.cfg.BlockTimeout, cancel) + } + + totalWritten := uint64(0) + blockCount := uint64(0) + cb := func(bytesWritten uint64) { + // record first byte received + if totalWritten == 0 { + br.events(events.FirstByte(br.request.RetrievalID, phaseStartTime, bitswapCandidate)) + } + atomic.AddUint64(&totalWritten, bytesWritten) + atomic.AddUint64(&blockCount, 1) + // reset the timer + if bytesWritten > 0 && lastBytesReceivedTimer != nil { + lastBytesReceivedTimer.Reset(br.cfg.BlockTimeout) + } + } + + // setup providers for this retrieval + br.routing.AddProviders(br.request.RetrievalID, candidates) + // setup providers linksystem for this retrieval + br.bstore.AddLinkSystem(br.request.RetrievalID, bitswaphelpers.NewByteCountingLinkSystem(&br.request.LinkSystem, cb)) + + // copy the link system + wrappedLsys := br.request.LinkSystem + // replace the opener with a blockservice wrapper (we still want any known adls + reifiers, hence the copy) + wrappedLsys.StorageReadOpener = loaderForSession(br.request.RetrievalID, br.inProgressCids, br.bsGetter) + // run the retrieval + err := easyTraverse(ctx, cidlink.Link{Cid: br.request.Cid}, selectorparse.CommonSelector_ExploreAllRecursively, &wrappedLsys) + + // unregister relevant provider records & LinkSystem + br.routing.RemoveProviders(br.request.RetrievalID) + br.bstore.RemoveLinkSystem(br.request.RetrievalID) + if err != nil { + // record failure + br.events(events.Failed(br.request.RetrievalID, phaseStartTime, types.RetrievalPhase, bitswapCandidate, err.Error())) + return nil, err + } + duration := br.clock.Since(phaseStartTime) + speed := uint64(float64(totalWritten) / duration.Seconds()) + + // record success + br.events(events.Success( + br.request.RetrievalID, + phaseStartTime, + bitswapCandidate, + totalWritten, + blockCount, + duration, + big.Zero())) + + // return stats + return &types.RetrievalStats{ + StorageProviderId: peer.ID(""), + RootCid: br.request.Cid, + Size: totalWritten, + Blocks: blockCount, + Duration: duration, + AverageSpeed: speed, + TotalPayment: big.Zero(), + NumPayments: 0, + AskPrice: big.Zero(), + }, nil +} + +func loaderForSession(retrievalID types.RetrievalID, inProgressCids InProgressCids, bs blockservice.BlockGetter) linking.BlockReadOpener { + return func(lctx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) { + cidLink, ok := lnk.(cidlink.Link) + if !ok { + return nil, fmt.Errorf("invalid link type for loading: %v", lnk) + } + inProgressCids.Inc(cidLink.Cid, retrievalID) + select { + case <-lctx.Ctx.Done(): + return nil, lctx.Ctx.Err() + default: + } + blk, err := bs.GetBlock(lctx.Ctx, cidLink.Cid) + inProgressCids.Dec(cidLink.Cid, retrievalID) + if err != nil { + return nil, err + } + + return bytes.NewReader(blk.RawData()), nil + } +} + +func easyTraverse(ctx context.Context, root datamodel.Link, traverseSelector datamodel.Node, lsys *linking.LinkSystem) error { + + protoChooser := dagpb.AddSupportToChooser(basicnode.Chooser) + + // retrieve first node + prototype, err := protoChooser(root, linking.LinkContext{Ctx: ctx}) + if err != nil { + return err + } + node, err := lsys.Load(linking.LinkContext{Ctx: ctx}, root, prototype) + if err != nil { + return err + } + + progress := traversal.Progress{ + Cfg: &traversal.Config{ + Ctx: ctx, + LinkSystem: *lsys, + LinkTargetNodePrototypeChooser: protoChooser, + }, + } + progress.LastBlock.Link = root + compiledSelector, err := selector.ParseSelector(traverseSelector) + if err != nil { + return err + } + return progress.WalkAdv(node, compiledSelector, func(prog traversal.Progress, n datamodel.Node, reason traversal.VisitReason) error { return nil }) } diff --git a/pkg/retriever/bitswapretriever_test.go b/pkg/retriever/bitswapretriever_test.go new file mode 100644 index 00000000..39745b68 --- /dev/null +++ b/pkg/retriever/bitswapretriever_test.go @@ -0,0 +1,432 @@ +package retriever_test + +import ( + "context" + "errors" + "io" + "testing" + "time" + + "github.com/benbjohnson/clock" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lassie/pkg/internal/testutil" + "github.com/filecoin-project/lassie/pkg/retriever" + "github.com/filecoin-project/lassie/pkg/retriever/bitswaphelpers" + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + gstestutil "github.com/ipfs/go-graphsync/testutil" + exchange "github.com/ipfs/go-ipfs-exchange-interface" + format "github.com/ipfs/go-ipld-format" + "github.com/ipfs/go-libipfs/blocks" + "github.com/ipld/go-ipld-prime/linking" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/storage/memstore" + "github.com/stretchr/testify/require" +) + +func TestBitswapRetriever(t *testing.T) { + ctx := context.Background() + + store := &correctedMemStore{&memstore.Store{ + Bag: make(map[string][]byte), + }} + lsys := cidlink.DefaultLinkSystem() + lsys.SetReadStorage(store) + lsys.SetWriteStorage(store) + tbc1 := gstestutil.SetupBlockChain(ctx, t, lsys, 1000, 100) + tbc2 := gstestutil.SetupBlockChain(ctx, t, lsys, 1000, 100) + cid1 := tbc1.TipLink.(cidlink.Link).Cid + cid2 := tbc2.TipLink.(cidlink.Link).Cid + remoteBlockDuration := 50 * time.Millisecond + testCases := []struct { + name string + localLinkSystems map[cid.Cid]*linking.LinkSystem + remoteLinkSystems map[cid.Cid]*linking.LinkSystem + expectedCandidates map[cid.Cid][]types.RetrievalCandidate + expectedEvents map[cid.Cid][]types.EventCode + expectedStats map[cid.Cid]*types.RetrievalStats + expectedErrors map[cid.Cid]struct{} + cfg retriever.BitswapConfig + }{ + { + name: "successful full remote fetch", + remoteLinkSystems: map[cid.Cid]*linking.LinkSystem{ + cid1: makeLsys(tbc1.AllBlocks()), + cid2: makeLsys(tbc2.AllBlocks()), + }, + expectedCandidates: map[cid.Cid][]types.RetrievalCandidate{ + cid1: testutil.GenerateRetrievalCandidates(5), + cid2: testutil.GenerateRetrievalCandidates(7), + }, + expectedEvents: map[cid.Cid][]types.EventCode{ + cid1: {types.StartedCode, types.FirstByteCode, types.SuccessCode}, + cid2: {types.StartedCode, types.FirstByteCode, types.SuccessCode}, + }, + expectedStats: map[cid.Cid]*types.RetrievalStats{ + cid1: { + RootCid: cid1, + Size: sizeOf(tbc1.AllBlocks()), + Blocks: 100, + Duration: remoteBlockDuration * 100, + AverageSpeed: uint64(float64(sizeOf(tbc1.AllBlocks())) / (remoteBlockDuration * 100).Seconds()), + TotalPayment: big.Zero(), + AskPrice: big.Zero(), + }, + cid2: { + RootCid: cid2, + Size: sizeOf(tbc2.AllBlocks()), + Blocks: 100, + Duration: remoteBlockDuration * 100, + AverageSpeed: uint64(float64(sizeOf(tbc2.AllBlocks())) / (remoteBlockDuration * 100).Seconds()), + TotalPayment: big.Zero(), + AskPrice: big.Zero(), + }, + }, + }, + { + name: "successful partial remote fetch", + remoteLinkSystems: map[cid.Cid]*linking.LinkSystem{ + cid1: makeLsys(tbc1.Blocks(50, 100)), + cid2: makeLsys(append(tbc2.Blocks(25, 45), tbc2.Blocks(75, 100)...)), + }, + localLinkSystems: map[cid.Cid]*linking.LinkSystem{ + cid1: makeLsys(tbc1.Blocks(0, 50)), + cid2: makeLsys(append(tbc2.Blocks(0, 25), tbc2.Blocks(45, 75)...)), + }, + expectedCandidates: map[cid.Cid][]types.RetrievalCandidate{ + cid1: testutil.GenerateRetrievalCandidates(5), + cid2: testutil.GenerateRetrievalCandidates(7), + }, + expectedEvents: map[cid.Cid][]types.EventCode{ + cid1: {types.StartedCode, types.FirstByteCode, types.SuccessCode}, + cid2: {types.StartedCode, types.FirstByteCode, types.SuccessCode}, + }, + expectedStats: map[cid.Cid]*types.RetrievalStats{ + cid1: { + RootCid: cid1, + Size: sizeOf(tbc1.Blocks(50, 100)), + Blocks: 50, + Duration: remoteBlockDuration * 50, + AverageSpeed: uint64(float64(sizeOf(tbc1.Blocks(50, 100))) / (remoteBlockDuration * 50).Seconds()), + TotalPayment: big.Zero(), + AskPrice: big.Zero(), + }, + cid2: { + RootCid: cid2, + Size: sizeOf(append(tbc2.Blocks(25, 45), tbc2.Blocks(75, 100)...)), + Blocks: 45, + Duration: remoteBlockDuration * 45, + AverageSpeed: uint64(float64(sizeOf(append(tbc2.Blocks(25, 45), tbc2.Blocks(75, 100)...))) / (remoteBlockDuration * 45).Seconds()), + TotalPayment: big.Zero(), + AskPrice: big.Zero(), + }, + }, + }, + { + name: "fail remote fetch about non-zero blocks", + remoteLinkSystems: map[cid.Cid]*linking.LinkSystem{ + cid1: makeLsys(tbc1.Blocks(0, 50)), + cid2: makeLsys(tbc2.Blocks(0, 50)), + }, + expectedCandidates: map[cid.Cid][]types.RetrievalCandidate{ + cid1: testutil.GenerateRetrievalCandidates(5), + cid2: testutil.GenerateRetrievalCandidates(7), + }, + expectedEvents: map[cid.Cid][]types.EventCode{ + cid1: {types.StartedCode, types.FirstByteCode, types.FailedCode}, + cid2: {types.StartedCode, types.FirstByteCode, types.FailedCode}, + }, + expectedErrors: map[cid.Cid]struct{}{ + cid1: {}, + cid2: {}, + }, + }, + { + name: "fail remote fetch immediately", + localLinkSystems: map[cid.Cid]*linking.LinkSystem{ + cid1: makeLsys(tbc1.Blocks(0, 50)), + cid2: makeLsys(tbc2.Blocks(0, 50)), + }, + expectedCandidates: map[cid.Cid][]types.RetrievalCandidate{ + cid1: testutil.GenerateRetrievalCandidates(5), + cid2: testutil.GenerateRetrievalCandidates(7), + }, + expectedEvents: map[cid.Cid][]types.EventCode{ + cid1: {types.StartedCode, types.FailedCode}, + cid2: {types.StartedCode, types.FailedCode}, + }, + expectedErrors: map[cid.Cid]struct{}{ + cid1: {}, + cid2: {}, + }, + }, + { + name: "timeout", + remoteLinkSystems: map[cid.Cid]*linking.LinkSystem{ + cid1: makeLsys(tbc1.AllBlocks()), + cid2: makeLsys(tbc2.AllBlocks()), + }, + expectedCandidates: map[cid.Cid][]types.RetrievalCandidate{ + cid1: testutil.GenerateRetrievalCandidates(5), + cid2: testutil.GenerateRetrievalCandidates(7), + }, + expectedEvents: map[cid.Cid][]types.EventCode{ + cid1: {types.StartedCode, types.FirstByteCode, types.FailedCode}, + cid2: {types.StartedCode, types.FirstByteCode, types.FailedCode}, + }, + + expectedErrors: map[cid.Cid]struct{}{ + cid1: {}, + cid2: {}, + }, + cfg: retriever.BitswapConfig{ + BlockTimeout: 10 * time.Millisecond, + }, + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + req := require.New(t) + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + localLinkSystems := testCase.localLinkSystems + if localLinkSystems == nil { + localLinkSystems = make(map[cid.Cid]*linking.LinkSystem) + } + remoteLinkSystems := testCase.remoteLinkSystems + if remoteLinkSystems == nil { + remoteLinkSystems = make(map[cid.Cid]*linking.LinkSystem) + } + linkSystemForCid := func(c cid.Cid, lsMap map[cid.Cid]*linking.LinkSystem) *linking.LinkSystem { + + if _, ok := lsMap[c]; !ok { + lsMap[c] = makeLsys(nil) + } + return lsMap[c] + } + rid1, err := types.NewRetrievalID() + req.NoError(err) + req1Context := types.RegisterRetrievalIDToContext(ctx, rid1) + req1 := types.RetrievalRequest{ + RetrievalID: rid1, + Cid: cid1, + LinkSystem: *linkSystemForCid(cid1, localLinkSystems), + } + rid2, err := types.NewRetrievalID() + req.NoError(err) + req2Context := types.RegisterRetrievalIDToContext(ctx, rid2) + req2 := types.RetrievalRequest{ + RetrievalID: rid2, + Cid: cid2, + LinkSystem: *linkSystemForCid(cid2, localLinkSystems), + } + mbs := bitswaphelpers.NewMultiblockstore() + clock := clock.NewMock() + + exchange := &mockExchange{ + getLsys: func(ctx context.Context) (*linking.LinkSystem, error) { + clock.Add(remoteBlockDuration) + id, err := types.RetrievalIDFromContext(ctx) + if err != nil { + return nil, err + } + switch id { + case rid1: + return linkSystemForCid(cid1, remoteLinkSystems), nil + case rid2: + return linkSystemForCid(cid2, remoteLinkSystems), nil + default: + return nil, errors.New("unrecognized retrieval") + } + }, + } + bsrv := blockservice.New(mbs, exchange) + mir := newMockIndexerRouting() + mipc := &mockInProgressCids{} + bsr := retriever.NewBitswapRetrieverFromDeps(bsrv, mir, mipc, mbs, testCase.cfg, clock) + receivedEvents := make(map[cid.Cid][]types.RetrievalEvent) + retrievalCollector := func(evt types.RetrievalEvent) { + receivedEvents[evt.PayloadCid()] = append(receivedEvents[evt.PayloadCid()], evt) + } + retrieval1 := bsr.Retrieve(req1Context, req1, retrievalCollector) + retrieval2 := bsr.Retrieve(req2Context, req2, retrievalCollector) + receivedStats := make(map[cid.Cid]*types.RetrievalStats, 2) + receivedErrors := make(map[cid.Cid]struct{}, 2) + expectedCandidates := make(map[types.RetrievalID][]types.RetrievalCandidate) + if testCase.expectedCandidates != nil { + for key, candidates := range testCase.expectedCandidates { + switch key { + case cid1: + expectedCandidates[rid1] = candidates + case cid2: + expectedCandidates[rid2] = candidates + } + } + } + // reset the clock + clock.Set(time.Now()) + stats, err := retrieval1.RetrieveFromCandidates(expectedCandidates[rid1]) + if stats != nil { + receivedStats[cid1] = stats + } + if err != nil { + receivedErrors[cid1] = struct{}{} + } + // reset the clock + clock.Set(time.Now()) + stats, err = retrieval2.RetrieveFromCandidates(expectedCandidates[rid2]) + if stats != nil { + receivedStats[cid2] = stats + } + if err != nil { + receivedErrors[cid2] = struct{}{} + } + receivedCodes := make(map[cid.Cid][]types.EventCode, len(receivedEvents)) + expectedErrors := testCase.expectedErrors + if expectedErrors == nil { + expectedErrors = make(map[cid.Cid]struct{}) + } + req.Equal(expectedErrors, receivedErrors) + expectedStats := testCase.expectedStats + if expectedStats == nil { + expectedStats = make(map[cid.Cid]*types.RetrievalStats) + } + req.Equal(expectedStats, receivedStats) + for key, events := range receivedEvents { + receivedCodes[key] = make([]types.EventCode, 0, len(events)) + for _, event := range events { + receivedCodes[key] = append(receivedCodes[key], event.Code()) + } + } + req.Equal(testCase.expectedEvents, receivedCodes) + req.Equal(expectedCandidates, mir.candidatesAdded) + req.Equal(map[types.RetrievalID]struct{}{rid1: {}, rid2: {}}, mir.candidatesRemoved) + if len(expectedErrors) == 0 { + var allCids []cid.Cid + for _, blk := range tbc1.AllBlocks() { + allCids = append(allCids, blk.Cid()) + } + for _, blk := range tbc2.AllBlocks() { + allCids = append(allCids, blk.Cid()) + } + req.ElementsMatch(allCids, mipc.incremented) + req.ElementsMatch(allCids, mipc.decremented) + } + }) + } +} + +type mockExchange struct { + getLsys func(ctx context.Context) (*linking.LinkSystem, error) +} + +// GetBlock returns the block associated with a given key. +func (me *mockExchange) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) { + lsys, err := me.getLsys(ctx) + if err != nil { + return nil, err + } + r, err := lsys.StorageReadOpener(linking.LinkContext{Ctx: ctx}, cidlink.Link{Cid: c}) + if err != nil { + return nil, err + } + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + return blocks.NewBlockWithCid(data, c) +} + +func (me *mockExchange) GetBlocks(_ context.Context, _ []cid.Cid) (<-chan blocks.Block, error) { + panic("not implemented") // TODO: Implement +} + +// NotifyNewBlocks tells the exchange that new blocks are available and can be served. +func (me *mockExchange) NotifyNewBlocks(ctx context.Context, blocks ...blocks.Block) error { + return nil +} + +func (me *mockExchange) Close() error { + panic("not implemented") // TODO: Implement +} + +func (me *mockExchange) NewSession(_ context.Context) exchange.Fetcher { + return me +} + +type mockIndexerRouting struct { + candidatesAdded map[types.RetrievalID][]types.RetrievalCandidate + candidatesRemoved map[types.RetrievalID]struct{} +} + +func newMockIndexerRouting() *mockIndexerRouting { + return &mockIndexerRouting{ + candidatesAdded: make(map[types.RetrievalID][]types.RetrievalCandidate), + candidatesRemoved: make(map[types.RetrievalID]struct{}), + } +} +func (mir *mockIndexerRouting) AddProviders(rid types.RetrievalID, candidates []types.RetrievalCandidate) { + mir.candidatesAdded[rid] = append(mir.candidatesAdded[rid], candidates...) +} + +func (mir *mockIndexerRouting) RemoveProviders(rid types.RetrievalID) { + mir.candidatesRemoved[rid] = struct{}{} +} + +func makeLsys(blocks []blocks.Block) *linking.LinkSystem { + bag := make(map[string][]byte, len(blocks)) + for _, block := range blocks { + bag[cidlink.Link{Cid: block.Cid()}.Binary()] = block.RawData() + } + lsys := cidlink.DefaultLinkSystem() + store := &correctedMemStore{&memstore.Store{Bag: bag}} + lsys.SetReadStorage(store) + lsys.SetWriteStorage(store) + return &lsys +} + +func sizeOf(blocks []blocks.Block) uint64 { + total := uint64(0) + for _, block := range blocks { + total += uint64(len(block.RawData())) + } + return total +} + +// TODO: remove when this is fixed in IPLD prime +type correctedMemStore struct { + *memstore.Store +} + +func (cms *correctedMemStore) Get(ctx context.Context, key string) ([]byte, error) { + data, err := cms.Store.Get(ctx, key) + if err != nil && err.Error() == "404" { + err = format.ErrNotFound{} + } + return data, err +} + +func (cms *correctedMemStore) GetStream(ctx context.Context, key string) (io.ReadCloser, error) { + rc, err := cms.Store.GetStream(ctx, key) + if err != nil && err.Error() == "404" { + err = format.ErrNotFound{} + } + return rc, err +} + +type mockInProgressCids struct { + incremented []cid.Cid + decremented []cid.Cid +} + +func (mipc *mockInProgressCids) Inc(c cid.Cid, _ types.RetrievalID) { + mipc.incremented = append(mipc.incremented, c) +} + +func (mipc *mockInProgressCids) Dec(c cid.Cid, _ types.RetrievalID) { + mipc.decremented = append(mipc.decremented, c) +} diff --git a/pkg/retriever/graphsyncretriever.go b/pkg/retriever/graphsyncretriever.go index 959a96e4..4ac447d8 100644 --- a/pkg/retriever/graphsyncretriever.go +++ b/pkg/retriever/graphsyncretriever.go @@ -58,10 +58,9 @@ var queryCompare prioritywaitqueue.ComparePriority[*queryCandidate] = func(a, b } type GraphSyncRetriever struct { - GetStorageProviderTimeout GetStorageProviderTimeout - IsAcceptableStorageProvider IsAcceptableStorageProvider - IsAcceptableQueryResponse IsAcceptableQueryResponse - Client RetrievalClient + GetStorageProviderTimeout GetStorageProviderTimeout + IsAcceptableQueryResponse IsAcceptableQueryResponse + Client RetrievalClient waitGroup sync.WaitGroup // only used internally for testing cleanup } diff --git a/pkg/retriever/graphsyncretriever_test.go b/pkg/retriever/graphsyncretriever_test.go index 7210deb5..bbc1db38 100644 --- a/pkg/retriever/graphsyncretriever_test.go +++ b/pkg/retriever/graphsyncretriever_test.go @@ -12,7 +12,7 @@ import ( "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lassie/pkg/events" - "github.com/filecoin-project/lassie/pkg/retriever/testutil" + "github.com/filecoin-project/lassie/pkg/internal/testutil" "github.com/filecoin-project/lassie/pkg/types" "github.com/google/go-cmp/cmp" "github.com/google/uuid" @@ -93,8 +93,11 @@ func TestQueryFiltering(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { - retrievalId := types.RetrievalID(uuid.New()) + t.Parallel() + + retrievalID := types.RetrievalID(uuid.New()) dqr := make(map[string]testutil.DelayedQueryReturn, 0) for p, qr := range tc.queryResponses { dqr[p] = testutil.DelayedQueryReturn{QueryResponse: qr, Err: nil, Delay: time.Millisecond * 50} @@ -106,8 +109,7 @@ func TestQueryFiltering(t *testing.T) { } cfg := &GraphSyncRetriever{ - GetStorageProviderTimeout: func(peer peer.ID) time.Duration { return time.Second }, - IsAcceptableStorageProvider: func(peer peer.ID) bool { return true }, + GetStorageProviderTimeout: func(peer peer.ID) time.Duration { return time.Second }, IsAcceptableQueryResponse: func(peer peer.ID, req types.RetrievalRequest, queryResponse *retrievalmarket.QueryResponse) bool { return tc.paid || big.Add(big.Mul(queryResponse.MinPricePerByte, big.NewIntUnsigned(queryResponse.Size)), queryResponse.UnsealPrice).Equals(big.Zero()) }, @@ -121,10 +123,10 @@ func TestQueryFiltering(t *testing.T) { // perform retrieval and test top-level results, we should only error in this test stats, err := cfg.Retrieve(context.Background(), types.RetrievalRequest{ Cid: cid.Undef, - RetrievalID: retrievalId, + RetrievalID: retrievalID, LinkSystem: cidlink.DefaultLinkSystem(), }, func(event types.RetrievalEvent) { - qt.Assert(t, event.RetrievalId(), qt.Equals, retrievalId) + qt.Assert(t, event.RetrievalId(), qt.Equals, retrievalID) switch ret := event.(type) { case events.RetrievalEventQueryAsked: candidateQueries = append(candidateQueries, candidateQuery{ret.StorageProviderId(), ret.QueryResponse()}) @@ -293,13 +295,13 @@ func TestRetrievalRacing(t *testing.T) { name: "racing chooses fastest query", queryReturns: map[string]testutil.DelayedQueryReturn{ "foo": {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 10}, - "bar": {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 3, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 110}, - "baz": {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 3, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 100}, + "bar": {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 3, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 220}, + "baz": {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 3, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 200}, "bang": {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 3, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 50}, }, expectedQueryReturns: []string{"foo", "bar", "baz", "bang"}, retrievalReturns: map[string]testutil.DelayedRetrievalReturn{ - "foo": {ResultErr: errors.New("Nope"), Delay: time.Millisecond * 100}, + "foo": {ResultErr: errors.New("Nope"), Delay: time.Millisecond * 400}, "bar": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bar"), Size: 3}, Delay: time.Millisecond * 20}, "baz": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("baz"), Size: 2}, Delay: time.Millisecond * 20}, "bang": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bang"), Size: 4}, Delay: time.Millisecond * 20}, @@ -310,18 +312,20 @@ func TestRetrievalRacing(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { - retrievalId := types.RetrievalID(uuid.New()) + t.Parallel() + + retrievalID := types.RetrievalID(uuid.New()) mockClient := testutil.NewMockClient(tc.queryReturns, tc.retrievalReturns) candidates := []types.RetrievalCandidate{} for p := range tc.queryReturns { candidates = append(candidates, types.RetrievalCandidate{MinerPeer: peer.AddrInfo{ID: peer.ID(p)}}) } cfg := &GraphSyncRetriever{ - GetStorageProviderTimeout: func(peer peer.ID) time.Duration { return time.Second }, - IsAcceptableStorageProvider: func(peer peer.ID) bool { return true }, - IsAcceptableQueryResponse: func(peer peer.ID, req types.RetrievalRequest, qr *retrievalmarket.QueryResponse) bool { return true }, - Client: mockClient, + GetStorageProviderTimeout: func(peer peer.ID) time.Duration { return time.Second }, + IsAcceptableQueryResponse: func(peer peer.ID, req types.RetrievalRequest, qr *retrievalmarket.QueryResponse) bool { return true }, + Client: mockClient, } retrievingPeers := make([]peer.ID, 0) @@ -331,10 +335,10 @@ func TestRetrievalRacing(t *testing.T) { // perform retrieval and make sure we got a result stats, err := cfg.Retrieve(context.Background(), types.RetrievalRequest{ Cid: cid.Undef, - RetrievalID: retrievalId, + RetrievalID: retrievalID, LinkSystem: cidlink.DefaultLinkSystem(), }, func(event types.RetrievalEvent) { - qt.Assert(t, event.RetrievalId(), qt.Equals, retrievalId) + qt.Assert(t, event.RetrievalId(), qt.Equals, retrievalID) switch ret := event.(type) { case events.RetrievalEventQueryAsked: candidateQueries = append(candidateQueries, candidateQuery{ret.StorageProviderId(), ret.QueryResponse()}) @@ -397,7 +401,7 @@ func TestRetrievalRacing(t *testing.T) { // run two retrievals simultaneously on a single CidRetrieval func TestMultipleRetrievals(t *testing.T) { - retrievalId := types.RetrievalID(uuid.New()) + retrievalID := types.RetrievalID(uuid.New()) cid1 := cid.MustParse("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi") cid2 := cid.MustParse("bafyrgqhai26anf3i7pips7q22coa4sz2fr4gk4q4sqdtymvvjyginfzaqewveaeqdh524nsktaq43j65v22xxrybrtertmcfxufdam3da3hbk") successfulQueryResponse := retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()} @@ -413,19 +417,18 @@ func TestMultipleRetrievals(t *testing.T) { }, map[string]testutil.DelayedRetrievalReturn{ "foo": {ResultErr: errors.New("Nope"), Delay: time.Millisecond * 20}, - "bar": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bar"), Size: 2}, Delay: time.Millisecond * 100}, - "baz": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("baz"), Size: 3}, Delay: time.Millisecond * 100}, - "bang": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bang"), Size: 3}, Delay: time.Millisecond * 100}, - "boom": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("boom"), Size: 3}, Delay: time.Millisecond * 100}, - "bing": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bing"), Size: 3}, Delay: time.Millisecond * 100}, + "bar": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bar"), Size: 2}, Delay: time.Millisecond * 200}, + "baz": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("baz"), Size: 3}, Delay: time.Millisecond * 200}, + "bang": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bang"), Size: 3}, Delay: time.Millisecond * 200}, + "boom": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("boom"), Size: 3}, Delay: time.Millisecond * 200}, + "bing": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bing"), Size: 3}, Delay: time.Millisecond * 200}, }, ) cfg := &GraphSyncRetriever{ - GetStorageProviderTimeout: func(peer peer.ID) time.Duration { return time.Second }, - IsAcceptableStorageProvider: func(peer peer.ID) bool { return true }, - IsAcceptableQueryResponse: func(peer peer.ID, req types.RetrievalRequest, qr *retrievalmarket.QueryResponse) bool { return true }, - Client: mockClient, + GetStorageProviderTimeout: func(peer peer.ID) time.Duration { return time.Second }, + IsAcceptableQueryResponse: func(peer peer.ID, req types.RetrievalRequest, qr *retrievalmarket.QueryResponse) bool { return true }, + Client: mockClient, } candidateQueries := make([]candidateQuery, 0) @@ -457,7 +460,7 @@ func TestMultipleRetrievals(t *testing.T) { go func() { stats, err := cfg.Retrieve(context.Background(), types.RetrievalRequest{ Cid: cid1, - RetrievalID: retrievalId, + RetrievalID: retrievalID, LinkSystem: cidlink.DefaultLinkSystem(), }, evtCb).RetrieveFromCandidates([]types.RetrievalCandidate{ {MinerPeer: peer.AddrInfo{ID: peer.ID("foo")}}, @@ -473,7 +476,7 @@ func TestMultipleRetrievals(t *testing.T) { stats, err := cfg.Retrieve(context.Background(), types.RetrievalRequest{ Cid: cid2, - RetrievalID: retrievalId, + RetrievalID: retrievalID, LinkSystem: cidlink.DefaultLinkSystem(), }, evtCb).RetrieveFromCandidates([]types.RetrievalCandidate{ {MinerPeer: peer.AddrInfo{ID: peer.ID("bang")}}, @@ -485,13 +488,13 @@ func TestMultipleRetrievals(t *testing.T) { // make sure we got the final retrieval we wanted qt.Assert(t, stats, qt.Equals, mockClient.GetRetrievalReturns()["bing"].ResultStats) - // both retrievals should be ~ 50+100ms + // both retrievals should be ~ 20+200ms waitStart := time.Now() cfg.wait() // internal goroutine cleanup - qt.Assert(t, time.Since(waitStart) < time.Millisecond*20, qt.IsTrue, qt.Commentf("wait took %s", time.Since(waitStart))) + qt.Assert(t, time.Since(waitStart) < time.Millisecond*50, qt.IsTrue, qt.Commentf("wait took %s", time.Since(waitStart))) wg.Wait() // make sure we're done with our own goroutine - qt.Assert(t, time.Since(waitStart) < time.Millisecond*20, qt.IsTrue, qt.Commentf("wg wait took %s", time.Since(waitStart))) + qt.Assert(t, time.Since(waitStart) < time.Millisecond*50, qt.IsTrue, qt.Commentf("wg wait took %s", time.Since(waitStart))) // make sure we handled the queries we expected qt.Assert(t, len(mockClient.GetReceivedQueries()), qt.Equals, 6) @@ -513,34 +516,33 @@ func TestMultipleRetrievals(t *testing.T) { // Verify we can use a single retrieval multiple times with different candidates (so it can be used in the future with a stream) func TestRetrievalReuse(t *testing.T) { - retrievalId := types.RetrievalID(uuid.New()) + retrievalID := types.RetrievalID(uuid.New()) cid1 := cid.MustParse("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi") successfulQueryResponse := retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()} mockClient := testutil.NewMockClient( map[string]testutil.DelayedQueryReturn{ - "foo": {QueryResponse: &successfulQueryResponse, Err: nil, Delay: time.Millisecond * 20}, - "bar": {QueryResponse: &successfulQueryResponse, Err: nil, Delay: time.Millisecond * 50}, + "foo": {QueryResponse: &successfulQueryResponse, Err: nil, Delay: time.Millisecond * 10}, + "bar": {QueryResponse: &successfulQueryResponse, Err: nil, Delay: time.Millisecond * 20}, "baz": {QueryResponse: &successfulQueryResponse, Err: nil, Delay: time.Millisecond * 500}, // should not finish this "bang": {QueryResponse: &successfulQueryResponse, Err: nil, Delay: time.Millisecond * 500}, // should not finish this "boom": {QueryResponse: nil, Err: errors.New("Nope"), Delay: time.Millisecond * 20}, - "bing": {QueryResponse: &successfulQueryResponse, Err: nil, Delay: time.Millisecond * 50}, + "bing": {QueryResponse: &successfulQueryResponse, Err: nil, Delay: time.Millisecond * 20}, }, map[string]testutil.DelayedRetrievalReturn{ "foo": {ResultErr: errors.New("Nope"), Delay: time.Millisecond * 20}, - "bar": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bar"), Size: 2}, Delay: time.Millisecond * 100}, - "baz": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("baz"), Size: 3}, Delay: time.Millisecond * 100}, - "bang": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bang"), Size: 3}, Delay: time.Millisecond * 100}, - "boom": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("boom"), Size: 3}, Delay: time.Millisecond * 100}, - "bing": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bing"), Size: 3}, Delay: time.Millisecond * 100}, + "bar": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bar"), Size: 2}, Delay: time.Millisecond * 200}, + "baz": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("baz"), Size: 3}, Delay: time.Millisecond * 200}, + "bang": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bang"), Size: 3}, Delay: time.Millisecond * 200}, + "boom": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("boom"), Size: 3}, Delay: time.Millisecond * 200}, + "bing": {ResultStats: &types.RetrievalStats{StorageProviderId: peer.ID("bing"), Size: 3}, Delay: time.Millisecond * 200}, }, ) cfg := &GraphSyncRetriever{ - GetStorageProviderTimeout: func(peer peer.ID) time.Duration { return time.Second }, - IsAcceptableStorageProvider: func(peer peer.ID) bool { return true }, - IsAcceptableQueryResponse: func(peer peer.ID, req types.RetrievalRequest, qr *retrievalmarket.QueryResponse) bool { return true }, - Client: mockClient, + GetStorageProviderTimeout: func(peer peer.ID) time.Duration { return time.Second }, + IsAcceptableQueryResponse: func(peer peer.ID, req types.RetrievalRequest, qr *retrievalmarket.QueryResponse) bool { return true }, + Client: mockClient, } candidateQueries := make([]candidateQuery, 0) @@ -569,7 +571,7 @@ func TestRetrievalReuse(t *testing.T) { retrieval := cfg.Retrieve(context.Background(), types.RetrievalRequest{ Cid: cid1, - RetrievalID: retrievalId, + RetrievalID: retrievalID, LinkSystem: cidlink.DefaultLinkSystem(), }, evtCb) @@ -597,7 +599,7 @@ func TestRetrievalReuse(t *testing.T) { waitStart := time.Now() cfg.wait() // internal goroutine cleanup - qt.Assert(t, time.Since(waitStart) < time.Millisecond*20, qt.IsTrue, qt.Commentf("wait took %s", time.Since(waitStart))) + qt.Assert(t, time.Since(waitStart) < time.Millisecond*50, qt.IsTrue, qt.Commentf("wait took %s", time.Since(waitStart))) // make sure we handled the queries we expected qt.Assert(t, len(mockClient.GetReceivedQueries()), qt.Equals, 6) diff --git a/pkg/retriever/retriever.go b/pkg/retriever/retriever.go index eae31283..f7c3e109 100644 --- a/pkg/retriever/retriever.go +++ b/pkg/retriever/retriever.go @@ -90,26 +90,29 @@ func NewRetriever( config RetrieverConfig, client RetrievalClient, candidateFinder CandidateFinder, + bitswapRetriever types.CandidateRetriever, ) (*Retriever, error) { retriever := &Retriever{ config: config, eventManager: events.NewEventManager(ctx), spTracker: newSpTracker(nil), } + candidateRetrievers := []types.CandidateRetriever{ + &GraphSyncRetriever{ + GetStorageProviderTimeout: retriever.getStorageProviderTimeout, + IsAcceptableQueryResponse: retriever.isAcceptableQueryResponse, + Client: client, + }, + } + if bitswapRetriever != nil { + candidateRetrievers = append(candidateRetrievers, bitswapRetriever) + } retriever.executor = combinators.RetrieverWithCandidateFinder{ CandidateFinder: NewAssignableCandidateFinder(candidateFinder, retriever.isAcceptableStorageProvider), CandidateRetriever: combinators.SplitRetriever{ - CandidateSplitter: NewProtocolSplitter([]multicodec.Code{multicodec.TransportGraphsyncFilecoinv1, multicodec.TransportBitswap}), - CandidateRetrievers: []types.CandidateRetriever{ - &GraphSyncRetriever{ - GetStorageProviderTimeout: retriever.getStorageProviderTimeout, - IsAcceptableStorageProvider: retriever.isAcceptableStorageProvider, - IsAcceptableQueryResponse: retriever.isAcceptableQueryResponse, - Client: client, - }, - NewBitswapRetriever(), - }, - CoordinationKind: types.RaceCoordination, + CandidateSplitter: NewProtocolSplitter([]multicodec.Code{multicodec.TransportGraphsyncFilecoinv1, multicodec.TransportBitswap}), + CandidateRetrievers: candidateRetrievers, + CoordinationKind: types.RaceCoordination, }, } @@ -192,6 +195,7 @@ func (retriever *Retriever) Retrieve( eventsCB func(types.RetrievalEvent), ) (*types.RetrievalStats, error) { + ctx = types.RegisterRetrievalIDToContext(ctx, request.RetrievalID) if !retriever.eventManager.IsStarted() { return nil, ErrRetrieverNotStarted } diff --git a/pkg/retriever/retriever_test.go b/pkg/retriever/retriever_test.go index 5ce3f239..9acfd612 100644 --- a/pkg/retriever/retriever_test.go +++ b/pkg/retriever/retriever_test.go @@ -11,8 +11,8 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/index-provider/metadata" "github.com/filecoin-project/lassie/pkg/events" + "github.com/filecoin-project/lassie/pkg/internal/testutil" "github.com/filecoin-project/lassie/pkg/retriever" - "github.com/filecoin-project/lassie/pkg/retriever/testutil" "github.com/filecoin-project/lassie/pkg/types" "github.com/google/uuid" "github.com/ipfs/go-cid" @@ -28,7 +28,7 @@ func TestRetrieverStart(t *testing.T) { config := retriever.RetrieverConfig{} candidateFinder := &testutil.MockCandidateFinder{} client := &testutil.MockClient{} - ret, err := retriever.NewRetriever(context.Background(), config, client, candidateFinder) + ret, err := retriever.NewRetriever(context.Background(), config, client, candidateFinder, nil) require.NoError(t, err) // --- run --- @@ -104,7 +104,7 @@ func TestRetriever(t *testing.T) { }, returns_queries: map[string]testutil.DelayedQueryReturn{ // fastest is blacklisted, shouldn't even touch it - string(peerA): {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 50}, + string(peerA): {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 200}, string(peerB): {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 5}, }, returns_retrievals: map[string]testutil.DelayedRetrievalReturn{ @@ -221,7 +221,7 @@ func TestRetriever(t *testing.T) { }, returns_queries: map[string]testutil.DelayedQueryReturn{ // fastest is blacklisted, shouldn't even touch it - string(peerA): {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 50}, + string(peerA): {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 200}, string(peerB): {QueryResponse: &retrievalmarket.QueryResponse{Status: retrievalmarket.QueryResponseAvailable, MinPricePerByte: big.Zero(), Size: 2, UnsealPrice: big.Zero()}, Err: nil, Delay: time.Millisecond * 5}, }, returns_retrievals: map[string]testutil.DelayedRetrievalReturn{ @@ -350,7 +350,10 @@ func TestRetriever(t *testing.T) { } for _, tc := range tc { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() + // --- setup --- candidateFinder := &testutil.MockCandidateFinder{Candidates: map[cid.Cid][]types.RetrievalCandidate{cid1: tc.candidates}} client := testutil.NewMockClient(tc.returns_queries, tc.returns_retrievals) @@ -363,7 +366,7 @@ func TestRetriever(t *testing.T) { } // --- create --- - ret, err := retriever.NewRetriever(context.Background(), config, client, candidateFinder) + ret, err := retriever.NewRetriever(context.Background(), config, client, candidateFinder, nil) require.NoError(t, err) ret.RegisterSubscriber(subscriber.Collect) @@ -461,7 +464,7 @@ func TestLinkSystemPerRequest(t *testing.T) { config := retriever.RetrieverConfig{} // --- create --- - ret, err := retriever.NewRetriever(context.Background(), config, client, candidateFinder) + ret, err := retriever.NewRetriever(context.Background(), config, client, candidateFinder, nil) require.NoError(t, err) ret.RegisterSubscriber(subscriber.Collect) diff --git a/pkg/server/http/server.go b/pkg/server/http/server.go index 22a371c0..24568b16 100644 --- a/pkg/server/http/server.go +++ b/pkg/server/http/server.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "net/http" - "time" "github.com/filecoin-project/lassie/pkg/lassie" logging "github.com/ipfs/go-log/v2" @@ -23,7 +22,7 @@ type HttpServer struct { } // NewHttpServer creates a new HttpServer -func NewHttpServer(ctx context.Context, address string, port uint) (*HttpServer, error) { +func NewHttpServer(ctx context.Context, lassie *lassie.Lassie, address string, port uint) (*HttpServer, error) { addr := fmt.Sprintf("%s:%d", address, port) listener, err := net.Listen("tcp", addr) // assigns a port if port is 0 if err != nil { @@ -40,13 +39,6 @@ func NewHttpServer(ctx context.Context, address string, port uint) (*HttpServer, Handler: mux, } - // create a lassie instance - lassie, err := lassie.NewLassie(ctx, lassie.WithTimeout(20*time.Second)) - if err != nil { - cancel() - return nil, err - } - httpServer := &HttpServer{ cancel: cancel, ctx: ctx, diff --git a/pkg/types/types.go b/pkg/types/types.go index 433be9d8..fc4a4648 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -2,6 +2,7 @@ package types import ( "context" + "errors" "time" "github.com/filecoin-project/go-state-types/abi" @@ -10,6 +11,7 @@ import ( "github.com/ipfs/go-cid" "github.com/ipld/go-ipld-prime" "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multicodec" ) type RetrievalCandidate struct { @@ -18,10 +20,12 @@ type RetrievalCandidate struct { Metadata metadata.Metadata } -func NewRetrievalCandidate(pid peer.ID, rootCid cid.Cid) RetrievalCandidate { +func NewRetrievalCandidate(pid peer.ID, rootCid cid.Cid, protocols ...metadata.Protocol) RetrievalCandidate { + md := metadata.Default.New(protocols...) return RetrievalCandidate{ MinerPeer: peer.AddrInfo{ID: pid}, RootCid: rootCid, + Metadata: md, } } @@ -156,6 +160,21 @@ type RetrievalEvent interface { // StorageProviderId returns the peer ID of the storage provider if this // retrieval was requested via peer ID StorageProviderId() peer.ID + // Protocol + Protocols() []multicodec.Code +} + +const BitswapIndentifier = "Bitswap" + +func Identifier(event RetrievalEvent) string { + if event.StorageProviderId() != peer.ID("") { + return event.StorageProviderId().String() + } + protocols := event.Protocols() + if len(protocols) == 1 && protocols[0] == multicodec.TransportBitswap { + return BitswapIndentifier + } + return "" } // RetrievalEventSubscriber is a function that receives a stream of retrieval @@ -168,3 +187,30 @@ type FindCandidatesResult struct { Candidate RetrievalCandidate Err error } + +type contextKey string + +const retrievalIDKey = contextKey("retrieval-id-key") + +func RegisterRetrievalIDToContext(parentCtx context.Context, id RetrievalID) context.Context { + ctx := context.WithValue(parentCtx, retrievalIDKey, id) + return ctx +} + +// ErrMissingContextKey indicates no retrieval context key was present for a given context +var ErrMissingContextKey = errors.New("context key for retrieval is missing") + +// ErrIncorrectContextValue indicates a value for the retrieval id context key that wasn't a retrieval id +var ErrIncorrectContextValue = errors.New("context key does not point to a valid retrieval id") + +func RetrievalIDFromContext(ctx context.Context) (RetrievalID, error) { + sk := ctx.Value(retrievalIDKey) + if sk == nil { + return RetrievalID{}, ErrMissingContextKey + } + id, ok := sk.(RetrievalID) + if !ok { + return RetrievalID{}, ErrIncorrectContextValue + } + return id, nil +}