github.com/storacha/go-ucanto@v0.7.2/core/message/message.go (about)

     1  package message
     2  
     3  import (
     4  	"fmt"
     5  	"iter"
     6  
     7  	"github.com/storacha/go-ucanto/core/dag/blockstore"
     8  	"github.com/storacha/go-ucanto/core/invocation"
     9  	"github.com/storacha/go-ucanto/core/ipld"
    10  	"github.com/storacha/go-ucanto/core/ipld/block"
    11  	"github.com/storacha/go-ucanto/core/ipld/codec/cbor"
    12  	"github.com/storacha/go-ucanto/core/ipld/hash/sha256"
    13  	mdm "github.com/storacha/go-ucanto/core/message/datamodel"
    14  	"github.com/storacha/go-ucanto/core/receipt"
    15  )
    16  
    17  type AgentMessage interface {
    18  	ipld.View
    19  	// Invocations is a list of links to the root block of invocations than can
    20  	// be found in the message.
    21  	Invocations() []ipld.Link
    22  	// Get a given invocation from the message by root CID.
    23  	Invocation(root ipld.Link) (invocation.Invocation, bool, error)
    24  	// Receipts is a list of links to the root block of receipts that can be
    25  	// found in the message.
    26  	Receipts() []ipld.Link
    27  	// Get a given receipt from the message by root CID.
    28  	Receipt(root ipld.Link) (receipt.AnyReceipt, bool, error)
    29  	// Get returns a receipt link from the message, given an invocation link.
    30  	Get(link ipld.Link) (ipld.Link, bool)
    31  }
    32  
    33  type message struct {
    34  	root ipld.Block
    35  	data *mdm.DataModel
    36  	blks blockstore.BlockReader
    37  	// invs is cache of invocations decoded from the message
    38  	invs map[string]invocation.Invocation
    39  	// rcpts is a cache of receipts decoded from the message
    40  	rcpts map[string]receipt.AnyReceipt
    41  }
    42  
    43  var _ AgentMessage = (*message)(nil)
    44  
    45  func (m *message) Root() ipld.Block {
    46  	return m.root
    47  }
    48  
    49  func (m *message) Blocks() iter.Seq2[ipld.Block, error] {
    50  	return m.blks.Iterator()
    51  }
    52  
    53  func (m *message) Invocations() []ipld.Link {
    54  	return m.data.Execute
    55  }
    56  
    57  func (m *message) Invocation(root ipld.Link) (invocation.Invocation, bool, error) {
    58  	if inv, ok := m.invs[root.String()]; ok {
    59  		return inv, true, nil
    60  	}
    61  	rtBlk, ok, err := m.blks.Get(root)
    62  	if !ok || err != nil {
    63  		return nil, ok, err
    64  	}
    65  	inv, err := invocation.NewInvocation(rtBlk, m.blks)
    66  	if err != nil {
    67  		return nil, false, err
    68  	}
    69  	m.invs[root.String()] = inv
    70  	return inv, true, nil
    71  }
    72  
    73  func (m *message) Receipts() []ipld.Link {
    74  	if m.data.Report == nil {
    75  		return []ipld.Link{}
    76  	}
    77  	var rcpts []ipld.Link
    78  	for _, k := range m.data.Report.Keys {
    79  		l, ok := m.data.Report.Values[k]
    80  		if ok {
    81  			rcpts = append(rcpts, l)
    82  		}
    83  	}
    84  	return rcpts
    85  }
    86  
    87  func (m *message) Receipt(root ipld.Link) (receipt.AnyReceipt, bool, error) {
    88  	if rcpt, ok := m.rcpts[root.String()]; ok {
    89  		return rcpt, true, nil
    90  	}
    91  	_, ok, err := m.blks.Get(root)
    92  	if !ok || err != nil {
    93  		return nil, ok, err
    94  	}
    95  	rcpt, err := receipt.NewAnyReceipt(root, m.blks)
    96  	if err != nil {
    97  		return nil, false, err
    98  	}
    99  	m.rcpts[root.String()] = rcpt
   100  	return rcpt, true, nil
   101  }
   102  
   103  func (m *message) Get(link ipld.Link) (ipld.Link, bool) {
   104  	if m.data.Report == nil {
   105  		return nil, false
   106  	}
   107  	var rcpt ipld.Link
   108  	found := false
   109  	for _, k := range m.data.Report.Keys {
   110  		if k == link.String() {
   111  			rcpt = m.data.Report.Values[k]
   112  			found = true
   113  			break
   114  		}
   115  	}
   116  	if !found {
   117  		return nil, false
   118  	}
   119  	return rcpt, true
   120  }
   121  
   122  func Build(invocations []invocation.Invocation, receipts []receipt.AnyReceipt) (AgentMessage, error) {
   123  	bs, err := blockstore.NewBlockStore()
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	ex := []ipld.Link{}
   129  	invCache := map[string]invocation.Invocation{}
   130  	for _, inv := range invocations {
   131  		ex = append(ex, inv.Link())
   132  		invCache[inv.Link().String()] = inv
   133  
   134  		err := blockstore.WriteInto(inv, bs)
   135  		if err != nil {
   136  			return nil, err
   137  		}
   138  	}
   139  
   140  	var report *mdm.ReportModel
   141  	rcptCache := map[string]receipt.AnyReceipt{}
   142  	if len(receipts) > 0 {
   143  		report = &mdm.ReportModel{
   144  			Keys:   make([]string, 0, len(receipts)),
   145  			Values: make(map[string]ipld.Link, len(receipts)),
   146  		}
   147  		for _, receipt := range receipts {
   148  			err := blockstore.WriteInto(receipt, bs)
   149  			if err != nil {
   150  				return nil, err
   151  			}
   152  			rcptCache[receipt.Root().Link().String()] = receipt
   153  
   154  			key := receipt.Ran().Link().String()
   155  			report.Keys = append(report.Keys, key)
   156  			if _, ok := report.Values[key]; !ok {
   157  				report.Values[key] = receipt.Root().Link()
   158  			}
   159  		}
   160  	}
   161  
   162  	msg := mdm.AgentMessageModel{
   163  		UcantoMessage7: &mdm.DataModel{
   164  			Execute: ex,
   165  			Report:  report,
   166  		},
   167  	}
   168  
   169  	rt, err := block.Encode(
   170  		&msg,
   171  		mdm.Type(),
   172  		cbor.Codec,
   173  		sha256.Hasher,
   174  	)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	err = bs.Put(rt)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	return &message{
   184  		root:  rt,
   185  		data:  msg.UcantoMessage7,
   186  		blks:  bs,
   187  		invs:  invCache,
   188  		rcpts: rcptCache,
   189  	}, nil
   190  }
   191  
   192  func NewMessage(root ipld.Link, blks blockstore.BlockReader) (AgentMessage, error) {
   193  	rblock, ok, err := blks.Get(root)
   194  	if err != nil {
   195  		return nil, fmt.Errorf("getting root block: %w", err)
   196  	}
   197  	if !ok {
   198  		return nil, fmt.Errorf("missing root block: %s", root)
   199  	}
   200  
   201  	msg := mdm.AgentMessageModel{}
   202  	err = block.Decode(
   203  		rblock,
   204  		&msg,
   205  		mdm.Type(),
   206  		cbor.Codec,
   207  		sha256.Hasher,
   208  	)
   209  	if err != nil {
   210  		return nil, fmt.Errorf("decoding message: %w", err)
   211  	}
   212  
   213  	return &message{
   214  		root:  rblock,
   215  		data:  msg.UcantoMessage7,
   216  		blks:  blks,
   217  		invs:  map[string]invocation.Invocation{},
   218  		rcpts: map[string]receipt.AnyReceipt{},
   219  	}, nil
   220  }