Commit f08f9eb1 authored by protolambda's avatar protolambda

cmd: step command

parent 0cb0a00d
package cmd
import "github.com/urfave/cli/v2"
func GenProof(ctx *cli.Context) error {
// TODO
return nil
}
var GenProofCommand = &cli.Command{
Name: "gen-proof",
Usage: "",
Description: "",
Action: GenProof,
Flags: nil,
}
package cmd
import (
"encoding/json"
"fmt"
"io"
"os"
)
func loadJSON[X any](inputPath string) (*X, error) {
f, err := os.OpenFile(inputPath, os.O_RDONLY, 0)
if err != nil {
return nil, fmt.Errorf("failed to open file %q: %w", inputPath, err)
}
defer f.Close()
var state X
if err := json.NewDecoder(f).Decode(&state); err != nil {
return nil, fmt.Errorf("failed to decode file %q: %w", inputPath, err)
}
return &state, nil
}
func writeJSON[X any](outputPath string, value X, outIfEmpty bool) error {
var out io.Writer
if outputPath != "" {
f, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return fmt.Errorf("failed to open output file: %w", err)
}
defer f.Close()
out = f
} else if outIfEmpty {
out = os.Stdout
} else {
return nil
}
enc := json.NewEncoder(out)
if err := enc.Encode(value); err != nil {
return fmt.Errorf("failed to encode to JSON: %w", err)
}
_, err := out.Write([]byte{'\n'})
if err != nil {
return fmt.Errorf("failed to append new-line: %w", err)
}
return nil
}
...@@ -2,10 +2,7 @@ package cmd ...@@ -2,10 +2,7 @@ package cmd
import ( import (
"debug/elf" "debug/elf"
"encoding/json"
"fmt" "fmt"
"io"
"os"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -56,23 +53,7 @@ func LoadELF(ctx *cli.Context) error { ...@@ -56,23 +53,7 @@ func LoadELF(ctx *cli.Context) error {
return fmt.Errorf("failed to apply patch %s: %w", typ, err) return fmt.Errorf("failed to apply patch %s: %w", typ, err)
} }
} }
p := ctx.Path(LoadELFOutFlag.Name) return writeJSON[*mipsevm.State](ctx.Path(LoadELFOutFlag.Name), state, true)
var out io.Writer
if p != "" {
f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return fmt.Errorf("failed to open output file: %w", err)
}
defer f.Close()
out = f
} else {
out = os.Stdout
}
enc := json.NewEncoder(out)
if err := enc.Encode(state); err != nil {
return fmt.Errorf("failed to encode state to JSON: %w", err)
}
return nil
} }
var LoadELFCommand = &cli.Command{ var LoadELFCommand = &cli.Command{
......
package cmd
import (
"io"
"github.com/ethereum/go-ethereum/log"
)
func Logger(w io.Writer, lvl log.Lvl) log.Logger {
h := log.StreamHandler(w, log.LogfmtFormat())
h = log.SyncHandler(h)
h = log.LvlFilterHandler(lvl, h)
l := log.New()
l.SetHandler(h)
return l
}
package cmd
import (
"fmt"
"strconv"
"strings"
"cannon/mipsevm"
)
type StepMatcher func(st *mipsevm.State) bool
type StepMatcherFlag struct {
repr string
matcher StepMatcher
}
func (m *StepMatcherFlag) Set(value string) error {
m.repr = value
if value == "" || value == "never" {
m.matcher = func(st *mipsevm.State) bool {
return false
}
} else if value == "always" {
m.matcher = func(st *mipsevm.State) bool {
return true
}
} else if strings.HasPrefix(value, "=") {
when, err := strconv.ParseUint(value[1:], 0, 64)
if err != nil {
return fmt.Errorf("failed to parse step number: %w", err)
}
m.matcher = func(st *mipsevm.State) bool {
return st.Step == when
}
} else if strings.HasPrefix(value, "%") {
when, err := strconv.ParseUint(value[1:], 0, 64)
if err != nil {
return fmt.Errorf("failed to parse step interval number: %w", err)
}
m.matcher = func(st *mipsevm.State) bool {
return st.Step%when == 0
}
} else {
return fmt.Errorf("unrecognized step matcher: %q", value)
}
return nil
}
func (m *StepMatcherFlag) String() string {
return m.repr
}
func (m *StepMatcherFlag) Matcher() StepMatcher {
if m.matcher == nil { // Set(value) is not called for omitted inputs, default to never matching.
return func(st *mipsevm.State) bool {
return false
}
}
return m.matcher
}
package cmd
import (
"fmt"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
"cannon/mipsevm"
)
var (
RunInputFlag = &cli.PathFlag{
Name: "input",
Usage: "path of input JSON state. Stdin if left empty.",
TakesFile: true,
Value: "state.json",
Required: true,
}
RunOutputFlag = &cli.PathFlag{
Name: "output",
Usage: "path of output JSON state. Stdout if left empty.",
TakesFile: true,
Value: "out.json",
Required: false,
}
patternHelp = "'never' (default), 'always', '=123' at exactly step 123, '%123' for every 123 steps"
RunProofAtFlag = &cli.GenericFlag{
Name: "proof-at",
Usage: "step pattern to output proof at: " + patternHelp,
Value: new(StepMatcherFlag),
Required: false,
}
RunProofFmtFlag = &cli.StringFlag{
Name: "proof-fmt",
Usage: "format for proof data output file names. Proof data is written to stdout if empty.",
Value: "proof-%d.json",
Required: false,
}
RunSnapshotAtFlag = &cli.GenericFlag{
Name: "snapshot-at",
Usage: "step pattern to output snapshots at: " + patternHelp,
Value: new(StepMatcherFlag),
Required: false,
}
RunSnapshotFmtFlag = &cli.StringFlag{
Name: "snapshot-fmt",
Usage: "format for snapshot output file names.",
Value: "state-%d.json",
Required: false,
}
RunStopAtFlag = &cli.GenericFlag{
Name: "stop-at",
Usage: "step pattern to stop at: " + patternHelp,
Value: new(StepMatcherFlag),
Required: false,
}
)
type Proof struct {
Step uint64 `json:"step"`
Pre common.Hash `json:"pre"`
Post common.Hash `json:"post"`
StepInput hexutil.Bytes `json:"step-input"`
OracleInput hexutil.Bytes `json:"oracle-input"`
}
func Run(ctx *cli.Context) error {
state, err := loadJSON[mipsevm.State](ctx.Path(RunInputFlag.Name))
if err != nil {
return err
}
mu, err := mipsevm.NewUnicorn()
if err != nil {
return fmt.Errorf("failed to create unicorn emulator: %w", err)
}
if err := mipsevm.LoadUnicorn(state, mu); err != nil {
return fmt.Errorf("failed to load state into unicorn emulator: %w", err)
}
l := Logger(os.Stderr, log.LvlInfo)
outLog := &mipsevm.LoggingWriter{Name: "program std-out", Log: l}
errLog := &mipsevm.LoggingWriter{Name: "program std-err", Log: l}
var po mipsevm.PreimageOracle // TODO need to set this up
stopAt := ctx.Generic(RunStopAtFlag.Name).(*StepMatcherFlag).Matcher()
proofAt := ctx.Generic(RunProofAtFlag.Name).(*StepMatcherFlag).Matcher()
snapshotAt := ctx.Generic(RunSnapshotAtFlag.Name).(*StepMatcherFlag).Matcher()
us, err := mipsevm.NewUnicornState(mu, state, po, outLog, errLog)
if err != nil {
return fmt.Errorf("failed to setup instrumented VM state: %w", err)
}
proofFmt := ctx.String(RunProofFmtFlag.Name)
snapshotFmt := ctx.String(RunSnapshotFmtFlag.Name)
for !state.Exited {
step := state.Step
if stopAt(state) {
break
}
if snapshotAt(state) {
if err := writeJSON[*mipsevm.State](fmt.Sprintf(snapshotFmt, step), state, false); err != nil {
return fmt.Errorf("failed to write state snapshot: %w", err)
}
}
if proofAt(state) {
preStateHash := crypto.Keccak256Hash(state.EncodeWitness())
witness := us.Step(true)
postStateHash := crypto.Keccak256Hash(state.EncodeWitness())
proof := &Proof{
Step: step,
Pre: preStateHash,
Post: postStateHash,
StepInput: witness.EncodeStepInput(),
}
if witness.HasPreimage() {
proof.OracleInput, err = witness.EncodePreimageOracleInput()
if err != nil {
return fmt.Errorf("failed to encode pre-image oracle input: %w", err)
}
}
if err := writeJSON[*Proof](fmt.Sprintf(proofFmt, step), proof, true); err != nil {
return fmt.Errorf("failed to write proof data: %w", err)
}
} else {
_ = us.Step(false)
}
}
if err := writeJSON[*mipsevm.State](ctx.Path(RunOutputFlag.Name), state, true); err != nil {
return fmt.Errorf("failed to write state output: %w", err)
}
return nil
}
var RunCommand = &cli.Command{
Name: "run",
Usage: "Run VM step(s) and generate proof data to replicate onchain.",
Description: "Run VM step(s) and generate proof data to replicate onchain. See flags to match when to output a proof, a snapshot, or to stop early.",
Action: Run,
Flags: []cli.Flag{
RunInputFlag,
RunOutputFlag,
RunProofAtFlag,
RunProofFmtFlag,
RunSnapshotAtFlag,
RunSnapshotFmtFlag,
RunStopAtFlag,
},
}
package cmd
import "github.com/urfave/cli/v2"
func RunSteps(ctx *cli.Context) error {
// TODO
return nil
}
var RunStepsCommand = &cli.Command{
Name: "run-steps",
Usage: "",
Description: "",
Action: RunSteps,
Flags: nil,
}
...@@ -16,8 +16,7 @@ func main() { ...@@ -16,8 +16,7 @@ func main() {
app.Description = "MIPS Fault Proof tool" app.Description = "MIPS Fault Proof tool"
app.Commands = []*cli.Command{ app.Commands = []*cli.Command{
cmd.LoadELFCommand, cmd.LoadELFCommand,
cmd.RunStepsCommand, cmd.RunCommand,
cmd.GenProofCommand,
} }
err := app.Run(os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
......
package mipsevm
import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
)
// LoggingWriter is a simple util to wrap a logger,
// and expose an io Writer interface,
// for the program running within the VM to write to.
type LoggingWriter struct {
Name string
Log log.Logger
}
func logAsText(b string) bool {
for _, c := range b {
if (c < 0x20 || c >= 0x7F) && (c != '\n' && c != '\t') {
return false
}
}
return true
}
func (lw *LoggingWriter) Write(b []byte) (int, error) {
t := string(b)
if logAsText(t) {
lw.Log.Info("", "text", t)
} else {
lw.Log.Info("", "data", hexutil.Bytes(b))
}
return len(b), nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment