Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion pkg/client/accessdelegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

accesscap "github.com/storacha/go-libstoracha/capabilities/access"
"github.com/storacha/go-ucanto/core/dag/blockstore"
"github.com/storacha/go-ucanto/core/delegation"
"github.com/storacha/go-ucanto/core/result"
"github.com/storacha/go-ucanto/did"
Expand All @@ -31,10 +32,17 @@ func (c *Client) AccessDelegate(ctx context.Context, space did.DID, delegations
},
}

// Include the delegations themselves as proofs
// Include the delegations themselves as proofs
delOptions := make([]delegation.Option, 0, len(delegations))
for _, del := range delegations {
delOptions = append(delOptions, delegation.WithProof(delegation.FromDelegation(del)))
// Prune the delegation to remove nested proofs (blocks) to reduce header size.
// We use d.Root() because d.Block() does not exist on the interface.
pruned, err := pruneDelegation(del)
if err != nil {
return accesscap.DelegateOk{}, fmt.Errorf("pruning delegation: %w", err)
}
delOptions = append(delOptions, delegation.WithProof(delegation.FromDelegation(pruned)))
}

res, _, err := invokeAndExecute[accesscap.DelegateCaveats, accesscap.DelegateOk](
Expand All @@ -57,3 +65,18 @@ func (c *Client) AccessDelegate(ctx context.Context, space did.DID, delegations

return delegateOk, nil
}

// pruneDelegation creates a shallow copy of the delegation that only contains
// the root block in its blockstore. This prevents the entire proof chain (nested blocks)
// from being serialized into the invocation header.
func pruneDelegation(d delegation.Delegation) (delegation.Delegation, error) {
blk := d.Root()
bs, err := blockstore.NewBlockStore()
if err != nil {
return nil, fmt.Errorf("creating blockstore: %w", err)
}
if err := bs.Put(blk); err != nil {
return nil, fmt.Errorf("putting block: %w", err)
}
return delegation.NewDelegation(blk, bs)
}
77 changes: 77 additions & 0 deletions pkg/client/accessdelegate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,81 @@ func TestAccessDelegate(t *testing.T) {
require.Error(t, err)
assert.Contains(t, err.Error(), "access/delegate")
})

t.Run("prunes nested proofs from delegations", func(t *testing.T) {
root := testutil.Must(signer.Generate())(t)
middle := testutil.Must(signer.Generate())(t)
leaf := testutil.Must(signer.Generate())(t)

// Root -> Middle
rootToMiddle := testutil.Must(delegation.Delegate(
root,
middle,
[]ucan.Capability[ucan.NoCaveats]{
ucan.NewCapability("store/add", root.DID().String(), ucan.NoCaveats{}),
},
delegation.WithNoExpiration(),
))(t)

// Middle -> Leaf (includes Root->Middle as proof)
// This creates a "heavy" delegation because it carries the history
middleToLeaf := testutil.Must(delegation.Delegate(
middle,
leaf,
[]ucan.Capability[ucan.NoCaveats]{
ucan.NewCapability("store/add", root.DID().String(), ucan.NoCaveats{}),
},
delegation.WithNoExpiration(),
delegation.WithProof(delegation.FromDelegation(rootToMiddle)),
))(t)

var receivedProofLinks []ucan.Link
var c *client.Client

connection := ctestutil.NewTestServerConnection(
server.WithServiceMethod(
access.DelegateAbility,
server.Provide(
access.Delegate,
func(
ctx context.Context,
cap ucan.Capability[access.DelegateCaveats],
inv invocation.Invocation,
context server.InvocationContext,
) (result.Result[access.DelegateOk, failure.IPLDBuilderFailure], fx.Effects, error) {
receivedProofLinks = inv.Proofs()
return result.Ok[access.DelegateOk, failure.IPLDBuilderFailure](access.DelegateOk{}), nil, nil
},
),
),
)

c = testutil.Must(client.NewClient(
client.WithConnection(connection),
))(t)

// Authorize client to call access/delegate
accessDelegateProof := testutil.Must(delegation.Delegate(
root,
c.Issuer(),
[]ucan.Capability[ucan.NoCaveats]{
ucan.NewCapability("access/delegate", root.DID().String(), ucan.NoCaveats{}),
},
delegation.WithNoExpiration(),
))(t)
require.NoError(t, c.AddProofs(accessDelegateProof))

// Action: Call AccessDelegate with the "heavy" delegation
_, err := c.AccessDelegate(testContext(t), root.DID(), middleToLeaf)
require.NoError(t, err)

// Verification: Ensure the leaf was sent
var foundLeafProof delegation.Proof
for _, pl := range receivedProofLinks {
if pl.String() == middleToLeaf.Link().String() {
foundLeafProof = delegation.FromDelegation(middleToLeaf)
}
}
require.NotNil(t, foundLeafProof, "expected leaf proof link to be sent")
})
}