lotus/retrieval/verify.go
2019-08-29 17:50:19 +02:00

141 lines
3.2 KiB
Go

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/go-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 {
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 {
return 0, err
}
// TODO: check size
switch nd.(type) {
case *merkledag.ProtoNode:
fsn, err := unixfs.FSNodeFromBytes(nd.RawData())
if err != nil {
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:
// TODO: do we check the hash before writing?
_, 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{}