Skip to content

Commit

Permalink
w1p: go/oasis-node/cmd/debug/consim: Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
Yawning committed Apr 2, 2020
1 parent 0396c4c commit 19a591c
Show file tree
Hide file tree
Showing 9 changed files with 882 additions and 2 deletions.
14 changes: 12 additions & 2 deletions go/consensus/tendermint/abci/mux_mock.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// +build gofuzz

package abci

import (
"context"

epochtime "github.com/oasislabs/oasis-core/go/epochtime/api"
upgrade "github.com/oasislabs/oasis-core/go/upgrade/api"
)

Expand All @@ -18,6 +17,17 @@ func (mux *MockABCIMux) MockRegisterApp(app Application) error {
return mux.doRegister(app)
}

// MockSetEpochtime sets the timesource used by this muxer when testing.
func (mux *MockABCIMux) MockSetEpochtime(epochTime epochtime.Backend) {
mux.state.timeSource = epochTime
}

// MockSetTransactionAuthHandler sets the transaction auth hander used by
// this muxer when testing.
func (mux *MockABCIMux) MockSetTransactionAuthHandler(handler TransactionAuthHandler) {
mux.state.txAuthHandler = handler
}

// MockClose cleans up the muxer's state; it must be called once the muxer is no longer needed.
func (mux *MockABCIMux) MockClose() {
mux.doCleanup()
Expand Down
233 changes: 233 additions & 0 deletions go/oasis-node/cmd/debug/consim/consim.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
// Package consim implements the mock consensus simulator.
package consim

import (
"context"
"crypto"
"encoding/hex"
"fmt"
"math/rand"
"path/filepath"

"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"

"github.com/oasislabs/oasis-core/go/common/crypto/drbg"
"github.com/oasislabs/oasis-core/go/common/crypto/mathrand"
"github.com/oasislabs/oasis-core/go/common/logging"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/abci"
registryApp "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/registry"
stakingApp "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/staking"
genesisFile "github.com/oasislabs/oasis-core/go/genesis/file"
cmdCommon "github.com/oasislabs/oasis-core/go/oasis-node/cmd/common"
cmdFlags "github.com/oasislabs/oasis-core/go/oasis-node/cmd/common/flags"
)

const (
cfgWorkload = "consim.workload"
cfgWorkloadSeed = "consim.workload.seed"
)

var (
logger = logging.GetLogger("cmd/consim")

flagsConsim = flag.NewFlagSet("", flag.ContinueOnError)

conSimCmd = &cobra.Command{
Use: "consim",
Short: "mock consensus simulator",
RunE: doRun,
}
)

func doRun(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true

if err := cmdCommon.Init(); err != nil {
cmdCommon.EarlyLogAndExit(err)
}

dataDir := cmdCommon.DataDir()
if dataDir == "" {
return fmt.Errorf("datadir is mandatory")
}

ctx, cancelFn := context.WithCancel(context.Background())
defer cancelFn()

// Load the genesis document.
genesisProvider, err := genesisFile.DefaultFileProvider()
if err != nil {
logger.Error("failed to initialize genesis provider",
"err", err,
)
return err
}
genesisDoc, err := genesisProvider.GetGenesisDocument()
if err != nil {
logger.Error("failed to get genesis document",
"err", err,
)
return err
}
genesisDoc.SetChainContext()
tmChainID := genesisDoc.ChainContext()[:tmtypes.MaxChainIDLen]

// Initialize the DRBG and workload.
rngSrc, err := drbg.New(crypto.SHA512, []byte(viper.GetString(cfgWorkloadSeed)), nil, []byte("consim workload generator"))
if err != nil {
logger.Error("failed to initialize DRBG",
"err", err,
)
return err
}

workload, err := newWorkload(rand.New(mathrand.New(rngSrc)))
if err != nil {
logger.Error("failed to create workload",
"err", err,
)
return err
}
defer workload.Cleanup()

if err = workload.Init(genesisDoc); err != nil {
logger.Error("failed to initialize workload",
"err", err,
)
return err
}

// Initialize the mock chain backend.
txAuthApp := stakingApp.New()
cfg := &mockChainCfg{
dataDir: dataDir,
apps: []abci.Application{
registryApp.New(),
txAuthApp, // This is the staking app.
},
genesisDoc: genesisDoc,
tmChainID: tmChainID,
txAuthHandler: txAuthApp.(abci.TransactionAuthHandler),
}
mockChain, err := initMockChain(ctx, cfg)
if err != nil {
logger.Error("failed to initialize mock chain backend",
"err", err,
)
return err
}
defer mockChain.close()
chainState, err := mockChain.stateToGenesis(ctx)
if err != nil {
logger.Error("failed to obtain chain state",
"err", err,
)
return err
}

// Start the workload.
cancelCh, errCh := make(chan struct{}), make(chan error)
defer close(cancelCh)
txVecCh, err := workload.Start(chainState, cancelCh, errCh)
if err != nil {
logger.Error("failed to start workload",
"err", err,
)
return err
}

// Emulate the tendermint block generation loop.
txLoop:
for {
var (
txVec []BlockTx
ok bool
)
select {
case err = <-errCh:
logger.Error("workload error",
"err", err,
)
return err
case txVec, ok = <-txVecCh:
if !ok {
break txLoop
}
}

mockChain.beginBlock()

// CheckTx all the pending transactions for this block.
var toDeliver []BlockTx
for _, v := range txVec {
txCode := mockChain.checkTx(v.Tx)
if txCode != v.Code {
logger.Error("CheckTx response code mismatch",
"tx", hex.EncodeToString(v.Tx),
"code", txCode,
)
return fmt.Errorf("consim: CheckTx response code mismatch")
} else if v.Code == types.CodeTypeOK {
toDeliver = append(toDeliver, v)
}
}

// DeliverTx all the pending transactions for this block.
for _, v := range toDeliver {
txCode := mockChain.deliverTx(v.Tx)
if txCode != types.CodeTypeOK {
logger.Error("DeliverTx failed",
"tx", hex.EncodeToString(v.Tx),
"code", txCode,
)
return fmt.Errorf("consim: DeliverTx response code mismatch")
}
}

mockChain.endBlock()
}

// Dump the final state to a JSON document.
finalGenesis, err := mockChain.stateToGenesis(ctx)
if err != nil {
logger.Error("failed to obtain state dump",
"err", err,
)
return err
}

if err = workload.Finalize(finalGenesis); err != nil {
logger.Error("failed to finalize workload",
"err", err,
)
return err
}

if err = finalGenesis.WriteFileJSON(filepath.Join(dataDir, "dump.json")); err != nil {
logger.Error("failed to write state dump",
"err", err,
)
return err
}

return nil
}

// Register registers the consim sub-command.
func Register(parentCmd *cobra.Command) {
conSimCmd.Flags().AddFlagSet(cmdFlags.GenesisFileFlags)
conSimCmd.Flags().AddFlagSet(flagsConsim)
conSimCmd.Flags().AddFlagSet(fileTxsFlag)
conSimCmd.Flags().AddFlagSet(xferFlags)
parentCmd.AddCommand(conSimCmd)
}

func init() {
flagsConsim.String(cfgWorkload, fileWorkloadName, "workload to execute")
flagsConsim.String(cfgWorkloadSeed, "seeeeeeeeeeeeeeeeeeeeeeeeeeeeeed", "DRBG seed for workloads")
_ = viper.BindPFlags(flagsConsim)
}
86 changes: 86 additions & 0 deletions go/oasis-node/cmd/debug/consim/file_workload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package consim

import (
"bufio"
"encoding/json"
"fmt"
"math/rand"
"os"

flag "github.com/spf13/pflag"
"github.com/spf13/viper"

genesis "github.com/oasislabs/oasis-core/go/genesis/api"
)

const (
cfgFileTxs = "consim.workload.file.txs"
fileWorkloadName = "file"
)

var fileTxsFlag = flag.NewFlagSet("", flag.ContinueOnError)

type fileWorkload struct {
ch chan []BlockTx
dec *json.Decoder

f *os.File
}

func (w *fileWorkload) Init(doc *genesis.Document) error {
return nil
}

func (w *fileWorkload) Start(doc *genesis.Document, cancelCh <-chan struct{}, errCh chan<- error) (<-chan []BlockTx, error) {
if _, err := w.dec.Token(); err != nil {
return nil, fmt.Errorf("consim/workload/file: failed to find opening delimiter: %w", err)
}
w.ch = make(chan []BlockTx)

go func() {
defer close(w.ch)
for w.dec.More() {
var txVec []BlockTx
if err := w.dec.Decode(&txVec); err != nil {
errCh <- fmt.Errorf("consim/workload/file: failed to decode block tx: %w", err)
return
}
select {
case <-cancelCh:
return
case w.ch <- txVec:
}
}
}()

return w.ch, nil
}

func (w *fileWorkload) Finalize(*genesis.Document) error {
if _, err := w.dec.Token(); err != nil {
return fmt.Errorf("consim/workload/file: failed to find closing delimiter: %w", err)
}

return nil
}

func (w *fileWorkload) Cleanup() {
_ = w.f.Close()
}

func newFileWorkload(rng *rand.Rand) (Workload, error) {
f, err := os.Open(viper.GetString(cfgFileTxs))
if err != nil {
return nil, fmt.Errorf("consim/workload/file: failed to open transaction file: %w", err)
}

return &fileWorkload{
dec: json.NewDecoder(bufio.NewReader(f)),
f: f,
}, nil
}

func init() {
fileTxsFlag.String(cfgFileTxs, "transactions.json", "path to transactions document")
_ = viper.BindPFlags(fileTxsFlag)
}
Loading

0 comments on commit 19a591c

Please sign in to comment.