use old go-path

This commit is contained in:
Łukasz Magiera 2022-03-03 14:14:12 +00:00
parent 1bb65189af
commit 7dcfeee0ea
3 changed files with 408 additions and 9 deletions

View File

@ -0,0 +1,198 @@
package oldresolver
import (
"context"
"errors"
"fmt"
"time"
path "github.com/filecoin-project/lotus/lib/oldpath"
cid "github.com/ipfs/go-cid"
ipld "github.com/ipfs/go-ipld-format"
logging "github.com/ipfs/go-log/v2"
dag "github.com/ipfs/go-merkledag"
)
var log = logging.Logger("pathresolv")
// ErrNoComponents is used when Paths after a protocol
// do not contain at least one component
var ErrNoComponents = errors.New(
"path must contain at least one component")
// ErrNoLink is returned when a link is not found in a path
type ErrNoLink struct {
Name string
Node cid.Cid
}
// Error implements the Error interface for ErrNoLink with a useful
// human readable message.
func (e ErrNoLink) Error() string {
return fmt.Sprintf("no link named %q under %s", e.Name, e.Node.String())
}
// ResolveOnce resolves path through a single node
type ResolveOnce func(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, names []string) (*ipld.Link, []string, error)
// Resolver provides path resolution to IPFS
// It has a pointer to a DAGService, which is uses to resolve nodes.
// TODO: now that this is more modular, try to unify this code with the
// the resolvers in namesys
type Resolver struct {
DAG ipld.NodeGetter
ResolveOnce ResolveOnce
}
// NewBasicResolver constructs a new basic resolver.
func NewBasicResolver(ds ipld.DAGService) *Resolver {
return &Resolver{
DAG: ds,
ResolveOnce: ResolveSingle,
}
}
// ResolveToLastNode walks the given path and returns the cid of the last node
// referenced by the path
func (r *Resolver) ResolveToLastNode(ctx context.Context, fpath path.Path) (cid.Cid, []string, error) {
c, p, err := path.SplitAbsPath(fpath)
if err != nil {
return cid.Cid{}, nil, err
}
if len(p) == 0 {
return c, nil, nil
}
nd, err := r.DAG.Get(ctx, c)
if err != nil {
return cid.Cid{}, nil, err
}
for len(p) > 0 {
lnk, rest, err := r.ResolveOnce(ctx, r.DAG, nd, p)
// Note: have to drop the error here as `ResolveOnce` doesn't handle 'leaf'
// paths (so e.g. for `echo '{"foo":123}' | ipfs dag put` we wouldn't be
// able to resolve `zdpu[...]/foo`)
if lnk == nil {
break
}
if err != nil {
if err == dag.ErrLinkNotFound {
err = ErrNoLink{Name: p[0], Node: nd.Cid()}
}
return cid.Cid{}, nil, err
}
next, err := lnk.GetNode(ctx, r.DAG)
if err != nil {
return cid.Cid{}, nil, err
}
nd = next
p = rest
}
if len(p) == 0 {
return nd.Cid(), nil, nil
}
// Confirm the path exists within the object
val, rest, err := nd.Resolve(p)
if err != nil {
if err == dag.ErrLinkNotFound {
err = ErrNoLink{Name: p[0], Node: nd.Cid()}
}
return cid.Cid{}, nil, err
}
if len(rest) > 0 {
return cid.Cid{}, nil, errors.New("path failed to resolve fully")
}
switch val.(type) {
case *ipld.Link:
return cid.Cid{}, nil, errors.New("inconsistent ResolveOnce / nd.Resolve")
default:
return nd.Cid(), p, nil
}
}
// ResolvePath fetches the node for given path. It returns the last item
// returned by ResolvePathComponents.
func (r *Resolver) ResolvePath(ctx context.Context, fpath path.Path) (ipld.Node, error) {
// validate path
if err := fpath.IsValid(); err != nil {
return nil, err
}
nodes, err := r.ResolvePathComponents(ctx, fpath)
if err != nil || nodes == nil {
return nil, err
}
return nodes[len(nodes)-1], err
}
// ResolveSingle simply resolves one hop of a path through a graph with no
// extra context (does not opaquely resolve through sharded nodes)
func ResolveSingle(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, names []string) (*ipld.Link, []string, error) {
return nd.ResolveLink(names)
}
// ResolvePathComponents fetches the nodes for each segment of the given path.
// It uses the first path component as a hash (key) of the first node, then
// resolves all other components walking the links, with ResolveLinks.
func (r *Resolver) ResolvePathComponents(ctx context.Context, fpath path.Path) ([]ipld.Node, error) {
h, parts, err := path.SplitAbsPath(fpath)
if err != nil {
return nil, err
}
log.Debug("resolve dag get")
nd, err := r.DAG.Get(ctx, h)
if err != nil {
return nil, err
}
return r.ResolveLinks(ctx, nd, parts)
}
// ResolveLinks iteratively resolves names by walking the link hierarchy.
// Every node is fetched from the DAGService, resolving the next name.
// Returns the list of nodes forming the path, starting with ndd. This list is
// guaranteed never to be empty.
//
// ResolveLinks(nd, []string{"foo", "bar", "baz"})
// would retrieve "baz" in ("bar" in ("foo" in nd.Links).Links).Links
func (r *Resolver) ResolveLinks(ctx context.Context, ndd ipld.Node, names []string) ([]ipld.Node, error) {
result := make([]ipld.Node, 0, len(names)+1)
result = append(result, ndd)
nd := ndd // dup arg workaround
// for each of the path components
for len(names) > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Minute)
defer cancel()
lnk, rest, err := r.ResolveOnce(ctx, r.DAG, nd, names)
if err == dag.ErrLinkNotFound {
return result, ErrNoLink{Name: names[0], Node: nd.Cid()}
} else if err != nil {
return result, err
}
nextnode, err := lnk.GetNode(ctx, r.DAG)
if err != nil {
return result, err
}
nd = nextnode
result = append(result, nextnode)
names = rest
}
return result, nil
}

199
lib/oldpath/path.go Normal file
View File

@ -0,0 +1,199 @@
package oldpath
import (
"fmt"
"path"
"strings"
cid "github.com/ipfs/go-cid"
)
// helper type so path parsing errors include the path
type pathError struct {
error error
path string
}
func (e *pathError) Error() string {
return fmt.Sprintf("invalid path %q: %s", e.path, e.error)
}
func (e *pathError) Unwrap() error {
return e.error
}
func (e *pathError) Path() string {
return e.path
}
// A Path represents an ipfs content path:
// * /<cid>/path/to/file
// * /ipfs/<cid>
// * /ipns/<cid>/path/to/folder
// * etc
type Path string
// ^^^
// TODO: debate making this a private struct wrapped in a public interface
// would allow us to control creation, and cache segments.
// FromString safely converts a string type to a Path type.
func FromString(s string) Path {
return Path(s)
}
// FromCid safely converts a cid.Cid type to a Path type.
func FromCid(c cid.Cid) Path {
return Path("/ipfs/" + c.String())
}
// Segments returns the different elements of a path
// (elements are delimited by a /).
func (p Path) Segments() []string {
cleaned := path.Clean(string(p))
segments := strings.Split(cleaned, "/")
// Ignore leading slash
if len(segments[0]) == 0 {
segments = segments[1:]
}
return segments
}
// String converts a path to string.
func (p Path) String() string {
return string(p)
}
// IsJustAKey returns true if the path is of the form <key> or /ipfs/<key>, or
// /ipld/<key>
func (p Path) IsJustAKey() bool {
parts := p.Segments()
return len(parts) == 2 && (parts[0] == "ipfs" || parts[0] == "ipld")
}
// PopLastSegment returns a new Path without its final segment, and the final
// segment, separately. If there is no more to pop (the path is just a key),
// the original path is returned.
func (p Path) PopLastSegment() (Path, string, error) {
if p.IsJustAKey() {
return p, "", nil
}
segs := p.Segments()
newPath, err := ParsePath("/" + strings.Join(segs[:len(segs)-1], "/"))
if err != nil {
return "", "", err
}
return newPath, segs[len(segs)-1], nil
}
// FromSegments returns a path given its different segments.
func FromSegments(prefix string, seg ...string) (Path, error) {
return ParsePath(prefix + strings.Join(seg, "/"))
}
// ParsePath returns a well-formed ipfs Path.
// The returned path will always be prefixed with /ipfs/ or /ipns/.
// The prefix will be added if not present in the given string.
// This function will return an error when the given string is
// not a valid ipfs path.
func ParsePath(txt string) (Path, error) {
parts := strings.Split(txt, "/")
if len(parts) == 1 {
kp, err := ParseCidToPath(txt)
if err == nil {
return kp, nil
}
}
// if the path doesnt begin with a '/'
// we expect this to start with a hash, and be an 'ipfs' path
if parts[0] != "" {
if _, err := cid.Decode(parts[0]); err != nil {
return "", &pathError{error: err, path: txt}
}
// The case when the path starts with hash without a protocol prefix
return Path("/ipfs/" + txt), nil
}
if len(parts) < 3 {
return "", &pathError{error: fmt.Errorf("path does not begin with '/'"), path: txt}
}
//TODO: make this smarter
switch parts[1] {
case "ipfs", "ipld":
if parts[2] == "" {
return "", &pathError{error: fmt.Errorf("not enough path components"), path: txt}
}
// Validate Cid.
_, err := cid.Decode(parts[2])
if err != nil {
return "", &pathError{error: fmt.Errorf("invalid CID: %s", err), path: txt}
}
case "ipns":
if parts[2] == "" {
return "", &pathError{error: fmt.Errorf("not enough path components"), path: txt}
}
default:
return "", &pathError{error: fmt.Errorf("unknown namespace %q", parts[1]), path: txt}
}
return Path(txt), nil
}
// ParseCidToPath takes a CID in string form and returns a valid ipfs Path.
func ParseCidToPath(txt string) (Path, error) {
if txt == "" {
return "", &pathError{error: fmt.Errorf("empty"), path: txt}
}
c, err := cid.Decode(txt)
if err != nil {
return "", &pathError{error: err, path: txt}
}
return FromCid(c), nil
}
// IsValid checks if a path is a valid ipfs Path.
func (p *Path) IsValid() error {
_, err := ParsePath(p.String())
return err
}
// Join joins strings slices using /
func Join(pths []string) string {
return strings.Join(pths, "/")
}
// SplitList splits strings usings /
func SplitList(pth string) []string {
return strings.Split(pth, "/")
}
// SplitAbsPath clean up and split fpath. It extracts the first component (which
// must be a Multihash) and return it separately.
func SplitAbsPath(fpath Path) (cid.Cid, []string, error) {
parts := fpath.Segments()
if parts[0] == "ipfs" || parts[0] == "ipld" {
parts = parts[1:]
}
// if nothing, bail.
if len(parts) == 0 {
return cid.Cid{}, nil, &pathError{error: fmt.Errorf("empty"), path: string(fpath)}
}
c, err := cid.Decode(parts[0])
// first element in the path is a cid
if err != nil {
return cid.Cid{}, nil, &pathError{error: fmt.Errorf("invalid CID: %s", err), path: string(fpath)}
}
return c, parts[1:], nil
}

View File

@ -17,15 +17,11 @@ import (
"github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
bsfetcher "github.com/ipfs/go-fetcher/impl/blockservice"
offline "github.com/ipfs/go-ipfs-exchange-offline"
cbor "github.com/ipfs/go-ipld-cbor"
ipld "github.com/ipfs/go-ipld-format"
logging "github.com/ipfs/go-log/v2"
"github.com/ipfs/go-merkledag"
"github.com/ipfs/go-path"
"github.com/ipfs/go-path/resolver"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
mh "github.com/multiformats/go-multihash"
cbg "github.com/whyrusleeping/cbor-gen"
@ -38,6 +34,8 @@ import (
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/lib/oldpath"
"github.com/filecoin-project/lotus/lib/oldpath/oldresolver"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
@ -552,23 +550,27 @@ func resolveOnce(bs blockstore.Blockstore, tse stmgr.Executor) func(ctx context.
}
func (a *ChainAPI) ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) {
ip, err := path.ParsePath(p)
ip, err := oldpath.ParsePath(p)
if err != nil {
return nil, xerrors.Errorf("parsing path: %w", err)
}
bs := a.ExposedBlockstore
bsvc := blockservice.New(bs, offline.Exchange(bs))
fc := bsfetcher.NewFetcherConfig(bsvc)
r := resolver.NewBasicResolver(fc)
dag := merkledag.NewDAGService(bsvc)
node, lnk, err := r.ResolvePath(ctx, ip)
r := &oldresolver.Resolver{
DAG: dag,
ResolveOnce: resolveOnce(bs, a.TsExec),
}
node, err := r.ResolvePath(ctx, ip)
if err != nil {
return nil, err
}
return &api.IpldObject{
Cid: lnk.(cidlink.Link).Cid,
Cid: node.Cid(),
Obj: node,
}, nil
}