Skip to content

Commit

Permalink
feat: attach blocks to delegation (#27)
Browse files Browse the repository at this point in the history
This PR adds a method `Attach` to `Delegation` that allows additional
blocks to be attached. The primary usecase is to allow blocks linked
from capabilities or facts to be included in the delegation payload.

Note: this feature exists in JS Ucanto already and it actually validates
the blocks you attach are referenced from capabilities/facts. I will
open an issue to track feature parity here assuming this is approved.
  • Loading branch information
alanshaw authored Oct 22, 2024
1 parent f12c0d0 commit 7edcd37
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 11 deletions.
2 changes: 1 addition & 1 deletion core/delegation/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,5 @@ func Delegate[C ucan.CaveatBuilder](issuer ucan.Signer, audience ucan.Principal,
return nil, fmt.Errorf("adding delegation root to store: %s", err)
}

return NewDelegation(rt, bs), nil
return NewDelegation(rt, bs)
}
30 changes: 22 additions & 8 deletions core/delegation/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/storacha/go-ucanto/core/ipld/block"
"github.com/storacha/go-ucanto/core/ipld/codec/cbor"
"github.com/storacha/go-ucanto/core/ipld/hash/sha256"
"github.com/storacha/go-ucanto/core/iterable"
"github.com/storacha/go-ucanto/ucan"
"github.com/storacha/go-ucanto/ucan/crypto/signature"
udm "github.com/storacha/go-ucanto/ucan/datamodel/ucan"
Expand All @@ -30,13 +31,18 @@ type Delegation interface {
Link() ucan.Link
// Archive writes the delegation to a Content Addressed aRchive (CAR).
Archive() io.Reader
// Attach a block to the delegation DAG so it will be included in the block
// iterator. You should only attach blocks that are referenced from
// `Capabilities` or `Facts`.
Attach(block block.Block) error
}

type delegation struct {
rt ipld.Block
blks blockstore.BlockReader
ucan ucan.View
once sync.Once
rt ipld.Block
blks blockstore.BlockReader
atchblks blockstore.BlockStore
ucan ucan.View
once sync.Once
}

var _ Delegation = (*delegation)(nil)
Expand Down Expand Up @@ -65,7 +71,7 @@ func (d *delegation) Link() ucan.Link {
}

func (d *delegation) Blocks() iter.Seq2[ipld.Block, error] {
return d.blks.Iterator()
return iterable.Concat2(d.blks.Iterator(), d.atchblks.Iterator())
}

func (d *delegation) Archive() io.Reader {
Expand Down Expand Up @@ -112,8 +118,16 @@ func (d *delegation) Signature() signature.SignatureView {
return d.Data().Signature()
}

func NewDelegation(root ipld.Block, bs blockstore.BlockReader) Delegation {
return &delegation{rt: root, blks: bs}
func (d *delegation) Attach(b block.Block) error {
return d.atchblks.Put(b)
}

func NewDelegation(root ipld.Block, bs blockstore.BlockReader) (Delegation, error) {
attachments, err := blockstore.NewBlockStore()
if err != nil {
return nil, err
}
return &delegation{rt: root, blks: bs, atchblks: attachments}, nil
}

func NewDelegationView(root ipld.Link, bs blockstore.BlockReader) (Delegation, error) {
Expand All @@ -124,7 +138,7 @@ func NewDelegationView(root ipld.Link, bs blockstore.BlockReader) (Delegation, e
if !ok {
return nil, fmt.Errorf("missing delegation root block: %s", root)
}
return NewDelegation(blk, bs), nil
return NewDelegation(blk, bs)
}

func Archive(d Delegation) io.Reader {
Expand Down
36 changes: 36 additions & 0 deletions core/delegation/delegation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package delegation

import (
"testing"

"github.com/storacha/go-ucanto/core/ipld/block"
"github.com/storacha/go-ucanto/testing/fixtures"
"github.com/storacha/go-ucanto/testing/helpers"
"github.com/storacha/go-ucanto/ucan"
"github.com/stretchr/testify/require"
)

func TestAttach(t *testing.T) {
dlg, err := Delegate(
fixtures.Alice,
fixtures.Bob,
[]ucan.Capability[ucan.NoCaveats]{
ucan.NewCapability("test/attach", fixtures.Alice.DID().String(), ucan.NoCaveats{}),
},
)
require.NoError(t, err)

blk := block.NewBlock(helpers.RandomCID(), helpers.RandomBytes(32))
err = dlg.Attach(blk)
require.NoError(t, err)

found := false
for b, err := range dlg.Blocks() {
require.NoError(t, err)
if b.Link().String() == blk.Link().String() {
found = true
break
}
}
require.True(t, found)
}
2 changes: 1 addition & 1 deletion core/invocation/invocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Invocation interface {
delegation.Delegation
}

func NewInvocation(root ipld.Block, bs blockstore.BlockReader) Invocation {
func NewInvocation(root ipld.Block, bs blockstore.BlockReader) (Invocation, error) {
return delegation.NewDelegation(root, bs)
}

Expand Down
26 changes: 26 additions & 0 deletions testing/helpers/helpers.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package helpers

import (
crand "crypto/rand"

"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/datamodel"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/multiformats/go-multihash"
)

// Must takes return values from a function and returns the non-error one. If
// the error value is non-nil then it panics.
func Must[T any](val T, err error) T {
Expand All @@ -8,3 +17,20 @@ func Must[T any](val T, err error) T {
}
return val
}

func RandomBytes(size int) []byte {
bytes := make([]byte, size)
_, _ = crand.Read(bytes)
return bytes
}

func RandomCID() datamodel.Link {
bytes := RandomBytes(10)
c, _ := cid.Prefix{
Version: 1,
Codec: cid.Raw,
MhType: multihash.SHA2_256,
MhLength: -1,
}.Sum(bytes)
return cidlink.Link{Cid: c}
}
3 changes: 2 additions & 1 deletion validator/lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,8 @@ func TestAccess(t *testing.T) {
bs, err := blockstore.NewBlockStore(blockstore.WithBlocks([]block.Block{rt}))
require.NoError(t, err)

dlg := delegation.NewDelegation(rt, bs)
dlg, err := delegation.NewDelegation(rt, bs)
require.NoError(t, err)

inv, err := storeAdd.Invoke(
fixtures.Bob,
Expand Down

0 comments on commit 7edcd37

Please sign in to comment.