From 256e508665fdbbed477628147fc44b4b4b9c2ced Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 11:16:46 +0100 Subject: [PATCH 01/23] feat: UCAN 1.0 --- README.md | 2 + cmd/client/admin/provider/deregister.go | 6 +- cmd/client/admin/provider/list.go | 6 +- cmd/client/admin/provider/register.go | 12 +- cmd/client/admin/provider/weight/set.go | 6 +- cmd/client/lib/client.go | 9 +- cmd/identity/parse.go | 6 +- go.mod | 64 +- go.sum | 60 +- internal/fx/app.go | 3 - internal/fx/app_test.go | 6 +- internal/fx/clients.go | 7 +- internal/fx/service/handlers/provider.go | 91 ++- internal/fx/service/provider.go | 17 +- internal/testutil/alias.go | 3 +- internal/testutil/aws.go | 5 +- pkg/billing/service.go | 8 +- pkg/billing/service_test.go | 4 +- .../admin/provider/datamodel/cbor_gen.go | 658 ++++++++++++++++ .../admin/provider/datamodel/deregister.go | 7 + .../admin/provider/datamodel/gen/main.go | 23 + .../admin/provider/datamodel/json_gen.go | 648 ++++++++++++++++ .../admin/provider/datamodel/list.go | 16 + .../admin/provider/datamodel/register.go | 8 + pkg/capabilities/admin/provider/deregister.go | 33 +- pkg/capabilities/admin/provider/list.go | 43 +- .../admin/provider/provider.ipldsch | 33 - pkg/capabilities/admin/provider/register.go | 33 +- pkg/capabilities/admin/provider/schema.go | 54 -- .../provider/weight/datamodel/cbor_gen.go | 209 ++++++ .../provider/weight/datamodel/gen/main.go | 19 + .../provider/weight/datamodel/json_gen.go | 180 +++++ .../admin/provider/weight/datamodel/set.go | 9 + pkg/capabilities/admin/provider/weight/set.go | 24 + pkg/capabilities/admin/provider/weight_set.go | 33 - pkg/client/client.go | 310 +++----- pkg/identity/identity.go | 12 +- pkg/indexerclient/client.go | 250 ++----- pkg/lib/didmailto/did.go | 63 -- pkg/lib/didmailto/did_test.go | 141 ---- pkg/lib/errors/alias.go | 8 - pkg/lib/errors/error.go | 40 - pkg/lib/ucan_client/execute.go | 84 +++ pkg/lib/ucan_server/email_auth.go | 96 +++ pkg/lib/ucan_server/events.go | 69 ++ pkg/lib/ucan_server/proofs.go | 73 ++ pkg/lib/ucan_server/validation.go | 65 ++ pkg/lib/ucan_server/validation_test.go | 122 +++ pkg/lib/ucans/delegations.go | 82 -- pkg/piriclient/client.go | 536 ++++---------- pkg/piriclient/provider.go | 2 +- pkg/provisioning/service.go | 35 +- pkg/provisioning/service_test.go | 4 +- pkg/routing/service.go | 22 +- pkg/routing/service_test.go | 18 +- pkg/service/handlers/access_authorize.go | 199 ----- pkg/service/handlers/access_authorize_test.go | 220 ------ pkg/service/handlers/access_claim.go | 252 ++----- pkg/service/handlers/access_claim_test.go | 208 +++--- pkg/service/handlers/access_confirm.go | 292 ++++---- pkg/service/handlers/access_confirm_test.go | 238 +++--- pkg/service/handlers/access_delegate.go | 147 ++-- pkg/service/handlers/access_delegate_test.go | 206 +++--- pkg/service/handlers/access_request.go | 149 ++++ pkg/service/handlers/access_request_test.go | 210 ++++++ .../handlers/admin_provider_deregister.go | 68 +- .../admin_provider_deregister_test.go | 149 ++++ pkg/service/handlers/admin_provider_list.go | 94 +-- .../handlers/admin_provider_list_test.go | 149 ++++ .../handlers/admin_provider_register.go | 110 +-- .../handlers/admin_provider_register_test.go | 172 ++--- .../handlers/admin_provider_weight_set.go | 79 +- .../admin_provider_weight_set_test.go | 116 +-- pkg/service/handlers/blob_add.go | 415 +++++++++++ pkg/service/handlers/blob_add_test.go | 520 +++++++++++++ pkg/service/handlers/blob_list.go | 55 ++ pkg/service/handlers/blob_list_test.go | 203 +++++ pkg/service/handlers/blob_replicate.go | 477 ++++++++++++ pkg/service/handlers/blob_replicate_test.go | 493 ++++++++++++ pkg/service/handlers/filecoin_offer.go | 48 -- pkg/service/handlers/handlers.go | 11 + pkg/service/handlers/index_add.go | 74 ++ pkg/service/handlers/index_add_test.go | 244 ++++++ pkg/service/handlers/provider_add.go | 144 ++-- pkg/service/handlers/provider_add_test.go | 330 ++++----- pkg/service/handlers/space_blob_add.go | 430 ----------- pkg/service/handlers/space_blob_add_test.go | 343 --------- pkg/service/handlers/space_blob_list.go | 79 -- pkg/service/handlers/space_blob_list_test.go | 202 ----- pkg/service/handlers/space_blob_replicate.go | 477 ------------ .../handlers/space_blob_replicate_test.go | 493 ------------ pkg/service/handlers/space_index_add.go | 148 ---- pkg/service/handlers/space_index_add_test.go | 228 ------ pkg/service/handlers/space_info.go | 82 +- pkg/service/handlers/space_info_test.go | 156 ++-- pkg/service/handlers/ucan_conclude.go | 155 ++-- .../ucan_conclude_blob_replica_transfer.go | 432 +++++------ ...can_conclude_blob_replica_transfer_test.go | 700 +++++++++--------- .../handlers/ucan_conclude_http_put.go | 112 +-- .../handlers/ucan_conclude_http_put_test.go | 348 ++++----- pkg/service/handlers/ucan_conclude_test.go | 276 ++++--- pkg/service/handlers/upload_add.go | 92 +-- pkg/service/handlers/upload_add_test.go | 263 ++++--- pkg/service/handlers/upload_list.go | 74 +- pkg/service/handlers/upload_list_test.go | 220 +++--- pkg/service/handlers/upload_shard_list.go | 77 +- .../handlers/upload_shard_list_test.go | 172 +++-- pkg/service/service.go | 230 +----- pkg/store/agent/agent.go | 18 +- pkg/store/agent/agent_test.go | 102 +-- pkg/store/agent/aws/store.go | 83 ++- pkg/store/agent/index.go | 255 +------ pkg/store/agent/memory/store.go | 99 ++- pkg/store/agent/memory/util.go | 51 -- pkg/store/blob_registry/aws/store.go | 10 +- pkg/store/blob_registry/blob_registry.go | 10 +- pkg/store/blob_registry/blob_registry_test.go | 9 +- pkg/store/blob_registry/memory/store.go | 6 +- pkg/store/consumer/aws/store.go | 2 +- pkg/store/consumer/consumer.go | 4 +- pkg/store/consumer/consumer_test.go | 1 - pkg/store/consumer/memory/store.go | 2 +- pkg/store/customer/aws/store.go | 2 +- pkg/store/customer/customer.go | 4 +- pkg/store/customer/customer_test.go | 1 - pkg/store/customer/memory/memory.go | 2 +- pkg/store/delegation/aws/store.go | 81 +- pkg/store/delegation/delegation.go | 8 +- pkg/store/delegation/delegation_test.go | 34 +- pkg/store/delegation/memory/store.go | 50 +- pkg/store/metrics/aws/store.go | 2 +- pkg/store/metrics/memory/store.go | 2 +- pkg/store/metrics/metrics.go | 18 +- pkg/store/metrics/metrics_test.go | 1 - pkg/store/replica/aws/store.go | 4 +- pkg/store/replica/memory/store.go | 4 +- pkg/store/replica/replica.go | 4 +- pkg/store/replica/replica_test.go | 1 - pkg/store/revocation/aws/store.go | 2 +- pkg/store/revocation/memory/store.go | 2 +- pkg/store/revocation/revocation.go | 2 +- pkg/store/revocation/revocation_test.go | 1 - pkg/store/space_diff/aws/store.go | 2 +- pkg/store/space_diff/memory/store.go | 2 +- pkg/store/space_diff/space_diff.go | 2 +- pkg/store/space_diff/space_diff_test.go | 1 - pkg/store/storage_provider/aws/store.go | 25 +- pkg/store/storage_provider/memory/store.go | 15 +- .../storage_provider/storage_provider.go | 9 +- .../storage_provider/storage_provider_test.go | 48 +- pkg/store/subscription/aws/store.go | 2 +- pkg/store/subscription/memory/store.go | 2 +- pkg/store/subscription/subscription.go | 4 +- pkg/store/subscription/subscription_test.go | 3 +- pkg/store/upload/aws/store.go | 40 +- pkg/store/upload/memory/store.go | 6 +- pkg/store/upload/upload.go | 7 +- pkg/store/upload/upload_test.go | 27 +- 158 files changed, 8966 insertions(+), 8344 deletions(-) create mode 100644 pkg/capabilities/admin/provider/datamodel/cbor_gen.go create mode 100644 pkg/capabilities/admin/provider/datamodel/deregister.go create mode 100644 pkg/capabilities/admin/provider/datamodel/gen/main.go create mode 100644 pkg/capabilities/admin/provider/datamodel/json_gen.go create mode 100644 pkg/capabilities/admin/provider/datamodel/list.go create mode 100644 pkg/capabilities/admin/provider/datamodel/register.go delete mode 100644 pkg/capabilities/admin/provider/provider.ipldsch delete mode 100644 pkg/capabilities/admin/provider/schema.go create mode 100644 pkg/capabilities/admin/provider/weight/datamodel/cbor_gen.go create mode 100644 pkg/capabilities/admin/provider/weight/datamodel/gen/main.go create mode 100644 pkg/capabilities/admin/provider/weight/datamodel/json_gen.go create mode 100644 pkg/capabilities/admin/provider/weight/datamodel/set.go create mode 100644 pkg/capabilities/admin/provider/weight/set.go delete mode 100644 pkg/capabilities/admin/provider/weight_set.go delete mode 100644 pkg/lib/didmailto/did.go delete mode 100644 pkg/lib/didmailto/did_test.go delete mode 100644 pkg/lib/errors/alias.go delete mode 100644 pkg/lib/errors/error.go create mode 100644 pkg/lib/ucan_client/execute.go create mode 100644 pkg/lib/ucan_server/email_auth.go create mode 100644 pkg/lib/ucan_server/events.go create mode 100644 pkg/lib/ucan_server/proofs.go create mode 100644 pkg/lib/ucan_server/validation.go create mode 100644 pkg/lib/ucan_server/validation_test.go delete mode 100644 pkg/lib/ucans/delegations.go delete mode 100644 pkg/service/handlers/access_authorize.go delete mode 100644 pkg/service/handlers/access_authorize_test.go create mode 100644 pkg/service/handlers/access_request.go create mode 100644 pkg/service/handlers/access_request_test.go create mode 100644 pkg/service/handlers/admin_provider_deregister_test.go create mode 100644 pkg/service/handlers/admin_provider_list_test.go create mode 100644 pkg/service/handlers/blob_add.go create mode 100644 pkg/service/handlers/blob_add_test.go create mode 100644 pkg/service/handlers/blob_list.go create mode 100644 pkg/service/handlers/blob_list_test.go create mode 100644 pkg/service/handlers/blob_replicate.go create mode 100644 pkg/service/handlers/blob_replicate_test.go delete mode 100644 pkg/service/handlers/filecoin_offer.go create mode 100644 pkg/service/handlers/handlers.go create mode 100644 pkg/service/handlers/index_add.go create mode 100644 pkg/service/handlers/index_add_test.go delete mode 100644 pkg/service/handlers/space_blob_add.go delete mode 100644 pkg/service/handlers/space_blob_add_test.go delete mode 100644 pkg/service/handlers/space_blob_list.go delete mode 100644 pkg/service/handlers/space_blob_list_test.go delete mode 100644 pkg/service/handlers/space_blob_replicate.go delete mode 100644 pkg/service/handlers/space_blob_replicate_test.go delete mode 100644 pkg/service/handlers/space_index_add.go delete mode 100644 pkg/service/handlers/space_index_add_test.go delete mode 100644 pkg/store/agent/memory/util.go diff --git a/README.md b/README.md index 354d313..8da3f42 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,5 @@ Sprue supports three store backends, selected by * The following dynamo tables have GSIs that do not exist in w3infra that need to be added: * `consumer` - `consumerV3` and `customerV2` * Using `cid.Cid` in new code over `ipld.Link` to ease transition to UCAN 1.0 when it comes. +* `retrievalAuth` is now an array of CIDs - an explicit delegation chain. +* `/upload/add` now takes an optional `index` CID, allowing us to track/remove indexes. diff --git a/cmd/client/admin/provider/deregister.go b/cmd/client/admin/provider/deregister.go index e3ef99c..61ff1e3 100644 --- a/cmd/client/admin/provider/deregister.go +++ b/cmd/client/admin/provider/deregister.go @@ -1,8 +1,8 @@ package provider import ( - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/cmd/client/lib" + "github.com/fil-forge/ucantone/did" "github.com/spf13/cobra" ) @@ -15,12 +15,12 @@ var deregisterCmd = &cobra.Command{ } func doDeregister(cmd *cobra.Command, args []string) error { - c, _, _, id := lib.InitClient(cmd) + c, _, _, _ := lib.InitClient(cmd) providerID, err := did.Parse(args[0]) cobra.CheckErr(err) - _, err = c.AdminProviderDeregister(cmd.Context(), id.Signer, providerID) + _, err = c.AdminProviderDeregister(cmd.Context(), providerID) cobra.CheckErr(err) cmd.Println("Provider deregistered successfully") diff --git a/cmd/client/admin/provider/list.go b/cmd/client/admin/provider/list.go index 0433e74..123a524 100644 --- a/cmd/client/admin/provider/list.go +++ b/cmd/client/admin/provider/list.go @@ -16,9 +16,9 @@ var listCmd = &cobra.Command{ } func doList(cmd *cobra.Command, args []string) error { - c, _, _, id := lib.InitClient(cmd) + c, _, _, _ := lib.InitClient(cmd) - res, err := c.AdminProviderList(cmd.Context(), id.Signer) + res, _, err := c.AdminProviderList(cmd.Context()) cobra.CheckErr(err) if len(res.Providers) == 0 { @@ -29,7 +29,7 @@ func doList(cmd *cobra.Command, args []string) error { table := lib.NewTable(cmd.OutOrStdout()) table.SetHeader([]string{"ID", "Weight", "Replication Weight", "URL"}) for _, p := range res.Providers { - table.Append([]string{p.ID.String(), fmt.Sprintf("%d", p.Weight), fmt.Sprintf("%d", p.ReplicationWeight), p.Endpoint}) + table.Append([]string{p.Provider.String(), fmt.Sprintf("%d", p.Weight), fmt.Sprintf("%d", p.ReplicationWeight), p.Endpoint}) } table.Render() diff --git a/cmd/client/admin/provider/register.go b/cmd/client/admin/provider/register.go index 1502eab..d8a44af 100644 --- a/cmd/client/admin/provider/register.go +++ b/cmd/client/admin/provider/register.go @@ -3,13 +3,13 @@ package provider import ( "net/url" - "github.com/fil-forge/go-ucanto/core/delegation" "github.com/fil-forge/sprue/cmd/client/lib" + "github.com/fil-forge/ucantone/did" "github.com/spf13/cobra" ) var registerCmd = &cobra.Command{ - Use: "register ", + Use: "register ", Aliases: []string{"add"}, Short: "Register a storage provider with the service", Args: cobra.ExactArgs(2), @@ -17,15 +17,15 @@ var registerCmd = &cobra.Command{ } func doRegister(cmd *cobra.Command, args []string) error { - c, _, _, id := lib.InitClient(cmd) + c, _, _, _ := lib.InitClient(cmd) - endpoint, err := url.Parse(args[0]) + id, err := did.Parse(args[0]) cobra.CheckErr(err) - proof, err := delegation.Parse(args[1]) + endpoint, err := url.Parse(args[1]) cobra.CheckErr(err) - _, err = c.AdminProviderRegister(cmd.Context(), id.Signer, endpoint.String(), proof) + _, err = c.AdminProviderRegister(cmd.Context(), id, endpoint.String()) cobra.CheckErr(err) cmd.Println("Provider registered successfully") diff --git a/cmd/client/admin/provider/weight/set.go b/cmd/client/admin/provider/weight/set.go index 23f4bed..a3290a5 100644 --- a/cmd/client/admin/provider/weight/set.go +++ b/cmd/client/admin/provider/weight/set.go @@ -3,8 +3,8 @@ package weight import ( "strconv" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/cmd/client/lib" + "github.com/fil-forge/ucantone/did" "github.com/spf13/cobra" ) @@ -16,7 +16,7 @@ var setCmd = &cobra.Command{ } func doSet(cmd *cobra.Command, args []string) error { - c, _, _, id := lib.InitClient(cmd) + c, _, _, _ := lib.InitClient(cmd) providerID, err := did.Parse(args[0]) cobra.CheckErr(err) @@ -27,7 +27,7 @@ func doSet(cmd *cobra.Command, args []string) error { replicationWeight, err := strconv.ParseInt(args[2], 10, 0) cobra.CheckErr(err) - _, err = c.AdminProviderWeightSet(cmd.Context(), id.Signer, providerID, int(weight), int(replicationWeight)) + _, err = c.AdminProviderWeightSet(cmd.Context(), providerID, int(weight), int(replicationWeight)) cobra.CheckErr(err) cmd.Println("Provider weight set successfully") diff --git a/cmd/client/lib/client.go b/cmd/client/lib/client.go index d3ac93d..f2e19fb 100644 --- a/cmd/client/lib/client.go +++ b/cmd/client/lib/client.go @@ -2,6 +2,7 @@ package lib import ( "fmt" + "net/url" "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/internal/fx" @@ -25,10 +26,10 @@ func InitClient(cmd *cobra.Command) (*client.Client, *config.Config, *zap.Logger id, err := fx.NewIdentity(cfg, logger) cobra.CheckErr(err) - c, err := client.New( - id.Signer.DID(), - client.WithServiceURL(fmt.Sprintf("http://%s:%d", cfg.Server.Host, cfg.Server.Port)), - ) + endpoint, err := url.Parse(fmt.Sprintf("http://%s:%d", cfg.Server.Host, cfg.Server.Port)) + cobra.CheckErr(err) + + c, err := client.New(id.Signer.DID(), endpoint, id.Signer, logger) cobra.CheckErr(err) return c, cfg, logger, id } diff --git a/cmd/identity/parse.go b/cmd/identity/parse.go index b88740d..3d8eacf 100644 --- a/cmd/identity/parse.go +++ b/cmd/identity/parse.go @@ -8,8 +8,8 @@ import ( "io" "os" - signer "github.com/fil-forge/go-ucanto/principal/ed25519/signer" - verifier "github.com/fil-forge/go-ucanto/principal/ed25519/verifier" + signer "github.com/fil-forge/ucantone/principal/ed25519" + verifier "github.com/fil-forge/ucantone/principal/ed25519/verifier" "github.com/spf13/cobra" ) @@ -61,7 +61,7 @@ var parseCmd = &cobra.Command{ return fmt.Errorf("PKCS#8 private key does not implement ed25519") } - key, err := signer.FromRaw(ed25519SK) + key, err := signer.FromRaw(ed25519SK.Seed()) if err != nil { return fmt.Errorf("decoding ed25519 private key: %w", err) } diff --git a/go.mod b/go.mod index b4b26cb..3c3a872 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,23 @@ module github.com/fil-forge/sprue go 1.25.3 require ( + github.com/alanshaw/dag-json-gen v0.0.4 github.com/aws/aws-sdk-go-v2 v1.41.3 github.com/aws/aws-sdk-go-v2/config v1.32.11 github.com/aws/aws-sdk-go-v2/credentials v1.19.11 github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.34 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 + github.com/docker/docker v28.5.2+incompatible github.com/fil-forge/go-libstoracha v0.0.0-20260507180245-218ac18ff773 github.com/fil-forge/go-ucanto v0.0.0-20260507172450-5cb5d073f8ab + github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f + github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b github.com/google/uuid v1.6.0 - github.com/ipfs/go-cid v0.6.0 - github.com/ipfs/go-log/v2 v2.9.1 + github.com/ipfs/go-cid v0.6.1 github.com/ipld/go-ipld-prime v0.22.0 - github.com/jackc/pgx/v5 v5.9.1 + github.com/jackc/pgx/v5 v5.8.0 github.com/labstack/echo/v4 v4.14.0 - github.com/multiformats/go-multiaddr v0.16.1 - github.com/multiformats/go-multibase v0.2.0 github.com/multiformats/go-multihash v0.2.3 github.com/olekukonko/tablewriter v0.0.5 github.com/pressly/goose/v3 v3.27.0 @@ -29,24 +30,10 @@ require ( github.com/testcontainers/testcontainers-go/modules/dynamodb v0.41.0 github.com/testcontainers/testcontainers-go/modules/minio v0.40.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 + github.com/whyrusleeping/cbor-gen v0.3.1 go.uber.org/fx v1.24.0 go.uber.org/zap v1.27.1 -) - -require ( - github.com/ipfs/boxo v0.34.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/mfridman/interpolate v0.0.2 // indirect - github.com/moby/moby/api v1.54.1 // indirect - github.com/moby/moby/client v0.4.0 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect - github.com/sethvargo/go-retry v0.3.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect - golang.org/x/sync v0.20.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da ) require ( @@ -79,16 +66,13 @@ require ( github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.10.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/filecoin-project/go-data-segment v0.0.1 // indirect github.com/filecoin-project/go-fil-commcid v0.2.0 // indirect - github.com/filecoin-project/go-fil-commp-hashhash v0.2.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -99,6 +83,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect + github.com/ipfs/boxo v0.34.0 // indirect github.com/ipfs/go-block-format v0.2.3 // indirect github.com/ipfs/go-blockservice v0.5.2 // indirect github.com/ipfs/go-datastore v0.9.1 // indirect @@ -110,33 +95,40 @@ require ( github.com/ipfs/go-ipld-format v0.6.3 // indirect github.com/ipfs/go-ipld-legacy v0.2.2 // indirect github.com/ipfs/go-log v1.0.5 // indirect + github.com/ipfs/go-log/v2 v2.9.1 // indirect github.com/ipfs/go-merkledag v0.11.0 // indirect github.com/ipfs/go-metrics-interface v0.3.0 // indirect github.com/ipfs/go-verifcid v0.0.3 // indirect github.com/ipld/go-car v0.6.2 // indirect github.com/ipld/go-codec-dagpb v1.7.0 // indirect - github.com/ipni/go-libipni v0.7.5 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/klauspost/compress v1.18.5 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/labstack/gommon v0.4.2 // indirect - github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/libp2p/go-libp2p v0.47.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/moby/api v1.54.1 // indirect + github.com/moby/moby/client v0.4.0 // indirect github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect + github.com/mr-tron/base58 v1.3.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multiaddr v0.16.1 // indirect + github.com/multiformats/go-multibase v0.3.0 // indirect github.com/multiformats/go-multicodec v0.10.0 // indirect github.com/multiformats/go-varint v0.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -148,6 +140,7 @@ require ( github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect github.com/shirou/gopsutil/v4 v4.26.3 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect @@ -161,27 +154,32 @@ require ( github.com/ucan-wg/go-ucan v0.0.0-20240916120445-37f52863156c // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/whyrusleeping/cbor-gen v0.3.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.49.0 // indirect + golang.org/x/crypto v0.50.0 // indirect golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/net v0.52.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect + pitr.ca/jsontokenizer v0.3.0 // indirect ) diff --git a/go.sum b/go.sum index 4146b1d..b2ce600 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/alanshaw/dag-json-gen v0.0.4 h1:qoryz04TVH6zu16NRFnzgolzQGaPfTvoIawv/F5rDoY= +github.com/alanshaw/dag-json-gen v0.0.4/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -141,8 +143,6 @@ github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TI 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= -github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= -github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -171,14 +171,16 @@ github.com/fil-forge/go-libstoracha v0.0.0-20260507180245-218ac18ff773 h1:MUBpVn github.com/fil-forge/go-libstoracha v0.0.0-20260507180245-218ac18ff773/go.mod h1:zE7vPrAYZpoamvIFOtcZvfrWXRi6jyvS5O4o04+X1HE= github.com/fil-forge/go-ucanto v0.0.0-20260507172450-5cb5d073f8ab h1:2J2cDThqTKP6/0k3SfdlSxfyPa3aLqjTYnmvbEcryfg= github.com/fil-forge/go-ucanto v0.0.0-20260507172450-5cb5d073f8ab/go.mod h1:lZF3UXZ2hGLKYmXdquG50JqI9pRlUrV6lubGtgOYfwc= +github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f h1:bxGaLAF+kD5ADpX5N68hwBn2Wc+qW4cC9ptCzymQ7xs= +github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f/go.mod h1:IdNOBIQeH59dG99FnLmqrwrvaJ4Akm4IPUng8DeqFig= +github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b h1:8tvw5e7S1ntUnm0v/OTWLZelbJWQcho/WZI+ttCPv+A= +github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= github.com/filecoin-project/go-data-segment v0.0.1 h1:1wmDxOG4ubWQm3ZC1XI5nCon5qgSq7Ra3Rb6Dbu10Gs= github.com/filecoin-project/go-data-segment v0.0.1/go.mod h1:H0/NKbsRxmRFBcLibmABv+yFNHdmtl5AyplYLnb0Zv4= github.com/filecoin-project/go-fil-commcid v0.2.0 h1:B+5UX8XGgdg/XsdUpST4pEBviKkFOw+Fvl2bLhSKGpI= github.com/filecoin-project/go-fil-commcid v0.2.0/go.mod h1:8yigf3JDIil+/WpqR5zoKyP0jBPCOGtEqq/K1CcMy9Q= -github.com/filecoin-project/go-fil-commp-hashhash v0.2.0 h1:HYIUugzjq78YvV3vC6rL95+SfC/aSTVSnZSZiDV5pCk= -github.com/filecoin-project/go-fil-commp-hashhash v0.2.0/go.mod h1:VH3fAFOru4yyWar4626IoS5+VGE8SfZiBODJLUigEo4= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -249,6 +251,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -315,8 +319,8 @@ github.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHh github.com/ipfs/go-block-format v0.2.3/go.mod h1:WJaQmPAKhD3LspLixqlqNFxiZ3BZ3xgqxxoSR/76pnA= github.com/ipfs/go-blockservice v0.5.2 h1:in9Bc+QcXwd1apOVM7Un9t8tixPKdaHQFdLSUM1Xgk8= github.com/ipfs/go-blockservice v0.5.2/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk= -github.com/ipfs/go-cid v0.6.0 h1:DlOReBV1xhHBhhfy/gBNNTSyfOM6rLiIx9J7A4DGf30= -github.com/ipfs/go-cid v0.6.0/go.mod h1:NC4kS1LZjzfhK40UGmpXv5/qD2kcMzACYJNntCUiDhQ= +github.com/ipfs/go-cid v0.6.1 h1:T5TnNb08+ueovG76Z5gx1L4Y7QOaGTXHg1F6raWFxIc= +github.com/ipfs/go-cid v0.6.1/go.mod h1:zrY0SwOhjrrIdfPQ/kf+k1sXyJ0QE7cMxfCployLBs0= github.com/ipfs/go-datastore v0.9.1 h1:67Po2epre/o0UxrmkzdS9ZTe2GFGODgTd2odx8Wh6Yo= github.com/ipfs/go-datastore v0.9.1/go.mod h1:zi07Nvrpq1bQwSkEnx3bfjz+SQZbdbWyCNvyxMh9pN0= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= @@ -356,8 +360,6 @@ github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6 github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY= github.com/ipfs/go-peertaskqueue v0.8.2 h1:PaHFRaVFdxQk1Qo3OKiHPYjmmusQy7gKQUaL8JDszAU= github.com/ipfs/go-peertaskqueue v0.8.2/go.mod h1:L6QPvou0346c2qPJNiJa6BvOibxDfaiPlqHInmzg0FA= -github.com/ipfs/go-test v0.2.3 h1:Z/jXNAReQFtCYyn7bsv/ZqUwS6E7iIcSpJ2CuzCvnrc= -github.com/ipfs/go-test v0.2.3/go.mod h1:QW8vSKkwYvWFwIZQLGQXdkt9Ud76eQXRQ9Ao2H+cA1o= github.com/ipfs/go-verifcid v0.0.3 h1:gmRKccqhWDocCRkC+a59g5QW7uJw5bpX9HWBevXa0zs= github.com/ipfs/go-verifcid v0.0.3/go.mod h1:gcCtGniVzelKrbk9ooUSX/pM3xlH73fZZJDzQJRvOUw= github.com/ipld/go-car v0.6.2 h1:Hlnl3Awgnq8icK+ze3iRghk805lu8YNq3wlREDTF2qc= @@ -366,14 +368,12 @@ github.com/ipld/go-codec-dagpb v1.7.0 h1:hpuvQjCSVSLnTnHXn+QAMR0mLmb1gA6wl10LExo github.com/ipld/go-codec-dagpb v1.7.0/go.mod h1:rD3Zg+zub9ZnxcLwfol/OTQRVjaLzXypgy4UqHQvilM= github.com/ipld/go-ipld-prime v0.22.0 h1:YJhDhjEOvOYaqshd3b4atIWUoRg/rKrgmwCyUHwlbuY= github.com/ipld/go-ipld-prime v0.22.0/go.mod h1:ol7vKxOOVgEh0iAPuiDalM+0gScXVMA5ZZa4DVrTnEA= -github.com/ipni/go-libipni v0.7.5 h1:IpEjuYhhUXhB6FFSOzyyXgqJ8v0TH6h4FkFSF2jYvs8= -github.com/ipni/go-libipni v0.7.5/go.mod h1:Dnx4ojxBI/TwVgngsa+M/zzNeKxqY0hiwqip0PObYhc= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= -github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -421,13 +421,15 @@ github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUI 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-netroute v0.3.0 h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc= -github.com/libp2p/go-netroute v0.3.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= +github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8= +github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -485,8 +487,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI= +github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= @@ -495,8 +497,8 @@ github.com/multiformats/go-multiaddr v0.16.1 h1:fgJ0Pitow+wWXzN9do+1b8Pyjmo8m5Wh github.com/multiformats/go-multiaddr v0.16.1/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= -github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= -github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multibase v0.3.0 h1:8helZD2+4Db7NNWFiktk2NePbF0boolBe6bDQvM4r68= +github.com/multiformats/go-multibase v0.3.0/go.mod h1:MoBLQPCkRTOL3eveIPO81860j2AQY8JwcnNlRkGRUfI= github.com/multiformats/go-multicodec v0.10.0 h1:UpP223cig/Cx8J76jWt91njpK3GTAO1w02sdcjZDSuc= github.com/multiformats/go-multicodec v0.10.0/go.mod h1:wg88pM+s2kZJEQfRCKBNU+g32F5aWBEjyFHXvZLTcLI= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= @@ -644,6 +646,10 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= +gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= +gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= +gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -704,8 +710,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -865,14 +871,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -883,8 +889,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1101,6 +1107,8 @@ modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +pitr.ca/jsontokenizer v0.3.0 h1:Qr70hk4/wcpFEgu/6aJ+nvYQ6x/xS0WOkC627ceiI/M= +pitr.ca/jsontokenizer v0.3.0/go.mod h1:3DJdA2QNOU6cI0XkH6pRKZ4Oe8G5SDRUQ6PFAwaQ3YY= 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/internal/fx/app.go b/internal/fx/app.go index 4c5ca76..cb3070d 100644 --- a/internal/fx/app.go +++ b/internal/fx/app.go @@ -1,14 +1,11 @@ package fx import ( - "fmt" - "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/internal/fx/service" "github.com/fil-forge/sprue/internal/fx/service/handlers" "github.com/fil-forge/sprue/internal/fx/store/aws" "github.com/fil-forge/sprue/internal/fx/store/memory" - "github.com/fil-forge/sprue/internal/fx/store/postgres" "go.uber.org/fx" ) diff --git a/internal/fx/app_test.go b/internal/fx/app_test.go index 7ff844c..f077372 100644 --- a/internal/fx/app_test.go +++ b/internal/fx/app_test.go @@ -4,10 +4,10 @@ import ( "runtime" "testing" - ed25519 "github.com/fil-forge/go-ucanto/principal/ed25519/signer" "github.com/fil-forge/sprue/internal/config" appfx "github.com/fil-forge/sprue/internal/fx" "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/ucantone/principal/signer" "github.com/google/uuid" "go.uber.org/fx/fxtest" ) @@ -45,7 +45,7 @@ func TestWireApp(t *testing.T) { Port: 0, }, Identity: config.IdentityConfig{ - PrivateKey: testutil.Must(ed25519.Format(testutil.WebService))(t), + PrivateKey: signer.Format(testutil.WebService), ServiceDID: testutil.WebService.DID().String(), }, Indexer: config.IndexerConfig{ @@ -152,7 +152,7 @@ func TestWireApp(t *testing.T) { Port: 0, }, Identity: config.IdentityConfig{ - PrivateKey: testutil.Must(ed25519.Format(testutil.WebService))(t), + PrivateKey: signer.Format(testutil.WebService), ServiceDID: testutil.WebService.DID().String(), }, Indexer: config.IndexerConfig{ diff --git a/internal/fx/clients.go b/internal/fx/clients.go index 5a672fe..e61bba3 100644 --- a/internal/fx/clients.go +++ b/internal/fx/clients.go @@ -3,14 +3,13 @@ package fx import ( "net/url" - "github.com/fil-forge/go-ucanto/did" - "go.uber.org/fx" - "go.uber.org/zap" - "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/indexerclient" "github.com/fil-forge/sprue/pkg/piriclient" + "github.com/fil-forge/ucantone/did" + "go.uber.org/fx" + "go.uber.org/zap" ) // ClientsModule provides external service clients. diff --git a/internal/fx/service/handlers/provider.go b/internal/fx/service/handlers/provider.go index c21496c..3d3dbbd 100644 --- a/internal/fx/service/handlers/provider.go +++ b/internal/fx/service/handlers/provider.go @@ -1,90 +1,89 @@ package handlers import ( - "go.uber.org/fx" - - "github.com/fil-forge/go-ucanto/ucan" "github.com/fil-forge/sprue/pkg/service/handlers" + "github.com/fil-forge/ucantone/ucan" + "go.uber.org/fx" ) var Module = fx.Module("service-handlers", fx.Provide( fx.Annotate( - handlers.WithAccessAuthorizeMethod, - fx.ResultTags(`group:"ucan_options"`), - ), - fx.Annotate( - handlers.WithAccessClaimMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewAccessClaimHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithAccessDelegateMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewAccessConfirmHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithAdminProviderDeregisterMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewAccessDelegateHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithAdminProviderListMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewAccessRequestHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithAdminProviderRegisterMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewAdminProviderDeregisterHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithAdminProviderWeightSetMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewAdminProviderListHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithFilecoinOfferMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewAdminProviderRegisterHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithProviderAddMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewAdminProviderWeightSetHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithSpaceBlobAddMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewBlobAddHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), + // fx.Annotate( + // handlers.NewBlobReplicateHandler, + // fx.ResultTags(`group:"ucan_handlers"`), + // ), fx.Annotate( - handlers.WithSpaceBlobReplicateMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewIndexAddHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithSpaceInfoMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewProviderAddHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithSpaceIndexAddMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewSpaceInfoHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithUCANConcludeMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewUCANConcludeHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithUploadAddMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewUploadAddHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithUploadListMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewUploadListHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( - handlers.WithUploadShardListMethod, - fx.ResultTags(`group:"ucan_options"`), + handlers.NewUploadShardListHandler, + fx.ResultTags(`group:"ucan_handlers"`), ), fx.Annotate( handlers.NewHTTPPutConcludeHandler, fx.ResultTags(`group:"ucan_conclude_handlers"`), ), - fx.Annotate( - handlers.NewBlobReplicaTransferConcludeHandler, - fx.ResultTags(`group:"ucan_conclude_handlers"`), - ), + // fx.Annotate( + // handlers.NewBlobReplicaTransferConcludeHandler, + // fx.ResultTags(`group:"ucan_conclude_handlers"`), + // ), NewConcludeHandlers, ), ) @@ -94,10 +93,10 @@ type ConcludeHandlersParams struct { Handlers []handlers.ConclusionHandler `group:"ucan_conclude_handlers"` } -func NewConcludeHandlers(params ConcludeHandlersParams) map[ucan.Ability]handlers.ConclusionHandlerFunc { - handlers := make(map[ucan.Ability]handlers.ConclusionHandlerFunc, len(params.Handlers)) +func NewConcludeHandlers(params ConcludeHandlersParams) map[ucan.Command]handlers.ConclusionHandlerFunc { + handlers := make(map[ucan.Command]handlers.ConclusionHandlerFunc, len(params.Handlers)) for _, h := range params.Handlers { - handlers[h.Ability] = h.Handler + handlers[h.Command] = h.Handler } return handlers } diff --git a/internal/fx/service/provider.go b/internal/fx/service/provider.go index ed851b6..e32696d 100644 --- a/internal/fx/service/provider.go +++ b/internal/fx/service/provider.go @@ -1,15 +1,14 @@ package service import ( - "go.uber.org/fx" - "go.uber.org/zap" - - "github.com/fil-forge/go-ucanto/server" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/indexerclient" "github.com/fil-forge/sprue/pkg/service" + "github.com/fil-forge/sprue/pkg/service/handlers" "github.com/fil-forge/sprue/pkg/store/agent" "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/server" + "go.uber.org/fx" + "go.uber.org/zap" ) // Module provides the UCAN service. @@ -24,12 +23,12 @@ type ServiceParams struct { Identity *identity.Identity AgentStore agent.Store DelegationStore delegation.Store - IndexerClient *indexerclient.Client `optional:"true"` Logger *zap.Logger - Options []server.Option `group:"ucan_options"` + Handlers []handlers.Handler `group:"ucan_handlers"` + Options []server.HTTPOption `group:"ucan_options"` } // NewService creates the UCAN service with all handlers registered. -func NewService(p ServiceParams) (*service.Service, error) { - return service.New(p.Identity, p.AgentStore, p.DelegationStore, p.IndexerClient, p.Logger, p.Options...) +func NewService(p ServiceParams) *service.Service { + return service.New(p.Identity, p.AgentStore, p.DelegationStore, p.Handlers, p.Logger, p.Options...) } diff --git a/internal/testutil/alias.go b/internal/testutil/alias.go index dbc142d..0507ba9 100644 --- a/internal/testutil/alias.go +++ b/internal/testutil/alias.go @@ -3,13 +3,14 @@ package testutil import ( "testing" - "github.com/fil-forge/go-libstoracha/testutil" + "github.com/fil-forge/libforge/testutil" "github.com/ipfs/go-cid" ) var ( Alice = testutil.Alice Bob = testutil.Bob + Carol = testutil.Carol Mallory = testutil.Mallory Service = testutil.Service WebService = testutil.WebService diff --git a/internal/testutil/aws.go b/internal/testutil/aws.go index be036a9..053adb8 100644 --- a/internal/testutil/aws.go +++ b/internal/testutil/aws.go @@ -10,7 +10,6 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/fil-forge/go-libstoracha/testutil" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" tcdynamodb "github.com/testcontainers/testcontainers-go/modules/dynamodb" @@ -27,7 +26,7 @@ func CreateDynamo(t *testing.T) *url.URL { require.NoError(t, err) t.Logf("DynamoDB listening on: http://%s", endpoint) - return testutil.Must(url.Parse("http://" + endpoint))(t) + return Must(url.Parse("http://" + endpoint))(t) } func NewDynamoClient(t *testing.T, endpoint *url.URL) *dynamodb.Client { @@ -62,7 +61,7 @@ func CreateS3(t *testing.T) *url.URL { require.NoError(t, err) t.Logf("S3 listening on: http://%s", endpoint) - return testutil.Must(url.Parse("http://" + endpoint))(t) + return Must(url.Parse("http://" + endpoint))(t) } func NewS3Client(t *testing.T, endpoint *url.URL) *s3.Client { diff --git a/pkg/billing/service.go b/pkg/billing/service.go index a3a5d32..0d87049 100644 --- a/pkg/billing/service.go +++ b/pkg/billing/service.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store/customer" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" ) const MissingPaymentPlanErrorName = "MissingPaymentPlan" @@ -30,9 +30,9 @@ func (s *Service) PaymentPlan(ctx context.Context, account did.DID) (did.DID, er r, err := s.customerStore.Get(ctx, account) if err != nil { if errors.Is(err, customer.ErrCustomerNotFound) { - return did.Undef, ErrMissingPaymentPlan + return did.DID{}, ErrMissingPaymentPlan } - return did.Undef, fmt.Errorf("getting customer: %w", err) + return did.DID{}, fmt.Errorf("getting customer: %w", err) } return r.Product, nil } diff --git a/pkg/billing/service_test.go b/pkg/billing/service_test.go index fa5038a..fec7e13 100644 --- a/pkg/billing/service_test.go +++ b/pkg/billing/service_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/didmailto" + "github.com/fil-forge/libforge/didmailto" customermemory "github.com/fil-forge/sprue/pkg/store/customer/memory" + "github.com/fil-forge/ucantone/did" "github.com/stretchr/testify/require" ) diff --git a/pkg/capabilities/admin/provider/datamodel/cbor_gen.go b/pkg/capabilities/admin/provider/datamodel/cbor_gen.go new file mode 100644 index 0000000..6719e9a --- /dev/null +++ b/pkg/capabilities/admin/provider/datamodel/cbor_gen.go @@ -0,0 +1,658 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package datamodel + +import ( + "fmt" + "io" + "math" + "sort" + + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +func (t *ListArgumentsModel) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{160}); err != nil { + return err + } + return nil +} + +func (t *ListArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { + *t = ListArgumentsModel{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("ListArgumentsModel: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 0) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *ProviderModel) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{164}); err != nil { + return err + } + + // t.Weight (int64) (int64) + if len("weight") > 8192 { + return xerrors.Errorf("Value in field \"weight\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("weight"))); err != nil { + return err + } + if _, err := cw.WriteString(string("weight")); err != nil { + return err + } + + if t.Weight >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Weight)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Weight-1)); err != nil { + return err + } + } + + // t.Endpoint (string) (string) + if len("endpoint") > 8192 { + return xerrors.Errorf("Value in field \"endpoint\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("endpoint"))); err != nil { + return err + } + if _, err := cw.WriteString(string("endpoint")); err != nil { + return err + } + + if len(t.Endpoint) > 8192 { + return xerrors.Errorf("Value in field t.Endpoint was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Endpoint))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.Endpoint)); err != nil { + return err + } + + // t.Provider (did.DID) (struct) + if len("provider") > 8192 { + return xerrors.Errorf("Value in field \"provider\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("provider"))); err != nil { + return err + } + if _, err := cw.WriteString(string("provider")); err != nil { + return err + } + + if err := t.Provider.MarshalCBOR(cw); err != nil { + return err + } + + // t.ReplicationWeight (int64) (int64) + if len("replicationWeight") > 8192 { + return xerrors.Errorf("Value in field \"replicationWeight\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("replicationWeight"))); err != nil { + return err + } + if _, err := cw.WriteString(string("replicationWeight")); err != nil { + return err + } + + if t.ReplicationWeight >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ReplicationWeight)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ReplicationWeight-1)); err != nil { + return err + } + } + + return nil +} + +func (t *ProviderModel) UnmarshalCBOR(r io.Reader) (err error) { + *t = ProviderModel{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("ProviderModel: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 17) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Weight (int64) (int64) + case "weight": + { + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + var extraI int64 + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.Weight = int64(extraI) + } + // t.Endpoint (string) (string) + case "endpoint": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Endpoint = string(sval) + } + // t.Provider (did.DID) (struct) + case "provider": + + { + + if err := t.Provider.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Provider: %w", err) + } + + } + // t.ReplicationWeight (int64) (int64) + case "replicationWeight": + { + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + var extraI int64 + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.ReplicationWeight = int64(extraI) + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *ListOKModel) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{161}); err != nil { + return err + } + + // t.Providers ([]datamodel.ProviderModel) (slice) + if len("providers") > 8192 { + return xerrors.Errorf("Value in field \"providers\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("providers"))); err != nil { + return err + } + if _, err := cw.WriteString(string("providers")); err != nil { + return err + } + + if len(t.Providers) > 8192 { + return xerrors.Errorf("Slice value in field t.Providers was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Providers))); err != nil { + return err + } + for _, v := range t.Providers { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + + } + return nil +} + +func (t *ListOKModel) UnmarshalCBOR(r io.Reader) (err error) { + *t = ListOKModel{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("ListOKModel: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 9) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Providers ([]datamodel.ProviderModel) (slice) + case "providers": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 8192 { + return fmt.Errorf("t.Providers: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Providers = make([]ProviderModel, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + _ = maj + _ = extra + _ = err + + { + + if err := t.Providers[i].UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Providers[i]: %w", err) + } + + } + + } + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *RegisterArgumentsModel) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{162}); err != nil { + return err + } + + // t.Endpoint (string) (string) + if len("endpoint") > 8192 { + return xerrors.Errorf("Value in field \"endpoint\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("endpoint"))); err != nil { + return err + } + if _, err := cw.WriteString(string("endpoint")); err != nil { + return err + } + + if len(t.Endpoint) > 8192 { + return xerrors.Errorf("Value in field t.Endpoint was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Endpoint))); err != nil { + return err + } + if _, err := cw.WriteString(string(t.Endpoint)); err != nil { + return err + } + + // t.Provider (did.DID) (struct) + if len("provider") > 8192 { + return xerrors.Errorf("Value in field \"provider\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("provider"))); err != nil { + return err + } + if _, err := cw.WriteString(string("provider")); err != nil { + return err + } + + if err := t.Provider.MarshalCBOR(cw); err != nil { + return err + } + return nil +} + +func (t *RegisterArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { + *t = RegisterArgumentsModel{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("RegisterArgumentsModel: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 8) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Endpoint (string) (string) + case "endpoint": + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Endpoint = string(sval) + } + // t.Provider (did.DID) (struct) + case "provider": + + { + + if err := t.Provider.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Provider: %w", err) + } + + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} +func (t *DeregisterArgumentsModel) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{161}); err != nil { + return err + } + + // t.Provider (did.DID) (struct) + if len("provider") > 8192 { + return xerrors.Errorf("Value in field \"provider\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("provider"))); err != nil { + return err + } + if _, err := cw.WriteString(string("provider")); err != nil { + return err + } + + if err := t.Provider.MarshalCBOR(cw); err != nil { + return err + } + return nil +} + +func (t *DeregisterArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { + *t = DeregisterArgumentsModel{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("DeregisterArgumentsModel: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 8) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Provider (did.DID) (struct) + case "provider": + + { + + if err := t.Provider.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Provider: %w", err) + } + + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} diff --git a/pkg/capabilities/admin/provider/datamodel/deregister.go b/pkg/capabilities/admin/provider/datamodel/deregister.go new file mode 100644 index 0000000..b5e542f --- /dev/null +++ b/pkg/capabilities/admin/provider/datamodel/deregister.go @@ -0,0 +1,7 @@ +package datamodel + +import "github.com/fil-forge/ucantone/did" + +type DeregisterArgumentsModel struct { + Provider did.DID `cborgen:"provider" dagjsongen:"provider"` +} diff --git a/pkg/capabilities/admin/provider/datamodel/gen/main.go b/pkg/capabilities/admin/provider/datamodel/gen/main.go new file mode 100644 index 0000000..47b87ee --- /dev/null +++ b/pkg/capabilities/admin/provider/datamodel/gen/main.go @@ -0,0 +1,23 @@ +package main + +import ( + jsg "github.com/alanshaw/dag-json-gen" + pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" + cbg "github.com/whyrusleeping/cbor-gen" +) + +func main() { + models := []any{ + pdm.ListArgumentsModel{}, + pdm.ProviderModel{}, + pdm.ListOKModel{}, + pdm.RegisterArgumentsModel{}, + pdm.DeregisterArgumentsModel{}, + } + if err := cbg.WriteMapEncodersToFile("../cbor_gen.go", "datamodel", models...); err != nil { + panic(err) + } + if err := jsg.WriteMapEncodersToFile("../json_gen.go", "datamodel", models...); err != nil { + panic(err) + } +} diff --git a/pkg/capabilities/admin/provider/datamodel/json_gen.go b/pkg/capabilities/admin/provider/datamodel/json_gen.go new file mode 100644 index 0000000..0634e1e --- /dev/null +++ b/pkg/capabilities/admin/provider/datamodel/json_gen.go @@ -0,0 +1,648 @@ +// Code generated by github.com/alanshaw/dag-json-gen. DO NOT EDIT. + +package datamodel + +import ( + "errors" + "fmt" + "io" + "math" + "sort" + + jsg "github.com/alanshaw/dag-json-gen" + cid "github.com/ipfs/go-cid" +) + +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort +var _ = errors.Is + +func (t *ListArgumentsModel) MarshalDagJSON(w io.Writer) error { + jw := jsg.NewDagJsonWriter(w) + if t == nil { + err := jw.WriteNull() + return err + } + if err := jw.WriteObjectOpen(); err != nil { + return err + } + if err := jw.WriteObjectClose(); err != nil { + return err + } + return nil +} +func (t *ListArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { + *t = ListArgumentsModel{} + + jr := jsg.NewDagJsonReader(r) + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if err := jr.ReadObjectOpen(); err != nil { + return fmt.Errorf("ListArgumentsModel: %w", err) + } + close, err := jr.PeekObjectClose() + if err != nil { + return fmt.Errorf("ListArgumentsModel: %w", err) + } + if close { + if err := jr.ReadObjectClose(); err != nil { + return fmt.Errorf("ListArgumentsModel: %w", err) + } + } else { + for i := uint64(0); i < 8192; i++ { + name, err := jr.ReadString(8192) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("ListArgumentsModel: string too large") + } + return fmt.Errorf("ListArgumentsModel: %w", err) + } + if err := jr.ReadObjectColon(); err != nil { + return fmt.Errorf("ListArgumentsModel: %w", err) + } + switch name { + default: + // Field doesn't exist on this type, so ignore it + if err := jr.DiscardType(); err != nil { + return fmt.Errorf("ListArgumentsModel: ignoring field %s: %w", name, err) + } + } + + close, err := jr.ReadObjectCloseOrComma() + if err != nil { + return fmt.Errorf("ListArgumentsModel: %w", err) + } + if close { + break + } + if i == 8192-1 { + return fmt.Errorf("ListArgumentsModel: map too large") + } + } + } + + return nil +} +func (t *ProviderModel) MarshalDagJSON(w io.Writer) error { + jw := jsg.NewDagJsonWriter(w) + if t == nil { + err := jw.WriteNull() + return err + } + if err := jw.WriteObjectOpen(); err != nil { + return err + } + written := 0 + + // t.Endpoint (string) (string) + if len("endpoint") > 8192 { + return fmt.Errorf("String in field \"endpoint\" was too long") + } + if err := jw.WriteString(string("endpoint")); err != nil { + return fmt.Errorf("\"endpoint\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + if len(t.Endpoint) > 8192 { + return fmt.Errorf("String in field t.Endpoint was too long") + } + if err := jw.WriteString(string(t.Endpoint)); err != nil { + return fmt.Errorf("t.Endpoint: %w", err) + } + written++ + if written > 0 { + if err := jw.WriteComma(); err != nil { + return err + } + } + + // t.Provider (did.DID) (struct) + if len("provider") > 8192 { + return fmt.Errorf("String in field \"provider\" was too long") + } + if err := jw.WriteString(string("provider")); err != nil { + return fmt.Errorf("\"provider\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + if err := t.Provider.MarshalDagJSON(jw); err != nil { + return fmt.Errorf("t.Provider: %w", err) + } + written++ + if written > 0 { + if err := jw.WriteComma(); err != nil { + return err + } + } + + // t.ReplicationWeight (int64) (int64) + if len("replicationWeight") > 8192 { + return fmt.Errorf("String in field \"replicationWeight\" was too long") + } + if err := jw.WriteString(string("replicationWeight")); err != nil { + return fmt.Errorf("\"replicationWeight\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + + if err := jw.WriteInt64(int64(t.ReplicationWeight)); err != nil { + return fmt.Errorf("t.ReplicationWeight: %w", err) + } + + written++ + if written > 0 { + if err := jw.WriteComma(); err != nil { + return err + } + } + + // t.Weight (int64) (int64) + if len("weight") > 8192 { + return fmt.Errorf("String in field \"weight\" was too long") + } + if err := jw.WriteString(string("weight")); err != nil { + return fmt.Errorf("\"weight\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + + if err := jw.WriteInt64(int64(t.Weight)); err != nil { + return fmt.Errorf("t.Weight: %w", err) + } + + written++ + if err := jw.WriteObjectClose(); err != nil { + return err + } + return nil +} +func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { + *t = ProviderModel{} + + jr := jsg.NewDagJsonReader(r) + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if err := jr.ReadObjectOpen(); err != nil { + return fmt.Errorf("ProviderModel: %w", err) + } + close, err := jr.PeekObjectClose() + if err != nil { + return fmt.Errorf("ProviderModel: %w", err) + } + if close { + if err := jr.ReadObjectClose(); err != nil { + return fmt.Errorf("ProviderModel: %w", err) + } + } else { + for i := uint64(0); i < 8192; i++ { + name, err := jr.ReadString(8192) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("ProviderModel: string too large") + } + return fmt.Errorf("ProviderModel: %w", err) + } + if err := jr.ReadObjectColon(); err != nil { + return fmt.Errorf("ProviderModel: %w", err) + } + switch name { + + // t.Endpoint (string) (string) + case "endpoint": + { + sval, err := jr.ReadString(8192) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("t.Endpoint: string too long") + } + return fmt.Errorf("t.Endpoint: %w", err) + } + t.Endpoint = string(sval) + } + + // t.Provider (did.DID) (struct) + case "provider": + + if err := t.Provider.UnmarshalDagJSON(jr); err != nil { + return fmt.Errorf("unmarshaling t.Provider: %w", err) + } + + // t.ReplicationWeight (int64) (int64) + case "replicationWeight": + { + + nval, err := jr.ReadNumberAsInt64() + if err != nil { + return fmt.Errorf("t.ReplicationWeight: %w", err) + } + t.ReplicationWeight = int64(nval) + + } + + // t.Weight (int64) (int64) + case "weight": + { + + nval, err := jr.ReadNumberAsInt64() + if err != nil { + return fmt.Errorf("t.Weight: %w", err) + } + t.Weight = int64(nval) + + } + default: + // Field doesn't exist on this type, so ignore it + if err := jr.DiscardType(); err != nil { + return fmt.Errorf("ProviderModel: ignoring field %s: %w", name, err) + } + } + + close, err := jr.ReadObjectCloseOrComma() + if err != nil { + return fmt.Errorf("ProviderModel: %w", err) + } + if close { + break + } + if i == 8192-1 { + return fmt.Errorf("ProviderModel: map too large") + } + } + } + + return nil +} +func (t *ListOKModel) MarshalDagJSON(w io.Writer) error { + jw := jsg.NewDagJsonWriter(w) + if t == nil { + err := jw.WriteNull() + return err + } + if err := jw.WriteObjectOpen(); err != nil { + return err + } + + // t.Providers ([]datamodel.ProviderModel) (slice) + if len("providers") > 8192 { + return fmt.Errorf("String in field \"providers\" was too long") + } + if err := jw.WriteString(string("providers")); err != nil { + return fmt.Errorf("\"providers\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + if len(t.Providers) > 8192 { + return fmt.Errorf("Slice value in field t.Providers was too long") + } + + if err := jw.WriteArrayOpen(); err != nil { + return fmt.Errorf("t.Providers: %w", err) + } + for i, v := range t.Providers { + if i > 0 { + if err := jw.WriteComma(); err != nil { + return fmt.Errorf("t.Providers: %w", err) + } + } + if err := v.MarshalDagJSON(jw); err != nil { + return fmt.Errorf("v: %w", err) + } + } + if err := jw.WriteArrayClose(); err != nil { + return fmt.Errorf("t.Providers: %w", err) + } + + if err := jw.WriteObjectClose(); err != nil { + return err + } + return nil +} +func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { + *t = ListOKModel{} + + jr := jsg.NewDagJsonReader(r) + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if err := jr.ReadObjectOpen(); err != nil { + return fmt.Errorf("ListOKModel: %w", err) + } + close, err := jr.PeekObjectClose() + if err != nil { + return fmt.Errorf("ListOKModel: %w", err) + } + if close { + if err := jr.ReadObjectClose(); err != nil { + return fmt.Errorf("ListOKModel: %w", err) + } + } else { + for i := uint64(0); i < 8192; i++ { + name, err := jr.ReadString(8192) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("ListOKModel: string too large") + } + return fmt.Errorf("ListOKModel: %w", err) + } + if err := jr.ReadObjectColon(); err != nil { + return fmt.Errorf("ListOKModel: %w", err) + } + switch name { + + // t.Providers ([]datamodel.ProviderModel) (slice) + case "providers": + { + + if err := jr.ReadArrayOpen(); err != nil { + return fmt.Errorf("t.Providers: %w", err) + } + + close, err := jr.PeekArrayClose() + if err != nil { + return fmt.Errorf("t.Providers: %w", err) + } + if close { + if err := jr.ReadArrayClose(); err != nil { + return fmt.Errorf("t.Providers: %w", err) + } + + } else { + for i := 0; i < 8192; i++ { + item := make([]ProviderModel, 1) + + if err := item[0].UnmarshalDagJSON(jr); err != nil { + return fmt.Errorf("unmarshaling item[0]: %w", err) + } + + t.Providers = append(t.Providers, item[0]) + + close, err := jr.ReadArrayCloseOrComma() + if err != nil { + return fmt.Errorf("t.Providers: %w", err) + } + if close { + break + } + if i == 8192-1 { + return fmt.Errorf("t.Providers: slice too large") + } + } + } + + } + default: + // Field doesn't exist on this type, so ignore it + if err := jr.DiscardType(); err != nil { + return fmt.Errorf("ListOKModel: ignoring field %s: %w", name, err) + } + } + + close, err := jr.ReadObjectCloseOrComma() + if err != nil { + return fmt.Errorf("ListOKModel: %w", err) + } + if close { + break + } + if i == 8192-1 { + return fmt.Errorf("ListOKModel: map too large") + } + } + } + + return nil +} +func (t *RegisterArgumentsModel) MarshalDagJSON(w io.Writer) error { + jw := jsg.NewDagJsonWriter(w) + if t == nil { + err := jw.WriteNull() + return err + } + if err := jw.WriteObjectOpen(); err != nil { + return err + } + written := 0 + + // t.Endpoint (string) (string) + if len("endpoint") > 8192 { + return fmt.Errorf("String in field \"endpoint\" was too long") + } + if err := jw.WriteString(string("endpoint")); err != nil { + return fmt.Errorf("\"endpoint\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + if len(t.Endpoint) > 8192 { + return fmt.Errorf("String in field t.Endpoint was too long") + } + if err := jw.WriteString(string(t.Endpoint)); err != nil { + return fmt.Errorf("t.Endpoint: %w", err) + } + written++ + if written > 0 { + if err := jw.WriteComma(); err != nil { + return err + } + } + + // t.Provider (did.DID) (struct) + if len("provider") > 8192 { + return fmt.Errorf("String in field \"provider\" was too long") + } + if err := jw.WriteString(string("provider")); err != nil { + return fmt.Errorf("\"provider\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + if err := t.Provider.MarshalDagJSON(jw); err != nil { + return fmt.Errorf("t.Provider: %w", err) + } + written++ + if err := jw.WriteObjectClose(); err != nil { + return err + } + return nil +} +func (t *RegisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { + *t = RegisterArgumentsModel{} + + jr := jsg.NewDagJsonReader(r) + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if err := jr.ReadObjectOpen(); err != nil { + return fmt.Errorf("RegisterArgumentsModel: %w", err) + } + close, err := jr.PeekObjectClose() + if err != nil { + return fmt.Errorf("RegisterArgumentsModel: %w", err) + } + if close { + if err := jr.ReadObjectClose(); err != nil { + return fmt.Errorf("RegisterArgumentsModel: %w", err) + } + } else { + for i := uint64(0); i < 8192; i++ { + name, err := jr.ReadString(8192) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("RegisterArgumentsModel: string too large") + } + return fmt.Errorf("RegisterArgumentsModel: %w", err) + } + if err := jr.ReadObjectColon(); err != nil { + return fmt.Errorf("RegisterArgumentsModel: %w", err) + } + switch name { + + // t.Endpoint (string) (string) + case "endpoint": + { + sval, err := jr.ReadString(8192) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("t.Endpoint: string too long") + } + return fmt.Errorf("t.Endpoint: %w", err) + } + t.Endpoint = string(sval) + } + + // t.Provider (did.DID) (struct) + case "provider": + + if err := t.Provider.UnmarshalDagJSON(jr); err != nil { + return fmt.Errorf("unmarshaling t.Provider: %w", err) + } + + default: + // Field doesn't exist on this type, so ignore it + if err := jr.DiscardType(); err != nil { + return fmt.Errorf("RegisterArgumentsModel: ignoring field %s: %w", name, err) + } + } + + close, err := jr.ReadObjectCloseOrComma() + if err != nil { + return fmt.Errorf("RegisterArgumentsModel: %w", err) + } + if close { + break + } + if i == 8192-1 { + return fmt.Errorf("RegisterArgumentsModel: map too large") + } + } + } + + return nil +} +func (t *DeregisterArgumentsModel) MarshalDagJSON(w io.Writer) error { + jw := jsg.NewDagJsonWriter(w) + if t == nil { + err := jw.WriteNull() + return err + } + if err := jw.WriteObjectOpen(); err != nil { + return err + } + + // t.Provider (did.DID) (struct) + if len("provider") > 8192 { + return fmt.Errorf("String in field \"provider\" was too long") + } + if err := jw.WriteString(string("provider")); err != nil { + return fmt.Errorf("\"provider\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + if err := t.Provider.MarshalDagJSON(jw); err != nil { + return fmt.Errorf("t.Provider: %w", err) + } + if err := jw.WriteObjectClose(); err != nil { + return err + } + return nil +} +func (t *DeregisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { + *t = DeregisterArgumentsModel{} + + jr := jsg.NewDagJsonReader(r) + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if err := jr.ReadObjectOpen(); err != nil { + return fmt.Errorf("DeregisterArgumentsModel: %w", err) + } + close, err := jr.PeekObjectClose() + if err != nil { + return fmt.Errorf("DeregisterArgumentsModel: %w", err) + } + if close { + if err := jr.ReadObjectClose(); err != nil { + return fmt.Errorf("DeregisterArgumentsModel: %w", err) + } + } else { + for i := uint64(0); i < 8192; i++ { + name, err := jr.ReadString(8192) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("DeregisterArgumentsModel: string too large") + } + return fmt.Errorf("DeregisterArgumentsModel: %w", err) + } + if err := jr.ReadObjectColon(); err != nil { + return fmt.Errorf("DeregisterArgumentsModel: %w", err) + } + switch name { + + // t.Provider (did.DID) (struct) + case "provider": + + if err := t.Provider.UnmarshalDagJSON(jr); err != nil { + return fmt.Errorf("unmarshaling t.Provider: %w", err) + } + + default: + // Field doesn't exist on this type, so ignore it + if err := jr.DiscardType(); err != nil { + return fmt.Errorf("DeregisterArgumentsModel: ignoring field %s: %w", name, err) + } + } + + close, err := jr.ReadObjectCloseOrComma() + if err != nil { + return fmt.Errorf("DeregisterArgumentsModel: %w", err) + } + if close { + break + } + if i == 8192-1 { + return fmt.Errorf("DeregisterArgumentsModel: map too large") + } + } + } + + return nil +} diff --git a/pkg/capabilities/admin/provider/datamodel/list.go b/pkg/capabilities/admin/provider/datamodel/list.go new file mode 100644 index 0000000..1227185 --- /dev/null +++ b/pkg/capabilities/admin/provider/datamodel/list.go @@ -0,0 +1,16 @@ +package datamodel + +import "github.com/fil-forge/ucantone/did" + +type ListArgumentsModel struct{} + +type ProviderModel struct { + Provider did.DID `cborgen:"provider" dagjsongen:"provider"` + Endpoint string `cborgen:"endpoint" dagjsongen:"endpoint"` + Weight int64 `cborgen:"weight" dagjsongen:"weight"` + ReplicationWeight int64 `cborgen:"replicationWeight" dagjsongen:"replicationWeight"` +} + +type ListOKModel struct { + Providers []ProviderModel `cborgen:"providers" dagjsongen:"providers"` +} diff --git a/pkg/capabilities/admin/provider/datamodel/register.go b/pkg/capabilities/admin/provider/datamodel/register.go new file mode 100644 index 0000000..d867cf4 --- /dev/null +++ b/pkg/capabilities/admin/provider/datamodel/register.go @@ -0,0 +1,8 @@ +package datamodel + +import "github.com/fil-forge/ucantone/did" + +type RegisterArgumentsModel struct { + Provider did.DID `cborgen:"provider" dagjsongen:"provider"` + Endpoint string `cborgen:"endpoint" dagjsongen:"endpoint"` +} diff --git a/pkg/capabilities/admin/provider/deregister.go b/pkg/capabilities/admin/provider/deregister.go index f05f279..3db3d40 100644 --- a/pkg/capabilities/admin/provider/deregister.go +++ b/pkg/capabilities/admin/provider/deregister.go @@ -1,31 +1,16 @@ package provider import ( - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/result/ok" - "github.com/fil-forge/go-ucanto/core/schema" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/validator" - "github.com/ipld/go-ipld-prime/datamodel" - - "github.com/fil-forge/go-libstoracha/capabilities/types" + cdm "github.com/fil-forge/libforge/capabilities/datamodel" + pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" + "github.com/fil-forge/ucantone/validator/bindcap" ) -const DeregisterAbility = "admin/provider/deregister" - -type DeregisterCaveats struct { - Provider did.DID -} - -func (dc DeregisterCaveats) ToIPLD() (datamodel.Node, error) { - return ipld.WrapWithRecovery(&dc, DeregisterCaveatsType(), types.Converters...) -} +const DeregisterCommand = "/admin/provider/deregister" -type DeregisterOk = ok.Unit - -var Deregister = validator.NewCapability( - DeregisterAbility, - schema.DIDString(), - schema.Struct[DeregisterCaveats](DeregisterCaveatsType(), nil, types.Converters...), - validator.DefaultDerives[DeregisterCaveats], +type ( + DeregisterArguments = pdm.DeregisterArgumentsModel + DeregisterOK = cdm.UnitModel ) + +var Deregister, _ = bindcap.New[*DeregisterArguments](DeregisterCommand) diff --git a/pkg/capabilities/admin/provider/list.go b/pkg/capabilities/admin/provider/list.go index ac7238e..9875fee 100644 --- a/pkg/capabilities/admin/provider/list.go +++ b/pkg/capabilities/admin/provider/list.go @@ -1,41 +1,16 @@ package provider import ( - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/schema" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/validator" - "github.com/ipld/go-ipld-prime/datamodel" - - "github.com/fil-forge/go-libstoracha/capabilities/types" + pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" + "github.com/fil-forge/ucantone/validator/bindcap" ) -const ListAbility = "admin/provider/list" - -type ListCaveats struct{} - -func (lc ListCaveats) ToIPLD() (datamodel.Node, error) { - return ipld.WrapWithRecovery(&lc, ListCaveatsType(), types.Converters...) -} - -type Provider struct { - ID did.DID - Endpoint string - Weight int - ReplicationWeight int -} +const ListCommand = "/admin/provider/list" -type ListOk struct { - Providers []Provider -} - -func (lo ListOk) ToIPLD() (datamodel.Node, error) { - return ipld.WrapWithRecovery(&lo, ListOkType(), types.Converters...) -} - -var List = validator.NewCapability( - ListAbility, - schema.DIDString(), - schema.Struct[ListCaveats](ListCaveatsType(), nil, types.Converters...), - validator.DefaultDerives[ListCaveats], +type ( + ListArguments = pdm.ListArgumentsModel + ListOK = pdm.ListOKModel + Provider = pdm.ProviderModel ) + +var List, _ = bindcap.New[*ListArguments](ListCommand) diff --git a/pkg/capabilities/admin/provider/provider.ipldsch b/pkg/capabilities/admin/provider/provider.ipldsch deleted file mode 100644 index 7dfdafb..0000000 --- a/pkg/capabilities/admin/provider/provider.ipldsch +++ /dev/null @@ -1,33 +0,0 @@ -type RegisterCaveats struct { - endpoint String - proof Link -} - -type RegisterOk struct {} - -type DeregisterCaveats struct { - provider DID -} - -type DeregisterOk struct {} - -type ListCaveats struct {} - -type Provider struct { - ID DID (rename "id") - endpoint String - weight Int - replicationWeight Int -} - -type ListOk struct { - providers [Provider] -} - -type WeightSetCaveats struct { - provider DID - weight Int - replicationWeight Int -} - -type WeightSetOk struct {} diff --git a/pkg/capabilities/admin/provider/register.go b/pkg/capabilities/admin/provider/register.go index 1d0f811..ab85d89 100644 --- a/pkg/capabilities/admin/provider/register.go +++ b/pkg/capabilities/admin/provider/register.go @@ -1,31 +1,16 @@ package provider import ( - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/result/ok" - "github.com/fil-forge/go-ucanto/core/schema" - "github.com/fil-forge/go-ucanto/validator" - "github.com/ipld/go-ipld-prime/datamodel" - - "github.com/fil-forge/go-libstoracha/capabilities/types" + cdm "github.com/fil-forge/libforge/capabilities/datamodel" + pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" + "github.com/fil-forge/ucantone/validator/bindcap" ) -const RegisterAbility = "admin/provider/register" - -type RegisterCaveats struct { - Endpoint string - Proof ipld.Link -} - -func (rc RegisterCaveats) ToIPLD() (datamodel.Node, error) { - return ipld.WrapWithRecovery(&rc, RegisterCaveatsType(), types.Converters...) -} +const RegisterCommand = "/admin/provider/register" -type RegisterOk = ok.Unit - -var Register = validator.NewCapability( - RegisterAbility, - schema.DIDString(), - schema.Struct[RegisterCaveats](RegisterCaveatsType(), nil, types.Converters...), - validator.DefaultDerives[RegisterCaveats], +type ( + RegisterArguments = pdm.RegisterArgumentsModel + RegisterOK = cdm.UnitModel ) + +var Register, _ = bindcap.New[*RegisterArguments](RegisterCommand) diff --git a/pkg/capabilities/admin/provider/schema.go b/pkg/capabilities/admin/provider/schema.go deleted file mode 100644 index 19449ea..0000000 --- a/pkg/capabilities/admin/provider/schema.go +++ /dev/null @@ -1,54 +0,0 @@ -package provider - -import ( - _ "embed" - "fmt" - - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/ipld/go-ipld-prime/schema" -) - -//go:embed provider.ipldsch -var providerSchema []byte - -var providerTS = mustLoadTS() - -func mustLoadTS() *schema.TypeSystem { - ts, err := types.LoadSchemaBytes(providerSchema) - if err != nil { - panic(fmt.Errorf("loading provider schema: %w", err)) - } - return ts -} - -func RegisterCaveatsType() schema.Type { - return providerTS.TypeByName("RegisterCaveats") -} - -func RegisterOkType() schema.Type { - return providerTS.TypeByName("RegisterOk") -} - -func DeregisterCaveatsType() schema.Type { - return providerTS.TypeByName("DeregisterCaveats") -} - -func DeregisterOkType() schema.Type { - return providerTS.TypeByName("DeregisterOk") -} - -func ListCaveatsType() schema.Type { - return providerTS.TypeByName("ListCaveats") -} - -func ListOkType() schema.Type { - return providerTS.TypeByName("ListOk") -} - -func WeightSetCaveatsType() schema.Type { - return providerTS.TypeByName("WeightSetCaveats") -} - -func WeightSetOkType() schema.Type { - return providerTS.TypeByName("WeightSetOk") -} diff --git a/pkg/capabilities/admin/provider/weight/datamodel/cbor_gen.go b/pkg/capabilities/admin/provider/weight/datamodel/cbor_gen.go new file mode 100644 index 0000000..546e152 --- /dev/null +++ b/pkg/capabilities/admin/provider/weight/datamodel/cbor_gen.go @@ -0,0 +1,209 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package datamodel + +import ( + "fmt" + "io" + "math" + "sort" + + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +func (t *SetArgumentsModel) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{163}); err != nil { + return err + } + + // t.Weight (int64) (int64) + if len("weight") > 8192 { + return xerrors.Errorf("Value in field \"weight\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("weight"))); err != nil { + return err + } + if _, err := cw.WriteString(string("weight")); err != nil { + return err + } + + if t.Weight >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Weight)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Weight-1)); err != nil { + return err + } + } + + // t.Provider (did.DID) (struct) + if len("provider") > 8192 { + return xerrors.Errorf("Value in field \"provider\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("provider"))); err != nil { + return err + } + if _, err := cw.WriteString(string("provider")); err != nil { + return err + } + + if err := t.Provider.MarshalCBOR(cw); err != nil { + return err + } + + // t.ReplicationWeight (int64) (int64) + if len("replicationWeight") > 8192 { + return xerrors.Errorf("Value in field \"replicationWeight\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("replicationWeight"))); err != nil { + return err + } + if _, err := cw.WriteString(string("replicationWeight")); err != nil { + return err + } + + if t.ReplicationWeight >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ReplicationWeight)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ReplicationWeight-1)); err != nil { + return err + } + } + + return nil +} + +func (t *SetArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { + *t = SetArgumentsModel{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SetArgumentsModel: map struct too large (%d)", extra) + } + + n := extra + + nameBuf := make([]byte, 17) + for i := uint64(0); i < n; i++ { + nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) + if err != nil { + return err + } + + if !ok { + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { + return err + } + continue + } + + switch string(nameBuf[:nameLen]) { + // t.Weight (int64) (int64) + case "weight": + { + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + var extraI int64 + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.Weight = int64(extraI) + } + // t.Provider (did.DID) (struct) + case "provider": + + { + + if err := t.Provider.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Provider: %w", err) + } + + } + // t.ReplicationWeight (int64) (int64) + case "replicationWeight": + { + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + var extraI int64 + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.ReplicationWeight = int64(extraI) + } + + default: + // Field doesn't exist on this type, so ignore it + if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { + return err + } + } + } + + return nil +} diff --git a/pkg/capabilities/admin/provider/weight/datamodel/gen/main.go b/pkg/capabilities/admin/provider/weight/datamodel/gen/main.go new file mode 100644 index 0000000..fdf3888 --- /dev/null +++ b/pkg/capabilities/admin/provider/weight/datamodel/gen/main.go @@ -0,0 +1,19 @@ +package main + +import ( + jsg "github.com/alanshaw/dag-json-gen" + wdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight/datamodel" + cbg "github.com/whyrusleeping/cbor-gen" +) + +func main() { + models := []any{ + wdm.SetArgumentsModel{}, + } + if err := cbg.WriteMapEncodersToFile("../cbor_gen.go", "datamodel", models...); err != nil { + panic(err) + } + if err := jsg.WriteMapEncodersToFile("../json_gen.go", "datamodel", models...); err != nil { + panic(err) + } +} diff --git a/pkg/capabilities/admin/provider/weight/datamodel/json_gen.go b/pkg/capabilities/admin/provider/weight/datamodel/json_gen.go new file mode 100644 index 0000000..fae8f59 --- /dev/null +++ b/pkg/capabilities/admin/provider/weight/datamodel/json_gen.go @@ -0,0 +1,180 @@ +// Code generated by github.com/alanshaw/dag-json-gen. DO NOT EDIT. + +package datamodel + +import ( + "errors" + "fmt" + "io" + "math" + "sort" + + jsg "github.com/alanshaw/dag-json-gen" + cid "github.com/ipfs/go-cid" +) + +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort +var _ = errors.Is + +func (t *SetArgumentsModel) MarshalDagJSON(w io.Writer) error { + jw := jsg.NewDagJsonWriter(w) + if t == nil { + err := jw.WriteNull() + return err + } + if err := jw.WriteObjectOpen(); err != nil { + return err + } + written := 0 + + // t.Provider (did.DID) (struct) + if len("provider") > 8192 { + return fmt.Errorf("String in field \"provider\" was too long") + } + if err := jw.WriteString(string("provider")); err != nil { + return fmt.Errorf("\"provider\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + if err := t.Provider.MarshalDagJSON(jw); err != nil { + return fmt.Errorf("t.Provider: %w", err) + } + written++ + if written > 0 { + if err := jw.WriteComma(); err != nil { + return err + } + } + + // t.ReplicationWeight (int64) (int64) + if len("replicationWeight") > 8192 { + return fmt.Errorf("String in field \"replicationWeight\" was too long") + } + if err := jw.WriteString(string("replicationWeight")); err != nil { + return fmt.Errorf("\"replicationWeight\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + + if err := jw.WriteInt64(int64(t.ReplicationWeight)); err != nil { + return fmt.Errorf("t.ReplicationWeight: %w", err) + } + + written++ + if written > 0 { + if err := jw.WriteComma(); err != nil { + return err + } + } + + // t.Weight (int64) (int64) + if len("weight") > 8192 { + return fmt.Errorf("String in field \"weight\" was too long") + } + if err := jw.WriteString(string("weight")); err != nil { + return fmt.Errorf("\"weight\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + + if err := jw.WriteInt64(int64(t.Weight)); err != nil { + return fmt.Errorf("t.Weight: %w", err) + } + + written++ + if err := jw.WriteObjectClose(); err != nil { + return err + } + return nil +} +func (t *SetArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { + *t = SetArgumentsModel{} + + jr := jsg.NewDagJsonReader(r) + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if err := jr.ReadObjectOpen(); err != nil { + return fmt.Errorf("SetArgumentsModel: %w", err) + } + close, err := jr.PeekObjectClose() + if err != nil { + return fmt.Errorf("SetArgumentsModel: %w", err) + } + if close { + if err := jr.ReadObjectClose(); err != nil { + return fmt.Errorf("SetArgumentsModel: %w", err) + } + } else { + for i := uint64(0); i < 8192; i++ { + name, err := jr.ReadString(8192) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("SetArgumentsModel: string too large") + } + return fmt.Errorf("SetArgumentsModel: %w", err) + } + if err := jr.ReadObjectColon(); err != nil { + return fmt.Errorf("SetArgumentsModel: %w", err) + } + switch name { + + // t.Provider (did.DID) (struct) + case "provider": + + if err := t.Provider.UnmarshalDagJSON(jr); err != nil { + return fmt.Errorf("unmarshaling t.Provider: %w", err) + } + + // t.ReplicationWeight (int64) (int64) + case "replicationWeight": + { + + nval, err := jr.ReadNumberAsInt64() + if err != nil { + return fmt.Errorf("t.ReplicationWeight: %w", err) + } + t.ReplicationWeight = int64(nval) + + } + + // t.Weight (int64) (int64) + case "weight": + { + + nval, err := jr.ReadNumberAsInt64() + if err != nil { + return fmt.Errorf("t.Weight: %w", err) + } + t.Weight = int64(nval) + + } + default: + // Field doesn't exist on this type, so ignore it + if err := jr.DiscardType(); err != nil { + return fmt.Errorf("SetArgumentsModel: ignoring field %s: %w", name, err) + } + } + + close, err := jr.ReadObjectCloseOrComma() + if err != nil { + return fmt.Errorf("SetArgumentsModel: %w", err) + } + if close { + break + } + if i == 8192-1 { + return fmt.Errorf("SetArgumentsModel: map too large") + } + } + } + + return nil +} diff --git a/pkg/capabilities/admin/provider/weight/datamodel/set.go b/pkg/capabilities/admin/provider/weight/datamodel/set.go new file mode 100644 index 0000000..5658bf9 --- /dev/null +++ b/pkg/capabilities/admin/provider/weight/datamodel/set.go @@ -0,0 +1,9 @@ +package datamodel + +import "github.com/fil-forge/ucantone/did" + +type SetArgumentsModel struct { + Provider did.DID `cborgen:"provider" dagjsongen:"provider"` + Weight int64 `cborgen:"weight" dagjsongen:"weight"` + ReplicationWeight int64 `cborgen:"replicationWeight" dagjsongen:"replicationWeight"` +} diff --git a/pkg/capabilities/admin/provider/weight/set.go b/pkg/capabilities/admin/provider/weight/set.go new file mode 100644 index 0000000..3e4b068 --- /dev/null +++ b/pkg/capabilities/admin/provider/weight/set.go @@ -0,0 +1,24 @@ +package weight + +import ( + cdm "github.com/fil-forge/libforge/capabilities/datamodel" + wdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight/datamodel" + "github.com/fil-forge/ucantone/ucan/delegation/policy" + "github.com/fil-forge/ucantone/validator/bindcap" + "github.com/fil-forge/ucantone/validator/capability" +) + +const SetCommand = "/provider/weight/set" + +type ( + SetArguments = wdm.SetArgumentsModel + SetOK = cdm.UnitModel +) + +var Set, _ = bindcap.New[*SetArguments]( + SetCommand, + capability.WithPolicyBuilder( + policy.GreaterThanOrEqual(".weight", 0), + policy.GreaterThanOrEqual(".replicationWeight", 0), + ), +) diff --git a/pkg/capabilities/admin/provider/weight_set.go b/pkg/capabilities/admin/provider/weight_set.go deleted file mode 100644 index 8b0e48c..0000000 --- a/pkg/capabilities/admin/provider/weight_set.go +++ /dev/null @@ -1,33 +0,0 @@ -package provider - -import ( - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/result/ok" - "github.com/fil-forge/go-ucanto/core/schema" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/validator" - "github.com/ipld/go-ipld-prime/datamodel" - - "github.com/fil-forge/go-libstoracha/capabilities/types" -) - -const WeightSetAbility = "admin/provider/weight/set" - -type WeightSetCaveats struct { - Provider did.DID - Weight int - ReplicationWeight int -} - -func (wc WeightSetCaveats) ToIPLD() (datamodel.Node, error) { - return ipld.WrapWithRecovery(&wc, WeightSetCaveatsType(), types.Converters...) -} - -type WeightSetOk = ok.Unit - -var WeightSet = validator.NewCapability( - WeightSetAbility, - schema.DIDString(), - schema.Struct[WeightSetCaveats](WeightSetCaveatsType(), nil, types.Converters...), - validator.DefaultDerives[WeightSetCaveats], -) diff --git a/pkg/client/client.go b/pkg/client/client.go index e104e86..81dd1c1 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -3,282 +3,160 @@ package client import ( "context" "fmt" - "net/http" "net/url" - "strings" - "time" - - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-ucanto/client" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/result" - fdm "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/did" - ucan_http "github.com/fil-forge/go-ucanto/transport/http" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "slices" + + providercap "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + weightcap "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight" + "github.com/fil-forge/sprue/pkg/lib/ucan_client" + "github.com/fil-forge/ucantone/client" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" + "go.uber.org/zap" ) -type Option func(*Client) error - -// WithServiceURL configures the URL to use when sending invocations. Unused -// when [WithConnection] option is passed. -func WithServiceURL(serviceURL string) Option { - return func(c *Client) error { - parsedURL, err := url.Parse(serviceURL) - if err != nil { - return fmt.Errorf("parsing service URL: %w", err) - } - c.serviceURL = parsedURL - return nil - } +type Client struct { + uploadServiceID did.DID + client *client.HTTPClient + signer ucan.Signer + logger *zap.Logger } -// WithHTTPClient configures the HTTP client to use when sending invocation -// requests. Unused when [WithConnection] option is passed. -func WithHTTPClient(httpClient *http.Client) Option { - return func(c *Client) error { - c.httpClient = httpClient - return nil +func New(uploadServiceID did.DID, endpoint *url.URL, signer ucan.Signer, logger *zap.Logger) (*Client, error) { + client, err := client.NewHTTP(endpoint) + if err != nil { + return nil, fmt.Errorf("creating HTTP client: %w", err) } + return NewWithClient(uploadServiceID, client, signer, logger), nil } -// WithConnection configures the client connection to use for invocations. -func WithConnection(conn client.Connection) Option { - return func(c *Client) error { - c.Connection = conn - return nil +func NewWithClient(uploadServiceID did.DID, client *client.HTTPClient, signer ucan.Signer, logger *zap.Logger) *Client { + return &Client{ + uploadServiceID: uploadServiceID, + signer: signer, + client: client, + logger: logger, } } -type Client struct { - serviceURL *url.URL - httpClient *http.Client - Connection client.Connection -} - -func New(serviceID ucan.Principal, options ...Option) (*Client, error) { - c := Client{ - httpClient: &http.Client{Timeout: 30 * time.Second}, - } - for _, opt := range options { - if err := opt(&c); err != nil { - return nil, err - } - } - if c.Connection != nil { - return &c, nil - } - if c.serviceURL == nil { - if !strings.HasPrefix(serviceID.DID().String(), "did:web:") { - return nil, fmt.Errorf("service URL must be provided if no connection is set") - } - // For did:web, we can derive the service URL from the DID by replacing - // "did:web:" with "https://". - domain := strings.TrimPrefix(serviceID.DID().String(), "did:web:") - u, err := url.Parse(fmt.Sprintf("https://%s", domain)) - if err != nil { - return nil, fmt.Errorf("parsing derived service URL: %w", err) - } - c.serviceURL = u - } - channel := ucan_http.NewChannel(c.serviceURL, ucan_http.WithClient(c.httpClient)) - conn, err := client.NewConnection(serviceID, channel) - if err != nil { - return nil, fmt.Errorf("creating connection: %w", err) +func (c *Client) AdminProviderRegister(ctx context.Context, providerID did.DID, endpoint string, options ...invocation.Option) (ucan.Receipt, error) { + if c.signer.DID() != c.uploadServiceID { + return nil, fmt.Errorf("admin operation not permitted: signer DID %s does not match upload service ID %s", c.signer.DID(), c.uploadServiceID) } - c.Connection = conn - return &c, nil -} -func (c *Client) AdminProviderRegister(ctx context.Context, signer ucan.Signer, endpoint string, proof delegation.Delegation, options ...delegation.Option) (provider.RegisterOk, error) { - inv, err := provider.Register.Invoke( - signer, - c.Connection.ID(), - c.Connection.ID().DID().String(), - provider.RegisterCaveats{ + options = slices.Clone(options) + options = append( + options, + invocation.WithAudience(c.uploadServiceID), + ) + + inv, err := providercap.Register.Invoke( + c.signer, + c.uploadServiceID, + &providercap.RegisterArguments{ + Provider: providerID, Endpoint: endpoint, - Proof: proof.Link(), }, options..., ) if err != nil { - return provider.RegisterOk{}, fmt.Errorf("invoking provider register: %w", err) - } - for b, err := range proof.Blocks() { - if err != nil { - return provider.RegisterOk{}, fmt.Errorf("iterating proof blocks: %w", err) - } - if err := inv.Attach(b); err != nil { - return provider.RegisterOk{}, fmt.Errorf("attaching proof block: %w", err) - } + return nil, fmt.Errorf("invoking provider register: %w", err) } - res, err := client.Execute(ctx, []invocation.Invocation{inv}, c.Connection) + _, rcpt, err := ucan_client.Execute[*providercap.RegisterOK](ctx, c.client, c.logger, inv) if err != nil { - return provider.RegisterOk{}, fmt.Errorf("executing invocation: %w", err) - } - - rcptLink, ok := res.Get(inv.Link()) - if !ok { - return provider.RegisterOk{}, fmt.Errorf("no receipt found for invocation in response") + return nil, fmt.Errorf("executing provider register invocation: %w", err) } + return rcpt, nil +} - reader := receipt.NewAnyReceiptReader(types.Converters...) - rcpt, err := reader.Read(rcptLink, res.Blocks()) - if err != nil { - return provider.RegisterOk{}, fmt.Errorf("reading receipt: %w", err) +func (c *Client) AdminProviderDeregister(ctx context.Context, providerID did.DID, options ...invocation.Option) (ucan.Receipt, error) { + if c.signer.DID() != c.uploadServiceID { + return nil, fmt.Errorf("admin operation not permitted: signer DID %s does not match upload service ID %s", c.signer.DID(), c.uploadServiceID) } - return result.MatchResultR2( - rcpt.Out(), - func(o ipld.Node) (provider.RegisterOk, error) { - v, err := ipld.Rebind[provider.RegisterOk](o, provider.RegisterOkType(), types.Converters...) - if err != nil { - return provider.RegisterOk{}, fmt.Errorf("binding register success: %w", err) - } - return v, nil - }, - func(x ipld.Node) (provider.RegisterOk, error) { - return provider.RegisterOk{}, fdm.Bind(x) - }, + options = slices.Clone(options) + options = append( + options, + invocation.WithAudience(c.uploadServiceID), ) -} -func (c *Client) AdminProviderDeregister(ctx context.Context, signer ucan.Signer, providerID did.DID, options ...delegation.Option) (provider.DeregisterOk, error) { - inv, err := provider.Deregister.Invoke( - signer, - c.Connection.ID(), - c.Connection.ID().DID().String(), - provider.DeregisterCaveats{ + inv, err := providercap.Deregister.Invoke( + c.signer, + c.uploadServiceID, + &providercap.DeregisterArguments{ Provider: providerID, }, options..., ) if err != nil { - return provider.DeregisterOk{}, fmt.Errorf("invoking provider deregister: %w", err) + return nil, fmt.Errorf("invoking provider deregister: %w", err) } - res, err := client.Execute(ctx, []invocation.Invocation{inv}, c.Connection) + _, rcpt, err := ucan_client.Execute[*providercap.DeregisterOK](ctx, c.client, c.logger, inv) if err != nil { - return provider.DeregisterOk{}, fmt.Errorf("executing invocation: %w", err) - } - - rcptLink, ok := res.Get(inv.Link()) - if !ok { - return provider.DeregisterOk{}, fmt.Errorf("no receipt found for invocation in response") + return nil, fmt.Errorf("executing provider deregister invocation: %w", err) } + return rcpt, nil +} - reader := receipt.NewAnyReceiptReader(types.Converters...) - rcpt, err := reader.Read(rcptLink, res.Blocks()) - if err != nil { - return provider.DeregisterOk{}, fmt.Errorf("reading receipt: %w", err) +func (c *Client) AdminProviderList(ctx context.Context, options ...invocation.Option) (*providercap.ListOK, ucan.Receipt, error) { + if c.signer.DID() != c.uploadServiceID { + return nil, nil, fmt.Errorf("admin operation not permitted: signer DID %s does not match upload service ID %s", c.signer.DID(), c.uploadServiceID) } - return result.MatchResultR2( - rcpt.Out(), - func(o ipld.Node) (provider.DeregisterOk, error) { - v, err := ipld.Rebind[provider.DeregisterOk](o, provider.DeregisterOkType(), types.Converters...) - if err != nil { - return provider.DeregisterOk{}, fmt.Errorf("binding deregister success: %w", err) - } - return v, nil - }, - func(x ipld.Node) (provider.DeregisterOk, error) { - return provider.DeregisterOk{}, fdm.Bind(x) - }, + options = slices.Clone(options) + options = append( + options, + invocation.WithAudience(c.uploadServiceID), ) -} -func (c *Client) AdminProviderList(ctx context.Context, signer ucan.Signer, options ...delegation.Option) (provider.ListOk, error) { - inv, err := provider.List.Invoke( - signer, - c.Connection.ID(), - c.Connection.ID().DID().String(), - provider.ListCaveats{}, + inv, err := providercap.List.Invoke( + c.signer, + c.uploadServiceID, + &providercap.ListArguments{}, options..., ) if err != nil { - return provider.ListOk{}, fmt.Errorf("invoking provider list: %w", err) + return nil, nil, fmt.Errorf("invoking provider list: %w", err) } - res, err := client.Execute(ctx, []invocation.Invocation{inv}, c.Connection) + listOK, rcpt, err := ucan_client.Execute[*providercap.ListOK](ctx, c.client, c.logger, inv) if err != nil { - return provider.ListOk{}, fmt.Errorf("executing invocation: %w", err) - } - - rcptLink, ok := res.Get(inv.Link()) - if !ok { - return provider.ListOk{}, fmt.Errorf("no receipt found for invocation in response") + return nil, nil, fmt.Errorf("executing provider list invocation: %w", err) } + return listOK, rcpt, nil +} - reader := receipt.NewAnyReceiptReader(types.Converters...) - rcpt, err := reader.Read(rcptLink, res.Blocks()) - if err != nil { - return provider.ListOk{}, fmt.Errorf("reading receipt: %w", err) +func (c *Client) AdminProviderWeightSet(ctx context.Context, providerID did.DID, weight int, replicationWeight int, options ...invocation.Option) (ucan.Receipt, error) { + if c.signer.DID() != c.uploadServiceID { + return nil, fmt.Errorf("admin operation not permitted: signer DID %s does not match upload service ID %s", c.signer.DID(), c.uploadServiceID) } - return result.MatchResultR2( - rcpt.Out(), - func(o ipld.Node) (provider.ListOk, error) { - v, err := ipld.Rebind[provider.ListOk](o, provider.ListOkType(), types.Converters...) - if err != nil { - return provider.ListOk{}, fmt.Errorf("binding list success: %w", err) - } - return v, nil - }, - func(x ipld.Node) (provider.ListOk, error) { - return provider.ListOk{}, fdm.Bind(x) - }, + options = slices.Clone(options) + options = append( + options, + invocation.WithAudience(c.uploadServiceID), ) -} -func (c *Client) AdminProviderWeightSet(ctx context.Context, signer ucan.Signer, providerID did.DID, weight int, replicationWeight int, options ...delegation.Option) (provider.WeightSetOk, error) { - inv, err := provider.WeightSet.Invoke( - signer, - c.Connection.ID(), - c.Connection.ID().DID().String(), - provider.WeightSetCaveats{ + inv, err := weightcap.Set.Invoke( + c.signer, + c.uploadServiceID, + &weightcap.SetArguments{ Provider: providerID, - Weight: weight, - ReplicationWeight: replicationWeight, + Weight: int64(weight), + ReplicationWeight: int64(replicationWeight), }, options..., ) if err != nil { - return provider.WeightSetOk{}, fmt.Errorf("invoking provider weight set: %w", err) - } - - res, err := client.Execute(ctx, []invocation.Invocation{inv}, c.Connection) - if err != nil { - return provider.WeightSetOk{}, fmt.Errorf("executing invocation: %w", err) + return nil, fmt.Errorf("invoking provider weight set: %w", err) } - rcptLink, ok := res.Get(inv.Link()) - if !ok { - return provider.WeightSetOk{}, fmt.Errorf("no receipt found for invocation in response") - } - - reader := receipt.NewAnyReceiptReader(types.Converters...) - rcpt, err := reader.Read(rcptLink, res.Blocks()) + _, rcpt, err := ucan_client.Execute[*weightcap.SetOK](ctx, c.client, c.logger, inv) if err != nil { - return provider.WeightSetOk{}, fmt.Errorf("reading receipt: %w", err) + return nil, fmt.Errorf("executing provider weight set invocation: %w", err) } - - return result.MatchResultR2( - rcpt.Out(), - func(o ipld.Node) (provider.WeightSetOk, error) { - v, err := ipld.Rebind[provider.WeightSetOk](o, provider.WeightSetOkType(), types.Converters...) - if err != nil { - return provider.WeightSetOk{}, fmt.Errorf("binding weight set success: %w", err) - } - return v, nil - }, - func(x ipld.Node) (provider.WeightSetOk, error) { - return provider.WeightSetOk{}, fdm.Bind(x) - }, - ) + return rcpt, nil } diff --git a/pkg/identity/identity.go b/pkg/identity/identity.go index fba2f22..edcc665 100644 --- a/pkg/identity/identity.go +++ b/pkg/identity/identity.go @@ -9,10 +9,10 @@ import ( "os" "strings" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/principal" - ed25519 "github.com/fil-forge/go-ucanto/principal/ed25519/signer" - "github.com/fil-forge/go-ucanto/principal/signer" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/principal/ed25519" + "github.com/fil-forge/ucantone/principal/signer" ) // Identity holds the service's cryptographic identity. @@ -53,7 +53,7 @@ func (i *Identity) DID() string { // For unwrapped signers, returns the same as DID(). func (i *Identity) UnderlyingKeyDID() string { // Try to unwrap if it's a wrapped signer - if wrapped, ok := i.Signer.(signer.WrappedSigner); ok { + if wrapped, ok := i.Signer.(signer.Unwrapper); ok { return wrapped.Unwrap().DID().String() } return i.Signer.DID().String() @@ -169,5 +169,5 @@ func signerFromEd25519PEMFile(path string) (principal.Signer, error) { return nil, fmt.Errorf("no PRIVATE KEY block found in PEM file") } - return ed25519.FromRaw(*privateKey) + return ed25519.FromRaw(privateKey.Seed()) } diff --git a/pkg/indexerclient/client.go b/pkg/indexerclient/client.go index c34c412..26107be 100644 --- a/pkg/indexerclient/client.go +++ b/pkg/indexerclient/client.go @@ -5,244 +5,86 @@ import ( "fmt" "net/url" + assertcaps "github.com/fil-forge/libforge/capabilities/assert" + contentcaps "github.com/fil-forge/libforge/capabilities/content" + "github.com/fil-forge/sprue/pkg/lib/ucan_client" + "github.com/fil-forge/sprue/pkg/lib/ucan_server" + "github.com/fil-forge/ucantone/client" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" - "github.com/ipld/go-ipld-prime/datamodel" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - "github.com/ipld/go-ipld-prime/node/basicnode" - "github.com/multiformats/go-multiaddr" "go.uber.org/zap" - - assertcap "github.com/fil-forge/go-libstoracha/capabilities/assert" - claimcap "github.com/fil-forge/go-libstoracha/capabilities/claim" - contentcap "github.com/fil-forge/go-libstoracha/capabilities/space/content" - captypes "github.com/fil-forge/go-libstoracha/capabilities/types" - uclient "github.com/fil-forge/go-ucanto/client" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/principal" - ucanhttp "github.com/fil-forge/go-ucanto/transport/http" - "github.com/fil-forge/go-ucanto/ucan" ) -// retrievalAuthFact implements ucan.FactBuilder for the retrievalAuth fact. -// This is used to include a retrieval delegation link in the assert/index invocation -// so the indexer can fetch the index blob from storage providers that require UCAN auth. -type retrievalAuthFact struct { - link ipld.Link -} - -func (f retrievalAuthFact) ToIPLD() (map[string]datamodel.Node, error) { - return map[string]datamodel.Node{ - "retrievalAuth": basicnode.NewLink(f.link), - }, nil -} - // Client is a UCAN client for communicating with the indexer service. type Client struct { endpoint *url.URL indexerDID did.DID - signer principal.Signer - connection uclient.Connection + signer ucan.Signer + client *client.HTTPClient logger *zap.Logger } // New creates a new indexer client. -func New(endpoint *url.URL, indexerDID did.DID, signer principal.Signer, logger *zap.Logger) (*Client, error) { - channel := ucanhttp.NewChannel(endpoint) - conn, err := uclient.NewConnection(indexerDID, channel) +func New(endpoint *url.URL, indexerDID did.DID, signer ucan.Signer, logger *zap.Logger) (*Client, error) { + client, err := client.NewHTTP(endpoint) if err != nil { - return nil, fmt.Errorf("creating connection: %w", err) + return nil, fmt.Errorf("creating HTTP client: %w", err) } return &Client{ endpoint: endpoint, indexerDID: indexerDID, signer: signer, - connection: conn, + client: client, logger: logger, }, nil } -// PublishIndexClaim sends an assert/index claim to the indexer. -// clientAuth is the space/content/retrieve delegation from the client (guppy). -// If provided, the upload service creates a fresh delegation to the indexer -// using the same caveats. The client's proof chain is NOT included to avoid -// leaking did:mailto identities to storage nodes. -func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, content, index cid.Cid, clientAuth delegation.Delegation) error { - var opts []delegation.Option - - // Re-delegate the client's retrieval auth to the indexer if provided - if clientAuth != nil { - caps := clientAuth.Capabilities() - if len(caps) == 0 { - return fmt.Errorf("no capabilities in retrieval auth delegation") - } - - // Parse the original caveats from the client's delegation - origCaveats, readErr := contentcap.RetrieveCaveatsReader.Read(caps[0].Nb()) - if readErr != nil { - return fmt.Errorf("reading retrieval caveats: %w", readErr) - } - - // Create a delegation from upload service to indexer, including the client's - // proof chain. This allows the indexer to prove authority to piri. - // - // Note: This DOES include the client's proof chain (which may contain did:mailto). - // In production, a different authorization model should be used to avoid - // leaking client identities. For this mock/test setup, the did:mailto is only - // visible to piri (the storage node), not publicly exposed. - indexerDelegation, delegateErr := contentcap.Retrieve.Delegate( - c.signer, // issuer: upload service (did:key) - c.indexerDID, // audience: indexer (did:web:indexer) - space.String(), // with: space DID (resource) - origCaveats, // same caveats (Blob, Range) from client - delegation.WithNoExpiration(), - delegation.WithProof(delegation.FromDelegation(clientAuth)), // Include client's proof chain - ) - if delegateErr != nil { - return fmt.Errorf("creating indexer delegation: %w", delegateErr) - } - - // Include the delegation in the assert/index invocation - opts = append(opts, - delegation.WithFacts([]ucan.FactBuilder{ - retrievalAuthFact{link: indexerDelegation.Link()}, - }), - delegation.WithProof(delegation.FromDelegation(indexerDelegation)), - ) - } - - // assert/* capabilities are self-issued assertions, so the resource (with) - // should be the signer's DID, not the indexer's DID - inv, err := assertcap.Index.Invoke( - c.signer, - c.indexerDID, - c.signer.DID().String(), - assertcap.IndexCaveats{ - Content: cidlink.Link{Cid: content}, - Index: cidlink.Link{Cid: index}, - }, - opts..., - ) +// PublishIndexClaim sends an /assert/index claim to the indexer. +// +// The proofStore parameter is used to build the delegation chain authorizing +// the upload service to retrieve the index blob via `/content/retrieve` command. +func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid.Cid, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Receipt, error) { + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer, contentcaps.RetrieveCommand, space) if err != nil { - return fmt.Errorf("creating assert/index invocation: %w", err) + return nil, fmt.Errorf("building proof chain: %w", err) } - - resp, err := uclient.Execute(ctx, []invocation.Invocation{inv}, c.connection) + attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer) if err != nil { - return fmt.Errorf("executing assert/index: %w", err) - } - - rcptLink, ok := resp.Get(inv.Link()) - if !ok { - return fmt.Errorf("receipt not found for invocation") + return nil, fmt.Errorf("building attestations: %w", err) } - - // Read receipt and check for errors - anyReader := receipt.NewAnyReceiptReader(captypes.Converters...) - anyRcpt, err := anyReader.Read(rcptLink, resp.Blocks()) + // Create a content retrieval delegation from upload service to indexer + indexerDelegation, err := contentcaps.Retrieve.Delegate(c.signer, c.indexerDID, space) if err != nil { - return fmt.Errorf("reading receipt: %w", err) - } - - _, errNode := result.Unwrap(anyRcpt.Out()) - if errNode != nil { - // Extract error details for better debugging - var errDetails string - if msgNode, lookupErr := errNode.LookupByString("message"); lookupErr == nil { - if msg, asErr := msgNode.AsString(); asErr == nil { - errDetails = msg - } - } - if errDetails == "" { - if nameNode, lookupErr := errNode.LookupByString("name"); lookupErr == nil { - if name, asErr := nameNode.AsString(); asErr == nil { - errDetails = name - } - } - } - if errDetails == "" { - errDetails = "unknown error" - } - return fmt.Errorf("assert/index failed: %s", errDetails) + return nil, fmt.Errorf("creating indexer delegation: %w", err) } - return nil -} - -// CacheLocationClaim sends a claim/cache invocation to cache a location claim with the indexer. -// This tells the indexer where content is stored (provider address). -func (c *Client) CacheLocationClaim(ctx context.Context, claim delegation.Delegation, providerAddrs []multiaddr.Multiaddr) error { - c.logger.Debug("CacheLocationClaim", - zap.String("claim", claim.Link().String()), - zap.String("issuer", c.signer.DID().String()), - zap.String("audience", c.indexerDID.String()), - zap.Int("providerAddrs", len(providerAddrs))) - - inv, err := claimcap.Cache.Invoke( + inv, err := assertcaps.Index.Invoke( c.signer, - c.indexerDID, - c.signer.DID().String(), - claimcap.CacheCaveats{ - Claim: claim.Link(), - Provider: claimcap.Provider{ - Addresses: providerAddrs, - }, - }, - delegation.WithProof(delegation.FromDelegation(claim)), + c.signer, + &assertcaps.IndexArguments{Index: index}, + invocation.WithAudience(c.indexerDID), + invocation.WithMetadata( + datamodel.Map{"retrievalAuth": append(prfLinks, indexerDelegation.Link())}, + ), ) if err != nil { - return fmt.Errorf("creating claim/cache invocation: %w", err) - } - c.logger.Debug("created invocation", zap.String("link", inv.Link().String())) - - resp, err := uclient.Execute(ctx, []invocation.Invocation{inv}, c.connection) - if err != nil { - c.logger.Error("execute error", zap.Error(err)) - return fmt.Errorf("executing claim/cache: %w", err) - } - c.logger.Debug("execute succeeded") - - rcptLink, ok := resp.Get(inv.Link()) - if !ok { - c.logger.Debug("no receipt in response") - return fmt.Errorf("receipt not found for invocation") + return nil, fmt.Errorf("creating invocation: %w", err) } - c.logger.Debug("got receipt", zap.String("link", rcptLink.String())) - // Read receipt and check for errors - anyReader := receipt.NewAnyReceiptReader(captypes.Converters...) - anyRcpt, err := anyReader.Read(rcptLink, resp.Blocks()) + _, rcpt, err := ucan_client.Execute[*assertcaps.IndexOK]( + ctx, + c.client, + c.logger, + inv, + execution.WithDelegations(prfs...), + execution.WithInvocations(attestations...), + ) if err != nil { - c.logger.Error("reading receipt error", zap.Error(err)) - return fmt.Errorf("reading receipt: %w", err) - } - - okNode, errNode := result.Unwrap(anyRcpt.Out()) - c.logger.Debug("receipt result", zap.Bool("ok", okNode != nil), zap.Bool("err", errNode != nil)) - if errNode != nil { - // Extract error details for better debugging - var errDetails string - if msgNode, lookupErr := errNode.LookupByString("message"); lookupErr == nil { - if msg, asErr := msgNode.AsString(); asErr == nil { - errDetails = msg - } - } - if errDetails == "" { - if nameNode, lookupErr := errNode.LookupByString("name"); lookupErr == nil { - if name, asErr := nameNode.AsString(); asErr == nil { - errDetails = name - } - } - } - if errDetails == "" { - errDetails = "unknown error" - } - return fmt.Errorf("claim/cache failed: %s", errDetails) + return nil, fmt.Errorf("executing assert index invocation: %w", err) } - - return nil + return rcpt, nil } diff --git a/pkg/lib/didmailto/did.go b/pkg/lib/didmailto/did.go deleted file mode 100644 index 7b34361..0000000 --- a/pkg/lib/didmailto/did.go +++ /dev/null @@ -1,63 +0,0 @@ -package didmailto - -import ( - "fmt" - "net/mail" - "net/url" - "strings" - - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" -) - -const InvalidMailtoDIDErrorName = "InvalidMailtoDID" - -var ErrInvalidMailtoDID = errors.New(InvalidMailtoDIDErrorName, "invalid mailto DID") - -// New creates a new mailto DID from an RFC 5322 formatted email address. -func New(email string) (did.DID, error) { - a, err := mail.ParseAddress(email) - if err != nil { - return did.Undef, fmt.Errorf("parsing email: %w", err) - } - at := strings.LastIndex(a.Address, "@") - var local, domain string - if at < 0 { - // This is a malformed address ("@" is required in addr-spec); - return did.Undef, fmt.Errorf("malformed email address: %s", email) - } - local, domain = a.Address[:at], a.Address[at+1:] - return did.Parse(fmt.Sprintf("did:mailto:%s:%s", url.QueryEscape(domain), url.QueryEscape(local))) -} - -// Email extracts the email address from the DID. -func Email(d did.DID) (string, error) { - if !strings.HasPrefix(d.String(), "did:mailto:") { - return "", ErrInvalidMailtoDID - } - parts := strings.Split(d.String(), ":") - emailLocal, err := url.QueryUnescape(parts[3]) - if err != nil { - return "", ErrInvalidMailtoDID - } - domain, err := url.QueryUnescape(parts[2]) - if err != nil { - return "", ErrInvalidMailtoDID - } - return fmt.Sprintf("%s@%s", emailLocal, domain), nil -} - -func Parse(str string) (did.DID, error) { - d, err := did.Parse(str) - if err != nil { - return did.Undef, err - } - if !strings.HasPrefix(d.String(), "did:mailto:") { - return did.Undef, ErrInvalidMailtoDID - } - return d, nil -} - -func Format(d did.DID) string { - return d.String() -} diff --git a/pkg/lib/didmailto/did_test.go b/pkg/lib/didmailto/did_test.go deleted file mode 100644 index ee4b3ed..0000000 --- a/pkg/lib/didmailto/did_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package didmailto - -import ( - "testing" - - "github.com/fil-forge/go-ucanto/did" - "github.com/stretchr/testify/require" -) - -func TestNew(t *testing.T) { - t.Run("simple email", func(t *testing.T) { - d, err := New("user@example.com") - require.NoError(t, err) - require.Equal(t, "did:mailto:example.com:user", d.String()) - }) - - t.Run("subdomain", func(t *testing.T) { - d, err := New("user@mail.example.com") - require.NoError(t, err) - require.Equal(t, "did:mailto:mail.example.com:user", d.String()) - }) - - t.Run("missing @ returns error", func(t *testing.T) { - _, err := New("notanemail") - require.Error(t, err) - }) - - t.Run("multiple @ returns error", func(t *testing.T) { - _, err := New("a@b@c") - require.Error(t, err) - }) - - t.Run("empty string returns error", func(t *testing.T) { - _, err := New("") - require.Error(t, err) - }) -} - -func TestEmail(t *testing.T) { - t.Run("round-trips simple email", func(t *testing.T) { - d, err := New("user@example.com") - require.NoError(t, err) - email, err := Email(d) - require.NoError(t, err) - require.Equal(t, "user@example.com", email) - }) - - t.Run("round-trips local part with plus sign", func(t *testing.T) { - d, err := New("user+tag@example.com") - require.NoError(t, err) - email, err := Email(d) - require.NoError(t, err) - require.Equal(t, "user+tag@example.com", email) - }) - - t.Run("non-mailto DID returns error", func(t *testing.T) { - d, err := did.Parse("did:web:example.com") - require.NoError(t, err) - _, err = Email(d) - require.ErrorIs(t, err, ErrInvalidMailtoDID) - }) - - t.Run("round-trips unusual emails", func(t *testing.T) { - emails := []string{ - // https://gist.github.com/cjaoude/fd9910626629b53c4d25#file-gistfile1-txt-L5 - "email@example.com", - "firstname.lastname@example.com", - "email@subdomain.example.com", - "firstname+lastname@example.com", - "email@123.123.123.123", - "email@[123.123.123.123]", - // TODO: quoted addresses do not roundtrip with net/mail because the - // quotes are stripped out during parsing - // "\"email\"@example.com", - // "\"email@1\"@example.com", - "1234567890@example.com", - "email@example-one.com", - "_______@example.com", - "email@example.name", - "email@example.museum", - "email@example.co.jp", - "firstname-lastname@example.com", - // https://gist.github.com/cjaoude/fd9910626629b53c4d25#file-gistfile1-txt-L24 - // TODO: none of these parse with net/mail - // "much.”more\\ unusual”@example.com", - // "very.unusual.”@”.unusual.com@example.com", - // "very.”(),:;<>[]”.VERY.”very@\\ \"very”.unusual@strange.example.com", - } - for _, email := range emails { - t.Run(email, func(t *testing.T) { - d, err := New(email) - require.NoError(t, err) - t.Log(d.String()) - got, err := Email(d) - require.NoError(t, err) - require.Equal(t, email, got) - }) - } - }) -} - -func TestParse(t *testing.T) { - t.Run("valid mailto DID", func(t *testing.T) { - d, err := Parse("did:mailto:example.com:user") - require.NoError(t, err) - require.Equal(t, "did:mailto:example.com:user", d.String()) - }) - - t.Run("non-mailto DID returns error", func(t *testing.T) { - _, err := Parse("did:web:example.com") - require.ErrorIs(t, err, ErrInvalidMailtoDID) - }) - - t.Run("invalid DID string returns error", func(t *testing.T) { - _, err := Parse("not-a-did") - require.Error(t, err) - }) -} - -func TestFormat(t *testing.T) { - d, err := New("user@example.com") - require.NoError(t, err) - require.Equal(t, d.String(), Format(d)) -} - -func TestNewEmailRoundTrip(t *testing.T) { - emails := []string{ - "user@example.com", - "user+tag@example.com", - "first.last@sub.domain.org", - } - for _, email := range emails { - t.Run(email, func(t *testing.T) { - d, err := New(email) - require.NoError(t, err) - got, err := Email(d) - require.NoError(t, err) - require.Equal(t, email, got) - }) - } -} diff --git a/pkg/lib/errors/alias.go b/pkg/lib/errors/alias.go deleted file mode 100644 index 23ae3a2..0000000 --- a/pkg/lib/errors/alias.go +++ /dev/null @@ -1,8 +0,0 @@ -package errors - -import "errors" - -var ( - As = errors.As - Is = errors.Is -) diff --git a/pkg/lib/errors/error.go b/pkg/lib/errors/error.go deleted file mode 100644 index 924e19e..0000000 --- a/pkg/lib/errors/error.go +++ /dev/null @@ -1,40 +0,0 @@ -package errors - -import ( - "fmt" - - "github.com/ipld/go-ipld-prime/datamodel" - "github.com/ipld/go-ipld-prime/fluent" - "github.com/ipld/go-ipld-prime/node/basicnode" -) - -type ErrorModel struct { - ErrorName string - Message string -} - -// New creates an IPLD error that has a name as well as a message. -func New(name, message string, args ...any) ErrorModel { - if len(args) > 0 { - message = fmt.Sprintf(message, args...) - } - return ErrorModel{ - ErrorName: name, - Message: message, - } -} - -func (em ErrorModel) Name() string { - return em.ErrorName -} - -func (em ErrorModel) Error() string { - return em.Message -} - -func (em ErrorModel) ToIPLD() (datamodel.Node, error) { - return fluent.BuildMap(basicnode.Prototype.Map, 2, func(ma fluent.MapAssembler) { - ma.AssembleEntry("name").AssignString(em.ErrorName) - ma.AssembleEntry("message").AssignString(em.Message) - }) -} diff --git a/pkg/lib/ucan_client/execute.go b/pkg/lib/ucan_client/execute.go new file mode 100644 index 0000000..c0c063a --- /dev/null +++ b/pkg/lib/ucan_client/execute.go @@ -0,0 +1,84 @@ +package ucan_client + +import ( + "context" + "fmt" + "reflect" + + "github.com/fil-forge/ucantone/client" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/ipld/codec/dagcbor" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "go.uber.org/zap" +) + +// Execute sends the given invocation using the provided client and decodes the +// response into the specified type. +func Execute[T dagcbor.Unmarshaler]( + ctx context.Context, + client *client.HTTPClient, + logger *zap.Logger, + inv ucan.Invocation, + options ...execution.RequestOption, +) (T, ucan.Receipt, error) { + fields := []zap.Field{ + zap.Stringer("issuer", inv.Issuer().DID()), + zap.Stringer("subject", inv.Subject().DID()), + zap.Stringer("command", inv.Command()), + zap.Any("arguments", inv.Arguments()), + } + if inv.Audience() != nil { + fields = append(fields, zap.Stringer("audience", inv.Audience().DID())) + } + if len(inv.Metadata()) > 0 { + fields = append(fields, zap.Any("metadata", inv.Metadata())) + } + if len(inv.Proofs()) > 0 { + fields = append(fields, zap.Stringers("proofs", inv.Proofs())) + } + log := logger.With(zap.Dict("invocation", fields...)) + log.Debug("executing invocation") + + var zero T + resp, err := client.Execute(execution.NewRequest(ctx, inv, options...)) + if err != nil { + log.Error("failed to execute invocation", zap.Error(err)) + return zero, nil, fmt.Errorf("executing invocation: %w", err) + } + + rcpt := resp.Receipt() + ok, err := result.MatchResultR2( + rcpt.Out(), + func(o ipld.Any) (T, error) { + var ok T + // if ok is a pointer type, then we need to create an instance of it + // because rebind requires a non-nil pointer. + typ := reflect.TypeOf(ok) + if typ.Kind() == reflect.Ptr { + ok = reflect.New(typ.Elem()).Interface().(T) + } + err := datamodel.Rebind(datamodel.NewAny(o), ok) + if err != nil { + log.Error("failed to bind invocation response", zap.Error(err)) + return zero, fmt.Errorf("binding invocation response: %w", err) + } + return ok, nil + }, + func(x ipld.Any) (T, error) { + var model edm.ErrorModel + err := datamodel.Rebind(datamodel.NewAny(x), &model) + if err != nil { + log.Error("failed to bind execution failure", zap.Error(err)) + log.Error("failed execution", zap.Any("error", x)) + return zero, fmt.Errorf("executing invocation: %v", x) + } + log.Error("failed execution", zap.String("name", model.ErrorName), zap.Error(model)) + return zero, fmt.Errorf("executing invocation: %w", model) + }, + ) + return ok, rcpt, err +} diff --git a/pkg/lib/ucan_server/email_auth.go b/pkg/lib/ucan_server/email_auth.go new file mode 100644 index 0000000..5ef555d --- /dev/null +++ b/pkg/lib/ucan_server/email_auth.go @@ -0,0 +1,96 @@ +package ucan_server + +import ( + "context" + "fmt" + + "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/didmailto" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" +) + +type AccessConfirmResult struct { + Email string + Audience string + UCAN string + Meta ipld.Map +} + +// ExecBase64urlAccessConfirm executes an /access/confirm UCAN invocation +// contained in a base64url-encoded container string. Typically used by the +// email authentication flow. +func ExecBase64urlAccessConfirm(ctx context.Context, executor execution.Executor, input string) (AccessConfirmResult, error) { + inCt, err := container.Decode([]byte(input)) + if err != nil { + return AccessConfirmResult{}, fmt.Errorf("decoding UCAN container: %w", err) + } + if len(inCt.Invocations()) != 1 { + return AccessConfirmResult{}, fmt.Errorf("unexpected number of invocations found in UCAN") + } + + confirmation := inCt.Invocations()[0] + // check this is a confirmation invocation + if confirmation.Command() != access.ConfirmCommand { + return AccessConfirmResult{}, fmt.Errorf("unexpected command in invocation, expected %s but got %s", access.ConfirmCommand, confirmation.Command()) + } + + req := execution.NewRequest( + ctx, + confirmation, + execution.WithDelegations(inCt.Delegations()...), + execution.WithReceipts(inCt.Receipts()...), + ) + + res, err := executor.Execute(req) + if err != nil { + return AccessConfirmResult{}, fmt.Errorf("executing confirm task %s: %w", confirmation.Task().Link(), err) + } + + _, x := result.Unwrap(res.Receipt().Out()) + if x != nil { + return AccessConfirmResult{}, fmt.Errorf("invocation failure: %v", x) + } + + confirmArgs := access.ConfirmArguments{} + err = datamodel.Rebind(datamodel.NewAny(confirmation.Arguments()), &confirmArgs) + if err != nil { + return AccessConfirmResult{}, fmt.Errorf("binding confirmation arguments: %w", err) + } + + email, err := didmailto.Email(confirmArgs.Issuer) + if err != nil { + return AccessConfirmResult{}, fmt.Errorf("parsing account DID: %w", err) + } + + var invocations []ucan.Invocation + var delegations []ucan.Delegation + receipts := []ucan.Receipt{res.Receipt()} + if res.Metadata() != nil { + invocations = append(invocations, res.Metadata().Invocations()...) + delegations = append(delegations, res.Metadata().Delegations()...) + receipts = append(receipts, res.Metadata().Receipts()...) + } + + outCt := container.New( + container.WithInvocations(invocations...), + container.WithDelegations(delegations...), + container.WithReceipts(receipts...), + ) + + output, err := container.Encode(container.Base64urlGzip, outCt) + if err != nil { + return AccessConfirmResult{}, fmt.Errorf("encoding output UCAN container: %w", err) + } + + return AccessConfirmResult{ + Email: email, + Audience: confirmArgs.Audience.String(), + UCAN: string(output), + Meta: confirmation.Metadata(), + }, nil +} diff --git a/pkg/lib/ucan_server/events.go b/pkg/lib/ucan_server/events.go new file mode 100644 index 0000000..a87857e --- /dev/null +++ b/pkg/lib/ucan_server/events.go @@ -0,0 +1,69 @@ +package ucan_server + +import ( + "context" + "fmt" + + "github.com/fil-forge/sprue/pkg/store/agent" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/server" + "github.com/fil-forge/ucantone/ucan" + "go.uber.org/zap" +) + +type ErrorHandler struct { + Logger *zap.Logger +} + +var _ server.ResponseEncodeListener = (*ErrorHandler)(nil) + +func (l ErrorHandler) OnResponseEncode(ctx context.Context, ct ucan.Container) error { + for _, inv := range ct.Invocations() { + if r, ok := ct.Receipt(inv.Task().Link()); ok { + _, x := result.Unwrap(r.Out()) + if x != nil { + var model edm.ErrorModel + datamodel.Rebind(datamodel.NewAny(x), &model) + if model.ErrorName == execution.HandlerExecutionErrorName { + l.Logger.Error( + "handler execution error", + zap.Stringer("task", inv.Task().Link()), + zap.Stringer("command", inv.Command()), + zap.Any("args", inv.Arguments()), + zap.Error(model), + ) + } + } + } + } + return nil +} + +type AgentMessageLogger struct { + Logger *zap.Logger + AgentStore agent.Store +} + +var _ server.RequestDecodeListener = (*AgentMessageLogger)(nil) +var _ server.ResponseEncodeListener = (*AgentMessageLogger)(nil) + +func (r *AgentMessageLogger) OnRequestDecode(ctx context.Context, msg ucan.Container) error { + err := r.AgentStore.Write(ctx, msg, agent.Index(msg)) + if err != nil { + r.Logger.Error("failed to write incoming agent message to store", zap.Error(err)) + return fmt.Errorf("writing incoming agent message to agent store: %w", err) + } + return nil +} + +func (r *AgentMessageLogger) OnResponseEncode(ctx context.Context, msg ucan.Container) error { + err := r.AgentStore.Write(ctx, msg, agent.Index(msg)) + if err != nil { + r.Logger.Error("failed to write outgoing agent message to store", zap.Error(err)) + return fmt.Errorf("writing outgoing agent message to agent store: %w", err) + } + return nil +} diff --git a/pkg/lib/ucan_server/proofs.go b/pkg/lib/ucan_server/proofs.go new file mode 100644 index 0000000..9477c6d --- /dev/null +++ b/pkg/lib/ucan_server/proofs.go @@ -0,0 +1,73 @@ +package ucan_server + +import ( + "context" + "iter" + + ucanlib "github.com/fil-forge/libforge/ucan" + "github.com/fil-forge/ucantone/ucan" +) + +type ProofStore interface { + ProofChain(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Principal) ([]ucan.Delegation, []ucan.Link, error) + ProofAttestations(ctx context.Context, proofs []ucan.Delegation, authority ucan.Principal) ([]ucan.Invocation, error) +} + +// ContainerProofStore is a proof store backed by an in-memory container. +type ContainerProofStore struct { + container ucan.Container +} + +// NewContainerProofStore creates a proof store backed by an in-memory container. +func NewContainerProofStore(ct ucan.Container) *ContainerProofStore { + return &ContainerProofStore{container: ct} +} + +func (cps *ContainerProofStore) ProofChain(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Principal) ([]ucan.Delegation, []ucan.Link, error) { + return ucanlib.ProofChain(ctx, cps.matchDelegations, aud, cmd, sub) +} + +func (cps *ContainerProofStore) ProofAttestations(ctx context.Context, proofs []ucan.Delegation, authority ucan.Principal) ([]ucan.Invocation, error) { + return ucanlib.ProofAttestations(ctx, cps.listInvocations, proofs, authority) +} + +func (ps *ContainerProofStore) listDelegations(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Subject) iter.Seq2[ucan.Delegation, error] { + return func(yield func(ucan.Delegation, error) bool) { + if ps.container == nil { + return + } + for _, d := range ps.container.Delegations() { + if d.Audience().DID() == aud.DID() && d.Command() == cmd && equalSubject(d.Subject(), sub) { + if !yield(d, nil) { + return + } + } + } + } +} + +func (ps *ContainerProofStore) matchDelegations(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Subject) iter.Seq2[ucan.Delegation, error] { + return ucanlib.NewDelegationMatcher(ps.listDelegations)(ctx, aud, cmd, sub) +} + +func (ps *ContainerProofStore) listInvocations(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Subject) iter.Seq2[ucan.Invocation, error] { + return func(yield func(ucan.Invocation, error) bool) { + if ps.container == nil { + return + } + for _, d := range ps.container.Invocations() { + if d.Audience().DID() == aud.DID() && d.Command() == cmd && equalSubject(d.Subject(), sub) { + if !yield(d, nil) { + return + } + } + } + } +} + +func equalSubject(a, b ucan.Subject) bool { + if a == nil || b == nil { + return a == nil && b == nil + } + return a.DID() == b.DID() +} diff --git a/pkg/lib/ucan_server/validation.go b/pkg/lib/ucan_server/validation.go new file mode 100644 index 0000000..f3b4e59 --- /dev/null +++ b/pkg/lib/ucan_server/validation.go @@ -0,0 +1,65 @@ +package ucan_server + +import ( + "context" + "fmt" + + "github.com/fil-forge/libforge/capabilities/ucan/attest" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + ed_verifier "github.com/fil-forge/ucantone/principal/ed25519/verifier" + secp_verifier "github.com/fil-forge/ucantone/principal/secp256k1/verifier" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/validator" +) + +// PrincipalParser is a [validator.PrincipalParserFunc] that enables support for +// both ed25519 and secp256k1 principals in UCANs. +func PrincipalParser(str string) (principal.Verifier, error) { + if v, err := ed_verifier.Parse(str); err == nil { + return v, nil + } + if v, err := secp_verifier.Parse(str); err == nil { + return v, nil + } + return nil, fmt.Errorf("unknown principal type: %s", str) +} + +// NewAttestationVerifier creates a [validator.NonStandardSignatureVerifierFunc] +// that validates that a delegation is attested by the given authority. +func NewAttestationVerifier(authority principal.Verifier) validator.NonStandardSignatureVerifierFunc { + return func(ctx context.Context, token ucan.Token, meta ucan.Container) error { + // We only support attestations as delegations - attested delegation MUST + // delegate to an agent DID which is then used in the invocation. + dlg, ok := token.(ucan.Delegation) + if !ok { + return fmt.Errorf("token is not a delegation") + } + for _, inv := range meta.Invocations() { + if inv.Command() != attest.ProofCommand { + continue + } + // only trust attestations we issued + if inv.Issuer().DID() != authority.DID() || inv.Subject() == nil || inv.Subject().DID() != authority.DID() { + continue + } + args := attest.ProofArguments{} + err := datamodel.Rebind(datamodel.NewAny(inv.Arguments()), &args) + if err != nil { + continue + } + // make sure the attestation is for the delegation in question + if args.Proof != dlg.Link() { + continue + } + // finally, make sure the signature is valid + ok, err := invocation.VerifySignature(inv, authority) + if !ok || err != nil { + continue + } + return nil + } + return fmt.Errorf("no valid attestation found for delegation") + } +} diff --git a/pkg/lib/ucan_server/validation_test.go b/pkg/lib/ucan_server/validation_test.go new file mode 100644 index 0000000..9385566 --- /dev/null +++ b/pkg/lib/ucan_server/validation_test.go @@ -0,0 +1,122 @@ +package ucan_server + +import ( + "testing" + + "github.com/fil-forge/libforge/capabilities/ucan/attest" + "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal/absentee" + "github.com/fil-forge/ucantone/principal/ed25519" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/stretchr/testify/require" +) + +func TestNewAttestationVerifier(t *testing.T) { + authority := testutil.WebService + agent := testutil.Alice + space := testutil.Must(ed25519.Generate())(t) + other := testutil.Must(ed25519.Generate())(t) + + account := absentee.From(testutil.Must(did.Parse("did:mailto:web.mail:alice"))(t)) + + dlg, err := delegation.Delegate(account, agent, space, "/blob/add") + require.NoError(t, err) + + verify := NewAttestationVerifier(authority.Verifier()) + + t.Run("token is not a delegation", func(t *testing.T) { + inv, err := invocation.Invoke(agent, space, "/blob/add", datamodel.Map{}) + require.NoError(t, err) + + err = verify(t.Context(), inv, container.New()) + require.Error(t, err) + require.Contains(t, err.Error(), "not a delegation") + }) + + t.Run("no attestations in container", func(t *testing.T) { + err := verify(t.Context(), dlg, container.New()) + require.Error(t, err) + require.Contains(t, err.Error(), "no valid attestation") + }) + + t.Run("invocation with non-attest command is ignored", func(t *testing.T) { + inv, err := invocation.Invoke(authority, authority, "/some/other", datamodel.Map{}) + require.NoError(t, err) + + err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) + require.Error(t, err) + require.Contains(t, err.Error(), "no valid attestation") + }) + + t.Run("attestation issued by non-authority is ignored", func(t *testing.T) { + inv, err := attest.Proof.Invoke(other, other, &attest.ProofArguments{Proof: dlg.Link()}) + require.NoError(t, err) + + err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) + require.Error(t, err) + require.Contains(t, err.Error(), "no valid attestation") + }) + + t.Run("attestation with subject other than authority is ignored", func(t *testing.T) { + inv, err := attest.Proof.Invoke(authority, other, &attest.ProofArguments{Proof: dlg.Link()}) + require.NoError(t, err) + + err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) + require.Error(t, err) + require.Contains(t, err.Error(), "no valid attestation") + }) + + t.Run("attestation proof for a different delegation is ignored", func(t *testing.T) { + otherDlg, err := delegation.Delegate(account, agent, space, "/blob/list") + require.NoError(t, err) + + inv, err := attest.Proof.Invoke(authority, authority, &attest.ProofArguments{Proof: otherDlg.Link()}) + require.NoError(t, err) + + err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) + require.Error(t, err) + require.Contains(t, err.Error(), "no valid attestation") + }) + + t.Run("attestation with malformed arguments is ignored", func(t *testing.T) { + inv, err := invocation.Invoke( + authority, + authority, + attest.ProofCommand, + datamodel.Map{"unrelated": "foo"}, + ) + require.NoError(t, err) + + err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) + require.Error(t, err) + require.Contains(t, err.Error(), "no valid attestation") + }) + + t.Run("valid attestation passes verification", func(t *testing.T) { + inv, err := attest.Proof.Invoke(authority, authority, &attest.ProofArguments{Proof: dlg.Link()}) + require.NoError(t, err) + + err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) + require.NoError(t, err) + }) + + t.Run("valid attestation found among invalid ones", func(t *testing.T) { + untrusted, err := attest.Proof.Invoke(other, other, &attest.ProofArguments{Proof: dlg.Link()}) + require.NoError(t, err) + wrongCmd, err := invocation.Invoke(authority, authority, "/some/other", datamodel.Map{}) + require.NoError(t, err) + valid, err := attest.Proof.Invoke(authority, authority, &attest.ProofArguments{Proof: dlg.Link()}) + require.NoError(t, err) + + err = verify( + t.Context(), + dlg, + container.New(container.WithInvocations(untrusted, wrongCmd, valid)), + ) + require.NoError(t, err) + }) +} diff --git a/pkg/lib/ucans/delegations.go b/pkg/lib/ucans/delegations.go deleted file mode 100644 index 489c5c6..0000000 --- a/pkg/lib/ucans/delegations.go +++ /dev/null @@ -1,82 +0,0 @@ -package ucans - -import ( - "bytes" - "io" - - "github.com/fil-forge/go-ucanto/core/car" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/ipfs/go-cid" - "github.com/multiformats/go-multibase" -) - -// ArchiveDelegations takes a list of delegations and returns CAR file bytes. -// This is the legacy format used in w3infra. -func ArchiveDelegations(delegations ...delegation.Delegation) ([]byte, error) { - var roots []ipld.Link - blocks := map[cid.Cid]ipld.Block{} - for _, d := range delegations { - roots = append(roots, d.Link()) - for b, err := range d.Export() { - if err != nil { - return nil, err - } - root, err := ipldutil.ToCID(b.Link()) - if err != nil { - return nil, err - } - blocks[root] = b - } - } - r := car.Encode(roots, func(yield func(ipld.Block, error) bool) { - for _, b := range blocks { - if !yield(b, nil) { - return - } - } - }) - return io.ReadAll(r) -} - -// FormatDelegations takes a list of delegations and returns a string that can -// be included in a URL. -func FormatDelegations(delegations ...delegation.Delegation) (string, error) { - carBytes, err := ArchiveDelegations(delegations...) - if err != nil { - return "", err - } - return multibase.Encode(multibase.Base64url, carBytes) -} - -func ParseDelegations(s string) ([]delegation.Delegation, error) { - _, carBytes, err := multibase.Decode(s) - if err != nil { - return nil, err - } - return ExtractDelegations(carBytes) -} - -// ExtractDelegations extracts a set of delegations from a CAR file encoded -// in legacy format. -func ExtractDelegations(b []byte) ([]delegation.Delegation, error) { - roots, blocks, err := car.Decode(bytes.NewReader(b)) - if err != nil { - return nil, err - } - bs, err := blockstore.NewBlockStore(blockstore.WithBlocksIterator(blocks)) - if err != nil { - return nil, err - } - dlgs := make([]delegation.Delegation, 0, len(roots)) - for _, root := range roots { - d, err := delegation.NewDelegationView(root, bs) - if err != nil { - return nil, err - } - dlgs = append(dlgs, d) - } - return dlgs, nil -} diff --git a/pkg/piriclient/client.go b/pkg/piriclient/client.go index 4c19f6e..d381174 100644 --- a/pkg/piriclient/client.go +++ b/pkg/piriclient/client.go @@ -4,22 +4,20 @@ import ( "context" "fmt" "net/url" + "slices" "time" - blobcap "github.com/fil-forge/go-libstoracha/capabilities/blob" - blobreplicacap "github.com/fil-forge/go-libstoracha/capabilities/blob/replica" - "github.com/fil-forge/go-libstoracha/capabilities/types" - uclient "github.com/fil-forge/go-ucanto/client" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/result" - fdm "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/did" - ucanhttp "github.com/fil-forge/go-ucanto/transport/http" - "github.com/fil-forge/go-ucanto/ucan" + blobcap "github.com/fil-forge/libforge/capabilities/blob" + blobreplicacap "github.com/fil-forge/libforge/capabilities/blob/replica" + "github.com/fil-forge/sprue/pkg/lib/ucan_client" + "github.com/fil-forge/sprue/pkg/lib/ucan_server" + "github.com/fil-forge/ucantone/client" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/ucan/promise" + "github.com/ipfs/go-cid" "go.uber.org/zap" ) @@ -30,214 +28,103 @@ import ( // allow for retries and/or job queue delays. const replicaAllocationTTL = time.Hour * 24 -// DelegationFetcher provides an interface for fetching delegation proofs on-demand. -type DelegationFetcher interface { - // GetDelegation fetches the delegation proof for the given audience. - // Returns nil if no delegation is available (not an error condition). - GetDelegation(ctx context.Context, audience ucan.Principal) (delegation.Delegation, error) -} - // Client is a UCAN client for communicating with Piri nodes. type Client struct { - piriDID did.DID - signer ucan.Signer - connection uclient.Connection - logger *zap.Logger + piriDID did.DID + signer ucan.Signer + client *client.HTTPClient + logger *zap.Logger } // New creates a new Piri client. // The delegationFetcher is used to fetch delegation proofs on-demand for each request. func New(endpoint *url.URL, piriDID did.DID, signer ucan.Signer, logger *zap.Logger) (*Client, error) { - channel := ucanhttp.NewChannel(endpoint) - conn, err := uclient.NewConnection(piriDID, channel) + client, err := client.NewHTTP(endpoint) if err != nil { - return nil, fmt.Errorf("creating connection: %w", err) + return nil, fmt.Errorf("creating HTTP client: %w", err) } - return NewWithConnection(piriDID, signer, conn, logger), nil + return NewWithClient(piriDID, signer, client, logger), nil } -func NewWithConnection(piriDID did.DID, signer ucan.Signer, conn uclient.Connection, logger *zap.Logger) *Client { +func NewWithClient(piriDID did.DID, signer ucan.Signer, client *client.HTTPClient, logger *zap.Logger) *Client { return &Client{ - piriDID: piriDID, - signer: signer, - connection: conn, - logger: logger, + piriDID: piriDID, + signer: signer, + client: client, + logger: logger, } } -// AllocateRequest contains the parameters for a blob/allocate invocation. +// AllocateRequest contains the parameters for a /blob/allocate invocation. type AllocateRequest struct { Space did.DID Digest []byte Size uint64 - Cause ipld.Link -} - -// AllocateResponse contains the response from a blob/allocate invocation. -type AllocateResponse struct { - Size uint64 - Address *blobcap.Address -} - -// fetchDelegationOpts fetches the delegation proof and returns delegation options. -func (c *Client) fetchDelegationOpts(ctx context.Context, fetcher DelegationFetcher) ([]delegation.Option, error) { - var opts []delegation.Option - - if fetcher != nil { - c.logger.Debug("fetching delegation", zap.String("piriDID", c.piriDID.String())) - proof, err := fetcher.GetDelegation(ctx, c.signer) - if err != nil { - c.logger.Error("delegation fetch error", zap.Error(err)) - return nil, fmt.Errorf("fetching delegation: %w", err) - } - if proof != nil { - c.logger.Debug("found delegation", - zap.String("issuer", proof.Issuer().DID().String()), - zap.String("audience", proof.Audience().DID().String())) - opts = append(opts, delegation.WithProof(delegation.FromDelegation(proof))) - } else { - c.logger.Debug("no delegation found", zap.String("piriDID", c.piriDID.String())) - } - } else { - c.logger.Debug("no delegation fetcher configured") - } - - return opts, nil + Cause cid.Cid } -// Allocate sends a blob/allocate invocation to the piri node. +// Allocate sends a /blob/allocate invocation to the piri node. // Returns the response data, the invocation that was sent, and the receipt from piri. -func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, fetcher DelegationFetcher) (*AllocateResponse, invocation.Invocation, receipt.AnyReceipt, error) { - // Fetch delegation fresh for each request - opts, err := c.fetchDelegationOpts(ctx, fetcher) - if err != nil { - return nil, nil, nil, err - } - - // Create the invocation - // The resource (With) must be the piri node's DID for blob/allocate - inv, err := blobcap.Allocate.Invoke( - c.signer, - c.piriDID, - c.piriDID.String(), // resource is the piri DID - blobcap.AllocateCaveats{ - Space: req.Space, - Blob: types.Blob{ - Digest: req.Digest, - Size: req.Size, - }, - Cause: req.Cause, - }, - opts..., - ) +func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (*blobcap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { + inv, prfs, attestations, err := c.AllocateInvocation(ctx, req, proofStore, options...) if err != nil { return nil, nil, nil, fmt.Errorf("creating allocate invocation: %w", err) } - // Log invocation details - proofLinks := inv.Proofs() - blockCount := 0 - for _, blkErr := range inv.Export() { - if blkErr != nil { - continue - } - blockCount++ - } c.logger.Debug("ALLOCATE invocation created", - zap.String("issuer", inv.Issuer().DID().String()), - zap.String("audience", inv.Audience().DID().String()), - zap.Int("proofLinks", len(proofLinks)), - zap.Int("blocks", blockCount)) + zap.Stringer("issuer", inv.Issuer().DID()), + zap.Stringer("audience", inv.Audience().DID()), + zap.Int("proofs", len(prfs)), + zap.Int("attestations", len(attestations)), + ) - // Execute the invocation - resp, err := uclient.Execute(ctx, []invocation.Invocation{inv}, c.connection) + allocOK, rcpt, err := ucan_client.Execute[*blobcap.AllocateOK]( + ctx, + c.client, + c.logger, + inv, + execution.WithProofs(prfs...), + execution.WithInvocations(attestations...), + ) if err != nil { - return nil, nil, nil, fmt.Errorf("executing allocate invocation: %w", err) - } - - // Get the receipt - rcptLink, ok := resp.Get(inv.Link()) - if !ok { - return nil, nil, nil, fmt.Errorf("receipt not found for invocation") + return nil, nil, nil, err } + return allocOK, inv, rcpt, nil +} - // Read the receipt using the any reader to avoid type issues - anyReader := receipt.NewAnyReceiptReader(types.Converters...) - anyRcpt, err := anyReader.Read(rcptLink, resp.Blocks()) +// AllocateInvocation returns the invocation for the allocate request (for use in effects). +func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer, blobcap.AllocateCommand, req.Space) if err != nil { - return nil, nil, nil, fmt.Errorf("reading receipt: %w", err) + return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } - // Check for error response - okNode, errNode := result.Unwrap(anyRcpt.Out()) - if errNode != nil { - // Try to extract error details - var errDetails string - if msgNode, lookupErr := errNode.LookupByString("message"); lookupErr == nil { - if msg, asErr := msgNode.AsString(); asErr == nil { - errDetails = msg - } - } - if errDetails == "" { - if nameNode, lookupErr := errNode.LookupByString("name"); lookupErr == nil { - if name, asErr := nameNode.AsString(); asErr == nil { - errDetails = name - } - } - } - if errDetails == "" { - errDetails = "unknown error" - } - return nil, nil, nil, fmt.Errorf("allocate failed: %s", errDetails) - } - if okNode == nil { - return nil, nil, nil, fmt.Errorf("allocate returned nil result") - } - - // Rebind to the typed receipt - typedRcpt, err := receipt.Rebind[blobcap.AllocateOk, fdm.FailureModel]( - anyRcpt, - blobcap.AllocateOkType(), - fdm.FailureType(), - types.Converters..., - ) + attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer) if err != nil { - return nil, nil, nil, fmt.Errorf("rebinding receipt: %w", err) + return nil, nil, nil, fmt.Errorf("getting proof attestations: %w", err) } - // Extract the result - allocateOk, failErr := result.Unwrap(typedRcpt.Out()) - if (failErr != fdm.FailureModel{}) { - return nil, nil, nil, fmt.Errorf("allocate failed: %s", failErr.Message) - } - - return &AllocateResponse{ - Size: allocateOk.Size, - Address: allocateOk.Address, - }, inv, anyRcpt, nil -} - -// AllocateInvocation returns the invocation for the allocate request (for use in effects). -func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, fetcher DelegationFetcher) (invocation.IssuedInvocation, error) { - opts, err := c.fetchDelegationOpts(ctx, fetcher) - if err != nil { - return nil, err - } + options = slices.Clone(options) + options = append( + options, + invocation.WithAudience(c.piriDID), + invocation.WithProofs(prfLinks...), + ) - return blobcap.Allocate.Invoke( + inv, err := blobcap.Allocate.Invoke( c.signer, - c.piriDID, - c.piriDID.String(), - blobcap.AllocateCaveats{ - Space: req.Space, - Blob: types.Blob{ - Digest: req.Digest, - Size: req.Size, - }, + req.Space, + &blobcap.AllocateArguments{ + Blob: blobcap.Blob{Digest: req.Digest, Size: req.Size}, Cause: req.Cause, }, - opts..., + options..., ) + if err != nil { + return nil, nil, nil, fmt.Errorf("creating allocate invocation: %w", err) + } + + return inv, prfs, attestations, nil } // PiriDID returns the DID of the piri node. @@ -245,267 +132,140 @@ func (c *Client) PiriDID() did.DID { return c.piriDID } -// AcceptRequest contains the parameters for a blob/accept invocation. +// AcceptRequest contains the parameters for a /blob/accept invocation. type AcceptRequest struct { Space did.DID Digest []byte Size uint64 - Put ipld.Link // Link to the http/put invocation that uploaded the blob + Put cid.Cid // Link to the /http/put task that uploaded the blob } -// AcceptResponse contains the response from a blob/accept invocation. -type AcceptResponse struct { - Site ipld.Link // Link to the location claim delegation -} - -// Accept sends a blob/accept invocation to the piri node. -func (c *Client) Accept(ctx context.Context, req *AcceptRequest, fetcher DelegationFetcher) (*AcceptResponse, invocation.Invocation, receipt.AnyReceipt, error) { - // Fetch delegation fresh for each request - opts, err := c.fetchDelegationOpts(ctx, fetcher) - if err != nil { - return nil, nil, nil, err - } - - // Use WithNoExpiration so the invocation CID is deterministic and matches - // the accept invocation created in space/blob/add for effects - opts = append(opts, delegation.WithNoExpiration()) - inv, err := blobcap.Accept.Invoke( - c.signer, - c.piriDID, - c.piriDID.String(), - blobcap.AcceptCaveats{ - Space: req.Space, - Blob: types.Blob{ - Digest: req.Digest, - Size: req.Size, - }, - Put: blobcap.Promise{ - UcanAwait: blobcap.Await{ - Selector: ".out.ok", - Link: req.Put, - }, - }, - }, - opts..., - ) +// Accept sends a /blob/accept invocation to the piri node. +func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (*blobcap.AcceptOK, ucan.Invocation, ucan.Receipt, error) { + inv, prfs, attestations, err := c.AcceptInvocation(ctx, req, proofStore, options...) if err != nil { return nil, nil, nil, fmt.Errorf("creating accept invocation: %w", err) } - // Log invocation details - acceptProofLinks := inv.Proofs() - acceptBlockCount := 0 - for _, blkErr := range inv.Export() { - if blkErr != nil { - continue - } - acceptBlockCount++ - } c.logger.Debug("ACCEPT invocation created", - zap.String("issuer", inv.Issuer().DID().String()), - zap.String("audience", inv.Audience().DID().String()), - zap.Int("proofLinks", len(acceptProofLinks)), - zap.Int("blocks", acceptBlockCount)) + zap.Stringer("issuer", inv.Issuer().DID()), + zap.Stringer("audience", inv.Audience().DID()), + zap.Int("proofs", len(prfs)), + zap.Int("attestations", len(attestations)), + ) - // Execute the invocation - resp, err := uclient.Execute(ctx, []invocation.Invocation{inv}, c.connection) + acceptOK, rcpt, err := ucan_client.Execute[*blobcap.AcceptOK]( + ctx, + c.client, + c.logger, + inv, + execution.WithProofs(prfs...), + execution.WithInvocations(attestations...), + ) if err != nil { - return nil, nil, nil, fmt.Errorf("executing accept invocation: %w", err) - } - - // Get the receipt - rcptLink, ok := resp.Get(inv.Link()) - if !ok { - return nil, nil, nil, fmt.Errorf("receipt not found for invocation") + return nil, nil, nil, err } + return acceptOK, inv, rcpt, nil +} - // Read the receipt using the any reader - anyReader := receipt.NewAnyReceiptReader(types.Converters...) - anyRcpt, err := anyReader.Read(rcptLink, resp.Blocks()) +// AcceptInvocation returns the invocation for the accept request (for use in effects). +func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer, blobcap.AcceptCommand, req.Space) if err != nil { - return nil, nil, nil, fmt.Errorf("reading receipt: %w", err) - } - - // Check for error response - okNode, errNode := result.Unwrap(anyRcpt.Out()) - if errNode != nil { - var errDetails string - if msgNode, lookupErr := errNode.LookupByString("message"); lookupErr == nil { - if msg, asErr := msgNode.AsString(); asErr == nil { - errDetails = msg - } - } - if errDetails == "" { - if nameNode, lookupErr := errNode.LookupByString("name"); lookupErr == nil { - if name, asErr := nameNode.AsString(); asErr == nil { - errDetails = name - } - } - } - if errDetails == "" { - errDetails = "unknown error" - } - return nil, nil, nil, fmt.Errorf("accept failed: %s", errDetails) - } - if okNode == nil { - return nil, nil, nil, fmt.Errorf("accept returned nil result") + return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } - // Extract the site link from the ok node - var site ipld.Link - if siteNode, lookupErr := okNode.LookupByString("site"); lookupErr == nil { - if siteLink, asErr := siteNode.AsLink(); asErr == nil { - site = siteLink - } - } - - return &AcceptResponse{ - Site: site, - }, inv, anyRcpt, nil -} - -// AcceptInvocation returns the invocation for the accept request (for use in effects). -func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, fetcher DelegationFetcher) (invocation.IssuedInvocation, error) { - opts, err := c.fetchDelegationOpts(ctx, fetcher) + attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer) if err != nil { - return nil, err + return nil, nil, nil, fmt.Errorf("getting proof attestations: %w", err) } - opts = append(opts, delegation.WithNoExpiration()) - return blobcap.Accept.Invoke( + options = slices.Clone(options) + options = append( + options, + invocation.WithAudience(c.piriDID), + invocation.WithProofs(prfLinks...), + ) + + inv, err := blobcap.Accept.Invoke( c.signer, - c.piriDID, - c.piriDID.String(), - blobcap.AcceptCaveats{ - Space: req.Space, - Blob: types.Blob{ - Digest: req.Digest, - Size: req.Size, - }, - Put: blobcap.Promise{ - UcanAwait: blobcap.Await{ - Selector: ".out.ok", - Link: req.Put, - }, - }, + req.Space, + &blobcap.AcceptArguments{ + Blob: blobcap.Blob{Digest: req.Digest, Size: req.Size}, + Put: promise.AwaitOK{Task: req.Put}, }, - opts..., + options..., ) + if err != nil { + return nil, nil, nil, fmt.Errorf("creating accept invocation: %w", err) + } + + return inv, prfs, attestations, nil } -// ReplicaAllocateRequest contains the parameters for a blob/replica/allocate invocation. +// ReplicaAllocateRequest contains the parameters for a /blob/replica/allocate invocation. type ReplicaAllocateRequest struct { Space did.DID Digest []byte Size uint64 - Site delegation.Delegation // Location commitment - Cause ipld.Link + Site ucan.Invocation // Location commitment + Cause cid.Cid } -// ReplicaAllocateResponse contains the response from a blob/replica/allocate invocation. -type ReplicaAllocateResponse struct { - // Size is the number of bytes allocated for the Blob. - Size uint64 - // Site resolves to an additional location for the blob. - // The selector MUST be ".out.ok.site" i.e. [AllocateSiteSelector] and it - // links to a receipt of a "blob/replica/transfer" task. - Site types.Promise - // Transfer is the invocation referenced in the promise, which is included in - // the allocation response. - Transfer invocation.Invocation -} - -// ReplicaAllocate sends a blob/replica/allocate invocation to the piri node. +// ReplicaAllocate sends a /blob/replica/allocate invocation to the piri node. // Returns the response data, the invocation that was sent, and the receipt from // piri. It returns an error if the receipt contains a failure result. -func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, fetcher DelegationFetcher) (*ReplicaAllocateResponse, invocation.Invocation, receipt.AnyReceipt, error) { - opts, err := c.fetchDelegationOpts(ctx, fetcher) +func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer, blobreplicacap.AllocateCommand, req.Space) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } - // We set a reasonably large expiration as replication nodes use the - // invocation as proof for obtaining a retrieval delegation, and we want to - // allow for retries and/or job queue delays. - exp := time.Now().Add(replicaAllocationTTL).Unix() - opts = append(opts, delegation.WithExpiration(int(exp))) + attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer) + if err != nil { + return nil, nil, nil, fmt.Errorf("getting proof attestations: %w", err) + } + + options = slices.Clone(options) + options = append( + options, + invocation.WithAudience(c.piriDID), + invocation.WithProofs(prfLinks...), + // We set a reasonably large expiration as replication nodes use the + // invocation as proof for obtaining a retrieval delegation, and we want to + // allow for retries and/or job queue delays. + invocation.WithExpiration(uint64(time.Now().Add(replicaAllocationTTL).Unix())), + ) inv, err := blobreplicacap.Allocate.Invoke( c.signer, - c.piriDID, - c.piriDID.String(), // resource is the piri DID - blobreplicacap.AllocateCaveats{ - Space: req.Space, - Blob: types.Blob{ - Digest: req.Digest, - Size: req.Size, - }, + req.Space, + &blobreplicacap.AllocateArguments{ + Blob: blobreplicacap.Blob{Digest: req.Digest, Size: req.Size}, Site: req.Site.Link(), Cause: req.Cause, }, - opts..., + options..., ) if err != nil { return nil, nil, nil, fmt.Errorf("creating replica allocate invocation: %w", err) } - // attach the location commitment to the allocation invocation - for b, err := range req.Site.Blocks() { - if err != nil { - return nil, nil, nil, fmt.Errorf("iterating location commitment blocks: %w", err) - } - if err := inv.Attach(b); err != nil { - return nil, nil, nil, fmt.Errorf("attaching location commitment block: %w", err) - } - } - c.logger.Debug("REPLICA ALLOCATE invocation created", zap.Stringer("issuer", inv.Issuer().DID()), zap.Stringer("audience", inv.Audience().DID()), zap.Int("proofs", len(inv.Proofs()))) - resp, err := uclient.Execute(ctx, []invocation.Invocation{inv}, c.connection) - if err != nil { - return nil, nil, nil, fmt.Errorf("executing replica allocate invocation: %w", err) - } - - rcptLink, ok := resp.Get(inv.Link()) - if !ok { - return nil, nil, nil, fmt.Errorf("receipt not found for invocation") - } - - reader := receipt.NewAnyReceiptReader(types.Converters...) - rcpt, err := reader.Read(rcptLink, resp.Blocks()) - if err != nil { - return nil, nil, nil, fmt.Errorf("reading receipt: %w", err) - } - - o, x := result.Unwrap(rcpt.Out()) - if x != nil { - return nil, nil, nil, fmt.Errorf("allocate failed: %s", fdm.Bind(x).Message) - } - - allocateOk, err := ipld.Rebind[blobreplicacap.AllocateOk](o, blobreplicacap.AllocateOkType(), types.Converters...) - if err != nil { - return nil, nil, nil, fmt.Errorf("rebinding receipt: %w", err) - } - - if allocateOk.Site.UcanAwait.Selector != blobreplicacap.AllocateSiteSelector { - return nil, nil, nil, fmt.Errorf("unexpected site selector: %s", allocateOk.Site.UcanAwait.Selector) - } - - br, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(rcpt.Blocks())) - if err != nil { - return nil, nil, nil, fmt.Errorf("creating block reader: %w", err) - } - transfer, err := invocation.NewInvocationView(allocateOk.Site.UcanAwait.Link, br) + allocOK, rcpt, err := ucan_client.Execute[*blobreplicacap.AllocateOK]( + ctx, + c.client, + c.logger, + inv, + execution.WithProofs(prfs...), + execution.WithInvocations(attestations...), + ) if err != nil { - return nil, nil, nil, fmt.Errorf("creating transfer invocation view: %w", err) + return nil, nil, nil, err } - - return &ReplicaAllocateResponse{ - Size: allocateOk.Size, - Site: allocateOk.Site, - Transfer: transfer, - }, inv, rcpt, nil + return allocOK, inv, rcpt, nil } diff --git a/pkg/piriclient/provider.go b/pkg/piriclient/provider.go index 5a966c3..e878c4d 100644 --- a/pkg/piriclient/provider.go +++ b/pkg/piriclient/provider.go @@ -3,7 +3,7 @@ package piriclient import ( "net/url" - "github.com/fil-forge/go-ucanto/ucan" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) diff --git a/pkg/provisioning/service.go b/pkg/provisioning/service.go index 523341a..b36be94 100644 --- a/pkg/provisioning/service.go +++ b/pkg/provisioning/service.go @@ -6,14 +6,13 @@ import ( "fmt" "slices" - "github.com/fil-forge/go-ucanto/core/ipld/codec/cbor" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store/consumer" "github.com/fil-forge/sprue/pkg/store/subscription" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/ipld/codec/dagcbor" + "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/ipfs/go-cid" - "github.com/ipld/go-ipld-prime/codec/dagcbor" - basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/multiformats/go-multihash" ) @@ -79,9 +78,9 @@ func (s *Service) ListServiceProviders(ctx context.Context, space SpaceDID) ([]S } // Provision provisions a service provider for a consumer (space) on behalf of -// a customer (account). It may return [customer.ErrCustomerNotFound] -// if the customer does not exist and [consumer.ErrConsumerExists] if the -// consumer is already provisioned for the provider. +// a customer (account). It may return [ErrProviderNotAllowed] if the requested +// provider is not on the list, and [consumer.ErrConsumerExists] if the consumer +// is already provisioned for the provider. func (s *Service) Provision(ctx context.Context, customer AccountDID, consumer SpaceDID, provider ServiceDID, cause cid.Cid) (SubscriptionID, error) { // Ensure the provider is allowed. if !slices.ContainsFunc(s.providers, func(p ServiceDID) bool { @@ -109,28 +108,14 @@ func (s *Service) Provision(ctx context.Context, customer AccountDID, consumer S } func NewSubscriptionID(consumer SpaceDID) (string, error) { - nb := basicnode.Prototype.Map.NewBuilder() - ma, err := nb.BeginMap(1) - if err != nil { - return "", err - } - na, err := ma.AssembleEntry("consumer") - if err != nil { - return "", err - } - if err := na.AssignString(consumer.String()); err != nil { - return "", err - } - if err := ma.Finish(); err != nil { - return "", err - } + model := datamodel.Map{"consumer": consumer.String()} var buf bytes.Buffer - if err := dagcbor.Encode(nb.Build(), &buf); err != nil { + if err := model.MarshalCBOR(&buf); err != nil { return "", err } c, err := cid.Prefix{ Version: 1, - Codec: cbor.Code, + Codec: dagcbor.Code, MhType: multihash.SHA2_256, MhLength: -1, }.Sum(buf.Bytes()) diff --git a/pkg/provisioning/service_test.go b/pkg/provisioning/service_test.go index b8bad05..423ea0b 100644 --- a/pkg/provisioning/service_test.go +++ b/pkg/provisioning/service_test.go @@ -4,14 +4,14 @@ import ( "context" "testing" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/lib/didmailto" "github.com/fil-forge/sprue/pkg/provisioning" "github.com/fil-forge/sprue/pkg/store/consumer" consumermemory "github.com/fil-forge/sprue/pkg/store/consumer/memory" "github.com/fil-forge/sprue/pkg/store/subscription" subscriptionmemory "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/ucantone/did" "github.com/stretchr/testify/require" ) diff --git a/pkg/routing/service.go b/pkg/routing/service.go index dcdb23a..f133475 100644 --- a/pkg/routing/service.go +++ b/pkg/routing/service.go @@ -6,13 +6,12 @@ import ( "net/url" "slices" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/lib/errors" + "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/digestutil" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) @@ -39,12 +38,6 @@ func WithExclusions(providers ...ucan.Principal) SelectOption { type StorageProviderInfo struct { ID ucan.Principal Endpoint url.URL - // FIXME: the client should authorize the upload service to blob/allocate and - // blob/accept in the blob/add invocation. The upload service should use that - // delegation to authorize allocate and accept calls to the storage provider. - // This removes the need for storage providers to grant love lived delegations - // to the upload service. We will fix this in UCAN 1.0. - Proof delegation.Delegation } type Service struct { @@ -69,14 +62,13 @@ func (s *Service) GetProviderInfo(ctx context.Context, provider ucan.Principal) return StorageProviderInfo{ ID: rec.Provider, Endpoint: rec.Endpoint, - Proof: rec.Proof, }, nil } // SelectStorageProvider selects a candidate for blob allocation from the // current list of available storage nodes. It may return // [ErrCandidateUnavailable] if no candidates are available. -func (s *Service) SelectStorageProvider(ctx context.Context, blob types.Blob, options ...SelectOption) (StorageProviderInfo, error) { +func (s *Service) SelectStorageProvider(ctx context.Context, blob blob.Blob, options ...SelectOption) (StorageProviderInfo, error) { cfg := &selectCfg{} for _, option := range options { option(cfg) @@ -113,14 +105,13 @@ func (s *Service) SelectStorageProvider(ctx context.Context, blob types.Blob, op return StorageProviderInfo{ ID: selected.Provider, Endpoint: selected.Endpoint, - Proof: selected.Proof, }, nil } // SelectReplicationProvider selects a candidate for blob allocation from the // current list of available storage nodes, excluding the primary node. It may // return [ErrCandidateUnavailable] if no candidates are available. -func (s *Service) SelectReplicationProvider(ctx context.Context, primary ucan.Principal, blob types.Blob, options ...SelectOption) (StorageProviderInfo, error) { +func (s *Service) SelectReplicationProvider(ctx context.Context, primary ucan.Principal, blob blob.Blob, options ...SelectOption) (StorageProviderInfo, error) { cfg := &selectCfg{} for _, option := range options { option(cfg) @@ -144,7 +135,6 @@ func (s *Service) SelectReplicationProvider(ctx context.Context, primary ucan.Pr return StorageProviderInfo{ ID: selected.Provider, Endpoint: selected.Endpoint, - Proof: selected.Proof, }, nil } diff --git a/pkg/routing/service_test.go b/pkg/routing/service_test.go index 255e664..fe89631 100644 --- a/pkg/routing/service_test.go +++ b/pkg/routing/service_test.go @@ -4,9 +4,7 @@ import ( "net/url" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/ucan" + "github.com/fil-forge/libforge/capabilities/blob" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/routing" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" @@ -20,15 +18,7 @@ func addProvider(t *testing.T, store *spmemory.Store, weight int, replicationWei ctx := t.Context() storageProvider := testutil.RandomSigner(t) endpoint := testutil.Must(url.Parse("https://piri.example.com"))(t) - proof, err := delegation.Delegate( - storageProvider, - testutil.WebService, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("blob/allocate", storageProvider.DID().String(), ucan.NoCaveats{}), - }, - ) - require.NoError(t, err) - err = store.Put(ctx, *endpoint, proof, weight, replicationWeight) + err := store.Put(ctx, storageProvider.DID(), *endpoint, weight, replicationWeight) require.NoError(t, err) rec, err := store.Get(ctx, storageProvider.DID()) require.NoError(t, err) @@ -63,7 +53,7 @@ func TestGetProviderInfo(t *testing.T) { func TestSelectStorageProvider(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() - blob := types.Blob{Size: 1024} + blob := blob.Blob{Size: 1024} t.Run("no providers", func(t *testing.T) { store := spmemory.New() @@ -136,7 +126,7 @@ func TestSelectStorageProvider(t *testing.T) { func TestSelectReplicationProvider(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() - blob := types.Blob{Size: 1024} + blob := blob.Blob{Size: 1024} t.Run("excludes primary", func(t *testing.T) { store := spmemory.New() diff --git a/pkg/service/handlers/access_authorize.go b/pkg/service/handlers/access_authorize.go deleted file mode 100644 index 76a1545..0000000 --- a/pkg/service/handlers/access_authorize.go +++ /dev/null @@ -1,199 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - "net/url" - "time" - - "go.uber.org/zap" - - "github.com/fil-forge/go-libstoracha/capabilities/access" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/internal/config" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/didmailto" - "github.com/fil-forge/sprue/pkg/lib/errors" - "github.com/fil-forge/sprue/pkg/lib/ucans" - "github.com/fil-forge/sprue/pkg/mailer" - "github.com/ipld/go-ipld-prime/datamodel" -) - -const ( - InvalidAuthorizationAccountErrorName = "InvalidAuthorizationAccount" - InvalidAuthorizationAudienceErrorName = "InvalidAuthorizationAudience" -) - -// Standard email flow - create confirmation delegation and send email -// We allow granting access within the next 15 minutes -const confirmationTTL = time.Minute * 15 - -var ( - ErrMissingAuthorizationAccount = errors.New(InvalidAuthorizationAccountErrorName, "missing authorization account DID") - ErrInvalidAuthorizationAccount = errors.New(InvalidAuthorizationAccountErrorName, "invalid authorization account DID") - ErrInvalidAuthorizationAudience = errors.New(InvalidAuthorizationAudienceErrorName, "invalid authorization audience DID") -) - -// WithAccessAuthorizeMethod registers the access/authorize handler. -func WithAccessAuthorizeMethod(serverCfg config.ServerConfig, id *identity.Identity, mailer mailer.Mailer, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - access.AuthorizeAbility, - server.Provide( - access.Authorize, - AccessAuthorizeHandler(serverCfg, id, mailer, logger), - ), - ) -} - -func AccessAuthorizeHandler(serverCfg config.ServerConfig, id *identity.Identity, mailer mailer.Mailer, logger *zap.Logger) server.HandlerFunc[access.AuthorizeCaveats, access.AuthorizeOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", access.AuthorizeAbility)) - return func(ctx context.Context, - cap ucan.Capability[access.AuthorizeCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[access.AuthorizeOk, failure.IPLDBuilderFailure], fx.Effects, error) { - // Get the account DID from the caveats - if cap.Nb().Iss == nil { - log.Warn("missing account") - return result.Error[access.AuthorizeOk, failure.IPLDBuilderFailure](ErrMissingAuthorizationAccount), nil, nil - } - account, err := didmailto.Parse(*cap.Nb().Iss) - if err != nil { - log.Warn("invalid account", zap.String("account", *cap.Nb().Iss)) - return result.Error[access.AuthorizeOk, failure.IPLDBuilderFailure]( - errors.New(InvalidAuthorizationAccountErrorName, "invalid authorization account DID: %v", err), - ), nil, nil - } - // we should be able to extract the email from the DID since we just - // parsed it as a did:mailto: - email, err := didmailto.Email(account) - if err != nil { - return nil, nil, fmt.Errorf("extracting email from mailto DID: %w", err) - } - audience, err := did.Parse(cap.With()) - if err != nil { - log.Warn("invalid audience", zap.String("audience", cap.With())) - return result.Error[access.AuthorizeOk, failure.IPLDBuilderFailure]( - errors.New(InvalidAuthorizationAudienceErrorName, "invalid authorization audience DID: %v", err), - ), nil, nil - } - - agent := inv.Issuer().DID() - log := log.With( - zap.Stringer("agent", agent), - zap.Stringer("account", account), - zap.Stringer("audience", audience), - ) - log.Debug("authorizing access") - - exp := int(time.Now().Add(confirmationTTL).Unix()) - - // We issue `access/confirm` invocation which will - // get embedded in the URL that we send to the user. When user clicks the - // link we'll get this delegation back in the `/validate-email` endpoint - // which will allow us to verify that it was the user who clicked the link - // and not some attacker impersonating the user. We will know that because - // the `with` field is our service DID and only private key holder is able - // to issue such delegation. - // - // We limit lifetime of this UCAN to 15 minutes to reduce the attack - // surface where an attacker could attempt concurrent authorization - // request in attempt confuse a user into clicking the wrong link. - confirmation, err := access.Confirm.Invoke( - id.Signer, - // audience same as issuer because this is a service invocation - // that will get handled by access/confirm handler - // but only if the receiver of this email wants it to be - id.Signer.DID(), - id.DID(), - // We link to the authorization request so that this attestation can - // not be used to authorize a different request. - access.ConfirmCaveats{ - // we copy request details and set the `aud` field to the agent DID - // that requested the authorization. - Iss: account, - Aud: audience, - Att: cap.Nb().Att, - // Link to the invocation that requested the authorization. - Cause: inv.Link(), - }, - delegation.WithExpiration(exp), - // we copy the facts in so that information can be passed - // from the invoker of this capability to the invoker of the confirm - // capability - we use this, for example, to let bsky.storage users - // specify that they should be redirected back to bsky.storage after - // completing the Stripe plan selection flow - delegation.WithFacts(toFactBuilders(inv.Facts())), - ) - if err != nil { - log.Error("failed to create confirmation delegation", zap.Error(err)) - return nil, nil, fmt.Errorf("creating confirmation delegation: %w", err) - } - - confirmationStr, err := ucans.FormatDelegations(confirmation) - if err != nil { - log.Error("failed to format confirmation", zap.Error(err)) - return nil, nil, fmt.Errorf("formatting confirmation: %w", err) - } - - pubUrlStr := serverCfg.PublicURL - if pubUrlStr == "" { - pubUrlStr = fmt.Sprintf("http://%s:%d", serverCfg.Host, serverCfg.Port) - } - validationURL, err := url.Parse(fmt.Sprintf("%s/validate-email?ucan=%s&mode=authorize", pubUrlStr, confirmationStr)) - if err != nil { - log.Error("failed to parse validation URL", zap.Error(err)) - return nil, nil, fmt.Errorf("parsing validation URL: %w", err) - } - - err = mailer.SendValidation(ctx, email, *validationURL) - if err != nil { - log.Error("failed to send validation email", zap.Error(err)) - return nil, nil, fmt.Errorf("sending validation email: %w", err) - } - - ok := result.Ok[access.AuthorizeOk, failure.IPLDBuilderFailure](access.AuthorizeOk{ - // link to this authorization request - Request: inv.Link(), - // let client know when the confirmation will expire - Expiration: exp, - }) - - // link to the authorization confirmation so it could be used to lookup - // the delegation by the authorization request. - join := fx.NewEffects(fx.WithJoin(fx.FromLink(confirmation.Root().Link()))) - - return ok, join, nil - } -} - -type anyFactBuilder struct { - fct ucan.Fact -} - -func (afb anyFactBuilder) ToIPLD() (map[string]datamodel.Node, error) { - nodeFacts := make(map[string]datamodel.Node, len(afb.fct)) - for k, v := range afb.fct { - if node, ok := v.(datamodel.Node); ok { - nodeFacts[k] = node - } else { - return nil, fmt.Errorf("fact %q is not an IPLD node", k) - } - } - return nodeFacts, nil -} - -func toFactBuilders(fcts []ucan.Fact) []ucan.FactBuilder { - builders := make([]ucan.FactBuilder, len(fcts)) - for i, fct := range fcts { - builders[i] = anyFactBuilder{fct: fct} - } - return builders -} diff --git a/pkg/service/handlers/access_authorize_test.go b/pkg/service/handlers/access_authorize_test.go deleted file mode 100644 index ba4183d..0000000 --- a/pkg/service/handlers/access_authorize_test.go +++ /dev/null @@ -1,220 +0,0 @@ -package handlers - -import ( - "context" - "errors" - "net/url" - "testing" - - "github.com/fil-forge/go-libstoracha/capabilities/access" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/internal/config" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zaptest" -) - -type mockMailer struct { - lastTo string - lastURL url.URL - err error -} - -func (m *mockMailer) SendValidation(ctx context.Context, to string, validationURL url.URL) error { - m.lastTo = to - m.lastURL = validationURL - return m.err -} - -func newTestIdentity(t *testing.T) *identity.Identity { - t.Helper() - id, err := identity.New("") - require.NoError(t, err) - return id -} - -func TestAccessAuthorizeHandler(t *testing.T) { - logger := zaptest.NewLogger(t) - serverCfg := config.ServerConfig{ - Host: "localhost", - Port: 8080, - PublicURL: "http://localhost:8080", - } - - t.Run("success", func(t *testing.T) { - id := newTestIdentity(t) - m := &mockMailer{} - handler := AccessAuthorizeHandler(serverCfg, id, m, logger) - - iss := "did:mailto:example.com:alice" - cap := ucan.NewCapability( - access.AuthorizeAbility, - id.DID(), - access.AuthorizeCaveats{ - Iss: &iss, - Att: []access.CapabilityRequest{{Can: "*"}}, - }, - ) - - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) - require.NoError(t, err) - - res, effects, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - require.NotNil(t, effects) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.Equal(t, inv.Link(), ok.Request) - require.NotZero(t, ok.Expiration) - - require.Equal(t, "alice@example.com", m.lastTo) - require.Contains(t, m.lastURL.String(), "/validate-email") - require.Contains(t, m.lastURL.Query().Get("mode"), "authorize") - }) - - t.Run("missing account", func(t *testing.T) { - id := newTestIdentity(t) - m := &mockMailer{} - handler := AccessAuthorizeHandler(serverCfg, id, m, logger) - - cap := ucan.NewCapability( - access.AuthorizeAbility, - id.DID(), - access.AuthorizeCaveats{ - Iss: nil, - Att: []access.CapabilityRequest{{Can: "*"}}, - }, - ) - - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - }) - - t.Run("invalid account DID", func(t *testing.T) { - id := newTestIdentity(t) - m := &mockMailer{} - handler := AccessAuthorizeHandler(serverCfg, id, m, logger) - - iss := "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" - cap := ucan.NewCapability( - access.AuthorizeAbility, - id.DID(), - access.AuthorizeCaveats{ - Iss: &iss, - Att: []access.CapabilityRequest{{Can: "*"}}, - }, - ) - - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - }) - - t.Run("invalid audience DID", func(t *testing.T) { - id := newTestIdentity(t) - m := &mockMailer{} - handler := AccessAuthorizeHandler(serverCfg, id, m, logger) - - iss := "did:mailto:example.com:alice" - cap := ucan.NewCapability( - access.AuthorizeAbility, - "not-a-did", - access.AuthorizeCaveats{ - Iss: &iss, - Att: []access.CapabilityRequest{{Can: "*"}}, - }, - ) - - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - }) - - t.Run("mailer error", func(t *testing.T) { - id := newTestIdentity(t) - m := &mockMailer{err: errors.New("smtp failure")} - handler := AccessAuthorizeHandler(serverCfg, id, m, logger) - - iss := "did:mailto:example.com:alice" - cap := ucan.NewCapability( - access.AuthorizeAbility, - id.DID(), - access.AuthorizeCaveats{ - Iss: &iss, - Att: []access.CapabilityRequest{{Can: "*"}}, - }, - ) - - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) - require.NoError(t, err) - - _, _, err = handler(context.Background(), cap, inv, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "sending validation email") - }) - - t.Run("public URL fallback", func(t *testing.T) { - id := newTestIdentity(t) - m := &mockMailer{} - cfgNoPublicURL := config.ServerConfig{ - Host: "myhost", - Port: 9090, - } - handler := AccessAuthorizeHandler(cfgNoPublicURL, id, m, logger) - - iss := "did:mailto:example.com:bob" - cap := ucan.NewCapability( - access.AuthorizeAbility, - id.DID(), - access.AuthorizeCaveats{ - Iss: &iss, - Att: []access.CapabilityRequest{{Can: "*"}}, - }, - ) - - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) - require.NoError(t, err) - - _, _, err = handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - require.Contains(t, m.lastURL.String(), "http://myhost:9090/validate-email") - }) -} diff --git a/pkg/service/handlers/access_claim.go b/pkg/service/handlers/access_claim.go index a1e6dda..9083aa9 100644 --- a/pkg/service/handlers/access_claim.go +++ b/pkg/service/handlers/access_claim.go @@ -1,220 +1,70 @@ package handlers import ( - "context" "fmt" - "github.com/fil-forge/go-libstoracha/capabilities/access" - ucancap "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/principal/absentee" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/go-ucanto/validator" + "github.com/fil-forge/libforge/capabilities/access" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/didmailto" - "github.com/fil-forge/sprue/pkg/lib/errors" - "github.com/fil-forge/sprue/pkg/lib/ucans" - "github.com/fil-forge/sprue/pkg/store" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" "github.com/ipfs/go-cid" "go.uber.org/zap" ) -const InvalidClaimAudienceErrorName = "InvalidClaimAudience" - -var ErrInvalidClaimAudience = errors.New(InvalidClaimAudienceErrorName, "invalid claim audience DID") - -// WithAccessClaimMethod registers the access/claim handler. -func WithAccessClaimMethod(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - access.ClaimAbility, - server.Provide( - access.Claim, - AccessClaimHandler(id, delegationStore, logger), - ), - ) -} - -func AccessClaimHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) server.HandlerFunc[access.ClaimCaveats, access.ClaimOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", access.ClaimAbility)) - return func(ctx context.Context, - cap ucan.Capability[access.ClaimCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[access.ClaimOk, failure.IPLDBuilderFailure], fx.Effects, error) { - agent := inv.Issuer().DID() - audience, err := did.Parse(cap.With()) - if err != nil { - log.Warn("invalid audience", zap.String("audience", cap.With())) - return result.Error[access.ClaimOk, failure.IPLDBuilderFailure](ErrInvalidClaimAudience), nil, nil - } - - log := log.With( - zap.Stringer("agent", agent), - zap.Stringer("audience", audience), - ) - log.Debug("claiming delegations") - - dlgs := map[cid.Cid]delegation.Delegation{} - var cursor *string - for { - var opts []delegation_store.ListByAudienceOption - if cursor != nil { - opts = append(opts, delegation_store.WithListByAudienceCursor(*cursor)) - } - page, err := delegationStore.ListByAudience(ctx, audience, opts...) - if err != nil { - return nil, nil, fmt.Errorf("listing delegations: %w", err) - } - for _, dlg := range page.Results { - root, err := ipldutil.ToCID(dlg.Link()) - if err != nil { - log.Warn("invalid delegation CID", zap.String("root", dlg.Link().String()), zap.Error(err)) - continue - } - dlgs[root] = dlg - } - if page.Cursor == nil { - break - } - cursor = page.Cursor - } - - refreshedDlgs := map[cid.Cid]delegation.Delegation{} - // Find any attested ucan:* delegations and replace them with fresh ones. - for root, dlg := range dlgs { - if len(dlg.Capabilities()) == 0 { - log.Warn("delegation with no capabilities", zap.String("root", dlg.Link().String())) - refreshedDlgs[root] = dlg - continue - } - - // Ignore delegations that aren't attestations, and ours. - cap := dlg.Capabilities()[0] - - var err error - match, err := ucancap.Attest.Match(validator.NewSource(cap, dlg)) - if err != nil { - refreshedDlgs[root] = dlg - continue - } - if match.Value().With() != id.DID() { - refreshedDlgs[root] = dlg - continue - } - - // Ignore invalid attestations. - if valid, _ := ucan.VerifySignature(dlg.Data(), id.Signer.Verifier()); !valid { - refreshedDlgs[root] = dlg - continue - } - if ucan.IsTooEarly(dlg) || ucan.IsExpired(dlg) { - refreshedDlgs[root] = dlg - continue - } - - attestedDlgRoot, err := ipldutil.ToCID(match.Value().Nb().Proof) - if err != nil { - log.Warn("invalid proof CID in attestation", zap.String("proof", match.Value().Nb().Proof.String()), zap.Error(err)) - refreshedDlgs[root] = dlg - continue - } - - // Ignore attestations of delegations we don't have. - attestedDlg, ok := dlgs[attestedDlgRoot] - if !ok { - refreshedDlgs[root] = dlg - continue - } - - // Create new session proofs for the attested delegation. - sessionPrfs, err := createSessionProofsForLogin( - ctx, - id.Signer, - delegationStore, - attestedDlg, +func NewAccessClaimHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", access.ClaimCommand)) + return Handler{ + Capability: access.Claim, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*access.ClaimArguments], + res *bindexec.Response[*access.ClaimOK], + ) error { + agent := req.Invocation().Issuer().DID() + audience := req.Invocation().Subject().DID() + + log := log.With( + zap.Stringer("agent", agent), + zap.Stringer("audience", audience), ) - if err != nil { - log.Error("failed to create session proofs for login", zap.Error(err)) - return nil, nil, fmt.Errorf("creating session proofs for login: %w", err) - } - for _, d := range sessionPrfs { - root, err := ipldutil.ToCID(d.Link()) + log.Debug("claiming delegations") + + links := []cid.Cid{} + delegations := []ucan.Delegation{} + attestations := []ucan.Invocation{} + var cursor *string + for { + var opts []delegation_store.ListByAudienceOption + if cursor != nil { + opts = append(opts, delegation_store.WithListByAudienceCursor(*cursor)) + } + page, err := delegationStore.ListByAudience(req.Context(), audience, opts...) if err != nil { - return nil, nil, fmt.Errorf("getting CID of session proof delegation: %w", err) + return fmt.Errorf("listing delegations: %w", err) } - refreshedDlgs[root] = d - } - } - - mdl := access.DelegationsModel{Values: map[string][]byte{}} - for _, d := range dlgs { - k := d.Link().String() - v, err := ucans.ArchiveDelegations(d) - if err != nil { - log.Error("failed to archive delegation", zap.Error(err)) - return nil, nil, fmt.Errorf("archiving delegation: %w", err) + for _, token := range page.Results { + if dlg, ok := token.(ucan.Delegation); ok { + delegations = append(delegations, dlg) + links = append(links, dlg.Link()) + } else if inv, ok := token.(ucan.Invocation); ok { + attestations = append(attestations, inv) + } else { + log.Warn("unexpected token type in delegation store", zap.Stringer("link", token.Link())) + } + } + if page.Cursor == nil { + break + } + cursor = page.Cursor } - mdl.Keys = append(mdl.Keys, k) - mdl.Values[k] = v - } - - return result.Ok[access.ClaimOk, failure.IPLDBuilderFailure](access.ClaimOk{ - Delegations: mdl, - }), nil, nil - } -} -func createSessionProofsForLogin( - ctx context.Context, - service ucan.Signer, - delegationStore delegation_store.Store, - loginDlg delegation.Delegation, -) ([]delegation.Delegation, error) { - // These should always be accounts (did:mailto:), but if one's not, skip it. - account, err := didmailto.Parse(loginDlg.Issuer().DID().String()) - if err != nil { - return []delegation.Delegation{}, nil - } - - accountDlgs, err := store.Collect(ctx, func(ctx context.Context, options store.PaginationConfig) (store.Page[delegation.Delegation], error) { - var opts []delegation_store.ListByAudienceOption - if options.Cursor != nil { - opts = append(opts, delegation_store.WithListByAudienceCursor(*options.Cursor)) - } - return delegationStore.ListByAudience(ctx, account, opts...) - }) - if err != nil { - return nil, fmt.Errorf("collecting delegations for account: %w", err) - } - - caps := make([]ucan.Capability[ucan.NoCaveats], 0, len(loginDlg.Capabilities())) - for _, cap := range loginDlg.Capabilities() { - caps = append(caps, ucan.NewCapability(cap.Can(), cap.With(), ucan.NoCaveats{})) - } + res.SetMetadata(container.New( + container.WithDelegations(delegations...), + container.WithInvocations(attestations...), + )) - dlg, attestation, err := createSessionProofs( - service, - absentee.From(account), - loginDlg.Audience(), - toFactBuilders(loginDlg.Facts()), - caps, - // We include all the delegations to the account so that the agent will - // have delegation chains to all the delegated resources. - // We should actually filter out only delegations that support delegated - // capabilities, but for now we just include all of them since we only - // implement sudo access anyway. - accountDlgs, - ) - if err != nil { - return nil, fmt.Errorf("creating session proofs: %w", err) + return res.SetSuccess(&access.ClaimOK{Delegations: links}) + }), } - return []delegation.Delegation{dlg, attestation}, nil } diff --git a/pkg/service/handlers/access_claim_test.go b/pkg/service/handlers/access_claim_test.go index 9e90154..25c913e 100644 --- a/pkg/service/handlers/access_claim_test.go +++ b/pkg/service/handlers/access_claim_test.go @@ -1,17 +1,18 @@ package handlers import ( - "context" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/access" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/ucan" + "github.com/fil-forge/libforge/capabilities/access" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/identity" dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -19,183 +20,156 @@ import ( func TestAccessClaimHandler(t *testing.T) { logger := zaptest.NewLogger(t) - t.Run("invalid audience DID", func(t *testing.T) { - id := newTestIdentity(t) - store := dlgmemory.New() - handler := AccessClaimHandler(id, store, logger) - - cap := ucan.NewCapability( - access.ClaimAbility, - "not-a-did", - access.ClaimCaveats{}, - ) - - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - }) - t.Run("no delegations", func(t *testing.T) { id := newTestIdentity(t) store := dlgmemory.New() - handler := AccessClaimHandler(id, store, logger) + handler := NewAccessClaimHandler(id, store, logger) - agent, err := identity.New("") - require.NoError(t, err) + agent := testutil.RandomSigner(t) - cap := ucan.NewCapability( - access.ClaimAbility, - agent.DID(), - access.ClaimCaveats{}, + args := access.ClaimArguments{} + inv, err := access.Claim.Invoke( + agent, + agent, + &args, + invocation.WithAudience(id.Signer), ) + require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.Empty(t, ok.Delegations.Keys) + require.NotNil(t, o) + + ok := access.ClaimOK{} + err = datamodel.Rebind(datamodel.NewAny(o), &ok) + require.NoError(t, err) + require.Empty(t, ok.Delegations) }) t.Run("returns stored delegations", func(t *testing.T) { id := newTestIdentity(t) store := dlgmemory.New() - handler := AccessClaimHandler(id, store, logger) + handler := NewAccessClaimHandler(id, store, logger) - agent, err := identity.New("") - require.NoError(t, err) + agent := testutil.RandomSigner(t) - // Create a delegation to the agent - dlg, err := delegation.Delegate( - testutil.Alice, - agent.Signer, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("test/thing", testutil.Alice.DID().String(), ucan.NoCaveats{}), - }, - ) + dlg, err := delegation.Delegate(testutil.Alice, agent, testutil.Alice, "/test/thing") require.NoError(t, err) - cause := testutil.RandomCID(t) - err = store.PutMany(context.Background(), []delegation.Delegation{dlg}, cause) + err = store.PutMany(t.Context(), []ucan.Token{dlg}, testutil.RandomCID(t)) require.NoError(t, err) - cap := ucan.NewCapability( - access.ClaimAbility, - agent.DID(), - access.ClaimCaveats{}, + args := access.ClaimArguments{} + inv, err := access.Claim.Invoke( + agent, + agent, + &args, + invocation.WithAudience(id.Signer), ) + require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.Len(t, ok.Delegations.Keys, 1) - require.Equal(t, dlg.Link().String(), ok.Delegations.Keys[0]) - require.NotEmpty(t, ok.Delegations.Values[ok.Delegations.Keys[0]]) + + ok := access.ClaimOK{} + err = datamodel.Rebind(datamodel.NewAny(o), &ok) + require.NoError(t, err) + require.Equal(t, []cid.Cid{dlg.Link()}, ok.Delegations) }) t.Run("returns multiple delegations", func(t *testing.T) { id := newTestIdentity(t) store := dlgmemory.New() - handler := AccessClaimHandler(id, store, logger) + handler := NewAccessClaimHandler(id, store, logger) - agent, err := identity.New("") - require.NoError(t, err) + agent := testutil.RandomSigner(t) - dlg1, err := delegation.Delegate( - testutil.Alice, - agent.Signer, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("test/one", testutil.Alice.DID().String(), ucan.NoCaveats{}), - }, - ) + dlg1, err := delegation.Delegate(testutil.Alice, agent, testutil.Alice, "/test/one") require.NoError(t, err) - dlg2, err := delegation.Delegate( - testutil.Bob, - agent.Signer, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("test/two", testutil.Bob.DID().String(), ucan.NoCaveats{}), - }, - ) + dlg2, err := delegation.Delegate(testutil.Bob, agent, testutil.Bob, "/test/two") require.NoError(t, err) - cause := testutil.RandomCID(t) - err = store.PutMany(context.Background(), []delegation.Delegation{dlg1, dlg2}, cause) + err = store.PutMany(t.Context(), []ucan.Token{dlg1, dlg2}, testutil.RandomCID(t)) require.NoError(t, err) - cap := ucan.NewCapability( - access.ClaimAbility, - agent.DID(), - access.ClaimCaveats{}, + args := access.ClaimArguments{} + inv, err := access.Claim.Invoke( + agent, + agent, + &args, + invocation.WithAudience(id.Signer), ) + require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.Len(t, ok.Delegations.Keys, 2) + + ok := access.ClaimOK{} + err = datamodel.Rebind(datamodel.NewAny(o), &ok) + require.NoError(t, err) + require.Len(t, ok.Delegations, 2) + require.ElementsMatch(t, []cid.Cid{dlg1.Link(), dlg2.Link()}, ok.Delegations) }) t.Run("does not return delegations for other audiences", func(t *testing.T) { id := newTestIdentity(t) store := dlgmemory.New() - handler := AccessClaimHandler(id, store, logger) - - agent, err := identity.New("") - require.NoError(t, err) + handler := NewAccessClaimHandler(id, store, logger) - otherAgent, err := identity.New("") - require.NoError(t, err) + agent := testutil.RandomSigner(t) + otherAgent := testutil.RandomSigner(t) - // Delegate to a different agent - dlg, err := delegation.Delegate( - testutil.Alice, - otherAgent.Signer, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("test/thing", testutil.Alice.DID().String(), ucan.NoCaveats{}), - }, - ) + // Delegation is for otherAgent, not agent. + dlg, err := delegation.Delegate(testutil.Alice, otherAgent, testutil.Alice, "/test/thing") require.NoError(t, err) - cause := testutil.RandomCID(t) - err = store.PutMany(context.Background(), []delegation.Delegation{dlg}, cause) + err = store.PutMany(t.Context(), []ucan.Token{dlg}, testutil.RandomCID(t)) require.NoError(t, err) - // Claim as the original agent — should get nothing - cap := ucan.NewCapability( - access.ClaimAbility, - agent.DID(), - access.ClaimCaveats{}, + args := access.ClaimArguments{} + inv, err := access.Claim.Invoke( + agent, + agent, + &args, + invocation.WithAudience(id.Signer), ) + require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.Empty(t, ok.Delegations.Keys) + + ok := access.ClaimOK{} + err = datamodel.Rebind(datamodel.NewAny(o), &ok) + require.NoError(t, err) + require.Empty(t, ok.Delegations) }) } diff --git a/pkg/service/handlers/access_confirm.go b/pkg/service/handlers/access_confirm.go index 3256c79..269c2c6 100644 --- a/pkg/service/handlers/access_confirm.go +++ b/pkg/service/handlers/access_confirm.go @@ -1,192 +1,158 @@ package handlers import ( - "context" "fmt" - "go.uber.org/zap" - - "github.com/fil-forge/go-libstoracha/capabilities/access" - ucan_caps "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/principal/absentee" - "github.com/fil-forge/go-ucanto/principal/ed25519/verifier" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" + "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/capabilities/ucan/attest" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/errors" - "github.com/fil-forge/sprue/pkg/lib/ucans" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal/absentee" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" - "github.com/ipld/go-ipld-prime/node/basicnode" + "go.uber.org/zap" ) -const InvalidAccessConfirmDelegationErrorName = "InvalidAccessConfirmDelegation" - -// WithAccessConfirmMethod registers the access/confirm handler. -func WithAccessConfirmMethod(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - access.ConfirmAbility, - server.Provide( - access.Confirm, - AccessConfirmHandler(id, delegationStore, logger), - ), - ) -} - -func AccessConfirmHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) server.HandlerFunc[access.ConfirmCaveats, access.ConfirmOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", access.ConfirmAbility)) - return func(ctx context.Context, - cap ucan.Capability[access.ConfirmCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[access.ConfirmOk, failure.IPLDBuilderFailure], fx.Effects, error) { - if cap.With() != id.DID() { - log.Warn("not a valid delegation", zap.String("resource", cap.With())) - return result.Error[access.ConfirmOk, failure.IPLDBuilderFailure]( - errors.New(InvalidAccessConfirmDelegationErrorName, "not a valid access/confirm delegation"), - ), nil, nil - } +const InvalidAccessConfirmInvocationErrorName = "InvalidAccessConfirmInvocation" + +func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", access.ConfirmCommand)) + return Handler{ + Capability: access.Confirm, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*access.ConfirmArguments], + res *bindexec.Response[*access.ConfirmOK], + ) error { + args := req.Task().BindArguments() + if req.Invocation().Subject().DID() != id.Signer.DID() { + log.Warn("not a valid invocation", zap.Stringer("subject", req.Invocation().Subject().DID())) + return res.SetFailure(errors.New(InvalidAccessConfirmInvocationErrorName, "not a valid access/confirm delegation")) + } - // Create a absentee signer for the account that authorized the delegation - account := absentee.From(cap.Nb().Iss) - agent, err := verifier.Parse(cap.Nb().Aud.String()) - if err != nil { - log.Warn("invalid audience", zap.Stringer("audience", cap.Nb().Aud)) - return result.Error[access.ConfirmOk, failure.IPLDBuilderFailure]( - errors.New(InvalidAccessConfirmDelegationErrorName, "invalid agent DID in delegation"), - ), nil, nil - } + accountDID, err := didmailto.Parse(args.Issuer.DID().String()) + if err != nil { + log.Warn("invalid issuer DID", zap.Stringer("issuer", args.Issuer.DID()), zap.Error(err)) + return res.SetFailure(errors.New(InvalidAccessConfirmInvocationErrorName, "invalid issuer DID in delegation")) + } - abilities := []ucan.Ability{} - for _, att := range cap.Nb().Att { - abilities = append(abilities, att.Can) - } + // Create a absentee signer for the account that authorized the delegation + account := absentee.From(accountDID) + agent := args.Audience - log := log.With( - zap.Stringer("agent", agent.DID()), - zap.Stringer("account", account.DID()), - ) - log.Debug("confirming access", zap.Strings("abilities", abilities)) - - // In the future we should instead render a page and allow a user to select - // which delegations they wish to re-delegate. Right now we just re-delegate - // everything that was requested for all of the resources. - var capabilities []ucan.Capability[ucan.NoCaveats] - for _, att := range cap.Nb().Att { - capabilities = append(capabilities, ucan.NewCapability(att.Can, "ucan:*", ucan.NoCaveats{})) - } + cmds := make([]string, 0, len(args.Attenuations)) + for _, att := range args.Attenuations { + cmds = append(cmds, att.Command.String()) + } - // Create session proofs, but containing no Space proofs. We'll store these, - // and generate the Space proofs on access/claim. - dlg, attestation, err := createSessionProofs( - id.Signer, - account, - agent, - []ucan.FactBuilder{ - accessConfirmFact{ - accessRequest: cap.Nb().Cause, - accessConfirm: inv.Link(), + log := log.With( + zap.Stringer("agent", agent.DID()), + zap.Stringer("account", account.DID()), + zap.Stringer("cause", args.Cause), + zap.Strings("commands", cmds), + ) + log.Debug("confirming access") + + // Create session proofs, but containing no Space proofs. We'll store these, + // and generate the Space proofs on access/claim. + delegations, attestations, err := createSessionProofs( + id.Signer, + account, + agent, + args.Attenuations, + datamodel.Map{ + access.RequestMetaKey: args.Cause, + access.ConfirmMetaKey: req.Invocation().Task().Link(), }, - }, - capabilities, - []delegation.Delegation{}, - ) - if err != nil { - return nil, nil, fmt.Errorf("creating session proofs: %w", err) - } - - dlgs := []delegation.Delegation{dlg, attestation} + ) + if err != nil { + return fmt.Errorf("creating session proofs: %w", err) + } - // Store the delegations so that they can be pulled during access/claim. - // Since there is no invocation that contains these delegations, don't pass - // a `cause` parameter. - // TODO: we should invoke access/delegate here rather than interacting with - // the delegations storage system directly. - err = delegationStore.PutMany(ctx, dlgs, cid.Undef) - if err != nil { - log.Error("failed to store delegations", zap.Error(err)) - return nil, nil, fmt.Errorf("storing delegations: %w", err) - } + links := make([]cid.Cid, 0, len(delegations)) + tokens := make([]ucan.Token, 0, len(delegations)+len(attestations)) + for _, d := range delegations { + tokens = append(tokens, d) + links = append(links, d.Link()) + } + for _, a := range attestations { + tokens = append(tokens, a) + } - mdl := access.DelegationsModel{Values: map[string][]byte{}} - for _, d := range dlgs { - k := d.Link().String() - v, err := ucans.ArchiveDelegations(d) + // Store the delegations so that they can be pulled during /access/claim. + // Since there is no invocation that contains these delegations, don't pass + // a `cause` parameter. + // TODO: we should invoke /access/delegate here rather than interacting + // with the delegations storage system directly. + err = delegationStore.PutMany(req.Context(), tokens, cid.Undef) if err != nil { - log.Error("failed to archive delegation", zap.Error(err)) - return nil, nil, fmt.Errorf("archiving delegation: %w", err) + log.Error("failed to store delegations", zap.Error(err)) + return fmt.Errorf("storing delegations: %w", err) } - mdl.Keys = append(mdl.Keys, k) - mdl.Values[k] = v - } - return result.Ok[access.ConfirmOk, failure.IPLDBuilderFailure](access.ConfirmOk{ - Delegations: mdl, - }), nil, nil - } -} - -type accessConfirmFact struct { - accessRequest ipld.Link - accessConfirm ipld.Link -} + // Include the delegations and attestations in the response metadata. + res.SetMetadata(container.New( + container.WithDelegations(delegations...), + container.WithInvocations(attestations...), + )) -func (f accessConfirmFact) ToIPLD() (map[string]ipld.Node, error) { - return map[string]ipld.Node{ - "access/request": basicnode.NewLink(f.accessRequest), - "access/confirm": basicnode.NewLink(f.accessConfirm), - }, nil + return res.SetSuccess(&access.ConfirmOK{Delegations: links}) + }), + } } -// createSessionProofs creates a delegation from the account to the agent, and -// an attestation from the service to the agent referencing that delegation. -func createSessionProofs[C ucan.CaveatBuilder]( +// createSessionProofs creates delegations from the account to the agent, and +// attestations from the service to the agent referencing those delegations. +func createSessionProofs( service ucan.Signer, - account ucan.Signer, + account absentee.Signer, agent ucan.Principal, - facts []ucan.FactBuilder, - capabilities []ucan.Capability[C], - delegationProofs []delegation.Delegation, -) (delegation.Delegation, delegation.Delegation, error) { - var proofs []delegation.Proof - for _, d := range delegationProofs { - proofs = append(proofs, delegation.FromDelegation(d)) - } - - // create an delegation on behalf of the account with an absent signature. - dlg, err := delegation.Delegate( - account, - agent, - capabilities, - delegation.WithProof(proofs...), - delegation.WithFacts(facts), - // default to Infinity is reasonable here because - // account consented to this. - delegation.WithNoExpiration(), - ) - if err != nil { - return nil, nil, fmt.Errorf("creating delegation: %w", err) - } + attenuations []access.CapabilityRequest, + meta ipld.Map, +) ([]ucan.Delegation, []ucan.Invocation, error) { + delegations := make([]ucan.Delegation, 0, len(attenuations)) + attestations := make([]ucan.Invocation, 0, len(attenuations)) + + for _, req := range attenuations { + dlg, err := delegation.Delegate( + account, + agent, + // TODO: optionally set subject in capability request + // no subject (powerline) will apply to all spaces present and future + nil, + req.Command, + delegation.WithMetadata(meta), + // default to Infinity is reasonable here because + // account consented to this. + delegation.WithNoExpiration(), + ) + if err != nil { + return nil, nil, fmt.Errorf("creating delegation: %w", err) + } + delegations = append(delegations, dlg) - attestation, err := ucan_caps.Attest.Delegate( - service, - agent, - service.DID().String(), - ucan_caps.AttestCaveats{ - Proof: dlg.Link(), - }, - delegation.WithFacts(facts), - delegation.WithNoExpiration(), - ) - if err != nil { - return nil, nil, fmt.Errorf("creating attestation: %w", err) + attestation, err := attest.Proof.Invoke( + service, + service, + &attest.ProofArguments{ + Proof: dlg.Link(), + }, + invocation.WithAudience(agent), + invocation.WithMetadata(meta), + invocation.WithNoExpiration(), + ) + if err != nil { + return nil, nil, fmt.Errorf("creating attestation: %w", err) + } + attestations = append(attestations, attestation) } - return dlg, attestation, nil + return delegations, attestations, nil } diff --git a/pkg/service/handlers/access_confirm_test.go b/pkg/service/handlers/access_confirm_test.go index f258600..9b457a2 100644 --- a/pkg/service/handlers/access_confirm_test.go +++ b/pkg/service/handlers/access_confirm_test.go @@ -1,18 +1,18 @@ package handlers import ( - "context" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/access" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/ucan" + "github.com/fil-forge/libforge/capabilities/access" + adm "github.com/fil-forge/libforge/capabilities/access/datamodel" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/didmailto" dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -20,115 +20,135 @@ import ( func TestAccessConfirmHandler(t *testing.T) { logger := zaptest.NewLogger(t) - t.Run("wrong resource DID", func(t *testing.T) { + t.Run("wrong subject", func(t *testing.T) { id := newTestIdentity(t) store := dlgmemory.New() - handler := AccessConfirmHandler(id, store, logger) - - agent, err := identity.New("") - require.NoError(t, err) - - account := mustMailtoDID(t, "alice@example.com") - causeCID := testutil.RandomCID(t) - - cap := ucan.NewCapability( - access.ConfirmAbility, - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", - access.ConfirmCaveats{ - Iss: account, - Aud: agent.Signer.DID(), - Att: []access.CapabilityRequest{{Can: "*"}}, - Cause: testutil.Must(invocation.Invoke(agent.Signer, id.Signer, ucan.NewCapability("test/thing", id.DID(), ucan.NoCaveats{})))(t).Link(), + handler := NewAccessConfirmHandler(id, store, logger) + + account := testutil.Must(didmailto.New("alice@example.com"))(t) + agent := testutil.RandomSigner(t) + notService := testutil.RandomSigner(t) + + args := access.ConfirmArguments{ + Cause: testutil.RandomCID(t), + Issuer: account, + Audience: agent.DID(), + Attenuations: []adm.CapabilityRequestModel{ + {Command: "/"}, }, + } + + // Subject is not id.Signer — handler should reject. + inv, err := access.Confirm.Invoke( + id.Signer, + notService, + &args, + invocation.WithAudience(id.Signer), ) - _ = causeCID + require.NoError(t, err) - inv, err := invocation.Invoke(id.Signer, id.Signer, cap) + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.NotNil(t, fail) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, InvalidAccessConfirmInvocationErrorName, model.Name()) }) - t.Run("invalid agent DID", func(t *testing.T) { + t.Run("invalid issuer DID", func(t *testing.T) { id := newTestIdentity(t) store := dlgmemory.New() - handler := AccessConfirmHandler(id, store, logger) + handler := NewAccessConfirmHandler(id, store, logger) + + // A did:key (not a did:mailto) — didmailto.Parse will reject it. + nonMailto := testutil.RandomSigner(t) + agent := testutil.RandomSigner(t) + + args := access.ConfirmArguments{ + Cause: testutil.RandomCID(t), + Issuer: nonMailto.DID(), + Audience: agent.DID(), + Attenuations: []adm.CapabilityRequestModel{ + {Command: "/"}, + }, + } - account := mustMailtoDID(t, "alice@example.com") - badAud, err := did.Parse("did:mailto:example.com:alice") + inv, err := access.Confirm.Invoke( + id.Signer, + id.Signer, + &args, + invocation.WithAudience(id.Signer), + ) require.NoError(t, err) - agent, err := identity.New("") + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - causeInv, err := invocation.Invoke(agent.Signer, id.Signer, ucan.NewCapability("test/thing", id.DID(), ucan.NoCaveats{})) + err = handler.Handler(req, res) require.NoError(t, err) - cap := ucan.NewCapability( - access.ConfirmAbility, - id.DID(), - access.ConfirmCaveats{ - Iss: account, - Aud: badAud, - Att: []access.CapabilityRequest{{Can: "*"}}, - Cause: causeInv.Link(), - }, - ) - - inv, err := invocation.Invoke(id.Signer, id.Signer, cap) - require.NoError(t, err) + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) - res, _, err := handler(context.Background(), cap, inv, nil) + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) + require.Equal(t, InvalidAccessConfirmInvocationErrorName, model.Name()) }) t.Run("success", func(t *testing.T) { id := newTestIdentity(t) store := dlgmemory.New() - handler := AccessConfirmHandler(id, store, logger) - - agent, err := identity.New("") - require.NoError(t, err) - - account := mustMailtoDID(t, "bob@example.com") + handler := NewAccessConfirmHandler(id, store, logger) - causeInv, err := invocation.Invoke(agent.Signer, id.Signer, ucan.NewCapability("test/thing", id.DID(), ucan.NoCaveats{})) - require.NoError(t, err) + account := testutil.Must(didmailto.New("bob@example.com"))(t) + agent := testutil.RandomSigner(t) - cap := ucan.NewCapability( - access.ConfirmAbility, - id.DID(), - access.ConfirmCaveats{ - Iss: account, - Aud: agent.Signer.DID(), - Att: []access.CapabilityRequest{{Can: "*"}}, - Cause: causeInv.Link(), + args := access.ConfirmArguments{ + Cause: testutil.RandomCID(t), + Issuer: account, + Audience: agent.DID(), + Attenuations: []adm.CapabilityRequestModel{ + {Command: "/"}, }, + } + + inv, err := access.Confirm.Invoke( + id.Signer, + id.Signer, + &args, + invocation.WithAudience(id.Signer), ) + require.NoError(t, err) - inv, err := invocation.Invoke(id.Signer, id.Signer, cap) + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - // Should return 2 delegations: the account->agent delegation and the service attestation - require.Len(t, ok.Delegations.Keys, 2) - for _, k := range ok.Delegations.Keys { - require.NotEmpty(t, ok.Delegations.Values[k]) - } + require.NotNil(t, o) - // Verify delegations were stored - page, err := store.ListByAudience(context.Background(), agent.Signer.DID()) + ok := access.ConfirmOK{} + err = datamodel.Rebind(datamodel.NewAny(o), &ok) + require.NoError(t, err) + // One delegation link per attenuation. + require.Len(t, ok.Delegations, 1) + + // Store holds the delegation and its attestation, both keyed by the agent. + page, err := store.ListByAudience(t.Context(), agent.DID()) require.NoError(t, err) require.Len(t, page.Results, 2) }) @@ -136,45 +156,47 @@ func TestAccessConfirmHandler(t *testing.T) { t.Run("multiple capabilities", func(t *testing.T) { id := newTestIdentity(t) store := dlgmemory.New() - handler := AccessConfirmHandler(id, store, logger) + handler := NewAccessConfirmHandler(id, store, logger) + + account := testutil.Must(didmailto.New("carol@example.com"))(t) + agent := testutil.RandomSigner(t) + + args := access.ConfirmArguments{ + Cause: testutil.RandomCID(t), + Issuer: account, + Audience: agent.DID(), + Attenuations: []adm.CapabilityRequestModel{ + {Command: "/space/blob/add"}, + {Command: "/upload/add"}, + }, + } - agent, err := identity.New("") + inv, err := access.Confirm.Invoke( + id.Signer, + id.Signer, + &args, + invocation.WithAudience(id.Signer), + ) require.NoError(t, err) - account := mustMailtoDID(t, "carol@example.com") + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) + require.NoError(t, err) - causeInv, err := invocation.Invoke(agent.Signer, id.Signer, ucan.NewCapability("test/thing", id.DID(), ucan.NoCaveats{})) + err = handler.Handler(req, res) require.NoError(t, err) - cap := ucan.NewCapability( - access.ConfirmAbility, - id.DID(), - access.ConfirmCaveats{ - Iss: account, - Aud: agent.Signer.DID(), - Att: []access.CapabilityRequest{ - {Can: "space/blob/add"}, - {Can: "upload/add"}, - }, - Cause: causeInv.Link(), - }, - ) + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) - inv, err := invocation.Invoke(id.Signer, id.Signer, cap) + ok := access.ConfirmOK{} + err = datamodel.Rebind(datamodel.NewAny(o), &ok) require.NoError(t, err) + require.Len(t, ok.Delegations, 2) - res, _, err := handler(context.Background(), cap, inv, nil) + // Two attenuations → two delegations and two attestations stored. + page, err := store.ListByAudience(t.Context(), agent.DID()) require.NoError(t, err) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.Len(t, ok.Delegations.Keys, 2) + require.Len(t, page.Results, 4) }) } - -func mustMailtoDID(t *testing.T, email string) did.DID { - t.Helper() - d, err := didmailto.New(email) - require.NoError(t, err) - return d -} diff --git a/pkg/service/handlers/access_delegate.go b/pkg/service/handlers/access_delegate.go index 9082935..3ecbc6c 100644 --- a/pkg/service/handlers/access_delegate.go +++ b/pkg/service/handlers/access_delegate.go @@ -1,25 +1,16 @@ package handlers import ( - "context" "fmt" - "go.uber.org/zap" - - "github.com/fil-forge/go-libstoracha/capabilities/access" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" + "github.com/fil-forge/libforge/capabilities/access" "github.com/fil-forge/sprue/pkg/provisioning" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" + "github.com/ipfs/go-cid" + "go.uber.org/zap" ) const ( @@ -27,98 +18,62 @@ const ( InsufficientStorageErrorName = "InsufficientStorage" ) -// WithAccessDelegateMethod registers the access/delegate handler. -// This handler stores delegations for later retrieval. -func WithAccessDelegateMethod(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - access.DelegateAbility, - server.Provide( - access.Delegate, - AccessDelegateHandler(delegationStore, provisioningSvc, logger), - ), - ) -} - -func AccessDelegateHandler(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) server.HandlerFunc[access.DelegateCaveats, access.DelegateOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", access.DelegateAbility)) - return func(ctx context.Context, - cap ucan.Capability[access.DelegateCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[access.DelegateOk, failure.IPLDBuilderFailure], fx.Effects, error) { - agent := inv.Issuer().DID() - space, err := did.Parse(cap.With()) - if err != nil { - log.Warn("invalid resource", zap.String("resource", cap.With())) - return nil, nil, fmt.Errorf("invalid resource: %w", err) - } - delegations := cap.Nb().Delegations - - log := log.With( - zap.Stringer("agent", agent), - zap.Stringer("space", space), - ) - log.Debug("delegating access", zap.Stringer("agent", agent)) - - providers, err := provisioningSvc.ListServiceProviders(ctx, space) - if err != nil { - log.Error("failed to list service providers", zap.Error(err)) - return nil, nil, fmt.Errorf("listing service providers: %w", err) - } - if len(providers) == 0 { - return result.Error[access.DelegateOk, failure.IPLDBuilderFailure]( - errors.New(InsufficientStorageErrorName, "space has no storage provider"), - ), nil, nil - } +func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", access.DelegateCommand)) + return Handler{ + Capability: access.Delegate, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*access.DelegateArguments], + res *bindexec.Response[*access.DelegateOK], + ) error { + args := req.Task().BindArguments() + agent := req.Invocation().Issuer().DID() + space := req.Invocation().Subject().DID() - inv.Proofs() + log := log.With( + zap.Stringer("agent", agent), + zap.Stringer("space", space), + ) + log.Debug("delegating access", zap.Stringer("agent", agent)) - dlgs, err := extractDelegations(cap, inv) - if err != nil { - log.Error("failed to extract delegations", zap.Error(err)) - return nil, nil, err - } + providers, err := provisioningSvc.ListServiceProviders(req.Context(), space) + if err != nil { + log.Error("failed to list service providers", zap.Error(err)) + return fmt.Errorf("listing service providers: %w", err) + } + if len(providers) == 0 { + return res.SetFailure(errors.New(InsufficientStorageErrorName, "space has no storage provider")) + } - cause, err := ipldutil.ToCID(inv.Link()) - if err != nil { - return nil, nil, err - } - err = delegationStore.PutMany(ctx, dlgs, cause) - if err != nil { - log.Error("failed to store delegations", zap.Error(err)) - return nil, nil, err - } + dlgs, err := extractDelegations(args, req.Metadata()) + if err != nil { + log.Error("failed to extract delegations", zap.Error(err)) + return err + } - // For a mock service, we just acknowledge receipt of the delegations - // In a real service, these would be stored for later retrieval - for _, key := range delegations.Keys { - link := delegations.Values[key] - if link != nil { - logger.Debug("stored delegation", zap.String("link", link.String())) + err = delegationStore.PutMany(req.Context(), dlgs, req.Invocation().Task().Link()) + if err != nil { + log.Error("failed to store delegations", zap.Error(err)) + return err } - } - return result.Ok[access.DelegateOk, failure.IPLDBuilderFailure](access.DelegateOk{}), nil, nil + return res.SetSuccess(&access.DelegateOK{}) + }), } } -func extractDelegations(cap ucan.Capability[access.DelegateCaveats], inv invocation.Invocation) ([]delegation.Delegation, error) { - br, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(inv.Blocks())) - if err != nil { - return nil, fmt.Errorf("creating block reader: %w", err) - } - dlgs := make([]delegation.Delegation, 0, len(cap.Nb().Delegations.Keys)) - for _, root := range cap.Nb().Delegations.Values { - block, ok, err := br.Get(root) - if err != nil { - return nil, fmt.Errorf("getting delegation root block %q: %w", root.String(), err) +func extractDelegations(args *access.DelegateArguments, meta ucan.Container) ([]ucan.Token, error) { + all := make(map[cid.Cid]ucan.Token, len(args.Delegations)) + if meta != nil { + for _, d := range meta.Delegations() { + all[d.Link()] = d } + } + dlgs := make([]ucan.Token, 0, len(args.Delegations)) + for _, link := range args.Delegations { + d, ok := all[link] if !ok { - return nil, errors.New(DelegationNotFoundErrorName, "delegation not found: %s", root.String()) - } - d, err := delegation.NewDelegation(block, br) - if err != nil { - return nil, fmt.Errorf("creating delegation view for root %s: %w", root.String(), err) + return nil, errors.New(DelegationNotFoundErrorName, "delegation not found: %s", link.String()) } dlgs = append(dlgs, d) } diff --git a/pkg/service/handlers/access_delegate_test.go b/pkg/service/handlers/access_delegate_test.go index 331593e..abb5873 100644 --- a/pkg/service/handlers/access_delegate_test.go +++ b/pkg/service/handlers/access_delegate_test.go @@ -4,18 +4,19 @@ import ( "context" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/access" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/ucan" + "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/provisioning" consumermemory "github.com/fil-forge/sprue/pkg/store/consumer/memory" dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" subscriptionmemory "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -34,7 +35,7 @@ func newProvisionedService(t *testing.T, serviceDID did.DID, space did.DID) *pro consumerStore := consumermemory.New() subscriptionStore := subscriptionmemory.New() - account := mustMailtoDID(t, "test@example.com") + account := testutil.Must(didmailto.New("test@example.com"))(t) ps := provisioning.NewService( []did.DID{serviceDID}, @@ -52,152 +53,151 @@ func newProvisionedService(t *testing.T, serviceDID did.DID, space did.DID) *pro func TestAccessDelegateHandler(t *testing.T) { logger := zaptest.NewLogger(t) - t.Run("invalid resource DID", func(t *testing.T) { + t.Run("no providers for space", func(t *testing.T) { id := newTestIdentity(t) dlgStore := dlgmemory.New() ps := newTestProvisioningService(t, nil) - handler := AccessDelegateHandler(dlgStore, ps, logger) + handler := NewAccessDelegateHandler(dlgStore, ps, logger) - agent, err := identity.New("") - require.NoError(t, err) + agent := testutil.RandomSigner(t) + space := testutil.RandomSigner(t) + + args := access.DelegateArguments{Delegations: []cid.Cid{}} - cap := ucan.NewCapability( - access.DelegateAbility, - "not-a-did", - access.DelegateCaveats{ - Delegations: access.DelegationLinksModel{ - Keys: []string{}, - Values: map[string]ucan.Link{}, - }, - }, + inv, err := access.Delegate.Invoke( + agent, + space, + &args, + invocation.WithAudience(id.Signer), ) + require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - _, _, err = handler(context.Background(), cap, inv, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid resource") + err = handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) }) - t.Run("no providers for space", func(t *testing.T) { + t.Run("success with delegation", func(t *testing.T) { id := newTestIdentity(t) dlgStore := dlgmemory.New() - // No providers provisioned - ps := newTestProvisioningService(t, nil) - handler := AccessDelegateHandler(dlgStore, ps, logger) - agent, err := identity.New("") + space := testutil.RandomSigner(t) + + ps := newProvisionedService(t, id.Signer.DID(), space.DID()) + handler := NewAccessDelegateHandler(dlgStore, ps, logger) + + agent := testutil.RandomSigner(t) + + // Create a delegation from the space to the agent for some capability. + dlg, err := delegation.Delegate(space, agent, space, "/space/blob/add") require.NoError(t, err) - cap := ucan.NewCapability( - access.DelegateAbility, - agent.DID(), - access.DelegateCaveats{ - Delegations: access.DelegationLinksModel{ - Keys: []string{}, - Values: map[string]ucan.Link{}, - }, - }, + args := access.DelegateArguments{ + Delegations: []cid.Cid{dlg.Link()}, + } + + inv, err := access.Delegate.Invoke( + agent, + space, + &args, + invocation.WithAudience(id.Signer), ) + require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) + // Attach the delegation to the request metadata so extractDelegations can find it. + req := execution.NewRequest(t.Context(), inv, execution.WithDelegations(dlg)) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) - require.NotNil(t, fail) + _, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + + // Verify the delegation was stored. + page, err := dlgStore.ListByAudience(t.Context(), agent.DID()) + require.NoError(t, err) + require.Len(t, page.Results, 1) }) - t.Run("success with delegation", func(t *testing.T) { + t.Run("empty delegations with provisioned space", func(t *testing.T) { id := newTestIdentity(t) dlgStore := dlgmemory.New() - space, err := identity.New("") - require.NoError(t, err) + space := testutil.RandomSigner(t) - ps := newProvisionedService(t, id.Signer.DID(), space.Signer.DID()) - handler := AccessDelegateHandler(dlgStore, ps, logger) + ps := newProvisionedService(t, id.Signer.DID(), space.DID()) + handler := NewAccessDelegateHandler(dlgStore, ps, logger) - agent, err := identity.New("") - require.NoError(t, err) + agent := testutil.RandomSigner(t) - // Create a delegation from space to agent - dlg, err := delegation.Delegate( - space.Signer, - agent.Signer, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("space/blob/add", space.DID(), ucan.NoCaveats{}), - }, - ) - require.NoError(t, err) + args := access.DelegateArguments{Delegations: []cid.Cid{}} - k := dlg.Link().String() - cap := ucan.NewCapability( - access.DelegateAbility, - space.DID(), - access.DelegateCaveats{ - Delegations: access.DelegationLinksModel{ - Keys: []string{k}, - Values: map[string]ucan.Link{k: dlg.Link()}, - }, - }, + inv, err := access.Delegate.Invoke( + agent, + space, + &args, + invocation.WithAudience(id.Signer), ) + require.NoError(t, err) - // Create invocation and attach delegation blocks so extractDelegations can find them - inv, err := invocation.Invoke( - agent.Signer, - id.Signer, - cap, - delegation.WithProof(delegation.FromDelegation(dlg)), - ) + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - - // Verify the delegation was stored - page, err := dlgStore.ListByAudience(context.Background(), agent.Signer.DID()) - require.NoError(t, err) - require.Len(t, page.Results, 1) }) - t.Run("empty delegations with provisioned space", func(t *testing.T) { + t.Run("delegation not found in metadata", func(t *testing.T) { id := newTestIdentity(t) dlgStore := dlgmemory.New() - space, err := identity.New("") - require.NoError(t, err) + space := testutil.RandomSigner(t) - ps := newProvisionedService(t, id.Signer.DID(), space.Signer.DID()) - handler := AccessDelegateHandler(dlgStore, ps, logger) + ps := newProvisionedService(t, id.Signer.DID(), space.DID()) + handler := NewAccessDelegateHandler(dlgStore, ps, logger) - agent, err := identity.New("") + agent := testutil.RandomSigner(t) + + // Reference a delegation by CID, but don't include it in the request metadata. + // We still need at least one delegation in the request so req.Metadata() is non-nil. + other, err := delegation.Delegate(space, agent, space, "/other") require.NoError(t, err) - cap := ucan.NewCapability( - access.DelegateAbility, - space.DID(), - access.DelegateCaveats{ - Delegations: access.DelegationLinksModel{ - Keys: []string{}, - Values: map[string]ucan.Link{}, - }, - }, - ) + missing, err := delegation.Delegate(space, agent, space, "/space/blob/add") + require.NoError(t, err) + + args := access.DelegateArguments{ + Delegations: []cid.Cid{missing.Link()}, + } - inv, err := invocation.Invoke(agent.Signer, id.Signer, cap) + inv, err := access.Delegate.Invoke( + agent, + space, + &args, + invocation.WithAudience(id.Signer), + ) require.NoError(t, err) - res, _, err := handler(context.Background(), cap, inv, nil) + req := execution.NewRequest(t.Context(), inv, execution.WithDelegations(other)) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) require.NoError(t, err) - _, fail := result.Unwrap(res) - require.Nil(t, fail) + // extractDelegations returns an error directly (not via SetFailure) when a + // referenced delegation is missing from the request metadata. + err = handler.Handler(req, res) + require.Error(t, err) + require.Contains(t, err.Error(), "delegation not found") }) } diff --git a/pkg/service/handlers/access_request.go b/pkg/service/handlers/access_request.go new file mode 100644 index 0000000..41e8744 --- /dev/null +++ b/pkg/service/handlers/access_request.go @@ -0,0 +1,149 @@ +package handlers + +import ( + "fmt" + "net/url" + "time" + + "go.uber.org/zap" + + "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/didmailto" + "github.com/fil-forge/sprue/internal/config" + "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/identity" + "github.com/fil-forge/sprue/pkg/mailer" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/ucan/promise" +) + +const ( + InvalidAuthorizationAccountErrorName = "InvalidAuthorizationAccount" + InvalidAuthorizationAudienceErrorName = "InvalidAuthorizationAudience" +) + +// Standard email flow - create confirmation delegation and send email +// We allow granting access within the next 15 minutes +const confirmationTTL = time.Minute * 15 + +var ( + ErrMissingAuthorizationAccount = errors.New(InvalidAuthorizationAccountErrorName, "missing authorization account DID") + ErrInvalidAuthorizationAccount = errors.New(InvalidAuthorizationAccountErrorName, "invalid authorization account DID") + ErrInvalidAuthorizationAudience = errors.New(InvalidAuthorizationAudienceErrorName, "invalid authorization audience DID") +) + +func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identity, mailer mailer.Mailer, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", access.RequestCommand)) + return Handler{ + Capability: provider.List, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*access.RequestArguments], + res *bindexec.Response[*access.RequestOK], + ) error { + args := req.Task().BindArguments() + account, err := didmailto.Parse(args.Issuer.String()) + if err != nil { + log.Warn("failed to parse mailto DID", zap.Stringer("account", args.Issuer)) + return res.SetFailure(errors.New(InvalidAuthorizationAccountErrorName, "invalid authorization account DID: %v", err)) + } + // we should be able to extract the email from the DID since we just + // parsed it as a did:mailto: + email, err := didmailto.Email(account) + if err != nil { + log.Warn("failed to extract email from DID", zap.Stringer("account", args.Issuer)) + return res.SetFailure(errors.New(InvalidAuthorizationAccountErrorName, "invalid authorization account DID: %v", err)) + } + audience := req.Invocation().Subject().DID() + agent := req.Invocation().Issuer().DID() + log := log.With( + zap.Stringer("agent", agent), + zap.Stringer("account", account), + zap.Stringer("audience", audience), + ) + log.Debug("requesting access") + + exp := int(time.Now().Add(confirmationTTL).Unix()) + + // We issue an `/access/confirm` invocation which will be embedded in the + // URL that we send to the user. When the user clicks the link we'll get + // this invocation back in the `/validate-email` endpoint which will allow + // us to verify that it was the user who clicked the link and not some + // attacker impersonating the user. We will know that because the `subject` + // will be our service DID and only private key holder is able to issue + // such an invocation. + // + // We limit the lifetime of this UCAN to 15 minutes to reduce the attack + // surface where an attacker could attempt concurrent authorization + // requests in an attempt to confuse a user into clicking the wrong link. + confirmation, err := access.Confirm.Invoke( + id.Signer, + id.Signer, + // We link to the authorization request so that this invocation can + // not be used to authorize a different request. + &access.ConfirmArguments{ + // we copy request details and set the `aud` field to the agent DID + // that requested the authorization. + Issuer: account, + Audience: audience, + Attenuations: args.Attenuations, + // Link to the task that requested the authorization. + Cause: req.Invocation().Task().Link(), + }, + // audience same as issuer because this is a service invocation + // that will get handled by /access/confirm handler + // but only if the receiver of this email wants it to be + invocation.WithAudience(id.Signer), + invocation.WithExpiration(ucan.UTCUnixTimestamp(exp)), + // we copy the facts in so that information can be passed + // from the invoker of this capability to the invoker of the confirm + // capability - we use this, for example, to let bsky.storage users + // specify that they should be redirected back to bsky.storage after + // completing the Stripe plan selection flow + invocation.WithMetadata(req.Invocation().Metadata()), + ) + if err != nil { + log.Error("failed to create confirmation delegation", zap.Error(err)) + return fmt.Errorf("creating confirmation delegation: %w", err) + } + + confirmationStr, err := container.Encode( + container.Base64urlGzip, + container.New(container.WithInvocations(confirmation)), + ) + if err != nil { + log.Error("failed to format confirmation", zap.Error(err)) + return fmt.Errorf("formatting confirmation: %w", err) + } + + pubUrlStr := serverCfg.PublicURL + if pubUrlStr == "" { + pubUrlStr = fmt.Sprintf("http://%s:%d", serverCfg.Host, serverCfg.Port) + } + validationURL, err := url.Parse(fmt.Sprintf("%s/validate-email?ucan=%s&mode=authorize", pubUrlStr, confirmationStr)) + if err != nil { + log.Error("failed to parse validation URL", zap.Error(err)) + return fmt.Errorf("parsing validation URL: %w", err) + } + + err = mailer.SendValidation(req.Context(), email, *validationURL) + if err != nil { + log.Error("failed to send validation email", zap.Error(err)) + return fmt.Errorf("sending validation email: %w", err) + } + + return res.SetSuccess(&access.RequestOK{ + // link to this access request + Request: req.Invocation().Link(), + // link to the authorization confirmation so it could be used to lookup + // the delegation by the access request. + Confirm: promise.AwaitOK{Task: confirmation.Task().Link()}, + // let client know when the confirmation will expire + Expiration: int64(exp), + }) + }), + } +} diff --git a/pkg/service/handlers/access_request_test.go b/pkg/service/handlers/access_request_test.go new file mode 100644 index 0000000..ef2eae2 --- /dev/null +++ b/pkg/service/handlers/access_request_test.go @@ -0,0 +1,210 @@ +package handlers + +import ( + "context" + "errors" + "net/url" + "testing" + + "github.com/fil-forge/libforge/capabilities/access" + adm "github.com/fil-forge/libforge/capabilities/access/datamodel" + "github.com/fil-forge/libforge/didmailto" + "github.com/fil-forge/sprue/internal/config" + "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/sprue/pkg/identity" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +type mockMailer struct { + lastTo string + lastURL url.URL + err error +} + +func (m *mockMailer) SendValidation(ctx context.Context, to string, validationURL url.URL) error { + m.lastTo = to + m.lastURL = validationURL + return m.err +} + +func newTestIdentity(t *testing.T) *identity.Identity { + t.Helper() + id, err := identity.New("") + require.NoError(t, err) + return id +} + +func TestAccessRequestHandler(t *testing.T) { + logger := zaptest.NewLogger(t) + serverCfg := config.ServerConfig{ + Host: "localhost", + Port: 8080, + PublicURL: "http://localhost:8080", + } + + t.Run("success", func(t *testing.T) { + id := newTestIdentity(t) + m := &mockMailer{} + handler := NewAccessRequestHandler(serverCfg, id, m, logger) + + account, err := didmailto.New("alice@example.com") + require.NoError(t, err) + + args := access.RequestArguments{ + Issuer: account, + Attenuations: []adm.CapabilityRequestModel{ + {Command: "/"}, + }, + } + + agent := testutil.RandomSigner(t) + + inv, err := access.Request.Invoke( + agent, + id.Signer, + &args, + invocation.WithAudience(id.Signer), + ) + require.NoError(t, err) + + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + o, x := result.Unwrap(res.Receipt().Out()) + require.Nil(t, x) + require.NotNil(t, o) + + ok := access.RequestOK{} + err = datamodel.Rebind(datamodel.NewAny(o), &ok) + require.NoError(t, err) + require.Equal(t, inv.Link(), ok.Request) + require.NotZero(t, ok.Expiration) + + require.Equal(t, "alice@example.com", m.lastTo) + require.Contains(t, m.lastURL.String(), "/validate-email") + require.Contains(t, m.lastURL.Query().Get("mode"), "authorize") + }) + + t.Run("invalid account DID", func(t *testing.T) { + id := newTestIdentity(t) + m := &mockMailer{} + handler := NewAccessRequestHandler(serverCfg, id, m, logger) + + // A did:key (not did:mailto) — didmailto.Parse will reject it. + nonMailtoSigner := testutil.RandomSigner(t) + args := access.RequestArguments{ + Issuer: nonMailtoSigner.DID(), + Attenuations: []adm.CapabilityRequestModel{ + {Command: "/"}, + }, + } + + agent := testutil.RandomSigner(t) + + inv, err := access.Request.Invoke( + agent, + id.Signer, + &args, + invocation.WithAudience(id.Signer), + ) + require.NoError(t, err) + + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + _, x := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, x) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(x), &model) + require.NoError(t, err) + require.Equal(t, InvalidAuthorizationAccountErrorName, model.Name()) + }) + + t.Run("mailer error", func(t *testing.T) { + id := newTestIdentity(t) + m := &mockMailer{err: errors.New("smtp failure")} + handler := NewAccessRequestHandler(serverCfg, id, m, logger) + + account, err := didmailto.New("alice@example.com") + require.NoError(t, err) + + args := access.RequestArguments{ + Issuer: account, + Attenuations: []adm.CapabilityRequestModel{ + {Command: "/"}, + }, + } + + agent := testutil.RandomSigner(t) + + inv, err := access.Request.Invoke( + agent, + id.Signer, + &args, + invocation.WithAudience(id.Signer), + ) + require.NoError(t, err) + + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.Error(t, err) + require.Contains(t, err.Error(), "sending validation email") + }) + + t.Run("public URL fallback", func(t *testing.T) { + id := newTestIdentity(t) + m := &mockMailer{} + cfgNoPublicURL := config.ServerConfig{ + Host: "myhost", + Port: 9090, + } + handler := NewAccessRequestHandler(cfgNoPublicURL, id, m, logger) + + account, err := didmailto.New("bob@example.com") + require.NoError(t, err) + + args := access.RequestArguments{ + Issuer: account, + Attenuations: []adm.CapabilityRequestModel{ + {Command: "/"}, + }, + } + + agent := testutil.RandomSigner(t) + + inv, err := access.Request.Invoke( + agent, + id.Signer, + &args, + invocation.WithAudience(id.Signer), + ) + require.NoError(t, err) + + req := execution.NewRequest(t.Context(), inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(id.Signer)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + require.Contains(t, m.lastURL.String(), "http://myhost:9090/validate-email") + }) +} diff --git a/pkg/service/handlers/admin_provider_deregister.go b/pkg/service/handlers/admin_provider_deregister.go index 90a8016..2d03985 100644 --- a/pkg/service/handlers/admin_provider_deregister.go +++ b/pkg/service/handlers/admin_provider_deregister.go @@ -1,53 +1,39 @@ package handlers import ( - "context" - - "go.uber.org/zap" - - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/errors" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" + "go.uber.org/zap" ) -// WithAdminProviderDeregisterMethod registers the admin/provider/deregister handler. -func WithAdminProviderDeregisterMethod(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - provider.DeregisterAbility, - server.Provide( - provider.Deregister, - AdminProviderDeregisterHandler(id, providerStore, logger), - ), - ) -} +func NewAdminProviderDeregisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", provider.DeregisterCommand)) + return Handler{ + Capability: provider.Deregister, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*provider.DeregisterArguments], + res *bindexec.Response[*provider.DeregisterOK], + ) error { + args := req.Task().BindArguments() -func AdminProviderDeregisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.HandlerFunc[provider.DeregisterCaveats, provider.DeregisterOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", provider.DeregisterAbility)) - return func(ctx context.Context, - cap ucan.Capability[provider.DeregisterCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[provider.DeregisterOk, failure.IPLDBuilderFailure], fx.Effects, error) { - args := cap.Nb() - if inv.Issuer().DID() != id.Signer.DID() { - log.Warn("Unauthorized access attempt", zap.Stringer("issuer", inv.Issuer().DID())) - return result.Error[provider.DeregisterOk, failure.IPLDBuilderFailure]( - errors.New("Unauthorized", "only the service identity can deregister a provider"), - ), nil, nil - } + if req.Invocation().Issuer().DID() != id.Signer.DID() { + log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) + return res.SetFailure(errors.New("Unauthorized", "only the service identity can deregister a provider")) + } - err := providerStore.Delete(ctx, args.Provider) - if err != nil { - log.Error("Failed to deregister provider", zap.Error(err)) - return nil, nil, err - } - return result.Ok[provider.DeregisterOk, failure.IPLDBuilderFailure](provider.DeregisterOk{}), nil, nil + err := providerStore.Delete(req.Context(), args.Provider) + if err != nil { + if errors.Is(err, storageprovider.ErrStorageProviderNotFound) { + log.Warn("Provider not found", zap.Stringer("provider", args.Provider)) + return res.SetFailure(err) + } + log.Error("Failed to deregister provider", zap.Error(err)) + return err + } + return res.SetSuccess(&provider.DeregisterOK{}) + }), } } diff --git a/pkg/service/handlers/admin_provider_deregister_test.go b/pkg/service/handlers/admin_provider_deregister_test.go new file mode 100644 index 0000000..9184b78 --- /dev/null +++ b/pkg/service/handlers/admin_provider_deregister_test.go @@ -0,0 +1,149 @@ +package handlers_test + +import ( + "net/url" + "testing" + + "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/identity" + "github.com/fil-forge/sprue/pkg/service/handlers" + storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func issueDeregisterInvocation( + t *testing.T, + issuer ucan.Signer, + audience ucan.Principal, + args provider.DeregisterArguments, +) execution.Request { + t.Helper() + + inv, err := provider.Deregister.Invoke( + issuer, + audience, + &args, + invocation.WithAudience(audience), + ) + require.NoError(t, err) + + return execution.NewRequest(t.Context(), inv) +} + +func TestAdminProviderDeregisterHandler(t *testing.T) { + logger := zaptest.NewLogger(t) + ctx := t.Context() + + uploadService := testutil.WebService + + t.Run("unauthorized issuer", func(t *testing.T) { + spStore := storage_provider_store.New() + + handler := handlers.NewAdminProviderDeregisterHandler( + &identity.Identity{Signer: uploadService}, spStore, logger, + ) + + storageProvider := testutil.RandomSigner(t) + unauthorizedIssuer := testutil.RandomSigner(t) + + // Pre-populate the store so we can verify the record is NOT removed. + endpoint, err := url.Parse("https://piri.example.com") + require.NoError(t, err) + err = spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, nil) + require.NoError(t, err) + + args := provider.DeregisterArguments{ + Provider: storageProvider.DID(), + } + + req := issueDeregisterInvocation(t, unauthorizedIssuer, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, "Unauthorized", model.Name()) + + // Record should still be present. + _, err = spStore.Get(ctx, storageProvider.DID()) + require.NoError(t, err) + }) + + t.Run("service identity can deregister", func(t *testing.T) { + spStore := storage_provider_store.New() + + handler := handlers.NewAdminProviderDeregisterHandler( + &identity.Identity{Signer: uploadService}, spStore, logger, + ) + + storageProvider := testutil.RandomSigner(t) + + endpoint, err := url.Parse("https://piri.example.com") + require.NoError(t, err) + err = spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, nil) + require.NoError(t, err) + + args := provider.DeregisterArguments{ + Provider: storageProvider.DID(), + } + + req := issueDeregisterInvocation(t, uploadService, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, o) + + _, err = spStore.Get(ctx, storageProvider.DID()) + require.ErrorIs(t, err, storageprovider.ErrStorageProviderNotFound) + }) + + t.Run("provider not found", func(t *testing.T) { + spStore := storage_provider_store.New() + + handler := handlers.NewAdminProviderDeregisterHandler( + &identity.Identity{Signer: uploadService}, spStore, logger, + ) + + storageProvider := testutil.RandomSigner(t) + + args := provider.DeregisterArguments{ + Provider: storageProvider.DID(), + } + + req := issueDeregisterInvocation(t, uploadService, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, storageprovider.StorageProviderNotFoundErrorName, model.Name()) + }) +} diff --git a/pkg/service/handlers/admin_provider_list.go b/pkg/service/handlers/admin_provider_list.go index 5fd0d8c..2ce2cee 100644 --- a/pkg/service/handlers/admin_provider_list.go +++ b/pkg/service/handlers/admin_provider_list.go @@ -5,72 +5,54 @@ import ( "go.uber.org/zap" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" ) -// WithAdminProviderListMethod registers the admin/provider/list handler. -func WithAdminProviderListMethod(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - provider.ListAbility, - server.Provide( - provider.List, - AdminProviderListHandler(id, providerStore, logger), - ), - ) -} - -func AdminProviderListHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.HandlerFunc[provider.ListCaveats, provider.ListOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", provider.ListAbility)) - return func(ctx context.Context, - cap ucan.Capability[provider.ListCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[provider.ListOk, failure.IPLDBuilderFailure], fx.Effects, error) { - if inv.Issuer().DID() != id.Signer.DID() { - log.Warn("Unauthorized access attempt", zap.Stringer("issuer", inv.Issuer().DID())) - return result.Error[provider.ListOk, failure.IPLDBuilderFailure]( - errors.New("Unauthorized", "only the service identity can list providers"), - ), nil, nil - } +func NewAdminProviderListHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", provider.ListCommand)) + return Handler{ + Capability: provider.List, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*provider.ListArguments], + res *bindexec.Response[*provider.ListOK], + ) error { + if req.Invocation().Issuer().DID() != id.Signer.DID() { + log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) + return res.SetFailure(errors.New("Unauthorized", "only the service identity can list providers")) + } - records, err := store.Collect(ctx, func(ctx context.Context, options store.PaginationConfig) (store.Page[storageprovider.Record], error) { - opts := []storageprovider.ListOption{} - if options.Cursor != nil { - opts = append(opts, storageprovider.WithListCursor(*options.Cursor)) + records, err := store.Collect(req.Context(), func(ctx context.Context, options store.PaginationConfig) (store.Page[storageprovider.Record], error) { + opts := []storageprovider.ListOption{} + if options.Cursor != nil { + opts = append(opts, storageprovider.WithListCursor(*options.Cursor)) + } + return providerStore.List(ctx, opts...) + }) + if err != nil { + log.Error("Failed to list providers", zap.Error(err)) + return err } - return providerStore.List(ctx, opts...) - }) - if err != nil { - log.Error("Failed to list providers", zap.Error(err)) - return nil, nil, err - } - var providers []provider.Provider - for _, p := range records { - replicationWeight := p.Weight - if p.ReplicationWeight != nil { - replicationWeight = *p.ReplicationWeight + var providers []provider.Provider + for _, p := range records { + replicationWeight := p.Weight + if p.ReplicationWeight != nil { + replicationWeight = *p.ReplicationWeight + } + providers = append(providers, provider.Provider{ + Provider: p.Provider, + Endpoint: p.Endpoint.String(), + Weight: int64(p.Weight), + ReplicationWeight: int64(replicationWeight), + }) } - providers = append(providers, provider.Provider{ - ID: p.Provider, - Endpoint: p.Endpoint.String(), - Weight: p.Weight, - ReplicationWeight: replicationWeight, - }) - } - return result.Ok[provider.ListOk, failure.IPLDBuilderFailure]( - provider.ListOk{Providers: providers}, - ), nil, nil + return res.SetSuccess(&provider.ListOK{Providers: providers}) + }), } } diff --git a/pkg/service/handlers/admin_provider_list_test.go b/pkg/service/handlers/admin_provider_list_test.go new file mode 100644 index 0000000..a4a6fc6 --- /dev/null +++ b/pkg/service/handlers/admin_provider_list_test.go @@ -0,0 +1,149 @@ +package handlers_test + +import ( + "net/url" + "testing" + + "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/identity" + "github.com/fil-forge/sprue/pkg/service/handlers" + storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func issueListInvocation( + t *testing.T, + issuer ucan.Signer, + audience ucan.Principal, +) execution.Request { + t.Helper() + + args := provider.ListArguments{} + inv, err := provider.List.Invoke( + issuer, + audience, + &args, + invocation.WithAudience(audience), + ) + require.NoError(t, err) + + return execution.NewRequest(t.Context(), inv) +} + +func TestAdminProviderListHandler(t *testing.T) { + logger := zaptest.NewLogger(t) + ctx := t.Context() + + uploadService := testutil.WebService + + t.Run("unauthorized issuer", func(t *testing.T) { + spStore := storage_provider_store.New() + + handler := handlers.NewAdminProviderListHandler( + &identity.Identity{Signer: uploadService}, spStore, logger, + ) + + unauthorizedIssuer := testutil.RandomSigner(t) + + req := issueListInvocation(t, unauthorizedIssuer, uploadService) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, "Unauthorized", model.Name()) + }) + + t.Run("empty list", func(t *testing.T) { + spStore := storage_provider_store.New() + + handler := handlers.NewAdminProviderListHandler( + &identity.Identity{Signer: uploadService}, spStore, logger, + ) + + req := issueListInvocation(t, uploadService, uploadService) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + ok, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, ok) + + listOK := provider.ListOK{} + err = datamodel.Rebind(datamodel.NewAny(ok), &listOK) + require.NoError(t, err) + require.Empty(t, listOK.Providers) + }) + + t.Run("returns registered providers", func(t *testing.T) { + spStore := storage_provider_store.New() + + handler := handlers.NewAdminProviderListHandler( + &identity.Identity{Signer: uploadService}, spStore, logger, + ) + + sp1 := testutil.RandomSigner(t) + sp2 := testutil.RandomSigner(t) + + endpoint1, err := url.Parse("https://piri-1.example.com") + require.NoError(t, err) + endpoint2, err := url.Parse("https://piri-2.example.com") + require.NoError(t, err) + + repWeight := 50 + require.NoError(t, spStore.Put(ctx, sp1.DID(), *endpoint1, 100, &repWeight)) + require.NoError(t, spStore.Put(ctx, sp2.DID(), *endpoint2, 200, nil)) + + req := issueListInvocation(t, uploadService, uploadService) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + ok, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, ok) + + listOK := provider.ListOK{} + err = datamodel.Rebind(datamodel.NewAny(ok), &listOK) + require.NoError(t, err) + require.Len(t, listOK.Providers, 2) + + byDID := map[string]provider.Provider{} + for _, p := range listOK.Providers { + byDID[p.Provider.String()] = p + } + + got1, ok1 := byDID[sp1.DID().String()] + require.True(t, ok1) + require.Equal(t, "https://piri-1.example.com", got1.Endpoint) + require.Equal(t, int64(100), got1.Weight) + require.Equal(t, int64(50), got1.ReplicationWeight) + + got2, ok2 := byDID[sp2.DID().String()] + require.True(t, ok2) + require.Equal(t, "https://piri-2.example.com", got2.Endpoint) + require.Equal(t, int64(200), got2.Weight) + // When ReplicationWeight is nil in the store, the handler defaults it to Weight. + require.Equal(t, int64(200), got2.ReplicationWeight) + }) +} diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index 27511d6..e2a7a9a 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -1,24 +1,14 @@ package handlers import ( - "context" - "fmt" "net/url" - "go.uber.org/zap" - - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/errors" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" + "go.uber.org/zap" ) var ( @@ -26,69 +16,43 @@ var ( initialReplicationWeight = 0 ) -// WithAdminProviderRegisterMethod registers the admin/provider/register handler. -func WithAdminProviderRegisterMethod(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - provider.RegisterAbility, - server.Provide( - provider.Register, - AdminProviderRegisterHandler(id, providerStore, logger), - ), - ) -} - -func AdminProviderRegisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.HandlerFunc[provider.RegisterCaveats, provider.RegisterOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", provider.RegisterAbility)) - return func(ctx context.Context, - cap ucan.Capability[provider.RegisterCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[provider.RegisterOk, failure.IPLDBuilderFailure], fx.Effects, error) { - args := cap.Nb() - - endpoint, err := url.Parse(args.Endpoint) - if err != nil { - log.Warn("Invalid endpoint", zap.String("endpoint", args.Endpoint), zap.Error(err)) - return result.Error[provider.RegisterOk, failure.IPLDBuilderFailure]( - errors.New("InvalidEndpoint", "parsing endpoint: %s", err.Error()), - ), nil, nil - } - bs, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(inv.Blocks())) - if err != nil { - log.Error("Failed to create block reader", zap.Error(err)) - return nil, nil, fmt.Errorf("creating block reader: %w", err) - } - proof, err := delegation.NewDelegationView(args.Proof, bs) - if err != nil { - log.Error("Failed to create proof delegation view", zap.Error(err)) - return nil, nil, fmt.Errorf("creating proof delegation view: %w", err) - } +func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", provider.RegisterCommand)) + return Handler{ + Capability: provider.Register, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*provider.RegisterArguments], + res *bindexec.Response[*provider.RegisterOK], + ) error { + args := req.Task().BindArguments() + if req.Invocation().Issuer().DID() != id.Signer.DID() { + log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) + return res.SetFailure(errors.New("Unauthorized", "only the service identity can register providers")) + } - if inv.Issuer().DID() != id.Signer.DID() && inv.Issuer().DID() != proof.Issuer().DID() { - log.Warn("Unauthorized access attempt", zap.Stringer("issuer", inv.Issuer().DID())) - return result.Error[provider.RegisterOk, failure.IPLDBuilderFailure]( - errors.New("Unauthorized", "only the service identity or the provider itself can register a provider"), - ), nil, nil - } + endpoint, err := url.Parse(args.Endpoint) + if err != nil { + log.Warn("Invalid endpoint", zap.String("endpoint", args.Endpoint), zap.Error(err)) + return res.SetFailure(errors.New("InvalidEndpoint", "parsing endpoint: %s", err.Error())) + } - _, err = providerStore.Get(ctx, proof.Issuer().DID()) - if err != nil { - if !errors.Is(err, storageprovider.ErrStorageProviderNotFound) { - log.Error("Failed to get existing provider", zap.Error(err)) - return nil, nil, err + _, err = providerStore.Get(req.Context(), args.Provider) + if err != nil { + if !errors.Is(err, storageprovider.ErrStorageProviderNotFound) { + log.Error("Failed to get existing provider", zap.Error(err)) + return err + } + } else { + log.Warn("Provider already registered", zap.Stringer("provider", args.Provider)) + return res.SetFailure(errors.New("ProviderAlreadyRegistered", "a provider with this DID is already registered")) } - } else { - log.Warn("Provider already registered", zap.Stringer("provider", proof.Issuer().DID())) - return result.Error[provider.RegisterOk, failure.IPLDBuilderFailure]( - errors.New("ProviderAlreadyRegistered", "a provider with this DID is already registered"), - ), nil, nil - } - err = providerStore.Put(ctx, *endpoint, proof, initialWeight, &initialReplicationWeight) - if err != nil { - log.Error("Failed to register provider", zap.Error(err)) - return nil, nil, err - } - return result.Ok[provider.RegisterOk, failure.IPLDBuilderFailure](provider.RegisterOk{}), nil, nil + err = providerStore.Put(req.Context(), args.Provider, *endpoint, initialWeight, &initialReplicationWeight) + if err != nil { + log.Error("Failed to register provider", zap.Error(err)) + return err + } + return res.SetSuccess(&provider.RegisterOK{}) + }), } } diff --git a/pkg/service/handlers/admin_provider_register_test.go b/pkg/service/handlers/admin_provider_register_test.go index 0b69a14..8e201d3 100644 --- a/pkg/service/handlers/admin_provider_register_test.go +++ b/pkg/service/handlers/admin_provider_register_test.go @@ -3,50 +3,39 @@ package handlers_test import ( "testing" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) -// issueRegisterInvocation creates an admin/provider/register invocation with -// the proof delegation blocks attached. +// issueRegisterInvocation creates an admin/provider/register invocation request func issueRegisterInvocation( t *testing.T, issuer ucan.Signer, audience ucan.Principal, - caveats provider.RegisterCaveats, - proof delegation.Delegation, -) (ucan.Capability[provider.RegisterCaveats], invocation.Invocation) { + args provider.RegisterArguments, +) execution.Request { t.Helper() inv, err := provider.Register.Invoke( - issuer, audience, - audience.DID().String(), - caveats, + issuer, + audience, + &args, + invocation.WithAudience(audience), ) require.NoError(t, err) - // Attach proof delegation blocks to the invocation - for blk, err := range proof.Blocks() { - require.NoError(t, err) - require.NoError(t, inv.Attach(blk)) - } - - cap := provider.Register.New( - audience.DID().String(), - caveats, - ) - - return cap, inv + return execution.NewRequest(t.Context(), inv) } func TestAdminProviderRegisterHandler(t *testing.T) { @@ -58,155 +47,104 @@ func TestAdminProviderRegisterHandler(t *testing.T) { t.Run("unauthorized issuer", func(t *testing.T) { spStore := storage_provider_store.New() - handler := handlers.AdminProviderRegisterHandler( + handler := handlers.NewAdminProviderRegisterHandler( &identity.Identity{Signer: uploadService}, spStore, logger, ) storageProvider := testutil.RandomSigner(t) unauthorizedIssuer := testutil.RandomSigner(t) - // Create a proof delegation from storageProvider to uploadService - proof, err := delegation.Delegate( - storageProvider, uploadService, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("blob/allocate", storageProvider.DID().String(), ucan.NoCaveats{}), - }, - ) - require.NoError(t, err) - - caveats := provider.RegisterCaveats{ + args := provider.RegisterArguments{ + Provider: storageProvider.DID(), Endpoint: "https://piri.example.com", - Proof: proof.Link(), } // Issuer is neither the service nor the provider - cap, inv := issueRegisterInvocation(t, unauthorizedIssuer, uploadService, caveats, proof) + req := issueRegisterInvocation(t, unauthorizedIssuer, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) - res, _, err := handler(ctx, cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.NotNil(t, fail) - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, "Unauthorized", *model.Name) + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, "Unauthorized", model.Name()) }) t.Run("provider already registered", func(t *testing.T) { spStore := storage_provider_store.New() - handler := handlers.AdminProviderRegisterHandler( + handler := handlers.NewAdminProviderRegisterHandler( &identity.Identity{Signer: uploadService}, spStore, logger, ) storageProvider := testutil.RandomSigner(t) - // Create a proof delegation - proof, err := delegation.Delegate( - storageProvider, uploadService, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("blob/allocate", storageProvider.DID().String(), ucan.NoCaveats{}), - }, - ) - require.NoError(t, err) - - caveats := provider.RegisterCaveats{ + args := provider.RegisterArguments{ + Provider: storageProvider.DID(), Endpoint: "https://piri.example.com", - Proof: proof.Link(), } // First registration by service identity (authorized) - cap, inv := issueRegisterInvocation(t, uploadService, uploadService, caveats, proof) - res, _, err := handler(ctx, cap, inv, nil) + req := issueRegisterInvocation(t, uploadService, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) - o, fail := result.Unwrap(res) - require.Nil(t, fail) - require.NotNil(t, o) - - // Second registration should fail - cap2, inv2 := issueRegisterInvocation(t, uploadService, uploadService, caveats, proof) - res2, _, err := handler(ctx, cap2, inv2, nil) + err = handler.Handler(req, res) require.NoError(t, err) - _, fail2 := result.Unwrap(res2) - require.NotNil(t, fail2) - - model := datamodel.Bind(testutil.Must(fail2.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, "ProviderAlreadyRegistered", *model.Name) - }) - - t.Run("service identity can register", func(t *testing.T) { - spStore := storage_provider_store.New() - - handler := handlers.AdminProviderRegisterHandler( - &identity.Identity{Signer: uploadService}, spStore, logger, - ) - - storageProvider := testutil.RandomSigner(t) + o, x := result.Unwrap(res.Receipt().Out()) + require.Nil(t, x) + require.NotNil(t, o) - proof, err := delegation.Delegate( - storageProvider, uploadService, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("blob/allocate", storageProvider.DID().String(), ucan.NoCaveats{}), - }, - ) + // Second registration should fail + req2 := issueRegisterInvocation(t, uploadService, uploadService, args) + res2, err := execution.NewResponse(req2.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) - caveats := provider.RegisterCaveats{ - Endpoint: "https://piri.example.com", - Proof: proof.Link(), - } - - cap, inv := issueRegisterInvocation(t, uploadService, uploadService, caveats, proof) - - res, _, err := handler(ctx, cap, inv, nil) + err = handler.Handler(req2, res2) require.NoError(t, err) - o, fail := result.Unwrap(res) - require.Nil(t, fail) - require.NotNil(t, o) + _, x2 := result.Unwrap(res2.Receipt().Out()) + require.NotNil(t, x2) - // Verify provider was stored - rec, err := spStore.Get(ctx, storageProvider.DID()) + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(x2), &model) require.NoError(t, err) - require.Equal(t, "https://piri.example.com", rec.Endpoint.String()) + require.Equal(t, "ProviderAlreadyRegistered", model.Name()) }) - t.Run("provider itself can register", func(t *testing.T) { + t.Run("service identity can register", func(t *testing.T) { spStore := storage_provider_store.New() - handler := handlers.AdminProviderRegisterHandler( + handler := handlers.NewAdminProviderRegisterHandler( &identity.Identity{Signer: uploadService}, spStore, logger, ) storageProvider := testutil.RandomSigner(t) - proof, err := delegation.Delegate( - storageProvider, uploadService, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("blob/allocate", storageProvider.DID().String(), ucan.NoCaveats{}), - }, - ) - require.NoError(t, err) - - caveats := provider.RegisterCaveats{ + args := provider.RegisterArguments{ + Provider: storageProvider.DID(), Endpoint: "https://piri.example.com", - Proof: proof.Link(), } - // Issued by the provider itself - cap, inv := issueRegisterInvocation(t, storageProvider, uploadService, caveats, proof) + req := issueRegisterInvocation(t, uploadService, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) - res, _, err := handler(ctx, cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res) - require.Nil(t, fail) + o, x := result.Unwrap(res.Receipt().Out()) + require.Nil(t, x) require.NotNil(t, o) + // Verify provider was stored rec, err := spStore.Get(ctx, storageProvider.DID()) require.NoError(t, err) require.Equal(t, "https://piri.example.com", rec.Endpoint.String()) diff --git a/pkg/service/handlers/admin_provider_weight_set.go b/pkg/service/handlers/admin_provider_weight_set.go index f83013e..0adc551 100644 --- a/pkg/service/handlers/admin_provider_weight_set.go +++ b/pkg/service/handlers/admin_provider_weight_set.go @@ -1,59 +1,46 @@ package handlers import ( - "context" - "go.uber.org/zap" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/errors" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" ) -// WithAdminProviderWeightSetMethod registers the admin/provider/weight/set handler. -func WithAdminProviderWeightSetMethod(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - provider.WeightSetAbility, - server.Provide( - provider.WeightSet, - AdminProviderWeightSetHandler(id, providerStore, logger), - ), - ) -} - -func AdminProviderWeightSetHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.HandlerFunc[provider.WeightSetCaveats, provider.WeightSetOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", provider.WeightSetAbility)) - return func(ctx context.Context, - cap ucan.Capability[provider.WeightSetCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[provider.WeightSetOk, failure.IPLDBuilderFailure], fx.Effects, error) { - args := cap.Nb() - if inv.Issuer().DID() != id.Signer.DID() { - log.Warn("Unauthorized access attempt", zap.Stringer("issuer", inv.Issuer().DID())) - return result.Error[provider.WeightSetOk, failure.IPLDBuilderFailure]( - errors.New("Unauthorized", "only the service identity can set provider weights"), - ), nil, nil - } +func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", weight.SetCommand)) + return Handler{ + Capability: weight.Set, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*weight.SetArguments], + res *bindexec.Response[*weight.SetOK], + ) error { + args := req.Task().BindArguments() + if req.Invocation().Issuer().DID() != id.Signer.DID() { + log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) + return res.SetFailure(errors.New("Unauthorized", "only the service identity can set provider weights")) + } - p, err := providerStore.Get(ctx, args.Provider) - if err != nil { - log.Error("Failed to get existing provider", zap.Error(err)) - return nil, nil, err - } + p, err := providerStore.Get(req.Context(), args.Provider) + if err != nil { + log.Error("Failed to get existing provider", zap.Error(err)) + return res.SetFailure(errors.New("Failed to get existing provider", err.Error())) + } - err = providerStore.Put(ctx, p.Endpoint, p.Proof, args.Weight, &args.ReplicationWeight) - if err != nil { - log.Error("Failed to update provider weights", zap.Error(err)) - return nil, nil, err - } - return result.Ok[provider.WeightSetOk, failure.IPLDBuilderFailure](provider.WeightSetOk{}), nil, nil + replicationWeight := int(args.ReplicationWeight) + err = providerStore.Put(req.Context(), p.Provider, p.Endpoint, int(args.Weight), &replicationWeight) + if err != nil { + if errors.Is(err, storageprovider.ErrStorageProviderNotFound) { + log.Warn("Provider not found", zap.Stringer("provider", args.Provider)) + return res.SetFailure(err) + } + log.Error("Failed to update provider weights", zap.Error(err)) + return err + } + return res.SetSuccess(&weight.SetOK{}) + }), } } diff --git a/pkg/service/handlers/admin_provider_weight_set_test.go b/pkg/service/handlers/admin_provider_weight_set_test.go index 13d02f6..2573cb8 100644 --- a/pkg/service/handlers/admin_provider_weight_set_test.go +++ b/pkg/service/handlers/admin_provider_weight_set_test.go @@ -4,19 +4,40 @@ import ( "net/url" "testing" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/ucan" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) +func issueWeightSetInvocation( + t *testing.T, + issuer ucan.Signer, + audience ucan.Principal, + args weight.SetArguments, +) execution.Request { + t.Helper() + + inv, err := weight.Set.Invoke( + issuer, + audience, + &args, + invocation.WithAudience(audience), + ) + require.NoError(t, err) + + return execution.NewRequest(t.Context(), inv) +} + func TestAdminProviderWeightSetHandler(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() @@ -26,115 +47,100 @@ func TestAdminProviderWeightSetHandler(t *testing.T) { t.Run("unauthorized issuer", func(t *testing.T) { spStore := storage_provider_store.New() - handler := handlers.AdminProviderWeightSetHandler( + handler := handlers.NewAdminProviderWeightSetHandler( &identity.Identity{Signer: uploadService}, spStore, logger, ) storageProvider := testutil.RandomSigner(t) unauthorizedIssuer := testutil.RandomSigner(t) - caveats := provider.WeightSetCaveats{ + args := weight.SetArguments{ Provider: storageProvider.DID(), Weight: 50, ReplicationWeight: 25, } - inv, err := provider.WeightSet.Invoke( - unauthorizedIssuer, uploadService, - uploadService.DID().String(), - caveats, - ) + req := issueWeightSetInvocation(t, unauthorizedIssuer, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) - cap := provider.WeightSet.New(uploadService.DID().String(), caveats) - - res, _, err := handler(ctx, cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) - require.NotNil(t, fail) + _, x := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, x) - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, "Unauthorized", *model.Name) + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(x), &model) + require.NoError(t, err) + require.Equal(t, "Unauthorized", model.Name()) }) t.Run("provider not found", func(t *testing.T) { spStore := storage_provider_store.New() - handler := handlers.AdminProviderWeightSetHandler( + handler := handlers.NewAdminProviderWeightSetHandler( &identity.Identity{Signer: uploadService}, spStore, logger, ) storageProvider := testutil.RandomSigner(t) - caveats := provider.WeightSetCaveats{ + args := weight.SetArguments{ Provider: storageProvider.DID(), Weight: 50, ReplicationWeight: 25, } - inv, err := provider.WeightSet.Invoke( - uploadService, uploadService, - uploadService.DID().String(), - caveats, - ) + req := issueWeightSetInvocation(t, uploadService, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) - cap := provider.WeightSet.New(uploadService.DID().String(), caveats) + err = handler.Handler(req, res) + require.NoError(t, err) - // Provider is not registered, so Get will fail - _, _, err = handler(ctx, cap, inv, nil) - require.Error(t, err) + _, x := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, x) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(x), &model) + require.NoError(t, err) + require.Equal(t, "Failed to get existing provider", model.Name()) }) t.Run("success updates weights", func(t *testing.T) { spStore := storage_provider_store.New() - handler := handlers.AdminProviderWeightSetHandler( + handler := handlers.NewAdminProviderWeightSetHandler( &identity.Identity{Signer: uploadService}, spStore, logger, ) storageProvider := testutil.RandomSigner(t) - endpoint := testutil.Must(url.Parse("https://piri.example.com"))(t) - - proof, err := delegation.Delegate( - storageProvider, uploadService, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("blob/allocate", storageProvider.DID().String(), ucan.NoCaveats{}), - }, - ) + endpoint, err := url.Parse("https://piri.example.com") require.NoError(t, err) - // Pre-register the provider with initial weights + // Pre-register the provider with initial weights. initialReplWeight := 0 - err = spStore.Put(ctx, *endpoint, proof, 0, &initialReplWeight) + err = spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, &initialReplWeight) require.NoError(t, err) - // Set new weights - caveats := provider.WeightSetCaveats{ + args := weight.SetArguments{ Provider: storageProvider.DID(), Weight: 75, ReplicationWeight: 30, } - inv, err := provider.WeightSet.Invoke( - uploadService, uploadService, - uploadService.DID().String(), - caveats, - ) + req := issueWeightSetInvocation(t, uploadService, uploadService, args) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) - cap := provider.WeightSet.New(uploadService.DID().String(), caveats) - - res, _, err := handler(ctx, cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res) - require.Nil(t, fail) + o, x := result.Unwrap(res.Receipt().Out()) + require.Nil(t, x) require.NotNil(t, o) - // Verify weights were updated + // Verify weights were updated. rec, err := spStore.Get(ctx, storageProvider.DID()) require.NoError(t, err) require.Equal(t, 75, rec.Weight) diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go new file mode 100644 index 0000000..733af14 --- /dev/null +++ b/pkg/service/handlers/blob_add.go @@ -0,0 +1,415 @@ +package handlers + +import ( + "context" + "crypto/ed25519" + "fmt" + + blobcaps "github.com/fil-forge/libforge/capabilities/blob" + httpcaps "github.com/fil-forge/libforge/capabilities/http" + "github.com/fil-forge/libforge/digestutil" + "github.com/fil-forge/sprue/pkg/identity" + "github.com/fil-forge/sprue/pkg/lib/ucan_server" + "github.com/fil-forge/sprue/pkg/piriclient" + "github.com/fil-forge/sprue/pkg/provisioning" + "github.com/fil-forge/sprue/pkg/routing" + "github.com/fil-forge/sprue/pkg/store/agent" + blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + ed25519signer "github.com/fil-forge/ucantone/principal/ed25519" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/ucan/promise" + "github.com/fil-forge/ucantone/ucan/receipt" + "github.com/multiformats/go-multihash" + "go.uber.org/zap" +) + +func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, router *routing.Service, nodeProvider piriclient.Provider, agentStore agent.Store, blobRegistry blobregistry.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", blobcaps.AddCommand)) + return Handler{ + Capability: blobcaps.Add, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*blobcaps.AddArguments], + res *bindexec.Response[*blobcaps.AddOK], + ) error { + args := req.Task().BindArguments() + blob := args.Blob + space := req.Invocation().Subject().DID() + b58digest := digestutil.Format(blob.Digest) + + log := log.With( + zap.Stringer("space", space), + zap.Dict( + "blob", + zap.String("digest", b58digest), + zap.Uint64("size", blob.Size), + ), + ) + log.Debug("adding blob") + + providers, err := provisioningSvc.ListServiceProviders(req.Context(), space) + if err != nil { + log.Error("failed to list service providers", zap.Error(err)) + return fmt.Errorf("listing service providers: %w", err) + } + if len(providers) == 0 { + return res.SetFailure(errors.New(InsufficientStorageErrorName, "space has no storage provider")) + } + + reg, err := blobRegistry.Get(req.Context(), space, blob.Digest) + if err != nil { + if !errors.Is(err, blobregistry.ErrEntryNotFound) { + log.Error("failed to get blob registration", zap.Error(err)) + return err + } + } + + // If blob is already registered in the space, we can skip allocation and + // return the information from the original receipt, plus the invocations + // and receipts for the /blob/allocate /http/put and /blob/accept tasks + // that happened. + if err == nil { + log.Debug("blob already registered in space") + + // blob registration cause is the CID of the `/space/blob/add` task + addRcpt, err := agentStore.GetReceipt(req.Context(), reg.Cause) + if err != nil { + log.Error("failed to get receipt for blob registration", zap.Error(err)) + return err + } + + addOK, err := result.MatchResultR2( + addRcpt.Out(), + func(o ipld.Any) (*blobcaps.AddOK, error) { + var addOK blobcaps.AddOK + err := datamodel.Rebind(datamodel.NewAny(o), &addOK) + if err != nil { + log.Error("failed to rebind add OK result", zap.Error(err)) + return nil, fmt.Errorf("rebinding add OK result: %w", err) + } + return &addOK, nil + }, + func(x ipld.Any) (*blobcaps.AddOK, error) { + // should not have been registered on error + log.Error("blob registration receipt contains failure", zap.Any("error", x)) + return nil, fmt.Errorf("blob registration receipt contains failure: %v", x) + }, + ) + if err != nil { + log.Error("failed to match blob add receipt result", zap.Error(err)) + return fmt.Errorf("matching blob add receipt result: %w", err) + } + + accRcpt, err := agentStore.GetReceipt(req.Context(), addOK.Site.Task) + if err != nil { + log.Error("failed to get receipt for blob accept", zap.Error(err)) + return fmt.Errorf("getting receipt for blob accept: %w", err) + } + + accInv, err := agentStore.GetInvocation(req.Context(), addOK.Site.Task) + if err != nil { + log.Error("failed to get invocation for blob accept", zap.Error(err)) + return fmt.Errorf("getting invocation for blob accept: %w", err) + } + + accArgs := blobcaps.AcceptArguments{} + err = datamodel.Rebind(datamodel.NewAny(accInv.Arguments()), &accArgs) + if err != nil { + log.Error("failed to rebind accept OK result", zap.Error(err)) + return fmt.Errorf("rebinding accept OK result: %w", err) + } + + putRcpt, err := agentStore.GetReceipt(req.Context(), accArgs.Put.Task) + if err != nil { + log.Error("failed to get receipt for HTTP PUT", zap.Error(err)) + return fmt.Errorf("getting receipt for HTTP PUT: %w", err) + } + + putInv, err := agentStore.GetInvocation(req.Context(), accArgs.Put.Task) + if err != nil { + log.Error("failed to get invocation for HTTP PUT", zap.Error(err)) + return fmt.Errorf("getting invocation for HTTP PUT: %w", err) + } + + putArgs := httpcaps.PutArguments{} + err = datamodel.Rebind(datamodel.NewAny(putInv.Arguments()), &putArgs) + if err != nil { + log.Error("failed to rebind HTTP PUT arguments", zap.Error(err)) + return fmt.Errorf("rebinding HTTP PUT arguments: %w", err) + } + + allocRcpt, err := agentStore.GetReceipt(req.Context(), putArgs.Destination.Task) + if err != nil { + log.Error("failed to get receipt for allocation", zap.Error(err)) + return fmt.Errorf("getting receipt for allocation: %w", err) + } + + allocInv, err := agentStore.GetInvocation(req.Context(), putArgs.Destination.Task) + if err != nil { + log.Error("failed to get invocation for allocation", zap.Error(err)) + return fmt.Errorf("getting invocation for allocation: %w", err) + } + + res.SetMetadata(container.New( + container.WithInvocations(allocInv, putInv, accInv), + container.WithReceipts(allocRcpt, putRcpt, accRcpt), + )) + + return res.SetSuccess(addOK) + } + + cause := req.Invocation().Task().Link() + proofStore := ucan_server.NewContainerProofStore(req.Metadata()) + provider, allocInv, allocRcpt, allocOK, err := doAllocate(req.Context(), router, nodeProvider, agentStore, space, blob, cause, proofStore, log) + if err != nil { + if errors.Is(err, routing.ErrCandidateUnavailable) { + return res.SetFailure(routing.ErrCandidateUnavailable) + } + log.Error("allocation failed", zap.Error(err)) + return fmt.Errorf("allocating space: %w", err) + } + log = log.With(zap.Stringer("provider", provider.ID.DID())) + + putInv, putRcpt, err := genPut(blob, allocInv, allocOK, log) + if err != nil { + log.Error("failed to generate put invocation", zap.Error(err)) + return fmt.Errorf("generating put invocation: %w", err) + } + + accInv, accRcpt, err := maybeAccept(req.Context(), agentStore, blobRegistry, nodeProvider, provider, space, blob, cause, putInv, putRcpt, proofStore, log) + if err != nil { + return err + } + + metaOpts := []container.Option{container.WithInvocations(allocInv, putInv, accInv)} + for _, rcpt := range []ucan.Receipt{allocRcpt, putRcpt, accRcpt} { + if rcpt != nil { + metaOpts = append(metaOpts, container.WithReceipts(rcpt)) + } + } + res.SetMetadata(container.New(metaOpts...)) + + return res.SetSuccess(&blobcaps.AddOK{ + Site: promise.AwaitOK{ + Task: accInv.Task().Link(), + }, + }) + }), + } +} + +func doAllocate( + ctx context.Context, + router *routing.Service, + nodeProvider piriclient.Provider, + agentStore agent.Store, + space did.DID, + blob blobcaps.Blob, + cause ucan.Link, + proofStore ucan_server.ProofStore, + logger *zap.Logger, +) (routing.StorageProviderInfo, ucan.Invocation, ucan.Receipt, blobcaps.AllocateOK, error) { + log := logger.With(zap.Stringer("cause", cause)) + log.Debug("doing allocation") + + var exclusions []ucan.Principal + for { + candidate, err := router.SelectStorageProvider(ctx, blob, routing.WithExclusions(exclusions...)) + if err != nil { + log.Error("failed to select storage node", zap.Error(err)) + return routing.StorageProviderInfo{}, nil, nil, blobcaps.AllocateOK{}, err + } + log := logger.With(zap.Stringer("candidate", candidate.ID.DID()), zap.String("endpoint", candidate.Endpoint.String())) + log.Debug("selected storage provider candidate") + + client, err := nodeProvider.Client(candidate.ID, candidate.Endpoint) + if err != nil { + log.Error("failed to create piri node", zap.Error(err)) + return routing.StorageProviderInfo{}, nil, nil, blobcaps.AllocateOK{}, err + } + + res, inv, rcpt, err := client.Allocate(ctx, &piriclient.AllocateRequest{ + Space: space, + Digest: blob.Digest, + Size: blob.Size, + Cause: cause, + }, proofStore) + if err != nil { + log.Warn("failed to allocate blob", zap.Error(err)) + exclusions = append(exclusions, candidate.ID) + continue + } + + err = writeAgentMessage(ctx, agentStore, []ucan.Invocation{inv}, []ucan.Receipt{rcpt}) + if err != nil { + log.Error("failed to write agent message", zap.Error(err)) + exclusions = append(exclusions, candidate.ID) + continue + } + + return candidate, inv, rcpt, *res, nil + } +} + +// TODO(ash): move this into the client +func writeAgentMessage(ctx context.Context, agentStore agent.Store, invs []ucan.Invocation, rcpts []ucan.Receipt) error { + msg := container.New(container.WithInvocations(invs...), container.WithReceipts(rcpts...)) + idx := agent.Index(msg) + return agentStore.Write(ctx, msg, idx) +} + +// Generates an invocation to put the blob to the storage provider. It MAY +// return a receipt if the allocation result indicates that the provider already +// has the blob. +func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.AllocateOK, logger *zap.Logger) (ucan.Invocation, ucan.Receipt, error) { + log := logger + log.Debug("generating put invocation") + + // Derive the principal that will provide the blob from the blob digest. + // we do this so that any actor with a blob could issue a receipt for the + // `/http/put` invocation. + blobProvider, err := deriveDID(blob.Digest) + if err != nil { + return nil, nil, err + } + + putInv, err := httpcaps.Put.Invoke( + blobProvider, + blobProvider, + &httpcaps.PutArguments{ + Body: blob, + Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, + }, + invocation.WithAudience(blobProvider), + // We encode the keys for the blob provider principal that can be used + // by the client to use in order to sign a receipt. Client could + // actually derive the same principal from the blob digest like we did + // above, however by embedding the keys we make API more flexible and + // could in the future generate one-off principals instead. + invocation.WithMetadata( + datamodel.Map{ + "keys": datamodel.Map{ + "id": blobProvider.DID().String(), + "keys": datamodel.Map{ + blobProvider.DID().String(): blobProvider.Bytes(), + }, + }, + }, + ), + ) + if err != nil { + return nil, nil, fmt.Errorf("invoking %q: %w", httpcaps.PutCommand, err) + } + + var putRcpt ucan.Receipt + + // If no address was provided we have a blob in store already and we can issue + // a receipt for the `/http/put` without requiring blob to be provided. + if allocOK.Address == nil { + log.Debug("blob present on provider, issuing receipt for put") + var ok datamodel.Map + err = datamodel.Rebind(&httpcaps.PutOK{}, &ok) + if err != nil { + return nil, nil, fmt.Errorf("rebinding %q OK: %w", httpcaps.PutCommand, err) + } + putRcpt, err = receipt.Issue( + blobProvider, + putInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](ok), + ) + if err != nil { + return nil, nil, fmt.Errorf("issuing %q receipt: %w", httpcaps.PutCommand, err) + } + } + + return putInv, putRcpt, nil +} + +// Derives did:key principal from (blob) multihash that can be used to +// sign ucan invocations/receipts for the the subject (blob) multihash. +func deriveDID(digest multihash.Multihash) (principal.Signer, error) { + if len(digest) < ed25519.SeedSize { + return nil, fmt.Errorf("expected []byte with length %d, got %d", ed25519.SeedSize, len(digest)) + } + seed := digest[len(digest)-ed25519.SeedSize:] + return ed25519signer.FromRaw(seed) +} + +// maybeAccept generates and possibly executes a `/blob/accept` invocation if +// the provided put receipt is non-nil and non-failure. +func maybeAccept( + ctx context.Context, + agentStore agent.Store, + blobRegistry blobregistry.Store, + nodeProvider piriclient.Provider, + providerInfo routing.StorageProviderInfo, + space ucan.Principal, + blob blobcaps.Blob, + cause ucan.Link, // original /space/blob/add task + putInv ucan.Invocation, + putRcpt ucan.Receipt, + proofStore ucan_server.ProofStore, + logger *zap.Logger, +) (ucan.Invocation, ucan.Receipt, error) { + log := logger + log.Debug("generating accept invocation") + + c, err := nodeProvider.Client(providerInfo.ID, providerInfo.Endpoint) + if err != nil { + log.Error("failed to create piri client for accept", zap.Error(err)) + return nil, nil, err + } + + accReq := piriclient.AcceptRequest{ + Space: space.DID(), + Digest: blob.Digest, + Size: blob.Size, + Put: putInv.Link(), + } + + accInv, _, _, err := c.AcceptInvocation(ctx, &accReq, proofStore, invocation.WithNoNonce()) + if err != nil { + log.Error("failed to create accept invocation", zap.Error(err)) + return nil, nil, err + } + + var accRcpt ucan.Receipt + + // If put has already succeeded, we can execute `/blob/accept` right away. + if putRcpt != nil { + _, x := result.Unwrap(putRcpt.Out()) + if x == nil { + res, inv, rcpt, err := c.Accept(ctx, &accReq, proofStore, invocation.WithNoNonce()) + if err != nil { + log.Error("failed to execute accept on piri", zap.Error(err)) + return nil, nil, err + } + log.Debug("blob accepted", zap.Stringer("site", res.Site)) + + err = writeAgentMessage(ctx, agentStore, []ucan.Invocation{inv}, []ucan.Receipt{rcpt}) + if err != nil { + log.Error("failed to write agent message for accept", zap.Error(err)) + return nil, nil, err + } + + err = blobRegistry.Register(ctx, space.DID(), blob, cause) + if err != nil { + log.Error("failed to register blob", zap.Error(err)) + return nil, nil, err + } + + accInv = inv + accRcpt = rcpt + } + } + + return accInv, accRcpt, nil +} diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go new file mode 100644 index 0000000..2fbf069 --- /dev/null +++ b/pkg/service/handlers/blob_add_test.go @@ -0,0 +1,520 @@ +package handlers_test + +import ( + "context" + "crypto/ed25519" + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/fil-forge/libforge/capabilities" + blobcaps "github.com/fil-forge/libforge/capabilities/blob" + httpcaps "github.com/fil-forge/libforge/capabilities/http" + "github.com/fil-forge/libforge/didmailto" + "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/sprue/pkg/identity" + "github.com/fil-forge/sprue/pkg/piriclient" + "github.com/fil-forge/sprue/pkg/provisioning" + "github.com/fil-forge/sprue/pkg/routing" + "github.com/fil-forge/sprue/pkg/service/handlers" + "github.com/fil-forge/sprue/pkg/store/agent" + agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" + blob_registry "github.com/fil-forge/sprue/pkg/store/blob_registry/memory" + consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" + metrics_store "github.com/fil-forge/sprue/pkg/store/metrics/memory" + spacediff_store "github.com/fil-forge/sprue/pkg/store/space_diff/memory" + storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" + subscription_store "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/ipld/codec/dagcbor" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + ed25519signer "github.com/fil-forge/ucantone/principal/ed25519" + "github.com/fil-forge/ucantone/principal/signer" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/server" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/ucan/promise" + "github.com/fil-forge/ucantone/ucan/receipt" + "github.com/fil-forge/ucantone/validator" + "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +type blobAddTestDeps struct { + handler handlers.Handler + consumerStore *consumer_store.Store + subscriptionStore *subscription_store.Store + spStore *storage_provider_store.Store + agentStore *agent_store.Store + blobReg *blob_registry.Store +} + +func newBlobAddTestDeps(t *testing.T, uploadService principal.Signer, logger *zap.Logger) *blobAddTestDeps { + t.Helper() + consumerStore := consumer_store.New() + subscriptionStore := subscription_store.New() + provisioningSvc := provisioning.NewService([]did.DID{uploadService.DID()}, consumerStore, subscriptionStore) + spStore := storage_provider_store.New() + router := routing.NewService(spStore, logger) + agentStore := agent_store.New() + blobReg := blob_registry.New( + spacediff_store.New(), + consumerStore, + metrics_store.NewSpaceStore(), + metrics_store.New(), + ) + nodeProvider := piriclient.NewProvider(uploadService, logger) + handler := handlers.NewBlobAddHandler( + &identity.Identity{Signer: uploadService}, + provisioningSvc, + router, + nodeProvider, + agentStore, + blobReg, + logger, + ) + return &blobAddTestDeps{ + handler: handler, + consumerStore: consumerStore, + subscriptionStore: subscriptionStore, + spStore: spStore, + agentStore: agentStore, + blobReg: blobReg, + } +} + +// provisionSpace adds the consumer record so provisioningSvc.ListServiceProviders +// returns the upload service for the space. +func provisionSpace(t *testing.T, deps *blobAddTestDeps, uploadService principal.Signer, space did.DID) { + t.Helper() + account := testutil.Must(didmailto.New("alice@example.com"))(t) + err := deps.consumerStore.Add( + context.Background(), + uploadService.DID(), + space, + account, + "sub-1", + testutil.RandomCID(t), + ) + require.NoError(t, err) +} + +// newMockPiriServer stands up a UCAN HTTP server that handles /blob/allocate & +// /blob/accept by returning the canned responses. Wraps the upload service's +// did:web identity so signatures verify against the underlying did:key. +func newMockPiriServer( + t *testing.T, + storageProvider principal.Signer, + uploadService principal.Signer, + allocateOK *blobcaps.AllocateOK, + acceptOK *blobcaps.AcceptOK, +) *httptest.Server { + t.Helper() + + resolveDIDKey := func(ctx context.Context, d did.DID) ([]did.DID, error) { + if d == uploadService.DID() { + if w, ok := uploadService.(signer.Unwrapper); ok { + return []did.DID{w.Unwrap().DID()}, nil + } + } + return validator.FailDIDKeyResolution(ctx, d) + } + + srv := server.NewHTTP( + storageProvider, + server.WithValidationOptions(validator.WithDIDResolver(resolveDIDKey)), + ) + + srv.Handle(blobcaps.Allocate, bindexec.NewHandler(func( + req *bindexec.Request[*blobcaps.AllocateArguments], + res *bindexec.Response[*blobcaps.AllocateOK], + ) error { + return res.SetSuccess(allocateOK) + })) + + srv.Handle(blobcaps.Accept, bindexec.NewHandler(func( + req *bindexec.Request[*blobcaps.AcceptArguments], + res *bindexec.Response[*blobcaps.AcceptOK], + ) error { + return res.SetSuccess(acceptOK) + })) + + httpSrv := httptest.NewServer(srv) + t.Cleanup(httpSrv.Close) + return httpSrv +} + +func TestBlobAddHandler(t *testing.T) { + logger := zaptest.NewLogger(t) + ctx := t.Context() + + uploadService := testutil.WebService + + t.Run("no providers for space", func(t *testing.T) { + deps := newBlobAddTestDeps(t, uploadService, logger) + + space := testutil.RandomSigner(t) + args := blobcaps.AddArguments{ + Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + } + + inv, err := blobcaps.Add.Invoke( + testutil.Alice, + space, + &args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = deps.handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, handlers.InsufficientStorageErrorName, model.Name()) + }) + + t.Run("no candidates available", func(t *testing.T) { + deps := newBlobAddTestDeps(t, uploadService, logger) + + space := testutil.RandomSigner(t) + provisionSpace(t, deps, uploadService, space.DID()) + + // No storage providers in spStore — the router will return ErrCandidateUnavailable. + args := blobcaps.AddArguments{ + Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + } + + inv, err := blobcaps.Add.Invoke( + testutil.Alice, + space, + &args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = deps.handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, routing.CandidateUnavailableErrorName, model.Name()) + }) + + t.Run("zero weight providers returns candidate unavailable", func(t *testing.T) { + deps := newBlobAddTestDeps(t, uploadService, logger) + + space := testutil.RandomSigner(t) + provisionSpace(t, deps, uploadService, space.DID()) + + // Register a storage provider with weight 0 — it'll be filtered out. + storageProvider := testutil.RandomSigner(t) + endpoint := testutil.Must(url.Parse("https://piri.example.com"))(t) + err := deps.spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, nil) + require.NoError(t, err) + + args := blobcaps.AddArguments{ + Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + } + + inv, err := blobcaps.Add.Invoke( + testutil.Alice, + space, + &args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = deps.handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, routing.CandidateUnavailableErrorName, model.Name()) + }) + + t.Run("successful allocation with address", func(t *testing.T) { + deps := newBlobAddTestDeps(t, uploadService, logger) + + space := testutil.RandomSigner(t) + provisionSpace(t, deps, uploadService, space.DID()) + + storageProvider := testutil.RandomSigner(t) + putURL := testutil.Must(url.Parse("https://storage.example.com/put"))(t) + allocateOK := &blobcaps.AllocateOK{ + Size: 1024, + Address: &blobcaps.BlobAddress{ + URL: capabilities.CborURL(*putURL), + Headers: map[string]string{}, + Expires: time.Now().Add(time.Hour).Unix(), + }, + } + // Accept handler is registered but should not be invoked when an Address is + // returned — the put receipt isn't issued, so maybeAccept skips Accept. + acceptOK := &blobcaps.AcceptOK{Site: testutil.RandomCID(t)} + + piriSrv := newMockPiriServer(t, storageProvider, uploadService, allocateOK, acceptOK) + piriURL := testutil.Must(url.Parse(piriSrv.URL))(t) + + err := deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil) + require.NoError(t, err) + + args := blobcaps.AddArguments{ + Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + } + + inv, err := blobcaps.Add.Invoke( + testutil.Alice, + space, + &args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + + // Authorize the upload service to invoke /blob/allocate and /blob/accept + // on the space. This is the proof chain the upload service forwards to the + // storage provider. + allocProof := testutil.Must(delegation.Delegate(space, uploadService, space, blobcaps.AllocateCommand))(t) + acceptProof := testutil.Must(delegation.Delegate(space, uploadService, space, blobcaps.AcceptCommand))(t) + + req := execution.NewRequest(ctx, inv, execution.WithDelegations(allocProof, acceptProof)) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = deps.handler.Handler(req, res) + require.NoError(t, err) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, o) + + // Response metadata should carry the allocate, put, and accept invocations. + require.NotNil(t, res.Metadata()) + require.NotEmpty(t, res.Metadata().Invocations()) + }) + + t.Run("successful allocation blob already stored", func(t *testing.T) { + deps := newBlobAddTestDeps(t, uploadService, logger) + + space := testutil.RandomSigner(t) + provisionSpace(t, deps, uploadService, space.DID()) + + storageProvider := testutil.RandomSigner(t) + // No address signals the blob is already on the provider — the handler + // then issues the put receipt itself and proceeds to Accept on piri. + allocateOK := &blobcaps.AllocateOK{Size: 1024, Address: nil} + acceptOK := &blobcaps.AcceptOK{Site: testutil.RandomCID(t)} + + piriSrv := newMockPiriServer(t, storageProvider, uploadService, allocateOK, acceptOK) + piriURL := testutil.Must(url.Parse(piriSrv.URL))(t) + + err := deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil) + require.NoError(t, err) + + args := blobcaps.AddArguments{ + Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + } + + inv, err := blobcaps.Add.Invoke( + testutil.Alice, + space, + &args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + + allocProof := testutil.Must(delegation.Delegate(space, uploadService, space, blobcaps.AllocateCommand))(t) + acceptProof := testutil.Must(delegation.Delegate(space, uploadService, space, blobcaps.AcceptCommand))(t) + + req := execution.NewRequest(ctx, inv, execution.WithDelegations(allocProof, acceptProof)) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = deps.handler.Handler(req, res) + require.NoError(t, err) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, o) + + // Both invocations and receipts should be in the metadata since accept ran. + require.NotNil(t, res.Metadata()) + require.NotEmpty(t, res.Metadata().Invocations()) + require.NotEmpty(t, res.Metadata().Receipts()) + }) + + t.Run("blob already registered in space", func(t *testing.T) { + deps := newBlobAddTestDeps(t, uploadService, logger) + + space := testutil.RandomSigner(t) + provisionSpace(t, deps, uploadService, space.DID()) + + storageProvider := testutil.RandomSigner(t) + digest := testutil.RandomMultihash(t) + blob := blobcaps.Blob{Digest: digest, Size: 1024} + + // Build the chain that the handler will walk back through: + // addRcpt → accInv/accRcpt → putInv/putRcpt → allocInv/allocRcpt + blobProvider := deriveBlobProvider(t, digest) + + // /blob/allocate + allocInv := testutil.Must(blobcaps.Allocate.Invoke( + uploadService, + space, + &blobcaps.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, + invocation.WithAudience(storageProvider), + ))(t) + allocOK := mustRebindMap(t, &blobcaps.AllocateOK{Size: blob.Size}) + allocRcpt := testutil.Must(receipt.Issue( + storageProvider, + allocInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](allocOK), + ))(t) + + // /http/put — issued by the principal derived from the blob digest. + putInv := testutil.Must(httpcaps.Put.Invoke( + blobProvider, + blobProvider, + &httpcaps.PutArguments{ + Body: blob, + Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, + }, + invocation.WithAudience(blobProvider), + ))(t) + putOK := mustRebindMap(t, &httpcaps.PutOK{}) + putRcpt := testutil.Must(receipt.Issue( + blobProvider, + putInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](putOK), + ))(t) + + // /blob/accept + accInv := testutil.Must(blobcaps.Accept.Invoke( + uploadService, + space, + &blobcaps.AcceptArguments{ + Blob: blob, + Put: promise.AwaitOK{Task: putInv.Task().Link()}, + }, + invocation.WithAudience(storageProvider), + ))(t) + accOK := mustRebindMap(t, &blobcaps.AcceptOK{Site: testutil.RandomCID(t)}) + accRcpt := testutil.Must(receipt.Issue( + storageProvider, + accInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](accOK), + ))(t) + + // The original /space/blob/add invocation and receipt — its receipt's + // task CID is what gets stored in the registry as the cause. + prevAddInv := testutil.Must(blobcaps.Add.Invoke( + testutil.Alice, + space, + &blobcaps.AddArguments{Blob: blob}, + invocation.WithAudience(uploadService), + ))(t) + addOK := mustRebindMap(t, &blobcaps.AddOK{ + Site: promise.AwaitOK{Task: accInv.Task().Link()}, + }) + prevAddRcpt := testutil.Must(receipt.Issue( + uploadService, + prevAddInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](addOK), + ))(t) + + // Persist the chain in the agent store and register the blob with cause + // pointing at the prior /blob/add invocation's task CID. + msg := container.New( + container.WithInvocations(allocInv, putInv, accInv, prevAddInv), + container.WithReceipts(allocRcpt, putRcpt, accRcpt, prevAddRcpt), + ) + require.NoError(t, deps.agentStore.Write(ctx, msg, agent.Index(msg))) + require.NoError(t, deps.blobReg.Register(ctx, space.DID(), blob, prevAddInv.Task().Link())) + + // Re-invoke /blob/add for the same blob/space — the handler should hit + // the already-registered short-circuit, walk the chain, and return the + // stored AddOK without contacting any storage provider. + inv := testutil.Must(blobcaps.Add.Invoke( + testutil.Alice, + space, + &blobcaps.AddArguments{Blob: blob}, + invocation.WithAudience(uploadService), + ))(t) + + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = deps.handler.Handler(req, res) + require.NoError(t, err) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, o) + + // The returned AddOK should match the one from the prior receipt. + gotAddOK := blobcaps.AddOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &gotAddOK)) + require.Equal(t, accInv.Task().Link(), gotAddOK.Site.Task) + + // Response metadata should carry all three invocations and all three + // receipts from the prior chain. + require.NotNil(t, res.Metadata()) + require.Len(t, res.Metadata().Invocations(), 3) + require.Len(t, res.Metadata().Receipts(), 3) + }) +} + +// deriveBlobProvider mirrors the production handler's logic for deriving a +// signer from a blob's digest, used to sign /http/put invocations and receipts. +func deriveBlobProvider(t *testing.T, digest multihash.Multihash) principal.Signer { + t.Helper() + require.GreaterOrEqual(t, len(digest), ed25519.SeedSize) + seed := digest[len(digest)-ed25519.SeedSize:] + s, err := ed25519signer.FromRaw(seed) + require.NoError(t, err) + return s +} + +// mustRebindMap rebinds a model into a datamodel.Map for use as receipt output — +// receipt.Issue can't marshal arbitrary struct pointers via ipld.Any. +func mustRebindMap(t *testing.T, model dagcbor.Marshaler) datamodel.Map { + t.Helper() + m := datamodel.Map{} + require.NoError(t, datamodel.Rebind(model, &m)) + return m +} diff --git a/pkg/service/handlers/blob_list.go b/pkg/service/handlers/blob_list.go new file mode 100644 index 0000000..cde15b9 --- /dev/null +++ b/pkg/service/handlers/blob_list.go @@ -0,0 +1,55 @@ +package handlers + +import ( + "fmt" + + blobcaps "github.com/fil-forge/libforge/capabilities/blob" + blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" + "github.com/fil-forge/ucantone/execution/bindexec" + "go.uber.org/zap" +) + +func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", blobcaps.ListCommand)) + return Handler{ + Capability: blobcaps.List, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*blobcaps.ListArguments], + res *bindexec.Response[*blobcaps.ListOK], + ) error { + args := req.Task().BindArguments() + space := req.Invocation().Subject() + log := log.With(zap.Stringer("space", space.DID())) + + var opts []blobregistry.ListOption + if args.Size != nil { + log = log.With(zap.Int64("size", *args.Size)) + opts = append(opts, blobregistry.WithListLimit(int(*args.Size))) + } + if args.Cursor != nil { + log = log.With(zap.String("cursor", *args.Cursor)) + opts = append(opts, blobregistry.WithListCursor(*args.Cursor)) + } + log.Debug("listing blobs") + + page, err := blobRegistry.List(req.Context(), space.DID(), opts...) + if err != nil { + log.Error("failed to list blobs", zap.Error(err)) + return fmt.Errorf("listing blobs: %w", err) + } + + results := make([]blobcaps.ListBlobItem, 0, len(page.Results)) + for _, r := range page.Results { + results = append(results, blobcaps.ListBlobItem{ + Blob: r.Blob, + InsertedAt: r.InsertedAt.Unix(), + }) + } + + return res.SetSuccess(&blobcaps.ListOK{ + Cursor: page.Cursor, + Results: results, + }) + }), + } +} diff --git a/pkg/service/handlers/blob_list_test.go b/pkg/service/handlers/blob_list_test.go new file mode 100644 index 0000000..566f134 --- /dev/null +++ b/pkg/service/handlers/blob_list_test.go @@ -0,0 +1,203 @@ +package handlers_test + +import ( + "context" + "testing" + + blobcaps "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/didmailto" + "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/sprue/pkg/service/handlers" + blob_registry "github.com/fil-forge/sprue/pkg/store/blob_registry/memory" + consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" + metrics_store "github.com/fil-forge/sprue/pkg/store/metrics/memory" + spacediff_store "github.com/fil-forge/sprue/pkg/store/space_diff/memory" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func newBlobRegistry(t *testing.T) (*blob_registry.Store, *consumer_store.Store) { + t.Helper() + consumerStore := consumer_store.New() + return blob_registry.New( + spacediff_store.New(), + consumerStore, + metrics_store.NewSpaceStore(), + metrics_store.New(), + ), consumerStore +} + +// invokeBlobList builds the invocation/request/response trio used by every +// subtest below. +func invokeBlobList( + t *testing.T, + ctx context.Context, + agent principal.Signer, + uploadService principal.Signer, + space principal.Signer, + args *blobcaps.ListArguments, +) (execution.Request, *execution.ExecResponse) { + t.Helper() + inv, err := blobcaps.List.Invoke( + agent, + space, + args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + return req, res +} + +func TestBlobListHandler(t *testing.T) { + logger := zaptest.NewLogger(t) + ctx := t.Context() + + uploadService := testutil.WebService + alice := testutil.Alice + aliceAccount := testutil.Must(didmailto.New("alice@example.com"))(t) + + t.Run("empty list", func(t *testing.T) { + blobReg, _ := newBlobRegistry(t) + handler := handlers.NewBlobListHandler(blobReg, logger) + + space := testutil.RandomSigner(t) + + req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{}) + + err := handler.Handler(req, res) + require.NoError(t, err) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, o) + + ok := blobcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.Empty(t, ok.Results) + }) + + t.Run("lists blobs", func(t *testing.T) { + blobReg, consumerStore := newBlobRegistry(t) + handler := handlers.NewBlobListHandler(blobReg, logger) + + space := testutil.RandomSigner(t) + require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) + + blob1 := blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 100} + blob2 := blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 200} + require.NoError(t, blobReg.Register(ctx, space.DID(), blob1, testutil.RandomCID(t))) + require.NoError(t, blobReg.Register(ctx, space.DID(), blob2, testutil.RandomCID(t))) + + req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{}) + + err := handler.Handler(req, res) + require.NoError(t, err) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + ok := blobcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.Len(t, ok.Results, 2) + }) + + t.Run("with size limit", func(t *testing.T) { + blobReg, consumerStore := newBlobRegistry(t) + handler := handlers.NewBlobListHandler(blobReg, logger) + + space := testutil.RandomSigner(t) + require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) + + for i := range 3 { + require.NoError(t, blobReg.Register( + ctx, space.DID(), + blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: uint64(i + 1)}, + testutil.RandomCID(t), + )) + } + + size := int64(2) + req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Size: &size}) + + err := handler.Handler(req, res) + require.NoError(t, err) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + ok := blobcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.Len(t, ok.Results, 2) + require.NotNil(t, ok.Cursor) + }) + + t.Run("with cursor pagination", func(t *testing.T) { + blobReg, consumerStore := newBlobRegistry(t) + handler := handlers.NewBlobListHandler(blobReg, logger) + + space := testutil.RandomSigner(t) + require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) + + for i := range 3 { + require.NoError(t, blobReg.Register( + ctx, space.DID(), + blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: uint64(i + 1)}, + testutil.RandomCID(t), + )) + } + + size := int64(1) + req1, res1 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Size: &size}) + require.NoError(t, handler.Handler(req1, res1)) + + o1, fail := result.Unwrap(res1.Receipt().Out()) + require.Nil(t, fail) + ok1 := blobcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o1), &ok1)) + require.Len(t, ok1.Results, 1) + require.NotNil(t, ok1.Cursor) + + // Second page using cursor. + cursor := *ok1.Cursor + req2, res2 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Cursor: &cursor, Size: &size}) + require.NoError(t, handler.Handler(req2, res2)) + + o2, fail := result.Unwrap(res2.Receipt().Out()) + require.Nil(t, fail) + ok2 := blobcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o2), &ok2)) + require.Len(t, ok2.Results, 1) + require.NotEqual(t, ok1.Results[0].Blob.Digest.HexString(), ok2.Results[0].Blob.Digest.HexString()) + }) + + t.Run("does not list blobs from other spaces", func(t *testing.T) { + blobReg, consumerStore := newBlobRegistry(t) + handler := handlers.NewBlobListHandler(blobReg, logger) + + space1 := testutil.RandomSigner(t) + space2 := testutil.RandomSigner(t) + require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space1.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) + + require.NoError(t, blobReg.Register( + ctx, space1.DID(), + blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 100}, + testutil.RandomCID(t), + )) + + // Query space2 — should be empty. + req, res := invokeBlobList(t, ctx, alice, uploadService, space2, &blobcaps.ListArguments{}) + require.NoError(t, handler.Handler(req, res)) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + ok := blobcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.Empty(t, ok.Results) + }) +} diff --git a/pkg/service/handlers/blob_replicate.go b/pkg/service/handlers/blob_replicate.go new file mode 100644 index 0000000..0fae0fd --- /dev/null +++ b/pkg/service/handlers/blob_replicate.go @@ -0,0 +1,477 @@ +package handlers + +// import ( +// "bytes" +// "context" +// "fmt" +// "slices" + +// "go.uber.org/zap" + +// "github.com/fil-forge/ucantone/did" +// "github.com/fil-forge/ucantone/errors" +// "github.com/multiformats/go-multihash" +// "github.com/storacha/go-libstoracha/capabilities/assert" +// blobreplicacap "github.com/storacha/go-libstoracha/capabilities/blob/replica" +// spaceblobcap "github.com/storacha/go-libstoracha/capabilities/space/blob" +// "github.com/storacha/go-libstoracha/capabilities/types" +// "github.com/storacha/go-libstoracha/digestutil" +// "github.com/storacha/go-ucanto/core/dag/blockstore" +// "github.com/storacha/go-ucanto/core/delegation" +// "github.com/storacha/go-ucanto/core/invocation" +// "github.com/storacha/go-ucanto/core/ipld" +// "github.com/storacha/go-ucanto/core/receipt" +// "github.com/storacha/go-ucanto/core/receipt/fx" +// "github.com/storacha/go-ucanto/core/result" +// "github.com/storacha/go-ucanto/core/result/failure" +// fdm "github.com/storacha/go-ucanto/core/result/failure/datamodel" +// "github.com/storacha/go-ucanto/server" +// "github.com/storacha/go-ucanto/ucan" +// "github.com/storacha/go-ucanto/validator" +// "github.com/fil-forge/sprue/internal/config" +// "github.com/fil-forge/sprue/pkg/identity" +// "github.com/fil-forge/sprue/pkg/internal/ipldutil" +// "github.com/fil-forge/sprue/pkg/piriclient" +// "github.com/fil-forge/sprue/pkg/routing" +// "github.com/fil-forge/sprue/pkg/store/agent" +// blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" +// "github.com/fil-forge/sprue/pkg/store/replica" +// ) + +// const ( +// // Too many or too few replicas were instructed. +// ReplicationCountRangeErrorName = "ReplicationCountRangeError" +// // There are not enough replication nodes available to replicate the data. +// ReplicationCandidateUnavailableErrorName = "ReplicationCandidateUnavailable" +// // Blob to replicate was not found in the space. +// ReplicationSourceNotFoundErrorName = "ReplicationSourceNotFound" +// // The location commitment was invalid in some way. For example, it has +// // expired, is revoked, had a signature that did not verify or referenced a +// // blob that was not requested to be replicated. +// InvalidReplicationSiteErrorName = "InvalidReplicationSite" +// ) + +// var ( +// ErrReplicationSourceNotFound = errors.New(ReplicationSourceNotFoundErrorName, "blob to replicate was not found in the space") +// ErrReplicationCandidateUnavailable = errors.New(ReplicationCandidateUnavailableErrorName, "no replication candidates available") +// ) + +// // WithSpaceBlobReplicateMethod registers the space/blob/replicate handler. +// func WithSpaceBlobReplicateMethod( +// cfg config.DeploymentConfig, +// id *identity.Identity, +// router *routing.Service, +// blobRegistry blobregistry.Store, +// replicaStore replica.Store, +// agentStore agent.Store, +// storageNode piriclient.Provider, +// logger *zap.Logger, +// ) server.Option { +// return server.WithServiceMethod( +// spaceblobcap.ReplicateAbility, +// server.Provide( +// spaceblobcap.Replicate, +// SpaceBlobReplicateHandler(cfg, id, router, blobRegistry, replicaStore, agentStore, storageNode, logger), +// ), +// ) +// } + +// func BlobReplicateHandler( +// cfg config.DeploymentConfig, +// id *identity.Identity, +// router *routing.Service, +// blobRegistry blobregistry.Store, +// replicaStore replica.Store, +// agentStore agent.Store, +// storageNode piriclient.Provider, +// logger *zap.Logger, +// ) server.HandlerFunc[spaceblobcap.ReplicateCaveats, spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure] { +// log := logger.With(zap.String("handler", spaceblobcap.ReplicateAbility)) +// return func(ctx context.Context, +// cap ucan.Capability[spaceblobcap.ReplicateCaveats], +// inv invocation.Invocation, +// iCtx server.InvocationContext, +// ) (result.Result[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure], fx.Effects, error) { +// space, err := did.Parse(cap.With()) +// if err != nil { +// return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( +// errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), +// ), nil, nil +// } +// blob := cap.Nb().Blob +// replicas := cap.Nb().Replicas + +// log := log.With( +// zap.Stringer("space", space), +// zap.Dict( +// "blob", +// zap.String("digest", digestutil.Format(blob.Digest)), +// zap.Uint64("size", blob.Size), +// ), +// zap.Uint("replicas", replicas), +// ) +// log.Debug("replicating blob") + +// if replicas > cfg.MaxReplicas { +// log.Warn("replication count out of range") +// return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( +// errors.New(ReplicationCountRangeErrorName, "requested number of replicas is greater than maximum: %d", cfg.MaxReplicas), +// ), nil, nil +// } + +// _, err = blobRegistry.Get(ctx, space, blob.Digest) +// if err != nil { +// if errors.Is(err, blobregistry.ErrEntryNotFound) { +// log.Warn("replication source not found in space") +// return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( +// ErrReplicationSourceNotFound, +// ), nil, nil +// } +// log.Error("failed to get blob registration") +// return nil, nil, fmt.Errorf("getting blob registration: %w", err) +// } + +// // check if we have any active replications +// records, err := replicaStore.List(ctx, space, blob.Digest) +// if err != nil { +// log.Error("failed to list replicas", zap.Error(err)) +// return nil, nil, fmt.Errorf("listing replicas: %w", err) +// } + +// // TODO: handle the case where a receipt was not received and the replica +// // still exists in "allocated", but has actually timed out/failed. + +// var activeReplicas []replica.Record +// var failedReplicas []replica.Record + +// var allocTasks []invocation.Invocation +// var allocReceipts []receipt.AnyReceipt +// var transferTasks []invocation.Invocation +// var transferReceipts []receipt.AnyReceipt + +// for _, r := range records { +// if r.Status == replica.Failed { +// failedReplicas = append(failedReplicas, r) +// } else { +// detail, err := replicaFxDetail(ctx, agentStore, r, logger) +// if err != nil { +// log.Error("failed to get replica details", zap.Error(err)) +// return nil, nil, fmt.Errorf("getting replica details: %w", err) +// } +// activeReplicas = append(activeReplicas, r) +// allocTasks = append(allocTasks, detail.allocate.invocation) +// allocReceipts = append(allocReceipts, detail.allocate.receipt) +// if detail.transfer != nil { +// transferTasks = append(transferTasks, detail.transfer.invocation) +// if detail.transfer.receipt != nil { +// transferReceipts = append(transferReceipts, detail.transfer.receipt) +// } +// } +// } +// } + +// // Note: We +1 below to include the source blob, which is not recorded in +// // the replicas table. +// newReplicasCount := int(replicas) - (len(activeReplicas) + 1) + +// // TODO: support reducing the number of replicas +// if newReplicasCount < 0 { +// return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( +// errors.New(ReplicationCountRangeErrorName, "reducing replica count not implemented"), +// ), nil, nil +// } + +// // lets allocate some replicas! +// if newReplicasCount > 0 { +// blocks, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(inv.Blocks())) +// if err != nil { +// return nil, nil, fmt.Errorf("creating block reader: %w", err) +// } +// site := cap.Nb().Site +// lComm, location, err := extractLocationCommitment(space, blob.Digest, site, blocks) +// if err != nil { +// log.Warn("failed to extract location commitment", zap.Stringer("site", site), zap.Error(err)) +// return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( +// errors.New(InvalidReplicationSiteErrorName, "invalid location commitment: %s", err.Error()), +// ), nil, nil +// } +// _, err = validator.Claim( +// ctx, +// assert.Location, +// []delegation.Proof{delegation.FromDelegation(lComm)}, +// validator.NewClaimContext( +// id.Signer.Verifier(), +// iCtx.CanIssue, +// iCtx.ValidateAuthorization, +// iCtx.ResolveProof, +// iCtx.ParsePrincipal, +// iCtx.ResolveDIDKey, +// iCtx.ValidateTimeBounds, +// iCtx.AuthorityProofs()..., +// ), +// ) +// if err != nil { +// log.Warn("failed to authorize location commitment", zap.Stringer("site", site), zap.Error(err)) +// return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( +// errors.New(InvalidReplicationSiteErrorName, "unauthorized location commitment: %s", err.Error()), +// ), nil, nil +// } + +// urls := make([]string, 0, len(location.Location)) +// for _, url := range location.Location { +// urls = append(urls, url.String()) +// } +// siteLogFields := []zap.Field{zap.Stringer("root", site), zap.Strings("locations", urls)} +// if location.Range != nil { +// siteLogFields = append(siteLogFields, zap.Uint64("offset", location.Range.Offset)) +// if location.Range.Length != nil { +// siteLogFields = append(siteLogFields, zap.Uint64("length", *location.Range.Length)) +// } +// } +// log = log.With(zap.Dict("site", siteLogFields...)) +// log.Debug("allocating space to replicate blob") + +// // do not include any nodes where we already have replications +// var exclude []ucan.Principal +// for _, r := range activeReplicas { +// exclude = append(exclude, r.Provider) +// } + +// for range newReplicasCount { +// for { +// candidate, err := router.SelectReplicationProvider(ctx, lComm.Issuer(), blob, routing.WithExclusions(exclude...)) +// if err != nil { +// if errors.Is(err, routing.ErrCandidateUnavailable) { +// log.Warn("no replication candidates available") +// return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( +// ErrReplicationCandidateUnavailable, +// ), nil, nil +// } +// log.Error("failed to select replication provider", zap.Error(err)) +// return nil, nil, fmt.Errorf("selecting replication provider: %w", err) +// } + +// log.Debug("selected replication provider", zap.Stringer("provider", candidate.ID.DID())) +// client, err := storageNode.Client(candidate.ID, candidate.Endpoint) +// if err != nil { +// log.Error("failed to create storage node client", zap.Error(err)) +// return nil, nil, fmt.Errorf("creating storage node client: %w", err) +// } + +// allocRes, allocInv, allocRcpt, err := client.ReplicaAllocate(ctx, &piriclient.ReplicaAllocateRequest{ +// Space: space, +// Digest: blob.Digest, +// Size: blob.Size, +// Site: lComm, +// Cause: inv.Link(), +// }, delegationFetcher{proof: candidate.Proof}) +// if err != nil { +// log.Warn("failed to allocate replica", zap.Error(err)) +// exclude = append(exclude, candidate.ID) +// continue +// } + +// // record the invocation and the receipt, so we can retrieve it later +// // when we get a blob/replica/transfer receipt in ucan/conclude +// err = writeAgentMessage(ctx, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) +// if err != nil { +// log.Error("failed to write agent message", zap.Error(err)) +// return nil, nil, fmt.Errorf("writing agent message: %w", err) +// } + +// // write a replication record to the store +// firstTimeReplica := !slices.ContainsFunc(failedReplicas, func(r replica.Record) bool { +// return r.Provider.DID() == candidate.ID.DID() +// }) +// status := result.MatchResultR1( +// allocRcpt.Out(), +// func(o ipld.Node) replica.ReplicationStatus { +// return replica.Allocated +// }, +// func(x ipld.Node) replica.ReplicationStatus { +// return replica.Failed +// }, +// ) +// cause, err := ipldutil.ToCID(allocInv.Link()) +// if err != nil { +// return nil, nil, err +// } +// if firstTimeReplica { +// err = replicaStore.Add(ctx, space, blob.Digest, candidate.ID.DID(), status, cause) +// } else { +// err = replicaStore.Retry(ctx, space, blob.Digest, candidate.ID.DID(), status, cause) +// } +// if err != nil { +// log.Error("failed to store replica record", zap.Error(err)) +// return nil, nil, fmt.Errorf("storing replica record: %w", err) +// } + +// allocTasks = append(allocTasks, allocInv) +// allocReceipts = append(allocReceipts, allocRcpt) +// transferTasks = append(transferTasks, allocRes.Transfer) +// // exclude this provider from next candidate selection (in case there +// // are more replicas to be allocated). +// exclude = append(exclude, candidate.ID) +// break +// } +// } +// } + +// var res spaceblobcap.ReplicateOk +// for _, t := range transferTasks { +// res.Site = append(res.Site, types.Promise{ +// UcanAwait: types.Await{ +// Selector: blobreplicacap.AllocateSiteSelector, +// Link: t.Link(), +// }, +// }) +// } + +// forks := []fx.Effect{} +// for _, t := range allocTasks { +// forks = append(forks, fx.FromInvocation(t)) +// } +// for _, t := range transferTasks { +// forks = append(forks, fx.FromInvocation(t)) +// } +// for _, r := range allocReceipts { +// // as a temporary solution we fork all allocate effects that add inline +// // receipts so they can be delivered to the client. +// conclude, err := issueConclude(id.Signer, r) +// if err != nil { +// log.Error("failed to create conclude invocation for replica allocate receipt", zap.Error(err)) +// return nil, nil, fmt.Errorf("creating conclude invocation: %w", err) +// } +// forks = append(forks, fx.FromInvocation(conclude)) +// } +// for _, r := range transferReceipts { +// // as a temporary solution we fork all transfer effects that add inline +// // receipts so they can be delivered to the client. +// conclude, err := issueConclude(id.Signer, r) +// if err != nil { +// log.Error("failed to create conclude invocation for replica transfer receipt", zap.Error(err)) +// return nil, nil, fmt.Errorf("creating conclude invocation: %w", err) +// } +// forks = append(forks, fx.FromInvocation(conclude)) +// } + +// fx := fx.NewEffects(fx.WithFork(forks...)) + +// return result.Ok[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure](res), fx, nil +// } +// } + +// type transaction struct { +// invocation invocation.Invocation +// receipt receipt.AnyReceipt +// } + +// type replicaDetail struct { +// allocate transaction +// transfer *transaction +// } + +// // Retrieves details of effect chain for replica allocations. +// // +// // If the allocation failed (receipt in error) then the return value will not +// // include any details about the transfer. i.e. `transfer` will be `nil`. +// // +// // If the receipt for `blob/replica/transfer` was not yet received, it will not +// // be included in the return value. i.e. `transfer.receipt` will be `nil`. +// func replicaFxDetail(ctx context.Context, agentStore agent.Store, rec replica.Record, logger *zap.Logger) (replicaDetail, error) { +// log := logger.With(zap.Stringer("allocation", rec.Cause)) + +// allocRcpt, err := agentStore.GetReceipt(ctx, rec.Cause) +// if err != nil { +// log.Error("failed to get replica allocation receipt", zap.Error(err)) +// return replicaDetail{}, fmt.Errorf("getting allocation receipt: %w", err) +// } + +// // receipt typically contains invocation +// allocInv, ok := allocRcpt.Ran().Invocation() +// if !ok { +// allocInv, err = agentStore.GetInvocation(ctx, rec.Cause) +// if err != nil { +// log.Error("failed to get replica allocation invocation", zap.Error(err)) +// return replicaDetail{}, fmt.Errorf("getting allocation invocation: %w", err) +// } +// } + +// o, x := result.Unwrap(allocRcpt.Out()) +// // if allocation failed, we cannot provide details for transfer +// if x != nil { +// log.Error("cannot get transfer details because allocation failed", zap.Error(fdm.Bind(x))) +// return replicaDetail{ +// allocate: transaction{ +// invocation: allocInv, +// receipt: allocRcpt, +// }, +// }, nil +// } + +// allocOk, err := ipld.Rebind[blobreplicacap.AllocateOk](o, blobreplicacap.AllocateOkType(), types.Converters...) +// if err != nil { +// log.Error("failed to rebind allocation result", zap.Error(err)) +// return replicaDetail{}, fmt.Errorf("rebinding allocation result: %w", err) +// } + +// transferTask, err := ipldutil.ToCID(allocOk.Site.UcanAwait.Link) +// if err != nil { +// return replicaDetail{}, err +// } +// log = log.With(zap.Stringer("transfer", transferTask)) + +// var transferInv invocation.Invocation +// transferRcpt, err := agentStore.GetReceipt(ctx, transferTask) +// if err != nil { +// if !errors.Is(err, agent.ErrReceiptNotFound) { +// log.Error("failed to get replica transfer receipt", zap.Error(err)) +// return replicaDetail{}, fmt.Errorf("getting transfer receipt: %w", err) +// } +// log.Debug("transfer receipt not found, may still be in progress") +// } + +// if transferRcpt != nil { +// transferInv, _ = transferRcpt.Ran().Invocation() +// } +// if transferInv == nil { +// transferInv, err = agentStore.GetInvocation(ctx, transferTask) +// if err != nil { +// log.Error("failed to get replica transfer invocation", zap.Error(err)) +// return replicaDetail{}, fmt.Errorf("getting transfer invocation: %w", err) +// } +// } + +// return replicaDetail{ +// allocate: transaction{ +// invocation: allocInv, +// receipt: allocRcpt, +// }, +// transfer: &transaction{ +// invocation: transferInv, +// receipt: transferRcpt, +// }, +// }, nil +// } + +// func extractLocationCommitment(space did.DID, digest multihash.Multihash, root ipld.Link, blocks blockstore.BlockReader) (delegation.Delegation, assert.LocationCaveats, error) { +// lComm, err := delegation.NewDelegationView(root, blocks) +// if err != nil { +// return nil, assert.LocationCaveats{}, fmt.Errorf("creating location commitment: %w", err) +// } +// if len(lComm.Capabilities()) == 0 { +// return nil, assert.LocationCaveats{}, fmt.Errorf("missing capabilities") +// } +// match, err := assert.Location.Match(validator.NewSource(lComm.Capabilities()[0], lComm)) +// if err != nil { +// return nil, assert.LocationCaveats{}, fmt.Errorf("matching caveats: %w", err) +// } +// nb := match.Value().Nb() +// if nb.Space != space { +// return nil, assert.LocationCaveats{}, fmt.Errorf("space mismatch: expected %s, got %s", space, nb.Space) +// } +// if !bytes.Equal(nb.Content.Hash(), digest) { +// return nil, assert.LocationCaveats{}, fmt.Errorf("digest mismatch: expected %s, got %s", digestutil.Format(digest), digestutil.Format(nb.Content.Hash())) +// } +// return lComm, nb, nil +// } diff --git a/pkg/service/handlers/blob_replicate_test.go b/pkg/service/handlers/blob_replicate_test.go new file mode 100644 index 0000000..6c6841a --- /dev/null +++ b/pkg/service/handlers/blob_replicate_test.go @@ -0,0 +1,493 @@ +package handlers_test + +// import ( +// "context" +// "fmt" +// "net/url" +// "testing" +// "time" + +// "github.com/fil-forge/libforge/didmailto" +// "github.com/fil-forge/ucantone/did" +// cidlink "github.com/ipld/go-ipld-prime/linking/cid" +// "github.com/storacha/go-libstoracha/capabilities/assert" +// blobreplicacap "github.com/storacha/go-libstoracha/capabilities/blob/replica" +// spaceblobcap "github.com/storacha/go-libstoracha/capabilities/space/blob" +// "github.com/storacha/go-libstoracha/capabilities/types" +// uclient "github.com/storacha/go-ucanto/client" +// "github.com/storacha/go-ucanto/core/delegation" +// "github.com/storacha/go-ucanto/core/invocation" +// "github.com/storacha/go-ucanto/core/receipt/fx" +// "github.com/storacha/go-ucanto/core/result" +// "github.com/storacha/go-ucanto/core/result/failure" +// "github.com/storacha/go-ucanto/core/result/failure/datamodel" +// "github.com/storacha/go-ucanto/principal" +// "github.com/storacha/go-ucanto/principal/signer" +// "github.com/storacha/go-ucanto/server" +// "github.com/storacha/go-ucanto/ucan" +// "github.com/storacha/go-ucanto/validator" +// "github.com/fil-forge/sprue/internal/config" +// "github.com/fil-forge/sprue/internal/testutil" +// "github.com/fil-forge/sprue/pkg/identity" +// "github.com/fil-forge/sprue/pkg/piriclient" +// "github.com/fil-forge/sprue/pkg/routing" +// "github.com/fil-forge/sprue/pkg/service/handlers" +// agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" +// "github.com/fil-forge/sprue/pkg/store/replica" +// replica_store "github.com/fil-forge/sprue/pkg/store/replica/memory" +// storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" +// "github.com/stretchr/testify/require" +// "go.uber.org/zap" +// "go.uber.org/zap/zaptest" +// ) + +// func TestSpaceBlobReplicateHandler(t *testing.T) { +// logger := zaptest.NewLogger(t) +// ctx := t.Context() + +// alice := testutil.Alice +// aliceAccount := testutil.Must(didmailto.Parse("did:mailto:example.com:alice"))(t) +// uploadService := testutil.WebService + +// defaultCfg := config.DeploymentConfig{MaxReplicas: 3} + +// t.Run("invalid space DID", func(t *testing.T) { +// spStore := storage_provider_store.New() +// router := routing.NewService(spStore, logger) +// blobReg, _ := newBlobRegistry() +// replicaStore := replica_store.New() +// agentStore := agent_store.New() +// nodeProvider := piriclient.NewProvider(uploadService, logger) + +// handler := handlers.SpaceBlobReplicateHandler( +// defaultCfg, &identity.Identity{Signer: uploadService}, +// router, blobReg, replicaStore, agentStore, nodeProvider, logger, +// ) + +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// site := cidlink.Link{Cid: testutil.RandomCID(t)} + +// cap := ucan.NewCapability( +// spaceblobcap.ReplicateAbility, +// "not-a-did", +// spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 2, Site: site}, +// ) + +// inv, err := invocation.Invoke(alice, uploadService, cap) +// require.NoError(t, err) + +// res, _, err := handler(ctx, cap, inv, nil) +// require.NoError(t, err) + +// _, fail := result.Unwrap(res) +// require.NotNil(t, fail) + +// model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) +// require.NotNil(t, model.Name) +// require.Equal(t, handlers.InvalidSpaceErrorName, *model.Name) +// }) + +// t.Run("replicas exceeds max", func(t *testing.T) { +// spStore := storage_provider_store.New() +// router := routing.NewService(spStore, logger) +// blobReg, _ := newBlobRegistry() +// replicaStore := replica_store.New() +// agentStore := agent_store.New() +// nodeProvider := piriclient.NewProvider(uploadService, logger) + +// cfg := config.DeploymentConfig{MaxReplicas: 2} +// handler := handlers.SpaceBlobReplicateHandler( +// cfg, &identity.Identity{Signer: uploadService}, +// router, blobReg, replicaStore, agentStore, nodeProvider, logger, +// ) + +// space := testutil.RandomSigner(t) +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// site := cidlink.Link{Cid: testutil.RandomCID(t)} + +// cap := ucan.NewCapability( +// spaceblobcap.ReplicateAbility, +// space.DID().String(), +// spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 3, Site: site}, +// ) + +// inv, err := invocation.Invoke(alice, uploadService, cap) +// require.NoError(t, err) + +// res, _, err := handler(ctx, cap, inv, nil) +// require.NoError(t, err) + +// _, fail := result.Unwrap(res) +// require.NotNil(t, fail) + +// model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) +// require.NotNil(t, model.Name) +// require.Equal(t, handlers.ReplicationCountRangeErrorName, *model.Name) +// }) + +// t.Run("blob not found in space", func(t *testing.T) { +// spStore := storage_provider_store.New() +// router := routing.NewService(spStore, logger) +// blobReg, _ := newBlobRegistry() +// replicaStore := replica_store.New() +// agentStore := agent_store.New() +// nodeProvider := piriclient.NewProvider(uploadService, logger) + +// handler := handlers.SpaceBlobReplicateHandler( +// defaultCfg, &identity.Identity{Signer: uploadService}, +// router, blobReg, replicaStore, agentStore, nodeProvider, logger, +// ) + +// space := testutil.RandomSigner(t) +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// site := cidlink.Link{Cid: testutil.RandomCID(t)} + +// cap := ucan.NewCapability( +// spaceblobcap.ReplicateAbility, +// space.DID().String(), +// spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 2, Site: site}, +// ) + +// inv, err := invocation.Invoke(alice, uploadService, cap) +// require.NoError(t, err) + +// res, _, err := handler(ctx, cap, inv, nil) +// require.NoError(t, err) + +// _, fail := result.Unwrap(res) +// require.NotNil(t, fail) + +// model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) +// require.NotNil(t, model.Name) +// require.Equal(t, handlers.ReplicationSourceNotFoundErrorName, *model.Name) +// }) + +// t.Run("invalid location commitment", func(t *testing.T) { +// spStore := storage_provider_store.New() +// router := routing.NewService(spStore, logger) +// blobReg, consumerStore := newBlobRegistry() +// replicaStore := replica_store.New() +// agentStore := agent_store.New() +// nodeProvider := piriclient.NewProvider(uploadService, logger) + +// handler := handlers.SpaceBlobReplicateHandler( +// defaultCfg, &identity.Identity{Signer: uploadService}, +// router, blobReg, replicaStore, agentStore, nodeProvider, logger, +// ) + +// space := testutil.RandomSigner(t) + +// // provision the space +// err := consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) +// require.NoError(t, err) + +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// // random CID is not a valid location commitment delegation +// site := cidlink.Link{Cid: testutil.RandomCID(t)} + +// // register the blob in the space +// err = blobReg.Register(ctx, space.DID(), blob, testutil.RandomCID(t)) +// require.NoError(t, err) + +// cap := ucan.NewCapability( +// spaceblobcap.ReplicateAbility, +// space.DID().String(), +// spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 2, Site: site}, +// ) + +// inv, err := invocation.Invoke(alice, uploadService, cap) +// require.NoError(t, err) + +// res, _, err := handler(ctx, cap, inv, nil) +// require.NoError(t, err) + +// _, fail := result.Unwrap(res) +// require.NotNil(t, fail) + +// model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) +// require.NotNil(t, model.Name) +// require.Equal(t, handlers.InvalidReplicationSiteErrorName, *model.Name) +// }) + +// t.Run("successful replication with new allocation", func(t *testing.T) { +// uploadID := testutil.Must(identity.New(""))(t) +// uploadService := uploadID.Signer + +// // primary storage provider (already has the blob) +// primaryProvider := testutil.RandomSigner(t) + +// // two replication providers (will each receive a replica) +// replicaProviderA := testutil.RandomSigner(t) +// replicaProviderAURL := testutil.Must(url.Parse("https://replica-a.example.com"))(t) +// replicaProviderAProof := delegateReplicaProviderProof(t, replicaProviderA, uploadService) + +// replicaProviderB := testutil.RandomSigner(t) +// replicaProviderBURL := testutil.Must(url.Parse("https://replica-b.example.com"))(t) +// replicaProviderBProof := delegateReplicaProviderProof(t, replicaProviderB, uploadService) + +// // register both replication providers in routing +// spStore := storage_provider_store.New() +// err := spStore.Put(ctx, *replicaProviderAURL, replicaProviderAProof, 100, nil) +// require.NoError(t, err) +// err = spStore.Put(ctx, *replicaProviderBURL, replicaProviderBProof, 100, nil) +// require.NoError(t, err) + +// router := routing.NewService(spStore, logger) +// blobReg, consumerStore := newBlobRegistry() +// replicaStore := replica_store.New() +// agentStore := agent_store.New() + +// // track which providers received allocations to verify exclusion +// var allocatedProviders []did.DID + +// // mock handler for blob/replica/allocate +// replicaAllocHandler := func( +// ctx context.Context, +// cap ucan.Capability[blobreplicacap.AllocateCaveats], +// inv invocation.Invocation, +// iCtx server.InvocationContext, +// ) (result.Result[blobreplicacap.AllocateOk, failure.IPLDBuilderFailure], fx.Effects, error) { +// allocatedProviders = append(allocatedProviders, iCtx.ID().DID()) + +// // create a transfer invocation to include in the response +// transferCap := ucan.NewCapability( +// "blob/replica/transfer", +// cap.With(), +// ucan.NoCaveats{}, +// ) +// transferInv, err := invocation.Invoke(iCtx.ID(), iCtx.ID(), transferCap) +// if err != nil { +// return nil, nil, err +// } + +// ok := blobreplicacap.AllocateOk{ +// Size: cap.Nb().Blob.Size, +// Site: types.Promise{ +// UcanAwait: types.Await{ +// Selector: blobreplicacap.AllocateSiteSelector, +// Link: transferInv.Link(), +// }, +// }, +// } + +// effects := fx.NewEffects(fx.WithFork(fx.FromInvocation(transferInv))) +// return result.Ok[blobreplicacap.AllocateOk, failure.IPLDBuilderFailure](ok), effects, nil +// } + +// nodeProvider := newMultiMockReplicaNodeProvider(t, uploadService, +// []principal.Signer{replicaProviderA, replicaProviderB}, +// replicaAllocHandler, logger, +// ) + +// handler := handlers.SpaceBlobReplicateHandler( +// defaultCfg, uploadID, +// router, blobReg, replicaStore, agentStore, nodeProvider, logger, +// ) + +// space := testutil.RandomSigner(t) + +// // provision the space +// err = consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) +// require.NoError(t, err) + +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} + +// // register the blob in the space +// err = blobReg.Register(ctx, space.DID(), blob, testutil.RandomCID(t)) +// require.NoError(t, err) + +// // create a valid location commitment +// blobURL := testutil.Must(url.Parse("https://storage.example.com/blob"))(t) +// locationCap := assert.Location.New(primaryProvider.DID().String(), assert.LocationCaveats{ +// Content: types.FromHash(digest), +// Location: []url.URL{*blobURL}, +// Space: space.DID(), +// }) +// lComm, err := delegation.Delegate( +// primaryProvider, +// uploadService, +// []ucan.Capability[assert.LocationCaveats]{locationCap}, +// delegation.WithExpiration(int(time.Now().Add(time.Hour).Unix())), +// ) +// require.NoError(t, err) + +// site := cidlink.Link{Cid: lComm.Link().(cidlink.Link).Cid} + +// // request 3 replicas: source + 2 new allocations +// cap := ucan.NewCapability( +// spaceblobcap.ReplicateAbility, +// space.DID().String(), +// spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 3, Site: site}, +// ) + +// inv, err := invocation.Invoke(alice, uploadService, cap) +// require.NoError(t, err) + +// // attach the location commitment blocks to the invocation +// for b, bErr := range lComm.Blocks() { +// require.NoError(t, bErr) +// err = inv.Attach(b) +// require.NoError(t, err) +// } + +// // create a server to get a valid InvocationContext +// srv, err := server.NewServer(uploadService) +// require.NoError(t, err) +// iCtx := srv.Context() + +// res, effects, err := handler(ctx, cap, inv, iCtx) +// require.NoError(t, err) + +// ok, fail := result.Unwrap(res) +// require.Nil(t, fail) +// require.NotNil(t, ok) + +// // should have 2 site promises (one per new replica) +// require.Len(t, ok.Site, 2) +// for _, s := range ok.Site { +// require.Equal(t, blobreplicacap.AllocateSiteSelector, s.UcanAwait.Selector) +// } + +// // should have effects +// require.NotNil(t, effects) + +// // verify both replicas were recorded with distinct providers +// records, err := replicaStore.List(ctx, space.DID(), digest) +// require.NoError(t, err) +// require.Len(t, records, 2) +// require.NotEqual(t, records[0].Provider.DID(), records[1].Provider.DID()) +// for _, r := range records { +// require.Equal(t, replica.Allocated, r.Status) +// } + +// // verify allocations went to two distinct providers (exclusion worked) +// require.Len(t, allocatedProviders, 2) +// require.NotEqual(t, allocatedProviders[0], allocatedProviders[1]) +// }) + +// t.Run("already fully replicated returns success", func(t *testing.T) { +// storageProvider := testutil.RandomSigner(t) +// storageProviderURL := testutil.Must(url.Parse("https://piri.example.com"))(t) +// storageProviderProof := delegateStorageProviderProof(t, storageProvider, uploadService) + +// spStore := storage_provider_store.New() +// err := spStore.Put(ctx, *storageProviderURL, storageProviderProof, 100, nil) +// require.NoError(t, err) + +// router := routing.NewService(spStore, logger) +// blobReg, consumerStore := newBlobRegistry() +// replicaStore := replica_store.New() +// agentStore := agent_store.New() +// nodeProvider := piriclient.NewProvider(uploadService, logger) + +// handler := handlers.SpaceBlobReplicateHandler( +// defaultCfg, &identity.Identity{Signer: uploadService}, +// router, blobReg, replicaStore, agentStore, nodeProvider, logger, +// ) + +// space := testutil.RandomSigner(t) + +// // provision the space +// err = consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) +// require.NoError(t, err) + +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// site := cidlink.Link{Cid: testutil.RandomCID(t)} + +// // register the blob in the space +// err = blobReg.Register(ctx, space.DID(), blob, testutil.RandomCID(t)) +// require.NoError(t, err) + +// // requesting 1 replica means source + 1 = 2 copies total +// // with 0 active replicas, newReplicasCount = 1 - (0 + 1) = 0 +// // so no new allocations needed => success with no effects +// cap := ucan.NewCapability( +// spaceblobcap.ReplicateAbility, +// space.DID().String(), +// spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 1, Site: site}, +// ) + +// inv, err := invocation.Invoke(alice, uploadService, cap) +// require.NoError(t, err) + +// res, _, err := handler(ctx, cap, inv, nil) +// require.NoError(t, err) + +// ok, fail := result.Unwrap(res) +// require.Nil(t, fail) +// require.NotNil(t, ok) +// }) +// } + +// // multiMockReplicaNodeProvider supports multiple storage node identities, +// // each backed by their own UCAN server for blob/replica/allocate. +// type multiMockReplicaNodeProvider struct { +// agentID ucan.Signer +// servers map[string]server.ServerView[server.Service] +// logger *zap.Logger +// } + +// func (m *multiMockReplicaNodeProvider) Client(id ucan.Principal, endpoint url.URL) (*piriclient.Client, error) { +// srv, ok := m.servers[id.DID().String()] +// if !ok { +// return nil, fmt.Errorf("no mock server for provider %s", id.DID()) +// } +// conn, err := uclient.NewConnection(id, srv) +// if err != nil { +// return nil, err +// } +// return piriclient.NewWithClient(id.DID(), m.agentID, conn, m.logger), nil +// } + +// func newMultiMockReplicaNodeProvider( +// t *testing.T, +// agentID ucan.Signer, +// serviceIDs []principal.Signer, +// allocHandler server.HandlerFunc[blobreplicacap.AllocateCaveats, blobreplicacap.AllocateOk, failure.IPLDBuilderFailure], +// logger *zap.Logger, +// ) *multiMockReplicaNodeProvider { +// t.Helper() + +// servers := make(map[string]server.ServerView[server.Service], len(serviceIDs)) +// for _, serviceID := range serviceIDs { +// ucanSrv, err := server.NewServer( +// serviceID, +// server.WithServiceMethod( +// blobreplicacap.AllocateAbility, +// server.Provide(blobreplicacap.Allocate, allocHandler), +// ), +// server.WithPrincipalResolver(func(ctx context.Context, id did.DID) (did.DID, validator.UnresolvedDID) { +// if id == agentID.DID() { +// if ws, ok := agentID.(signer.WrappedSigner); ok { +// return ws.Unwrap().DID(), nil +// } +// } +// return validator.FailDIDKeyResolution(ctx, id) +// }), +// ) +// require.NoError(t, err) +// servers[serviceID.DID().String()] = ucanSrv +// } + +// return &multiMockReplicaNodeProvider{agentID: agentID, servers: servers, logger: logger} +// } + +// // delegateReplicaProviderProof delegates blob/replica/allocate capability. +// func delegateReplicaProviderProof(t *testing.T, issuer principal.Signer, audience ucan.Principal) delegation.Delegation { +// t.Helper() +// proof, err := delegation.Delegate( +// issuer, +// audience, +// []ucan.Capability[ucan.NoCaveats]{ +// ucan.NewCapability(blobreplicacap.Allocate.Can(), issuer.DID().String(), ucan.NoCaveats{}), +// }, +// ) +// require.NoError(t, err) +// return proof +// } diff --git a/pkg/service/handlers/filecoin_offer.go b/pkg/service/handlers/filecoin_offer.go deleted file mode 100644 index 5a4be96..0000000 --- a/pkg/service/handlers/filecoin_offer.go +++ /dev/null @@ -1,48 +0,0 @@ -package handlers - -import ( - "context" - - "go.uber.org/zap" - - filecoincap "github.com/fil-forge/go-libstoracha/capabilities/filecoin" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" -) - -// WithFilecoinOfferMethod registers the filecoin/offer handler. -// This is a stub implementation that acknowledges Filecoin storage offers. -// TODO: Implement actual Filecoin deal making. -func WithFilecoinOfferMethod(logger *zap.Logger) server.Option { - return server.WithServiceMethod( - filecoincap.OfferAbility, - server.Provide( - filecoincap.Offer, - func(ctx context.Context, - cap ucan.Capability[filecoincap.OfferCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[filecoincap.OfferOk, failure.IPLDBuilderFailure], fx.Effects, error) { - spaceDID := cap.With() - content := cap.Nb().Content - piece := cap.Nb().Piece - - logger.Debug("filecoin/offer STUB (not implemented)", - zap.String("space", spaceDID), - zap.String("content", content.String()), - zap.String("piece", piece.String())) - - // Return success echoing back the piece CID - return result.Ok[filecoincap.OfferOk, failure.IPLDBuilderFailure]( - filecoincap.OfferOk{ - Piece: piece, - }, - ), nil, nil - }, - ), - ) -} diff --git a/pkg/service/handlers/handlers.go b/pkg/service/handlers/handlers.go new file mode 100644 index 0000000..b98935a --- /dev/null +++ b/pkg/service/handlers/handlers.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/validator" +) + +type Handler struct { + Capability validator.Capability + Handler execution.HandlerFunc +} diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go new file mode 100644 index 0000000..8c739ef --- /dev/null +++ b/pkg/service/handlers/index_add.go @@ -0,0 +1,74 @@ +package handlers + +import ( + "fmt" + + "go.uber.org/zap" + + indexcaps "github.com/fil-forge/libforge/capabilities/index" + "github.com/fil-forge/sprue/pkg/identity" + "github.com/fil-forge/sprue/pkg/indexerclient" + "github.com/fil-forge/sprue/pkg/lib/ucan_server" + "github.com/fil-forge/sprue/pkg/provisioning" + blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" +) + +const IndexNotFoundErrorName = "IndexNotFound" + +var ErrIndexNotFound = errors.New(IndexNotFoundErrorName, "index not found in space") + +func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", indexcaps.AddCommand)) + return Handler{ + Capability: indexcaps.Add, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*indexcaps.AddArguments], + res *bindexec.Response[*indexcaps.AddOK], + ) error { + args := req.Task().BindArguments() + space := req.Invocation().Subject() + index := args.Index + + log := log.With( + zap.Stringer("space", space.DID()), + zap.Stringer("index", index), + ) + log.Debug("adding index") + + provs, err := provisioningSvc.ListServiceProviders(req.Context(), space.DID()) + if err != nil { + log.Error("failed to list service providers", zap.Error(err)) + return fmt.Errorf("listing service providers: %w", err) + } + if len(provs) == 0 { + log.Warn("space has no service provider") + return res.SetFailure(errors.New(InsufficientStorageErrorName, "space has no service provider")) + } + + // Ensure the index is stored in the agent's space + _, err = blobRegistry.Get(req.Context(), space.DID(), index.Hash()) + if err != nil { + if errors.Is(err, blobregistry.ErrEntryNotFound) { + log.Warn("index not found in space") + return res.SetFailure(ErrIndexNotFound) + } + log.Error("failed to get index from blob registry", zap.Error(err)) + return err + } + + // Request MUST include a delegation to the upload service that gives it + // the ability to retrieve the index (a /content/retrieve delegation). + // This is re-delegated to the indexer for indexing. + proofStore := ucan_server.NewContainerProofStore(req.Metadata()) + // Publish to indexer with retrieval authorization + if _, err := indexerClient.PublishIndexClaim(req.Context(), space.DID(), index, proofStore); err != nil { + log.Error("failed to publish index claim", zap.Error(err)) + return fmt.Errorf("publishing index claim: %w", err) + } + + return res.SetSuccess(&indexcaps.AddOK{}) + }), + } +} diff --git a/pkg/service/handlers/index_add_test.go b/pkg/service/handlers/index_add_test.go new file mode 100644 index 0000000..b881065 --- /dev/null +++ b/pkg/service/handlers/index_add_test.go @@ -0,0 +1,244 @@ +package handlers_test + +import ( + "context" + "net/http/httptest" + "net/url" + "testing" + + assertcaps "github.com/fil-forge/libforge/capabilities/assert" + blobcaps "github.com/fil-forge/libforge/capabilities/blob" + contentcaps "github.com/fil-forge/libforge/capabilities/content" + indexcaps "github.com/fil-forge/libforge/capabilities/index" + "github.com/fil-forge/libforge/didmailto" + "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/sprue/pkg/identity" + "github.com/fil-forge/sprue/pkg/indexerclient" + "github.com/fil-forge/sprue/pkg/provisioning" + "github.com/fil-forge/sprue/pkg/service/handlers" + consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" + subscription_store "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/principal/signer" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/server" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/validator" + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +// newMockIndexerServer stands up a UCAN HTTP server that handles /assert/index +// by returning the canned response. Wraps the upload service's did:web identity +// so signatures verify against the underlying did:key. +func newMockIndexerServer( + t *testing.T, + indexerSigner principal.Signer, + uploadService principal.Signer, + indexOK *assertcaps.IndexOK, +) *httptest.Server { + t.Helper() + + resolveDIDKey := func(ctx context.Context, d did.DID) ([]did.DID, error) { + if d == uploadService.DID() { + if w, ok := uploadService.(signer.Unwrapper); ok { + return []did.DID{w.Unwrap().DID()}, nil + } + } + return validator.FailDIDKeyResolution(ctx, d) + } + + srv := server.NewHTTP( + indexerSigner, + server.WithValidationOptions(validator.WithDIDResolver(resolveDIDKey)), + ) + + srv.Handle(assertcaps.Index, bindexec.NewHandler(func( + req *bindexec.Request[*assertcaps.IndexArguments], + res *bindexec.Response[*assertcaps.IndexOK], + ) error { + return res.SetSuccess(indexOK) + })) + + httpSrv := httptest.NewServer(srv) + t.Cleanup(httpSrv.Close) + return httpSrv +} + +// invokeIndexAdd builds an /index/add invocation with optional metadata, +// returning the request and a signed response ready for the handler. +func invokeIndexAdd( + t *testing.T, + ctx context.Context, + agent principal.Signer, + uploadService principal.Signer, + space principal.Signer, + index cid.Cid, + reqOpts ...execution.RequestOption, +) (execution.Request, *execution.ExecResponse) { + t.Helper() + inv, err := indexcaps.Add.Invoke( + agent, + space, + &indexcaps.AddArguments{Index: index}, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + req := execution.NewRequest(ctx, inv, reqOpts...) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + return req, res +} + +func TestIndexAddHandler(t *testing.T) { + logger := zaptest.NewLogger(t) + ctx := t.Context() + + uploadService := testutil.WebService + alice := testutil.Alice + aliceAccount := testutil.Must(didmailto.New("alice@example.com"))(t) + + id := &identity.Identity{Signer: uploadService} + + t.Run("no service providers", func(t *testing.T) { + consumerStore := consumer_store.New() + subscriptionStore := subscription_store.New() + provisioningSvc := provisioning.NewService(nil, consumerStore, subscriptionStore) + blobReg, _ := newBlobRegistry(t) + + handler := handlers.NewIndexAddHandler(id, provisioningSvc, blobReg, nil, logger) + + space := testutil.RandomSigner(t) + req, res := invokeIndexAdd(t, ctx, alice, uploadService, space, testutil.RandomCID(t)) + + err := handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + require.Equal(t, handlers.InsufficientStorageErrorName, model.Name()) + }) + + t.Run("index not found in space", func(t *testing.T) { + blobReg, consumerStore := newBlobRegistry(t) + subscriptionStore := subscription_store.New() + provisioningSvc := provisioning.NewService( + []did.DID{uploadService.DID()}, + consumerStore, + subscriptionStore, + ) + + space := testutil.RandomSigner(t) + require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) + + handler := handlers.NewIndexAddHandler(id, provisioningSvc, blobReg, nil, logger) + + // Index blob is not registered for this space. + req, res := invokeIndexAdd(t, ctx, alice, uploadService, space, testutil.RandomCID(t)) + + err := handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + require.Equal(t, handlers.IndexNotFoundErrorName, model.Name()) + }) + + t.Run("retrieval auth supplied publishes index claim", func(t *testing.T) { + blobReg, consumerStore := newBlobRegistry(t) + subscriptionStore := subscription_store.New() + provisioningSvc := provisioning.NewService( + []did.DID{uploadService.DID()}, + consumerStore, + subscriptionStore, + ) + + space := testutil.RandomSigner(t) + require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) + + indexCID := testutil.RandomCID(t) + indexBlob := blobcaps.Blob{Digest: indexCID.Hash(), Size: 512} + require.NoError(t, blobReg.Register(ctx, space.DID(), indexBlob, testutil.RandomCID(t))) + + // Stand up a mock indexer that returns success on /assert/index. + indexerSigner := testutil.RandomSigner(t) + indexerSrv := newMockIndexerServer(t, indexerSigner, uploadService, &assertcaps.IndexOK{}) + indexerURL := testutil.Must(url.Parse(indexerSrv.URL))(t) + indexerCli, err := indexerclient.New(indexerURL, indexerSigner.DID(), uploadService, logger) + require.NoError(t, err) + + handler := handlers.NewIndexAddHandler(id, provisioningSvc, blobReg, indexerCli, logger) + + // /content/retrieve delegation from space → upload service so the + // handler can build a proof chain that authorizes the indexer to + // retrieve the index blob. + retrievalAuth, err := delegation.Delegate(space, uploadService, space, contentcaps.RetrieveCommand) + require.NoError(t, err) + + req, res := invokeIndexAdd(t, ctx, alice, uploadService, space, indexCID, + execution.WithDelegations(retrievalAuth), + ) + + err = handler.Handler(req, res) + require.NoError(t, err) + + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, o) + }) + + t.Run("missing retrieval auth fails to build proof chain", func(t *testing.T) { + blobReg, consumerStore := newBlobRegistry(t) + subscriptionStore := subscription_store.New() + provisioningSvc := provisioning.NewService( + []did.DID{uploadService.DID()}, + consumerStore, + subscriptionStore, + ) + + space := testutil.RandomSigner(t) + require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) + + indexCID := testutil.RandomCID(t) + indexBlob := blobcaps.Blob{Digest: indexCID.Hash(), Size: 512} + require.NoError(t, blobReg.Register(ctx, space.DID(), indexBlob, testutil.RandomCID(t))) + + indexerSigner := testutil.RandomSigner(t) + indexerSrv := newMockIndexerServer(t, indexerSigner, uploadService, &assertcaps.IndexOK{}) + indexerURL := testutil.Must(url.Parse(indexerSrv.URL))(t) + indexerCli, err := indexerclient.New(indexerURL, indexerSigner.DID(), uploadService, logger) + require.NoError(t, err) + + handler := handlers.NewIndexAddHandler(id, provisioningSvc, blobReg, indexerCli, logger) + + // No /content/retrieve delegation. The handler invokes /assert/index + // without proofs; the indexer accepts it (our mock has no validation + // on proofs), so this currently still succeeds. If the handler later + // requires retrieval auth before publishing, this test will need a + // stricter mock. + req, res := invokeIndexAdd(t, ctx, alice, uploadService, space, indexCID) + + err = handler.Handler(req, res) + require.NoError(t, err) + + // Currently the indexer client publishes even without retrieval auth + // because the proof chain is empty (not erroring). Document that + // behavior. + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.NotNil(t, o) + }) +} diff --git a/pkg/service/handlers/provider_add.go b/pkg/service/handlers/provider_add.go index 06cfb47..4a517b6 100644 --- a/pkg/service/handlers/provider_add.go +++ b/pkg/service/handlers/provider_add.go @@ -1,112 +1,82 @@ package handlers import ( - "context" "fmt" - "go.uber.org/zap" - - "github.com/fil-forge/go-libstoracha/capabilities/provider" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - + providercaps "github.com/fil-forge/libforge/capabilities/provider" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/pkg/billing" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/didmailto" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/provisioning" + "github.com/fil-forge/sprue/pkg/store/consumer" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" + "go.uber.org/zap" ) const ( InvalidAccountErrorName = "InvalidAccount" - InvalidProviderErrorName = "InvalidProvider" AccountPlanMissingErrorName = "AccountPlanMissing" ) -// WithProviderAddMethod registers the provider/add handler. -// This handler provisions a space to an account. -func WithProviderAddMethod(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - provider.AddAbility, - server.Provide( - provider.Add, - ProviderAddHandler(deploymentCfg, provisioningSvc, billingSvc, logger), - ), - ) -} +var ErrAccountPlanMissing = errors.New(AccountPlanMissingErrorName, "account does not have an active payment plan") -func ProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) server.HandlerFunc[provider.AddCaveats, provider.AddOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", provider.AddAbility)) - return func(ctx context.Context, - cap ucan.Capability[provider.AddCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[provider.AddOk, failure.IPLDBuilderFailure], fx.Effects, error) { - account, err := didmailto.Parse(cap.With()) - if err != nil { - log.Warn("invalid account", zap.String("account", cap.With())) - return result.Error[provider.AddOk, failure.IPLDBuilderFailure]( - errors.New(InvalidAccountErrorName, "invalid account DID: %v", err), - ), nil, nil - } - log := log.With(zap.Stringer("account", account)) +func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", providercaps.AddCommand)) + return Handler{ + Capability: providercaps.Add, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*providercaps.AddArguments], + res *bindexec.Response[*providercaps.AddOK], + ) error { + args := req.Task().BindArguments() + account, err := didmailto.Parse(req.Invocation().Subject().DID().String()) + if err != nil { + log.Warn("invalid account", zap.Stringer("account", req.Invocation().Subject().DID())) + return res.SetFailure(errors.New(InvalidAccountErrorName, "invalid account DID: %v", err)) + } + serviceProvider := args.Provider + space := args.Consumer + cause := req.Invocation().Task().Link() - serviceProvider, err := did.Parse(cap.Nb().Provider) - if err != nil { - log.Warn("invalid provider", zap.String("provider", cap.Nb().Provider)) - return result.Error[provider.AddOk, failure.IPLDBuilderFailure]( - errors.New(InvalidProviderErrorName, "invalid provider DID: %v", err), - ), nil, nil - } - log = log.With(zap.Stringer("provider", serviceProvider)) + log = log.With( + zap.Stringer("account", account), + zap.Stringer("provider", serviceProvider), + zap.Stringer("space", space), + ) + log.Debug("provisioning service for account") - space, err := did.Parse(cap.Nb().Consumer) - if err != nil { - log.Warn("invalid space", zap.String("space", cap.Nb().Consumer)) - return result.Error[provider.AddOk, failure.IPLDBuilderFailure]( - errors.New(InvalidProviderErrorName, "invalid space DID: %v", err), - ), nil, nil - } - log = log.With(zap.Stringer("space", space)) - log.Debug("provisioning service for account", zap.Stringer("account", account)) + if !deploymentCfg.AllowProvisionWithoutPaymentPlan { + // Check if the account has an active payment plan + // If not, return an error + plan, err := billingSvc.PaymentPlan(req.Context(), account) + if err != nil { + if errors.Is(err, billing.ErrMissingPaymentPlan) { + log.Warn("account does not have an active payment plan") + return res.SetFailure(ErrAccountPlanMissing) + } + log.Error("failed to check payment plan", zap.Error(err)) + return fmt.Errorf("checking payment plan: %w", err) + } + log = log.With(zap.Stringer("plan", plan)) + } - if !deploymentCfg.AllowProvisionWithoutPaymentPlan { - // Check if the account has an active payment plan - // If not, return an error - plan, err := billingSvc.PaymentPlan(ctx, account) + sub, err := provisioningSvc.Provision(req.Context(), account, space, serviceProvider, cause) if err != nil { - if errors.Is(err, billing.ErrMissingPaymentPlan) { - log.Warn("account does not have an active payment plan") - return result.Error[provider.AddOk, failure.IPLDBuilderFailure]( - errors.New(AccountPlanMissingErrorName, "account does not have an active payment plan"), - ), nil, nil + if errors.Is(err, provisioning.ErrProviderNotAllowed) { + log.Warn("provider is not allowed for this space") + return res.SetFailure(err) + } + if errors.Is(err, consumer.ErrConsumerExists) { + log.Warn("consumer already exists for this space") + return res.SetFailure(err) } - return nil, nil, fmt.Errorf("checking payment plan: %w", err) + log.Error("failed to provision service", zap.Error(err)) + return fmt.Errorf("provisioning service: %w", err) } - log = log.With(zap.Stringer("plan", plan)) - } - - cause, err := ipldutil.ToCID(inv.Link()) - if err != nil { - return nil, nil, err - } - - sub, err := provisioningSvc.Provision(ctx, account, space, serviceProvider, cause) - if err != nil { - log.Error("failed to provision service", zap.Error(err)) - return nil, nil, fmt.Errorf("provisioning service: %w", err) - } - - log.Debug("service provisioned successfully", zap.String("subscription", sub)) - return result.Ok[provider.AddOk, failure.IPLDBuilderFailure](provider.AddOk{ - Id: sub, - }), nil, nil + log.Debug("service provisioned successfully", zap.String("subscription", sub)) + return res.SetSuccess(&providercaps.AddOK{ID: sub}) + }), } } diff --git a/pkg/service/handlers/provider_add_test.go b/pkg/service/handlers/provider_add_test.go index 66577e5..2268477 100644 --- a/pkg/service/handlers/provider_add_test.go +++ b/pkg/service/handlers/provider_add_test.go @@ -1,256 +1,238 @@ -package handlers +package handlers_test import ( "context" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/provider" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/ucan" - + providercaps "github.com/fil-forge/libforge/capabilities/provider" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" + "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/billing" - "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/provisioning" - consumermemory "github.com/fil-forge/sprue/pkg/store/consumer/memory" - customermemory "github.com/fil-forge/sprue/pkg/store/customer/memory" - subscriptionmemory "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/sprue/pkg/service/handlers" + consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" + customer_store "github.com/fil-forge/sprue/pkg/store/customer/memory" + subscription_store "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) -func setupProviderAdd(t *testing.T, addCustomer bool) (*identity.Identity, did.DID, did.DID, *provisioning.Service, *billing.Service) { - t.Helper() - - serviceID := newTestIdentity(t) - providerDID := serviceID.Signer.DID() - account := mustMailtoDID(t, "alice@example.com") - product, err := did.Parse("did:web:free.web3.storage") - require.NoError(t, err) - - customerStore := customermemory.New() - if addCustomer { - err = customerStore.Add(context.Background(), account, nil, product, nil, nil) - require.NoError(t, err) - } +type providerAddDeps struct { + provisioningSvc *provisioning.Service + billingSvc *billing.Service + customerStore *customer_store.Store +} +func setupProviderAdd(t *testing.T, providerDID did.DID) *providerAddDeps { + t.Helper() + customerStore := customer_store.New() provisioningSvc := provisioning.NewService( []did.DID{providerDID}, - consumermemory.New(), - subscriptionmemory.New(), + consumer_store.New(), + subscription_store.New(), ) - billingSvc := billing.NewService(customerStore) + return &providerAddDeps{ + provisioningSvc: provisioningSvc, + billingSvc: billingSvc, + customerStore: customerStore, + } +} - return serviceID, providerDID, account, provisioningSvc, billingSvc +// invokeProviderAdd builds a /provider/add invocation with the account as the +// subject (matching the handler's expectation), plus a signed response. +func invokeProviderAdd( + t *testing.T, + ctx context.Context, + agent principal.Signer, + uploadService principal.Signer, + account ucan.Principal, + args *providercaps.AddArguments, +) (execution.Request, *execution.ExecResponse) { + t.Helper() + inv, err := providercaps.Add.Invoke( + agent, + account, + args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + return req, res } func TestProviderAddHandler(t *testing.T) { logger := zaptest.NewLogger(t) + ctx := t.Context() + + uploadService := testutil.WebService t.Run("success with payment plan", func(t *testing.T) { - deployCfg := config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: false} - serviceID, providerDID, account, provisioningSvc, billingSvc := setupProviderAdd(t, true) - handler := ProviderAddHandler(deployCfg, provisioningSvc, billingSvc, logger) + serviceProvider := testutil.RandomSigner(t) + deps := setupProviderAdd(t, serviceProvider.DID()) - space := newTestIdentity(t) + account := testutil.Must(didmailto.New("alice@example.com"))(t) + product := testutil.Must(did.Parse("did:web:free.web3.storage"))(t) + require.NoError(t, deps.customerStore.Add(ctx, account, nil, product, nil, nil)) - cap := ucan.NewCapability( - provider.AddAbility, - account.String(), - provider.AddCaveats{ - Provider: providerDID.String(), - Consumer: space.DID(), - }, + handler := handlers.NewProviderAddHandler( + config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: false}, + deps.provisioningSvc, deps.billingSvc, logger, ) - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, serviceID.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.NotEmpty(t, ok.Id) - }) - - t.Run("success skipping payment plan check", func(t *testing.T) { - deployCfg := config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: true} - // No customer added — but payment plan check is skipped - serviceID, providerDID, account, provisioningSvc, billingSvc := setupProviderAdd(t, false) - handler := ProviderAddHandler(deployCfg, provisioningSvc, billingSvc, logger) - - space := newTestIdentity(t) - - cap := ucan.NewCapability( - provider.AddAbility, - account.String(), - provider.AddCaveats{ - Provider: providerDID.String(), + space := testutil.RandomSigner(t) + agent := testutil.RandomSigner(t) + req, res := invokeProviderAdd(t, ctx, agent, uploadService, account, + &providercaps.AddArguments{ + Provider: serviceProvider.DID(), Consumer: space.DID(), }, ) - agent, err := identity.New("") + err := handler.Handler(req, res) require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, serviceID.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.NotEmpty(t, ok.Id) + require.NotNil(t, o) + + ok := providercaps.AddOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.NotEmpty(t, ok.ID) }) - t.Run("invalid account DID", func(t *testing.T) { - deployCfg := config.DeploymentConfig{} - serviceID, providerDID, _, provisioningSvc, billingSvc := setupProviderAdd(t, false) - handler := ProviderAddHandler(deployCfg, provisioningSvc, billingSvc, logger) + t.Run("success skipping payment plan check", func(t *testing.T) { + serviceProvider := testutil.RandomSigner(t) + deps := setupProviderAdd(t, serviceProvider.DID()) - space := newTestIdentity(t) + // No customer added — but payment plan check is skipped. + handler := handlers.NewProviderAddHandler( + config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: true}, + deps.provisioningSvc, deps.billingSvc, logger, + ) - cap := ucan.NewCapability( - provider.AddAbility, - "not-a-mailto-did", - provider.AddCaveats{ - Provider: providerDID.String(), + account := testutil.Must(didmailto.New("alice@example.com"))(t) + space := testutil.RandomSigner(t) + agent := testutil.RandomSigner(t) + req, res := invokeProviderAdd(t, ctx, agent, uploadService, account, + &providercaps.AddArguments{ + Provider: serviceProvider.DID(), Consumer: space.DID(), }, ) - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, serviceID.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) + err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) - require.NotNil(t, fail) + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + ok := providercaps.AddOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.NotEmpty(t, ok.ID) }) - t.Run("invalid provider DID", func(t *testing.T) { - deployCfg := config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: true} - serviceID, _, account, provisioningSvc, billingSvc := setupProviderAdd(t, false) - handler := ProviderAddHandler(deployCfg, provisioningSvc, billingSvc, logger) + t.Run("invalid account DID", func(t *testing.T) { + serviceProvider := testutil.RandomSigner(t) + deps := setupProviderAdd(t, serviceProvider.DID()) - space := newTestIdentity(t) + handler := handlers.NewProviderAddHandler( + config.DeploymentConfig{}, + deps.provisioningSvc, deps.billingSvc, logger, + ) - cap := ucan.NewCapability( - provider.AddAbility, - account.String(), - provider.AddCaveats{ - Provider: "bad-provider", + // Subject is a did:key (not a did:mailto), so didmailto.Parse rejects it. + notAMailto := testutil.RandomSigner(t) + space := testutil.RandomSigner(t) + agent := testutil.RandomSigner(t) + req, res := invokeProviderAdd(t, ctx, agent, uploadService, notAMailto, + &providercaps.AddArguments{ + Provider: serviceProvider.DID(), Consumer: space.DID(), }, ) - agent, err := identity.New("") + err := handler.Handler(req, res) require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, serviceID.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.NotNil(t, fail) - }) - t.Run("invalid space DID", func(t *testing.T) { - deployCfg := config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: true} - serviceID, providerDID, account, provisioningSvc, billingSvc := setupProviderAdd(t, false) - handler := ProviderAddHandler(deployCfg, provisioningSvc, billingSvc, logger) - - cap := ucan.NewCapability( - provider.AddAbility, - account.String(), - provider.AddCaveats{ - Provider: providerDID.String(), - Consumer: "bad-space", - }, - ) - - agent, err := identity.New("") - require.NoError(t, err) - - inv, err := invocation.Invoke(agent.Signer, serviceID.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) + model := edm.ErrorModel{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + require.Equal(t, handlers.InvalidAccountErrorName, model.Name()) }) t.Run("missing payment plan", func(t *testing.T) { - deployCfg := config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: false} - // No customer added — payment plan check will fail - serviceID, providerDID, account, provisioningSvc, billingSvc := setupProviderAdd(t, false) - handler := ProviderAddHandler(deployCfg, provisioningSvc, billingSvc, logger) - - space := newTestIdentity(t) - - cap := ucan.NewCapability( - provider.AddAbility, - account.String(), - provider.AddCaveats{ - Provider: providerDID.String(), + serviceProvider := testutil.RandomSigner(t) + deps := setupProviderAdd(t, serviceProvider.DID()) + + // No customer added — payment plan check fails with ErrMissingPaymentPlan. + handler := handlers.NewProviderAddHandler( + config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: false}, + deps.provisioningSvc, deps.billingSvc, logger, + ) + + account := testutil.Must(didmailto.New("alice@example.com"))(t) + space := testutil.RandomSigner(t) + agent := testutil.RandomSigner(t) + req, res := invokeProviderAdd(t, ctx, agent, uploadService, account, + &providercaps.AddArguments{ + Provider: serviceProvider.DID(), Consumer: space.DID(), }, ) - agent, err := identity.New("") + err := handler.Handler(req, res) require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, serviceID.Signer, cap) - require.NoError(t, err) - - res, _, err := handler(context.Background(), cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.NotNil(t, fail) + + model := edm.ErrorModel{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + require.Equal(t, handlers.AccountPlanMissingErrorName, model.Name()) }) t.Run("provider not allowed", func(t *testing.T) { - deployCfg := config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: true} - serviceID, _, account, provisioningSvc, billingSvc := setupProviderAdd(t, false) - handler := ProviderAddHandler(deployCfg, provisioningSvc, billingSvc, logger) + serviceProvider := testutil.RandomSigner(t) + deps := setupProviderAdd(t, serviceProvider.DID()) - space := newTestIdentity(t) - otherProvider := newTestIdentity(t) + handler := handlers.NewProviderAddHandler( + config.DeploymentConfig{AllowProvisionWithoutPaymentPlan: true}, + deps.provisioningSvc, deps.billingSvc, logger, + ) - cap := ucan.NewCapability( - provider.AddAbility, - account.String(), - provider.AddCaveats{ + // Args reference a different provider than the one allowed in setup. + otherProvider := testutil.RandomSigner(t) + account := testutil.Must(didmailto.New("alice@example.com"))(t) + space := testutil.RandomSigner(t) + agent := testutil.RandomSigner(t) + req, res := invokeProviderAdd(t, ctx, agent, uploadService, account, + &providercaps.AddArguments{ Provider: otherProvider.DID(), Consumer: space.DID(), }, ) - agent, err := identity.New("") + err := handler.Handler(req, res) require.NoError(t, err) - inv, err := invocation.Invoke(agent.Signer, serviceID.Signer, cap) - require.NoError(t, err) + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) - _, _, err = handler(context.Background(), cap, inv, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "provisioning service") + model := edm.ErrorModel{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + require.Equal(t, provisioning.ProviderNotAllowedErrorName, model.Name()) }) } diff --git a/pkg/service/handlers/space_blob_add.go b/pkg/service/handlers/space_blob_add.go deleted file mode 100644 index 5d6c75c..0000000 --- a/pkg/service/handlers/space_blob_add.go +++ /dev/null @@ -1,430 +0,0 @@ -package handlers - -import ( - "context" - "crypto/ed25519" - "fmt" - "io" - - blobcap "github.com/fil-forge/go-libstoracha/capabilities/blob" - httpcap "github.com/fil-forge/go-libstoracha/capabilities/http" - spaceblobcap "github.com/fil-forge/go-libstoracha/capabilities/space/blob" - "github.com/fil-forge/go-libstoracha/capabilities/types" - ucancap "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/core/car" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/receipt/ran" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/principal" - ed25519signer "github.com/fil-forge/go-ucanto/principal/ed25519/signer" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/ipld/go-ipld-prime/datamodel" - "github.com/ipld/go-ipld-prime/fluent/qp" - basicnode "github.com/ipld/go-ipld-prime/node/basic" - "github.com/multiformats/go-multihash" - "go.uber.org/zap" - - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" - "github.com/fil-forge/sprue/pkg/piriclient" - "github.com/fil-forge/sprue/pkg/routing" - "github.com/fil-forge/sprue/pkg/store/agent" - blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" -) - -func WithSpaceBlobAddMethod(id *identity.Identity, router *routing.Service, nodeProvider piriclient.Provider, agentStore agent.Store, blobRegistry blobregistry.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - spaceblobcap.AddAbility, - server.Provide( - spaceblobcap.Add, - SpaceBlobAddHandler(id, router, nodeProvider, agentStore, blobRegistry, logger), - ), - ) -} - -func SpaceBlobAddHandler(id *identity.Identity, router *routing.Service, nodeProvider piriclient.Provider, agentStore agent.Store, blobRegistry blobregistry.Store, logger *zap.Logger) server.HandlerFunc[spaceblobcap.AddCaveats, spaceblobcap.AddOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", spaceblobcap.AddAbility)) - return func(ctx context.Context, - cap ucan.Capability[spaceblobcap.AddCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[spaceblobcap.AddOk, failure.IPLDBuilderFailure], fx.Effects, error) { - blob := cap.Nb().Blob - b58digest := digestutil.Format(blob.Digest) - - log := log.With( - zap.String("space", cap.With()), - zap.Dict( - "blob", - zap.String("digest", b58digest), - zap.Uint64("size", blob.Size), - ), - ) - log.Debug("adding blob") - - space, err := did.Parse(cap.With()) - if err != nil { - return result.Error[spaceblobcap.AddOk, failure.IPLDBuilderFailure]( - errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), - ), nil, nil - } - - provider, allocInv, allocRcpt, allocOK, err := doAllocate(ctx, router, nodeProvider, agentStore, space, blob, inv.Link(), log) - if err != nil { - if errors.Is(err, routing.ErrCandidateUnavailable) { - return result.Error[spaceblobcap.AddOk, failure.IPLDBuilderFailure]( - routing.ErrCandidateUnavailable, - ), nil, nil - } - log.Error("allocation failed", zap.Error(err)) - return nil, nil, fmt.Errorf("allocating space: %w", err) - } - log = log.With(zap.Stringer("provider", provider.ID.DID())) - - putInv, putRcpt, err := genPut(blob, allocInv, allocOK, log) - if err != nil { - log.Error("failed to generate put invocation", zap.Error(err)) - return nil, nil, fmt.Errorf("generating put invocation: %w", err) - } - - accInv, accRcpt, err := maybeAccept(ctx, agentStore, blobRegistry, nodeProvider, provider, space, blob, putInv, putRcpt, log) - if err != nil { - return nil, nil, err - } - - forks := []fx.Effect{ - fx.FromInvocation(allocInv), - fx.FromInvocation(putInv), - fx.FromInvocation(accInv), - } - - // As a temporary solution we fork all add effects that add inline - // receipts so they can be delivered to the client. - for _, rcpt := range []receipt.AnyReceipt{allocRcpt, putRcpt, accRcpt} { - if rcpt == nil { - continue - } - conclude, err := issueConclude(id.Signer, rcpt) - if err != nil { - log.Error("failed to create conclude invocation for receipt", zap.Error(err)) - return nil, nil, fmt.Errorf("creating conclude invocation: %w", err) - } - forks = append(forks, fx.FromInvocation(conclude)) - } - - fx := fx.NewEffects(fx.WithFork(forks...)) - - return result.Ok[spaceblobcap.AddOk, failure.IPLDBuilderFailure](spaceblobcap.AddOk{ - Site: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.site", - Link: accInv.Link(), - }, - }, - }), fx, nil - } -} - -// issueConclude generates a ucan/conclude invocation for the given receipt. -func issueConclude(id ucan.Signer, receipt receipt.AnyReceipt) (invocation.IssuedInvocation, error) { - inv, err := ucancap.Conclude.Invoke( - id, - id, - id.DID().String(), - ucancap.ConcludeCaveats{ - Receipt: receipt.Root().Link(), - }, - ) - if err != nil { - return nil, fmt.Errorf("creating conclude invocation: %w", err) - } - // Attach the receipt blocks to the conclude invocation - for blk, err := range receipt.Blocks() { - if err != nil { - return nil, fmt.Errorf("getting receipt block: %w", err) - } - if err := inv.Attach(blk); err != nil { - return nil, fmt.Errorf("attaching receipt block: %w", err) - } - } - return inv, nil -} - -type delegationFetcher struct { - proof delegation.Delegation -} - -// FIXME: this is silly, really the client should authorize the upload service -// to blob/allocate and blob/accept and then the upload service should use that -// delegation to authorize allocate and accept calls to the storage provider. -// This removes the need for storage providers to grant love lived delegations -// to the upload service. We will fix this in UCAN 1.0. -func (df delegationFetcher) GetDelegation(ctx context.Context, audience ucan.Principal) (delegation.Delegation, error) { - if df.proof.Audience().DID() != audience.DID() { - return nil, fmt.Errorf("delegation audience is %s, but invocation requires proof with audience %s", df.proof.Audience().DID(), audience.DID()) - } - return df.proof, nil -} - -func doAllocate( - ctx context.Context, - router *routing.Service, - nodeProvider piriclient.Provider, - agentStore agent.Store, - space did.DID, - blob types.Blob, - cause ucan.Link, - logger *zap.Logger, -) (routing.StorageProviderInfo, invocation.Invocation, receipt.AnyReceipt, blobcap.AllocateOk, error) { - log := logger.With(zap.Stringer("cause", cause)) - log.Debug("doing allocation") - - var exclusions []ucan.Principal - for { - candidate, err := router.SelectStorageProvider(ctx, blob, routing.WithExclusions(exclusions...)) - if err != nil { - log.Error("failed to select storage node", zap.Error(err)) - return routing.StorageProviderInfo{}, nil, nil, blobcap.AllocateOk{}, err - } - log := logger.With(zap.Stringer("candidate", candidate.ID.DID()), zap.String("endpoint", candidate.Endpoint.String())) - log.Debug("selected storage provider candidate") - - client, err := nodeProvider.Client(candidate.ID, candidate.Endpoint) - if err != nil { - log.Error("failed to create piri node", zap.Error(err)) - return routing.StorageProviderInfo{}, nil, nil, blobcap.AllocateOk{}, err - } - - res, inv, rcpt, err := client.Allocate(ctx, &piriclient.AllocateRequest{ - Space: space, - Digest: blob.Digest, - Size: blob.Size, - Cause: cause, - }, delegationFetcher{candidate.Proof}) - if err != nil { - log.Warn("failed to allocate blob", zap.Error(err)) - exclusions = append(exclusions, candidate.ID) - continue - } - - err = writeAgentMessage(ctx, agentStore, []invocation.Invocation{inv}, []receipt.AnyReceipt{rcpt}) - if err != nil { - log.Error("failed to write agent message", zap.Error(err)) - exclusions = append(exclusions, candidate.ID) - continue - } - - return candidate, inv, rcpt, blobcap.AllocateOk{Size: res.Size, Address: res.Address}, nil - } -} - -// TODO(ash): move this into the client -func writeAgentMessage(ctx context.Context, agentStore agent.Store, invs []invocation.Invocation, rcpts []receipt.AnyReceipt) error { - msg, err := message.Build(invs, rcpts) - if err != nil { - return fmt.Errorf("building agent message: %w", err) - } - idx := []agent.IndexEntry{} - for e, err := range agent.Index(msg) { - if err != nil { - return fmt.Errorf("indexing agent message: %w", err) - } - idx = append(idx, e) - } - src, err := io.ReadAll(car.Encode([]ipld.Link{msg.Root().Link()}, msg.Blocks())) - if err != nil { - return fmt.Errorf("reading CAR data: %w", err) - } - return agentStore.Write(ctx, msg, idx, src) -} - -// Generates an invocation to put the blob to the storage provider. It MAY -// return a receipt if the allocation result indicates that the provider already -// has the blob. -func genPut(blob types.Blob, allocInv invocation.Invocation, allocOK blobcap.AllocateOk, logger *zap.Logger) (invocation.Invocation, receipt.AnyReceipt, error) { - log := logger - log.Debug("generating put invocation") - - // Derive the principal that will provide the blob from the blob digest. - // we do this so that any actor with a blob could issue a receipt for the - // `/http/put` invocation. - blobProvider, err := deriveDID(blob.Digest) - if err != nil { - return nil, nil, err - } - - // Create http/put invocation - fct := httpPutFact{ - id: blobProvider.DID().String(), - key: blobProvider.Encode(), - } - - putInv, err := httpcap.Put.Invoke( - blobProvider, - blobProvider, - blobProvider.DID().String(), - httpcap.PutCaveats{ - URL: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.address.url", - Link: allocInv.Link(), - }, - }, - Headers: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.address.headers", - Link: allocInv.Link(), - }, - }, - Body: httpcap.Body{ - Digest: blob.Digest, - Size: blob.Size, - }, - }, - // We encode the keys for the blob provider principal that can be used - // by the client to use in order to sign a receipt. Client could - // actually derive the same principal from the blob digest like we did - // above, however by embedding the keys we make API more flexible and - // could in the future generate one-off principals instead. - delegation.WithFacts([]ucan.FactBuilder{fct}), - ) - if err != nil { - return nil, nil, fmt.Errorf("invoking %q: %w", httpcap.PutAbility, err) - } - - var putRcpt receipt.AnyReceipt - - // If no address was provided we have a blob in store already and we can issue - // a receipt for the `/http/put` without requiring blob to be provided. - if allocOK.Address == nil { - log.Info("blob present on provider, issuing receipt for put") - putRcpt, err = receipt.Issue( - blobProvider, - result.Ok[httpcap.PutOk, failure.IPLDBuilderFailure](httpcap.PutOk{}), - ran.FromInvocation(putInv), - ) - if err != nil { - return nil, nil, fmt.Errorf("issuing %q receipt: %w", httpcap.PutAbility, err) - } - } - - return putInv, putRcpt, nil -} - -// Derives did:key principal from (blob) multihash that can be used to -// sign ucan invocations/receipts for the the subject (blob) multihash. -func deriveDID(digest multihash.Multihash) (principal.Signer, error) { - if len(digest) < 32 { - return nil, fmt.Errorf("expected []byte with length %d, got %d", ed25519.SeedSize, len(digest)) - } - seed := digest[len(digest)-32:] - pk := ed25519.NewKeyFromSeed(seed) - return ed25519signer.FromRaw(pk) -} - -// maybeAccept generates and possibly executes a `/blob/accept` invocation if -// the provided put receipt is non-nil and non-failure. -func maybeAccept( - ctx context.Context, - agentStore agent.Store, - blobRegistry blobregistry.Store, - nodeProvider piriclient.Provider, - providerInfo routing.StorageProviderInfo, - space ucan.Principal, - blob types.Blob, - putInv invocation.Invocation, - putRcpt receipt.AnyReceipt, - logger *zap.Logger, -) (invocation.Invocation, receipt.AnyReceipt, error) { - log := logger - log.Debug("generating accept invocation") - - c, err := nodeProvider.Client(providerInfo.ID, providerInfo.Endpoint) - if err != nil { - log.Error("failed to create piri client for accept", zap.Error(err)) - return nil, nil, err - } - - accReq := piriclient.AcceptRequest{ - Space: space.DID(), - Digest: blob.Digest, - Size: blob.Size, - Put: putInv.Link(), - } - - accInv, err := c.AcceptInvocation(ctx, &accReq, delegationFetcher{providerInfo.Proof}) - if err != nil { - log.Error("failed to create accept invocation", zap.Error(err)) - return nil, nil, err - } - - var accRcpt receipt.AnyReceipt - - // If put has already succeeded, we can execute `/blob/accept` right away. - if putRcpt != nil { - _, x := result.Unwrap(putRcpt.Out()) - if x == nil { - res, inv, rcpt, err := c.Accept(ctx, &accReq, delegationFetcher{providerInfo.Proof}) - if err != nil { - log.Error("failed to execute accept on piri", zap.Error(err)) - return nil, nil, err - } - log.Debug("blob accepted", zap.Stringer("site", res.Site)) - - err = writeAgentMessage(ctx, agentStore, []invocation.Invocation{inv}, []receipt.AnyReceipt{rcpt}) - if err != nil { - log.Error("failed to write agent message for accept", zap.Error(err)) - return nil, nil, err - } - - cause, err := ipldutil.ToCID(inv.Link()) - if err != nil { - return nil, nil, err - } - - err = blobRegistry.Register(ctx, space.DID(), blob, cause) - if err != nil { - log.Error("failed to register blob", zap.Error(err)) - return nil, nil, err - } - - accInv = inv - accRcpt = rcpt - } - } - - return accInv, accRcpt, nil -} - -// httpPutFact contains the fact data for the http/put invocation. -// TODO: should move to go-libstoracha -type httpPutFact struct { - id string - key []byte -} - -func (hpf httpPutFact) ToIPLD() (map[string]datamodel.Node, error) { - n, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) { - qp.MapEntry(ma, "id", qp.String(hpf.id)) - qp.MapEntry(ma, "keys", qp.Map(1, func(ma datamodel.MapAssembler) { - qp.MapEntry(ma, hpf.id, qp.Bytes(hpf.key)) - })) - }) - if err != nil { - return nil, err - } - - return map[string]datamodel.Node{ - "keys": n, - }, nil -} diff --git a/pkg/service/handlers/space_blob_add_test.go b/pkg/service/handlers/space_blob_add_test.go deleted file mode 100644 index 912f824..0000000 --- a/pkg/service/handlers/space_blob_add_test.go +++ /dev/null @@ -1,343 +0,0 @@ -package handlers_test - -import ( - "context" - "fmt" - "net/url" - "testing" - - blobcap "github.com/fil-forge/go-libstoracha/capabilities/blob" - spaceblobcap "github.com/fil-forge/go-libstoracha/capabilities/space/blob" - "github.com/fil-forge/go-libstoracha/capabilities/types" - uclient "github.com/fil-forge/go-ucanto/client" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/principal" - "github.com/fil-forge/go-ucanto/principal/signer" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/go-ucanto/validator" - "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/didmailto" - "github.com/fil-forge/sprue/pkg/piriclient" - "github.com/fil-forge/sprue/pkg/routing" - "github.com/fil-forge/sprue/pkg/service/handlers" - agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" - blob_registry "github.com/fil-forge/sprue/pkg/store/blob_registry/memory" - consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" - metrics_store "github.com/fil-forge/sprue/pkg/store/metrics/memory" - spacediff_store "github.com/fil-forge/sprue/pkg/store/space_diff/memory" - storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - "github.com/stretchr/testify/require" - "go.uber.org/zap" - "go.uber.org/zap/zaptest" -) - -func newBlobRegistry() (*blob_registry.Store, *consumer_store.Store) { - consumerStore := consumer_store.New() - return blob_registry.New( - spacediff_store.New(), - consumerStore, - metrics_store.NewSpaceStore(), - metrics_store.New(), - ), consumerStore -} - -// mockNodeProvider is a NodeProvider that creates piri clients connected -// directly to a UCAN server (which implements transport.Channel), bypassing -// HTTP entirely. -type mockNodeProvider struct { - signer ucan.Signer - srv server.ServerView[server.Service] - logger *zap.Logger -} - -func (m *mockNodeProvider) Client(id ucan.Principal, endpoint url.URL) (*piriclient.Client, error) { - // the ID must match the ID of the server this provider started - if id.DID() != m.srv.ID().DID() { - return nil, fmt.Errorf("unexpected client ID %s, expected %s", id.DID(), m.signer.DID()) - } - conn, err := uclient.NewConnection(id, m.srv) - if err != nil { - return nil, err - } - return piriclient.NewWithConnection(id.DID(), m.signer, conn, m.logger), nil -} - -// newMockNodeProvider creates a mock NodeProvider backed by a UCAN server. -// The server is created with the serviceID so that audience validation passes -// (doAllocate passes the upload service signer as the connection audience). -func newMockNodeProvider( - t *testing.T, - agentID ucan.Signer, // the ID of the upload service (signer of invocations) - serviceID principal.Signer, // the ID of the storage node (signer of receipts) - allocHandler server.HandlerFunc[blobcap.AllocateCaveats, blobcap.AllocateOk, failure.IPLDBuilderFailure], - acceptHandler server.HandlerFunc[blobcap.AcceptCaveats, blobcap.AcceptOk, failure.IPLDBuilderFailure], - logger *zap.Logger, -) *mockNodeProvider { - t.Helper() - - ucanSrv, err := server.NewServer( - serviceID, - server.WithServiceMethod( - blobcap.AllocateAbility, - server.Provide(blobcap.Allocate, allocHandler), - ), - server.WithServiceMethod( - blobcap.AcceptAbility, - server.Provide(blobcap.Accept, acceptHandler), - ), - server.WithPrincipalResolver(func(ctx context.Context, id did.DID) (did.DID, validator.UnresolvedDID) { - if id == agentID.DID() { - if ws, ok := agentID.(signer.WrappedSigner); ok { - return ws.Unwrap().DID(), nil - } - } - return validator.FailDIDKeyResolution(ctx, id) - }), - ) - require.NoError(t, err) - - return &mockNodeProvider{signer: agentID, srv: ucanSrv, logger: logger} -} - -func newOkHandler[Caveats any, Ok ipld.Builder](t *testing.T, ok Ok) server.HandlerFunc[Caveats, Ok, failure.IPLDBuilderFailure] { - t.Helper() - return func(ctx context.Context, cap ucan.Capability[Caveats], inv invocation.Invocation, iCtx server.InvocationContext, - ) (result.Result[Ok, failure.IPLDBuilderFailure], fx.Effects, error) { - return result.Ok[Ok, failure.IPLDBuilderFailure](ok), nil, nil - } -} - -// Delegates blob/allocate and blob/accept -func delegateStorageProviderProof(t *testing.T, issuer principal.Signer, audience ucan.Principal) delegation.Delegation { - t.Helper() - proof, err := delegation.Delegate( - issuer, - audience, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability(blobcap.Allocate.Can(), issuer.DID().String(), ucan.NoCaveats{}), - ucan.NewCapability(blobcap.Accept.Can(), issuer.DID().String(), ucan.NoCaveats{}), - }, - ) - require.NoError(t, err) - return proof -} - -func TestSpaceBlobAddHandler(t *testing.T) { - logger := zaptest.NewLogger(t) - ctx := t.Context() - - alice := testutil.Alice - aliceAccount := testutil.Must(didmailto.Parse("did:mailto:example.com:alice"))(t) - uploadService := testutil.WebService - storageProvider := testutil.RandomSigner(t) - storageProviderURL := testutil.Must(url.Parse("https://piri.example.com"))(t) - storageProviderProof := delegateStorageProviderProof(t, storageProvider, uploadService) - - t.Run("invalid space DID", func(t *testing.T) { - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, _ := newBlobRegistry() - - nodeProvider := piriclient.NewProvider(uploadService, logger) - handler := handlers.SpaceBlobAddHandler(&identity.Identity{Signer: uploadService}, router, nodeProvider, agentStore, blobReg, logger) - - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - - cap := ucan.NewCapability( - spaceblobcap.AddAbility, - "not-a-did", - spaceblobcap.AddCaveats{Blob: blob}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - }) - - t.Run("no candidates available", func(t *testing.T) { - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, _ := newBlobRegistry() - - nodeProvider := piriclient.NewProvider(uploadService, logger) - handler := handlers.SpaceBlobAddHandler(&identity.Identity{Signer: uploadService}, router, nodeProvider, agentStore, blobReg, logger) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - - cap := ucan.NewCapability( - spaceblobcap.AddAbility, - space.DID().String(), - spaceblobcap.AddCaveats{Blob: blob}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - }) - - t.Run("zero weight providers returns candidate unavailable", func(t *testing.T) { - spStore := storage_provider_store.New() - err := spStore.Put(ctx, *storageProviderURL, storageProviderProof, 0, nil) - require.NoError(t, err) - - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, _ := newBlobRegistry() - - nodeProvider := piriclient.NewProvider(uploadService, logger) - handler := handlers.SpaceBlobAddHandler(&identity.Identity{Signer: uploadService}, router, nodeProvider, agentStore, blobReg, logger) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - - cap := ucan.NewCapability( - spaceblobcap.AddAbility, - space.DID().String(), - spaceblobcap.AddCaveats{Blob: blob}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - }) - - t.Run("successful allocation with address", func(t *testing.T) { - putURL := testutil.Must(url.Parse("https://storage.example.com/put"))(t) - allocateOk := blobcap.AllocateOk{ - Size: 1024, - Address: &blobcap.Address{ - URL: *putURL, - Expires: 9999999999, - }, - } - - acceptOk := blobcap.AcceptOk{Site: cidlink.Link{Cid: testutil.RandomCID(t)}} - - nodeProvider := newMockNodeProvider( - t, - uploadService, - storageProvider, - newOkHandler[blobcap.AllocateCaveats](t, allocateOk), - newOkHandler[blobcap.AcceptCaveats](t, acceptOk), - logger, - ) - - spStore := storage_provider_store.New() - err := spStore.Put(ctx, *storageProviderURL, storageProviderProof, 100, nil) - require.NoError(t, err) - - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, consumerStore := newBlobRegistry() - - handler := handlers.SpaceBlobAddHandler(&identity.Identity{Signer: uploadService}, router, nodeProvider, agentStore, blobReg, logger) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - - // mock "provision" the space, by adding it to the consumer store - err = consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - cap := ucan.NewCapability( - spaceblobcap.AddAbility, - space.DID().String(), - spaceblobcap.AddCaveats{Blob: blob}, - ) - - // we don't need proof alice can invoke this capability since the handler - // is being tested directly and no validation is performed - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, effects, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.Nil(t, fail) - require.NotNil(t, effects) - }) - - t.Run("successful allocation blob already stored", func(t *testing.T) { - // No address means blob is already on the provider - allocateOk := blobcap.AllocateOk{Size: 1024, Address: nil} - acceptOk := blobcap.AcceptOk{Site: cidlink.Link{Cid: testutil.RandomCID(t)}} - - nodeProvider := newMockNodeProvider( - t, - uploadService, - storageProvider, - newOkHandler[blobcap.AllocateCaveats](t, allocateOk), - newOkHandler[blobcap.AcceptCaveats](t, acceptOk), - logger, - ) - - spStore := storage_provider_store.New() - err := spStore.Put(ctx, *storageProviderURL, storageProviderProof, 100, nil) - require.NoError(t, err) - - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, consumerStore := newBlobRegistry() - - handler := handlers.SpaceBlobAddHandler(&identity.Identity{Signer: uploadService}, router, nodeProvider, agentStore, blobReg, logger) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - - // mock "provision" the space, by adding it to the consumer store - err = consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - cap := ucan.NewCapability( - spaceblobcap.AddAbility, - space.DID().String(), - spaceblobcap.AddCaveats{Blob: blob}, - ) - - // we don't need proof alice can invoke this capability since the handler - // is being tested directly and no validation is performed - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, effects, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.Nil(t, fail) - // Should have effects including accept since blob was already stored - require.NotNil(t, effects) - }) -} diff --git a/pkg/service/handlers/space_blob_list.go b/pkg/service/handlers/space_blob_list.go deleted file mode 100644 index c1f3e1e..0000000 --- a/pkg/service/handlers/space_blob_list.go +++ /dev/null @@ -1,79 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - - "github.com/fil-forge/go-libstoracha/capabilities/space/blob" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/lib/errors" - blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - "go.uber.org/zap" -) - -// WithSpaceBlobListMethod registers the space/blob/list handler. -// This handler lists the blobs of a space. -func WithSpaceBlobListMethod(blobRegistry blobregistry.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - blob.ListAbility, - server.Provide(blob.List, SpaceBlobListHandler(blobRegistry, logger)), - ) -} - -func SpaceBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) server.HandlerFunc[blob.ListCaveats, blob.ListOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", blob.ListAbility)) - return server.HandlerFunc[blob.ListCaveats, blob.ListOk, failure.IPLDBuilderFailure]( - func(ctx context.Context, - cap ucan.Capability[blob.ListCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[blob.ListOk, failure.IPLDBuilderFailure], fx.Effects, error) { - args := cap.Nb() - log := log.With(zap.String("space", cap.With())) - - var opts []blobregistry.ListOption - if args.Size != nil { - log = log.With(zap.Uint64("size", *args.Size)) - opts = append(opts, blobregistry.WithListLimit(int(*args.Size))) - } - if args.Cursor != nil { - log = log.With(zap.String("cursor", *args.Cursor)) - opts = append(opts, blobregistry.WithListCursor(*args.Cursor)) - } - log.Debug("listing blobs") - - space, err := did.Parse(cap.With()) - if err != nil { - return result.Error[blob.ListOk, failure.IPLDBuilderFailure]( - errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), - ), nil, nil - } - - page, err := blobRegistry.List(ctx, space, opts...) - if err != nil { - log.Error("failed to list blobs", zap.Error(err)) - return nil, nil, fmt.Errorf("listing blobs: %w", err) - } - - results := make([]blob.ListBlobItem, 0, len(page.Results)) - for _, r := range page.Results { - results = append(results, blob.ListBlobItem{ - Blob: r.Blob, - Cause: cidlink.Link{Cid: r.Cause}, - InsertedAt: r.InsertedAt, - }) - } - - return result.Ok[blob.ListOk, failure.IPLDBuilderFailure](blob.ListOk{ - Results: results, - Cursor: page.Cursor, - }), nil, nil - }) -} diff --git a/pkg/service/handlers/space_blob_list_test.go b/pkg/service/handlers/space_blob_list_test.go deleted file mode 100644 index 82d7e18..0000000 --- a/pkg/service/handlers/space_blob_list_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package handlers_test - -import ( - "testing" - - "github.com/fil-forge/go-libstoracha/capabilities/space/blob" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/lib/didmailto" - "github.com/fil-forge/sprue/pkg/service/handlers" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zaptest" -) - -func TestSpaceBlobListHandler(t *testing.T) { - logger := zaptest.NewLogger(t) - ctx := t.Context() - - uploadService := testutil.WebService - alice := testutil.Alice - aliceAccount := testutil.Must(didmailto.Parse("did:mailto:example.com:alice"))(t) - - t.Run("invalid space DID", func(t *testing.T) { - blobRegistry, _ := newBlobRegistry() - handler := handlers.SpaceBlobListHandler(blobRegistry, logger) - - caveats := blob.ListCaveats{} - inv, err := blob.List.Invoke(alice, uploadService, "not-a-did", caveats) - require.NoError(t, err) - - cap := blob.List.New("not-a-did", caveats) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.InvalidSpaceErrorName, *model.Name) - }) - - t.Run("empty list", func(t *testing.T) { - blobRegistry, _ := newBlobRegistry() - handler := handlers.SpaceBlobListHandler(blobRegistry, logger) - - space := testutil.RandomSigner(t) - caveats := blob.ListCaveats{} - inv, err := blob.List.Invoke(alice, uploadService, space.DID().String(), caveats) - require.NoError(t, err) - - cap := blob.List.New(space.DID().String(), caveats) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.Empty(t, ok.Results) - }) - - t.Run("lists blobs", func(t *testing.T) { - blobRegistry, consumerStore := newBlobRegistry() - handler := handlers.SpaceBlobListHandler(blobRegistry, logger) - - space := testutil.RandomSigner(t) - - // Provision the space - err := consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - blob1 := types.Blob{Digest: testutil.RandomMultihash(t), Size: 100} - blob2 := types.Blob{Digest: testutil.RandomMultihash(t), Size: 200} - - err = blobRegistry.Register(ctx, space.DID(), blob1, testutil.RandomCID(t)) - require.NoError(t, err) - err = blobRegistry.Register(ctx, space.DID(), blob2, testutil.RandomCID(t)) - require.NoError(t, err) - - caveats := blob.ListCaveats{} - inv, err := blob.List.Invoke(alice, uploadService, space.DID().String(), caveats) - require.NoError(t, err) - - cap := blob.List.New(space.DID().String(), caveats) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.Len(t, ok.Results, 2) - }) - - t.Run("with size limit", func(t *testing.T) { - blobRegistry, consumerStore := newBlobRegistry() - handler := handlers.SpaceBlobListHandler(blobRegistry, logger) - - space := testutil.RandomSigner(t) - - err := consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - for i := range 3 { - err := blobRegistry.Register(ctx, space.DID(), types.Blob{Digest: testutil.RandomMultihash(t), Size: uint64(i + 1)}, testutil.RandomCID(t)) - require.NoError(t, err) - } - - size := uint64(2) - caveats := blob.ListCaveats{Size: &size} - inv, err := blob.List.Invoke(alice, uploadService, space.DID().String(), caveats) - require.NoError(t, err) - - cap := blob.List.New(space.DID().String(), caveats) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.Len(t, ok.Results, 2) - require.NotNil(t, ok.Cursor) - }) - - t.Run("with cursor pagination", func(t *testing.T) { - blobRegistry, consumerStore := newBlobRegistry() - handler := handlers.SpaceBlobListHandler(blobRegistry, logger) - - space := testutil.RandomSigner(t) - - err := consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - for i := range 3 { - err := blobRegistry.Register(ctx, space.DID(), types.Blob{Digest: testutil.RandomMultihash(t), Size: uint64(i + 1)}, testutil.RandomCID(t)) - require.NoError(t, err) - } - - // First page: size 1 - size := uint64(1) - caveats1 := blob.ListCaveats{Size: &size} - inv1, err := blob.List.Invoke(alice, uploadService, space.DID().String(), caveats1) - require.NoError(t, err) - - cap1 := blob.List.New(space.DID().String(), caveats1) - - res1, _, err := handler(ctx, cap1, inv1, nil) - require.NoError(t, err) - - ok1, fail := result.Unwrap(res1) - require.Nil(t, fail) - require.Len(t, ok1.Results, 1) - require.NotNil(t, ok1.Cursor) - - // Second page using cursor - cursor := *ok1.Cursor - caveats2 := blob.ListCaveats{Cursor: &cursor, Size: &size} - inv2, err := blob.List.Invoke(alice, uploadService, space.DID().String(), caveats2) - require.NoError(t, err) - - cap2 := blob.List.New(space.DID().String(), caveats2) - - res2, _, err := handler(ctx, cap2, inv2, nil) - require.NoError(t, err) - - ok2, fail := result.Unwrap(res2) - require.Nil(t, fail) - require.Len(t, ok2.Results, 1) - - // Results should be different blobs - require.NotEqual(t, ok1.Results[0].Blob.Digest.HexString(), ok2.Results[0].Blob.Digest.HexString()) - }) - - t.Run("does not list blobs from other spaces", func(t *testing.T) { - blobRegistry, consumerStore := newBlobRegistry() - handler := handlers.SpaceBlobListHandler(blobRegistry, logger) - - space1 := testutil.RandomSigner(t) - space2 := testutil.RandomSigner(t) - - err := consumerStore.Add(ctx, uploadService.DID(), space1.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - err = blobRegistry.Register(ctx, space1.DID(), types.Blob{Digest: testutil.RandomMultihash(t), Size: 100}, testutil.RandomCID(t)) - require.NoError(t, err) - - caveats := blob.ListCaveats{} - inv, err := blob.List.Invoke(alice, uploadService, space2.DID().String(), caveats) - require.NoError(t, err) - - cap := blob.List.New(space2.DID().String(), caveats) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.Empty(t, ok.Results) - }) -} diff --git a/pkg/service/handlers/space_blob_replicate.go b/pkg/service/handlers/space_blob_replicate.go deleted file mode 100644 index a03bf53..0000000 --- a/pkg/service/handlers/space_blob_replicate.go +++ /dev/null @@ -1,477 +0,0 @@ -package handlers - -import ( - "bytes" - "context" - "fmt" - "slices" - - "go.uber.org/zap" - - "github.com/fil-forge/go-libstoracha/capabilities/assert" - blobreplicacap "github.com/fil-forge/go-libstoracha/capabilities/blob/replica" - spaceblobcap "github.com/fil-forge/go-libstoracha/capabilities/space/blob" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - fdm "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/go-ucanto/validator" - "github.com/fil-forge/sprue/internal/config" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" - "github.com/fil-forge/sprue/pkg/piriclient" - "github.com/fil-forge/sprue/pkg/routing" - "github.com/fil-forge/sprue/pkg/store/agent" - blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" - "github.com/fil-forge/sprue/pkg/store/replica" - "github.com/multiformats/go-multihash" -) - -const ( - // Too many or too few replicas were instructed. - ReplicationCountRangeErrorName = "ReplicationCountRangeError" - // There are not enough replication nodes available to replicate the data. - ReplicationCandidateUnavailableErrorName = "ReplicationCandidateUnavailable" - // Blob to replicate was not found in the space. - ReplicationSourceNotFoundErrorName = "ReplicationSourceNotFound" - // The location commitment was invalid in some way. For example, it has - // expired, is revoked, had a signature that did not verify or referenced a - // blob that was not requested to be replicated. - InvalidReplicationSiteErrorName = "InvalidReplicationSite" -) - -var ( - ErrReplicationSourceNotFound = errors.New(ReplicationSourceNotFoundErrorName, "blob to replicate was not found in the space") - ErrReplicationCandidateUnavailable = errors.New(ReplicationCandidateUnavailableErrorName, "no replication candidates available") -) - -// WithSpaceBlobReplicateMethod registers the space/blob/replicate handler. -func WithSpaceBlobReplicateMethod( - cfg config.DeploymentConfig, - id *identity.Identity, - router *routing.Service, - blobRegistry blobregistry.Store, - replicaStore replica.Store, - agentStore agent.Store, - storageNode piriclient.Provider, - logger *zap.Logger, -) server.Option { - return server.WithServiceMethod( - spaceblobcap.ReplicateAbility, - server.Provide( - spaceblobcap.Replicate, - SpaceBlobReplicateHandler(cfg, id, router, blobRegistry, replicaStore, agentStore, storageNode, logger), - ), - ) -} - -func SpaceBlobReplicateHandler( - cfg config.DeploymentConfig, - id *identity.Identity, - router *routing.Service, - blobRegistry blobregistry.Store, - replicaStore replica.Store, - agentStore agent.Store, - storageNode piriclient.Provider, - logger *zap.Logger, -) server.HandlerFunc[spaceblobcap.ReplicateCaveats, spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", spaceblobcap.ReplicateAbility)) - return func(ctx context.Context, - cap ucan.Capability[spaceblobcap.ReplicateCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure], fx.Effects, error) { - space, err := did.Parse(cap.With()) - if err != nil { - return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( - errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), - ), nil, nil - } - blob := cap.Nb().Blob - replicas := cap.Nb().Replicas - - log := log.With( - zap.Stringer("space", space), - zap.Dict( - "blob", - zap.String("digest", digestutil.Format(blob.Digest)), - zap.Uint64("size", blob.Size), - ), - zap.Uint("replicas", replicas), - ) - log.Debug("replicating blob") - - if replicas > cfg.MaxReplicas { - log.Warn("replication count out of range") - return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( - errors.New(ReplicationCountRangeErrorName, "requested number of replicas is greater than maximum: %d", cfg.MaxReplicas), - ), nil, nil - } - - _, err = blobRegistry.Get(ctx, space, blob.Digest) - if err != nil { - if errors.Is(err, blobregistry.ErrEntryNotFound) { - log.Warn("replication source not found in space") - return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( - ErrReplicationSourceNotFound, - ), nil, nil - } - log.Error("failed to get blob registration") - return nil, nil, fmt.Errorf("getting blob registration: %w", err) - } - - // check if we have any active replications - records, err := replicaStore.List(ctx, space, blob.Digest) - if err != nil { - log.Error("failed to list replicas", zap.Error(err)) - return nil, nil, fmt.Errorf("listing replicas: %w", err) - } - - // TODO: handle the case where a receipt was not received and the replica - // still exists in "allocated", but has actually timed out/failed. - - var activeReplicas []replica.Record - var failedReplicas []replica.Record - - var allocTasks []invocation.Invocation - var allocReceipts []receipt.AnyReceipt - var transferTasks []invocation.Invocation - var transferReceipts []receipt.AnyReceipt - - for _, r := range records { - if r.Status == replica.Failed { - failedReplicas = append(failedReplicas, r) - } else { - detail, err := replicaFxDetail(ctx, agentStore, r, logger) - if err != nil { - log.Error("failed to get replica details", zap.Error(err)) - return nil, nil, fmt.Errorf("getting replica details: %w", err) - } - activeReplicas = append(activeReplicas, r) - allocTasks = append(allocTasks, detail.allocate.invocation) - allocReceipts = append(allocReceipts, detail.allocate.receipt) - if detail.transfer != nil { - transferTasks = append(transferTasks, detail.transfer.invocation) - if detail.transfer.receipt != nil { - transferReceipts = append(transferReceipts, detail.transfer.receipt) - } - } - } - } - - // Note: We +1 below to include the source blob, which is not recorded in - // the replicas table. - newReplicasCount := int(replicas) - (len(activeReplicas) + 1) - - // TODO: support reducing the number of replicas - if newReplicasCount < 0 { - return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( - errors.New(ReplicationCountRangeErrorName, "reducing replica count not implemented"), - ), nil, nil - } - - // lets allocate some replicas! - if newReplicasCount > 0 { - blocks, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(inv.Blocks())) - if err != nil { - return nil, nil, fmt.Errorf("creating block reader: %w", err) - } - site := cap.Nb().Site - lComm, location, err := extractLocationCommitment(space, blob.Digest, site, blocks) - if err != nil { - log.Warn("failed to extract location commitment", zap.Stringer("site", site), zap.Error(err)) - return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( - errors.New(InvalidReplicationSiteErrorName, "invalid location commitment: %s", err.Error()), - ), nil, nil - } - _, err = validator.Claim( - ctx, - assert.Location, - []delegation.Proof{delegation.FromDelegation(lComm)}, - validator.NewClaimContext( - id.Signer.Verifier(), - iCtx.CanIssue, - iCtx.ValidateAuthorization, - iCtx.ResolveProof, - iCtx.ParsePrincipal, - iCtx.ResolveDIDKey, - iCtx.ValidateTimeBounds, - iCtx.AuthorityProofs()..., - ), - ) - if err != nil { - log.Warn("failed to authorize location commitment", zap.Stringer("site", site), zap.Error(err)) - return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( - errors.New(InvalidReplicationSiteErrorName, "unauthorized location commitment: %s", err.Error()), - ), nil, nil - } - - urls := make([]string, 0, len(location.Location)) - for _, url := range location.Location { - urls = append(urls, url.String()) - } - siteLogFields := []zap.Field{zap.Stringer("root", site), zap.Strings("locations", urls)} - if location.Range != nil { - siteLogFields = append(siteLogFields, zap.Uint64("offset", location.Range.Offset)) - if location.Range.Length != nil { - siteLogFields = append(siteLogFields, zap.Uint64("length", *location.Range.Length)) - } - } - log = log.With(zap.Dict("site", siteLogFields...)) - log.Debug("allocating space to replicate blob") - - // do not include any nodes where we already have replications - var exclude []ucan.Principal - for _, r := range activeReplicas { - exclude = append(exclude, r.Provider) - } - - for range newReplicasCount { - for { - candidate, err := router.SelectReplicationProvider(ctx, lComm.Issuer(), blob, routing.WithExclusions(exclude...)) - if err != nil { - if errors.Is(err, routing.ErrCandidateUnavailable) { - log.Warn("no replication candidates available") - return result.Error[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure]( - ErrReplicationCandidateUnavailable, - ), nil, nil - } - log.Error("failed to select replication provider", zap.Error(err)) - return nil, nil, fmt.Errorf("selecting replication provider: %w", err) - } - - log.Debug("selected replication provider", zap.Stringer("provider", candidate.ID.DID())) - client, err := storageNode.Client(candidate.ID, candidate.Endpoint) - if err != nil { - log.Error("failed to create storage node client", zap.Error(err)) - return nil, nil, fmt.Errorf("creating storage node client: %w", err) - } - - allocRes, allocInv, allocRcpt, err := client.ReplicaAllocate(ctx, &piriclient.ReplicaAllocateRequest{ - Space: space, - Digest: blob.Digest, - Size: blob.Size, - Site: lComm, - Cause: inv.Link(), - }, delegationFetcher{proof: candidate.Proof}) - if err != nil { - log.Warn("failed to allocate replica", zap.Error(err)) - exclude = append(exclude, candidate.ID) - continue - } - - // record the invocation and the receipt, so we can retrieve it later - // when we get a blob/replica/transfer receipt in ucan/conclude - err = writeAgentMessage(ctx, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) - if err != nil { - log.Error("failed to write agent message", zap.Error(err)) - return nil, nil, fmt.Errorf("writing agent message: %w", err) - } - - // write a replication record to the store - firstTimeReplica := !slices.ContainsFunc(failedReplicas, func(r replica.Record) bool { - return r.Provider.DID() == candidate.ID.DID() - }) - status := result.MatchResultR1( - allocRcpt.Out(), - func(o ipld.Node) replica.ReplicationStatus { - return replica.Allocated - }, - func(x ipld.Node) replica.ReplicationStatus { - return replica.Failed - }, - ) - cause, err := ipldutil.ToCID(allocInv.Link()) - if err != nil { - return nil, nil, err - } - if firstTimeReplica { - err = replicaStore.Add(ctx, space, blob.Digest, candidate.ID.DID(), status, cause) - } else { - err = replicaStore.Retry(ctx, space, blob.Digest, candidate.ID.DID(), status, cause) - } - if err != nil { - log.Error("failed to store replica record", zap.Error(err)) - return nil, nil, fmt.Errorf("storing replica record: %w", err) - } - - allocTasks = append(allocTasks, allocInv) - allocReceipts = append(allocReceipts, allocRcpt) - transferTasks = append(transferTasks, allocRes.Transfer) - // exclude this provider from next candidate selection (in case there - // are more replicas to be allocated). - exclude = append(exclude, candidate.ID) - break - } - } - } - - var res spaceblobcap.ReplicateOk - for _, t := range transferTasks { - res.Site = append(res.Site, types.Promise{ - UcanAwait: types.Await{ - Selector: blobreplicacap.AllocateSiteSelector, - Link: t.Link(), - }, - }) - } - - forks := []fx.Effect{} - for _, t := range allocTasks { - forks = append(forks, fx.FromInvocation(t)) - } - for _, t := range transferTasks { - forks = append(forks, fx.FromInvocation(t)) - } - for _, r := range allocReceipts { - // as a temporary solution we fork all allocate effects that add inline - // receipts so they can be delivered to the client. - conclude, err := issueConclude(id.Signer, r) - if err != nil { - log.Error("failed to create conclude invocation for replica allocate receipt", zap.Error(err)) - return nil, nil, fmt.Errorf("creating conclude invocation: %w", err) - } - forks = append(forks, fx.FromInvocation(conclude)) - } - for _, r := range transferReceipts { - // as a temporary solution we fork all transfer effects that add inline - // receipts so they can be delivered to the client. - conclude, err := issueConclude(id.Signer, r) - if err != nil { - log.Error("failed to create conclude invocation for replica transfer receipt", zap.Error(err)) - return nil, nil, fmt.Errorf("creating conclude invocation: %w", err) - } - forks = append(forks, fx.FromInvocation(conclude)) - } - - fx := fx.NewEffects(fx.WithFork(forks...)) - - return result.Ok[spaceblobcap.ReplicateOk, failure.IPLDBuilderFailure](res), fx, nil - } -} - -type transaction struct { - invocation invocation.Invocation - receipt receipt.AnyReceipt -} - -type replicaDetail struct { - allocate transaction - transfer *transaction -} - -// Retrieves details of effect chain for replica allocations. -// -// If the allocation failed (receipt in error) then the return value will not -// include any details about the transfer. i.e. `transfer` will be `nil`. -// -// If the receipt for `blob/replica/transfer` was not yet received, it will not -// be included in the return value. i.e. `transfer.receipt` will be `nil`. -func replicaFxDetail(ctx context.Context, agentStore agent.Store, rec replica.Record, logger *zap.Logger) (replicaDetail, error) { - log := logger.With(zap.Stringer("allocation", rec.Cause)) - - allocRcpt, err := agentStore.GetReceipt(ctx, rec.Cause) - if err != nil { - log.Error("failed to get replica allocation receipt", zap.Error(err)) - return replicaDetail{}, fmt.Errorf("getting allocation receipt: %w", err) - } - - // receipt typically contains invocation - allocInv, ok := allocRcpt.Ran().Invocation() - if !ok { - allocInv, err = agentStore.GetInvocation(ctx, rec.Cause) - if err != nil { - log.Error("failed to get replica allocation invocation", zap.Error(err)) - return replicaDetail{}, fmt.Errorf("getting allocation invocation: %w", err) - } - } - - o, x := result.Unwrap(allocRcpt.Out()) - // if allocation failed, we cannot provide details for transfer - if x != nil { - log.Error("cannot get transfer details because allocation failed", zap.Error(fdm.Bind(x))) - return replicaDetail{ - allocate: transaction{ - invocation: allocInv, - receipt: allocRcpt, - }, - }, nil - } - - allocOk, err := ipld.Rebind[blobreplicacap.AllocateOk](o, blobreplicacap.AllocateOkType(), types.Converters...) - if err != nil { - log.Error("failed to rebind allocation result", zap.Error(err)) - return replicaDetail{}, fmt.Errorf("rebinding allocation result: %w", err) - } - - transferTask, err := ipldutil.ToCID(allocOk.Site.UcanAwait.Link) - if err != nil { - return replicaDetail{}, err - } - log = log.With(zap.Stringer("transfer", transferTask)) - - var transferInv invocation.Invocation - transferRcpt, err := agentStore.GetReceipt(ctx, transferTask) - if err != nil { - if !errors.Is(err, agent.ErrReceiptNotFound) { - log.Error("failed to get replica transfer receipt", zap.Error(err)) - return replicaDetail{}, fmt.Errorf("getting transfer receipt: %w", err) - } - log.Debug("transfer receipt not found, may still be in progress") - } - - if transferRcpt != nil { - transferInv, _ = transferRcpt.Ran().Invocation() - } - if transferInv == nil { - transferInv, err = agentStore.GetInvocation(ctx, transferTask) - if err != nil { - log.Error("failed to get replica transfer invocation", zap.Error(err)) - return replicaDetail{}, fmt.Errorf("getting transfer invocation: %w", err) - } - } - - return replicaDetail{ - allocate: transaction{ - invocation: allocInv, - receipt: allocRcpt, - }, - transfer: &transaction{ - invocation: transferInv, - receipt: transferRcpt, - }, - }, nil -} - -func extractLocationCommitment(space did.DID, digest multihash.Multihash, root ipld.Link, blocks blockstore.BlockReader) (delegation.Delegation, assert.LocationCaveats, error) { - lComm, err := delegation.NewDelegationView(root, blocks) - if err != nil { - return nil, assert.LocationCaveats{}, fmt.Errorf("creating location commitment: %w", err) - } - if len(lComm.Capabilities()) == 0 { - return nil, assert.LocationCaveats{}, fmt.Errorf("missing capabilities") - } - match, err := assert.Location.Match(validator.NewSource(lComm.Capabilities()[0], lComm)) - if err != nil { - return nil, assert.LocationCaveats{}, fmt.Errorf("matching caveats: %w", err) - } - nb := match.Value().Nb() - if nb.Space != space { - return nil, assert.LocationCaveats{}, fmt.Errorf("space mismatch: expected %s, got %s", space, nb.Space) - } - if !bytes.Equal(nb.Content.Hash(), digest) { - return nil, assert.LocationCaveats{}, fmt.Errorf("digest mismatch: expected %s, got %s", digestutil.Format(digest), digestutil.Format(nb.Content.Hash())) - } - return lComm, nb, nil -} diff --git a/pkg/service/handlers/space_blob_replicate_test.go b/pkg/service/handlers/space_blob_replicate_test.go deleted file mode 100644 index 9879326..0000000 --- a/pkg/service/handlers/space_blob_replicate_test.go +++ /dev/null @@ -1,493 +0,0 @@ -package handlers_test - -import ( - "context" - "fmt" - "net/url" - "testing" - "time" - - "github.com/fil-forge/go-libstoracha/capabilities/assert" - blobreplicacap "github.com/fil-forge/go-libstoracha/capabilities/blob/replica" - spaceblobcap "github.com/fil-forge/go-libstoracha/capabilities/space/blob" - "github.com/fil-forge/go-libstoracha/capabilities/types" - uclient "github.com/fil-forge/go-ucanto/client" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/principal" - "github.com/fil-forge/go-ucanto/principal/signer" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/go-ucanto/validator" - "github.com/fil-forge/sprue/internal/config" - "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/didmailto" - "github.com/fil-forge/sprue/pkg/piriclient" - "github.com/fil-forge/sprue/pkg/routing" - "github.com/fil-forge/sprue/pkg/service/handlers" - agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" - "github.com/fil-forge/sprue/pkg/store/replica" - replica_store "github.com/fil-forge/sprue/pkg/store/replica/memory" - storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - "github.com/stretchr/testify/require" - "go.uber.org/zap" - "go.uber.org/zap/zaptest" -) - -func TestSpaceBlobReplicateHandler(t *testing.T) { - logger := zaptest.NewLogger(t) - ctx := t.Context() - - alice := testutil.Alice - aliceAccount := testutil.Must(didmailto.Parse("did:mailto:example.com:alice"))(t) - uploadService := testutil.WebService - - defaultCfg := config.DeploymentConfig{MaxReplicas: 3} - - t.Run("invalid space DID", func(t *testing.T) { - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - blobReg, _ := newBlobRegistry() - replicaStore := replica_store.New() - agentStore := agent_store.New() - nodeProvider := piriclient.NewProvider(uploadService, logger) - - handler := handlers.SpaceBlobReplicateHandler( - defaultCfg, &identity.Identity{Signer: uploadService}, - router, blobReg, replicaStore, agentStore, nodeProvider, logger, - ) - - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - site := cidlink.Link{Cid: testutil.RandomCID(t)} - - cap := ucan.NewCapability( - spaceblobcap.ReplicateAbility, - "not-a-did", - spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 2, Site: site}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.InvalidSpaceErrorName, *model.Name) - }) - - t.Run("replicas exceeds max", func(t *testing.T) { - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - blobReg, _ := newBlobRegistry() - replicaStore := replica_store.New() - agentStore := agent_store.New() - nodeProvider := piriclient.NewProvider(uploadService, logger) - - cfg := config.DeploymentConfig{MaxReplicas: 2} - handler := handlers.SpaceBlobReplicateHandler( - cfg, &identity.Identity{Signer: uploadService}, - router, blobReg, replicaStore, agentStore, nodeProvider, logger, - ) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - site := cidlink.Link{Cid: testutil.RandomCID(t)} - - cap := ucan.NewCapability( - spaceblobcap.ReplicateAbility, - space.DID().String(), - spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 3, Site: site}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.ReplicationCountRangeErrorName, *model.Name) - }) - - t.Run("blob not found in space", func(t *testing.T) { - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - blobReg, _ := newBlobRegistry() - replicaStore := replica_store.New() - agentStore := agent_store.New() - nodeProvider := piriclient.NewProvider(uploadService, logger) - - handler := handlers.SpaceBlobReplicateHandler( - defaultCfg, &identity.Identity{Signer: uploadService}, - router, blobReg, replicaStore, agentStore, nodeProvider, logger, - ) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - site := cidlink.Link{Cid: testutil.RandomCID(t)} - - cap := ucan.NewCapability( - spaceblobcap.ReplicateAbility, - space.DID().String(), - spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 2, Site: site}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.ReplicationSourceNotFoundErrorName, *model.Name) - }) - - t.Run("invalid location commitment", func(t *testing.T) { - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - blobReg, consumerStore := newBlobRegistry() - replicaStore := replica_store.New() - agentStore := agent_store.New() - nodeProvider := piriclient.NewProvider(uploadService, logger) - - handler := handlers.SpaceBlobReplicateHandler( - defaultCfg, &identity.Identity{Signer: uploadService}, - router, blobReg, replicaStore, agentStore, nodeProvider, logger, - ) - - space := testutil.RandomSigner(t) - - // provision the space - err := consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - // random CID is not a valid location commitment delegation - site := cidlink.Link{Cid: testutil.RandomCID(t)} - - // register the blob in the space - err = blobReg.Register(ctx, space.DID(), blob, testutil.RandomCID(t)) - require.NoError(t, err) - - cap := ucan.NewCapability( - spaceblobcap.ReplicateAbility, - space.DID().String(), - spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 2, Site: site}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.InvalidReplicationSiteErrorName, *model.Name) - }) - - t.Run("successful replication with new allocation", func(t *testing.T) { - uploadID := testutil.Must(identity.New(""))(t) - uploadService := uploadID.Signer - - // primary storage provider (already has the blob) - primaryProvider := testutil.RandomSigner(t) - - // two replication providers (will each receive a replica) - replicaProviderA := testutil.RandomSigner(t) - replicaProviderAURL := testutil.Must(url.Parse("https://replica-a.example.com"))(t) - replicaProviderAProof := delegateReplicaProviderProof(t, replicaProviderA, uploadService) - - replicaProviderB := testutil.RandomSigner(t) - replicaProviderBURL := testutil.Must(url.Parse("https://replica-b.example.com"))(t) - replicaProviderBProof := delegateReplicaProviderProof(t, replicaProviderB, uploadService) - - // register both replication providers in routing - spStore := storage_provider_store.New() - err := spStore.Put(ctx, *replicaProviderAURL, replicaProviderAProof, 100, nil) - require.NoError(t, err) - err = spStore.Put(ctx, *replicaProviderBURL, replicaProviderBProof, 100, nil) - require.NoError(t, err) - - router := routing.NewService(spStore, logger) - blobReg, consumerStore := newBlobRegistry() - replicaStore := replica_store.New() - agentStore := agent_store.New() - - // track which providers received allocations to verify exclusion - var allocatedProviders []did.DID - - // mock handler for blob/replica/allocate - replicaAllocHandler := func( - ctx context.Context, - cap ucan.Capability[blobreplicacap.AllocateCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[blobreplicacap.AllocateOk, failure.IPLDBuilderFailure], fx.Effects, error) { - allocatedProviders = append(allocatedProviders, iCtx.ID().DID()) - - // create a transfer invocation to include in the response - transferCap := ucan.NewCapability( - "blob/replica/transfer", - cap.With(), - ucan.NoCaveats{}, - ) - transferInv, err := invocation.Invoke(iCtx.ID(), iCtx.ID(), transferCap) - if err != nil { - return nil, nil, err - } - - ok := blobreplicacap.AllocateOk{ - Size: cap.Nb().Blob.Size, - Site: types.Promise{ - UcanAwait: types.Await{ - Selector: blobreplicacap.AllocateSiteSelector, - Link: transferInv.Link(), - }, - }, - } - - effects := fx.NewEffects(fx.WithFork(fx.FromInvocation(transferInv))) - return result.Ok[blobreplicacap.AllocateOk, failure.IPLDBuilderFailure](ok), effects, nil - } - - nodeProvider := newMultiMockReplicaNodeProvider(t, uploadService, - []principal.Signer{replicaProviderA, replicaProviderB}, - replicaAllocHandler, logger, - ) - - handler := handlers.SpaceBlobReplicateHandler( - defaultCfg, uploadID, - router, blobReg, replicaStore, agentStore, nodeProvider, logger, - ) - - space := testutil.RandomSigner(t) - - // provision the space - err = consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - - // register the blob in the space - err = blobReg.Register(ctx, space.DID(), blob, testutil.RandomCID(t)) - require.NoError(t, err) - - // create a valid location commitment - blobURL := testutil.Must(url.Parse("https://storage.example.com/blob"))(t) - locationCap := assert.Location.New(primaryProvider.DID().String(), assert.LocationCaveats{ - Content: types.FromHash(digest), - Location: []url.URL{*blobURL}, - Space: space.DID(), - }) - lComm, err := delegation.Delegate( - primaryProvider, - uploadService, - []ucan.Capability[assert.LocationCaveats]{locationCap}, - delegation.WithExpiration(int(time.Now().Add(time.Hour).Unix())), - ) - require.NoError(t, err) - - site := cidlink.Link{Cid: lComm.Link().(cidlink.Link).Cid} - - // request 3 replicas: source + 2 new allocations - cap := ucan.NewCapability( - spaceblobcap.ReplicateAbility, - space.DID().String(), - spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 3, Site: site}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - // attach the location commitment blocks to the invocation - for b, bErr := range lComm.Blocks() { - require.NoError(t, bErr) - err = inv.Attach(b) - require.NoError(t, err) - } - - // create a server to get a valid InvocationContext - srv, err := server.NewServer(uploadService) - require.NoError(t, err) - iCtx := srv.Context() - - res, effects, err := handler(ctx, cap, inv, iCtx) - require.NoError(t, err) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.NotNil(t, ok) - - // should have 2 site promises (one per new replica) - require.Len(t, ok.Site, 2) - for _, s := range ok.Site { - require.Equal(t, blobreplicacap.AllocateSiteSelector, s.UcanAwait.Selector) - } - - // should have effects - require.NotNil(t, effects) - - // verify both replicas were recorded with distinct providers - records, err := replicaStore.List(ctx, space.DID(), digest) - require.NoError(t, err) - require.Len(t, records, 2) - require.NotEqual(t, records[0].Provider.DID(), records[1].Provider.DID()) - for _, r := range records { - require.Equal(t, replica.Allocated, r.Status) - } - - // verify allocations went to two distinct providers (exclusion worked) - require.Len(t, allocatedProviders, 2) - require.NotEqual(t, allocatedProviders[0], allocatedProviders[1]) - }) - - t.Run("already fully replicated returns success", func(t *testing.T) { - storageProvider := testutil.RandomSigner(t) - storageProviderURL := testutil.Must(url.Parse("https://piri.example.com"))(t) - storageProviderProof := delegateStorageProviderProof(t, storageProvider, uploadService) - - spStore := storage_provider_store.New() - err := spStore.Put(ctx, *storageProviderURL, storageProviderProof, 100, nil) - require.NoError(t, err) - - router := routing.NewService(spStore, logger) - blobReg, consumerStore := newBlobRegistry() - replicaStore := replica_store.New() - agentStore := agent_store.New() - nodeProvider := piriclient.NewProvider(uploadService, logger) - - handler := handlers.SpaceBlobReplicateHandler( - defaultCfg, &identity.Identity{Signer: uploadService}, - router, blobReg, replicaStore, agentStore, nodeProvider, logger, - ) - - space := testutil.RandomSigner(t) - - // provision the space - err = consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - site := cidlink.Link{Cid: testutil.RandomCID(t)} - - // register the blob in the space - err = blobReg.Register(ctx, space.DID(), blob, testutil.RandomCID(t)) - require.NoError(t, err) - - // requesting 1 replica means source + 1 = 2 copies total - // with 0 active replicas, newReplicasCount = 1 - (0 + 1) = 0 - // so no new allocations needed => success with no effects - cap := ucan.NewCapability( - spaceblobcap.ReplicateAbility, - space.DID().String(), - spaceblobcap.ReplicateCaveats{Blob: blob, Replicas: 1, Site: site}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - ok, fail := result.Unwrap(res) - require.Nil(t, fail) - require.NotNil(t, ok) - }) -} - -// multiMockReplicaNodeProvider supports multiple storage node identities, -// each backed by their own UCAN server for blob/replica/allocate. -type multiMockReplicaNodeProvider struct { - agentID ucan.Signer - servers map[string]server.ServerView[server.Service] - logger *zap.Logger -} - -func (m *multiMockReplicaNodeProvider) Client(id ucan.Principal, endpoint url.URL) (*piriclient.Client, error) { - srv, ok := m.servers[id.DID().String()] - if !ok { - return nil, fmt.Errorf("no mock server for provider %s", id.DID()) - } - conn, err := uclient.NewConnection(id, srv) - if err != nil { - return nil, err - } - return piriclient.NewWithConnection(id.DID(), m.agentID, conn, m.logger), nil -} - -func newMultiMockReplicaNodeProvider( - t *testing.T, - agentID ucan.Signer, - serviceIDs []principal.Signer, - allocHandler server.HandlerFunc[blobreplicacap.AllocateCaveats, blobreplicacap.AllocateOk, failure.IPLDBuilderFailure], - logger *zap.Logger, -) *multiMockReplicaNodeProvider { - t.Helper() - - servers := make(map[string]server.ServerView[server.Service], len(serviceIDs)) - for _, serviceID := range serviceIDs { - ucanSrv, err := server.NewServer( - serviceID, - server.WithServiceMethod( - blobreplicacap.AllocateAbility, - server.Provide(blobreplicacap.Allocate, allocHandler), - ), - server.WithPrincipalResolver(func(ctx context.Context, id did.DID) (did.DID, validator.UnresolvedDID) { - if id == agentID.DID() { - if ws, ok := agentID.(signer.WrappedSigner); ok { - return ws.Unwrap().DID(), nil - } - } - return validator.FailDIDKeyResolution(ctx, id) - }), - ) - require.NoError(t, err) - servers[serviceID.DID().String()] = ucanSrv - } - - return &multiMockReplicaNodeProvider{agentID: agentID, servers: servers, logger: logger} -} - -// delegateReplicaProviderProof delegates blob/replica/allocate capability. -func delegateReplicaProviderProof(t *testing.T, issuer principal.Signer, audience ucan.Principal) delegation.Delegation { - t.Helper() - proof, err := delegation.Delegate( - issuer, - audience, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability(blobreplicacap.Allocate.Can(), issuer.DID().String(), ucan.NoCaveats{}), - }, - ) - require.NoError(t, err) - return proof -} diff --git a/pkg/service/handlers/space_index_add.go b/pkg/service/handlers/space_index_add.go deleted file mode 100644 index 78388a9..0000000 --- a/pkg/service/handlers/space_index_add.go +++ /dev/null @@ -1,148 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - - "go.uber.org/zap" - - spaceindexcap "github.com/fil-forge/go-libstoracha/capabilities/space/index" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/indexerclient" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" - "github.com/fil-forge/sprue/pkg/provisioning" - blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" -) - -const IndexNotFoundErrorName = "IndexNotFound" - -var ErrIndexNotFound = errors.New(IndexNotFoundErrorName, "index not found in space") - -// extractRetrievalAuth extracts the space/content/retrieve delegation from the -// invocation facts. Guppy includes this delegation so the indexer can fetch -// the index blob from storage providers that require UCAN authorization. -func extractRetrievalAuth(inv invocation.Invocation) (delegation.Delegation, error) { - var authLink ipld.Link - for _, fact := range inv.Facts() { - if v, ok := fact["retrievalAuth"]; ok { - if node, ok := v.(ipld.Node); ok { - link, err := node.AsLink() - if err == nil { - authLink = link - break - } - } - } - } - if authLink == nil { - return nil, fmt.Errorf("retrievalAuth fact not found in invocation") - } - - // Build delegation from invocation blocks - bs, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(inv.Blocks())) - if err != nil { - return nil, fmt.Errorf("creating block reader: %w", err) - } - dlg, err := delegation.NewDelegationView(authLink, bs) - if err != nil { - return nil, fmt.Errorf("creating delegation view: %w", err) - } - return dlg, nil -} - -// WithSpaceIndexAddMethod registers the space/index/add handler. -// This handler publishes index claims to the indexer service. -func WithSpaceIndexAddMethod(provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - spaceindexcap.AddAbility, - server.Provide( - spaceindexcap.Add, - SpaceIndexAddHandler(provisioningSvc, blobRegistry, indexerClient, logger), - ), - ) -} - -func SpaceIndexAddHandler(provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) server.HandlerFunc[spaceindexcap.AddCaveats, spaceindexcap.AddOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", spaceindexcap.AddAbility)) - return func(ctx context.Context, - cap ucan.Capability[spaceindexcap.AddCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[spaceindexcap.AddOk, failure.IPLDBuilderFailure], fx.Effects, error) { - space, err := did.Parse(cap.With()) - if err != nil { - return result.Error[spaceindexcap.AddOk, failure.IPLDBuilderFailure]( - errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), - ), nil, nil - } - index, err := ipldutil.ToCID(cap.Nb().Index) - if err != nil { - return nil, nil, err - } - content, err := ipldutil.ToCID(cap.Nb().Content) - if err != nil { - return nil, nil, err - } - - log := log.With( - zap.Stringer("space", space), - zap.Stringer("index", index), - zap.Stringer("content", content), - ) - log.Debug("adding index") - - provs, err := provisioningSvc.ListServiceProviders(ctx, space) - if err != nil { - log.Error("failed to list service providers", zap.Error(err)) - return nil, nil, fmt.Errorf("listing service providers: %w", err) - } - if len(provs) == 0 { - log.Warn("space has no service provider") - return result.Error[spaceindexcap.AddOk, failure.IPLDBuilderFailure]( - errors.New(InsufficientStorageErrorName, "space has no service provider"), - ), nil, nil - } - - // Ensure the index is stored in the agent's space - _, err = blobRegistry.Get(ctx, space, index.Hash()) - if err != nil { - if errors.Is(err, blobregistry.ErrEntryNotFound) { - log.Warn("index not found in space") - return result.Error[spaceindexcap.AddOk, failure.IPLDBuilderFailure]( - ErrIndexNotFound, - ), nil, nil - } - log.Error("failed to get index from blob registry", zap.Error(err)) - return nil, nil, err - } - - // Extract retrievalAuth delegation from invocation facts - // Guppy provides this so the indexer can fetch the index blob from piri - retrievalAuth, err := extractRetrievalAuth(inv) - if err != nil { - log.Error("failed to extract retrieval auth", zap.Error(err)) - return nil, nil, fmt.Errorf("extracting retrieval auth: %w", err) - } - log.Debug("extracted retrieval auth", zap.Stringer("root", retrievalAuth.Link())) - - // Publish to indexer with retrieval authorization - if err := indexerClient.PublishIndexClaim(ctx, space, content, index, retrievalAuth); err != nil { - log.Error("failed to publish index claim", zap.Error(err)) - return nil, nil, fmt.Errorf("publishing index claim: %w", err) - } - - return result.Ok[spaceindexcap.AddOk, failure.IPLDBuilderFailure]( - spaceindexcap.AddOk{}, - ), nil, nil - } -} diff --git a/pkg/service/handlers/space_index_add_test.go b/pkg/service/handlers/space_index_add_test.go deleted file mode 100644 index a71cd66..0000000 --- a/pkg/service/handlers/space_index_add_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package handlers_test - -import ( - "testing" - - spaceindexcap "github.com/fil-forge/go-libstoracha/capabilities/space/index" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/lib/didmailto" - "github.com/fil-forge/sprue/pkg/provisioning" - "github.com/fil-forge/sprue/pkg/service/handlers" - consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" - subscription_store "github.com/fil-forge/sprue/pkg/store/subscription/memory" - ipldprime "github.com/ipld/go-ipld-prime" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - "github.com/ipld/go-ipld-prime/node/basicnode" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zaptest" -) - -func TestSpaceIndexAddHandler(t *testing.T) { - logger := zaptest.NewLogger(t) - ctx := t.Context() - - alice := testutil.Alice - aliceAccount := testutil.Must(didmailto.Parse("did:mailto:example.com:alice"))(t) - uploadService := testutil.WebService - - t.Run("invalid space DID", func(t *testing.T) { - consumerStore := consumer_store.New() - subscriptionStore := subscription_store.New() - provisioningSvc := provisioning.NewService(nil, consumerStore, subscriptionStore) - blobReg, _ := newBlobRegistry() - - handler := handlers.SpaceIndexAddHandler(provisioningSvc, blobReg, nil, logger) - - index := cidlink.Link{Cid: testutil.RandomCID(t)} - content := cidlink.Link{Cid: testutil.RandomCID(t)} - cap := ucan.NewCapability( - spaceindexcap.AddAbility, - "not-a-did", - spaceindexcap.AddCaveats{Index: index, Content: content}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.InvalidSpaceErrorName, *model.Name) - }) - - t.Run("no service providers", func(t *testing.T) { - consumerStore := consumer_store.New() - subscriptionStore := subscription_store.New() - provisioningSvc := provisioning.NewService(nil, consumerStore, subscriptionStore) - blobReg, _ := newBlobRegistry() - - handler := handlers.SpaceIndexAddHandler(provisioningSvc, blobReg, nil, logger) - - space := testutil.RandomSigner(t) - index := cidlink.Link{Cid: testutil.RandomCID(t)} - content := cidlink.Link{Cid: testutil.RandomCID(t)} - cap := ucan.NewCapability( - spaceindexcap.AddAbility, - space.DID().String(), - spaceindexcap.AddCaveats{Index: index, Content: content}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.InsufficientStorageErrorName, *model.Name) - }) - - t.Run("index not found in space", func(t *testing.T) { - consumerStore := consumer_store.New() - subscriptionStore := subscription_store.New() - provisioningSvc := provisioning.NewService(nil, consumerStore, subscriptionStore) - blobReg, _ := newBlobRegistry() - - // provision the space - err := consumerStore.Add(ctx, uploadService.DID(), testutil.RandomSigner(t).DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - space := testutil.RandomSigner(t) - err = consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - handler := handlers.SpaceIndexAddHandler(provisioningSvc, blobReg, nil, logger) - - index := cidlink.Link{Cid: testutil.RandomCID(t)} - content := cidlink.Link{Cid: testutil.RandomCID(t)} - cap := ucan.NewCapability( - spaceindexcap.AddAbility, - space.DID().String(), - spaceindexcap.AddCaveats{Index: index, Content: content}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.IndexNotFoundErrorName, *model.Name) - }) - - t.Run("missing retrieval auth fact", func(t *testing.T) { - blobReg, consumerSt := newBlobRegistry() - subscriptionStore := subscription_store.New() - provisioningSvc := provisioning.NewService(nil, consumerSt, subscriptionStore) - - space := testutil.RandomSigner(t) - err := consumerSt.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - // register the index blob in the space - indexCID := testutil.RandomCID(t) - indexBlob := types.Blob{Digest: indexCID.Hash(), Size: 512} - err = blobReg.Register(ctx, space.DID(), indexBlob, testutil.RandomCID(t)) - require.NoError(t, err) - - handler := handlers.SpaceIndexAddHandler(provisioningSvc, blobReg, nil, logger) - - index := cidlink.Link{Cid: indexCID} - content := cidlink.Link{Cid: testutil.RandomCID(t)} - cap := ucan.NewCapability( - spaceindexcap.AddAbility, - space.DID().String(), - spaceindexcap.AddCaveats{Index: index, Content: content}, - ) - - // invocation without retrievalAuth fact - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) - - _, _, err = handler(ctx, cap, inv, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "retrievalAuth") - }) - - t.Run("retrieval auth fact present", func(t *testing.T) { - blobReg, consumerSt := newBlobRegistry() - subscriptionStore := subscription_store.New() - provisioningSvc := provisioning.NewService(nil, consumerSt, subscriptionStore) - - space := testutil.RandomSigner(t) - err := consumerSt.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) - - // register the index blob in the space - indexCID := testutil.RandomCID(t) - indexBlob := types.Blob{Digest: indexCID.Hash(), Size: 512} - err = blobReg.Register(ctx, space.DID(), indexBlob, testutil.RandomCID(t)) - require.NoError(t, err) - - handler := handlers.SpaceIndexAddHandler(provisioningSvc, blobReg, nil, logger) - - index := cidlink.Link{Cid: indexCID} - content := cidlink.Link{Cid: testutil.RandomCID(t)} - cap := ucan.NewCapability( - spaceindexcap.AddAbility, - space.DID().String(), - spaceindexcap.AddCaveats{Index: index, Content: content}, - ) - - // create a retrieval auth delegation to include as a fact - retrievalAuth, err := delegation.Delegate( - alice, - uploadService, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("space/content/retrieve", space.DID().String(), ucan.NoCaveats{}), - }, - ) - require.NoError(t, err) - - inv, err := invocation.Invoke(alice, uploadService, cap, - delegation.WithFacts([]ucan.FactBuilder{ - retrievalAuthFact{link: retrievalAuth.Link()}, - }), - delegation.WithProof(delegation.FromDelegation(retrievalAuth)), - ) - require.NoError(t, err) - - // handler will fail at indexerClient.PublishIndexClaim since client is nil, - // but it should get past the extractRetrievalAuth step - _, _, err = handler(ctx, cap, inv, nil) - require.Error(t, err) - // should NOT be a retrievalAuth error - require.NotContains(t, err.Error(), "retrievalAuth") - }) -} - -// retrievalAuthFact implements ucan.FactBuilder for test invocations. -type retrievalAuthFact struct { - link ucan.Link -} - -func (f retrievalAuthFact) ToIPLD() (map[string]ipldprime.Node, error) { - return map[string]ipldprime.Node{ - "retrievalAuth": basicnode.NewLink(f.link), - }, nil -} diff --git a/pkg/service/handlers/space_info.go b/pkg/service/handlers/space_info.go index c7220e1..9bba718 100644 --- a/pkg/service/handlers/space_info.go +++ b/pkg/service/handlers/space_info.go @@ -1,79 +1,43 @@ package handlers import ( - "context" "fmt" "strings" - "github.com/fil-forge/go-libstoracha/capabilities/space" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/lib/errors" + spacecaps "github.com/fil-forge/libforge/capabilities/space" "github.com/fil-forge/sprue/pkg/provisioning" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" "go.uber.org/zap" ) -const SpaceUnknownErrorName = "SpaceUnknown" +const UnknownSpaceErrorName = "UnknownSpace" -// WithSpaceInfoMethod registers the space/info handler. // This handler returns info about a space, including its providers. -func WithSpaceInfoMethod(provisioningSvc *provisioning.Service, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - space.InfoAbility, - server.Provide(space.Info, SpaceInfoHandler(provisioningSvc, logger)), - ) -} - -func SpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logger) server.HandlerFunc[space.InfoCaveats, space.InfoOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", space.InfoAbility)) - return server.HandlerFunc[space.InfoCaveats, space.InfoOk, failure.IPLDBuilderFailure]( - func(ctx context.Context, - cap ucan.Capability[space.InfoCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[space.InfoOk, failure.IPLDBuilderFailure], fx.Effects, error) { - log := log.With(zap.String("space", cap.With())) - - if !strings.HasPrefix(cap.With(), did.KeyPrefix) { - return result.Error[space.InfoOk, failure.IPLDBuilderFailure]( - errors.New(SpaceUnknownErrorName, "can only get info for did:key spaces"), - ), nil, nil - } +func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", spacecaps.InfoCommand)) + return Handler{ + Capability: spacecaps.Info, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*spacecaps.InfoArguments], + res *bindexec.Response[*spacecaps.InfoOK], + ) error { + space := req.Invocation().Subject() + log := log.With(zap.Stringer("space", space.DID())) + log.Debug("getting space info") - spaceDID, err := did.Parse(cap.With()) - if err != nil { - return result.Error[space.InfoOk, failure.IPLDBuilderFailure]( - errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), - ), nil, nil + if !strings.HasPrefix(space.DID().String(), "did:key:") { + log.Warn("non-did:key space info requested") + return res.SetFailure(errors.New(UnknownSpaceErrorName, "can only get info for did:key spaces")) } - log.Debug("getting space info") - - providers, err := provisioningSvc.ListServiceProviders(ctx, spaceDID) + providers, err := provisioningSvc.ListServiceProviders(req.Context(), space.DID()) if err != nil { log.Error("failed to list service providers", zap.Error(err)) - return nil, nil, fmt.Errorf("listing service providers: %w", err) - } - - if len(providers) == 0 { - return result.Error[space.InfoOk, failure.IPLDBuilderFailure]( - errors.New(SpaceUnknownErrorName, "space not found"), - ), nil, nil - } - - providerStrings := make([]string, 0, len(providers)) - for _, p := range providers { - providerStrings = append(providerStrings, p.String()) + return fmt.Errorf("listing service providers: %w", err) } - return result.Ok[space.InfoOk, failure.IPLDBuilderFailure](space.InfoOk{ - Did: spaceDID.String(), - Providers: providerStrings, - }), nil, nil - }) + return res.SetSuccess(&spacecaps.InfoOK{Providers: providers}) + }), + } } diff --git a/pkg/service/handlers/space_info_test.go b/pkg/service/handlers/space_info_test.go index 6a37c87..b824740 100644 --- a/pkg/service/handlers/space_info_test.go +++ b/pkg/service/handlers/space_info_test.go @@ -1,21 +1,51 @@ package handlers_test import ( + "context" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/space" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" + spacecaps "github.com/fil-forge/libforge/capabilities/space" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/lib/didmailto" "github.com/fil-forge/sprue/pkg/provisioning" "github.com/fil-forge/sprue/pkg/service/handlers" - consumermemory "github.com/fil-forge/sprue/pkg/store/consumer/memory" - subscriptionmemory "github.com/fil-forge/sprue/pkg/store/subscription/memory" + consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" + subscription_store "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) +// invokeSpaceInfo builds a /space/info invocation against the given space and +// returns the request + signed response. +func invokeSpaceInfo( + t *testing.T, + ctx context.Context, + agent principal.Signer, + uploadService principal.Signer, + space ucan.Principal, +) (execution.Request, *execution.ExecResponse) { + t.Helper() + inv, err := spacecaps.Info.Invoke( + agent, + space, + &spacecaps.InfoArguments{}, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + return req, res +} + func TestSpaceInfoHandler(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() @@ -23,109 +53,83 @@ func TestSpaceInfoHandler(t *testing.T) { uploadService := testutil.WebService t.Run("returns providers for a provisioned space", func(t *testing.T) { + consumerStore := consumer_store.New() + subscriptionStore := subscription_store.New() provisioningSvc := provisioning.NewService( - []provisioning.ServiceDID{uploadService.DID()}, - consumermemory.New(), - subscriptionmemory.New(), + []did.DID{uploadService.DID()}, + consumerStore, + subscriptionStore, ) - handler := handlers.SpaceInfoHandler(provisioningSvc, logger) - - spaceSigner := testutil.RandomSigner(t) - account := testutil.Must(didmailto.Parse("did:mailto:example.com:alice"))(t) + handler := handlers.NewSpaceInfoHandler(provisioningSvc, logger) - _, err := provisioningSvc.Provision(ctx, account, spaceSigner.DID(), uploadService.DID(), testutil.RandomCID(t)) - require.NoError(t, err) + space := testutil.RandomSigner(t) + account := testutil.Must(didmailto.New("alice@example.com"))(t) - caveats := space.InfoCaveats{} - inv, err := space.Info.Invoke(testutil.Alice, uploadService, spaceSigner.DID().String(), caveats) + _, err := provisioningSvc.Provision(ctx, account, space.DID(), uploadService.DID(), testutil.RandomCID(t)) require.NoError(t, err) - cap := space.Info.New(spaceSigner.DID().String(), caveats) + req, res := invokeSpaceInfo(t, ctx, testutil.Alice, uploadService, space) - res, _, err := handler(ctx, cap, inv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.Equal(t, spaceSigner.DID().String(), ok.Did) + require.NotNil(t, o) + + ok := spacecaps.InfoOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) require.Len(t, ok.Providers, 1) - require.Equal(t, uploadService.DID().String(), ok.Providers[0]) + require.Equal(t, uploadService.DID(), ok.Providers[0]) }) - t.Run("returns SpaceUnknown for unprovisioned space", func(t *testing.T) { + t.Run("returns empty providers for unprovisioned did:key space", func(t *testing.T) { provisioningSvc := provisioning.NewService( - []provisioning.ServiceDID{uploadService.DID()}, - consumermemory.New(), - subscriptionmemory.New(), + []did.DID{uploadService.DID()}, + consumer_store.New(), + subscription_store.New(), ) - handler := handlers.SpaceInfoHandler(provisioningSvc, logger) - - spaceSigner := testutil.RandomSigner(t) + handler := handlers.NewSpaceInfoHandler(provisioningSvc, logger) - caveats := space.InfoCaveats{} - inv, err := space.Info.Invoke(testutil.Alice, uploadService, spaceSigner.DID().String(), caveats) - require.NoError(t, err) + space := testutil.RandomSigner(t) - cap := space.Info.New(spaceSigner.DID().String(), caveats) + req, res := invokeSpaceInfo(t, ctx, testutil.Alice, uploadService, space) - res, _, err := handler(ctx, cap, inv, nil) + err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) - require.NotNil(t, fail) + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.SpaceUnknownErrorName, *model.Name) + ok := spacecaps.InfoOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.Empty(t, ok.Providers) }) - t.Run("returns SpaceUnknown for non did:key space", func(t *testing.T) { + t.Run("returns UnknownSpace for non did:key space", func(t *testing.T) { provisioningSvc := provisioning.NewService( - []provisioning.ServiceDID{}, - consumermemory.New(), - subscriptionmemory.New(), + []did.DID{}, + consumer_store.New(), + subscription_store.New(), ) - handler := handlers.SpaceInfoHandler(provisioningSvc, logger) + handler := handlers.NewSpaceInfoHandler(provisioningSvc, logger) - caveats := space.InfoCaveats{} - inv, err := space.Info.Invoke(testutil.Alice, uploadService, "did:web:example.com", caveats) - require.NoError(t, err) - - cap := space.Info.New("did:web:example.com", caveats) + // did:web subject — handler rejects since only did:key spaces are + // supported. + webDID := testutil.Must(did.Parse("did:web:example.com"))(t) + req, res := invokeSpaceInfo(t, ctx, testutil.Alice, uploadService, webDID) - res, _, err := handler(ctx, cap, inv, nil) + err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.NotNil(t, fail) - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.SpaceUnknownErrorName, *model.Name) - }) - - t.Run("invalid space DID", func(t *testing.T) { - provisioningSvc := provisioning.NewService( - []provisioning.ServiceDID{}, - consumermemory.New(), - subscriptionmemory.New(), - ) - - handler := handlers.SpaceInfoHandler(provisioningSvc, logger) - - caveats := space.InfoCaveats{} - inv, err := space.Info.Invoke(testutil.Alice, uploadService, "not-a-did", caveats) - require.NoError(t, err) - - cap := space.Info.New("not-a-did", caveats) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) + model := edm.ErrorModel{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + require.Equal(t, handlers.UnknownSpaceErrorName, model.Name()) }) } diff --git a/pkg/service/handlers/ucan_conclude.go b/pkg/service/handlers/ucan_conclude.go index 166ab0a..d551934 100644 --- a/pkg/service/handlers/ucan_conclude.go +++ b/pkg/service/handlers/ucan_conclude.go @@ -5,117 +5,104 @@ import ( "fmt" "maps" "slices" - "time" - captypes "github.com/fil-forge/go-libstoracha/capabilities/types" - ucancap "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" + ucancaps "github.com/fil-forge/libforge/capabilities/ucan" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store/agent" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) -const InvalidInvocationErrorName = "InvalidInvocation" +const ( + InvalidInvocationErrorName = "InvalidInvocation" + ConclusionReceiptNotFoundErrorName = "ConclusionReceiptNotFound" +) + +var ErrConclusionReceiptNotFound = errors.New(ConclusionReceiptNotFoundErrorName, "conclusion receipt not found") -type ConclusionHandlerFunc func(context.Context, invocation.Invocation, receipt.AnyReceipt, server.InvocationContext) error +type ConclusionHandlerFunc func(context.Context, ucan.Invocation, ucan.Receipt, ucan.Container) error // ConclusionHandler is the definition of a handler for an invocation conclusion // - a receiver for a receipt attesting to an invocation result. type ConclusionHandler struct { - // Ability is the invoked ability this handler is expecting to receive + // Command is the invoked command this handler is expecting to receive // conclusions for. - Ability ucan.Ability + Command ucan.Command // Handler is the function that receives the conclusion for the invocation. Handler ConclusionHandlerFunc } -// WithUCANConcludeMethod registers the ucan/conclude handler. +// NewUCANConcludeHandler creates a handler for /ucan/conclude invocations. // This handler processes receipt conclusions from clients. -// When it receives an http/put receipt, it calls blob/accept on piri +// When it receives an /http/put receipt, it calls /blob/accept on piri // and stores the accept receipt for later retrieval. -func WithUCANConcludeMethod(id *identity.Identity, agentStore agent.Store, handlers map[ucan.Ability]ConclusionHandlerFunc, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - ucancap.ConcludeAbility, - server.Provide( - ucancap.Conclude, - UCANConcludeHandler(id, agentStore, handlers, logger), - ), - ) -} +func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handlers map[ucan.Command]ConclusionHandlerFunc, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", ucancaps.ConcludeCommand)) + log.Info("registered conclude handlers", zap.Stringers("commands", slices.Collect(maps.Keys(handlers)))) + return Handler{ + Capability: ucancaps.Conclude, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*ucancaps.ConcludeArguments], + res *bindexec.Response[*ucancaps.ConcludeOK], + ) error { + args := req.Task().BindArguments() + rcptRoot := args.Receipt -func UCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handlers map[ucan.Ability]ConclusionHandlerFunc, logger *zap.Logger) server.HandlerFunc[ucancap.ConcludeCaveats, ucancap.ConcludeOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", ucancap.ConcludeAbility)) - log.Info("registered conclude handlers", zap.Strings("abilities", slices.Collect(maps.Keys(handlers)))) - return func(ctx context.Context, - cap ucan.Capability[ucancap.ConcludeCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[ucancap.ConcludeOk, failure.IPLDBuilderFailure], fx.Effects, error) { - rcptRoot := cap.Nb().Receipt + log := log.With(zap.Stringer("receipt", rcptRoot)) - log := log.With(zap.Stringer("receipt", rcptRoot)) + log.Debug("concluding received receipt", zap.Stringer("receipt", rcptRoot)) - log.Debug("concluding received receipt", zap.String("receipt", rcptRoot.String())) - - // Read the concluded receipt from the invocation's attached blocks - anyReader := receipt.NewAnyReceiptReader(captypes.Converters...) - rcpt, err := anyReader.Read(rcptRoot, inv.Blocks()) - if err != nil { - log.Error("failed to read concluded receipt", zap.Error(err)) - return nil, nil, fmt.Errorf("reading receipt: %w", err) - } - - task, err := ipldutil.ToCID(rcpt.Ran().Link()) - if err != nil { - return nil, nil, err - } + var rcpt ucan.Receipt + if req.Metadata() != nil { + for _, r := range req.Metadata().Receipts() { + if r.Link() == rcptRoot { + rcpt = r + } + } + } + if rcpt == nil { + log.Warn("receipt not found in invocation metadata") + return res.SetFailure(ErrConclusionReceiptNotFound) + } + log = log.With(zap.Stringer("task", rcpt.Ran())) - // Get the invocation that the receipt is for - ranInv, ok := rcpt.Ran().Invocation() - if !ok { - inv, err := agentStore.GetInvocation(ctx, task) - if err != nil { - // If can not find task for this receipt there is nothing to do here, if - // it was a receipt for something we care about we would have an - // invocation recorded. - if errors.Is(err, agent.ErrInvocationNotFound) { - return result.Ok[ucancap.ConcludeOk, failure.IPLDBuilderFailure](ucancap.ConcludeOk{Time: time.Now()}), nil, nil + var ranInv ucan.Invocation + // check if the invocation was included in the invocation metadata + for _, inv := range req.Metadata().Invocations() { + if inv.Task().Link() == rcpt.Ran() { + ranInv = inv } - log.Error("failed to get invocation from agent store", zap.Error(err)) - return nil, nil, fmt.Errorf("getting invocation: %w", err) } - ranInv = inv - } - if len(ranInv.Capabilities()) == 0 { - log.Warn("invocation has no capabilities") - return nil, nil, errors.New(InvalidInvocationErrorName, "invocation has no capabilities") - } + // if not included in invocation, check our store + if ranInv == nil { + inv, err := agentStore.GetInvocation(req.Context(), rcpt.Ran()) + if err != nil { + // If can not find invocation for this receipt there is nothing to do + // here, if it was a receipt for something we care about we would have + // an invocation recorded. + if errors.Is(err, agent.ErrInvocationNotFound) { + return res.SetSuccess(&ucancaps.ConcludeOK{}) + } + log.Error("failed to get invocation from agent store", zap.Error(err)) + return fmt.Errorf("getting invocation: %w", err) + } + ranInv = inv + } - ability := ranInv.Capabilities()[0].Can() - log = log.With( - zap.Stringer("ran", ranInv.Link()), - zap.String("ability", ability), - ) - log.Debug("found invocation for conclusion") + log = log.With(zap.Stringer("command", ranInv.Command())) + log.Debug("found invocation for conclusion") - if handler, ok := handlers[ability]; ok { - err := handler(ctx, ranInv, rcpt, iCtx) - if err != nil { - log.Error("failed to conclude receipt", zap.Error(err)) - return nil, nil, fmt.Errorf("concluding %q: %w", ability, err) + if handler, ok := handlers[ranInv.Command()]; ok { + err := handler(req.Context(), ranInv, rcpt, req.Metadata()) + if err != nil { + log.Error("failed to conclude invocation", zap.Error(err)) + return fmt.Errorf("concluding %q: %w", ranInv.Command(), err) + } } - } - return result.Ok[ucancap.ConcludeOk, failure.IPLDBuilderFailure]( - ucancap.ConcludeOk{Time: time.Now()}, - ), nil, nil + return res.SetSuccess(&ucancaps.ConcludeOK{}) + }), } } diff --git a/pkg/service/handlers/ucan_conclude_blob_replica_transfer.go b/pkg/service/handlers/ucan_conclude_blob_replica_transfer.go index 0336fa8..f90d735 100644 --- a/pkg/service/handlers/ucan_conclude_blob_replica_transfer.go +++ b/pkg/service/handlers/ucan_conclude_blob_replica_transfer.go @@ -1,218 +1,218 @@ package handlers -import ( - "bytes" - "context" - "fmt" - - replicacaps "github.com/fil-forge/go-libstoracha/capabilities/blob/replica" - ucancaps "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/result" - fdm "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/principal" - "github.com/fil-forge/go-ucanto/principal/ed25519/verifier" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/go-ucanto/validator" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" - "github.com/fil-forge/sprue/pkg/store/agent" - "github.com/fil-forge/sprue/pkg/store/replica" - "go.uber.org/zap" -) - -const ( - InvalidReplicaTransferArgsErrorName = "InvalidReplicaTransferArgs" - InvalidReplicaTransferCauseErrorName = "InvalidReplicaTransferCause" - UnknownReplicaAllocationErrorName = "UnknownReplicaAllocation" - ReplicaTransferArgMismatchErrorName = "ReplicaTransferArgMismatch" - ReplicaAllocationFailedErrorName = "ReplicaAllocationFailed" - InvalidTransferReceiptSignatureErrorName = "InvalidTransferReceiptSignature" -) - -var ErrInvalidTransferReceiptSignature = errors.New(InvalidTransferReceiptSignatureErrorName, "invalid transfer receipt signature") - -func NewBlobReplicaTransferConcludeHandler( - id *identity.Identity, - agentStore agent.Store, - replicaStore replica.Store, - logger *zap.Logger, -) ConclusionHandler { - log := logger.With( - zap.String("handler", ucancaps.ConcludeAbility), - zap.String("conclude", replicacaps.TransferAbility), - ) - return ConclusionHandler{ - Ability: replicacaps.TransferAbility, - Handler: func(ctx context.Context, transferTask invocation.Invocation, transferRcpt receipt.AnyReceipt, iCtx server.InvocationContext) error { - log := log.With(zap.Stringer("ran", transferRcpt.Ran().Link())) - log.Debug("handling conclude") - - var err error - transferCap := transferTask.Capabilities()[0] - transferMatch, err := replicacaps.Transfer.Match(validator.NewSource(transferCap, transferTask)) - if err != nil { - log.Warn("failed to match replica transfer parameters", zap.Error(err)) - return errors.New(InvalidReplicaTransferArgsErrorName, "invalid replica transfer parameters: %v", err) - } - transferArgs := transferMatch.Value().Nb() - log = log.With(zap.Stringer("allocation", transferArgs.Cause)) - - allocTaskLink, err := ipldutil.ToCID(transferArgs.Cause) - if err != nil { - return err - } - - allocTask, err := agentStore.GetInvocation(ctx, allocTaskLink) - if err != nil { - log.Error("failed to get replica allocation invocation", zap.Error(err)) - return fmt.Errorf("getting replica allocation invocation: %w", err) - } - - ok, err := ucan.VerifySignature(allocTask.Data(), id.Signer.Verifier()) - if err != nil { - log.Error("failed to verify replica allocation invocation signature", zap.Error(err)) - return fmt.Errorf("verifying replica allocation invocation signature: %w", err) - } - // shouldn't happen - we should only store invocations made by our service... - if !ok { - log.Warn("replica allocation invocation issued by unknown DID", zap.Stringer("issuer", allocTask.Issuer().DID())) - return errors.New(UnknownReplicaAllocationErrorName, "allocation was not issued by this service") - } - - if len(allocTask.Capabilities()) != 1 { - log.Warn("invalid replica allocation invocation: expected exactly 1 capability", zap.Int("capabilities", len(allocTask.Capabilities()))) - return errors.New(InvalidReplicaTransferCauseErrorName, "invalid replica allocation invocation: expected exactly 1 capability, got %d", len(allocTask.Capabilities())) - } - - allocCap := allocTask.Capabilities()[0] - allocMatch, err := replicacaps.Allocate.Match(validator.NewSource(allocCap, allocTask)) - if err != nil { - log.Warn("failed to match replica allocation parameters", zap.Error(err)) - return errors.New(InvalidReplicaTransferCauseErrorName, "invalid replica allocation parameters: %v", err) - } - allocArgs := allocMatch.Value().Nb() - log = log.With( - zap.Stringer("space", allocArgs.Space), - zap.Dict( - "blob", - zap.String("digest", digestutil.Format(allocArgs.Blob.Digest)), - zap.Uint64("size", allocArgs.Blob.Size), - ), - ) - - var executor principal.Verifier - if transferRcpt.Issuer() != nil { - executor, err = verifier.Parse(transferRcpt.Issuer().DID().String()) - } else { - executor, err = verifier.Parse(transferTask.Audience().DID().String()) - } - if err != nil { - log.Warn("failed to parse executor DID", zap.Error(err)) - return fmt.Errorf("parsing executor DID: %w", err) - } - log = log.With(zap.Stringer("executor", executor.DID())) - - if executor.DID() != allocTask.Audience().DID() { - log.Warn("transfer executor does not match replica allocation audience", zap.Stringer("expected", allocTask.Audience().DID())) - return errors.New(ReplicaTransferArgMismatchErrorName, "transfer executor does not match replica allocation audience") - } - - updateReplicaStatus := func(status replica.ReplicationStatus) error { - err = replicaStore.SetStatus(ctx, allocArgs.Space, allocArgs.Blob.Digest, executor.DID(), status) - if err != nil { - log.Error("failed to update replica status", zap.Error(err)) - return err - } - return nil - } - - // verify the receipt was signed by the executor - ok, err = transferRcpt.VerifySignature(executor) - if err != nil { - log.Error("failed to verify receipt signature", zap.Error(err)) - return fmt.Errorf("verifying receipt signature: %w", err) - } - if !ok { - log.Warn("invalid receipt signature", zap.Error(err)) - _ = updateReplicaStatus(replica.Failed) - return ErrInvalidTransferReceiptSignature - } - - // verify the executor has delegated capability - proofs := []delegation.Proof{delegation.FromDelegation(transferTask)} - proofs = append(proofs, transferRcpt.Proofs()...) - _, err = validator.Claim( - ctx, - replicacaps.Transfer, - proofs, - validator.NewClaimContext( - executor, - iCtx.CanIssue, - iCtx.ValidateAuthorization, - iCtx.ResolveProof, - iCtx.ParsePrincipal, - iCtx.ResolveDIDKey, - iCtx.ValidateTimeBounds, - iCtx.AuthorityProofs()..., - ), - ) - if err != nil { - log.Warn("failed to validate executor capability", zap.Error(err)) - _ = updateReplicaStatus(replica.Failed) - return fmt.Errorf("validating executor capability: %w", err) - } - - allocRcpt, err := agentStore.GetReceipt(ctx, allocTaskLink) - if err != nil { - log.Error("failed to get replica allocation receipt", zap.Error(err)) - return fmt.Errorf("getting replica allocation receipt: %w", err) - } - - err = result.MatchResultR1( - allocRcpt.Out(), - func(o ipld.Node) error { return nil }, - func(x ipld.Node) error { return fdm.Bind(x) }, - ) - // if the receipt for the allocation was in error we should not be - // receiving a conclude for the transfer - if err != nil { - log.Warn("replica allocation failed", zap.Error(err)) - _ = updateReplicaStatus(replica.Failed) - return errors.New(ReplicaAllocationFailedErrorName, "Allocation associated with this transfer has failed: %s", err.Error()) - } - - if !bytes.Equal(transferArgs.Blob.Digest, allocArgs.Blob.Digest) || - transferArgs.Blob.Size != allocArgs.Blob.Size || - transferArgs.Space != allocArgs.Space { - log.Warn("transfer parameters do not match allocation parameters") - _ = updateReplicaStatus(replica.Failed) - return errors.New(ReplicaTransferArgMismatchErrorName, "transfer parameters do not match allocation parameters") - } - - status := result.MatchResultR1( - transferRcpt.Out(), - func(o ipld.Node) replica.ReplicationStatus { - return replica.Transferred - }, - func(x ipld.Node) replica.ReplicationStatus { - log.Warn("replica transfer failed", zap.Error(fdm.Bind(x))) - return replica.Failed - }, - ) - - err = updateReplicaStatus(status) - if err != nil { - return fmt.Errorf("updating replica status: %w", err) - } - - return nil - }, - } -} +// import ( +// "bytes" +// "context" +// "fmt" + +// "github.com/fil-forge/ucantone/errors" +// replicacaps "github.com/storacha/go-libstoracha/capabilities/blob/replica" +// ucancaps "github.com/storacha/go-libstoracha/capabilities/ucan" +// "github.com/storacha/go-libstoracha/digestutil" +// "github.com/storacha/go-ucanto/core/delegation" +// "github.com/storacha/go-ucanto/core/invocation" +// "github.com/storacha/go-ucanto/core/ipld" +// "github.com/storacha/go-ucanto/core/receipt" +// "github.com/storacha/go-ucanto/core/result" +// fdm "github.com/storacha/go-ucanto/core/result/failure/datamodel" +// "github.com/storacha/go-ucanto/principal" +// "github.com/storacha/go-ucanto/principal/ed25519/verifier" +// "github.com/storacha/go-ucanto/server" +// "github.com/storacha/go-ucanto/ucan" +// "github.com/storacha/go-ucanto/validator" +// "github.com/fil-forge/sprue/pkg/identity" +// "github.com/fil-forge/sprue/pkg/internal/ipldutil" +// "github.com/fil-forge/sprue/pkg/store/agent" +// "github.com/fil-forge/sprue/pkg/store/replica" +// "go.uber.org/zap" +// ) + +// const ( +// InvalidReplicaTransferArgsErrorName = "InvalidReplicaTransferArgs" +// InvalidReplicaTransferCauseErrorName = "InvalidReplicaTransferCause" +// UnknownReplicaAllocationErrorName = "UnknownReplicaAllocation" +// ReplicaTransferArgMismatchErrorName = "ReplicaTransferArgMismatch" +// ReplicaAllocationFailedErrorName = "ReplicaAllocationFailed" +// InvalidTransferReceiptSignatureErrorName = "InvalidTransferReceiptSignature" +// ) + +// var ErrInvalidTransferReceiptSignature = errors.New(InvalidTransferReceiptSignatureErrorName, "invalid transfer receipt signature") + +// func NewBlobReplicaTransferConcludeHandler( +// id *identity.Identity, +// agentStore agent.Store, +// replicaStore replica.Store, +// logger *zap.Logger, +// ) ConclusionHandler { +// log := logger.With( +// zap.String("handler", ucancaps.ConcludeAbility), +// zap.String("conclude", replicacaps.TransferAbility), +// ) +// return ConclusionHandler{ +// Ability: replicacaps.TransferAbility, +// Handler: func(ctx context.Context, transferTask invocation.Invocation, transferRcpt receipt.AnyReceipt, iCtx server.InvocationContext) error { +// log := log.With(zap.Stringer("ran", transferRcpt.Ran().Link())) +// log.Debug("handling conclude") + +// var err error +// transferCap := transferTask.Capabilities()[0] +// transferMatch, err := replicacaps.Transfer.Match(validator.NewSource(transferCap, transferTask)) +// if err != nil { +// log.Warn("failed to match replica transfer parameters", zap.Error(err)) +// return errors.New(InvalidReplicaTransferArgsErrorName, "invalid replica transfer parameters: %v", err) +// } +// transferArgs := transferMatch.Value().Nb() +// log = log.With(zap.Stringer("allocation", transferArgs.Cause)) + +// allocTaskLink, err := ipldutil.ToCID(transferArgs.Cause) +// if err != nil { +// return err +// } + +// allocTask, err := agentStore.GetInvocation(ctx, allocTaskLink) +// if err != nil { +// log.Error("failed to get replica allocation invocation", zap.Error(err)) +// return fmt.Errorf("getting replica allocation invocation: %w", err) +// } + +// ok, err := ucan.VerifySignature(allocTask.Data(), id.Signer.Verifier()) +// if err != nil { +// log.Error("failed to verify replica allocation invocation signature", zap.Error(err)) +// return fmt.Errorf("verifying replica allocation invocation signature: %w", err) +// } +// // shouldn't happen - we should only store invocations made by our service... +// if !ok { +// log.Warn("replica allocation invocation issued by unknown DID", zap.Stringer("issuer", allocTask.Issuer().DID())) +// return errors.New(UnknownReplicaAllocationErrorName, "allocation was not issued by this service") +// } + +// if len(allocTask.Capabilities()) != 1 { +// log.Warn("invalid replica allocation invocation: expected exactly 1 capability", zap.Int("capabilities", len(allocTask.Capabilities()))) +// return errors.New(InvalidReplicaTransferCauseErrorName, "invalid replica allocation invocation: expected exactly 1 capability, got %d", len(allocTask.Capabilities())) +// } + +// allocCap := allocTask.Capabilities()[0] +// allocMatch, err := replicacaps.Allocate.Match(validator.NewSource(allocCap, allocTask)) +// if err != nil { +// log.Warn("failed to match replica allocation parameters", zap.Error(err)) +// return errors.New(InvalidReplicaTransferCauseErrorName, "invalid replica allocation parameters: %v", err) +// } +// allocArgs := allocMatch.Value().Nb() +// log = log.With( +// zap.Stringer("space", allocArgs.Space), +// zap.Dict( +// "blob", +// zap.String("digest", digestutil.Format(allocArgs.Blob.Digest)), +// zap.Uint64("size", allocArgs.Blob.Size), +// ), +// ) + +// var executor principal.Verifier +// if transferRcpt.Issuer() != nil { +// executor, err = verifier.Parse(transferRcpt.Issuer().DID().String()) +// } else { +// executor, err = verifier.Parse(transferTask.Audience().DID().String()) +// } +// if err != nil { +// log.Warn("failed to parse executor DID", zap.Error(err)) +// return fmt.Errorf("parsing executor DID: %w", err) +// } +// log = log.With(zap.Stringer("executor", executor.DID())) + +// if executor.DID() != allocTask.Audience().DID() { +// log.Warn("transfer executor does not match replica allocation audience", zap.Stringer("expected", allocTask.Audience().DID())) +// return errors.New(ReplicaTransferArgMismatchErrorName, "transfer executor does not match replica allocation audience") +// } + +// updateReplicaStatus := func(status replica.ReplicationStatus) error { +// err = replicaStore.SetStatus(ctx, allocArgs.Space, allocArgs.Blob.Digest, executor.DID(), status) +// if err != nil { +// log.Error("failed to update replica status", zap.Error(err)) +// return err +// } +// return nil +// } + +// // verify the receipt was signed by the executor +// ok, err = transferRcpt.VerifySignature(executor) +// if err != nil { +// log.Error("failed to verify receipt signature", zap.Error(err)) +// return fmt.Errorf("verifying receipt signature: %w", err) +// } +// if !ok { +// log.Warn("invalid receipt signature", zap.Error(err)) +// _ = updateReplicaStatus(replica.Failed) +// return ErrInvalidTransferReceiptSignature +// } + +// // verify the executor has delegated capability +// proofs := []delegation.Proof{delegation.FromDelegation(transferTask)} +// proofs = append(proofs, transferRcpt.Proofs()...) +// _, err = validator.Claim( +// ctx, +// replicacaps.Transfer, +// proofs, +// validator.NewClaimContext( +// executor, +// iCtx.CanIssue, +// iCtx.ValidateAuthorization, +// iCtx.ResolveProof, +// iCtx.ParsePrincipal, +// iCtx.ResolveDIDKey, +// iCtx.ValidateTimeBounds, +// iCtx.AuthorityProofs()..., +// ), +// ) +// if err != nil { +// log.Warn("failed to validate executor capability", zap.Error(err)) +// _ = updateReplicaStatus(replica.Failed) +// return fmt.Errorf("validating executor capability: %w", err) +// } + +// allocRcpt, err := agentStore.GetReceipt(ctx, allocTaskLink) +// if err != nil { +// log.Error("failed to get replica allocation receipt", zap.Error(err)) +// return fmt.Errorf("getting replica allocation receipt: %w", err) +// } + +// err = result.MatchResultR1( +// allocRcpt.Out(), +// func(o ipld.Node) error { return nil }, +// func(x ipld.Node) error { return fdm.Bind(x) }, +// ) +// // if the receipt for the allocation was in error we should not be +// // receiving a conclude for the transfer +// if err != nil { +// log.Warn("replica allocation failed", zap.Error(err)) +// _ = updateReplicaStatus(replica.Failed) +// return errors.New(ReplicaAllocationFailedErrorName, "Allocation associated with this transfer has failed: %s", err.Error()) +// } + +// if !bytes.Equal(transferArgs.Blob.Digest, allocArgs.Blob.Digest) || +// transferArgs.Blob.Size != allocArgs.Blob.Size || +// transferArgs.Space != allocArgs.Space { +// log.Warn("transfer parameters do not match allocation parameters") +// _ = updateReplicaStatus(replica.Failed) +// return errors.New(ReplicaTransferArgMismatchErrorName, "transfer parameters do not match allocation parameters") +// } + +// status := result.MatchResultR1( +// transferRcpt.Out(), +// func(o ipld.Node) replica.ReplicationStatus { +// return replica.Transferred +// }, +// func(x ipld.Node) replica.ReplicationStatus { +// log.Warn("replica transfer failed", zap.Error(fdm.Bind(x))) +// return replica.Failed +// }, +// ) + +// err = updateReplicaStatus(status) +// if err != nil { +// return fmt.Errorf("updating replica status: %w", err) +// } + +// return nil +// }, +// } +// } diff --git a/pkg/service/handlers/ucan_conclude_blob_replica_transfer_test.go b/pkg/service/handlers/ucan_conclude_blob_replica_transfer_test.go index aecfc62..a91addb 100644 --- a/pkg/service/handlers/ucan_conclude_blob_replica_transfer_test.go +++ b/pkg/service/handlers/ucan_conclude_blob_replica_transfer_test.go @@ -1,352 +1,352 @@ package handlers_test -import ( - "io" - "testing" - - replicacaps "github.com/fil-forge/go-libstoracha/capabilities/blob/replica" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-ucanto/core/car" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/receipt/ran" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/ok" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/service/handlers" - "github.com/fil-forge/sprue/pkg/store/agent" - agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" - "github.com/fil-forge/sprue/pkg/store/replica" - replica_store "github.com/fil-forge/sprue/pkg/store/replica/memory" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zaptest" -) - -// writeAgentMessage writes invocations and receipts to the agent store using -// the same pattern as the production writeAgentMessage helper. -func writeAgentMessage(t *testing.T, store agent.Store, invocations []invocation.Invocation, receipts []receipt.AnyReceipt) { - t.Helper() - msg, err := message.Build(invocations, receipts) - require.NoError(t, err) - - var idx []agent.IndexEntry - for entry, err := range agent.Index(msg) { - require.NoError(t, err) - idx = append(idx, entry) - } - - src, err := io.ReadAll(car.Encode([]ipld.Link{msg.Root().Link()}, msg.Blocks())) - require.NoError(t, err) - - err = store.Write(t.Context(), msg, idx, src) - require.NoError(t, err) -} - -func mockInvocationContext(t *testing.T) server.InvocationContext { - t.Helper() - s, err := server.NewServer(testutil.RandomSigner(t)) - require.NoError(t, err) - return s.Context() -} - -func TestBlobReplicaTransferConcludeHandler(t *testing.T) { - logger := zaptest.NewLogger(t) - ctx := t.Context() - - uploadService := testutil.WebService - storageProvider := testutil.RandomSigner(t) - - t.Run("invalid transfer parameters", func(t *testing.T) { - agentStore := agent_store.New() - replicaStore := replica_store.New() - - ch := handlers.NewBlobReplicaTransferConcludeHandler( - &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, - ) - - // Create an invocation with wrong capability (not blob/replica/transfer) - cap := ucan.NewCapability( - "blob/allocate", - storageProvider.DID().String(), - ucan.NoCaveats{}, - ) - transferInv, err := invocation.Invoke(storageProvider, uploadService, cap) - require.NoError(t, err) - - transferRcpt, err := receipt.Issue( - storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(transferInv), - ) - require.NoError(t, err) - - err = ch.Handler(ctx, transferInv, transferRcpt, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid replica transfer parameters") - }) - - t.Run("allocation invocation not found", func(t *testing.T) { - agentStore := agent_store.New() - replicaStore := replica_store.New() - - ch := handlers.NewBlobReplicaTransferConcludeHandler( - &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, - ) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - site := cidlink.Link{Cid: testutil.RandomCID(t)} - // cause points to a non-existent allocation invocation - cause := cidlink.Link{Cid: testutil.RandomCID(t)} - - transferCap := ucan.NewCapability( - replicacaps.TransferAbility, - storageProvider.DID().String(), - replicacaps.TransferCaveats{ - Space: space.DID(), - Blob: blob, - Site: site, - Cause: cause, - }, - ) - transferInv, err := invocation.Invoke(storageProvider, uploadService, transferCap) - require.NoError(t, err) - - transferRcpt, err := receipt.Issue( - storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(transferInv), - ) - require.NoError(t, err) - - err = ch.Handler(ctx, transferInv, transferRcpt, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "getting replica allocation invocation") - }) - - t.Run("allocation not signed by service", func(t *testing.T) { - agentStore := agent_store.New() - replicaStore := replica_store.New() - - ch := handlers.NewBlobReplicaTransferConcludeHandler( - &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, - ) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - site := cidlink.Link{Cid: testutil.RandomCID(t)} - replicateCause := cidlink.Link{Cid: testutil.RandomCID(t)} - - // Create allocation invocation signed by someone OTHER than uploadService - imposter := testutil.RandomSigner(t) - allocCap := ucan.NewCapability( - replicacaps.AllocateAbility, - storageProvider.DID().String(), - replicacaps.AllocateCaveats{ - Space: space.DID(), - Blob: blob, - Site: site, - Cause: replicateCause, - }, - ) - allocInv, err := invocation.Invoke(imposter, storageProvider, allocCap) - require.NoError(t, err) - - allocRcpt, err := receipt.Issue( - storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(allocInv), - ) - require.NoError(t, err) - - // Store the allocation in the agent store - writeAgentMessage(t, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) - - // Create transfer invocation referencing the allocation - transferCap := ucan.NewCapability( - replicacaps.TransferAbility, - storageProvider.DID().String(), - replicacaps.TransferCaveats{ - Space: space.DID(), - Blob: blob, - Site: site, - Cause: allocInv.Link(), - }, - ) - transferInv, err := invocation.Invoke(storageProvider, uploadService, transferCap) - require.NoError(t, err) - - transferRcpt, err := receipt.Issue( - storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(transferInv), - ) - require.NoError(t, err) - - err = ch.Handler(ctx, transferInv, transferRcpt, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "allocation was not issued by this service") - }) - - t.Run("success updates replica status to transferred", func(t *testing.T) { - agentStore := agent_store.New() - replicaStore := replica_store.New() - - ch := handlers.NewBlobReplicaTransferConcludeHandler( - &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, - ) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - site := cidlink.Link{Cid: testutil.RandomCID(t)} - replicateCause := cidlink.Link{Cid: testutil.RandomCID(t)} - - // storageProvider delegates to uploadService so it can invoke allocate - allocProof, err := delegation.Delegate( - storageProvider, uploadService, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability( - replicacaps.AllocateAbility, - storageProvider.DID().String(), - ucan.NoCaveats{}, - ), - }, - ) - require.NoError(t, err) - - // uploadService invokes allocate on storageProvider with the delegation as proof - allocInv, err := replicacaps.Allocate.Invoke( - uploadService, storageProvider, - storageProvider.DID().String(), - replicacaps.AllocateCaveats{ - Space: space.DID(), - Blob: blob, - Site: site, - Cause: replicateCause, - }, - delegation.WithProof(delegation.FromDelegation(allocProof)), - ) - require.NoError(t, err) - - allocRcpt, err := receipt.Issue( - storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(allocInv), - ) - require.NoError(t, err) - - writeAgentMessage(t, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) - - // Add a replica record so SetStatus succeeds - err = replicaStore.Add(ctx, space.DID(), digest, storageProvider.DID(), replica.Allocated, testutil.RandomCID(t)) - require.NoError(t, err) - - // storageProvider self-issues the transfer invocation - transferInv, err := replicacaps.Transfer.Invoke( - storageProvider, storageProvider, - storageProvider.DID().String(), - replicacaps.TransferCaveats{ - Space: space.DID(), - Blob: blob, - Site: site, - Cause: allocInv.Link(), - }, - ) - require.NoError(t, err) - - // Receipt signed by storageProvider (the executor) - transferRcpt, err := receipt.Issue( - storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(transferInv), - ) - require.NoError(t, err) - - iCtx := mockInvocationContext(t) - err = ch.Handler(ctx, transferInv, transferRcpt, iCtx) - require.NoError(t, err) - - // Verify the replica status was updated to Transferred - records, err := replicaStore.List(ctx, space.DID(), digest) - require.NoError(t, err) - require.Len(t, records, 1) - require.Equal(t, replica.Transferred, records[0].Status) - }) - - t.Run("executor mismatch", func(t *testing.T) { - agentStore := agent_store.New() - replicaStore := replica_store.New() - - ch := handlers.NewBlobReplicaTransferConcludeHandler( - &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, - ) - - space := testutil.RandomSigner(t) - digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} - site := cidlink.Link{Cid: testutil.RandomCID(t)} - replicateCause := cidlink.Link{Cid: testutil.RandomCID(t)} - - // Create allocation invocation signed by uploadService, audience = storageProvider - allocCap := ucan.NewCapability( - replicacaps.AllocateAbility, - storageProvider.DID().String(), - replicacaps.AllocateCaveats{ - Space: space.DID(), - Blob: blob, - Site: site, - Cause: replicateCause, - }, - ) - allocInv, err := invocation.Invoke(uploadService, storageProvider, allocCap) - require.NoError(t, err) - - allocRcpt, err := receipt.Issue( - storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(allocInv), - ) - require.NoError(t, err) - - writeAgentMessage(t, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) - - // Create transfer invocation from a DIFFERENT provider than the allocation audience - otherProvider := testutil.RandomSigner(t) - transferCap := ucan.NewCapability( - replicacaps.TransferAbility, - otherProvider.DID().String(), - replicacaps.TransferCaveats{ - Space: space.DID(), - Blob: blob, - Site: site, - Cause: allocInv.Link(), - }, - ) - // audience is otherProvider (different from storageProvider who is alloc audience) - transferInv, err := invocation.Invoke(uploadService, otherProvider, transferCap) - require.NoError(t, err) - - // receipt issued by otherProvider - transferRcpt, err := receipt.Issue( - otherProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(transferInv), - ) - require.NoError(t, err) - - err = ch.Handler(ctx, transferInv, transferRcpt, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "transfer executor does not match replica allocation audience") - }) - -} +// import ( +// "io" +// "testing" + +// cidlink "github.com/ipld/go-ipld-prime/linking/cid" +// replicacaps "github.com/storacha/go-libstoracha/capabilities/blob/replica" +// "github.com/storacha/go-libstoracha/capabilities/types" +// "github.com/storacha/go-ucanto/core/car" +// "github.com/storacha/go-ucanto/core/delegation" +// "github.com/storacha/go-ucanto/core/invocation" +// "github.com/storacha/go-ucanto/core/ipld" +// "github.com/storacha/go-ucanto/core/message" +// "github.com/storacha/go-ucanto/core/receipt" +// "github.com/storacha/go-ucanto/core/receipt/ran" +// "github.com/storacha/go-ucanto/core/result" +// "github.com/storacha/go-ucanto/core/result/ok" +// "github.com/storacha/go-ucanto/server" +// "github.com/storacha/go-ucanto/ucan" +// "github.com/fil-forge/sprue/internal/testutil" +// "github.com/fil-forge/sprue/pkg/identity" +// "github.com/fil-forge/sprue/pkg/service/handlers" +// "github.com/fil-forge/sprue/pkg/store/agent" +// agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" +// "github.com/fil-forge/sprue/pkg/store/replica" +// replica_store "github.com/fil-forge/sprue/pkg/store/replica/memory" +// "github.com/stretchr/testify/require" +// "go.uber.org/zap/zaptest" +// ) + +// // writeAgentMessage writes invocations and receipts to the agent store using +// // the same pattern as the production writeAgentMessage helper. +// func writeAgentMessage(t *testing.T, store agent.Store, invocations []invocation.Invocation, receipts []receipt.AnyReceipt) { +// t.Helper() +// msg, err := message.Build(invocations, receipts) +// require.NoError(t, err) + +// var idx []agent.IndexEntry +// for entry, err := range agent.Index(msg) { +// require.NoError(t, err) +// idx = append(idx, entry) +// } + +// src, err := io.ReadAll(car.Encode([]ipld.Link{msg.Root().Link()}, msg.Blocks())) +// require.NoError(t, err) + +// err = store.Write(t.Context(), msg, idx, src) +// require.NoError(t, err) +// } + +// func mockInvocationContext(t *testing.T) server.InvocationContext { +// t.Helper() +// s, err := server.NewServer(testutil.RandomSigner(t)) +// require.NoError(t, err) +// return s.Context() +// } + +// func TestBlobReplicaTransferConcludeHandler(t *testing.T) { +// logger := zaptest.NewLogger(t) +// ctx := t.Context() + +// uploadService := testutil.WebService +// storageProvider := testutil.RandomSigner(t) + +// t.Run("invalid transfer parameters", func(t *testing.T) { +// agentStore := agent_store.New() +// replicaStore := replica_store.New() + +// ch := handlers.NewBlobReplicaTransferConcludeHandler( +// &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, +// ) + +// // Create an invocation with wrong capability (not blob/replica/transfer) +// cap := ucan.NewCapability( +// "blob/allocate", +// storageProvider.DID().String(), +// ucan.NoCaveats{}, +// ) +// transferInv, err := invocation.Invoke(storageProvider, uploadService, cap) +// require.NoError(t, err) + +// transferRcpt, err := receipt.Issue( +// storageProvider, +// result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), +// ran.FromInvocation(transferInv), +// ) +// require.NoError(t, err) + +// err = ch.Handler(ctx, transferInv, transferRcpt, nil) +// require.Error(t, err) +// require.Contains(t, err.Error(), "invalid replica transfer parameters") +// }) + +// t.Run("allocation invocation not found", func(t *testing.T) { +// agentStore := agent_store.New() +// replicaStore := replica_store.New() + +// ch := handlers.NewBlobReplicaTransferConcludeHandler( +// &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, +// ) + +// space := testutil.RandomSigner(t) +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// site := cidlink.Link{Cid: testutil.RandomCID(t)} +// // cause points to a non-existent allocation invocation +// cause := cidlink.Link{Cid: testutil.RandomCID(t)} + +// transferCap := ucan.NewCapability( +// replicacaps.TransferAbility, +// storageProvider.DID().String(), +// replicacaps.TransferCaveats{ +// Space: space.DID(), +// Blob: blob, +// Site: site, +// Cause: cause, +// }, +// ) +// transferInv, err := invocation.Invoke(storageProvider, uploadService, transferCap) +// require.NoError(t, err) + +// transferRcpt, err := receipt.Issue( +// storageProvider, +// result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), +// ran.FromInvocation(transferInv), +// ) +// require.NoError(t, err) + +// err = ch.Handler(ctx, transferInv, transferRcpt, nil) +// require.Error(t, err) +// require.Contains(t, err.Error(), "getting replica allocation invocation") +// }) + +// t.Run("allocation not signed by service", func(t *testing.T) { +// agentStore := agent_store.New() +// replicaStore := replica_store.New() + +// ch := handlers.NewBlobReplicaTransferConcludeHandler( +// &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, +// ) + +// space := testutil.RandomSigner(t) +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// site := cidlink.Link{Cid: testutil.RandomCID(t)} +// replicateCause := cidlink.Link{Cid: testutil.RandomCID(t)} + +// // Create allocation invocation signed by someone OTHER than uploadService +// imposter := testutil.RandomSigner(t) +// allocCap := ucan.NewCapability( +// replicacaps.AllocateAbility, +// storageProvider.DID().String(), +// replicacaps.AllocateCaveats{ +// Space: space.DID(), +// Blob: blob, +// Site: site, +// Cause: replicateCause, +// }, +// ) +// allocInv, err := invocation.Invoke(imposter, storageProvider, allocCap) +// require.NoError(t, err) + +// allocRcpt, err := receipt.Issue( +// storageProvider, +// result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), +// ran.FromInvocation(allocInv), +// ) +// require.NoError(t, err) + +// // Store the allocation in the agent store +// writeAgentMessage(t, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) + +// // Create transfer invocation referencing the allocation +// transferCap := ucan.NewCapability( +// replicacaps.TransferAbility, +// storageProvider.DID().String(), +// replicacaps.TransferCaveats{ +// Space: space.DID(), +// Blob: blob, +// Site: site, +// Cause: allocInv.Link(), +// }, +// ) +// transferInv, err := invocation.Invoke(storageProvider, uploadService, transferCap) +// require.NoError(t, err) + +// transferRcpt, err := receipt.Issue( +// storageProvider, +// result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), +// ran.FromInvocation(transferInv), +// ) +// require.NoError(t, err) + +// err = ch.Handler(ctx, transferInv, transferRcpt, nil) +// require.Error(t, err) +// require.Contains(t, err.Error(), "allocation was not issued by this service") +// }) + +// t.Run("success updates replica status to transferred", func(t *testing.T) { +// agentStore := agent_store.New() +// replicaStore := replica_store.New() + +// ch := handlers.NewBlobReplicaTransferConcludeHandler( +// &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, +// ) + +// space := testutil.RandomSigner(t) +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// site := cidlink.Link{Cid: testutil.RandomCID(t)} +// replicateCause := cidlink.Link{Cid: testutil.RandomCID(t)} + +// // storageProvider delegates to uploadService so it can invoke allocate +// allocProof, err := delegation.Delegate( +// storageProvider, uploadService, +// []ucan.Capability[ucan.NoCaveats]{ +// ucan.NewCapability( +// replicacaps.AllocateAbility, +// storageProvider.DID().String(), +// ucan.NoCaveats{}, +// ), +// }, +// ) +// require.NoError(t, err) + +// // uploadService invokes allocate on storageProvider with the delegation as proof +// allocInv, err := replicacaps.Allocate.Invoke( +// uploadService, storageProvider, +// storageProvider.DID().String(), +// replicacaps.AllocateCaveats{ +// Space: space.DID(), +// Blob: blob, +// Site: site, +// Cause: replicateCause, +// }, +// delegation.WithProof(delegation.FromDelegation(allocProof)), +// ) +// require.NoError(t, err) + +// allocRcpt, err := receipt.Issue( +// storageProvider, +// result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), +// ran.FromInvocation(allocInv), +// ) +// require.NoError(t, err) + +// writeAgentMessage(t, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) + +// // Add a replica record so SetStatus succeeds +// err = replicaStore.Add(ctx, space.DID(), digest, storageProvider.DID(), replica.Allocated, testutil.RandomCID(t)) +// require.NoError(t, err) + +// // storageProvider self-issues the transfer invocation +// transferInv, err := replicacaps.Transfer.Invoke( +// storageProvider, storageProvider, +// storageProvider.DID().String(), +// replicacaps.TransferCaveats{ +// Space: space.DID(), +// Blob: blob, +// Site: site, +// Cause: allocInv.Link(), +// }, +// ) +// require.NoError(t, err) + +// // Receipt signed by storageProvider (the executor) +// transferRcpt, err := receipt.Issue( +// storageProvider, +// result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), +// ran.FromInvocation(transferInv), +// ) +// require.NoError(t, err) + +// iCtx := mockInvocationContext(t) +// err = ch.Handler(ctx, transferInv, transferRcpt, iCtx) +// require.NoError(t, err) + +// // Verify the replica status was updated to Transferred +// records, err := replicaStore.List(ctx, space.DID(), digest) +// require.NoError(t, err) +// require.Len(t, records, 1) +// require.Equal(t, replica.Transferred, records[0].Status) +// }) + +// t.Run("executor mismatch", func(t *testing.T) { +// agentStore := agent_store.New() +// replicaStore := replica_store.New() + +// ch := handlers.NewBlobReplicaTransferConcludeHandler( +// &identity.Identity{Signer: uploadService}, agentStore, replicaStore, logger, +// ) + +// space := testutil.RandomSigner(t) +// digest := testutil.RandomMultihash(t) +// blob := types.Blob{Digest: digest, Size: 1024} +// site := cidlink.Link{Cid: testutil.RandomCID(t)} +// replicateCause := cidlink.Link{Cid: testutil.RandomCID(t)} + +// // Create allocation invocation signed by uploadService, audience = storageProvider +// allocCap := ucan.NewCapability( +// replicacaps.AllocateAbility, +// storageProvider.DID().String(), +// replicacaps.AllocateCaveats{ +// Space: space.DID(), +// Blob: blob, +// Site: site, +// Cause: replicateCause, +// }, +// ) +// allocInv, err := invocation.Invoke(uploadService, storageProvider, allocCap) +// require.NoError(t, err) + +// allocRcpt, err := receipt.Issue( +// storageProvider, +// result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), +// ran.FromInvocation(allocInv), +// ) +// require.NoError(t, err) + +// writeAgentMessage(t, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) + +// // Create transfer invocation from a DIFFERENT provider than the allocation audience +// otherProvider := testutil.RandomSigner(t) +// transferCap := ucan.NewCapability( +// replicacaps.TransferAbility, +// otherProvider.DID().String(), +// replicacaps.TransferCaveats{ +// Space: space.DID(), +// Blob: blob, +// Site: site, +// Cause: allocInv.Link(), +// }, +// ) +// // audience is otherProvider (different from storageProvider who is alloc audience) +// transferInv, err := invocation.Invoke(uploadService, otherProvider, transferCap) +// require.NoError(t, err) + +// // receipt issued by otherProvider +// transferRcpt, err := receipt.Issue( +// otherProvider, +// result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), +// ran.FromInvocation(transferInv), +// ) +// require.NoError(t, err) + +// err = ch.Handler(ctx, transferInv, transferRcpt, nil) +// require.Error(t, err) +// require.Contains(t, err.Error(), "transfer executor does not match replica allocation audience") +// }) + +// } diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index 6171f2d..3e62e52 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -4,23 +4,21 @@ import ( "context" "fmt" - "github.com/fil-forge/go-libstoracha/capabilities/blob" - "github.com/fil-forge/go-libstoracha/capabilities/http" - "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/validator" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" + blobcaps "github.com/fil-forge/libforge/capabilities/blob" + httpcaps "github.com/fil-forge/libforge/capabilities/http" + ucancaps "github.com/fil-forge/libforge/capabilities/ucan" + "github.com/fil-forge/libforge/digestutil" + "github.com/fil-forge/sprue/pkg/lib/ucan_server" "github.com/fil-forge/sprue/pkg/piriclient" "github.com/fil-forge/sprue/pkg/routing" "github.com/fil-forge/sprue/pkg/store/agent" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" + "github.com/fil-forge/ucantone/errors" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) @@ -32,49 +30,52 @@ func NewHTTPPutConcludeHandler( logger *zap.Logger, ) ConclusionHandler { log := logger.With( - zap.String("handler", ucan.ConcludeAbility), - zap.String("conclude", http.PutAbility), + zap.String("handler", ucancaps.ConcludeCommand), + zap.String("conclude", httpcaps.PutCommand), ) return ConclusionHandler{ - Ability: http.PutAbility, - Handler: func(ctx context.Context, putInv invocation.Invocation, putRcpt receipt.AnyReceipt, _ server.InvocationContext) error { - log := log.With(zap.Stringer("ran", putRcpt.Ran().Link())) + Command: httpcaps.PutCommand, + Handler: func(ctx context.Context, putInv ucan.Invocation, putRcpt ucan.Receipt, meta ucan.Container) error { + log := log.With(zap.Stringer("ran", putRcpt.Ran())) log.Debug("handling conclude") - var err error - putCap := putInv.Capabilities()[0] - putMatch, err := http.Put.Match(validator.NewSource(putCap, putInv)) + putArgs := httpcaps.PutArguments{} + err := datamodel.Rebind(datamodel.NewAny(putInv.Arguments()), &putArgs) if err != nil { - log.Error("failed to match http/put invocation", zap.Error(err)) - return fmt.Errorf("matching http/put invocation: %w", err) + log.Error("failed to rebind HTTP PUT arguments", zap.Error(err)) + return fmt.Errorf("rebinding HTTP PUT arguments: %w", err) } - allocateTaskLink, err := ipldutil.ToCID(putMatch.Value().Nb().URL.UcanAwait.Link) - if err != nil { - return err - } - log = log.With(zap.Stringer("allocation", allocateTaskLink)) + allocTaskLink := putArgs.Destination.Task + log = log.With(zap.Stringer("allocation", allocTaskLink)) - allocTask, err := agentStore.GetInvocation(ctx, allocateTaskLink) + allocInv, err := agentStore.GetInvocation(ctx, allocTaskLink) if err != nil { log.Error("failed to get allocation invocation", zap.Error(err)) return fmt.Errorf("getting allocation invocation: %w", err) } - log = log.With(zap.Stringer("provider", allocTask.Audience().DID())) - allocMatch, err := blob.Allocate.Match(validator.NewSource(allocTask.Capabilities()[0], allocTask)) - if err != nil { - log.Error("matching blob/allocate invocation", zap.Error(err)) - return fmt.Errorf("matching blob/allocate invocation: %w", err) + provider := allocInv.Audience() + if provider == nil { + // shouldn't happen, subject should be the space and audience the node + provider = allocInv.Subject() } + space := allocInv.Subject() - allocNb := allocMatch.Value().Nb() log = log.With( - zap.Stringer("space", allocNb.Space), - zap.String("digest", digestutil.Format(allocNb.Blob.Digest)), + zap.Stringer("space", space.DID()), + zap.Stringer("provider", provider.DID()), ) - info, err := router.GetProviderInfo(ctx, allocTask.Audience()) + allocArgs := blobcaps.AllocateArguments{} + err = datamodel.Rebind(datamodel.NewAny(allocInv.Arguments()), &allocArgs) + if err != nil { + log.Error("failed to rebind allocate arguments", zap.Error(err)) + return fmt.Errorf("rebinding allocate arguments: %w", err) + } + log = log.With(zap.String("digest", digestutil.Format(allocArgs.Blob.Digest))) + + info, err := router.GetProviderInfo(ctx, provider) if err != nil { log.Error("failed to get storage provider info", zap.Error(err)) return fmt.Errorf("getting storage provider info: %w", err) @@ -86,19 +87,20 @@ func NewHTTPPutConcludeHandler( return fmt.Errorf("creating client: %w", err) } - res, accTask, accRcpt, err := client.Accept(ctx, &piriclient.AcceptRequest{ - Space: allocNb.Space, - Digest: allocNb.Blob.Digest, - Size: allocNb.Blob.Size, + proofStore := ucan_server.NewContainerProofStore(meta) + res, accInv, accRcpt, err := client.Accept(ctx, &piriclient.AcceptRequest{ + Space: space.DID(), + Digest: allocArgs.Blob.Digest, + Size: allocArgs.Blob.Size, Put: putInv.Link(), - }, delegationFetcher{info.Proof}) + }, proofStore) if err != nil { - log.Error("failed to execute blob/accept", zap.Error(err)) - return fmt.Errorf("executing blob/accept: %w", err) + log.Error("failed to execute blob accept", zap.Error(err)) + return fmt.Errorf("executing blob accept: %w", err) } log = log.With(zap.Stringer("site", res.Site)) - err = writeAgentMessage(ctx, agentStore, []invocation.Invocation{accTask}, []receipt.AnyReceipt{accRcpt}) + err = writeAgentMessage(ctx, agentStore, []ucan.Invocation{accInv}, []ucan.Receipt{accRcpt}) if err != nil { log.Error("failed to write agent message", zap.Error(err)) return fmt.Errorf("writing agent message: %w", err) @@ -107,19 +109,25 @@ func NewHTTPPutConcludeHandler( // if accept task was not successful do not register the blob in the space return result.MatchResultR1( accRcpt.Out(), - func(o ipld.Node) error { + func(o ipld.Any) error { log.Debug("accept success") - err := blobRegistry.Register(ctx, allocNb.Space, allocNb.Blob, allocateTaskLink) + err := blobRegistry.Register(ctx, space.DID(), allocArgs.Blob, allocArgs.Cause) // it's ok if there's already a registration of this blob in this space if err != nil && !errors.Is(err, blobregistry.ErrEntryExists) { return err } return nil }, - func(x ipld.Node) error { - f := datamodel.Bind(x) - log.Error("failed blob/accept receipt", zap.Error(f)) - return f + func(x ipld.Any) error { + var model edm.ErrorModel + err := datamodel.Rebind(datamodel.NewAny(x), &model) + if err != nil { + log.Error("failed to bind execution failure", zap.Error(err)) + log.Error("failed execution", zap.Any("error", x)) + return fmt.Errorf("executing blob accept: %v", x) + } + log.Error("failed execution", zap.String("name", model.ErrorName), zap.Error(model)) + return fmt.Errorf("executing blob accept: %w", model) }, ) }, diff --git a/pkg/service/handlers/ucan_conclude_http_put_test.go b/pkg/service/handlers/ucan_conclude_http_put_test.go index 6a7dcf7..ae2da17 100644 --- a/pkg/service/handlers/ucan_conclude_http_put_test.go +++ b/pkg/service/handlers/ucan_conclude_http_put_test.go @@ -4,277 +4,231 @@ import ( "net/url" "testing" - blobcap "github.com/fil-forge/go-libstoracha/capabilities/blob" - httpcap "github.com/fil-forge/go-libstoracha/capabilities/http" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/receipt/ran" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/ok" - "github.com/fil-forge/go-ucanto/ucan" + blobcaps "github.com/fil-forge/libforge/capabilities/blob" + httpcaps "github.com/fil-forge/libforge/capabilities/http" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/lib/didmailto" "github.com/fil-forge/sprue/pkg/piriclient" "github.com/fil-forge/sprue/pkg/routing" "github.com/fil-forge/sprue/pkg/service/handlers" + "github.com/fil-forge/sprue/pkg/store/agent" agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" + blob_registry "github.com/fil-forge/sprue/pkg/store/blob_registry/memory" + consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" + metrics_store "github.com/fil-forge/sprue/pkg/store/metrics/memory" + spacediff_store "github.com/fil-forge/sprue/pkg/store/space_diff/memory" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/ucan/promise" + "github.com/fil-forge/ucantone/ucan/receipt" "github.com/stretchr/testify/require" + "go.uber.org/zap" "go.uber.org/zap/zaptest" ) +type httpPutDeps struct { + ch handlers.ConclusionHandler + spStore *storage_provider_store.Store + agentStore *agent_store.Store + consumerStore *consumer_store.Store + blobReg *blob_registry.Store +} + +func newHTTPPutDeps(t *testing.T, nodeProvider piriclient.Provider, logger *zap.Logger) *httpPutDeps { + t.Helper() + spStore := storage_provider_store.New() + router := routing.NewService(spStore, logger) + agentStore := agent_store.New() + consumerStore := consumer_store.New() + blobReg := blob_registry.New( + spacediff_store.New(), + consumerStore, + metrics_store.NewSpaceStore(), + metrics_store.New(), + ) + ch := handlers.NewHTTPPutConcludeHandler(router, nodeProvider, agentStore, blobReg, logger) + return &httpPutDeps{ + ch: ch, + spStore: spStore, + agentStore: agentStore, + consumerStore: consumerStore, + blobReg: blobReg, + } +} + func TestHTTPPutConcludeHandler(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() uploadService := testutil.WebService - t.Run("invalid http/put parameters", func(t *testing.T) { - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, _ := newBlobRegistry() - nodeProvider := piriclient.NewProvider(uploadService, logger) - - ch := handlers.NewHTTPPutConcludeHandler(router, nodeProvider, agentStore, blobReg, logger) - - // Create an invocation with a wrong capability (not http/put) - cap := ucan.NewCapability( - "blob/allocate", - uploadService.DID().String(), - ucan.NoCaveats{}, - ) - putInv, err := invocation.Invoke(uploadService, uploadService, cap) - require.NoError(t, err) - - putRcpt, err := receipt.Issue( - uploadService, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(putInv), - ) - require.NoError(t, err) - - err = ch.Handler(ctx, putInv, putRcpt, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "matching http/put invocation") - }) - t.Run("allocation invocation not found", func(t *testing.T) { - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, _ := newBlobRegistry() - nodeProvider := piriclient.NewProvider(uploadService, logger) - - ch := handlers.NewHTTPPutConcludeHandler(router, nodeProvider, agentStore, blobReg, logger) + deps := newHTTPPutDeps(t, piriclient.NewProvider(uploadService, logger), logger) digest := testutil.RandomMultihash(t) - // URL.UcanAwait.Link points to a non-existent allocation invocation - nonExistentAllocLink := cidlink.Link{Cid: testutil.RandomCID(t)} - - putInv, err := httpcap.Put.Invoke( - uploadService, uploadService, - uploadService.DID().String(), - httpcap.PutCaveats{ - URL: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.address.url", - Link: nonExistentAllocLink, - }, - }, - Headers: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.address.headers", - Link: nonExistentAllocLink, - }, - }, - Body: httpcap.Body{ - Digest: digest, - Size: 1024, - }, + // Destination.Task points to an invocation that's not in the agent store. + nonExistentAllocTask := testutil.RandomCID(t) + + blobProvider := deriveBlobProvider(t, digest) + putInv, err := httpcaps.Put.Invoke( + blobProvider, + blobProvider, + &httpcaps.PutArguments{ + Body: blobcaps.Blob{Digest: digest, Size: 1024}, + Destination: promise.AwaitOK{Task: nonExistentAllocTask}, }, + invocation.WithAudience(blobProvider), ) require.NoError(t, err) putRcpt, err := receipt.Issue( - uploadService, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(putInv), + blobProvider, + putInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &httpcaps.PutOK{})), ) require.NoError(t, err) - err = ch.Handler(ctx, putInv, putRcpt, nil) + err = deps.ch.Handler(ctx, putInv, putRcpt, nil) require.Error(t, err) require.Contains(t, err.Error(), "getting allocation invocation") }) t.Run("storage provider not found", func(t *testing.T) { - storageProvider := testutil.RandomSigner(t) - spStore := storage_provider_store.New() - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, _ := newBlobRegistry() - nodeProvider := piriclient.NewProvider(uploadService, logger) - - ch := handlers.NewHTTPPutConcludeHandler(router, nodeProvider, agentStore, blobReg, logger) + deps := newHTTPPutDeps(t, piriclient.NewProvider(uploadService, logger), logger) + storageProvider := testutil.RandomSigner(t) space := testutil.RandomSigner(t) digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} + blob := blobcaps.Blob{Digest: digest, Size: 1024} - // Create and store a blob/allocate invocation - allocInv, err := blobcap.Allocate.Invoke( - uploadService, storageProvider, - storageProvider.DID().String(), - blobcap.AllocateCaveats{ - Space: space.DID(), - Blob: blob, - Cause: cidlink.Link{Cid: testutil.RandomCID(t)}, - }, + // Persist a /blob/allocate invocation for the storage provider, but do + // NOT register that provider in the spStore — router lookup fails. + allocInv, err := blobcaps.Allocate.Invoke( + uploadService, + space, + &blobcaps.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, + invocation.WithAudience(storageProvider), ) require.NoError(t, err) - allocRcpt, err := receipt.Issue( storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(allocInv), + allocInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &blobcaps.AllocateOK{Size: blob.Size})), ) require.NoError(t, err) - - writeAgentMessage(t, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) - - // Create http/put invocation referencing the allocation - putInv, err := httpcap.Put.Invoke( - uploadService, uploadService, - uploadService.DID().String(), - httpcap.PutCaveats{ - URL: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.address.url", - Link: allocInv.Link(), - }, - }, - Headers: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.address.headers", - Link: allocInv.Link(), - }, - }, - Body: httpcap.Body{ - Digest: digest, - Size: 1024, - }, + msg := container.New( + container.WithInvocations(allocInv), + container.WithReceipts(allocRcpt), + ) + require.NoError(t, deps.agentStore.Write(ctx, msg, agent.Index(msg))) + + blobProvider := deriveBlobProvider(t, digest) + putInv, err := httpcaps.Put.Invoke( + blobProvider, + blobProvider, + &httpcaps.PutArguments{ + Body: blob, + Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, + invocation.WithAudience(blobProvider), ) require.NoError(t, err) - putRcpt, err := receipt.Issue( - uploadService, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(putInv), + blobProvider, + putInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &httpcaps.PutOK{})), ) require.NoError(t, err) - // storageProvider is NOT registered in spStore, so GetProviderInfo fails - err = ch.Handler(ctx, putInv, putRcpt, nil) + err = deps.ch.Handler(ctx, putInv, putRcpt, nil) require.Error(t, err) require.Contains(t, err.Error(), "getting storage provider info") }) t.Run("success registers blob in space", func(t *testing.T) { storageProvider := testutil.RandomSigner(t) - storageProviderURL := testutil.Must(url.Parse("https://piri.example.com"))(t) - storageProviderProof := delegateStorageProviderProof(t, storageProvider, uploadService) - - spStore := storage_provider_store.New() - err := spStore.Put(ctx, *storageProviderURL, storageProviderProof, 100, nil) - require.NoError(t, err) - - router := routing.NewService(spStore, logger) - agentStore := agent_store.New() - blobReg, consumerStore := newBlobRegistry() - space := testutil.RandomSigner(t) digest := testutil.RandomMultihash(t) - blob := types.Blob{Digest: digest, Size: 1024} + blob := blobcaps.Blob{Digest: digest, Size: 1024} + blobAddTaskLink := testutil.RandomCID(t) + + // Stand up a mock piri server. The handler under test only calls + // /blob/accept; the allocate handler is irrelevant but the helper + // requires both. + acceptOK := &blobcaps.AcceptOK{Site: testutil.RandomCID(t)} + piriSrv := newMockPiriServer( + t, storageProvider, uploadService, + &blobcaps.AllocateOK{Size: blob.Size}, + acceptOK, + ) + piriURL := testutil.Must(url.Parse(piriSrv.URL))(t) - // provision the space so blob registry Register succeeds - aliceAccount := testutil.Must(didmailto.Parse("did:mailto:example.com:alice"))(t) - err = consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "", testutil.RandomCID(t)) - require.NoError(t, err) + deps := newHTTPPutDeps(t, piriclient.NewProvider(uploadService, logger), logger) + require.NoError(t, deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil)) - // Create and store a blob/allocate invocation - allocInv, err := blobcap.Allocate.Invoke( - uploadService, storageProvider, - storageProvider.DID().String(), - blobcap.AllocateCaveats{ - Space: space.DID(), - Blob: blob, - Cause: cidlink.Link{Cid: testutil.RandomCID(t)}, - }, + // Provision the space so blob_registry.Register succeeds. + account := testutil.Must(didmailto.New("alice@example.com"))(t) + require.NoError(t, deps.consumerStore.Add( + ctx, uploadService.DID(), space.DID(), account, "sub-1", testutil.RandomCID(t), + )) + + // Prior /blob/allocate invocation in the agent store. + allocInv, err := blobcaps.Allocate.Invoke( + uploadService, + space, + &blobcaps.AllocateArguments{Blob: blob, Cause: blobAddTaskLink}, + invocation.WithAudience(storageProvider), ) require.NoError(t, err) - allocRcpt, err := receipt.Issue( storageProvider, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(allocInv), + allocInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &blobcaps.AllocateOK{Size: blob.Size})), ) require.NoError(t, err) - - writeAgentMessage(t, agentStore, []invocation.Invocation{allocInv}, []receipt.AnyReceipt{allocRcpt}) - - // Create http/put invocation referencing the allocation - putInv, err := httpcap.Put.Invoke( - uploadService, uploadService, - uploadService.DID().String(), - httpcap.PutCaveats{ - URL: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.address.url", - Link: allocInv.Link(), - }, - }, - Headers: types.Promise{ - UcanAwait: types.Await{ - Selector: ".out.ok.address.headers", - Link: allocInv.Link(), - }, - }, - Body: httpcap.Body{ - Digest: digest, - Size: 1024, - }, + msg := container.New( + container.WithInvocations(allocInv), + container.WithReceipts(allocRcpt), + ) + require.NoError(t, deps.agentStore.Write(ctx, msg, agent.Index(msg))) + + // /http/put invocation referring to the allocation task. + blobProvider := deriveBlobProvider(t, digest) + putInv, err := httpcaps.Put.Invoke( + blobProvider, + blobProvider, + &httpcaps.PutArguments{ + Body: blob, + Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, + invocation.WithAudience(blobProvider), ) require.NoError(t, err) - putRcpt, err := receipt.Issue( - uploadService, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(putInv), + blobProvider, + putInv.Task().Link(), + result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &httpcaps.PutOK{})), ) require.NoError(t, err) - // Create mock node provider with accept handler that returns success - acceptOk := blobcap.AcceptOk{ - Site: cidlink.Link{Cid: testutil.RandomCID(t)}, - } - mockProvider := newMockNodeProvider( - t, - uploadService, - storageProvider, - newOkHandler[blobcap.AllocateCaveats](t, blobcap.AllocateOk{}), - newOkHandler[blobcap.AcceptCaveats](t, acceptOk), - logger, - ) + // Authorize the upload service to invoke /blob/accept on the space and + // pass the proof through the conclude metadata so the piri client can + // forward it to the storage provider. + acceptProof, err := delegation.Delegate(space, uploadService, space, blobcaps.AcceptCommand) + require.NoError(t, err) + meta := container.New(container.WithDelegations(acceptProof)) - ch := handlers.NewHTTPPutConcludeHandler(router, mockProvider, agentStore, blobReg, logger) + err = deps.ch.Handler(ctx, putInv, putRcpt, meta) + require.NoError(t, err) - err = ch.Handler(ctx, putInv, putRcpt, nil) + // Blob should now be registered in the space, with cause = blobAddTaskLink. + rec, err := deps.blobReg.Get(ctx, space.DID(), digest) require.NoError(t, err) + require.Equal(t, blobAddTaskLink, rec.Cause) + require.Equal(t, blob.Size, rec.Blob.Size) }) } diff --git a/pkg/service/handlers/ucan_conclude_test.go b/pkg/service/handlers/ucan_conclude_test.go index b34f03a..2e56d0d 100644 --- a/pkg/service/handlers/ucan_conclude_test.go +++ b/pkg/service/handlers/ucan_conclude_test.go @@ -4,205 +4,241 @@ import ( "context" "testing" - ucancap "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/receipt/ran" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/ok" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" + ucancaps "github.com/fil-forge/libforge/capabilities/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" + "github.com/fil-forge/sprue/pkg/store/agent" agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/ucan/receipt" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) -// issueConclude creates a ucan/conclude invocation with the receipt blocks attached. -func issueConclude(t *testing.T, signer ucan.Signer, rcpt receipt.AnyReceipt) invocation.Invocation { - t.Helper() - inv, err := ucancap.Conclude.Invoke( - signer, signer, - signer.DID().String(), - ucancap.ConcludeCaveats{ - Receipt: rcpt.Root().Link(), - }, - ) - require.NoError(t, err) - - for blk, err := range rcpt.Blocks() { - require.NoError(t, err) - require.NoError(t, inv.Attach(blk)) - } - return inv -} - func TestUCANConcludeHandler(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() uploadService := testutil.WebService - t.Run("receipt not readable from blocks", func(t *testing.T) { + // Build a "task" invocation and a receipt for it. + newTaskAndReceipt := func(t *testing.T, cmd ucan.Command) (ucan.Invocation, ucan.Receipt) { + t.Helper() + taskInv, err := invocation.Invoke(uploadService, uploadService, cmd, datamodel.Map{}) + require.NoError(t, err) + rcpt, err := receipt.Issue( + uploadService, + taskInv.Task().Link(), + result.OK[int64, ipld.Any](int64(1)), + ) + require.NoError(t, err) + return taskInv, rcpt + } + + t.Run("receipt not in metadata", func(t *testing.T) { agentStore := agent_store.New() - handlerMap := map[ucan.Ability]handlers.ConclusionHandlerFunc{} + handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{} - handler := handlers.UCANConcludeHandler( + handler := handlers.NewUCANConcludeHandler( &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - // Create a conclude invocation WITHOUT attaching the receipt blocks - taskInv, err := invocation.Invoke( - uploadService, uploadService, - ucan.NewCapability("test/thing", uploadService.DID().String(), ucan.NoCaveats{}), - ) - require.NoError(t, err) + _, rcpt := newTaskAndReceipt(t, "/test/thing") - rcpt, err := receipt.Issue( + concludeInv, err := ucancaps.Conclude.Invoke( uploadService, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(taskInv), + uploadService, + &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + invocation.WithAudience(uploadService), ) require.NoError(t, err) - // Invoke conclude but don't attach receipt blocks - concludeInv, err := ucancap.Conclude.Invoke( - uploadService, uploadService, - uploadService.DID().String(), - ucancap.ConcludeCaveats{ - Receipt: rcpt.Root().Link(), - }, - ) + // The receipt is referenced in args but NOT attached to the request + // metadata, so the handler can't find it. + req := execution.NewRequest(ctx, concludeInv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) - concludeCap := ucancap.Conclude.New( - uploadService.DID().String(), - ucancap.ConcludeCaveats{Receipt: rcpt.Root().Link()}, - ) - _, _, err = handler(ctx, concludeCap, concludeInv, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "reading receipt") + err = handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.NotNil(t, fail) + + model := edm.ErrorModel{} + err = datamodel.Rebind(datamodel.NewAny(fail), &model) + require.NoError(t, err) + require.Equal(t, handlers.ConclusionReceiptNotFoundErrorName, model.Name()) }) t.Run("unknown invocation returns success", func(t *testing.T) { agentStore := agent_store.New() - handlerMap := map[ucan.Ability]handlers.ConclusionHandlerFunc{} + handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{} - handler := handlers.UCANConcludeHandler( + handler := handlers.NewUCANConcludeHandler( &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - // Create a receipt for some task that is NOT in the agent store - // and is NOT embedded in the receipt as an invocation - taskInv, err := invocation.Invoke( - uploadService, uploadService, - ucan.NewCapability("test/thing", uploadService.DID().String(), ucan.NoCaveats{}), - ) - require.NoError(t, err) + // Receipt is supplied but the ran invocation is neither in the request + // metadata nor in the agent store — the handler treats this as a no-op. + _, rcpt := newTaskAndReceipt(t, "/test/thing") - rcpt, err := receipt.Issue( + concludeInv, err := ucancaps.Conclude.Invoke( uploadService, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(taskInv), + uploadService, + &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + invocation.WithAudience(uploadService), ) require.NoError(t, err) - concludeInv := issueConclude(t, uploadService, rcpt) - concludeCap := ucancap.Conclude.New( - uploadService.DID().String(), - ucancap.ConcludeCaveats{Receipt: rcpt.Root().Link()}, - ) - res, _, err := handler(ctx, concludeCap, concludeInv, nil) + req := execution.NewRequest(ctx, concludeInv, execution.WithReceipts(rcpt)) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) - o, x := result.Unwrap(res) - require.Nil(t, x) - require.NotNil(t, o) + err = handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) }) t.Run("dispatches to registered handler", func(t *testing.T) { agentStore := agent_store.New() - var called bool - handlerMap := map[ucan.Ability]handlers.ConclusionHandlerFunc{ - "test/thing": func(_ context.Context, _ invocation.Invocation, _ receipt.AnyReceipt, _ server.InvocationContext) error { + var ( + called bool + gotInv ucan.Invocation + gotRcpt ucan.Receipt + ) + handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{ + "/test/thing": func(_ context.Context, inv ucan.Invocation, rcpt ucan.Receipt, _ ucan.Container) error { called = true + gotInv = inv + gotRcpt = rcpt return nil }, } - handler := handlers.UCANConcludeHandler( + handler := handlers.NewUCANConcludeHandler( &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - // Create a task invocation with "test/thing" ability - taskInv, err := invocation.Invoke( - uploadService, uploadService, - ucan.NewCapability("test/thing", uploadService.DID().String(), ucan.NoCaveats{}), + taskInv, rcpt := newTaskAndReceipt(t, "/test/thing") + + // Persist the task invocation in the agent store so the handler can + // look it up by the receipt's ran CID. + msg := container.New( + container.WithInvocations(taskInv), + container.WithReceipts(rcpt), ) - require.NoError(t, err) + require.NoError(t, agentStore.Write(ctx, msg, agent.Index(msg))) - rcpt, err := receipt.Issue( + concludeInv, err := ucancaps.Conclude.Invoke( + uploadService, uploadService, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(taskInv), + &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + invocation.WithAudience(uploadService), ) require.NoError(t, err) - // Store the task invocation so the handler can find it - writeAgentMessage(t, agentStore, []invocation.Invocation{taskInv}, []receipt.AnyReceipt{rcpt}) + req := execution.NewRequest(ctx, concludeInv, execution.WithReceipts(rcpt)) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) - concludeInv := issueConclude(t, uploadService, rcpt) - concludeCap := ucancap.Conclude.New( - uploadService.DID().String(), - ucancap.ConcludeCaveats{Receipt: rcpt.Root().Link()}, - ) - res, _, err := handler(ctx, concludeCap, concludeInv, nil) + err = handler.Handler(req, res) require.NoError(t, err) - require.True(t, called) - o, x := result.Unwrap(res) - require.Nil(t, x) - require.NotNil(t, o) + _, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + + require.True(t, called) + require.Equal(t, taskInv.Task().Link(), gotInv.Task().Link()) + require.Equal(t, rcpt.Link(), gotRcpt.Link()) }) - t.Run("no handler for ability returns success", func(t *testing.T) { + t.Run("no handler for command returns success", func(t *testing.T) { agentStore := agent_store.New() - // Register no handlers - handlerMap := map[ucan.Ability]handlers.ConclusionHandlerFunc{} + // No handlers registered. + handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{} - handler := handlers.UCANConcludeHandler( + handler := handlers.NewUCANConcludeHandler( &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - taskInv, err := invocation.Invoke( - uploadService, uploadService, - ucan.NewCapability("test/unhandled", uploadService.DID().String(), ucan.NoCaveats{}), + taskInv, rcpt := newTaskAndReceipt(t, "/test/unhandled") + + msg := container.New( + container.WithInvocations(taskInv), + container.WithReceipts(rcpt), ) - require.NoError(t, err) + require.NoError(t, agentStore.Write(ctx, msg, agent.Index(msg))) - rcpt, err := receipt.Issue( + concludeInv, err := ucancaps.Conclude.Invoke( + uploadService, uploadService, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(taskInv), + &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + invocation.WithAudience(uploadService), ) require.NoError(t, err) - writeAgentMessage(t, agentStore, []invocation.Invocation{taskInv}, []receipt.AnyReceipt{rcpt}) + req := execution.NewRequest(ctx, concludeInv, execution.WithReceipts(rcpt)) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = handler.Handler(req, res) + require.NoError(t, err) + + _, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + }) + + t.Run("invocation supplied via metadata", func(t *testing.T) { + agentStore := agent_store.New() + + var called bool + handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{ + "/test/thing": func(_ context.Context, _ ucan.Invocation, _ ucan.Receipt, _ ucan.Container) error { + called = true + return nil + }, + } - concludeInv := issueConclude(t, uploadService, rcpt) - concludeCap := ucancap.Conclude.New( - uploadService.DID().String(), - ucancap.ConcludeCaveats{Receipt: rcpt.Root().Link()}, + handler := handlers.NewUCANConcludeHandler( + &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - res, _, err := handler(ctx, concludeCap, concludeInv, nil) + + taskInv, rcpt := newTaskAndReceipt(t, "/test/thing") + + // The ran invocation is supplied directly in the request metadata — + // no agent-store lookup required. + concludeInv, err := ucancaps.Conclude.Invoke( + uploadService, + uploadService, + &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + + req := execution.NewRequest(ctx, concludeInv, + execution.WithReceipts(rcpt), + execution.WithInvocations(taskInv), + ) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + + err = handler.Handler(req, res) require.NoError(t, err) - o, x := result.Unwrap(res) - require.Nil(t, x) - require.NotNil(t, o) + _, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + require.True(t, called) }) } diff --git a/pkg/service/handlers/upload_add.go b/pkg/service/handlers/upload_add.go index cac3a0e..b0de3d6 100644 --- a/pkg/service/handlers/upload_add.go +++ b/pkg/service/handlers/upload_add.go @@ -1,84 +1,54 @@ package handlers import ( - "context" "fmt" - "github.com/fil-forge/go-libstoracha/capabilities/upload" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" + uploadcaps "github.com/fil-forge/libforge/capabilities/upload" + "github.com/fil-forge/sprue/pkg/provisioning" upload_store "github.com/fil-forge/sprue/pkg/store/upload" - "github.com/ipfs/go-cid" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/execution/bindexec" "go.uber.org/zap" ) -const InvalidSpaceErrorName = "InvalidSpace" - -// WithUploadAddMethod registers the upload/add handler. // This handler registers an upload (root CID + shards mapping). -func WithUploadAddMethod(uploadStore upload_store.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - upload.AddAbility, - server.Provide(upload.Add, UploadAddHandler(uploadStore, logger)), - ) -} - -func UploadAddHandler(uploadStore upload_store.Store, logger *zap.Logger) server.HandlerFunc[upload.AddCaveats, upload.AddOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", upload.AddAbility)) - return server.HandlerFunc[upload.AddCaveats, upload.AddOk, failure.IPLDBuilderFailure]( - func(ctx context.Context, - cap ucan.Capability[upload.AddCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[upload.AddOk, failure.IPLDBuilderFailure], fx.Effects, error) { +func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore upload_store.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", uploadcaps.AddCommand)) + return Handler{ + Capability: uploadcaps.Add, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*uploadcaps.AddArguments], + res *bindexec.Response[*uploadcaps.AddOK], + ) error { + args := req.Task().BindArguments() + space := req.Invocation().Subject() + cause := req.Invocation().Task().Link() log := log.With( - zap.String("space", cap.With()), - zap.Stringer("root", cap.Nb().Root), - zap.Int("shards", len(cap.Nb().Shards)), + zap.Stringer("space", space.DID()), + zap.Stringer("root", args.Root), ) - log.Debug("adding upload") - - space, err := did.Parse(cap.With()) - if err != nil { - return result.Error[upload.AddOk, failure.IPLDBuilderFailure]( - errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), - ), nil, nil + if args.Index != nil { + log = log.With(zap.Stringer("index", *args.Index)) } + log.Debug("adding upload") - root, err := ipldutil.ToCID(cap.Nb().Root) + provs, err := provisioningSvc.ListServiceProviders(req.Context(), space.DID()) if err != nil { - return nil, nil, err - } - - shards := make([]cid.Cid, 0, len(cap.Nb().Shards)) - for _, link := range cap.Nb().Shards { - s, err := ipldutil.ToCID(link) - if err != nil { - return nil, nil, err - } - shards = append(shards, s) + log.Error("failed to list service providers", zap.Error(err)) + return fmt.Errorf("listing service providers: %w", err) } - - cause, err := ipldutil.ToCID(inv.Link()) - if err != nil { - return nil, nil, err + if len(provs) == 0 { + log.Warn("space has no service provider") + return res.SetFailure(errors.New(InsufficientStorageErrorName, "space has no service provider")) } - err = uploadStore.Upsert(ctx, space, root, shards, cause) + err = uploadStore.Upsert(req.Context(), space.DID(), args.Root, args.Index, args.Shards, cause) if err != nil { log.Error("failed to upsert upload", zap.Error(err)) - return nil, nil, fmt.Errorf("upserting upload: %w", err) + return fmt.Errorf("upserting upload: %w", err) } - return result.Ok[upload.AddOk, failure.IPLDBuilderFailure](upload.AddOk{ - Root: cap.Nb().Root, - }), nil, nil - }) + return res.SetSuccess(&uploadcaps.AddOK{}) + }), + } } diff --git a/pkg/service/handlers/upload_add_test.go b/pkg/service/handlers/upload_add_test.go index a26f675..3c299c3 100644 --- a/pkg/service/handlers/upload_add_test.go +++ b/pkg/service/handlers/upload_add_test.go @@ -1,21 +1,88 @@ package handlers_test import ( + "context" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/upload" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" - "github.com/fil-forge/go-ucanto/ucan" + uploadcaps "github.com/fil-forge/libforge/capabilities/upload" + "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" + "github.com/fil-forge/sprue/pkg/provisioning" "github.com/fil-forge/sprue/pkg/service/handlers" - uploadmemory "github.com/fil-forge/sprue/pkg/store/upload/memory" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" + consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" + subscription_store "github.com/fil-forge/sprue/pkg/store/subscription/memory" + upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" + "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" + "go.uber.org/zap" "go.uber.org/zap/zaptest" ) +type uploadAddDeps struct { + handler handlers.Handler + store *upload_store.Store + consumerStore *consumer_store.Store +} + +func newUploadAddDeps(t *testing.T, uploadService principal.Signer, logger *zap.Logger) *uploadAddDeps { + t.Helper() + consumerStore := consumer_store.New() + provisioningSvc := provisioning.NewService( + []did.DID{uploadService.DID()}, + consumerStore, + subscription_store.New(), + ) + store := upload_store.New() + handler := handlers.NewUploadAddHandler(provisioningSvc, store, logger) + return &uploadAddDeps{handler: handler, store: store, consumerStore: consumerStore} +} + +// invokeUploadAdd builds an /upload/add invocation with the given args and a +// signed response ready for the handler. +func invokeUploadAdd( + t *testing.T, + ctx context.Context, + agent principal.Signer, + uploadService principal.Signer, + space principal.Signer, + args *uploadcaps.AddArguments, +) (execution.Request, *execution.ExecResponse) { + t.Helper() + inv, err := uploadcaps.Add.Invoke( + agent, + space, + args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + return req, res +} + +// provisionUploadSpace adds a consumer record so the upload service shows up as +// a provider for the space when the handler calls ListServiceProviders. +func provisionUploadSpace(t *testing.T, consumerStore *consumer_store.Store, uploadService principal.Signer, space principal.Signer) { + t.Helper() + account := testutil.Must(didmailto.New("alice@example.com"))(t) + require.NoError(t, consumerStore.Add( + t.Context(), + uploadService.DID(), + space.DID(), + account, + "sub-1", + testutil.RandomCID(t), + )) +} + func TestUploadAddHandler(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() @@ -23,143 +90,135 @@ func TestUploadAddHandler(t *testing.T) { uploadService := testutil.WebService alice := testutil.Alice - t.Run("invalid space DID", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadAddHandler(store, logger) + t.Run("space not provisioned", func(t *testing.T) { + deps := newUploadAddDeps(t, uploadService, logger) - root := cidlink.Link{Cid: testutil.RandomCID(t)} - cap := ucan.NewCapability( - upload.AddAbility, - "not-a-did", - upload.AddCaveats{Root: root}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) + space := testutil.RandomSigner(t) + root := testutil.RandomCID(t) + req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{Root: root}) - res, _, err := handler(ctx, cap, inv, nil) + err := deps.handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.NotNil(t, fail) - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.InvalidSpaceErrorName, *model.Name) + model := edm.ErrorModel{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + require.Equal(t, handlers.InsufficientStorageErrorName, model.Name()) + + // Nothing should have been persisted. + exists, err := deps.store.Exists(ctx, space.DID(), root) + require.NoError(t, err) + require.False(t, exists) }) t.Run("success with no shards", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadAddHandler(store, logger) + deps := newUploadAddDeps(t, uploadService, logger) space := testutil.RandomSigner(t) - root := cidlink.Link{Cid: testutil.RandomCID(t)} - cap := ucan.NewCapability( - upload.AddAbility, - space.DID().String(), - upload.AddCaveats{Root: root}, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) + provisionUploadSpace(t, deps.consumerStore, uploadService, space) - res, _, err := handler(ctx, cap, inv, nil) + root := testutil.RandomCID(t) + req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{Root: root}) + + err := deps.handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.Equal(t, root.String(), ok.Root.String()) - // Verify persisted - exists, err := store.Exists(ctx, space.DID(), testutil.RandomCID(t)) + // Upload should be persisted. + exists, err := deps.store.Exists(ctx, space.DID(), root) require.NoError(t, err) - require.False(t, exists) + require.True(t, exists) - exists, err = store.Exists(ctx, space.DID(), root.Cid) + // Unrelated CID should not be present. + exists, err = deps.store.Exists(ctx, space.DID(), testutil.RandomCID(t)) require.NoError(t, err) - require.True(t, exists) + require.False(t, exists) }) t.Run("success with shards", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadAddHandler(store, logger) + deps := newUploadAddDeps(t, uploadService, logger) space := testutil.RandomSigner(t) - root := cidlink.Link{Cid: testutil.RandomCID(t)} - shard1 := cidlink.Link{Cid: testutil.RandomCID(t)} - shard2 := cidlink.Link{Cid: testutil.RandomCID(t)} - - cap := ucan.NewCapability( - upload.AddAbility, - space.DID().String(), - upload.AddCaveats{ - Root: root, - Shards: []ucan.Link{shard1, shard2}, - }, - ) - - inv, err := invocation.Invoke(alice, uploadService, cap) - require.NoError(t, err) + provisionUploadSpace(t, deps.consumerStore, uploadService, space) + + root := testutil.RandomCID(t) + shard1 := testutil.RandomCID(t) + shard2 := testutil.RandomCID(t) + + req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{ + Root: root, + Shards: []cid.Cid{shard1, shard2}, + }) - res, _, err := handler(ctx, cap, inv, nil) + err := deps.handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + _, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.Equal(t, root.String(), ok.Root.String()) - // Verify upload exists - exists, err := store.Exists(ctx, space.DID(), root.Cid) + exists, err := deps.store.Exists(ctx, space.DID(), root) require.NoError(t, err) require.True(t, exists) }) - t.Run("upsert updates existing upload", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadAddHandler(store, logger) + t.Run("success with index", func(t *testing.T) { + deps := newUploadAddDeps(t, uploadService, logger) space := testutil.RandomSigner(t) - root := cidlink.Link{Cid: testutil.RandomCID(t)} - shard1 := cidlink.Link{Cid: testutil.RandomCID(t)} - - cap1 := ucan.NewCapability( - upload.AddAbility, - space.DID().String(), - upload.AddCaveats{ - Root: root, - Shards: []ucan.Link{shard1}, - }, - ) - - inv1, err := invocation.Invoke(alice, uploadService, cap1) - require.NoError(t, err) + provisionUploadSpace(t, deps.consumerStore, uploadService, space) - res1, _, err := handler(ctx, cap1, inv1, nil) - require.NoError(t, err) - _, fail1 := result.Unwrap(res1) - require.Nil(t, fail1) + root := testutil.RandomCID(t) + index := testutil.RandomCID(t) + + req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{ + Root: root, + Index: &index, + }) - // Add again with a new shard - shard2 := cidlink.Link{Cid: testutil.RandomCID(t)} - cap2 := ucan.NewCapability( - upload.AddAbility, - space.DID().String(), - upload.AddCaveats{ - Root: root, - Shards: []ucan.Link{shard2}, - }, - ) - - inv2, err := invocation.Invoke(alice, uploadService, cap2) + err := deps.handler.Handler(req, res) require.NoError(t, err) - res2, _, err := handler(ctx, cap2, inv2, nil) + _, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + + exists, err := deps.store.Exists(ctx, space.DID(), root) require.NoError(t, err) - _, fail2 := result.Unwrap(res2) + require.True(t, exists) + }) + + t.Run("upsert updates existing upload", func(t *testing.T) { + deps := newUploadAddDeps(t, uploadService, logger) + + space := testutil.RandomSigner(t) + provisionUploadSpace(t, deps.consumerStore, uploadService, space) + + root := testutil.RandomCID(t) + shard1 := testutil.RandomCID(t) + + req1, res1 := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{ + Root: root, + Shards: []cid.Cid{shard1}, + }) + require.NoError(t, deps.handler.Handler(req1, res1)) + _, fail1 := result.Unwrap(res1.Receipt().Out()) + require.Nil(t, fail1) + + // Add again with a new shard. + shard2 := testutil.RandomCID(t) + req2, res2 := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{ + Root: root, + Shards: []cid.Cid{shard2}, + }) + require.NoError(t, deps.handler.Handler(req2, res2)) + _, fail2 := result.Unwrap(res2.Receipt().Out()) require.Nil(t, fail2) - // Upload should still exist - exists, err := store.Exists(ctx, space.DID(), root.Cid) + // Upload should still exist. + exists, err := deps.store.Exists(ctx, space.DID(), root) require.NoError(t, err) require.True(t, exists) }) diff --git a/pkg/service/handlers/upload_list.go b/pkg/service/handlers/upload_list.go index d760d00..ce2717c 100644 --- a/pkg/service/handlers/upload_list.go +++ b/pkg/service/handlers/upload_list.go @@ -1,46 +1,29 @@ package handlers import ( - "context" "fmt" - "github.com/fil-forge/go-libstoracha/capabilities/upload" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/lib/errors" + uploadcaps "github.com/fil-forge/libforge/capabilities/upload" upload_store "github.com/fil-forge/sprue/pkg/store/upload" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/fil-forge/ucantone/execution/bindexec" "go.uber.org/zap" ) -// WithUploadAddMethod registers the upload/add handler. -// This handler registers an upload (root CID + shards mapping). -func WithUploadListMethod(uploadStore upload_store.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - upload.AddAbility, - server.Provide(upload.Add, UploadAddHandler(uploadStore, logger)), - ) -} - -func UploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) server.HandlerFunc[upload.ListCaveats, upload.ListOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", upload.ListAbility)) - return server.HandlerFunc[upload.ListCaveats, upload.ListOk, failure.IPLDBuilderFailure]( - func(ctx context.Context, - cap ucan.Capability[upload.ListCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[upload.ListOk, failure.IPLDBuilderFailure], fx.Effects, error) { - args := cap.Nb() - log := log.With(zap.String("space", cap.With())) +func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", uploadcaps.ListCommand)) + return Handler{ + Capability: uploadcaps.List, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*uploadcaps.ListArguments], + res *bindexec.Response[*uploadcaps.ListOK], + ) error { + args := req.Task().BindArguments() + space := req.Invocation().Subject() + log := log.With(zap.Stringer("space", space.DID())) var opts []upload_store.ListOption if args.Size != nil { - log = log.With(zap.Uint64("size", *args.Size)) + log = log.With(zap.Int64("size", *args.Size)) opts = append(opts, upload_store.WithListLimit(int(*args.Size))) } if args.Cursor != nil { @@ -49,31 +32,24 @@ func UploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) serve } log.Debug("listing uploads") - space, err := did.Parse(cap.With()) - if err != nil { - return result.Error[upload.ListOk, failure.IPLDBuilderFailure]( - errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), - ), nil, nil - } - - page, err := uploadStore.List(ctx, space, opts...) + page, err := uploadStore.List(req.Context(), space.DID(), opts...) if err != nil { - log.Error("failed to llist uploads", zap.Error(err)) - return nil, nil, fmt.Errorf("listing uploads: %w", err) + log.Error("failed to list uploads", zap.Error(err)) + return fmt.Errorf("listing uploads: %w", err) } - results := make([]upload.ListItem, 0, len(page.Results)) + results := make([]uploadcaps.ListUploadItem, 0, len(page.Results)) for _, r := range page.Results { - results = append(results, upload.ListItem{ - Root: cidlink.Link{Cid: r.Root}, - InsertedAt: r.InsertedAt, - UpdatedAt: r.UpdatedAt, + results = append(results, uploadcaps.ListUploadItem{ + Root: r.Root, + Index: r.Index, }) } - return result.Ok[upload.ListOk, failure.IPLDBuilderFailure](upload.ListOk{ + return res.SetSuccess(&uploadcaps.ListOK{ Results: results, Cursor: page.Cursor, - }), nil, nil - }) + }) + }), + } } diff --git a/pkg/service/handlers/upload_list_test.go b/pkg/service/handlers/upload_list_test.go index 745abb5..75ca4a5 100644 --- a/pkg/service/handlers/upload_list_test.go +++ b/pkg/service/handlers/upload_list_test.go @@ -1,19 +1,47 @@ package handlers_test import ( + "context" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/upload" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" + uploadcaps "github.com/fil-forge/libforge/capabilities/upload" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/service/handlers" - uploadmemory "github.com/fil-forge/sprue/pkg/store/upload/memory" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" + upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) +// invokeUploadList builds an /upload/list invocation with the given args and a +// signed response ready for the handler. +func invokeUploadList( + t *testing.T, + ctx context.Context, + agent principal.Signer, + uploadService principal.Signer, + space principal.Signer, + args *uploadcaps.ListArguments, +) (execution.Request, *execution.ExecResponse) { + t.Helper() + inv, err := uploadcaps.List.Invoke( + agent, + space, + args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + return req, res +} + func TestUploadListHandler(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() @@ -21,177 +49,151 @@ func TestUploadListHandler(t *testing.T) { uploadService := testutil.WebService alice := testutil.Alice - t.Run("invalid space DID", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadListHandler(store, logger) - - caveats := upload.ListCaveats{} - inv, err := upload.List.Invoke(alice, uploadService, "not-a-did", caveats) - require.NoError(t, err) - - cap := upload.List.New("not-a-did", caveats) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.InvalidSpaceErrorName, *model.Name) - }) - t.Run("empty list", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadListHandler(store, logger) + store := upload_store.New() + handler := handlers.NewUploadListHandler(store, logger) space := testutil.RandomSigner(t) - caveats := upload.ListCaveats{} - inv, err := upload.List.Invoke(alice, uploadService, space.DID().String(), caveats) - require.NoError(t, err) + req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{}) - cap := upload.List.New(space.DID().String(), caveats) - - res, _, err := handler(ctx, cap, inv, nil) + err := handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) + require.NotNil(t, o) + + ok := uploadcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) require.Empty(t, ok.Results) require.Nil(t, ok.Cursor) }) t.Run("lists uploads", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadListHandler(store, logger) + store := upload_store.New() + handler := handlers.NewUploadListHandler(store, logger) space := testutil.RandomSigner(t) root1 := testutil.RandomCID(t) root2 := testutil.RandomCID(t) - err := store.Upsert(ctx, space.DID(), root1, nil, testutil.RandomCID(t)) - require.NoError(t, err) - err = store.Upsert(ctx, space.DID(), root2, nil, testutil.RandomCID(t)) - require.NoError(t, err) - - caveats := upload.ListCaveats{} - inv, err := upload.List.Invoke(alice, uploadService, space.DID().String(), caveats) - require.NoError(t, err) + require.NoError(t, store.Upsert(ctx, space.DID(), root1, nil, nil, testutil.RandomCID(t))) + require.NoError(t, store.Upsert(ctx, space.DID(), root2, nil, nil, testutil.RandomCID(t))) - cap := upload.List.New(space.DID().String(), caveats) + req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{}) - res, _, err := handler(ctx, cap, inv, nil) + err := handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) + ok := uploadcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) require.Len(t, ok.Results, 2) - roots := make(map[string]bool) + roots := map[string]bool{} for _, item := range ok.Results { roots[item.Root.String()] = true } - require.True(t, roots[cidlink.Link{Cid: root1}.String()]) - require.True(t, roots[cidlink.Link{Cid: root2}.String()]) + require.True(t, roots[root1.String()]) + require.True(t, roots[root2.String()]) }) t.Run("with size limit", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadListHandler(store, logger) + store := upload_store.New() + handler := handlers.NewUploadListHandler(store, logger) space := testutil.RandomSigner(t) - - for i := 0; i < 3; i++ { - err := store.Upsert(ctx, space.DID(), testutil.RandomCID(t), nil, testutil.RandomCID(t)) - require.NoError(t, err) + for range 3 { + require.NoError(t, store.Upsert(ctx, space.DID(), testutil.RandomCID(t), nil, nil, testutil.RandomCID(t))) } - size := uint64(2) - caveats := upload.ListCaveats{Size: &size} - inv, err := upload.List.Invoke(alice, uploadService, space.DID().String(), caveats) - require.NoError(t, err) - - cap := upload.List.New(space.DID().String(), caveats) + size := int64(2) + req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Size: &size}) - res, _, err := handler(ctx, cap, inv, nil) + err := handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) + ok := uploadcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) require.Len(t, ok.Results, 2) require.NotNil(t, ok.Cursor) }) t.Run("with cursor pagination", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadListHandler(store, logger) + store := upload_store.New() + handler := handlers.NewUploadListHandler(store, logger) space := testutil.RandomSigner(t) + for range 3 { + require.NoError(t, store.Upsert(ctx, space.DID(), testutil.RandomCID(t), nil, nil, testutil.RandomCID(t))) + } - err := store.Upsert(ctx, space.DID(), testutil.RandomCID(t), nil, testutil.RandomCID(t)) - require.NoError(t, err) - err = store.Upsert(ctx, space.DID(), testutil.RandomCID(t), nil, testutil.RandomCID(t)) - require.NoError(t, err) - err = store.Upsert(ctx, space.DID(), testutil.RandomCID(t), nil, testutil.RandomCID(t)) - require.NoError(t, err) - - // First page: size 1 - size := uint64(1) - caveats1 := upload.ListCaveats{Size: &size} - inv1, err := upload.List.Invoke(alice, uploadService, space.DID().String(), caveats1) - require.NoError(t, err) - - cap1 := upload.List.New(space.DID().String(), caveats1) - - res1, _, err := handler(ctx, cap1, inv1, nil) - require.NoError(t, err) + size := int64(1) + req1, res1 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Size: &size}) + require.NoError(t, handler.Handler(req1, res1)) - ok1, fail := result.Unwrap(res1) + o1, fail := result.Unwrap(res1.Receipt().Out()) require.Nil(t, fail) + ok1 := uploadcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o1), &ok1)) require.Len(t, ok1.Results, 1) require.NotNil(t, ok1.Cursor) - // Second page using cursor + // Second page using cursor. cursor := *ok1.Cursor - caveats2 := upload.ListCaveats{Cursor: &cursor, Size: &size} - inv2, err := upload.List.Invoke(alice, uploadService, space.DID().String(), caveats2) - require.NoError(t, err) - - cap2 := upload.List.New(space.DID().String(), caveats2) + req2, res2 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Cursor: &cursor, Size: &size}) + require.NoError(t, handler.Handler(req2, res2)) - res2, _, err := handler(ctx, cap2, inv2, nil) - require.NoError(t, err) - - ok2, fail := result.Unwrap(res2) + o2, fail := result.Unwrap(res2.Receipt().Out()) require.Nil(t, fail) + ok2 := uploadcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o2), &ok2)) require.Len(t, ok2.Results, 1) - - // Results should be different require.NotEqual(t, ok1.Results[0].Root.String(), ok2.Results[0].Root.String()) }) t.Run("does not list uploads from other spaces", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadListHandler(store, logger) + store := upload_store.New() + handler := handlers.NewUploadListHandler(store, logger) space1 := testutil.RandomSigner(t) space2 := testutil.RandomSigner(t) - err := store.Upsert(ctx, space1.DID(), testutil.RandomCID(t), nil, testutil.RandomCID(t)) - require.NoError(t, err) + require.NoError(t, store.Upsert(ctx, space1.DID(), testutil.RandomCID(t), nil, nil, testutil.RandomCID(t))) - caveats := upload.ListCaveats{} - inv, err := upload.List.Invoke(alice, uploadService, space2.DID().String(), caveats) - require.NoError(t, err) + // Query space2 — should be empty. + req, res := invokeUploadList(t, ctx, alice, uploadService, space2, &uploadcaps.ListArguments{}) + require.NoError(t, handler.Handler(req, res)) - cap := upload.List.New(space2.DID().String(), caveats) + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + ok := uploadcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.Empty(t, ok.Results) + }) - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) + t.Run("preserves optional index pointer", func(t *testing.T) { + store := upload_store.New() + handler := handlers.NewUploadListHandler(store, logger) - ok, fail := result.Unwrap(res) + space := testutil.RandomSigner(t) + root := testutil.RandomCID(t) + index := testutil.RandomCID(t) + require.NoError(t, store.Upsert(ctx, space.DID(), root, &index, nil, testutil.RandomCID(t))) + + req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{}) + + require.NoError(t, handler.Handler(req, res)) + + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) - require.Empty(t, ok.Results) + ok := uploadcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.Len(t, ok.Results, 1) + require.NotNil(t, ok.Results[0].Index) + require.Equal(t, cid.Cid(index), *ok.Results[0].Index) }) } diff --git a/pkg/service/handlers/upload_shard_list.go b/pkg/service/handlers/upload_shard_list.go index 73e417f..8f73e27 100644 --- a/pkg/service/handlers/upload_shard_list.go +++ b/pkg/service/handlers/upload_shard_list.go @@ -1,48 +1,31 @@ package handlers import ( - "context" "fmt" - "github.com/fil-forge/go-libstoracha/capabilities/upload/shard" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/go-ucanto/server" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/fil-forge/sprue/pkg/lib/errors" + shardcaps "github.com/fil-forge/libforge/capabilities/upload/shard" upload_store "github.com/fil-forge/sprue/pkg/store/upload" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/fil-forge/ucantone/execution/bindexec" "go.uber.org/zap" ) -// WithUploadShardListMethod registers the upload/shard/list handler. // This handler lists the shards of an upload. -func WithUploadShardListMethod(uploadStore upload_store.Store, logger *zap.Logger) server.Option { - return server.WithServiceMethod( - shard.ListAbility, - server.Provide(shard.List, UploadShardListHandler(uploadStore, logger)), - ) -} - -func UploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logger) server.HandlerFunc[shard.ListCaveats, shard.ListOk, failure.IPLDBuilderFailure] { - log := logger.With(zap.String("handler", shard.ListAbility)) - return server.HandlerFunc[shard.ListCaveats, shard.ListOk, failure.IPLDBuilderFailure]( - func(ctx context.Context, - cap ucan.Capability[shard.ListCaveats], - inv invocation.Invocation, - iCtx server.InvocationContext, - ) (result.Result[shard.ListOk, failure.IPLDBuilderFailure], fx.Effects, error) { - args := cap.Nb() - log := log.With(zap.String("space", cap.With()), zap.Stringer("root", args.Root)) +func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { + log := logger.With(zap.String("handler", shardcaps.ListCommand)) + return Handler{ + Capability: shardcaps.List, + Handler: bindexec.NewHandler(func( + req *bindexec.Request[*shardcaps.ListArguments], + res *bindexec.Response[*shardcaps.ListOK], + ) error { + args := req.Task().BindArguments() + space := req.Invocation().Subject() + root := args.Root + log := log.With(zap.Stringer("space", space.DID()), zap.Stringer("root", root)) var opts []upload_store.ListShardsOption if args.Size != nil { - log = log.With(zap.Uint64("size", *args.Size)) + log = log.With(zap.Int64("size", *args.Size)) opts = append(opts, upload_store.WithListShardsLimit(int(*args.Size))) } if args.Cursor != nil { @@ -51,32 +34,16 @@ func UploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logger) } log.Debug("listing upload shards") - space, err := did.Parse(cap.With()) - if err != nil { - return result.Error[shard.ListOk, failure.IPLDBuilderFailure]( - errors.New(InvalidSpaceErrorName, "invalid space DID: %v", err), - ), nil, nil - } - - root, err := ipldutil.ToCID(args.Root) - if err != nil { - return nil, nil, err - } - - page, err := uploadStore.ListShards(ctx, space, root, opts...) + page, err := uploadStore.ListShards(req.Context(), space.DID(), root, opts...) if err != nil { log.Error("failed to list upload shards", zap.Error(err)) - return nil, nil, fmt.Errorf("listing upload shards: %w", err) - } - - results := make([]ipld.Link, 0, len(page.Results)) - for _, r := range page.Results { - results = append(results, cidlink.Link{Cid: r}) + return fmt.Errorf("listing upload shards: %w", err) } - return result.Ok[shard.ListOk, failure.IPLDBuilderFailure](shard.ListOk{ - Results: results, + return res.SetSuccess(&shardcaps.ListOK{ + Results: page.Results, Cursor: page.Cursor, - }), nil, nil - }) + }) + }), + } } diff --git a/pkg/service/handlers/upload_shard_list_test.go b/pkg/service/handlers/upload_shard_list_test.go index bacc1d8..8802bba 100644 --- a/pkg/service/handlers/upload_shard_list_test.go +++ b/pkg/service/handlers/upload_shard_list_test.go @@ -1,20 +1,47 @@ package handlers_test import ( + "context" "testing" - "github.com/fil-forge/go-libstoracha/capabilities/upload/shard" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/failure/datamodel" + shardcaps "github.com/fil-forge/libforge/capabilities/upload/shard" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/service/handlers" - uploadmemory "github.com/fil-forge/sprue/pkg/store/upload/memory" + upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" + "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) +// invokeUploadShardList builds an /upload/shard/list invocation with the given +// args and a signed response ready for the handler. +func invokeUploadShardList( + t *testing.T, + ctx context.Context, + agent principal.Signer, + uploadService principal.Signer, + space principal.Signer, + args *shardcaps.ListArguments, +) (execution.Request, *execution.ExecResponse) { + t.Helper() + inv, err := shardcaps.List.Invoke( + agent, + space, + args, + invocation.WithAudience(uploadService), + ) + require.NoError(t, err) + req := execution.NewRequest(ctx, inv) + res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) + require.NoError(t, err) + return req, res +} + func TestUploadShardListHandler(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() @@ -22,128 +49,117 @@ func TestUploadShardListHandler(t *testing.T) { uploadService := testutil.WebService alice := testutil.Alice - t.Run("invalid space DID", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadShardListHandler(store, logger) - - root := cidlink.Link{Cid: testutil.RandomCID(t)} - caveats := shard.ListCaveats{Root: root} - inv, err := shard.List.Invoke(alice, uploadService, "not-a-did", caveats) - require.NoError(t, err) - - cap := shard.List.New("not-a-did", caveats) - - res, _, err := handler(ctx, cap, inv, nil) - require.NoError(t, err) - - _, fail := result.Unwrap(res) - require.NotNil(t, fail) - - model := datamodel.Bind(testutil.Must(fail.ToIPLD())(t)) - require.NotNil(t, model.Name) - require.Equal(t, handlers.InvalidSpaceErrorName, *model.Name) - }) - t.Run("empty shards", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadShardListHandler(store, logger) + store := upload_store.New() + handler := handlers.NewUploadShardListHandler(store, logger) space := testutil.RandomSigner(t) root := testutil.RandomCID(t) - // Create upload with no shards - err := store.Upsert(ctx, space.DID(), root, nil, testutil.RandomCID(t)) - require.NoError(t, err) + // Upload exists with no shards. + require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, nil, testutil.RandomCID(t))) - rootLink := cidlink.Link{Cid: root} - caveats := shard.ListCaveats{Root: rootLink} - inv, err := shard.List.Invoke(alice, uploadService, space.DID().String(), caveats) - require.NoError(t, err) + req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root}) - cap := shard.List.New(space.DID().String(), caveats) - - res, _, err := handler(ctx, cap, inv, nil) + err := handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) + require.NotNil(t, o) + + ok := shardcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) require.Empty(t, ok.Results) }) t.Run("lists shards", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadShardListHandler(store, logger) + store := upload_store.New() + handler := handlers.NewUploadShardListHandler(store, logger) space := testutil.RandomSigner(t) root := testutil.RandomCID(t) shard1 := testutil.RandomCID(t) shard2 := testutil.RandomCID(t) - err := store.Upsert(ctx, space.DID(), root, []cid.Cid{shard1, shard2}, testutil.RandomCID(t)) - require.NoError(t, err) - - rootLink := cidlink.Link{Cid: root} - caveats := shard.ListCaveats{Root: rootLink} - inv, err := shard.List.Invoke(alice, uploadService, space.DID().String(), caveats) - require.NoError(t, err) + require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, []cid.Cid{shard1, shard2}, testutil.RandomCID(t))) - cap := shard.List.New(space.DID().String(), caveats) + req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root}) - res, _, err := handler(ctx, cap, inv, nil) + err := handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res) + o, fail := result.Unwrap(res.Receipt().Out()) require.Nil(t, fail) + ok := shardcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) require.Len(t, ok.Results, 2) + + got := map[string]bool{} + for _, c := range ok.Results { + got[c.String()] = true + } + require.True(t, got[shard1.String()]) + require.True(t, got[shard2.String()]) }) - t.Run("with cursor pagination", func(t *testing.T) { - store := uploadmemory.New() - handler := handlers.UploadShardListHandler(store, logger) + t.Run("with size limit", func(t *testing.T) { + store := upload_store.New() + handler := handlers.NewUploadShardListHandler(store, logger) space := testutil.RandomSigner(t) root := testutil.RandomCID(t) shard1 := testutil.RandomCID(t) shard2 := testutil.RandomCID(t) shard3 := testutil.RandomCID(t) + require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, []cid.Cid{shard1, shard2, shard3}, testutil.RandomCID(t))) + + size := int64(2) + req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Size: &size}) - err := store.Upsert(ctx, space.DID(), root, []cid.Cid{shard1, shard2, shard3}, testutil.RandomCID(t)) + err := handler.Handler(req, res) require.NoError(t, err) - rootLink := cidlink.Link{Cid: root} + o, fail := result.Unwrap(res.Receipt().Out()) + require.Nil(t, fail) + ok := shardcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + require.Len(t, ok.Results, 2) + require.NotNil(t, ok.Cursor) + }) - // First page: size 1 - size := uint64(1) - caveats1 := shard.ListCaveats{Root: rootLink, Size: &size} - inv1, err := shard.List.Invoke(alice, uploadService, space.DID().String(), caveats1) - require.NoError(t, err) + t.Run("with cursor pagination", func(t *testing.T) { + store := upload_store.New() + handler := handlers.NewUploadShardListHandler(store, logger) - cap1 := shard.List.New(space.DID().String(), caveats1) + space := testutil.RandomSigner(t) + root := testutil.RandomCID(t) + shard1 := testutil.RandomCID(t) + shard2 := testutil.RandomCID(t) + shard3 := testutil.RandomCID(t) + require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, []cid.Cid{shard1, shard2, shard3}, testutil.RandomCID(t))) - res1, _, err := handler(ctx, cap1, inv1, nil) - require.NoError(t, err) + size := int64(1) + req1, res1 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Size: &size}) + require.NoError(t, handler.Handler(req1, res1)) - ok1, fail := result.Unwrap(res1) + o1, fail := result.Unwrap(res1.Receipt().Out()) require.Nil(t, fail) + ok1 := shardcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o1), &ok1)) require.Len(t, ok1.Results, 1) require.NotNil(t, ok1.Cursor) - // Second page using cursor + // Second page using cursor. cursor := *ok1.Cursor - caveats2 := shard.ListCaveats{Root: rootLink, Cursor: &cursor, Size: &size} - inv2, err := shard.List.Invoke(alice, uploadService, space.DID().String(), caveats2) - require.NoError(t, err) + req2, res2 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Cursor: &cursor, Size: &size}) + require.NoError(t, handler.Handler(req2, res2)) - cap2 := shard.List.New(space.DID().String(), caveats2) - - res2, _, err := handler(ctx, cap2, inv2, nil) - require.NoError(t, err) - - ok2, fail := result.Unwrap(res2) + o2, fail := result.Unwrap(res2.Receipt().Out()) require.Nil(t, fail) + ok2 := shardcaps.ListOK{} + require.NoError(t, datamodel.Rebind(datamodel.NewAny(o2), &ok2)) require.Len(t, ok2.Results, 1) - - // Results should be different require.NotEqual(t, ok1.Results[0].String(), ok2.Results[0].String()) }) } diff --git a/pkg/service/service.go b/pkg/service/service.go index 45a7fc8..a274b58 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -2,37 +2,25 @@ package service import ( "bytes" - "context" "errors" "fmt" - "io" "net/http" "slices" - "github.com/fil-forge/go-libstoracha/capabilities/access" - "github.com/fil-forge/go-ucanto/core/car" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/server" - ucanhttp "github.com/fil-forge/go-ucanto/transport/http" - "github.com/fil-forge/go-ucanto/ucan" - "github.com/fil-forge/go-ucanto/validator" - "github.com/ipfs/go-cid" - "github.com/labstack/echo/v4" - "go.uber.org/zap" - "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/indexerclient" - "github.com/fil-forge/sprue/pkg/lib/didmailto" - "github.com/fil-forge/sprue/pkg/lib/ucans" + "github.com/fil-forge/sprue/pkg/lib/ucan_server" "github.com/fil-forge/sprue/pkg/service/handlers" "github.com/fil-forge/sprue/pkg/service/ui" "github.com/fil-forge/sprue/pkg/store/agent" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/ipld/codec/dagcbor" + "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/server" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/validator" + "github.com/ipfs/go-cid" + "github.com/labstack/echo/v4" + "go.uber.org/zap" ) // Service implements the sprue upload service logic. @@ -40,118 +28,46 @@ type Service struct { identity *identity.Identity agentStore agent.Store delegationStore delegation_store.Store - indexerClient *indexerclient.Client logger *zap.Logger - ucanServer server.ServerView[server.Service] - options []server.Option + ucanServer *server.HTTPServer } // New creates a new Service instance. -func New(id *identity.Identity, agentStore agent.Store, delegationStore delegation_store.Store, indexerClient *indexerclient.Client, logger *zap.Logger, options ...server.Option) (*Service, error) { - svc := &Service{ +func New(id *identity.Identity, agentStore agent.Store, delegationStore delegation_store.Store, handlers []handlers.Handler, logger *zap.Logger, options ...server.HTTPOption) *Service { + return &Service{ identity: id, agentStore: agentStore, delegationStore: delegationStore, - indexerClient: indexerClient, logger: logger, - options: options, - } - - // Create UCAN server with handlers - ucanSrv, err := svc.createUCANServer() - if err != nil { - return nil, fmt.Errorf("failed to create UCAN server: %w", err) + ucanServer: createUCANServer(id.Signer, agentStore, handlers, logger, options...), } - svc.ucanServer = ucanSrv - - return svc, nil } // createUCANServer creates the UCAN RPC server with registered handlers. -func (s *Service) createUCANServer() (server.ServerView[server.Service], error) { - log := s.logger - options := append( - slices.Clone(s.options), - server.WithErrorHandler(func(err server.HandlerExecutionError[any]) { - if stack := err.Stack(); stack != "" { - log = log.With(zap.String("stack", stack)) - } - log.Error("handler execution", zap.Error(err)) - }), +func createUCANServer(id principal.Signer, agentStore agent.Store, handlers []handlers.Handler, logger *zap.Logger, options ...server.HTTPOption) *server.HTTPServer { + options = append( + slices.Clone(options), + server.WithReceiptTimestamps(true), + server.WithEventListener(ucan_server.AgentMessageLogger{Logger: logger, AgentStore: agentStore}), + server.WithEventListener(ucan_server.ErrorHandler{Logger: logger}), + server.WithValidationOptions( + validator.WithPrincipalParser(ucan_server.PrincipalParser), + validator.WithNonStandardSignatureVerifier( + ucan_server.NewAttestationVerifier(id.Verifier()), + ), + ), ) - return server.NewServer(s.identity.Signer, options...) + srv := server.NewHTTP(id, options...) + for _, h := range handlers { + srv.Handle(h.Capability, h.Handler) + } + return srv } // HandleUCANRequest handles incoming UCAN RPC requests. func (s *Service) HandleUCANRequest(c echo.Context) error { - r := c.Request() - - inBytes, inMsg, inIdx, err := decodeAndIndex(r.Body) - if err != nil { - return fmt.Errorf("decoding and indexing incoming agent message: %w", err) - } - r.Body.Close() - - err = s.agentStore.Write(r.Context(), inMsg, inIdx, inBytes) - if err != nil { - return fmt.Errorf("writing incoming agent message to agent store: %w", err) - } - - res, err := s.ucanServer.Request(r.Context(), ucanhttp.NewRequest(bytes.NewReader(inBytes), r.Header)) - if err != nil { - s.logger.Error("UCAN request error", zap.Error(err)) - return fmt.Errorf("handling UCAN request: %w", err) - } - - outBytes, outMsg, outIdx, err := decodeAndIndex(res.Body()) - if err != nil { - return fmt.Errorf("decoding and indexing outgoing agent message: %w", err) - } - res.Body().Close() - - err = s.agentStore.Write(r.Context(), outMsg, outIdx, outBytes) - if err != nil { - return fmt.Errorf("writing outgoing agent message to agent store: %w", err) - } - - // Copy response headers - for key, vals := range res.Headers() { - for _, v := range vals { - c.Response().Header().Add(key, v) - } - } - - return c.Stream(res.Status(), "", bytes.NewReader(outBytes)) -} - -func decodeAndIndex(r io.Reader) ([]byte, message.AgentMessage, []agent.IndexEntry, error) { - body, err := io.ReadAll(r) - if err != nil { - return nil, nil, nil, fmt.Errorf("reading request body: %w", err) - } - roots, blocks, err := car.Decode(bytes.NewReader(body)) - if err != nil { - return nil, nil, nil, fmt.Errorf("decoding CAR: %w", err) - } - if len(roots) != 1 { - return nil, nil, nil, fmt.Errorf("expected exactly one root in CAR, got %d", len(roots)) - } - br, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(blocks)) - if err != nil { - return nil, nil, nil, fmt.Errorf("creating block reader: %w", err) - } - msg, err := message.NewMessage(roots[0], br) - if err != nil { - return nil, nil, nil, fmt.Errorf("creating agent message: %w", err) - } - var entries []agent.IndexEntry - for ent, err := range agent.Index(msg) { - if err != nil { - return nil, nil, nil, fmt.Errorf("indexing agent message: %w", err) - } - entries = append(entries, ent) - } - return body, msg, entries, nil + s.ucanServer.ServeHTTP(c.Response(), c.Request()) + return nil } func (s *Service) HandleValidateEmailRequest(c echo.Context) error { @@ -171,7 +87,7 @@ func (s *Service) HandleValidateEmailRequest(c echo.Context) error { } return c.Stream(http.StatusOK, "text/html", r) case http.MethodPost: - res, err := s.authorize(c.Request().Context(), c.QueryParam("ucan")) + res, err := ucan_server.ExecBase64urlAccessConfirm(c.Request().Context(), s.ucanServer, c.QueryParam("ucan")) if err != nil { s.logger.Error("authorization error", zap.Error(err)) r, err := ui.ErrorPage(fmt.Sprintf("Oops, something went wrong: %s", err.Error())) @@ -190,72 +106,6 @@ func (s *Service) HandleValidateEmailRequest(c echo.Context) error { } } -type authorizationResult struct { - Email string - Audience string - UCAN string - Facts []ucan.Fact -} - -func (s *Service) authorize(ctx context.Context, ucan string) (authorizationResult, error) { - dlgs, err := ucans.ParseDelegations(ucan) - if err != nil { - return authorizationResult{}, fmt.Errorf("parsing delegations: %w", err) - } - if len(dlgs) != 1 { - return authorizationResult{}, fmt.Errorf("unexpected number of delegations found in UCAN") - } - confirmation := dlgs[0] - - confirm := server.Provide( - access.Confirm, - handlers.AccessConfirmHandler(s.identity, s.delegationStore, s.logger), - ) - txn, err := confirm(ctx, confirmation, s.ucanServer.Context()) - if err != nil { - return authorizationResult{}, fmt.Errorf("executing access/confirm handler: %w", err) - } - o, x := result.Unwrap(txn.Out()) - if x != nil { - return authorizationResult{}, fmt.Errorf("access/confirm invocation failure: %w", x) - } - - // Extract the email and audience from the confirmation invocation. - // This should match since we just successfully invoked the handler. - match, err := access.Confirm.Match(validator.NewSource(confirmation.Capabilities()[0], confirmation)) - if err != nil { - return authorizationResult{}, fmt.Errorf("matching access/confirm capability: %w", err) - } - email, err := didmailto.Email(match.Value().Nb().Iss) - if err != nil { - return authorizationResult{}, fmt.Errorf("parsing account DID: %w", err) - } - - var confirmDlgs []delegation.Delegation - for _, bytes := range o.Delegations.Values { - dlgs, err := ucans.ExtractDelegations(bytes) - if err != nil { - return authorizationResult{}, fmt.Errorf("extracting delegations from confirmation result: %w", err) - } - if len(dlgs) != 1 { - return authorizationResult{}, fmt.Errorf("unexpected number of delegations found in confirmation result") - } - confirmDlgs = append(confirmDlgs, dlgs[0]) - } - - ucan, err = ucans.FormatDelegations(confirmDlgs...) - if err != nil { - return authorizationResult{}, fmt.Errorf("formatting delegations: %w", err) - } - - return authorizationResult{ - Email: email, - Audience: match.Value().Nb().Aud.String(), - UCAN: ucan, - Facts: confirmation.Facts(), - }, nil -} - // HandleReceiptRequest handles receipt retrieval requests. func (s *Service) HandleReceiptRequest(c echo.Context) error { task, err := cid.Parse(c.Param("cid")) @@ -276,15 +126,11 @@ func (s *Service) HandleReceiptRequest(c echo.Context) error { return fmt.Errorf("getting receipt: %w", err) } - // Build an agent message containing the receipt - msg, err := message.Build(nil, []receipt.AnyReceipt{rcpt}) - if err != nil { - s.logger.Error("failed to build message", zap.Error(err)) - return c.JSON(http.StatusInternalServerError, map[string]string{ - "error": "failed to build message", - }) + ct := container.New(container.WithReceipts(rcpt)) + var buf bytes.Buffer + if err := ct.MarshalCBOR(&buf); err != nil { + return fmt.Errorf("marshaling receipt container: %w", err) } - reader := car.Encode([]ipld.Link{msg.Root().Link()}, msg.Blocks()) - return c.Stream(http.StatusOK, car.ContentType, reader) + return c.Blob(http.StatusOK, dagcbor.ContentType, buf.Bytes()) } diff --git a/pkg/store/agent/agent.go b/pkg/store/agent/agent.go index 99b929b..d67688f 100644 --- a/pkg/store/agent/agent.go +++ b/pkg/store/agent/agent.go @@ -3,10 +3,8 @@ package agent import ( "context" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/sprue/pkg/lib/errors" + "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/ucan" "github.com/ipfs/go-cid" ) @@ -24,14 +22,12 @@ var ( type InvocationSource struct { Task cid.Cid - Invocation invocation.Invocation - Message cid.Cid + Invocation ucan.Invocation } type ReceiptSource struct { Task cid.Cid - Receipt receipt.AnyReceipt - Message cid.Cid + Receipt ucan.Receipt } // IndexEntry is either an indexed invocation OR an indexed receipt. @@ -42,9 +38,9 @@ type IndexEntry struct { type Store interface { // Write an agent message to the store. - Write(ctx context.Context, message message.AgentMessage, index []IndexEntry, source []byte) error + Write(ctx context.Context, message ucan.Container, index []IndexEntry) error // GetInvocation retrieves an invocation by its task CID. May return [ErrInvocationNotFound]. - GetInvocation(ctx context.Context, task cid.Cid) (invocation.Invocation, error) + GetInvocation(ctx context.Context, task cid.Cid) (ucan.Invocation, error) // GetReceipt retrieves a receipt by its task CID. May return [ErrReceiptNotFound]. - GetReceipt(ctx context.Context, task cid.Cid) (receipt.AnyReceipt, error) + GetReceipt(ctx context.Context, task cid.Cid) (ucan.Receipt, error) } diff --git a/pkg/store/agent/agent_test.go b/pkg/store/agent/agent_test.go index b90ad56..da1a3eb 100644 --- a/pkg/store/agent/agent_test.go +++ b/pkg/store/agent/agent_test.go @@ -2,27 +2,22 @@ package agent_test import ( "context" - "io" "runtime" "testing" - ucancap "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-ucanto/core/car" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/receipt/ran" - "github.com/fil-forge/go-ucanto/core/result" - "github.com/fil-forge/go-ucanto/core/result/ok" - "github.com/fil-forge/go-ucanto/ucan" + ucancap "github.com/fil-forge/libforge/capabilities/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store/agent" "github.com/fil-forge/sprue/pkg/store/agent/aws" "github.com/fil-forge/sprue/pkg/store/agent/memory" - agentpostgres "github.com/fil-forge/sprue/pkg/store/agent/postgres" + "github.com/fil-forge/ucantone/ipld" + "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/result" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/fil-forge/ucantone/ucan/invocation" + "github.com/fil-forge/ucantone/ucan/receipt" "github.com/google/uuid" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/stretchr/testify/require" ) @@ -100,45 +95,38 @@ func createAWSStore(t *testing.T) agent.Store { return store } -func makeInvocation(t *testing.T) invocation.Invocation { +func makeInvocation(t *testing.T) ucan.Invocation { t.Helper() inv, err := invocation.Invoke( testutil.Alice, - testutil.Bob, - ucan.NewCapability("test/invoke", testutil.Alice.DID().String(), ucan.NoCaveats{}), + testutil.Alice, + "/test/invoke", + datamodel.Map{}, + invocation.WithAudience(testutil.Bob), ) require.NoError(t, err) return inv } -func makeReceipt(t *testing.T, inv invocation.Invocation) receipt.AnyReceipt { +func makeReceipt(t *testing.T, inv ucan.Invocation) ucan.Receipt { t.Helper() rcpt, err := receipt.Issue( testutil.Alice, - result.Ok[ok.Unit, ipld.Builder](ok.Unit{}), - ran.FromInvocation(inv), + inv.Task().Link(), + result.OK[ipld.Any, ipld.Any](datamodel.Map{}), ) require.NoError(t, err) return rcpt } -func buildAndWrite(t *testing.T, store agent.Store, invocations []invocation.Invocation, receipts []receipt.AnyReceipt) { +func buildAndWrite(t *testing.T, store agent.Store, invocations []ucan.Invocation, receipts []ucan.Receipt) { t.Helper() - msg, err := message.Build(invocations, receipts) - require.NoError(t, err) - - carReader := car.Encode([]ipld.Link{msg.Root().Link()}, msg.Blocks()) - - source, err := io.ReadAll(carReader) - require.NoError(t, err) - - var index []agent.IndexEntry - for entry, err := range agent.Index(msg) { - require.NoError(t, err) - index = append(index, entry) - } - - err = store.Write(t.Context(), msg, index, source) + msg := container.New( + container.WithInvocations(invocations...), + container.WithReceipts(receipts...), + ) + index := agent.Index(msg) + err := store.Write(t.Context(), msg, index) require.NoError(t, err) } @@ -149,9 +137,9 @@ func TestAgentStore(t *testing.T) { t.Run("gets an invocation", func(t *testing.T) { inv := makeInvocation(t) - buildAndWrite(t, store, []invocation.Invocation{inv}, nil) + buildAndWrite(t, store, []ucan.Invocation{inv}, nil) - got, err := store.GetInvocation(t.Context(), inv.Link().(cidlink.Link).Cid) + got, err := store.GetInvocation(t.Context(), inv.Task().Link()) require.NoError(t, err) require.Equal(t, inv.Link().String(), got.Link().String()) }) @@ -164,11 +152,11 @@ func TestAgentStore(t *testing.T) { t.Run("gets a receipt", func(t *testing.T) { inv := makeInvocation(t) rcpt := makeReceipt(t, inv) - buildAndWrite(t, store, nil, []receipt.AnyReceipt{rcpt}) + buildAndWrite(t, store, nil, []ucan.Receipt{rcpt}) - got, err := store.GetReceipt(t.Context(), inv.Link().(cidlink.Link).Cid) + got, err := store.GetReceipt(t.Context(), inv.Task().Link()) require.NoError(t, err) - require.Equal(t, rcpt.Root().Link().String(), got.Root().Link().String()) + require.Equal(t, rcpt.Link().String(), got.Link().String()) }) t.Run("returns not found for missing receipt", func(t *testing.T) { @@ -183,38 +171,30 @@ func TestAgentStore(t *testing.T) { // Create a ucan/conclude invocation that carries the receipt as its // nb.receipt caveat. This is how agents communicate receipts in-band. - concludeInv, err := invocation.Invoke( + + concludeInv, err := ucancap.Conclude.Invoke( + testutil.Alice, testutil.Alice, - testutil.Bob, - ucan.NewCapability( - ucancap.ConcludeAbility, - testutil.Alice.DID().String(), - ucancap.ConcludeCaveats{Receipt: rcpt.Root().Link()}, - ), + &ucancap.ConcludeArguments{ + Receipt: rcpt.Link(), + }, + invocation.WithAudience(testutil.Bob), ) require.NoError(t, err) - // The indexer resolves the receipt link against the message blockstore, - // so the receipt blocks must travel with the conclude invocation. Attach - // them directly so they are included when the message is built. - for blk, err := range rcpt.Blocks() { - require.NoError(t, err) - require.NoError(t, concludeInv.Attach(blk)) - } - // The receipt is now retrievable by the original task invocation CID. - buildAndWrite(t, store, []invocation.Invocation{concludeInv}, nil) - got, err := store.GetReceipt(t.Context(), taskInv.Link().(cidlink.Link).Cid) + buildAndWrite(t, store, []ucan.Invocation{concludeInv}, []ucan.Receipt{rcpt}) + got, err := store.GetReceipt(t.Context(), taskInv.Task().Link()) require.NoError(t, err) - require.Equal(t, rcpt.Root().Link().String(), got.Root().Link().String()) + require.Equal(t, rcpt.Link().String(), got.Link().String()) }) t.Run("writes invocation and receipt in the same message", func(t *testing.T) { inv := makeInvocation(t) rcpt := makeReceipt(t, inv) - buildAndWrite(t, store, []invocation.Invocation{inv}, []receipt.AnyReceipt{rcpt}) + buildAndWrite(t, store, []ucan.Invocation{inv}, []ucan.Receipt{rcpt}) - task := inv.Link().(cidlink.Link).Cid + task := inv.Task().Link() gotInv, err := store.GetInvocation(t.Context(), task) require.NoError(t, err) @@ -222,7 +202,7 @@ func TestAgentStore(t *testing.T) { gotRcpt, err := store.GetReceipt(t.Context(), task) require.NoError(t, err) - require.Equal(t, rcpt.Root().Link().String(), gotRcpt.Root().Link().String()) + require.Equal(t, rcpt.Link().String(), gotRcpt.Link().String()) }) }) } diff --git a/pkg/store/agent/aws/store.go b/pkg/store/agent/aws/store.go index 825e127..e674568 100644 --- a/pkg/store/agent/aws/store.go +++ b/pkg/store/agent/aws/store.go @@ -14,16 +14,13 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/fil-forge/go-libstoracha/jobqueue" - "github.com/fil-forge/go-ucanto/core/car" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" + "github.com/fil-forge/libforge/jobqueue" "github.com/fil-forge/sprue/pkg/store/agent" + "github.com/fil-forge/ucantone/ipld/codec/dagcbor" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" cid "github.com/ipfs/go-cid" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/multiformats/go-multihash" ) const ( @@ -138,25 +135,34 @@ func (s *Store) Initialize(ctx context.Context) error { return nil } -func (s *Store) GetInvocation(ctx context.Context, task cid.Cid) (invocation.Invocation, error) { - root, bs, err := s.getByTask(ctx, task, "in") +func (s *Store) GetInvocation(ctx context.Context, task cid.Cid) (ucan.Invocation, error) { + _, ct, err := s.getByTask(ctx, task, "in") if err != nil { return nil, fmt.Errorf("getting invocation for task %s: %w", task, err) } - return invocation.NewInvocationView(cidlink.Link{Cid: root}, bs) + for _, inv := range ct.Invocations() { + if inv.Task().Link() == task { + return inv, nil + } + } + return nil, agent.ErrInvocationNotFound } -func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (receipt.AnyReceipt, error) { - root, bs, err := s.getByTask(ctx, task, "out") +func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (ucan.Receipt, error) { + _, ct, err := s.getByTask(ctx, task, "out") if err != nil { return nil, fmt.Errorf("getting receipt for task %s: %w", task, err) } - return receipt.NewAnyReceipt(cidlink.Link{Cid: root}, bs) + rcpt, ok := ct.Receipt(task) + if !ok { + return nil, agent.ErrReceiptNotFound + } + return rcpt, nil } -// getByTask is a helper method that retrieves the invocation or receipt root +// getByTask is a helper method that retrieves the invocation or receipt // CID and blocks for a given task CID and kind ("in" for invocation or "out" for receipt). -func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.Cid, blockstore.BlockReader, error) { +func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.Cid, *container.Container, error) { taskkind := fmt.Sprintf("%s.%s", task, kind) queryInput := &dynamodb.QueryInput{ TableName: &s.tableName, @@ -211,29 +217,40 @@ func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.C } defer getRes.Body.Close() - _, blocks, err := car.Decode(getRes.Body) - if err != nil { - return cid.Undef, nil, fmt.Errorf("decoding CAR: %w", err) + var ct container.Container + if err := ct.UnmarshalCBOR(getRes.Body); err != nil { + return cid.Undef, nil, fmt.Errorf("unmarshaling agent message from CBOR: %w", err) } - bs, err := blockstore.NewBlockStore(blockstore.WithBlocksIterator(blocks)) - if err != nil { - return cid.Undef, nil, fmt.Errorf("creating blockstore: %w", err) - } - return root, bs, nil + + return root, &ct, nil } func (s *Store) Shutdown(ctx context.Context) error { return s.writeQueue.Shutdown(ctx) } -func (s *Store) Write(ctx context.Context, message message.AgentMessage, index []agent.IndexEntry, source []byte) error { +func (s *Store) Write(ctx context.Context, message ucan.Container, index []agent.IndexEntry) error { var wg sync.WaitGroup var writeErrMutex sync.Mutex var writeErr error - msgRoot, err := ipldutil.ToCID(message.Root().Link()) + c, ok := message.(*container.Container) + if !ok { + c = container.New( + container.WithInvocations(message.Invocations()...), + container.WithReceipts(message.Receipts()...), + container.WithDelegations(message.Delegations()...), + ) + } + + var buf bytes.Buffer + if err := c.MarshalCBOR(&buf); err != nil { + return fmt.Errorf("marshaling agent message to CBOR: %w", err) + } + + msgRoot, err := cid.V1Builder{Codec: dagcbor.Code, MhType: multihash.SHA2_256}.Sum(buf.Bytes()) if err != nil { - return fmt.Errorf("converting message root link to CID: %w", err) + return fmt.Errorf("hashing agent message: %w", err) } callback := func(err error) { @@ -250,7 +267,7 @@ func (s *Store) Write(ctx context.Context, message message.AgentMessage, index [ s3Put: &s3.PutObjectInput{ Bucket: &s.bucketName, Key: aws.String(toMessagePath(msgRoot)), - Body: bytes.NewReader(source), + Body: &buf, }, callback: callback, }) @@ -260,10 +277,7 @@ func (s *Store) Write(ctx context.Context, message message.AgentMessage, index [ for _, entry := range index { if entry.Invocation != nil { - invRoot, err := ipldutil.ToCID(entry.Invocation.Invocation.Link()) - if err != nil { - return fmt.Errorf("converting invocation root link to CID: %w", err) - } + invRoot := entry.Invocation.Invocation.Link() wg.Add(1) err = s.writeQueue.Queue(ctx, awsWriteJob{ @@ -278,10 +292,7 @@ func (s *Store) Write(ctx context.Context, message message.AgentMessage, index [ } } if entry.Receipt != nil { - rcptRoot, err := ipldutil.ToCID(entry.Receipt.Receipt.Root().Link()) - if err != nil { - return fmt.Errorf("converting receipt root link to CID: %w", err) - } + rcptRoot := entry.Receipt.Receipt.Link() wg.Add(1) err = s.writeQueue.Queue(ctx, awsWriteJob{ diff --git a/pkg/store/agent/index.go b/pkg/store/agent/index.go index 0b58967..b3d6827 100644 --- a/pkg/store/agent/index.go +++ b/pkg/store/agent/index.go @@ -1,239 +1,26 @@ package agent -import ( - "fmt" - "iter" - - "github.com/fil-forge/go-libstoracha/capabilities/ucan" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/go-ucanto/core/receipt/fx" - "github.com/fil-forge/go-ucanto/validator" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - "github.com/ipld/go-ipld-prime" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" -) - -var log = logging.Logger("store/agent") - -type member struct { - invocation invocation.Invocation - receipt receipt.AnyReceipt -} - -// Iterates all embedded invocations & receipts of the given invocation. -func iterateInvocation(source cid.Cid, blocks blockstore.BlockReader, inv invocation.Invocation) iter.Seq2[member, error] { - return func(yield func(member, error) bool) { - caps := inv.Capabilities() - if len(caps) > 0 && caps[0].Can() == ucan.ConcludeAbility { - var err error - match, err := ucan.Conclude.Match(validator.NewSource(caps[0], inv)) - if err != nil { - log.Warnw("invalid invocation", "can", ucan.ConcludeAbility, "source", source, "error", err) - return - } - rcpt, err := receipt.NewAnyReceipt(match.Value().Nb().Receipt, blocks) - if err != nil { - log.Warnw("creating receipt", "source", source, "error", err) - return - } - if !yield(member{receipt: rcpt}, nil) { - return - } - for m, err := range iterateReceipt(source, blocks, rcpt) { - if err != nil { - yield(member{}, err) - return - } - if !yield(m, nil) { - return - } - } - } - } -} - -// Iterates all embedded invocations & receipts of the given receipt. -func iterateReceipt(source cid.Cid, blocks blockstore.BlockReader, rcpt receipt.AnyReceipt) iter.Seq2[member, error] { - return func(yield func(member, error) bool) { - invs := []invocation.Invocation{} - inv, ok := rcpt.Ran().Invocation() - if ok { - invs = append(invs, inv) - } - - if rcpt.Fx() != nil { - for _, fx := range rcpt.Fx().Fork() { - inv, ok := fx.Invocation() - if ok { - invs = append(invs, inv) - } - } - if rcpt.Fx().Join() != (fx.Effect{}) { - inv, ok := rcpt.Fx().Join().Invocation() - if ok { - invs = append(invs, inv) - } - } - } - - for _, inv := range invs { - if !yield(member{invocation: inv}, nil) { - return - } - for m, err := range iterateInvocation(source, blocks, inv) { - if err != nil { - yield(member{}, err) - return - } - if !yield(m, nil) { - return - } - } - } +import "github.com/fil-forge/ucantone/ucan" + +func Index(message ucan.Container) []IndexEntry { + var entries []IndexEntry + for _, inv := range message.Invocations() { + entry := IndexEntry{ + Invocation: &InvocationSource{ + Task: inv.Task().Link(), + Invocation: inv, + }, + } + entries = append(entries, entry) } -} - -func Index(message message.AgentMessage) iter.Seq2[IndexEntry, error] { - return func(yield func(IndexEntry, error) bool) { - source, err := toCID(message.Root().Link()) - if err != nil { - yield(IndexEntry{}, fmt.Errorf("converting message root link to CID: %w", err)) - return - } - - blocks, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(message.Blocks())) - if err != nil { - yield(IndexEntry{}, err) - return - } - for _, root := range message.Invocations() { - inv, err := invocation.NewInvocationView(root, blocks) - if err != nil { - log.Warnw("creating invocation", "source", source, "error", err) - continue - } - task, err := toCID(root) - if err != nil { - log.Warnw("converting invocation link to CID", "source", source, "link", root, "error", err) - continue - } - entry := IndexEntry{ - Invocation: &InvocationSource{ - Task: task, - Invocation: inv, - Message: source, - }, - } - if !yield(entry, nil) { - return - } - - for m, err := range iterateInvocation(source, blocks, inv) { - if err != nil { - yield(IndexEntry{}, err) - return - } - var entry IndexEntry - if m.invocation != nil { - task, err := toCID(m.invocation.Link()) - if err != nil { - log.Warnw("converting invocation link to CID", "source", source, "link", m.invocation.Link(), "error", err) - continue - } - entry.Invocation = &InvocationSource{ - Task: task, - Invocation: m.invocation, - Message: source, - } - } else if m.receipt != nil { - task, err := toCID(m.receipt.Ran().Link()) - if err != nil { - log.Warnw("converting receipt link to CID", "source", source, "link", m.receipt.Ran().Link(), "error", err) - continue - } - entry.Receipt = &ReceiptSource{ - Task: task, - Receipt: m.receipt, - Message: source, - } - } else { - yield(IndexEntry{}, fmt.Errorf("unexpected member with neither invocation nor receipt: %v", m)) - return - } - if !yield(entry, nil) { - return - } - } - } - - for _, root := range message.Receipts() { - rcpt, err := receipt.NewAnyReceipt(root, blocks) - if err != nil { - log.Warnw("creating receipt", "source", source, "error", err) - continue - } - task, err := toCID(rcpt.Ran().Link()) - if err != nil { - log.Warnw("converting receipt ran link to CID", "source", source, "link", rcpt.Ran().Link(), "error", err) - continue - } - entry := IndexEntry{ - Receipt: &ReceiptSource{ - Task: task, - Receipt: rcpt, - Message: source, - }, - } - if !yield(entry, nil) { - return - } - for m, err := range iterateReceipt(source, blocks, rcpt) { - if err != nil { - yield(IndexEntry{}, err) - return - } - var entry IndexEntry - if m.invocation != nil { - task, err := toCID(m.invocation.Link()) - if err != nil { - log.Warnw("converting invocation link to CID", "source", source, "link", m.invocation.Link(), "error", err) - continue - } - entry.Invocation = &InvocationSource{ - Task: task, - Invocation: m.invocation, - Message: source, - } - } else if m.receipt != nil { - task, err := toCID(m.receipt.Ran().Link()) - if err != nil { - log.Warnw("converting receipt link to CID", "source", source, "link", m.receipt.Ran().Link(), "error", err) - continue - } - entry.Receipt = &ReceiptSource{ - Task: task, - Receipt: m.receipt, - Message: source, - } - } else { - yield(IndexEntry{}, fmt.Errorf("unexpected member with neither invocation nor receipt: %v", m)) - return - } - if !yield(entry, nil) { - return - } - } - } - } -} - -func toCID(l ipld.Link) (cid.Cid, error) { - if c, ok := l.(cidlink.Link); ok { - return c.Cid, nil + for _, rcpt := range message.Receipts() { + entry := IndexEntry{ + Receipt: &ReceiptSource{ + Task: rcpt.Ran(), + Receipt: rcpt, + }, + } + entries = append(entries, entry) } - return cid.Parse(l.String()) + return entries } diff --git a/pkg/store/agent/memory/store.go b/pkg/store/agent/memory/store.go index 0dac671..eea1c7b 100644 --- a/pkg/store/agent/memory/store.go +++ b/pkg/store/agent/memory/store.go @@ -1,51 +1,37 @@ package memory import ( + "bytes" "context" "fmt" "sync" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" "github.com/fil-forge/sprue/pkg/store/agent" + "github.com/fil-forge/ucantone/ipld/codec/dagcbor" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" "github.com/ipfs/go-cid" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/multiformats/go-multihash" ) -type carModel struct { - roots []cid.Cid - blocks []ipld.Block -} - -type indexModel struct { - // the root CID of the invocation or receipt - root cid.Cid - // the agent message this invocation or receipt was found in - at cid.Cid -} - type Store struct { mutex sync.RWMutex - // agent message CID -> carModel - store map[cid.Cid]carModel - // "///" -> list of invocation/receipt roots found in that message - index map[string][]indexModel + // agent message CID -> ucan.Container + store map[cid.Cid]ucan.Container + // "///" -> list of agent messages invocation/receipt can be found in + index map[string][]cid.Cid } var _ agent.Store = (*Store)(nil) func New() *Store { return &Store{ - store: map[cid.Cid]carModel{}, - index: map[string][]indexModel{}, + store: map[cid.Cid]ucan.Container{}, + index: map[string][]cid.Cid{}, } } -func (s *Store) GetInvocation(ctx context.Context, task cid.Cid) (invocation.Invocation, error) { +func (s *Store) GetInvocation(ctx context.Context, task cid.Cid) (ucan.Invocation, error) { s.mutex.RLock() defer s.mutex.RUnlock() @@ -54,16 +40,16 @@ func (s *Store) GetInvocation(ctx context.Context, task cid.Cid) (invocation.Inv if !ok || len(records) == 0 { return nil, agent.ErrInvocationNotFound } - archive := s.store[records[0].at] - root := cidlink.Link{Cid: records[0].root} - bs, err := blockstore.NewBlockStore(blockstore.WithBlocks(archive.blocks)) - if err != nil { - return nil, fmt.Errorf("creating blockstore: %w", err) + ct := s.store[records[0]] + for _, inv := range ct.Invocations() { + if inv.Task().Link() == task { + return inv, nil + } } - return invocation.NewInvocationView(root, bs) + return nil, agent.ErrInvocationNotFound } -func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (receipt.AnyReceipt, error) { +func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (ucan.Receipt, error) { s.mutex.RLock() defer s.mutex.RUnlock() key := fmt.Sprintf("/%s/receipt/", task) @@ -71,41 +57,46 @@ func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (receipt.AnyReceip if !ok || len(records) == 0 { return nil, agent.ErrReceiptNotFound } - archive := s.store[records[0].at] - root := cidlink.Link{Cid: records[0].root} - bs, err := blockstore.NewBlockStore(blockstore.WithBlocks(archive.blocks)) - if err != nil { - return nil, fmt.Errorf("creating blockstore: %w", err) + ct := s.store[records[0]] + rcpt, ok := ct.Receipt(task) + if !ok { + return nil, agent.ErrReceiptNotFound } - return receipt.NewAnyReceipt(root, bs) + return rcpt, nil } -func (s *Store) Write(ctx context.Context, message message.AgentMessage, index []agent.IndexEntry, source []byte) error { +func (s *Store) Write(ctx context.Context, message ucan.Container, index []agent.IndexEntry) error { s.mutex.Lock() defer s.mutex.Unlock() - at, err := ipldutil.ToCID(message.Root().Link()) - if err != nil { - return err + c, ok := message.(*container.Container) + if !ok { + c = container.New( + container.WithInvocations(message.Invocations()...), + container.WithReceipts(message.Receipts()...), + container.WithDelegations(message.Delegations()...), + ) + } + + var buf bytes.Buffer + if err := c.MarshalCBOR(&buf); err != nil { + return fmt.Errorf("marshaling agent message to CBOR: %w", err) } - model, err := sourceToCARModel(source) + + at, err := cid.V1Builder{Codec: dagcbor.Code, MhType: multihash.SHA2_256}.Sum(buf.Bytes()) if err != nil { - return fmt.Errorf("converting to CAR model: %w", err) + return fmt.Errorf("hashing agent message: %w", err) } - s.store[at] = model + + s.store[at] = message for _, idx := range index { if idx.Invocation != nil { - root := idx.Invocation.Task - key := fmt.Sprintf("/%s/invocation/", root) - s.index[key] = append(s.index[key], indexModel{root: root, at: at}) + key := fmt.Sprintf("/%s/invocation/", idx.Invocation.Task) + s.index[key] = append(s.index[key], at) } if idx.Receipt != nil { key := fmt.Sprintf("/%s/receipt/", idx.Receipt.Task) - receiptRoot, err := ipldutil.ToCID(idx.Receipt.Receipt.Root().Link()) - if err != nil { - return fmt.Errorf("converting receipt root to CID: %w", err) - } - s.index[key] = append(s.index[key], indexModel{root: receiptRoot, at: at}) + s.index[key] = append(s.index[key], at) } } return nil diff --git a/pkg/store/agent/memory/util.go b/pkg/store/agent/memory/util.go deleted file mode 100644 index 20135cc..0000000 --- a/pkg/store/agent/memory/util.go +++ /dev/null @@ -1,51 +0,0 @@ -package memory - -import ( - "bytes" - "fmt" - "iter" - - "github.com/fil-forge/go-ucanto/core/car" - "github.com/fil-forge/go-ucanto/core/ipld" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" - "github.com/ipfs/go-cid" -) - -func collect[T any](seq iter.Seq2[T, error]) ([]T, error) { - var items []T - for item, err := range seq { - if err != nil { - return nil, err - } - items = append(items, item) - } - return items, nil -} - -func toCARModel(roots []ipld.Link, blocks iter.Seq2[ipld.Block, error]) (carModel, error) { - rts := make([]cid.Cid, 0, len(roots)) - for _, r := range roots { - c, err := ipldutil.ToCID(r) - if err != nil { - return carModel{}, fmt.Errorf("converting root link to CID: %w", err) - } - rts = append(rts, c) - } - bs, err := collect(blocks) - if err != nil { - return carModel{}, err - } - return carModel{rts, bs}, nil -} - -func sourceToCARModel(source []byte) (carModel, error) { - roots, blocks, err := car.Decode(bytes.NewReader(source)) - if err != nil { - return carModel{}, err - } - model, err := toCARModel(roots, blocks) - if err != nil { - return carModel{}, fmt.Errorf("converting to CAR model: %w", err) - } - return model, nil -} diff --git a/pkg/store/blob_registry/aws/store.go b/pkg/store/blob_registry/aws/store.go index 9db4463..81f4f35 100644 --- a/pkg/store/blob_registry/aws/store.go +++ b/pkg/store/blob_registry/aws/store.go @@ -9,9 +9,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - captypes "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/digestutil" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" @@ -19,6 +18,7 @@ import ( "github.com/fil-forge/sprue/pkg/store/metrics" metricsaws "github.com/fil-forge/sprue/pkg/store/metrics/aws" spacediffaws "github.com/fil-forge/sprue/pkg/store/space_diff/aws" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" multihash "github.com/multiformats/go-multihash" ) @@ -127,7 +127,7 @@ func (s *Store) Get(ctx context.Context, space did.DID, digest multihash.Multiha return itemToRecord(out.Item) } -func (s *Store) Register(ctx context.Context, space did.DID, blob captypes.Blob, cause cid.Cid) error { +func (s *Store) Register(ctx context.Context, space did.DID, blob blob.Blob, cause cid.Cid) error { consumers, err := s.collectConsumers(ctx, space) if err != nil { return fmt.Errorf("collecting consumers: %w", err) @@ -350,7 +350,7 @@ func itemToRecord(item map[string]types.AttributeValue) (blobregistry.Record, er return blobregistry.Record{ Space: space, - Blob: captypes.Blob{ + Blob: blob.Blob{ Digest: digest, Size: size, }, diff --git a/pkg/store/blob_registry/blob_registry.go b/pkg/store/blob_registry/blob_registry.go index 6db2259..0558625 100644 --- a/pkg/store/blob_registry/blob_registry.go +++ b/pkg/store/blob_registry/blob_registry.go @@ -4,10 +4,10 @@ import ( "context" "time" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" + "github.com/fil-forge/libforge/capabilities/blob" "github.com/fil-forge/sprue/pkg/store" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" ) @@ -43,7 +43,7 @@ func WithListCursor(cursor string) ListOption { type Record struct { Space did.DID - Blob types.Blob + Blob blob.Blob Cause cid.Cid InsertedAt time.Time } @@ -53,7 +53,7 @@ type Store interface { Get(ctx context.Context, space did.DID, digest multihash.Multihash) (Record, error) // Adds an item into the registry if it does not already exist. May return // [ErrEntryExists] if the blob is already registered in the space. - Register(ctx context.Context, space did.DID, blob types.Blob, cause cid.Cid) error + Register(ctx context.Context, space did.DID, blob blob.Blob, cause cid.Cid) error // List entries in the registry for a given space. List(ctx context.Context, space did.DID, options ...ListOption) (store.Page[Record], error) // Removes an item from the registry if it exists. diff --git a/pkg/store/blob_registry/blob_registry_test.go b/pkg/store/blob_registry/blob_registry_test.go index 373c51f..065eff1 100644 --- a/pkg/store/blob_registry/blob_registry_test.go +++ b/pkg/store/blob_registry/blob_registry_test.go @@ -5,21 +5,18 @@ import ( "runtime" "testing" - captypes "github.com/fil-forge/go-libstoracha/capabilities/types" + "github.com/fil-forge/libforge/capabilities/blob" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" blobregistryaws "github.com/fil-forge/sprue/pkg/store/blob_registry/aws" "github.com/fil-forge/sprue/pkg/store/blob_registry/memory" - blobregistrypostgres "github.com/fil-forge/sprue/pkg/store/blob_registry/postgres" "github.com/fil-forge/sprue/pkg/store/consumer" consumeraws "github.com/fil-forge/sprue/pkg/store/consumer/aws" memoryconsumer "github.com/fil-forge/sprue/pkg/store/consumer/memory" - consumerpostgres "github.com/fil-forge/sprue/pkg/store/consumer/postgres" "github.com/fil-forge/sprue/pkg/store/metrics" metricsaws "github.com/fil-forge/sprue/pkg/store/metrics/aws" memorymetrics "github.com/fil-forge/sprue/pkg/store/metrics/memory" - metricspostgres "github.com/fil-forge/sprue/pkg/store/metrics/postgres" spacediffaws "github.com/fil-forge/sprue/pkg/store/space_diff/aws" memoryspacediff "github.com/fil-forge/sprue/pkg/store/space_diff/memory" "github.com/google/uuid" @@ -130,9 +127,9 @@ func createAWSStores(t *testing.T) storeBundle { } // randomBlob returns a blob with a random digest and the given size. -func randomBlob(t *testing.T, size uint64) captypes.Blob { +func randomBlob(t *testing.T, size uint64) blob.Blob { t.Helper() - return captypes.Blob{Digest: testutil.RandomMultihash(t), Size: size} + return blob.Blob{Digest: testutil.RandomMultihash(t), Size: size} } func TestBlobRegistryStore(t *testing.T) { diff --git a/pkg/store/blob_registry/memory/store.go b/pkg/store/blob_registry/memory/store.go index e957a6b..e4e6014 100644 --- a/pkg/store/blob_registry/memory/store.go +++ b/pkg/store/blob_registry/memory/store.go @@ -7,13 +7,13 @@ import ( "sync" "time" - "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/libforge/capabilities/blob" "github.com/fil-forge/sprue/pkg/store" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/sprue/pkg/store/consumer" "github.com/fil-forge/sprue/pkg/store/metrics" spacediff "github.com/fil-forge/sprue/pkg/store/space_diff" + "github.com/fil-forge/ucantone/did" cid "github.com/ipfs/go-cid" multihash "github.com/multiformats/go-multihash" ) @@ -128,7 +128,7 @@ func (s *Store) List(ctx context.Context, space did.DID, options ...blobregistry return store.Page[blobregistry.Record]{Results: results, Cursor: cursor}, nil } -func (s *Store) Register(ctx context.Context, space did.DID, blob types.Blob, cause cid.Cid) error { +func (s *Store) Register(ctx context.Context, space did.DID, blob blob.Blob, cause cid.Cid) error { s.mutex.Lock() defer s.mutex.Unlock() diff --git a/pkg/store/consumer/aws/store.go b/pkg/store/consumer/aws/store.go index 66a8ab4..3078df8 100644 --- a/pkg/store/consumer/aws/store.go +++ b/pkg/store/consumer/aws/store.go @@ -10,9 +10,9 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/consumer" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/consumer/consumer.go b/pkg/store/consumer/consumer.go index 60afeac..c190543 100644 --- a/pkg/store/consumer/consumer.go +++ b/pkg/store/consumer/consumer.go @@ -3,9 +3,9 @@ package consumer import ( "context" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/consumer/consumer_test.go b/pkg/store/consumer/consumer_test.go index eaf3865..7c20f13 100644 --- a/pkg/store/consumer/consumer_test.go +++ b/pkg/store/consumer/consumer_test.go @@ -10,7 +10,6 @@ import ( "github.com/fil-forge/sprue/pkg/store/consumer" consumeraws "github.com/fil-forge/sprue/pkg/store/consumer/aws" "github.com/fil-forge/sprue/pkg/store/consumer/memory" - consumerpostgres "github.com/fil-forge/sprue/pkg/store/consumer/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) diff --git a/pkg/store/consumer/memory/store.go b/pkg/store/consumer/memory/store.go index 7e13962..9b500c7 100644 --- a/pkg/store/consumer/memory/store.go +++ b/pkg/store/consumer/memory/store.go @@ -8,9 +8,9 @@ import ( "strings" "sync" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/consumer" + "github.com/fil-forge/ucantone/did" cid "github.com/ipfs/go-cid" ) diff --git a/pkg/store/customer/aws/store.go b/pkg/store/customer/aws/store.go index 41470bb..5272ccd 100644 --- a/pkg/store/customer/aws/store.go +++ b/pkg/store/customer/aws/store.go @@ -10,10 +10,10 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/customer" + "github.com/fil-forge/ucantone/did" ) var DynamoCustomerTableProps = struct { diff --git a/pkg/store/customer/customer.go b/pkg/store/customer/customer.go index 8656ed7..629c702 100644 --- a/pkg/store/customer/customer.go +++ b/pkg/store/customer/customer.go @@ -4,9 +4,9 @@ import ( "context" "time" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" ) const ( diff --git a/pkg/store/customer/customer_test.go b/pkg/store/customer/customer_test.go index 2432b5e..ba13673 100644 --- a/pkg/store/customer/customer_test.go +++ b/pkg/store/customer/customer_test.go @@ -10,7 +10,6 @@ import ( "github.com/fil-forge/sprue/pkg/store/customer" customeraws "github.com/fil-forge/sprue/pkg/store/customer/aws" "github.com/fil-forge/sprue/pkg/store/customer/memory" - customerpostgres "github.com/fil-forge/sprue/pkg/store/customer/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) diff --git a/pkg/store/customer/memory/memory.go b/pkg/store/customer/memory/memory.go index f2ce659..8222b53 100644 --- a/pkg/store/customer/memory/memory.go +++ b/pkg/store/customer/memory/memory.go @@ -7,9 +7,9 @@ import ( "sync" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/customer" + "github.com/fil-forge/ucantone/did" ) type Store struct { diff --git a/pkg/store/delegation/aws/store.go b/pkg/store/delegation/aws/store.go index 3c75e91..b97d750 100644 --- a/pkg/store/delegation/aws/store.go +++ b/pkg/store/delegation/aws/store.go @@ -11,11 +11,13 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store" dlgstore "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" ) @@ -103,16 +105,27 @@ func (s *Store) Initialize(ctx context.Context) error { return nil } -func (s *Store) PutMany(ctx context.Context, delegations []delegation.Delegation, cause cid.Cid) error { +func (s *Store) PutMany(ctx context.Context, tokens []ucan.Token, cause cid.Cid) error { now := time.Now().UTC().Format(timeutil.SimplifiedISO8601) - for _, dlg := range delegations { - link := dlg.Root().Link().String() - - // Archive the delegation to a CAR and store in S3. - body, err := io.ReadAll(dlg.Archive()) - if err != nil { - return fmt.Errorf("archiving delegation %s: %w", link, err) + for _, token := range tokens { + link := token.Link().String() + + var body []byte + var err error + if dlg, ok := token.(ucan.Delegation); ok { + body, err = delegation.Encode(dlg) + if err != nil { + return fmt.Errorf("encoding delegation %s: %w", link, err) + } + } else if inv, ok := token.(ucan.Invocation); ok { + body, err = invocation.Encode(inv) + if err != nil { + return fmt.Errorf("encoding invocation %s: %w", link, err) + } + } else { + return fmt.Errorf("unsupported token type: %T", token) } + if _, err := s.s3.PutObject(ctx, &s3.PutObjectInput{ Bucket: &s.bucketName, Key: aws.String(link), @@ -121,18 +134,26 @@ func (s *Store) PutMany(ctx context.Context, delegations []delegation.Delegation return fmt.Errorf("storing delegation %s in S3: %w", link, err) } + var aud did.DID + // audience may be nil if the token is an invocation + if token.Audience() != nil { + aud = token.Audience().DID() + } else { + aud = token.Subject().DID() + } + // Write the index entry to DynamoDB. item := map[string]types.AttributeValue{ "link": &types.AttributeValueMemberS{Value: link}, - "audience": &types.AttributeValueMemberS{Value: dlg.Audience().DID().String()}, - "issuer": &types.AttributeValueMemberS{Value: dlg.Issuer().DID().String()}, + "audience": &types.AttributeValueMemberS{Value: aud.String()}, + "issuer": &types.AttributeValueMemberS{Value: token.Issuer().DID().String()}, "insertedAt": &types.AttributeValueMemberS{Value: now}, "updatedAt": &types.AttributeValueMemberS{Value: now}, } if cause != cid.Undef { item["cause"] = &types.AttributeValueMemberS{Value: cause.String()} } - if exp := dlg.Expiration(); exp != nil { + if exp := token.Expiration(); exp != nil { item["expiration"] = &types.AttributeValueMemberN{Value: fmt.Sprintf("%d", *exp)} } @@ -146,7 +167,7 @@ func (s *Store) PutMany(ctx context.Context, delegations []delegation.Delegation return nil } -func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options ...dlgstore.ListByAudienceOption) (store.Page[delegation.Delegation], error) { +func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options ...dlgstore.ListByAudienceOption) (store.Page[ucan.Token], error) { cfg := dlgstore.ListByAudienceConfig{} for _, opt := range options { opt(&cfg) @@ -175,18 +196,18 @@ func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options .. out, err := s.dynamo.Query(ctx, input) if err != nil { - return store.Page[delegation.Delegation]{}, fmt.Errorf("querying delegations by audience: %w", err) + return store.Page[ucan.Token]{}, fmt.Errorf("querying delegations by audience: %w", err) } - results := make([]delegation.Delegation, 0, len(out.Items)) + results := make([]ucan.Token, 0, len(out.Items)) for _, item := range out.Items { linkAttr, ok := item["link"].(*types.AttributeValueMemberS) if !ok { - return store.Page[delegation.Delegation]{}, fmt.Errorf("missing or invalid link attribute in DynamoDB item") + return store.Page[ucan.Token]{}, fmt.Errorf("missing or invalid link attribute in DynamoDB item") } - dlg, err := s.fetchDelegation(ctx, linkAttr.Value) + dlg, err := s.fetchToken(ctx, linkAttr.Value) if err != nil { - return store.Page[delegation.Delegation]{}, fmt.Errorf("fetching delegation %s: %w", linkAttr.Value, err) + return store.Page[ucan.Token]{}, fmt.Errorf("fetching delegation %s: %w", linkAttr.Value, err) } results = append(results, dlg) } @@ -198,11 +219,12 @@ func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options .. } } - return store.Page[delegation.Delegation]{Results: results, Cursor: cursor}, nil + return store.Page[ucan.Token]{Results: results, Cursor: cursor}, nil } -// fetchDelegation retrieves and decodes a delegation from S3 by its link CID string. -func (s *Store) fetchDelegation(ctx context.Context, link string) (delegation.Delegation, error) { +// fetchToken retrieves and decodes a delegation/invocation from S3 by its link +// CID string. +func (s *Store) fetchToken(ctx context.Context, link string) (ucan.Token, error) { out, err := s.s3.GetObject(ctx, &s3.GetObjectInput{ Bucket: &s.bucketName, Key: aws.String(link), @@ -212,13 +234,18 @@ func (s *Store) fetchDelegation(ctx context.Context, link string) (delegation.De } defer out.Body.Close() - data, err := io.ReadAll(out.Body) + body, err := io.ReadAll(out.Body) if err != nil { - return nil, fmt.Errorf("reading delegation from S3: %w", err) + return nil, fmt.Errorf("reading delegation body from S3: %w", err) } - dlg, err := delegation.Extract(data) + + inv, err := invocation.Decode(body) if err != nil { - return nil, fmt.Errorf("extracting delegation: %w", err) + dlg, err := delegation.Decode(body) + if err != nil { + return nil, fmt.Errorf("decoding token: %w", err) + } + return dlg, nil } - return dlg, nil + return inv, nil } diff --git a/pkg/store/delegation/delegation.go b/pkg/store/delegation/delegation.go index 180a776..55eddf9 100644 --- a/pkg/store/delegation/delegation.go +++ b/pkg/store/delegation/delegation.go @@ -3,9 +3,9 @@ package delegation import ( "context" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/ucan" "github.com/ipfs/go-cid" ) @@ -28,6 +28,6 @@ type Store interface { // Implementations MAY choose to avoid storing delegations as long as they can // reliably retrieve the invocation by CID when they need to return the given // delegations. - PutMany(ctx context.Context, delegations []delegation.Delegation, cause cid.Cid) error - ListByAudience(ctx context.Context, audience did.DID, options ...ListByAudienceOption) (store.Page[delegation.Delegation], error) + PutMany(ctx context.Context, tokens []ucan.Token, cause cid.Cid) error + ListByAudience(ctx context.Context, audience did.DID, options ...ListByAudienceOption) (store.Page[ucan.Token], error) } diff --git a/pkg/store/delegation/delegation_test.go b/pkg/store/delegation/delegation_test.go index 4318410..944b6ff 100644 --- a/pkg/store/delegation/delegation_test.go +++ b/pkg/store/delegation/delegation_test.go @@ -5,14 +5,13 @@ import ( "runtime" "testing" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store" dlgstore "github.com/fil-forge/sprue/pkg/store/delegation" delegationaws "github.com/fil-forge/sprue/pkg/store/delegation/aws" "github.com/fil-forge/sprue/pkg/store/delegation/memory" - delegationpostgres "github.com/fil-forge/sprue/pkg/store/delegation/postgres" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/delegation" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -83,16 +82,13 @@ func createAWSStore(t *testing.T) dlgstore.Store { } // makeDelegation creates a delegation from Alice to the given audience. -// A random nonce is included so each delegation has a unique CID. -func makeDelegation(t *testing.T, audience ucan.Principal) delegation.Delegation { +func makeDelegation(t *testing.T, audience ucan.Principal) ucan.Delegation { t.Helper() dlg, err := delegation.Delegate( testutil.Alice, audience, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("test/delegate", testutil.Alice.DID().String(), ucan.NoCaveats{}), - }, - delegation.WithNonce(uuid.NewString()), + testutil.Alice, + "/test/delegate", ) require.NoError(t, err) return dlg @@ -108,12 +104,12 @@ func TestDelegationStore(t *testing.T) { dlg := makeDelegation(t, audience) cause := testutil.RandomCID(t) - require.NoError(t, s.PutMany(t.Context(), []delegation.Delegation{dlg}, cause)) + require.NoError(t, s.PutMany(t.Context(), []ucan.Token{dlg}, cause)) page, err := s.ListByAudience(t.Context(), audience.DID()) require.NoError(t, err) require.Len(t, page.Results, 1) - require.Equal(t, dlg.Root().Link().String(), page.Results[0].Root().Link().String()) + require.Equal(t, dlg.Link().String(), page.Results[0].Link().String()) }) t.Run("ListByAudience returns empty page for unknown audience", func(t *testing.T) { @@ -131,7 +127,7 @@ func TestDelegationStore(t *testing.T) { dlg2 := makeDelegation(t, audience) cause := testutil.RandomCID(t) - require.NoError(t, s.PutMany(t.Context(), []delegation.Delegation{dlg1, dlg2}, cause)) + require.NoError(t, s.PutMany(t.Context(), []ucan.Token{dlg1, dlg2}, cause)) page, err := s.ListByAudience(t.Context(), audience.DID()) require.NoError(t, err) @@ -145,17 +141,17 @@ func TestDelegationStore(t *testing.T) { dlg2 := makeDelegation(t, aud2) cause := testutil.RandomCID(t) - require.NoError(t, s.PutMany(t.Context(), []delegation.Delegation{dlg1, dlg2}, cause)) + require.NoError(t, s.PutMany(t.Context(), []ucan.Token{dlg1, dlg2}, cause)) page1, err := s.ListByAudience(t.Context(), aud1.DID()) require.NoError(t, err) require.Len(t, page1.Results, 1) - require.Equal(t, dlg1.Root().Link().String(), page1.Results[0].Root().Link().String()) + require.Equal(t, dlg1.Link().String(), page1.Results[0].Link().String()) page2, err := s.ListByAudience(t.Context(), aud2.DID()) require.NoError(t, err) require.Len(t, page2.Results, 1) - require.Equal(t, dlg2.Root().Link().String(), page2.Results[0].Root().Link().String()) + require.Equal(t, dlg2.Link().String(), page2.Results[0].Link().String()) }) t.Run("ListByAudience isolates delegations by audience", func(t *testing.T) { @@ -164,9 +160,9 @@ func TestDelegationStore(t *testing.T) { cause := testutil.RandomCID(t) for range 3 { - require.NoError(t, s.PutMany(t.Context(), []delegation.Delegation{makeDelegation(t, aud1)}, cause)) + require.NoError(t, s.PutMany(t.Context(), []ucan.Token{makeDelegation(t, aud1)}, cause)) } - require.NoError(t, s.PutMany(t.Context(), []delegation.Delegation{makeDelegation(t, aud2)}, cause)) + require.NoError(t, s.PutMany(t.Context(), []ucan.Token{makeDelegation(t, aud2)}, cause)) page, err := s.ListByAudience(t.Context(), aud1.DID()) require.NoError(t, err) @@ -178,10 +174,10 @@ func TestDelegationStore(t *testing.T) { cause := testutil.RandomCID(t) for range 5 { - require.NoError(t, s.PutMany(t.Context(), []delegation.Delegation{makeDelegation(t, audience)}, cause)) + require.NoError(t, s.PutMany(t.Context(), []ucan.Token{makeDelegation(t, audience)}, cause)) } - all, err := store.Collect(t.Context(), func(ctx context.Context, opts store.PaginationConfig) (store.Page[delegation.Delegation], error) { + all, err := store.Collect(t.Context(), func(ctx context.Context, opts store.PaginationConfig) (store.Page[ucan.Token], error) { var listOpts []dlgstore.ListByAudienceOption if opts.Cursor != nil { listOpts = append(listOpts, dlgstore.WithListByAudienceCursor(*opts.Cursor)) diff --git a/pkg/store/delegation/memory/store.go b/pkg/store/delegation/memory/store.go index 15b0b8d..5d67f32 100644 --- a/pkg/store/delegation/memory/store.go +++ b/pkg/store/delegation/memory/store.go @@ -6,27 +6,27 @@ import ( "slices" "sync" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" dlgstore "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/ucan" cid "github.com/ipfs/go-cid" ) type Store struct { - mutex sync.RWMutex - delegations map[did.DID][]delegation.Delegation + mutex sync.RWMutex + tokens map[did.DID][]ucan.Token } var _ dlgstore.Store = (*Store)(nil) func New() *Store { return &Store{ - delegations: map[did.DID][]delegation.Delegation{}, + tokens: map[did.DID][]ucan.Token{}, } } -func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options ...dlgstore.ListByAudienceOption) (store.Page[delegation.Delegation], error) { +func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options ...dlgstore.ListByAudienceOption) (store.Page[ucan.Token], error) { s.mutex.RLock() defer s.mutex.RUnlock() @@ -35,39 +35,45 @@ func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options .. for _, opt := range options { opt(&cfg) } - delegations := slices.Clone(s.delegations[audience]) + tokens := slices.Clone(s.tokens[audience]) if cfg.Cursor != nil { - for i, d := range delegations { - if d.Root().Link().String() == *cfg.Cursor { - if i+1 < len(delegations) { - delegations = delegations[i+1:] + for i, d := range tokens { + if d.Link().String() == *cfg.Cursor { + if i+1 < len(tokens) { + tokens = tokens[i+1:] } break } } } var cursor *string - if cfg.Limit != nil && len(delegations) > *cfg.Limit { - delegations = delegations[:*cfg.Limit] - last := delegations[len(delegations)-1].Root().Link().String() + if cfg.Limit != nil && len(tokens) > *cfg.Limit { + tokens = tokens[:*cfg.Limit] + last := tokens[len(tokens)-1].Link().String() cursor = &last } - return store.Page[delegation.Delegation]{ + return store.Page[ucan.Token]{ Cursor: cursor, - Results: delegations, + Results: tokens, }, nil } -func (s *Store) PutMany(ctx context.Context, delegations []delegation.Delegation, cause cid.Cid) error { +func (s *Store) PutMany(ctx context.Context, tokens []ucan.Token, cause cid.Cid) error { s.mutex.Lock() defer s.mutex.Unlock() - for _, d := range delegations { - aud := d.Audience().DID() - s.delegations[aud] = append(s.delegations[aud], d) - slices.SortFunc(s.delegations[aud], func(a, b delegation.Delegation) int { - return bytes.Compare(a.Root().Bytes(), b.Root().Bytes()) + for _, d := range tokens { + var aud did.DID + // audience may be nil if the token is an invocation + if d.Audience() != nil { + aud = d.Audience().DID() + } else { + aud = d.Subject().DID() + } + s.tokens[aud] = append(s.tokens[aud], d) + slices.SortFunc(s.tokens[aud], func(a, b ucan.Token) int { + return bytes.Compare(a.Link().Bytes(), b.Link().Bytes()) }) } return nil diff --git a/pkg/store/metrics/aws/store.go b/pkg/store/metrics/aws/store.go index 3d486a4..96ed43a 100644 --- a/pkg/store/metrics/aws/store.go +++ b/pkg/store/metrics/aws/store.go @@ -8,8 +8,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store/metrics" + "github.com/fil-forge/ucantone/did" ) var DynamoMetricsTableProps = struct { diff --git a/pkg/store/metrics/memory/store.go b/pkg/store/metrics/memory/store.go index 71ecc00..a767415 100644 --- a/pkg/store/metrics/memory/store.go +++ b/pkg/store/metrics/memory/store.go @@ -4,8 +4,8 @@ import ( "context" "sync" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store/metrics" + "github.com/fil-forge/ucantone/did" ) type Store struct { diff --git a/pkg/store/metrics/metrics.go b/pkg/store/metrics/metrics.go index 4974489..b7adf4b 100644 --- a/pkg/store/metrics/metrics.go +++ b/pkg/store/metrics/metrics.go @@ -3,19 +3,19 @@ package metrics import ( "context" - "github.com/fil-forge/go-libstoracha/capabilities/space/blob" - "github.com/fil-forge/go-libstoracha/capabilities/upload" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/capabilities/upload" + "github.com/fil-forge/ucantone/did" ) -const BlobAddTotalMetric = blob.AddAbility + "-total" -const BlobAddSizeTotalMetric = blob.AddAbility + "-size-total" +const BlobAddTotalMetric = blob.AddCommand + "-total" +const BlobAddSizeTotalMetric = blob.AddCommand + "-size-total" -const BlobRemoveTotalMetric = blob.RemoveAbility + "-total" -const BlobRemoveSizeTotalMetric = blob.RemoveAbility + "-size-total" +const BlobRemoveTotalMetric = blob.RemoveCommand + "-total" +const BlobRemoveSizeTotalMetric = blob.RemoveCommand + "-size-total" -const UploadAddTotalMetric = upload.AddAbility + "-total" -const UploadRemoveTotalMetric = upload.RemoveAbility + "-total" +const UploadAddTotalMetric = upload.AddCommand + "-total" +const UploadRemoveTotalMetric = upload.RemoveCommand + "-total" type Store interface { // Get all metrics from storage. diff --git a/pkg/store/metrics/metrics_test.go b/pkg/store/metrics/metrics_test.go index a60e7b9..cdec872 100644 --- a/pkg/store/metrics/metrics_test.go +++ b/pkg/store/metrics/metrics_test.go @@ -8,7 +8,6 @@ import ( "github.com/fil-forge/sprue/pkg/store/metrics" metricsaws "github.com/fil-forge/sprue/pkg/store/metrics/aws" "github.com/fil-forge/sprue/pkg/store/metrics/memory" - metricspostgres "github.com/fil-forge/sprue/pkg/store/metrics/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) diff --git a/pkg/store/replica/aws/store.go b/pkg/store/replica/aws/store.go index c4a08b5..920c981 100644 --- a/pkg/store/replica/aws/store.go +++ b/pkg/store/replica/aws/store.go @@ -9,10 +9,10 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/libforge/digestutil" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store/replica" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" ) diff --git a/pkg/store/replica/memory/store.go b/pkg/store/replica/memory/store.go index 5e94bdd..b87fdd8 100644 --- a/pkg/store/replica/memory/store.go +++ b/pkg/store/replica/memory/store.go @@ -8,9 +8,9 @@ import ( "sync" "time" - "github.com/fil-forge/go-libstoracha/bytemap" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/libforge/bytemap" "github.com/fil-forge/sprue/pkg/store/replica" + "github.com/fil-forge/ucantone/did" cid "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" ) diff --git a/pkg/store/replica/replica.go b/pkg/store/replica/replica.go index 07151ba..a808b70 100644 --- a/pkg/store/replica/replica.go +++ b/pkg/store/replica/replica.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" ) diff --git a/pkg/store/replica/replica_test.go b/pkg/store/replica/replica_test.go index d1e0732..f180a1e 100644 --- a/pkg/store/replica/replica_test.go +++ b/pkg/store/replica/replica_test.go @@ -8,7 +8,6 @@ import ( "github.com/fil-forge/sprue/pkg/store/replica" replicaaws "github.com/fil-forge/sprue/pkg/store/replica/aws" "github.com/fil-forge/sprue/pkg/store/replica/memory" - replicapostgres "github.com/fil-forge/sprue/pkg/store/replica/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) diff --git a/pkg/store/revocation/aws/store.go b/pkg/store/revocation/aws/store.go index 08c5bd4..e8d8df7 100644 --- a/pkg/store/revocation/aws/store.go +++ b/pkg/store/revocation/aws/store.go @@ -8,9 +8,9 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store/revocation" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/revocation/memory/store.go b/pkg/store/revocation/memory/store.go index 79e71db..f8cafd9 100644 --- a/pkg/store/revocation/memory/store.go +++ b/pkg/store/revocation/memory/store.go @@ -5,8 +5,8 @@ import ( "maps" "sync" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store/revocation" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/revocation/revocation.go b/pkg/store/revocation/revocation.go index eebdf90..c59e2f0 100644 --- a/pkg/store/revocation/revocation.go +++ b/pkg/store/revocation/revocation.go @@ -3,7 +3,7 @@ package revocation import ( "context" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/revocation/revocation_test.go b/pkg/store/revocation/revocation_test.go index 2c345f5..a9e2550 100644 --- a/pkg/store/revocation/revocation_test.go +++ b/pkg/store/revocation/revocation_test.go @@ -8,7 +8,6 @@ import ( "github.com/fil-forge/sprue/pkg/store/revocation" revocationaws "github.com/fil-forge/sprue/pkg/store/revocation/aws" "github.com/fil-forge/sprue/pkg/store/revocation/memory" - revocationpostgres "github.com/fil-forge/sprue/pkg/store/revocation/postgres" "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" diff --git a/pkg/store/space_diff/aws/store.go b/pkg/store/space_diff/aws/store.go index e1995ba..7cb1181 100644 --- a/pkg/store/space_diff/aws/store.go +++ b/pkg/store/space_diff/aws/store.go @@ -9,10 +9,10 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store" spacediff "github.com/fil-forge/sprue/pkg/store/space_diff" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/space_diff/memory/store.go b/pkg/store/space_diff/memory/store.go index b98b70d..19475ad 100644 --- a/pkg/store/space_diff/memory/store.go +++ b/pkg/store/space_diff/memory/store.go @@ -7,10 +7,10 @@ import ( "sync" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store" spacediff "github.com/fil-forge/sprue/pkg/store/space_diff" + "github.com/fil-forge/ucantone/did" cid "github.com/ipfs/go-cid" ) diff --git a/pkg/store/space_diff/space_diff.go b/pkg/store/space_diff/space_diff.go index a1d88f5..ade7af2 100644 --- a/pkg/store/space_diff/space_diff.go +++ b/pkg/store/space_diff/space_diff.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/space_diff/space_diff_test.go b/pkg/store/space_diff/space_diff_test.go index 3590d8a..53b7d2e 100644 --- a/pkg/store/space_diff/space_diff_test.go +++ b/pkg/store/space_diff/space_diff_test.go @@ -11,7 +11,6 @@ import ( spacediff "github.com/fil-forge/sprue/pkg/store/space_diff" spacediffaws "github.com/fil-forge/sprue/pkg/store/space_diff/aws" "github.com/fil-forge/sprue/pkg/store/space_diff/memory" - spacediffpostgres "github.com/fil-forge/sprue/pkg/store/space_diff/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) diff --git a/pkg/store/storage_provider/aws/store.go b/pkg/store/storage_provider/aws/store.go index e3dfbda..d3faf42 100644 --- a/pkg/store/storage_provider/aws/store.go +++ b/pkg/store/storage_provider/aws/store.go @@ -11,11 +11,10 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/did" ) var DynamoStorageProviderTableProps = struct { @@ -65,16 +64,11 @@ func (s *Store) Initialize(ctx context.Context) error { return nil } -func (s *Store) Put(ctx context.Context, endpoint url.URL, proof delegation.Delegation, weight int, replicationWeight *int) error { - proofStr, err := delegation.Format(proof) - if err != nil { - return fmt.Errorf("formatting proof: %w", err) - } - +func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight int, replicationWeight *int) error { now := time.Now().UTC().Format(timeutil.SimplifiedISO8601) input := dynamodb.UpdateItemInput{ TableName: aws.String(s.tableName), - Key: map[string]types.AttributeValue{"provider": &types.AttributeValueMemberS{Value: proof.Issuer().DID().String()}}, + Key: map[string]types.AttributeValue{"provider": &types.AttributeValueMemberS{Value: id.String()}}, UpdateExpression: aws.String( "SET #endpoint = :endpoint, #proof = :proof, #weight = :weight, #replicationWeight = :replicationWeight, #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now", ), @@ -87,7 +81,6 @@ func (s *Store) Put(ctx context.Context, endpoint url.URL, proof delegation.Dele }, ExpressionAttributeValues: map[string]types.AttributeValue{ ":endpoint": &types.AttributeValueMemberS{Value: endpoint.String()}, - ":proof": &types.AttributeValueMemberS{Value: proofStr}, ":weight": &types.AttributeValueMemberN{Value: strconv.Itoa(weight)}, ":now": &types.AttributeValueMemberS{Value: now}, }, @@ -97,7 +90,7 @@ func (s *Store) Put(ctx context.Context, endpoint url.URL, proof delegation.Dele input.ExpressionAttributeValues[":replicationWeight"] = &types.AttributeValueMemberN{Value: strconv.Itoa(*replicationWeight)} } - _, err = s.dynamo.UpdateItem(ctx, &input) + _, err := s.dynamo.UpdateItem(ctx, &input) if err != nil { return fmt.Errorf("storing storage provider: %w", err) } @@ -199,15 +192,6 @@ func itemToRecord(item map[string]types.AttributeValue) (storageprovider.Record, return storageprovider.Record{}, fmt.Errorf("parsing endpoint URL: %w", err) } - proofAttr, ok := item["proof"].(*types.AttributeValueMemberS) - if !ok { - return storageprovider.Record{}, fmt.Errorf("missing or invalid proof attribute") - } - proof, err := delegation.Parse(proofAttr.Value) - if err != nil { - return storageprovider.Record{}, fmt.Errorf("parsing proof: %w", err) - } - weightAttr, ok := item["weight"].(*types.AttributeValueMemberN) if !ok { return storageprovider.Record{}, fmt.Errorf("missing or invalid weight attribute") @@ -233,7 +217,6 @@ func itemToRecord(item map[string]types.AttributeValue) (storageprovider.Record, rec := storageprovider.Record{ Provider: providerDID, Endpoint: *endpointURL, - Proof: proof, Weight: weight, ReplicationWeight: replicationWeight, } diff --git a/pkg/store/storage_provider/memory/store.go b/pkg/store/storage_provider/memory/store.go index a9607cd..37dc7b7 100644 --- a/pkg/store/storage_provider/memory/store.go +++ b/pkg/store/storage_provider/memory/store.go @@ -9,10 +9,9 @@ import ( "sync" "time" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/did" ) type Store struct { @@ -85,22 +84,20 @@ func (s *Store) List(ctx context.Context, options ...storageprovider.ListOption) return store.Page[storageprovider.Record]{Results: records, Cursor: cursor}, nil } -func (s *Store) Put(ctx context.Context, endpoint url.URL, proof delegation.Delegation, weight int, replicationWeight *int) error { +func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight int, replicationWeight *int) error { s.mutex.Lock() defer s.mutex.Unlock() - if sp, ok := s.providers[proof.Issuer().DID()]; ok { + if sp, ok := s.providers[id]; ok { sp.Endpoint = endpoint - sp.Proof = proof sp.Weight = weight sp.ReplicationWeight = replicationWeight sp.UpdatedAt = time.Now() - s.providers[proof.Issuer().DID()] = sp + s.providers[id] = sp return nil } - s.providers[proof.Issuer().DID()] = storageprovider.Record{ - Provider: proof.Issuer().DID(), + s.providers[id] = storageprovider.Record{ + Provider: id, Endpoint: endpoint, - Proof: proof, Weight: weight, ReplicationWeight: replicationWeight, InsertedAt: time.Now(), diff --git a/pkg/store/storage_provider/storage_provider.go b/pkg/store/storage_provider/storage_provider.go index 0524047..94771d6 100644 --- a/pkg/store/storage_provider/storage_provider.go +++ b/pkg/store/storage_provider/storage_provider.go @@ -5,10 +5,9 @@ import ( "net/url" "time" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" ) const ( @@ -43,8 +42,6 @@ type Record struct { Provider did.DID // Public URL that accepts UCAN invocations. Endpoint url.URL - // Proof the upload service can invoke blob/allocate and blob/accept. - Proof delegation.Delegation // Weight determines chance of selection for uploads relative to other // providers. Weight int @@ -58,7 +55,7 @@ type Record struct { } type Store interface { - Put(ctx context.Context, endpoint url.URL, proof delegation.Delegation, weight int, replicationWeight *int) error + Put(ctx context.Context, providerID did.DID, endpoint url.URL, weight int, replicationWeight *int) error // Get a storage provider record by provider DID. May return // [ErrStorageProviderNotFound]. Get(ctx context.Context, providerID did.DID) (Record, error) diff --git a/pkg/store/storage_provider/storage_provider_test.go b/pkg/store/storage_provider/storage_provider_test.go index d3c3042..bd1cc35 100644 --- a/pkg/store/storage_provider/storage_provider_test.go +++ b/pkg/store/storage_provider/storage_provider_test.go @@ -6,14 +6,11 @@ import ( "runtime" "testing" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" storageprovideraws "github.com/fil-forge/sprue/pkg/store/storage_provider/aws" "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - storageproviderpostgres "github.com/fil-forge/sprue/pkg/store/storage_provider/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -81,22 +78,6 @@ func randomEndpoint(t *testing.T) url.URL { return *u } -// makeProof creates a delegation from Alice to a random audience. -func makeProof(t *testing.T, issuer ucan.Signer) delegation.Delegation { - t.Helper() - audience := testutil.RandomSigner(t) - dlg, err := delegation.Delegate( - issuer, - audience, - []ucan.Capability[ucan.NoCaveats]{ - ucan.NewCapability("blob/allocate", testutil.Alice.DID().String(), ucan.NoCaveats{}), - }, - delegation.WithNonce(uuid.NewString()), - ) - require.NoError(t, err) - return dlg -} - func TestStorageProviderStore(t *testing.T) { for _, k := range storeKinds { t.Run(string(k), func(t *testing.T) { @@ -105,17 +86,15 @@ func TestStorageProviderStore(t *testing.T) { t.Run("puts and gets a provider", func(t *testing.T) { provider := testutil.Alice endpoint := randomEndpoint(t) - proof := makeProof(t, provider) weight := 10 replWeight := 5 - require.NoError(t, s.Put(t.Context(), endpoint, proof, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight)) rec, err := s.Get(t.Context(), provider.DID()) require.NoError(t, err) require.Equal(t, provider.DID(), rec.Provider) require.Equal(t, endpoint, rec.Endpoint) - require.Equal(t, proof.Root().Link(), rec.Proof.Root().Link()) require.Equal(t, weight, rec.Weight) require.Equal(t, replWeight, *rec.ReplicationWeight) require.False(t, rec.InsertedAt.IsZero()) @@ -125,20 +104,17 @@ func TestStorageProviderStore(t *testing.T) { provider := testutil.Alice endpoint1 := randomEndpoint(t) endpoint2 := randomEndpoint(t) - proof1 := makeProof(t, provider) - proof2 := makeProof(t, provider) weight1 := 10 weight2 := 20 replWeight1 := 5 replWeight2 := 15 - require.NoError(t, s.Put(t.Context(), endpoint1, proof1, weight1, &replWeight1)) - require.NoError(t, s.Put(t.Context(), endpoint2, proof2, weight2, &replWeight2)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint1, weight1, &replWeight1)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint2, weight2, &replWeight2)) rec, err := s.Get(t.Context(), provider.DID()) require.NoError(t, err) require.Equal(t, endpoint2, rec.Endpoint) - require.Equal(t, proof2.Root().Link(), rec.Proof.Root().Link()) require.Equal(t, weight2, rec.Weight) require.Equal(t, replWeight2, *rec.ReplicationWeight) }) @@ -153,11 +129,10 @@ func TestStorageProviderStore(t *testing.T) { t.Run("deletes a provider", func(t *testing.T) { provider := testutil.Alice endpoint := randomEndpoint(t) - proof := makeProof(t, provider) weight := 10 replWeight := 5 - require.NoError(t, s.Put(t.Context(), endpoint, proof, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight)) require.NoError(t, s.Delete(t.Context(), provider.DID())) _, err := s.Get(t.Context(), provider.DID()) @@ -175,13 +150,11 @@ func TestStorageProviderStore(t *testing.T) { provider1 := testutil.Alice provider2 := testutil.Bob endpoint := randomEndpoint(t) - proof1 := makeProof(t, provider1) - proof2 := makeProof(t, provider2) weight := 10 replWeight := 5 - require.NoError(t, s.Put(t.Context(), endpoint, proof1, weight, &replWeight)) - require.NoError(t, s.Put(t.Context(), endpoint, proof2, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider1.DID(), endpoint, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider2.DID(), endpoint, weight, &replWeight)) all, err := store.Collect(t.Context(), func(ctx context.Context, opts store.PaginationConfig) (store.Page[storageprovider.Record], error) { var listOpts []storageprovider.ListOption @@ -204,9 +177,9 @@ func TestStorageProviderStore(t *testing.T) { weight := 10 replWeight := 5 for range 5 { + provider := testutil.RandomDID(t) endpoint := randomEndpoint(t) - proof := makeProof(t, testutil.RandomSigner(t)) - require.NoError(t, s.Put(t.Context(), endpoint, proof, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider, endpoint, weight, &replWeight)) } all, err := store.Collect(t.Context(), func(ctx context.Context, opts store.PaginationConfig) (store.Page[storageprovider.Record], error) { @@ -223,11 +196,10 @@ func TestStorageProviderStore(t *testing.T) { t.Run("deleted provider does not appear in List", func(t *testing.T) { provider := testutil.Alice endpoint := randomEndpoint(t) - proof := makeProof(t, provider) weight := 10 replWeight := 5 - require.NoError(t, s.Put(t.Context(), endpoint, proof, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight)) require.NoError(t, s.Delete(t.Context(), provider.DID())) all, err := store.Collect(t.Context(), func(ctx context.Context, opts store.PaginationConfig) (store.Page[storageprovider.Record], error) { @@ -240,7 +212,7 @@ func TestStorageProviderStore(t *testing.T) { require.NoError(t, err) for _, r := range all { - require.NotEqual(t, provider, r.Provider) + require.NotEqual(t, provider.DID(), r.Provider) } }) }) diff --git a/pkg/store/subscription/aws/store.go b/pkg/store/subscription/aws/store.go index 8eee5af..fcee9b9 100644 --- a/pkg/store/subscription/aws/store.go +++ b/pkg/store/subscription/aws/store.go @@ -9,9 +9,9 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/subscription" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/subscription/memory/store.go b/pkg/store/subscription/memory/store.go index a7bf0b9..9e78cb4 100644 --- a/pkg/store/subscription/memory/store.go +++ b/pkg/store/subscription/memory/store.go @@ -8,9 +8,9 @@ import ( "strings" "sync" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/subscription" + "github.com/fil-forge/ucantone/did" cid "github.com/ipfs/go-cid" ) diff --git a/pkg/store/subscription/subscription.go b/pkg/store/subscription/subscription.go index c89e832..1783c08 100644 --- a/pkg/store/subscription/subscription.go +++ b/pkg/store/subscription/subscription.go @@ -4,9 +4,9 @@ import ( "context" "time" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" "github.com/ipfs/go-cid" ) diff --git a/pkg/store/subscription/subscription_test.go b/pkg/store/subscription/subscription_test.go index edf58af..f700b18 100644 --- a/pkg/store/subscription/subscription_test.go +++ b/pkg/store/subscription/subscription_test.go @@ -5,13 +5,12 @@ import ( "runtime" "testing" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/subscription" subscriptionaws "github.com/fil-forge/sprue/pkg/store/subscription/aws" "github.com/fil-forge/sprue/pkg/store/subscription/memory" - subscriptionpostgres "github.com/fil-forge/sprue/pkg/store/subscription/postgres" + "github.com/fil-forge/ucantone/did" "github.com/google/uuid" "github.com/stretchr/testify/require" ) diff --git a/pkg/store/upload/aws/store.go b/pkg/store/upload/aws/store.go index 9dba61f..e9ebd20 100644 --- a/pkg/store/upload/aws/store.go +++ b/pkg/store/upload/aws/store.go @@ -12,10 +12,10 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/upload" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/ipld/go-ipld-prime/codec/dagcbor" "github.com/ipld/go-ipld-prime/fluent" @@ -336,7 +336,7 @@ func (d *Store) Remove(ctx context.Context, space did.DID, root cid.Cid) error { return nil } -func (d *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, shards []cid.Cid, cause cid.Cid) error { +func (d *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, index *cid.Cid, shards []cid.Cid, cause cid.Cid) error { // Fetch the current item to get existing shards before merging. current, err := d.dynamo.GetItem(ctx, &dynamodb.GetItemInput{ TableName: aws.String(d.tableName), @@ -370,6 +370,7 @@ func (d *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, shards "#insertedAt": "insertedAt", "#updatedAt": "updatedAt", "#cause": "cause", + "#index": "index", "#shardsRef": "shardsRef", "#shards": "shards", } @@ -377,6 +378,9 @@ func (d *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, shards ":now": &types.AttributeValueMemberS{Value: now}, ":cause": &types.AttributeValueMemberS{Value: cause.String()}, } + if index != nil { + exprAttrValues[":index"] = &types.AttributeValueMemberS{Value: index.String()} + } var updateExpr string var newShardsRef string @@ -394,16 +398,28 @@ func (d *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, shards return fmt.Errorf("storing shards in S3: %w", err) } exprAttrValues[":shardsRef"] = &types.AttributeValueMemberS{Value: newShardsRef} - updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause, #shardsRef = :shardsRef REMOVE #shards" + if index != nil { + updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause, #shardsRef = :shardsRef, #index = :index REMOVE #shards" + } else { + updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause, #shardsRef = :shardsRef REMOVE #shards, #index" + } } else if len(merged) > 0 { shardStrs := make([]string, len(merged)) for i, s := range merged { shardStrs[i] = s.String() } exprAttrValues[":shards"] = &types.AttributeValueMemberSS{Value: shardStrs} - updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause, #shards = :shards REMOVE #shardsRef" + if index != nil { + updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause, #shards = :shards, #index = :index REMOVE #shardsRef" + } else { + updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause, #shards = :shards REMOVE #shardsRef, #index" + } } else { - updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause REMOVE #shards, #shardsRef" + if index != nil { + updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause, #index = :index REMOVE #shards, #shardsRef" + } else { + updateExpr = "SET #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now, #cause = :cause REMOVE #shards, #shardsRef, #index" + } } _, err = d.dynamo.UpdateItem(ctx, &dynamodb.UpdateItemInput{ @@ -497,6 +513,19 @@ func itemToRecord(item map[string]types.AttributeValue) (upload.UploadRecord, er return upload.UploadRecord{}, fmt.Errorf("parsing root CID: %w", err) } + var index *cid.Cid + if _, ok := item["index"]; ok { + indexAttr, ok := item["index"].(*types.AttributeValueMemberS) + if !ok { + return upload.UploadRecord{}, fmt.Errorf("missing or invalid index attribute") + } + c, err := cid.Parse(indexAttr.Value) + if err != nil { + return upload.UploadRecord{}, fmt.Errorf("parsing index CID: %w", err) + } + index = &c + } + causeAttr, ok := item["cause"].(*types.AttributeValueMemberS) if !ok { return upload.UploadRecord{}, fmt.Errorf("missing or invalid cause attribute") @@ -522,6 +551,7 @@ func itemToRecord(item map[string]types.AttributeValue) (upload.UploadRecord, er return upload.UploadRecord{ Space: space, Root: root, + Index: index, Cause: cause, InsertedAt: insertedAt, UpdatedAt: updatedAt, diff --git a/pkg/store/upload/memory/store.go b/pkg/store/upload/memory/store.go index 9513518..297d389 100644 --- a/pkg/store/upload/memory/store.go +++ b/pkg/store/upload/memory/store.go @@ -7,9 +7,9 @@ import ( "sync" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/upload" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" ) @@ -146,7 +146,7 @@ func (m *Store) Remove(ctx context.Context, space did.DID, root cid.Cid) error { return nil } -func (m *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, shards []cid.Cid, cause cid.Cid) error { +func (m *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, index *cid.Cid, shards []cid.Cid, cause cid.Cid) error { m.mutex.Lock() defer m.mutex.Unlock() @@ -162,6 +162,7 @@ func (m *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, shards uploads = append(uploads, upload.UploadRecord{ Space: space, Root: root, + Index: index, Cause: cause, InsertedAt: time.Now(), }) @@ -169,6 +170,7 @@ func (m *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, shards } else { uploads[idx].UpdatedAt = time.Now() uploads[idx].Cause = cause + uploads[idx].Index = index } shardsByUpload, ok := m.shards[space] if !ok { diff --git a/pkg/store/upload/upload.go b/pkg/store/upload/upload.go index efc9b9a..db21858 100644 --- a/pkg/store/upload/upload.go +++ b/pkg/store/upload/upload.go @@ -4,9 +4,9 @@ import ( "context" "time" - "github.com/fil-forge/go-ucanto/did" - "github.com/fil-forge/sprue/pkg/lib/errors" "github.com/fil-forge/sprue/pkg/store" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/errors" "github.com/ipfs/go-cid" ) @@ -49,6 +49,7 @@ func WithListShardsCursor(cursor string) ListShardsOption { type UploadRecord struct { Space did.DID Root cid.Cid + Index *cid.Cid Cause cid.Cid InsertedAt time.Time UpdatedAt time.Time @@ -70,5 +71,5 @@ type Store interface { Remove(ctx context.Context, space did.DID, root cid.Cid) error // Inserts an item in the table if it does not already exist or updates an // existing item if it does exist. - Upsert(ctx context.Context, space did.DID, root cid.Cid, shards []cid.Cid, cause cid.Cid) error + Upsert(ctx context.Context, space did.DID, root cid.Cid, index *cid.Cid, shards []cid.Cid, cause cid.Cid) error } diff --git a/pkg/store/upload/upload_test.go b/pkg/store/upload/upload_test.go index 42f8dc6..c040bb3 100644 --- a/pkg/store/upload/upload_test.go +++ b/pkg/store/upload/upload_test.go @@ -5,13 +5,12 @@ import ( "runtime" "testing" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/sprue/pkg/store/upload/aws" "github.com/fil-forge/sprue/pkg/store/upload/memory" - uploadpostgres "github.com/fil-forge/sprue/pkg/store/upload/postgres" + "github.com/fil-forge/ucantone/did" "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" @@ -99,10 +98,11 @@ func TestUploadStore(t *testing.T) { t.Run("adds an upload", func(t *testing.T) { space := testutil.RandomDID(t) root := testutil.RandomCID(t) + index := testutil.RandomCID(t) shards := []cid.Cid{testutil.RandomCID(t), testutil.RandomCID(t)} cause := testutil.RandomCID(t) - err := store.Upsert(t.Context(), space, root, shards, cause) + err := store.Upsert(t.Context(), space, root, &index, shards, cause) require.NoError(t, err) exists, err := store.Exists(t.Context(), space, root) @@ -119,10 +119,11 @@ func TestUploadStore(t *testing.T) { t.Run("lists uploads", func(t *testing.T) { space := testutil.RandomDID(t) roots := []cid.Cid{testutil.RandomCID(t), testutil.RandomCID(t), testutil.RandomCID(t)} + indexes := []cid.Cid{testutil.RandomCID(t), testutil.RandomCID(t), testutil.RandomCID(t)} cause := testutil.RandomCID(t) - for _, root := range roots { - err := store.Upsert(t.Context(), space, root, nil, cause) + for i, root := range roots { + err := store.Upsert(t.Context(), space, root, &indexes[i], nil, cause) require.NoError(t, err) } @@ -147,6 +148,7 @@ func TestUploadStore(t *testing.T) { t.Run("updates an upload", func(t *testing.T) { space := testutil.RandomDID(t) root := testutil.RandomCID(t) + index := testutil.RandomCID(t) cause := testutil.RandomCID(t) initialShards := make([]cid.Cid, 3) @@ -154,7 +156,7 @@ func TestUploadStore(t *testing.T) { initialShards[i] = testutil.RandomCID(t) } - err := store.Upsert(t.Context(), space, root, initialShards, cause) + err := store.Upsert(t.Context(), space, root, nil, initialShards, cause) require.NoError(t, err) // build a second batch of shards that includes one duplicate from the @@ -167,7 +169,7 @@ func TestUploadStore(t *testing.T) { } newCause := testutil.RandomCID(t) - err = store.Upsert(t.Context(), space, root, additionalShards, newCause) + err = store.Upsert(t.Context(), space, root, &index, additionalShards, newCause) require.NoError(t, err) // cause should be updated @@ -185,6 +187,7 @@ func TestUploadStore(t *testing.T) { t.Run("inspects an upload", func(t *testing.T) { root := testutil.RandomCID(t) + index := testutil.RandomCID(t) cause := testutil.RandomCID(t) // inspecting a root not in any space returns empty spaces @@ -195,8 +198,8 @@ func TestUploadStore(t *testing.T) { // upsert the root into two different spaces space1 := testutil.RandomDID(t) space2 := testutil.RandomDID(t) - require.NoError(t, store.Upsert(t.Context(), space1, root, nil, cause)) - require.NoError(t, store.Upsert(t.Context(), space2, root, nil, cause)) + require.NoError(t, store.Upsert(t.Context(), space1, root, &index, nil, cause)) + require.NoError(t, store.Upsert(t.Context(), space2, root, &index, nil, cause)) record, err = store.Inspect(t.Context(), root) require.NoError(t, err) @@ -216,6 +219,7 @@ func TestUploadStore(t *testing.T) { t.Run(tc.name, func(t *testing.T) { space := testutil.RandomDID(t) root := testutil.RandomCID(t) + index := testutil.RandomCID(t) cause := testutil.RandomCID(t) // removing a non-existent upload returns an error @@ -227,7 +231,7 @@ func TestUploadStore(t *testing.T) { shards[i] = testutil.RandomCID(t) } - err = store.Upsert(t.Context(), space, root, shards, cause) + err = store.Upsert(t.Context(), space, root, &index, shards, cause) require.NoError(t, err) err = store.Remove(t.Context(), space, root) @@ -260,6 +264,7 @@ func TestUploadStore(t *testing.T) { t.Run(tc.name, func(t *testing.T) { space := testutil.RandomDID(t) root := testutil.RandomCID(t) + index := testutil.RandomCID(t) cause := testutil.RandomCID(t) shards := make([]cid.Cid, tc.shardCount) @@ -267,7 +272,7 @@ func TestUploadStore(t *testing.T) { shards[i] = testutil.RandomCID(t) } - err := store.Upsert(t.Context(), space, root, shards, cause) + err := store.Upsert(t.Context(), space, root, &index, shards, cause) require.NoError(t, err) // list with a limit of 2 - should return first 2 and a cursor From 31fba55e172fb9b68ac1518a225087c225610583 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 13:54:39 +0100 Subject: [PATCH 02/23] feat: migrate postgres stores --- go.mod | 32 ----- go.sum | 130 ------------------ internal/fx/app.go | 2 + internal/fx/app_test.go | 2 +- .../migrations/sql/00003_storage_provider.sql | 1 - internal/migrations/sql/00011_upload.sql | 1 + internal/migrations/sql/00013_agent_index.sql | 16 ++- pkg/store/agent/agent_test.go | 9 +- pkg/store/agent/aws/store.go | 2 +- pkg/store/agent/postgres/store.go | 130 ++++++++++-------- pkg/store/blob_registry/blob_registry.go | 5 +- pkg/store/blob_registry/blob_registry_test.go | 21 +-- pkg/store/blob_registry/postgres/store.go | 9 +- pkg/store/consumer/consumer_test.go | 5 +- pkg/store/consumer/postgres/store.go | 2 +- pkg/store/customer/customer_test.go | 5 +- pkg/store/customer/postgres/store.go | 2 +- pkg/store/delegation/delegation_test.go | 5 +- pkg/store/delegation/postgres/store.go | 82 +++++++---- pkg/store/metrics/metrics_test.go | 7 +- pkg/store/metrics/postgres/store.go | 2 +- pkg/store/replica/postgres/store.go | 4 +- pkg/store/replica/replica_test.go | 5 +- pkg/store/revocation/postgres/store.go | 2 +- pkg/store/revocation/revocation_test.go | 5 +- pkg/store/space_diff/postgres/store.go | 2 +- pkg/store/space_diff/space_diff_test.go | 5 +- pkg/store/storage_provider/aws/store.go | 3 +- pkg/store/storage_provider/postgres/store.go | 31 ++--- .../storage_provider/storage_provider_test.go | 5 +- pkg/store/subscription/postgres/store.go | 2 +- pkg/store/subscription/subscription_test.go | 5 +- pkg/store/upload/postgres/store.go | 33 +++-- pkg/store/upload/upload_test.go | 19 +-- 34 files changed, 247 insertions(+), 344 deletions(-) diff --git a/go.mod b/go.mod index 3c3a872..d6f6303 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/go-libstoracha v0.0.0-20260507180245-218ac18ff773 - github.com/fil-forge/go-ucanto v0.0.0-20260507172450-5cb5d073f8ab github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b github.com/google/uuid v1.6.0 @@ -71,36 +69,13 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.10.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/filecoin-project/go-data-segment v0.0.1 // indirect - github.com/filecoin-project/go-fil-commcid v0.2.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ipfs/bbloom v0.0.4 // indirect - github.com/ipfs/boxo v0.34.0 // indirect - github.com/ipfs/go-block-format v0.2.3 // indirect - github.com/ipfs/go-blockservice v0.5.2 // indirect - github.com/ipfs/go-datastore v0.9.1 // indirect - github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect - github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect - github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect - github.com/ipfs/go-ipfs-util v0.0.3 // indirect - github.com/ipfs/go-ipld-cbor v0.2.0 // indirect - github.com/ipfs/go-ipld-format v0.6.3 // indirect - github.com/ipfs/go-ipld-legacy v0.2.2 // indirect - github.com/ipfs/go-log v1.0.5 // indirect - github.com/ipfs/go-log/v2 v2.9.1 // indirect - github.com/ipfs/go-merkledag v0.11.0 // indirect - github.com/ipfs/go-metrics-interface v0.3.0 // indirect - github.com/ipfs/go-verifcid v0.0.3 // indirect - github.com/ipld/go-car v0.6.2 // indirect - github.com/ipld/go-codec-dagpb v1.7.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect @@ -127,13 +102,10 @@ require ( github.com/mr-tron/base58 v1.3.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr v0.16.1 // indirect github.com/multiformats/go-multibase v0.3.0 // indirect - github.com/multiformats/go-multicodec v0.10.0 // indirect github.com/multiformats/go-varint v0.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -151,7 +123,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/numcpus v0.11.0 // indirect - github.com/ucan-wg/go-ucan v0.0.0-20240916120445-37f52863156c // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect @@ -165,12 +136,10 @@ require ( go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.50.0 // indirect - golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect @@ -178,7 +147,6 @@ require ( golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect pitr.ca/jsontokenizer v0.3.0 // indirect diff --git a/go.sum b/go.sum index b2ce600..8e9935a 100644 --- a/go.sum +++ b/go.sum @@ -99,10 +99,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 h1:XQTQTF75vnug2TXS8m7CVJfC2nni github.com/aws/aws-sdk-go-v2/service/sts v1.41.8/go.mod h1:Xgx+PR1NUOjNmQY+tRMnouRp83JRM8pRMw/vCaVhPkI= github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= -github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -138,13 +134,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -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= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= @@ -167,20 +159,10 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fil-forge/go-libstoracha v0.0.0-20260507180245-218ac18ff773 h1:MUBpVnBX9Eql6rBV/f6xKDz/Wm0zICsrcIWWGNH5XeM= -github.com/fil-forge/go-libstoracha v0.0.0-20260507180245-218ac18ff773/go.mod h1:zE7vPrAYZpoamvIFOtcZvfrWXRi6jyvS5O4o04+X1HE= -github.com/fil-forge/go-ucanto v0.0.0-20260507172450-5cb5d073f8ab h1:2J2cDThqTKP6/0k3SfdlSxfyPa3aLqjTYnmvbEcryfg= -github.com/fil-forge/go-ucanto v0.0.0-20260507172450-5cb5d073f8ab/go.mod h1:lZF3UXZ2hGLKYmXdquG50JqI9pRlUrV6lubGtgOYfwc= github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f h1:bxGaLAF+kD5ADpX5N68hwBn2Wc+qW4cC9ptCzymQ7xs= github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f/go.mod h1:IdNOBIQeH59dG99FnLmqrwrvaJ4Akm4IPUng8DeqFig= github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b h1:8tvw5e7S1ntUnm0v/OTWLZelbJWQcho/WZI+ttCPv+A= github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= -github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= -github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= -github.com/filecoin-project/go-data-segment v0.0.1 h1:1wmDxOG4ubWQm3ZC1XI5nCon5qgSq7Ra3Rb6Dbu10Gs= -github.com/filecoin-project/go-data-segment v0.0.1/go.mod h1:H0/NKbsRxmRFBcLibmABv+yFNHdmtl5AyplYLnb0Zv4= -github.com/filecoin-project/go-fil-commcid v0.2.0 h1:B+5UX8XGgdg/XsdUpST4pEBviKkFOw+Fvl2bLhSKGpI= -github.com/filecoin-project/go-fil-commcid v0.2.0/go.mod h1:8yigf3JDIil+/WpqR5zoKyP0jBPCOGtEqq/K1CcMy9Q= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -203,7 +185,6 @@ github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaL github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -251,8 +232,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -295,77 +274,18 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 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 v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= -github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -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/boxo v0.34.0 h1:pMP9bAsTs4xVh8R0ZmxIWviV7kjDa60U24QrlGgHb1g= -github.com/ipfs/boxo v0.34.0/go.mod h1:kzdH/ewDybtO3+M8MCVkpwnIIc/d2VISX95DFrY4vQA= -github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= -github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= -github.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHhtFKQk= -github.com/ipfs/go-block-format v0.2.3/go.mod h1:WJaQmPAKhD3LspLixqlqNFxiZ3BZ3xgqxxoSR/76pnA= -github.com/ipfs/go-blockservice v0.5.2 h1:in9Bc+QcXwd1apOVM7Un9t8tixPKdaHQFdLSUM1Xgk8= -github.com/ipfs/go-blockservice v0.5.2/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk= github.com/ipfs/go-cid v0.6.1 h1:T5TnNb08+ueovG76Z5gx1L4Y7QOaGTXHg1F6raWFxIc= github.com/ipfs/go-cid v0.6.1/go.mod h1:zrY0SwOhjrrIdfPQ/kf+k1sXyJ0QE7cMxfCployLBs0= -github.com/ipfs/go-datastore v0.9.1 h1:67Po2epre/o0UxrmkzdS9ZTe2GFGODgTd2odx8Wh6Yo= -github.com/ipfs/go-datastore v0.9.1/go.mod h1:zi07Nvrpq1bQwSkEnx3bfjz+SQZbdbWyCNvyxMh9pN0= -github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= -github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= -github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= -github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= -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-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.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= -github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo= -github.com/ipfs/go-ipfs-exchange-interface v0.2.1 h1:jMzo2VhLKSHbVe+mHNzYgs95n0+t0Q69GQ5WhRDZV/s= -github.com/ipfs/go-ipfs-exchange-interface v0.2.1/go.mod h1:MUsYn6rKbG6CTtsDp+lKJPmVt3ZrCViNyH3rfPGsZ2E= -github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= -github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= -github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE= -github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4= -github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= -github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= -github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= -github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= -github.com/ipfs/go-ipld-cbor v0.2.0 h1:VHIW3HVIjcMd8m4ZLZbrYpwjzqlVUfjLM7oK4T5/YF0= -github.com/ipfs/go-ipld-cbor v0.2.0/go.mod h1:Cp8T7w1NKcu4AQJLqK0tWpd1nkgTxEVB5C6kVpLW6/0= -github.com/ipfs/go-ipld-format v0.6.3 h1:9/lurLDTotJpZSuL++gh3sTdmcFhVkCwsgx2+rAh4j8= -github.com/ipfs/go-ipld-format v0.6.3/go.mod h1:74ilVN12NXVMIV+SrBAyC05UJRk0jVvGqdmrcYZvCBk= -github.com/ipfs/go-ipld-legacy v0.2.2 h1:DThbqCPVLpWBcGtU23KDLiY2YRZZnTkXQyfz8aOfBkQ= -github.com/ipfs/go-ipld-legacy v0.2.2/go.mod h1:hhkj+b3kG9b2BcUNw8IFYAsfeNo8E3U7eYlWeAOPyDU= -github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= -github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= -github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= -github.com/ipfs/go-log/v2 v2.9.1 h1:3JXwHWU31dsCpvQ+7asz6/QsFJHqFr4gLgQ0FWteujk= -github.com/ipfs/go-log/v2 v2.9.1/go.mod h1:evFx7sBiohUN3AG12mXlZBw5hacBQld3ZPHrowlJYoo= -github.com/ipfs/go-merkledag v0.11.0 h1:DgzwK5hprESOzS4O1t/wi6JDpyVQdvm9Bs59N/jqfBY= -github.com/ipfs/go-merkledag v0.11.0/go.mod h1:Q4f/1ezvBiJV0YCIXvt51W/9/kqJGH4I1LsA7+djsM4= -github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU= -github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY= -github.com/ipfs/go-peertaskqueue v0.8.2 h1:PaHFRaVFdxQk1Qo3OKiHPYjmmusQy7gKQUaL8JDszAU= -github.com/ipfs/go-peertaskqueue v0.8.2/go.mod h1:L6QPvou0346c2qPJNiJa6BvOibxDfaiPlqHInmzg0FA= -github.com/ipfs/go-verifcid v0.0.3 h1:gmRKccqhWDocCRkC+a59g5QW7uJw5bpX9HWBevXa0zs= -github.com/ipfs/go-verifcid v0.0.3/go.mod h1:gcCtGniVzelKrbk9ooUSX/pM3xlH73fZZJDzQJRvOUw= -github.com/ipld/go-car v0.6.2 h1:Hlnl3Awgnq8icK+ze3iRghk805lu8YNq3wlREDTF2qc= -github.com/ipld/go-car v0.6.2/go.mod h1:oEGXdwp6bmxJCZ+rARSkDliTeYnVzv3++eXajZ+Bmr8= -github.com/ipld/go-codec-dagpb v1.7.0 h1:hpuvQjCSVSLnTnHXn+QAMR0mLmb1gA6wl10LExo2Ts0= -github.com/ipld/go-codec-dagpb v1.7.0/go.mod h1:rD3Zg+zub9ZnxcLwfol/OTQRVjaLzXypgy4UqHQvilM= github.com/ipld/go-ipld-prime v0.22.0 h1:YJhDhjEOvOYaqshd3b4atIWUoRg/rKrgmwCyUHwlbuY= github.com/ipld/go-ipld-prime v0.22.0/go.mod h1:ol7vKxOOVgEh0iAPuiDalM+0gScXVMA5ZZa4DVrTnEA= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -376,10 +296,6 @@ github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= -github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 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= @@ -393,8 +309,6 @@ github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBF github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= -github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -409,20 +323,6 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -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= -github.com/libp2p/go-libp2p v0.47.0 h1:qQpBjSCWNQFF0hjBbKirMXE9RHLtSuzTDkTfr1rw0yc= -github.com/libp2p/go-libp2p v0.47.0/go.mod h1:s8HPh7mMV933OtXzONaGFseCg/BE//m1V34p3x4EUOY= -github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= -github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= -github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= -github.com/libp2p/go-libp2p-record v0.3.1/go.mod h1:T8itUkLcWQLCYMqtX7Th6r7SexyUJpIyPgks757td/E= -github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= -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-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8= -github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -493,22 +393,14 @@ github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aG github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.16.1 h1:fgJ0Pitow+wWXzN9do+1b8Pyjmo8m5WhGfzpL82MpCw= -github.com/multiformats/go-multiaddr v0.16.1/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0= -github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= -github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.3.0 h1:8helZD2+4Db7NNWFiktk2NePbF0boolBe6bDQvM4r68= github.com/multiformats/go-multibase v0.3.0/go.mod h1:MoBLQPCkRTOL3eveIPO81860j2AQY8JwcnNlRkGRUfI= github.com/multiformats/go-multicodec v0.10.0 h1:UpP223cig/Cx8J76jWt91njpK3GTAO1w02sdcjZDSuc= github.com/multiformats/go-multicodec v0.10.0/go.mod h1:wg88pM+s2kZJEQfRCKBNU+g32F5aWBEjyFHXvZLTcLI= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVOer28DeBBEYdQ= -github.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw= github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI= github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= @@ -519,8 +411,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -538,15 +428,7 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -625,15 +507,11 @@ github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYI github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= -github.com/ucan-wg/go-ucan v0.0.0-20240916120445-37f52863156c h1:A1pMNIlHPnJ6KROqNc6SKg7QlSiQA6umiEoy89Os4cM= -github.com/ucan-wg/go-ucan v0.0.0-20240916120445-37f52863156c/go.mod h1:IiRc1OKWUk7FziOTWmOo7iwbcEMr7ch0lgs3UrF13pU= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s= -github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= @@ -680,22 +558,16 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -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.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= @@ -912,8 +784,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/internal/fx/app.go b/internal/fx/app.go index cb3070d..1b8cb19 100644 --- a/internal/fx/app.go +++ b/internal/fx/app.go @@ -1,6 +1,8 @@ package fx import ( + "fmt" + "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/internal/fx/service" "github.com/fil-forge/sprue/internal/fx/service/handlers" diff --git a/internal/fx/app_test.go b/internal/fx/app_test.go index f077372..e53f168 100644 --- a/internal/fx/app_test.go +++ b/internal/fx/app_test.go @@ -111,7 +111,7 @@ func TestWireApp(t *testing.T) { Port: 0, }, Identity: config.IdentityConfig{ - PrivateKey: testutil.Must(ed25519.Format(testutil.WebService))(t), + PrivateKey: signer.Format(testutil.WebService), ServiceDID: testutil.WebService.DID().String(), }, Indexer: config.IndexerConfig{ diff --git a/internal/migrations/sql/00003_storage_provider.sql b/internal/migrations/sql/00003_storage_provider.sql index 10228d8..eb7cc71 100644 --- a/internal/migrations/sql/00003_storage_provider.sql +++ b/internal/migrations/sql/00003_storage_provider.sql @@ -3,7 +3,6 @@ CREATE TABLE storage_provider ( provider TEXT PRIMARY KEY, endpoint TEXT NOT NULL, - proof TEXT NOT NULL, weight INTEGER NOT NULL, replication_weight INTEGER, inserted_at TIMESTAMPTZ NOT NULL, diff --git a/internal/migrations/sql/00011_upload.sql b/internal/migrations/sql/00011_upload.sql index 160bfbc..ab5f0b9 100644 --- a/internal/migrations/sql/00011_upload.sql +++ b/internal/migrations/sql/00011_upload.sql @@ -3,6 +3,7 @@ CREATE TABLE upload ( space TEXT NOT NULL, root TEXT NOT NULL, + index TEXT, cause TEXT NOT NULL, inserted_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, diff --git a/internal/migrations/sql/00013_agent_index.sql b/internal/migrations/sql/00013_agent_index.sql index 8ee6aa6..4d3c66c 100644 --- a/internal/migrations/sql/00013_agent_index.sql +++ b/internal/migrations/sql/00013_agent_index.sql @@ -1,11 +1,17 @@ -- +goose Up -- +goose StatementBegin --- agent_index stores the "task + kind -> (root CID @ message CID)" mapping. --- "kind" is either "in" (an invocation) or "out" (a receipt). +-- agent_index stores a mapping from tasks to invocations/receipts and the +-- agent messages they were found in. +-- "kind" is the token type either "in" (an invocation) or "out" (a receipt). +-- "task" is CID of the task that was invoked (invocation) or ran (receipt). +-- "token" is the CID of the invocation/receipt. +-- "message" is the CID of the agent message the token can be found within. + CREATE TABLE agent_index ( - task TEXT NOT NULL, - kind TEXT NOT NULL, - identifier TEXT NOT NULL, + task TEXT NOT NULL, + kind TEXT NOT NULL, + token TEXT NOT NULL, + message TEXT NOT NULL, PRIMARY KEY (task, kind) ); -- +goose StatementEnd diff --git a/pkg/store/agent/agent_test.go b/pkg/store/agent/agent_test.go index da1a3eb..efe9966 100644 --- a/pkg/store/agent/agent_test.go +++ b/pkg/store/agent/agent_test.go @@ -8,8 +8,9 @@ import ( ucancap "github.com/fil-forge/libforge/capabilities/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store/agent" - "github.com/fil-forge/sprue/pkg/store/agent/aws" - "github.com/fil-forge/sprue/pkg/store/agent/memory" + agentaws "github.com/fil-forge/sprue/pkg/store/agent/aws" + agentmemory "github.com/fil-forge/sprue/pkg/store/agent/memory" + agentpostgres "github.com/fil-forge/sprue/pkg/store/agent/postgres" "github.com/fil-forge/ucantone/ipld" "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/result" @@ -34,7 +35,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) agent.Store { switch k { case Memory: - return memory.New() + return agentmemory.New() case AWS: return createAWSStore(t) case Postgres: @@ -83,7 +84,7 @@ func createAWSStore(t *testing.T) agent.Store { dynamo := testutil.NewDynamoClient(t, dynamoEndpoint) id := uuid.NewString() - store := aws.New(dynamo, "agent-index-"+id, s3, "agent-message-"+id) + store := agentaws.New(dynamo, "agent-index-"+id, s3, "agent-message-"+id) err := store.Initialize(t.Context()) require.NoError(t, err) diff --git a/pkg/store/agent/aws/store.go b/pkg/store/agent/aws/store.go index e674568..878ab16 100644 --- a/pkg/store/agent/aws/store.go +++ b/pkg/store/agent/aws/store.go @@ -267,7 +267,7 @@ func (s *Store) Write(ctx context.Context, message ucan.Container, index []agent s3Put: &s3.PutObjectInput{ Bucket: &s.bucketName, Key: aws.String(toMessagePath(msgRoot)), - Body: &buf, + Body: bytes.NewReader(buf.Bytes()), }, callback: callback, }) diff --git a/pkg/store/agent/postgres/store.go b/pkg/store/agent/postgres/store.go index bf9f9b7..bb86bf4 100644 --- a/pkg/store/agent/postgres/store.go +++ b/pkg/store/agent/postgres/store.go @@ -12,17 +12,14 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/fil-forge/go-ucanto/core/car" - "github.com/fil-forge/go-ucanto/core/dag/blockstore" - "github.com/fil-forge/go-ucanto/core/invocation" - "github.com/fil-forge/go-ucanto/core/message" - "github.com/fil-forge/go-ucanto/core/receipt" - "github.com/fil-forge/sprue/pkg/internal/ipldutil" "github.com/fil-forge/sprue/pkg/store/agent" - cid "github.com/ipfs/go-cid" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/fil-forge/ucantone/ipld/codec/dagcbor" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" + "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" + "github.com/multiformats/go-multihash" ) type Store struct { @@ -54,27 +51,36 @@ func (s *Store) Shutdown(ctx context.Context) error { return nil } -func (s *Store) GetInvocation(ctx context.Context, task cid.Cid) (invocation.Invocation, error) { - root, bs, err := s.getByTask(ctx, task, "in") +func (s *Store) GetInvocation(ctx context.Context, task cid.Cid) (ucan.Invocation, error) { + _, ct, err := s.getByTask(ctx, task, "in") if err != nil { return nil, fmt.Errorf("getting invocation for task %s: %w", task, err) } - return invocation.NewInvocationView(cidlink.Link{Cid: root}, bs) + for _, inv := range ct.Invocations() { + if inv.Task().Link() == task { + return inv, nil + } + } + return nil, agent.ErrInvocationNotFound } -func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (receipt.AnyReceipt, error) { - root, bs, err := s.getByTask(ctx, task, "out") +func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (ucan.Receipt, error) { + _, ct, err := s.getByTask(ctx, task, "out") if err != nil { return nil, fmt.Errorf("getting receipt for task %s: %w", task, err) } - return receipt.NewAnyReceipt(cidlink.Link{Cid: root}, bs) + rcpt, ok := ct.Receipt(task) + if !ok { + return nil, agent.ErrReceiptNotFound + } + return rcpt, nil } -func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.Cid, blockstore.BlockReader, error) { - var identifier string +func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.Cid, *container.Container, error) { + var tokenRootStr, msgRootStr string err := s.pool.QueryRow(ctx, ` - SELECT identifier FROM agent_index WHERE task = $1 AND kind = $2 - `, task.String(), kind).Scan(&identifier) + SELECT token, message FROM agent_index WHERE task = $1 AND kind = $2 + `, task.String(), kind).Scan(&tokenRootStr, &msgRootStr) if errors.Is(err, pgx.ErrNoRows) { if kind == "in" { return cid.Undef, nil, agent.ErrInvocationNotFound @@ -84,15 +90,11 @@ func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.C if err != nil { return cid.Undef, nil, fmt.Errorf("querying agent_index: %w", err) } - parts := strings.SplitN(identifier, "@", 2) - if len(parts) != 2 { - return cid.Undef, nil, fmt.Errorf("invalid identifier format: %s", identifier) - } - root, err := cid.Parse(parts[0]) + tokenRoot, err := cid.Parse(tokenRootStr) if err != nil { return cid.Undef, nil, fmt.Errorf("parsing root CID: %w", err) } - msgRoot, err := cid.Parse(parts[1]) + msgRoot, err := cid.Parse(msgRootStr) if err != nil { return cid.Undef, nil, fmt.Errorf("parsing message root CID: %w", err) } @@ -106,15 +108,12 @@ func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.C } defer out.Body.Close() - _, blocks, err := car.Decode(out.Body) - if err != nil { - return cid.Undef, nil, fmt.Errorf("decoding CAR: %w", err) + var msg container.Container + if err := msg.UnmarshalCBOR(out.Body); err != nil { + return cid.Undef, nil, fmt.Errorf("decoding container: %w", err) } - bs, err := blockstore.NewBlockStore(blockstore.WithBlocksIterator(blocks)) - if err != nil { - return cid.Undef, nil, fmt.Errorf("creating blockstore: %w", err) - } - return root, bs, nil + + return tokenRoot, &msg, nil } // Write uploads the agent message payload to S3 and records every index entry @@ -122,16 +121,34 @@ func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.C // partial failure leaves (at worst) an orphan S3 object rather than a dangling // index pointer to a missing payload. All work runs on the caller's context, // so cancellation propagates through both the S3 and Postgres calls. -func (s *Store) Write(ctx context.Context, msg message.AgentMessage, index []agent.IndexEntry, source []byte) error { - msgRoot, err := ipldutil.ToCID(msg.Root().Link()) +func (s *Store) Write(ctx context.Context, message ucan.Container, index []agent.IndexEntry) error { + if len(index) == 0 { + return nil + } + + c, ok := message.(*container.Container) + if !ok { + c = container.New( + container.WithInvocations(message.Invocations()...), + container.WithReceipts(message.Receipts()...), + container.WithDelegations(message.Delegations()...), + ) + } + + var buf bytes.Buffer + if err := c.MarshalCBOR(&buf); err != nil { + return fmt.Errorf("marshaling agent message to CBOR: %w", err) + } + + msgRoot, err := cid.V1Builder{Codec: dagcbor.Code, MhType: multihash.SHA2_256}.Sum(buf.Bytes()) if err != nil { - return fmt.Errorf("converting message root link to CID: %w", err) + return fmt.Errorf("hashing agent message: %w", err) } if _, err := s.s3.PutObject(ctx, &s3.PutObjectInput{ Bucket: &s.bucketName, Key: aws.String(toMessagePath(msgRoot)), - Body: bytes.NewReader(source), + Body: bytes.NewReader(buf.Bytes()), }); err != nil { return fmt.Errorf("writing agent message to S3: %w", err) } @@ -141,40 +158,37 @@ func (s *Store) Write(ctx context.Context, msg message.AgentMessage, index []age // batched INSERT below doesn't trip "ON CONFLICT DO UPDATE command cannot // affect row a second time". Duplicates within a single message carry the // same identifier by construction, so last-wins is safe. - type indexKey struct{ task, kind string } - rows := make(map[indexKey]string) + type indexKey struct { + task cid.Cid + kind string + } + rows := make(map[indexKey]agent.IndexEntry) for _, entry := range index { if entry.Invocation != nil { - invRoot, err := ipldutil.ToCID(entry.Invocation.Invocation.Link()) - if err != nil { - return fmt.Errorf("converting invocation root link to CID: %w", err) - } - rows[indexKey{entry.Invocation.Task.String(), "in"}] = fmt.Sprintf("%s@%s", invRoot, msgRoot) + rows[indexKey{entry.Invocation.Task, "in"}] = entry } if entry.Receipt != nil { - rcptRoot, err := ipldutil.ToCID(entry.Receipt.Receipt.Root().Link()) - if err != nil { - return fmt.Errorf("converting receipt root link to CID: %w", err) - } - rows[indexKey{entry.Receipt.Task.String(), "out"}] = fmt.Sprintf("%s@%s", rcptRoot, msgRoot) + rows[indexKey{entry.Receipt.Task, "out"}] = entry } } - if len(rows) == 0 { - return nil - } - placeholders := make([]string, 0, len(rows)) - args := make([]any, 0, 3*len(rows)) + args := make([]any, 0, 4*len(rows)) i := 0 - for k, identifier := range rows { - placeholders = append(placeholders, fmt.Sprintf("($%d, $%d, $%d)", 3*i+1, 3*i+2, 3*i+3)) - args = append(args, k.task, k.kind, identifier) + for k, entry := range rows { + placeholders = append(placeholders, fmt.Sprintf("($%d, $%d, $%d, $%d)", 4*i+1, 4*i+2, 4*i+3, 4*i+4)) + var token cid.Cid + if k.kind == "in" { + token = entry.Invocation.Invocation.Link() + } else { + token = entry.Receipt.Receipt.Link() + } + args = append(args, k.task, k.kind, token, msgRoot) i++ } - query := "INSERT INTO agent_index (task, kind, identifier) VALUES " + + query := "INSERT INTO agent_index (task, kind, token, message) VALUES " + strings.Join(placeholders, ", ") + - " ON CONFLICT (task, kind) DO UPDATE SET identifier = EXCLUDED.identifier" + " ON CONFLICT (task, kind) DO UPDATE SET token = EXCLUDED.token, message = EXCLUDED.message" if _, err := s.pool.Exec(ctx, query, args...); err != nil { return fmt.Errorf("writing agent index: %w", err) } diff --git a/pkg/store/blob_registry/blob_registry.go b/pkg/store/blob_registry/blob_registry.go index 0558625..4f5ce8a 100644 --- a/pkg/store/blob_registry/blob_registry.go +++ b/pkg/store/blob_registry/blob_registry.go @@ -27,6 +27,7 @@ var ( type ( ListConfig = store.PaginationConfig ListOption func(cfg *ListConfig) + Blob = blob.Blob ) func WithListLimit(limit int) ListOption { @@ -43,7 +44,7 @@ func WithListCursor(cursor string) ListOption { type Record struct { Space did.DID - Blob blob.Blob + Blob Blob Cause cid.Cid InsertedAt time.Time } @@ -53,7 +54,7 @@ type Store interface { Get(ctx context.Context, space did.DID, digest multihash.Multihash) (Record, error) // Adds an item into the registry if it does not already exist. May return // [ErrEntryExists] if the blob is already registered in the space. - Register(ctx context.Context, space did.DID, blob blob.Blob, cause cid.Cid) error + Register(ctx context.Context, space did.DID, blob Blob, cause cid.Cid) error // List entries in the registry for a given space. List(ctx context.Context, space did.DID, options ...ListOption) (store.Page[Record], error) // Removes an item from the registry if it exists. diff --git a/pkg/store/blob_registry/blob_registry_test.go b/pkg/store/blob_registry/blob_registry_test.go index 065eff1..cb46db0 100644 --- a/pkg/store/blob_registry/blob_registry_test.go +++ b/pkg/store/blob_registry/blob_registry_test.go @@ -10,15 +10,18 @@ import ( "github.com/fil-forge/sprue/pkg/store" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" blobregistryaws "github.com/fil-forge/sprue/pkg/store/blob_registry/aws" - "github.com/fil-forge/sprue/pkg/store/blob_registry/memory" + blobregistrymemory "github.com/fil-forge/sprue/pkg/store/blob_registry/memory" + blobregistrypostgres "github.com/fil-forge/sprue/pkg/store/blob_registry/postgres" "github.com/fil-forge/sprue/pkg/store/consumer" consumeraws "github.com/fil-forge/sprue/pkg/store/consumer/aws" - memoryconsumer "github.com/fil-forge/sprue/pkg/store/consumer/memory" + consumermemory "github.com/fil-forge/sprue/pkg/store/consumer/memory" + consumerpostgres "github.com/fil-forge/sprue/pkg/store/consumer/postgres" "github.com/fil-forge/sprue/pkg/store/metrics" metricsaws "github.com/fil-forge/sprue/pkg/store/metrics/aws" - memorymetrics "github.com/fil-forge/sprue/pkg/store/metrics/memory" + metricsmemory "github.com/fil-forge/sprue/pkg/store/metrics/memory" + metricspostgres "github.com/fil-forge/sprue/pkg/store/metrics/postgres" spacediffaws "github.com/fil-forge/sprue/pkg/store/space_diff/aws" - memoryspacediff "github.com/fil-forge/sprue/pkg/store/space_diff/memory" + spacediffmemory "github.com/fil-forge/sprue/pkg/store/space_diff/memory" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -45,11 +48,11 @@ type storeBundle struct { func makeStores(t *testing.T, k StoreKind) storeBundle { switch k { case Memory: - consumerStore := memoryconsumer.New() - spaceDiffStore := memoryspacediff.New() - spaceMetrics := memorymetrics.NewSpaceStore() - adminMetrics := memorymetrics.New() - registry := memory.New(spaceDiffStore, consumerStore, spaceMetrics, adminMetrics) + consumerStore := consumermemory.New() + spaceDiffStore := spacediffmemory.New() + spaceMetrics := metricsmemory.NewSpaceStore() + adminMetrics := metricsmemory.New() + registry := blobregistrymemory.New(spaceDiffStore, consumerStore, spaceMetrics, adminMetrics) return storeBundle{ registry: registry, consumers: consumerStore, diff --git a/pkg/store/blob_registry/postgres/store.go b/pkg/store/blob_registry/postgres/store.go index 3049dc4..eeef730 100644 --- a/pkg/store/blob_registry/postgres/store.go +++ b/pkg/store/blob_registry/postgres/store.go @@ -9,15 +9,14 @@ import ( "fmt" "time" - captypes "github.com/fil-forge/go-libstoracha/capabilities/types" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/libforge/digestutil" "github.com/fil-forge/sprue/pkg/store" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/sprue/pkg/store/consumer" "github.com/fil-forge/sprue/pkg/store/metrics" pgmetrics "github.com/fil-forge/sprue/pkg/store/metrics/postgres" pgspacediff "github.com/fil-forge/sprue/pkg/store/space_diff/postgres" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" @@ -63,7 +62,7 @@ func (s *Store) Get(ctx context.Context, space did.DID, digest multihash.Multiha return rec, nil } -func (s *Store) Register(ctx context.Context, space did.DID, blob captypes.Blob, cause cid.Cid) error { +func (s *Store) Register(ctx context.Context, space did.DID, blob blobregistry.Blob, cause cid.Cid) error { consumers, err := s.collectConsumers(ctx, space) if err != nil { return err @@ -256,7 +255,7 @@ func scanRecord(row rowScanner) (blobregistry.Record, error) { } return blobregistry.Record{ Space: space, - Blob: captypes.Blob{ + Blob: blobregistry.Blob{ Digest: digest, Size: uint64(size), }, diff --git a/pkg/store/consumer/consumer_test.go b/pkg/store/consumer/consumer_test.go index 7c20f13..a69aab7 100644 --- a/pkg/store/consumer/consumer_test.go +++ b/pkg/store/consumer/consumer_test.go @@ -9,7 +9,8 @@ import ( "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/consumer" consumeraws "github.com/fil-forge/sprue/pkg/store/consumer/aws" - "github.com/fil-forge/sprue/pkg/store/consumer/memory" + consumermemory "github.com/fil-forge/sprue/pkg/store/consumer/memory" + consumerpostgres "github.com/fil-forge/sprue/pkg/store/consumer/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -27,7 +28,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) consumer.Store { switch k { case Memory: - return memory.New() + return consumermemory.New() case AWS: return createAWSStore(t) case Postgres: diff --git a/pkg/store/consumer/postgres/store.go b/pkg/store/consumer/postgres/store.go index b89b902..fcd0aa6 100644 --- a/pkg/store/consumer/postgres/store.go +++ b/pkg/store/consumer/postgres/store.go @@ -8,9 +8,9 @@ import ( "strings" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/consumer" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" diff --git a/pkg/store/customer/customer_test.go b/pkg/store/customer/customer_test.go index ba13673..18100af 100644 --- a/pkg/store/customer/customer_test.go +++ b/pkg/store/customer/customer_test.go @@ -9,7 +9,8 @@ import ( "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/customer" customeraws "github.com/fil-forge/sprue/pkg/store/customer/aws" - "github.com/fil-forge/sprue/pkg/store/customer/memory" + customermemory "github.com/fil-forge/sprue/pkg/store/customer/memory" + customerpostgres "github.com/fil-forge/sprue/pkg/store/customer/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -27,7 +28,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) customer.Store { switch k { case Memory: - return memory.New() + return customermemory.New() case AWS: return createAWSStore(t) case Postgres: diff --git a/pkg/store/customer/postgres/store.go b/pkg/store/customer/postgres/store.go index 796e1fd..5259b3e 100644 --- a/pkg/store/customer/postgres/store.go +++ b/pkg/store/customer/postgres/store.go @@ -8,9 +8,9 @@ import ( "fmt" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/customer" + "github.com/fil-forge/ucantone/did" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" diff --git a/pkg/store/delegation/delegation_test.go b/pkg/store/delegation/delegation_test.go index 944b6ff..e5e9756 100644 --- a/pkg/store/delegation/delegation_test.go +++ b/pkg/store/delegation/delegation_test.go @@ -9,7 +9,8 @@ import ( "github.com/fil-forge/sprue/pkg/store" dlgstore "github.com/fil-forge/sprue/pkg/store/delegation" delegationaws "github.com/fil-forge/sprue/pkg/store/delegation/aws" - "github.com/fil-forge/sprue/pkg/store/delegation/memory" + delegationmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" + delegationpostgres "github.com/fil-forge/sprue/pkg/store/delegation/postgres" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/google/uuid" @@ -29,7 +30,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) dlgstore.Store { switch k { case Memory: - return memory.New() + return delegationmemory.New() case AWS: return createAWSStore(t) case Postgres: diff --git a/pkg/store/delegation/postgres/store.go b/pkg/store/delegation/postgres/store.go index e7c78b6..3853520 100644 --- a/pkg/store/delegation/postgres/store.go +++ b/pkg/store/delegation/postgres/store.go @@ -11,10 +11,12 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" dlgstore "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5/pgxpool" ) @@ -43,15 +45,27 @@ func (s *Store) Initialize(ctx context.Context) error { return nil } -func (s *Store) PutMany(ctx context.Context, delegations []delegation.Delegation, cause cid.Cid) error { +func (s *Store) PutMany(ctx context.Context, tokens []ucan.Token, cause cid.Cid) error { now := time.Now().UTC() - for _, dlg := range delegations { - link := dlg.Root().Link().String() - - body, err := io.ReadAll(dlg.Archive()) - if err != nil { - return fmt.Errorf("archiving delegation %s: %w", link, err) + for _, token := range tokens { + link := token.Link().String() + + var body []byte + var err error + if dlg, ok := token.(ucan.Delegation); ok { + body, err = delegation.Encode(dlg) + if err != nil { + return fmt.Errorf("encoding delegation %s: %w", link, err) + } + } else if inv, ok := token.(ucan.Invocation); ok { + body, err = invocation.Encode(inv) + if err != nil { + return fmt.Errorf("encoding invocation %s: %w", link, err) + } + } else { + return fmt.Errorf("unsupported token type: %T", token) } + if _, err := s.s3.PutObject(ctx, &s3.PutObjectInput{ Bucket: &s.bucketName, Key: aws.String(link), @@ -60,13 +74,20 @@ func (s *Store) PutMany(ctx context.Context, delegations []delegation.Delegation return fmt.Errorf("storing delegation %s in S3: %w", link, err) } + var aud did.DID + // audience may be nil if the token is an invocation + if token.Audience() != nil { + aud = token.Audience().DID() + } else { + aud = token.Subject().DID() + } var causeStr *string if cause != cid.Undef { c := cause.String() causeStr = &c } var expiration *int64 - if exp := dlg.Expiration(); exp != nil { + if exp := token.Expiration(); exp != nil { e := int64(*exp) expiration = &e } @@ -80,14 +101,14 @@ func (s *Store) PutMany(ctx context.Context, delegations []delegation.Delegation cause = EXCLUDED.cause, expiration = EXCLUDED.expiration, updated_at = EXCLUDED.updated_at - `, link, dlg.Audience().DID().String(), dlg.Issuer().DID().String(), causeStr, expiration, now); err != nil { + `, link, aud.String(), token.Issuer().DID().String(), causeStr, expiration, now); err != nil { return fmt.Errorf("indexing delegation %s: %w", link, err) } } return nil } -func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options ...dlgstore.ListByAudienceOption) (store.Page[delegation.Delegation], error) { +func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options ...dlgstore.ListByAudienceOption) (store.Page[ucan.Token], error) { cfg := dlgstore.ListByAudienceConfig{} for _, opt := range options { opt(&cfg) @@ -111,7 +132,7 @@ func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options .. rows, err := s.pool.Query(ctx, query, args...) if err != nil { - return store.Page[delegation.Delegation]{}, fmt.Errorf("querying delegations by audience: %w", err) + return store.Page[ucan.Token]{}, fmt.Errorf("querying delegations by audience: %w", err) } defer rows.Close() @@ -119,12 +140,12 @@ func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options .. for rows.Next() { var link string if err := rows.Scan(&link); err != nil { - return store.Page[delegation.Delegation]{}, fmt.Errorf("scanning delegation: %w", err) + return store.Page[ucan.Token]{}, fmt.Errorf("scanning delegation: %w", err) } links = append(links, link) } if err := rows.Err(); err != nil { - return store.Page[delegation.Delegation]{}, fmt.Errorf("iterating delegations: %w", err) + return store.Page[ucan.Token]{}, fmt.Errorf("iterating delegations: %w", err) } var cursor *string @@ -134,35 +155,42 @@ func (s *Store) ListByAudience(ctx context.Context, audience did.DID, options .. links = links[:limit] } - results := make([]delegation.Delegation, 0, len(links)) + results := make([]ucan.Token, 0, len(links)) for _, link := range links { - dlg, err := s.fetchDelegation(ctx, link) + token, err := s.fetchToken(ctx, link) if err != nil { - return store.Page[delegation.Delegation]{}, fmt.Errorf("fetching delegation %s: %w", link, err) + return store.Page[ucan.Token]{}, fmt.Errorf("fetching token %s: %w", link, err) } - results = append(results, dlg) + results = append(results, token) } - return store.Page[delegation.Delegation]{Results: results, Cursor: cursor}, nil + return store.Page[ucan.Token]{Results: results, Cursor: cursor}, nil } -func (s *Store) fetchDelegation(ctx context.Context, link string) (delegation.Delegation, error) { +// fetchToken retrieves and decodes a delegation/invocation from S3 by its link +// CID string. +func (s *Store) fetchToken(ctx context.Context, link string) (ucan.Token, error) { out, err := s.s3.GetObject(ctx, &s3.GetObjectInput{ Bucket: &s.bucketName, Key: aws.String(link), }) if err != nil { - return nil, fmt.Errorf("getting delegation from S3: %w", err) + return nil, fmt.Errorf("getting token %s from S3: %w", link, err) } defer out.Body.Close() - data, err := io.ReadAll(out.Body) + body, err := io.ReadAll(out.Body) if err != nil { - return nil, fmt.Errorf("reading delegation from S3: %w", err) + return nil, fmt.Errorf("reading token %s body from S3: %w", link, err) } - dlg, err := delegation.Extract(data) + + inv, err := invocation.Decode(body) if err != nil { - return nil, fmt.Errorf("extracting delegation: %w", err) + dlg, err := delegation.Decode(body) + if err != nil { + return nil, fmt.Errorf("decoding token %s: %w", link, err) + } + return dlg, nil } - return dlg, nil + return inv, nil } diff --git a/pkg/store/metrics/metrics_test.go b/pkg/store/metrics/metrics_test.go index cdec872..bd35624 100644 --- a/pkg/store/metrics/metrics_test.go +++ b/pkg/store/metrics/metrics_test.go @@ -7,7 +7,8 @@ import ( "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store/metrics" metricsaws "github.com/fil-forge/sprue/pkg/store/metrics/aws" - "github.com/fil-forge/sprue/pkg/store/metrics/memory" + metricsmemory "github.com/fil-forge/sprue/pkg/store/metrics/memory" + metricspostgres "github.com/fil-forge/sprue/pkg/store/metrics/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -25,7 +26,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) metrics.Store { switch k { case Memory: - return memory.New() + return metricsmemory.New() case AWS: return createAWSStore(t) case Postgres: @@ -37,7 +38,7 @@ func makeStore(t *testing.T, k StoreKind) metrics.Store { func makeSpaceStore(t *testing.T, k StoreKind) metrics.SpaceStore { switch k { case Memory: - return memory.NewSpaceStore() + return metricsmemory.NewSpaceStore() case AWS: return createAWSSpaceStore(t) case Postgres: diff --git a/pkg/store/metrics/postgres/store.go b/pkg/store/metrics/postgres/store.go index b9c4340..fcc6b7a 100644 --- a/pkg/store/metrics/postgres/store.go +++ b/pkg/store/metrics/postgres/store.go @@ -6,8 +6,8 @@ import ( "context" "fmt" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store/metrics" + "github.com/fil-forge/ucantone/did" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" ) diff --git a/pkg/store/replica/postgres/store.go b/pkg/store/replica/postgres/store.go index 0001f9d..6091da5 100644 --- a/pkg/store/replica/postgres/store.go +++ b/pkg/store/replica/postgres/store.go @@ -7,9 +7,9 @@ import ( "fmt" "time" - "github.com/fil-forge/go-libstoracha/digestutil" - "github.com/fil-forge/go-ucanto/did" + "github.com/fil-forge/libforge/digestutil" "github.com/fil-forge/sprue/pkg/store/replica" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" diff --git a/pkg/store/replica/replica_test.go b/pkg/store/replica/replica_test.go index f180a1e..7947055 100644 --- a/pkg/store/replica/replica_test.go +++ b/pkg/store/replica/replica_test.go @@ -7,7 +7,8 @@ import ( "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store/replica" replicaaws "github.com/fil-forge/sprue/pkg/store/replica/aws" - "github.com/fil-forge/sprue/pkg/store/replica/memory" + replicamemory "github.com/fil-forge/sprue/pkg/store/replica/memory" + replicapostgres "github.com/fil-forge/sprue/pkg/store/replica/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -25,7 +26,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) replica.Store { switch k { case Memory: - return memory.New() + return replicamemory.New() case AWS: return createAWSStore(t) case Postgres: diff --git a/pkg/store/revocation/postgres/store.go b/pkg/store/revocation/postgres/store.go index 6306d74..8e659e9 100644 --- a/pkg/store/revocation/postgres/store.go +++ b/pkg/store/revocation/postgres/store.go @@ -6,8 +6,8 @@ import ( "fmt" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store/revocation" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5/pgxpool" ) diff --git a/pkg/store/revocation/revocation_test.go b/pkg/store/revocation/revocation_test.go index a9e2550..f85cdb1 100644 --- a/pkg/store/revocation/revocation_test.go +++ b/pkg/store/revocation/revocation_test.go @@ -7,7 +7,8 @@ import ( "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store/revocation" revocationaws "github.com/fil-forge/sprue/pkg/store/revocation/aws" - "github.com/fil-forge/sprue/pkg/store/revocation/memory" + revocationmemory "github.com/fil-forge/sprue/pkg/store/revocation/memory" + revocationpostgres "github.com/fil-forge/sprue/pkg/store/revocation/postgres" "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" @@ -26,7 +27,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) revocation.Store { switch k { case Memory: - return memory.New() + return revocationmemory.New() case AWS: return createAWSStore(t) case Postgres: diff --git a/pkg/store/space_diff/postgres/store.go b/pkg/store/space_diff/postgres/store.go index 90d48e6..39a53c4 100644 --- a/pkg/store/space_diff/postgres/store.go +++ b/pkg/store/space_diff/postgres/store.go @@ -9,9 +9,9 @@ import ( "strings" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" spacediff "github.com/fil-forge/sprue/pkg/store/space_diff" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" diff --git a/pkg/store/space_diff/space_diff_test.go b/pkg/store/space_diff/space_diff_test.go index 53b7d2e..2d7acd2 100644 --- a/pkg/store/space_diff/space_diff_test.go +++ b/pkg/store/space_diff/space_diff_test.go @@ -10,7 +10,8 @@ import ( "github.com/fil-forge/sprue/pkg/store" spacediff "github.com/fil-forge/sprue/pkg/store/space_diff" spacediffaws "github.com/fil-forge/sprue/pkg/store/space_diff/aws" - "github.com/fil-forge/sprue/pkg/store/space_diff/memory" + spacediffmemory "github.com/fil-forge/sprue/pkg/store/space_diff/memory" + spacediffpostgres "github.com/fil-forge/sprue/pkg/store/space_diff/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -28,7 +29,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) spacediff.Store { switch k { case Memory: - return memory.New() + return spacediffmemory.New() case AWS: return createAWSStore(t) case Postgres: diff --git a/pkg/store/storage_provider/aws/store.go b/pkg/store/storage_provider/aws/store.go index d3faf42..6fbcea3 100644 --- a/pkg/store/storage_provider/aws/store.go +++ b/pkg/store/storage_provider/aws/store.go @@ -70,11 +70,10 @@ func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight in TableName: aws.String(s.tableName), Key: map[string]types.AttributeValue{"provider": &types.AttributeValueMemberS{Value: id.String()}}, UpdateExpression: aws.String( - "SET #endpoint = :endpoint, #proof = :proof, #weight = :weight, #replicationWeight = :replicationWeight, #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now", + "SET #endpoint = :endpoint, #weight = :weight, #replicationWeight = :replicationWeight, #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now", ), ExpressionAttributeNames: map[string]string{ "#endpoint": "endpoint", - "#proof": "proof", "#weight": "weight", "#insertedAt": "insertedAt", "#updatedAt": "updatedAt", diff --git a/pkg/store/storage_provider/postgres/store.go b/pkg/store/storage_provider/postgres/store.go index cf758e9..ef09ea6 100644 --- a/pkg/store/storage_provider/postgres/store.go +++ b/pkg/store/storage_provider/postgres/store.go @@ -8,10 +8,9 @@ import ( "net/url" "time" - "github.com/fil-forge/go-ucanto/core/delegation" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/did" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) @@ -30,23 +29,17 @@ func New(pool *pgxpool.Pool) *Store { func (s *Store) Initialize(ctx context.Context) error { return nil } -func (s *Store) Put(ctx context.Context, endpoint url.URL, proof delegation.Delegation, weight int, replicationWeight *int) error { - proofStr, err := delegation.Format(proof) - if err != nil { - return fmt.Errorf("formatting proof: %w", err) - } - +func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight int, replicationWeight *int) error { now := time.Now().UTC() - _, err = s.pool.Exec(ctx, ` - INSERT INTO storage_provider (provider, endpoint, proof, weight, replication_weight, inserted_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6, $6) + _, err := s.pool.Exec(ctx, ` + INSERT INTO storage_provider (provider, endpoint, weight, replication_weight, inserted_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $5) ON CONFLICT (provider) DO UPDATE SET endpoint = EXCLUDED.endpoint, - proof = EXCLUDED.proof, weight = EXCLUDED.weight, replication_weight = EXCLUDED.replication_weight, updated_at = EXCLUDED.updated_at - `, proof.Issuer().DID().String(), endpoint.String(), proofStr, weight, replicationWeight, now) + `, id.String(), endpoint.String(), weight, replicationWeight, now) if err != nil { return fmt.Errorf("storing storage provider: %w", err) } @@ -55,7 +48,7 @@ func (s *Store) Put(ctx context.Context, endpoint url.URL, proof delegation.Dele func (s *Store) Get(ctx context.Context, providerID did.DID) (storageprovider.Record, error) { row := s.pool.QueryRow(ctx, ` - SELECT provider, endpoint, proof, weight, replication_weight, inserted_at, updated_at + SELECT provider, endpoint, weight, replication_weight, inserted_at, updated_at FROM storage_provider WHERE provider = $1 `, providerID.String()) @@ -92,7 +85,7 @@ func (s *Store) List(ctx context.Context, options ...storageprovider.ListOption) args := []any{limit + 1} query := ` - SELECT provider, endpoint, proof, weight, replication_weight, inserted_at, updated_at + SELECT provider, endpoint, weight, replication_weight, inserted_at, updated_at FROM storage_provider ` if cfg.Cursor != nil { @@ -136,13 +129,12 @@ func scanRecord(row rowScanner) (storageprovider.Record, error) { var ( providerStr string endpointStr string - proofStr string weight int replicationWeight *int insertedAt time.Time updatedAt time.Time ) - if err := row.Scan(&providerStr, &endpointStr, &proofStr, &weight, &replicationWeight, &insertedAt, &updatedAt); err != nil { + if err := row.Scan(&providerStr, &endpointStr, &weight, &replicationWeight, &insertedAt, &updatedAt); err != nil { return storageprovider.Record{}, err } providerDID, err := did.Parse(providerStr) @@ -153,14 +145,9 @@ func scanRecord(row rowScanner) (storageprovider.Record, error) { if err != nil { return storageprovider.Record{}, fmt.Errorf("parsing endpoint URL: %w", err) } - proof, err := delegation.Parse(proofStr) - if err != nil { - return storageprovider.Record{}, fmt.Errorf("parsing proof: %w", err) - } return storageprovider.Record{ Provider: providerDID, Endpoint: *endpoint, - Proof: proof, Weight: weight, ReplicationWeight: replicationWeight, InsertedAt: insertedAt, diff --git a/pkg/store/storage_provider/storage_provider_test.go b/pkg/store/storage_provider/storage_provider_test.go index bd1cc35..890b406 100644 --- a/pkg/store/storage_provider/storage_provider_test.go +++ b/pkg/store/storage_provider/storage_provider_test.go @@ -10,7 +10,8 @@ import ( "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" storageprovideraws "github.com/fil-forge/sprue/pkg/store/storage_provider/aws" - "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" + storageprovidermemory "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" + storageproviderpostgres "github.com/fil-forge/sprue/pkg/store/storage_provider/postgres" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -28,7 +29,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) storageprovider.Store { switch k { case Memory: - return memory.New() + return storageprovidermemory.New() case AWS: return createAWSStore(t) case Postgres: diff --git a/pkg/store/subscription/postgres/store.go b/pkg/store/subscription/postgres/store.go index a91b149..04cd925 100644 --- a/pkg/store/subscription/postgres/store.go +++ b/pkg/store/subscription/postgres/store.go @@ -7,9 +7,9 @@ import ( "fmt" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/subscription" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" diff --git a/pkg/store/subscription/subscription_test.go b/pkg/store/subscription/subscription_test.go index f700b18..98bb268 100644 --- a/pkg/store/subscription/subscription_test.go +++ b/pkg/store/subscription/subscription_test.go @@ -9,7 +9,8 @@ import ( "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/subscription" subscriptionaws "github.com/fil-forge/sprue/pkg/store/subscription/aws" - "github.com/fil-forge/sprue/pkg/store/subscription/memory" + subscriptionmemory "github.com/fil-forge/sprue/pkg/store/subscription/memory" + subscriptionpostgres "github.com/fil-forge/sprue/pkg/store/subscription/postgres" "github.com/fil-forge/ucantone/did" "github.com/google/uuid" "github.com/stretchr/testify/require" @@ -28,7 +29,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) subscription.Store { switch k { case Memory: - return memory.New() + return subscriptionmemory.New() case AWS: return createAWSStore(t) case Postgres: diff --git a/pkg/store/upload/postgres/store.go b/pkg/store/upload/postgres/store.go index 7507418..5c2c561 100644 --- a/pkg/store/upload/postgres/store.go +++ b/pkg/store/upload/postgres/store.go @@ -11,9 +11,9 @@ import ( "fmt" "time" - "github.com/fil-forge/go-ucanto/did" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/upload" + "github.com/fil-forge/ucantone/did" "github.com/ipfs/go-cid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" @@ -52,7 +52,7 @@ func (s *Store) Exists(ctx context.Context, space did.DID, root cid.Cid) (bool, func (s *Store) Get(ctx context.Context, space did.DID, root cid.Cid) (upload.UploadRecord, error) { row := s.pool.QueryRow(ctx, ` - SELECT space, root, cause, inserted_at, updated_at + SELECT space, root, index, cause, inserted_at, updated_at FROM upload WHERE space = $1 AND root = $2 `, space.String(), root.String()) @@ -102,7 +102,7 @@ func (s *Store) List(ctx context.Context, space did.DID, options ...upload.ListO args := []any{space.String(), limit + 1} query := ` - SELECT space, root, cause, inserted_at, updated_at + SELECT space, root, index, cause, inserted_at, updated_at FROM upload WHERE space = $1 ` @@ -205,20 +205,25 @@ func (s *Store) Remove(ctx context.Context, space did.DID, root cid.Cid) error { return nil } -func (s *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, shards []cid.Cid, cause cid.Cid) error { +func (s *Store) Upsert(ctx context.Context, space did.DID, root cid.Cid, index *cid.Cid, shards []cid.Cid, cause cid.Cid) error { tx, err := s.pool.Begin(ctx) if err != nil { return fmt.Errorf("beginning transaction: %w", err) } defer func() { _ = tx.Rollback(ctx) }() + var indexStr *string + if index != nil { + str := index.String() + indexStr = &str + } now := time.Now().UTC() if _, err := tx.Exec(ctx, ` - INSERT INTO upload (space, root, cause, inserted_at, updated_at) - VALUES ($1, $2, $3, $4, $4) + INSERT INTO upload (space, root, index, cause, inserted_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $5) ON CONFLICT (space, root) DO UPDATE - SET cause = EXCLUDED.cause, updated_at = EXCLUDED.updated_at - `, space.String(), root.String(), cause.String(), now); err != nil { + SET index = EXCLUDED.index, cause = EXCLUDED.cause, updated_at = EXCLUDED.updated_at + `, space.String(), root.String(), indexStr, cause.String(), now); err != nil { return fmt.Errorf("upserting upload: %w", err) } @@ -246,11 +251,12 @@ func scanRecord(row rowScanner) (upload.UploadRecord, error) { var ( spaceStr string rootStr string + indexStr *string causeStr string insertedAt time.Time updatedAt time.Time ) - if err := row.Scan(&spaceStr, &rootStr, &causeStr, &insertedAt, &updatedAt); err != nil { + if err := row.Scan(&spaceStr, &rootStr, &indexStr, &causeStr, &insertedAt, &updatedAt); err != nil { return upload.UploadRecord{}, err } space, err := did.Parse(spaceStr) @@ -261,6 +267,14 @@ func scanRecord(row rowScanner) (upload.UploadRecord, error) { if err != nil { return upload.UploadRecord{}, fmt.Errorf("parsing root CID: %w", err) } + var index *cid.Cid + if indexStr != nil { + c, err := cid.Parse(*indexStr) + if err != nil { + return upload.UploadRecord{}, fmt.Errorf("parsing index CID: %w", err) + } + index = &c + } cause, err := cid.Parse(causeStr) if err != nil { return upload.UploadRecord{}, fmt.Errorf("parsing cause CID: %w", err) @@ -268,6 +282,7 @@ func scanRecord(row rowScanner) (upload.UploadRecord, error) { return upload.UploadRecord{ Space: space, Root: root, + Index: index, Cause: cause, InsertedAt: insertedAt, UpdatedAt: updatedAt, diff --git a/pkg/store/upload/upload_test.go b/pkg/store/upload/upload_test.go index c040bb3..c25ef04 100644 --- a/pkg/store/upload/upload_test.go +++ b/pkg/store/upload/upload_test.go @@ -8,8 +8,9 @@ import ( "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/upload" - "github.com/fil-forge/sprue/pkg/store/upload/aws" - "github.com/fil-forge/sprue/pkg/store/upload/memory" + uploadaws "github.com/fil-forge/sprue/pkg/store/upload/aws" + uploadmemory "github.com/fil-forge/sprue/pkg/store/upload/memory" + uploadpostgres "github.com/fil-forge/sprue/pkg/store/upload/postgres" "github.com/fil-forge/ucantone/did" "github.com/google/uuid" "github.com/ipfs/go-cid" @@ -29,7 +30,7 @@ var storeKinds = []StoreKind{Memory, AWS, Postgres} func makeStore(t *testing.T, k StoreKind) upload.Store { switch k { case Memory: - return memory.New() + return uploadmemory.New() case AWS: return createAWSStore(t) case Postgres: @@ -51,7 +52,7 @@ func createPostgresStore(t *testing.T) upload.Store { return uploadpostgres.New(pool) } -func createAWSStore(t *testing.T) *aws.Store { +func createAWSStore(t *testing.T) *uploadaws.Store { // This test expects docker to be running in linux CI environments and fails if it's not if testutil.IsRunningInCI(t) && runtime.GOOS == "linux" { if !testutil.IsDockerAvailable(t) { @@ -70,7 +71,7 @@ func createAWSStore(t *testing.T) *aws.Store { dynamo := testutil.NewDynamoClient(t, dynamoEndpoint) id := uuid.NewString() - store := aws.New(dynamo, "upload-"+id, s3, "upload-shards-"+id) + store := uploadaws.New(dynamo, "upload-"+id, s3, "upload-shards-"+id) err := store.Initialize(t.Context()) require.NoError(t, err) @@ -161,7 +162,7 @@ func TestUploadStore(t *testing.T) { // build a second batch of shards that includes one duplicate from the // first batch and enough new shards to push the total over ShardThreshold - newShardCount := aws.ShardThreshold - len(initialShards) + 2 // +2 to exceed threshold, accounting for the duplicate + newShardCount := uploadaws.ShardThreshold - len(initialShards) + 2 // +2 to exceed threshold, accounting for the duplicate additionalShards := make([]cid.Cid, newShardCount) additionalShards[0] = initialShards[0] // duplicate for i := 1; i < newShardCount; i++ { @@ -179,7 +180,7 @@ func TestUploadStore(t *testing.T) { // total unique shards = initialShards + additionalShards - 1 duplicate wantShards := len(initialShards) + newShardCount - 1 - require.Greater(t, wantShards, aws.ShardThreshold) + require.Greater(t, wantShards, uploadaws.ShardThreshold) allShards := listAllShards(t, store, space, root) require.Len(t, allShards, wantShards) @@ -213,7 +214,7 @@ func TestUploadStore(t *testing.T) { shardCount int }{ {"few shards", 3}, - {"many shards", aws.ShardThreshold + 1}, + {"many shards", uploadaws.ShardThreshold + 1}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -258,7 +259,7 @@ func TestUploadStore(t *testing.T) { shardCount int }{ {"few shards", 3}, - {"many shards", aws.ShardThreshold + 1}, + {"many shards", uploadaws.ShardThreshold + 1}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { From cd5c4a13625c681094f219703c9c20bbf6f5f1e7 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 13:57:50 +0100 Subject: [PATCH 03/23] fix: postgres datastore import --- internal/fx/app.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/fx/app.go b/internal/fx/app.go index 1b8cb19..4c5ca76 100644 --- a/internal/fx/app.go +++ b/internal/fx/app.go @@ -8,6 +8,7 @@ import ( "github.com/fil-forge/sprue/internal/fx/service/handlers" "github.com/fil-forge/sprue/internal/fx/store/aws" "github.com/fil-forge/sprue/internal/fx/store/memory" + "github.com/fil-forge/sprue/internal/fx/store/postgres" "go.uber.org/fx" ) From 402dd58a8e967117daa7a3e3deb8d83564d6e8be Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 14:36:30 +0100 Subject: [PATCH 04/23] chore: appease linter --- go.mod | 2 +- go.sum | 2 + .../admin/provider/datamodel/json_gen.go | 172 +++++++++--------- .../provider/weight/datamodel/json_gen.go | 40 ++-- 4 files changed, 109 insertions(+), 107 deletions(-) diff --git a/go.mod b/go.mod index d6f6303..c9d79be 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/fil-forge/sprue go 1.25.3 require ( - github.com/alanshaw/dag-json-gen v0.0.4 + github.com/alanshaw/dag-json-gen v0.0.5 github.com/aws/aws-sdk-go-v2 v1.41.3 github.com/aws/aws-sdk-go-v2/config v1.32.11 github.com/aws/aws-sdk-go-v2/credentials v1.19.11 diff --git a/go.sum b/go.sum index 8e9935a..dc51231 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alanshaw/dag-json-gen v0.0.4 h1:qoryz04TVH6zu16NRFnzgolzQGaPfTvoIawv/F5rDoY= github.com/alanshaw/dag-json-gen v0.0.4/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= +github.com/alanshaw/dag-json-gen v0.0.5 h1:jUOqsTrfZ7ddkBqAsx/xbCeJtpe70jrFL2Z+i4qQB1U= +github.com/alanshaw/dag-json-gen v0.0.5/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= diff --git a/pkg/capabilities/admin/provider/datamodel/json_gen.go b/pkg/capabilities/admin/provider/datamodel/json_gen.go index 0634e1e..8218fc9 100644 --- a/pkg/capabilities/admin/provider/datamodel/json_gen.go +++ b/pkg/capabilities/admin/provider/datamodel/json_gen.go @@ -42,45 +42,45 @@ func (t *ListArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("ListArgumentsModel: %w", err) + return fmt.Errorf("reading object open for ListArgumentsModel: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("ListArgumentsModel: %w", err) + return fmt.Errorf("peeking object close for ListArgumentsModel: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("ListArgumentsModel: %w", err) + return fmt.Errorf("reading object close for ListArgumentsModel: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("ListArgumentsModel: string too large") + return fmt.Errorf("reading string for field ListArgumentsModel: string too large") } - return fmt.Errorf("ListArgumentsModel: %w", err) + return fmt.Errorf("reading string for field ListArgumentsModel: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("ListArgumentsModel: %w", err) + return fmt.Errorf("reading object colon for field ListArgumentsModel: %w", err) } switch name { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ListArgumentsModel: ignoring field %s: %w", name, err) + return fmt.Errorf("ignoring field %s for ListArgumentsModel: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("ListArgumentsModel: %w", err) + return fmt.Errorf("reading object close or comma for field ListArgumentsModel: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("ListArgumentsModel: map too large") + return fmt.Errorf("map too large for ListArgumentsModel") } } } @@ -100,19 +100,19 @@ func (t *ProviderModel) MarshalDagJSON(w io.Writer) error { // t.Endpoint (string) (string) if len("endpoint") > 8192 { - return fmt.Errorf("String in field \"endpoint\" was too long") + return fmt.Errorf("string in field \"endpoint\" was too long") } if err := jw.WriteString(string("endpoint")); err != nil { - return fmt.Errorf("\"endpoint\": %w", err) + return fmt.Errorf("writing string for field \"endpoint\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if len(t.Endpoint) > 8192 { - return fmt.Errorf("String in field t.Endpoint was too long") + return fmt.Errorf("string in field t.Endpoint was too long") } if err := jw.WriteString(string(t.Endpoint)); err != nil { - return fmt.Errorf("t.Endpoint: %w", err) + return fmt.Errorf("writing string for field t.Endpoint: %w", err) } written++ if written > 0 { @@ -123,16 +123,16 @@ func (t *ProviderModel) MarshalDagJSON(w io.Writer) error { // t.Provider (did.DID) (struct) if len("provider") > 8192 { - return fmt.Errorf("String in field \"provider\" was too long") + return fmt.Errorf("string in field \"provider\" was too long") } if err := jw.WriteString(string("provider")); err != nil { - return fmt.Errorf("\"provider\": %w", err) + return fmt.Errorf("writing string for field \"provider\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if err := t.Provider.MarshalDagJSON(jw); err != nil { - return fmt.Errorf("t.Provider: %w", err) + return fmt.Errorf("marshaling field t.Provider: %w", err) } written++ if written > 0 { @@ -143,17 +143,17 @@ func (t *ProviderModel) MarshalDagJSON(w io.Writer) error { // t.ReplicationWeight (int64) (int64) if len("replicationWeight") > 8192 { - return fmt.Errorf("String in field \"replicationWeight\" was too long") + return fmt.Errorf("string in field \"replicationWeight\" was too long") } if err := jw.WriteString(string("replicationWeight")); err != nil { - return fmt.Errorf("\"replicationWeight\": %w", err) + return fmt.Errorf("writing string for field \"replicationWeight\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if err := jw.WriteInt64(int64(t.ReplicationWeight)); err != nil { - return fmt.Errorf("t.ReplicationWeight: %w", err) + return fmt.Errorf("writing int64 for field t.ReplicationWeight: %w", err) } written++ @@ -165,17 +165,17 @@ func (t *ProviderModel) MarshalDagJSON(w io.Writer) error { // t.Weight (int64) (int64) if len("weight") > 8192 { - return fmt.Errorf("String in field \"weight\" was too long") + return fmt.Errorf("string in field \"weight\" was too long") } if err := jw.WriteString(string("weight")); err != nil { - return fmt.Errorf("\"weight\": %w", err) + return fmt.Errorf("writing string for field \"weight\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if err := jw.WriteInt64(int64(t.Weight)); err != nil { - return fmt.Errorf("t.Weight: %w", err) + return fmt.Errorf("writing int64 for field t.Weight: %w", err) } written++ @@ -194,27 +194,27 @@ func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("ProviderModel: %w", err) + return fmt.Errorf("reading object open for ProviderModel: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("ProviderModel: %w", err) + return fmt.Errorf("peeking object close for ProviderModel: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("ProviderModel: %w", err) + return fmt.Errorf("reading object close for ProviderModel: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("ProviderModel: string too large") + return fmt.Errorf("reading string for field ProviderModel: string too large") } - return fmt.Errorf("ProviderModel: %w", err) + return fmt.Errorf("reading string for field ProviderModel: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("ProviderModel: %w", err) + return fmt.Errorf("reading object colon for field ProviderModel: %w", err) } switch name { @@ -224,9 +224,9 @@ func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { sval, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("t.Endpoint: string too long") + return fmt.Errorf("reading string for field t.Endpoint: string too long") } - return fmt.Errorf("t.Endpoint: %w", err) + return fmt.Errorf("reading string for field t.Endpoint: %w", err) } t.Endpoint = string(sval) } @@ -244,7 +244,7 @@ func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { nval, err := jr.ReadNumberAsInt64() if err != nil { - return fmt.Errorf("t.ReplicationWeight: %w", err) + return fmt.Errorf("reading int64 for field t.ReplicationWeight: %w", err) } t.ReplicationWeight = int64(nval) @@ -256,7 +256,7 @@ func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { nval, err := jr.ReadNumberAsInt64() if err != nil { - return fmt.Errorf("t.Weight: %w", err) + return fmt.Errorf("reading int64 for field t.Weight: %w", err) } t.Weight = int64(nval) @@ -264,19 +264,19 @@ func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ProviderModel: ignoring field %s: %w", name, err) + return fmt.Errorf("ignoring field %s for ProviderModel: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("ProviderModel: %w", err) + return fmt.Errorf("reading object close or comma for field ProviderModel: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("ProviderModel: map too large") + return fmt.Errorf("map too large for ProviderModel") } } } @@ -295,33 +295,33 @@ func (t *ListOKModel) MarshalDagJSON(w io.Writer) error { // t.Providers ([]datamodel.ProviderModel) (slice) if len("providers") > 8192 { - return fmt.Errorf("String in field \"providers\" was too long") + return fmt.Errorf("string in field \"providers\" was too long") } if err := jw.WriteString(string("providers")); err != nil { - return fmt.Errorf("\"providers\": %w", err) + return fmt.Errorf("writing string for field \"providers\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if len(t.Providers) > 8192 { - return fmt.Errorf("Slice value in field t.Providers was too long") + return fmt.Errorf("slice value in field t.Providers was too long") } if err := jw.WriteArrayOpen(); err != nil { - return fmt.Errorf("t.Providers: %w", err) + return fmt.Errorf("writing array open for field t.Providers: %w", err) } for i, v := range t.Providers { if i > 0 { if err := jw.WriteComma(); err != nil { - return fmt.Errorf("t.Providers: %w", err) + return fmt.Errorf("writing comma for field t.Providers: %w", err) } } if err := v.MarshalDagJSON(jw); err != nil { - return fmt.Errorf("v: %w", err) + return fmt.Errorf("marshaling field v: %w", err) } } if err := jw.WriteArrayClose(); err != nil { - return fmt.Errorf("t.Providers: %w", err) + return fmt.Errorf("writing array close for field t.Providers: %w", err) } if err := jw.WriteObjectClose(); err != nil { @@ -339,27 +339,27 @@ func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("ListOKModel: %w", err) + return fmt.Errorf("reading object open for ListOKModel: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("ListOKModel: %w", err) + return fmt.Errorf("peeking object close for ListOKModel: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("ListOKModel: %w", err) + return fmt.Errorf("reading object close for ListOKModel: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("ListOKModel: string too large") + return fmt.Errorf("reading string for field ListOKModel: string too large") } - return fmt.Errorf("ListOKModel: %w", err) + return fmt.Errorf("reading string for field ListOKModel: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("ListOKModel: %w", err) + return fmt.Errorf("reading object colon for field ListOKModel: %w", err) } switch name { @@ -368,16 +368,16 @@ func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { { if err := jr.ReadArrayOpen(); err != nil { - return fmt.Errorf("t.Providers: %w", err) + return fmt.Errorf("reading array open for field t.Providers: %w", err) } close, err := jr.PeekArrayClose() if err != nil { - return fmt.Errorf("t.Providers: %w", err) + return fmt.Errorf("peeking array close for field t.Providers: %w", err) } if close { if err := jr.ReadArrayClose(); err != nil { - return fmt.Errorf("t.Providers: %w", err) + return fmt.Errorf("reading array close for field t.Providers: %w", err) } } else { @@ -392,13 +392,13 @@ func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { close, err := jr.ReadArrayCloseOrComma() if err != nil { - return fmt.Errorf("t.Providers: %w", err) + return fmt.Errorf("reading array close or comma for field t.Providers: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("t.Providers: slice too large") + return fmt.Errorf("reading array for field t.Providers: slice too large") } } } @@ -407,19 +407,19 @@ func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ListOKModel: ignoring field %s: %w", name, err) + return fmt.Errorf("ignoring field %s for ListOKModel: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("ListOKModel: %w", err) + return fmt.Errorf("reading object close or comma for field ListOKModel: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("ListOKModel: map too large") + return fmt.Errorf("map too large for ListOKModel") } } } @@ -439,19 +439,19 @@ func (t *RegisterArgumentsModel) MarshalDagJSON(w io.Writer) error { // t.Endpoint (string) (string) if len("endpoint") > 8192 { - return fmt.Errorf("String in field \"endpoint\" was too long") + return fmt.Errorf("string in field \"endpoint\" was too long") } if err := jw.WriteString(string("endpoint")); err != nil { - return fmt.Errorf("\"endpoint\": %w", err) + return fmt.Errorf("writing string for field \"endpoint\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if len(t.Endpoint) > 8192 { - return fmt.Errorf("String in field t.Endpoint was too long") + return fmt.Errorf("string in field t.Endpoint was too long") } if err := jw.WriteString(string(t.Endpoint)); err != nil { - return fmt.Errorf("t.Endpoint: %w", err) + return fmt.Errorf("writing string for field t.Endpoint: %w", err) } written++ if written > 0 { @@ -462,16 +462,16 @@ func (t *RegisterArgumentsModel) MarshalDagJSON(w io.Writer) error { // t.Provider (did.DID) (struct) if len("provider") > 8192 { - return fmt.Errorf("String in field \"provider\" was too long") + return fmt.Errorf("string in field \"provider\" was too long") } if err := jw.WriteString(string("provider")); err != nil { - return fmt.Errorf("\"provider\": %w", err) + return fmt.Errorf("writing string for field \"provider\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if err := t.Provider.MarshalDagJSON(jw); err != nil { - return fmt.Errorf("t.Provider: %w", err) + return fmt.Errorf("marshaling field t.Provider: %w", err) } written++ if err := jw.WriteObjectClose(); err != nil { @@ -489,27 +489,27 @@ func (t *RegisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading object open for RegisterArgumentsModel: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("RegisterArgumentsModel: %w", err) + return fmt.Errorf("peeking object close for RegisterArgumentsModel: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading object close for RegisterArgumentsModel: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("RegisterArgumentsModel: string too large") + return fmt.Errorf("reading string for field RegisterArgumentsModel: string too large") } - return fmt.Errorf("RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading string for field RegisterArgumentsModel: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading object colon for field RegisterArgumentsModel: %w", err) } switch name { @@ -519,9 +519,9 @@ func (t *RegisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { sval, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("t.Endpoint: string too long") + return fmt.Errorf("reading string for field t.Endpoint: string too long") } - return fmt.Errorf("t.Endpoint: %w", err) + return fmt.Errorf("reading string for field t.Endpoint: %w", err) } t.Endpoint = string(sval) } @@ -536,19 +536,19 @@ func (t *RegisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("RegisterArgumentsModel: ignoring field %s: %w", name, err) + return fmt.Errorf("ignoring field %s for RegisterArgumentsModel: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading object close or comma for field RegisterArgumentsModel: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("RegisterArgumentsModel: map too large") + return fmt.Errorf("map too large for RegisterArgumentsModel") } } } @@ -567,16 +567,16 @@ func (t *DeregisterArgumentsModel) MarshalDagJSON(w io.Writer) error { // t.Provider (did.DID) (struct) if len("provider") > 8192 { - return fmt.Errorf("String in field \"provider\" was too long") + return fmt.Errorf("string in field \"provider\" was too long") } if err := jw.WriteString(string("provider")); err != nil { - return fmt.Errorf("\"provider\": %w", err) + return fmt.Errorf("writing string for field \"provider\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if err := t.Provider.MarshalDagJSON(jw); err != nil { - return fmt.Errorf("t.Provider: %w", err) + return fmt.Errorf("marshaling field t.Provider: %w", err) } if err := jw.WriteObjectClose(); err != nil { return err @@ -593,27 +593,27 @@ func (t *DeregisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading object open for DeregisterArgumentsModel: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("DeregisterArgumentsModel: %w", err) + return fmt.Errorf("peeking object close for DeregisterArgumentsModel: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading object close for DeregisterArgumentsModel: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("DeregisterArgumentsModel: string too large") + return fmt.Errorf("reading string for field DeregisterArgumentsModel: string too large") } - return fmt.Errorf("DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading string for field DeregisterArgumentsModel: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading object colon for field DeregisterArgumentsModel: %w", err) } switch name { @@ -627,19 +627,19 @@ func (t *DeregisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("DeregisterArgumentsModel: ignoring field %s: %w", name, err) + return fmt.Errorf("ignoring field %s for DeregisterArgumentsModel: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading object close or comma for field DeregisterArgumentsModel: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("DeregisterArgumentsModel: map too large") + return fmt.Errorf("map too large for DeregisterArgumentsModel") } } } diff --git a/pkg/capabilities/admin/provider/weight/datamodel/json_gen.go b/pkg/capabilities/admin/provider/weight/datamodel/json_gen.go index fae8f59..a8f1f81 100644 --- a/pkg/capabilities/admin/provider/weight/datamodel/json_gen.go +++ b/pkg/capabilities/admin/provider/weight/datamodel/json_gen.go @@ -31,16 +31,16 @@ func (t *SetArgumentsModel) MarshalDagJSON(w io.Writer) error { // t.Provider (did.DID) (struct) if len("provider") > 8192 { - return fmt.Errorf("String in field \"provider\" was too long") + return fmt.Errorf("string in field \"provider\" was too long") } if err := jw.WriteString(string("provider")); err != nil { - return fmt.Errorf("\"provider\": %w", err) + return fmt.Errorf("writing string for field \"provider\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if err := t.Provider.MarshalDagJSON(jw); err != nil { - return fmt.Errorf("t.Provider: %w", err) + return fmt.Errorf("marshaling field t.Provider: %w", err) } written++ if written > 0 { @@ -51,17 +51,17 @@ func (t *SetArgumentsModel) MarshalDagJSON(w io.Writer) error { // t.ReplicationWeight (int64) (int64) if len("replicationWeight") > 8192 { - return fmt.Errorf("String in field \"replicationWeight\" was too long") + return fmt.Errorf("string in field \"replicationWeight\" was too long") } if err := jw.WriteString(string("replicationWeight")); err != nil { - return fmt.Errorf("\"replicationWeight\": %w", err) + return fmt.Errorf("writing string for field \"replicationWeight\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if err := jw.WriteInt64(int64(t.ReplicationWeight)); err != nil { - return fmt.Errorf("t.ReplicationWeight: %w", err) + return fmt.Errorf("writing int64 for field t.ReplicationWeight: %w", err) } written++ @@ -73,17 +73,17 @@ func (t *SetArgumentsModel) MarshalDagJSON(w io.Writer) error { // t.Weight (int64) (int64) if len("weight") > 8192 { - return fmt.Errorf("String in field \"weight\" was too long") + return fmt.Errorf("string in field \"weight\" was too long") } if err := jw.WriteString(string("weight")); err != nil { - return fmt.Errorf("\"weight\": %w", err) + return fmt.Errorf("writing string for field \"weight\": %w", err) } if err := jw.WriteObjectColon(); err != nil { return err } if err := jw.WriteInt64(int64(t.Weight)); err != nil { - return fmt.Errorf("t.Weight: %w", err) + return fmt.Errorf("writing int64 for field t.Weight: %w", err) } written++ @@ -102,27 +102,27 @@ func (t *SetArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("SetArgumentsModel: %w", err) + return fmt.Errorf("reading object open for SetArgumentsModel: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("SetArgumentsModel: %w", err) + return fmt.Errorf("peeking object close for SetArgumentsModel: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("SetArgumentsModel: %w", err) + return fmt.Errorf("reading object close for SetArgumentsModel: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("SetArgumentsModel: string too large") + return fmt.Errorf("reading string for field SetArgumentsModel: string too large") } - return fmt.Errorf("SetArgumentsModel: %w", err) + return fmt.Errorf("reading string for field SetArgumentsModel: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("SetArgumentsModel: %w", err) + return fmt.Errorf("reading object colon for field SetArgumentsModel: %w", err) } switch name { @@ -139,7 +139,7 @@ func (t *SetArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { nval, err := jr.ReadNumberAsInt64() if err != nil { - return fmt.Errorf("t.ReplicationWeight: %w", err) + return fmt.Errorf("reading int64 for field t.ReplicationWeight: %w", err) } t.ReplicationWeight = int64(nval) @@ -151,7 +151,7 @@ func (t *SetArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { nval, err := jr.ReadNumberAsInt64() if err != nil { - return fmt.Errorf("t.Weight: %w", err) + return fmt.Errorf("reading int64 for field t.Weight: %w", err) } t.Weight = int64(nval) @@ -159,19 +159,19 @@ func (t *SetArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("SetArgumentsModel: ignoring field %s: %w", name, err) + return fmt.Errorf("ignoring field %s for SetArgumentsModel: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("SetArgumentsModel: %w", err) + return fmt.Errorf("reading object close or comma for field SetArgumentsModel: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("SetArgumentsModel: map too large") + return fmt.Errorf("map too large for SetArgumentsModel") } } } From bda5b2bfdf2bf009f0d1bb3910f4c9ab07c060aa Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 15:12:02 +0100 Subject: [PATCH 05/23] chore: use error definitions from libforge --- go.mod | 2 +- go.sum | 2 ++ pkg/internal/ipldutil/cids.go | 17 ----------------- pkg/service/handlers/access_confirm.go | 7 ++----- pkg/service/handlers/access_confirm_test.go | 4 ++-- pkg/service/handlers/access_delegate.go | 9 ++------- pkg/service/handlers/access_request.go | 11 +++-------- pkg/service/handlers/access_request_test.go | 2 +- pkg/service/handlers/blob_add.go | 3 ++- pkg/service/handlers/blob_add_test.go | 3 ++- pkg/service/handlers/index_add.go | 9 +++------ pkg/service/handlers/index_add_test.go | 5 +++-- pkg/service/handlers/provider_add.go | 11 ++--------- pkg/service/handlers/provider_add_test.go | 4 ++-- pkg/service/handlers/space_info.go | 4 +--- pkg/service/handlers/space_info_test.go | 2 +- pkg/service/handlers/ucan_conclude.go | 9 +-------- pkg/service/handlers/ucan_conclude_test.go | 2 +- pkg/service/handlers/upload_add.go | 3 ++- pkg/service/handlers/upload_add_test.go | 3 ++- 20 files changed, 35 insertions(+), 77 deletions(-) delete mode 100644 pkg/internal/ipldutil/cids.go diff --git a/go.mod b/go.mod index c9d79be..45fe766 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f + github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 diff --git a/go.sum b/go.sum index dc51231..039d557 100644 --- a/go.sum +++ b/go.sum @@ -163,6 +163,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f h1:bxGaLAF+kD5ADpX5N68hwBn2Wc+qW4cC9ptCzymQ7xs= github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f/go.mod h1:IdNOBIQeH59dG99FnLmqrwrvaJ4Akm4IPUng8DeqFig= +github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e h1:t+/Q3U7qeUkkaOoUx0XOY3AjFHx+2k9HZ7MVWpVW/1I= +github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e/go.mod h1:IdNOBIQeH59dG99FnLmqrwrvaJ4Akm4IPUng8DeqFig= github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b h1:8tvw5e7S1ntUnm0v/OTWLZelbJWQcho/WZI+ttCPv+A= github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/pkg/internal/ipldutil/cids.go b/pkg/internal/ipldutil/cids.go deleted file mode 100644 index cb94fc0..0000000 --- a/pkg/internal/ipldutil/cids.go +++ /dev/null @@ -1,17 +0,0 @@ -package ipldutil - -import ( - "github.com/ipfs/go-cid" - "github.com/ipld/go-ipld-prime" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" -) - -// ToCID converts an [ipld.Link] to a [cid.Cid]. It'll type assert the link to a -// [cidlink.Link] first, and if that fails it'll attempt to parse the string -// representation of the link as a CID. -func ToCID(l ipld.Link) (cid.Cid, error) { - if c, ok := l.(cidlink.Link); ok { - return c.Cid, nil - } - return cid.Parse(l.String()) -} diff --git a/pkg/service/handlers/access_confirm.go b/pkg/service/handlers/access_confirm.go index 269c2c6..7cb60d2 100644 --- a/pkg/service/handlers/access_confirm.go +++ b/pkg/service/handlers/access_confirm.go @@ -8,7 +8,6 @@ import ( "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/pkg/identity" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" - "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/ipld" "github.com/fil-forge/ucantone/ipld/datamodel" @@ -21,8 +20,6 @@ import ( "go.uber.org/zap" ) -const InvalidAccessConfirmInvocationErrorName = "InvalidAccessConfirmInvocation" - func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { log := logger.With(zap.String("handler", access.ConfirmCommand)) return Handler{ @@ -34,13 +31,13 @@ func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_s args := req.Task().BindArguments() if req.Invocation().Subject().DID() != id.Signer.DID() { log.Warn("not a valid invocation", zap.Stringer("subject", req.Invocation().Subject().DID())) - return res.SetFailure(errors.New(InvalidAccessConfirmInvocationErrorName, "not a valid access/confirm delegation")) + return res.SetFailure(access.ErrInvalidAccessConfirmSubject) } accountDID, err := didmailto.Parse(args.Issuer.DID().String()) if err != nil { log.Warn("invalid issuer DID", zap.Stringer("issuer", args.Issuer.DID()), zap.Error(err)) - return res.SetFailure(errors.New(InvalidAccessConfirmInvocationErrorName, "invalid issuer DID in delegation")) + return res.SetFailure(access.ErrInvalidAccessConfirmIssuer) } // Create a absentee signer for the account that authorized the delegation diff --git a/pkg/service/handlers/access_confirm_test.go b/pkg/service/handlers/access_confirm_test.go index 9b457a2..12135c1 100644 --- a/pkg/service/handlers/access_confirm_test.go +++ b/pkg/service/handlers/access_confirm_test.go @@ -60,7 +60,7 @@ func TestAccessConfirmHandler(t *testing.T) { model := edm.ErrorModel{} err = datamodel.Rebind(datamodel.NewAny(fail), &model) require.NoError(t, err) - require.Equal(t, InvalidAccessConfirmInvocationErrorName, model.Name()) + require.Equal(t, access.InvalidAccessConfirmSubjectErrorName, model.Name()) }) t.Run("invalid issuer DID", func(t *testing.T) { @@ -102,7 +102,7 @@ func TestAccessConfirmHandler(t *testing.T) { model := edm.ErrorModel{} err = datamodel.Rebind(datamodel.NewAny(fail), &model) require.NoError(t, err) - require.Equal(t, InvalidAccessConfirmInvocationErrorName, model.Name()) + require.Equal(t, access.InvalidAccessConfirmIssuerErrorName, model.Name()) }) t.Run("success", func(t *testing.T) { diff --git a/pkg/service/handlers/access_delegate.go b/pkg/service/handlers/access_delegate.go index 3ecbc6c..5fe8140 100644 --- a/pkg/service/handlers/access_delegate.go +++ b/pkg/service/handlers/access_delegate.go @@ -13,11 +13,6 @@ import ( "go.uber.org/zap" ) -const ( - DelegationNotFoundErrorName = "DelegationNotFound" - InsufficientStorageErrorName = "InsufficientStorage" -) - func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { log := logger.With(zap.String("handler", access.DelegateCommand)) return Handler{ @@ -42,7 +37,7 @@ func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioni return fmt.Errorf("listing service providers: %w", err) } if len(providers) == 0 { - return res.SetFailure(errors.New(InsufficientStorageErrorName, "space has no storage provider")) + return res.SetFailure(errors.New(access.InsufficientStorageErrorName, "space has no storage provider")) } dlgs, err := extractDelegations(args, req.Metadata()) @@ -73,7 +68,7 @@ func extractDelegations(args *access.DelegateArguments, meta ucan.Container) ([] for _, link := range args.Delegations { d, ok := all[link] if !ok { - return nil, errors.New(DelegationNotFoundErrorName, "delegation not found: %s", link.String()) + return nil, errors.New(access.DelegationNotFoundErrorName, "delegation not found: %s", link.String()) } dlgs = append(dlgs, d) } diff --git a/pkg/service/handlers/access_request.go b/pkg/service/handlers/access_request.go index 41e8744..823d014 100644 --- a/pkg/service/handlers/access_request.go +++ b/pkg/service/handlers/access_request.go @@ -21,19 +21,14 @@ import ( "github.com/fil-forge/ucantone/ucan/promise" ) -const ( - InvalidAuthorizationAccountErrorName = "InvalidAuthorizationAccount" - InvalidAuthorizationAudienceErrorName = "InvalidAuthorizationAudience" -) - // Standard email flow - create confirmation delegation and send email // We allow granting access within the next 15 minutes const confirmationTTL = time.Minute * 15 var ( - ErrMissingAuthorizationAccount = errors.New(InvalidAuthorizationAccountErrorName, "missing authorization account DID") - ErrInvalidAuthorizationAccount = errors.New(InvalidAuthorizationAccountErrorName, "invalid authorization account DID") - ErrInvalidAuthorizationAudience = errors.New(InvalidAuthorizationAudienceErrorName, "invalid authorization audience DID") + ErrMissingAuthorizationAccount = errors.New(access.InvalidAuthorizationAccountErrorName, "missing authorization account DID") + ErrInvalidAuthorizationAccount = errors.New(access.InvalidAuthorizationAccountErrorName, "invalid authorization account DID") + ErrInvalidAuthorizationAudience = errors.New(access.InvalidAuthorizationAudienceErrorName, "invalid authorization audience DID") ) func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identity, mailer mailer.Mailer, logger *zap.Logger) Handler { diff --git a/pkg/service/handlers/access_request_test.go b/pkg/service/handlers/access_request_test.go index ef2eae2..65af48a 100644 --- a/pkg/service/handlers/access_request_test.go +++ b/pkg/service/handlers/access_request_test.go @@ -132,7 +132,7 @@ func TestAccessRequestHandler(t *testing.T) { model := edm.ErrorModel{} err = datamodel.Rebind(datamodel.NewAny(x), &model) require.NoError(t, err) - require.Equal(t, InvalidAuthorizationAccountErrorName, model.Name()) + require.Equal(t, access.InvalidAuthorizationAccountErrorName, model.Name()) }) t.Run("mailer error", func(t *testing.T) { diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index 733af14..2cd863d 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -5,6 +5,7 @@ import ( "crypto/ed25519" "fmt" + accesscaps "github.com/fil-forge/libforge/capabilities/access" blobcaps "github.com/fil-forge/libforge/capabilities/blob" httpcaps "github.com/fil-forge/libforge/capabilities/http" "github.com/fil-forge/libforge/digestutil" @@ -61,7 +62,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv return fmt.Errorf("listing service providers: %w", err) } if len(providers) == 0 { - return res.SetFailure(errors.New(InsufficientStorageErrorName, "space has no storage provider")) + return res.SetFailure(errors.New(accesscaps.InsufficientStorageErrorName, "space has no storage provider")) } reg, err := blobRegistry.Get(req.Context(), space, blob.Digest) diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go index 2fbf069..1a651fb 100644 --- a/pkg/service/handlers/blob_add_test.go +++ b/pkg/service/handlers/blob_add_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/fil-forge/libforge/capabilities" + accesscaps "github.com/fil-forge/libforge/capabilities/access" blobcaps "github.com/fil-forge/libforge/capabilities/blob" httpcaps "github.com/fil-forge/libforge/capabilities/http" "github.com/fil-forge/libforge/didmailto" @@ -189,7 +190,7 @@ func TestBlobAddHandler(t *testing.T) { model := edm.ErrorModel{} err = datamodel.Rebind(datamodel.NewAny(fail), &model) require.NoError(t, err) - require.Equal(t, handlers.InsufficientStorageErrorName, model.Name()) + require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) }) t.Run("no candidates available", func(t *testing.T) { diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go index 8c739ef..097f41b 100644 --- a/pkg/service/handlers/index_add.go +++ b/pkg/service/handlers/index_add.go @@ -5,6 +5,7 @@ import ( "go.uber.org/zap" + accesscaps "github.com/fil-forge/libforge/capabilities/access" indexcaps "github.com/fil-forge/libforge/capabilities/index" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/indexerclient" @@ -15,10 +16,6 @@ import ( "github.com/fil-forge/ucantone/execution/bindexec" ) -const IndexNotFoundErrorName = "IndexNotFound" - -var ErrIndexNotFound = errors.New(IndexNotFoundErrorName, "index not found in space") - func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) Handler { log := logger.With(zap.String("handler", indexcaps.AddCommand)) return Handler{ @@ -44,7 +41,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser } if len(provs) == 0 { log.Warn("space has no service provider") - return res.SetFailure(errors.New(InsufficientStorageErrorName, "space has no service provider")) + return res.SetFailure(errors.New(accesscaps.InsufficientStorageErrorName, "space has no service provider")) } // Ensure the index is stored in the agent's space @@ -52,7 +49,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser if err != nil { if errors.Is(err, blobregistry.ErrEntryNotFound) { log.Warn("index not found in space") - return res.SetFailure(ErrIndexNotFound) + return res.SetFailure(indexcaps.ErrIndexNotFound) } log.Error("failed to get index from blob registry", zap.Error(err)) return err diff --git a/pkg/service/handlers/index_add_test.go b/pkg/service/handlers/index_add_test.go index b881065..80ca91b 100644 --- a/pkg/service/handlers/index_add_test.go +++ b/pkg/service/handlers/index_add_test.go @@ -6,6 +6,7 @@ import ( "net/url" "testing" + accesscaps "github.com/fil-forge/libforge/capabilities/access" assertcaps "github.com/fil-forge/libforge/capabilities/assert" blobcaps "github.com/fil-forge/libforge/capabilities/blob" contentcaps "github.com/fil-forge/libforge/capabilities/content" @@ -126,7 +127,7 @@ func TestIndexAddHandler(t *testing.T) { model := edm.ErrorModel{} require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) - require.Equal(t, handlers.InsufficientStorageErrorName, model.Name()) + require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) }) t.Run("index not found in space", func(t *testing.T) { @@ -154,7 +155,7 @@ func TestIndexAddHandler(t *testing.T) { model := edm.ErrorModel{} require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) - require.Equal(t, handlers.IndexNotFoundErrorName, model.Name()) + require.Equal(t, indexcaps.IndexNotFoundErrorName, model.Name()) }) t.Run("retrieval auth supplied publishes index claim", func(t *testing.T) { diff --git a/pkg/service/handlers/provider_add.go b/pkg/service/handlers/provider_add.go index 4a517b6..835e90b 100644 --- a/pkg/service/handlers/provider_add.go +++ b/pkg/service/handlers/provider_add.go @@ -14,13 +14,6 @@ import ( "go.uber.org/zap" ) -const ( - InvalidAccountErrorName = "InvalidAccount" - AccountPlanMissingErrorName = "AccountPlanMissing" -) - -var ErrAccountPlanMissing = errors.New(AccountPlanMissingErrorName, "account does not have an active payment plan") - func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) Handler { log := logger.With(zap.String("handler", providercaps.AddCommand)) return Handler{ @@ -33,7 +26,7 @@ func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSv account, err := didmailto.Parse(req.Invocation().Subject().DID().String()) if err != nil { log.Warn("invalid account", zap.Stringer("account", req.Invocation().Subject().DID())) - return res.SetFailure(errors.New(InvalidAccountErrorName, "invalid account DID: %v", err)) + return res.SetFailure(errors.New(providercaps.InvalidAccountErrorName, "invalid account DID: %v", err)) } serviceProvider := args.Provider space := args.Consumer @@ -53,7 +46,7 @@ func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSv if err != nil { if errors.Is(err, billing.ErrMissingPaymentPlan) { log.Warn("account does not have an active payment plan") - return res.SetFailure(ErrAccountPlanMissing) + return res.SetFailure(providercaps.ErrAccountPlanMissing) } log.Error("failed to check payment plan", zap.Error(err)) return fmt.Errorf("checking payment plan: %w", err) diff --git a/pkg/service/handlers/provider_add_test.go b/pkg/service/handlers/provider_add_test.go index 2268477..8e1bfca 100644 --- a/pkg/service/handlers/provider_add_test.go +++ b/pkg/service/handlers/provider_add_test.go @@ -170,7 +170,7 @@ func TestProviderAddHandler(t *testing.T) { model := edm.ErrorModel{} require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) - require.Equal(t, handlers.InvalidAccountErrorName, model.Name()) + require.Equal(t, providercaps.InvalidAccountErrorName, model.Name()) }) t.Run("missing payment plan", func(t *testing.T) { @@ -201,7 +201,7 @@ func TestProviderAddHandler(t *testing.T) { model := edm.ErrorModel{} require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) - require.Equal(t, handlers.AccountPlanMissingErrorName, model.Name()) + require.Equal(t, providercaps.AccountPlanMissingErrorName, model.Name()) }) t.Run("provider not allowed", func(t *testing.T) { diff --git a/pkg/service/handlers/space_info.go b/pkg/service/handlers/space_info.go index 9bba718..5c2fdc8 100644 --- a/pkg/service/handlers/space_info.go +++ b/pkg/service/handlers/space_info.go @@ -11,8 +11,6 @@ import ( "go.uber.org/zap" ) -const UnknownSpaceErrorName = "UnknownSpace" - // This handler returns info about a space, including its providers. func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { log := logger.With(zap.String("handler", spacecaps.InfoCommand)) @@ -28,7 +26,7 @@ func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logg if !strings.HasPrefix(space.DID().String(), "did:key:") { log.Warn("non-did:key space info requested") - return res.SetFailure(errors.New(UnknownSpaceErrorName, "can only get info for did:key spaces")) + return res.SetFailure(errors.New(spacecaps.UnknownSpaceErrorName, "can only get info for did:key spaces")) } providers, err := provisioningSvc.ListServiceProviders(req.Context(), space.DID()) diff --git a/pkg/service/handlers/space_info_test.go b/pkg/service/handlers/space_info_test.go index b824740..8ef9b28 100644 --- a/pkg/service/handlers/space_info_test.go +++ b/pkg/service/handlers/space_info_test.go @@ -130,6 +130,6 @@ func TestSpaceInfoHandler(t *testing.T) { model := edm.ErrorModel{} require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) - require.Equal(t, handlers.UnknownSpaceErrorName, model.Name()) + require.Equal(t, spacecaps.UnknownSpaceErrorName, model.Name()) }) } diff --git a/pkg/service/handlers/ucan_conclude.go b/pkg/service/handlers/ucan_conclude.go index d551934..c09134f 100644 --- a/pkg/service/handlers/ucan_conclude.go +++ b/pkg/service/handlers/ucan_conclude.go @@ -15,13 +15,6 @@ import ( "go.uber.org/zap" ) -const ( - InvalidInvocationErrorName = "InvalidInvocation" - ConclusionReceiptNotFoundErrorName = "ConclusionReceiptNotFound" -) - -var ErrConclusionReceiptNotFound = errors.New(ConclusionReceiptNotFoundErrorName, "conclusion receipt not found") - type ConclusionHandlerFunc func(context.Context, ucan.Invocation, ucan.Receipt, ucan.Container) error // ConclusionHandler is the definition of a handler for an invocation conclusion @@ -64,7 +57,7 @@ func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handl } if rcpt == nil { log.Warn("receipt not found in invocation metadata") - return res.SetFailure(ErrConclusionReceiptNotFound) + return res.SetFailure(ucancaps.ErrConclusionReceiptNotFound) } log = log.With(zap.Stringer("task", rcpt.Ran())) diff --git a/pkg/service/handlers/ucan_conclude_test.go b/pkg/service/handlers/ucan_conclude_test.go index 2e56d0d..424f432 100644 --- a/pkg/service/handlers/ucan_conclude_test.go +++ b/pkg/service/handlers/ucan_conclude_test.go @@ -76,7 +76,7 @@ func TestUCANConcludeHandler(t *testing.T) { model := edm.ErrorModel{} err = datamodel.Rebind(datamodel.NewAny(fail), &model) require.NoError(t, err) - require.Equal(t, handlers.ConclusionReceiptNotFoundErrorName, model.Name()) + require.Equal(t, ucancaps.ConclusionReceiptNotFoundErrorName, model.Name()) }) t.Run("unknown invocation returns success", func(t *testing.T) { diff --git a/pkg/service/handlers/upload_add.go b/pkg/service/handlers/upload_add.go index b0de3d6..47e4116 100644 --- a/pkg/service/handlers/upload_add.go +++ b/pkg/service/handlers/upload_add.go @@ -3,6 +3,7 @@ package handlers import ( "fmt" + accesscaps "github.com/fil-forge/libforge/capabilities/access" uploadcaps "github.com/fil-forge/libforge/capabilities/upload" "github.com/fil-forge/sprue/pkg/provisioning" upload_store "github.com/fil-forge/sprue/pkg/store/upload" @@ -39,7 +40,7 @@ func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore uplo } if len(provs) == 0 { log.Warn("space has no service provider") - return res.SetFailure(errors.New(InsufficientStorageErrorName, "space has no service provider")) + return res.SetFailure(errors.New(accesscaps.InsufficientStorageErrorName, "space has no service provider")) } err = uploadStore.Upsert(req.Context(), space.DID(), args.Root, args.Index, args.Shards, cause) diff --git a/pkg/service/handlers/upload_add_test.go b/pkg/service/handlers/upload_add_test.go index 3c299c3..1aa58b1 100644 --- a/pkg/service/handlers/upload_add_test.go +++ b/pkg/service/handlers/upload_add_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + accesscaps "github.com/fil-forge/libforge/capabilities/access" uploadcaps "github.com/fil-forge/libforge/capabilities/upload" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" @@ -105,7 +106,7 @@ func TestUploadAddHandler(t *testing.T) { model := edm.ErrorModel{} require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) - require.Equal(t, handlers.InsufficientStorageErrorName, model.Name()) + require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) // Nothing should have been persisted. exists, err := deps.store.Exists(ctx, space.DID(), root) From 4e81137ac3ebcaca15b286933192b8ce0cf9b7c8 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 16:54:07 +0100 Subject: [PATCH 06/23] fix: missing change --- pkg/service/handlers/access_request.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/service/handlers/access_request.go b/pkg/service/handlers/access_request.go index 823d014..776ebc9 100644 --- a/pkg/service/handlers/access_request.go +++ b/pkg/service/handlers/access_request.go @@ -43,14 +43,14 @@ func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identit account, err := didmailto.Parse(args.Issuer.String()) if err != nil { log.Warn("failed to parse mailto DID", zap.Stringer("account", args.Issuer)) - return res.SetFailure(errors.New(InvalidAuthorizationAccountErrorName, "invalid authorization account DID: %v", err)) + return res.SetFailure(errors.New(access.InvalidAuthorizationAccountErrorName, "invalid authorization account DID: %v", err)) } // we should be able to extract the email from the DID since we just // parsed it as a did:mailto: email, err := didmailto.Email(account) if err != nil { log.Warn("failed to extract email from DID", zap.Stringer("account", args.Issuer)) - return res.SetFailure(errors.New(InvalidAuthorizationAccountErrorName, "invalid authorization account DID: %v", err)) + return res.SetFailure(errors.New(access.InvalidAuthorizationAccountErrorName, "invalid authorization account DID: %v", err)) } audience := req.Invocation().Subject().DID() agent := req.Invocation().Issuer().DID() From 043567d816cf1b4ee795e8785597c5a9a2607e6a Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 16:57:02 +0100 Subject: [PATCH 07/23] chore: mod tidy --- go.sum | 5 ----- 1 file changed, 5 deletions(-) diff --git a/go.sum b/go.sum index 039d557..b612562 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/alanshaw/dag-json-gen v0.0.4 h1:qoryz04TVH6zu16NRFnzgolzQGaPfTvoIawv/F5rDoY= -github.com/alanshaw/dag-json-gen v0.0.4/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= github.com/alanshaw/dag-json-gen v0.0.5 h1:jUOqsTrfZ7ddkBqAsx/xbCeJtpe70jrFL2Z+i4qQB1U= github.com/alanshaw/dag-json-gen v0.0.5/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -161,8 +159,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f h1:bxGaLAF+kD5ADpX5N68hwBn2Wc+qW4cC9ptCzymQ7xs= -github.com/fil-forge/libforge v0.0.0-20260511104837-383044b9b01f/go.mod h1:IdNOBIQeH59dG99FnLmqrwrvaJ4Akm4IPUng8DeqFig= github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e h1:t+/Q3U7qeUkkaOoUx0XOY3AjFHx+2k9HZ7MVWpVW/1I= github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e/go.mod h1:IdNOBIQeH59dG99FnLmqrwrvaJ4Akm4IPUng8DeqFig= github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b h1:8tvw5e7S1ntUnm0v/OTWLZelbJWQcho/WZI+ttCPv+A= @@ -954,7 +950,6 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From d143017cc7d24aff4023733f1425b3b7b2b6193d Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 17:09:45 +0100 Subject: [PATCH 08/23] fix: remove last ipld prime reference --- go.mod | 4 - go.sum | 606 ---------------------------------- pkg/store/upload/aws/store.go | 52 +-- 3 files changed, 11 insertions(+), 651 deletions(-) diff --git a/go.mod b/go.mod index 45fe766..3be4d82 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 - github.com/ipld/go-ipld-prime v0.22.0 github.com/jackc/pgx/v5 v5.8.0 github.com/labstack/echo/v4 v4.14.0 github.com/multiformats/go-multihash v0.2.3 @@ -109,7 +108,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect @@ -145,8 +143,6 @@ require ( golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect pitr.ca/jsontokenizer v0.3.0 // indirect diff --git a/go.sum b/go.sum index b612562..c64d70e 100644 --- a/go.sum +++ b/go.sum @@ -1,58 +1,13 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alanshaw/dag-json-gen v0.0.5 h1:jUOqsTrfZ7ddkBqAsx/xbCeJtpe70jrFL2Z+i4qQB1U= github.com/alanshaw/dag-json-gen v0.0.5/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA= github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 h1:N4lRUXZpZ1KVEUn6hxtco/1d2lgYhNn1fHkkl8WhlyQ= @@ -99,24 +54,14 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 h1:XQTQTF75vnug2TXS8m7CVJfC2nni github.com/aws/aws-sdk-go-v2/service/sts v1.41.8/go.mod h1:Xgx+PR1NUOjNmQY+tRMnouRp83JRM8pRMw/vCaVhPkI= github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.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= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -125,12 +70,8 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -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.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= @@ -149,14 +90,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e h1:t+/Q3U7qeUkkaOoUx0XOY3AjFHx+2k9HZ7MVWpVW/1I= @@ -165,13 +98,8 @@ github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b h1:8tvw5e7S1ntU github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -181,113 +109,19 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= -github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -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/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/go-cid v0.6.1 h1:T5TnNb08+ueovG76Z5gx1L4Y7QOaGTXHg1F6raWFxIc= github.com/ipfs/go-cid v0.6.1/go.mod h1:zrY0SwOhjrrIdfPQ/kf+k1sXyJ0QE7cMxfCployLBs0= -github.com/ipld/go-ipld-prime v0.22.0 h1:YJhDhjEOvOYaqshd3b4atIWUoRg/rKrgmwCyUHwlbuY= -github.com/ipld/go-ipld-prime v0.22.0/go.mod h1:ol7vKxOOVgEh0iAPuiDalM+0gScXVMA5ZZa4DVrTnEA= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -296,25 +130,14 @@ github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -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= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -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.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/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/labstack/echo/v4 v4.14.0 h1:+tiMrDLxwv6u0oKtD03mv+V1vXXB3wCqPHJqPuIe+7M= @@ -325,15 +148,12 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -343,21 +163,12 @@ github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5L github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.68 h1:hTqSIfLlpXaKuNy4baAp4Jjy2sqZEN9hRxD0M4aOfrQ= github.com/minio/minio-go/v7 v7.0.68/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= @@ -378,11 +189,8 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -395,104 +203,65 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multibase v0.3.0 h1:8helZD2+4Db7NNWFiktk2NePbF0boolBe6bDQvM4r68= github.com/multiformats/go-multibase v0.3.0/go.mod h1:MoBLQPCkRTOL3eveIPO81860j2AQY8JwcnNlRkGRUfI= -github.com/multiformats/go-multicodec v0.10.0 h1:UpP223cig/Cx8J76jWt91njpK3GTAO1w02sdcjZDSuc= -github.com/multiformats/go-multicodec v0.10.0/go.mod h1:wg88pM+s2kZJEQfRCKBNU+g32F5aWBEjyFHXvZLTcLI= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI= github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a h1:cgqrm0F3zwf9IPzca7xN4w+Zy6MC9ZkPvAC8QEWa/iQ= -github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a/go.mod h1:ocZfO/tLSHqfScRDNTJbAJR1by4D1lewauX9OwTaPuY= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= -github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= -github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= -github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= @@ -507,37 +276,18 @@ github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYI github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= -github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= -github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= @@ -558,412 +308,59 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -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-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= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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-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-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -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= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -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= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ= @@ -978,6 +375,3 @@ pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= pitr.ca/jsontokenizer v0.3.0 h1:Qr70hk4/wcpFEgu/6aJ+nvYQ6x/xS0WOkC627ceiI/M= pitr.ca/jsontokenizer v0.3.0/go.mod h1:3DJdA2QNOU6cI0XkH6pRKZ4Oe8G5SDRUQ6PFAwaQ3YY= -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/store/upload/aws/store.go b/pkg/store/upload/aws/store.go index e9ebd20..9ce325d 100644 --- a/pkg/store/upload/aws/store.go +++ b/pkg/store/upload/aws/store.go @@ -16,11 +16,8 @@ import ( "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/ipfs/go-cid" - "github.com/ipld/go-ipld-prime/codec/dagcbor" - "github.com/ipld/go-ipld-prime/fluent" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/multiformats/go-multihash" ) @@ -579,18 +576,12 @@ func itemToShards(item map[string]types.AttributeValue) ([]cid.Cid, error) { } func encodeShards(shards []cid.Cid) (cid.Cid, []byte, error) { - n, err := fluent.BuildList(basicnode.Prototype.List, int64(len(shards)), func(la fluent.ListAssembler) { - for _, s := range shards { - la.AssembleValue().AssignLink(cidlink.Link{Cid: s}) - } - }) - if err != nil { - return cid.Undef, nil, fmt.Errorf("encoding shards: %w", err) - } var buf bytes.Buffer - if err := dagcbor.Encode(n, &buf); err != nil { - return cid.Undef, nil, fmt.Errorf("encoding shards to DAG-CBOR: %w", err) + model := datamodel.NewAny(shards) + if err := model.MarshalCBOR(&buf); err != nil { + return cid.Undef, nil, fmt.Errorf("marshaling shards to DAG-CBOR: %w", err) } + c, err := cid.Prefix{ Version: 1, Codec: cid.DagCBOR, @@ -604,34 +595,13 @@ func encodeShards(shards []cid.Cid) (cid.Cid, []byte, error) { } func decodeShards(data []byte) ([]cid.Cid, error) { - r := bytes.NewReader(data) - nb := basicnode.Prototype.List.NewBuilder() - err := dagcbor.Decode(nb, r) - if err != nil { - return nil, fmt.Errorf("decoding shards from DAG-CBOR: %w", err) + model := datamodel.NewAny([]cid.Cid{}) + if err := model.UnmarshalCBOR(bytes.NewReader(data)); err != nil { + return nil, fmt.Errorf("unmarshaling shards from DAG-CBOR: %w", err) } - n := nb.Build() - shards := []cid.Cid{} - iter := n.ListIterator() - for !iter.Done() { - _, shardNode, err := iter.Next() - if err != nil { - return nil, fmt.Errorf("iterating over shards list: %w", err) - } - sl, err := shardNode.AsLink() - if err != nil { - return nil, fmt.Errorf("getting shard link: %w", err) - } - var shard cid.Cid - if cl, ok := sl.(cidlink.Link); ok { - shard = cl.Cid - } else { - shard, err = cid.Parse(sl.String()) - if err != nil { - return nil, fmt.Errorf("parsing shard CID: %w", err) - } - } - shards = append(shards, shard) + shards, ok := model.Value.([]cid.Cid) + if !ok { + return nil, fmt.Errorf("unexpected type for shards list: %T", model.Value) } return shards, nil } From daf9d39dd3135dc22a4d3f738245184005a60c17 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 11 May 2026 21:33:40 +0100 Subject: [PATCH 09/23] chore: upgrade libforge --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3be4d82..af62a1d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e + github.com/fil-forge/libforge v0.0.0-20260511203158-705fd4124b8c github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 diff --git a/go.sum b/go.sum index c64d70e..f011a29 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e h1:t+/Q3U7qeUkkaOoUx0XOY3AjFHx+2k9HZ7MVWpVW/1I= -github.com/fil-forge/libforge v0.0.0-20260511140612-bf046e51661e/go.mod h1:IdNOBIQeH59dG99FnLmqrwrvaJ4Akm4IPUng8DeqFig= +github.com/fil-forge/libforge v0.0.0-20260511203158-705fd4124b8c h1:m7F+gafris6u7W27q9kD1llVzqGX6CU9pzmXvSAUfuU= +github.com/fil-forge/libforge v0.0.0-20260511203158-705fd4124b8c/go.mod h1:kYmQIZuSsjWPDD7pGW3yb5/FnhpWPOIko9Gme1yg9EY= github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b h1:8tvw5e7S1ntUnm0v/OTWLZelbJWQcho/WZI+ttCPv+A= github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= From 43b47ac0aea6cc29eb047ccee675b03ebb734f14 Mon Sep 17 00:00:00 2001 From: ash Date: Fri, 15 May 2026 16:44:54 +0100 Subject: [PATCH 10/23] chore: upgrade for deferred types in ucantone (#3) Upgrades ucantone and makes changes here to support the deferred argument/metadata types. refs https://github.com/fil-forge/ucantone/pull/8 --- go.mod | 4 +- go.sum | 8 +- pkg/lib/ucan_client/execute.go | 69 ++++++------ pkg/lib/ucan_server/email_auth.go | 15 ++- pkg/lib/ucan_server/events.go | 38 ++++--- pkg/lib/ucan_server/validation.go | 7 +- pkg/lib/zapipld/raw_map.go | 29 +++++ pkg/lib/zapipld/raw_map_test.go | 41 +++++++ pkg/service/handlers/access_claim_test.go | 42 ++++---- pkg/service/handlers/access_confirm.go | 2 +- pkg/service/handlers/access_confirm_test.go | 42 ++++---- pkg/service/handlers/access_delegate.go | 2 +- pkg/service/handlers/access_delegate_test.go | 13 +-- pkg/service/handlers/access_request.go | 15 ++- pkg/service/handlers/access_request_test.go | 17 ++- .../handlers/admin_provider_deregister.go | 2 +- .../admin_provider_deregister_test.go | 26 ++--- .../handlers/admin_provider_list_test.go | 34 +++--- .../handlers/admin_provider_register.go | 2 +- .../handlers/admin_provider_register_test.go | 32 +++--- .../handlers/admin_provider_weight_set.go | 2 +- .../admin_provider_weight_set_test.go | 21 ++-- pkg/service/handlers/blob_add.go | 102 +++++++----------- pkg/service/handlers/blob_add_test.go | 81 +++++--------- pkg/service/handlers/blob_list.go | 2 +- pkg/service/handlers/blob_list_test.go | 51 +++++---- pkg/service/handlers/index_add.go | 2 +- pkg/service/handlers/index_add_test.go | 27 ++--- pkg/service/handlers/provider_add.go | 2 +- pkg/service/handlers/provider_add_test.go | 43 ++++---- pkg/service/handlers/space_info_test.go | 27 +++-- pkg/service/handlers/ucan_conclude.go | 2 +- .../handlers/ucan_conclude_http_put.go | 64 +++++------ .../handlers/ucan_conclude_http_put_test.go | 22 ++-- pkg/service/handlers/ucan_conclude_test.go | 28 ++--- pkg/service/handlers/upload_add.go | 2 +- pkg/service/handlers/upload_add_test.go | 26 ++--- pkg/service/handlers/upload_list.go | 2 +- pkg/service/handlers/upload_list_test.go | 59 +++++----- pkg/service/handlers/upload_shard_list.go | 2 +- .../handlers/upload_shard_list_test.go | 43 ++++---- pkg/store/agent/agent_test.go | 6 +- 42 files changed, 507 insertions(+), 549 deletions(-) create mode 100644 pkg/lib/zapipld/raw_map.go create mode 100644 pkg/lib/zapipld/raw_map_test.go diff --git a/go.mod b/go.mod index af62a1d..3134288 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260511203158-705fd4124b8c - github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b + github.com/fil-forge/libforge v0.0.0-20260512195724-a514bd7a6c50 + github.com/fil-forge/ucantone v0.0.0-20260512173820-ea7128569686 github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 github.com/jackc/pgx/v5 v5.8.0 diff --git a/go.sum b/go.sum index f011a29..cab7c0d 100644 --- a/go.sum +++ b/go.sum @@ -92,10 +92,10 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fil-forge/libforge v0.0.0-20260511203158-705fd4124b8c h1:m7F+gafris6u7W27q9kD1llVzqGX6CU9pzmXvSAUfuU= -github.com/fil-forge/libforge v0.0.0-20260511203158-705fd4124b8c/go.mod h1:kYmQIZuSsjWPDD7pGW3yb5/FnhpWPOIko9Gme1yg9EY= -github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b h1:8tvw5e7S1ntUnm0v/OTWLZelbJWQcho/WZI+ttCPv+A= -github.com/fil-forge/ucantone v0.0.0-20260507115308-bdd3b86f5b1b/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= +github.com/fil-forge/libforge v0.0.0-20260512195724-a514bd7a6c50 h1:+UmG/EqW+OIRWS0JfOmvVneNoWIWKPbMzbBcvbF4Auw= +github.com/fil-forge/libforge v0.0.0-20260512195724-a514bd7a6c50/go.mod h1:muFokjvd+LB7EGfsftSTpj5RZ1be1yjXe3XBXPswtuw= +github.com/fil-forge/ucantone v0.0.0-20260512173820-ea7128569686 h1:Weu36rT9cQrmM41qX1EL3ML1K1a+bdidVGacy75HSro= +github.com/fil-forge/ucantone v0.0.0-20260512173820-ea7128569686/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/pkg/lib/ucan_client/execute.go b/pkg/lib/ucan_client/execute.go index c0c063a..e1c7e25 100644 --- a/pkg/lib/ucan_client/execute.go +++ b/pkg/lib/ucan_client/execute.go @@ -1,24 +1,23 @@ package ucan_client import ( + "bytes" "context" "fmt" "reflect" + "github.com/fil-forge/sprue/pkg/lib/zapipld" "github.com/fil-forge/ucantone/client" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld" - "github.com/fil-forge/ucantone/ipld/codec/dagcbor" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" + cbg "github.com/whyrusleeping/cbor-gen" "go.uber.org/zap" ) // Execute sends the given invocation using the provided client and decodes the // response into the specified type. -func Execute[T dagcbor.Unmarshaler]( +func Execute[T cbg.CBORUnmarshaler]( ctx context.Context, client *client.HTTPClient, logger *zap.Logger, @@ -29,13 +28,13 @@ func Execute[T dagcbor.Unmarshaler]( zap.Stringer("issuer", inv.Issuer().DID()), zap.Stringer("subject", inv.Subject().DID()), zap.Stringer("command", inv.Command()), - zap.Any("arguments", inv.Arguments()), + zap.Object("arguments", zapipld.RawMap(inv.ArgumentsBytes())), } if inv.Audience() != nil { fields = append(fields, zap.Stringer("audience", inv.Audience().DID())) } - if len(inv.Metadata()) > 0 { - fields = append(fields, zap.Any("metadata", inv.Metadata())) + if len(inv.MetadataBytes()) > 0 { + fields = append(fields, zap.Object("metadata", zapipld.RawMap(inv.MetadataBytes()))) } if len(inv.Proofs()) > 0 { fields = append(fields, zap.Stringers("proofs", inv.Proofs())) @@ -51,34 +50,28 @@ func Execute[T dagcbor.Unmarshaler]( } rcpt := resp.Receipt() - ok, err := result.MatchResultR2( - rcpt.Out(), - func(o ipld.Any) (T, error) { - var ok T - // if ok is a pointer type, then we need to create an instance of it - // because rebind requires a non-nil pointer. - typ := reflect.TypeOf(ok) - if typ.Kind() == reflect.Ptr { - ok = reflect.New(typ.Elem()).Interface().(T) - } - err := datamodel.Rebind(datamodel.NewAny(o), ok) - if err != nil { - log.Error("failed to bind invocation response", zap.Error(err)) - return zero, fmt.Errorf("binding invocation response: %w", err) - } - return ok, nil - }, - func(x ipld.Any) (T, error) { - var model edm.ErrorModel - err := datamodel.Rebind(datamodel.NewAny(x), &model) - if err != nil { - log.Error("failed to bind execution failure", zap.Error(err)) - log.Error("failed execution", zap.Any("error", x)) - return zero, fmt.Errorf("executing invocation: %v", x) - } - log.Error("failed execution", zap.String("name", model.ErrorName), zap.Error(model)) - return zero, fmt.Errorf("executing invocation: %w", model) - }, - ) - return ok, rcpt, err + + o, x := rcpt.Out().Unpack() + if rcpt.Out().IsErr() { + var model edm.ErrorModel + if err := model.UnmarshalCBOR(bytes.NewReader(x)); err != nil { + log.Error("failed to unmarshal execution failure", zap.Error(err), zap.Binary("input", x)) + return zero, nil, fmt.Errorf("executing invocation") + } + log.Error("failed execution", zap.String("name", model.ErrorName), zap.Error(model)) + return zero, nil, fmt.Errorf("executing invocation: %w", model) + } + + // if ok is a pointer type, allocate the underlying value so + // UnmarshalCBOR has a non-nil pointer to write into. + var ok T + typ := reflect.TypeOf(ok) + if typ.Kind() == reflect.Ptr { + ok = reflect.New(typ.Elem()).Interface().(T) + } + if err := ok.UnmarshalCBOR(bytes.NewReader(o)); err != nil { + log.Error("failed to unmarshal invocation response", zap.Error(err), zap.Binary("input", o)) + return zero, nil, fmt.Errorf("unmarshaling invocation response: %w", err) + } + return ok, rcpt, nil } diff --git a/pkg/lib/ucan_server/email_auth.go b/pkg/lib/ucan_server/email_auth.go index 5ef555d..1f44405 100644 --- a/pkg/lib/ucan_server/email_auth.go +++ b/pkg/lib/ucan_server/email_auth.go @@ -1,15 +1,13 @@ package ucan_server import ( + "bytes" "context" "fmt" "github.com/fil-forge/libforge/capabilities/access" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" ) @@ -18,7 +16,7 @@ type AccessConfirmResult struct { Email string Audience string UCAN string - Meta ipld.Map + Meta []byte } // ExecBase64urlAccessConfirm executes an /access/confirm UCAN invocation @@ -51,15 +49,14 @@ func ExecBase64urlAccessConfirm(ctx context.Context, executor execution.Executor return AccessConfirmResult{}, fmt.Errorf("executing confirm task %s: %w", confirmation.Task().Link(), err) } - _, x := result.Unwrap(res.Receipt().Out()) + _, x := res.Receipt().Out().Unpack() if x != nil { return AccessConfirmResult{}, fmt.Errorf("invocation failure: %v", x) } confirmArgs := access.ConfirmArguments{} - err = datamodel.Rebind(datamodel.NewAny(confirmation.Arguments()), &confirmArgs) - if err != nil { - return AccessConfirmResult{}, fmt.Errorf("binding confirmation arguments: %w", err) + if err := confirmArgs.UnmarshalCBOR(bytes.NewReader(confirmation.ArgumentsBytes())); err != nil { + return AccessConfirmResult{}, fmt.Errorf("unmarshaling confirmation arguments: %w", err) } email, err := didmailto.Email(confirmArgs.Issuer) @@ -91,6 +88,6 @@ func ExecBase64urlAccessConfirm(ctx context.Context, executor execution.Executor Email: email, Audience: confirmArgs.Audience.String(), UCAN: string(output), - Meta: confirmation.Metadata(), + Meta: confirmation.MetadataBytes(), }, nil } diff --git a/pkg/lib/ucan_server/events.go b/pkg/lib/ucan_server/events.go index a87857e..c0aaa9a 100644 --- a/pkg/lib/ucan_server/events.go +++ b/pkg/lib/ucan_server/events.go @@ -1,14 +1,14 @@ package ucan_server import ( + "bytes" "context" "fmt" + "github.com/fil-forge/sprue/pkg/lib/zapipld" "github.com/fil-forge/sprue/pkg/store/agent" - edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" @@ -22,22 +22,26 @@ var _ server.ResponseEncodeListener = (*ErrorHandler)(nil) func (l ErrorHandler) OnResponseEncode(ctx context.Context, ct ucan.Container) error { for _, inv := range ct.Invocations() { - if r, ok := ct.Receipt(inv.Task().Link()); ok { - _, x := result.Unwrap(r.Out()) - if x != nil { - var model edm.ErrorModel - datamodel.Rebind(datamodel.NewAny(x), &model) - if model.ErrorName == execution.HandlerExecutionErrorName { - l.Logger.Error( - "handler execution error", - zap.Stringer("task", inv.Task().Link()), - zap.Stringer("command", inv.Command()), - zap.Any("args", inv.Arguments()), - zap.Error(model), - ) - } - } + r, ok := ct.Receipt(inv.Task().Link()) + if !ok || !r.Out().IsErr() { + continue } + _, x := r.Out().Unpack() + var model datamodel.Map + if err := model.UnmarshalCBOR(bytes.NewReader(x)); err != nil { + l.Logger.Error("failed to unmarshal handler execution error", zap.Error(err), zap.Binary("input", x)) + continue + } + if model["name"].(string) != execution.HandlerExecutionErrorName { + continue + } + l.Logger.Error( + "handler execution error", + zap.Stringer("task", inv.Task().Link()), + zap.Stringer("command", inv.Command()), + zap.Any("arguments", zapipld.RawMap(inv.ArgumentsBytes())), + zap.Any("error", model), + ) } return nil } diff --git a/pkg/lib/ucan_server/validation.go b/pkg/lib/ucan_server/validation.go index f3b4e59..4d707c2 100644 --- a/pkg/lib/ucan_server/validation.go +++ b/pkg/lib/ucan_server/validation.go @@ -1,11 +1,11 @@ package ucan_server import ( + "bytes" "context" "fmt" "github.com/fil-forge/libforge/capabilities/ucan/attest" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" ed_verifier "github.com/fil-forge/ucantone/principal/ed25519/verifier" secp_verifier "github.com/fil-forge/ucantone/principal/secp256k1/verifier" @@ -44,9 +44,8 @@ func NewAttestationVerifier(authority principal.Verifier) validator.NonStandardS if inv.Issuer().DID() != authority.DID() || inv.Subject() == nil || inv.Subject().DID() != authority.DID() { continue } - args := attest.ProofArguments{} - err := datamodel.Rebind(datamodel.NewAny(inv.Arguments()), &args) - if err != nil { + var args attest.ProofArguments + if err := args.UnmarshalCBOR(bytes.NewReader(inv.ArgumentsBytes())); err != nil { continue } // make sure the attestation is for the delegation in question diff --git a/pkg/lib/zapipld/raw_map.go b/pkg/lib/zapipld/raw_map.go new file mode 100644 index 0000000..625413a --- /dev/null +++ b/pkg/lib/zapipld/raw_map.go @@ -0,0 +1,29 @@ +package zapipld + +import ( + "bytes" + + "github.com/fil-forge/ucantone/ipld/datamodel" + "go.uber.org/zap/zapcore" +) + +// RawMap is a [zapcore.ObjectMarshaler] that decodes the given bytes as a +// CBOR-encoded IPLD map and logs its keys and values. +type RawMap []byte + +func (rm RawMap) MarshalLogObject(enc zapcore.ObjectEncoder) error { + if len(rm) == 0 { + return nil + } + var m datamodel.Map + if err := m.UnmarshalCBOR(bytes.NewReader(rm)); err != nil { + return err + } + for k, v := range m { + err := enc.AddReflected(k, v) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/lib/zapipld/raw_map_test.go b/pkg/lib/zapipld/raw_map_test.go new file mode 100644 index 0000000..70e3dc6 --- /dev/null +++ b/pkg/lib/zapipld/raw_map_test.go @@ -0,0 +1,41 @@ +package zapipld + +import ( + "bytes" + "testing" + + "github.com/fil-forge/libforge/testutil" + "github.com/fil-forge/ucantone/ipld/datamodel" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +func TestRawMap(t *testing.T) { + log := zaptest.NewLogger(t) + t.Run("logs raw map", func(t *testing.T) { + m := datamodel.Map{ + "foo": "bar", + "baz": 123, + "nested": datamodel.Map{ + "hello": "world", + }, + "array": []string{"a", "b", "c"}, + "mixedArray": []any{"a", 1, true, datamodel.Map{"key": "value"}}, + "nilly": nil, + "cid": testutil.RandomCID(t), + } + var buf bytes.Buffer + if err := m.MarshalCBOR(&buf); err != nil { + t.Fatalf("failed to marshal map: %v", err) + } + log.With(zap.Object("rawMap", RawMap(buf.Bytes()))).Info("logging raw map") + }) + + t.Run("raw map empty bytes", func(t *testing.T) { + log.With(zap.Object("rawMap", RawMap([]byte{}))).Info("empty bytes") + }) + + t.Run("raw map invalid bytes (non CBOR)", func(t *testing.T) { + log.With(zap.Object("rawMap", RawMap([]byte{1, 2, 3}))).Info("invalid bytes") + }) +} diff --git a/pkg/service/handlers/access_claim_test.go b/pkg/service/handlers/access_claim_test.go index 25c913e..dba766c 100644 --- a/pkg/service/handlers/access_claim_test.go +++ b/pkg/service/handlers/access_claim_test.go @@ -1,14 +1,13 @@ package handlers import ( + "bytes" "testing" "github.com/fil-forge/libforge/capabilities/access" "github.com/fil-forge/sprue/internal/testutil" dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" @@ -43,13 +42,12 @@ func TestAccessClaimHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) require.NotNil(t, o) - ok := access.ClaimOK{} - err = datamodel.Rebind(datamodel.NewAny(o), &ok) - require.NoError(t, err) + var ok access.ClaimOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Delegations) }) @@ -82,12 +80,12 @@ func TestAccessClaimHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + require.NotNil(t, o) - ok := access.ClaimOK{} - err = datamodel.Rebind(datamodel.NewAny(o), &ok) - require.NoError(t, err) + var ok access.ClaimOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Equal(t, []cid.Cid{dlg.Link()}, ok.Delegations) }) @@ -123,12 +121,12 @@ func TestAccessClaimHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + require.NotNil(t, o) - ok := access.ClaimOK{} - err = datamodel.Rebind(datamodel.NewAny(o), &ok) - require.NoError(t, err) + var ok access.ClaimOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Delegations, 2) require.ElementsMatch(t, []cid.Cid{dlg1.Link(), dlg2.Link()}, ok.Delegations) }) @@ -164,12 +162,12 @@ func TestAccessClaimHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + require.NotNil(t, o) - ok := access.ClaimOK{} - err = datamodel.Rebind(datamodel.NewAny(o), &ok) - require.NoError(t, err) + var ok access.ClaimOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Delegations) }) } diff --git a/pkg/service/handlers/access_confirm.go b/pkg/service/handlers/access_confirm.go index 7cb60d2..f6ba71e 100644 --- a/pkg/service/handlers/access_confirm.go +++ b/pkg/service/handlers/access_confirm.go @@ -28,7 +28,7 @@ func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_s req *bindexec.Request[*access.ConfirmArguments], res *bindexec.Response[*access.ConfirmOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() if req.Invocation().Subject().DID() != id.Signer.DID() { log.Warn("not a valid invocation", zap.Stringer("subject", req.Invocation().Subject().DID())) return res.SetFailure(access.ErrInvalidAccessConfirmSubject) diff --git a/pkg/service/handlers/access_confirm_test.go b/pkg/service/handlers/access_confirm_test.go index 12135c1..f414f4a 100644 --- a/pkg/service/handlers/access_confirm_test.go +++ b/pkg/service/handlers/access_confirm_test.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "testing" "github.com/fil-forge/libforge/capabilities/access" @@ -10,8 +11,6 @@ import ( dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -54,12 +53,12 @@ func TestAccessConfirmHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, o) + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, access.InvalidAccessConfirmSubjectErrorName, model.Name()) }) @@ -96,12 +95,12 @@ func TestAccessConfirmHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, o) + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, access.InvalidAccessConfirmIssuerErrorName, model.Name()) }) @@ -137,13 +136,12 @@ func TestAccessConfirmHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) require.NotNil(t, o) - ok := access.ConfirmOK{} - err = datamodel.Rebind(datamodel.NewAny(o), &ok) - require.NoError(t, err) + var ok access.ConfirmOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) // One delegation link per attenuation. require.Len(t, ok.Delegations, 1) @@ -186,12 +184,12 @@ func TestAccessConfirmHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + require.NotNil(t, o) - ok := access.ConfirmOK{} - err = datamodel.Rebind(datamodel.NewAny(o), &ok) - require.NoError(t, err) + var ok access.ConfirmOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Delegations, 2) // Two attenuations → two delegations and two attestations stored. diff --git a/pkg/service/handlers/access_delegate.go b/pkg/service/handlers/access_delegate.go index 5fe8140..95e9c92 100644 --- a/pkg/service/handlers/access_delegate.go +++ b/pkg/service/handlers/access_delegate.go @@ -21,7 +21,7 @@ func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioni req *bindexec.Request[*access.DelegateArguments], res *bindexec.Response[*access.DelegateOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() agent := req.Invocation().Issuer().DID() space := req.Invocation().Subject().DID() diff --git a/pkg/service/handlers/access_delegate_test.go b/pkg/service/handlers/access_delegate_test.go index abb5873..cf0ae0c 100644 --- a/pkg/service/handlers/access_delegate_test.go +++ b/pkg/service/handlers/access_delegate_test.go @@ -13,7 +13,6 @@ import ( subscriptionmemory "github.com/fil-forge/sprue/pkg/store/subscription/memory" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" @@ -78,9 +77,7 @@ func TestAccessDelegateHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + require.True(t, res.Receipt().Out().IsErr()) }) t.Run("success with delegation", func(t *testing.T) { @@ -117,9 +114,7 @@ func TestAccessDelegateHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) // Verify the delegation was stored. page, err := dlgStore.ListByAudience(t.Context(), agent.DID()) @@ -154,9 +149,7 @@ func TestAccessDelegateHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) }) t.Run("delegation not found in metadata", func(t *testing.T) { diff --git a/pkg/service/handlers/access_request.go b/pkg/service/handlers/access_request.go index 776ebc9..8c1fafd 100644 --- a/pkg/service/handlers/access_request.go +++ b/pkg/service/handlers/access_request.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "fmt" "net/url" "time" @@ -15,6 +16,7 @@ import ( "github.com/fil-forge/sprue/pkg/mailer" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" @@ -39,7 +41,7 @@ func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identit req *bindexec.Request[*access.RequestArguments], res *bindexec.Response[*access.RequestOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() account, err := didmailto.Parse(args.Issuer.String()) if err != nil { log.Warn("failed to parse mailto DID", zap.Stringer("account", args.Issuer)) @@ -63,6 +65,15 @@ func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identit exp := int(time.Now().Add(confirmationTTL).Unix()) + metaBytes := req.Invocation().MetadataBytes() + meta := datamodel.Map{} + if len(metaBytes) > 0 { + if err := meta.UnmarshalCBOR(bytes.NewReader(metaBytes)); err != nil { + log.Error("failed to unmarshal invocation metadata", zap.Error(err)) + return fmt.Errorf("unmarshaling invocation metadata: %w", err) + } + } + // We issue an `/access/confirm` invocation which will be embedded in the // URL that we send to the user. When the user clicks the link we'll get // this invocation back in the `/validate-email` endpoint which will allow @@ -98,7 +109,7 @@ func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identit // capability - we use this, for example, to let bsky.storage users // specify that they should be redirected back to bsky.storage after // completing the Stripe plan selection flow - invocation.WithMetadata(req.Invocation().Metadata()), + invocation.WithMetadata(meta), ) if err != nil { log.Error("failed to create confirmation delegation", zap.Error(err)) diff --git a/pkg/service/handlers/access_request_test.go b/pkg/service/handlers/access_request_test.go index 65af48a..d70f5ee 100644 --- a/pkg/service/handlers/access_request_test.go +++ b/pkg/service/handlers/access_request_test.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "context" "errors" "net/url" @@ -14,8 +15,6 @@ import ( "github.com/fil-forge/sprue/pkg/identity" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -80,13 +79,12 @@ func TestAccessRequestHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, x := result.Unwrap(res.Receipt().Out()) + o, x := res.Receipt().Out().Unpack() require.Nil(t, x) require.NotNil(t, o) - ok := access.RequestOK{} - err = datamodel.Rebind(datamodel.NewAny(o), &ok) - require.NoError(t, err) + var ok access.RequestOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Equal(t, inv.Link(), ok.Request) require.NotZero(t, ok.Expiration) @@ -126,12 +124,11 @@ func TestAccessRequestHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, x := result.Unwrap(res.Receipt().Out()) + _, x := res.Receipt().Out().Unpack() require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(x), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, access.InvalidAuthorizationAccountErrorName, model.Name()) }) diff --git a/pkg/service/handlers/admin_provider_deregister.go b/pkg/service/handlers/admin_provider_deregister.go index 2d03985..5019f07 100644 --- a/pkg/service/handlers/admin_provider_deregister.go +++ b/pkg/service/handlers/admin_provider_deregister.go @@ -17,7 +17,7 @@ func NewAdminProviderDeregisterHandler(id *identity.Identity, providerStore stor req *bindexec.Request[*provider.DeregisterArguments], res *bindexec.Response[*provider.DeregisterOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() if req.Invocation().Issuer().DID() != id.Signer.DID() { log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) diff --git a/pkg/service/handlers/admin_provider_deregister_test.go b/pkg/service/handlers/admin_provider_deregister_test.go index 9184b78..70e7cf4 100644 --- a/pkg/service/handlers/admin_provider_deregister_test.go +++ b/pkg/service/handlers/admin_provider_deregister_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "net/url" "testing" @@ -12,8 +13,6 @@ import ( storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" @@ -72,12 +71,11 @@ func TestAdminProviderDeregisterHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, "Unauthorized", model.Name()) // Record should still be present. @@ -109,10 +107,7 @@ func TestAdminProviderDeregisterHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - require.NotNil(t, o) + require.False(t, res.Receipt().Out().IsErr()) _, err = spStore.Get(ctx, storageProvider.DID()) require.ErrorIs(t, err, storageprovider.ErrStorageProviderNotFound) @@ -138,12 +133,11 @@ func TestAdminProviderDeregisterHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, storageprovider.StorageProviderNotFoundErrorName, model.Name()) }) } diff --git a/pkg/service/handlers/admin_provider_list_test.go b/pkg/service/handlers/admin_provider_list_test.go index a4a6fc6..0459104 100644 --- a/pkg/service/handlers/admin_provider_list_test.go +++ b/pkg/service/handlers/admin_provider_list_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "net/url" "testing" @@ -11,8 +12,6 @@ import ( storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" @@ -60,12 +59,11 @@ func TestAdminProviderListHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, "Unauthorized", model.Name()) }) @@ -83,13 +81,12 @@ func TestAdminProviderListHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - require.NotNil(t, ok) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + require.NotNil(t, o) - listOK := provider.ListOK{} - err = datamodel.Rebind(datamodel.NewAny(ok), &listOK) - require.NoError(t, err) + var listOK provider.ListOK + require.NoError(t, listOK.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, listOK.Providers) }) @@ -119,13 +116,12 @@ func TestAdminProviderListHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - ok, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - require.NotNil(t, ok) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + require.NotNil(t, o) - listOK := provider.ListOK{} - err = datamodel.Rebind(datamodel.NewAny(ok), &listOK) - require.NoError(t, err) + var listOK provider.ListOK + require.NoError(t, listOK.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, listOK.Providers, 2) byDID := map[string]provider.Provider{} diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index e2a7a9a..0054fd2 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -24,7 +24,7 @@ func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storag req *bindexec.Request[*provider.RegisterArguments], res *bindexec.Response[*provider.RegisterOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() if req.Invocation().Issuer().DID() != id.Signer.DID() { log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) return res.SetFailure(errors.New("Unauthorized", "only the service identity can register providers")) diff --git a/pkg/service/handlers/admin_provider_register_test.go b/pkg/service/handlers/admin_provider_register_test.go index 8e201d3..98a1664 100644 --- a/pkg/service/handlers/admin_provider_register_test.go +++ b/pkg/service/handlers/admin_provider_register_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "testing" "github.com/fil-forge/sprue/internal/testutil" @@ -10,8 +11,6 @@ import ( storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" @@ -67,12 +66,11 @@ func TestAdminProviderRegisterHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, "Unauthorized", model.Name()) }) @@ -97,10 +95,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - - o, x := result.Unwrap(res.Receipt().Out()) - require.Nil(t, x) - require.NotNil(t, o) + require.False(t, res.Receipt().Out().IsErr()) // Second registration should fail req2 := issueRegisterInvocation(t, uploadService, uploadService, args) @@ -109,13 +104,13 @@ func TestAdminProviderRegisterHandler(t *testing.T) { err = handler.Handler(req2, res2) require.NoError(t, err) + require.True(t, res2.Receipt().Out().IsErr()) - _, x2 := result.Unwrap(res2.Receipt().Out()) - require.NotNil(t, x2) + _, x := res2.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(x2), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, "ProviderAlreadyRegistered", model.Name()) }) @@ -139,10 +134,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - - o, x := result.Unwrap(res.Receipt().Out()) - require.Nil(t, x) - require.NotNil(t, o) + require.False(t, res.Receipt().Out().IsErr()) // Verify provider was stored rec, err := spStore.Get(ctx, storageProvider.DID()) diff --git a/pkg/service/handlers/admin_provider_weight_set.go b/pkg/service/handlers/admin_provider_weight_set.go index 0adc551..e83696d 100644 --- a/pkg/service/handlers/admin_provider_weight_set.go +++ b/pkg/service/handlers/admin_provider_weight_set.go @@ -18,7 +18,7 @@ func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore stora req *bindexec.Request[*weight.SetArguments], res *bindexec.Response[*weight.SetOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() if req.Invocation().Issuer().DID() != id.Signer.DID() { log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) return res.SetFailure(errors.New("Unauthorized", "only the service identity can set provider weights")) diff --git a/pkg/service/handlers/admin_provider_weight_set_test.go b/pkg/service/handlers/admin_provider_weight_set_test.go index 2573cb8..9256aaf 100644 --- a/pkg/service/handlers/admin_provider_weight_set_test.go +++ b/pkg/service/handlers/admin_provider_weight_set_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "net/url" "testing" @@ -11,8 +12,6 @@ import ( storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" @@ -67,12 +66,11 @@ func TestAdminProviderWeightSetHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, x := result.Unwrap(res.Receipt().Out()) + _, x := res.Receipt().Out().Unpack() require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(x), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, "Unauthorized", model.Name()) }) @@ -98,12 +96,11 @@ func TestAdminProviderWeightSetHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, x := result.Unwrap(res.Receipt().Out()) + _, x := res.Receipt().Out().Unpack() require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(x), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, "Failed to get existing provider", model.Name()) }) @@ -136,9 +133,7 @@ func TestAdminProviderWeightSetHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, x := result.Unwrap(res.Receipt().Out()) - require.Nil(t, x) - require.NotNil(t, o) + require.False(t, res.Receipt().Out().IsErr()) // Verify weights were updated. rec, err := spStore.Get(ctx, storageProvider.DID()) diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index 2cd863d..2d6693d 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "context" "crypto/ed25519" "fmt" @@ -19,11 +20,9 @@ import ( "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ipld" "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" ed25519signer "github.com/fil-forge/ucantone/principal/ed25519" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" @@ -41,7 +40,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv req *bindexec.Request[*blobcaps.AddArguments], res *bindexec.Response[*blobcaps.AddOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() blob := args.Blob space := req.Invocation().Subject().DID() b58digest := digestutil.Format(blob.Digest) @@ -87,26 +86,17 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv return err } - addOK, err := result.MatchResultR2( - addRcpt.Out(), - func(o ipld.Any) (*blobcaps.AddOK, error) { - var addOK blobcaps.AddOK - err := datamodel.Rebind(datamodel.NewAny(o), &addOK) - if err != nil { - log.Error("failed to rebind add OK result", zap.Error(err)) - return nil, fmt.Errorf("rebinding add OK result: %w", err) - } - return &addOK, nil - }, - func(x ipld.Any) (*blobcaps.AddOK, error) { - // should not have been registered on error - log.Error("blob registration receipt contains failure", zap.Any("error", x)) - return nil, fmt.Errorf("blob registration receipt contains failure: %v", x) - }, - ) - if err != nil { - log.Error("failed to match blob add receipt result", zap.Error(err)) - return fmt.Errorf("matching blob add receipt result: %w", err) + // should not have been registered on error + if addRcpt.Out().IsErr() { + log.Error("blob registration receipt contains failure") + return fmt.Errorf("blob registration receipt contains failure") + } + + o, _ := addRcpt.Out().Unpack() + var addOK blobcaps.AddOK + if err := addOK.UnmarshalCBOR(bytes.NewReader(o)); err != nil { + log.Error("failed to unmarshal add OK result", zap.Error(err)) + return fmt.Errorf("unmarshaling add OK result: %w", err) } accRcpt, err := agentStore.GetReceipt(req.Context(), addOK.Site.Task) @@ -121,9 +111,8 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv return fmt.Errorf("getting invocation for blob accept: %w", err) } - accArgs := blobcaps.AcceptArguments{} - err = datamodel.Rebind(datamodel.NewAny(accInv.Arguments()), &accArgs) - if err != nil { + var accArgs blobcaps.AcceptArguments + if err := accArgs.UnmarshalCBOR(bytes.NewReader(accInv.ArgumentsBytes())); err != nil { log.Error("failed to rebind accept OK result", zap.Error(err)) return fmt.Errorf("rebinding accept OK result: %w", err) } @@ -140,11 +129,10 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv return fmt.Errorf("getting invocation for HTTP PUT: %w", err) } - putArgs := httpcaps.PutArguments{} - err = datamodel.Rebind(datamodel.NewAny(putInv.Arguments()), &putArgs) - if err != nil { - log.Error("failed to rebind HTTP PUT arguments", zap.Error(err)) - return fmt.Errorf("rebinding HTTP PUT arguments: %w", err) + var putArgs httpcaps.PutArguments + if err := putArgs.UnmarshalCBOR(bytes.NewReader(putInv.ArgumentsBytes())); err != nil { + log.Error("failed to unmarshal HTTP PUT arguments", zap.Error(err)) + return fmt.Errorf("unmarshaling HTTP PUT arguments: %w", err) } allocRcpt, err := agentStore.GetReceipt(req.Context(), putArgs.Destination.Task) @@ -164,7 +152,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv container.WithReceipts(allocRcpt, putRcpt, accRcpt), )) - return res.SetSuccess(addOK) + return res.SetSuccess(&addOK) } cause := req.Invocation().Task().Link() @@ -316,15 +304,10 @@ func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.Alloc // a receipt for the `/http/put` without requiring blob to be provided. if allocOK.Address == nil { log.Debug("blob present on provider, issuing receipt for put") - var ok datamodel.Map - err = datamodel.Rebind(&httpcaps.PutOK{}, &ok) - if err != nil { - return nil, nil, fmt.Errorf("rebinding %q OK: %w", httpcaps.PutCommand, err) - } - putRcpt, err = receipt.Issue( + putRcpt, err = receipt.IssueOK( blobProvider, putInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](ok), + &httpcaps.PutOK{}, ) if err != nil { return nil, nil, fmt.Errorf("issuing %q receipt: %w", httpcaps.PutCommand, err) @@ -385,31 +368,28 @@ func maybeAccept( var accRcpt ucan.Receipt // If put has already succeeded, we can execute `/blob/accept` right away. - if putRcpt != nil { - _, x := result.Unwrap(putRcpt.Out()) - if x == nil { - res, inv, rcpt, err := c.Accept(ctx, &accReq, proofStore, invocation.WithNoNonce()) - if err != nil { - log.Error("failed to execute accept on piri", zap.Error(err)) - return nil, nil, err - } - log.Debug("blob accepted", zap.Stringer("site", res.Site)) - - err = writeAgentMessage(ctx, agentStore, []ucan.Invocation{inv}, []ucan.Receipt{rcpt}) - if err != nil { - log.Error("failed to write agent message for accept", zap.Error(err)) - return nil, nil, err - } + if putRcpt != nil && putRcpt.Out().IsOK() { + res, inv, rcpt, err := c.Accept(ctx, &accReq, proofStore, invocation.WithNoNonce()) + if err != nil { + log.Error("failed to execute accept on piri", zap.Error(err)) + return nil, nil, err + } + log.Debug("blob accepted", zap.Stringer("site", res.Site)) - err = blobRegistry.Register(ctx, space.DID(), blob, cause) - if err != nil { - log.Error("failed to register blob", zap.Error(err)) - return nil, nil, err - } + err = writeAgentMessage(ctx, agentStore, []ucan.Invocation{inv}, []ucan.Receipt{rcpt}) + if err != nil { + log.Error("failed to write agent message for accept", zap.Error(err)) + return nil, nil, err + } - accInv = inv - accRcpt = rcpt + err = blobRegistry.Register(ctx, space.DID(), blob, cause) + if err != nil { + log.Error("failed to register blob", zap.Error(err)) + return nil, nil, err } + + accInv = inv + accRcpt = rcpt } return accInv, accRcpt, nil diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go index 1a651fb..f397ceb 100644 --- a/pkg/service/handlers/blob_add_test.go +++ b/pkg/service/handlers/blob_add_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "crypto/ed25519" "net/http/httptest" @@ -31,13 +32,9 @@ import ( edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ipld" - "github.com/fil-forge/ucantone/ipld/codec/dagcbor" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" ed25519signer "github.com/fil-forge/ucantone/principal/ed25519" "github.com/fil-forge/ucantone/principal/signer" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/delegation" @@ -184,12 +181,11 @@ func TestBlobAddHandler(t *testing.T) { err = deps.handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) }) @@ -219,12 +215,11 @@ func TestBlobAddHandler(t *testing.T) { err = deps.handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, routing.CandidateUnavailableErrorName, model.Name()) }) @@ -259,12 +254,11 @@ func TestBlobAddHandler(t *testing.T) { err = deps.handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, routing.CandidateUnavailableErrorName, model.Name()) }) @@ -319,9 +313,7 @@ func TestBlobAddHandler(t *testing.T) { err = deps.handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - require.NotNil(t, o) + require.False(t, res.Receipt().Out().IsErr()) // Response metadata should carry the allocate, put, and accept invocations. require.NotNil(t, res.Metadata()) @@ -368,9 +360,7 @@ func TestBlobAddHandler(t *testing.T) { err = deps.handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - require.NotNil(t, o) + require.False(t, res.Receipt().Out().IsErr()) // Both invocations and receipts should be in the metadata since accept ran. require.NotNil(t, res.Metadata()) @@ -399,11 +389,10 @@ func TestBlobAddHandler(t *testing.T) { &blobcaps.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, invocation.WithAudience(storageProvider), ))(t) - allocOK := mustRebindMap(t, &blobcaps.AllocateOK{Size: blob.Size}) - allocRcpt := testutil.Must(receipt.Issue( + allocRcpt := testutil.Must(receipt.IssueOK( storageProvider, allocInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](allocOK), + &blobcaps.AllocateOK{Size: blob.Size}, ))(t) // /http/put — issued by the principal derived from the blob digest. @@ -416,11 +405,10 @@ func TestBlobAddHandler(t *testing.T) { }, invocation.WithAudience(blobProvider), ))(t) - putOK := mustRebindMap(t, &httpcaps.PutOK{}) - putRcpt := testutil.Must(receipt.Issue( + putRcpt := testutil.Must(receipt.IssueOK( blobProvider, putInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](putOK), + &httpcaps.PutOK{}, ))(t) // /blob/accept @@ -433,11 +421,10 @@ func TestBlobAddHandler(t *testing.T) { }, invocation.WithAudience(storageProvider), ))(t) - accOK := mustRebindMap(t, &blobcaps.AcceptOK{Site: testutil.RandomCID(t)}) - accRcpt := testutil.Must(receipt.Issue( + accRcpt := testutil.Must(receipt.IssueOK( storageProvider, accInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](accOK), + &blobcaps.AcceptOK{Site: testutil.RandomCID(t)}, ))(t) // The original /space/blob/add invocation and receipt — its receipt's @@ -448,13 +435,12 @@ func TestBlobAddHandler(t *testing.T) { &blobcaps.AddArguments{Blob: blob}, invocation.WithAudience(uploadService), ))(t) - addOK := mustRebindMap(t, &blobcaps.AddOK{ - Site: promise.AwaitOK{Task: accInv.Task().Link()}, - }) - prevAddRcpt := testutil.Must(receipt.Issue( + prevAddRcpt := testutil.Must(receipt.IssueOK( uploadService, prevAddInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](addOK), + &blobcaps.AddOK{ + Site: promise.AwaitOK{Task: accInv.Task().Link()}, + }, ))(t) // Persist the chain in the agent store and register the blob with cause @@ -483,13 +469,13 @@ func TestBlobAddHandler(t *testing.T) { err = deps.handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) require.NotNil(t, o) // The returned AddOK should match the one from the prior receipt. - gotAddOK := blobcaps.AddOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &gotAddOK)) + var gotAddOK blobcaps.AddOK + require.NoError(t, gotAddOK.UnmarshalCBOR(bytes.NewReader(o))) require.Equal(t, accInv.Task().Link(), gotAddOK.Site.Task) // Response metadata should carry all three invocations and all three @@ -510,12 +496,3 @@ func deriveBlobProvider(t *testing.T, digest multihash.Multihash) principal.Sign require.NoError(t, err) return s } - -// mustRebindMap rebinds a model into a datamodel.Map for use as receipt output — -// receipt.Issue can't marshal arbitrary struct pointers via ipld.Any. -func mustRebindMap(t *testing.T, model dagcbor.Marshaler) datamodel.Map { - t.Helper() - m := datamodel.Map{} - require.NoError(t, datamodel.Rebind(model, &m)) - return m -} diff --git a/pkg/service/handlers/blob_list.go b/pkg/service/handlers/blob_list.go index cde15b9..480d651 100644 --- a/pkg/service/handlers/blob_list.go +++ b/pkg/service/handlers/blob_list.go @@ -17,7 +17,7 @@ func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Han req *bindexec.Request[*blobcaps.ListArguments], res *bindexec.Response[*blobcaps.ListOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() space := req.Invocation().Subject() log := log.With(zap.Stringer("space", space.DID())) diff --git a/pkg/service/handlers/blob_list_test.go b/pkg/service/handlers/blob_list_test.go index 566f134..9ce8b1c 100644 --- a/pkg/service/handlers/blob_list_test.go +++ b/pkg/service/handlers/blob_list_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "testing" @@ -13,9 +14,7 @@ import ( metrics_store "github.com/fil-forge/sprue/pkg/store/metrics/memory" spacediff_store "github.com/fil-forge/sprue/pkg/store/space_diff/memory" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -75,12 +74,12 @@ func TestBlobListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) require.NotNil(t, o) - ok := blobcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + var ok blobcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) }) @@ -101,10 +100,10 @@ func TestBlobListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := blobcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok blobcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) }) @@ -129,10 +128,10 @@ func TestBlobListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := blobcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok blobcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) require.NotNil(t, ok.Cursor) }) @@ -156,10 +155,10 @@ func TestBlobListHandler(t *testing.T) { req1, res1 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Size: &size}) require.NoError(t, handler.Handler(req1, res1)) - o1, fail := result.Unwrap(res1.Receipt().Out()) - require.Nil(t, fail) - ok1 := blobcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o1), &ok1)) + o1, x := res1.Receipt().Out().Unpack() + require.Nil(t, x) + var ok1 blobcaps.ListOK + require.NoError(t, ok1.UnmarshalCBOR(bytes.NewReader(o1))) require.Len(t, ok1.Results, 1) require.NotNil(t, ok1.Cursor) @@ -168,10 +167,10 @@ func TestBlobListHandler(t *testing.T) { req2, res2 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Cursor: &cursor, Size: &size}) require.NoError(t, handler.Handler(req2, res2)) - o2, fail := result.Unwrap(res2.Receipt().Out()) - require.Nil(t, fail) - ok2 := blobcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o2), &ok2)) + o2, x := res2.Receipt().Out().Unpack() + require.Nil(t, x) + var ok2 blobcaps.ListOK + require.NoError(t, ok2.UnmarshalCBOR(bytes.NewReader(o2))) require.Len(t, ok2.Results, 1) require.NotEqual(t, ok1.Results[0].Blob.Digest.HexString(), ok2.Results[0].Blob.Digest.HexString()) }) @@ -194,10 +193,10 @@ func TestBlobListHandler(t *testing.T) { req, res := invokeBlobList(t, ctx, alice, uploadService, space2, &blobcaps.ListArguments{}) require.NoError(t, handler.Handler(req, res)) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := blobcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok blobcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) }) } diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go index 097f41b..fef7ba2 100644 --- a/pkg/service/handlers/index_add.go +++ b/pkg/service/handlers/index_add.go @@ -24,7 +24,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser req *bindexec.Request[*indexcaps.AddArguments], res *bindexec.Response[*indexcaps.AddOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() space := req.Invocation().Subject() index := args.Index diff --git a/pkg/service/handlers/index_add_test.go b/pkg/service/handlers/index_add_test.go index 80ca91b..790f572 100644 --- a/pkg/service/handlers/index_add_test.go +++ b/pkg/service/handlers/index_add_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "net/http/httptest" "net/url" @@ -23,10 +24,8 @@ import ( edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" "github.com/fil-forge/ucantone/principal/signer" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" @@ -122,11 +121,11 @@ func TestIndexAddHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) }) @@ -150,11 +149,11 @@ func TestIndexAddHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, indexcaps.IndexNotFoundErrorName, model.Name()) }) @@ -196,9 +195,7 @@ func TestIndexAddHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - require.NotNil(t, o) + require.False(t, res.Receipt().Out().IsErr()) }) t.Run("missing retrieval auth fails to build proof chain", func(t *testing.T) { @@ -238,8 +235,6 @@ func TestIndexAddHandler(t *testing.T) { // Currently the indexer client publishes even without retrieval auth // because the proof chain is empty (not erroring). Document that // behavior. - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - require.NotNil(t, o) + require.False(t, res.Receipt().Out().IsErr()) }) } diff --git a/pkg/service/handlers/provider_add.go b/pkg/service/handlers/provider_add.go index 835e90b..b6d4595 100644 --- a/pkg/service/handlers/provider_add.go +++ b/pkg/service/handlers/provider_add.go @@ -22,7 +22,7 @@ func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSv req *bindexec.Request[*providercaps.AddArguments], res *bindexec.Response[*providercaps.AddOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() account, err := didmailto.Parse(req.Invocation().Subject().DID().String()) if err != nil { log.Warn("invalid account", zap.Stringer("account", req.Invocation().Subject().DID())) diff --git a/pkg/service/handlers/provider_add_test.go b/pkg/service/handlers/provider_add_test.go index 8e1bfca..c7d45bd 100644 --- a/pkg/service/handlers/provider_add_test.go +++ b/pkg/service/handlers/provider_add_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "testing" @@ -17,9 +18,7 @@ import ( "github.com/fil-forge/ucantone/did" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" @@ -103,12 +102,12 @@ func TestProviderAddHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) require.NotNil(t, o) - ok := providercaps.AddOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + var ok providercaps.AddOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.NotEmpty(t, ok.ID) }) @@ -135,10 +134,10 @@ func TestProviderAddHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := providercaps.AddOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok providercaps.AddOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.NotEmpty(t, ok.ID) }) @@ -165,11 +164,11 @@ func TestProviderAddHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, providercaps.InvalidAccountErrorName, model.Name()) }) @@ -196,11 +195,11 @@ func TestProviderAddHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, providercaps.AccountPlanMissingErrorName, model.Name()) }) @@ -228,11 +227,11 @@ func TestProviderAddHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, provisioning.ProviderNotAllowedErrorName, model.Name()) }) } diff --git a/pkg/service/handlers/space_info_test.go b/pkg/service/handlers/space_info_test.go index 8ef9b28..35f1626 100644 --- a/pkg/service/handlers/space_info_test.go +++ b/pkg/service/handlers/space_info_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "testing" @@ -14,9 +15,7 @@ import ( "github.com/fil-forge/ucantone/did" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" @@ -74,12 +73,12 @@ func TestSpaceInfoHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) require.NotNil(t, o) - ok := spacecaps.InfoOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + var ok spacecaps.InfoOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Providers, 1) require.Equal(t, uploadService.DID(), ok.Providers[0]) }) @@ -100,11 +99,11 @@ func TestSpaceInfoHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) - ok := spacecaps.InfoOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + var ok spacecaps.InfoOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Providers) }) @@ -125,11 +124,11 @@ func TestSpaceInfoHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, spacecaps.UnknownSpaceErrorName, model.Name()) }) } diff --git a/pkg/service/handlers/ucan_conclude.go b/pkg/service/handlers/ucan_conclude.go index c09134f..1c87e14 100644 --- a/pkg/service/handlers/ucan_conclude.go +++ b/pkg/service/handlers/ucan_conclude.go @@ -40,7 +40,7 @@ func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handl req *bindexec.Request[*ucancaps.ConcludeArguments], res *bindexec.Response[*ucancaps.ConcludeOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() rcptRoot := args.Receipt log := log.With(zap.Stringer("receipt", rcptRoot)) diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index 3e62e52..be579a0 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "context" "fmt" @@ -15,9 +16,6 @@ import ( blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/errors" edm "github.com/fil-forge/ucantone/errors/datamodel" - "github.com/fil-forge/ucantone/ipld" - "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) @@ -39,11 +37,10 @@ func NewHTTPPutConcludeHandler( log := log.With(zap.Stringer("ran", putRcpt.Ran())) log.Debug("handling conclude") - putArgs := httpcaps.PutArguments{} - err := datamodel.Rebind(datamodel.NewAny(putInv.Arguments()), &putArgs) - if err != nil { - log.Error("failed to rebind HTTP PUT arguments", zap.Error(err)) - return fmt.Errorf("rebinding HTTP PUT arguments: %w", err) + var putArgs httpcaps.PutArguments + if err := putArgs.UnmarshalCBOR(bytes.NewReader(putInv.ArgumentsBytes())); err != nil { + log.Error("failed to unmarshal HTTP PUT arguments", zap.Error(err)) + return fmt.Errorf("unmarshaling HTTP PUT arguments: %w", err) } allocTaskLink := putArgs.Destination.Task @@ -67,11 +64,10 @@ func NewHTTPPutConcludeHandler( zap.Stringer("provider", provider.DID()), ) - allocArgs := blobcaps.AllocateArguments{} - err = datamodel.Rebind(datamodel.NewAny(allocInv.Arguments()), &allocArgs) - if err != nil { - log.Error("failed to rebind allocate arguments", zap.Error(err)) - return fmt.Errorf("rebinding allocate arguments: %w", err) + var allocArgs blobcaps.AllocateArguments + if err := allocArgs.UnmarshalCBOR(bytes.NewReader(allocInv.ArgumentsBytes())); err != nil { + log.Error("failed to unmarshal allocate arguments", zap.Error(err)) + return fmt.Errorf("unmarshaling allocate arguments: %w", err) } log = log.With(zap.String("digest", digestutil.Format(allocArgs.Blob.Digest))) @@ -106,30 +102,24 @@ func NewHTTPPutConcludeHandler( return fmt.Errorf("writing agent message: %w", err) } - // if accept task was not successful do not register the blob in the space - return result.MatchResultR1( - accRcpt.Out(), - func(o ipld.Any) error { - log.Debug("accept success") - err := blobRegistry.Register(ctx, space.DID(), allocArgs.Blob, allocArgs.Cause) - // it's ok if there's already a registration of this blob in this space - if err != nil && !errors.Is(err, blobregistry.ErrEntryExists) { - return err - } - return nil - }, - func(x ipld.Any) error { - var model edm.ErrorModel - err := datamodel.Rebind(datamodel.NewAny(x), &model) - if err != nil { - log.Error("failed to bind execution failure", zap.Error(err)) - log.Error("failed execution", zap.Any("error", x)) - return fmt.Errorf("executing blob accept: %v", x) - } - log.Error("failed execution", zap.String("name", model.ErrorName), zap.Error(model)) - return fmt.Errorf("executing blob accept: %w", model) - }, - ) + if accRcpt.Out().IsErr() { + _, x := accRcpt.Out().Unpack() + var model edm.ErrorModel + if err := model.UnmarshalCBOR(bytes.NewReader(x)); err != nil { + log.Error("failed to unmarshal blob accept execution failure", zap.Error(err), zap.Binary("input", x)) + return fmt.Errorf("executing blob accept: %v", x) + } + log.Error("failed execution of blob accept", zap.String("name", model.ErrorName), zap.Error(model)) + return fmt.Errorf("executing blob accept: %w", model) + } + + log.Debug("accept success") + err = blobRegistry.Register(ctx, space.DID(), allocArgs.Blob, allocArgs.Cause) + // it's ok if there's already a registration of this blob in this space + if err != nil && !errors.Is(err, blobregistry.ErrEntryExists) { + return err + } + return nil }, } } diff --git a/pkg/service/handlers/ucan_conclude_http_put_test.go b/pkg/service/handlers/ucan_conclude_http_put_test.go index ae2da17..81e3174 100644 --- a/pkg/service/handlers/ucan_conclude_http_put_test.go +++ b/pkg/service/handlers/ucan_conclude_http_put_test.go @@ -18,8 +18,6 @@ import ( metrics_store "github.com/fil-forge/sprue/pkg/store/metrics/memory" spacediff_store "github.com/fil-forge/sprue/pkg/store/space_diff/memory" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - "github.com/fil-forge/ucantone/ipld" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" @@ -85,10 +83,10 @@ func TestHTTPPutConcludeHandler(t *testing.T) { ) require.NoError(t, err) - putRcpt, err := receipt.Issue( + putRcpt, err := receipt.IssueOK( blobProvider, putInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &httpcaps.PutOK{})), + &httpcaps.PutOK{}, ) require.NoError(t, err) @@ -114,10 +112,10 @@ func TestHTTPPutConcludeHandler(t *testing.T) { invocation.WithAudience(storageProvider), ) require.NoError(t, err) - allocRcpt, err := receipt.Issue( + allocRcpt, err := receipt.IssueOK( storageProvider, allocInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &blobcaps.AllocateOK{Size: blob.Size})), + &blobcaps.AllocateOK{Size: blob.Size}, ) require.NoError(t, err) msg := container.New( @@ -137,10 +135,10 @@ func TestHTTPPutConcludeHandler(t *testing.T) { invocation.WithAudience(blobProvider), ) require.NoError(t, err) - putRcpt, err := receipt.Issue( + putRcpt, err := receipt.IssueOK( blobProvider, putInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &httpcaps.PutOK{})), + &httpcaps.PutOK{}, ) require.NoError(t, err) @@ -184,10 +182,10 @@ func TestHTTPPutConcludeHandler(t *testing.T) { invocation.WithAudience(storageProvider), ) require.NoError(t, err) - allocRcpt, err := receipt.Issue( + allocRcpt, err := receipt.IssueOK( storageProvider, allocInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &blobcaps.AllocateOK{Size: blob.Size})), + &blobcaps.AllocateOK{Size: blob.Size}, ) require.NoError(t, err) msg := container.New( @@ -208,10 +206,10 @@ func TestHTTPPutConcludeHandler(t *testing.T) { invocation.WithAudience(blobProvider), ) require.NoError(t, err) - putRcpt, err := receipt.Issue( + putRcpt, err := receipt.IssueOK( blobProvider, putInv.Task().Link(), - result.OK[ipld.Map, ipld.Any](mustRebindMap(t, &httpcaps.PutOK{})), + &httpcaps.PutOK{}, ) require.NoError(t, err) diff --git a/pkg/service/handlers/ucan_conclude_test.go b/pkg/service/handlers/ucan_conclude_test.go index 424f432..591d532 100644 --- a/pkg/service/handlers/ucan_conclude_test.go +++ b/pkg/service/handlers/ucan_conclude_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "testing" @@ -12,9 +13,7 @@ import ( agent_store "github.com/fil-forge/sprue/pkg/store/agent/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld" "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" @@ -34,10 +33,10 @@ func TestUCANConcludeHandler(t *testing.T) { t.Helper() taskInv, err := invocation.Invoke(uploadService, uploadService, cmd, datamodel.Map{}) require.NoError(t, err) - rcpt, err := receipt.Issue( + rcpt, err := receipt.IssueOK( uploadService, taskInv.Task().Link(), - result.OK[int64, ipld.Any](int64(1)), + datamodel.NewAny(int64(1)), ) require.NoError(t, err) return taskInv, rcpt @@ -70,12 +69,11 @@ func TestUCANConcludeHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - err = datamodel.Rebind(datamodel.NewAny(fail), &model) - require.NoError(t, err) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, ucancaps.ConclusionReceiptNotFoundErrorName, model.Name()) }) @@ -106,8 +104,7 @@ func TestUCANConcludeHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) }) t.Run("dispatches to registered handler", func(t *testing.T) { @@ -156,8 +153,7 @@ func TestUCANConcludeHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) require.True(t, called) require.Equal(t, taskInv.Task().Link(), gotInv.Task().Link()) @@ -196,8 +192,7 @@ func TestUCANConcludeHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) }) t.Run("invocation supplied via metadata", func(t *testing.T) { @@ -237,8 +232,7 @@ func TestUCANConcludeHandler(t *testing.T) { err = handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) require.True(t, called) }) } diff --git a/pkg/service/handlers/upload_add.go b/pkg/service/handlers/upload_add.go index 47e4116..f99c1c5 100644 --- a/pkg/service/handlers/upload_add.go +++ b/pkg/service/handlers/upload_add.go @@ -21,7 +21,7 @@ func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore uplo req *bindexec.Request[*uploadcaps.AddArguments], res *bindexec.Response[*uploadcaps.AddOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() space := req.Invocation().Subject() cause := req.Invocation().Task().Link() log := log.With( diff --git a/pkg/service/handlers/upload_add_test.go b/pkg/service/handlers/upload_add_test.go index 1aa58b1..82ff5d0 100644 --- a/pkg/service/handlers/upload_add_test.go +++ b/pkg/service/handlers/upload_add_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "testing" @@ -16,9 +17,7 @@ import ( "github.com/fil-forge/ucantone/did" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" @@ -101,11 +100,11 @@ func TestUploadAddHandler(t *testing.T) { err := deps.handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.NotNil(t, fail) + _, x := res.Receipt().Out().Unpack() + require.NotNil(t, x) - model := edm.ErrorModel{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(fail), &model)) + var model edm.ErrorModel + require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) // Nothing should have been persisted. @@ -126,8 +125,7 @@ func TestUploadAddHandler(t *testing.T) { err := deps.handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) // Upload should be persisted. exists, err := deps.store.Exists(ctx, space.DID(), root) @@ -158,8 +156,7 @@ func TestUploadAddHandler(t *testing.T) { err := deps.handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) exists, err := deps.store.Exists(ctx, space.DID(), root) require.NoError(t, err) @@ -183,8 +180,7 @@ func TestUploadAddHandler(t *testing.T) { err := deps.handler.Handler(req, res) require.NoError(t, err) - _, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + require.False(t, res.Receipt().Out().IsErr()) exists, err := deps.store.Exists(ctx, space.DID(), root) require.NoError(t, err) @@ -205,8 +201,7 @@ func TestUploadAddHandler(t *testing.T) { Shards: []cid.Cid{shard1}, }) require.NoError(t, deps.handler.Handler(req1, res1)) - _, fail1 := result.Unwrap(res1.Receipt().Out()) - require.Nil(t, fail1) + require.False(t, res1.Receipt().Out().IsErr()) // Add again with a new shard. shard2 := testutil.RandomCID(t) @@ -215,8 +210,7 @@ func TestUploadAddHandler(t *testing.T) { Shards: []cid.Cid{shard2}, }) require.NoError(t, deps.handler.Handler(req2, res2)) - _, fail2 := result.Unwrap(res2.Receipt().Out()) - require.Nil(t, fail2) + require.False(t, res2.Receipt().Out().IsErr()) // Upload should still exist. exists, err := deps.store.Exists(ctx, space.DID(), root) diff --git a/pkg/service/handlers/upload_list.go b/pkg/service/handlers/upload_list.go index ce2717c..a7656cd 100644 --- a/pkg/service/handlers/upload_list.go +++ b/pkg/service/handlers/upload_list.go @@ -17,7 +17,7 @@ func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Ha req *bindexec.Request[*uploadcaps.ListArguments], res *bindexec.Response[*uploadcaps.ListOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() space := req.Invocation().Subject() log := log.With(zap.Stringer("space", space.DID())) diff --git a/pkg/service/handlers/upload_list_test.go b/pkg/service/handlers/upload_list_test.go index 75ca4a5..904bdfa 100644 --- a/pkg/service/handlers/upload_list_test.go +++ b/pkg/service/handlers/upload_list_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "testing" @@ -9,9 +10,7 @@ import ( "github.com/fil-forge/sprue/pkg/service/handlers" upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" @@ -59,12 +58,12 @@ func TestUploadListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) require.NotNil(t, o) - ok := uploadcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + var ok uploadcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) require.Nil(t, ok.Cursor) }) @@ -85,10 +84,10 @@ func TestUploadListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := uploadcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok uploadcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) roots := map[string]bool{} @@ -114,10 +113,10 @@ func TestUploadListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := uploadcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok uploadcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) require.NotNil(t, ok.Cursor) }) @@ -135,10 +134,10 @@ func TestUploadListHandler(t *testing.T) { req1, res1 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Size: &size}) require.NoError(t, handler.Handler(req1, res1)) - o1, fail := result.Unwrap(res1.Receipt().Out()) - require.Nil(t, fail) - ok1 := uploadcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o1), &ok1)) + o1, x := res1.Receipt().Out().Unpack() + require.Nil(t, x) + var ok1 uploadcaps.ListOK + require.NoError(t, ok1.UnmarshalCBOR(bytes.NewReader(o1))) require.Len(t, ok1.Results, 1) require.NotNil(t, ok1.Cursor) @@ -147,10 +146,10 @@ func TestUploadListHandler(t *testing.T) { req2, res2 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Cursor: &cursor, Size: &size}) require.NoError(t, handler.Handler(req2, res2)) - o2, fail := result.Unwrap(res2.Receipt().Out()) - require.Nil(t, fail) - ok2 := uploadcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o2), &ok2)) + o2, x := res2.Receipt().Out().Unpack() + require.Nil(t, x) + var ok2 uploadcaps.ListOK + require.NoError(t, ok2.UnmarshalCBOR(bytes.NewReader(o2))) require.Len(t, ok2.Results, 1) require.NotEqual(t, ok1.Results[0].Root.String(), ok2.Results[0].Root.String()) }) @@ -168,10 +167,10 @@ func TestUploadListHandler(t *testing.T) { req, res := invokeUploadList(t, ctx, alice, uploadService, space2, &uploadcaps.ListArguments{}) require.NoError(t, handler.Handler(req, res)) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := uploadcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok uploadcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) }) @@ -188,10 +187,10 @@ func TestUploadListHandler(t *testing.T) { require.NoError(t, handler.Handler(req, res)) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := uploadcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok uploadcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 1) require.NotNil(t, ok.Results[0].Index) require.Equal(t, cid.Cid(index), *ok.Results[0].Index) diff --git a/pkg/service/handlers/upload_shard_list.go b/pkg/service/handlers/upload_shard_list.go index 8f73e27..43fb5fc 100644 --- a/pkg/service/handlers/upload_shard_list.go +++ b/pkg/service/handlers/upload_shard_list.go @@ -18,7 +18,7 @@ func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logge req *bindexec.Request[*shardcaps.ListArguments], res *bindexec.Response[*shardcaps.ListOK], ) error { - args := req.Task().BindArguments() + args := req.Task().Arguments() space := req.Invocation().Subject() root := args.Root log := log.With(zap.Stringer("space", space.DID()), zap.Stringer("root", root)) diff --git a/pkg/service/handlers/upload_shard_list_test.go b/pkg/service/handlers/upload_shard_list_test.go index 8802bba..0500366 100644 --- a/pkg/service/handlers/upload_shard_list_test.go +++ b/pkg/service/handlers/upload_shard_list_test.go @@ -1,6 +1,7 @@ package handlers_test import ( + "bytes" "context" "testing" @@ -9,9 +10,7 @@ import ( "github.com/fil-forge/sprue/pkg/service/handlers" upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" @@ -64,12 +63,12 @@ func TestUploadShardListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) require.NotNil(t, o) - ok := shardcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + var ok shardcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) }) @@ -89,10 +88,10 @@ func TestUploadShardListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := shardcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok shardcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) got := map[string]bool{} @@ -120,10 +119,10 @@ func TestUploadShardListHandler(t *testing.T) { err := handler.Handler(req, res) require.NoError(t, err) - o, fail := result.Unwrap(res.Receipt().Out()) - require.Nil(t, fail) - ok := shardcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o), &ok)) + o, x := res.Receipt().Out().Unpack() + require.Nil(t, x) + var ok shardcaps.ListOK + require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) require.NotNil(t, ok.Cursor) }) @@ -143,10 +142,10 @@ func TestUploadShardListHandler(t *testing.T) { req1, res1 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Size: &size}) require.NoError(t, handler.Handler(req1, res1)) - o1, fail := result.Unwrap(res1.Receipt().Out()) - require.Nil(t, fail) - ok1 := shardcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o1), &ok1)) + o1, x := res1.Receipt().Out().Unpack() + require.Nil(t, x) + var ok1 shardcaps.ListOK + require.NoError(t, ok1.UnmarshalCBOR(bytes.NewReader(o1))) require.Len(t, ok1.Results, 1) require.NotNil(t, ok1.Cursor) @@ -155,10 +154,10 @@ func TestUploadShardListHandler(t *testing.T) { req2, res2 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Cursor: &cursor, Size: &size}) require.NoError(t, handler.Handler(req2, res2)) - o2, fail := result.Unwrap(res2.Receipt().Out()) - require.Nil(t, fail) - ok2 := shardcaps.ListOK{} - require.NoError(t, datamodel.Rebind(datamodel.NewAny(o2), &ok2)) + o2, x := res2.Receipt().Out().Unpack() + require.Nil(t, x) + var ok2 shardcaps.ListOK + require.NoError(t, ok2.UnmarshalCBOR(bytes.NewReader(o2))) require.Len(t, ok2.Results, 1) require.NotEqual(t, ok1.Results[0].String(), ok2.Results[0].String()) }) diff --git a/pkg/store/agent/agent_test.go b/pkg/store/agent/agent_test.go index efe9966..2600248 100644 --- a/pkg/store/agent/agent_test.go +++ b/pkg/store/agent/agent_test.go @@ -11,9 +11,7 @@ import ( agentaws "github.com/fil-forge/sprue/pkg/store/agent/aws" agentmemory "github.com/fil-forge/sprue/pkg/store/agent/memory" agentpostgres "github.com/fil-forge/sprue/pkg/store/agent/postgres" - "github.com/fil-forge/ucantone/ipld" "github.com/fil-forge/ucantone/ipld/datamodel" - "github.com/fil-forge/ucantone/result" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" @@ -111,10 +109,10 @@ func makeInvocation(t *testing.T) ucan.Invocation { func makeReceipt(t *testing.T, inv ucan.Invocation) ucan.Receipt { t.Helper() - rcpt, err := receipt.Issue( + rcpt, err := receipt.IssueOK( testutil.Alice, inv.Task().Link(), - result.OK[ipld.Any, ipld.Any](datamodel.Map{}), + datamodel.Map{}, ) require.NoError(t, err) return rcpt From a1dfb17517bc482f21fccef95fa7e3ad4fbf4253 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 18 May 2026 17:29:34 +0100 Subject: [PATCH 11/23] fix: include indexer delegation --- pkg/indexerclient/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/indexerclient/client.go b/pkg/indexerclient/client.go index 26107be..b8208d9 100644 --- a/pkg/indexerclient/client.go +++ b/pkg/indexerclient/client.go @@ -81,6 +81,7 @@ func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid c.logger, inv, execution.WithDelegations(prfs...), + execution.WithDelegations(indexerDelegation), execution.WithInvocations(attestations...), ) if err != nil { From b25816ce6039205114d821e510721424528dcbe3 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 18 May 2026 20:24:33 +0100 Subject: [PATCH 12/23] chore: upgrade ucantone --- go.mod | 4 +- go.sum | 10 ++-- pkg/capabilities/admin/provider/deregister.go | 4 +- pkg/capabilities/admin/provider/register.go | 4 +- pkg/capabilities/admin/provider/weight/set.go | 4 +- pkg/indexerclient/client.go | 6 +-- pkg/lib/ucan_client/execute.go | 8 ++-- pkg/lib/ucan_server/proofs.go | 27 +++++------ pkg/lib/ucan_server/validation.go | 3 +- pkg/lib/ucan_server/validation_test.go | 24 +++++----- pkg/piriclient/client.go | 26 +++++----- pkg/piriclient/provider.go | 7 +-- pkg/routing/service.go | 16 +++---- pkg/routing/service_test.go | 8 ++-- pkg/service/handlers/access_claim.go | 4 +- pkg/service/handlers/access_claim_test.go | 24 +++++----- pkg/service/handlers/access_confirm.go | 17 +++---- pkg/service/handlers/access_confirm_test.go | 25 +++++----- pkg/service/handlers/access_delegate.go | 4 +- pkg/service/handlers/access_delegate_test.go | 22 ++++----- pkg/service/handlers/access_request.go | 10 ++-- pkg/service/handlers/access_request_test.go | 25 +++++----- .../handlers/admin_provider_deregister.go | 4 +- .../admin_provider_deregister_test.go | 9 ++-- pkg/service/handlers/admin_provider_list.go | 4 +- .../handlers/admin_provider_list_test.go | 9 ++-- .../handlers/admin_provider_register.go | 4 +- .../handlers/admin_provider_register_test.go | 11 +++-- .../handlers/admin_provider_weight_set.go | 4 +- .../admin_provider_weight_set_test.go | 9 ++-- pkg/service/handlers/blob_add.go | 23 ++++----- pkg/service/handlers/blob_add_test.go | 48 +++++++++---------- pkg/service/handlers/blob_list.go | 6 +-- pkg/service/handlers/blob_list_test.go | 8 ++-- pkg/service/handlers/index_add.go | 8 ++-- pkg/service/handlers/index_add_test.go | 6 +-- pkg/service/handlers/provider_add.go | 4 +- pkg/service/handlers/provider_add_test.go | 7 ++- pkg/service/handlers/space_info.go | 6 +-- pkg/service/handlers/space_info_test.go | 9 ++-- .../handlers/ucan_conclude_http_put.go | 10 ++-- .../handlers/ucan_conclude_http_put_test.go | 22 ++++----- pkg/service/handlers/ucan_conclude_test.go | 22 ++++----- pkg/service/handlers/upload_add.go | 6 +-- pkg/service/handlers/upload_add_test.go | 4 +- pkg/service/handlers/upload_list.go | 6 +-- pkg/service/handlers/upload_list_test.go | 8 ++-- pkg/service/handlers/upload_shard_list.go | 6 +-- .../handlers/upload_shard_list_test.go | 8 ++-- pkg/store/agent/agent_test.go | 8 ++-- pkg/store/delegation/aws/store.go | 10 ++-- pkg/store/delegation/delegation_test.go | 19 ++++---- pkg/store/delegation/memory/store.go | 8 ++-- pkg/store/delegation/postgres/store.go | 10 ++-- 54 files changed, 305 insertions(+), 303 deletions(-) diff --git a/go.mod b/go.mod index 3134288..6af3a17 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260512195724-a514bd7a6c50 - github.com/fil-forge/ucantone v0.0.0-20260512173820-ea7128569686 + github.com/fil-forge/libforge v0.0.0-20260518170714-75d1051f495e + github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096 github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 github.com/jackc/pgx/v5 v5.8.0 diff --git a/go.sum b/go.sum index cab7c0d..2531bb8 100644 --- a/go.sum +++ b/go.sum @@ -92,10 +92,12 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fil-forge/libforge v0.0.0-20260512195724-a514bd7a6c50 h1:+UmG/EqW+OIRWS0JfOmvVneNoWIWKPbMzbBcvbF4Auw= -github.com/fil-forge/libforge v0.0.0-20260512195724-a514bd7a6c50/go.mod h1:muFokjvd+LB7EGfsftSTpj5RZ1be1yjXe3XBXPswtuw= -github.com/fil-forge/ucantone v0.0.0-20260512173820-ea7128569686 h1:Weu36rT9cQrmM41qX1EL3ML1K1a+bdidVGacy75HSro= -github.com/fil-forge/ucantone v0.0.0-20260512173820-ea7128569686/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= +github.com/fil-forge/libforge v0.0.0-20260518163938-03d44aed8634 h1:XESaVIJmFelO82tcCtqKDk5MRch5IFl+Fr77+7v7Oxk= +github.com/fil-forge/libforge v0.0.0-20260518163938-03d44aed8634/go.mod h1:AzKrj2fvRy00U3s/TrfXjjxhNqdImaaiSEV5HkJXFbU= +github.com/fil-forge/libforge v0.0.0-20260518170714-75d1051f495e h1:STRMQCozqw7PQiS2sRAgDcoUhfoFmYFVYPaampklTyM= +github.com/fil-forge/libforge v0.0.0-20260518170714-75d1051f495e/go.mod h1:AzKrj2fvRy00U3s/TrfXjjxhNqdImaaiSEV5HkJXFbU= +github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096 h1:T/JkfzRNu4/9OnqgggVWbn+OXs/y12xDSVEen0NS7EY= +github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/pkg/capabilities/admin/provider/deregister.go b/pkg/capabilities/admin/provider/deregister.go index 3db3d40..2243cf8 100644 --- a/pkg/capabilities/admin/provider/deregister.go +++ b/pkg/capabilities/admin/provider/deregister.go @@ -1,7 +1,7 @@ package provider import ( - cdm "github.com/fil-forge/libforge/capabilities/datamodel" + cdm "github.com/fil-forge/libforge/capabilities" pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" "github.com/fil-forge/ucantone/validator/bindcap" ) @@ -10,7 +10,7 @@ const DeregisterCommand = "/admin/provider/deregister" type ( DeregisterArguments = pdm.DeregisterArgumentsModel - DeregisterOK = cdm.UnitModel + DeregisterOK = cdm.Unit ) var Deregister, _ = bindcap.New[*DeregisterArguments](DeregisterCommand) diff --git a/pkg/capabilities/admin/provider/register.go b/pkg/capabilities/admin/provider/register.go index ab85d89..1a2baa3 100644 --- a/pkg/capabilities/admin/provider/register.go +++ b/pkg/capabilities/admin/provider/register.go @@ -1,7 +1,7 @@ package provider import ( - cdm "github.com/fil-forge/libforge/capabilities/datamodel" + cdm "github.com/fil-forge/libforge/capabilities" pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" "github.com/fil-forge/ucantone/validator/bindcap" ) @@ -10,7 +10,7 @@ const RegisterCommand = "/admin/provider/register" type ( RegisterArguments = pdm.RegisterArgumentsModel - RegisterOK = cdm.UnitModel + RegisterOK = cdm.Unit ) var Register, _ = bindcap.New[*RegisterArguments](RegisterCommand) diff --git a/pkg/capabilities/admin/provider/weight/set.go b/pkg/capabilities/admin/provider/weight/set.go index 3e4b068..1bec8a8 100644 --- a/pkg/capabilities/admin/provider/weight/set.go +++ b/pkg/capabilities/admin/provider/weight/set.go @@ -1,7 +1,7 @@ package weight import ( - cdm "github.com/fil-forge/libforge/capabilities/datamodel" + cdm "github.com/fil-forge/libforge/capabilities" wdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight/datamodel" "github.com/fil-forge/ucantone/ucan/delegation/policy" "github.com/fil-forge/ucantone/validator/bindcap" @@ -12,7 +12,7 @@ const SetCommand = "/provider/weight/set" type ( SetArguments = wdm.SetArgumentsModel - SetOK = cdm.UnitModel + SetOK = cdm.Unit ) var Set, _ = bindcap.New[*SetArguments]( diff --git a/pkg/indexerclient/client.go b/pkg/indexerclient/client.go index b8208d9..7683b84 100644 --- a/pkg/indexerclient/client.go +++ b/pkg/indexerclient/client.go @@ -48,11 +48,11 @@ func New(endpoint *url.URL, indexerDID did.DID, signer ucan.Signer, logger *zap. // The proofStore parameter is used to build the delegation chain authorizing // the upload service to retrieve the index blob via `/content/retrieve` command. func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid.Cid, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Receipt, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer, contentcaps.RetrieveCommand, space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), contentcaps.RetrieveCommand, space) if err != nil { return nil, fmt.Errorf("building proof chain: %w", err) } - attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer) + attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer.DID()) if err != nil { return nil, fmt.Errorf("building attestations: %w", err) } @@ -64,7 +64,7 @@ func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid inv, err := assertcaps.Index.Invoke( c.signer, - c.signer, + c.signer.DID(), &assertcaps.IndexArguments{Index: index}, invocation.WithAudience(c.indexerDID), invocation.WithMetadata( diff --git a/pkg/lib/ucan_client/execute.go b/pkg/lib/ucan_client/execute.go index e1c7e25..60c6589 100644 --- a/pkg/lib/ucan_client/execute.go +++ b/pkg/lib/ucan_client/execute.go @@ -25,13 +25,13 @@ func Execute[T cbg.CBORUnmarshaler]( options ...execution.RequestOption, ) (T, ucan.Receipt, error) { fields := []zap.Field{ - zap.Stringer("issuer", inv.Issuer().DID()), - zap.Stringer("subject", inv.Subject().DID()), + zap.Stringer("issuer", inv.Issuer()), + zap.Stringer("subject", inv.Subject()), zap.Stringer("command", inv.Command()), zap.Object("arguments", zapipld.RawMap(inv.ArgumentsBytes())), } - if inv.Audience() != nil { - fields = append(fields, zap.Stringer("audience", inv.Audience().DID())) + if inv.Audience().Defined() { + fields = append(fields, zap.Stringer("audience", inv.Audience())) } if len(inv.MetadataBytes()) > 0 { fields = append(fields, zap.Object("metadata", zapipld.RawMap(inv.MetadataBytes()))) diff --git a/pkg/lib/ucan_server/proofs.go b/pkg/lib/ucan_server/proofs.go index 9477c6d..a570b23 100644 --- a/pkg/lib/ucan_server/proofs.go +++ b/pkg/lib/ucan_server/proofs.go @@ -5,12 +5,14 @@ import ( "iter" ucanlib "github.com/fil-forge/libforge/ucan" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/ucan" + "github.com/ipfs/go-cid" ) type ProofStore interface { - ProofChain(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Principal) ([]ucan.Delegation, []ucan.Link, error) - ProofAttestations(ctx context.Context, proofs []ucan.Delegation, authority ucan.Principal) ([]ucan.Invocation, error) + ProofChain(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) ([]ucan.Delegation, []cid.Cid, error) + ProofAttestations(ctx context.Context, proofs []ucan.Delegation, authority did.DID) ([]ucan.Invocation, error) } // ContainerProofStore is a proof store backed by an in-memory container. @@ -23,21 +25,21 @@ func NewContainerProofStore(ct ucan.Container) *ContainerProofStore { return &ContainerProofStore{container: ct} } -func (cps *ContainerProofStore) ProofChain(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Principal) ([]ucan.Delegation, []ucan.Link, error) { +func (cps *ContainerProofStore) ProofChain(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) ([]ucan.Delegation, []cid.Cid, error) { return ucanlib.ProofChain(ctx, cps.matchDelegations, aud, cmd, sub) } -func (cps *ContainerProofStore) ProofAttestations(ctx context.Context, proofs []ucan.Delegation, authority ucan.Principal) ([]ucan.Invocation, error) { +func (cps *ContainerProofStore) ProofAttestations(ctx context.Context, proofs []ucan.Delegation, authority did.DID) ([]ucan.Invocation, error) { return ucanlib.ProofAttestations(ctx, cps.listInvocations, proofs, authority) } -func (ps *ContainerProofStore) listDelegations(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Subject) iter.Seq2[ucan.Delegation, error] { +func (ps *ContainerProofStore) listDelegations(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) iter.Seq2[ucan.Delegation, error] { return func(yield func(ucan.Delegation, error) bool) { if ps.container == nil { return } for _, d := range ps.container.Delegations() { - if d.Audience().DID() == aud.DID() && d.Command() == cmd && equalSubject(d.Subject(), sub) { + if d.Audience() == aud && d.Command() == cmd && d.Subject() == sub { if !yield(d, nil) { return } @@ -46,17 +48,17 @@ func (ps *ContainerProofStore) listDelegations(ctx context.Context, aud ucan.Pri } } -func (ps *ContainerProofStore) matchDelegations(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Subject) iter.Seq2[ucan.Delegation, error] { +func (ps *ContainerProofStore) matchDelegations(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) iter.Seq2[ucan.Delegation, error] { return ucanlib.NewDelegationMatcher(ps.listDelegations)(ctx, aud, cmd, sub) } -func (ps *ContainerProofStore) listInvocations(ctx context.Context, aud ucan.Principal, cmd ucan.Command, sub ucan.Subject) iter.Seq2[ucan.Invocation, error] { +func (ps *ContainerProofStore) listInvocations(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) iter.Seq2[ucan.Invocation, error] { return func(yield func(ucan.Invocation, error) bool) { if ps.container == nil { return } for _, d := range ps.container.Invocations() { - if d.Audience().DID() == aud.DID() && d.Command() == cmd && equalSubject(d.Subject(), sub) { + if d.Audience() == aud && d.Command() == cmd && d.Subject() == sub { if !yield(d, nil) { return } @@ -64,10 +66,3 @@ func (ps *ContainerProofStore) listInvocations(ctx context.Context, aud ucan.Pri } } } - -func equalSubject(a, b ucan.Subject) bool { - if a == nil || b == nil { - return a == nil && b == nil - } - return a.DID() == b.DID() -} diff --git a/pkg/lib/ucan_server/validation.go b/pkg/lib/ucan_server/validation.go index 4d707c2..9b757fc 100644 --- a/pkg/lib/ucan_server/validation.go +++ b/pkg/lib/ucan_server/validation.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/fil-forge/libforge/capabilities/ucan/attest" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/principal" ed_verifier "github.com/fil-forge/ucantone/principal/ed25519/verifier" secp_verifier "github.com/fil-forge/ucantone/principal/secp256k1/verifier" @@ -41,7 +42,7 @@ func NewAttestationVerifier(authority principal.Verifier) validator.NonStandardS continue } // only trust attestations we issued - if inv.Issuer().DID() != authority.DID() || inv.Subject() == nil || inv.Subject().DID() != authority.DID() { + if inv.Issuer() != authority.DID() || inv.Subject() == did.Undef || inv.Subject() != authority.DID() { continue } var args attest.ProofArguments diff --git a/pkg/lib/ucan_server/validation_test.go b/pkg/lib/ucan_server/validation_test.go index 9385566..0a645ea 100644 --- a/pkg/lib/ucan_server/validation_test.go +++ b/pkg/lib/ucan_server/validation_test.go @@ -23,13 +23,13 @@ func TestNewAttestationVerifier(t *testing.T) { account := absentee.From(testutil.Must(did.Parse("did:mailto:web.mail:alice"))(t)) - dlg, err := delegation.Delegate(account, agent, space, "/blob/add") + dlg, err := delegation.Delegate(account, agent.DID(), space.DID(), "/blob/add") require.NoError(t, err) verify := NewAttestationVerifier(authority.Verifier()) t.Run("token is not a delegation", func(t *testing.T) { - inv, err := invocation.Invoke(agent, space, "/blob/add", datamodel.Map{}) + inv, err := invocation.Invoke(agent, space.DID(), "/blob/add", datamodel.Map{}) require.NoError(t, err) err = verify(t.Context(), inv, container.New()) @@ -44,7 +44,7 @@ func TestNewAttestationVerifier(t *testing.T) { }) t.Run("invocation with non-attest command is ignored", func(t *testing.T) { - inv, err := invocation.Invoke(authority, authority, "/some/other", datamodel.Map{}) + inv, err := invocation.Invoke(authority, authority.DID(), "/some/other", datamodel.Map{}) require.NoError(t, err) err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) @@ -53,7 +53,7 @@ func TestNewAttestationVerifier(t *testing.T) { }) t.Run("attestation issued by non-authority is ignored", func(t *testing.T) { - inv, err := attest.Proof.Invoke(other, other, &attest.ProofArguments{Proof: dlg.Link()}) + inv, err := attest.Proof.Invoke(other, other.DID(), &attest.ProofArguments{Proof: dlg.Link()}) require.NoError(t, err) err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) @@ -62,7 +62,7 @@ func TestNewAttestationVerifier(t *testing.T) { }) t.Run("attestation with subject other than authority is ignored", func(t *testing.T) { - inv, err := attest.Proof.Invoke(authority, other, &attest.ProofArguments{Proof: dlg.Link()}) + inv, err := attest.Proof.Invoke(authority, other.DID(), &attest.ProofArguments{Proof: dlg.Link()}) require.NoError(t, err) err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) @@ -71,10 +71,10 @@ func TestNewAttestationVerifier(t *testing.T) { }) t.Run("attestation proof for a different delegation is ignored", func(t *testing.T) { - otherDlg, err := delegation.Delegate(account, agent, space, "/blob/list") + otherDlg, err := delegation.Delegate(account, agent.DID(), space.DID(), "/blob/list") require.NoError(t, err) - inv, err := attest.Proof.Invoke(authority, authority, &attest.ProofArguments{Proof: otherDlg.Link()}) + inv, err := attest.Proof.Invoke(authority, authority.DID(), &attest.ProofArguments{Proof: otherDlg.Link()}) require.NoError(t, err) err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) @@ -85,7 +85,7 @@ func TestNewAttestationVerifier(t *testing.T) { t.Run("attestation with malformed arguments is ignored", func(t *testing.T) { inv, err := invocation.Invoke( authority, - authority, + authority.DID(), attest.ProofCommand, datamodel.Map{"unrelated": "foo"}, ) @@ -97,7 +97,7 @@ func TestNewAttestationVerifier(t *testing.T) { }) t.Run("valid attestation passes verification", func(t *testing.T) { - inv, err := attest.Proof.Invoke(authority, authority, &attest.ProofArguments{Proof: dlg.Link()}) + inv, err := attest.Proof.Invoke(authority, authority.DID(), &attest.ProofArguments{Proof: dlg.Link()}) require.NoError(t, err) err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) @@ -105,11 +105,11 @@ func TestNewAttestationVerifier(t *testing.T) { }) t.Run("valid attestation found among invalid ones", func(t *testing.T) { - untrusted, err := attest.Proof.Invoke(other, other, &attest.ProofArguments{Proof: dlg.Link()}) + untrusted, err := attest.Proof.Invoke(other, other.DID(), &attest.ProofArguments{Proof: dlg.Link()}) require.NoError(t, err) - wrongCmd, err := invocation.Invoke(authority, authority, "/some/other", datamodel.Map{}) + wrongCmd, err := invocation.Invoke(authority, authority.DID(), "/some/other", datamodel.Map{}) require.NoError(t, err) - valid, err := attest.Proof.Invoke(authority, authority, &attest.ProofArguments{Proof: dlg.Link()}) + valid, err := attest.Proof.Invoke(authority, authority.DID(), &attest.ProofArguments{Proof: dlg.Link()}) require.NoError(t, err) err = verify( diff --git a/pkg/piriclient/client.go b/pkg/piriclient/client.go index d381174..d6e0543 100644 --- a/pkg/piriclient/client.go +++ b/pkg/piriclient/client.go @@ -72,8 +72,8 @@ func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore } c.logger.Debug("ALLOCATE invocation created", - zap.Stringer("issuer", inv.Issuer().DID()), - zap.Stringer("audience", inv.Audience().DID()), + zap.Stringer("issuer", inv.Issuer()), + zap.Stringer("audience", inv.Audience()), zap.Int("proofs", len(prfs)), zap.Int("attestations", len(attestations)), ) @@ -94,12 +94,12 @@ func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore // AllocateInvocation returns the invocation for the allocate request (for use in effects). func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer, blobcap.AllocateCommand, req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.AllocateCommand, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } - attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer) + attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer.DID()) if err != nil { return nil, nil, nil, fmt.Errorf("getting proof attestations: %w", err) } @@ -148,8 +148,8 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan } c.logger.Debug("ACCEPT invocation created", - zap.Stringer("issuer", inv.Issuer().DID()), - zap.Stringer("audience", inv.Audience().DID()), + zap.Stringer("issuer", inv.Issuer()), + zap.Stringer("audience", inv.Audience()), zap.Int("proofs", len(prfs)), zap.Int("attestations", len(attestations)), ) @@ -170,12 +170,12 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan // AcceptInvocation returns the invocation for the accept request (for use in effects). func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer, blobcap.AcceptCommand, req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.AcceptCommand, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } - attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer) + attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer.DID()) if err != nil { return nil, nil, nil, fmt.Errorf("getting proof attestations: %w", err) } @@ -216,12 +216,12 @@ type ReplicaAllocateRequest struct { // Returns the response data, the invocation that was sent, and the receipt from // piri. It returns an error if the receipt contains a failure result. func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer, blobreplicacap.AllocateCommand, req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobreplicacap.AllocateCommand, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } - attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer) + attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer.DID()) if err != nil { return nil, nil, nil, fmt.Errorf("getting proof attestations: %w", err) } @@ -234,7 +234,7 @@ func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateReques // We set a reasonably large expiration as replication nodes use the // invocation as proof for obtaining a retrieval delegation, and we want to // allow for retries and/or job queue delays. - invocation.WithExpiration(uint64(time.Now().Add(replicaAllocationTTL).Unix())), + invocation.WithExpiration(ucan.UnixTimestamp(time.Now().Add(replicaAllocationTTL).Unix())), ) inv, err := blobreplicacap.Allocate.Invoke( @@ -252,8 +252,8 @@ func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateReques } c.logger.Debug("REPLICA ALLOCATE invocation created", - zap.Stringer("issuer", inv.Issuer().DID()), - zap.Stringer("audience", inv.Audience().DID()), + zap.Stringer("issuer", inv.Issuer()), + zap.Stringer("audience", inv.Audience()), zap.Int("proofs", len(inv.Proofs()))) allocOK, rcpt, err := ucan_client.Execute[*blobreplicacap.AllocateOK]( diff --git a/pkg/piriclient/provider.go b/pkg/piriclient/provider.go index e878c4d..de5038b 100644 --- a/pkg/piriclient/provider.go +++ b/pkg/piriclient/provider.go @@ -3,13 +3,14 @@ package piriclient import ( "net/url" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) // Provider creates piri clients for communicating with storage nodes. type Provider interface { - Client(id ucan.Principal, endpoint url.URL) (*Client, error) + Client(id did.DID, endpoint url.URL) (*Client, error) } // PiriProvider is the default Provider that creates HTTP-connected piri clients. @@ -26,6 +27,6 @@ func NewProvider(signer ucan.Signer, logger *zap.Logger) *PiriProvider { // Client provides a client configured to communicate with the specified storage // node. -func (p *PiriProvider) Client(id ucan.Principal, endpoint url.URL) (*Client, error) { - return New(&endpoint, id.DID(), p.signer, p.logger) +func (p *PiriProvider) Client(id did.DID, endpoint url.URL) (*Client, error) { + return New(&endpoint, id, p.signer, p.logger) } diff --git a/pkg/routing/service.go b/pkg/routing/service.go index f133475..a1769db 100644 --- a/pkg/routing/service.go +++ b/pkg/routing/service.go @@ -10,8 +10,8 @@ import ( "github.com/fil-forge/libforge/digestutil" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) @@ -22,21 +22,21 @@ const CandidateUnavailableErrorName = "CandidateUnavailable" var ErrCandidateUnavailable = errors.New(CandidateUnavailableErrorName, "no storage providers available") type selectCfg struct { - exclusions []ucan.Principal + exclusions []did.DID } type SelectOption func(*selectCfg) // WithExclusions configures a list of storage providers that should be excluded // from the routing selection. -func WithExclusions(providers ...ucan.Principal) SelectOption { +func WithExclusions(providers ...did.DID) SelectOption { return func(cfg *selectCfg) { cfg.exclusions = append(cfg.exclusions, providers...) } } type StorageProviderInfo struct { - ID ucan.Principal + ID did.DID Endpoint url.URL } @@ -54,8 +54,8 @@ func NewService(storageProviderStore storageprovider.Store, logger *zap.Logger) // GetProviderInfo returns information about a registered storage provider. It // may return [storageprovider.ErrStorageProviderNotFound]. -func (s *Service) GetProviderInfo(ctx context.Context, provider ucan.Principal) (StorageProviderInfo, error) { - rec, err := s.storageProviderStore.Get(ctx, provider.DID()) +func (s *Service) GetProviderInfo(ctx context.Context, provider did.DID) (StorageProviderInfo, error) { + rec, err := s.storageProviderStore.Get(ctx, provider) if err != nil { return StorageProviderInfo{}, err } @@ -111,7 +111,7 @@ func (s *Service) SelectStorageProvider(ctx context.Context, blob blob.Blob, opt // SelectReplicationProvider selects a candidate for blob allocation from the // current list of available storage nodes, excluding the primary node. It may // return [ErrCandidateUnavailable] if no candidates are available. -func (s *Service) SelectReplicationProvider(ctx context.Context, primary ucan.Principal, blob blob.Blob, options ...SelectOption) (StorageProviderInfo, error) { +func (s *Service) SelectReplicationProvider(ctx context.Context, primary did.DID, blob blob.Blob, options ...SelectOption) (StorageProviderInfo, error) { cfg := &selectCfg{} for _, option := range options { option(cfg) @@ -151,7 +151,7 @@ func listProviders(ctx context.Context, providerStore storageprovider.Store) ([] }) } -func filterExcludedProviders(providers []storageprovider.Record, exclusions []ucan.Principal) []storageprovider.Record { +func filterExcludedProviders(providers []storageprovider.Record, exclusions []did.DID) []storageprovider.Record { var filtered []storageprovider.Record for _, prov := range providers { if prov.Weight <= 0 { diff --git a/pkg/routing/service_test.go b/pkg/routing/service_test.go index fe89631..2db8788 100644 --- a/pkg/routing/service_test.go +++ b/pkg/routing/service_test.go @@ -45,7 +45,7 @@ func TestGetProviderInfo(t *testing.T) { svc := routing.NewService(store, logger) unknown := testutil.RandomSigner(t) - _, err := svc.GetProviderInfo(ctx, unknown) + _, err := svc.GetProviderInfo(ctx, unknown.DID()) require.ErrorIs(t, err, storageprovider.ErrStorageProviderNotFound) }) } @@ -115,11 +115,11 @@ func TestSelectStorageProvider(t *testing.T) { for range 100 { info, err := svc.SelectStorageProvider(ctx, blob) require.NoError(t, err) - seen[info.ID.DID().String()] = true + seen[info.ID.String()] = true } // With equal weights over 100 iterations, both should be selected - require.True(t, seen[p1.Provider.DID().String()]) - require.True(t, seen[p2.Provider.DID().String()]) + require.True(t, seen[p1.Provider.String()]) + require.True(t, seen[p2.Provider.String()]) }) } diff --git a/pkg/service/handlers/access_claim.go b/pkg/service/handlers/access_claim.go index 9083aa9..8fa6abf 100644 --- a/pkg/service/handlers/access_claim.go +++ b/pkg/service/handlers/access_claim.go @@ -21,8 +21,8 @@ func NewAccessClaimHandler(id *identity.Identity, delegationStore delegation_sto req *bindexec.Request[*access.ClaimArguments], res *bindexec.Response[*access.ClaimOK], ) error { - agent := req.Invocation().Issuer().DID() - audience := req.Invocation().Subject().DID() + agent := req.Invocation().Issuer() + audience := req.Invocation().Subject() log := log.With( zap.Stringer("agent", agent), diff --git a/pkg/service/handlers/access_claim_test.go b/pkg/service/handlers/access_claim_test.go index dba766c..19ff85a 100644 --- a/pkg/service/handlers/access_claim_test.go +++ b/pkg/service/handlers/access_claim_test.go @@ -29,9 +29,9 @@ func TestAccessClaimHandler(t *testing.T) { args := access.ClaimArguments{} inv, err := access.Claim.Invoke( agent, - agent, + agent.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -58,7 +58,7 @@ func TestAccessClaimHandler(t *testing.T) { agent := testutil.RandomSigner(t) - dlg, err := delegation.Delegate(testutil.Alice, agent, testutil.Alice, "/test/thing") + dlg, err := delegation.Delegate(testutil.Alice, agent.DID(), testutil.Alice.DID(), "/test/thing") require.NoError(t, err) err = store.PutMany(t.Context(), []ucan.Token{dlg}, testutil.RandomCID(t)) @@ -67,9 +67,9 @@ func TestAccessClaimHandler(t *testing.T) { args := access.ClaimArguments{} inv, err := access.Claim.Invoke( agent, - agent, + agent.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -96,10 +96,10 @@ func TestAccessClaimHandler(t *testing.T) { agent := testutil.RandomSigner(t) - dlg1, err := delegation.Delegate(testutil.Alice, agent, testutil.Alice, "/test/one") + dlg1, err := delegation.Delegate(testutil.Alice, agent.DID(), testutil.Alice.DID(), "/test/one") require.NoError(t, err) - dlg2, err := delegation.Delegate(testutil.Bob, agent, testutil.Bob, "/test/two") + dlg2, err := delegation.Delegate(testutil.Bob, agent.DID(), testutil.Bob.DID(), "/test/two") require.NoError(t, err) err = store.PutMany(t.Context(), []ucan.Token{dlg1, dlg2}, testutil.RandomCID(t)) @@ -108,9 +108,9 @@ func TestAccessClaimHandler(t *testing.T) { args := access.ClaimArguments{} inv, err := access.Claim.Invoke( agent, - agent, + agent.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -140,7 +140,7 @@ func TestAccessClaimHandler(t *testing.T) { otherAgent := testutil.RandomSigner(t) // Delegation is for otherAgent, not agent. - dlg, err := delegation.Delegate(testutil.Alice, otherAgent, testutil.Alice, "/test/thing") + dlg, err := delegation.Delegate(testutil.Alice, otherAgent.DID(), testutil.Alice.DID(), "/test/thing") require.NoError(t, err) err = store.PutMany(t.Context(), []ucan.Token{dlg}, testutil.RandomCID(t)) @@ -149,9 +149,9 @@ func TestAccessClaimHandler(t *testing.T) { args := access.ClaimArguments{} inv, err := access.Claim.Invoke( agent, - agent, + agent.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) diff --git a/pkg/service/handlers/access_confirm.go b/pkg/service/handlers/access_confirm.go index f6ba71e..4f19d6e 100644 --- a/pkg/service/handlers/access_confirm.go +++ b/pkg/service/handlers/access_confirm.go @@ -8,6 +8,7 @@ import ( "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/pkg/identity" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/ipld" "github.com/fil-forge/ucantone/ipld/datamodel" @@ -29,14 +30,14 @@ func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_s res *bindexec.Response[*access.ConfirmOK], ) error { args := req.Task().Arguments() - if req.Invocation().Subject().DID() != id.Signer.DID() { - log.Warn("not a valid invocation", zap.Stringer("subject", req.Invocation().Subject().DID())) + if req.Invocation().Subject() != id.Signer.DID() { + log.Warn("not a valid invocation", zap.Stringer("subject", req.Invocation().Subject())) return res.SetFailure(access.ErrInvalidAccessConfirmSubject) } - accountDID, err := didmailto.Parse(args.Issuer.DID().String()) + accountDID, err := didmailto.Parse(args.Issuer.String()) if err != nil { - log.Warn("invalid issuer DID", zap.Stringer("issuer", args.Issuer.DID()), zap.Error(err)) + log.Warn("invalid issuer DID", zap.Stringer("issuer", args.Issuer), zap.Error(err)) return res.SetFailure(access.ErrInvalidAccessConfirmIssuer) } @@ -50,7 +51,7 @@ func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_s } log := log.With( - zap.Stringer("agent", agent.DID()), + zap.Stringer("agent", agent), zap.Stringer("account", account.DID()), zap.Stringer("cause", args.Cause), zap.Strings("commands", cmds), @@ -110,7 +111,7 @@ func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_s func createSessionProofs( service ucan.Signer, account absentee.Signer, - agent ucan.Principal, + agent did.DID, attenuations []access.CapabilityRequest, meta ipld.Map, ) ([]ucan.Delegation, []ucan.Invocation, error) { @@ -123,7 +124,7 @@ func createSessionProofs( agent, // TODO: optionally set subject in capability request // no subject (powerline) will apply to all spaces present and future - nil, + did.Undef, req.Command, delegation.WithMetadata(meta), // default to Infinity is reasonable here because @@ -137,7 +138,7 @@ func createSessionProofs( attestation, err := attest.Proof.Invoke( service, - service, + service.DID(), &attest.ProofArguments{ Proof: dlg.Link(), }, diff --git a/pkg/service/handlers/access_confirm_test.go b/pkg/service/handlers/access_confirm_test.go index f414f4a..9d6df34 100644 --- a/pkg/service/handlers/access_confirm_test.go +++ b/pkg/service/handlers/access_confirm_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/fil-forge/libforge/capabilities/access" - adm "github.com/fil-forge/libforge/capabilities/access/datamodel" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" @@ -32,7 +31,7 @@ func TestAccessConfirmHandler(t *testing.T) { Cause: testutil.RandomCID(t), Issuer: account, Audience: agent.DID(), - Attenuations: []adm.CapabilityRequestModel{ + Attenuations: []access.CapabilityRequest{ {Command: "/"}, }, } @@ -40,9 +39,9 @@ func TestAccessConfirmHandler(t *testing.T) { // Subject is not id.Signer — handler should reject. inv, err := access.Confirm.Invoke( id.Signer, - notService, + notService.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -75,16 +74,16 @@ func TestAccessConfirmHandler(t *testing.T) { Cause: testutil.RandomCID(t), Issuer: nonMailto.DID(), Audience: agent.DID(), - Attenuations: []adm.CapabilityRequestModel{ + Attenuations: []access.CapabilityRequest{ {Command: "/"}, }, } inv, err := access.Confirm.Invoke( id.Signer, - id.Signer, + id.Signer.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -116,16 +115,16 @@ func TestAccessConfirmHandler(t *testing.T) { Cause: testutil.RandomCID(t), Issuer: account, Audience: agent.DID(), - Attenuations: []adm.CapabilityRequestModel{ + Attenuations: []access.CapabilityRequest{ {Command: "/"}, }, } inv, err := access.Confirm.Invoke( id.Signer, - id.Signer, + id.Signer.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -163,7 +162,7 @@ func TestAccessConfirmHandler(t *testing.T) { Cause: testutil.RandomCID(t), Issuer: account, Audience: agent.DID(), - Attenuations: []adm.CapabilityRequestModel{ + Attenuations: []access.CapabilityRequest{ {Command: "/space/blob/add"}, {Command: "/upload/add"}, }, @@ -171,9 +170,9 @@ func TestAccessConfirmHandler(t *testing.T) { inv, err := access.Confirm.Invoke( id.Signer, - id.Signer, + id.Signer.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) diff --git a/pkg/service/handlers/access_delegate.go b/pkg/service/handlers/access_delegate.go index 95e9c92..887b8e2 100644 --- a/pkg/service/handlers/access_delegate.go +++ b/pkg/service/handlers/access_delegate.go @@ -22,8 +22,8 @@ func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioni res *bindexec.Response[*access.DelegateOK], ) error { args := req.Task().Arguments() - agent := req.Invocation().Issuer().DID() - space := req.Invocation().Subject().DID() + agent := req.Invocation().Issuer() + space := req.Invocation().Subject() log := log.With( zap.Stringer("agent", agent), diff --git a/pkg/service/handlers/access_delegate_test.go b/pkg/service/handlers/access_delegate_test.go index cf0ae0c..47a3974 100644 --- a/pkg/service/handlers/access_delegate_test.go +++ b/pkg/service/handlers/access_delegate_test.go @@ -65,9 +65,9 @@ func TestAccessDelegateHandler(t *testing.T) { inv, err := access.Delegate.Invoke( agent, - space, + space.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -92,7 +92,7 @@ func TestAccessDelegateHandler(t *testing.T) { agent := testutil.RandomSigner(t) // Create a delegation from the space to the agent for some capability. - dlg, err := delegation.Delegate(space, agent, space, "/space/blob/add") + dlg, err := delegation.Delegate(space, agent.DID(), space.DID(), "/space/blob/add") require.NoError(t, err) args := access.DelegateArguments{ @@ -101,9 +101,9 @@ func TestAccessDelegateHandler(t *testing.T) { inv, err := access.Delegate.Invoke( agent, - space, + space.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -137,9 +137,9 @@ func TestAccessDelegateHandler(t *testing.T) { inv, err := access.Delegate.Invoke( agent, - space, + space.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -165,10 +165,10 @@ func TestAccessDelegateHandler(t *testing.T) { // Reference a delegation by CID, but don't include it in the request metadata. // We still need at least one delegation in the request so req.Metadata() is non-nil. - other, err := delegation.Delegate(space, agent, space, "/other") + other, err := delegation.Delegate(space, agent.DID(), space.DID(), "/other") require.NoError(t, err) - missing, err := delegation.Delegate(space, agent, space, "/space/blob/add") + missing, err := delegation.Delegate(space, agent.DID(), space.DID(), "/space/blob/add") require.NoError(t, err) args := access.DelegateArguments{ @@ -177,9 +177,9 @@ func TestAccessDelegateHandler(t *testing.T) { inv, err := access.Delegate.Invoke( agent, - space, + space.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) diff --git a/pkg/service/handlers/access_request.go b/pkg/service/handlers/access_request.go index 8c1fafd..0c2277d 100644 --- a/pkg/service/handlers/access_request.go +++ b/pkg/service/handlers/access_request.go @@ -54,8 +54,8 @@ func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identit log.Warn("failed to extract email from DID", zap.Stringer("account", args.Issuer)) return res.SetFailure(errors.New(access.InvalidAuthorizationAccountErrorName, "invalid authorization account DID: %v", err)) } - audience := req.Invocation().Subject().DID() - agent := req.Invocation().Issuer().DID() + audience := req.Invocation().Subject() + agent := req.Invocation().Issuer() log := log.With( zap.Stringer("agent", agent), zap.Stringer("account", account), @@ -87,7 +87,7 @@ func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identit // requests in an attempt to confuse a user into clicking the wrong link. confirmation, err := access.Confirm.Invoke( id.Signer, - id.Signer, + id.Signer.DID(), // We link to the authorization request so that this invocation can // not be used to authorize a different request. &access.ConfirmArguments{ @@ -102,8 +102,8 @@ func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identit // audience same as issuer because this is a service invocation // that will get handled by /access/confirm handler // but only if the receiver of this email wants it to be - invocation.WithAudience(id.Signer), - invocation.WithExpiration(ucan.UTCUnixTimestamp(exp)), + invocation.WithAudience(id.Signer.DID()), + invocation.WithExpiration(ucan.UnixTimestamp(exp)), // we copy the facts in so that information can be passed // from the invoker of this capability to the invoker of the confirm // capability - we use this, for example, to let bsky.storage users diff --git a/pkg/service/handlers/access_request_test.go b/pkg/service/handlers/access_request_test.go index d70f5ee..ad5999a 100644 --- a/pkg/service/handlers/access_request_test.go +++ b/pkg/service/handlers/access_request_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/fil-forge/libforge/capabilities/access" - adm "github.com/fil-forge/libforge/capabilities/access/datamodel" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/internal/testutil" @@ -57,7 +56,7 @@ func TestAccessRequestHandler(t *testing.T) { args := access.RequestArguments{ Issuer: account, - Attenuations: []adm.CapabilityRequestModel{ + Attenuations: []access.CapabilityRequest{ {Command: "/"}, }, } @@ -66,9 +65,9 @@ func TestAccessRequestHandler(t *testing.T) { inv, err := access.Request.Invoke( agent, - id.Signer, + id.Signer.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -102,7 +101,7 @@ func TestAccessRequestHandler(t *testing.T) { nonMailtoSigner := testutil.RandomSigner(t) args := access.RequestArguments{ Issuer: nonMailtoSigner.DID(), - Attenuations: []adm.CapabilityRequestModel{ + Attenuations: []access.CapabilityRequest{ {Command: "/"}, }, } @@ -111,9 +110,9 @@ func TestAccessRequestHandler(t *testing.T) { inv, err := access.Request.Invoke( agent, - id.Signer, + id.Signer.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -142,7 +141,7 @@ func TestAccessRequestHandler(t *testing.T) { args := access.RequestArguments{ Issuer: account, - Attenuations: []adm.CapabilityRequestModel{ + Attenuations: []access.CapabilityRequest{ {Command: "/"}, }, } @@ -151,9 +150,9 @@ func TestAccessRequestHandler(t *testing.T) { inv, err := access.Request.Invoke( agent, - id.Signer, + id.Signer.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) @@ -180,7 +179,7 @@ func TestAccessRequestHandler(t *testing.T) { args := access.RequestArguments{ Issuer: account, - Attenuations: []adm.CapabilityRequestModel{ + Attenuations: []access.CapabilityRequest{ {Command: "/"}, }, } @@ -189,9 +188,9 @@ func TestAccessRequestHandler(t *testing.T) { inv, err := access.Request.Invoke( agent, - id.Signer, + id.Signer.DID(), &args, - invocation.WithAudience(id.Signer), + invocation.WithAudience(id.Signer.DID()), ) require.NoError(t, err) diff --git a/pkg/service/handlers/admin_provider_deregister.go b/pkg/service/handlers/admin_provider_deregister.go index 5019f07..52088c9 100644 --- a/pkg/service/handlers/admin_provider_deregister.go +++ b/pkg/service/handlers/admin_provider_deregister.go @@ -19,8 +19,8 @@ func NewAdminProviderDeregisterHandler(id *identity.Identity, providerStore stor ) error { args := req.Task().Arguments() - if req.Invocation().Issuer().DID() != id.Signer.DID() { - log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) + if req.Invocation().Issuer() != id.Signer.DID() { + log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer())) return res.SetFailure(errors.New("Unauthorized", "only the service identity can deregister a provider")) } diff --git a/pkg/service/handlers/admin_provider_deregister_test.go b/pkg/service/handlers/admin_provider_deregister_test.go index 70e7cf4..d49c0ff 100644 --- a/pkg/service/handlers/admin_provider_deregister_test.go +++ b/pkg/service/handlers/admin_provider_deregister_test.go @@ -12,6 +12,7 @@ import ( storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" @@ -22,7 +23,7 @@ import ( func issueDeregisterInvocation( t *testing.T, issuer ucan.Signer, - audience ucan.Principal, + audience did.DID, args provider.DeregisterArguments, ) execution.Request { t.Helper() @@ -64,7 +65,7 @@ func TestAdminProviderDeregisterHandler(t *testing.T) { Provider: storageProvider.DID(), } - req := issueDeregisterInvocation(t, unauthorizedIssuer, uploadService, args) + req := issueDeregisterInvocation(t, unauthorizedIssuer, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -101,7 +102,7 @@ func TestAdminProviderDeregisterHandler(t *testing.T) { Provider: storageProvider.DID(), } - req := issueDeregisterInvocation(t, uploadService, uploadService, args) + req := issueDeregisterInvocation(t, uploadService, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -126,7 +127,7 @@ func TestAdminProviderDeregisterHandler(t *testing.T) { Provider: storageProvider.DID(), } - req := issueDeregisterInvocation(t, uploadService, uploadService, args) + req := issueDeregisterInvocation(t, uploadService, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) diff --git a/pkg/service/handlers/admin_provider_list.go b/pkg/service/handlers/admin_provider_list.go index 2ce2cee..1a9a512 100644 --- a/pkg/service/handlers/admin_provider_list.go +++ b/pkg/service/handlers/admin_provider_list.go @@ -21,8 +21,8 @@ func NewAdminProviderListHandler(id *identity.Identity, providerStore storagepro req *bindexec.Request[*provider.ListArguments], res *bindexec.Response[*provider.ListOK], ) error { - if req.Invocation().Issuer().DID() != id.Signer.DID() { - log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) + if req.Invocation().Issuer() != id.Signer.DID() { + log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer())) return res.SetFailure(errors.New("Unauthorized", "only the service identity can list providers")) } diff --git a/pkg/service/handlers/admin_provider_list_test.go b/pkg/service/handlers/admin_provider_list_test.go index 0459104..babe84c 100644 --- a/pkg/service/handlers/admin_provider_list_test.go +++ b/pkg/service/handlers/admin_provider_list_test.go @@ -11,6 +11,7 @@ import ( "github.com/fil-forge/sprue/pkg/service/handlers" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" @@ -21,7 +22,7 @@ import ( func issueListInvocation( t *testing.T, issuer ucan.Signer, - audience ucan.Principal, + audience did.DID, ) execution.Request { t.Helper() @@ -52,7 +53,7 @@ func TestAdminProviderListHandler(t *testing.T) { unauthorizedIssuer := testutil.RandomSigner(t) - req := issueListInvocation(t, unauthorizedIssuer, uploadService) + req := issueListInvocation(t, unauthorizedIssuer, uploadService.DID()) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -74,7 +75,7 @@ func TestAdminProviderListHandler(t *testing.T) { &identity.Identity{Signer: uploadService}, spStore, logger, ) - req := issueListInvocation(t, uploadService, uploadService) + req := issueListInvocation(t, uploadService, uploadService.DID()) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -109,7 +110,7 @@ func TestAdminProviderListHandler(t *testing.T) { require.NoError(t, spStore.Put(ctx, sp1.DID(), *endpoint1, 100, &repWeight)) require.NoError(t, spStore.Put(ctx, sp2.DID(), *endpoint2, 200, nil)) - req := issueListInvocation(t, uploadService, uploadService) + req := issueListInvocation(t, uploadService, uploadService.DID()) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index 0054fd2..a68ebb4 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -25,8 +25,8 @@ func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storag res *bindexec.Response[*provider.RegisterOK], ) error { args := req.Task().Arguments() - if req.Invocation().Issuer().DID() != id.Signer.DID() { - log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) + if req.Invocation().Issuer() != id.Signer.DID() { + log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer())) return res.SetFailure(errors.New("Unauthorized", "only the service identity can register providers")) } diff --git a/pkg/service/handlers/admin_provider_register_test.go b/pkg/service/handlers/admin_provider_register_test.go index 98a1664..aad1671 100644 --- a/pkg/service/handlers/admin_provider_register_test.go +++ b/pkg/service/handlers/admin_provider_register_test.go @@ -10,6 +10,7 @@ import ( "github.com/fil-forge/sprue/pkg/service/handlers" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" @@ -21,7 +22,7 @@ import ( func issueRegisterInvocation( t *testing.T, issuer ucan.Signer, - audience ucan.Principal, + audience did.DID, args provider.RegisterArguments, ) execution.Request { t.Helper() @@ -59,7 +60,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { } // Issuer is neither the service nor the provider - req := issueRegisterInvocation(t, unauthorizedIssuer, uploadService, args) + req := issueRegisterInvocation(t, unauthorizedIssuer, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -89,7 +90,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { } // First registration by service identity (authorized) - req := issueRegisterInvocation(t, uploadService, uploadService, args) + req := issueRegisterInvocation(t, uploadService, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -98,7 +99,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { require.False(t, res.Receipt().Out().IsErr()) // Second registration should fail - req2 := issueRegisterInvocation(t, uploadService, uploadService, args) + req2 := issueRegisterInvocation(t, uploadService, uploadService.DID(), args) res2, err := execution.NewResponse(req2.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -128,7 +129,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { Endpoint: "https://piri.example.com", } - req := issueRegisterInvocation(t, uploadService, uploadService, args) + req := issueRegisterInvocation(t, uploadService, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) diff --git a/pkg/service/handlers/admin_provider_weight_set.go b/pkg/service/handlers/admin_provider_weight_set.go index e83696d..0f69d7d 100644 --- a/pkg/service/handlers/admin_provider_weight_set.go +++ b/pkg/service/handlers/admin_provider_weight_set.go @@ -19,8 +19,8 @@ func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore stora res *bindexec.Response[*weight.SetOK], ) error { args := req.Task().Arguments() - if req.Invocation().Issuer().DID() != id.Signer.DID() { - log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer().DID())) + if req.Invocation().Issuer() != id.Signer.DID() { + log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer())) return res.SetFailure(errors.New("Unauthorized", "only the service identity can set provider weights")) } diff --git a/pkg/service/handlers/admin_provider_weight_set_test.go b/pkg/service/handlers/admin_provider_weight_set_test.go index 9256aaf..6db889d 100644 --- a/pkg/service/handlers/admin_provider_weight_set_test.go +++ b/pkg/service/handlers/admin_provider_weight_set_test.go @@ -11,6 +11,7 @@ import ( "github.com/fil-forge/sprue/pkg/service/handlers" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" @@ -21,7 +22,7 @@ import ( func issueWeightSetInvocation( t *testing.T, issuer ucan.Signer, - audience ucan.Principal, + audience did.DID, args weight.SetArguments, ) execution.Request { t.Helper() @@ -59,7 +60,7 @@ func TestAdminProviderWeightSetHandler(t *testing.T) { ReplicationWeight: 25, } - req := issueWeightSetInvocation(t, unauthorizedIssuer, uploadService, args) + req := issueWeightSetInvocation(t, unauthorizedIssuer, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -89,7 +90,7 @@ func TestAdminProviderWeightSetHandler(t *testing.T) { ReplicationWeight: 25, } - req := issueWeightSetInvocation(t, uploadService, uploadService, args) + req := issueWeightSetInvocation(t, uploadService, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -126,7 +127,7 @@ func TestAdminProviderWeightSetHandler(t *testing.T) { ReplicationWeight: 30, } - req := issueWeightSetInvocation(t, uploadService, uploadService, args) + req := issueWeightSetInvocation(t, uploadService, uploadService.DID(), args) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index 2d6693d..242cccd 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -18,6 +18,7 @@ import ( "github.com/fil-forge/sprue/pkg/store/agent" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/did" + "github.com/ipfs/go-cid" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/ipld/datamodel" @@ -42,7 +43,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv ) error { args := req.Task().Arguments() blob := args.Blob - space := req.Invocation().Subject().DID() + space := req.Invocation().Subject() b58digest := digestutil.Format(blob.Digest) log := log.With( @@ -165,7 +166,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv log.Error("allocation failed", zap.Error(err)) return fmt.Errorf("allocating space: %w", err) } - log = log.With(zap.Stringer("provider", provider.ID.DID())) + log = log.With(zap.Stringer("provider", provider.ID)) putInv, putRcpt, err := genPut(blob, allocInv, allocOK, log) if err != nil { @@ -202,21 +203,21 @@ func doAllocate( agentStore agent.Store, space did.DID, blob blobcaps.Blob, - cause ucan.Link, + cause cid.Cid, proofStore ucan_server.ProofStore, logger *zap.Logger, ) (routing.StorageProviderInfo, ucan.Invocation, ucan.Receipt, blobcaps.AllocateOK, error) { log := logger.With(zap.Stringer("cause", cause)) log.Debug("doing allocation") - var exclusions []ucan.Principal + var exclusions []did.DID for { candidate, err := router.SelectStorageProvider(ctx, blob, routing.WithExclusions(exclusions...)) if err != nil { log.Error("failed to select storage node", zap.Error(err)) return routing.StorageProviderInfo{}, nil, nil, blobcaps.AllocateOK{}, err } - log := logger.With(zap.Stringer("candidate", candidate.ID.DID()), zap.String("endpoint", candidate.Endpoint.String())) + log := logger.With(zap.Stringer("candidate", candidate.ID), zap.String("endpoint", candidate.Endpoint.String())) log.Debug("selected storage provider candidate") client, err := nodeProvider.Client(candidate.ID, candidate.Endpoint) @@ -272,12 +273,12 @@ func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.Alloc putInv, err := httpcaps.Put.Invoke( blobProvider, - blobProvider, + blobProvider.DID(), &httpcaps.PutArguments{ Body: blob, Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, - invocation.WithAudience(blobProvider), + invocation.WithAudience(blobProvider.DID()), // We encode the keys for the blob provider principal that can be used // by the client to use in order to sign a receipt. Client could // actually derive the same principal from the blob digest like we did @@ -335,9 +336,9 @@ func maybeAccept( blobRegistry blobregistry.Store, nodeProvider piriclient.Provider, providerInfo routing.StorageProviderInfo, - space ucan.Principal, + space did.DID, blob blobcaps.Blob, - cause ucan.Link, // original /space/blob/add task + cause cid.Cid, // original /space/blob/add task putInv ucan.Invocation, putRcpt ucan.Receipt, proofStore ucan_server.ProofStore, @@ -353,7 +354,7 @@ func maybeAccept( } accReq := piriclient.AcceptRequest{ - Space: space.DID(), + Space: space, Digest: blob.Digest, Size: blob.Size, Put: putInv.Link(), @@ -382,7 +383,7 @@ func maybeAccept( return nil, nil, err } - err = blobRegistry.Register(ctx, space.DID(), blob, cause) + err = blobRegistry.Register(ctx, space, blob, cause) if err != nil { log.Error("failed to register blob", zap.Error(err)) return nil, nil, err diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go index f397ceb..dbd08ff 100644 --- a/pkg/service/handlers/blob_add_test.go +++ b/pkg/service/handlers/blob_add_test.go @@ -168,9 +168,9 @@ func TestBlobAddHandler(t *testing.T) { inv, err := blobcaps.Add.Invoke( testutil.Alice, - space, + space.DID(), &args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -202,9 +202,9 @@ func TestBlobAddHandler(t *testing.T) { inv, err := blobcaps.Add.Invoke( testutil.Alice, - space, + space.DID(), &args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -241,9 +241,9 @@ func TestBlobAddHandler(t *testing.T) { inv, err := blobcaps.Add.Invoke( testutil.Alice, - space, + space.DID(), &args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -294,17 +294,17 @@ func TestBlobAddHandler(t *testing.T) { inv, err := blobcaps.Add.Invoke( testutil.Alice, - space, + space.DID(), &args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) // Authorize the upload service to invoke /blob/allocate and /blob/accept // on the space. This is the proof chain the upload service forwards to the // storage provider. - allocProof := testutil.Must(delegation.Delegate(space, uploadService, space, blobcaps.AllocateCommand))(t) - acceptProof := testutil.Must(delegation.Delegate(space, uploadService, space, blobcaps.AcceptCommand))(t) + allocProof := testutil.Must(delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AllocateCommand))(t) + acceptProof := testutil.Must(delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AcceptCommand))(t) req := execution.NewRequest(ctx, inv, execution.WithDelegations(allocProof, acceptProof)) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) @@ -344,14 +344,14 @@ func TestBlobAddHandler(t *testing.T) { inv, err := blobcaps.Add.Invoke( testutil.Alice, - space, + space.DID(), &args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) - allocProof := testutil.Must(delegation.Delegate(space, uploadService, space, blobcaps.AllocateCommand))(t) - acceptProof := testutil.Must(delegation.Delegate(space, uploadService, space, blobcaps.AcceptCommand))(t) + allocProof := testutil.Must(delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AllocateCommand))(t) + acceptProof := testutil.Must(delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AcceptCommand))(t) req := execution.NewRequest(ctx, inv, execution.WithDelegations(allocProof, acceptProof)) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) @@ -385,9 +385,9 @@ func TestBlobAddHandler(t *testing.T) { // /blob/allocate allocInv := testutil.Must(blobcaps.Allocate.Invoke( uploadService, - space, + space.DID(), &blobcaps.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, - invocation.WithAudience(storageProvider), + invocation.WithAudience(storageProvider.DID()), ))(t) allocRcpt := testutil.Must(receipt.IssueOK( storageProvider, @@ -398,12 +398,12 @@ func TestBlobAddHandler(t *testing.T) { // /http/put — issued by the principal derived from the blob digest. putInv := testutil.Must(httpcaps.Put.Invoke( blobProvider, - blobProvider, + blobProvider.DID(), &httpcaps.PutArguments{ Body: blob, Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, - invocation.WithAudience(blobProvider), + invocation.WithAudience(blobProvider.DID()), ))(t) putRcpt := testutil.Must(receipt.IssueOK( blobProvider, @@ -414,12 +414,12 @@ func TestBlobAddHandler(t *testing.T) { // /blob/accept accInv := testutil.Must(blobcaps.Accept.Invoke( uploadService, - space, + space.DID(), &blobcaps.AcceptArguments{ Blob: blob, Put: promise.AwaitOK{Task: putInv.Task().Link()}, }, - invocation.WithAudience(storageProvider), + invocation.WithAudience(storageProvider.DID()), ))(t) accRcpt := testutil.Must(receipt.IssueOK( storageProvider, @@ -431,9 +431,9 @@ func TestBlobAddHandler(t *testing.T) { // task CID is what gets stored in the registry as the cause. prevAddInv := testutil.Must(blobcaps.Add.Invoke( testutil.Alice, - space, + space.DID(), &blobcaps.AddArguments{Blob: blob}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ))(t) prevAddRcpt := testutil.Must(receipt.IssueOK( uploadService, @@ -457,9 +457,9 @@ func TestBlobAddHandler(t *testing.T) { // stored AddOK without contacting any storage provider. inv := testutil.Must(blobcaps.Add.Invoke( testutil.Alice, - space, + space.DID(), &blobcaps.AddArguments{Blob: blob}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ))(t) req := execution.NewRequest(ctx, inv) diff --git a/pkg/service/handlers/blob_list.go b/pkg/service/handlers/blob_list.go index 480d651..b48772c 100644 --- a/pkg/service/handlers/blob_list.go +++ b/pkg/service/handlers/blob_list.go @@ -19,11 +19,11 @@ func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Han ) error { args := req.Task().Arguments() space := req.Invocation().Subject() - log := log.With(zap.Stringer("space", space.DID())) + log := log.With(zap.Stringer("space", space)) var opts []blobregistry.ListOption if args.Size != nil { - log = log.With(zap.Int64("size", *args.Size)) + log = log.With(zap.Uint64("size", *args.Size)) opts = append(opts, blobregistry.WithListLimit(int(*args.Size))) } if args.Cursor != nil { @@ -32,7 +32,7 @@ func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Han } log.Debug("listing blobs") - page, err := blobRegistry.List(req.Context(), space.DID(), opts...) + page, err := blobRegistry.List(req.Context(), space, opts...) if err != nil { log.Error("failed to list blobs", zap.Error(err)) return fmt.Errorf("listing blobs: %w", err) diff --git a/pkg/service/handlers/blob_list_test.go b/pkg/service/handlers/blob_list_test.go index 9ce8b1c..6b52e47 100644 --- a/pkg/service/handlers/blob_list_test.go +++ b/pkg/service/handlers/blob_list_test.go @@ -44,9 +44,9 @@ func invokeBlobList( t.Helper() inv, err := blobcaps.List.Invoke( agent, - space, + space.DID(), args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) req := execution.NewRequest(ctx, inv) @@ -122,7 +122,7 @@ func TestBlobListHandler(t *testing.T) { )) } - size := int64(2) + size := uint64(2) req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Size: &size}) err := handler.Handler(req, res) @@ -151,7 +151,7 @@ func TestBlobListHandler(t *testing.T) { )) } - size := int64(1) + size := uint64(1) req1, res1 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Size: &size}) require.NoError(t, handler.Handler(req1, res1)) diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go index fef7ba2..e0ad52f 100644 --- a/pkg/service/handlers/index_add.go +++ b/pkg/service/handlers/index_add.go @@ -29,12 +29,12 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser index := args.Index log := log.With( - zap.Stringer("space", space.DID()), + zap.Stringer("space", space), zap.Stringer("index", index), ) log.Debug("adding index") - provs, err := provisioningSvc.ListServiceProviders(req.Context(), space.DID()) + provs, err := provisioningSvc.ListServiceProviders(req.Context(), space) if err != nil { log.Error("failed to list service providers", zap.Error(err)) return fmt.Errorf("listing service providers: %w", err) @@ -45,7 +45,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser } // Ensure the index is stored in the agent's space - _, err = blobRegistry.Get(req.Context(), space.DID(), index.Hash()) + _, err = blobRegistry.Get(req.Context(), space, index.Hash()) if err != nil { if errors.Is(err, blobregistry.ErrEntryNotFound) { log.Warn("index not found in space") @@ -60,7 +60,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser // This is re-delegated to the indexer for indexing. proofStore := ucan_server.NewContainerProofStore(req.Metadata()) // Publish to indexer with retrieval authorization - if _, err := indexerClient.PublishIndexClaim(req.Context(), space.DID(), index, proofStore); err != nil { + if _, err := indexerClient.PublishIndexClaim(req.Context(), space, index, proofStore); err != nil { log.Error("failed to publish index claim", zap.Error(err)) return fmt.Errorf("publishing index claim: %w", err) } diff --git a/pkg/service/handlers/index_add_test.go b/pkg/service/handlers/index_add_test.go index 790f572..261027d 100644 --- a/pkg/service/handlers/index_add_test.go +++ b/pkg/service/handlers/index_add_test.go @@ -86,9 +86,9 @@ func invokeIndexAdd( t.Helper() inv, err := indexcaps.Add.Invoke( agent, - space, + space.DID(), &indexcaps.AddArguments{Index: index}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) req := execution.NewRequest(ctx, inv, reqOpts...) @@ -185,7 +185,7 @@ func TestIndexAddHandler(t *testing.T) { // /content/retrieve delegation from space → upload service so the // handler can build a proof chain that authorizes the indexer to // retrieve the index blob. - retrievalAuth, err := delegation.Delegate(space, uploadService, space, contentcaps.RetrieveCommand) + retrievalAuth, err := delegation.Delegate(space, uploadService.DID(), space.DID(), contentcaps.RetrieveCommand) require.NoError(t, err) req, res := invokeIndexAdd(t, ctx, alice, uploadService, space, indexCID, diff --git a/pkg/service/handlers/provider_add.go b/pkg/service/handlers/provider_add.go index b6d4595..c3dfaf1 100644 --- a/pkg/service/handlers/provider_add.go +++ b/pkg/service/handlers/provider_add.go @@ -23,9 +23,9 @@ func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSv res *bindexec.Response[*providercaps.AddOK], ) error { args := req.Task().Arguments() - account, err := didmailto.Parse(req.Invocation().Subject().DID().String()) + account, err := didmailto.Parse(req.Invocation().Subject().String()) if err != nil { - log.Warn("invalid account", zap.Stringer("account", req.Invocation().Subject().DID())) + log.Warn("invalid account", zap.Stringer("account", req.Invocation().Subject())) return res.SetFailure(errors.New(providercaps.InvalidAccountErrorName, "invalid account DID: %v", err)) } serviceProvider := args.Provider diff --git a/pkg/service/handlers/provider_add_test.go b/pkg/service/handlers/provider_add_test.go index c7d45bd..0c4b585 100644 --- a/pkg/service/handlers/provider_add_test.go +++ b/pkg/service/handlers/provider_add_test.go @@ -19,7 +19,6 @@ import ( edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/principal" - "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -54,7 +53,7 @@ func invokeProviderAdd( ctx context.Context, agent principal.Signer, uploadService principal.Signer, - account ucan.Principal, + account did.DID, args *providercaps.AddArguments, ) (execution.Request, *execution.ExecResponse) { t.Helper() @@ -62,7 +61,7 @@ func invokeProviderAdd( agent, account, args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) req := execution.NewRequest(ctx, inv) @@ -154,7 +153,7 @@ func TestProviderAddHandler(t *testing.T) { notAMailto := testutil.RandomSigner(t) space := testutil.RandomSigner(t) agent := testutil.RandomSigner(t) - req, res := invokeProviderAdd(t, ctx, agent, uploadService, notAMailto, + req, res := invokeProviderAdd(t, ctx, agent, uploadService, notAMailto.DID(), &providercaps.AddArguments{ Provider: serviceProvider.DID(), Consumer: space.DID(), diff --git a/pkg/service/handlers/space_info.go b/pkg/service/handlers/space_info.go index 5c2fdc8..d06c9cb 100644 --- a/pkg/service/handlers/space_info.go +++ b/pkg/service/handlers/space_info.go @@ -21,15 +21,15 @@ func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logg res *bindexec.Response[*spacecaps.InfoOK], ) error { space := req.Invocation().Subject() - log := log.With(zap.Stringer("space", space.DID())) + log := log.With(zap.Stringer("space", space)) log.Debug("getting space info") - if !strings.HasPrefix(space.DID().String(), "did:key:") { + if !strings.HasPrefix(space.String(), "did:key:") { log.Warn("non-did:key space info requested") return res.SetFailure(errors.New(spacecaps.UnknownSpaceErrorName, "can only get info for did:key spaces")) } - providers, err := provisioningSvc.ListServiceProviders(req.Context(), space.DID()) + providers, err := provisioningSvc.ListServiceProviders(req.Context(), space) if err != nil { log.Error("failed to list service providers", zap.Error(err)) return fmt.Errorf("listing service providers: %w", err) diff --git a/pkg/service/handlers/space_info_test.go b/pkg/service/handlers/space_info_test.go index 35f1626..79144c7 100644 --- a/pkg/service/handlers/space_info_test.go +++ b/pkg/service/handlers/space_info_test.go @@ -16,7 +16,6 @@ import ( edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/principal" - "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -29,14 +28,14 @@ func invokeSpaceInfo( ctx context.Context, agent principal.Signer, uploadService principal.Signer, - space ucan.Principal, + space did.DID, ) (execution.Request, *execution.ExecResponse) { t.Helper() inv, err := spacecaps.Info.Invoke( agent, space, &spacecaps.InfoArguments{}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) req := execution.NewRequest(ctx, inv) @@ -68,7 +67,7 @@ func TestSpaceInfoHandler(t *testing.T) { _, err := provisioningSvc.Provision(ctx, account, space.DID(), uploadService.DID(), testutil.RandomCID(t)) require.NoError(t, err) - req, res := invokeSpaceInfo(t, ctx, testutil.Alice, uploadService, space) + req, res := invokeSpaceInfo(t, ctx, testutil.Alice, uploadService, space.DID()) err = handler.Handler(req, res) require.NoError(t, err) @@ -94,7 +93,7 @@ func TestSpaceInfoHandler(t *testing.T) { space := testutil.RandomSigner(t) - req, res := invokeSpaceInfo(t, ctx, testutil.Alice, uploadService, space) + req, res := invokeSpaceInfo(t, ctx, testutil.Alice, uploadService, space.DID()) err := handler.Handler(req, res) require.NoError(t, err) diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index be579a0..6c35388 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -53,15 +53,15 @@ func NewHTTPPutConcludeHandler( } provider := allocInv.Audience() - if provider == nil { + if !provider.Defined() { // shouldn't happen, subject should be the space and audience the node provider = allocInv.Subject() } space := allocInv.Subject() log = log.With( - zap.Stringer("space", space.DID()), - zap.Stringer("provider", provider.DID()), + zap.Stringer("space", space), + zap.Stringer("provider", provider), ) var allocArgs blobcaps.AllocateArguments @@ -85,7 +85,7 @@ func NewHTTPPutConcludeHandler( proofStore := ucan_server.NewContainerProofStore(meta) res, accInv, accRcpt, err := client.Accept(ctx, &piriclient.AcceptRequest{ - Space: space.DID(), + Space: space, Digest: allocArgs.Blob.Digest, Size: allocArgs.Blob.Size, Put: putInv.Link(), @@ -114,7 +114,7 @@ func NewHTTPPutConcludeHandler( } log.Debug("accept success") - err = blobRegistry.Register(ctx, space.DID(), allocArgs.Blob, allocArgs.Cause) + err = blobRegistry.Register(ctx, space, allocArgs.Blob, allocArgs.Cause) // it's ok if there's already a registration of this blob in this space if err != nil && !errors.Is(err, blobregistry.ErrEntryExists) { return err diff --git a/pkg/service/handlers/ucan_conclude_http_put_test.go b/pkg/service/handlers/ucan_conclude_http_put_test.go index 81e3174..24883dd 100644 --- a/pkg/service/handlers/ucan_conclude_http_put_test.go +++ b/pkg/service/handlers/ucan_conclude_http_put_test.go @@ -74,12 +74,12 @@ func TestHTTPPutConcludeHandler(t *testing.T) { blobProvider := deriveBlobProvider(t, digest) putInv, err := httpcaps.Put.Invoke( blobProvider, - blobProvider, + blobProvider.DID(), &httpcaps.PutArguments{ Body: blobcaps.Blob{Digest: digest, Size: 1024}, Destination: promise.AwaitOK{Task: nonExistentAllocTask}, }, - invocation.WithAudience(blobProvider), + invocation.WithAudience(blobProvider.DID()), ) require.NoError(t, err) @@ -107,9 +107,9 @@ func TestHTTPPutConcludeHandler(t *testing.T) { // NOT register that provider in the spStore — router lookup fails. allocInv, err := blobcaps.Allocate.Invoke( uploadService, - space, + space.DID(), &blobcaps.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, - invocation.WithAudience(storageProvider), + invocation.WithAudience(storageProvider.DID()), ) require.NoError(t, err) allocRcpt, err := receipt.IssueOK( @@ -127,12 +127,12 @@ func TestHTTPPutConcludeHandler(t *testing.T) { blobProvider := deriveBlobProvider(t, digest) putInv, err := httpcaps.Put.Invoke( blobProvider, - blobProvider, + blobProvider.DID(), &httpcaps.PutArguments{ Body: blob, Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, - invocation.WithAudience(blobProvider), + invocation.WithAudience(blobProvider.DID()), ) require.NoError(t, err) putRcpt, err := receipt.IssueOK( @@ -177,9 +177,9 @@ func TestHTTPPutConcludeHandler(t *testing.T) { // Prior /blob/allocate invocation in the agent store. allocInv, err := blobcaps.Allocate.Invoke( uploadService, - space, + space.DID(), &blobcaps.AllocateArguments{Blob: blob, Cause: blobAddTaskLink}, - invocation.WithAudience(storageProvider), + invocation.WithAudience(storageProvider.DID()), ) require.NoError(t, err) allocRcpt, err := receipt.IssueOK( @@ -198,12 +198,12 @@ func TestHTTPPutConcludeHandler(t *testing.T) { blobProvider := deriveBlobProvider(t, digest) putInv, err := httpcaps.Put.Invoke( blobProvider, - blobProvider, + blobProvider.DID(), &httpcaps.PutArguments{ Body: blob, Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, - invocation.WithAudience(blobProvider), + invocation.WithAudience(blobProvider.DID()), ) require.NoError(t, err) putRcpt, err := receipt.IssueOK( @@ -216,7 +216,7 @@ func TestHTTPPutConcludeHandler(t *testing.T) { // Authorize the upload service to invoke /blob/accept on the space and // pass the proof through the conclude metadata so the piri client can // forward it to the storage provider. - acceptProof, err := delegation.Delegate(space, uploadService, space, blobcaps.AcceptCommand) + acceptProof, err := delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AcceptCommand) require.NoError(t, err) meta := container.New(container.WithDelegations(acceptProof)) diff --git a/pkg/service/handlers/ucan_conclude_test.go b/pkg/service/handlers/ucan_conclude_test.go index 591d532..b90ec66 100644 --- a/pkg/service/handlers/ucan_conclude_test.go +++ b/pkg/service/handlers/ucan_conclude_test.go @@ -31,7 +31,7 @@ func TestUCANConcludeHandler(t *testing.T) { // Build a "task" invocation and a receipt for it. newTaskAndReceipt := func(t *testing.T, cmd ucan.Command) (ucan.Invocation, ucan.Receipt) { t.Helper() - taskInv, err := invocation.Invoke(uploadService, uploadService, cmd, datamodel.Map{}) + taskInv, err := invocation.Invoke(uploadService, uploadService.DID(), cmd, datamodel.Map{}) require.NoError(t, err) rcpt, err := receipt.IssueOK( uploadService, @@ -54,9 +54,9 @@ func TestUCANConcludeHandler(t *testing.T) { concludeInv, err := ucancaps.Conclude.Invoke( uploadService, - uploadService, + uploadService.DID(), &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -91,9 +91,9 @@ func TestUCANConcludeHandler(t *testing.T) { concludeInv, err := ucancaps.Conclude.Invoke( uploadService, - uploadService, + uploadService.DID(), &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -140,9 +140,9 @@ func TestUCANConcludeHandler(t *testing.T) { concludeInv, err := ucancaps.Conclude.Invoke( uploadService, - uploadService, + uploadService.DID(), &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -179,9 +179,9 @@ func TestUCANConcludeHandler(t *testing.T) { concludeInv, err := ucancaps.Conclude.Invoke( uploadService, - uploadService, + uploadService.DID(), &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -216,9 +216,9 @@ func TestUCANConcludeHandler(t *testing.T) { // no agent-store lookup required. concludeInv, err := ucancaps.Conclude.Invoke( uploadService, - uploadService, + uploadService.DID(), &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) diff --git a/pkg/service/handlers/upload_add.go b/pkg/service/handlers/upload_add.go index f99c1c5..a7160a3 100644 --- a/pkg/service/handlers/upload_add.go +++ b/pkg/service/handlers/upload_add.go @@ -25,7 +25,7 @@ func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore uplo space := req.Invocation().Subject() cause := req.Invocation().Task().Link() log := log.With( - zap.Stringer("space", space.DID()), + zap.Stringer("space", space), zap.Stringer("root", args.Root), ) if args.Index != nil { @@ -33,7 +33,7 @@ func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore uplo } log.Debug("adding upload") - provs, err := provisioningSvc.ListServiceProviders(req.Context(), space.DID()) + provs, err := provisioningSvc.ListServiceProviders(req.Context(), space) if err != nil { log.Error("failed to list service providers", zap.Error(err)) return fmt.Errorf("listing service providers: %w", err) @@ -43,7 +43,7 @@ func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore uplo return res.SetFailure(errors.New(accesscaps.InsufficientStorageErrorName, "space has no service provider")) } - err = uploadStore.Upsert(req.Context(), space.DID(), args.Root, args.Index, args.Shards, cause) + err = uploadStore.Upsert(req.Context(), space, args.Root, args.Index, args.Shards, cause) if err != nil { log.Error("failed to upsert upload", zap.Error(err)) return fmt.Errorf("upserting upload: %w", err) diff --git a/pkg/service/handlers/upload_add_test.go b/pkg/service/handlers/upload_add_test.go index 82ff5d0..2a5f237 100644 --- a/pkg/service/handlers/upload_add_test.go +++ b/pkg/service/handlers/upload_add_test.go @@ -57,9 +57,9 @@ func invokeUploadAdd( t.Helper() inv, err := uploadcaps.Add.Invoke( agent, - space, + space.DID(), args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) req := execution.NewRequest(ctx, inv) diff --git a/pkg/service/handlers/upload_list.go b/pkg/service/handlers/upload_list.go index a7656cd..ec36952 100644 --- a/pkg/service/handlers/upload_list.go +++ b/pkg/service/handlers/upload_list.go @@ -19,11 +19,11 @@ func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Ha ) error { args := req.Task().Arguments() space := req.Invocation().Subject() - log := log.With(zap.Stringer("space", space.DID())) + log := log.With(zap.Stringer("space", space)) var opts []upload_store.ListOption if args.Size != nil { - log = log.With(zap.Int64("size", *args.Size)) + log = log.With(zap.Uint64("size", *args.Size)) opts = append(opts, upload_store.WithListLimit(int(*args.Size))) } if args.Cursor != nil { @@ -32,7 +32,7 @@ func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Ha } log.Debug("listing uploads") - page, err := uploadStore.List(req.Context(), space.DID(), opts...) + page, err := uploadStore.List(req.Context(), space, opts...) if err != nil { log.Error("failed to list uploads", zap.Error(err)) return fmt.Errorf("listing uploads: %w", err) diff --git a/pkg/service/handlers/upload_list_test.go b/pkg/service/handlers/upload_list_test.go index 904bdfa..5b99c45 100644 --- a/pkg/service/handlers/upload_list_test.go +++ b/pkg/service/handlers/upload_list_test.go @@ -30,9 +30,9 @@ func invokeUploadList( t.Helper() inv, err := uploadcaps.List.Invoke( agent, - space, + space.DID(), args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) req := execution.NewRequest(ctx, inv) @@ -107,7 +107,7 @@ func TestUploadListHandler(t *testing.T) { require.NoError(t, store.Upsert(ctx, space.DID(), testutil.RandomCID(t), nil, nil, testutil.RandomCID(t))) } - size := int64(2) + size := uint64(2) req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Size: &size}) err := handler.Handler(req, res) @@ -130,7 +130,7 @@ func TestUploadListHandler(t *testing.T) { require.NoError(t, store.Upsert(ctx, space.DID(), testutil.RandomCID(t), nil, nil, testutil.RandomCID(t))) } - size := int64(1) + size := uint64(1) req1, res1 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Size: &size}) require.NoError(t, handler.Handler(req1, res1)) diff --git a/pkg/service/handlers/upload_shard_list.go b/pkg/service/handlers/upload_shard_list.go index 43fb5fc..7385a69 100644 --- a/pkg/service/handlers/upload_shard_list.go +++ b/pkg/service/handlers/upload_shard_list.go @@ -21,11 +21,11 @@ func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logge args := req.Task().Arguments() space := req.Invocation().Subject() root := args.Root - log := log.With(zap.Stringer("space", space.DID()), zap.Stringer("root", root)) + log := log.With(zap.Stringer("space", space), zap.Stringer("root", root)) var opts []upload_store.ListShardsOption if args.Size != nil { - log = log.With(zap.Int64("size", *args.Size)) + log = log.With(zap.Uint64("size", *args.Size)) opts = append(opts, upload_store.WithListShardsLimit(int(*args.Size))) } if args.Cursor != nil { @@ -34,7 +34,7 @@ func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logge } log.Debug("listing upload shards") - page, err := uploadStore.ListShards(req.Context(), space.DID(), root, opts...) + page, err := uploadStore.ListShards(req.Context(), space, root, opts...) if err != nil { log.Error("failed to list upload shards", zap.Error(err)) return fmt.Errorf("listing upload shards: %w", err) diff --git a/pkg/service/handlers/upload_shard_list_test.go b/pkg/service/handlers/upload_shard_list_test.go index 0500366..78d680f 100644 --- a/pkg/service/handlers/upload_shard_list_test.go +++ b/pkg/service/handlers/upload_shard_list_test.go @@ -30,9 +30,9 @@ func invokeUploadShardList( t.Helper() inv, err := shardcaps.List.Invoke( agent, - space, + space.DID(), args, - invocation.WithAudience(uploadService), + invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) req := execution.NewRequest(ctx, inv) @@ -113,7 +113,7 @@ func TestUploadShardListHandler(t *testing.T) { shard3 := testutil.RandomCID(t) require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, []cid.Cid{shard1, shard2, shard3}, testutil.RandomCID(t))) - size := int64(2) + size := uint64(2) req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Size: &size}) err := handler.Handler(req, res) @@ -138,7 +138,7 @@ func TestUploadShardListHandler(t *testing.T) { shard3 := testutil.RandomCID(t) require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, []cid.Cid{shard1, shard2, shard3}, testutil.RandomCID(t))) - size := int64(1) + size := uint64(1) req1, res1 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Size: &size}) require.NoError(t, handler.Handler(req1, res1)) diff --git a/pkg/store/agent/agent_test.go b/pkg/store/agent/agent_test.go index 2600248..3ac3343 100644 --- a/pkg/store/agent/agent_test.go +++ b/pkg/store/agent/agent_test.go @@ -98,10 +98,10 @@ func makeInvocation(t *testing.T) ucan.Invocation { t.Helper() inv, err := invocation.Invoke( testutil.Alice, - testutil.Alice, + testutil.Alice.DID(), "/test/invoke", datamodel.Map{}, - invocation.WithAudience(testutil.Bob), + invocation.WithAudience(testutil.Bob.DID()), ) require.NoError(t, err) return inv @@ -173,11 +173,11 @@ func TestAgentStore(t *testing.T) { concludeInv, err := ucancap.Conclude.Invoke( testutil.Alice, - testutil.Alice, + testutil.Alice.DID(), &ucancap.ConcludeArguments{ Receipt: rcpt.Link(), }, - invocation.WithAudience(testutil.Bob), + invocation.WithAudience(testutil.Bob.DID()), ) require.NoError(t, err) diff --git a/pkg/store/delegation/aws/store.go b/pkg/store/delegation/aws/store.go index b97d750..4113633 100644 --- a/pkg/store/delegation/aws/store.go +++ b/pkg/store/delegation/aws/store.go @@ -135,18 +135,18 @@ func (s *Store) PutMany(ctx context.Context, tokens []ucan.Token, cause cid.Cid) } var aud did.DID - // audience may be nil if the token is an invocation - if token.Audience() != nil { - aud = token.Audience().DID() + // audience may be undefined if the token is an invocation + if token.Audience().Defined() { + aud = token.Audience() } else { - aud = token.Subject().DID() + aud = token.Subject() } // Write the index entry to DynamoDB. item := map[string]types.AttributeValue{ "link": &types.AttributeValueMemberS{Value: link}, "audience": &types.AttributeValueMemberS{Value: aud.String()}, - "issuer": &types.AttributeValueMemberS{Value: token.Issuer().DID().String()}, + "issuer": &types.AttributeValueMemberS{Value: token.Issuer().String()}, "insertedAt": &types.AttributeValueMemberS{Value: now}, "updatedAt": &types.AttributeValueMemberS{Value: now}, } diff --git a/pkg/store/delegation/delegation_test.go b/pkg/store/delegation/delegation_test.go index e5e9756..0ec8e90 100644 --- a/pkg/store/delegation/delegation_test.go +++ b/pkg/store/delegation/delegation_test.go @@ -11,6 +11,7 @@ import ( delegationaws "github.com/fil-forge/sprue/pkg/store/delegation/aws" delegationmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" delegationpostgres "github.com/fil-forge/sprue/pkg/store/delegation/postgres" + "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/google/uuid" @@ -83,12 +84,12 @@ func createAWSStore(t *testing.T) dlgstore.Store { } // makeDelegation creates a delegation from Alice to the given audience. -func makeDelegation(t *testing.T, audience ucan.Principal) ucan.Delegation { +func makeDelegation(t *testing.T, audience did.DID) ucan.Delegation { t.Helper() dlg, err := delegation.Delegate( testutil.Alice, audience, - testutil.Alice, + testutil.Alice.DID(), "/test/delegate", ) require.NoError(t, err) @@ -107,7 +108,7 @@ func TestDelegationStore(t *testing.T) { require.NoError(t, s.PutMany(t.Context(), []ucan.Token{dlg}, cause)) - page, err := s.ListByAudience(t.Context(), audience.DID()) + page, err := s.ListByAudience(t.Context(), audience) require.NoError(t, err) require.Len(t, page.Results, 1) require.Equal(t, dlg.Link().String(), page.Results[0].Link().String()) @@ -116,7 +117,7 @@ func TestDelegationStore(t *testing.T) { t.Run("ListByAudience returns empty page for unknown audience", func(t *testing.T) { audience := testutil.RandomDID(t) - page, err := s.ListByAudience(t.Context(), audience.DID()) + page, err := s.ListByAudience(t.Context(), audience) require.NoError(t, err) require.Empty(t, page.Results) require.Nil(t, page.Cursor) @@ -130,7 +131,7 @@ func TestDelegationStore(t *testing.T) { require.NoError(t, s.PutMany(t.Context(), []ucan.Token{dlg1, dlg2}, cause)) - page, err := s.ListByAudience(t.Context(), audience.DID()) + page, err := s.ListByAudience(t.Context(), audience) require.NoError(t, err) require.Len(t, page.Results, 2) }) @@ -144,12 +145,12 @@ func TestDelegationStore(t *testing.T) { require.NoError(t, s.PutMany(t.Context(), []ucan.Token{dlg1, dlg2}, cause)) - page1, err := s.ListByAudience(t.Context(), aud1.DID()) + page1, err := s.ListByAudience(t.Context(), aud1) require.NoError(t, err) require.Len(t, page1.Results, 1) require.Equal(t, dlg1.Link().String(), page1.Results[0].Link().String()) - page2, err := s.ListByAudience(t.Context(), aud2.DID()) + page2, err := s.ListByAudience(t.Context(), aud2) require.NoError(t, err) require.Len(t, page2.Results, 1) require.Equal(t, dlg2.Link().String(), page2.Results[0].Link().String()) @@ -165,7 +166,7 @@ func TestDelegationStore(t *testing.T) { } require.NoError(t, s.PutMany(t.Context(), []ucan.Token{makeDelegation(t, aud2)}, cause)) - page, err := s.ListByAudience(t.Context(), aud1.DID()) + page, err := s.ListByAudience(t.Context(), aud1) require.NoError(t, err) require.Len(t, page.Results, 3) }) @@ -184,7 +185,7 @@ func TestDelegationStore(t *testing.T) { listOpts = append(listOpts, dlgstore.WithListByAudienceCursor(*opts.Cursor)) } listOpts = append(listOpts, dlgstore.WithListByAudienceLimit(2)) - return s.ListByAudience(ctx, audience.DID(), listOpts...) + return s.ListByAudience(ctx, audience, listOpts...) }) require.NoError(t, err) require.Len(t, all, 5) diff --git a/pkg/store/delegation/memory/store.go b/pkg/store/delegation/memory/store.go index 5d67f32..489a243 100644 --- a/pkg/store/delegation/memory/store.go +++ b/pkg/store/delegation/memory/store.go @@ -65,11 +65,11 @@ func (s *Store) PutMany(ctx context.Context, tokens []ucan.Token, cause cid.Cid) for _, d := range tokens { var aud did.DID - // audience may be nil if the token is an invocation - if d.Audience() != nil { - aud = d.Audience().DID() + // audience may be undefined if the token is an invocation + if d.Audience().Defined() { + aud = d.Audience() } else { - aud = d.Subject().DID() + aud = d.Subject() } s.tokens[aud] = append(s.tokens[aud], d) slices.SortFunc(s.tokens[aud], func(a, b ucan.Token) int { diff --git a/pkg/store/delegation/postgres/store.go b/pkg/store/delegation/postgres/store.go index 3853520..2fa9673 100644 --- a/pkg/store/delegation/postgres/store.go +++ b/pkg/store/delegation/postgres/store.go @@ -75,11 +75,11 @@ func (s *Store) PutMany(ctx context.Context, tokens []ucan.Token, cause cid.Cid) } var aud did.DID - // audience may be nil if the token is an invocation - if token.Audience() != nil { - aud = token.Audience().DID() + // audience may be undefined if the token is an invocation + if token.Audience().Defined() { + aud = token.Audience() } else { - aud = token.Subject().DID() + aud = token.Subject() } var causeStr *string if cause != cid.Undef { @@ -101,7 +101,7 @@ func (s *Store) PutMany(ctx context.Context, tokens []ucan.Token, cause cid.Cid) cause = EXCLUDED.cause, expiration = EXCLUDED.expiration, updated_at = EXCLUDED.updated_at - `, link, aud.String(), token.Issuer().DID().String(), causeStr, expiration, now); err != nil { + `, link, aud.String(), token.Issuer().String(), causeStr, expiration, now); err != nil { return fmt.Errorf("indexing delegation %s: %w", link, err) } } From f4d6c5fee43660352eb7a4c341e9a2428835cc09 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 18 May 2026 21:02:59 +0100 Subject: [PATCH 13/23] refactor: use proof store implementation from ucanlib --- go.mod | 2 +- go.sum | 2 + pkg/indexerclient/client.go | 4 +- pkg/lib/ucan_server/proofs.go | 68 ------------------- pkg/piriclient/client.go | 12 ++-- pkg/service/handlers/blob_add.go | 10 +-- pkg/service/handlers/index_add.go | 4 +- .../handlers/ucan_conclude_http_put.go | 4 +- 8 files changed, 20 insertions(+), 86 deletions(-) delete mode 100644 pkg/lib/ucan_server/proofs.go diff --git a/go.mod b/go.mod index 6af3a17..6d5f0d4 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260518170714-75d1051f495e + github.com/fil-forge/libforge v0.0.0-20260518195954-b604c6d784e0 github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096 github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 diff --git a/go.sum b/go.sum index 2531bb8..5a89dd5 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,8 @@ github.com/fil-forge/libforge v0.0.0-20260518163938-03d44aed8634 h1:XESaVIJmFelO github.com/fil-forge/libforge v0.0.0-20260518163938-03d44aed8634/go.mod h1:AzKrj2fvRy00U3s/TrfXjjxhNqdImaaiSEV5HkJXFbU= github.com/fil-forge/libforge v0.0.0-20260518170714-75d1051f495e h1:STRMQCozqw7PQiS2sRAgDcoUhfoFmYFVYPaampklTyM= github.com/fil-forge/libforge v0.0.0-20260518170714-75d1051f495e/go.mod h1:AzKrj2fvRy00U3s/TrfXjjxhNqdImaaiSEV5HkJXFbU= +github.com/fil-forge/libforge v0.0.0-20260518195954-b604c6d784e0 h1:Z6eQVuyJIP+hjZDk8FNFpH9fwR6c3/IjAPtx9LMxeaY= +github.com/fil-forge/libforge v0.0.0-20260518195954-b604c6d784e0/go.mod h1:AzKrj2fvRy00U3s/TrfXjjxhNqdImaaiSEV5HkJXFbU= github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096 h1:T/JkfzRNu4/9OnqgggVWbn+OXs/y12xDSVEen0NS7EY= github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/pkg/indexerclient/client.go b/pkg/indexerclient/client.go index 7683b84..e0e00fb 100644 --- a/pkg/indexerclient/client.go +++ b/pkg/indexerclient/client.go @@ -7,8 +7,8 @@ import ( assertcaps "github.com/fil-forge/libforge/capabilities/assert" contentcaps "github.com/fil-forge/libforge/capabilities/content" + ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/lib/ucan_client" - "github.com/fil-forge/sprue/pkg/lib/ucan_server" "github.com/fil-forge/ucantone/client" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution" @@ -47,7 +47,7 @@ func New(endpoint *url.URL, indexerDID did.DID, signer ucan.Signer, logger *zap. // // The proofStore parameter is used to build the delegation chain authorizing // the upload service to retrieve the index blob via `/content/retrieve` command. -func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid.Cid, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Receipt, error) { +func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid.Cid, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Receipt, error) { prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), contentcaps.RetrieveCommand, space) if err != nil { return nil, fmt.Errorf("building proof chain: %w", err) diff --git a/pkg/lib/ucan_server/proofs.go b/pkg/lib/ucan_server/proofs.go deleted file mode 100644 index a570b23..0000000 --- a/pkg/lib/ucan_server/proofs.go +++ /dev/null @@ -1,68 +0,0 @@ -package ucan_server - -import ( - "context" - "iter" - - ucanlib "github.com/fil-forge/libforge/ucan" - "github.com/fil-forge/ucantone/did" - "github.com/fil-forge/ucantone/ucan" - "github.com/ipfs/go-cid" -) - -type ProofStore interface { - ProofChain(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) ([]ucan.Delegation, []cid.Cid, error) - ProofAttestations(ctx context.Context, proofs []ucan.Delegation, authority did.DID) ([]ucan.Invocation, error) -} - -// ContainerProofStore is a proof store backed by an in-memory container. -type ContainerProofStore struct { - container ucan.Container -} - -// NewContainerProofStore creates a proof store backed by an in-memory container. -func NewContainerProofStore(ct ucan.Container) *ContainerProofStore { - return &ContainerProofStore{container: ct} -} - -func (cps *ContainerProofStore) ProofChain(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) ([]ucan.Delegation, []cid.Cid, error) { - return ucanlib.ProofChain(ctx, cps.matchDelegations, aud, cmd, sub) -} - -func (cps *ContainerProofStore) ProofAttestations(ctx context.Context, proofs []ucan.Delegation, authority did.DID) ([]ucan.Invocation, error) { - return ucanlib.ProofAttestations(ctx, cps.listInvocations, proofs, authority) -} - -func (ps *ContainerProofStore) listDelegations(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) iter.Seq2[ucan.Delegation, error] { - return func(yield func(ucan.Delegation, error) bool) { - if ps.container == nil { - return - } - for _, d := range ps.container.Delegations() { - if d.Audience() == aud && d.Command() == cmd && d.Subject() == sub { - if !yield(d, nil) { - return - } - } - } - } -} - -func (ps *ContainerProofStore) matchDelegations(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) iter.Seq2[ucan.Delegation, error] { - return ucanlib.NewDelegationMatcher(ps.listDelegations)(ctx, aud, cmd, sub) -} - -func (ps *ContainerProofStore) listInvocations(ctx context.Context, aud did.DID, cmd ucan.Command, sub did.DID) iter.Seq2[ucan.Invocation, error] { - return func(yield func(ucan.Invocation, error) bool) { - if ps.container == nil { - return - } - for _, d := range ps.container.Invocations() { - if d.Audience() == aud && d.Command() == cmd && d.Subject() == sub { - if !yield(d, nil) { - return - } - } - } - } -} diff --git a/pkg/piriclient/client.go b/pkg/piriclient/client.go index d6e0543..5cf5586 100644 --- a/pkg/piriclient/client.go +++ b/pkg/piriclient/client.go @@ -9,8 +9,8 @@ import ( blobcap "github.com/fil-forge/libforge/capabilities/blob" blobreplicacap "github.com/fil-forge/libforge/capabilities/blob/replica" + ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/lib/ucan_client" - "github.com/fil-forge/sprue/pkg/lib/ucan_server" "github.com/fil-forge/ucantone/client" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution" @@ -65,7 +65,7 @@ type AllocateRequest struct { // Allocate sends a /blob/allocate invocation to the piri node. // Returns the response data, the invocation that was sent, and the receipt from piri. -func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (*blobcap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { +func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobcap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { inv, prfs, attestations, err := c.AllocateInvocation(ctx, req, proofStore, options...) if err != nil { return nil, nil, nil, fmt.Errorf("creating allocate invocation: %w", err) @@ -93,7 +93,7 @@ func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore } // AllocateInvocation returns the invocation for the allocate request (for use in effects). -func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { +func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.AllocateCommand, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) @@ -141,7 +141,7 @@ type AcceptRequest struct { } // Accept sends a /blob/accept invocation to the piri node. -func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (*blobcap.AcceptOK, ucan.Invocation, ucan.Receipt, error) { +func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobcap.AcceptOK, ucan.Invocation, ucan.Receipt, error) { inv, prfs, attestations, err := c.AcceptInvocation(ctx, req, proofStore, options...) if err != nil { return nil, nil, nil, fmt.Errorf("creating accept invocation: %w", err) @@ -169,7 +169,7 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan } // AcceptInvocation returns the invocation for the accept request (for use in effects). -func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { +func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.AcceptCommand, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) @@ -215,7 +215,7 @@ type ReplicaAllocateRequest struct { // ReplicaAllocate sends a /blob/replica/allocate invocation to the piri node. // Returns the response data, the invocation that was sent, and the receipt from // piri. It returns an error if the receipt contains a failure result. -func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucan_server.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { +func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobreplicacap.AllocateCommand, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index 242cccd..49fb7fe 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -10,15 +10,14 @@ import ( blobcaps "github.com/fil-forge/libforge/capabilities/blob" httpcaps "github.com/fil-forge/libforge/capabilities/http" "github.com/fil-forge/libforge/digestutil" + ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/identity" - "github.com/fil-forge/sprue/pkg/lib/ucan_server" "github.com/fil-forge/sprue/pkg/piriclient" "github.com/fil-forge/sprue/pkg/provisioning" "github.com/fil-forge/sprue/pkg/routing" "github.com/fil-forge/sprue/pkg/store/agent" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/did" - "github.com/ipfs/go-cid" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/ipld/datamodel" @@ -29,6 +28,7 @@ import ( "github.com/fil-forge/ucantone/ucan/invocation" "github.com/fil-forge/ucantone/ucan/promise" "github.com/fil-forge/ucantone/ucan/receipt" + "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" "go.uber.org/zap" ) @@ -157,7 +157,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv } cause := req.Invocation().Task().Link() - proofStore := ucan_server.NewContainerProofStore(req.Metadata()) + proofStore := ucanlib.NewContainerProofStore(req.Metadata()) provider, allocInv, allocRcpt, allocOK, err := doAllocate(req.Context(), router, nodeProvider, agentStore, space, blob, cause, proofStore, log) if err != nil { if errors.Is(err, routing.ErrCandidateUnavailable) { @@ -204,7 +204,7 @@ func doAllocate( space did.DID, blob blobcaps.Blob, cause cid.Cid, - proofStore ucan_server.ProofStore, + proofStore ucanlib.ProofStore, logger *zap.Logger, ) (routing.StorageProviderInfo, ucan.Invocation, ucan.Receipt, blobcaps.AllocateOK, error) { log := logger.With(zap.Stringer("cause", cause)) @@ -341,7 +341,7 @@ func maybeAccept( cause cid.Cid, // original /space/blob/add task putInv ucan.Invocation, putRcpt ucan.Receipt, - proofStore ucan_server.ProofStore, + proofStore ucanlib.ProofStore, logger *zap.Logger, ) (ucan.Invocation, ucan.Receipt, error) { log := logger diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go index e0ad52f..8872b9f 100644 --- a/pkg/service/handlers/index_add.go +++ b/pkg/service/handlers/index_add.go @@ -7,9 +7,9 @@ import ( accesscaps "github.com/fil-forge/libforge/capabilities/access" indexcaps "github.com/fil-forge/libforge/capabilities/index" + ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/indexerclient" - "github.com/fil-forge/sprue/pkg/lib/ucan_server" "github.com/fil-forge/sprue/pkg/provisioning" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/errors" @@ -58,7 +58,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser // Request MUST include a delegation to the upload service that gives it // the ability to retrieve the index (a /content/retrieve delegation). // This is re-delegated to the indexer for indexing. - proofStore := ucan_server.NewContainerProofStore(req.Metadata()) + proofStore := ucanlib.NewContainerProofStore(req.Metadata()) // Publish to indexer with retrieval authorization if _, err := indexerClient.PublishIndexClaim(req.Context(), space, index, proofStore); err != nil { log.Error("failed to publish index claim", zap.Error(err)) diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index 6c35388..d924855 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -9,7 +9,7 @@ import ( httpcaps "github.com/fil-forge/libforge/capabilities/http" ucancaps "github.com/fil-forge/libforge/capabilities/ucan" "github.com/fil-forge/libforge/digestutil" - "github.com/fil-forge/sprue/pkg/lib/ucan_server" + ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/piriclient" "github.com/fil-forge/sprue/pkg/routing" "github.com/fil-forge/sprue/pkg/store/agent" @@ -83,7 +83,7 @@ func NewHTTPPutConcludeHandler( return fmt.Errorf("creating client: %w", err) } - proofStore := ucan_server.NewContainerProofStore(meta) + proofStore := ucanlib.NewContainerProofStore(meta) res, accInv, accRcpt, err := client.Accept(ctx, &piriclient.AcceptRequest{ Space: space, Digest: allocArgs.Blob.Digest, From 3094181a5543fdec0e068b7b7f48d5c0515ad17e Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 19 May 2026 17:30:15 +0100 Subject: [PATCH 14/23] refactor: upgrade ucantone --- go.mod | 5 +-- go.sum | 14 ++++---- internal/fx/service/provider.go | 2 +- pkg/capabilities/admin/provider/deregister.go | 16 --------- pkg/capabilities/admin/provider/list.go | 16 --------- pkg/capabilities/admin/provider/register.go | 16 --------- pkg/capabilities/admin/provider/weight/set.go | 24 ------------- pkg/client/client.go | 4 +-- .../admin/provider/datamodel/cbor_gen.go | 0 .../admin/provider/datamodel/deregister.go | 0 .../admin/provider/datamodel/gen/main.go | 2 +- .../admin/provider/datamodel/json_gen.go | 0 .../admin/provider/datamodel/list.go | 0 .../admin/provider/datamodel/register.go | 0 pkg/commands/admin/provider/deregister.go | 13 +++++++ pkg/commands/admin/provider/list.go | 14 ++++++++ pkg/commands/admin/provider/register.go | 14 ++++++++ .../provider/weight/datamodel/cbor_gen.go | 0 .../provider/weight/datamodel/gen/main.go | 2 +- .../provider/weight/datamodel/json_gen.go | 0 .../admin/provider/weight/datamodel/set.go | 0 pkg/commands/admin/provider/weight/set.go | 13 +++++++ pkg/indexerclient/client.go | 6 ++-- pkg/lib/ucan_server/email_auth.go | 6 ++-- pkg/lib/ucan_server/validation.go | 30 ++++++++-------- pkg/lib/ucan_server/validation_test.go | 5 +-- pkg/piriclient/client.go | 10 +++--- pkg/routing/service.go | 2 +- pkg/routing/service_test.go | 2 +- pkg/service/handlers/access_claim.go | 6 ++-- pkg/service/handlers/access_claim_test.go | 2 +- pkg/service/handlers/access_confirm.go | 8 ++--- pkg/service/handlers/access_confirm_test.go | 2 +- pkg/service/handlers/access_delegate.go | 6 ++-- pkg/service/handlers/access_delegate_test.go | 2 +- pkg/service/handlers/access_request.go | 7 ++-- pkg/service/handlers/access_request_test.go | 2 +- .../handlers/admin_provider_deregister.go | 7 ++-- .../admin_provider_deregister_test.go | 4 +-- pkg/service/handlers/admin_provider_list.go | 2 +- .../handlers/admin_provider_list_test.go | 4 +-- .../handlers/admin_provider_register.go | 2 +- .../handlers/admin_provider_register_test.go | 4 +-- .../handlers/admin_provider_weight_set.go | 7 ++-- .../admin_provider_weight_set_test.go | 4 +-- pkg/service/handlers/blob_add.go | 14 ++++---- pkg/service/handlers/blob_add_test.go | 35 ++++++++++--------- pkg/service/handlers/blob_list.go | 7 ++-- pkg/service/handlers/blob_list_test.go | 2 +- pkg/service/handlers/handlers.go | 6 ++-- pkg/service/handlers/index_add.go | 9 ++--- pkg/service/handlers/index_add_test.go | 27 +++++++------- pkg/service/handlers/provider_add.go | 7 ++-- pkg/service/handlers/provider_add_test.go | 2 +- pkg/service/handlers/space_info.go | 7 ++-- pkg/service/handlers/space_info_test.go | 2 +- pkg/service/handlers/ucan_conclude.go | 6 ++-- .../handlers/ucan_conclude_http_put.go | 12 +++---- .../handlers/ucan_conclude_http_put_test.go | 7 ++-- pkg/service/handlers/ucan_conclude_test.go | 2 +- pkg/service/handlers/upload_add.go | 9 ++--- pkg/service/handlers/upload_add_test.go | 4 +-- pkg/service/handlers/upload_list.go | 7 ++-- pkg/service/handlers/upload_list_test.go | 2 +- pkg/service/handlers/upload_shard_list.go | 7 ++-- .../handlers/upload_shard_list_test.go | 2 +- pkg/service/service.go | 33 +++++++++++++---- pkg/store/agent/agent_test.go | 2 +- pkg/store/blob_registry/aws/store.go | 2 +- pkg/store/blob_registry/blob_registry.go | 2 +- pkg/store/blob_registry/blob_registry_test.go | 2 +- pkg/store/blob_registry/memory/store.go | 2 +- pkg/store/metrics/metrics.go | 16 ++++----- 73 files changed, 266 insertions(+), 254 deletions(-) delete mode 100644 pkg/capabilities/admin/provider/deregister.go delete mode 100644 pkg/capabilities/admin/provider/list.go delete mode 100644 pkg/capabilities/admin/provider/register.go delete mode 100644 pkg/capabilities/admin/provider/weight/set.go rename pkg/{capabilities => commands}/admin/provider/datamodel/cbor_gen.go (100%) rename pkg/{capabilities => commands}/admin/provider/datamodel/deregister.go (100%) rename pkg/{capabilities => commands}/admin/provider/datamodel/gen/main.go (86%) rename pkg/{capabilities => commands}/admin/provider/datamodel/json_gen.go (100%) rename pkg/{capabilities => commands}/admin/provider/datamodel/list.go (100%) rename pkg/{capabilities => commands}/admin/provider/datamodel/register.go (100%) create mode 100644 pkg/commands/admin/provider/deregister.go create mode 100644 pkg/commands/admin/provider/list.go create mode 100644 pkg/commands/admin/provider/register.go rename pkg/{capabilities => commands}/admin/provider/weight/datamodel/cbor_gen.go (100%) rename pkg/{capabilities => commands}/admin/provider/weight/datamodel/gen/main.go (82%) rename pkg/{capabilities => commands}/admin/provider/weight/datamodel/json_gen.go (100%) rename pkg/{capabilities => commands}/admin/provider/weight/datamodel/set.go (100%) create mode 100644 pkg/commands/admin/provider/weight/set.go diff --git a/go.mod b/go.mod index 6d5f0d4..b2707a4 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260518195954-b604c6d784e0 - github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096 + github.com/fil-forge/libforge v0.0.0-20260519133220-4d5747ca0927 + github.com/fil-forge/ucantone v0.0.0-20260519122919-0ee8deb17aa4 github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 github.com/jackc/pgx/v5 v5.8.0 @@ -105,6 +105,7 @@ require ( github.com/multiformats/go-varint v0.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 5a89dd5..7eb1f97 100644 --- a/go.sum +++ b/go.sum @@ -92,14 +92,10 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fil-forge/libforge v0.0.0-20260518163938-03d44aed8634 h1:XESaVIJmFelO82tcCtqKDk5MRch5IFl+Fr77+7v7Oxk= -github.com/fil-forge/libforge v0.0.0-20260518163938-03d44aed8634/go.mod h1:AzKrj2fvRy00U3s/TrfXjjxhNqdImaaiSEV5HkJXFbU= -github.com/fil-forge/libforge v0.0.0-20260518170714-75d1051f495e h1:STRMQCozqw7PQiS2sRAgDcoUhfoFmYFVYPaampklTyM= -github.com/fil-forge/libforge v0.0.0-20260518170714-75d1051f495e/go.mod h1:AzKrj2fvRy00U3s/TrfXjjxhNqdImaaiSEV5HkJXFbU= -github.com/fil-forge/libforge v0.0.0-20260518195954-b604c6d784e0 h1:Z6eQVuyJIP+hjZDk8FNFpH9fwR6c3/IjAPtx9LMxeaY= -github.com/fil-forge/libforge v0.0.0-20260518195954-b604c6d784e0/go.mod h1:AzKrj2fvRy00U3s/TrfXjjxhNqdImaaiSEV5HkJXFbU= -github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096 h1:T/JkfzRNu4/9OnqgggVWbn+OXs/y12xDSVEen0NS7EY= -github.com/fil-forge/ucantone v0.0.0-20260514184915-8bebe15b0096/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= +github.com/fil-forge/libforge v0.0.0-20260519133220-4d5747ca0927 h1:AqqpZ7jsb26N+1Fmq2URToRjOBha26NmYptqCIO1qJY= +github.com/fil-forge/libforge v0.0.0-20260519133220-4d5747ca0927/go.mod h1:FtlBeLxBao1mIU2ILE5loXr+KSrCFAoXoQHOPJBZGsE= +github.com/fil-forge/ucantone v0.0.0-20260519122919-0ee8deb17aa4 h1:Bxmm9l6RE+68luYvekbnmTe7+1epRPZHnSagcMtdvyY= +github.com/fil-forge/ucantone v0.0.0-20260519122919-0ee8deb17aa4/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -219,6 +215,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/internal/fx/service/provider.go b/internal/fx/service/provider.go index e32696d..b99f788 100644 --- a/internal/fx/service/provider.go +++ b/internal/fx/service/provider.go @@ -29,6 +29,6 @@ type ServiceParams struct { } // NewService creates the UCAN service with all handlers registered. -func NewService(p ServiceParams) *service.Service { +func NewService(p ServiceParams) (*service.Service, error) { return service.New(p.Identity, p.AgentStore, p.DelegationStore, p.Handlers, p.Logger, p.Options...) } diff --git a/pkg/capabilities/admin/provider/deregister.go b/pkg/capabilities/admin/provider/deregister.go deleted file mode 100644 index 2243cf8..0000000 --- a/pkg/capabilities/admin/provider/deregister.go +++ /dev/null @@ -1,16 +0,0 @@ -package provider - -import ( - cdm "github.com/fil-forge/libforge/capabilities" - pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" - "github.com/fil-forge/ucantone/validator/bindcap" -) - -const DeregisterCommand = "/admin/provider/deregister" - -type ( - DeregisterArguments = pdm.DeregisterArgumentsModel - DeregisterOK = cdm.Unit -) - -var Deregister, _ = bindcap.New[*DeregisterArguments](DeregisterCommand) diff --git a/pkg/capabilities/admin/provider/list.go b/pkg/capabilities/admin/provider/list.go deleted file mode 100644 index 9875fee..0000000 --- a/pkg/capabilities/admin/provider/list.go +++ /dev/null @@ -1,16 +0,0 @@ -package provider - -import ( - pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" - "github.com/fil-forge/ucantone/validator/bindcap" -) - -const ListCommand = "/admin/provider/list" - -type ( - ListArguments = pdm.ListArgumentsModel - ListOK = pdm.ListOKModel - Provider = pdm.ProviderModel -) - -var List, _ = bindcap.New[*ListArguments](ListCommand) diff --git a/pkg/capabilities/admin/provider/register.go b/pkg/capabilities/admin/provider/register.go deleted file mode 100644 index 1a2baa3..0000000 --- a/pkg/capabilities/admin/provider/register.go +++ /dev/null @@ -1,16 +0,0 @@ -package provider - -import ( - cdm "github.com/fil-forge/libforge/capabilities" - pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" - "github.com/fil-forge/ucantone/validator/bindcap" -) - -const RegisterCommand = "/admin/provider/register" - -type ( - RegisterArguments = pdm.RegisterArgumentsModel - RegisterOK = cdm.Unit -) - -var Register, _ = bindcap.New[*RegisterArguments](RegisterCommand) diff --git a/pkg/capabilities/admin/provider/weight/set.go b/pkg/capabilities/admin/provider/weight/set.go deleted file mode 100644 index 1bec8a8..0000000 --- a/pkg/capabilities/admin/provider/weight/set.go +++ /dev/null @@ -1,24 +0,0 @@ -package weight - -import ( - cdm "github.com/fil-forge/libforge/capabilities" - wdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight/datamodel" - "github.com/fil-forge/ucantone/ucan/delegation/policy" - "github.com/fil-forge/ucantone/validator/bindcap" - "github.com/fil-forge/ucantone/validator/capability" -) - -const SetCommand = "/provider/weight/set" - -type ( - SetArguments = wdm.SetArgumentsModel - SetOK = cdm.Unit -) - -var Set, _ = bindcap.New[*SetArguments]( - SetCommand, - capability.WithPolicyBuilder( - policy.GreaterThanOrEqual(".weight", 0), - policy.GreaterThanOrEqual(".replicationWeight", 0), - ), -) diff --git a/pkg/client/client.go b/pkg/client/client.go index 81dd1c1..d00fd05 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -6,8 +6,8 @@ import ( "net/url" "slices" - providercap "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" - weightcap "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight" + providercap "github.com/fil-forge/sprue/pkg/commands/admin/provider" + weightcap "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight" "github.com/fil-forge/sprue/pkg/lib/ucan_client" "github.com/fil-forge/ucantone/client" "github.com/fil-forge/ucantone/did" diff --git a/pkg/capabilities/admin/provider/datamodel/cbor_gen.go b/pkg/commands/admin/provider/datamodel/cbor_gen.go similarity index 100% rename from pkg/capabilities/admin/provider/datamodel/cbor_gen.go rename to pkg/commands/admin/provider/datamodel/cbor_gen.go diff --git a/pkg/capabilities/admin/provider/datamodel/deregister.go b/pkg/commands/admin/provider/datamodel/deregister.go similarity index 100% rename from pkg/capabilities/admin/provider/datamodel/deregister.go rename to pkg/commands/admin/provider/datamodel/deregister.go diff --git a/pkg/capabilities/admin/provider/datamodel/gen/main.go b/pkg/commands/admin/provider/datamodel/gen/main.go similarity index 86% rename from pkg/capabilities/admin/provider/datamodel/gen/main.go rename to pkg/commands/admin/provider/datamodel/gen/main.go index 47b87ee..141b39d 100644 --- a/pkg/capabilities/admin/provider/datamodel/gen/main.go +++ b/pkg/commands/admin/provider/datamodel/gen/main.go @@ -2,7 +2,7 @@ package main import ( jsg "github.com/alanshaw/dag-json-gen" - pdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/datamodel" + pdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/datamodel" cbg "github.com/whyrusleeping/cbor-gen" ) diff --git a/pkg/capabilities/admin/provider/datamodel/json_gen.go b/pkg/commands/admin/provider/datamodel/json_gen.go similarity index 100% rename from pkg/capabilities/admin/provider/datamodel/json_gen.go rename to pkg/commands/admin/provider/datamodel/json_gen.go diff --git a/pkg/capabilities/admin/provider/datamodel/list.go b/pkg/commands/admin/provider/datamodel/list.go similarity index 100% rename from pkg/capabilities/admin/provider/datamodel/list.go rename to pkg/commands/admin/provider/datamodel/list.go diff --git a/pkg/capabilities/admin/provider/datamodel/register.go b/pkg/commands/admin/provider/datamodel/register.go similarity index 100% rename from pkg/capabilities/admin/provider/datamodel/register.go rename to pkg/commands/admin/provider/datamodel/register.go diff --git a/pkg/commands/admin/provider/deregister.go b/pkg/commands/admin/provider/deregister.go new file mode 100644 index 0000000..73c13c7 --- /dev/null +++ b/pkg/commands/admin/provider/deregister.go @@ -0,0 +1,13 @@ +package provider + +import ( + "github.com/fil-forge/libforge/commands" + pdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/datamodel" +) + +type ( + DeregisterArguments = pdm.DeregisterArgumentsModel + DeregisterOK = commands.Unit +) + +var Deregister = commands.MustParse[*DeregisterArguments]("/admin/provider/deregister") diff --git a/pkg/commands/admin/provider/list.go b/pkg/commands/admin/provider/list.go new file mode 100644 index 0000000..18a0092 --- /dev/null +++ b/pkg/commands/admin/provider/list.go @@ -0,0 +1,14 @@ +package provider + +import ( + "github.com/fil-forge/libforge/commands" + pdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/datamodel" +) + +type ( + ListArguments = pdm.ListArgumentsModel + ListOK = pdm.ListOKModel + Provider = pdm.ProviderModel +) + +var List = commands.MustParse[*ListArguments]("/admin/provider/list") diff --git a/pkg/commands/admin/provider/register.go b/pkg/commands/admin/provider/register.go new file mode 100644 index 0000000..0da37db --- /dev/null +++ b/pkg/commands/admin/provider/register.go @@ -0,0 +1,14 @@ +package provider + +import ( + "github.com/fil-forge/libforge/commands" + cdm "github.com/fil-forge/libforge/commands" + pdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/datamodel" +) + +type ( + RegisterArguments = pdm.RegisterArgumentsModel + RegisterOK = cdm.Unit +) + +var Register = commands.MustParse[*RegisterArguments]("/admin/provider/register") diff --git a/pkg/capabilities/admin/provider/weight/datamodel/cbor_gen.go b/pkg/commands/admin/provider/weight/datamodel/cbor_gen.go similarity index 100% rename from pkg/capabilities/admin/provider/weight/datamodel/cbor_gen.go rename to pkg/commands/admin/provider/weight/datamodel/cbor_gen.go diff --git a/pkg/capabilities/admin/provider/weight/datamodel/gen/main.go b/pkg/commands/admin/provider/weight/datamodel/gen/main.go similarity index 82% rename from pkg/capabilities/admin/provider/weight/datamodel/gen/main.go rename to pkg/commands/admin/provider/weight/datamodel/gen/main.go index fdf3888..22f1406 100644 --- a/pkg/capabilities/admin/provider/weight/datamodel/gen/main.go +++ b/pkg/commands/admin/provider/weight/datamodel/gen/main.go @@ -2,7 +2,7 @@ package main import ( jsg "github.com/alanshaw/dag-json-gen" - wdm "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight/datamodel" + wdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight/datamodel" cbg "github.com/whyrusleeping/cbor-gen" ) diff --git a/pkg/capabilities/admin/provider/weight/datamodel/json_gen.go b/pkg/commands/admin/provider/weight/datamodel/json_gen.go similarity index 100% rename from pkg/capabilities/admin/provider/weight/datamodel/json_gen.go rename to pkg/commands/admin/provider/weight/datamodel/json_gen.go diff --git a/pkg/capabilities/admin/provider/weight/datamodel/set.go b/pkg/commands/admin/provider/weight/datamodel/set.go similarity index 100% rename from pkg/capabilities/admin/provider/weight/datamodel/set.go rename to pkg/commands/admin/provider/weight/datamodel/set.go diff --git a/pkg/commands/admin/provider/weight/set.go b/pkg/commands/admin/provider/weight/set.go new file mode 100644 index 0000000..201b641 --- /dev/null +++ b/pkg/commands/admin/provider/weight/set.go @@ -0,0 +1,13 @@ +package weight + +import ( + "github.com/fil-forge/libforge/commands" + wdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight/datamodel" +) + +type ( + SetArguments = wdm.SetArgumentsModel + SetOK = commands.Unit +) + +var Set = commands.MustParse[*SetArguments]("/provider/weight/set") diff --git a/pkg/indexerclient/client.go b/pkg/indexerclient/client.go index e0e00fb..f31a47f 100644 --- a/pkg/indexerclient/client.go +++ b/pkg/indexerclient/client.go @@ -5,8 +5,8 @@ import ( "fmt" "net/url" - assertcaps "github.com/fil-forge/libforge/capabilities/assert" - contentcaps "github.com/fil-forge/libforge/capabilities/content" + assertcaps "github.com/fil-forge/libforge/commands/assert" + contentcaps "github.com/fil-forge/libforge/commands/content" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/lib/ucan_client" "github.com/fil-forge/ucantone/client" @@ -48,7 +48,7 @@ func New(endpoint *url.URL, indexerDID did.DID, signer ucan.Signer, logger *zap. // The proofStore parameter is used to build the delegation chain authorizing // the upload service to retrieve the index blob via `/content/retrieve` command. func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid.Cid, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Receipt, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), contentcaps.RetrieveCommand, space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), ucan.Command(contentcaps.Retrieve), space) if err != nil { return nil, fmt.Errorf("building proof chain: %w", err) } diff --git a/pkg/lib/ucan_server/email_auth.go b/pkg/lib/ucan_server/email_auth.go index 1f44405..d69bedc 100644 --- a/pkg/lib/ucan_server/email_auth.go +++ b/pkg/lib/ucan_server/email_auth.go @@ -5,7 +5,7 @@ import ( "context" "fmt" - "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" @@ -33,8 +33,8 @@ func ExecBase64urlAccessConfirm(ctx context.Context, executor execution.Executor confirmation := inCt.Invocations()[0] // check this is a confirmation invocation - if confirmation.Command() != access.ConfirmCommand { - return AccessConfirmResult{}, fmt.Errorf("unexpected command in invocation, expected %s but got %s", access.ConfirmCommand, confirmation.Command()) + if confirmation.Command() != ucan.Command(access.Confirm) { + return AccessConfirmResult{}, fmt.Errorf("unexpected command in invocation, expected %s but got %s", access.Confirm, confirmation.Command()) } req := execution.NewRequest( diff --git a/pkg/lib/ucan_server/validation.go b/pkg/lib/ucan_server/validation.go index 9b757fc..2b42880 100644 --- a/pkg/lib/ucan_server/validation.go +++ b/pkg/lib/ucan_server/validation.go @@ -5,26 +5,24 @@ import ( "context" "fmt" - "github.com/fil-forge/libforge/capabilities/ucan/attest" + "github.com/fil-forge/libforge/commands/ucan/attest" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/principal" - ed_verifier "github.com/fil-forge/ucantone/principal/ed25519/verifier" - secp_verifier "github.com/fil-forge/ucantone/principal/secp256k1/verifier" + secp256k1_verifier "github.com/fil-forge/ucantone/principal/secp256k1/verifier" + "github.com/fil-forge/ucantone/principal/verifier" "github.com/fil-forge/ucantone/ucan" - "github.com/fil-forge/ucantone/ucan/invocation" + ucan_token "github.com/fil-forge/ucantone/ucan/token" "github.com/fil-forge/ucantone/validator" ) -// PrincipalParser is a [validator.PrincipalParserFunc] that enables support for -// both ed25519 and secp256k1 principals in UCANs. -func PrincipalParser(str string) (principal.Verifier, error) { - if v, err := ed_verifier.Parse(str); err == nil { - return v, nil - } - if v, err := secp_verifier.Parse(str); err == nil { - return v, nil - } - return nil, fmt.Errorf("unknown principal type: %s", str) +func init() { + verifier.Register(secp256k1_verifier.Code, func(b []byte) (principal.Verifier, error) { + return secp256k1_verifier.Decode(b) + }) +} + +func ResolveDIDKey(ctx context.Context, did did.DID) (ucan.Verifier, error) { + return verifier.FromDIDKey(did) } // NewAttestationVerifier creates a [validator.NonStandardSignatureVerifierFunc] @@ -38,7 +36,7 @@ func NewAttestationVerifier(authority principal.Verifier) validator.NonStandardS return fmt.Errorf("token is not a delegation") } for _, inv := range meta.Invocations() { - if inv.Command() != attest.ProofCommand { + if inv.Command() != ucan.Command(attest.Proof) { continue } // only trust attestations we issued @@ -54,7 +52,7 @@ func NewAttestationVerifier(authority principal.Verifier) validator.NonStandardS continue } // finally, make sure the signature is valid - ok, err := invocation.VerifySignature(inv, authority) + ok, err := ucan_token.VerifySignature(inv, authority) if !ok || err != nil { continue } diff --git a/pkg/lib/ucan_server/validation_test.go b/pkg/lib/ucan_server/validation_test.go index 0a645ea..29f6232 100644 --- a/pkg/lib/ucan_server/validation_test.go +++ b/pkg/lib/ucan_server/validation_test.go @@ -3,12 +3,13 @@ package ucan_server import ( "testing" - "github.com/fil-forge/libforge/capabilities/ucan/attest" + "github.com/fil-forge/libforge/commands/ucan/attest" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal/absentee" "github.com/fil-forge/ucantone/principal/ed25519" + "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" @@ -86,7 +87,7 @@ func TestNewAttestationVerifier(t *testing.T) { inv, err := invocation.Invoke( authority, authority.DID(), - attest.ProofCommand, + ucan.Command(attest.Proof), datamodel.Map{"unrelated": "foo"}, ) require.NoError(t, err) diff --git a/pkg/piriclient/client.go b/pkg/piriclient/client.go index 5cf5586..b9194cc 100644 --- a/pkg/piriclient/client.go +++ b/pkg/piriclient/client.go @@ -7,8 +7,8 @@ import ( "slices" "time" - blobcap "github.com/fil-forge/libforge/capabilities/blob" - blobreplicacap "github.com/fil-forge/libforge/capabilities/blob/replica" + blobcap "github.com/fil-forge/libforge/commands/blob" + blobreplicacap "github.com/fil-forge/libforge/commands/blob/replica" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/lib/ucan_client" "github.com/fil-forge/ucantone/client" @@ -94,7 +94,7 @@ func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore // AllocateInvocation returns the invocation for the allocate request (for use in effects). func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.AllocateCommand, req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), ucan.Command(blobcap.Allocate), req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -170,7 +170,7 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan // AcceptInvocation returns the invocation for the accept request (for use in effects). func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.AcceptCommand, req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), ucan.Command(blobcap.Accept), req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -216,7 +216,7 @@ type ReplicaAllocateRequest struct { // Returns the response data, the invocation that was sent, and the receipt from // piri. It returns an error if the receipt contains a failure result. func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobreplicacap.AllocateCommand, req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), ucan.Command(blobreplicacap.Allocate), req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } diff --git a/pkg/routing/service.go b/pkg/routing/service.go index a1769db..b78f2df 100644 --- a/pkg/routing/service.go +++ b/pkg/routing/service.go @@ -6,7 +6,7 @@ import ( "net/url" "slices" - "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/commands/blob" "github.com/fil-forge/libforge/digestutil" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" diff --git a/pkg/routing/service_test.go b/pkg/routing/service_test.go index 2db8788..2052fc6 100644 --- a/pkg/routing/service_test.go +++ b/pkg/routing/service_test.go @@ -4,7 +4,7 @@ import ( "net/url" "testing" - "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/commands/blob" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/routing" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" diff --git a/pkg/service/handlers/access_claim.go b/pkg/service/handlers/access_claim.go index 8fa6abf..a18ae2c 100644 --- a/pkg/service/handlers/access_claim.go +++ b/pkg/service/handlers/access_claim.go @@ -3,7 +3,7 @@ package handlers import ( "fmt" - "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/sprue/pkg/identity" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" "github.com/fil-forge/ucantone/execution/bindexec" @@ -14,9 +14,9 @@ import ( ) func NewAccessClaimHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", access.ClaimCommand)) + log := logger.With(zap.String("handler", string(access.Claim))) return Handler{ - Capability: access.Claim, + Command: ucan.Command(access.Claim), Handler: bindexec.NewHandler(func( req *bindexec.Request[*access.ClaimArguments], res *bindexec.Response[*access.ClaimOK], diff --git a/pkg/service/handlers/access_claim_test.go b/pkg/service/handlers/access_claim_test.go index 19ff85a..ae977a1 100644 --- a/pkg/service/handlers/access_claim_test.go +++ b/pkg/service/handlers/access_claim_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/sprue/internal/testutil" dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" "github.com/fil-forge/ucantone/execution" diff --git a/pkg/service/handlers/access_confirm.go b/pkg/service/handlers/access_confirm.go index 4f19d6e..2056c79 100644 --- a/pkg/service/handlers/access_confirm.go +++ b/pkg/service/handlers/access_confirm.go @@ -3,8 +3,8 @@ package handlers import ( "fmt" - "github.com/fil-forge/libforge/capabilities/access" - "github.com/fil-forge/libforge/capabilities/ucan/attest" + "github.com/fil-forge/libforge/commands/access" + "github.com/fil-forge/libforge/commands/ucan/attest" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/pkg/identity" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" @@ -22,9 +22,9 @@ import ( ) func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", access.ConfirmCommand)) + log := logger.With(zap.String("handler", string(access.Confirm))) return Handler{ - Capability: access.Confirm, + Command: ucan.Command(access.Confirm), Handler: bindexec.NewHandler(func( req *bindexec.Request[*access.ConfirmArguments], res *bindexec.Response[*access.ConfirmOK], diff --git a/pkg/service/handlers/access_confirm_test.go b/pkg/service/handlers/access_confirm_test.go index 9d6df34..7164466 100644 --- a/pkg/service/handlers/access_confirm_test.go +++ b/pkg/service/handlers/access_confirm_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" diff --git a/pkg/service/handlers/access_delegate.go b/pkg/service/handlers/access_delegate.go index 887b8e2..670536f 100644 --- a/pkg/service/handlers/access_delegate.go +++ b/pkg/service/handlers/access_delegate.go @@ -3,7 +3,7 @@ package handlers import ( "fmt" - "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/sprue/pkg/provisioning" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" "github.com/fil-forge/ucantone/errors" @@ -14,9 +14,9 @@ import ( ) func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", access.DelegateCommand)) + log := logger.With(zap.String("handler", access.Delegate)) return Handler{ - Capability: access.Delegate, + Command: ucan.Command(access.Delegate), Handler: bindexec.NewHandler(func( req *bindexec.Request[*access.DelegateArguments], res *bindexec.Response[*access.DelegateOK], diff --git a/pkg/service/handlers/access_delegate_test.go b/pkg/service/handlers/access_delegate_test.go index 47a3974..d39f5eb 100644 --- a/pkg/service/handlers/access_delegate_test.go +++ b/pkg/service/handlers/access_delegate_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/provisioning" diff --git a/pkg/service/handlers/access_request.go b/pkg/service/handlers/access_request.go index 0c2277d..c22fa64 100644 --- a/pkg/service/handlers/access_request.go +++ b/pkg/service/handlers/access_request.go @@ -8,10 +8,9 @@ import ( "go.uber.org/zap" - "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/mailer" "github.com/fil-forge/ucantone/errors" @@ -34,9 +33,9 @@ var ( ) func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identity, mailer mailer.Mailer, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", access.RequestCommand)) + log := logger.With(zap.String("handler", string(access.Request))) return Handler{ - Capability: provider.List, + Command: ucan.Command(access.Request), Handler: bindexec.NewHandler(func( req *bindexec.Request[*access.RequestArguments], res *bindexec.Response[*access.RequestOK], diff --git a/pkg/service/handlers/access_request_test.go b/pkg/service/handlers/access_request_test.go index ad5999a..0788630 100644 --- a/pkg/service/handlers/access_request_test.go +++ b/pkg/service/handlers/access_request_test.go @@ -7,7 +7,7 @@ import ( "net/url" "testing" - "github.com/fil-forge/libforge/capabilities/access" + "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/internal/testutil" diff --git a/pkg/service/handlers/admin_provider_deregister.go b/pkg/service/handlers/admin_provider_deregister.go index 52088c9..5dbc570 100644 --- a/pkg/service/handlers/admin_provider_deregister.go +++ b/pkg/service/handlers/admin_provider_deregister.go @@ -1,18 +1,19 @@ package handlers import ( - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) func NewAdminProviderDeregisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", provider.DeregisterCommand)) + log := logger.With(zap.String("handler", string(provider.Deregister))) return Handler{ - Capability: provider.Deregister, + Command: ucan.Command(provider.Deregister), Handler: bindexec.NewHandler(func( req *bindexec.Request[*provider.DeregisterArguments], res *bindexec.Response[*provider.DeregisterOK], diff --git a/pkg/service/handlers/admin_provider_deregister_test.go b/pkg/service/handlers/admin_provider_deregister_test.go index d49c0ff..acdf198 100644 --- a/pkg/service/handlers/admin_provider_deregister_test.go +++ b/pkg/service/handlers/admin_provider_deregister_test.go @@ -6,13 +6,13 @@ import ( "testing" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" diff --git a/pkg/service/handlers/admin_provider_list.go b/pkg/service/handlers/admin_provider_list.go index 1a9a512..a3548f1 100644 --- a/pkg/service/handlers/admin_provider_list.go +++ b/pkg/service/handlers/admin_provider_list.go @@ -5,7 +5,7 @@ import ( "go.uber.org/zap" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" diff --git a/pkg/service/handlers/admin_provider_list_test.go b/pkg/service/handlers/admin_provider_list_test.go index babe84c..0cc5cd0 100644 --- a/pkg/service/handlers/admin_provider_list_test.go +++ b/pkg/service/handlers/admin_provider_list_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index a68ebb4..70c66b0 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -3,7 +3,7 @@ package handlers import ( "net/url" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" diff --git a/pkg/service/handlers/admin_provider_register_test.go b/pkg/service/handlers/admin_provider_register_test.go index aad1671..c866c74 100644 --- a/pkg/service/handlers/admin_provider_register_test.go +++ b/pkg/service/handlers/admin_provider_register_test.go @@ -5,12 +5,12 @@ import ( "testing" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider" + "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" diff --git a/pkg/service/handlers/admin_provider_weight_set.go b/pkg/service/handlers/admin_provider_weight_set.go index 0f69d7d..4ca44e2 100644 --- a/pkg/service/handlers/admin_provider_weight_set.go +++ b/pkg/service/handlers/admin_provider_weight_set.go @@ -3,17 +3,18 @@ package handlers import ( "go.uber.org/zap" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight" + "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight" "github.com/fil-forge/sprue/pkg/identity" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" ) func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", weight.SetCommand)) + log := logger.With(zap.String("handler", string(weight.Set))) return Handler{ - Capability: weight.Set, + Command: ucan.Command(weight.Set), Handler: bindexec.NewHandler(func( req *bindexec.Request[*weight.SetArguments], res *bindexec.Response[*weight.SetOK], diff --git a/pkg/service/handlers/admin_provider_weight_set_test.go b/pkg/service/handlers/admin_provider_weight_set_test.go index 6db889d..ca791bd 100644 --- a/pkg/service/handlers/admin_provider_weight_set_test.go +++ b/pkg/service/handlers/admin_provider_weight_set_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/fil-forge/sprue/internal/testutil" - "github.com/fil-forge/sprue/pkg/capabilities/admin/provider/weight" + "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" - edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/did" + edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index 49fb7fe..59aed17 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -6,9 +6,9 @@ import ( "crypto/ed25519" "fmt" - accesscaps "github.com/fil-forge/libforge/capabilities/access" - blobcaps "github.com/fil-forge/libforge/capabilities/blob" - httpcaps "github.com/fil-forge/libforge/capabilities/http" + accesscaps "github.com/fil-forge/libforge/commands/access" + blobcaps "github.com/fil-forge/libforge/commands/blob" + httpcaps "github.com/fil-forge/libforge/commands/http" "github.com/fil-forge/libforge/digestutil" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/identity" @@ -34,9 +34,9 @@ import ( ) func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, router *routing.Service, nodeProvider piriclient.Provider, agentStore agent.Store, blobRegistry blobregistry.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", blobcaps.AddCommand)) + log := logger.With(zap.String("handler", string(blobcaps.Add))) return Handler{ - Capability: blobcaps.Add, + Command: ucan.Command(blobcaps.Add), Handler: bindexec.NewHandler(func( req *bindexec.Request[*blobcaps.AddArguments], res *bindexec.Response[*blobcaps.AddOK], @@ -296,7 +296,7 @@ func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.Alloc ), ) if err != nil { - return nil, nil, fmt.Errorf("invoking %q: %w", httpcaps.PutCommand, err) + return nil, nil, fmt.Errorf("invoking %q: %w", httpcaps.Put, err) } var putRcpt ucan.Receipt @@ -311,7 +311,7 @@ func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.Alloc &httpcaps.PutOK{}, ) if err != nil { - return nil, nil, fmt.Errorf("issuing %q receipt: %w", httpcaps.PutCommand, err) + return nil, nil, fmt.Errorf("issuing %q receipt: %w", httpcaps.Put, err) } } diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go index dbd08ff..4f50309 100644 --- a/pkg/service/handlers/blob_add_test.go +++ b/pkg/service/handlers/blob_add_test.go @@ -4,15 +4,16 @@ import ( "bytes" "context" "crypto/ed25519" + "fmt" "net/http/httptest" "net/url" "testing" "time" - "github.com/fil-forge/libforge/capabilities" - accesscaps "github.com/fil-forge/libforge/capabilities/access" - blobcaps "github.com/fil-forge/libforge/capabilities/blob" - httpcaps "github.com/fil-forge/libforge/capabilities/http" + "github.com/fil-forge/libforge/commands" + accesscaps "github.com/fil-forge/libforge/commands/access" + blobcaps "github.com/fil-forge/libforge/commands/blob" + httpcaps "github.com/fil-forge/libforge/commands/http" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/identity" @@ -35,13 +36,15 @@ import ( "github.com/fil-forge/ucantone/principal" ed25519signer "github.com/fil-forge/ucantone/principal/ed25519" "github.com/fil-forge/ucantone/principal/signer" + "github.com/fil-forge/ucantone/principal/verifier" "github.com/fil-forge/ucantone/server" + "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" - "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/fil-forge/ucantone/ucan/promise" "github.com/fil-forge/ucantone/ucan/receipt" "github.com/fil-forge/ucantone/validator" + "github.com/fil-forge/ucantone/validator/errors" "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -119,28 +122,28 @@ func newMockPiriServer( ) *httptest.Server { t.Helper() - resolveDIDKey := func(ctx context.Context, d did.DID) ([]did.DID, error) { + resolveDIDKey := func(ctx context.Context, d did.DID) (ucan.Verifier, error) { if d == uploadService.DID() { if w, ok := uploadService.(signer.Unwrapper); ok { - return []did.DID{w.Unwrap().DID()}, nil + return verifier.FromDIDKey(w.Unwrap().DID()) } } - return validator.FailDIDKeyResolution(ctx, d) + return nil, errors.NewDIDKeyResolutionError(d, fmt.Errorf("unexpected DID to resolve")) } srv := server.NewHTTP( storageProvider, - server.WithValidationOptions(validator.WithDIDResolver(resolveDIDKey)), + server.WithValidationOptions(validator.WithDIDVerifierResolver(resolveDIDKey)), ) - srv.Handle(blobcaps.Allocate, bindexec.NewHandler(func( + srv.Handle(ucan.Command(blobcaps.Allocate), bindexec.NewHandler(func( req *bindexec.Request[*blobcaps.AllocateArguments], res *bindexec.Response[*blobcaps.AllocateOK], ) error { return res.SetSuccess(allocateOK) })) - srv.Handle(blobcaps.Accept, bindexec.NewHandler(func( + srv.Handle(ucan.Command(blobcaps.Accept), bindexec.NewHandler(func( req *bindexec.Request[*blobcaps.AcceptArguments], res *bindexec.Response[*blobcaps.AcceptOK], ) error { @@ -273,7 +276,7 @@ func TestBlobAddHandler(t *testing.T) { allocateOK := &blobcaps.AllocateOK{ Size: 1024, Address: &blobcaps.BlobAddress{ - URL: capabilities.CborURL(*putURL), + URL: commands.CborURL(*putURL), Headers: map[string]string{}, Expires: time.Now().Add(time.Hour).Unix(), }, @@ -303,8 +306,8 @@ func TestBlobAddHandler(t *testing.T) { // Authorize the upload service to invoke /blob/allocate and /blob/accept // on the space. This is the proof chain the upload service forwards to the // storage provider. - allocProof := testutil.Must(delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AllocateCommand))(t) - acceptProof := testutil.Must(delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AcceptCommand))(t) + allocProof := testutil.Must(blobcaps.Allocate.Delegate(space, uploadService.DID(), space.DID()))(t) + acceptProof := testutil.Must(blobcaps.Accept.Delegate(space, uploadService.DID(), space.DID()))(t) req := execution.NewRequest(ctx, inv, execution.WithDelegations(allocProof, acceptProof)) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) @@ -350,8 +353,8 @@ func TestBlobAddHandler(t *testing.T) { ) require.NoError(t, err) - allocProof := testutil.Must(delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AllocateCommand))(t) - acceptProof := testutil.Must(delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AcceptCommand))(t) + allocProof := testutil.Must(blobcaps.Allocate.Delegate(space, uploadService.DID(), space.DID()))(t) + acceptProof := testutil.Must(blobcaps.Accept.Delegate(space, uploadService.DID(), space.DID()))(t) req := execution.NewRequest(ctx, inv, execution.WithDelegations(allocProof, acceptProof)) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) diff --git a/pkg/service/handlers/blob_list.go b/pkg/service/handlers/blob_list.go index b48772c..8d5f369 100644 --- a/pkg/service/handlers/blob_list.go +++ b/pkg/service/handlers/blob_list.go @@ -3,16 +3,17 @@ package handlers import ( "fmt" - blobcaps "github.com/fil-forge/libforge/capabilities/blob" + blobcaps "github.com/fil-forge/libforge/commands/blob" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", blobcaps.ListCommand)) + log := logger.With(zap.String("handler", string(blobcaps.List))) return Handler{ - Capability: blobcaps.List, + Command: ucan.Command(blobcaps.List), Handler: bindexec.NewHandler(func( req *bindexec.Request[*blobcaps.ListArguments], res *bindexec.Response[*blobcaps.ListOK], diff --git a/pkg/service/handlers/blob_list_test.go b/pkg/service/handlers/blob_list_test.go index 6b52e47..2b41aad 100644 --- a/pkg/service/handlers/blob_list_test.go +++ b/pkg/service/handlers/blob_list_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - blobcaps "github.com/fil-forge/libforge/capabilities/blob" + blobcaps "github.com/fil-forge/libforge/commands/blob" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/service/handlers" diff --git a/pkg/service/handlers/handlers.go b/pkg/service/handlers/handlers.go index b98935a..ca63991 100644 --- a/pkg/service/handlers/handlers.go +++ b/pkg/service/handlers/handlers.go @@ -2,10 +2,10 @@ package handlers import ( "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/validator" + "github.com/fil-forge/ucantone/ucan" ) type Handler struct { - Capability validator.Capability - Handler execution.HandlerFunc + Command ucan.Command + Handler execution.HandlerFunc } diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go index 8872b9f..e895ca6 100644 --- a/pkg/service/handlers/index_add.go +++ b/pkg/service/handlers/index_add.go @@ -5,8 +5,8 @@ import ( "go.uber.org/zap" - accesscaps "github.com/fil-forge/libforge/capabilities/access" - indexcaps "github.com/fil-forge/libforge/capabilities/index" + accesscaps "github.com/fil-forge/libforge/commands/access" + indexcaps "github.com/fil-forge/libforge/commands/index" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/indexerclient" @@ -14,12 +14,13 @@ import ( blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" ) func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", indexcaps.AddCommand)) + log := logger.With(zap.String("handler", string(indexcaps.Add))) return Handler{ - Capability: indexcaps.Add, + Command: ucan.Command(indexcaps.Add), Handler: bindexec.NewHandler(func( req *bindexec.Request[*indexcaps.AddArguments], res *bindexec.Response[*indexcaps.AddOK], diff --git a/pkg/service/handlers/index_add_test.go b/pkg/service/handlers/index_add_test.go index 261027d..5db749d 100644 --- a/pkg/service/handlers/index_add_test.go +++ b/pkg/service/handlers/index_add_test.go @@ -3,15 +3,16 @@ package handlers_test import ( "bytes" "context" + "fmt" "net/http/httptest" "net/url" "testing" - accesscaps "github.com/fil-forge/libforge/capabilities/access" - assertcaps "github.com/fil-forge/libforge/capabilities/assert" - blobcaps "github.com/fil-forge/libforge/capabilities/blob" - contentcaps "github.com/fil-forge/libforge/capabilities/content" - indexcaps "github.com/fil-forge/libforge/capabilities/index" + accesscaps "github.com/fil-forge/libforge/commands/access" + assertcaps "github.com/fil-forge/libforge/commands/assert" + blobcaps "github.com/fil-forge/libforge/commands/blob" + contentcaps "github.com/fil-forge/libforge/commands/content" + indexcaps "github.com/fil-forge/libforge/commands/index" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/identity" @@ -26,10 +27,12 @@ import ( "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/principal" "github.com/fil-forge/ucantone/principal/signer" + "github.com/fil-forge/ucantone/principal/verifier" "github.com/fil-forge/ucantone/server" - "github.com/fil-forge/ucantone/ucan/delegation" + "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/fil-forge/ucantone/validator" + "github.com/fil-forge/ucantone/validator/errors" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -46,21 +49,21 @@ func newMockIndexerServer( ) *httptest.Server { t.Helper() - resolveDIDKey := func(ctx context.Context, d did.DID) ([]did.DID, error) { + resolveDIDKey := func(ctx context.Context, d did.DID) (ucan.Verifier, error) { if d == uploadService.DID() { if w, ok := uploadService.(signer.Unwrapper); ok { - return []did.DID{w.Unwrap().DID()}, nil + return verifier.FromDIDKey(w.Unwrap().DID()) } } - return validator.FailDIDKeyResolution(ctx, d) + return nil, errors.NewDIDKeyResolutionError(d, fmt.Errorf("unexpected DID to resolve")) } srv := server.NewHTTP( indexerSigner, - server.WithValidationOptions(validator.WithDIDResolver(resolveDIDKey)), + server.WithValidationOptions(validator.WithDIDVerifierResolver(resolveDIDKey)), ) - srv.Handle(assertcaps.Index, bindexec.NewHandler(func( + srv.Handle(ucan.Command(assertcaps.Index), bindexec.NewHandler(func( req *bindexec.Request[*assertcaps.IndexArguments], res *bindexec.Response[*assertcaps.IndexOK], ) error { @@ -185,7 +188,7 @@ func TestIndexAddHandler(t *testing.T) { // /content/retrieve delegation from space → upload service so the // handler can build a proof chain that authorizes the indexer to // retrieve the index blob. - retrievalAuth, err := delegation.Delegate(space, uploadService.DID(), space.DID(), contentcaps.RetrieveCommand) + retrievalAuth, err := contentcaps.Retrieve.Delegate(space, uploadService.DID(), space.DID()) require.NoError(t, err) req, res := invokeIndexAdd(t, ctx, alice, uploadService, space, indexCID, diff --git a/pkg/service/handlers/provider_add.go b/pkg/service/handlers/provider_add.go index c3dfaf1..7f8ec36 100644 --- a/pkg/service/handlers/provider_add.go +++ b/pkg/service/handlers/provider_add.go @@ -3,7 +3,7 @@ package handlers import ( "fmt" - providercaps "github.com/fil-forge/libforge/capabilities/provider" + providercaps "github.com/fil-forge/libforge/commands/provider" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/pkg/billing" @@ -11,13 +11,14 @@ import ( "github.com/fil-forge/sprue/pkg/store/consumer" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", providercaps.AddCommand)) + log := logger.With(zap.String("handler", string(providercaps.Add))) return Handler{ - Capability: providercaps.Add, + Command: ucan.Command(providercaps.Add), Handler: bindexec.NewHandler(func( req *bindexec.Request[*providercaps.AddArguments], res *bindexec.Response[*providercaps.AddOK], diff --git a/pkg/service/handlers/provider_add_test.go b/pkg/service/handlers/provider_add_test.go index 0c4b585..28e4a46 100644 --- a/pkg/service/handlers/provider_add_test.go +++ b/pkg/service/handlers/provider_add_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - providercaps "github.com/fil-forge/libforge/capabilities/provider" + providercaps "github.com/fil-forge/libforge/commands/provider" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/internal/testutil" diff --git a/pkg/service/handlers/space_info.go b/pkg/service/handlers/space_info.go index d06c9cb..13301d4 100644 --- a/pkg/service/handlers/space_info.go +++ b/pkg/service/handlers/space_info.go @@ -4,18 +4,19 @@ import ( "fmt" "strings" - spacecaps "github.com/fil-forge/libforge/capabilities/space" + spacecaps "github.com/fil-forge/libforge/commands/space" "github.com/fil-forge/sprue/pkg/provisioning" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) // This handler returns info about a space, including its providers. func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", spacecaps.InfoCommand)) + log := logger.With(zap.String("handler", string(spacecaps.Info))) return Handler{ - Capability: spacecaps.Info, + Command: ucan.Command(spacecaps.Info), Handler: bindexec.NewHandler(func( req *bindexec.Request[*spacecaps.InfoArguments], res *bindexec.Response[*spacecaps.InfoOK], diff --git a/pkg/service/handlers/space_info_test.go b/pkg/service/handlers/space_info_test.go index 79144c7..0a7b7d5 100644 --- a/pkg/service/handlers/space_info_test.go +++ b/pkg/service/handlers/space_info_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - spacecaps "github.com/fil-forge/libforge/capabilities/space" + spacecaps "github.com/fil-forge/libforge/commands/space" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/provisioning" diff --git a/pkg/service/handlers/ucan_conclude.go b/pkg/service/handlers/ucan_conclude.go index 1c87e14..30720dd 100644 --- a/pkg/service/handlers/ucan_conclude.go +++ b/pkg/service/handlers/ucan_conclude.go @@ -6,7 +6,7 @@ import ( "maps" "slices" - ucancaps "github.com/fil-forge/libforge/capabilities/ucan" + ucancaps "github.com/fil-forge/libforge/commands/ucan" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/store/agent" "github.com/fil-forge/ucantone/errors" @@ -32,10 +32,10 @@ type ConclusionHandler struct { // When it receives an /http/put receipt, it calls /blob/accept on piri // and stores the accept receipt for later retrieval. func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handlers map[ucan.Command]ConclusionHandlerFunc, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", ucancaps.ConcludeCommand)) + log := logger.With(zap.String("handler", string(ucancaps.Conclude))) log.Info("registered conclude handlers", zap.Stringers("commands", slices.Collect(maps.Keys(handlers)))) return Handler{ - Capability: ucancaps.Conclude, + Command: ucan.Command(ucancaps.Conclude), Handler: bindexec.NewHandler(func( req *bindexec.Request[*ucancaps.ConcludeArguments], res *bindexec.Response[*ucancaps.ConcludeOK], diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index d924855..ee4677e 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -5,9 +5,9 @@ import ( "context" "fmt" - blobcaps "github.com/fil-forge/libforge/capabilities/blob" - httpcaps "github.com/fil-forge/libforge/capabilities/http" - ucancaps "github.com/fil-forge/libforge/capabilities/ucan" + blobcaps "github.com/fil-forge/libforge/commands/blob" + httpcaps "github.com/fil-forge/libforge/commands/http" + ucancaps "github.com/fil-forge/libforge/commands/ucan" "github.com/fil-forge/libforge/digestutil" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/piriclient" @@ -28,11 +28,11 @@ func NewHTTPPutConcludeHandler( logger *zap.Logger, ) ConclusionHandler { log := logger.With( - zap.String("handler", ucancaps.ConcludeCommand), - zap.String("conclude", httpcaps.PutCommand), + zap.String("handler", string(ucancaps.Conclude)), + zap.String("conclude", string(httpcaps.Put)), ) return ConclusionHandler{ - Command: httpcaps.PutCommand, + Command: ucan.Command(httpcaps.Put), Handler: func(ctx context.Context, putInv ucan.Invocation, putRcpt ucan.Receipt, meta ucan.Container) error { log := log.With(zap.Stringer("ran", putRcpt.Ran())) log.Debug("handling conclude") diff --git a/pkg/service/handlers/ucan_conclude_http_put_test.go b/pkg/service/handlers/ucan_conclude_http_put_test.go index 24883dd..60bbcc3 100644 --- a/pkg/service/handlers/ucan_conclude_http_put_test.go +++ b/pkg/service/handlers/ucan_conclude_http_put_test.go @@ -4,8 +4,8 @@ import ( "net/url" "testing" - blobcaps "github.com/fil-forge/libforge/capabilities/blob" - httpcaps "github.com/fil-forge/libforge/capabilities/http" + blobcaps "github.com/fil-forge/libforge/commands/blob" + httpcaps "github.com/fil-forge/libforge/commands/http" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/piriclient" @@ -19,7 +19,6 @@ import ( spacediff_store "github.com/fil-forge/sprue/pkg/store/space_diff/memory" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" "github.com/fil-forge/ucantone/ucan/container" - "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/fil-forge/ucantone/ucan/promise" "github.com/fil-forge/ucantone/ucan/receipt" @@ -216,7 +215,7 @@ func TestHTTPPutConcludeHandler(t *testing.T) { // Authorize the upload service to invoke /blob/accept on the space and // pass the proof through the conclude metadata so the piri client can // forward it to the storage provider. - acceptProof, err := delegation.Delegate(space, uploadService.DID(), space.DID(), blobcaps.AcceptCommand) + acceptProof, err := blobcaps.Accept.Delegate(space, uploadService.DID(), space.DID()) require.NoError(t, err) meta := container.New(container.WithDelegations(acceptProof)) diff --git a/pkg/service/handlers/ucan_conclude_test.go b/pkg/service/handlers/ucan_conclude_test.go index b90ec66..e3b99f6 100644 --- a/pkg/service/handlers/ucan_conclude_test.go +++ b/pkg/service/handlers/ucan_conclude_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - ucancaps "github.com/fil-forge/libforge/capabilities/ucan" + ucancaps "github.com/fil-forge/libforge/commands/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" diff --git a/pkg/service/handlers/upload_add.go b/pkg/service/handlers/upload_add.go index a7160a3..492f6da 100644 --- a/pkg/service/handlers/upload_add.go +++ b/pkg/service/handlers/upload_add.go @@ -3,20 +3,21 @@ package handlers import ( "fmt" - accesscaps "github.com/fil-forge/libforge/capabilities/access" - uploadcaps "github.com/fil-forge/libforge/capabilities/upload" + accesscaps "github.com/fil-forge/libforge/commands/access" + uploadcaps "github.com/fil-forge/libforge/commands/upload" "github.com/fil-forge/sprue/pkg/provisioning" upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) // This handler registers an upload (root CID + shards mapping). func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", uploadcaps.AddCommand)) + log := logger.With(zap.String("handler", string(uploadcaps.Add))) return Handler{ - Capability: uploadcaps.Add, + Command: ucan.Command(uploadcaps.Add), Handler: bindexec.NewHandler(func( req *bindexec.Request[*uploadcaps.AddArguments], res *bindexec.Response[*uploadcaps.AddOK], diff --git a/pkg/service/handlers/upload_add_test.go b/pkg/service/handlers/upload_add_test.go index 2a5f237..0843007 100644 --- a/pkg/service/handlers/upload_add_test.go +++ b/pkg/service/handlers/upload_add_test.go @@ -5,8 +5,8 @@ import ( "context" "testing" - accesscaps "github.com/fil-forge/libforge/capabilities/access" - uploadcaps "github.com/fil-forge/libforge/capabilities/upload" + accesscaps "github.com/fil-forge/libforge/commands/access" + uploadcaps "github.com/fil-forge/libforge/commands/upload" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/provisioning" diff --git a/pkg/service/handlers/upload_list.go b/pkg/service/handlers/upload_list.go index ec36952..32fb59d 100644 --- a/pkg/service/handlers/upload_list.go +++ b/pkg/service/handlers/upload_list.go @@ -3,16 +3,17 @@ package handlers import ( "fmt" - uploadcaps "github.com/fil-forge/libforge/capabilities/upload" + uploadcaps "github.com/fil-forge/libforge/commands/upload" upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", uploadcaps.ListCommand)) + log := logger.With(zap.String("handler", string(uploadcaps.List))) return Handler{ - Capability: uploadcaps.List, + Command: ucan.Command(uploadcaps.List), Handler: bindexec.NewHandler(func( req *bindexec.Request[*uploadcaps.ListArguments], res *bindexec.Response[*uploadcaps.ListOK], diff --git a/pkg/service/handlers/upload_list_test.go b/pkg/service/handlers/upload_list_test.go index 5b99c45..9f1b85f 100644 --- a/pkg/service/handlers/upload_list_test.go +++ b/pkg/service/handlers/upload_list_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - uploadcaps "github.com/fil-forge/libforge/capabilities/upload" + uploadcaps "github.com/fil-forge/libforge/commands/upload" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/service/handlers" upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" diff --git a/pkg/service/handlers/upload_shard_list.go b/pkg/service/handlers/upload_shard_list.go index 7385a69..9f52f0a 100644 --- a/pkg/service/handlers/upload_shard_list.go +++ b/pkg/service/handlers/upload_shard_list.go @@ -3,17 +3,18 @@ package handlers import ( "fmt" - shardcaps "github.com/fil-forge/libforge/capabilities/upload/shard" + shardcaps "github.com/fil-forge/libforge/commands/upload/shard" upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) // This handler lists the shards of an upload. func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", shardcaps.ListCommand)) + log := logger.With(zap.String("handler", string(shardcaps.List))) return Handler{ - Capability: shardcaps.List, + Command: ucan.Command(shardcaps.List), Handler: bindexec.NewHandler(func( req *bindexec.Request[*shardcaps.ListArguments], res *bindexec.Response[*shardcaps.ListOK], diff --git a/pkg/service/handlers/upload_shard_list_test.go b/pkg/service/handlers/upload_shard_list_test.go index 78d680f..aa3a2e9 100644 --- a/pkg/service/handlers/upload_shard_list_test.go +++ b/pkg/service/handlers/upload_shard_list_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - shardcaps "github.com/fil-forge/libforge/capabilities/upload/shard" + shardcaps "github.com/fil-forge/libforge/commands/upload/shard" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/service/handlers" upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" diff --git a/pkg/service/service.go b/pkg/service/service.go index a274b58..06a3066 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -6,7 +6,9 @@ import ( "fmt" "net/http" "slices" + "time" + "github.com/fil-forge/libforge/didresolver" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/lib/ucan_server" "github.com/fil-forge/sprue/pkg/service/handlers" @@ -33,35 +35,52 @@ type Service struct { } // New creates a new Service instance. -func New(id *identity.Identity, agentStore agent.Store, delegationStore delegation_store.Store, handlers []handlers.Handler, logger *zap.Logger, options ...server.HTTPOption) *Service { +func New(id *identity.Identity, agentStore agent.Store, delegationStore delegation_store.Store, handlers []handlers.Handler, logger *zap.Logger, options ...server.HTTPOption) (*Service, error) { + server, err := createUCANServer(id.Signer, agentStore, handlers, logger, options...) + if err != nil { + return nil, err + } return &Service{ identity: id, agentStore: agentStore, delegationStore: delegationStore, logger: logger, - ucanServer: createUCANServer(id.Signer, agentStore, handlers, logger, options...), - } + ucanServer: server, + }, nil } // createUCANServer creates the UCAN RPC server with registered handlers. -func createUCANServer(id principal.Signer, agentStore agent.Store, handlers []handlers.Handler, logger *zap.Logger, options ...server.HTTPOption) *server.HTTPServer { +func createUCANServer(id principal.Signer, agentStore agent.Store, handlers []handlers.Handler, logger *zap.Logger, options ...server.HTTPOption) (*server.HTTPServer, error) { + httpResolver, err := didresolver.NewHTTPResolver() + if err != nil { + return nil, err + } + cacheResolver, err := didresolver.NewCachedResolver(httpResolver.Resolve, time.Hour*3) + if err != nil { + return nil, err + } + options = append( slices.Clone(options), server.WithReceiptTimestamps(true), server.WithEventListener(ucan_server.AgentMessageLogger{Logger: logger, AgentStore: agentStore}), server.WithEventListener(ucan_server.ErrorHandler{Logger: logger}), server.WithValidationOptions( - validator.WithPrincipalParser(ucan_server.PrincipalParser), + validator.WithDIDVerifierResolvers(map[string]validator.DIDVerifierResolverFunc{ + "key": ucan_server.ResolveDIDKey, + "web": cacheResolver.Resolve, + }), validator.WithNonStandardSignatureVerifier( ucan_server.NewAttestationVerifier(id.Verifier()), ), ), ) + srv := server.NewHTTP(id, options...) for _, h := range handlers { - srv.Handle(h.Capability, h.Handler) + srv.Handle(h.Command, h.Handler) } - return srv + return srv, nil } // HandleUCANRequest handles incoming UCAN RPC requests. diff --git a/pkg/store/agent/agent_test.go b/pkg/store/agent/agent_test.go index 3ac3343..d1c7d50 100644 --- a/pkg/store/agent/agent_test.go +++ b/pkg/store/agent/agent_test.go @@ -5,7 +5,7 @@ import ( "runtime" "testing" - ucancap "github.com/fil-forge/libforge/capabilities/ucan" + ucancap "github.com/fil-forge/libforge/commands/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store/agent" agentaws "github.com/fil-forge/sprue/pkg/store/agent/aws" diff --git a/pkg/store/blob_registry/aws/store.go b/pkg/store/blob_registry/aws/store.go index 81f4f35..70a6f58 100644 --- a/pkg/store/blob_registry/aws/store.go +++ b/pkg/store/blob_registry/aws/store.go @@ -9,7 +9,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/commands/blob" "github.com/fil-forge/libforge/digestutil" "github.com/fil-forge/sprue/pkg/internal/timeutil" "github.com/fil-forge/sprue/pkg/store" diff --git a/pkg/store/blob_registry/blob_registry.go b/pkg/store/blob_registry/blob_registry.go index 4f5ce8a..753bcd2 100644 --- a/pkg/store/blob_registry/blob_registry.go +++ b/pkg/store/blob_registry/blob_registry.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/commands/blob" "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/errors" diff --git a/pkg/store/blob_registry/blob_registry_test.go b/pkg/store/blob_registry/blob_registry_test.go index cb46db0..35a9af7 100644 --- a/pkg/store/blob_registry/blob_registry_test.go +++ b/pkg/store/blob_registry/blob_registry_test.go @@ -5,7 +5,7 @@ import ( "runtime" "testing" - "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/commands/blob" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/store" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" diff --git a/pkg/store/blob_registry/memory/store.go b/pkg/store/blob_registry/memory/store.go index e4e6014..33148ac 100644 --- a/pkg/store/blob_registry/memory/store.go +++ b/pkg/store/blob_registry/memory/store.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/fil-forge/libforge/capabilities/blob" + "github.com/fil-forge/libforge/commands/blob" "github.com/fil-forge/sprue/pkg/store" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/sprue/pkg/store/consumer" diff --git a/pkg/store/metrics/metrics.go b/pkg/store/metrics/metrics.go index b7adf4b..3e8d13a 100644 --- a/pkg/store/metrics/metrics.go +++ b/pkg/store/metrics/metrics.go @@ -3,19 +3,19 @@ package metrics import ( "context" - "github.com/fil-forge/libforge/capabilities/blob" - "github.com/fil-forge/libforge/capabilities/upload" + "github.com/fil-forge/libforge/commands/blob" + "github.com/fil-forge/libforge/commands/upload" "github.com/fil-forge/ucantone/did" ) -const BlobAddTotalMetric = blob.AddCommand + "-total" -const BlobAddSizeTotalMetric = blob.AddCommand + "-size-total" +var BlobAddTotalMetric = string(blob.Add) + "-total" +var BlobAddSizeTotalMetric = string(blob.Add) + "-size-total" -const BlobRemoveTotalMetric = blob.RemoveCommand + "-total" -const BlobRemoveSizeTotalMetric = blob.RemoveCommand + "-size-total" +var BlobRemoveTotalMetric = string(blob.Remove) + "-total" +var BlobRemoveSizeTotalMetric = string(blob.Remove) + "-size-total" -const UploadAddTotalMetric = upload.AddCommand + "-total" -const UploadRemoveTotalMetric = upload.RemoveCommand + "-total" +var UploadAddTotalMetric = string(upload.Add) + "-total" +var UploadRemoveTotalMetric = string(upload.Remove) + "-total" type Store interface { // Get all metrics from storage. From 5f7a6d73bec69068c281da4d77a28153081e2b65 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 19 May 2026 17:44:11 +0100 Subject: [PATCH 15/23] refactor: commands --- Makefile | 3 + .../provider/{datamodel => }/cbor_gen.go | 108 +++-------- .../admin/provider/datamodel/deregister.go | 7 - .../admin/provider/datamodel/gen/main.go | 23 --- pkg/commands/admin/provider/datamodel/list.go | 16 -- .../admin/provider/datamodel/register.go | 8 - pkg/commands/admin/provider/deregister.go | 12 +- pkg/commands/admin/provider/gen/main.go | 44 +++++ .../provider/{datamodel => }/json_gen.go | 175 ++++++------------ pkg/commands/admin/provider/list.go | 13 +- pkg/commands/admin/provider/register.go | 13 +- pkg/commands/admin/provider/types.go | 23 +++ .../weight/{datamodel => }/cbor_gen.go | 12 +- .../provider/weight/datamodel/gen/main.go | 19 -- .../admin/provider/weight/gen/main.go | 41 ++++ .../weight/{datamodel => }/json_gen.go | 28 +-- pkg/commands/admin/provider/weight/set.go | 12 +- .../weight/{datamodel/set.go => types.go} | 4 +- 18 files changed, 227 insertions(+), 334 deletions(-) rename pkg/commands/admin/provider/{datamodel => }/cbor_gen.go (82%) delete mode 100644 pkg/commands/admin/provider/datamodel/deregister.go delete mode 100644 pkg/commands/admin/provider/datamodel/gen/main.go delete mode 100644 pkg/commands/admin/provider/datamodel/list.go delete mode 100644 pkg/commands/admin/provider/datamodel/register.go create mode 100644 pkg/commands/admin/provider/gen/main.go rename pkg/commands/admin/provider/{datamodel => }/json_gen.go (73%) create mode 100644 pkg/commands/admin/provider/types.go rename pkg/commands/admin/provider/weight/{datamodel => }/cbor_gen.go (94%) delete mode 100644 pkg/commands/admin/provider/weight/datamodel/gen/main.go create mode 100644 pkg/commands/admin/provider/weight/gen/main.go rename pkg/commands/admin/provider/weight/{datamodel => }/json_gen.go (82%) rename pkg/commands/admin/provider/weight/{datamodel/set.go => types.go} (84%) diff --git a/Makefile b/Makefile index 355532e..15afe45 100644 --- a/Makefile +++ b/Makefile @@ -37,3 +37,6 @@ clean: docker-build: $(DOCKER) build -t sprue:latest . + +gen: + go generate ./... diff --git a/pkg/commands/admin/provider/datamodel/cbor_gen.go b/pkg/commands/admin/provider/cbor_gen.go similarity index 82% rename from pkg/commands/admin/provider/datamodel/cbor_gen.go rename to pkg/commands/admin/provider/cbor_gen.go index 6719e9a..5229e35 100644 --- a/pkg/commands/admin/provider/datamodel/cbor_gen.go +++ b/pkg/commands/admin/provider/cbor_gen.go @@ -1,6 +1,8 @@ +//go:build !codegen + // Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. -package datamodel +package provider import ( "fmt" @@ -18,73 +20,7 @@ var _ = cid.Undef var _ = math.E var _ = sort.Sort -func (t *ListArgumentsModel) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - - cw := cbg.NewCborWriter(w) - - if _, err := cw.Write([]byte{160}); err != nil { - return err - } - return nil -} - -func (t *ListArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { - *t = ListArgumentsModel{} - - cr := cbg.NewCborReader(r) - - maj, extra, err := cr.ReadHeader() - if err != nil { - return err - } - defer func() { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - }() - - if maj != cbg.MajMap { - return fmt.Errorf("cbor input should be of type map") - } - - if extra > cbg.MaxLength { - return fmt.Errorf("ListArgumentsModel: map struct too large (%d)", extra) - } - - n := extra - - nameBuf := make([]byte, 0) - for i := uint64(0); i < n; i++ { - nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192) - if err != nil { - return err - } - - if !ok { - // Field doesn't exist on this type, so ignore it - if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil { - return err - } - continue - } - - switch string(nameBuf[:nameLen]) { - - default: - // Field doesn't exist on this type, so ignore it - if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil { - return err - } - } - } - - return nil -} -func (t *ProviderModel) MarshalCBOR(w io.Writer) error { +func (t *Provider) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err @@ -182,8 +118,8 @@ func (t *ProviderModel) MarshalCBOR(w io.Writer) error { return nil } -func (t *ProviderModel) UnmarshalCBOR(r io.Reader) (err error) { - *t = ProviderModel{} +func (t *Provider) UnmarshalCBOR(r io.Reader) (err error) { + *t = Provider{} cr := cbg.NewCborReader(r) @@ -202,7 +138,7 @@ func (t *ProviderModel) UnmarshalCBOR(r io.Reader) (err error) { } if extra > cbg.MaxLength { - return fmt.Errorf("ProviderModel: map struct too large (%d)", extra) + return fmt.Errorf("Provider: map struct too large (%d)", extra) } n := extra @@ -307,7 +243,7 @@ func (t *ProviderModel) UnmarshalCBOR(r io.Reader) (err error) { return nil } -func (t *ListOKModel) MarshalCBOR(w io.Writer) error { +func (t *ListOK) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err @@ -319,7 +255,7 @@ func (t *ListOKModel) MarshalCBOR(w io.Writer) error { return err } - // t.Providers ([]datamodel.ProviderModel) (slice) + // t.Providers ([]provider.Provider) (slice) if len("providers") > 8192 { return xerrors.Errorf("Value in field \"providers\" was too long") } @@ -347,8 +283,8 @@ func (t *ListOKModel) MarshalCBOR(w io.Writer) error { return nil } -func (t *ListOKModel) UnmarshalCBOR(r io.Reader) (err error) { - *t = ListOKModel{} +func (t *ListOK) UnmarshalCBOR(r io.Reader) (err error) { + *t = ListOK{} cr := cbg.NewCborReader(r) @@ -367,7 +303,7 @@ func (t *ListOKModel) UnmarshalCBOR(r io.Reader) (err error) { } if extra > cbg.MaxLength { - return fmt.Errorf("ListOKModel: map struct too large (%d)", extra) + return fmt.Errorf("ListOK: map struct too large (%d)", extra) } n := extra @@ -388,7 +324,7 @@ func (t *ListOKModel) UnmarshalCBOR(r io.Reader) (err error) { } switch string(nameBuf[:nameLen]) { - // t.Providers ([]datamodel.ProviderModel) (slice) + // t.Providers ([]provider.Provider) (slice) case "providers": maj, extra, err = cr.ReadHeader() @@ -405,7 +341,7 @@ func (t *ListOKModel) UnmarshalCBOR(r io.Reader) (err error) { } if extra > 0 { - t.Providers = make([]ProviderModel, extra) + t.Providers = make([]Provider, extra) } for i := 0; i < int(extra); i++ { @@ -438,7 +374,7 @@ func (t *ListOKModel) UnmarshalCBOR(r io.Reader) (err error) { return nil } -func (t *RegisterArgumentsModel) MarshalCBOR(w io.Writer) error { +func (t *RegisterArguments) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err @@ -491,8 +427,8 @@ func (t *RegisterArgumentsModel) MarshalCBOR(w io.Writer) error { return nil } -func (t *RegisterArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { - *t = RegisterArgumentsModel{} +func (t *RegisterArguments) UnmarshalCBOR(r io.Reader) (err error) { + *t = RegisterArguments{} cr := cbg.NewCborReader(r) @@ -511,7 +447,7 @@ func (t *RegisterArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { } if extra > cbg.MaxLength { - return fmt.Errorf("RegisterArgumentsModel: map struct too large (%d)", extra) + return fmt.Errorf("RegisterArguments: map struct too large (%d)", extra) } n := extra @@ -564,7 +500,7 @@ func (t *RegisterArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { return nil } -func (t *DeregisterArgumentsModel) MarshalCBOR(w io.Writer) error { +func (t *DeregisterArguments) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err @@ -594,8 +530,8 @@ func (t *DeregisterArgumentsModel) MarshalCBOR(w io.Writer) error { return nil } -func (t *DeregisterArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { - *t = DeregisterArgumentsModel{} +func (t *DeregisterArguments) UnmarshalCBOR(r io.Reader) (err error) { + *t = DeregisterArguments{} cr := cbg.NewCborReader(r) @@ -614,7 +550,7 @@ func (t *DeregisterArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { } if extra > cbg.MaxLength { - return fmt.Errorf("DeregisterArgumentsModel: map struct too large (%d)", extra) + return fmt.Errorf("DeregisterArguments: map struct too large (%d)", extra) } n := extra diff --git a/pkg/commands/admin/provider/datamodel/deregister.go b/pkg/commands/admin/provider/datamodel/deregister.go deleted file mode 100644 index b5e542f..0000000 --- a/pkg/commands/admin/provider/datamodel/deregister.go +++ /dev/null @@ -1,7 +0,0 @@ -package datamodel - -import "github.com/fil-forge/ucantone/did" - -type DeregisterArgumentsModel struct { - Provider did.DID `cborgen:"provider" dagjsongen:"provider"` -} diff --git a/pkg/commands/admin/provider/datamodel/gen/main.go b/pkg/commands/admin/provider/datamodel/gen/main.go deleted file mode 100644 index 141b39d..0000000 --- a/pkg/commands/admin/provider/datamodel/gen/main.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - jsg "github.com/alanshaw/dag-json-gen" - pdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/datamodel" - cbg "github.com/whyrusleeping/cbor-gen" -) - -func main() { - models := []any{ - pdm.ListArgumentsModel{}, - pdm.ProviderModel{}, - pdm.ListOKModel{}, - pdm.RegisterArgumentsModel{}, - pdm.DeregisterArgumentsModel{}, - } - if err := cbg.WriteMapEncodersToFile("../cbor_gen.go", "datamodel", models...); err != nil { - panic(err) - } - if err := jsg.WriteMapEncodersToFile("../json_gen.go", "datamodel", models...); err != nil { - panic(err) - } -} diff --git a/pkg/commands/admin/provider/datamodel/list.go b/pkg/commands/admin/provider/datamodel/list.go deleted file mode 100644 index 1227185..0000000 --- a/pkg/commands/admin/provider/datamodel/list.go +++ /dev/null @@ -1,16 +0,0 @@ -package datamodel - -import "github.com/fil-forge/ucantone/did" - -type ListArgumentsModel struct{} - -type ProviderModel struct { - Provider did.DID `cborgen:"provider" dagjsongen:"provider"` - Endpoint string `cborgen:"endpoint" dagjsongen:"endpoint"` - Weight int64 `cborgen:"weight" dagjsongen:"weight"` - ReplicationWeight int64 `cborgen:"replicationWeight" dagjsongen:"replicationWeight"` -} - -type ListOKModel struct { - Providers []ProviderModel `cborgen:"providers" dagjsongen:"providers"` -} diff --git a/pkg/commands/admin/provider/datamodel/register.go b/pkg/commands/admin/provider/datamodel/register.go deleted file mode 100644 index d867cf4..0000000 --- a/pkg/commands/admin/provider/datamodel/register.go +++ /dev/null @@ -1,8 +0,0 @@ -package datamodel - -import "github.com/fil-forge/ucantone/did" - -type RegisterArgumentsModel struct { - Provider did.DID `cborgen:"provider" dagjsongen:"provider"` - Endpoint string `cborgen:"endpoint" dagjsongen:"endpoint"` -} diff --git a/pkg/commands/admin/provider/deregister.go b/pkg/commands/admin/provider/deregister.go index 73c13c7..d84ad48 100644 --- a/pkg/commands/admin/provider/deregister.go +++ b/pkg/commands/admin/provider/deregister.go @@ -1,13 +1,9 @@ +//go:build !codegen + package provider -import ( - "github.com/fil-forge/libforge/commands" - pdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/datamodel" -) +import "github.com/fil-forge/libforge/commands" -type ( - DeregisterArguments = pdm.DeregisterArgumentsModel - DeregisterOK = commands.Unit -) +type DeregisterOK = commands.Unit var Deregister = commands.MustParse[*DeregisterArguments]("/admin/provider/deregister") diff --git a/pkg/commands/admin/provider/gen/main.go b/pkg/commands/admin/provider/gen/main.go new file mode 100644 index 0000000..7604f11 --- /dev/null +++ b/pkg/commands/admin/provider/gen/main.go @@ -0,0 +1,44 @@ +//go:generate go run -tags codegen . + +package main + +import ( + "os" + + jsg "github.com/alanshaw/dag-json-gen" + provider "github.com/fil-forge/sprue/pkg/commands/admin/provider" + cbg "github.com/whyrusleeping/cbor-gen" +) + +const buildTag = "//go:build !codegen\n\n" + +func tag(path string) { + data, err := os.ReadFile(path) + if err != nil { + panic(err) + } + if err := os.WriteFile(path, append([]byte(buildTag), data...), 0644); err != nil { + panic(err) + } +} + +func main() { + models := []any{ + provider.Provider{}, + provider.ListOK{}, + provider.RegisterArguments{}, + provider.DeregisterArguments{}, + } + const ( + cborFile = "../cbor_gen.go" + jsonFile = "../json_gen.go" + ) + if err := cbg.WriteMapEncodersToFile(cborFile, "provider", models...); err != nil { + panic(err) + } + if err := jsg.WriteMapEncodersToFile(jsonFile, "provider", models...); err != nil { + panic(err) + } + tag(cborFile) + tag(jsonFile) +} diff --git a/pkg/commands/admin/provider/datamodel/json_gen.go b/pkg/commands/admin/provider/json_gen.go similarity index 73% rename from pkg/commands/admin/provider/datamodel/json_gen.go rename to pkg/commands/admin/provider/json_gen.go index 8218fc9..4f46f70 100644 --- a/pkg/commands/admin/provider/datamodel/json_gen.go +++ b/pkg/commands/admin/provider/json_gen.go @@ -1,6 +1,8 @@ +//go:build !codegen + // Code generated by github.com/alanshaw/dag-json-gen. DO NOT EDIT. -package datamodel +package provider import ( "errors" @@ -18,76 +20,7 @@ var _ = math.E var _ = sort.Sort var _ = errors.Is -func (t *ListArgumentsModel) MarshalDagJSON(w io.Writer) error { - jw := jsg.NewDagJsonWriter(w) - if t == nil { - err := jw.WriteNull() - return err - } - if err := jw.WriteObjectOpen(); err != nil { - return err - } - if err := jw.WriteObjectClose(); err != nil { - return err - } - return nil -} -func (t *ListArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { - *t = ListArgumentsModel{} - - jr := jsg.NewDagJsonReader(r) - defer func() { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - }() - if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("reading object open for ListArgumentsModel: %w", err) - } - close, err := jr.PeekObjectClose() - if err != nil { - return fmt.Errorf("peeking object close for ListArgumentsModel: %w", err) - } - if close { - if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("reading object close for ListArgumentsModel: %w", err) - } - } else { - for i := uint64(0); i < 8192; i++ { - name, err := jr.ReadString(8192) - if err != nil { - if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("reading string for field ListArgumentsModel: string too large") - } - return fmt.Errorf("reading string for field ListArgumentsModel: %w", err) - } - if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("reading object colon for field ListArgumentsModel: %w", err) - } - switch name { - default: - // Field doesn't exist on this type, so ignore it - if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ignoring field %s for ListArgumentsModel: %w", name, err) - } - } - - close, err := jr.ReadObjectCloseOrComma() - if err != nil { - return fmt.Errorf("reading object close or comma for field ListArgumentsModel: %w", err) - } - if close { - break - } - if i == 8192-1 { - return fmt.Errorf("map too large for ListArgumentsModel") - } - } - } - - return nil -} -func (t *ProviderModel) MarshalDagJSON(w io.Writer) error { +func (t *Provider) MarshalDagJSON(w io.Writer) error { jw := jsg.NewDagJsonWriter(w) if t == nil { err := jw.WriteNull() @@ -184,8 +117,8 @@ func (t *ProviderModel) MarshalDagJSON(w io.Writer) error { } return nil } -func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { - *t = ProviderModel{} +func (t *Provider) UnmarshalDagJSON(r io.Reader) (err error) { + *t = Provider{} jr := jsg.NewDagJsonReader(r) defer func() { @@ -194,27 +127,27 @@ func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("reading object open for ProviderModel: %w", err) + return fmt.Errorf("reading object open for Provider: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("peeking object close for ProviderModel: %w", err) + return fmt.Errorf("peeking object close for Provider: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("reading object close for ProviderModel: %w", err) + return fmt.Errorf("reading object close for Provider: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("reading string for field ProviderModel: string too large") + return fmt.Errorf("reading string for field Provider: string too large") } - return fmt.Errorf("reading string for field ProviderModel: %w", err) + return fmt.Errorf("reading string for field Provider: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("reading object colon for field ProviderModel: %w", err) + return fmt.Errorf("reading object colon for field Provider: %w", err) } switch name { @@ -264,26 +197,26 @@ func (t *ProviderModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ignoring field %s for ProviderModel: %w", name, err) + return fmt.Errorf("ignoring field %s for Provider: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("reading object close or comma for field ProviderModel: %w", err) + return fmt.Errorf("reading object close or comma for field Provider: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("map too large for ProviderModel") + return fmt.Errorf("map too large for Provider") } } } return nil } -func (t *ListOKModel) MarshalDagJSON(w io.Writer) error { +func (t *ListOK) MarshalDagJSON(w io.Writer) error { jw := jsg.NewDagJsonWriter(w) if t == nil { err := jw.WriteNull() @@ -293,7 +226,7 @@ func (t *ListOKModel) MarshalDagJSON(w io.Writer) error { return err } - // t.Providers ([]datamodel.ProviderModel) (slice) + // t.Providers ([]provider.Provider) (slice) if len("providers") > 8192 { return fmt.Errorf("string in field \"providers\" was too long") } @@ -329,8 +262,8 @@ func (t *ListOKModel) MarshalDagJSON(w io.Writer) error { } return nil } -func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { - *t = ListOKModel{} +func (t *ListOK) UnmarshalDagJSON(r io.Reader) (err error) { + *t = ListOK{} jr := jsg.NewDagJsonReader(r) defer func() { @@ -339,31 +272,31 @@ func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("reading object open for ListOKModel: %w", err) + return fmt.Errorf("reading object open for ListOK: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("peeking object close for ListOKModel: %w", err) + return fmt.Errorf("peeking object close for ListOK: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("reading object close for ListOKModel: %w", err) + return fmt.Errorf("reading object close for ListOK: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("reading string for field ListOKModel: string too large") + return fmt.Errorf("reading string for field ListOK: string too large") } - return fmt.Errorf("reading string for field ListOKModel: %w", err) + return fmt.Errorf("reading string for field ListOK: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("reading object colon for field ListOKModel: %w", err) + return fmt.Errorf("reading object colon for field ListOK: %w", err) } switch name { - // t.Providers ([]datamodel.ProviderModel) (slice) + // t.Providers ([]provider.Provider) (slice) case "providers": { @@ -382,7 +315,7 @@ func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { } else { for i := 0; i < 8192; i++ { - item := make([]ProviderModel, 1) + item := make([]Provider, 1) if err := item[0].UnmarshalDagJSON(jr); err != nil { return fmt.Errorf("unmarshaling item[0]: %w", err) @@ -407,26 +340,26 @@ func (t *ListOKModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ignoring field %s for ListOKModel: %w", name, err) + return fmt.Errorf("ignoring field %s for ListOK: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("reading object close or comma for field ListOKModel: %w", err) + return fmt.Errorf("reading object close or comma for field ListOK: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("map too large for ListOKModel") + return fmt.Errorf("map too large for ListOK") } } } return nil } -func (t *RegisterArgumentsModel) MarshalDagJSON(w io.Writer) error { +func (t *RegisterArguments) MarshalDagJSON(w io.Writer) error { jw := jsg.NewDagJsonWriter(w) if t == nil { err := jw.WriteNull() @@ -479,8 +412,8 @@ func (t *RegisterArgumentsModel) MarshalDagJSON(w io.Writer) error { } return nil } -func (t *RegisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { - *t = RegisterArgumentsModel{} +func (t *RegisterArguments) UnmarshalDagJSON(r io.Reader) (err error) { + *t = RegisterArguments{} jr := jsg.NewDagJsonReader(r) defer func() { @@ -489,27 +422,27 @@ func (t *RegisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("reading object open for RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading object open for RegisterArguments: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("peeking object close for RegisterArgumentsModel: %w", err) + return fmt.Errorf("peeking object close for RegisterArguments: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("reading object close for RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading object close for RegisterArguments: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("reading string for field RegisterArgumentsModel: string too large") + return fmt.Errorf("reading string for field RegisterArguments: string too large") } - return fmt.Errorf("reading string for field RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading string for field RegisterArguments: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("reading object colon for field RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading object colon for field RegisterArguments: %w", err) } switch name { @@ -536,26 +469,26 @@ func (t *RegisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ignoring field %s for RegisterArgumentsModel: %w", name, err) + return fmt.Errorf("ignoring field %s for RegisterArguments: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("reading object close or comma for field RegisterArgumentsModel: %w", err) + return fmt.Errorf("reading object close or comma for field RegisterArguments: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("map too large for RegisterArgumentsModel") + return fmt.Errorf("map too large for RegisterArguments") } } } return nil } -func (t *DeregisterArgumentsModel) MarshalDagJSON(w io.Writer) error { +func (t *DeregisterArguments) MarshalDagJSON(w io.Writer) error { jw := jsg.NewDagJsonWriter(w) if t == nil { err := jw.WriteNull() @@ -583,8 +516,8 @@ func (t *DeregisterArgumentsModel) MarshalDagJSON(w io.Writer) error { } return nil } -func (t *DeregisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { - *t = DeregisterArgumentsModel{} +func (t *DeregisterArguments) UnmarshalDagJSON(r io.Reader) (err error) { + *t = DeregisterArguments{} jr := jsg.NewDagJsonReader(r) defer func() { @@ -593,27 +526,27 @@ func (t *DeregisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("reading object open for DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading object open for DeregisterArguments: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("peeking object close for DeregisterArgumentsModel: %w", err) + return fmt.Errorf("peeking object close for DeregisterArguments: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("reading object close for DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading object close for DeregisterArguments: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("reading string for field DeregisterArgumentsModel: string too large") + return fmt.Errorf("reading string for field DeregisterArguments: string too large") } - return fmt.Errorf("reading string for field DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading string for field DeregisterArguments: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("reading object colon for field DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading object colon for field DeregisterArguments: %w", err) } switch name { @@ -627,19 +560,19 @@ func (t *DeregisterArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ignoring field %s for DeregisterArgumentsModel: %w", name, err) + return fmt.Errorf("ignoring field %s for DeregisterArguments: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("reading object close or comma for field DeregisterArgumentsModel: %w", err) + return fmt.Errorf("reading object close or comma for field DeregisterArguments: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("map too large for DeregisterArgumentsModel") + return fmt.Errorf("map too large for DeregisterArguments") } } } diff --git a/pkg/commands/admin/provider/list.go b/pkg/commands/admin/provider/list.go index 18a0092..801472c 100644 --- a/pkg/commands/admin/provider/list.go +++ b/pkg/commands/admin/provider/list.go @@ -1,14 +1,9 @@ +//go:build !codegen + package provider -import ( - "github.com/fil-forge/libforge/commands" - pdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/datamodel" -) +import "github.com/fil-forge/libforge/commands" -type ( - ListArguments = pdm.ListArgumentsModel - ListOK = pdm.ListOKModel - Provider = pdm.ProviderModel -) +type ListArguments = commands.Unit var List = commands.MustParse[*ListArguments]("/admin/provider/list") diff --git a/pkg/commands/admin/provider/register.go b/pkg/commands/admin/provider/register.go index 0da37db..a2bfdbf 100644 --- a/pkg/commands/admin/provider/register.go +++ b/pkg/commands/admin/provider/register.go @@ -1,14 +1,9 @@ +//go:build !codegen + package provider -import ( - "github.com/fil-forge/libforge/commands" - cdm "github.com/fil-forge/libforge/commands" - pdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/datamodel" -) +import "github.com/fil-forge/libforge/commands" -type ( - RegisterArguments = pdm.RegisterArgumentsModel - RegisterOK = cdm.Unit -) +type RegisterOK = commands.Unit var Register = commands.MustParse[*RegisterArguments]("/admin/provider/register") diff --git a/pkg/commands/admin/provider/types.go b/pkg/commands/admin/provider/types.go new file mode 100644 index 0000000..b4754b3 --- /dev/null +++ b/pkg/commands/admin/provider/types.go @@ -0,0 +1,23 @@ +package provider + +import "github.com/fil-forge/ucantone/did" + +type RegisterArguments struct { + Provider did.DID `cborgen:"provider" dagjsongen:"provider"` + Endpoint string `cborgen:"endpoint" dagjsongen:"endpoint"` +} + +type Provider struct { + Provider did.DID `cborgen:"provider" dagjsongen:"provider"` + Endpoint string `cborgen:"endpoint" dagjsongen:"endpoint"` + Weight int64 `cborgen:"weight" dagjsongen:"weight"` + ReplicationWeight int64 `cborgen:"replicationWeight" dagjsongen:"replicationWeight"` +} + +type ListOK struct { + Providers []Provider `cborgen:"providers" dagjsongen:"providers"` +} + +type DeregisterArguments struct { + Provider did.DID `cborgen:"provider" dagjsongen:"provider"` +} diff --git a/pkg/commands/admin/provider/weight/datamodel/cbor_gen.go b/pkg/commands/admin/provider/weight/cbor_gen.go similarity index 94% rename from pkg/commands/admin/provider/weight/datamodel/cbor_gen.go rename to pkg/commands/admin/provider/weight/cbor_gen.go index 546e152..6f2a88d 100644 --- a/pkg/commands/admin/provider/weight/datamodel/cbor_gen.go +++ b/pkg/commands/admin/provider/weight/cbor_gen.go @@ -1,6 +1,8 @@ +//go:build !codegen + // Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. -package datamodel +package weight import ( "fmt" @@ -18,7 +20,7 @@ var _ = cid.Undef var _ = math.E var _ = sort.Sort -func (t *SetArgumentsModel) MarshalCBOR(w io.Writer) error { +func (t *SetArguments) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err @@ -93,8 +95,8 @@ func (t *SetArgumentsModel) MarshalCBOR(w io.Writer) error { return nil } -func (t *SetArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { - *t = SetArgumentsModel{} +func (t *SetArguments) UnmarshalCBOR(r io.Reader) (err error) { + *t = SetArguments{} cr := cbg.NewCborReader(r) @@ -113,7 +115,7 @@ func (t *SetArgumentsModel) UnmarshalCBOR(r io.Reader) (err error) { } if extra > cbg.MaxLength { - return fmt.Errorf("SetArgumentsModel: map struct too large (%d)", extra) + return fmt.Errorf("SetArguments: map struct too large (%d)", extra) } n := extra diff --git a/pkg/commands/admin/provider/weight/datamodel/gen/main.go b/pkg/commands/admin/provider/weight/datamodel/gen/main.go deleted file mode 100644 index 22f1406..0000000 --- a/pkg/commands/admin/provider/weight/datamodel/gen/main.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - jsg "github.com/alanshaw/dag-json-gen" - wdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight/datamodel" - cbg "github.com/whyrusleeping/cbor-gen" -) - -func main() { - models := []any{ - wdm.SetArgumentsModel{}, - } - if err := cbg.WriteMapEncodersToFile("../cbor_gen.go", "datamodel", models...); err != nil { - panic(err) - } - if err := jsg.WriteMapEncodersToFile("../json_gen.go", "datamodel", models...); err != nil { - panic(err) - } -} diff --git a/pkg/commands/admin/provider/weight/gen/main.go b/pkg/commands/admin/provider/weight/gen/main.go new file mode 100644 index 0000000..2e42a67 --- /dev/null +++ b/pkg/commands/admin/provider/weight/gen/main.go @@ -0,0 +1,41 @@ +//go:generate go run -tags codegen . + +package main + +import ( + "os" + + jsg "github.com/alanshaw/dag-json-gen" + "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight" + cbg "github.com/whyrusleeping/cbor-gen" +) + +const buildTag = "//go:build !codegen\n\n" + +func tag(path string) { + data, err := os.ReadFile(path) + if err != nil { + panic(err) + } + if err := os.WriteFile(path, append([]byte(buildTag), data...), 0644); err != nil { + panic(err) + } +} + +func main() { + models := []any{ + weight.SetArguments{}, + } + const ( + cborFile = "../cbor_gen.go" + jsonFile = "../json_gen.go" + ) + if err := cbg.WriteMapEncodersToFile(cborFile, "weight", models...); err != nil { + panic(err) + } + if err := jsg.WriteMapEncodersToFile(jsonFile, "weight", models...); err != nil { + panic(err) + } + tag(cborFile) + tag(jsonFile) +} diff --git a/pkg/commands/admin/provider/weight/datamodel/json_gen.go b/pkg/commands/admin/provider/weight/json_gen.go similarity index 82% rename from pkg/commands/admin/provider/weight/datamodel/json_gen.go rename to pkg/commands/admin/provider/weight/json_gen.go index a8f1f81..c4bf3f2 100644 --- a/pkg/commands/admin/provider/weight/datamodel/json_gen.go +++ b/pkg/commands/admin/provider/weight/json_gen.go @@ -1,6 +1,8 @@ +//go:build !codegen + // Code generated by github.com/alanshaw/dag-json-gen. DO NOT EDIT. -package datamodel +package weight import ( "errors" @@ -18,7 +20,7 @@ var _ = math.E var _ = sort.Sort var _ = errors.Is -func (t *SetArgumentsModel) MarshalDagJSON(w io.Writer) error { +func (t *SetArguments) MarshalDagJSON(w io.Writer) error { jw := jsg.NewDagJsonWriter(w) if t == nil { err := jw.WriteNull() @@ -92,8 +94,8 @@ func (t *SetArgumentsModel) MarshalDagJSON(w io.Writer) error { } return nil } -func (t *SetArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { - *t = SetArgumentsModel{} +func (t *SetArguments) UnmarshalDagJSON(r io.Reader) (err error) { + *t = SetArguments{} jr := jsg.NewDagJsonReader(r) defer func() { @@ -102,27 +104,27 @@ func (t *SetArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { } }() if err := jr.ReadObjectOpen(); err != nil { - return fmt.Errorf("reading object open for SetArgumentsModel: %w", err) + return fmt.Errorf("reading object open for SetArguments: %w", err) } close, err := jr.PeekObjectClose() if err != nil { - return fmt.Errorf("peeking object close for SetArgumentsModel: %w", err) + return fmt.Errorf("peeking object close for SetArguments: %w", err) } if close { if err := jr.ReadObjectClose(); err != nil { - return fmt.Errorf("reading object close for SetArgumentsModel: %w", err) + return fmt.Errorf("reading object close for SetArguments: %w", err) } } else { for i := uint64(0); i < 8192; i++ { name, err := jr.ReadString(8192) if err != nil { if errors.Is(err, jsg.ErrLimitExceeded) { - return fmt.Errorf("reading string for field SetArgumentsModel: string too large") + return fmt.Errorf("reading string for field SetArguments: string too large") } - return fmt.Errorf("reading string for field SetArgumentsModel: %w", err) + return fmt.Errorf("reading string for field SetArguments: %w", err) } if err := jr.ReadObjectColon(); err != nil { - return fmt.Errorf("reading object colon for field SetArgumentsModel: %w", err) + return fmt.Errorf("reading object colon for field SetArguments: %w", err) } switch name { @@ -159,19 +161,19 @@ func (t *SetArgumentsModel) UnmarshalDagJSON(r io.Reader) (err error) { default: // Field doesn't exist on this type, so ignore it if err := jr.DiscardType(); err != nil { - return fmt.Errorf("ignoring field %s for SetArgumentsModel: %w", name, err) + return fmt.Errorf("ignoring field %s for SetArguments: %w", name, err) } } close, err := jr.ReadObjectCloseOrComma() if err != nil { - return fmt.Errorf("reading object close or comma for field SetArgumentsModel: %w", err) + return fmt.Errorf("reading object close or comma for field SetArguments: %w", err) } if close { break } if i == 8192-1 { - return fmt.Errorf("map too large for SetArgumentsModel") + return fmt.Errorf("map too large for SetArguments") } } } diff --git a/pkg/commands/admin/provider/weight/set.go b/pkg/commands/admin/provider/weight/set.go index 201b641..e6d23c2 100644 --- a/pkg/commands/admin/provider/weight/set.go +++ b/pkg/commands/admin/provider/weight/set.go @@ -1,13 +1,9 @@ +//go:build !codegen + package weight -import ( - "github.com/fil-forge/libforge/commands" - wdm "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight/datamodel" -) +import "github.com/fil-forge/libforge/commands" -type ( - SetArguments = wdm.SetArgumentsModel - SetOK = commands.Unit -) +type SetOK = commands.Unit var Set = commands.MustParse[*SetArguments]("/provider/weight/set") diff --git a/pkg/commands/admin/provider/weight/datamodel/set.go b/pkg/commands/admin/provider/weight/types.go similarity index 84% rename from pkg/commands/admin/provider/weight/datamodel/set.go rename to pkg/commands/admin/provider/weight/types.go index 5658bf9..2ab9e13 100644 --- a/pkg/commands/admin/provider/weight/datamodel/set.go +++ b/pkg/commands/admin/provider/weight/types.go @@ -1,8 +1,8 @@ -package datamodel +package weight import "github.com/fil-forge/ucantone/did" -type SetArgumentsModel struct { +type SetArguments struct { Provider did.DID `cborgen:"provider" dagjsongen:"provider"` Weight int64 `cborgen:"weight" dagjsongen:"weight"` ReplicationWeight int64 `cborgen:"replicationWeight" dagjsongen:"replicationWeight"` From 14d8638a4b3687c6f4b1de5c68feafe4a6561358 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 19 May 2026 17:51:23 +0100 Subject: [PATCH 16/23] refactor: consolidate migrations --- internal/migrations/sql/00001_init.sql | 162 +++++++++++++++++- internal/migrations/sql/00002_customer.sql | 19 -- .../migrations/sql/00003_storage_provider.sql | 16 -- internal/migrations/sql/00004_consumer.sql | 20 --- .../migrations/sql/00005_subscription.sql | 19 -- internal/migrations/sql/00006_metrics.sql | 20 --- internal/migrations/sql/00007_space_diff.sql | 18 -- internal/migrations/sql/00008_replica.sql | 18 -- internal/migrations/sql/00009_revocation.sql | 15 -- internal/migrations/sql/00010_delegation.sql | 19 -- internal/migrations/sql/00011_upload.sql | 28 --- .../migrations/sql/00012_blob_registry.sql | 18 -- internal/migrations/sql/00013_agent_index.sql | 22 --- 13 files changed, 158 insertions(+), 236 deletions(-) delete mode 100644 internal/migrations/sql/00002_customer.sql delete mode 100644 internal/migrations/sql/00003_storage_provider.sql delete mode 100644 internal/migrations/sql/00004_consumer.sql delete mode 100644 internal/migrations/sql/00005_subscription.sql delete mode 100644 internal/migrations/sql/00006_metrics.sql delete mode 100644 internal/migrations/sql/00007_space_diff.sql delete mode 100644 internal/migrations/sql/00008_replica.sql delete mode 100644 internal/migrations/sql/00009_revocation.sql delete mode 100644 internal/migrations/sql/00010_delegation.sql delete mode 100644 internal/migrations/sql/00011_upload.sql delete mode 100644 internal/migrations/sql/00012_blob_registry.sql delete mode 100644 internal/migrations/sql/00013_agent_index.sql diff --git a/internal/migrations/sql/00001_init.sql b/internal/migrations/sql/00001_init.sql index 0388775..aee9249 100644 --- a/internal/migrations/sql/00001_init.sql +++ b/internal/migrations/sql/00001_init.sql @@ -1,11 +1,165 @@ -- +goose Up -- +goose StatementBegin --- Sprue Postgres schema — tables are added by per-store migrations. --- This file exists so the embedded FS is non-empty and goose has a baseline. -SELECT 1; +CREATE TABLE customer ( + customer TEXT PRIMARY KEY, + account TEXT, + product TEXT NOT NULL, + details JSONB, + reserved_capacity BIGINT, + inserted_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ +); + +CREATE INDEX customer_account_idx ON customer (account) WHERE account IS NOT NULL; + +CREATE TABLE storage_provider ( + provider TEXT PRIMARY KEY, + endpoint TEXT NOT NULL, + weight INTEGER NOT NULL, + replication_weight INTEGER, + inserted_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL +); + +CREATE TABLE consumer ( + subscription TEXT NOT NULL, + provider TEXT NOT NULL, + consumer TEXT NOT NULL, + customer TEXT NOT NULL, + cause TEXT NOT NULL, + inserted_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (subscription, provider) +); + +CREATE INDEX consumer_space_idx ON consumer (consumer, provider, subscription); +CREATE INDEX consumer_customer_idx ON consumer (customer, subscription, provider); + +CREATE TABLE subscription ( + subscription TEXT NOT NULL, + provider TEXT NOT NULL, + customer TEXT NOT NULL, + cause TEXT NOT NULL, + inserted_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (subscription, provider) +); + +CREATE INDEX subscription_customer_provider_idx + ON subscription (customer, provider, subscription); + +CREATE TABLE admin_metrics ( + name TEXT PRIMARY KEY, + value BIGINT NOT NULL DEFAULT 0 +); + +CREATE TABLE space_metrics ( + space TEXT NOT NULL, + name TEXT NOT NULL, + value BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (space, name) +); + +CREATE TABLE space_diff ( + provider TEXT NOT NULL, + space TEXT NOT NULL, + receipt_at TIMESTAMPTZ NOT NULL, + cause TEXT NOT NULL, + subscription TEXT NOT NULL, + delta BIGINT NOT NULL, + inserted_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (provider, space, receipt_at, cause) +); + +CREATE TABLE replica ( + space TEXT NOT NULL, + digest TEXT NOT NULL, + provider TEXT NOT NULL, + status TEXT NOT NULL, + cause TEXT NOT NULL, + inserted_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (space, digest, provider) +); + +CREATE TABLE revocation ( + revoke TEXT NOT NULL, + scope TEXT NOT NULL, + cause TEXT NOT NULL, + inserted_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (revoke, scope) +); + +CREATE TABLE delegation ( + link TEXT PRIMARY KEY, + audience TEXT NOT NULL, + issuer TEXT NOT NULL, + cause TEXT, + expiration BIGINT, + inserted_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL +); + +CREATE INDEX delegation_audience_idx ON delegation (audience, link); + +CREATE TABLE upload ( + space TEXT NOT NULL, + root TEXT NOT NULL, + index TEXT, + cause TEXT NOT NULL, + inserted_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (space, root) +); + +CREATE INDEX upload_root_idx ON upload (root); + +CREATE TABLE upload_shard ( + space TEXT NOT NULL, + root TEXT NOT NULL, + shard TEXT NOT NULL, + PRIMARY KEY (space, root, shard), + FOREIGN KEY (space, root) REFERENCES upload (space, root) ON DELETE CASCADE +); + +CREATE TABLE blob_registry ( + space TEXT NOT NULL, + digest TEXT NOT NULL, + size BIGINT NOT NULL, + cause TEXT NOT NULL, + inserted_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (space, digest) +); + +CREATE INDEX blob_registry_digest_idx ON blob_registry (digest, space); + +-- agent_index stores a mapping from tasks to invocations/receipts and the +-- agent messages they were found in. +-- "kind" is the token type either "in" (an invocation) or "out" (a receipt). +-- "task" is CID of the task that was invoked (invocation) or ran (receipt). +-- "token" is the CID of the invocation/receipt. +-- "message" is the CID of the agent message the token can be found within. +CREATE TABLE agent_index ( + task TEXT NOT NULL, + kind TEXT NOT NULL, + token TEXT NOT NULL, + message TEXT NOT NULL, + PRIMARY KEY (task, kind) +); -- +goose StatementEnd -- +goose Down -- +goose StatementBegin -SELECT 1; +DROP TABLE IF EXISTS agent_index; +DROP TABLE IF EXISTS blob_registry; +DROP TABLE IF EXISTS upload_shard; +DROP TABLE IF EXISTS upload; +DROP TABLE IF EXISTS delegation; +DROP TABLE IF EXISTS revocation; +DROP TABLE IF EXISTS replica; +DROP TABLE IF EXISTS space_diff; +DROP TABLE IF EXISTS space_metrics; +DROP TABLE IF EXISTS admin_metrics; +DROP TABLE IF EXISTS subscription; +DROP TABLE IF EXISTS consumer; +DROP TABLE IF EXISTS storage_provider; +DROP TABLE IF EXISTS customer; -- +goose StatementEnd diff --git a/internal/migrations/sql/00002_customer.sql b/internal/migrations/sql/00002_customer.sql deleted file mode 100644 index 405aa49..0000000 --- a/internal/migrations/sql/00002_customer.sql +++ /dev/null @@ -1,19 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE customer ( - customer TEXT PRIMARY KEY, - account TEXT, - product TEXT NOT NULL, - details JSONB, - reserved_capacity BIGINT, - inserted_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ -); - -CREATE INDEX customer_account_idx ON customer (account) WHERE account IS NOT NULL; --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS customer; --- +goose StatementEnd diff --git a/internal/migrations/sql/00003_storage_provider.sql b/internal/migrations/sql/00003_storage_provider.sql deleted file mode 100644 index eb7cc71..0000000 --- a/internal/migrations/sql/00003_storage_provider.sql +++ /dev/null @@ -1,16 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE storage_provider ( - provider TEXT PRIMARY KEY, - endpoint TEXT NOT NULL, - weight INTEGER NOT NULL, - replication_weight INTEGER, - inserted_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS storage_provider; --- +goose StatementEnd diff --git a/internal/migrations/sql/00004_consumer.sql b/internal/migrations/sql/00004_consumer.sql deleted file mode 100644 index 40597b0..0000000 --- a/internal/migrations/sql/00004_consumer.sql +++ /dev/null @@ -1,20 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE consumer ( - subscription TEXT NOT NULL, - provider TEXT NOT NULL, - consumer TEXT NOT NULL, - customer TEXT NOT NULL, - cause TEXT NOT NULL, - inserted_at TIMESTAMPTZ NOT NULL, - PRIMARY KEY (subscription, provider) -); - -CREATE INDEX consumer_space_idx ON consumer (consumer, provider, subscription); -CREATE INDEX consumer_customer_idx ON consumer (customer, subscription, provider); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS consumer; --- +goose StatementEnd diff --git a/internal/migrations/sql/00005_subscription.sql b/internal/migrations/sql/00005_subscription.sql deleted file mode 100644 index 5dd6761..0000000 --- a/internal/migrations/sql/00005_subscription.sql +++ /dev/null @@ -1,19 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE subscription ( - subscription TEXT NOT NULL, - provider TEXT NOT NULL, - customer TEXT NOT NULL, - cause TEXT NOT NULL, - inserted_at TIMESTAMPTZ NOT NULL, - PRIMARY KEY (subscription, provider) -); - -CREATE INDEX subscription_customer_provider_idx - ON subscription (customer, provider, subscription); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS subscription; --- +goose StatementEnd diff --git a/internal/migrations/sql/00006_metrics.sql b/internal/migrations/sql/00006_metrics.sql deleted file mode 100644 index c96fc58..0000000 --- a/internal/migrations/sql/00006_metrics.sql +++ /dev/null @@ -1,20 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE admin_metrics ( - name TEXT PRIMARY KEY, - value BIGINT NOT NULL DEFAULT 0 -); - -CREATE TABLE space_metrics ( - space TEXT NOT NULL, - name TEXT NOT NULL, - value BIGINT NOT NULL DEFAULT 0, - PRIMARY KEY (space, name) -); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS space_metrics; -DROP TABLE IF EXISTS admin_metrics; --- +goose StatementEnd diff --git a/internal/migrations/sql/00007_space_diff.sql b/internal/migrations/sql/00007_space_diff.sql deleted file mode 100644 index 2ed88df..0000000 --- a/internal/migrations/sql/00007_space_diff.sql +++ /dev/null @@ -1,18 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE space_diff ( - provider TEXT NOT NULL, - space TEXT NOT NULL, - receipt_at TIMESTAMPTZ NOT NULL, - cause TEXT NOT NULL, - subscription TEXT NOT NULL, - delta BIGINT NOT NULL, - inserted_at TIMESTAMPTZ NOT NULL, - PRIMARY KEY (provider, space, receipt_at, cause) -); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS space_diff; --- +goose StatementEnd diff --git a/internal/migrations/sql/00008_replica.sql b/internal/migrations/sql/00008_replica.sql deleted file mode 100644 index 5cc8293..0000000 --- a/internal/migrations/sql/00008_replica.sql +++ /dev/null @@ -1,18 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE replica ( - space TEXT NOT NULL, - digest TEXT NOT NULL, - provider TEXT NOT NULL, - status TEXT NOT NULL, - cause TEXT NOT NULL, - inserted_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL, - PRIMARY KEY (space, digest, provider) -); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS replica; --- +goose StatementEnd diff --git a/internal/migrations/sql/00009_revocation.sql b/internal/migrations/sql/00009_revocation.sql deleted file mode 100644 index a48faa6..0000000 --- a/internal/migrations/sql/00009_revocation.sql +++ /dev/null @@ -1,15 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE revocation ( - revoke TEXT NOT NULL, - scope TEXT NOT NULL, - cause TEXT NOT NULL, - inserted_at TIMESTAMPTZ NOT NULL, - PRIMARY KEY (revoke, scope) -); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS revocation; --- +goose StatementEnd diff --git a/internal/migrations/sql/00010_delegation.sql b/internal/migrations/sql/00010_delegation.sql deleted file mode 100644 index 21a3ecc..0000000 --- a/internal/migrations/sql/00010_delegation.sql +++ /dev/null @@ -1,19 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE delegation ( - link TEXT PRIMARY KEY, - audience TEXT NOT NULL, - issuer TEXT NOT NULL, - cause TEXT, - expiration BIGINT, - inserted_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE INDEX delegation_audience_idx ON delegation (audience, link); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS delegation; --- +goose StatementEnd diff --git a/internal/migrations/sql/00011_upload.sql b/internal/migrations/sql/00011_upload.sql deleted file mode 100644 index ab5f0b9..0000000 --- a/internal/migrations/sql/00011_upload.sql +++ /dev/null @@ -1,28 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE upload ( - space TEXT NOT NULL, - root TEXT NOT NULL, - index TEXT, - cause TEXT NOT NULL, - inserted_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL, - PRIMARY KEY (space, root) -); - -CREATE INDEX upload_root_idx ON upload (root); - -CREATE TABLE upload_shard ( - space TEXT NOT NULL, - root TEXT NOT NULL, - shard TEXT NOT NULL, - PRIMARY KEY (space, root, shard), - FOREIGN KEY (space, root) REFERENCES upload (space, root) ON DELETE CASCADE -); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS upload_shard; -DROP TABLE IF EXISTS upload; --- +goose StatementEnd diff --git a/internal/migrations/sql/00012_blob_registry.sql b/internal/migrations/sql/00012_blob_registry.sql deleted file mode 100644 index c2992f5..0000000 --- a/internal/migrations/sql/00012_blob_registry.sql +++ /dev/null @@ -1,18 +0,0 @@ --- +goose Up --- +goose StatementBegin -CREATE TABLE blob_registry ( - space TEXT NOT NULL, - digest TEXT NOT NULL, - size BIGINT NOT NULL, - cause TEXT NOT NULL, - inserted_at TIMESTAMPTZ NOT NULL, - PRIMARY KEY (space, digest) -); - -CREATE INDEX blob_registry_digest_idx ON blob_registry (digest, space); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS blob_registry; --- +goose StatementEnd diff --git a/internal/migrations/sql/00013_agent_index.sql b/internal/migrations/sql/00013_agent_index.sql deleted file mode 100644 index 4d3c66c..0000000 --- a/internal/migrations/sql/00013_agent_index.sql +++ /dev/null @@ -1,22 +0,0 @@ --- +goose Up --- +goose StatementBegin --- agent_index stores a mapping from tasks to invocations/receipts and the --- agent messages they were found in. --- "kind" is the token type either "in" (an invocation) or "out" (a receipt). --- "task" is CID of the task that was invoked (invocation) or ran (receipt). --- "token" is the CID of the invocation/receipt. --- "message" is the CID of the agent message the token can be found within. - -CREATE TABLE agent_index ( - task TEXT NOT NULL, - kind TEXT NOT NULL, - token TEXT NOT NULL, - message TEXT NOT NULL, - PRIMARY KEY (task, kind) -); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP TABLE IF EXISTS agent_index; --- +goose StatementEnd From 435007eb3b35b695f0ebc6d1cd07cc62c79c853d Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 19 May 2026 17:55:06 +0100 Subject: [PATCH 17/23] fix: sometimes I forget to save the files --- pkg/service/handlers/access_delegate.go | 2 +- pkg/service/handlers/admin_provider_list.go | 5 +++-- pkg/service/handlers/admin_provider_register.go | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/service/handlers/access_delegate.go b/pkg/service/handlers/access_delegate.go index 670536f..cbd5b13 100644 --- a/pkg/service/handlers/access_delegate.go +++ b/pkg/service/handlers/access_delegate.go @@ -14,7 +14,7 @@ import ( ) func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", access.Delegate)) + log := logger.With(zap.String("handler", string(access.Delegate))) return Handler{ Command: ucan.Command(access.Delegate), Handler: bindexec.NewHandler(func( diff --git a/pkg/service/handlers/admin_provider_list.go b/pkg/service/handlers/admin_provider_list.go index a3548f1..1bb2c8a 100644 --- a/pkg/service/handlers/admin_provider_list.go +++ b/pkg/service/handlers/admin_provider_list.go @@ -11,12 +11,13 @@ import ( storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" ) func NewAdminProviderListHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", provider.ListCommand)) + log := logger.With(zap.String("handler", string(provider.List))) return Handler{ - Capability: provider.List, + Command: ucan.Command(provider.List), Handler: bindexec.NewHandler(func( req *bindexec.Request[*provider.ListArguments], res *bindexec.Response[*provider.ListOK], diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index 70c66b0..460ef07 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -8,6 +8,7 @@ import ( storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) @@ -17,9 +18,9 @@ var ( ) func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", provider.RegisterCommand)) + log := logger.With(zap.String("handler", string(provider.Register))) return Handler{ - Capability: provider.Register, + Command: ucan.Command(provider.Register), Handler: bindexec.NewHandler(func( req *bindexec.Request[*provider.RegisterArguments], res *bindexec.Response[*provider.RegisterOK], From f62a8e79272a2d953822a67affc3f0e99fafdeaf Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 19 May 2026 21:08:43 +0100 Subject: [PATCH 18/23] chore: upgrade ucantone and libforge --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/indexerclient/client.go | 2 +- pkg/lib/ucan_server/email_auth.go | 2 +- pkg/lib/ucan_server/validation.go | 2 +- pkg/lib/ucan_server/validation_test.go | 3 +-- pkg/piriclient/client.go | 6 +++--- pkg/service/handlers/access_claim.go | 4 ++-- pkg/service/handlers/access_confirm.go | 4 ++-- pkg/service/handlers/access_delegate.go | 4 ++-- pkg/service/handlers/access_request.go | 4 ++-- pkg/service/handlers/admin_provider_deregister.go | 5 ++--- pkg/service/handlers/admin_provider_list.go | 5 ++--- pkg/service/handlers/admin_provider_register.go | 5 ++--- pkg/service/handlers/admin_provider_weight_set.go | 5 ++--- pkg/service/handlers/blob_add.go | 4 ++-- pkg/service/handlers/blob_add_test.go | 4 ++-- pkg/service/handlers/blob_list.go | 5 ++--- pkg/service/handlers/index_add.go | 5 ++--- pkg/service/handlers/index_add_test.go | 2 +- pkg/service/handlers/provider_add.go | 5 ++--- pkg/service/handlers/space_info.go | 5 ++--- pkg/service/handlers/ucan_conclude.go | 4 ++-- pkg/service/handlers/ucan_conclude_http_put.go | 6 +++--- pkg/service/handlers/upload_add.go | 5 ++--- pkg/service/handlers/upload_list.go | 5 ++--- pkg/service/handlers/upload_shard_list.go | 5 ++--- pkg/store/metrics/metrics.go | 12 ++++++------ 28 files changed, 59 insertions(+), 71 deletions(-) diff --git a/go.mod b/go.mod index b2707a4..0026ba9 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260519133220-4d5747ca0927 - github.com/fil-forge/ucantone v0.0.0-20260519122919-0ee8deb17aa4 + github.com/fil-forge/libforge v0.0.0-20260519193938-fc442aef7e5c + github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10 github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 github.com/jackc/pgx/v5 v5.8.0 diff --git a/go.sum b/go.sum index 7eb1f97..8d3e17a 100644 --- a/go.sum +++ b/go.sum @@ -92,10 +92,10 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fil-forge/libforge v0.0.0-20260519133220-4d5747ca0927 h1:AqqpZ7jsb26N+1Fmq2URToRjOBha26NmYptqCIO1qJY= -github.com/fil-forge/libforge v0.0.0-20260519133220-4d5747ca0927/go.mod h1:FtlBeLxBao1mIU2ILE5loXr+KSrCFAoXoQHOPJBZGsE= -github.com/fil-forge/ucantone v0.0.0-20260519122919-0ee8deb17aa4 h1:Bxmm9l6RE+68luYvekbnmTe7+1epRPZHnSagcMtdvyY= -github.com/fil-forge/ucantone v0.0.0-20260519122919-0ee8deb17aa4/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= +github.com/fil-forge/libforge v0.0.0-20260519193938-fc442aef7e5c h1:dzAiOAYDTJDnrPFE7KxVqCn6mVK1grXiaiMuPf5z/Mo= +github.com/fil-forge/libforge v0.0.0-20260519193938-fc442aef7e5c/go.mod h1:/sKL0TfiBzknJTuL1vSZ5IHALmWndwxOexU3jtlNVSE= +github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10 h1:ApgWAIpXjCYjZw/yDxLn8IA9WrH/ENPRWCWPT/MoCvU= +github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/pkg/indexerclient/client.go b/pkg/indexerclient/client.go index f31a47f..1054f5c 100644 --- a/pkg/indexerclient/client.go +++ b/pkg/indexerclient/client.go @@ -48,7 +48,7 @@ func New(endpoint *url.URL, indexerDID did.DID, signer ucan.Signer, logger *zap. // The proofStore parameter is used to build the delegation chain authorizing // the upload service to retrieve the index blob via `/content/retrieve` command. func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid.Cid, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Receipt, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), ucan.Command(contentcaps.Retrieve), space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), contentcaps.Retrieve.Command, space) if err != nil { return nil, fmt.Errorf("building proof chain: %w", err) } diff --git a/pkg/lib/ucan_server/email_auth.go b/pkg/lib/ucan_server/email_auth.go index d69bedc..ad95ac3 100644 --- a/pkg/lib/ucan_server/email_auth.go +++ b/pkg/lib/ucan_server/email_auth.go @@ -33,7 +33,7 @@ func ExecBase64urlAccessConfirm(ctx context.Context, executor execution.Executor confirmation := inCt.Invocations()[0] // check this is a confirmation invocation - if confirmation.Command() != ucan.Command(access.Confirm) { + if confirmation.Command() != access.Confirm.Command { return AccessConfirmResult{}, fmt.Errorf("unexpected command in invocation, expected %s but got %s", access.Confirm, confirmation.Command()) } diff --git a/pkg/lib/ucan_server/validation.go b/pkg/lib/ucan_server/validation.go index 2b42880..f7d96c9 100644 --- a/pkg/lib/ucan_server/validation.go +++ b/pkg/lib/ucan_server/validation.go @@ -36,7 +36,7 @@ func NewAttestationVerifier(authority principal.Verifier) validator.NonStandardS return fmt.Errorf("token is not a delegation") } for _, inv := range meta.Invocations() { - if inv.Command() != ucan.Command(attest.Proof) { + if inv.Command() != attest.Proof.Command { continue } // only trust attestations we issued diff --git a/pkg/lib/ucan_server/validation_test.go b/pkg/lib/ucan_server/validation_test.go index 29f6232..af5b728 100644 --- a/pkg/lib/ucan_server/validation_test.go +++ b/pkg/lib/ucan_server/validation_test.go @@ -9,7 +9,6 @@ import ( "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal/absentee" "github.com/fil-forge/ucantone/principal/ed25519" - "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" @@ -87,7 +86,7 @@ func TestNewAttestationVerifier(t *testing.T) { inv, err := invocation.Invoke( authority, authority.DID(), - ucan.Command(attest.Proof), + attest.Proof.Command, datamodel.Map{"unrelated": "foo"}, ) require.NoError(t, err) diff --git a/pkg/piriclient/client.go b/pkg/piriclient/client.go index b9194cc..c712967 100644 --- a/pkg/piriclient/client.go +++ b/pkg/piriclient/client.go @@ -94,7 +94,7 @@ func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore // AllocateInvocation returns the invocation for the allocate request (for use in effects). func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), ucan.Command(blobcap.Allocate), req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.Allocate.Command, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -170,7 +170,7 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan // AcceptInvocation returns the invocation for the accept request (for use in effects). func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), ucan.Command(blobcap.Accept), req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.Accept.Command, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -216,7 +216,7 @@ type ReplicaAllocateRequest struct { // Returns the response data, the invocation that was sent, and the receipt from // piri. It returns an error if the receipt contains a failure result. func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), ucan.Command(blobreplicacap.Allocate), req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobreplicacap.Allocate.Command, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } diff --git a/pkg/service/handlers/access_claim.go b/pkg/service/handlers/access_claim.go index a18ae2c..40de3c6 100644 --- a/pkg/service/handlers/access_claim.go +++ b/pkg/service/handlers/access_claim.go @@ -14,9 +14,9 @@ import ( ) func NewAccessClaimHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(access.Claim))) + log := logger.With(zap.Stringer("handler", access.Claim)) return Handler{ - Command: ucan.Command(access.Claim), + Command: access.Claim.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*access.ClaimArguments], res *bindexec.Response[*access.ClaimOK], diff --git a/pkg/service/handlers/access_confirm.go b/pkg/service/handlers/access_confirm.go index 2056c79..dfaf927 100644 --- a/pkg/service/handlers/access_confirm.go +++ b/pkg/service/handlers/access_confirm.go @@ -22,9 +22,9 @@ import ( ) func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(access.Confirm))) + log := logger.With(zap.Stringer("handler", access.Confirm)) return Handler{ - Command: ucan.Command(access.Confirm), + Command: access.Confirm.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*access.ConfirmArguments], res *bindexec.Response[*access.ConfirmOK], diff --git a/pkg/service/handlers/access_delegate.go b/pkg/service/handlers/access_delegate.go index cbd5b13..a02710a 100644 --- a/pkg/service/handlers/access_delegate.go +++ b/pkg/service/handlers/access_delegate.go @@ -14,9 +14,9 @@ import ( ) func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(access.Delegate))) + log := logger.With(zap.Stringer("handler", access.Delegate)) return Handler{ - Command: ucan.Command(access.Delegate), + Command: access.Delegate.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*access.DelegateArguments], res *bindexec.Response[*access.DelegateOK], diff --git a/pkg/service/handlers/access_request.go b/pkg/service/handlers/access_request.go index c22fa64..66fe177 100644 --- a/pkg/service/handlers/access_request.go +++ b/pkg/service/handlers/access_request.go @@ -33,9 +33,9 @@ var ( ) func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identity, mailer mailer.Mailer, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(access.Request))) + log := logger.With(zap.Stringer("handler", access.Request)) return Handler{ - Command: ucan.Command(access.Request), + Command: access.Request.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*access.RequestArguments], res *bindexec.Response[*access.RequestOK], diff --git a/pkg/service/handlers/admin_provider_deregister.go b/pkg/service/handlers/admin_provider_deregister.go index 5dbc570..160f9c4 100644 --- a/pkg/service/handlers/admin_provider_deregister.go +++ b/pkg/service/handlers/admin_provider_deregister.go @@ -6,14 +6,13 @@ import ( storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) func NewAdminProviderDeregisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(provider.Deregister))) + log := logger.With(zap.Stringer("handler", provider.Deregister)) return Handler{ - Command: ucan.Command(provider.Deregister), + Command: provider.Deregister.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*provider.DeregisterArguments], res *bindexec.Response[*provider.DeregisterOK], diff --git a/pkg/service/handlers/admin_provider_list.go b/pkg/service/handlers/admin_provider_list.go index 1bb2c8a..1461884 100644 --- a/pkg/service/handlers/admin_provider_list.go +++ b/pkg/service/handlers/admin_provider_list.go @@ -11,13 +11,12 @@ import ( storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" ) func NewAdminProviderListHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(provider.List))) + log := logger.With(zap.Stringer("handler", provider.List)) return Handler{ - Command: ucan.Command(provider.List), + Command: provider.List.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*provider.ListArguments], res *bindexec.Response[*provider.ListOK], diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index 460ef07..5c11c06 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -8,7 +8,6 @@ import ( storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) @@ -18,9 +17,9 @@ var ( ) func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(provider.Register))) + log := logger.With(zap.Stringer("handler", provider.Register)) return Handler{ - Command: ucan.Command(provider.Register), + Command: provider.Register.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*provider.RegisterArguments], res *bindexec.Response[*provider.RegisterOK], diff --git a/pkg/service/handlers/admin_provider_weight_set.go b/pkg/service/handlers/admin_provider_weight_set.go index 4ca44e2..ac6c2fd 100644 --- a/pkg/service/handlers/admin_provider_weight_set.go +++ b/pkg/service/handlers/admin_provider_weight_set.go @@ -8,13 +8,12 @@ import ( storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" ) func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(weight.Set))) + log := logger.With(zap.Stringer("handler", weight.Set)) return Handler{ - Command: ucan.Command(weight.Set), + Command: weight.Set.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*weight.SetArguments], res *bindexec.Response[*weight.SetOK], diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index 59aed17..85e22a6 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -34,9 +34,9 @@ import ( ) func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, router *routing.Service, nodeProvider piriclient.Provider, agentStore agent.Store, blobRegistry blobregistry.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(blobcaps.Add))) + log := logger.With(zap.Stringer("handler", blobcaps.Add)) return Handler{ - Command: ucan.Command(blobcaps.Add), + Command: blobcaps.Add.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*blobcaps.AddArguments], res *bindexec.Response[*blobcaps.AddOK], diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go index 4f50309..48f60ec 100644 --- a/pkg/service/handlers/blob_add_test.go +++ b/pkg/service/handlers/blob_add_test.go @@ -136,14 +136,14 @@ func newMockPiriServer( server.WithValidationOptions(validator.WithDIDVerifierResolver(resolveDIDKey)), ) - srv.Handle(ucan.Command(blobcaps.Allocate), bindexec.NewHandler(func( + srv.Handle(blobcaps.Allocate.Command, bindexec.NewHandler(func( req *bindexec.Request[*blobcaps.AllocateArguments], res *bindexec.Response[*blobcaps.AllocateOK], ) error { return res.SetSuccess(allocateOK) })) - srv.Handle(ucan.Command(blobcaps.Accept), bindexec.NewHandler(func( + srv.Handle(blobcaps.Accept.Command, bindexec.NewHandler(func( req *bindexec.Request[*blobcaps.AcceptArguments], res *bindexec.Response[*blobcaps.AcceptOK], ) error { diff --git a/pkg/service/handlers/blob_list.go b/pkg/service/handlers/blob_list.go index 8d5f369..5f117ab 100644 --- a/pkg/service/handlers/blob_list.go +++ b/pkg/service/handlers/blob_list.go @@ -6,14 +6,13 @@ import ( blobcaps "github.com/fil-forge/libforge/commands/blob" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(blobcaps.List))) + log := logger.With(zap.Stringer("handler", blobcaps.List)) return Handler{ - Command: ucan.Command(blobcaps.List), + Command: blobcaps.List.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*blobcaps.ListArguments], res *bindexec.Response[*blobcaps.ListOK], diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go index e895ca6..8170333 100644 --- a/pkg/service/handlers/index_add.go +++ b/pkg/service/handlers/index_add.go @@ -14,13 +14,12 @@ import ( blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" ) func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(indexcaps.Add))) + log := logger.With(zap.Stringer("handler", indexcaps.Add)) return Handler{ - Command: ucan.Command(indexcaps.Add), + Command: indexcaps.Add.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*indexcaps.AddArguments], res *bindexec.Response[*indexcaps.AddOK], diff --git a/pkg/service/handlers/index_add_test.go b/pkg/service/handlers/index_add_test.go index 5db749d..d763d7f 100644 --- a/pkg/service/handlers/index_add_test.go +++ b/pkg/service/handlers/index_add_test.go @@ -63,7 +63,7 @@ func newMockIndexerServer( server.WithValidationOptions(validator.WithDIDVerifierResolver(resolveDIDKey)), ) - srv.Handle(ucan.Command(assertcaps.Index), bindexec.NewHandler(func( + srv.Handle(assertcaps.Index.Command, bindexec.NewHandler(func( req *bindexec.Request[*assertcaps.IndexArguments], res *bindexec.Response[*assertcaps.IndexOK], ) error { diff --git a/pkg/service/handlers/provider_add.go b/pkg/service/handlers/provider_add.go index 7f8ec36..45d8fa2 100644 --- a/pkg/service/handlers/provider_add.go +++ b/pkg/service/handlers/provider_add.go @@ -11,14 +11,13 @@ import ( "github.com/fil-forge/sprue/pkg/store/consumer" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(providercaps.Add))) + log := logger.With(zap.Stringer("handler", providercaps.Add)) return Handler{ - Command: ucan.Command(providercaps.Add), + Command: providercaps.Add.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*providercaps.AddArguments], res *bindexec.Response[*providercaps.AddOK], diff --git a/pkg/service/handlers/space_info.go b/pkg/service/handlers/space_info.go index 13301d4..472b594 100644 --- a/pkg/service/handlers/space_info.go +++ b/pkg/service/handlers/space_info.go @@ -8,15 +8,14 @@ import ( "github.com/fil-forge/sprue/pkg/provisioning" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) // This handler returns info about a space, including its providers. func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(spacecaps.Info))) + log := logger.With(zap.Stringer("handler", spacecaps.Info)) return Handler{ - Command: ucan.Command(spacecaps.Info), + Command: spacecaps.Info.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*spacecaps.InfoArguments], res *bindexec.Response[*spacecaps.InfoOK], diff --git a/pkg/service/handlers/ucan_conclude.go b/pkg/service/handlers/ucan_conclude.go index 30720dd..0cc0642 100644 --- a/pkg/service/handlers/ucan_conclude.go +++ b/pkg/service/handlers/ucan_conclude.go @@ -32,10 +32,10 @@ type ConclusionHandler struct { // When it receives an /http/put receipt, it calls /blob/accept on piri // and stores the accept receipt for later retrieval. func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handlers map[ucan.Command]ConclusionHandlerFunc, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(ucancaps.Conclude))) + log := logger.With(zap.Stringer("handler", ucancaps.Conclude)) log.Info("registered conclude handlers", zap.Stringers("commands", slices.Collect(maps.Keys(handlers)))) return Handler{ - Command: ucan.Command(ucancaps.Conclude), + Command: ucancaps.Conclude.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*ucancaps.ConcludeArguments], res *bindexec.Response[*ucancaps.ConcludeOK], diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index ee4677e..dae2d58 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -28,11 +28,11 @@ func NewHTTPPutConcludeHandler( logger *zap.Logger, ) ConclusionHandler { log := logger.With( - zap.String("handler", string(ucancaps.Conclude)), - zap.String("conclude", string(httpcaps.Put)), + zap.Stringer("handler", ucancaps.Conclude), + zap.Stringer("conclude", httpcaps.Put), ) return ConclusionHandler{ - Command: ucan.Command(httpcaps.Put), + Command: httpcaps.Put.Command, Handler: func(ctx context.Context, putInv ucan.Invocation, putRcpt ucan.Receipt, meta ucan.Container) error { log := log.With(zap.Stringer("ran", putRcpt.Ran())) log.Debug("handling conclude") diff --git a/pkg/service/handlers/upload_add.go b/pkg/service/handlers/upload_add.go index 492f6da..9dbd83d 100644 --- a/pkg/service/handlers/upload_add.go +++ b/pkg/service/handlers/upload_add.go @@ -9,15 +9,14 @@ import ( upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) // This handler registers an upload (root CID + shards mapping). func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(uploadcaps.Add))) + log := logger.With(zap.Stringer("handler", uploadcaps.Add)) return Handler{ - Command: ucan.Command(uploadcaps.Add), + Command: uploadcaps.Add.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*uploadcaps.AddArguments], res *bindexec.Response[*uploadcaps.AddOK], diff --git a/pkg/service/handlers/upload_list.go b/pkg/service/handlers/upload_list.go index 32fb59d..874f361 100644 --- a/pkg/service/handlers/upload_list.go +++ b/pkg/service/handlers/upload_list.go @@ -6,14 +6,13 @@ import ( uploadcaps "github.com/fil-forge/libforge/commands/upload" upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(uploadcaps.List))) + log := logger.With(zap.Stringer("handler", uploadcaps.List)) return Handler{ - Command: ucan.Command(uploadcaps.List), + Command: uploadcaps.List.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*uploadcaps.ListArguments], res *bindexec.Response[*uploadcaps.ListOK], diff --git a/pkg/service/handlers/upload_shard_list.go b/pkg/service/handlers/upload_shard_list.go index 9f52f0a..a699596 100644 --- a/pkg/service/handlers/upload_shard_list.go +++ b/pkg/service/handlers/upload_shard_list.go @@ -6,15 +6,14 @@ import ( shardcaps "github.com/fil-forge/libforge/commands/upload/shard" upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/execution/bindexec" - "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) // This handler lists the shards of an upload. func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.String("handler", string(shardcaps.List))) + log := logger.With(zap.Stringer("handler", shardcaps.List)) return Handler{ - Command: ucan.Command(shardcaps.List), + Command: shardcaps.List.Command, Handler: bindexec.NewHandler(func( req *bindexec.Request[*shardcaps.ListArguments], res *bindexec.Response[*shardcaps.ListOK], diff --git a/pkg/store/metrics/metrics.go b/pkg/store/metrics/metrics.go index 3e8d13a..e5f6a00 100644 --- a/pkg/store/metrics/metrics.go +++ b/pkg/store/metrics/metrics.go @@ -8,14 +8,14 @@ import ( "github.com/fil-forge/ucantone/did" ) -var BlobAddTotalMetric = string(blob.Add) + "-total" -var BlobAddSizeTotalMetric = string(blob.Add) + "-size-total" +var BlobAddTotalMetric = blob.Add.String() + "-total" +var BlobAddSizeTotalMetric = blob.Add.String() + "-size-total" -var BlobRemoveTotalMetric = string(blob.Remove) + "-total" -var BlobRemoveSizeTotalMetric = string(blob.Remove) + "-size-total" +var BlobRemoveTotalMetric = blob.Remove.String() + "-total" +var BlobRemoveSizeTotalMetric = blob.Remove.String() + "-size-total" -var UploadAddTotalMetric = string(upload.Add) + "-total" -var UploadRemoveTotalMetric = string(upload.Remove) + "-total" +var UploadAddTotalMetric = upload.Add.String() + "-total" +var UploadRemoveTotalMetric = upload.Remove.String() + "-total" type Store interface { // Get all metrics from storage. From 43210e99fe245224f1c35887461f63816171293e Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 21 May 2026 18:06:21 +0100 Subject: [PATCH 19/23] fix: receipts endpoint --- pkg/client/client.go | 8 +- pkg/indexerclient/client.go | 2 +- pkg/lib/ucan_client/execute.go | 12 +-- pkg/piriclient/client.go | 30 +++--- .../handlers/ucan_conclude_http_put.go | 12 ++- pkg/service/service.go | 35 ++++-- pkg/store/agent/agent.go | 20 ++++ pkg/store/agent/agent_test.go | 78 ++++++++++++++ pkg/store/agent/aws/store.go | 102 ++++++++++++++++++ pkg/store/agent/memory/store.go | 56 ++++++++++ pkg/store/agent/postgres/store.go | 79 ++++++++++++++ 11 files changed, 398 insertions(+), 36 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index d00fd05..734d7b4 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -64,7 +64,7 @@ func (c *Client) AdminProviderRegister(ctx context.Context, providerID did.DID, return nil, fmt.Errorf("invoking provider register: %w", err) } - _, rcpt, err := ucan_client.Execute[*providercap.RegisterOK](ctx, c.client, c.logger, inv) + _, rcpt, _, err := ucan_client.Execute[*providercap.RegisterOK](ctx, c.client, c.logger, inv) if err != nil { return nil, fmt.Errorf("executing provider register invocation: %w", err) } @@ -94,7 +94,7 @@ func (c *Client) AdminProviderDeregister(ctx context.Context, providerID did.DID return nil, fmt.Errorf("invoking provider deregister: %w", err) } - _, rcpt, err := ucan_client.Execute[*providercap.DeregisterOK](ctx, c.client, c.logger, inv) + _, rcpt, _, err := ucan_client.Execute[*providercap.DeregisterOK](ctx, c.client, c.logger, inv) if err != nil { return nil, fmt.Errorf("executing provider deregister invocation: %w", err) } @@ -122,7 +122,7 @@ func (c *Client) AdminProviderList(ctx context.Context, options ...invocation.Op return nil, nil, fmt.Errorf("invoking provider list: %w", err) } - listOK, rcpt, err := ucan_client.Execute[*providercap.ListOK](ctx, c.client, c.logger, inv) + listOK, rcpt, _, err := ucan_client.Execute[*providercap.ListOK](ctx, c.client, c.logger, inv) if err != nil { return nil, nil, fmt.Errorf("executing provider list invocation: %w", err) } @@ -154,7 +154,7 @@ func (c *Client) AdminProviderWeightSet(ctx context.Context, providerID did.DID, return nil, fmt.Errorf("invoking provider weight set: %w", err) } - _, rcpt, err := ucan_client.Execute[*weightcap.SetOK](ctx, c.client, c.logger, inv) + _, rcpt, _, err := ucan_client.Execute[*weightcap.SetOK](ctx, c.client, c.logger, inv) if err != nil { return nil, fmt.Errorf("executing provider weight set invocation: %w", err) } diff --git a/pkg/indexerclient/client.go b/pkg/indexerclient/client.go index 1054f5c..da1dec5 100644 --- a/pkg/indexerclient/client.go +++ b/pkg/indexerclient/client.go @@ -75,7 +75,7 @@ func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid return nil, fmt.Errorf("creating invocation: %w", err) } - _, rcpt, err := ucan_client.Execute[*assertcaps.IndexOK]( + _, rcpt, _, err := ucan_client.Execute[*assertcaps.IndexOK]( ctx, c.client, c.logger, diff --git a/pkg/lib/ucan_client/execute.go b/pkg/lib/ucan_client/execute.go index 60c6589..ad3a3a6 100644 --- a/pkg/lib/ucan_client/execute.go +++ b/pkg/lib/ucan_client/execute.go @@ -23,7 +23,7 @@ func Execute[T cbg.CBORUnmarshaler]( logger *zap.Logger, inv ucan.Invocation, options ...execution.RequestOption, -) (T, ucan.Receipt, error) { +) (T, ucan.Receipt, ucan.Container, error) { fields := []zap.Field{ zap.Stringer("issuer", inv.Issuer()), zap.Stringer("subject", inv.Subject()), @@ -46,7 +46,7 @@ func Execute[T cbg.CBORUnmarshaler]( resp, err := client.Execute(execution.NewRequest(ctx, inv, options...)) if err != nil { log.Error("failed to execute invocation", zap.Error(err)) - return zero, nil, fmt.Errorf("executing invocation: %w", err) + return zero, nil, nil, fmt.Errorf("executing invocation: %w", err) } rcpt := resp.Receipt() @@ -56,10 +56,10 @@ func Execute[T cbg.CBORUnmarshaler]( var model edm.ErrorModel if err := model.UnmarshalCBOR(bytes.NewReader(x)); err != nil { log.Error("failed to unmarshal execution failure", zap.Error(err), zap.Binary("input", x)) - return zero, nil, fmt.Errorf("executing invocation") + return zero, nil, nil, fmt.Errorf("executing invocation") } log.Error("failed execution", zap.String("name", model.ErrorName), zap.Error(model)) - return zero, nil, fmt.Errorf("executing invocation: %w", model) + return zero, nil, nil, fmt.Errorf("executing invocation: %w", model) } // if ok is a pointer type, allocate the underlying value so @@ -71,7 +71,7 @@ func Execute[T cbg.CBORUnmarshaler]( } if err := ok.UnmarshalCBOR(bytes.NewReader(o)); err != nil { log.Error("failed to unmarshal invocation response", zap.Error(err), zap.Binary("input", o)) - return zero, nil, fmt.Errorf("unmarshaling invocation response: %w", err) + return zero, nil, nil, fmt.Errorf("unmarshaling invocation response: %w", err) } - return ok, rcpt, nil + return ok, rcpt, resp.Metadata(), nil } diff --git a/pkg/piriclient/client.go b/pkg/piriclient/client.go index c712967..e9152bf 100644 --- a/pkg/piriclient/client.go +++ b/pkg/piriclient/client.go @@ -78,7 +78,7 @@ func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore zap.Int("attestations", len(attestations)), ) - allocOK, rcpt, err := ucan_client.Execute[*blobcap.AllocateOK]( + allocOK, rcpt, _, err := ucan_client.Execute[*blobcap.AllocateOK]( ctx, c.client, c.logger, @@ -141,10 +141,10 @@ type AcceptRequest struct { } // Accept sends a /blob/accept invocation to the piri node. -func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobcap.AcceptOK, ucan.Invocation, ucan.Receipt, error) { +func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobcap.AcceptOK, ucan.Invocation, ucan.Receipt, ucan.Container, error) { inv, prfs, attestations, err := c.AcceptInvocation(ctx, req, proofStore, options...) if err != nil { - return nil, nil, nil, fmt.Errorf("creating accept invocation: %w", err) + return nil, nil, nil, nil, fmt.Errorf("creating accept invocation: %w", err) } c.logger.Debug("ACCEPT invocation created", @@ -154,7 +154,7 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan zap.Int("attestations", len(attestations)), ) - acceptOK, rcpt, err := ucan_client.Execute[*blobcap.AcceptOK]( + acceptOK, rcpt, meta, err := ucan_client.Execute[*blobcap.AcceptOK]( ctx, c.client, c.logger, @@ -163,9 +163,9 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan execution.WithInvocations(attestations...), ) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } - return acceptOK, inv, rcpt, nil + return acceptOK, inv, rcpt, meta, nil } // AcceptInvocation returns the invocation for the accept request (for use in effects). @@ -213,17 +213,17 @@ type ReplicaAllocateRequest struct { } // ReplicaAllocate sends a /blob/replica/allocate invocation to the piri node. -// Returns the response data, the invocation that was sent, and the receipt from -// piri. It returns an error if the receipt contains a failure result. -func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { +// Returns the response data, the invocation that was sent, the receipt from +// piri, and any metadata. It returns an error if the receipt contains a failure result. +func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, ucan.Container, error) { prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobreplicacap.Allocate.Command, req.Space) if err != nil { - return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) + return nil, nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } attestations, err := proofStore.ProofAttestations(ctx, prfs, c.signer.DID()) if err != nil { - return nil, nil, nil, fmt.Errorf("getting proof attestations: %w", err) + return nil, nil, nil, nil, fmt.Errorf("getting proof attestations: %w", err) } options = slices.Clone(options) @@ -248,7 +248,7 @@ func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateReques options..., ) if err != nil { - return nil, nil, nil, fmt.Errorf("creating replica allocate invocation: %w", err) + return nil, nil, nil, nil, fmt.Errorf("creating replica allocate invocation: %w", err) } c.logger.Debug("REPLICA ALLOCATE invocation created", @@ -256,7 +256,7 @@ func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateReques zap.Stringer("audience", inv.Audience()), zap.Int("proofs", len(inv.Proofs()))) - allocOK, rcpt, err := ucan_client.Execute[*blobreplicacap.AllocateOK]( + allocOK, rcpt, meta, err := ucan_client.Execute[*blobreplicacap.AllocateOK]( ctx, c.client, c.logger, @@ -265,7 +265,7 @@ func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateReques execution.WithInvocations(attestations...), ) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } - return allocOK, inv, rcpt, nil + return allocOK, inv, rcpt, meta, nil } diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index dae2d58..e65c9e4 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -84,7 +84,7 @@ func NewHTTPPutConcludeHandler( } proofStore := ucanlib.NewContainerProofStore(meta) - res, accInv, accRcpt, err := client.Accept(ctx, &piriclient.AcceptRequest{ + res, accInv, accRcpt, meta, err := client.Accept(ctx, &piriclient.AcceptRequest{ Space: space, Digest: allocArgs.Blob.Digest, Size: allocArgs.Blob.Size, @@ -96,7 +96,15 @@ func NewHTTPPutConcludeHandler( } log = log.With(zap.Stringer("site", res.Site)) - err = writeAgentMessage(ctx, agentStore, []ucan.Invocation{accInv}, []ucan.Receipt{accRcpt}) + // accept invocation includes a location commitment (invocation) in response + accInvs := []ucan.Invocation{accInv} + accRcpts := []ucan.Receipt{accRcpt} + if meta != nil { + accInvs = append(accInvs, meta.Invocations()...) + accRcpts = append(accRcpts, meta.Receipts()...) + } + + err = writeAgentMessage(ctx, agentStore, accInvs, accRcpts) if err != nil { log.Error("failed to write agent message", zap.Error(err)) return fmt.Errorf("writing agent message: %w", err) diff --git a/pkg/service/service.go b/pkg/service/service.go index 06a3066..d5dbca2 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -2,7 +2,6 @@ package service import ( "bytes" - "errors" "fmt" "net/http" "slices" @@ -18,6 +17,7 @@ import ( "github.com/fil-forge/ucantone/ipld/codec/dagcbor" "github.com/fil-forge/ucantone/principal" "github.com/fil-forge/ucantone/server" + "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/validator" "github.com/ipfs/go-cid" @@ -135,17 +135,36 @@ func (s *Service) HandleReceiptRequest(c echo.Context) error { } s.logger.Debug("receipt request", zap.String("task", task.String())) - rcpt, err := s.agentStore.GetReceipt(c.Request().Context(), task) + page, err := s.agentStore.List(c.Request().Context(), task, agent.WithListLimit(25)) if err != nil { - if errors.Is(err, agent.ErrReceiptNotFound) { - return c.JSON(http.StatusNotFound, map[string]string{ - "error": "receipt not found", - }) + return fmt.Errorf("listing agent messages: %w", err) + } + + found := false + var invs []ucan.Invocation + var dlgs []ucan.Delegation + var rcpts []ucan.Receipt + for _, msg := range page.Results { + invs = append(invs, msg.Invocations()...) + dlgs = append(dlgs, msg.Delegations()...) + rcpts = append(rcpts, msg.Receipts()...) + for _, r := range msg.Receipts() { + if r.Ran() == task { + found = true + } } - return fmt.Errorf("getting receipt: %w", err) + } + if !found { + return c.JSON(http.StatusNotFound, map[string]string{ + "error": "receipt not found", + }) } - ct := container.New(container.WithReceipts(rcpt)) + ct := container.New( + container.WithInvocations(invs...), + container.WithDelegations(dlgs...), + container.WithReceipts(rcpts...), + ) var buf bytes.Buffer if err := ct.MarshalCBOR(&buf); err != nil { return fmt.Errorf("marshaling receipt container: %w", err) diff --git a/pkg/store/agent/agent.go b/pkg/store/agent/agent.go index d67688f..777f670 100644 --- a/pkg/store/agent/agent.go +++ b/pkg/store/agent/agent.go @@ -3,6 +3,7 @@ package agent import ( "context" + "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/ucan" "github.com/ipfs/go-cid" @@ -20,6 +21,23 @@ var ( ErrReceiptNotFound = errors.New(ReceiptNotFoundErrorName, "receipt not found") ) +type ( + ListConfig = store.PaginationConfig + ListOption func(cfg *ListConfig) +) + +func WithListLimit(limit int) ListOption { + return func(cfg *ListConfig) { + cfg.Limit = &limit + } +} + +func WithListCursor(cursor string) ListOption { + return func(cfg *ListConfig) { + cfg.Cursor = &cursor + } +} + type InvocationSource struct { Task cid.Cid Invocation ucan.Invocation @@ -43,4 +61,6 @@ type Store interface { GetInvocation(ctx context.Context, task cid.Cid) (ucan.Invocation, error) // GetReceipt retrieves a receipt by its task CID. May return [ErrReceiptNotFound]. GetReceipt(ctx context.Context, task cid.Cid) (ucan.Receipt, error) + // List agent messages with invocations and receipts relevant to the task CID. + List(ctx context.Context, task cid.Cid, options ...ListOption) (store.Page[ucan.Container], error) } diff --git a/pkg/store/agent/agent_test.go b/pkg/store/agent/agent_test.go index d1c7d50..7d17d0d 100644 --- a/pkg/store/agent/agent_test.go +++ b/pkg/store/agent/agent_test.go @@ -203,6 +203,84 @@ func TestAgentStore(t *testing.T) { require.NoError(t, err) require.Equal(t, rcpt.Link().String(), gotRcpt.Link().String()) }) + + t.Run("returns empty list for unknown task", func(t *testing.T) { + page, err := store.List(t.Context(), testutil.RandomCID(t)) + require.NoError(t, err) + require.Empty(t, page.Results) + require.Nil(t, page.Cursor) + }) + + t.Run("lists messages with invocations and receipts for a task", func(t *testing.T) { + inv := makeInvocation(t) + rcpt := makeReceipt(t, inv) + task := inv.Task().Link() + + // Write the invocation and receipt in separate messages so the + // index references two distinct agent messages for the task. + buildAndWrite(t, store, []ucan.Invocation{inv}, nil) + buildAndWrite(t, store, nil, []ucan.Receipt{rcpt}) + + page, err := store.List(t.Context(), task) + require.NoError(t, err) + require.Nil(t, page.Cursor) + require.Len(t, page.Results, 2) + + var hasInv, hasRcpt bool + for _, m := range page.Results { + for _, i := range m.Invocations() { + if i.Task().Link() == task { + hasInv = true + } + } + if _, ok := m.Receipt(task); ok { + hasRcpt = true + } + } + require.True(t, hasInv, "expected listed messages to contain the invocation") + require.True(t, hasRcpt, "expected listed messages to contain the receipt") + }) + + t.Run("paginates list results", func(t *testing.T) { + inv := makeInvocation(t) + rcpt := makeReceipt(t, inv) + task := inv.Task().Link() + + buildAndWrite(t, store, []ucan.Invocation{inv}, nil) + buildAndWrite(t, store, nil, []ucan.Receipt{rcpt}) + + var collected []ucan.Container + var cursor *string + for i := 0; i < 4; i++ { + opts := []agent.ListOption{agent.WithListLimit(1)} + if cursor != nil { + opts = append(opts, agent.WithListCursor(*cursor)) + } + page, err := store.List(t.Context(), task, opts...) + require.NoError(t, err) + require.LessOrEqual(t, len(page.Results), 1) + collected = append(collected, page.Results...) + if page.Cursor == nil { + break + } + cursor = page.Cursor + } + require.Len(t, collected, 2) + + var hasInv, hasRcpt bool + for _, m := range collected { + for _, i := range m.Invocations() { + if i.Task().Link() == task { + hasInv = true + } + } + if _, ok := m.Receipt(task); ok { + hasRcpt = true + } + } + require.True(t, hasInv) + require.True(t, hasRcpt) + }) }) } } diff --git a/pkg/store/agent/aws/store.go b/pkg/store/agent/aws/store.go index 878ab16..3e7f889 100644 --- a/pkg/store/agent/aws/store.go +++ b/pkg/store/agent/aws/store.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "slices" "strings" "sync" "time" @@ -15,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/fil-forge/libforge/jobqueue" + "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/agent" "github.com/fil-forge/ucantone/ipld/codec/dagcbor" "github.com/fil-forge/ucantone/ucan" @@ -160,6 +162,106 @@ func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (ucan.Receipt, err return rcpt, nil } +func (s *Store) List(ctx context.Context, task cid.Cid, options ...agent.ListOption) (store.Page[ucan.Container], error) { + cfg := agent.ListConfig{} + for _, opt := range options { + opt(&cfg) + } + + // Index entries for a task live under two partition keys (.in and + // .out). Query both, then sort + paginate by message CID so the + // resulting page is stable across calls. + var msgRoots []cid.Cid + for _, kind := range []string{"in", "out"} { + taskkind := fmt.Sprintf("%s.%s", task, kind) + var startKey map[string]types.AttributeValue + for { + out, err := s.dynamo.Query(ctx, &dynamodb.QueryInput{ + TableName: &s.tableName, + KeyConditionExpression: aws.String("taskkind = :taskkind"), + ExpressionAttributeValues: map[string]types.AttributeValue{ + ":taskkind": &types.AttributeValueMemberS{Value: taskkind}, + }, + ExclusiveStartKey: startKey, + }) + if err != nil { + return store.Page[ucan.Container]{}, fmt.Errorf("querying DynamoDB for %s: %w", taskkind, err) + } + for _, item := range out.Items { + v, ok := item["identifier"] + if !ok { + return store.Page[ucan.Container]{}, fmt.Errorf("missing identifier attribute in DynamoDB item: %v", item) + } + sv, ok := v.(*types.AttributeValueMemberS) + if !ok { + return store.Page[ucan.Container]{}, fmt.Errorf("unexpected type for identifier attribute in DynamoDB item: %T", v) + } + parts := strings.SplitN(sv.Value, "@", 2) + if len(parts) != 2 { + return store.Page[ucan.Container]{}, fmt.Errorf("invalid identifier format in DynamoDB item: %s", sv.Value) + } + msgRoot, err := cid.Parse(parts[1]) + if err != nil { + return store.Page[ucan.Container]{}, fmt.Errorf("parsing message root CID: %w", err) + } + msgRoots = append(msgRoots, msgRoot) + } + if len(out.LastEvaluatedKey) == 0 { + break + } + startKey = out.LastEvaluatedKey + } + } + + slices.SortFunc(msgRoots, func(a, b cid.Cid) int { + return bytes.Compare(a.Bytes(), b.Bytes()) + }) + + if cfg.Cursor != nil { + idx := slices.IndexFunc(msgRoots, func(c cid.Cid) bool { + return c.String() == *cfg.Cursor + }) + if idx == -1 { + return store.Page[ucan.Container]{}, fmt.Errorf("invalid cursor: %s", *cfg.Cursor) + } + msgRoots = msgRoots[idx+1:] + } + + var nextCursor *string + if cfg.Limit != nil && len(msgRoots) > *cfg.Limit { + last := msgRoots[*cfg.Limit-1].String() + nextCursor = &last + msgRoots = msgRoots[:*cfg.Limit] + } + + results := make([]ucan.Container, 0, len(msgRoots)) + for _, m := range msgRoots { + ct, err := s.fetchMessage(ctx, m) + if err != nil { + return store.Page[ucan.Container]{}, err + } + results = append(results, ct) + } + return store.Page[ucan.Container]{Results: results, Cursor: nextCursor}, nil +} + +func (s *Store) fetchMessage(ctx context.Context, msgRoot cid.Cid) (*container.Container, error) { + out, err := s.s3.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &s.bucketName, + Key: aws.String(toMessagePath(msgRoot)), + }) + if err != nil { + return nil, fmt.Errorf("getting message from S3: %w", err) + } + defer out.Body.Close() + + var ct container.Container + if err := ct.UnmarshalCBOR(out.Body); err != nil { + return nil, fmt.Errorf("unmarshaling agent message from CBOR: %w", err) + } + return &ct, nil +} + // getByTask is a helper method that retrieves the invocation or receipt // CID and blocks for a given task CID and kind ("in" for invocation or "out" for receipt). func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.Cid, *container.Container, error) { diff --git a/pkg/store/agent/memory/store.go b/pkg/store/agent/memory/store.go index eea1c7b..6243534 100644 --- a/pkg/store/agent/memory/store.go +++ b/pkg/store/agent/memory/store.go @@ -4,8 +4,10 @@ import ( "bytes" "context" "fmt" + "slices" "sync" + "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/agent" "github.com/fil-forge/ucantone/ipld/codec/dagcbor" "github.com/fil-forge/ucantone/ucan" @@ -101,3 +103,57 @@ func (s *Store) Write(ctx context.Context, message ucan.Container, index []agent } return nil } + +func (s *Store) List(ctx context.Context, task cid.Cid, options ...agent.ListOption) (store.Page[ucan.Container], error) { + s.mutex.RLock() + defer s.mutex.RUnlock() + + cfg := &agent.ListConfig{} + for _, opt := range options { + opt(cfg) + } + + var msgLinks []cid.Cid + for _, l := range s.index[fmt.Sprintf("/%s/invocation/", task)] { + msgLinks = append(msgLinks, l) + } + for _, l := range s.index[fmt.Sprintf("/%s/receipt/", task)] { + msgLinks = append(msgLinks, l) + } + slices.SortFunc(msgLinks, func(a, b cid.Cid) int { + return bytes.Compare(a.Bytes(), b.Bytes()) + }) + + resultLinks := slices.Clone(msgLinks) + results := make([]ucan.Container, 0, len(msgLinks)) + for _, l := range msgLinks { + results = append(results, s.store[l]) + } + + if cfg.Cursor != nil { + index := slices.IndexFunc(msgLinks, func(c cid.Cid) bool { + return c.String() == *cfg.Cursor + }) + if index == -1 { + return store.Page[ucan.Container]{}, fmt.Errorf("invalid cursor: %s", *cfg.Cursor) + } + results = results[index+1:] + resultLinks = resultLinks[index+1:] + } + + if cfg.Limit != nil && len(results) > *cfg.Limit { + results = results[:*cfg.Limit] + resultLinks = resultLinks[:*cfg.Limit] + } + + var nextCursor *string + if len(results) > 0 && resultLinks[len(results)-1] != msgLinks[len(msgLinks)-1] { + cursor := resultLinks[len(resultLinks)-1].String() + nextCursor = &cursor + } + + return store.Page[ucan.Container]{ + Results: results, + Cursor: nextCursor, + }, nil +} diff --git a/pkg/store/agent/postgres/store.go b/pkg/store/agent/postgres/store.go index bb86bf4..d24d63d 100644 --- a/pkg/store/agent/postgres/store.go +++ b/pkg/store/agent/postgres/store.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/sprue/pkg/store/agent" "github.com/fil-forge/ucantone/ipld/codec/dagcbor" "github.com/fil-forge/ucantone/ucan" @@ -22,6 +23,8 @@ import ( "github.com/multiformats/go-multihash" ) +const defaultListLimit = 1000 + type Store struct { pool *pgxpool.Pool s3 *s3.Client @@ -76,6 +79,82 @@ func (s *Store) GetReceipt(ctx context.Context, task cid.Cid) (ucan.Receipt, err return rcpt, nil } +func (s *Store) List(ctx context.Context, task cid.Cid, options ...agent.ListOption) (store.Page[ucan.Container], error) { + cfg := agent.ListConfig{} + for _, opt := range options { + opt(&cfg) + } + limit := defaultListLimit + if cfg.Limit != nil && *cfg.Limit > 0 { + limit = *cfg.Limit + } + + args := []any{task.String(), limit + 1} + query := `SELECT message FROM agent_index WHERE task = $1` + if cfg.Cursor != nil { + args = append(args, *cfg.Cursor) + query += ` AND message > $3` + } + query += ` ORDER BY message ASC LIMIT $2` + + rows, err := s.pool.Query(ctx, query, args...) + if err != nil { + return store.Page[ucan.Container]{}, fmt.Errorf("querying agent_index: %w", err) + } + defer rows.Close() + + msgRoots := make([]string, 0, limit) + for rows.Next() { + var msgRoot string + if err := rows.Scan(&msgRoot); err != nil { + return store.Page[ucan.Container]{}, fmt.Errorf("scanning message: %w", err) + } + msgRoots = append(msgRoots, msgRoot) + } + if err := rows.Err(); err != nil { + return store.Page[ucan.Container]{}, fmt.Errorf("iterating messages: %w", err) + } + + var cursor *string + if len(msgRoots) > limit { + last := msgRoots[limit-1] + cursor = &last + msgRoots = msgRoots[:limit] + } + + results := make([]ucan.Container, 0, len(msgRoots)) + for _, m := range msgRoots { + msgRoot, err := cid.Parse(m) + if err != nil { + return store.Page[ucan.Container]{}, fmt.Errorf("parsing message CID: %w", err) + } + ct, err := s.fetchMessage(ctx, msgRoot) + if err != nil { + return store.Page[ucan.Container]{}, err + } + results = append(results, ct) + } + + return store.Page[ucan.Container]{Results: results, Cursor: cursor}, nil +} + +func (s *Store) fetchMessage(ctx context.Context, msgRoot cid.Cid) (*container.Container, error) { + out, err := s.s3.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &s.bucketName, + Key: aws.String(toMessagePath(msgRoot)), + }) + if err != nil { + return nil, fmt.Errorf("getting message from S3: %w", err) + } + defer out.Body.Close() + + var ct container.Container + if err := ct.UnmarshalCBOR(out.Body); err != nil { + return nil, fmt.Errorf("decoding container: %w", err) + } + return &ct, nil +} + func (s *Store) getByTask(ctx context.Context, task cid.Cid, kind string) (cid.Cid, *container.Container, error) { var tokenRootStr, msgRootStr string err := s.pool.QueryRow(ctx, ` From e92ca8f0fe7c713a4d1dc98297ba9b4525f3ba18 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 21 May 2026 19:02:54 +0100 Subject: [PATCH 20/23] fix: use libforge PEM decoder --- go.mod | 2 +- go.sum | 2 ++ pkg/identity/identity.go | 65 ++++++++-------------------------------- 3 files changed, 16 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 0026ba9..fab876e 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260519193938-fc442aef7e5c + github.com/fil-forge/libforge v0.0.0-20260521174431-708e600522c4 github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10 github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 diff --git a/go.sum b/go.sum index 8d3e17a..018b498 100644 --- a/go.sum +++ b/go.sum @@ -94,6 +94,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fil-forge/libforge v0.0.0-20260519193938-fc442aef7e5c h1:dzAiOAYDTJDnrPFE7KxVqCn6mVK1grXiaiMuPf5z/Mo= github.com/fil-forge/libforge v0.0.0-20260519193938-fc442aef7e5c/go.mod h1:/sKL0TfiBzknJTuL1vSZ5IHALmWndwxOexU3jtlNVSE= +github.com/fil-forge/libforge v0.0.0-20260521174431-708e600522c4 h1:4cOrR1n8xSno+JbTmnc6rpXg7hRcQEwdqj6LVynj5wA= +github.com/fil-forge/libforge v0.0.0-20260521174431-708e600522c4/go.mod h1:/sKL0TfiBzknJTuL1vSZ5IHALmWndwxOexU3jtlNVSE= github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10 h1:ApgWAIpXjCYjZw/yDxLn8IA9WrH/ENPRWCWPT/MoCvU= github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/pkg/identity/identity.go b/pkg/identity/identity.go index edcc665..af3e832 100644 --- a/pkg/identity/identity.go +++ b/pkg/identity/identity.go @@ -1,14 +1,11 @@ package identity import ( - crypto_ed25519 "crypto/ed25519" - "crypto/x509" - "encoding/pem" "fmt" - "io" "os" "strings" + "github.com/fil-forge/libforge/identity" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/principal" "github.com/fil-forge/ucantone/principal/ed25519" @@ -92,9 +89,13 @@ func (i *Identity) DIDDocument() map[string]interface{} { // NewFromPEMFile creates a new identity from an Ed25519 PEM key file. func NewFromPEMFile(keyFilePath string) (*Identity, error) { - keySigner, err := signerFromEd25519PEMFile(keyFilePath) + pem, err := os.ReadFile(keyFilePath) if err != nil { - return nil, fmt.Errorf("failed to load key from PEM file: %w", err) + return nil, fmt.Errorf("failed to read key file: %w", err) + } + keySigner, err := identity.DecodeEd25519SignerFromPEM(pem) + if err != nil { + return nil, fmt.Errorf("failed to decode key from PEM file: %w", err) } return &Identity{Signer: keySigner}, nil } @@ -105,9 +106,13 @@ func NewFromPEMFile(keyFilePath string) (*Identity, error) { // signer is wrapped so the service presents itself as the did:web identity // and accepts UCANs addressed to that did:web. func NewFromPEMFileWithDID(keyFilePath string, serviceDID string) (*Identity, error) { - keySigner, err := signerFromEd25519PEMFile(keyFilePath) + pem, err := os.ReadFile(keyFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read key file: %w", err) + } + keySigner, err := identity.DecodeEd25519SignerFromPEM(pem) if err != nil { - return nil, fmt.Errorf("failed to load key from PEM file: %w", err) + return nil, fmt.Errorf("failed to decode key from PEM file: %w", err) } // If serviceDID is provided, wrap the signer with the did:web identity @@ -127,47 +132,3 @@ func NewFromPEMFileWithDID(keyFilePath string, serviceDID string) (*Identity, er return &Identity{Signer: keySigner}, nil } - -// signerFromEd25519PEMFile loads an Ed25519 private key from a PKCS#8 PEM file. -func signerFromEd25519PEMFile(path string) (principal.Signer, error) { - f, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("failed to open key file: %w", err) - } - defer f.Close() - - pemData, err := io.ReadAll(f) - if err != nil { - return nil, fmt.Errorf("failed to read key file: %w", err) - } - - var privateKey *crypto_ed25519.PrivateKey - rest := pemData - for { - block, remaining := pem.Decode(rest) - if block == nil { - break - } - rest = remaining - - if block.Type == "PRIVATE KEY" { - parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("failed to parse PKCS#8 private key: %w", err) - } - - key, ok := parsedKey.(crypto_ed25519.PrivateKey) - if !ok { - return nil, fmt.Errorf("key is not an Ed25519 private key") - } - privateKey = &key - break - } - } - - if privateKey == nil { - return nil, fmt.Errorf("no PRIVATE KEY block found in PEM file") - } - - return ed25519.FromRaw(privateKey.Seed()) -} From 670c930909d08d4213a6988e56f1f22f60b3a199 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 21 May 2026 21:58:45 +0100 Subject: [PATCH 21/23] refactor: package alias renames --- pkg/indexerclient/client.go | 14 +-- pkg/service/handlers/blob_add.go | 57 +++++----- pkg/service/handlers/blob_add_test.go | 104 +++++++++--------- pkg/service/handlers/blob_list.go | 16 +-- pkg/service/handlers/blob_list_test.go | 40 +++---- pkg/service/handlers/index_add.go | 18 +-- pkg/service/handlers/index_add_test.go | 36 +++--- pkg/service/handlers/provider_add.go | 16 +-- pkg/service/handlers/provider_add_test.go | 24 ++-- pkg/service/handlers/space_info.go | 14 +-- pkg/service/handlers/space_info_test.go | 12 +- pkg/service/handlers/ucan_conclude.go | 16 +-- .../ucan_conclude_blob_replica_transfer.go | 4 +- ...can_conclude_blob_replica_transfer_test.go | 2 +- .../handlers/ucan_conclude_http_put.go | 16 +-- .../handlers/ucan_conclude_http_put_test.go | 46 ++++---- pkg/service/handlers/ucan_conclude_test.go | 24 ++-- pkg/service/handlers/upload_add.go | 16 +-- pkg/service/handlers/upload_add_test.go | 22 ++-- pkg/service/handlers/upload_list.go | 16 +-- pkg/service/handlers/upload_list_test.go | 34 +++--- pkg/service/handlers/upload_shard_list.go | 12 +- .../handlers/upload_shard_list_test.go | 26 ++--- 23 files changed, 296 insertions(+), 289 deletions(-) diff --git a/pkg/indexerclient/client.go b/pkg/indexerclient/client.go index da1dec5..e58b033 100644 --- a/pkg/indexerclient/client.go +++ b/pkg/indexerclient/client.go @@ -5,8 +5,8 @@ import ( "fmt" "net/url" - assertcaps "github.com/fil-forge/libforge/commands/assert" - contentcaps "github.com/fil-forge/libforge/commands/content" + assertcmds "github.com/fil-forge/libforge/commands/assert" + contentcmds "github.com/fil-forge/libforge/commands/content" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/lib/ucan_client" "github.com/fil-forge/ucantone/client" @@ -48,7 +48,7 @@ func New(endpoint *url.URL, indexerDID did.DID, signer ucan.Signer, logger *zap. // The proofStore parameter is used to build the delegation chain authorizing // the upload service to retrieve the index blob via `/content/retrieve` command. func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid.Cid, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Receipt, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), contentcaps.Retrieve.Command, space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), contentcmds.Retrieve.Command, space) if err != nil { return nil, fmt.Errorf("building proof chain: %w", err) } @@ -57,15 +57,15 @@ func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid return nil, fmt.Errorf("building attestations: %w", err) } // Create a content retrieval delegation from upload service to indexer - indexerDelegation, err := contentcaps.Retrieve.Delegate(c.signer, c.indexerDID, space) + indexerDelegation, err := contentcmds.Retrieve.Delegate(c.signer, c.indexerDID, space) if err != nil { return nil, fmt.Errorf("creating indexer delegation: %w", err) } - inv, err := assertcaps.Index.Invoke( + inv, err := assertcmds.Index.Invoke( c.signer, c.signer.DID(), - &assertcaps.IndexArguments{Index: index}, + &assertcmds.IndexArguments{Index: index}, invocation.WithAudience(c.indexerDID), invocation.WithMetadata( datamodel.Map{"retrievalAuth": append(prfLinks, indexerDelegation.Link())}, @@ -75,7 +75,7 @@ func (c *Client) PublishIndexClaim(ctx context.Context, space did.DID, index cid return nil, fmt.Errorf("creating invocation: %w", err) } - _, rcpt, _, err := ucan_client.Execute[*assertcaps.IndexOK]( + _, rcpt, _, err := ucan_client.Execute[*assertcmds.IndexOK]( ctx, c.client, c.logger, diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index 85e22a6..d7b8526 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -6,9 +6,9 @@ import ( "crypto/ed25519" "fmt" - accesscaps "github.com/fil-forge/libforge/commands/access" - blobcaps "github.com/fil-forge/libforge/commands/blob" - httpcaps "github.com/fil-forge/libforge/commands/http" + accesscmds "github.com/fil-forge/libforge/commands/access" + blobcmds "github.com/fil-forge/libforge/commands/blob" + httpcmds "github.com/fil-forge/libforge/commands/http" "github.com/fil-forge/libforge/digestutil" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/identity" @@ -34,12 +34,12 @@ import ( ) func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, router *routing.Service, nodeProvider piriclient.Provider, agentStore agent.Store, blobRegistry blobregistry.Store, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", blobcaps.Add)) + log := logger.With(zap.Stringer("handler", blobcmds.Add)) return Handler{ - Command: blobcaps.Add.Command, + Command: blobcmds.Add.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*blobcaps.AddArguments], - res *bindexec.Response[*blobcaps.AddOK], + req *bindexec.Request[*blobcmds.AddArguments], + res *bindexec.Response[*blobcmds.AddOK], ) error { args := req.Task().Arguments() blob := args.Blob @@ -62,7 +62,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv return fmt.Errorf("listing service providers: %w", err) } if len(providers) == 0 { - return res.SetFailure(errors.New(accesscaps.InsufficientStorageErrorName, "space has no storage provider")) + return res.SetFailure(errors.New(accesscmds.InsufficientStorageErrorName, "space has no storage provider")) } reg, err := blobRegistry.Get(req.Context(), space, blob.Digest) @@ -94,7 +94,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv } o, _ := addRcpt.Out().Unpack() - var addOK blobcaps.AddOK + var addOK blobcmds.AddOK if err := addOK.UnmarshalCBOR(bytes.NewReader(o)); err != nil { log.Error("failed to unmarshal add OK result", zap.Error(err)) return fmt.Errorf("unmarshaling add OK result: %w", err) @@ -112,7 +112,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv return fmt.Errorf("getting invocation for blob accept: %w", err) } - var accArgs blobcaps.AcceptArguments + var accArgs blobcmds.AcceptArguments if err := accArgs.UnmarshalCBOR(bytes.NewReader(accInv.ArgumentsBytes())); err != nil { log.Error("failed to rebind accept OK result", zap.Error(err)) return fmt.Errorf("rebinding accept OK result: %w", err) @@ -130,7 +130,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv return fmt.Errorf("getting invocation for HTTP PUT: %w", err) } - var putArgs httpcaps.PutArguments + var putArgs httpcmds.PutArguments if err := putArgs.UnmarshalCBOR(bytes.NewReader(putInv.ArgumentsBytes())); err != nil { log.Error("failed to unmarshal HTTP PUT arguments", zap.Error(err)) return fmt.Errorf("unmarshaling HTTP PUT arguments: %w", err) @@ -187,7 +187,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv } res.SetMetadata(container.New(metaOpts...)) - return res.SetSuccess(&blobcaps.AddOK{ + return res.SetSuccess(&blobcmds.AddOK{ Site: promise.AwaitOK{ Task: accInv.Task().Link(), }, @@ -202,11 +202,11 @@ func doAllocate( nodeProvider piriclient.Provider, agentStore agent.Store, space did.DID, - blob blobcaps.Blob, + blob blobcmds.Blob, cause cid.Cid, proofStore ucanlib.ProofStore, logger *zap.Logger, -) (routing.StorageProviderInfo, ucan.Invocation, ucan.Receipt, blobcaps.AllocateOK, error) { +) (routing.StorageProviderInfo, ucan.Invocation, ucan.Receipt, blobcmds.AllocateOK, error) { log := logger.With(zap.Stringer("cause", cause)) log.Debug("doing allocation") @@ -215,7 +215,7 @@ func doAllocate( candidate, err := router.SelectStorageProvider(ctx, blob, routing.WithExclusions(exclusions...)) if err != nil { log.Error("failed to select storage node", zap.Error(err)) - return routing.StorageProviderInfo{}, nil, nil, blobcaps.AllocateOK{}, err + return routing.StorageProviderInfo{}, nil, nil, blobcmds.AllocateOK{}, err } log := logger.With(zap.Stringer("candidate", candidate.ID), zap.String("endpoint", candidate.Endpoint.String())) log.Debug("selected storage provider candidate") @@ -223,7 +223,7 @@ func doAllocate( client, err := nodeProvider.Client(candidate.ID, candidate.Endpoint) if err != nil { log.Error("failed to create piri node", zap.Error(err)) - return routing.StorageProviderInfo{}, nil, nil, blobcaps.AllocateOK{}, err + return routing.StorageProviderInfo{}, nil, nil, blobcmds.AllocateOK{}, err } res, inv, rcpt, err := client.Allocate(ctx, &piriclient.AllocateRequest{ @@ -259,7 +259,7 @@ func writeAgentMessage(ctx context.Context, agentStore agent.Store, invs []ucan. // Generates an invocation to put the blob to the storage provider. It MAY // return a receipt if the allocation result indicates that the provider already // has the blob. -func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.AllocateOK, logger *zap.Logger) (ucan.Invocation, ucan.Receipt, error) { +func genPut(blob blobcmds.Blob, allocInv ucan.Invocation, allocOK blobcmds.AllocateOK, logger *zap.Logger) (ucan.Invocation, ucan.Receipt, error) { log := logger log.Debug("generating put invocation") @@ -271,10 +271,10 @@ func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.Alloc return nil, nil, err } - putInv, err := httpcaps.Put.Invoke( + putInv, err := httpcmds.Put.Invoke( blobProvider, blobProvider.DID(), - &httpcaps.PutArguments{ + &httpcmds.PutArguments{ Body: blob, Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, @@ -296,7 +296,7 @@ func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.Alloc ), ) if err != nil { - return nil, nil, fmt.Errorf("invoking %q: %w", httpcaps.Put, err) + return nil, nil, fmt.Errorf("invoking %q: %w", httpcmds.Put, err) } var putRcpt ucan.Receipt @@ -308,10 +308,10 @@ func genPut(blob blobcaps.Blob, allocInv ucan.Invocation, allocOK blobcaps.Alloc putRcpt, err = receipt.IssueOK( blobProvider, putInv.Task().Link(), - &httpcaps.PutOK{}, + &httpcmds.PutOK{}, ) if err != nil { - return nil, nil, fmt.Errorf("issuing %q receipt: %w", httpcaps.Put, err) + return nil, nil, fmt.Errorf("issuing %q receipt: %w", httpcmds.Put, err) } } @@ -337,7 +337,7 @@ func maybeAccept( nodeProvider piriclient.Provider, providerInfo routing.StorageProviderInfo, space did.DID, - blob blobcaps.Blob, + blob blobcmds.Blob, cause cid.Cid, // original /space/blob/add task putInv ucan.Invocation, putRcpt ucan.Receipt, @@ -370,14 +370,21 @@ func maybeAccept( // If put has already succeeded, we can execute `/blob/accept` right away. if putRcpt != nil && putRcpt.Out().IsOK() { - res, inv, rcpt, err := c.Accept(ctx, &accReq, proofStore, invocation.WithNoNonce()) + res, inv, rcpt, meta, err := c.Accept(ctx, &accReq, proofStore, invocation.WithNoNonce()) if err != nil { log.Error("failed to execute accept on piri", zap.Error(err)) return nil, nil, err } log.Debug("blob accepted", zap.Stringer("site", res.Site)) - err = writeAgentMessage(ctx, agentStore, []ucan.Invocation{inv}, []ucan.Receipt{rcpt}) + invs := []ucan.Invocation{inv} + rcpts := []ucan.Receipt{rcpt} + if meta != nil { + invs = append(invs, meta.Invocations()...) + rcpts = append(rcpts, meta.Receipts()...) + } + + err = writeAgentMessage(ctx, agentStore, invs, rcpts) if err != nil { log.Error("failed to write agent message for accept", zap.Error(err)) return nil, nil, err diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go index 48f60ec..5d76e3b 100644 --- a/pkg/service/handlers/blob_add_test.go +++ b/pkg/service/handlers/blob_add_test.go @@ -11,9 +11,9 @@ import ( "time" "github.com/fil-forge/libforge/commands" - accesscaps "github.com/fil-forge/libforge/commands/access" - blobcaps "github.com/fil-forge/libforge/commands/blob" - httpcaps "github.com/fil-forge/libforge/commands/http" + accesscmds "github.com/fil-forge/libforge/commands/access" + blobcmds "github.com/fil-forge/libforge/commands/blob" + httpcmds "github.com/fil-forge/libforge/commands/http" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/identity" @@ -117,8 +117,8 @@ func newMockPiriServer( t *testing.T, storageProvider principal.Signer, uploadService principal.Signer, - allocateOK *blobcaps.AllocateOK, - acceptOK *blobcaps.AcceptOK, + allocateOK *blobcmds.AllocateOK, + acceptOK *blobcmds.AcceptOK, ) *httptest.Server { t.Helper() @@ -136,16 +136,16 @@ func newMockPiriServer( server.WithValidationOptions(validator.WithDIDVerifierResolver(resolveDIDKey)), ) - srv.Handle(blobcaps.Allocate.Command, bindexec.NewHandler(func( - req *bindexec.Request[*blobcaps.AllocateArguments], - res *bindexec.Response[*blobcaps.AllocateOK], + srv.Handle(blobcmds.Allocate.Command, bindexec.NewHandler(func( + req *bindexec.Request[*blobcmds.AllocateArguments], + res *bindexec.Response[*blobcmds.AllocateOK], ) error { return res.SetSuccess(allocateOK) })) - srv.Handle(blobcaps.Accept.Command, bindexec.NewHandler(func( - req *bindexec.Request[*blobcaps.AcceptArguments], - res *bindexec.Response[*blobcaps.AcceptOK], + srv.Handle(blobcmds.Accept.Command, bindexec.NewHandler(func( + req *bindexec.Request[*blobcmds.AcceptArguments], + res *bindexec.Response[*blobcmds.AcceptOK], ) error { return res.SetSuccess(acceptOK) })) @@ -165,11 +165,11 @@ func TestBlobAddHandler(t *testing.T) { deps := newBlobAddTestDeps(t, uploadService, logger) space := testutil.RandomSigner(t) - args := blobcaps.AddArguments{ - Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + args := blobcmds.AddArguments{ + Blob: blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, } - inv, err := blobcaps.Add.Invoke( + inv, err := blobcmds.Add.Invoke( testutil.Alice, space.DID(), &args, @@ -189,7 +189,7 @@ func TestBlobAddHandler(t *testing.T) { var model edm.ErrorModel require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) - require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) + require.Equal(t, accesscmds.InsufficientStorageErrorName, model.Name()) }) t.Run("no candidates available", func(t *testing.T) { @@ -199,11 +199,11 @@ func TestBlobAddHandler(t *testing.T) { provisionSpace(t, deps, uploadService, space.DID()) // No storage providers in spStore — the router will return ErrCandidateUnavailable. - args := blobcaps.AddArguments{ - Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + args := blobcmds.AddArguments{ + Blob: blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, } - inv, err := blobcaps.Add.Invoke( + inv, err := blobcmds.Add.Invoke( testutil.Alice, space.DID(), &args, @@ -238,11 +238,11 @@ func TestBlobAddHandler(t *testing.T) { err := deps.spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, nil) require.NoError(t, err) - args := blobcaps.AddArguments{ - Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + args := blobcmds.AddArguments{ + Blob: blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, } - inv, err := blobcaps.Add.Invoke( + inv, err := blobcmds.Add.Invoke( testutil.Alice, space.DID(), &args, @@ -273,9 +273,9 @@ func TestBlobAddHandler(t *testing.T) { storageProvider := testutil.RandomSigner(t) putURL := testutil.Must(url.Parse("https://storage.example.com/put"))(t) - allocateOK := &blobcaps.AllocateOK{ + allocateOK := &blobcmds.AllocateOK{ Size: 1024, - Address: &blobcaps.BlobAddress{ + Address: &blobcmds.BlobAddress{ URL: commands.CborURL(*putURL), Headers: map[string]string{}, Expires: time.Now().Add(time.Hour).Unix(), @@ -283,7 +283,7 @@ func TestBlobAddHandler(t *testing.T) { } // Accept handler is registered but should not be invoked when an Address is // returned — the put receipt isn't issued, so maybeAccept skips Accept. - acceptOK := &blobcaps.AcceptOK{Site: testutil.RandomCID(t)} + acceptOK := &blobcmds.AcceptOK{Site: testutil.RandomCID(t)} piriSrv := newMockPiriServer(t, storageProvider, uploadService, allocateOK, acceptOK) piriURL := testutil.Must(url.Parse(piriSrv.URL))(t) @@ -291,11 +291,11 @@ func TestBlobAddHandler(t *testing.T) { err := deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil) require.NoError(t, err) - args := blobcaps.AddArguments{ - Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + args := blobcmds.AddArguments{ + Blob: blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, } - inv, err := blobcaps.Add.Invoke( + inv, err := blobcmds.Add.Invoke( testutil.Alice, space.DID(), &args, @@ -306,8 +306,8 @@ func TestBlobAddHandler(t *testing.T) { // Authorize the upload service to invoke /blob/allocate and /blob/accept // on the space. This is the proof chain the upload service forwards to the // storage provider. - allocProof := testutil.Must(blobcaps.Allocate.Delegate(space, uploadService.DID(), space.DID()))(t) - acceptProof := testutil.Must(blobcaps.Accept.Delegate(space, uploadService.DID(), space.DID()))(t) + allocProof := testutil.Must(blobcmds.Allocate.Delegate(space, uploadService.DID(), space.DID()))(t) + acceptProof := testutil.Must(blobcmds.Accept.Delegate(space, uploadService.DID(), space.DID()))(t) req := execution.NewRequest(ctx, inv, execution.WithDelegations(allocProof, acceptProof)) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) @@ -332,8 +332,8 @@ func TestBlobAddHandler(t *testing.T) { storageProvider := testutil.RandomSigner(t) // No address signals the blob is already on the provider — the handler // then issues the put receipt itself and proceeds to Accept on piri. - allocateOK := &blobcaps.AllocateOK{Size: 1024, Address: nil} - acceptOK := &blobcaps.AcceptOK{Site: testutil.RandomCID(t)} + allocateOK := &blobcmds.AllocateOK{Size: 1024, Address: nil} + acceptOK := &blobcmds.AcceptOK{Site: testutil.RandomCID(t)} piriSrv := newMockPiriServer(t, storageProvider, uploadService, allocateOK, acceptOK) piriURL := testutil.Must(url.Parse(piriSrv.URL))(t) @@ -341,11 +341,11 @@ func TestBlobAddHandler(t *testing.T) { err := deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil) require.NoError(t, err) - args := blobcaps.AddArguments{ - Blob: blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, + args := blobcmds.AddArguments{ + Blob: blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: 1024}, } - inv, err := blobcaps.Add.Invoke( + inv, err := blobcmds.Add.Invoke( testutil.Alice, space.DID(), &args, @@ -353,8 +353,8 @@ func TestBlobAddHandler(t *testing.T) { ) require.NoError(t, err) - allocProof := testutil.Must(blobcaps.Allocate.Delegate(space, uploadService.DID(), space.DID()))(t) - acceptProof := testutil.Must(blobcaps.Accept.Delegate(space, uploadService.DID(), space.DID()))(t) + allocProof := testutil.Must(blobcmds.Allocate.Delegate(space, uploadService.DID(), space.DID()))(t) + acceptProof := testutil.Must(blobcmds.Accept.Delegate(space, uploadService.DID(), space.DID()))(t) req := execution.NewRequest(ctx, inv, execution.WithDelegations(allocProof, acceptProof)) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) @@ -379,30 +379,30 @@ func TestBlobAddHandler(t *testing.T) { storageProvider := testutil.RandomSigner(t) digest := testutil.RandomMultihash(t) - blob := blobcaps.Blob{Digest: digest, Size: 1024} + blob := blobcmds.Blob{Digest: digest, Size: 1024} // Build the chain that the handler will walk back through: // addRcpt → accInv/accRcpt → putInv/putRcpt → allocInv/allocRcpt blobProvider := deriveBlobProvider(t, digest) // /blob/allocate - allocInv := testutil.Must(blobcaps.Allocate.Invoke( + allocInv := testutil.Must(blobcmds.Allocate.Invoke( uploadService, space.DID(), - &blobcaps.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, + &blobcmds.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, invocation.WithAudience(storageProvider.DID()), ))(t) allocRcpt := testutil.Must(receipt.IssueOK( storageProvider, allocInv.Task().Link(), - &blobcaps.AllocateOK{Size: blob.Size}, + &blobcmds.AllocateOK{Size: blob.Size}, ))(t) // /http/put — issued by the principal derived from the blob digest. - putInv := testutil.Must(httpcaps.Put.Invoke( + putInv := testutil.Must(httpcmds.Put.Invoke( blobProvider, blobProvider.DID(), - &httpcaps.PutArguments{ + &httpcmds.PutArguments{ Body: blob, Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, @@ -411,14 +411,14 @@ func TestBlobAddHandler(t *testing.T) { putRcpt := testutil.Must(receipt.IssueOK( blobProvider, putInv.Task().Link(), - &httpcaps.PutOK{}, + &httpcmds.PutOK{}, ))(t) // /blob/accept - accInv := testutil.Must(blobcaps.Accept.Invoke( + accInv := testutil.Must(blobcmds.Accept.Invoke( uploadService, space.DID(), - &blobcaps.AcceptArguments{ + &blobcmds.AcceptArguments{ Blob: blob, Put: promise.AwaitOK{Task: putInv.Task().Link()}, }, @@ -427,21 +427,21 @@ func TestBlobAddHandler(t *testing.T) { accRcpt := testutil.Must(receipt.IssueOK( storageProvider, accInv.Task().Link(), - &blobcaps.AcceptOK{Site: testutil.RandomCID(t)}, + &blobcmds.AcceptOK{Site: testutil.RandomCID(t)}, ))(t) // The original /space/blob/add invocation and receipt — its receipt's // task CID is what gets stored in the registry as the cause. - prevAddInv := testutil.Must(blobcaps.Add.Invoke( + prevAddInv := testutil.Must(blobcmds.Add.Invoke( testutil.Alice, space.DID(), - &blobcaps.AddArguments{Blob: blob}, + &blobcmds.AddArguments{Blob: blob}, invocation.WithAudience(uploadService.DID()), ))(t) prevAddRcpt := testutil.Must(receipt.IssueOK( uploadService, prevAddInv.Task().Link(), - &blobcaps.AddOK{ + &blobcmds.AddOK{ Site: promise.AwaitOK{Task: accInv.Task().Link()}, }, ))(t) @@ -458,10 +458,10 @@ func TestBlobAddHandler(t *testing.T) { // Re-invoke /blob/add for the same blob/space — the handler should hit // the already-registered short-circuit, walk the chain, and return the // stored AddOK without contacting any storage provider. - inv := testutil.Must(blobcaps.Add.Invoke( + inv := testutil.Must(blobcmds.Add.Invoke( testutil.Alice, space.DID(), - &blobcaps.AddArguments{Blob: blob}, + &blobcmds.AddArguments{Blob: blob}, invocation.WithAudience(uploadService.DID()), ))(t) @@ -477,7 +477,7 @@ func TestBlobAddHandler(t *testing.T) { require.NotNil(t, o) // The returned AddOK should match the one from the prior receipt. - var gotAddOK blobcaps.AddOK + var gotAddOK blobcmds.AddOK require.NoError(t, gotAddOK.UnmarshalCBOR(bytes.NewReader(o))) require.Equal(t, accInv.Task().Link(), gotAddOK.Site.Task) diff --git a/pkg/service/handlers/blob_list.go b/pkg/service/handlers/blob_list.go index 5f117ab..742da71 100644 --- a/pkg/service/handlers/blob_list.go +++ b/pkg/service/handlers/blob_list.go @@ -3,19 +3,19 @@ package handlers import ( "fmt" - blobcaps "github.com/fil-forge/libforge/commands/blob" + blobcmds "github.com/fil-forge/libforge/commands/blob" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" "github.com/fil-forge/ucantone/execution/bindexec" "go.uber.org/zap" ) func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", blobcaps.List)) + log := logger.With(zap.Stringer("handler", blobcmds.List)) return Handler{ - Command: blobcaps.List.Command, + Command: blobcmds.List.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*blobcaps.ListArguments], - res *bindexec.Response[*blobcaps.ListOK], + req *bindexec.Request[*blobcmds.ListArguments], + res *bindexec.Response[*blobcmds.ListOK], ) error { args := req.Task().Arguments() space := req.Invocation().Subject() @@ -38,15 +38,15 @@ func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Han return fmt.Errorf("listing blobs: %w", err) } - results := make([]blobcaps.ListBlobItem, 0, len(page.Results)) + results := make([]blobcmds.ListBlobItem, 0, len(page.Results)) for _, r := range page.Results { - results = append(results, blobcaps.ListBlobItem{ + results = append(results, blobcmds.ListBlobItem{ Blob: r.Blob, InsertedAt: r.InsertedAt.Unix(), }) } - return res.SetSuccess(&blobcaps.ListOK{ + return res.SetSuccess(&blobcmds.ListOK{ Cursor: page.Cursor, Results: results, }) diff --git a/pkg/service/handlers/blob_list_test.go b/pkg/service/handlers/blob_list_test.go index 2b41aad..8013045 100644 --- a/pkg/service/handlers/blob_list_test.go +++ b/pkg/service/handlers/blob_list_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - blobcaps "github.com/fil-forge/libforge/commands/blob" + blobcmds "github.com/fil-forge/libforge/commands/blob" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/service/handlers" @@ -39,10 +39,10 @@ func invokeBlobList( agent principal.Signer, uploadService principal.Signer, space principal.Signer, - args *blobcaps.ListArguments, + args *blobcmds.ListArguments, ) (execution.Request, *execution.ExecResponse) { t.Helper() - inv, err := blobcaps.List.Invoke( + inv, err := blobcmds.List.Invoke( agent, space.DID(), args, @@ -69,7 +69,7 @@ func TestBlobListHandler(t *testing.T) { space := testutil.RandomSigner(t) - req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{}) + req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcmds.ListArguments{}) err := handler.Handler(req, res) require.NoError(t, err) @@ -78,7 +78,7 @@ func TestBlobListHandler(t *testing.T) { require.Nil(t, x) require.NotNil(t, o) - var ok blobcaps.ListOK + var ok blobcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) }) @@ -90,19 +90,19 @@ func TestBlobListHandler(t *testing.T) { space := testutil.RandomSigner(t) require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) - blob1 := blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 100} - blob2 := blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 200} + blob1 := blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: 100} + blob2 := blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: 200} require.NoError(t, blobReg.Register(ctx, space.DID(), blob1, testutil.RandomCID(t))) require.NoError(t, blobReg.Register(ctx, space.DID(), blob2, testutil.RandomCID(t))) - req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{}) + req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcmds.ListArguments{}) err := handler.Handler(req, res) require.NoError(t, err) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok blobcaps.ListOK + var ok blobcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) }) @@ -117,20 +117,20 @@ func TestBlobListHandler(t *testing.T) { for i := range 3 { require.NoError(t, blobReg.Register( ctx, space.DID(), - blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: uint64(i + 1)}, + blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: uint64(i + 1)}, testutil.RandomCID(t), )) } size := uint64(2) - req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Size: &size}) + req, res := invokeBlobList(t, ctx, alice, uploadService, space, &blobcmds.ListArguments{Size: &size}) err := handler.Handler(req, res) require.NoError(t, err) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok blobcaps.ListOK + var ok blobcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) require.NotNil(t, ok.Cursor) @@ -146,30 +146,30 @@ func TestBlobListHandler(t *testing.T) { for i := range 3 { require.NoError(t, blobReg.Register( ctx, space.DID(), - blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: uint64(i + 1)}, + blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: uint64(i + 1)}, testutil.RandomCID(t), )) } size := uint64(1) - req1, res1 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Size: &size}) + req1, res1 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcmds.ListArguments{Size: &size}) require.NoError(t, handler.Handler(req1, res1)) o1, x := res1.Receipt().Out().Unpack() require.Nil(t, x) - var ok1 blobcaps.ListOK + var ok1 blobcmds.ListOK require.NoError(t, ok1.UnmarshalCBOR(bytes.NewReader(o1))) require.Len(t, ok1.Results, 1) require.NotNil(t, ok1.Cursor) // Second page using cursor. cursor := *ok1.Cursor - req2, res2 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcaps.ListArguments{Cursor: &cursor, Size: &size}) + req2, res2 := invokeBlobList(t, ctx, alice, uploadService, space, &blobcmds.ListArguments{Cursor: &cursor, Size: &size}) require.NoError(t, handler.Handler(req2, res2)) o2, x := res2.Receipt().Out().Unpack() require.Nil(t, x) - var ok2 blobcaps.ListOK + var ok2 blobcmds.ListOK require.NoError(t, ok2.UnmarshalCBOR(bytes.NewReader(o2))) require.Len(t, ok2.Results, 1) require.NotEqual(t, ok1.Results[0].Blob.Digest.HexString(), ok2.Results[0].Blob.Digest.HexString()) @@ -185,17 +185,17 @@ func TestBlobListHandler(t *testing.T) { require.NoError(t, blobReg.Register( ctx, space1.DID(), - blobcaps.Blob{Digest: testutil.RandomMultihash(t), Size: 100}, + blobcmds.Blob{Digest: testutil.RandomMultihash(t), Size: 100}, testutil.RandomCID(t), )) // Query space2 — should be empty. - req, res := invokeBlobList(t, ctx, alice, uploadService, space2, &blobcaps.ListArguments{}) + req, res := invokeBlobList(t, ctx, alice, uploadService, space2, &blobcmds.ListArguments{}) require.NoError(t, handler.Handler(req, res)) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok blobcaps.ListOK + var ok blobcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) }) diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go index 8170333..ffaa40a 100644 --- a/pkg/service/handlers/index_add.go +++ b/pkg/service/handlers/index_add.go @@ -5,8 +5,8 @@ import ( "go.uber.org/zap" - accesscaps "github.com/fil-forge/libforge/commands/access" - indexcaps "github.com/fil-forge/libforge/commands/index" + accesscmds "github.com/fil-forge/libforge/commands/access" + indexcmds "github.com/fil-forge/libforge/commands/index" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/indexerclient" @@ -17,12 +17,12 @@ import ( ) func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", indexcaps.Add)) + log := logger.With(zap.Stringer("handler", indexcmds.Add)) return Handler{ - Command: indexcaps.Add.Command, + Command: indexcmds.Add.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*indexcaps.AddArguments], - res *bindexec.Response[*indexcaps.AddOK], + req *bindexec.Request[*indexcmds.AddArguments], + res *bindexec.Response[*indexcmds.AddOK], ) error { args := req.Task().Arguments() space := req.Invocation().Subject() @@ -41,7 +41,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser } if len(provs) == 0 { log.Warn("space has no service provider") - return res.SetFailure(errors.New(accesscaps.InsufficientStorageErrorName, "space has no service provider")) + return res.SetFailure(errors.New(accesscmds.InsufficientStorageErrorName, "space has no service provider")) } // Ensure the index is stored in the agent's space @@ -49,7 +49,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser if err != nil { if errors.Is(err, blobregistry.ErrEntryNotFound) { log.Warn("index not found in space") - return res.SetFailure(indexcaps.ErrIndexNotFound) + return res.SetFailure(indexcmds.ErrIndexNotFound) } log.Error("failed to get index from blob registry", zap.Error(err)) return err @@ -65,7 +65,7 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser return fmt.Errorf("publishing index claim: %w", err) } - return res.SetSuccess(&indexcaps.AddOK{}) + return res.SetSuccess(&indexcmds.AddOK{}) }), } } diff --git a/pkg/service/handlers/index_add_test.go b/pkg/service/handlers/index_add_test.go index d763d7f..bf65c2b 100644 --- a/pkg/service/handlers/index_add_test.go +++ b/pkg/service/handlers/index_add_test.go @@ -8,11 +8,11 @@ import ( "net/url" "testing" - accesscaps "github.com/fil-forge/libforge/commands/access" - assertcaps "github.com/fil-forge/libforge/commands/assert" - blobcaps "github.com/fil-forge/libforge/commands/blob" - contentcaps "github.com/fil-forge/libforge/commands/content" - indexcaps "github.com/fil-forge/libforge/commands/index" + accesscmds "github.com/fil-forge/libforge/commands/access" + assertcmds "github.com/fil-forge/libforge/commands/assert" + blobcmds "github.com/fil-forge/libforge/commands/blob" + contentcmds "github.com/fil-forge/libforge/commands/content" + indexcmds "github.com/fil-forge/libforge/commands/index" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/identity" @@ -45,7 +45,7 @@ func newMockIndexerServer( t *testing.T, indexerSigner principal.Signer, uploadService principal.Signer, - indexOK *assertcaps.IndexOK, + indexOK *assertcmds.IndexOK, ) *httptest.Server { t.Helper() @@ -63,9 +63,9 @@ func newMockIndexerServer( server.WithValidationOptions(validator.WithDIDVerifierResolver(resolveDIDKey)), ) - srv.Handle(assertcaps.Index.Command, bindexec.NewHandler(func( - req *bindexec.Request[*assertcaps.IndexArguments], - res *bindexec.Response[*assertcaps.IndexOK], + srv.Handle(assertcmds.Index.Command, bindexec.NewHandler(func( + req *bindexec.Request[*assertcmds.IndexArguments], + res *bindexec.Response[*assertcmds.IndexOK], ) error { return res.SetSuccess(indexOK) })) @@ -87,10 +87,10 @@ func invokeIndexAdd( reqOpts ...execution.RequestOption, ) (execution.Request, *execution.ExecResponse) { t.Helper() - inv, err := indexcaps.Add.Invoke( + inv, err := indexcmds.Add.Invoke( agent, space.DID(), - &indexcaps.AddArguments{Index: index}, + &indexcmds.AddArguments{Index: index}, invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -129,7 +129,7 @@ func TestIndexAddHandler(t *testing.T) { var model edm.ErrorModel require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) - require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) + require.Equal(t, accesscmds.InsufficientStorageErrorName, model.Name()) }) t.Run("index not found in space", func(t *testing.T) { @@ -157,7 +157,7 @@ func TestIndexAddHandler(t *testing.T) { var model edm.ErrorModel require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) - require.Equal(t, indexcaps.IndexNotFoundErrorName, model.Name()) + require.Equal(t, indexcmds.IndexNotFoundErrorName, model.Name()) }) t.Run("retrieval auth supplied publishes index claim", func(t *testing.T) { @@ -173,12 +173,12 @@ func TestIndexAddHandler(t *testing.T) { require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) indexCID := testutil.RandomCID(t) - indexBlob := blobcaps.Blob{Digest: indexCID.Hash(), Size: 512} + indexBlob := blobcmds.Blob{Digest: indexCID.Hash(), Size: 512} require.NoError(t, blobReg.Register(ctx, space.DID(), indexBlob, testutil.RandomCID(t))) // Stand up a mock indexer that returns success on /assert/index. indexerSigner := testutil.RandomSigner(t) - indexerSrv := newMockIndexerServer(t, indexerSigner, uploadService, &assertcaps.IndexOK{}) + indexerSrv := newMockIndexerServer(t, indexerSigner, uploadService, &assertcmds.IndexOK{}) indexerURL := testutil.Must(url.Parse(indexerSrv.URL))(t) indexerCli, err := indexerclient.New(indexerURL, indexerSigner.DID(), uploadService, logger) require.NoError(t, err) @@ -188,7 +188,7 @@ func TestIndexAddHandler(t *testing.T) { // /content/retrieve delegation from space → upload service so the // handler can build a proof chain that authorizes the indexer to // retrieve the index blob. - retrievalAuth, err := contentcaps.Retrieve.Delegate(space, uploadService.DID(), space.DID()) + retrievalAuth, err := contentcmds.Retrieve.Delegate(space, uploadService.DID(), space.DID()) require.NoError(t, err) req, res := invokeIndexAdd(t, ctx, alice, uploadService, space, indexCID, @@ -214,11 +214,11 @@ func TestIndexAddHandler(t *testing.T) { require.NoError(t, consumerStore.Add(ctx, uploadService.DID(), space.DID(), aliceAccount, "sub-1", testutil.RandomCID(t))) indexCID := testutil.RandomCID(t) - indexBlob := blobcaps.Blob{Digest: indexCID.Hash(), Size: 512} + indexBlob := blobcmds.Blob{Digest: indexCID.Hash(), Size: 512} require.NoError(t, blobReg.Register(ctx, space.DID(), indexBlob, testutil.RandomCID(t))) indexerSigner := testutil.RandomSigner(t) - indexerSrv := newMockIndexerServer(t, indexerSigner, uploadService, &assertcaps.IndexOK{}) + indexerSrv := newMockIndexerServer(t, indexerSigner, uploadService, &assertcmds.IndexOK{}) indexerURL := testutil.Must(url.Parse(indexerSrv.URL))(t) indexerCli, err := indexerclient.New(indexerURL, indexerSigner.DID(), uploadService, logger) require.NoError(t, err) diff --git a/pkg/service/handlers/provider_add.go b/pkg/service/handlers/provider_add.go index 45d8fa2..1845a19 100644 --- a/pkg/service/handlers/provider_add.go +++ b/pkg/service/handlers/provider_add.go @@ -3,7 +3,7 @@ package handlers import ( "fmt" - providercaps "github.com/fil-forge/libforge/commands/provider" + providercmds "github.com/fil-forge/libforge/commands/provider" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/pkg/billing" @@ -15,18 +15,18 @@ import ( ) func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", providercaps.Add)) + log := logger.With(zap.Stringer("handler", providercmds.Add)) return Handler{ - Command: providercaps.Add.Command, + Command: providercmds.Add.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*providercaps.AddArguments], - res *bindexec.Response[*providercaps.AddOK], + req *bindexec.Request[*providercmds.AddArguments], + res *bindexec.Response[*providercmds.AddOK], ) error { args := req.Task().Arguments() account, err := didmailto.Parse(req.Invocation().Subject().String()) if err != nil { log.Warn("invalid account", zap.Stringer("account", req.Invocation().Subject())) - return res.SetFailure(errors.New(providercaps.InvalidAccountErrorName, "invalid account DID: %v", err)) + return res.SetFailure(errors.New(providercmds.InvalidAccountErrorName, "invalid account DID: %v", err)) } serviceProvider := args.Provider space := args.Consumer @@ -46,7 +46,7 @@ func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSv if err != nil { if errors.Is(err, billing.ErrMissingPaymentPlan) { log.Warn("account does not have an active payment plan") - return res.SetFailure(providercaps.ErrAccountPlanMissing) + return res.SetFailure(providercmds.ErrAccountPlanMissing) } log.Error("failed to check payment plan", zap.Error(err)) return fmt.Errorf("checking payment plan: %w", err) @@ -69,7 +69,7 @@ func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSv } log.Debug("service provisioned successfully", zap.String("subscription", sub)) - return res.SetSuccess(&providercaps.AddOK{ID: sub}) + return res.SetSuccess(&providercmds.AddOK{ID: sub}) }), } } diff --git a/pkg/service/handlers/provider_add_test.go b/pkg/service/handlers/provider_add_test.go index 28e4a46..e13608a 100644 --- a/pkg/service/handlers/provider_add_test.go +++ b/pkg/service/handlers/provider_add_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - providercaps "github.com/fil-forge/libforge/commands/provider" + providercmds "github.com/fil-forge/libforge/commands/provider" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/internal/testutil" @@ -54,10 +54,10 @@ func invokeProviderAdd( agent principal.Signer, uploadService principal.Signer, account did.DID, - args *providercaps.AddArguments, + args *providercmds.AddArguments, ) (execution.Request, *execution.ExecResponse) { t.Helper() - inv, err := providercaps.Add.Invoke( + inv, err := providercmds.Add.Invoke( agent, account, args, @@ -92,7 +92,7 @@ func TestProviderAddHandler(t *testing.T) { space := testutil.RandomSigner(t) agent := testutil.RandomSigner(t) req, res := invokeProviderAdd(t, ctx, agent, uploadService, account, - &providercaps.AddArguments{ + &providercmds.AddArguments{ Provider: serviceProvider.DID(), Consumer: space.DID(), }, @@ -105,7 +105,7 @@ func TestProviderAddHandler(t *testing.T) { require.Nil(t, x) require.NotNil(t, o) - var ok providercaps.AddOK + var ok providercmds.AddOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.NotEmpty(t, ok.ID) }) @@ -124,7 +124,7 @@ func TestProviderAddHandler(t *testing.T) { space := testutil.RandomSigner(t) agent := testutil.RandomSigner(t) req, res := invokeProviderAdd(t, ctx, agent, uploadService, account, - &providercaps.AddArguments{ + &providercmds.AddArguments{ Provider: serviceProvider.DID(), Consumer: space.DID(), }, @@ -135,7 +135,7 @@ func TestProviderAddHandler(t *testing.T) { o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok providercaps.AddOK + var ok providercmds.AddOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.NotEmpty(t, ok.ID) }) @@ -154,7 +154,7 @@ func TestProviderAddHandler(t *testing.T) { space := testutil.RandomSigner(t) agent := testutil.RandomSigner(t) req, res := invokeProviderAdd(t, ctx, agent, uploadService, notAMailto.DID(), - &providercaps.AddArguments{ + &providercmds.AddArguments{ Provider: serviceProvider.DID(), Consumer: space.DID(), }, @@ -168,7 +168,7 @@ func TestProviderAddHandler(t *testing.T) { var model edm.ErrorModel require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) - require.Equal(t, providercaps.InvalidAccountErrorName, model.Name()) + require.Equal(t, providercmds.InvalidAccountErrorName, model.Name()) }) t.Run("missing payment plan", func(t *testing.T) { @@ -185,7 +185,7 @@ func TestProviderAddHandler(t *testing.T) { space := testutil.RandomSigner(t) agent := testutil.RandomSigner(t) req, res := invokeProviderAdd(t, ctx, agent, uploadService, account, - &providercaps.AddArguments{ + &providercmds.AddArguments{ Provider: serviceProvider.DID(), Consumer: space.DID(), }, @@ -199,7 +199,7 @@ func TestProviderAddHandler(t *testing.T) { var model edm.ErrorModel require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) - require.Equal(t, providercaps.AccountPlanMissingErrorName, model.Name()) + require.Equal(t, providercmds.AccountPlanMissingErrorName, model.Name()) }) t.Run("provider not allowed", func(t *testing.T) { @@ -217,7 +217,7 @@ func TestProviderAddHandler(t *testing.T) { space := testutil.RandomSigner(t) agent := testutil.RandomSigner(t) req, res := invokeProviderAdd(t, ctx, agent, uploadService, account, - &providercaps.AddArguments{ + &providercmds.AddArguments{ Provider: otherProvider.DID(), Consumer: space.DID(), }, diff --git a/pkg/service/handlers/space_info.go b/pkg/service/handlers/space_info.go index 472b594..e70bff0 100644 --- a/pkg/service/handlers/space_info.go +++ b/pkg/service/handlers/space_info.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - spacecaps "github.com/fil-forge/libforge/commands/space" + spacecmds "github.com/fil-forge/libforge/commands/space" "github.com/fil-forge/sprue/pkg/provisioning" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/execution/bindexec" @@ -13,12 +13,12 @@ import ( // This handler returns info about a space, including its providers. func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", spacecaps.Info)) + log := logger.With(zap.Stringer("handler", spacecmds.Info)) return Handler{ - Command: spacecaps.Info.Command, + Command: spacecmds.Info.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*spacecaps.InfoArguments], - res *bindexec.Response[*spacecaps.InfoOK], + req *bindexec.Request[*spacecmds.InfoArguments], + res *bindexec.Response[*spacecmds.InfoOK], ) error { space := req.Invocation().Subject() log := log.With(zap.Stringer("space", space)) @@ -26,7 +26,7 @@ func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logg if !strings.HasPrefix(space.String(), "did:key:") { log.Warn("non-did:key space info requested") - return res.SetFailure(errors.New(spacecaps.UnknownSpaceErrorName, "can only get info for did:key spaces")) + return res.SetFailure(errors.New(spacecmds.UnknownSpaceErrorName, "can only get info for did:key spaces")) } providers, err := provisioningSvc.ListServiceProviders(req.Context(), space) @@ -35,7 +35,7 @@ func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logg return fmt.Errorf("listing service providers: %w", err) } - return res.SetSuccess(&spacecaps.InfoOK{Providers: providers}) + return res.SetSuccess(&spacecmds.InfoOK{Providers: providers}) }), } } diff --git a/pkg/service/handlers/space_info_test.go b/pkg/service/handlers/space_info_test.go index 0a7b7d5..1983b36 100644 --- a/pkg/service/handlers/space_info_test.go +++ b/pkg/service/handlers/space_info_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - spacecaps "github.com/fil-forge/libforge/commands/space" + spacecmds "github.com/fil-forge/libforge/commands/space" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/provisioning" @@ -31,10 +31,10 @@ func invokeSpaceInfo( space did.DID, ) (execution.Request, *execution.ExecResponse) { t.Helper() - inv, err := spacecaps.Info.Invoke( + inv, err := spacecmds.Info.Invoke( agent, space, - &spacecaps.InfoArguments{}, + &spacecmds.InfoArguments{}, invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -76,7 +76,7 @@ func TestSpaceInfoHandler(t *testing.T) { require.Nil(t, x) require.NotNil(t, o) - var ok spacecaps.InfoOK + var ok spacecmds.InfoOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Providers, 1) require.Equal(t, uploadService.DID(), ok.Providers[0]) @@ -101,7 +101,7 @@ func TestSpaceInfoHandler(t *testing.T) { o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok spacecaps.InfoOK + var ok spacecmds.InfoOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Providers) }) @@ -128,6 +128,6 @@ func TestSpaceInfoHandler(t *testing.T) { var model edm.ErrorModel require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) - require.Equal(t, spacecaps.UnknownSpaceErrorName, model.Name()) + require.Equal(t, spacecmds.UnknownSpaceErrorName, model.Name()) }) } diff --git a/pkg/service/handlers/ucan_conclude.go b/pkg/service/handlers/ucan_conclude.go index 0cc0642..88d45f6 100644 --- a/pkg/service/handlers/ucan_conclude.go +++ b/pkg/service/handlers/ucan_conclude.go @@ -6,7 +6,7 @@ import ( "maps" "slices" - ucancaps "github.com/fil-forge/libforge/commands/ucan" + ucancmds "github.com/fil-forge/libforge/commands/ucan" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/store/agent" "github.com/fil-forge/ucantone/errors" @@ -32,13 +32,13 @@ type ConclusionHandler struct { // When it receives an /http/put receipt, it calls /blob/accept on piri // and stores the accept receipt for later retrieval. func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handlers map[ucan.Command]ConclusionHandlerFunc, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", ucancaps.Conclude)) + log := logger.With(zap.Stringer("handler", ucancmds.Conclude)) log.Info("registered conclude handlers", zap.Stringers("commands", slices.Collect(maps.Keys(handlers)))) return Handler{ - Command: ucancaps.Conclude.Command, + Command: ucancmds.Conclude.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*ucancaps.ConcludeArguments], - res *bindexec.Response[*ucancaps.ConcludeOK], + req *bindexec.Request[*ucancmds.ConcludeArguments], + res *bindexec.Response[*ucancmds.ConcludeOK], ) error { args := req.Task().Arguments() rcptRoot := args.Receipt @@ -57,7 +57,7 @@ func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handl } if rcpt == nil { log.Warn("receipt not found in invocation metadata") - return res.SetFailure(ucancaps.ErrConclusionReceiptNotFound) + return res.SetFailure(ucancmds.ErrConclusionReceiptNotFound) } log = log.With(zap.Stringer("task", rcpt.Ran())) @@ -76,7 +76,7 @@ func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handl // here, if it was a receipt for something we care about we would have // an invocation recorded. if errors.Is(err, agent.ErrInvocationNotFound) { - return res.SetSuccess(&ucancaps.ConcludeOK{}) + return res.SetSuccess(&ucancmds.ConcludeOK{}) } log.Error("failed to get invocation from agent store", zap.Error(err)) return fmt.Errorf("getting invocation: %w", err) @@ -95,7 +95,7 @@ func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handl } } - return res.SetSuccess(&ucancaps.ConcludeOK{}) + return res.SetSuccess(&ucancmds.ConcludeOK{}) }), } } diff --git a/pkg/service/handlers/ucan_conclude_blob_replica_transfer.go b/pkg/service/handlers/ucan_conclude_blob_replica_transfer.go index f90d735..aa1ed29 100644 --- a/pkg/service/handlers/ucan_conclude_blob_replica_transfer.go +++ b/pkg/service/handlers/ucan_conclude_blob_replica_transfer.go @@ -6,8 +6,8 @@ package handlers // "fmt" // "github.com/fil-forge/ucantone/errors" -// replicacaps "github.com/storacha/go-libstoracha/capabilities/blob/replica" -// ucancaps "github.com/storacha/go-libstoracha/capabilities/ucan" +// replicacmds "github.com/storacha/go-libstoracha/capabilities/blob/replica" +// ucancmds "github.com/storacha/go-libstoracha/capabilities/ucan" // "github.com/storacha/go-libstoracha/digestutil" // "github.com/storacha/go-ucanto/core/delegation" // "github.com/storacha/go-ucanto/core/invocation" diff --git a/pkg/service/handlers/ucan_conclude_blob_replica_transfer_test.go b/pkg/service/handlers/ucan_conclude_blob_replica_transfer_test.go index a91addb..0ea8f47 100644 --- a/pkg/service/handlers/ucan_conclude_blob_replica_transfer_test.go +++ b/pkg/service/handlers/ucan_conclude_blob_replica_transfer_test.go @@ -5,7 +5,7 @@ package handlers_test // "testing" // cidlink "github.com/ipld/go-ipld-prime/linking/cid" -// replicacaps "github.com/storacha/go-libstoracha/capabilities/blob/replica" +// replicacmds "github.com/storacha/go-libstoracha/capabilities/blob/replica" // "github.com/storacha/go-libstoracha/capabilities/types" // "github.com/storacha/go-ucanto/core/car" // "github.com/storacha/go-ucanto/core/delegation" diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index e65c9e4..3563006 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -5,9 +5,9 @@ import ( "context" "fmt" - blobcaps "github.com/fil-forge/libforge/commands/blob" - httpcaps "github.com/fil-forge/libforge/commands/http" - ucancaps "github.com/fil-forge/libforge/commands/ucan" + blobcmds "github.com/fil-forge/libforge/commands/blob" + httpcmds "github.com/fil-forge/libforge/commands/http" + ucancmds "github.com/fil-forge/libforge/commands/ucan" "github.com/fil-forge/libforge/digestutil" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/piriclient" @@ -28,16 +28,16 @@ func NewHTTPPutConcludeHandler( logger *zap.Logger, ) ConclusionHandler { log := logger.With( - zap.Stringer("handler", ucancaps.Conclude), - zap.Stringer("conclude", httpcaps.Put), + zap.Stringer("handler", ucancmds.Conclude), + zap.Stringer("conclude", httpcmds.Put), ) return ConclusionHandler{ - Command: httpcaps.Put.Command, + Command: httpcmds.Put.Command, Handler: func(ctx context.Context, putInv ucan.Invocation, putRcpt ucan.Receipt, meta ucan.Container) error { log := log.With(zap.Stringer("ran", putRcpt.Ran())) log.Debug("handling conclude") - var putArgs httpcaps.PutArguments + var putArgs httpcmds.PutArguments if err := putArgs.UnmarshalCBOR(bytes.NewReader(putInv.ArgumentsBytes())); err != nil { log.Error("failed to unmarshal HTTP PUT arguments", zap.Error(err)) return fmt.Errorf("unmarshaling HTTP PUT arguments: %w", err) @@ -64,7 +64,7 @@ func NewHTTPPutConcludeHandler( zap.Stringer("provider", provider), ) - var allocArgs blobcaps.AllocateArguments + var allocArgs blobcmds.AllocateArguments if err := allocArgs.UnmarshalCBOR(bytes.NewReader(allocInv.ArgumentsBytes())); err != nil { log.Error("failed to unmarshal allocate arguments", zap.Error(err)) return fmt.Errorf("unmarshaling allocate arguments: %w", err) diff --git a/pkg/service/handlers/ucan_conclude_http_put_test.go b/pkg/service/handlers/ucan_conclude_http_put_test.go index 60bbcc3..7bc9eab 100644 --- a/pkg/service/handlers/ucan_conclude_http_put_test.go +++ b/pkg/service/handlers/ucan_conclude_http_put_test.go @@ -4,8 +4,8 @@ import ( "net/url" "testing" - blobcaps "github.com/fil-forge/libforge/commands/blob" - httpcaps "github.com/fil-forge/libforge/commands/http" + blobcmds "github.com/fil-forge/libforge/commands/blob" + httpcmds "github.com/fil-forge/libforge/commands/http" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/piriclient" @@ -71,11 +71,11 @@ func TestHTTPPutConcludeHandler(t *testing.T) { nonExistentAllocTask := testutil.RandomCID(t) blobProvider := deriveBlobProvider(t, digest) - putInv, err := httpcaps.Put.Invoke( + putInv, err := httpcmds.Put.Invoke( blobProvider, blobProvider.DID(), - &httpcaps.PutArguments{ - Body: blobcaps.Blob{Digest: digest, Size: 1024}, + &httpcmds.PutArguments{ + Body: blobcmds.Blob{Digest: digest, Size: 1024}, Destination: promise.AwaitOK{Task: nonExistentAllocTask}, }, invocation.WithAudience(blobProvider.DID()), @@ -85,7 +85,7 @@ func TestHTTPPutConcludeHandler(t *testing.T) { putRcpt, err := receipt.IssueOK( blobProvider, putInv.Task().Link(), - &httpcaps.PutOK{}, + &httpcmds.PutOK{}, ) require.NoError(t, err) @@ -100,21 +100,21 @@ func TestHTTPPutConcludeHandler(t *testing.T) { storageProvider := testutil.RandomSigner(t) space := testutil.RandomSigner(t) digest := testutil.RandomMultihash(t) - blob := blobcaps.Blob{Digest: digest, Size: 1024} + blob := blobcmds.Blob{Digest: digest, Size: 1024} // Persist a /blob/allocate invocation for the storage provider, but do // NOT register that provider in the spStore — router lookup fails. - allocInv, err := blobcaps.Allocate.Invoke( + allocInv, err := blobcmds.Allocate.Invoke( uploadService, space.DID(), - &blobcaps.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, + &blobcmds.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, invocation.WithAudience(storageProvider.DID()), ) require.NoError(t, err) allocRcpt, err := receipt.IssueOK( storageProvider, allocInv.Task().Link(), - &blobcaps.AllocateOK{Size: blob.Size}, + &blobcmds.AllocateOK{Size: blob.Size}, ) require.NoError(t, err) msg := container.New( @@ -124,10 +124,10 @@ func TestHTTPPutConcludeHandler(t *testing.T) { require.NoError(t, deps.agentStore.Write(ctx, msg, agent.Index(msg))) blobProvider := deriveBlobProvider(t, digest) - putInv, err := httpcaps.Put.Invoke( + putInv, err := httpcmds.Put.Invoke( blobProvider, blobProvider.DID(), - &httpcaps.PutArguments{ + &httpcmds.PutArguments{ Body: blob, Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, @@ -137,7 +137,7 @@ func TestHTTPPutConcludeHandler(t *testing.T) { putRcpt, err := receipt.IssueOK( blobProvider, putInv.Task().Link(), - &httpcaps.PutOK{}, + &httpcmds.PutOK{}, ) require.NoError(t, err) @@ -150,16 +150,16 @@ func TestHTTPPutConcludeHandler(t *testing.T) { storageProvider := testutil.RandomSigner(t) space := testutil.RandomSigner(t) digest := testutil.RandomMultihash(t) - blob := blobcaps.Blob{Digest: digest, Size: 1024} + blob := blobcmds.Blob{Digest: digest, Size: 1024} blobAddTaskLink := testutil.RandomCID(t) // Stand up a mock piri server. The handler under test only calls // /blob/accept; the allocate handler is irrelevant but the helper // requires both. - acceptOK := &blobcaps.AcceptOK{Site: testutil.RandomCID(t)} + acceptOK := &blobcmds.AcceptOK{Site: testutil.RandomCID(t)} piriSrv := newMockPiriServer( t, storageProvider, uploadService, - &blobcaps.AllocateOK{Size: blob.Size}, + &blobcmds.AllocateOK{Size: blob.Size}, acceptOK, ) piriURL := testutil.Must(url.Parse(piriSrv.URL))(t) @@ -174,17 +174,17 @@ func TestHTTPPutConcludeHandler(t *testing.T) { )) // Prior /blob/allocate invocation in the agent store. - allocInv, err := blobcaps.Allocate.Invoke( + allocInv, err := blobcmds.Allocate.Invoke( uploadService, space.DID(), - &blobcaps.AllocateArguments{Blob: blob, Cause: blobAddTaskLink}, + &blobcmds.AllocateArguments{Blob: blob, Cause: blobAddTaskLink}, invocation.WithAudience(storageProvider.DID()), ) require.NoError(t, err) allocRcpt, err := receipt.IssueOK( storageProvider, allocInv.Task().Link(), - &blobcaps.AllocateOK{Size: blob.Size}, + &blobcmds.AllocateOK{Size: blob.Size}, ) require.NoError(t, err) msg := container.New( @@ -195,10 +195,10 @@ func TestHTTPPutConcludeHandler(t *testing.T) { // /http/put invocation referring to the allocation task. blobProvider := deriveBlobProvider(t, digest) - putInv, err := httpcaps.Put.Invoke( + putInv, err := httpcmds.Put.Invoke( blobProvider, blobProvider.DID(), - &httpcaps.PutArguments{ + &httpcmds.PutArguments{ Body: blob, Destination: promise.AwaitOK{Task: allocInv.Task().Link()}, }, @@ -208,14 +208,14 @@ func TestHTTPPutConcludeHandler(t *testing.T) { putRcpt, err := receipt.IssueOK( blobProvider, putInv.Task().Link(), - &httpcaps.PutOK{}, + &httpcmds.PutOK{}, ) require.NoError(t, err) // Authorize the upload service to invoke /blob/accept on the space and // pass the proof through the conclude metadata so the piri client can // forward it to the storage provider. - acceptProof, err := blobcaps.Accept.Delegate(space, uploadService.DID(), space.DID()) + acceptProof, err := blobcmds.Accept.Delegate(space, uploadService.DID(), space.DID()) require.NoError(t, err) meta := container.New(container.WithDelegations(acceptProof)) diff --git a/pkg/service/handlers/ucan_conclude_test.go b/pkg/service/handlers/ucan_conclude_test.go index e3b99f6..0d475ea 100644 --- a/pkg/service/handlers/ucan_conclude_test.go +++ b/pkg/service/handlers/ucan_conclude_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - ucancaps "github.com/fil-forge/libforge/commands/ucan" + ucancmds "github.com/fil-forge/libforge/commands/ucan" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service/handlers" @@ -52,10 +52,10 @@ func TestUCANConcludeHandler(t *testing.T) { _, rcpt := newTaskAndReceipt(t, "/test/thing") - concludeInv, err := ucancaps.Conclude.Invoke( + concludeInv, err := ucancmds.Conclude.Invoke( uploadService, uploadService.DID(), - &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + &ucancmds.ConcludeArguments{Receipt: rcpt.Link()}, invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -74,7 +74,7 @@ func TestUCANConcludeHandler(t *testing.T) { var model edm.ErrorModel require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) - require.Equal(t, ucancaps.ConclusionReceiptNotFoundErrorName, model.Name()) + require.Equal(t, ucancmds.ConclusionReceiptNotFoundErrorName, model.Name()) }) t.Run("unknown invocation returns success", func(t *testing.T) { @@ -89,10 +89,10 @@ func TestUCANConcludeHandler(t *testing.T) { // metadata nor in the agent store — the handler treats this as a no-op. _, rcpt := newTaskAndReceipt(t, "/test/thing") - concludeInv, err := ucancaps.Conclude.Invoke( + concludeInv, err := ucancmds.Conclude.Invoke( uploadService, uploadService.DID(), - &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + &ucancmds.ConcludeArguments{Receipt: rcpt.Link()}, invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -138,10 +138,10 @@ func TestUCANConcludeHandler(t *testing.T) { ) require.NoError(t, agentStore.Write(ctx, msg, agent.Index(msg))) - concludeInv, err := ucancaps.Conclude.Invoke( + concludeInv, err := ucancmds.Conclude.Invoke( uploadService, uploadService.DID(), - &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + &ucancmds.ConcludeArguments{Receipt: rcpt.Link()}, invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -177,10 +177,10 @@ func TestUCANConcludeHandler(t *testing.T) { ) require.NoError(t, agentStore.Write(ctx, msg, agent.Index(msg))) - concludeInv, err := ucancaps.Conclude.Invoke( + concludeInv, err := ucancmds.Conclude.Invoke( uploadService, uploadService.DID(), - &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + &ucancmds.ConcludeArguments{Receipt: rcpt.Link()}, invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) @@ -214,10 +214,10 @@ func TestUCANConcludeHandler(t *testing.T) { // The ran invocation is supplied directly in the request metadata — // no agent-store lookup required. - concludeInv, err := ucancaps.Conclude.Invoke( + concludeInv, err := ucancmds.Conclude.Invoke( uploadService, uploadService.DID(), - &ucancaps.ConcludeArguments{Receipt: rcpt.Link()}, + &ucancmds.ConcludeArguments{Receipt: rcpt.Link()}, invocation.WithAudience(uploadService.DID()), ) require.NoError(t, err) diff --git a/pkg/service/handlers/upload_add.go b/pkg/service/handlers/upload_add.go index 9dbd83d..46789aa 100644 --- a/pkg/service/handlers/upload_add.go +++ b/pkg/service/handlers/upload_add.go @@ -3,8 +3,8 @@ package handlers import ( "fmt" - accesscaps "github.com/fil-forge/libforge/commands/access" - uploadcaps "github.com/fil-forge/libforge/commands/upload" + accesscmds "github.com/fil-forge/libforge/commands/access" + uploadcmds "github.com/fil-forge/libforge/commands/upload" "github.com/fil-forge/sprue/pkg/provisioning" upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/errors" @@ -14,12 +14,12 @@ import ( // This handler registers an upload (root CID + shards mapping). func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", uploadcaps.Add)) + log := logger.With(zap.Stringer("handler", uploadcmds.Add)) return Handler{ - Command: uploadcaps.Add.Command, + Command: uploadcmds.Add.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*uploadcaps.AddArguments], - res *bindexec.Response[*uploadcaps.AddOK], + req *bindexec.Request[*uploadcmds.AddArguments], + res *bindexec.Response[*uploadcmds.AddOK], ) error { args := req.Task().Arguments() space := req.Invocation().Subject() @@ -40,7 +40,7 @@ func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore uplo } if len(provs) == 0 { log.Warn("space has no service provider") - return res.SetFailure(errors.New(accesscaps.InsufficientStorageErrorName, "space has no service provider")) + return res.SetFailure(errors.New(accesscmds.InsufficientStorageErrorName, "space has no service provider")) } err = uploadStore.Upsert(req.Context(), space, args.Root, args.Index, args.Shards, cause) @@ -49,7 +49,7 @@ func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore uplo return fmt.Errorf("upserting upload: %w", err) } - return res.SetSuccess(&uploadcaps.AddOK{}) + return res.SetSuccess(&uploadcmds.AddOK{}) }), } } diff --git a/pkg/service/handlers/upload_add_test.go b/pkg/service/handlers/upload_add_test.go index 0843007..5b3d0ec 100644 --- a/pkg/service/handlers/upload_add_test.go +++ b/pkg/service/handlers/upload_add_test.go @@ -5,8 +5,8 @@ import ( "context" "testing" - accesscaps "github.com/fil-forge/libforge/commands/access" - uploadcaps "github.com/fil-forge/libforge/commands/upload" + accesscmds "github.com/fil-forge/libforge/commands/access" + uploadcmds "github.com/fil-forge/libforge/commands/upload" "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/provisioning" @@ -52,10 +52,10 @@ func invokeUploadAdd( agent principal.Signer, uploadService principal.Signer, space principal.Signer, - args *uploadcaps.AddArguments, + args *uploadcmds.AddArguments, ) (execution.Request, *execution.ExecResponse) { t.Helper() - inv, err := uploadcaps.Add.Invoke( + inv, err := uploadcmds.Add.Invoke( agent, space.DID(), args, @@ -95,7 +95,7 @@ func TestUploadAddHandler(t *testing.T) { space := testutil.RandomSigner(t) root := testutil.RandomCID(t) - req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{Root: root}) + req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcmds.AddArguments{Root: root}) err := deps.handler.Handler(req, res) require.NoError(t, err) @@ -105,7 +105,7 @@ func TestUploadAddHandler(t *testing.T) { var model edm.ErrorModel require.NoError(t, model.UnmarshalCBOR(bytes.NewReader(x))) - require.Equal(t, accesscaps.InsufficientStorageErrorName, model.Name()) + require.Equal(t, accesscmds.InsufficientStorageErrorName, model.Name()) // Nothing should have been persisted. exists, err := deps.store.Exists(ctx, space.DID(), root) @@ -120,7 +120,7 @@ func TestUploadAddHandler(t *testing.T) { provisionUploadSpace(t, deps.consumerStore, uploadService, space) root := testutil.RandomCID(t) - req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{Root: root}) + req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcmds.AddArguments{Root: root}) err := deps.handler.Handler(req, res) require.NoError(t, err) @@ -148,7 +148,7 @@ func TestUploadAddHandler(t *testing.T) { shard1 := testutil.RandomCID(t) shard2 := testutil.RandomCID(t) - req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{ + req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcmds.AddArguments{ Root: root, Shards: []cid.Cid{shard1, shard2}, }) @@ -172,7 +172,7 @@ func TestUploadAddHandler(t *testing.T) { root := testutil.RandomCID(t) index := testutil.RandomCID(t) - req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{ + req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcmds.AddArguments{ Root: root, Index: &index, }) @@ -196,7 +196,7 @@ func TestUploadAddHandler(t *testing.T) { root := testutil.RandomCID(t) shard1 := testutil.RandomCID(t) - req1, res1 := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{ + req1, res1 := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcmds.AddArguments{ Root: root, Shards: []cid.Cid{shard1}, }) @@ -205,7 +205,7 @@ func TestUploadAddHandler(t *testing.T) { // Add again with a new shard. shard2 := testutil.RandomCID(t) - req2, res2 := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcaps.AddArguments{ + req2, res2 := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcmds.AddArguments{ Root: root, Shards: []cid.Cid{shard2}, }) diff --git a/pkg/service/handlers/upload_list.go b/pkg/service/handlers/upload_list.go index 874f361..da406f2 100644 --- a/pkg/service/handlers/upload_list.go +++ b/pkg/service/handlers/upload_list.go @@ -3,19 +3,19 @@ package handlers import ( "fmt" - uploadcaps "github.com/fil-forge/libforge/commands/upload" + uploadcmds "github.com/fil-forge/libforge/commands/upload" upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/execution/bindexec" "go.uber.org/zap" ) func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", uploadcaps.List)) + log := logger.With(zap.Stringer("handler", uploadcmds.List)) return Handler{ - Command: uploadcaps.List.Command, + Command: uploadcmds.List.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*uploadcaps.ListArguments], - res *bindexec.Response[*uploadcaps.ListOK], + req *bindexec.Request[*uploadcmds.ListArguments], + res *bindexec.Response[*uploadcmds.ListOK], ) error { args := req.Task().Arguments() space := req.Invocation().Subject() @@ -38,15 +38,15 @@ func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Ha return fmt.Errorf("listing uploads: %w", err) } - results := make([]uploadcaps.ListUploadItem, 0, len(page.Results)) + results := make([]uploadcmds.ListUploadItem, 0, len(page.Results)) for _, r := range page.Results { - results = append(results, uploadcaps.ListUploadItem{ + results = append(results, uploadcmds.ListUploadItem{ Root: r.Root, Index: r.Index, }) } - return res.SetSuccess(&uploadcaps.ListOK{ + return res.SetSuccess(&uploadcmds.ListOK{ Results: results, Cursor: page.Cursor, }) diff --git a/pkg/service/handlers/upload_list_test.go b/pkg/service/handlers/upload_list_test.go index 9f1b85f..c2766b2 100644 --- a/pkg/service/handlers/upload_list_test.go +++ b/pkg/service/handlers/upload_list_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - uploadcaps "github.com/fil-forge/libforge/commands/upload" + uploadcmds "github.com/fil-forge/libforge/commands/upload" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/service/handlers" upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" @@ -25,10 +25,10 @@ func invokeUploadList( agent principal.Signer, uploadService principal.Signer, space principal.Signer, - args *uploadcaps.ListArguments, + args *uploadcmds.ListArguments, ) (execution.Request, *execution.ExecResponse) { t.Helper() - inv, err := uploadcaps.List.Invoke( + inv, err := uploadcmds.List.Invoke( agent, space.DID(), args, @@ -53,7 +53,7 @@ func TestUploadListHandler(t *testing.T) { handler := handlers.NewUploadListHandler(store, logger) space := testutil.RandomSigner(t) - req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{}) + req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcmds.ListArguments{}) err := handler.Handler(req, res) require.NoError(t, err) @@ -62,7 +62,7 @@ func TestUploadListHandler(t *testing.T) { require.Nil(t, x) require.NotNil(t, o) - var ok uploadcaps.ListOK + var ok uploadcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) require.Nil(t, ok.Cursor) @@ -79,14 +79,14 @@ func TestUploadListHandler(t *testing.T) { require.NoError(t, store.Upsert(ctx, space.DID(), root1, nil, nil, testutil.RandomCID(t))) require.NoError(t, store.Upsert(ctx, space.DID(), root2, nil, nil, testutil.RandomCID(t))) - req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{}) + req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcmds.ListArguments{}) err := handler.Handler(req, res) require.NoError(t, err) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok uploadcaps.ListOK + var ok uploadcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) @@ -108,14 +108,14 @@ func TestUploadListHandler(t *testing.T) { } size := uint64(2) - req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Size: &size}) + req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcmds.ListArguments{Size: &size}) err := handler.Handler(req, res) require.NoError(t, err) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok uploadcaps.ListOK + var ok uploadcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) require.NotNil(t, ok.Cursor) @@ -131,24 +131,24 @@ func TestUploadListHandler(t *testing.T) { } size := uint64(1) - req1, res1 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Size: &size}) + req1, res1 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcmds.ListArguments{Size: &size}) require.NoError(t, handler.Handler(req1, res1)) o1, x := res1.Receipt().Out().Unpack() require.Nil(t, x) - var ok1 uploadcaps.ListOK + var ok1 uploadcmds.ListOK require.NoError(t, ok1.UnmarshalCBOR(bytes.NewReader(o1))) require.Len(t, ok1.Results, 1) require.NotNil(t, ok1.Cursor) // Second page using cursor. cursor := *ok1.Cursor - req2, res2 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{Cursor: &cursor, Size: &size}) + req2, res2 := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcmds.ListArguments{Cursor: &cursor, Size: &size}) require.NoError(t, handler.Handler(req2, res2)) o2, x := res2.Receipt().Out().Unpack() require.Nil(t, x) - var ok2 uploadcaps.ListOK + var ok2 uploadcmds.ListOK require.NoError(t, ok2.UnmarshalCBOR(bytes.NewReader(o2))) require.Len(t, ok2.Results, 1) require.NotEqual(t, ok1.Results[0].Root.String(), ok2.Results[0].Root.String()) @@ -164,12 +164,12 @@ func TestUploadListHandler(t *testing.T) { require.NoError(t, store.Upsert(ctx, space1.DID(), testutil.RandomCID(t), nil, nil, testutil.RandomCID(t))) // Query space2 — should be empty. - req, res := invokeUploadList(t, ctx, alice, uploadService, space2, &uploadcaps.ListArguments{}) + req, res := invokeUploadList(t, ctx, alice, uploadService, space2, &uploadcmds.ListArguments{}) require.NoError(t, handler.Handler(req, res)) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok uploadcaps.ListOK + var ok uploadcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) }) @@ -183,13 +183,13 @@ func TestUploadListHandler(t *testing.T) { index := testutil.RandomCID(t) require.NoError(t, store.Upsert(ctx, space.DID(), root, &index, nil, testutil.RandomCID(t))) - req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcaps.ListArguments{}) + req, res := invokeUploadList(t, ctx, alice, uploadService, space, &uploadcmds.ListArguments{}) require.NoError(t, handler.Handler(req, res)) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok uploadcaps.ListOK + var ok uploadcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 1) require.NotNil(t, ok.Results[0].Index) diff --git a/pkg/service/handlers/upload_shard_list.go b/pkg/service/handlers/upload_shard_list.go index a699596..46feb4b 100644 --- a/pkg/service/handlers/upload_shard_list.go +++ b/pkg/service/handlers/upload_shard_list.go @@ -3,7 +3,7 @@ package handlers import ( "fmt" - shardcaps "github.com/fil-forge/libforge/commands/upload/shard" + shardcmds "github.com/fil-forge/libforge/commands/upload/shard" upload_store "github.com/fil-forge/sprue/pkg/store/upload" "github.com/fil-forge/ucantone/execution/bindexec" "go.uber.org/zap" @@ -11,12 +11,12 @@ import ( // This handler lists the shards of an upload. func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { - log := logger.With(zap.Stringer("handler", shardcaps.List)) + log := logger.With(zap.Stringer("handler", shardcmds.List)) return Handler{ - Command: shardcaps.List.Command, + Command: shardcmds.List.Command, Handler: bindexec.NewHandler(func( - req *bindexec.Request[*shardcaps.ListArguments], - res *bindexec.Response[*shardcaps.ListOK], + req *bindexec.Request[*shardcmds.ListArguments], + res *bindexec.Response[*shardcmds.ListOK], ) error { args := req.Task().Arguments() space := req.Invocation().Subject() @@ -40,7 +40,7 @@ func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logge return fmt.Errorf("listing upload shards: %w", err) } - return res.SetSuccess(&shardcaps.ListOK{ + return res.SetSuccess(&shardcmds.ListOK{ Results: page.Results, Cursor: page.Cursor, }) diff --git a/pkg/service/handlers/upload_shard_list_test.go b/pkg/service/handlers/upload_shard_list_test.go index aa3a2e9..42c7773 100644 --- a/pkg/service/handlers/upload_shard_list_test.go +++ b/pkg/service/handlers/upload_shard_list_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - shardcaps "github.com/fil-forge/libforge/commands/upload/shard" + shardcmds "github.com/fil-forge/libforge/commands/upload/shard" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/service/handlers" upload_store "github.com/fil-forge/sprue/pkg/store/upload/memory" @@ -25,10 +25,10 @@ func invokeUploadShardList( agent principal.Signer, uploadService principal.Signer, space principal.Signer, - args *shardcaps.ListArguments, + args *shardcmds.ListArguments, ) (execution.Request, *execution.ExecResponse) { t.Helper() - inv, err := shardcaps.List.Invoke( + inv, err := shardcmds.List.Invoke( agent, space.DID(), args, @@ -58,7 +58,7 @@ func TestUploadShardListHandler(t *testing.T) { // Upload exists with no shards. require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, nil, testutil.RandomCID(t))) - req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root}) + req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcmds.ListArguments{Root: root}) err := handler.Handler(req, res) require.NoError(t, err) @@ -67,7 +67,7 @@ func TestUploadShardListHandler(t *testing.T) { require.Nil(t, x) require.NotNil(t, o) - var ok shardcaps.ListOK + var ok shardcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Empty(t, ok.Results) }) @@ -83,14 +83,14 @@ func TestUploadShardListHandler(t *testing.T) { require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, []cid.Cid{shard1, shard2}, testutil.RandomCID(t))) - req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root}) + req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcmds.ListArguments{Root: root}) err := handler.Handler(req, res) require.NoError(t, err) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok shardcaps.ListOK + var ok shardcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) @@ -114,14 +114,14 @@ func TestUploadShardListHandler(t *testing.T) { require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, []cid.Cid{shard1, shard2, shard3}, testutil.RandomCID(t))) size := uint64(2) - req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Size: &size}) + req, res := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcmds.ListArguments{Root: root, Size: &size}) err := handler.Handler(req, res) require.NoError(t, err) o, x := res.Receipt().Out().Unpack() require.Nil(t, x) - var ok shardcaps.ListOK + var ok shardcmds.ListOK require.NoError(t, ok.UnmarshalCBOR(bytes.NewReader(o))) require.Len(t, ok.Results, 2) require.NotNil(t, ok.Cursor) @@ -139,24 +139,24 @@ func TestUploadShardListHandler(t *testing.T) { require.NoError(t, store.Upsert(ctx, space.DID(), root, nil, []cid.Cid{shard1, shard2, shard3}, testutil.RandomCID(t))) size := uint64(1) - req1, res1 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Size: &size}) + req1, res1 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcmds.ListArguments{Root: root, Size: &size}) require.NoError(t, handler.Handler(req1, res1)) o1, x := res1.Receipt().Out().Unpack() require.Nil(t, x) - var ok1 shardcaps.ListOK + var ok1 shardcmds.ListOK require.NoError(t, ok1.UnmarshalCBOR(bytes.NewReader(o1))) require.Len(t, ok1.Results, 1) require.NotNil(t, ok1.Cursor) // Second page using cursor. cursor := *ok1.Cursor - req2, res2 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcaps.ListArguments{Root: root, Cursor: &cursor, Size: &size}) + req2, res2 := invokeUploadShardList(t, ctx, alice, uploadService, space, &shardcmds.ListArguments{Root: root, Cursor: &cursor, Size: &size}) require.NoError(t, handler.Handler(req2, res2)) o2, x := res2.Receipt().Out().Unpack() require.Nil(t, x) - var ok2 shardcaps.ListOK + var ok2 shardcmds.ListOK require.NoError(t, ok2.UnmarshalCBOR(bytes.NewReader(o2))) require.Len(t, ok2.Results, 1) require.NotEqual(t, ok1.Results[0].String(), ok2.Results[0].String()) From a951bfaeb511cce8e7846fcc950255aca0f05603 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 22 May 2026 10:01:08 +0100 Subject: [PATCH 22/23] chore: upgrade libforge and ucantone --- go.mod | 5 +- go.sum | 6 +++ internal/fx/service/provider.go | 3 +- pkg/commands/admin/provider/deregister.go | 8 +++- pkg/commands/admin/provider/list.go | 8 +++- pkg/commands/admin/provider/register.go | 8 +++- pkg/commands/admin/provider/weight/set.go | 8 +++- pkg/lib/ucan_server/events.go | 6 ++- pkg/lib/ucan_server/validation_test.go | 11 +++-- pkg/piriclient/client.go | 46 +++++++++---------- pkg/service/handlers/access_claim.go | 18 ++++---- pkg/service/handlers/access_claim_test.go | 9 ++-- pkg/service/handlers/access_confirm.go | 18 ++++---- pkg/service/handlers/access_confirm_test.go | 11 +++-- pkg/service/handlers/access_delegate.go | 18 ++++---- pkg/service/handlers/access_delegate_test.go | 7 +-- pkg/service/handlers/access_request.go | 18 ++++---- pkg/service/handlers/access_request_test.go | 9 ++-- .../handlers/admin_provider_deregister.go | 18 ++++---- pkg/service/handlers/admin_provider_list.go | 18 ++++---- .../handlers/admin_provider_register.go | 18 ++++---- .../handlers/admin_provider_weight_set.go | 18 ++++---- pkg/service/handlers/blob_add.go | 18 ++++---- pkg/service/handlers/blob_add_test.go | 16 +++---- pkg/service/handlers/blob_list.go | 18 ++++---- pkg/service/handlers/handlers.go | 11 ----- pkg/service/handlers/index_add.go | 18 ++++---- pkg/service/handlers/index_add_test.go | 8 ++-- pkg/service/handlers/provider_add.go | 18 ++++---- pkg/service/handlers/space_info.go | 18 ++++---- pkg/service/handlers/ucan_conclude.go | 18 ++++---- pkg/service/handlers/ucan_conclude_test.go | 15 +++--- pkg/service/handlers/upload_add.go | 18 ++++---- pkg/service/handlers/upload_add_test.go | 19 ++++---- pkg/service/handlers/upload_list.go | 18 ++++---- pkg/service/handlers/upload_shard_list.go | 18 ++++---- pkg/service/service.go | 9 ++-- pkg/store/agent/agent_test.go | 3 +- pkg/store/delegation/delegation_test.go | 3 +- 39 files changed, 262 insertions(+), 273 deletions(-) delete mode 100644 pkg/service/handlers/handlers.go diff --git a/go.mod b/go.mod index fab876e..b471046 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.56.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 github.com/docker/docker v28.5.2+incompatible - github.com/fil-forge/libforge v0.0.0-20260521174431-708e600522c4 - github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10 + github.com/fil-forge/libforge v0.0.0-20260521215559-0f7fa19c9ebc + github.com/fil-forge/ucantone v0.0.0-20260521210642-84d8c533075b github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 github.com/jackc/pgx/v5 v5.8.0 @@ -68,6 +68,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.10.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/filecoin-project/go-state-types v0.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/go.sum b/go.sum index 018b498..f9af797 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,14 @@ github.com/fil-forge/libforge v0.0.0-20260519193938-fc442aef7e5c h1:dzAiOAYDTJDn github.com/fil-forge/libforge v0.0.0-20260519193938-fc442aef7e5c/go.mod h1:/sKL0TfiBzknJTuL1vSZ5IHALmWndwxOexU3jtlNVSE= github.com/fil-forge/libforge v0.0.0-20260521174431-708e600522c4 h1:4cOrR1n8xSno+JbTmnc6rpXg7hRcQEwdqj6LVynj5wA= github.com/fil-forge/libforge v0.0.0-20260521174431-708e600522c4/go.mod h1:/sKL0TfiBzknJTuL1vSZ5IHALmWndwxOexU3jtlNVSE= +github.com/fil-forge/libforge v0.0.0-20260521215559-0f7fa19c9ebc h1:/F8zCG3cINBscp5BTRms8kF+ysKxPd7PODQ52AqK+5g= +github.com/fil-forge/libforge v0.0.0-20260521215559-0f7fa19c9ebc/go.mod h1:mw0OzGZhVtcXwE4/KhgEV5kIQSTY0ly9HyZZtlqm7yA= github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10 h1:ApgWAIpXjCYjZw/yDxLn8IA9WrH/ENPRWCWPT/MoCvU= github.com/fil-forge/ucantone v0.0.0-20260519193222-ad31490eaa10/go.mod h1:vqgVEsy6LEEsY24Zyjxem0vSofj1XTIx29GbV635f+I= +github.com/fil-forge/ucantone v0.0.0-20260521210642-84d8c533075b h1:ILG7dtSWiOO/fYiesqYj8Esm1qeIxFy7qkZoFdnjPpU= +github.com/fil-forge/ucantone v0.0.0-20260521210642-84d8c533075b/go.mod h1:XAVqsZwYoZ9vncjZoRUAJ+mL/ApLMFn9HHX7ipohVdY= +github.com/filecoin-project/go-state-types v0.18.0 h1:oDcjihXRlf2cM176atZzllp79Zc+kcbiuQM9DPL/1a4= +github.com/filecoin-project/go-state-types v0.18.0/go.mod h1:CcyG4ZQRDWW+QUY2WDf1KtVDRN7W4twjsfgnGbQfJVI= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/internal/fx/service/provider.go b/internal/fx/service/provider.go index b99f788..60bb30e 100644 --- a/internal/fx/service/provider.go +++ b/internal/fx/service/provider.go @@ -3,7 +3,6 @@ package service import ( "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/service" - "github.com/fil-forge/sprue/pkg/service/handlers" "github.com/fil-forge/sprue/pkg/store/agent" "github.com/fil-forge/sprue/pkg/store/delegation" "github.com/fil-forge/ucantone/server" @@ -24,7 +23,7 @@ type ServiceParams struct { AgentStore agent.Store DelegationStore delegation.Store Logger *zap.Logger - Handlers []handlers.Handler `group:"ucan_handlers"` + Handlers []server.Route `group:"ucan_handlers"` Options []server.HTTPOption `group:"ucan_options"` } diff --git a/pkg/commands/admin/provider/deregister.go b/pkg/commands/admin/provider/deregister.go index d84ad48..4843aa0 100644 --- a/pkg/commands/admin/provider/deregister.go +++ b/pkg/commands/admin/provider/deregister.go @@ -2,8 +2,12 @@ package provider -import "github.com/fil-forge/libforge/commands" +import ( + "github.com/fil-forge/libforge/commands" + "github.com/fil-forge/ucantone/binding" + "github.com/fil-forge/ucantone/ucan/command" +) type DeregisterOK = commands.Unit -var Deregister = commands.MustParse[*DeregisterArguments]("/admin/provider/deregister") +var Deregister = binding.Bind[*DeregisterArguments, *DeregisterOK](command.MustParse("/admin/provider/deregister")) diff --git a/pkg/commands/admin/provider/list.go b/pkg/commands/admin/provider/list.go index 801472c..db17d22 100644 --- a/pkg/commands/admin/provider/list.go +++ b/pkg/commands/admin/provider/list.go @@ -2,8 +2,12 @@ package provider -import "github.com/fil-forge/libforge/commands" +import ( + "github.com/fil-forge/libforge/commands" + "github.com/fil-forge/ucantone/binding" + "github.com/fil-forge/ucantone/ucan/command" +) type ListArguments = commands.Unit -var List = commands.MustParse[*ListArguments]("/admin/provider/list") +var List = binding.Bind[*ListArguments, *ListOK](command.MustParse("/admin/provider/list")) diff --git a/pkg/commands/admin/provider/register.go b/pkg/commands/admin/provider/register.go index a2bfdbf..4c7bf61 100644 --- a/pkg/commands/admin/provider/register.go +++ b/pkg/commands/admin/provider/register.go @@ -2,8 +2,12 @@ package provider -import "github.com/fil-forge/libforge/commands" +import ( + "github.com/fil-forge/libforge/commands" + "github.com/fil-forge/ucantone/binding" + "github.com/fil-forge/ucantone/ucan/command" +) type RegisterOK = commands.Unit -var Register = commands.MustParse[*RegisterArguments]("/admin/provider/register") +var Register = binding.Bind[*RegisterArguments, *RegisterOK](command.MustParse("/admin/provider/register")) diff --git a/pkg/commands/admin/provider/weight/set.go b/pkg/commands/admin/provider/weight/set.go index e6d23c2..0892f6c 100644 --- a/pkg/commands/admin/provider/weight/set.go +++ b/pkg/commands/admin/provider/weight/set.go @@ -2,8 +2,12 @@ package weight -import "github.com/fil-forge/libforge/commands" +import ( + "github.com/fil-forge/libforge/commands" + "github.com/fil-forge/ucantone/binding" + "github.com/fil-forge/ucantone/ucan/command" +) type SetOK = commands.Unit -var Set = commands.MustParse[*SetArguments]("/provider/weight/set") +var Set = binding.Bind[*SetArguments, *SetOK](command.MustParse("/provider/weight/set")) diff --git a/pkg/lib/ucan_server/events.go b/pkg/lib/ucan_server/events.go index c0aaa9a..e35e706 100644 --- a/pkg/lib/ucan_server/events.go +++ b/pkg/lib/ucan_server/events.go @@ -20,7 +20,11 @@ type ErrorHandler struct { var _ server.ResponseEncodeListener = (*ErrorHandler)(nil) -func (l ErrorHandler) OnResponseEncode(ctx context.Context, ct ucan.Container) error { +func (l *ErrorHandler) OnRequestDecode(ctx context.Context, container ucan.Container) error { + return nil +} + +func (l *ErrorHandler) OnResponseEncode(ctx context.Context, ct ucan.Container) error { for _, inv := range ct.Invocations() { r, ok := ct.Receipt(inv.Task().Link()) if !ok || !r.Out().IsErr() { diff --git a/pkg/lib/ucan_server/validation_test.go b/pkg/lib/ucan_server/validation_test.go index af5b728..4c89ce4 100644 --- a/pkg/lib/ucan_server/validation_test.go +++ b/pkg/lib/ucan_server/validation_test.go @@ -9,6 +9,7 @@ import ( "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal/absentee" "github.com/fil-forge/ucantone/principal/ed25519" + "github.com/fil-forge/ucantone/ucan/command" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" @@ -23,13 +24,13 @@ func TestNewAttestationVerifier(t *testing.T) { account := absentee.From(testutil.Must(did.Parse("did:mailto:web.mail:alice"))(t)) - dlg, err := delegation.Delegate(account, agent.DID(), space.DID(), "/blob/add") + dlg, err := delegation.Delegate(account, agent.DID(), space.DID(), command.MustParse("/blob/add")) require.NoError(t, err) verify := NewAttestationVerifier(authority.Verifier()) t.Run("token is not a delegation", func(t *testing.T) { - inv, err := invocation.Invoke(agent, space.DID(), "/blob/add", datamodel.Map{}) + inv, err := invocation.Invoke(agent, space.DID(), command.MustParse("/blob/add"), datamodel.Map{}) require.NoError(t, err) err = verify(t.Context(), inv, container.New()) @@ -44,7 +45,7 @@ func TestNewAttestationVerifier(t *testing.T) { }) t.Run("invocation with non-attest command is ignored", func(t *testing.T) { - inv, err := invocation.Invoke(authority, authority.DID(), "/some/other", datamodel.Map{}) + inv, err := invocation.Invoke(authority, authority.DID(), command.MustParse("/some/other"), datamodel.Map{}) require.NoError(t, err) err = verify(t.Context(), dlg, container.New(container.WithInvocations(inv))) @@ -71,7 +72,7 @@ func TestNewAttestationVerifier(t *testing.T) { }) t.Run("attestation proof for a different delegation is ignored", func(t *testing.T) { - otherDlg, err := delegation.Delegate(account, agent.DID(), space.DID(), "/blob/list") + otherDlg, err := delegation.Delegate(account, agent.DID(), space.DID(), command.MustParse("/blob/list")) require.NoError(t, err) inv, err := attest.Proof.Invoke(authority, authority.DID(), &attest.ProofArguments{Proof: otherDlg.Link()}) @@ -107,7 +108,7 @@ func TestNewAttestationVerifier(t *testing.T) { t.Run("valid attestation found among invalid ones", func(t *testing.T) { untrusted, err := attest.Proof.Invoke(other, other.DID(), &attest.ProofArguments{Proof: dlg.Link()}) require.NoError(t, err) - wrongCmd, err := invocation.Invoke(authority, authority.DID(), "/some/other", datamodel.Map{}) + wrongCmd, err := invocation.Invoke(authority, authority.DID(), command.MustParse("/some/other"), datamodel.Map{}) require.NoError(t, err) valid, err := attest.Proof.Invoke(authority, authority.DID(), &attest.ProofArguments{Proof: dlg.Link()}) require.NoError(t, err) diff --git a/pkg/piriclient/client.go b/pkg/piriclient/client.go index e9152bf..7e20825 100644 --- a/pkg/piriclient/client.go +++ b/pkg/piriclient/client.go @@ -7,8 +7,8 @@ import ( "slices" "time" - blobcap "github.com/fil-forge/libforge/commands/blob" - blobreplicacap "github.com/fil-forge/libforge/commands/blob/replica" + blobcmds "github.com/fil-forge/libforge/commands/blob" + blobreplicacmds "github.com/fil-forge/libforge/commands/blob/replica" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/lib/ucan_client" "github.com/fil-forge/ucantone/client" @@ -65,7 +65,7 @@ type AllocateRequest struct { // Allocate sends a /blob/allocate invocation to the piri node. // Returns the response data, the invocation that was sent, and the receipt from piri. -func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobcap.AllocateOK, ucan.Invocation, ucan.Receipt, error) { +func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobcmds.AllocateOK, ucan.Invocation, ucan.Receipt, error) { inv, prfs, attestations, err := c.AllocateInvocation(ctx, req, proofStore, options...) if err != nil { return nil, nil, nil, fmt.Errorf("creating allocate invocation: %w", err) @@ -78,12 +78,12 @@ func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore zap.Int("attestations", len(attestations)), ) - allocOK, rcpt, _, err := ucan_client.Execute[*blobcap.AllocateOK]( + allocOK, rcpt, _, err := ucan_client.Execute[*blobcmds.AllocateOK]( ctx, c.client, c.logger, inv, - execution.WithProofs(prfs...), + execution.WithDelegations(prfs...), execution.WithInvocations(attestations...), ) if err != nil { @@ -94,7 +94,7 @@ func (c *Client) Allocate(ctx context.Context, req *AllocateRequest, proofStore // AllocateInvocation returns the invocation for the allocate request (for use in effects). func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.Allocate.Command, req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcmds.Allocate.Command, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -111,11 +111,11 @@ func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, p invocation.WithProofs(prfLinks...), ) - inv, err := blobcap.Allocate.Invoke( + inv, err := blobcmds.Allocate.Invoke( c.signer, req.Space, - &blobcap.AllocateArguments{ - Blob: blobcap.Blob{Digest: req.Digest, Size: req.Size}, + &blobcmds.AllocateArguments{ + Blob: blobcmds.Blob{Digest: req.Digest, Size: req.Size}, Cause: req.Cause, }, options..., @@ -141,7 +141,7 @@ type AcceptRequest struct { } // Accept sends a /blob/accept invocation to the piri node. -func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobcap.AcceptOK, ucan.Invocation, ucan.Receipt, ucan.Container, error) { +func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobcmds.AcceptOK, ucan.Invocation, ucan.Receipt, ucan.Container, error) { inv, prfs, attestations, err := c.AcceptInvocation(ctx, req, proofStore, options...) if err != nil { return nil, nil, nil, nil, fmt.Errorf("creating accept invocation: %w", err) @@ -154,12 +154,12 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan zap.Int("attestations", len(attestations)), ) - acceptOK, rcpt, meta, err := ucan_client.Execute[*blobcap.AcceptOK]( + acceptOK, rcpt, meta, err := ucan_client.Execute[*blobcmds.AcceptOK]( ctx, c.client, c.logger, inv, - execution.WithProofs(prfs...), + execution.WithDelegations(prfs...), execution.WithInvocations(attestations...), ) if err != nil { @@ -170,7 +170,7 @@ func (c *Client) Accept(ctx context.Context, req *AcceptRequest, proofStore ucan // AcceptInvocation returns the invocation for the accept request (for use in effects). func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (ucan.Invocation, []ucan.Delegation, []ucan.Invocation, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcap.Accept.Command, req.Space) + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcmds.Accept.Command, req.Space) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -187,11 +187,11 @@ func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proof invocation.WithProofs(prfLinks...), ) - inv, err := blobcap.Accept.Invoke( + inv, err := blobcmds.Accept.Invoke( c.signer, req.Space, - &blobcap.AcceptArguments{ - Blob: blobcap.Blob{Digest: req.Digest, Size: req.Size}, + &blobcmds.AcceptArguments{ + Blob: blobcmds.Blob{Digest: req.Digest, Size: req.Size}, Put: promise.AwaitOK{Task: req.Put}, }, options..., @@ -215,8 +215,8 @@ type ReplicaAllocateRequest struct { // ReplicaAllocate sends a /blob/replica/allocate invocation to the piri node. // Returns the response data, the invocation that was sent, the receipt from // piri, and any metadata. It returns an error if the receipt contains a failure result. -func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobreplicacap.AllocateOK, ucan.Invocation, ucan.Receipt, ucan.Container, error) { - prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobreplicacap.Allocate.Command, req.Space) +func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateRequest, proofStore ucanlib.ProofStore, options ...invocation.Option) (*blobreplicacmds.AllocateOK, ucan.Invocation, ucan.Receipt, ucan.Container, error) { + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobreplicacmds.Allocate.Command, req.Space) if err != nil { return nil, nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -237,11 +237,11 @@ func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateReques invocation.WithExpiration(ucan.UnixTimestamp(time.Now().Add(replicaAllocationTTL).Unix())), ) - inv, err := blobreplicacap.Allocate.Invoke( + inv, err := blobreplicacmds.Allocate.Invoke( c.signer, req.Space, - &blobreplicacap.AllocateArguments{ - Blob: blobreplicacap.Blob{Digest: req.Digest, Size: req.Size}, + &blobreplicacmds.AllocateArguments{ + Blob: blobcmds.Blob{Digest: req.Digest, Size: req.Size}, Site: req.Site.Link(), Cause: req.Cause, }, @@ -256,12 +256,12 @@ func (c *Client) ReplicaAllocate(ctx context.Context, req *ReplicaAllocateReques zap.Stringer("audience", inv.Audience()), zap.Int("proofs", len(inv.Proofs()))) - allocOK, rcpt, meta, err := ucan_client.Execute[*blobreplicacap.AllocateOK]( + allocOK, rcpt, meta, err := ucan_client.Execute[*blobreplicacmds.AllocateOK]( ctx, c.client, c.logger, inv, - execution.WithProofs(prfs...), + execution.WithDelegations(prfs...), execution.WithInvocations(attestations...), ) if err != nil { diff --git a/pkg/service/handlers/access_claim.go b/pkg/service/handlers/access_claim.go index 40de3c6..0867a7b 100644 --- a/pkg/service/handlers/access_claim.go +++ b/pkg/service/handlers/access_claim.go @@ -6,21 +6,19 @@ import ( "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/sprue/pkg/identity" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/binding" + "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/ipfs/go-cid" "go.uber.org/zap" ) -func NewAccessClaimHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { +func NewAccessClaimHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", access.Claim)) - return Handler{ - Command: access.Claim.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*access.ClaimArguments], - res *bindexec.Response[*access.ClaimOK], - ) error { + return server.NewRoute( + access.Claim, + func(req *binding.Request[*access.ClaimArguments], res *binding.Response[*access.ClaimOK]) error { agent := req.Invocation().Issuer() audience := req.Invocation().Subject() @@ -65,6 +63,6 @@ func NewAccessClaimHandler(id *identity.Identity, delegationStore delegation_sto )) return res.SetSuccess(&access.ClaimOK{Delegations: links}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/access_claim_test.go b/pkg/service/handlers/access_claim_test.go index ae977a1..e2a052e 100644 --- a/pkg/service/handlers/access_claim_test.go +++ b/pkg/service/handlers/access_claim_test.go @@ -9,6 +9,7 @@ import ( dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/command" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" @@ -58,7 +59,7 @@ func TestAccessClaimHandler(t *testing.T) { agent := testutil.RandomSigner(t) - dlg, err := delegation.Delegate(testutil.Alice, agent.DID(), testutil.Alice.DID(), "/test/thing") + dlg, err := delegation.Delegate(testutil.Alice, agent.DID(), testutil.Alice.DID(), command.MustParse("/test/thing")) require.NoError(t, err) err = store.PutMany(t.Context(), []ucan.Token{dlg}, testutil.RandomCID(t)) @@ -96,10 +97,10 @@ func TestAccessClaimHandler(t *testing.T) { agent := testutil.RandomSigner(t) - dlg1, err := delegation.Delegate(testutil.Alice, agent.DID(), testutil.Alice.DID(), "/test/one") + dlg1, err := delegation.Delegate(testutil.Alice, agent.DID(), testutil.Alice.DID(), command.MustParse("/test/one")) require.NoError(t, err) - dlg2, err := delegation.Delegate(testutil.Bob, agent.DID(), testutil.Bob.DID(), "/test/two") + dlg2, err := delegation.Delegate(testutil.Bob, agent.DID(), testutil.Bob.DID(), command.MustParse("/test/two")) require.NoError(t, err) err = store.PutMany(t.Context(), []ucan.Token{dlg1, dlg2}, testutil.RandomCID(t)) @@ -140,7 +141,7 @@ func TestAccessClaimHandler(t *testing.T) { otherAgent := testutil.RandomSigner(t) // Delegation is for otherAgent, not agent. - dlg, err := delegation.Delegate(testutil.Alice, otherAgent.DID(), testutil.Alice.DID(), "/test/thing") + dlg, err := delegation.Delegate(testutil.Alice, otherAgent.DID(), testutil.Alice.DID(), command.MustParse("/test/thing")) require.NoError(t, err) err = store.PutMany(t.Context(), []ucan.Token{dlg}, testutil.RandomCID(t)) diff --git a/pkg/service/handlers/access_confirm.go b/pkg/service/handlers/access_confirm.go index dfaf927..c6446b0 100644 --- a/pkg/service/handlers/access_confirm.go +++ b/pkg/service/handlers/access_confirm.go @@ -8,11 +8,12 @@ import ( "github.com/fil-forge/libforge/didmailto" "github.com/fil-forge/sprue/pkg/identity" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/did" - "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/ipld" "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal/absentee" + "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/delegation" @@ -21,14 +22,11 @@ import ( "go.uber.org/zap" ) -func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) Handler { +func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_store.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", access.Confirm)) - return Handler{ - Command: access.Confirm.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*access.ConfirmArguments], - res *bindexec.Response[*access.ConfirmOK], - ) error { + return server.NewRoute( + access.Confirm, + func(req *binding.Request[*access.ConfirmArguments], res *binding.Response[*access.ConfirmOK]) error { args := req.Task().Arguments() if req.Invocation().Subject() != id.Signer.DID() { log.Warn("not a valid invocation", zap.Stringer("subject", req.Invocation().Subject())) @@ -102,8 +100,8 @@ func NewAccessConfirmHandler(id *identity.Identity, delegationStore delegation_s )) return res.SetSuccess(&access.ConfirmOK{Delegations: links}) - }), - } + }, + ) } // createSessionProofs creates delegations from the account to the agent, and diff --git a/pkg/service/handlers/access_confirm_test.go b/pkg/service/handlers/access_confirm_test.go index 7164466..4bfdcf9 100644 --- a/pkg/service/handlers/access_confirm_test.go +++ b/pkg/service/handlers/access_confirm_test.go @@ -10,6 +10,7 @@ import ( dlgmemory "github.com/fil-forge/sprue/pkg/store/delegation/memory" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ucan/command" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -32,7 +33,7 @@ func TestAccessConfirmHandler(t *testing.T) { Issuer: account, Audience: agent.DID(), Attenuations: []access.CapabilityRequest{ - {Command: "/"}, + {Command: command.Top()}, }, } @@ -75,7 +76,7 @@ func TestAccessConfirmHandler(t *testing.T) { Issuer: nonMailto.DID(), Audience: agent.DID(), Attenuations: []access.CapabilityRequest{ - {Command: "/"}, + {Command: command.Top()}, }, } @@ -116,7 +117,7 @@ func TestAccessConfirmHandler(t *testing.T) { Issuer: account, Audience: agent.DID(), Attenuations: []access.CapabilityRequest{ - {Command: "/"}, + {Command: command.Top()}, }, } @@ -163,8 +164,8 @@ func TestAccessConfirmHandler(t *testing.T) { Issuer: account, Audience: agent.DID(), Attenuations: []access.CapabilityRequest{ - {Command: "/space/blob/add"}, - {Command: "/upload/add"}, + {Command: command.MustParse("/blob/add")}, + {Command: command.MustParse("/upload/add")}, }, } diff --git a/pkg/service/handlers/access_delegate.go b/pkg/service/handlers/access_delegate.go index a02710a..b438356 100644 --- a/pkg/service/handlers/access_delegate.go +++ b/pkg/service/handlers/access_delegate.go @@ -6,21 +6,19 @@ import ( "github.com/fil-forge/libforge/commands/access" "github.com/fil-forge/sprue/pkg/provisioning" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan" "github.com/ipfs/go-cid" "go.uber.org/zap" ) -func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { +func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioningSvc *provisioning.Service, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", access.Delegate)) - return Handler{ - Command: access.Delegate.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*access.DelegateArguments], - res *bindexec.Response[*access.DelegateOK], - ) error { + return server.NewRoute( + access.Delegate, + func(req *binding.Request[*access.DelegateArguments], res *binding.Response[*access.DelegateOK]) error { args := req.Task().Arguments() agent := req.Invocation().Issuer() space := req.Invocation().Subject() @@ -53,8 +51,8 @@ func NewAccessDelegateHandler(delegationStore delegation_store.Store, provisioni } return res.SetSuccess(&access.DelegateOK{}) - }), - } + }, + ) } func extractDelegations(args *access.DelegateArguments, meta ucan.Container) ([]ucan.Token, error) { diff --git a/pkg/service/handlers/access_delegate_test.go b/pkg/service/handlers/access_delegate_test.go index d39f5eb..01038f4 100644 --- a/pkg/service/handlers/access_delegate_test.go +++ b/pkg/service/handlers/access_delegate_test.go @@ -13,6 +13,7 @@ import ( subscriptionmemory "github.com/fil-forge/sprue/pkg/store/subscription/memory" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ucan/command" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" @@ -92,7 +93,7 @@ func TestAccessDelegateHandler(t *testing.T) { agent := testutil.RandomSigner(t) // Create a delegation from the space to the agent for some capability. - dlg, err := delegation.Delegate(space, agent.DID(), space.DID(), "/space/blob/add") + dlg, err := delegation.Delegate(space, agent.DID(), space.DID(), command.MustParse("/blob/add")) require.NoError(t, err) args := access.DelegateArguments{ @@ -165,10 +166,10 @@ func TestAccessDelegateHandler(t *testing.T) { // Reference a delegation by CID, but don't include it in the request metadata. // We still need at least one delegation in the request so req.Metadata() is non-nil. - other, err := delegation.Delegate(space, agent.DID(), space.DID(), "/other") + other, err := delegation.Delegate(space, agent.DID(), space.DID(), command.MustParse("/other")) require.NoError(t, err) - missing, err := delegation.Delegate(space, agent.DID(), space.DID(), "/space/blob/add") + missing, err := delegation.Delegate(space, agent.DID(), space.DID(), command.MustParse("/blob/add")) require.NoError(t, err) args := access.DelegateArguments{ diff --git a/pkg/service/handlers/access_request.go b/pkg/service/handlers/access_request.go index 66fe177..a8c7f1a 100644 --- a/pkg/service/handlers/access_request.go +++ b/pkg/service/handlers/access_request.go @@ -13,9 +13,10 @@ import ( "github.com/fil-forge/sprue/internal/config" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/mailer" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/ipld/datamodel" + "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" @@ -32,14 +33,11 @@ var ( ErrInvalidAuthorizationAudience = errors.New(access.InvalidAuthorizationAudienceErrorName, "invalid authorization audience DID") ) -func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identity, mailer mailer.Mailer, logger *zap.Logger) Handler { +func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identity, mailer mailer.Mailer, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", access.Request)) - return Handler{ - Command: access.Request.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*access.RequestArguments], - res *bindexec.Response[*access.RequestOK], - ) error { + return server.NewRoute( + access.Request, + func(req *binding.Request[*access.RequestArguments], res *binding.Response[*access.RequestOK]) error { args := req.Task().Arguments() account, err := didmailto.Parse(args.Issuer.String()) if err != nil { @@ -149,6 +147,6 @@ func NewAccessRequestHandler(serverCfg config.ServerConfig, id *identity.Identit // let client know when the confirmation will expire Expiration: int64(exp), }) - }), - } + }, + ) } diff --git a/pkg/service/handlers/access_request_test.go b/pkg/service/handlers/access_request_test.go index 0788630..328c59e 100644 --- a/pkg/service/handlers/access_request_test.go +++ b/pkg/service/handlers/access_request_test.go @@ -14,6 +14,7 @@ import ( "github.com/fil-forge/sprue/pkg/identity" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" + "github.com/fil-forge/ucantone/ucan/command" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -57,7 +58,7 @@ func TestAccessRequestHandler(t *testing.T) { args := access.RequestArguments{ Issuer: account, Attenuations: []access.CapabilityRequest{ - {Command: "/"}, + {Command: command.Top()}, }, } @@ -102,7 +103,7 @@ func TestAccessRequestHandler(t *testing.T) { args := access.RequestArguments{ Issuer: nonMailtoSigner.DID(), Attenuations: []access.CapabilityRequest{ - {Command: "/"}, + {Command: command.Top()}, }, } @@ -142,7 +143,7 @@ func TestAccessRequestHandler(t *testing.T) { args := access.RequestArguments{ Issuer: account, Attenuations: []access.CapabilityRequest{ - {Command: "/"}, + {Command: command.Top()}, }, } @@ -180,7 +181,7 @@ func TestAccessRequestHandler(t *testing.T) { args := access.RequestArguments{ Issuer: account, Attenuations: []access.CapabilityRequest{ - {Command: "/"}, + {Command: command.Top()}, }, } diff --git a/pkg/service/handlers/admin_provider_deregister.go b/pkg/service/handlers/admin_provider_deregister.go index 160f9c4..05e8abe 100644 --- a/pkg/service/handlers/admin_provider_deregister.go +++ b/pkg/service/handlers/admin_provider_deregister.go @@ -4,19 +4,17 @@ import ( "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" "go.uber.org/zap" ) -func NewAdminProviderDeregisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { +func NewAdminProviderDeregisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", provider.Deregister)) - return Handler{ - Command: provider.Deregister.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*provider.DeregisterArguments], - res *bindexec.Response[*provider.DeregisterOK], - ) error { + return server.NewRoute( + provider.Deregister, + func(req *binding.Request[*provider.DeregisterArguments], res *binding.Response[*provider.DeregisterOK]) error { args := req.Task().Arguments() if req.Invocation().Issuer() != id.Signer.DID() { @@ -34,6 +32,6 @@ func NewAdminProviderDeregisterHandler(id *identity.Identity, providerStore stor return err } return res.SetSuccess(&provider.DeregisterOK{}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/admin_provider_list.go b/pkg/service/handlers/admin_provider_list.go index 1461884..918f6a8 100644 --- a/pkg/service/handlers/admin_provider_list.go +++ b/pkg/service/handlers/admin_provider_list.go @@ -9,18 +9,16 @@ import ( "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/store" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" ) -func NewAdminProviderListHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { +func NewAdminProviderListHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", provider.List)) - return Handler{ - Command: provider.List.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*provider.ListArguments], - res *bindexec.Response[*provider.ListOK], - ) error { + return server.NewRoute( + provider.List, + func(req *binding.Request[*provider.ListArguments], res *binding.Response[*provider.ListOK]) error { if req.Invocation().Issuer() != id.Signer.DID() { log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer())) return res.SetFailure(errors.New("Unauthorized", "only the service identity can list providers")) @@ -53,6 +51,6 @@ func NewAdminProviderListHandler(id *identity.Identity, providerStore storagepro } return res.SetSuccess(&provider.ListOK{Providers: providers}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index 5c11c06..c10224e 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -6,8 +6,9 @@ import ( "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" "go.uber.org/zap" ) @@ -16,14 +17,11 @@ var ( initialReplicationWeight = 0 ) -func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { +func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", provider.Register)) - return Handler{ - Command: provider.Register.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*provider.RegisterArguments], - res *bindexec.Response[*provider.RegisterOK], - ) error { + return server.NewRoute( + provider.Register, + func(req *binding.Request[*provider.RegisterArguments], res *binding.Response[*provider.RegisterOK]) error { args := req.Task().Arguments() if req.Invocation().Issuer() != id.Signer.DID() { log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer())) @@ -53,6 +51,6 @@ func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storag return err } return res.SetSuccess(&provider.RegisterOK{}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/admin_provider_weight_set.go b/pkg/service/handlers/admin_provider_weight_set.go index ac6c2fd..22f4d4b 100644 --- a/pkg/service/handlers/admin_provider_weight_set.go +++ b/pkg/service/handlers/admin_provider_weight_set.go @@ -6,18 +6,16 @@ import ( "github.com/fil-forge/sprue/pkg/commands/admin/provider/weight" "github.com/fil-forge/sprue/pkg/identity" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" ) -func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) Handler { +func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", weight.Set)) - return Handler{ - Command: weight.Set.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*weight.SetArguments], - res *bindexec.Response[*weight.SetOK], - ) error { + return server.NewRoute( + weight.Set, + func(req *binding.Request[*weight.SetArguments], res *binding.Response[*weight.SetOK]) error { args := req.Task().Arguments() if req.Invocation().Issuer() != id.Signer.DID() { log.Warn("Unauthorized access attempt", zap.Stringer("issuer", req.Invocation().Issuer())) @@ -41,6 +39,6 @@ func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore stora return err } return res.SetSuccess(&weight.SetOK{}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index d7b8526..09b66c2 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -17,12 +17,13 @@ import ( "github.com/fil-forge/sprue/pkg/routing" "github.com/fil-forge/sprue/pkg/store/agent" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/principal" ed25519signer "github.com/fil-forge/ucantone/principal/ed25519" + "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" @@ -33,14 +34,11 @@ import ( "go.uber.org/zap" ) -func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, router *routing.Service, nodeProvider piriclient.Provider, agentStore agent.Store, blobRegistry blobregistry.Store, logger *zap.Logger) Handler { +func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, router *routing.Service, nodeProvider piriclient.Provider, agentStore agent.Store, blobRegistry blobregistry.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", blobcmds.Add)) - return Handler{ - Command: blobcmds.Add.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*blobcmds.AddArguments], - res *bindexec.Response[*blobcmds.AddOK], - ) error { + return server.NewRoute( + blobcmds.Add, + func(req *binding.Request[*blobcmds.AddArguments], res *binding.Response[*blobcmds.AddOK]) error { args := req.Task().Arguments() blob := args.Blob space := req.Invocation().Subject() @@ -192,8 +190,8 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv Task: accInv.Task().Link(), }, }) - }), - } + }, + ) } func doAllocate( diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go index 5d76e3b..0fc11dc 100644 --- a/pkg/service/handlers/blob_add_test.go +++ b/pkg/service/handlers/blob_add_test.go @@ -29,10 +29,10 @@ import ( spacediff_store "github.com/fil-forge/sprue/pkg/store/space_diff/memory" storage_provider_store "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" subscription_store "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/did" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/principal" ed25519signer "github.com/fil-forge/ucantone/principal/ed25519" "github.com/fil-forge/ucantone/principal/signer" @@ -52,7 +52,7 @@ import ( ) type blobAddTestDeps struct { - handler handlers.Handler + handler server.Route consumerStore *consumer_store.Store subscriptionStore *subscription_store.Store spStore *storage_provider_store.Store @@ -136,16 +136,16 @@ func newMockPiriServer( server.WithValidationOptions(validator.WithDIDVerifierResolver(resolveDIDKey)), ) - srv.Handle(blobcmds.Allocate.Command, bindexec.NewHandler(func( - req *bindexec.Request[*blobcmds.AllocateArguments], - res *bindexec.Response[*blobcmds.AllocateOK], + srv.Handle(blobcmds.Allocate.Command, blobcmds.Allocate.Handler(func( + req *binding.Request[*blobcmds.AllocateArguments], + res *binding.Response[*blobcmds.AllocateOK], ) error { return res.SetSuccess(allocateOK) })) - srv.Handle(blobcmds.Accept.Command, bindexec.NewHandler(func( - req *bindexec.Request[*blobcmds.AcceptArguments], - res *bindexec.Response[*blobcmds.AcceptOK], + srv.Handle(blobcmds.Accept.Command, blobcmds.Accept.Handler(func( + req *binding.Request[*blobcmds.AcceptArguments], + res *binding.Response[*blobcmds.AcceptOK], ) error { return res.SetSuccess(acceptOK) })) diff --git a/pkg/service/handlers/blob_list.go b/pkg/service/handlers/blob_list.go index 742da71..f6ba878 100644 --- a/pkg/service/handlers/blob_list.go +++ b/pkg/service/handlers/blob_list.go @@ -5,18 +5,16 @@ import ( blobcmds "github.com/fil-forge/libforge/commands/blob" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/binding" + "github.com/fil-forge/ucantone/server" "go.uber.org/zap" ) -func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Handler { +func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", blobcmds.List)) - return Handler{ - Command: blobcmds.List.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*blobcmds.ListArguments], - res *bindexec.Response[*blobcmds.ListOK], - ) error { + return server.NewRoute( + blobcmds.List, + func(req *binding.Request[*blobcmds.ListArguments], res *binding.Response[*blobcmds.ListOK]) error { args := req.Task().Arguments() space := req.Invocation().Subject() log := log.With(zap.Stringer("space", space)) @@ -50,6 +48,6 @@ func NewBlobListHandler(blobRegistry blobregistry.Store, logger *zap.Logger) Han Cursor: page.Cursor, Results: results, }) - }), - } + }, + ) } diff --git a/pkg/service/handlers/handlers.go b/pkg/service/handlers/handlers.go deleted file mode 100644 index ca63991..0000000 --- a/pkg/service/handlers/handlers.go +++ /dev/null @@ -1,11 +0,0 @@ -package handlers - -import ( - "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/ucan" -) - -type Handler struct { - Command ucan.Command - Handler execution.HandlerFunc -} diff --git a/pkg/service/handlers/index_add.go b/pkg/service/handlers/index_add.go index ffaa40a..620b8a7 100644 --- a/pkg/service/handlers/index_add.go +++ b/pkg/service/handlers/index_add.go @@ -12,18 +12,16 @@ import ( "github.com/fil-forge/sprue/pkg/indexerclient" "github.com/fil-forge/sprue/pkg/provisioning" blobregistry "github.com/fil-forge/sprue/pkg/store/blob_registry" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" ) -func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) Handler { +func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Service, blobRegistry blobregistry.Store, indexerClient *indexerclient.Client, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", indexcmds.Add)) - return Handler{ - Command: indexcmds.Add.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*indexcmds.AddArguments], - res *bindexec.Response[*indexcmds.AddOK], - ) error { + return server.NewRoute( + indexcmds.Add, + func(req *binding.Request[*indexcmds.AddArguments], res *binding.Response[*indexcmds.AddOK]) error { args := req.Task().Arguments() space := req.Invocation().Subject() index := args.Index @@ -66,6 +64,6 @@ func NewIndexAddHandler(id *identity.Identity, provisioningSvc *provisioning.Ser } return res.SetSuccess(&indexcmds.AddOK{}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/index_add_test.go b/pkg/service/handlers/index_add_test.go index bf65c2b..e6cbe96 100644 --- a/pkg/service/handlers/index_add_test.go +++ b/pkg/service/handlers/index_add_test.go @@ -21,10 +21,10 @@ import ( "github.com/fil-forge/sprue/pkg/service/handlers" consumer_store "github.com/fil-forge/sprue/pkg/store/consumer/memory" subscription_store "github.com/fil-forge/sprue/pkg/store/subscription/memory" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/did" edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" - "github.com/fil-forge/ucantone/execution/bindexec" "github.com/fil-forge/ucantone/principal" "github.com/fil-forge/ucantone/principal/signer" "github.com/fil-forge/ucantone/principal/verifier" @@ -63,9 +63,9 @@ func newMockIndexerServer( server.WithValidationOptions(validator.WithDIDVerifierResolver(resolveDIDKey)), ) - srv.Handle(assertcmds.Index.Command, bindexec.NewHandler(func( - req *bindexec.Request[*assertcmds.IndexArguments], - res *bindexec.Response[*assertcmds.IndexOK], + srv.Handle(assertcmds.Index.Command, assertcmds.Index.Handler(func( + req *binding.Request[*assertcmds.IndexArguments], + res *binding.Response[*assertcmds.IndexOK], ) error { return res.SetSuccess(indexOK) })) diff --git a/pkg/service/handlers/provider_add.go b/pkg/service/handlers/provider_add.go index 1845a19..2f6339e 100644 --- a/pkg/service/handlers/provider_add.go +++ b/pkg/service/handlers/provider_add.go @@ -9,19 +9,17 @@ import ( "github.com/fil-forge/sprue/pkg/billing" "github.com/fil-forge/sprue/pkg/provisioning" "github.com/fil-forge/sprue/pkg/store/consumer" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" "go.uber.org/zap" ) -func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) Handler { +func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSvc *provisioning.Service, billingSvc *billing.Service, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", providercmds.Add)) - return Handler{ - Command: providercmds.Add.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*providercmds.AddArguments], - res *bindexec.Response[*providercmds.AddOK], - ) error { + return server.NewRoute( + providercmds.Add, + func(req *binding.Request[*providercmds.AddArguments], res *binding.Response[*providercmds.AddOK]) error { args := req.Task().Arguments() account, err := didmailto.Parse(req.Invocation().Subject().String()) if err != nil { @@ -70,6 +68,6 @@ func NewProviderAddHandler(deploymentCfg config.DeploymentConfig, provisioningSv log.Debug("service provisioned successfully", zap.String("subscription", sub)) return res.SetSuccess(&providercmds.AddOK{ID: sub}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/space_info.go b/pkg/service/handlers/space_info.go index e70bff0..08fa0df 100644 --- a/pkg/service/handlers/space_info.go +++ b/pkg/service/handlers/space_info.go @@ -6,20 +6,18 @@ import ( spacecmds "github.com/fil-forge/libforge/commands/space" "github.com/fil-forge/sprue/pkg/provisioning" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" "go.uber.org/zap" ) // This handler returns info about a space, including its providers. -func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logger) Handler { +func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", spacecmds.Info)) - return Handler{ - Command: spacecmds.Info.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*spacecmds.InfoArguments], - res *bindexec.Response[*spacecmds.InfoOK], - ) error { + return server.NewRoute( + spacecmds.Info, + func(req *binding.Request[*spacecmds.InfoArguments], res *binding.Response[*spacecmds.InfoOK]) error { space := req.Invocation().Subject() log := log.With(zap.Stringer("space", space)) log.Debug("getting space info") @@ -36,6 +34,6 @@ func NewSpaceInfoHandler(provisioningSvc *provisioning.Service, logger *zap.Logg } return res.SetSuccess(&spacecmds.InfoOK{Providers: providers}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/ucan_conclude.go b/pkg/service/handlers/ucan_conclude.go index 88d45f6..ab58174 100644 --- a/pkg/service/handlers/ucan_conclude.go +++ b/pkg/service/handlers/ucan_conclude.go @@ -9,8 +9,9 @@ import ( ucancmds "github.com/fil-forge/libforge/commands/ucan" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/store/agent" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan" "go.uber.org/zap" ) @@ -31,15 +32,12 @@ type ConclusionHandler struct { // This handler processes receipt conclusions from clients. // When it receives an /http/put receipt, it calls /blob/accept on piri // and stores the accept receipt for later retrieval. -func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handlers map[ucan.Command]ConclusionHandlerFunc, logger *zap.Logger) Handler { +func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handlers map[ucan.Command]ConclusionHandlerFunc, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", ucancmds.Conclude)) log.Info("registered conclude handlers", zap.Stringers("commands", slices.Collect(maps.Keys(handlers)))) - return Handler{ - Command: ucancmds.Conclude.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*ucancmds.ConcludeArguments], - res *bindexec.Response[*ucancmds.ConcludeOK], - ) error { + return server.NewRoute( + ucancmds.Conclude, + func(req *binding.Request[*ucancmds.ConcludeArguments], res *binding.Response[*ucancmds.ConcludeOK]) error { args := req.Task().Arguments() rcptRoot := args.Receipt @@ -96,6 +94,6 @@ func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handl } return res.SetSuccess(&ucancmds.ConcludeOK{}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/ucan_conclude_test.go b/pkg/service/handlers/ucan_conclude_test.go index 0d475ea..8e712eb 100644 --- a/pkg/service/handlers/ucan_conclude_test.go +++ b/pkg/service/handlers/ucan_conclude_test.go @@ -15,6 +15,7 @@ import ( "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/command" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/fil-forge/ucantone/ucan/receipt" @@ -50,7 +51,7 @@ func TestUCANConcludeHandler(t *testing.T) { &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - _, rcpt := newTaskAndReceipt(t, "/test/thing") + _, rcpt := newTaskAndReceipt(t, command.MustParse("/test/thing")) concludeInv, err := ucancmds.Conclude.Invoke( uploadService, @@ -87,7 +88,7 @@ func TestUCANConcludeHandler(t *testing.T) { // Receipt is supplied but the ran invocation is neither in the request // metadata nor in the agent store — the handler treats this as a no-op. - _, rcpt := newTaskAndReceipt(t, "/test/thing") + _, rcpt := newTaskAndReceipt(t, command.MustParse("/test/thing")) concludeInv, err := ucancmds.Conclude.Invoke( uploadService, @@ -116,7 +117,7 @@ func TestUCANConcludeHandler(t *testing.T) { gotRcpt ucan.Receipt ) handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{ - "/test/thing": func(_ context.Context, inv ucan.Invocation, rcpt ucan.Receipt, _ ucan.Container) error { + command.MustParse("/test/thing"): func(_ context.Context, inv ucan.Invocation, rcpt ucan.Receipt, _ ucan.Container) error { called = true gotInv = inv gotRcpt = rcpt @@ -128,7 +129,7 @@ func TestUCANConcludeHandler(t *testing.T) { &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - taskInv, rcpt := newTaskAndReceipt(t, "/test/thing") + taskInv, rcpt := newTaskAndReceipt(t, command.MustParse("/test/thing")) // Persist the task invocation in the agent store so the handler can // look it up by the receipt's ran CID. @@ -169,7 +170,7 @@ func TestUCANConcludeHandler(t *testing.T) { &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - taskInv, rcpt := newTaskAndReceipt(t, "/test/unhandled") + taskInv, rcpt := newTaskAndReceipt(t, command.MustParse("/test/unhandled")) msg := container.New( container.WithInvocations(taskInv), @@ -200,7 +201,7 @@ func TestUCANConcludeHandler(t *testing.T) { var called bool handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{ - "/test/thing": func(_ context.Context, _ ucan.Invocation, _ ucan.Receipt, _ ucan.Container) error { + command.MustParse("/test/thing"): func(_ context.Context, _ ucan.Invocation, _ ucan.Receipt, _ ucan.Container) error { called = true return nil }, @@ -210,7 +211,7 @@ func TestUCANConcludeHandler(t *testing.T) { &identity.Identity{Signer: uploadService}, agentStore, handlerMap, logger, ) - taskInv, rcpt := newTaskAndReceipt(t, "/test/thing") + taskInv, rcpt := newTaskAndReceipt(t, command.MustParse("/test/thing")) // The ran invocation is supplied directly in the request metadata — // no agent-store lookup required. diff --git a/pkg/service/handlers/upload_add.go b/pkg/service/handlers/upload_add.go index 46789aa..bdb25f2 100644 --- a/pkg/service/handlers/upload_add.go +++ b/pkg/service/handlers/upload_add.go @@ -7,20 +7,18 @@ import ( uploadcmds "github.com/fil-forge/libforge/commands/upload" "github.com/fil-forge/sprue/pkg/provisioning" upload_store "github.com/fil-forge/sprue/pkg/store/upload" + "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/server" "go.uber.org/zap" ) // This handler registers an upload (root CID + shards mapping). -func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore upload_store.Store, logger *zap.Logger) Handler { +func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore upload_store.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", uploadcmds.Add)) - return Handler{ - Command: uploadcmds.Add.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*uploadcmds.AddArguments], - res *bindexec.Response[*uploadcmds.AddOK], - ) error { + return server.NewRoute( + uploadcmds.Add, + func(req *binding.Request[*uploadcmds.AddArguments], res *binding.Response[*uploadcmds.AddOK]) error { args := req.Task().Arguments() space := req.Invocation().Subject() cause := req.Invocation().Task().Link() @@ -50,6 +48,6 @@ func NewUploadAddHandler(provisioningSvc *provisioning.Service, uploadStore uplo } return res.SetSuccess(&uploadcmds.AddOK{}) - }), - } + }, + ) } diff --git a/pkg/service/handlers/upload_add_test.go b/pkg/service/handlers/upload_add_test.go index 5b3d0ec..780bd25 100644 --- a/pkg/service/handlers/upload_add_test.go +++ b/pkg/service/handlers/upload_add_test.go @@ -18,6 +18,7 @@ import ( edm "github.com/fil-forge/ucantone/errors/datamodel" "github.com/fil-forge/ucantone/execution" "github.com/fil-forge/ucantone/principal" + "github.com/fil-forge/ucantone/server" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" @@ -26,7 +27,7 @@ import ( ) type uploadAddDeps struct { - handler handlers.Handler + route server.Route store *upload_store.Store consumerStore *consumer_store.Store } @@ -40,8 +41,8 @@ func newUploadAddDeps(t *testing.T, uploadService principal.Signer, logger *zap. subscription_store.New(), ) store := upload_store.New() - handler := handlers.NewUploadAddHandler(provisioningSvc, store, logger) - return &uploadAddDeps{handler: handler, store: store, consumerStore: consumerStore} + route := handlers.NewUploadAddHandler(provisioningSvc, store, logger) + return &uploadAddDeps{route: route, store: store, consumerStore: consumerStore} } // invokeUploadAdd builds an /upload/add invocation with the given args and a @@ -97,7 +98,7 @@ func TestUploadAddHandler(t *testing.T) { root := testutil.RandomCID(t) req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcmds.AddArguments{Root: root}) - err := deps.handler.Handler(req, res) + err := deps.route.Handler(req, res) require.NoError(t, err) _, x := res.Receipt().Out().Unpack() @@ -122,7 +123,7 @@ func TestUploadAddHandler(t *testing.T) { root := testutil.RandomCID(t) req, res := invokeUploadAdd(t, ctx, alice, uploadService, space, &uploadcmds.AddArguments{Root: root}) - err := deps.handler.Handler(req, res) + err := deps.route.Handler(req, res) require.NoError(t, err) require.False(t, res.Receipt().Out().IsErr()) @@ -153,7 +154,7 @@ func TestUploadAddHandler(t *testing.T) { Shards: []cid.Cid{shard1, shard2}, }) - err := deps.handler.Handler(req, res) + err := deps.route.Handler(req, res) require.NoError(t, err) require.False(t, res.Receipt().Out().IsErr()) @@ -177,7 +178,7 @@ func TestUploadAddHandler(t *testing.T) { Index: &index, }) - err := deps.handler.Handler(req, res) + err := deps.route.Handler(req, res) require.NoError(t, err) require.False(t, res.Receipt().Out().IsErr()) @@ -200,7 +201,7 @@ func TestUploadAddHandler(t *testing.T) { Root: root, Shards: []cid.Cid{shard1}, }) - require.NoError(t, deps.handler.Handler(req1, res1)) + require.NoError(t, deps.route.Handler(req1, res1)) require.False(t, res1.Receipt().Out().IsErr()) // Add again with a new shard. @@ -209,7 +210,7 @@ func TestUploadAddHandler(t *testing.T) { Root: root, Shards: []cid.Cid{shard2}, }) - require.NoError(t, deps.handler.Handler(req2, res2)) + require.NoError(t, deps.route.Handler(req2, res2)) require.False(t, res2.Receipt().Out().IsErr()) // Upload should still exist. diff --git a/pkg/service/handlers/upload_list.go b/pkg/service/handlers/upload_list.go index da406f2..15acebd 100644 --- a/pkg/service/handlers/upload_list.go +++ b/pkg/service/handlers/upload_list.go @@ -5,18 +5,16 @@ import ( uploadcmds "github.com/fil-forge/libforge/commands/upload" upload_store "github.com/fil-forge/sprue/pkg/store/upload" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/binding" + "github.com/fil-forge/ucantone/server" "go.uber.org/zap" ) -func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { +func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", uploadcmds.List)) - return Handler{ - Command: uploadcmds.List.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*uploadcmds.ListArguments], - res *bindexec.Response[*uploadcmds.ListOK], - ) error { + return server.NewRoute( + uploadcmds.List, + func(req *binding.Request[*uploadcmds.ListArguments], res *binding.Response[*uploadcmds.ListOK]) error { args := req.Task().Arguments() space := req.Invocation().Subject() log := log.With(zap.Stringer("space", space)) @@ -50,6 +48,6 @@ func NewUploadListHandler(uploadStore upload_store.Store, logger *zap.Logger) Ha Results: results, Cursor: page.Cursor, }) - }), - } + }, + ) } diff --git a/pkg/service/handlers/upload_shard_list.go b/pkg/service/handlers/upload_shard_list.go index 46feb4b..bdad70b 100644 --- a/pkg/service/handlers/upload_shard_list.go +++ b/pkg/service/handlers/upload_shard_list.go @@ -5,19 +5,17 @@ import ( shardcmds "github.com/fil-forge/libforge/commands/upload/shard" upload_store "github.com/fil-forge/sprue/pkg/store/upload" - "github.com/fil-forge/ucantone/execution/bindexec" + "github.com/fil-forge/ucantone/binding" + "github.com/fil-forge/ucantone/server" "go.uber.org/zap" ) // This handler lists the shards of an upload. -func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logger) Handler { +func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", shardcmds.List)) - return Handler{ - Command: shardcmds.List.Command, - Handler: bindexec.NewHandler(func( - req *bindexec.Request[*shardcmds.ListArguments], - res *bindexec.Response[*shardcmds.ListOK], - ) error { + return server.NewRoute( + shardcmds.List, + func(req *binding.Request[*shardcmds.ListArguments], res *binding.Response[*shardcmds.ListOK]) error { args := req.Task().Arguments() space := req.Invocation().Subject() root := args.Root @@ -44,6 +42,6 @@ func NewUploadShardListHandler(uploadStore upload_store.Store, logger *zap.Logge Results: page.Results, Cursor: page.Cursor, }) - }), - } + }, + ) } diff --git a/pkg/service/service.go b/pkg/service/service.go index d5dbca2..9d420f7 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -10,7 +10,6 @@ import ( "github.com/fil-forge/libforge/didresolver" "github.com/fil-forge/sprue/pkg/identity" "github.com/fil-forge/sprue/pkg/lib/ucan_server" - "github.com/fil-forge/sprue/pkg/service/handlers" "github.com/fil-forge/sprue/pkg/service/ui" "github.com/fil-forge/sprue/pkg/store/agent" delegation_store "github.com/fil-forge/sprue/pkg/store/delegation" @@ -35,7 +34,7 @@ type Service struct { } // New creates a new Service instance. -func New(id *identity.Identity, agentStore agent.Store, delegationStore delegation_store.Store, handlers []handlers.Handler, logger *zap.Logger, options ...server.HTTPOption) (*Service, error) { +func New(id *identity.Identity, agentStore agent.Store, delegationStore delegation_store.Store, handlers []server.Route, logger *zap.Logger, options ...server.HTTPOption) (*Service, error) { server, err := createUCANServer(id.Signer, agentStore, handlers, logger, options...) if err != nil { return nil, err @@ -50,7 +49,7 @@ func New(id *identity.Identity, agentStore agent.Store, delegationStore delegati } // createUCANServer creates the UCAN RPC server with registered handlers. -func createUCANServer(id principal.Signer, agentStore agent.Store, handlers []handlers.Handler, logger *zap.Logger, options ...server.HTTPOption) (*server.HTTPServer, error) { +func createUCANServer(id principal.Signer, agentStore agent.Store, handlers []server.Route, logger *zap.Logger, options ...server.HTTPOption) (*server.HTTPServer, error) { httpResolver, err := didresolver.NewHTTPResolver() if err != nil { return nil, err @@ -63,8 +62,8 @@ func createUCANServer(id principal.Signer, agentStore agent.Store, handlers []ha options = append( slices.Clone(options), server.WithReceiptTimestamps(true), - server.WithEventListener(ucan_server.AgentMessageLogger{Logger: logger, AgentStore: agentStore}), - server.WithEventListener(ucan_server.ErrorHandler{Logger: logger}), + server.WithEventListener(&ucan_server.AgentMessageLogger{Logger: logger, AgentStore: agentStore}), + server.WithEventListener(&ucan_server.ErrorHandler{Logger: logger}), server.WithValidationOptions( validator.WithDIDVerifierResolvers(map[string]validator.DIDVerifierResolverFunc{ "key": ucan_server.ResolveDIDKey, diff --git a/pkg/store/agent/agent_test.go b/pkg/store/agent/agent_test.go index 7d17d0d..a0016d4 100644 --- a/pkg/store/agent/agent_test.go +++ b/pkg/store/agent/agent_test.go @@ -13,6 +13,7 @@ import ( agentpostgres "github.com/fil-forge/sprue/pkg/store/agent/postgres" "github.com/fil-forge/ucantone/ipld/datamodel" "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/command" "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/fil-forge/ucantone/ucan/receipt" @@ -99,7 +100,7 @@ func makeInvocation(t *testing.T) ucan.Invocation { inv, err := invocation.Invoke( testutil.Alice, testutil.Alice.DID(), - "/test/invoke", + command.MustParse("/test/invoke"), datamodel.Map{}, invocation.WithAudience(testutil.Bob.DID()), ) diff --git a/pkg/store/delegation/delegation_test.go b/pkg/store/delegation/delegation_test.go index 0ec8e90..d325108 100644 --- a/pkg/store/delegation/delegation_test.go +++ b/pkg/store/delegation/delegation_test.go @@ -13,6 +13,7 @@ import ( delegationpostgres "github.com/fil-forge/sprue/pkg/store/delegation/postgres" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/command" "github.com/fil-forge/ucantone/ucan/delegation" "github.com/google/uuid" "github.com/stretchr/testify/require" @@ -90,7 +91,7 @@ func makeDelegation(t *testing.T, audience did.DID) ucan.Delegation { testutil.Alice, audience, testutil.Alice.DID(), - "/test/delegate", + command.MustParse("/test/delegate"), ) require.NoError(t, err) return dlg From 759bac2897d859ba3cf2f5544440974dcac7cc44 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 22 May 2026 17:00:26 +0100 Subject: [PATCH 23/23] chore: appease linter --- pkg/store/agent/memory/store.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/store/agent/memory/store.go b/pkg/store/agent/memory/store.go index 6243534..6e8855b 100644 --- a/pkg/store/agent/memory/store.go +++ b/pkg/store/agent/memory/store.go @@ -114,12 +114,9 @@ func (s *Store) List(ctx context.Context, task cid.Cid, options ...agent.ListOpt } var msgLinks []cid.Cid - for _, l := range s.index[fmt.Sprintf("/%s/invocation/", task)] { - msgLinks = append(msgLinks, l) - } - for _, l := range s.index[fmt.Sprintf("/%s/receipt/", task)] { - msgLinks = append(msgLinks, l) - } + msgLinks = append(msgLinks, s.index[fmt.Sprintf("/%s/invocation/", task)]...) + msgLinks = append(msgLinks, s.index[fmt.Sprintf("/%s/receipt/", task)]...) + slices.SortFunc(msgLinks, func(a, b cid.Cid) int { return bytes.Compare(a.Bytes(), b.Bytes()) })