storj/pkg/audit/verifier.go

268 lines
7.5 KiB
Go
Raw Normal View History

2019-01-24 20:15:10 +00:00
// Copyright (C) 2019 Storj Labs, Inc.
2018-10-09 22:10:37 +01:00
// See LICENSE for copying information.
package audit
import (
"bytes"
"context"
"io"
"github.com/vivint/infectious"
2019-01-29 20:42:27 +00:00
"github.com/zeebo/errs"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
2018-10-09 22:10:37 +01:00
2019-01-30 20:47:21 +00:00
"storj.io/storj/pkg/identity"
2018-10-09 22:10:37 +01:00
"storj.io/storj/pkg/overlay"
"storj.io/storj/pkg/pb"
"storj.io/storj/pkg/piecestore/psclient"
"storj.io/storj/pkg/storj"
2018-10-09 22:10:37 +01:00
"storj.io/storj/pkg/transport"
)
var mon = monkit.Package()
2019-01-23 19:58:44 +00:00
// Share represents required information about an audited share
type Share struct {
2018-10-09 22:10:37 +01:00
Error error
PieceNumber int
Data []byte
}
// Verifier helps verify the correctness of a given stripe
type Verifier struct {
2018-10-09 22:10:37 +01:00
downloader downloader
}
type downloader interface {
DownloadShares(ctx context.Context, pointer *pb.Pointer, stripeIndex int, pba *pb.OrderLimit) (shares map[int]Share, nodes map[int]storj.NodeID, err error)
2018-10-09 22:10:37 +01:00
}
// defaultDownloader downloads shares from networked storage nodes
2018-10-09 22:10:37 +01:00
type defaultDownloader struct {
transport transport.Client
2019-01-23 19:58:44 +00:00
overlay *overlay.Cache
2019-01-30 20:47:21 +00:00
identity *identity.FullIdentity
reporter
2018-10-09 22:10:37 +01:00
}
// newDefaultDownloader creates a defaultDownloader
2019-01-30 20:47:21 +00:00
func newDefaultDownloader(transport transport.Client, overlay *overlay.Cache, id *identity.FullIdentity) *defaultDownloader {
return &defaultDownloader{transport: transport, overlay: overlay, identity: id}
2018-10-09 22:10:37 +01:00
}
// NewVerifier creates a Verifier
2019-01-30 20:47:21 +00:00
func NewVerifier(transport transport.Client, overlay *overlay.Cache, id *identity.FullIdentity) *Verifier {
return &Verifier{downloader: newDefaultDownloader(transport, overlay, id)}
2018-10-09 22:10:37 +01:00
}
// getShare use piece store clients to download shares from a given node
func (d *defaultDownloader) getShare(ctx context.Context, stripeIndex, shareSize, pieceNumber int,
id psclient.PieceID, pieceSize int64, fromNode *pb.Node, pba *pb.OrderLimit) (s Share, err error) {
2019-01-23 19:58:44 +00:00
// TODO: too many arguments use a struct
2018-10-09 22:10:37 +01:00
defer mon.Task()(&ctx)(&err)
if fromNode == nil {
// TODO(moby) perhaps we should not penalize this node's reputation if it is not returned by the overlay
return s, Error.New("no node returned from overlay for piece %s", id.String())
}
fromNode.Type.DPanicOnInvalid("audit getShare")
// TODO(nat): the reason for dividing by 8 is because later in psclient/readerwriter.go
// the bandwidthMsgSize is arbitrarily multiplied by 8 as a reasonable threshold
// for message trust size drift. The 8 should eventually be a config value.
var bandwidthMsgSize int
remainder := shareSize % 8
if remainder == 0 {
bandwidthMsgSize = shareSize / 8
} else {
bandwidthMsgSize = (shareSize + 8 - remainder) / 8
}
ps, err := psclient.NewPSClient(ctx, d.transport, fromNode, bandwidthMsgSize)
2018-10-09 22:10:37 +01:00
if err != nil {
return s, err
}
derivedPieceID, err := id.Derive(fromNode.Id.Bytes())
2018-10-09 22:10:37 +01:00
if err != nil {
return s, err
}
rr, err := ps.Get(ctx, derivedPieceID, pieceSize, pba)
2018-10-09 22:10:37 +01:00
if err != nil {
return s, err
}
offset := shareSize * stripeIndex
rc, err := rr.Range(ctx, int64(offset), int64(shareSize))
if err != nil {
return s, err
}
2019-01-29 20:42:27 +00:00
defer func() { err = errs.Combine(err, rc.Close()) }()
2018-10-09 22:10:37 +01:00
buf := make([]byte, shareSize)
_, err = io.ReadFull(rc, buf)
if err != nil {
return s, err
}
2019-01-23 19:58:44 +00:00
s = Share{
2018-10-09 22:10:37 +01:00
Error: nil,
PieceNumber: pieceNumber,
Data: buf,
}
return s, nil
}
// Download Shares downloads shares from the nodes where remote pieces are located
func (d *defaultDownloader) DownloadShares(ctx context.Context, pointer *pb.Pointer,
stripeIndex int, pba *pb.OrderLimit) (shares map[int]Share, nodes map[int]storj.NodeID, err error) {
2018-10-09 22:10:37 +01:00
defer mon.Task()(&ctx)(&err)
2018-11-28 07:33:17 +00:00
var nodeIds storj.NodeIDList
2018-10-09 22:10:37 +01:00
pieces := pointer.Remote.GetRemotePieces()
for _, p := range pieces {
nodeIds = append(nodeIds, p.NodeId)
2018-10-09 22:10:37 +01:00
}
2018-11-28 07:33:17 +00:00
// TODO(moby) nodeSlice will not include offline nodes, so overlay should update uptime for these nodes
2019-01-23 19:58:44 +00:00
nodeSlice, err := d.overlay.GetAll(ctx, nodeIds)
2018-10-09 22:10:37 +01:00
if err != nil {
return nil, nodes, err
}
2019-01-23 19:58:44 +00:00
shares = make(map[int]Share, len(nodeSlice))
2019-02-01 14:48:57 +00:00
nodes = make(map[int]storj.NodeID, len(nodeSlice))
2018-11-28 07:33:17 +00:00
2018-10-09 22:10:37 +01:00
shareSize := int(pointer.Remote.Redundancy.GetErasureShareSize())
pieceID := psclient.PieceID(pointer.Remote.GetPieceId())
2018-10-09 22:10:37 +01:00
// this downloads shares from nodes at the given stripe index
2018-11-28 07:33:17 +00:00
for i, node := range nodeSlice {
paddedSize := calcPadded(pointer.GetSegmentSize(), shareSize)
2018-10-09 22:10:37 +01:00
pieceSize := paddedSize / int64(pointer.Remote.Redundancy.GetMinReq())
s, err := d.getShare(ctx, stripeIndex, shareSize, int(pieces[i].PieceNum), pieceID, pieceSize, node, pba)
2018-10-09 22:10:37 +01:00
if err != nil {
2019-01-23 19:58:44 +00:00
s = Share{
2018-10-09 22:10:37 +01:00
Error: err,
2018-11-07 01:16:43 +00:00
PieceNumber: int(pieces[i].PieceNum),
2018-10-09 22:10:37 +01:00
Data: nil,
}
}
2018-11-28 07:33:17 +00:00
shares[s.PieceNumber] = s
2019-02-01 14:48:57 +00:00
nodes[s.PieceNumber] = nodeIds[i]
2018-10-09 22:10:37 +01:00
}
2018-10-09 22:10:37 +01:00
return shares, nodes, nil
}
2019-01-23 19:58:44 +00:00
func makeCopies(ctx context.Context, originals map[int]Share) (copies []infectious.Share, err error) {
2018-10-09 22:10:37 +01:00
defer mon.Task()(&ctx)(&err)
2018-11-07 01:16:43 +00:00
copies = make([]infectious.Share, 0, len(originals))
for _, original := range originals {
2018-10-09 22:10:37 +01:00
if original.Error != nil {
continue
}
2018-11-07 01:16:43 +00:00
copies = append(copies, infectious.Share{
Data: append([]byte{}, original.Data...),
Number: original.PieceNumber})
2018-10-09 22:10:37 +01:00
}
return copies, nil
}
// auditShares takes the downloaded shares and uses infectious's Correct function to check that they
// haven't been altered. auditShares returns a slice containing the piece numbers of altered shares.
2019-01-23 19:58:44 +00:00
func auditShares(ctx context.Context, required, total int, originals map[int]Share) (pieceNums []int, err error) {
2018-10-09 22:10:37 +01:00
defer mon.Task()(&ctx)(&err)
f, err := infectious.NewFEC(required, total)
if err != nil {
return nil, err
}
2018-11-07 01:16:43 +00:00
2018-10-09 22:10:37 +01:00
copies, err := makeCopies(ctx, originals)
if err != nil {
return nil, err
}
err = f.Correct(copies)
if err != nil {
return nil, err
}
2018-11-28 07:33:17 +00:00
for _, share := range copies {
if !bytes.Equal(originals[share.Number].Data, share.Data) {
2018-10-09 22:10:37 +01:00
pieceNums = append(pieceNums, share.Number)
}
}
return pieceNums, nil
}
func calcPadded(size int64, blockSize int) int64 {
mod := size % int64(blockSize)
if mod == 0 {
return size
}
return size + int64(blockSize) - mod
}
// verify downloads shares then verifies the data correctness at the given stripe
func (verifier *Verifier) verify(ctx context.Context, stripe *Stripe) (verifiedNodes *RecordAuditsInfo, err error) {
2018-10-09 22:10:37 +01:00
defer mon.Task()(&ctx)(&err)
shares, nodes, err := verifier.downloader.DownloadShares(ctx, stripe.Segment, stripe.Index, stripe.PBA)
2018-10-09 22:10:37 +01:00
if err != nil {
return nil, err
}
var offlineNodes storj.NodeIDList
2018-11-28 07:33:17 +00:00
for pieceNum := range shares {
if shares[pieceNum].Error != nil {
2019-02-01 14:48:57 +00:00
offlineNodes = append(offlineNodes, nodes[pieceNum])
}
}
pointer := stripe.Segment
2018-10-09 22:10:37 +01:00
required := int(pointer.Remote.Redundancy.GetMinReq())
total := int(pointer.Remote.Redundancy.GetTotal())
pieceNums, err := auditShares(ctx, required, total, shares)
if err != nil {
return nil, err
}
var failedNodes storj.NodeIDList
2018-10-09 22:10:37 +01:00
for _, pieceNum := range pieceNums {
2019-02-01 14:48:57 +00:00
failedNodes = append(failedNodes, nodes[pieceNum])
}
successNodes := getSuccessNodes(ctx, nodes, failedNodes, offlineNodes)
return &RecordAuditsInfo{
SuccessNodeIDs: successNodes,
FailNodeIDs: failedNodes,
OfflineNodeIDs: offlineNodes,
}, nil
}
// getSuccessNodes uses the failed nodes and offline nodes arrays to determine which nodes passed the audit
2019-02-01 14:48:57 +00:00
func getSuccessNodes(ctx context.Context, nodes map[int]storj.NodeID, failedNodes, offlineNodes storj.NodeIDList) (successNodes storj.NodeIDList) {
fails := make(map[storj.NodeID]bool)
for _, fail := range failedNodes {
fails[fail] = true
2018-10-09 22:10:37 +01:00
}
for _, offline := range offlineNodes {
fails[offline] = true
}
for _, node := range nodes {
2019-02-01 14:48:57 +00:00
if !fails[node] {
successNodes = append(successNodes, node)
}
}
2019-02-01 14:48:57 +00:00
return successNodes
}