2020-09-27 19:10:05 +00:00
package main
import (
2020-12-16 21:40:37 +00:00
"bufio"
2020-09-27 19:10:05 +00:00
"encoding/json"
"fmt"
"io"
"log"
"os"
2020-12-16 21:40:37 +00:00
"path/filepath"
"strings"
2020-09-27 19:10:05 +00:00
"github.com/fatih/color"
2020-12-16 21:40:37 +00:00
"github.com/filecoin-project/go-address"
cbornode "github.com/ipfs/go-ipld-cbor"
2020-09-27 19:10:05 +00:00
"github.com/urfave/cli/v2"
"github.com/filecoin-project/test-vectors/schema"
2020-12-16 21:40:37 +00:00
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/conformance"
"github.com/filecoin-project/lotus/lib/blockstore"
2020-09-27 19:10:05 +00:00
)
var execFlags struct {
2020-12-15 17:44:56 +00:00
file string
2020-12-16 21:40:37 +00:00
out string
driverOpts cli . StringSlice
2020-12-15 17:44:56 +00:00
fallbackBlockstore bool
2020-09-27 19:10:05 +00:00
}
2020-12-16 21:40:37 +00:00
const (
optSaveBalances = "save-balances"
)
2020-09-27 19:10:05 +00:00
var execCmd = & cli . Command {
Name : "exec" ,
2020-12-16 21:40:37 +00:00
Description : "execute one or many test vectors against Lotus; supplied as a single JSON file, a directory, or a ndjson stdin stream" ,
Action : runExec ,
2020-09-27 19:10:05 +00:00
Flags : [ ] cli . Flag {
2020-12-16 21:40:37 +00:00
& repoFlag ,
2020-09-27 19:10:05 +00:00
& cli . StringFlag {
Name : "file" ,
2020-12-16 21:40:37 +00:00
Usage : "input file or directory; if not supplied, the vector will be read from stdin" ,
2020-09-27 19:10:05 +00:00
TakesFile : true ,
Destination : & execFlags . file ,
} ,
2020-12-15 17:44:56 +00:00
& cli . BoolFlag {
Name : "fallback-blockstore" ,
Usage : "sets the full node API as a fallback blockstore; use this if you're transplanting vectors and get block not found errors" ,
Destination : & execFlags . fallbackBlockstore ,
} ,
2020-12-16 21:40:37 +00:00
& cli . StringFlag {
Name : "out" ,
Usage : "output directory where to save the results, only used when the input is a directory" ,
Destination : & execFlags . out ,
} ,
& cli . StringSliceFlag {
Name : "driver-opt" ,
Usage : "comma-separated list of driver options (EXPERIMENTAL; will change), supported: 'save-balances=<dst>', 'pipeline-basefee' (unimplemented); only available in single-file mode" ,
Destination : & execFlags . driverOpts ,
} ,
2020-09-27 19:10:05 +00:00
} ,
}
2020-12-16 21:40:37 +00:00
func runExec ( c * cli . Context ) error {
2020-12-15 17:44:56 +00:00
if execFlags . fallbackBlockstore {
if err := initialize ( c ) ; err != nil {
return fmt . Errorf ( "fallback blockstore was enabled, but could not resolve lotus API endpoint: %w" , err )
}
defer destroy ( c ) //nolint:errcheck
conformance . FallbackBlockstoreGetter = FullAPI
}
2020-12-16 21:40:37 +00:00
path := execFlags . file
if path == "" {
return execVectorsStdin ( )
}
fi , err := os . Stat ( path )
if err != nil {
return err
}
if fi . IsDir ( ) {
// we're in directory mode; ensure the out directory exists.
outdir := execFlags . out
if outdir == "" {
return fmt . Errorf ( "no output directory provided" )
}
if err := ensureDir ( outdir ) ; err != nil {
return err
2020-09-27 19:10:05 +00:00
}
2020-12-16 21:40:37 +00:00
return execVectorDir ( path , outdir )
}
2020-09-27 19:10:05 +00:00
2020-12-16 21:40:37 +00:00
// process tipset vector options.
if err := processTipsetOpts ( ) ; err != nil {
return err
}
2020-09-27 19:10:05 +00:00
2020-12-16 21:40:37 +00:00
_ , err = execVectorFile ( new ( conformance . LogReporter ) , path )
return err
}
func processTipsetOpts ( ) error {
for _ , opt := range execFlags . driverOpts . Value ( ) {
switch ss := strings . Split ( opt , "=" ) ; {
case ss [ 0 ] == optSaveBalances :
filename := ss [ 1 ]
log . Printf ( "saving balances after each tipset in: %s" , filename )
balancesFile , err := os . Create ( filename )
if err != nil {
return err
}
w := bufio . NewWriter ( balancesFile )
cb := func ( bs blockstore . Blockstore , params * conformance . ExecuteTipsetParams , res * conformance . ExecuteTipsetResult ) {
cst := cbornode . NewCborStore ( bs )
st , err := state . LoadStateTree ( cst , res . PostStateRoot )
if err != nil {
return
}
_ = st . ForEach ( func ( addr address . Address , actor * types . Actor ) error {
_ , err := fmt . Fprintln ( w , params . ExecEpoch , addr , actor . Balance )
return err
} )
_ = w . Flush ( )
}
conformance . TipsetVectorOpts . OnTipsetApplied = append ( conformance . TipsetVectorOpts . OnTipsetApplied , cb )
}
}
return nil
}
func execVectorDir ( path string , outdir string ) error {
files , err := filepath . Glob ( filepath . Join ( path , "*" ) )
if err != nil {
return fmt . Errorf ( "failed to glob input directory %s: %w" , path , err )
}
for _ , f := range files {
outfile := strings . TrimSuffix ( filepath . Base ( f ) , filepath . Ext ( f ) ) + ".out"
outpath := filepath . Join ( outdir , outfile )
outw , err := os . Create ( outpath )
if err != nil {
return fmt . Errorf ( "failed to create file %s: %w" , outpath , err )
2020-09-27 19:10:05 +00:00
}
2020-12-16 21:40:37 +00:00
log . Printf ( "processing vector %s; sending output to %s" , f , outpath )
log . SetOutput ( io . MultiWriter ( os . Stderr , outw ) ) // tee the output.
_ , _ = execVectorFile ( new ( conformance . LogReporter ) , f )
log . SetOutput ( os . Stderr )
_ = outw . Close ( )
2020-09-27 19:10:05 +00:00
}
2020-12-16 21:40:37 +00:00
return nil
}
2020-09-27 19:10:05 +00:00
2020-12-16 21:40:37 +00:00
func execVectorsStdin ( ) error {
r := new ( conformance . LogReporter )
2020-09-27 19:10:05 +00:00
for dec := json . NewDecoder ( os . Stdin ) ; ; {
var tv schema . TestVector
switch err := dec . Decode ( & tv ) ; err {
case nil :
2020-12-16 21:40:37 +00:00
if _ , err = executeTestVector ( r , tv ) ; err != nil {
2020-09-27 19:10:05 +00:00
return err
}
case io . EOF :
// we're done.
return nil
default :
// something bad happened.
return err
}
}
}
2020-12-16 21:40:37 +00:00
func execVectorFile ( r conformance . Reporter , path string ) ( diffs [ ] string , error error ) {
file , err := os . Open ( path )
if err != nil {
return nil , fmt . Errorf ( "failed to open test vector: %w" , err )
}
var tv schema . TestVector
if err = json . NewDecoder ( file ) . Decode ( & tv ) ; err != nil {
return nil , fmt . Errorf ( "failed to decode test vector: %w" , err )
}
return executeTestVector ( r , tv )
}
func executeTestVector ( r conformance . Reporter , tv schema . TestVector ) ( diffs [ ] string , err error ) {
2020-09-27 19:10:05 +00:00
log . Println ( "executing test vector:" , tv . Meta . ID )
2020-10-13 22:00:01 +00:00
for _ , v := range tv . Pre . Variants {
2020-10-15 11:14:16 +00:00
switch class , v := tv . Class , v ; class {
2020-10-13 22:00:01 +00:00
case "message" :
2020-12-16 21:40:37 +00:00
diffs , err = conformance . ExecuteMessageVector ( r , & tv , & v )
2020-10-13 22:00:01 +00:00
case "tipset" :
2020-12-16 21:40:37 +00:00
diffs , err = conformance . ExecuteTipsetVector ( r , & tv , & v )
2020-10-13 22:00:01 +00:00
default :
2020-12-16 21:40:37 +00:00
return nil , fmt . Errorf ( "test vector class %s not supported" , class )
2020-10-13 22:00:01 +00:00
}
if r . Failed ( ) {
log . Println ( color . HiRedString ( "❌ test vector failed for variant %s" , v . ID ) )
} else {
log . Println ( color . GreenString ( "✅ test vector succeeded for variant %s" , v . ID ) )
}
2020-09-27 19:10:05 +00:00
}
2020-12-16 21:40:37 +00:00
return diffs , err
2020-09-27 19:10:05 +00:00
}