package retrieval import ( "context" "io" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-merkledag" "github.com/ipfs/go-unixfs" pb "github.com/ipfs/go-unixfs/pb" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/build" ) type BlockVerifier interface { Verify(context.Context, blocks.Block, io.Writer) (internal bool, err error) } type OptimisticVerifier struct { } func (o *OptimisticVerifier) Verify(context.Context, blocks.Block, io.Writer) (bool, error) { // It's probably fine return false, nil } type UnixFs0Verifier struct { Root cid.Cid rootBlk blocks.Block expect int seen int sub *UnixFs0Verifier } func (b *UnixFs0Verifier) verify(ctx context.Context, blk blocks.Block, out io.Writer) (last bool, internal bool, err error) { if b.sub != nil { // TODO: check links here (iff b.sub.sub == nil) subLast, internal, err := b.sub.verify(ctx, blk, out) if err != nil { return false, false, err } if subLast { b.sub = nil b.seen++ } return b.seen == b.expect, internal, nil } if b.seen >= b.expect { // this is probably impossible return false, false, xerrors.New("unixfs verifier: too many nodes in level") } links, err := b.checkInternal(blk, out) if err != nil { return false, false, err } if links > 0 { // TODO: check if all links are intermediate (or all aren't) if links > build.UnixfsLinksPerLevel { return false, false, xerrors.New("unixfs verifier: too many links in intermediate node") } if b.seen+1 == b.expect && links != build.UnixfsLinksPerLevel { return false, false, xerrors.New("unixfs verifier: too few nodes in level") } b.sub = &UnixFs0Verifier{ Root: blk.Cid(), rootBlk: blk, expect: links, } // don't mark as seen yet return false, true, nil } b.seen++ return b.seen == b.expect, false, nil } func (b *UnixFs0Verifier) checkInternal(blk blocks.Block, out io.Writer) (int, error) { nd, err := ipld.Decode(blk) if err != nil { log.Warnf("IPLD Decode failed: %s", err) return 0, err } // TODO: check size switch nd := nd.(type) { case *merkledag.ProtoNode: fsn, err := unixfs.FSNodeFromBytes(nd.Data()) if err != nil { log.Warnf("unixfs.FSNodeFromBytes failed: %s", err) return 0, err } if fsn.Type() != pb.Data_File { return 0, xerrors.New("internal nodes must be a file") } if len(fsn.Data()) > 0 { return 0, xerrors.New("internal node with data") } if len(nd.Links()) == 0 { return 0, xerrors.New("internal node with no links") } return len(nd.Links()), nil case *merkledag.RawNode: _, err := out.Write(nd.RawData()) return 0, err default: return 0, xerrors.New("verifier: unknown node type") } } func (b *UnixFs0Verifier) Verify(ctx context.Context, blk blocks.Block, w io.Writer) (bool, error) { // root is special if b.rootBlk == nil { if !b.Root.Equals(blk.Cid()) { return false, xerrors.Errorf("unixfs verifier: root block CID didn't match: valid %s, got %s", b.Root, blk.Cid()) } b.rootBlk = blk links, err := b.checkInternal(blk, w) if err != nil { return false, err } b.expect = links return links != 0, nil } _, internal, err := b.verify(ctx, blk, w) return internal, err } var _ BlockVerifier = &OptimisticVerifier{} var _ BlockVerifier = &UnixFs0Verifier{}