Skip to content

Commit

Permalink
Merge pull request #1 from storacha-network/feat/receipt-writing
Browse files Browse the repository at this point in the history
Receipt writing
  • Loading branch information
Alan Shaw authored Aug 1, 2024
2 parents aa68249 + 3bfd389 commit 6fb87be
Show file tree
Hide file tree
Showing 18 changed files with 507 additions and 83 deletions.
2 changes: 1 addition & 1 deletion client/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type ExecutionResponse interface {
}

func Execute(invocations []invocation.Invocation, conn Connection) (ExecutionResponse, error) {
input, err := message.Build(invocations)
input, err := message.Build(invocations, nil)
if err != nil {
return nil, fmt.Errorf("building message: %s", err)
}
Expand Down
18 changes: 18 additions & 0 deletions core/dag/blockstore/blockstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,21 @@ func NewBlockReader(options ...Option) (BlockReader, error) {

return &blockreader{keys, blks}, nil
}

func WriteInto(view ipld.View, bs BlockWriter) error {
blks := view.Blocks()
for {
b, err := blks.Next()
if err != nil {
if err == io.EOF {
break
}
return fmt.Errorf("reading proof blocks: %s", err)
}
err = bs.Put(b)
if err != nil {
return fmt.Errorf("putting proof block: %s", err)
}
}
return nil
}
25 changes: 5 additions & 20 deletions core/delegation/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package delegation

import (
"fmt"
"io"

"github.com/web3-storage/go-ucanto/core/dag/blockstore"
"github.com/web3-storage/go-ucanto/core/ipld/block"
Expand All @@ -20,7 +19,7 @@ type delegationConfig struct {
nbf uint64
nnc string
fct []ucan.FactBuilder
prf []Delegation
prf Proofs
}

// WithExpiration configures the expiration time in UTC seconds since Unix
Expand Down Expand Up @@ -61,7 +60,7 @@ func WithFacts(fct []ucan.FactBuilder) Option {
// `Delegation` is not the resource owner / service provider, for the delegated
// capabilities, the `proofs` must contain valid `Proof`s containing
// delegations to the `issuer`.
func WithProofs(prf []Delegation) Option {
func WithProofs(prf Proofs) Option {
return func(cfg *delegationConfig) error {
cfg.prf = prf
return nil
Expand All @@ -79,28 +78,14 @@ func Delegate(issuer ucan.Signer, audience ucan.Principal, capabilities []ucan.C
}
}

var links []ucan.Link
bs, err := blockstore.NewBlockStore()
if err != nil {
return nil, err
}

for _, p := range cfg.prf {
links = append(links, p.Link())
blks := p.Blocks()
for {
b, err := blks.Next()
if err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("reading proof blocks: %s", err)
}
err = bs.Put(b)
if err != nil {
return nil, fmt.Errorf("putting proof block: %s", err)
}
}
links, err := cfg.prf.WriteInto(bs)
if err != nil {
return nil, err
}

data, err := ucan.Issue(
Expand Down
2 changes: 1 addition & 1 deletion core/delegation/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
// Delagation is a materialized view of a UCAN delegation, which can be encoded
// into a UCAN token and used as proof for an invocation or further delegations.
type Delegation interface {
ipld.IPLDView
ipld.View
// Link returns the IPLD link of the root block of the delegation.
Link() ucan.Link
// Archive writes the delegation to a Content Addressed aRchive (CAR).
Expand Down
60 changes: 60 additions & 0 deletions core/delegation/proofs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package delegation

import (
"github.com/web3-storage/go-ucanto/core/dag/blockstore"
"github.com/web3-storage/go-ucanto/core/ipld"
"github.com/web3-storage/go-ucanto/ucan"
)

type Proof struct {
delegation Delegation
link ucan.Link
}

func (p Proof) Delegation() (Delegation, bool) {
return p.delegation, p.delegation != nil
}

func (p Proof) Link() ucan.Link {
if p.delegation != nil {
return p.delegation.Link()
}
return p.link
}

func FromDelegation(delegation Delegation) Proof {
return Proof{delegation, nil}
}

func FromLink(link ucan.Link) Proof {
return Proof{nil, link}
}

type Proofs []Proof

func NewProofsView(links []ipld.Link, bs blockstore.BlockReader) Proofs {
proofs := make(Proofs, 0, len(links))
for _, link := range links {
if delegation, err := NewDelegationView(link, bs); err == nil {
proofs = append(proofs, FromDelegation(delegation))
} else {
proofs = append(proofs, FromLink(link))
}
}
return proofs
}

// WriteInto writes a set of proofs, some of which may be full delegations to a blockstore
func (proofs Proofs) WriteInto(bs blockstore.BlockWriter) ([]ipld.Link, error) {
links := make([]ucan.Link, 0, len(proofs))
for _, p := range proofs {
links = append(links, p.Link())
if delegation, isDelegation := p.Delegation(); isDelegation {
err := blockstore.WriteInto(delegation, bs)
if err != nil {
return nil, err
}
}
}
return links, nil
}
31 changes: 31 additions & 0 deletions core/invocation/invocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,34 @@ type IssuedInvocation interface {
func Invoke(issuer ucan.Signer, audience ucan.Principal, capability ucan.Capability[ucan.CaveatBuilder], options ...delegation.Option) (IssuedInvocation, error) {
return delegation.Delegate(issuer, audience, []ucan.Capability[ucan.CaveatBuilder]{capability}, options...)
}

type Ran struct {
invocation Invocation
link ucan.Link
}

func (r Ran) Invocation() (Invocation, bool) {
return r.invocation, r.invocation != nil
}

func (r Ran) Link() ucan.Link {
if r.invocation != nil {
return r.invocation.Link()
}
return r.link
}

func FromInvocation(invocation Invocation) Ran {
return Ran{invocation, nil}
}

func FromLink(link ucan.Link) Ran {
return Ran{nil, link}
}

func (r Ran) WriteInto(bs blockstore.BlockWriter) (ipld.Link, error) {
if invocation, ok := r.Invocation(); ok {
return r.Link(), blockstore.WriteInto(invocation, bs)
}
return r.Link(), nil
}
1 change: 1 addition & 0 deletions core/ipld/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ import (

type Link = ipld.Link
type Block = block.Block
type Node = ipld.Node
13 changes: 12 additions & 1 deletion core/ipld/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
// View represents a materialized IPLD DAG View, which provides a generic
// traversal API. It is useful for encoding (potentially partial) IPLD DAGs
// into content archives (e.g. CARs).
type IPLDView interface {
type View interface {
// Root is the root block of the IPLD DAG this is the view of. This is the
// block from which all other blocks are linked directly or transitively.
Root() Block
Expand All @@ -21,3 +21,14 @@ type IPLDView interface {
// omitting it when encoding the view into a CAR archive.
Blocks() iterable.Iterator[Block]
}

// ViewBuilder represents a materializable IPLD DAG View. It is a useful
// abstraction that can be used to defer actual IPLD encoding.
//
// Note that represented DAG could be partial implying that some of the blocks
// may not be included. This by design allowing a user to include whatever
// blocks they want to include.
type ViewBuilder[V View] interface {
// BuildIPLDView encodes all the blocks and creates a new IPLDView instance over them.
BuildIPLDView() V
}
36 changes: 23 additions & 13 deletions core/message/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package message

import (
"fmt"
"io"

"github.com/web3-storage/go-ucanto/core/dag/blockstore"
"github.com/web3-storage/go-ucanto/core/invocation"
Expand All @@ -12,10 +11,11 @@ import (
"github.com/web3-storage/go-ucanto/core/ipld/hash/sha256"
"github.com/web3-storage/go-ucanto/core/iterable"
mdm "github.com/web3-storage/go-ucanto/core/message/datamodel"
"github.com/web3-storage/go-ucanto/core/receipt"
)

type AgentMessage interface {
ipld.IPLDView
ipld.View
// Invocations is a list of links to the root block of invocations than can
// be found in the message.
Invocations() []ipld.Link
Expand Down Expand Up @@ -73,7 +73,7 @@ func (m *message) Get(link ipld.Link) (ipld.Link, bool) {
return rcpt, true
}

func Build(invocations []invocation.Invocation) (AgentMessage, error) {
func Build(invocations []invocation.Invocation, receipts []receipt.AnyReceipt) (AgentMessage, error) {
bs, err := blockstore.NewBlockStore()
if err != nil {
return nil, err
Expand All @@ -83,25 +83,35 @@ func Build(invocations []invocation.Invocation) (AgentMessage, error) {
for _, inv := range invocations {
ex = append(ex, inv.Link())

blks := inv.Blocks()
for {
b, err := blks.Next()
err := blockstore.WriteInto(inv, bs)
if err != nil {
return nil, err
}
}

var report *mdm.ReportModel
if len(receipts) > 0 {
report = &mdm.ReportModel{
Keys: make([]string, 0, len(receipts)),
Values: make(map[string]ipld.Link, len(receipts)),
}
for _, receipt := range receipts {
err := blockstore.WriteInto(receipt, bs)
if err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("reading invocation blocks: %s", err)
return nil, err
}
err = bs.Put(b)
if err != nil {
return nil, fmt.Errorf("putting invocation block: %s", err)

key := receipt.Ran().Link().String()
if _, ok := report.Values[key]; !ok {
report.Values[key] = receipt.Root().Link()
}
}
}

msg := mdm.AgentMessageModel{
UcantoMessage7: &mdm.DataModel{
Execute: ex,
Report: report,
},
}

Expand Down
4 changes: 4 additions & 0 deletions core/receipt/anyresult.ipldsch
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type Result union {
| any "ok"
| any "error"
} representation keyed
4 changes: 2 additions & 2 deletions core/receipt/datamodel/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ type MetaModel struct {
}

type ResultModel[O any, X any] struct {
Ok O
Err X
Ok *O
Err *X
}

// NewReceiptModelType creates a new schema.Type for a Receipt. You must
Expand Down
16 changes: 8 additions & 8 deletions core/receipt/datamodel/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ func TestEncodeDecode(t *testing.T) {
}

l := cidlink.Link{Cid: cid.MustParse("bafkreiem4twkqzsq2aj4shbycd4yvoj2cx72vezicletlhi7dijjciqpui")}
r0 := rdm.ReceiptModel[*resultOk, *resultErr]{
Ocm: rdm.OutcomeModel[*resultOk, *resultErr]{
r0 := rdm.ReceiptModel[resultOk, resultErr]{
Ocm: rdm.OutcomeModel[resultOk, resultErr]{
Ran: l,
Out: rdm.ResultModel[*resultOk, *resultErr]{
Out: rdm.ResultModel[resultOk, resultErr]{
Ok: &resultOk{Status: "done"},
},
},
Expand All @@ -51,7 +51,7 @@ func TestEncodeDecode(t *testing.T) {
if err != nil {
t.Fatalf("encoding receipt: %s", err)
}
r1 := rdm.ReceiptModel[*resultOk, *resultErr]{}
r1 := rdm.ReceiptModel[resultOk, resultErr]{}
err = block.Decode(b0, &r1, typ, cbor.Codec, sha256.Hasher)
if err != nil {
t.Fatalf("decoding receipt: %s", err)
Expand All @@ -63,10 +63,10 @@ func TestEncodeDecode(t *testing.T) {
t.Fatalf("status was not done")
}

r2 := rdm.ReceiptModel[*resultOk, *resultErr]{
Ocm: rdm.OutcomeModel[*resultOk, *resultErr]{
r2 := rdm.ReceiptModel[resultOk, resultErr]{
Ocm: rdm.OutcomeModel[resultOk, resultErr]{
Ran: l,
Out: rdm.ResultModel[*resultOk, *resultErr]{
Out: rdm.ResultModel[resultOk, resultErr]{
Err: &resultErr{Message: "boom"},
},
},
Expand All @@ -75,7 +75,7 @@ func TestEncodeDecode(t *testing.T) {
if err != nil {
t.Fatalf("encoding receipt: %s", err)
}
r3 := rdm.ReceiptModel[*resultOk, *resultErr]{}
r3 := rdm.ReceiptModel[resultOk, resultErr]{}
err = block.Decode(b1, &r3, typ, cbor.Codec, sha256.Hasher)
if err != nil {
t.Fatalf("decoding receipt: %s", err)
Expand Down
Loading

0 comments on commit 6fb87be

Please sign in to comment.