262 lines
7.5 KiB
Go
262 lines
7.5 KiB
Go
package util
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tutus-one/tutus-chain/cli/cmdargs"
|
|
"github.com/tutus-one/tutus-chain/cli/options"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/block"
|
|
"github.com/tutus-one/tutus-chain/pkg/io"
|
|
"github.com/tutus-one/tutus-chain/pkg/rpcclient"
|
|
"github.com/tutus-one/tutus-chain/pkg/wallet"
|
|
"github.com/nspcc-dev/neofs-sdk-go/client"
|
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
|
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
|
"github.com/nspcc-dev/neofs-sdk-go/pool"
|
|
"github.com/nspcc-dev/neofs-sdk-go/user"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
func auditBin(ctx *cli.Context) error {
|
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
|
return err
|
|
}
|
|
var (
|
|
numWorkers = ctx.Uint("workers")
|
|
|
|
err error
|
|
errs = make(chan error, numWorkers)
|
|
haveErrors bool
|
|
tasks = make(chan func() error)
|
|
wg sync.WaitGroup
|
|
)
|
|
for range numWorkers {
|
|
wg.Add(1)
|
|
go func() {
|
|
for f := range tasks {
|
|
err := f()
|
|
if err != nil {
|
|
errs <- err
|
|
break
|
|
}
|
|
}
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
err = auditBinInt(ctx, tasks, errs)
|
|
close(tasks)
|
|
wg.Wait()
|
|
|
|
drainErrors:
|
|
for {
|
|
select {
|
|
case anotherErr := <-errs:
|
|
fmt.Fprintf(ctx.App.Writer, "error in worker thread: %s\n", anotherErr)
|
|
haveErrors = true
|
|
default:
|
|
break drainErrors
|
|
}
|
|
}
|
|
if err == nil {
|
|
if haveErrors {
|
|
err = cli.Exit(errors.New("audit failed"), 1) // Change return code to signal thread errors.
|
|
} else {
|
|
fmt.Fprintln(ctx.App.Writer, "Audit is completed.")
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func auditBinInt(ctx *cli.Context, tasks chan func() error, errs chan error) error {
|
|
retries := ctx.Uint("retries")
|
|
cnrID := ctx.String("container")
|
|
debug := ctx.Bool("debug")
|
|
dryRun := ctx.Bool("dry-run")
|
|
blockAttr := ctx.String("block-attribute")
|
|
curH := uint64(ctx.Uint("skip"))
|
|
|
|
acc, _, err := options.GetAccFromContext(ctx)
|
|
if err != nil {
|
|
if errors.Is(err, options.ErrNoWallet) {
|
|
acc, err = wallet.NewAccount()
|
|
if err != nil {
|
|
return cli.Exit(fmt.Errorf("no wallet provided and failed to create account for NeoFS interaction: %w", err), 1)
|
|
}
|
|
} else {
|
|
return cli.Exit(fmt.Errorf("failed to load account: %w", err), 1)
|
|
}
|
|
}
|
|
signer, neoFSPool, err := options.GetNeoFSClientPool(ctx, acc)
|
|
if err != nil {
|
|
return cli.Exit(err, 1)
|
|
}
|
|
defer neoFSPool.Close()
|
|
|
|
var containerID cid.ID
|
|
if err = containerID.DecodeString(cnrID); err != nil {
|
|
return cli.Exit(fmt.Errorf("failed to decode container ID: %w", err), 1)
|
|
}
|
|
if _, err = neoFSPool.ContainerGet(ctx.Context, containerID, client.PrmContainerGet{}); err != nil {
|
|
return cli.Exit(fmt.Errorf("failed to get container %s: %w", containerID, err), 1)
|
|
}
|
|
|
|
if curH != 0 {
|
|
fmt.Fprintf(ctx.App.Writer, "Skipping first %d blocks\n", curH)
|
|
}
|
|
|
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
|
defer cancel()
|
|
rpc, err := options.GetRPCClient(gctx, ctx)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Errorf("failed to create RPC client: %w", err), 1)
|
|
}
|
|
|
|
var (
|
|
prevH uint64
|
|
cursor string
|
|
curOID oid.ID
|
|
f = object.NewSearchFilters()
|
|
)
|
|
f.AddFilter(blockAttr, strconv.FormatUint(curH, 10), object.MatchNumGE)
|
|
|
|
for {
|
|
var page []client.SearchResultItem
|
|
err = retry(func() error {
|
|
page, cursor, err = neoFSPool.SearchObjects(ctx.Context, containerID, f, []string{blockAttr}, cursor, signer, client.SearchObjectsOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to search objects: %w", err)
|
|
}
|
|
return nil
|
|
}, retries, debug)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Errorf("search block objects: %w", err), 1)
|
|
}
|
|
|
|
for _, itm := range page {
|
|
select {
|
|
case <-ctx.Done():
|
|
return cli.Exit("context cancelled", 1)
|
|
case err := <-errs:
|
|
return cli.Exit(fmt.Errorf("error in worker thread: %w", err), 1)
|
|
default:
|
|
}
|
|
if len(itm.Attributes) != 1 {
|
|
fmt.Fprintf(ctx.App.Writer, "invalid number attributes for %s: expected %d, got %d", itm.ID, 1, len(itm.Attributes))
|
|
continue
|
|
}
|
|
h, err := strconv.ParseUint(itm.Attributes[0], 10, 64)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Errorf("failed to parse block OID (%s): %w", itm.ID, err), 1)
|
|
}
|
|
|
|
if !curOID.IsZero() && prevH == h {
|
|
if dryRun {
|
|
fmt.Fprintf(ctx.App.Writer, "[dry-run] block duplicate %s / %s (%d)\n", itm.ID, curOID, prevH)
|
|
} else {
|
|
tasks <- wrapDropBlock(ctx, neoFSPool, signer, containerID, itm.ID, retries, prevH, curOID, debug)
|
|
}
|
|
continue
|
|
}
|
|
|
|
for ; curH < h; curH++ {
|
|
if dryRun {
|
|
fmt.Fprintf(ctx.App.Writer, "[dry-run] block %d is missing\n", curH)
|
|
} else {
|
|
tasks <- wrapRestoreMissingBlock(ctx, rpc, neoFSPool, signer, containerID, blockAttr, retries, curH, debug)
|
|
}
|
|
}
|
|
curOID = itm.ID
|
|
prevH = curH
|
|
curH++
|
|
}
|
|
if cursor == "" {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func wrapDropBlock(ctx *cli.Context, p *pool.Pool, signer user.Signer, containerID cid.ID, objID oid.ID, retries uint, prevH uint64, curOID oid.ID, debug bool) func() error {
|
|
return func() error {
|
|
return dropBlock(ctx, p, signer, containerID, objID, retries, prevH, curOID, debug)
|
|
}
|
|
}
|
|
|
|
func dropBlock(ctx *cli.Context, p *pool.Pool, signer user.Signer, containerID cid.ID, objID oid.ID, retries uint, prevH uint64, curOID oid.ID, debug bool) error {
|
|
err := retry(func() error {
|
|
_, e := p.ObjectDelete(ctx.Context, containerID, objID, signer, client.PrmObjectDelete{})
|
|
return e
|
|
}, retries, debug)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to remove block duplicate %s / %s (%d): %w", objID, curOID, prevH, err)
|
|
} else if debug {
|
|
fmt.Fprintf(ctx.App.Writer, "block duplicate %s for %d is removed (%s kept)\n", objID, prevH, curOID)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func wrapRestoreMissingBlock(ctx *cli.Context, rpc *rpcclient.Client, p *pool.Pool, signer user.Signer, containerID cid.ID,
|
|
blockAttr string, retries uint, index uint64, debug bool) func() error {
|
|
return func() error {
|
|
return restoreMissingBlock(ctx, rpc, p, signer, containerID, blockAttr, retries, index, debug)
|
|
}
|
|
}
|
|
func restoreMissingBlock(ctx *cli.Context, rpc *rpcclient.Client, p *pool.Pool, signer user.Signer, containerID cid.ID,
|
|
blockAttr string, retries uint, index uint64, debug bool) error {
|
|
var (
|
|
b *block.Block
|
|
err error
|
|
)
|
|
err = retry(func() error {
|
|
b, err = rpc.GetBlockByIndex(uint32(index))
|
|
return err
|
|
}, retries, debug)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch block %d: %w", index, err)
|
|
}
|
|
|
|
bw := io.NewBufBinWriter()
|
|
b.EncodeBinary(bw.BinWriter)
|
|
if bw.Err != nil {
|
|
return fmt.Errorf("failed to encode block %d: %w", index, bw.Err)
|
|
}
|
|
|
|
_, err = createBlockAndUpload(ctx, p, signer, containerID, b, bw, blockAttr, retries, index, debug)
|
|
return err
|
|
}
|
|
|
|
func createBlockAndUpload(ctx *cli.Context, p *pool.Pool, signer user.Signer, containerID cid.ID, b *block.Block,
|
|
bw *io.BufBinWriter, blockAttr string, retries uint, index uint64, debug bool) (oid.ID, error) {
|
|
attrs := []object.Attribute{
|
|
object.NewAttribute(blockAttr, strconv.FormatUint(uint64(b.Index), 10)),
|
|
object.NewAttribute("Hash", b.Hash().StringLE()),
|
|
object.NewAttribute("PrevHash", b.PrevHash.StringLE()),
|
|
object.NewAttribute("BlockTime", strconv.FormatUint(b.Timestamp, 10)),
|
|
object.NewAttribute("Timestamp", strconv.FormatInt(time.Now().Unix(), 10)),
|
|
}
|
|
|
|
var (
|
|
objBytes = bw.Bytes()
|
|
OID oid.ID
|
|
)
|
|
err := retry(func() error {
|
|
var e error
|
|
OID, e = uploadObj(ctx.Context, p, signer, containerID, objBytes, attrs)
|
|
return e
|
|
}, retries, debug)
|
|
if err != nil {
|
|
return oid.ID{}, fmt.Errorf("failed to upload block %d: %w", index, err)
|
|
}
|
|
if debug {
|
|
fmt.Fprintf(ctx.App.Writer, "block %d is uploaded: %s\n", index, OID)
|
|
}
|
|
return OID, nil
|
|
}
|