From bb601845268fa7ffcf90cfe21d5eca9c8b42f590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Wed, 26 Jun 2019 23:07:35 +0200 Subject: [PATCH 1/6] pin cmd: stream recursive pins Add a --stream flag to stream the results instead of accumulating the final result in memory. This is a rework of https://github.com/ipfs/go-ipfs/pull/5005 --- core/commands/pin.go | 157 ++++++++++++++++++++++++++++++------------- 1 file changed, 110 insertions(+), 47 deletions(-) diff --git a/core/commands/pin.go b/core/commands/pin.go index c1548904a..55f10385e 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -259,8 +259,9 @@ collected if needed. (By default, recursively. Use -r=false for direct pins.) } const ( - pinTypeOptionName = "type" - pinQuietOptionName = "quiet" + pinTypeOptionName = "type" + pinQuietOptionName = "quiet" + pinStreamOptionName = "stream" ) var listPinCmd = &cmds.Command{ @@ -313,6 +314,7 @@ Example: Options: []cmds.Option{ cmds.StringOption(pinTypeOptionName, "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\".").WithDefault("all"), cmds.BoolOption(pinQuietOptionName, "q", "Write just hashes of objects."), + cmds.BoolOption(pinStreamOptionName, "s", "Don't buffer pins before sending."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { n, err := cmdenv.GetNode(env) @@ -326,9 +328,7 @@ Example: } typeStr, _ := req.Options[pinTypeOptionName].(string) - if err != nil { - return err - } + stream, _ := req.Options[pinStreamOptionName].(bool) switch typeStr { case "all", "direct", "indirect", "recursive": @@ -337,34 +337,50 @@ Example: return err } - enc, err := cmdenv.GetCidEncoder(req) - if err != nil { - return err + // For backward compatibility, we accumulate the pins in the same output type as before. + emit := res.Emit + lgcList := map[string]RefObject{} + if !stream { + emit = func(v interface{}) error { + obj := v.(*PinLsOutputWrapper) + lgcList[obj.RefKeyObject.Cid] = RefObject{Type: obj.RefKeyObject.Type} + return nil + } } - var keys map[cid.Cid]RefKeyObject if len(req.Arguments) > 0 { - keys, err = pinLsKeys(req.Context, req.Arguments, typeStr, n, api) + err = pinLsKeys(req.Context, req.Arguments, typeStr, n, api, emit) } else { - keys, err = pinLsAll(req.Context, typeStr, n) + err = pinLsAll(req.Context, typeStr, n, emit) } if err != nil { return err } - refKeys := make(map[string]RefKeyObject, len(keys)) - for k, v := range keys { - refKeys[enc.Encode(k)] = v + if !stream { + return cmds.EmitOnce(res, &PinLsOutputWrapper{ + RefKeyList: RefKeyList{Keys: lgcList}, + }) } - return cmds.EmitOnce(res, &RefKeyList{Keys: refKeys}) + return nil }, - Type: RefKeyList{}, + Type: &PinLsOutputWrapper{}, Encoders: cmds.EncoderMap{ - cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *RefKeyList) error { + cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *PinLsOutputWrapper) error { quiet, _ := req.Options[pinQuietOptionName].(bool) + stream, _ := req.Options[pinStreamOptionName].(bool) - for k, v := range out.Keys { + if stream { + if quiet { + fmt.Fprintf(w, "%s\n", out.RefKeyObject.Cid) + } else { + fmt.Fprintf(w, "%s %s\n", out.RefKeyObject.Cid, out.RefKeyObject.Type) + } + return nil + } + + for k, v := range out.RefKeyList.Keys { if quiet { fmt.Fprintf(w, "%s\n", k) } else { @@ -492,35 +508,44 @@ var verifyPinCmd = &cmds.Command{ } type RefKeyObject struct { + Cid string + Type string +} + +type RefObject struct { Type string } type RefKeyList struct { - Keys map[string]RefKeyObject + Keys map[string]RefObject } -func pinLsKeys(ctx context.Context, args []string, typeStr string, n *core.IpfsNode, api coreiface.CoreAPI) (map[cid.Cid]RefKeyObject, error) { +// Pin ls needs to output two different type depending on if it's streamed or not. +// We use this to bypass the cmds lib refusing to have interface{} +type PinLsOutputWrapper struct { + RefKeyList + RefKeyObject +} +func pinLsKeys(ctx context.Context, args []string, typeStr string, n *core.IpfsNode, api coreiface.CoreAPI, emit func(value interface{}) error) error { mode, ok := pin.StringToMode(typeStr) if !ok { - return nil, fmt.Errorf("invalid pin mode '%s'", typeStr) + return fmt.Errorf("invalid pin mode '%s'", typeStr) } - keys := make(map[cid.Cid]RefKeyObject) - for _, p := range args { c, err := api.ResolvePath(ctx, path.New(p)) if err != nil { - return nil, err + return err } pinType, pinned, err := n.Pinning.IsPinnedWithType(c.Cid(), mode) if err != nil { - return nil, err + return err } if !pinned { - return nil, fmt.Errorf("path '%s' is not pinned", p) + return fmt.Errorf("path '%s' is not pinned", p) } switch pinType { @@ -528,44 +553,82 @@ func pinLsKeys(ctx context.Context, args []string, typeStr string, n *core.IpfsN default: pinType = "indirect through " + pinType } - keys[c.Cid()] = RefKeyObject{ - Type: pinType, + + err = emit(&PinLsOutputWrapper{ + RefKeyObject: RefKeyObject{ + Type: pinType, + Cid: c.Cid().String(), + }, + }) + if err != nil { + return err } } - return keys, nil + return nil } -func pinLsAll(ctx context.Context, typeStr string, n *core.IpfsNode) (map[cid.Cid]RefKeyObject, error) { +func pinLsAll(ctx context.Context, typeStr string, n *core.IpfsNode, emit func(value interface{}) error) error { + keys := cid.NewSet() - keys := make(map[cid.Cid]RefKeyObject) - - AddToResultKeys := func(keyList []cid.Cid, typeStr string) { + AddToResultKeys := func(keyList []cid.Cid, typeStr string) error { for _, c := range keyList { - keys[c] = RefKeyObject{ - Type: typeStr, + if keys.Visit(c) { + err := emit(&PinLsOutputWrapper{ + RefKeyObject: RefKeyObject{ + Type: typeStr, + Cid: c.String(), + }, + }) + if err != nil { + return err + } } } + return nil } if typeStr == "direct" || typeStr == "all" { - AddToResultKeys(n.Pinning.DirectKeys(), "direct") - } - if typeStr == "indirect" || typeStr == "all" { - set := cid.NewSet() - for _, k := range n.Pinning.RecursiveKeys() { - err := dag.EnumerateChildren(ctx, dag.GetLinksWithDAG(n.DAG), k, set.Visit) - if err != nil { - return nil, err - } + err := AddToResultKeys(n.Pinning.DirectKeys(), "direct") + if err != nil { + return err } - AddToResultKeys(set.Keys(), "indirect") } if typeStr == "recursive" || typeStr == "all" { - AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive") + err := AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive") + if err != nil { + return err + } + } + if typeStr == "indirect" || typeStr == "all" { + for _, k := range n.Pinning.RecursiveKeys() { + var visitErr error + err := dag.EnumerateChildren(ctx, dag.GetLinksWithDAG(n.DAG), k, func(c cid.Cid) bool { + r := keys.Visit(c) + if r { + err := emit(&PinLsOutputWrapper{ + RefKeyObject: RefKeyObject{ + Type: "indirect", + Cid: c.String(), + }, + }) + if err != nil { + visitErr = err + } + } + return r + }) + + if visitErr != nil { + return visitErr + } + if err != nil { + return err + } + } } - return keys, nil + return nil } // PinVerifyRes is the result returned for each pin checked in "pin verify" From dd06956d30df86126af314c7b88d846d44f3c583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Thu, 11 Jul 2019 13:51:54 +0200 Subject: [PATCH 2/6] pin cmd: proper CID encoding and backward compat --- core/commands/ls.go | 2 +- core/commands/pin.go | 264 ++++++++++++++++++++++--------------------- 2 files changed, 138 insertions(+), 128 deletions(-) diff --git a/core/commands/ls.go b/core/commands/ls.go index 23c0ebc05..4b0114d33 100644 --- a/core/commands/ls.go +++ b/core/commands/ls.go @@ -65,7 +65,7 @@ The JSON output contains type information. cmds.BoolOption(lsHeadersOptionNameTime, "v", "Print table headers (Hash, Size, Name)."), cmds.BoolOption(lsResolveTypeOptionName, "Resolve linked objects to find out their types.").WithDefault(true), cmds.BoolOption(lsSizeOptionName, "Resolve linked objects to find out their file size.").WithDefault(true), - cmds.BoolOption(lsStreamOptionName, "s", "Enable exprimental streaming of directory entries as they are traversed."), + cmds.BoolOption(lsStreamOptionName, "s", "Enable experimental streaming of directory entries as they are traversed."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { api, err := cmdenv.GetApi(env, req) diff --git a/core/commands/pin.go b/core/commands/pin.go index 55f10385e..b9e925306 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -314,7 +314,7 @@ Example: Options: []cmds.Option{ cmds.StringOption(pinTypeOptionName, "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\".").WithDefault("all"), cmds.BoolOption(pinQuietOptionName, "q", "Write just hashes of objects."), - cmds.BoolOption(pinStreamOptionName, "s", "Don't buffer pins before sending."), + cmds.BoolOption(pinStreamOptionName, "s", "Enable streaming of pins as they are discovered."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { n, err := cmdenv.GetNode(env) @@ -349,9 +349,9 @@ Example: } if len(req.Arguments) > 0 { - err = pinLsKeys(req.Context, req.Arguments, typeStr, n, api, emit) + err = pinLsKeys(req, typeStr, n, api, emit) } else { - err = pinLsAll(req.Context, typeStr, n, emit) + err = pinLsAll(req, typeStr, n, emit) } if err != nil { return err @@ -393,6 +393,140 @@ Example: }, } +type RefKeyObject struct { + Cid string `json:",omitempty"` + Type string `json:",omitempty"` +} + +type RefObject struct { + Type string +} + +type RefKeyList struct { + Keys map[string]RefObject `json:",omitempty"` +} + +// Pin ls needs to output two different type depending on if it's streamed or not. +// We use this to bypass the cmds lib refusing to have interface{} +type PinLsOutputWrapper struct { + RefKeyList + RefKeyObject +} + +func pinLsKeys(req *cmds.Request, typeStr string, n *core.IpfsNode, api coreiface.CoreAPI, emit func(value interface{}) error) error { + mode, ok := pin.StringToMode(typeStr) + if !ok { + return fmt.Errorf("invalid pin mode '%s'", typeStr) + } + + enc, err := cmdenv.GetCidEncoder(req) + if err != nil { + return err + } + + for _, p := range req.Arguments { + c, err := api.ResolvePath(req.Context, path.New(p)) + if err != nil { + return err + } + + pinType, pinned, err := n.Pinning.IsPinnedWithType(c.Cid(), mode) + if err != nil { + return err + } + + if !pinned { + return fmt.Errorf("path '%s' is not pinned", p) + } + + switch pinType { + case "direct", "indirect", "recursive", "internal": + default: + pinType = "indirect through " + pinType + } + + err = emit(&PinLsOutputWrapper{ + RefKeyObject: RefKeyObject{ + Type: pinType, + Cid: enc.Encode(c.Cid()), + }, + }) + if err != nil { + return err + } + } + + return nil +} + +func pinLsAll(req *cmds.Request, typeStr string, n *core.IpfsNode, emit func(value interface{}) error) error { + enc, err := cmdenv.GetCidEncoder(req) + if err != nil { + return err + } + + keys := cid.NewSet() + + AddToResultKeys := func(keyList []cid.Cid, typeStr string) error { + for _, c := range keyList { + if keys.Visit(c) { + err := emit(&PinLsOutputWrapper{ + RefKeyObject: RefKeyObject{ + Type: typeStr, + Cid: enc.Encode(c), + }, + }) + if err != nil { + return err + } + } + } + return nil + } + + if typeStr == "direct" || typeStr == "all" { + err := AddToResultKeys(n.Pinning.DirectKeys(), "direct") + if err != nil { + return err + } + } + if typeStr == "indirect" || typeStr == "all" { + for _, k := range n.Pinning.RecursiveKeys() { + var visitErr error + err := dag.EnumerateChildren(req.Context, dag.GetLinksWithDAG(n.DAG), k, func(c cid.Cid) bool { + r := keys.Visit(c) + if r { + err := emit(&PinLsOutputWrapper{ + RefKeyObject: RefKeyObject{ + Type: typeStr, + Cid: enc.Encode(c), + }, + }) + if err != nil { + visitErr = err + } + } + return r + }) + + if visitErr != nil { + return visitErr + } + if err != nil { + return err + } + } + } + if typeStr == "recursive" || typeStr == "all" { + err := AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive") + if err != nil { + return err + } + } + + return nil +} + const ( pinUnpinOptionName = "unpin" ) @@ -507,130 +641,6 @@ var verifyPinCmd = &cmds.Command{ }, } -type RefKeyObject struct { - Cid string - Type string -} - -type RefObject struct { - Type string -} - -type RefKeyList struct { - Keys map[string]RefObject -} - -// Pin ls needs to output two different type depending on if it's streamed or not. -// We use this to bypass the cmds lib refusing to have interface{} -type PinLsOutputWrapper struct { - RefKeyList - RefKeyObject -} - -func pinLsKeys(ctx context.Context, args []string, typeStr string, n *core.IpfsNode, api coreiface.CoreAPI, emit func(value interface{}) error) error { - mode, ok := pin.StringToMode(typeStr) - if !ok { - return fmt.Errorf("invalid pin mode '%s'", typeStr) - } - - for _, p := range args { - c, err := api.ResolvePath(ctx, path.New(p)) - if err != nil { - return err - } - - pinType, pinned, err := n.Pinning.IsPinnedWithType(c.Cid(), mode) - if err != nil { - return err - } - - if !pinned { - return fmt.Errorf("path '%s' is not pinned", p) - } - - switch pinType { - case "direct", "indirect", "recursive", "internal": - default: - pinType = "indirect through " + pinType - } - - err = emit(&PinLsOutputWrapper{ - RefKeyObject: RefKeyObject{ - Type: pinType, - Cid: c.Cid().String(), - }, - }) - if err != nil { - return err - } - } - - return nil -} - -func pinLsAll(ctx context.Context, typeStr string, n *core.IpfsNode, emit func(value interface{}) error) error { - keys := cid.NewSet() - - AddToResultKeys := func(keyList []cid.Cid, typeStr string) error { - for _, c := range keyList { - if keys.Visit(c) { - err := emit(&PinLsOutputWrapper{ - RefKeyObject: RefKeyObject{ - Type: typeStr, - Cid: c.String(), - }, - }) - if err != nil { - return err - } - } - } - return nil - } - - if typeStr == "direct" || typeStr == "all" { - err := AddToResultKeys(n.Pinning.DirectKeys(), "direct") - if err != nil { - return err - } - } - if typeStr == "recursive" || typeStr == "all" { - err := AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive") - if err != nil { - return err - } - } - if typeStr == "indirect" || typeStr == "all" { - for _, k := range n.Pinning.RecursiveKeys() { - var visitErr error - err := dag.EnumerateChildren(ctx, dag.GetLinksWithDAG(n.DAG), k, func(c cid.Cid) bool { - r := keys.Visit(c) - if r { - err := emit(&PinLsOutputWrapper{ - RefKeyObject: RefKeyObject{ - Type: "indirect", - Cid: c.String(), - }, - }) - if err != nil { - visitErr = err - } - } - return r - }) - - if visitErr != nil { - return visitErr - } - if err != nil { - return err - } - } - } - - return nil -} - // PinVerifyRes is the result returned for each pin checked in "pin verify" type PinVerifyRes struct { Cid string From 58ea970135284a4ba3f64c1bf868f2f1dffe488b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Thu, 11 Jul 2019 13:59:09 +0200 Subject: [PATCH 3/6] pin cmd: better struct naming --- core/commands/pin.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/core/commands/pin.go b/core/commands/pin.go index b9e925306..10fd4b242 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -339,11 +339,11 @@ Example: // For backward compatibility, we accumulate the pins in the same output type as before. emit := res.Emit - lgcList := map[string]RefObject{} + lgcList := map[string]PinLsType{} if !stream { emit = func(v interface{}) error { obj := v.(*PinLsOutputWrapper) - lgcList[obj.RefKeyObject.Cid] = RefObject{Type: obj.RefKeyObject.Type} + lgcList[obj.PinLsObject.Cid] = PinLsType{Type: obj.PinLsObject.Type} return nil } } @@ -359,7 +359,7 @@ Example: if !stream { return cmds.EmitOnce(res, &PinLsOutputWrapper{ - RefKeyList: RefKeyList{Keys: lgcList}, + PinLsList: PinLsList{Keys: lgcList}, }) } @@ -373,14 +373,14 @@ Example: if stream { if quiet { - fmt.Fprintf(w, "%s\n", out.RefKeyObject.Cid) + fmt.Fprintf(w, "%s\n", out.PinLsObject.Cid) } else { - fmt.Fprintf(w, "%s %s\n", out.RefKeyObject.Cid, out.RefKeyObject.Type) + fmt.Fprintf(w, "%s %s\n", out.PinLsObject.Cid, out.PinLsObject.Type) } return nil } - for k, v := range out.RefKeyList.Keys { + for k, v := range out.PinLsList.Keys { if quiet { fmt.Fprintf(w, "%s\n", k) } else { @@ -393,24 +393,24 @@ Example: }, } -type RefKeyObject struct { +type PinLsObject struct { Cid string `json:",omitempty"` Type string `json:",omitempty"` } -type RefObject struct { +type PinLsType struct { Type string } -type RefKeyList struct { - Keys map[string]RefObject `json:",omitempty"` +type PinLsList struct { + Keys map[string]PinLsType `json:",omitempty"` } // Pin ls needs to output two different type depending on if it's streamed or not. // We use this to bypass the cmds lib refusing to have interface{} type PinLsOutputWrapper struct { - RefKeyList - RefKeyObject + PinLsList + PinLsObject } func pinLsKeys(req *cmds.Request, typeStr string, n *core.IpfsNode, api coreiface.CoreAPI, emit func(value interface{}) error) error { @@ -446,7 +446,7 @@ func pinLsKeys(req *cmds.Request, typeStr string, n *core.IpfsNode, api coreifac } err = emit(&PinLsOutputWrapper{ - RefKeyObject: RefKeyObject{ + PinLsObject: PinLsObject{ Type: pinType, Cid: enc.Encode(c.Cid()), }, @@ -471,7 +471,7 @@ func pinLsAll(req *cmds.Request, typeStr string, n *core.IpfsNode, emit func(val for _, c := range keyList { if keys.Visit(c) { err := emit(&PinLsOutputWrapper{ - RefKeyObject: RefKeyObject{ + PinLsObject: PinLsObject{ Type: typeStr, Cid: enc.Encode(c), }, @@ -497,7 +497,7 @@ func pinLsAll(req *cmds.Request, typeStr string, n *core.IpfsNode, emit func(val r := keys.Visit(c) if r { err := emit(&PinLsOutputWrapper{ - RefKeyObject: RefKeyObject{ + PinLsObject: PinLsObject{ Type: typeStr, Cid: enc.Encode(c), }, From 4ec37b6f7ba672b3f68c4a4c75ce55c10fcfc7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Thu, 11 Jul 2019 14:05:05 +0200 Subject: [PATCH 4/6] pin cmd: document pin ls output types --- core/commands/pin.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/core/commands/pin.go b/core/commands/pin.go index 10fd4b242..c5a774702 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -393,19 +393,7 @@ Example: }, } -type PinLsObject struct { - Cid string `json:",omitempty"` - Type string `json:",omitempty"` -} - -type PinLsType struct { - Type string -} - -type PinLsList struct { - Keys map[string]PinLsType `json:",omitempty"` -} - +// PinLsOutputWrapper is the output type of the pin ls command. // Pin ls needs to output two different type depending on if it's streamed or not. // We use this to bypass the cmds lib refusing to have interface{} type PinLsOutputWrapper struct { @@ -413,6 +401,22 @@ type PinLsOutputWrapper struct { PinLsObject } +// PinLsList is a set of pins with their type +type PinLsList struct { + Keys map[string]PinLsType `json:",omitempty"` +} + +// PinLsType contains the type of a pin +type PinLsType struct { + Type string +} + +// PinLsObject contains the description of a pin +type PinLsObject struct { + Cid string `json:",omitempty"` + Type string `json:",omitempty"` +} + func pinLsKeys(req *cmds.Request, typeStr string, n *core.IpfsNode, api coreiface.CoreAPI, emit func(value interface{}) error) error { mode, ok := pin.StringToMode(typeStr) if !ok { From 9b21269e037cf92396c752610d17b684a3152df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Thu, 11 Jul 2019 14:33:34 +0200 Subject: [PATCH 5/6] pin cmd: output the recursive pins first for performance --- core/commands/pin.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/commands/pin.go b/core/commands/pin.go index c5a774702..27092d698 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -494,6 +494,12 @@ func pinLsAll(req *cmds.Request, typeStr string, n *core.IpfsNode, emit func(val return err } } + if typeStr == "recursive" || typeStr == "all" { + err := AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive") + if err != nil { + return err + } + } if typeStr == "indirect" || typeStr == "all" { for _, k := range n.Pinning.RecursiveKeys() { var visitErr error @@ -521,12 +527,6 @@ func pinLsAll(req *cmds.Request, typeStr string, n *core.IpfsNode, emit func(val } } } - if typeStr == "recursive" || typeStr == "all" { - err := AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive") - if err != nil { - return err - } - } return nil } From 16b4d74d3b372f9a404ab4c92fa4c81dd8449d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Thu, 11 Jul 2019 14:34:23 +0200 Subject: [PATCH 6/6] pin cmd: fix incorect pin type for indirect pins --- core/commands/pin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/commands/pin.go b/core/commands/pin.go index 27092d698..1f77a90d7 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -508,7 +508,7 @@ func pinLsAll(req *cmds.Request, typeStr string, n *core.IpfsNode, emit func(val if r { err := emit(&PinLsOutputWrapper{ PinLsObject: PinLsObject{ - Type: typeStr, + Type: "indirect", Cid: enc.Encode(c), }, })