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 }