From 3ac007e5ee643c9bf33d4356fcf4d2c14d159b71 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Jun 2026 10:52:32 +0100 Subject: [PATCH 1/8] fix: pre-authorize allocate and accept invocations to storage nodes --- cmd/client/admin/provider/register.go | 32 ++++++++++-- go.mod | 2 +- go.sum | 2 + internal/migrations/sql/00001_init.sql | 1 + pkg/client/client.go | 13 ++++- pkg/commands/admin/provider/cbor_gen.go | 51 ++++++++++++++++++- pkg/commands/admin/provider/json_gen.go | 41 +++++++++++++++ pkg/commands/admin/provider/types.go | 1 + pkg/piriclient/client.go | 19 ++++--- pkg/routing/service.go | 8 +++ pkg/routing/service_test.go | 3 +- .../admin_provider_deregister_test.go | 5 +- .../handlers/admin_provider_list_test.go | 5 +- .../handlers/admin_provider_register.go | 9 +++- .../handlers/admin_provider_register_test.go | 16 ++++++ .../handlers/admin_provider_weight_set.go | 2 +- .../admin_provider_weight_set_test.go | 3 +- pkg/service/handlers/blob_add.go | 14 +++-- pkg/service/handlers/blob_add_test.go | 34 +++++++------ .../handlers/ucan_conclude_http_put.go | 25 +++++---- .../handlers/ucan_conclude_http_put_test.go | 22 ++++---- pkg/store/storage_provider/aws/store.go | 26 ++++++++-- pkg/store/storage_provider/memory/store.go | 9 +++- pkg/store/storage_provider/postgres/store.go | 35 ++++++++++--- .../storage_provider/storage_provider.go | 6 ++- .../storage_provider/storage_provider_test.go | 44 +++++++++++++--- 26 files changed, 338 insertions(+), 90 deletions(-) diff --git a/cmd/client/admin/provider/register.go b/cmd/client/admin/provider/register.go index d8a44af..e15db19 100644 --- a/cmd/client/admin/provider/register.go +++ b/cmd/client/admin/provider/register.go @@ -2,18 +2,24 @@ package provider import ( "net/url" + "os" "github.com/fil-forge/sprue/cmd/client/lib" "github.com/fil-forge/ucantone/did" + "github.com/fil-forge/ucantone/ucan/container" "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), - RunE: doRegister, + Long: "Register a storage provider with the service.\n\n" + + " is a UCAN container granting the upload service `/blob/allocate`,\n" + + "`/blob/accept` and `/blob/replica/allocate`. It may be passed inline as a\n" + + "string or as a path to a file containing the encoded container.", + Args: cobra.ExactArgs(3), + RunE: doRegister, } func doRegister(cmd *cobra.Command, args []string) error { @@ -25,9 +31,27 @@ func doRegister(cmd *cobra.Command, args []string) error { endpoint, err := url.Parse(args[1]) cobra.CheckErr(err) - _, err = c.AdminProviderRegister(cmd.Context(), id, endpoint.String()) + proofs, err := decodeProofs(args[2]) + cobra.CheckErr(err) + + _, err = c.AdminProviderRegister(cmd.Context(), id, endpoint.String(), proofs) cobra.CheckErr(err) cmd.Println("Provider registered successfully") return nil } + +// decodeProofs decodes a UCAN container from arg, which is either the encoded +// container itself or a path to a file containing it. The inline form is tried +// first so a file that happens to share the name of a valid container string +// does not shadow it. +func decodeProofs(arg string) (*container.Container, error) { + if ct, err := container.Decode([]byte(arg)); err == nil { + return ct, nil + } + data, err := os.ReadFile(arg) + if err != nil { + return nil, err + } + return container.Decode(data) +} diff --git a/go.mod b/go.mod index f37010d..a7a77f9 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-20260527182359-ebb22552c348 + github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564 github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684 github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 diff --git a/go.sum b/go.sum index acce4a8..9c3d74d 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-20260527182359-ebb22552c348 h1:roYe4llNv0fpQnvXMontrdt/hy9kH5K/cnNsXmhqId4= github.com/fil-forge/libforge v0.0.0-20260527182359-ebb22552c348/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= +github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564 h1:wPNGF6OMBe5c9iiOoaZTD8zA6b7aJK+5xXwoymoIn6A= +github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684 h1:kWJLKVltJXPXO7tKS1z0GhzA+c59gwhediGyByEXE0o= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684/go.mod h1:XAVqsZwYoZ9vncjZoRUAJ+mL/ApLMFn9HHX7ipohVdY= github.com/filecoin-project/go-state-types v0.18.0 h1:oDcjihXRlf2cM176atZzllp79Zc+kcbiuQM9DPL/1a4= diff --git a/internal/migrations/sql/00001_init.sql b/internal/migrations/sql/00001_init.sql index aee9249..e9ca356 100644 --- a/internal/migrations/sql/00001_init.sql +++ b/internal/migrations/sql/00001_init.sql @@ -17,6 +17,7 @@ CREATE TABLE storage_provider ( endpoint TEXT NOT NULL, weight INTEGER NOT NULL, replication_weight INTEGER, + proofs BYTEA, inserted_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); diff --git a/pkg/client/client.go b/pkg/client/client.go index 734d7b4..01baf71 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -12,6 +12,7 @@ import ( "github.com/fil-forge/ucantone/client" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/ucan" + "github.com/fil-forge/ucantone/ucan/container" "github.com/fil-forge/ucantone/ucan/invocation" "go.uber.org/zap" ) @@ -40,11 +41,20 @@ func NewWithClient(uploadServiceID did.DID, client *client.HTTPClient, signer uc } } -func (c *Client) AdminProviderRegister(ctx context.Context, providerID did.DID, endpoint string, options ...invocation.Option) (ucan.Receipt, error) { +func (c *Client) AdminProviderRegister(ctx context.Context, providerID did.DID, endpoint string, proofs ucan.Container, 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) } + if proofs == nil { + return nil, fmt.Errorf("missing proofs") + } + + proofBytes, err := container.Encode(container.Raw, proofs) + if err != nil { + return nil, fmt.Errorf("encoding proofs: %w", err) + } + options = slices.Clone(options) options = append( options, @@ -57,6 +67,7 @@ func (c *Client) AdminProviderRegister(ctx context.Context, providerID did.DID, &providercap.RegisterArguments{ Provider: providerID, Endpoint: endpoint, + Proofs: proofBytes, }, options..., ) diff --git a/pkg/commands/admin/provider/cbor_gen.go b/pkg/commands/admin/provider/cbor_gen.go index 5229e35..23e90b7 100644 --- a/pkg/commands/admin/provider/cbor_gen.go +++ b/pkg/commands/admin/provider/cbor_gen.go @@ -382,7 +382,31 @@ func (t *RegisterArguments) MarshalCBOR(w io.Writer) error { cw := cbg.NewCborWriter(w) - if _, err := cw.Write([]byte{162}); err != nil { + if _, err := cw.Write([]byte{163}); err != nil { + return err + } + + // t.Proofs ([]uint8) (slice) + if len("proofs") > 8192 { + return xerrors.Errorf("Value in field \"proofs\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("proofs"))); err != nil { + return err + } + if _, err := cw.WriteString(string("proofs")); err != nil { + return err + } + + if len(t.Proofs) > 2097152 { + return xerrors.Errorf("Byte array in field t.Proofs was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Proofs))); err != nil { + return err + } + + if _, err := cw.Write(t.Proofs); err != nil { return err } @@ -468,7 +492,30 @@ func (t *RegisterArguments) UnmarshalCBOR(r io.Reader) (err error) { } switch string(nameBuf[:nameLen]) { - // t.Endpoint (string) (string) + // t.Proofs ([]uint8) (slice) + case "proofs": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 2097152 { + return fmt.Errorf("t.Proofs: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Proofs = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Proofs); err != nil { + return err + } + + // t.Endpoint (string) (string) case "endpoint": { diff --git a/pkg/commands/admin/provider/json_gen.go b/pkg/commands/admin/provider/json_gen.go index 4f46f70..5e8120a 100644 --- a/pkg/commands/admin/provider/json_gen.go +++ b/pkg/commands/admin/provider/json_gen.go @@ -393,6 +393,31 @@ func (t *RegisterArguments) MarshalDagJSON(w io.Writer) error { } } + // t.Proofs ([]uint8) (slice) + if len("proofs") > 8192 { + return fmt.Errorf("string in field \"proofs\" was too long") + } + if err := jw.WriteString(string("proofs")); err != nil { + return fmt.Errorf("writing string for field \"proofs\": %w", err) + } + if err := jw.WriteObjectColon(); err != nil { + return err + } + if len(t.Proofs) > 2097152 { + return fmt.Errorf("byte array in field t.Proofs was too long") + } + + if err := jw.WriteBytes(t.Proofs); err != nil { + return fmt.Errorf("writing bytes for field t.Proofs: %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") @@ -459,6 +484,22 @@ func (t *RegisterArguments) UnmarshalDagJSON(r io.Reader) (err error) { t.Endpoint = string(sval) } + // t.Proofs ([]uint8) (slice) + case "proofs": + + { + bval, err := jr.ReadBytes(2097152) + if err != nil { + if errors.Is(err, jsg.ErrLimitExceeded) { + return fmt.Errorf("reading bytes for field t.Proofs: byte array too large") + } + return fmt.Errorf("reading bytes for field t.Proofs: %w", err) + } + if len(bval) > 0 { + t.Proofs = []uint8(bval) + } + } + // t.Provider (did.DID) (struct) case "provider": diff --git a/pkg/commands/admin/provider/types.go b/pkg/commands/admin/provider/types.go index b4754b3..4c76a47 100644 --- a/pkg/commands/admin/provider/types.go +++ b/pkg/commands/admin/provider/types.go @@ -5,6 +5,7 @@ import "github.com/fil-forge/ucantone/did" type RegisterArguments struct { Provider did.DID `cborgen:"provider" dagjsongen:"provider"` Endpoint string `cborgen:"endpoint" dagjsongen:"endpoint"` + Proofs []byte `cborgen:"proofs" dagjsongen:"proofs"` } type Provider struct { diff --git a/pkg/piriclient/client.go b/pkg/piriclient/client.go index 7e20825..a66a8be 100644 --- a/pkg/piriclient/client.go +++ b/pkg/piriclient/client.go @@ -94,7 +94,10 @@ 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(), blobcmds.Allocate.Command, req.Space) + // The proof chain is rooted at the storage provider (the proofs the provider + // granted the upload service at registration), so the subject is the provider + // DID, not the space. The space rides in the invocation arguments instead. + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcmds.Allocate.Command, c.piriDID) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -113,8 +116,9 @@ func (c *Client) AllocateInvocation(ctx context.Context, req *AllocateRequest, p inv, err := blobcmds.Allocate.Invoke( c.signer, - req.Space, + c.piriDID, &blobcmds.AllocateArguments{ + Space: req.Space, Blob: blobcmds.Blob{Digest: req.Digest, Size: req.Size}, Cause: req.Cause, }, @@ -170,7 +174,9 @@ 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(), blobcmds.Accept.Command, req.Space) + // As with allocate, the proof chain is rooted at the storage provider, so the + // subject is the provider DID and the space travels in the arguments. + prfs, prfLinks, err := proofStore.ProofChain(ctx, c.signer.DID(), blobcmds.Accept.Command, c.piriDID) if err != nil { return nil, nil, nil, fmt.Errorf("building proof chain: %w", err) } @@ -189,10 +195,11 @@ func (c *Client) AcceptInvocation(ctx context.Context, req *AcceptRequest, proof inv, err := blobcmds.Accept.Invoke( c.signer, - req.Space, + c.piriDID, &blobcmds.AcceptArguments{ - Blob: blobcmds.Blob{Digest: req.Digest, Size: req.Size}, - Put: promise.AwaitOK{Task: req.Put}, + Space: req.Space, + Blob: blobcmds.Blob{Digest: req.Digest, Size: req.Size}, + Put: promise.AwaitOK{Task: req.Put}, }, options..., ) diff --git a/pkg/routing/service.go b/pkg/routing/service.go index b78f2df..21dac3a 100644 --- a/pkg/routing/service.go +++ b/pkg/routing/service.go @@ -12,6 +12,7 @@ import ( 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" ) @@ -38,6 +39,10 @@ func WithExclusions(providers ...did.DID) SelectOption { type StorageProviderInfo struct { ID did.DID Endpoint url.URL + // Proofs are the UCAN delegations the provider granted the upload service at + // registration (`/blob/allocate`, `/blob/accept`, `/blob/replica/allocate`), + // used to build the proof chain for invocations sent to the provider. + Proofs ucan.Container } type Service struct { @@ -62,6 +67,7 @@ func (s *Service) GetProviderInfo(ctx context.Context, provider did.DID) (Storag return StorageProviderInfo{ ID: rec.Provider, Endpoint: rec.Endpoint, + Proofs: rec.Proofs, }, nil } @@ -105,6 +111,7 @@ func (s *Service) SelectStorageProvider(ctx context.Context, blob blob.Blob, opt return StorageProviderInfo{ ID: selected.Provider, Endpoint: selected.Endpoint, + Proofs: selected.Proofs, }, nil } @@ -135,6 +142,7 @@ func (s *Service) SelectReplicationProvider(ctx context.Context, primary did.DID return StorageProviderInfo{ ID: selected.Provider, Endpoint: selected.Endpoint, + Proofs: selected.Proofs, }, nil } diff --git a/pkg/routing/service_test.go b/pkg/routing/service_test.go index 2052fc6..e7927b5 100644 --- a/pkg/routing/service_test.go +++ b/pkg/routing/service_test.go @@ -9,6 +9,7 @@ import ( "github.com/fil-forge/sprue/pkg/routing" storageprovider "github.com/fil-forge/sprue/pkg/store/storage_provider" spmemory "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" + "github.com/fil-forge/ucantone/ucan/container" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -18,7 +19,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) - err := store.Put(ctx, storageProvider.DID(), *endpoint, weight, replicationWeight) + err := store.Put(ctx, storageProvider.DID(), *endpoint, weight, replicationWeight, container.New()) require.NoError(t, err) rec, err := store.Get(ctx, storageProvider.DID()) require.NoError(t, err) diff --git a/pkg/service/handlers/admin_provider_deregister_test.go b/pkg/service/handlers/admin_provider_deregister_test.go index 34a7d9f..fd83bbb 100644 --- a/pkg/service/handlers/admin_provider_deregister_test.go +++ b/pkg/service/handlers/admin_provider_deregister_test.go @@ -15,6 +15,7 @@ import ( "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/container" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -58,7 +59,7 @@ func TestAdminProviderDeregisterHandler(t *testing.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) + err = spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, nil, container.New()) require.NoError(t, err) args := provider.DeregisterArguments{ @@ -93,7 +94,7 @@ func TestAdminProviderDeregisterHandler(t *testing.T) { endpoint, err := url.Parse("https://piri.example.com") require.NoError(t, err) - err = spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, nil) + err = spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, nil, container.New()) require.NoError(t, err) args := provider.DeregisterArguments{ diff --git a/pkg/service/handlers/admin_provider_list_test.go b/pkg/service/handlers/admin_provider_list_test.go index 6675edd..8ac4b77 100644 --- a/pkg/service/handlers/admin_provider_list_test.go +++ b/pkg/service/handlers/admin_provider_list_test.go @@ -13,6 +13,7 @@ import ( "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/container" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -100,8 +101,8 @@ func TestAdminProviderListHandler(t *testing.T) { 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)) + require.NoError(t, spStore.Put(ctx, sp1.DID(), *endpoint1, 100, &repWeight, container.New())) + require.NoError(t, spStore.Put(ctx, sp2.DID(), *endpoint2, 200, nil, container.New())) req := issueListInvocation(t, uploadService, uploadService.DID()) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index 4bfaea5..31e3056 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -9,6 +9,7 @@ import ( "github.com/fil-forge/ucantone/binding" "github.com/fil-forge/ucantone/errors" "github.com/fil-forge/ucantone/server" + "github.com/fil-forge/ucantone/ucan/container" "go.uber.org/zap" ) @@ -33,6 +34,12 @@ func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storag return res.SetFailure(errors.New("InvalidEndpoint", "parsing endpoint: %s", err.Error())) } + proofs, err := container.Decode(args.Proofs) + if err != nil { + log.Warn("Invalid proofs", zap.Error(err)) + return res.SetFailure(errors.New("InvalidProofs", "decoding proofs: %s", err.Error())) + } + _, err = providerStore.Get(req.Context(), args.Provider) if err != nil { if !errors.Is(err, storageprovider.ErrStorageProviderNotFound) { @@ -44,7 +51,7 @@ func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storag return res.SetFailure(errors.New("ProviderAlreadyRegistered", "a provider with this DID is already registered")) } - err = providerStore.Put(req.Context(), args.Provider, *endpoint, initialWeight, &initialReplicationWeight) + err = providerStore.Put(req.Context(), args.Provider, *endpoint, initialWeight, &initialReplicationWeight, proofs) if err != nil { log.Error("Failed to register provider", zap.Error(err)) return err diff --git a/pkg/service/handlers/admin_provider_register_test.go b/pkg/service/handlers/admin_provider_register_test.go index 3f78e2d..2a19c41 100644 --- a/pkg/service/handlers/admin_provider_register_test.go +++ b/pkg/service/handlers/admin_provider_register_test.go @@ -13,11 +13,25 @@ import ( "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/command" + "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" "go.uber.org/zap/zaptest" ) +// registerProofs returns an encoded UCAN container delegating allocation +// capabilities to the upload service, as expected by the register handler. +func registerProofs(t *testing.T, provider did.DID) []byte { + t.Helper() + dlg, err := delegation.Delegate(testutil.Alice, testutil.WebService.DID(), provider, command.MustParse("/blob/allocate")) + require.NoError(t, err) + proofBytes, err := container.Encode(container.Raw, container.New(container.WithDelegations(dlg))) + require.NoError(t, err) + return proofBytes +} + // issueRegisterInvocation creates an admin/provider/register invocation request func issueRegisterInvocation( t *testing.T, @@ -85,6 +99,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { args := provider.RegisterArguments{ Provider: storageProvider.DID(), Endpoint: "https://piri.example.com", + Proofs: registerProofs(t, storageProvider.DID()), } // First registration by service identity (authorized) @@ -124,6 +139,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { args := provider.RegisterArguments{ Provider: storageProvider.DID(), Endpoint: "https://piri.example.com", + Proofs: registerProofs(t, storageProvider.DID()), } req := issueRegisterInvocation(t, uploadService, uploadService.DID(), args) diff --git a/pkg/service/handlers/admin_provider_weight_set.go b/pkg/service/handlers/admin_provider_weight_set.go index 9b6e394..cb043c7 100644 --- a/pkg/service/handlers/admin_provider_weight_set.go +++ b/pkg/service/handlers/admin_provider_weight_set.go @@ -28,7 +28,7 @@ func NewAdminProviderWeightSetHandler(id *identity.Identity, providerStore stora } replicationWeight := int(args.ReplicationWeight) - err = providerStore.Put(req.Context(), p.Provider, p.Endpoint, int(args.Weight), &replicationWeight) + err = providerStore.Put(req.Context(), p.Provider, p.Endpoint, int(args.Weight), &replicationWeight, p.Proofs) if err != nil { if errors.Is(err, storageprovider.ErrStorageProviderNotFound) { log.Warn("Provider not found", zap.Stringer("provider", args.Provider)) diff --git a/pkg/service/handlers/admin_provider_weight_set_test.go b/pkg/service/handlers/admin_provider_weight_set_test.go index 837ccf2..70407e5 100644 --- a/pkg/service/handlers/admin_provider_weight_set_test.go +++ b/pkg/service/handlers/admin_provider_weight_set_test.go @@ -14,6 +14,7 @@ import ( "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/container" "github.com/fil-forge/ucantone/ucan/invocation" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -114,7 +115,7 @@ func TestAdminProviderWeightSetHandler(t *testing.T) { // Pre-register the provider with initial weights. initialReplWeight := 0 - err = spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, &initialReplWeight) + err = spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, &initialReplWeight, container.New()) require.NoError(t, err) args := weight.SetArguments{ diff --git a/pkg/service/handlers/blob_add.go b/pkg/service/handlers/blob_add.go index d6a5e6c..901affe 100644 --- a/pkg/service/handlers/blob_add.go +++ b/pkg/service/handlers/blob_add.go @@ -154,8 +154,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv } cause := req.Invocation().Task().Link() - proofStore := ucanlib.NewContainerProofStore(req.Metadata()) - provider, allocInv, allocRcpt, allocOK, err := doAllocate(req.Context(), router, nodeProvider, agentStore, space, blob, cause, proofStore, log) + provider, allocInv, allocRcpt, allocOK, err := doAllocate(req.Context(), router, nodeProvider, agentStore, space, blob, cause, log) if err != nil { if errors.Is(err, routing.ErrCandidateUnavailable) { return res.SetFailure(routing.ErrCandidateUnavailable) @@ -171,7 +170,7 @@ func NewBlobAddHandler(id *identity.Identity, provisioningSvc *provisioning.Serv return fmt.Errorf("generating put invocation: %w", err) } - accInv, accRcpt, accExtras, err := maybeAccept(req.Context(), agentStore, blobRegistry, nodeProvider, provider, space, blob, cause, putInv, putRcpt, proofStore, log) + accInv, accRcpt, accExtras, err := maybeAccept(req.Context(), agentStore, blobRegistry, nodeProvider, provider, space, blob, cause, putInv, putRcpt, log) if err != nil { return err } @@ -209,7 +208,6 @@ func doAllocate( space did.DID, blob blobcmds.Blob, cause cid.Cid, - proofStore ucanlib.ProofStore, logger *zap.Logger, ) (routing.StorageProviderInfo, ucan.Invocation, ucan.Receipt, blobcmds.AllocateOK, error) { log := logger.With(zap.Stringer("cause", cause)) @@ -231,6 +229,9 @@ func doAllocate( return routing.StorageProviderInfo{}, nil, nil, blobcmds.AllocateOK{}, err } + // The proof chain for `/blob/allocate` comes from the proofs the selected + // provider granted the upload service at registration. + proofStore := ucanlib.NewContainerProofStore(candidate.Proofs) res, inv, rcpt, err := client.Allocate(ctx, &piriclient.AllocateRequest{ Space: space, Digest: blob.Digest, @@ -357,7 +358,6 @@ func maybeAccept( cause cid.Cid, // original /space/blob/add task putInv ucan.Invocation, putRcpt ucan.Receipt, - proofStore ucanlib.ProofStore, logger *zap.Logger, ) (ucan.Invocation, ucan.Receipt, acceptExtras, error) { log := logger @@ -369,6 +369,10 @@ func maybeAccept( return nil, nil, acceptExtras{}, err } + // The proof chain for `/blob/accept` comes from the proofs the provider + // granted the upload service at registration. + proofStore := ucanlib.NewContainerProofStore(providerInfo.Proofs) + accReq := piriclient.AcceptRequest{ Space: space, Digest: blob.Digest, diff --git a/pkg/service/handlers/blob_add_test.go b/pkg/service/handlers/blob_add_test.go index 4aac726..3c1f226 100644 --- a/pkg/service/handlers/blob_add_test.go +++ b/pkg/service/handlers/blob_add_test.go @@ -154,6 +154,16 @@ func newMockPiriServer( return httpSrv } +// providerProofs builds the proof container a storage provider grants the +// upload service at registration: self-issued delegations (subject = provider) +// authorizing `/blob/allocate` and `/blob/accept`. +func providerProofs(t *testing.T, storageProvider, uploadService principal.Signer) ucan.Container { + t.Helper() + allocProof := testutil.Must(blobcmds.Allocate.Delegate(storageProvider, uploadService.DID(), storageProvider.DID()))(t) + acceptProof := testutil.Must(blobcmds.Accept.Delegate(storageProvider, uploadService.DID(), storageProvider.DID()))(t) + return container.New(container.WithDelegations(allocProof, acceptProof)) +} + func TestBlobAddHandler(t *testing.T) { logger := zaptest.NewLogger(t) ctx := t.Context() @@ -228,7 +238,7 @@ func TestBlobAddHandler(t *testing.T) { // 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) + err := deps.spStore.Put(ctx, storageProvider.DID(), *endpoint, 0, nil, container.New()) require.NoError(t, err) args := blobcmds.AddArguments{ @@ -280,7 +290,10 @@ func TestBlobAddHandler(t *testing.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) + // The upload service is authorized to invoke /blob/allocate and /blob/accept + // by the proofs the provider granted it at registration, sourced from the + // provider record rather than the invocation metadata. + err := deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil, providerProofs(t, storageProvider, uploadService)) require.NoError(t, err) args := blobcmds.AddArguments{ @@ -295,15 +308,7 @@ func TestBlobAddHandler(t *testing.T) { ) require.NoError(t, err) - addProof := testutil.Must(blobcmds.Add.Delegate(space, testutil.Alice.DID(), space.DID()))(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(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(addProof, allocProof, acceptProof)) + req := execution.NewRequest(ctx, inv) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) require.NoError(t, err) @@ -336,7 +341,7 @@ func TestBlobAddHandler(t *testing.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) + err := deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil, providerProofs(t, storageProvider, uploadService)) require.NoError(t, err) args := blobcmds.AddArguments{ @@ -351,10 +356,7 @@ func TestBlobAddHandler(t *testing.T) { ) require.NoError(t, err) - 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)) + req := execution.NewRequest(ctx, inv) res, err := execution.NewResponse(req.Invocation().Task().Link(), execution.WithSigner(uploadService)) 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 f8d8a66..a01f5c7 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -53,24 +53,23 @@ func NewHTTPPutConcludeHandler( return fmt.Errorf("getting allocation invocation: %w", err) } - provider := allocInv.Audience() - 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), - zap.Stringer("provider", provider), - ) + // The allocate invocation's subject and audience are both the storage + // provider (its proofs are rooted at the provider). The space now + // travels in the allocate arguments rather than on the subject. + provider := allocInv.Subject() 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) } - log = log.With(zap.String("digest", digestutil.Format(allocArgs.Blob.Digest))) + space := allocArgs.Space + + log = log.With( + zap.Stringer("space", space), + zap.Stringer("provider", provider), + zap.String("digest", digestutil.Format(allocArgs.Blob.Digest)), + ) info, err := router.GetProviderInfo(ctx, provider) if err != nil { @@ -84,7 +83,7 @@ func NewHTTPPutConcludeHandler( return fmt.Errorf("creating client: %w", err) } - proofStore := ucanlib.NewContainerProofStore(meta) + proofStore := ucanlib.NewContainerProofStore(info.Proofs) // Must match the accInv constructed in blob_add.go maybeAccept: // (1) Put = putInv.Task().Link() and // (2) WithNoNonce, so this invocation's CID matches the one whose diff --git a/pkg/service/handlers/ucan_conclude_http_put_test.go b/pkg/service/handlers/ucan_conclude_http_put_test.go index 6deffd6..1528eca 100644 --- a/pkg/service/handlers/ucan_conclude_http_put_test.go +++ b/pkg/service/handlers/ucan_conclude_http_put_test.go @@ -106,8 +106,8 @@ func TestHTTPPutConcludeHandler(t *testing.T) { // NOT register that provider in the spStore — router lookup fails. allocInv, err := blobcmds.Allocate.Invoke( uploadService, - space.DID(), - &blobcmds.AllocateArguments{Blob: blob, Cause: testutil.RandomCID(t)}, + storageProvider.DID(), + &blobcmds.AllocateArguments{Space: space.DID(), Blob: blob, Cause: testutil.RandomCID(t)}, invocation.WithAudience(storageProvider.DID()), ) require.NoError(t, err) @@ -168,7 +168,7 @@ func TestHTTPPutConcludeHandler(t *testing.T) { piriURL := testutil.Must(url.Parse(piriSrv.URL))(t) deps := newHTTPPutDeps(t, piriclient.NewProvider(uploadService, logger), logger) - require.NoError(t, deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil)) + require.NoError(t, deps.spStore.Put(ctx, storageProvider.DID(), *piriURL, 100, nil, providerProofs(t, storageProvider, uploadService))) // Provision the space so blob_registry.Register succeeds. account := testutil.Must(didmailto.New("alice@example.com"))(t) @@ -179,8 +179,8 @@ func TestHTTPPutConcludeHandler(t *testing.T) { // Prior /blob/allocate invocation in the agent store. allocInv, err := blobcmds.Allocate.Invoke( uploadService, - space.DID(), - &blobcmds.AllocateArguments{Blob: blob, Cause: blobAddTaskLink}, + storageProvider.DID(), + &blobcmds.AllocateArguments{Space: space.DID(), Blob: blob, Cause: blobAddTaskLink}, invocation.WithAudience(storageProvider.DID()), ) require.NoError(t, err) @@ -215,14 +215,10 @@ func TestHTTPPutConcludeHandler(t *testing.T) { ) 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 := blobcmds.Accept.Delegate(space, uploadService.DID(), space.DID()) - require.NoError(t, err) - meta := container.New(container.WithDelegations(acceptProof)) - - err = deps.ch.Handler(ctx, putInv, putRcpt, meta) + // The upload service is authorized to invoke /blob/accept by the proofs + // the provider granted it at registration (sourced from the provider + // record), so no proof travels in the conclude metadata. + err = deps.ch.Handler(ctx, putInv, putRcpt, nil) require.NoError(t, err) // Blob should now be registered in the space, with cause = blobAddTaskLink. diff --git a/pkg/store/storage_provider/aws/store.go b/pkg/store/storage_provider/aws/store.go index 6fbcea3..3dbab56 100644 --- a/pkg/store/storage_provider/aws/store.go +++ b/pkg/store/storage_provider/aws/store.go @@ -15,6 +15,8 @@ import ( "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/ucan" + "github.com/fil-forge/ucantone/ucan/container" ) var DynamoStorageProviderTableProps = struct { @@ -64,23 +66,32 @@ func (s *Store) Initialize(ctx context.Context) error { return nil } -func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight int, replicationWeight *int) error { +func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight int, replicationWeight *int, proofs ucan.Container) error { + if proofs == nil { + return fmt.Errorf("missing proofs") + } + proofBytes, err := container.Encode(container.Raw, proofs) + if err != nil { + return fmt.Errorf("encoding proofs: %w", err) + } now := time.Now().UTC().Format(timeutil.SimplifiedISO8601) input := dynamodb.UpdateItemInput{ TableName: aws.String(s.tableName), Key: map[string]types.AttributeValue{"provider": &types.AttributeValueMemberS{Value: id.String()}}, UpdateExpression: aws.String( - "SET #endpoint = :endpoint, #weight = :weight, #replicationWeight = :replicationWeight, #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now", + "SET #endpoint = :endpoint, #weight = :weight, #replicationWeight = :replicationWeight, #proofs = :proofs, #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now", ), ExpressionAttributeNames: map[string]string{ "#endpoint": "endpoint", "#weight": "weight", + "#proofs": "proofs", "#insertedAt": "insertedAt", "#updatedAt": "updatedAt", }, ExpressionAttributeValues: map[string]types.AttributeValue{ ":endpoint": &types.AttributeValueMemberS{Value: endpoint.String()}, ":weight": &types.AttributeValueMemberN{Value: strconv.Itoa(weight)}, + ":proofs": &types.AttributeValueMemberB{Value: proofBytes}, ":now": &types.AttributeValueMemberS{Value: now}, }, } @@ -89,7 +100,7 @@ func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight in 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) } @@ -213,11 +224,20 @@ func itemToRecord(item map[string]types.AttributeValue) (storageprovider.Record, replicationWeight = &weight } + var proofs ucan.Container + if proofsAttr, ok := item["proofs"].(*types.AttributeValueMemberB); ok && len(proofsAttr.Value) > 0 { + proofs, err = container.Decode(proofsAttr.Value) + if err != nil { + return storageprovider.Record{}, fmt.Errorf("decoding proofs: %w", err) + } + } + rec := storageprovider.Record{ Provider: providerDID, Endpoint: *endpointURL, Weight: weight, ReplicationWeight: replicationWeight, + Proofs: proofs, } if v, ok := item["insertedAt"].(*types.AttributeValueMemberS); ok { rec.InsertedAt, _ = time.Parse(timeutil.SimplifiedISO8601, v.Value) diff --git a/pkg/store/storage_provider/memory/store.go b/pkg/store/storage_provider/memory/store.go index 37dc7b7..2bbfc91 100644 --- a/pkg/store/storage_provider/memory/store.go +++ b/pkg/store/storage_provider/memory/store.go @@ -2,6 +2,7 @@ package memory import ( "context" + "fmt" "maps" "net/url" "slices" @@ -12,6 +13,7 @@ import ( "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/ucan" ) type Store struct { @@ -84,13 +86,17 @@ 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, id did.DID, endpoint url.URL, weight int, replicationWeight *int) error { +func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight int, replicationWeight *int, proofs ucan.Container) error { + if proofs == nil { + return fmt.Errorf("missing proofs") + } s.mutex.Lock() defer s.mutex.Unlock() if sp, ok := s.providers[id]; ok { sp.Endpoint = endpoint sp.Weight = weight sp.ReplicationWeight = replicationWeight + sp.Proofs = proofs sp.UpdatedAt = time.Now() s.providers[id] = sp return nil @@ -100,6 +106,7 @@ func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight in Endpoint: endpoint, Weight: weight, ReplicationWeight: replicationWeight, + Proofs: proofs, InsertedAt: time.Now(), } return nil diff --git a/pkg/store/storage_provider/postgres/store.go b/pkg/store/storage_provider/postgres/store.go index ef09ea6..2a788e0 100644 --- a/pkg/store/storage_provider/postgres/store.go +++ b/pkg/store/storage_provider/postgres/store.go @@ -11,6 +11,8 @@ import ( "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/ucan" + "github.com/fil-forge/ucantone/ucan/container" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) @@ -29,17 +31,25 @@ func New(pool *pgxpool.Pool) *Store { func (s *Store) Initialize(ctx context.Context) error { return nil } -func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight int, replicationWeight *int) error { +func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight int, replicationWeight *int, proofs ucan.Container) error { + if proofs == nil { + return fmt.Errorf("missing proofs") + } + proofBytes, err := container.Encode(container.Raw, proofs) + if err != nil { + return fmt.Errorf("encoding proofs: %w", err) + } now := time.Now().UTC() - _, 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) + _, err = s.pool.Exec(ctx, ` + INSERT INTO storage_provider (provider, endpoint, weight, replication_weight, proofs, inserted_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $6) ON CONFLICT (provider) DO UPDATE SET endpoint = EXCLUDED.endpoint, weight = EXCLUDED.weight, replication_weight = EXCLUDED.replication_weight, + proofs = EXCLUDED.proofs, updated_at = EXCLUDED.updated_at - `, id.String(), endpoint.String(), weight, replicationWeight, now) + `, id.String(), endpoint.String(), weight, replicationWeight, proofBytes, now) if err != nil { return fmt.Errorf("storing storage provider: %w", err) } @@ -48,7 +58,7 @@ func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight in func (s *Store) Get(ctx context.Context, providerID did.DID) (storageprovider.Record, error) { row := s.pool.QueryRow(ctx, ` - SELECT provider, endpoint, weight, replication_weight, inserted_at, updated_at + SELECT provider, endpoint, weight, replication_weight, proofs, inserted_at, updated_at FROM storage_provider WHERE provider = $1 `, providerID.String()) @@ -85,7 +95,7 @@ func (s *Store) List(ctx context.Context, options ...storageprovider.ListOption) args := []any{limit + 1} query := ` - SELECT provider, endpoint, weight, replication_weight, inserted_at, updated_at + SELECT provider, endpoint, weight, replication_weight, proofs, inserted_at, updated_at FROM storage_provider ` if cfg.Cursor != nil { @@ -131,10 +141,11 @@ func scanRecord(row rowScanner) (storageprovider.Record, error) { endpointStr string weight int replicationWeight *int + proofBytes []byte insertedAt time.Time updatedAt time.Time ) - if err := row.Scan(&providerStr, &endpointStr, &weight, &replicationWeight, &insertedAt, &updatedAt); err != nil { + if err := row.Scan(&providerStr, &endpointStr, &weight, &replicationWeight, &proofBytes, &insertedAt, &updatedAt); err != nil { return storageprovider.Record{}, err } providerDID, err := did.Parse(providerStr) @@ -145,11 +156,19 @@ func scanRecord(row rowScanner) (storageprovider.Record, error) { if err != nil { return storageprovider.Record{}, fmt.Errorf("parsing endpoint URL: %w", err) } + var proofs ucan.Container + if len(proofBytes) > 0 { + proofs, err = container.Decode(proofBytes) + if err != nil { + return storageprovider.Record{}, fmt.Errorf("decoding proofs: %w", err) + } + } return storageprovider.Record{ Provider: providerDID, Endpoint: *endpoint, Weight: weight, ReplicationWeight: replicationWeight, + Proofs: proofs, InsertedAt: insertedAt, UpdatedAt: updatedAt, }, nil diff --git a/pkg/store/storage_provider/storage_provider.go b/pkg/store/storage_provider/storage_provider.go index 94771d6..33dcf45 100644 --- a/pkg/store/storage_provider/storage_provider.go +++ b/pkg/store/storage_provider/storage_provider.go @@ -8,6 +8,7 @@ import ( "github.com/fil-forge/sprue/pkg/store" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/errors" + "github.com/fil-forge/ucantone/ucan" ) const ( @@ -48,6 +49,9 @@ type Record struct { // ReplicationWeight determines the chance of selection for replications // relative to other providers. Defaults to weight if not set. ReplicationWeight *int + // Proofs are UCAN delegations granting the upload service `/blob/allocate`, + // `/blob/accept` and `/blob/replica/allocate`. + Proofs ucan.Container // Date and time the record was created (ISO 8601). InsertedAt time.Time // Date and time the record was last updated (ISO 8601). @@ -55,7 +59,7 @@ type Record struct { } type Store interface { - Put(ctx context.Context, providerID did.DID, endpoint url.URL, weight int, replicationWeight *int) error + Put(ctx context.Context, providerID did.DID, endpoint url.URL, weight int, replicationWeight *int, proofs ucan.Container) 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 890b406..c17bd81 100644 --- a/pkg/store/storage_provider/storage_provider_test.go +++ b/pkg/store/storage_provider/storage_provider_test.go @@ -12,6 +12,10 @@ import ( storageprovideraws "github.com/fil-forge/sprue/pkg/store/storage_provider/aws" storageprovidermemory "github.com/fil-forge/sprue/pkg/store/storage_provider/memory" storageproviderpostgres "github.com/fil-forge/sprue/pkg/store/storage_provider/postgres" + "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/delegation" "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -79,6 +83,15 @@ func randomEndpoint(t *testing.T) url.URL { return *u } +// randomProofs returns a container holding a single delegation, used to +// exercise proof serialization round-trips. +func randomProofs(t *testing.T) ucan.Container { + t.Helper() + dlg, err := delegation.Delegate(testutil.Alice, testutil.Bob.DID(), testutil.Alice.DID(), command.MustParse("/blob/allocate")) + require.NoError(t, err) + return container.New(container.WithDelegations(dlg)) +} + func TestStorageProviderStore(t *testing.T) { for _, k := range storeKinds { t.Run(string(k), func(t *testing.T) { @@ -89,8 +102,9 @@ func TestStorageProviderStore(t *testing.T) { endpoint := randomEndpoint(t) weight := 10 replWeight := 5 + proofs := randomProofs(t) - require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight, proofs)) rec, err := s.Get(t.Context(), provider.DID()) require.NoError(t, err) @@ -99,6 +113,20 @@ func TestStorageProviderStore(t *testing.T) { require.Equal(t, weight, rec.Weight) require.Equal(t, replWeight, *rec.ReplicationWeight) require.False(t, rec.InsertedAt.IsZero()) + + require.NotNil(t, rec.Proofs) + wantBytes, err := container.Encode(container.Raw, proofs) + require.NoError(t, err) + gotBytes, err := container.Encode(container.Raw, rec.Proofs) + require.NoError(t, err) + require.Equal(t, wantBytes, gotBytes) + }) + + t.Run("Put returns an error when proofs are missing", func(t *testing.T) { + provider := testutil.Alice + endpoint := randomEndpoint(t) + + require.Error(t, s.Put(t.Context(), provider.DID(), endpoint, 10, nil, nil)) }) t.Run("put updates an existing provider", func(t *testing.T) { @@ -110,8 +138,8 @@ func TestStorageProviderStore(t *testing.T) { replWeight1 := 5 replWeight2 := 15 - require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint1, weight1, &replWeight1)) - require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint2, weight2, &replWeight2)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint1, weight1, &replWeight1, randomProofs(t))) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint2, weight2, &replWeight2, randomProofs(t))) rec, err := s.Get(t.Context(), provider.DID()) require.NoError(t, err) @@ -133,7 +161,7 @@ func TestStorageProviderStore(t *testing.T) { weight := 10 replWeight := 5 - require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight, randomProofs(t))) require.NoError(t, s.Delete(t.Context(), provider.DID())) _, err := s.Get(t.Context(), provider.DID()) @@ -154,8 +182,8 @@ func TestStorageProviderStore(t *testing.T) { weight := 10 replWeight := 5 - require.NoError(t, s.Put(t.Context(), provider1.DID(), endpoint, weight, &replWeight)) - require.NoError(t, s.Put(t.Context(), provider2.DID(), endpoint, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider1.DID(), endpoint, weight, &replWeight, randomProofs(t))) + require.NoError(t, s.Put(t.Context(), provider2.DID(), endpoint, weight, &replWeight, randomProofs(t))) all, err := store.Collect(t.Context(), func(ctx context.Context, opts store.PaginationConfig) (store.Page[storageprovider.Record], error) { var listOpts []storageprovider.ListOption @@ -180,7 +208,7 @@ func TestStorageProviderStore(t *testing.T) { for range 5 { provider := testutil.RandomDID(t) endpoint := randomEndpoint(t) - require.NoError(t, s.Put(t.Context(), provider, endpoint, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider, endpoint, weight, &replWeight, randomProofs(t))) } all, err := store.Collect(t.Context(), func(ctx context.Context, opts store.PaginationConfig) (store.Page[storageprovider.Record], error) { @@ -200,7 +228,7 @@ func TestStorageProviderStore(t *testing.T) { weight := 10 replWeight := 5 - require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight)) + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight, randomProofs(t))) 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) { From 3007e4ebeb8777ccdbd9b37e9ec1212d5d1583b0 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Jun 2026 10:59:55 +0100 Subject: [PATCH 2/8] refactor: no need to pass metadata to conclude handlers --- pkg/service/handlers/ucan_conclude.go | 4 ++-- pkg/service/handlers/ucan_conclude_http_put.go | 2 +- pkg/service/handlers/ucan_conclude_http_put_test.go | 6 +++--- pkg/service/handlers/ucan_conclude_test.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/service/handlers/ucan_conclude.go b/pkg/service/handlers/ucan_conclude.go index 45bfed3..ba30b42 100644 --- a/pkg/service/handlers/ucan_conclude.go +++ b/pkg/service/handlers/ucan_conclude.go @@ -16,7 +16,7 @@ import ( "go.uber.org/zap" ) -type ConclusionHandlerFunc func(context.Context, ucan.Invocation, ucan.Receipt, ucan.Container) error +type ConclusionHandlerFunc func(context.Context, ucan.Invocation, ucan.Receipt) error // ConclusionHandler is the definition of a handler for an invocation conclusion // - a receiver for a receipt attesting to an invocation result. @@ -85,7 +85,7 @@ func NewUCANConcludeHandler(id *identity.Identity, agentStore agent.Store, handl log.Debug("found invocation for conclusion") if handler, ok := handlers[ranInv.Command()]; ok { - err := handler(req.Context(), ranInv, rcpt, req.Metadata()) + err := handler(req.Context(), ranInv, rcpt) if err != nil { log.Error("failed to conclude invocation", zap.Error(err)) return fmt.Errorf("concluding %q: %w", ranInv.Command(), err) diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index a01f5c7..f289adc 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -34,7 +34,7 @@ func NewHTTPPutConcludeHandler( ) return ConclusionHandler{ Command: httpcmds.Put.Command, - Handler: func(ctx context.Context, putInv ucan.Invocation, putRcpt ucan.Receipt, meta ucan.Container) error { + Handler: func(ctx context.Context, putInv ucan.Invocation, putRcpt ucan.Receipt, _ 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 1528eca..0fb0eaf 100644 --- a/pkg/service/handlers/ucan_conclude_http_put_test.go +++ b/pkg/service/handlers/ucan_conclude_http_put_test.go @@ -89,7 +89,7 @@ func TestHTTPPutConcludeHandler(t *testing.T) { ) require.NoError(t, err) - err = deps.ch.Handler(ctx, putInv, putRcpt, nil) + err = deps.ch.Handler(ctx, putInv, putRcpt) require.Error(t, err) require.Contains(t, err.Error(), "getting allocation invocation") }) @@ -141,7 +141,7 @@ func TestHTTPPutConcludeHandler(t *testing.T) { ) require.NoError(t, err) - err = deps.ch.Handler(ctx, putInv, putRcpt, nil) + err = deps.ch.Handler(ctx, putInv, putRcpt) require.Error(t, err) require.Contains(t, err.Error(), "getting storage provider info") }) @@ -218,7 +218,7 @@ func TestHTTPPutConcludeHandler(t *testing.T) { // The upload service is authorized to invoke /blob/accept by the proofs // the provider granted it at registration (sourced from the provider // record), so no proof travels in the conclude metadata. - err = deps.ch.Handler(ctx, putInv, putRcpt, nil) + err = deps.ch.Handler(ctx, putInv, putRcpt) require.NoError(t, err) // Blob should now be registered in the space, with cause = blobAddTaskLink. diff --git a/pkg/service/handlers/ucan_conclude_test.go b/pkg/service/handlers/ucan_conclude_test.go index 99e0c4b..fb6a9ca 100644 --- a/pkg/service/handlers/ucan_conclude_test.go +++ b/pkg/service/handlers/ucan_conclude_test.go @@ -113,7 +113,7 @@ func TestUCANConcludeHandler(t *testing.T) { gotRcpt ucan.Receipt ) handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{ - command.MustParse("/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) error { called = true gotInv = inv gotRcpt = rcpt @@ -199,7 +199,7 @@ func TestUCANConcludeHandler(t *testing.T) { var called bool handlerMap := map[ucan.Command]handlers.ConclusionHandlerFunc{ - command.MustParse("/test/thing"): func(_ context.Context, _ ucan.Invocation, _ ucan.Receipt, _ ucan.Container) error { + command.MustParse("/test/thing"): func(_ context.Context, _ ucan.Invocation, _ ucan.Receipt) error { called = true return nil }, From a1b03201108fbef6e521d9ff8639d8ed9ae821b8 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Jun 2026 11:23:57 +0100 Subject: [PATCH 3/8] fix: aws storage provider put implementation --- .../handlers/ucan_conclude_http_put.go | 2 +- pkg/store/storage_provider/aws/store.go | 18 +++++++++-- .../storage_provider/storage_provider_test.go | 30 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/pkg/service/handlers/ucan_conclude_http_put.go b/pkg/service/handlers/ucan_conclude_http_put.go index f289adc..01a6b84 100644 --- a/pkg/service/handlers/ucan_conclude_http_put.go +++ b/pkg/service/handlers/ucan_conclude_http_put.go @@ -34,7 +34,7 @@ func NewHTTPPutConcludeHandler( ) return ConclusionHandler{ Command: httpcmds.Put.Command, - Handler: func(ctx context.Context, putInv ucan.Invocation, putRcpt ucan.Receipt, _ ucan.Container) error { + Handler: func(ctx context.Context, putInv ucan.Invocation, putRcpt ucan.Receipt) error { log := log.With(zap.Stringer("ran", putRcpt.Ran())) log.Debug("handling conclude") diff --git a/pkg/store/storage_provider/aws/store.go b/pkg/store/storage_provider/aws/store.go index 3dbab56..929452f 100644 --- a/pkg/store/storage_provider/aws/store.go +++ b/pkg/store/storage_provider/aws/store.go @@ -6,6 +6,7 @@ import ( "fmt" "net/url" "strconv" + "strings" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -75,12 +76,16 @@ func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight in return fmt.Errorf("encoding proofs: %w", err) } now := time.Now().UTC().Format(timeutil.SimplifiedISO8601) + setClauses := []string{ + "#endpoint = :endpoint", + "#weight = :weight", + "#proofs = :proofs", + "#insertedAt = if_not_exists(#insertedAt, :now)", + "#updatedAt = :now", + } input := dynamodb.UpdateItemInput{ TableName: aws.String(s.tableName), Key: map[string]types.AttributeValue{"provider": &types.AttributeValueMemberS{Value: id.String()}}, - UpdateExpression: aws.String( - "SET #endpoint = :endpoint, #weight = :weight, #replicationWeight = :replicationWeight, #proofs = :proofs, #insertedAt = if_not_exists(#insertedAt, :now), #updatedAt = :now", - ), ExpressionAttributeNames: map[string]string{ "#endpoint": "endpoint", "#weight": "weight", @@ -95,10 +100,17 @@ func (s *Store) Put(ctx context.Context, id did.DID, endpoint url.URL, weight in ":now": &types.AttributeValueMemberS{Value: now}, }, } + updateExpr := "SET " + strings.Join(setClauses, ", ") if replicationWeight != nil { input.ExpressionAttributeNames["#replicationWeight"] = "replicationWeight" input.ExpressionAttributeValues[":replicationWeight"] = &types.AttributeValueMemberN{Value: strconv.Itoa(*replicationWeight)} + updateExpr += ", #replicationWeight = :replicationWeight" + } else { + // Clear any previously-stored replicationWeight when none is provided. + input.ExpressionAttributeNames["#replicationWeight"] = "replicationWeight" + updateExpr += " REMOVE #replicationWeight" } + input.UpdateExpression = aws.String(updateExpr) _, err = s.dynamo.UpdateItem(ctx, &input) if err != nil { diff --git a/pkg/store/storage_provider/storage_provider_test.go b/pkg/store/storage_provider/storage_provider_test.go index c17bd81..75419b1 100644 --- a/pkg/store/storage_provider/storage_provider_test.go +++ b/pkg/store/storage_provider/storage_provider_test.go @@ -148,6 +148,36 @@ func TestStorageProviderStore(t *testing.T) { require.Equal(t, replWeight2, *rec.ReplicationWeight) }) + t.Run("Put with nil replicationWeight clears a previously-stored value", func(t *testing.T) { + provider := testutil.Alice + endpoint := randomEndpoint(t) + weight := 10 + replWeight := 5 + + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, &replWeight, randomProofs(t))) + + rec, err := s.Get(t.Context(), provider.DID()) + require.NoError(t, err) + require.NotNil(t, rec.ReplicationWeight) + + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, weight, nil, randomProofs(t))) + + rec, err = s.Get(t.Context(), provider.DID()) + require.NoError(t, err) + require.Nil(t, rec.ReplicationWeight) + }) + + t.Run("Put accepts a nil replicationWeight on insert", func(t *testing.T) { + provider := testutil.Bob + endpoint := randomEndpoint(t) + + require.NoError(t, s.Put(t.Context(), provider.DID(), endpoint, 10, nil, randomProofs(t))) + + rec, err := s.Get(t.Context(), provider.DID()) + require.NoError(t, err) + require.Nil(t, rec.ReplicationWeight) + }) + t.Run("Get returns ErrStorageProviderNotFound for unknown provider", func(t *testing.T) { provider := testutil.RandomDID(t) From 31a9790c8776a8daa85e14e023936bf5c876debd Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Jun 2026 11:54:43 +0100 Subject: [PATCH 4/8] feat: validate required proofs when registering --- .../handlers/admin_provider_register.go | 27 ++++++++ .../handlers/admin_provider_register_test.go | 69 ++++++++++++++++--- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index 31e3056..850923d 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -3,12 +3,16 @@ package handlers import ( "net/url" + blobcmds "github.com/fil-forge/libforge/commands/blob" + replicacmds "github.com/fil-forge/libforge/commands/blob/replica" + ucanlib "github.com/fil-forge/libforge/ucan" "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/server" + "github.com/fil-forge/ucantone/ucan" "github.com/fil-forge/ucantone/ucan/container" "go.uber.org/zap" ) @@ -18,6 +22,14 @@ var ( initialReplicationWeight = 0 ) +// requiredProofs are the capabilities a registering provider must delegate to +// the service, identified by command. +var requiredProofs = []ucan.Command{ + blobcmds.Allocate.Command, + blobcmds.Accept.Command, + replicacmds.Allocate.Command, +} + func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Route { log := logger.With(zap.Stringer("handler", provider.Register)) return provider.Register.Route( @@ -40,6 +52,21 @@ func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storag return res.SetFailure(errors.New("InvalidProofs", "decoding proofs: %s", err.Error())) } + // Verify the proofs delegate every required capability from the + // provider (subject) to the service (audience). + proofStore := ucanlib.NewContainerProofStore(proofs) + for _, cmd := range requiredProofs { + chain, _, err := proofStore.ProofChain(req.Context(), id.Signer.DID(), cmd, args.Provider) + if err != nil { + log.Error("Failed to build proof chain", zap.Stringer("command", cmd), zap.Error(err)) + return res.SetFailure(errors.New("InvalidProofs", "building proof chain for %s: %s", cmd, err.Error())) + } + if len(chain) == 0 { + log.Warn("Missing required proof", zap.Stringer("command", cmd)) + return res.SetFailure(errors.New("InvalidProofs", "missing required %s delegation", cmd)) + } + } + _, err = providerStore.Get(req.Context(), args.Provider) if err != nil { if !errors.Is(err, storageprovider.ErrStorageProviderNotFound) { diff --git a/pkg/service/handlers/admin_provider_register_test.go b/pkg/service/handlers/admin_provider_register_test.go index 2a19c41..17ed2ea 100644 --- a/pkg/service/handlers/admin_provider_register_test.go +++ b/pkg/service/handlers/admin_provider_register_test.go @@ -8,6 +8,7 @@ import ( "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" "github.com/fil-forge/ucantone/did" "github.com/fil-forge/ucantone/errors/datamodel" @@ -21,13 +22,26 @@ import ( "go.uber.org/zap/zaptest" ) -// registerProofs returns an encoded UCAN container delegating allocation -// capabilities to the upload service, as expected by the register handler. -func registerProofs(t *testing.T, provider did.DID) []byte { +// requiredProofCommands are the capabilities the register handler expects the +// provider to delegate to the upload service. +var requiredProofCommands = []command.Command{ + command.MustParse("/blob/allocate"), + command.MustParse("/blob/accept"), + command.MustParse("/blob/replica/allocate"), +} + +// registerProofs returns an encoded UCAN container delegating the required +// allocation capabilities from the provider to the upload service, as expected +// by the register handler. +func registerProofs(t *testing.T, providerSigner ucan.Signer, audience did.DID) []byte { t.Helper() - dlg, err := delegation.Delegate(testutil.Alice, testutil.WebService.DID(), provider, command.MustParse("/blob/allocate")) - require.NoError(t, err) - proofBytes, err := container.Encode(container.Raw, container.New(container.WithDelegations(dlg))) + dlgs := make([]ucan.Delegation, 0, len(requiredProofCommands)) + for _, cmd := range requiredProofCommands { + dlg, err := delegation.Delegate(providerSigner, audience, providerSigner.DID(), cmd) + require.NoError(t, err) + dlgs = append(dlgs, dlg) + } + proofBytes, err := container.Encode(container.Raw, container.New(container.WithDelegations(dlgs...))) require.NoError(t, err) return proofBytes } @@ -99,7 +113,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { args := provider.RegisterArguments{ Provider: storageProvider.DID(), Endpoint: "https://piri.example.com", - Proofs: registerProofs(t, storageProvider.DID()), + Proofs: registerProofs(t, storageProvider, uploadService.DID()), } // First registration by service identity (authorized) @@ -127,6 +141,45 @@ func TestAdminProviderRegisterHandler(t *testing.T) { require.Equal(t, "ProviderAlreadyRegistered", errModel.Name()) }) + t.Run("rejects proofs missing a required delegation", func(t *testing.T) { + spStore := storage_provider_store.New() + + handler := handlers.NewAdminProviderRegisterHandler( + &identity.Identity{Signer: uploadService}, spStore, logger, + ) + + storageProvider := testutil.RandomSigner(t) + + // Delegate only /blob/allocate, omitting /blob/accept and + // /blob/replica/allocate. + dlg, err := delegation.Delegate(storageProvider, uploadService.DID(), storageProvider.DID(), command.MustParse("/blob/allocate")) + require.NoError(t, err) + proofBytes, err := container.Encode(container.Raw, container.New(container.WithDelegations(dlg))) + require.NoError(t, err) + + args := provider.RegisterArguments{ + Provider: storageProvider.DID(), + Endpoint: "https://piri.example.com", + Proofs: proofBytes, + } + + req := issueRegisterInvocation(t, uploadService, uploadService.DID(), 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) + + _, err = provider.Register.Unpack(res.Receipt()) + var errModel datamodel.ErrorModel + require.ErrorAs(t, err, &errModel) + require.Equal(t, "InvalidProofs", errModel.Name()) + + // Provider must not have been stored. + _, err = spStore.Get(ctx, storageProvider.DID()) + require.ErrorIs(t, err, storageprovider.ErrStorageProviderNotFound) + }) + t.Run("service identity can register", func(t *testing.T) { spStore := storage_provider_store.New() @@ -139,7 +192,7 @@ func TestAdminProviderRegisterHandler(t *testing.T) { args := provider.RegisterArguments{ Provider: storageProvider.DID(), Endpoint: "https://piri.example.com", - Proofs: registerProofs(t, storageProvider.DID()), + Proofs: registerProofs(t, storageProvider, uploadService.DID()), } req := issueRegisterInvocation(t, uploadService, uploadService.DID(), args) From 7131d59662d742a4d724254efe95caf8fc86a9d7 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Jun 2026 11:56:30 +0100 Subject: [PATCH 5/8] chore: mod tidy --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 9c3d74d..92d6889 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,6 @@ 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-20260527182359-ebb22552c348 h1:roYe4llNv0fpQnvXMontrdt/hy9kH5K/cnNsXmhqId4= -github.com/fil-forge/libforge v0.0.0-20260527182359-ebb22552c348/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564 h1:wPNGF6OMBe5c9iiOoaZTD8zA6b7aJK+5xXwoymoIn6A= github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684 h1:kWJLKVltJXPXO7tKS1z0GhzA+c59gwhediGyByEXE0o= From d8e94ae65e82f10b6fbb63ac2813e9340d72b7ad Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Jun 2026 12:50:29 +0100 Subject: [PATCH 6/8] fix: add `/pdp/info` to required proofs --- go.mod | 2 ++ go.sum | 6 ++++++ pkg/service/handlers/admin_provider_register.go | 2 ++ pkg/service/handlers/admin_provider_register_test.go | 11 +++++++---- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index a7a77f9..fbcd2a0 100644 --- a/go.mod +++ b/go.mod @@ -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-data-segment v0.0.1 // 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 @@ -140,6 +141,7 @@ require ( 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 diff --git a/go.sum b/go.sum index 92d6889..eb23c79 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,10 @@ github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564 h1:wPNGF6OMBe5c github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684 h1:kWJLKVltJXPXO7tKS1z0GhzA+c59gwhediGyByEXE0o= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684/go.mod h1:XAVqsZwYoZ9vncjZoRUAJ+mL/ApLMFn9HHX7ipohVdY= +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.3.1 h1:4EfxpHSlvtkOqa9weG2Yt5kxFmPib2xU7Uc9Lbqk7fs= +github.com/filecoin-project/go-fil-commcid v0.3.1/go.mod h1:z7Ssf8d7kspF9QRAVHDbZ+43JK4mkhbGH5lyph1TnKY= 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= @@ -205,6 +209,8 @@ 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= diff --git a/pkg/service/handlers/admin_provider_register.go b/pkg/service/handlers/admin_provider_register.go index 850923d..3e6a165 100644 --- a/pkg/service/handlers/admin_provider_register.go +++ b/pkg/service/handlers/admin_provider_register.go @@ -5,6 +5,7 @@ import ( blobcmds "github.com/fil-forge/libforge/commands/blob" replicacmds "github.com/fil-forge/libforge/commands/blob/replica" + pdpcmds "github.com/fil-forge/libforge/commands/pdp" ucanlib "github.com/fil-forge/libforge/ucan" "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" @@ -28,6 +29,7 @@ var requiredProofs = []ucan.Command{ blobcmds.Allocate.Command, blobcmds.Accept.Command, replicacmds.Allocate.Command, + pdpcmds.Info.Command, } func NewAdminProviderRegisterHandler(id *identity.Identity, providerStore storageprovider.Store, logger *zap.Logger) server.Route { diff --git a/pkg/service/handlers/admin_provider_register_test.go b/pkg/service/handlers/admin_provider_register_test.go index 17ed2ea..17c53c9 100644 --- a/pkg/service/handlers/admin_provider_register_test.go +++ b/pkg/service/handlers/admin_provider_register_test.go @@ -4,6 +4,8 @@ import ( "testing" blobcmds "github.com/fil-forge/libforge/commands/blob" + replicacmds "github.com/fil-forge/libforge/commands/blob/replica" + pdpcmds "github.com/fil-forge/libforge/commands/pdp" "github.com/fil-forge/sprue/internal/testutil" "github.com/fil-forge/sprue/pkg/commands/admin/provider" "github.com/fil-forge/sprue/pkg/identity" @@ -24,10 +26,11 @@ import ( // requiredProofCommands are the capabilities the register handler expects the // provider to delegate to the upload service. -var requiredProofCommands = []command.Command{ - command.MustParse("/blob/allocate"), - command.MustParse("/blob/accept"), - command.MustParse("/blob/replica/allocate"), +var requiredProofCommands = []ucan.Command{ + blobcmds.Allocate.Command, + blobcmds.Accept.Command, + replicacmds.Allocate.Command, + pdpcmds.Info.Command, } // registerProofs returns an encoded UCAN container delegating the required From 7f29733424a4648a0cc39072e1a45d6cd24a6e63 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 19 Jun 2026 09:52:06 +0100 Subject: [PATCH 7/8] chore: update libforge --- go.mod | 12 ++++++------ go.sum | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index fbcd2a0..730c3a8 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.5 + github.com/alanshaw/dag-json-gen v0.0.6 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 @@ -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-20260615094549-c97f4def5564 + github.com/fil-forge/libforge v0.0.0-20260619084920-1753f2265c95 github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684 github.com/google/uuid v1.6.0 github.com/ipfs/go-cid v0.6.1 @@ -29,7 +29,7 @@ require ( 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 + go.uber.org/zap v1.28.0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da ) @@ -62,7 +62,7 @@ require ( github.com/containerd/log v0.1.0 // indirect 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -110,7 +110,7 @@ require ( 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 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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 @@ -141,7 +141,7 @@ require ( 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/exp v0.0.0-20260312153236-7ab1446f8b90 // 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 diff --git a/go.sum b/go.sum index eb23c79..00cd454 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,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.5 h1:jUOqsTrfZ7ddkBqAsx/xbCeJtpe70jrFL2Z+i4qQB1U= github.com/alanshaw/dag-json-gen v0.0.5/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= +github.com/alanshaw/dag-json-gen v0.0.6 h1:MiscvWVOhs6/ux7OUdPz2nDRA7GwklZyaAy4XWexpr0= +github.com/alanshaw/dag-json-gen v0.0.6/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= 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= @@ -78,6 +80,8 @@ github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfv 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -94,8 +98,14 @@ 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-20260615094549-c97f4def5564 h1:wPNGF6OMBe5c9iiOoaZTD8zA6b7aJK+5xXwoymoIn6A= github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= +github.com/fil-forge/libforge v0.0.0-20260619083649-eb26d871cda1 h1:BZUTgenH/AawsAzH8xOx2tFyGXIhSacGiir4PwojX2M= +github.com/fil-forge/libforge v0.0.0-20260619083649-eb26d871cda1/go.mod h1:0kXihIQ4L2uZ00nR5XrZ/Y8Db7Ht/qQNuiWslwMJ95M= +github.com/fil-forge/libforge v0.0.0-20260619084920-1753f2265c95 h1:GUnpBYLuWK3mzDsGVXt0CEIHIFeEd3B+Ne2FkqnBfJU= +github.com/fil-forge/libforge v0.0.0-20260619084920-1753f2265c95/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684 h1:kWJLKVltJXPXO7tKS1z0GhzA+c59gwhediGyByEXE0o= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684/go.mod h1:XAVqsZwYoZ9vncjZoRUAJ+mL/ApLMFn9HHX7ipohVdY= +github.com/fil-forge/ucantone v0.0.0-20260619013642-7985ec010b88 h1:N0gbL3Ik+XBYk4y/5BxTVymwbRGlxRXwC5eNWzi1bGI= +github.com/fil-forge/ucantone v0.0.0-20260619013642-7985ec010b88/go.mod h1:rTIRXz4xErI4U+YlBU9ZvhlTbr4Hs5tJhVMwereVkSg= 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.3.1 h1:4EfxpHSlvtkOqa9weG2Yt5kxFmPib2xU7Uc9Lbqk7fs= @@ -231,6 +241,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= @@ -328,12 +340,16 @@ 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.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= 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.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-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= 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/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= From a6ef45b9616eb7616e101288dab22f61b56e5509 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 19 Jun 2026 10:01:05 +0100 Subject: [PATCH 8/8] chore: mod tidy --- go.sum | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/go.sum b/go.sum index 00cd454..32641db 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 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/alanshaw/dag-json-gen v0.0.6 h1:MiscvWVOhs6/ux7OUdPz2nDRA7GwklZyaAy4XWexpr0= github.com/alanshaw/dag-json-gen v0.0.6/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY= github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA= @@ -78,8 +76,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -96,16 +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-20260615094549-c97f4def5564 h1:wPNGF6OMBe5c9iiOoaZTD8zA6b7aJK+5xXwoymoIn6A= -github.com/fil-forge/libforge v0.0.0-20260615094549-c97f4def5564/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= -github.com/fil-forge/libforge v0.0.0-20260619083649-eb26d871cda1 h1:BZUTgenH/AawsAzH8xOx2tFyGXIhSacGiir4PwojX2M= -github.com/fil-forge/libforge v0.0.0-20260619083649-eb26d871cda1/go.mod h1:0kXihIQ4L2uZ00nR5XrZ/Y8Db7Ht/qQNuiWslwMJ95M= github.com/fil-forge/libforge v0.0.0-20260619084920-1753f2265c95 h1:GUnpBYLuWK3mzDsGVXt0CEIHIFeEd3B+Ne2FkqnBfJU= github.com/fil-forge/libforge v0.0.0-20260619084920-1753f2265c95/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684 h1:kWJLKVltJXPXO7tKS1z0GhzA+c59gwhediGyByEXE0o= github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684/go.mod h1:XAVqsZwYoZ9vncjZoRUAJ+mL/ApLMFn9HHX7ipohVdY= -github.com/fil-forge/ucantone v0.0.0-20260619013642-7985ec010b88 h1:N0gbL3Ik+XBYk4y/5BxTVymwbRGlxRXwC5eNWzi1bGI= -github.com/fil-forge/ucantone v0.0.0-20260619013642-7985ec010b88/go.mod h1:rTIRXz4xErI4U+YlBU9ZvhlTbr4Hs5tJhVMwereVkSg= 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.3.1 h1:4EfxpHSlvtkOqa9weG2Yt5kxFmPib2xU7Uc9Lbqk7fs= @@ -239,7 +229,6 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -338,16 +327,12 @@ 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.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= 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.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-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=