diff --git a/core/delegation/delegate.go b/core/delegation/delegate.go index 4df7dcc..c98606c 100644 --- a/core/delegation/delegate.go +++ b/core/delegation/delegate.go @@ -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) } diff --git a/core/delegation/delegation.go b/core/delegation/delegation.go index 182b637..32e9c33 100644 --- a/core/delegation/delegation.go +++ b/core/delegation/delegation.go @@ -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" @@ -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) @@ -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 { @@ -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) { @@ -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 { diff --git a/core/delegation/delegation_test.go b/core/delegation/delegation_test.go new file mode 100644 index 0000000..c577f7c --- /dev/null +++ b/core/delegation/delegation_test.go @@ -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) +} diff --git a/core/invocation/invocation.go b/core/invocation/invocation.go index ed37fb5..c6e16f4 100644 --- a/core/invocation/invocation.go +++ b/core/invocation/invocation.go @@ -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) } diff --git a/testing/helpers/helpers.go b/testing/helpers/helpers.go index 99cf62f..b4e9f77 100644 --- a/testing/helpers/helpers.go +++ b/testing/helpers/helpers.go @@ -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 { @@ -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} +} diff --git a/validator/lib_test.go b/validator/lib_test.go index e73e9c2..e155103 100644 --- a/validator/lib_test.go +++ b/validator/lib_test.go @@ -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,