Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions cmd/message/extract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package message

import (
"bytes"
"fmt"
"io"
"os"

logging "github.com/ipfs/go-log/v2"
"github.com/spf13/cobra"
"github.com/storacha/debugger/pkg/ipldfmt"
"github.com/storacha/debugger/pkg/ucanfmt"
"github.com/storacha/go-ucanto/core/car"
"github.com/storacha/go-ucanto/core/dag/blockstore"
"github.com/storacha/go-ucanto/core/message"
)

var extractCmd = &cobra.Command{
Use: "extract [car-file]",
Short: "Extract a message from a CAR.",
Long: "Extract a message that has been archived to a CAR. You can pipe directly to this command.",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
logging.SetLogLevel("*", "info")

var archive []byte
var err error
if len(args) > 0 {
archive, err = os.ReadFile(args[0])
} else {
archive, err = io.ReadAll(cmd.InOrStdin())
}
if err != nil {
panic(err)
}

// Decode CAR file
roots, blocks, err := car.Decode(bytes.NewReader(archive))
if err != nil {
panic(fmt.Errorf("decoding CAR: %w", err))
}
if len(roots) != 1 {
panic(fmt.Errorf("unexpected number of roots: %d, expected: 1", len(roots)))
}

// Create blockstore from blocks
bstore, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(blocks))
if err != nil {
panic(fmt.Errorf("creating blockstore: %w", err))
}

// Create message from root and blockstore
msg, err := message.NewMessage(roots[0], bstore)
if err != nil {
panic(fmt.Errorf("creating message: %w", err))
}

jsonOutput, _ := cmd.Flags().GetBool("json")
if jsonOutput {
for b, err := range msg.Blocks() {
if err != nil {
panic(fmt.Errorf("iterating message blocks: %w", err))
}
cmd.Printf("%s\n", b.Link())
s, err := ipldfmt.FormatDagCBOR(b.Bytes())
if err != nil {
panic(fmt.Errorf("formatting block %s: %w", b.Link(), err))
}
cmd.Println(s)
cmd.Println("")
}
} else {
ucanfmt.PrintMessage(msg)
}
},
}

func init() {
extractCmd.Flags().Bool("json", false, "Output DAG JSON")
Cmd.AddCommand(extractCmd)
}
75 changes: 75 additions & 0 deletions cmd/message/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package message

import (
"bytes"
"fmt"

logging "github.com/ipfs/go-log/v2"
"github.com/multiformats/go-multibase"
"github.com/spf13/cobra"
"github.com/storacha/debugger/pkg/ipldfmt"
"github.com/storacha/debugger/pkg/ucanfmt"
"github.com/storacha/go-ucanto/core/car"
"github.com/storacha/go-ucanto/core/dag/blockstore"
"github.com/storacha/go-ucanto/core/message"
)

var parseCmd = &cobra.Command{
Use: "parse <value>",
Short: "Parse a message.",
Long: `Parse a multibase encoded CID, with an identity multihash that contains message data in a CAR file.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
logging.SetLogLevel("*", "info")

// Decode the multibase encoded value
_, data, err := multibase.Decode(args[0])
if err != nil {
panic(fmt.Errorf("decoding multibase: %w", err))
}

// Decode CAR file
roots, blocks, err := car.Decode(bytes.NewReader(data))
if err != nil {
panic(fmt.Errorf("decoding CAR: %w", err))
}
if len(roots) != 1 {
panic(fmt.Errorf("unexpected number of roots: %d, expected: 1", len(roots)))
}

// Create blockstore from blocks
bstore, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(blocks))
if err != nil {
panic(fmt.Errorf("creating blockstore: %w", err))
}

// Create message from root and blockstore
msg, err := message.NewMessage(roots[0], bstore)
if err != nil {
panic(fmt.Errorf("creating message: %w", err))
}

jsonOutput, _ := cmd.Flags().GetBool("json")
if jsonOutput {
for b, err := range msg.Blocks() {
if err != nil {
panic(fmt.Errorf("iterating message blocks: %w", err))
}
cmd.Printf("%s\n", b.Link())
s, err := ipldfmt.FormatDagCBOR(b.Bytes())
if err != nil {
panic(fmt.Errorf("formatting block %s: %w", b.Link(), err))
}
cmd.Println(s)
cmd.Println("")
}
} else {
ucanfmt.PrintMessage(msg)
}
},
}

func init() {
parseCmd.Flags().Bool("json", false, "Output DAG JSON")
Cmd.AddCommand(parseCmd)
}
10 changes: 10 additions & 0 deletions cmd/message/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package message

import (
"github.com/spf13/cobra"
)

var Cmd = &cobra.Command{
Use: "message",
Short: "Tools for debugging UCAN messages",
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/storacha/debugger/cmd/delegation"
"github.com/storacha/debugger/cmd/flatfs"
"github.com/storacha/debugger/cmd/ipni"
"github.com/storacha/debugger/cmd/message"
"github.com/storacha/debugger/cmd/xagentmessage"
)

Expand Down Expand Up @@ -41,5 +42,6 @@ func init() {
rootCmd.AddCommand(delegation.Cmd)
rootCmd.AddCommand(flatfs.Cmd)
rootCmd.AddCommand(ipni.Cmd)
rootCmd.AddCommand(message.Cmd)
rootCmd.AddCommand(xagentmessage.Cmd)
}
7 changes: 6 additions & 1 deletion pkg/ipldfmt/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ func FormatDagCBOR(buf []byte) (string, error) {
if err != nil {
return "", fmt.Errorf("decoding CBOR: %w", err)
}
return FormatNode(n, "")
}

// FormatNode formats an ipld-node to a dag-json encoded string.
func FormatNode(n ipld.Node, prefix string) (string, error) {
jsonData, err := ipld.Encode(n, dagjson.Encode)
if err != nil {
return "", fmt.Errorf("encoding JSON: %w", err)
}
var indentedJSON bytes.Buffer
err = json.Indent(&indentedJSON, jsonData, "", " ")
err = json.Indent(&indentedJSON, jsonData, prefix, " ")
if err != nil {
return "", fmt.Errorf("indenting JSON: %w", err)
}
Expand Down
45 changes: 45 additions & 0 deletions pkg/ucanfmt/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ucanfmt

import (
"fmt"

"github.com/storacha/go-ucanto/core/message"
)

func PrintMessage(m message.AgentMessage) {
fmt.Printf("%s\n", m.Root().Link())

invocations := m.Invocations()
if len(invocations) > 0 {
fmt.Println(" Invocations:")
for _, invLink := range invocations {
inv, ok, err := m.Invocation(invLink)
if err != nil {
fmt.Printf(" Error getting invocation %s: %v\n", invLink, err)
continue
}
if !ok {
fmt.Printf(" Invocation not found: %s\n", invLink)
continue
}
doPrintDelegation(inv, 1)
}
}

receipts := m.Receipts()
if len(receipts) > 0 {
fmt.Println(" Receipts:")
for _, rcptLink := range receipts {
rcpt, ok, err := m.Receipt(rcptLink)
if err != nil {
fmt.Printf(" Error getting receipt %s: %v\n", rcptLink, err)
continue
}
if !ok {
fmt.Printf(" Receipt not found: %s\n", rcptLink)
continue
}
doPrintReceipt(rcpt, 1)
}
}
}
92 changes: 92 additions & 0 deletions pkg/ucanfmt/receipt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package ucanfmt

import (
"fmt"

"github.com/storacha/debugger/pkg/ipldfmt"
"github.com/storacha/go-ucanto/core/dag/blockstore"
"github.com/storacha/go-ucanto/core/delegation"
"github.com/storacha/go-ucanto/core/ipld"
"github.com/storacha/go-ucanto/core/receipt"
"github.com/storacha/go-ucanto/core/result"
)

func PrintReceipt(r receipt.AnyReceipt) {
doPrintReceipt(r, 0)
}

func doPrintReceipt(r receipt.AnyReceipt, level int) {
log := withIndent(level)

log("%s", r.Root().Link())

if r.Issuer() != nil {
log(" Issuer: %s", r.Issuer().DID())
}

log(" Ran: %s", r.Ran().Link())

// Print the result (Out)
out := r.Out()
result.MatchResultR0(out, func(ok ipld.Node) {
log(" Out: Ok")
jsonString, err := ipldfmt.FormatNode(ok, " ")
if err != nil {
log(" Error formatting JSON: %v", err)
} else {
log("%s", jsonString)
}
}, func(err ipld.Node) {
log(" Out: Error")
jsonString, jsonErr := ipldfmt.FormatNode(err, " ")
if jsonErr != nil {
log(" Error formatting JSON: %v", jsonErr)
} else {
log("%s", jsonString)
}
})

// Print effects
fx := r.Fx()
if len(fx.Fork()) > 0 {
log(" Effects:")
log(" Fork:")
for _, f := range fx.Fork() {
log(" %s", f.Link())
}
}

if fx.Join().Link() != nil {
if len(fx.Fork()) == 0 {
log(" Effects:")
}
log(" Join: %s", fx.Join().Link())
}

// Print metadata
meta := r.Meta()
if len(meta) > 0 {
log(" Meta:")
for k, v := range meta {
log(" %s: %v", k, v)
}
}

bs, err := blockstore.NewBlockReader(blockstore.WithBlocksIterator(r.Blocks()))
if err != nil {
panic(fmt.Errorf("creating blockstore: %w", err))
}

// Print proofs recursively
if len(r.Proofs()) > 0 {
log(" Proofs:")
for _, p := range r.Proofs() {
pd, err := delegation.NewDelegationView(p.Link(), bs)
if err != nil {
log(" %s", p.Link())
continue
}
doPrintDelegation(pd, level+2)
}
}
}