diff --git a/abci/example/orderbook/app_test.go b/abci/example/orderbook/app_test.go index 3f5b1e310..fd15ce5c2 100644 --- a/abci/example/orderbook/app_test.go +++ b/abci/example/orderbook/app_test.go @@ -26,11 +26,12 @@ func TestCheckTx(t *testing.T) { responseCode uint32 expOrderSize int }{ - // { - // name: "test empty tx", - // msg: &orderbook.Msg{}, - // responseCode: orderbook.StatusErrUnknownMessage, - // }, + { + name: "test empty tx", + msg: &orderbook.Msg{}, + responseCode: orderbook.StatusErrValidateBasic, + expOrderSize: 0, + }, { name: "test msg ask", msg: &orderbook.Msg{ @@ -49,26 +50,60 @@ func TestCheckTx(t *testing.T) { responseCode: orderbook.StatusOK, expOrderSize: 1, }, - // { - // name: "test msg bid", - // msg: &orderbook.Msg{Sum: &orderbook.Msg_MsgBid{MsgBid: &orderbook.MsgBid{ - // Pair: testPair, - // BidOrder: &orderbook.OrderBid{ - // MaxQuantity: 15, - // MaxPrice: 5, - // OwnerId: 1, - // Signature: []byte("signature"), - // }, - // }}}, - // responseCode: orderbook.StatusOK, - // }, - // { - // name: "test msg register pair", - // msg: &orderbook.Msg{Sum: &orderbook.Msg_MsgRegisterPair{MsgRegisterPair: &orderbook.MsgRegisterPair{ - // Pair: testPair, - // }}}, - // responseCode: orderbook.StatusOK, - // }, + { + name: "test msg ask wrong signature", + msg: &orderbook.Msg{ + Sum: &orderbook.Msg_MsgAsk{ + MsgAsk: &orderbook.MsgAsk{ + Pair: testPair, + AskOrder: &orderbook.OrderAsk{ + Quantity: 10, + AskPrice: 1, + OwnerId: 1, + Signature: crypto.CRandBytes(62), + }, + }, + }, + }, + responseCode: orderbook.StatusErrValidateBasic, + expOrderSize: 1, + }, + { + name: "test msg bid", + msg: &orderbook.Msg{Sum: &orderbook.Msg_MsgBid{MsgBid: &orderbook.MsgBid{ + Pair: testPair, + BidOrder: &orderbook.OrderBid{ + MaxQuantity: 15, + MaxPrice: 5, + OwnerId: 1, + Signature: crypto.CRandBytes(ed25519.SignatureSize), + }, + }}}, + responseCode: orderbook.StatusOK, + expOrderSize: 2, + }, + { + name: "test msg bid blank", + msg: &orderbook.Msg{Sum: &orderbook.Msg_MsgBid{MsgBid: &orderbook.MsgBid{ + Pair: testPair, + BidOrder: &orderbook.OrderBid{ + MaxQuantity: 0, + MaxPrice: 0, + OwnerId: 0, + Signature: crypto.CRandBytes(ed25519.SignatureSize), + }, + }}}, + responseCode: orderbook.StatusErrValidateBasic, + expOrderSize: 2, + }, + { + name: "test msg register duplicate pair", + msg: &orderbook.Msg{Sum: &orderbook.Msg_MsgRegisterPair{MsgRegisterPair: &orderbook.MsgRegisterPair{ + Pair: &orderbook.Pair{BuyersDenomination: "ATOM", SellersDenomination: "ATOM"}, + }}}, + responseCode: orderbook.StatusErrValidateBasic, + expOrderSize: 2, + }, } for _, tc := range testCases { @@ -78,20 +113,62 @@ func TestCheckTx(t *testing.T) { resp := app.CheckTx(types.RequestCheckTx{Tx: bz}) require.Equal(t, tc.responseCode, resp.Code, resp.Log) bids, asks := app.Orders(testPair) - require.Equal(t, tc.expOrderSize, len(bids) + len(asks)) + require.Equal(t, tc.expOrderSize, len(bids)+len(asks)) }) } } -// TODO: we should check that transactions in -// a market are being validated and added to the proposal -// and that other transactions get in -// func TestPrepareProposal(t *testing.T) { -// app := orderbook.New(dbm.NewMemDB()) +// func ValidateTx(t *testing.T) { +// db := dbm.NewMemDB() +// require.NoError(t, orderbook.InitDB(db, []*orderbook.Pair{testPair}, nil)) +// app, err := orderbook.New(db) +// require.NoError(t, err) + +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// bz, err := proto.Marshal(tc.msg) +// require.NoError(t, err) +// resp := app.CheckTx(types.RequestCheckTx{Tx: bz}) +// require.Equal(t, tc.responseCode, resp.Code, resp.Log) +// bids, asks := app.Orders(testPair) +// require.Equal(t, tc.expOrderSize, len(bids)+len(asks)) +// }) +// } // } +// TODO: we should check that transactions in +// a market are being validated and added to the proposal +// // and that other transactions get in +// func TestPrepareProposal(t *testing.T) { +// db := dbm.NewMemDB() +// require.NoError(t, orderbook.InitDB(db, []*orderbook.Pair{testPair}, nil)) +// app, err := orderbook.New(db) +// require.NoError(t, err) + +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// bz, err := proto.Marshal(tc.msg) +// require.NoError(t, err) +// resp := app.CheckTx(types.RequestCheckTx{Tx: bz}) +// require.Equal(t, tc.responseCode, resp.Code, resp.Log) +// bids, asks := app.Orders(testPair) +// require.Equal(t, tc.expOrderSize, len(bids)+len(asks)) +// }) +// } +// } + +// { +// name: "test msg register pair", +// msg: &orderbook.Msg{Sum: &orderbook.Msg_MsgRegisterPair{MsgRegisterPair: &orderbook.MsgRegisterPair{ +// Pair: &orderbook.Pair{BuyersDenomination: "ATOM", SellersDenomination: "AUD"}, +// }}}, +// responseCode: orderbook.StatusOK, +// expOrderSize: 2, +// pairSize: 2, +// }, + // TODO: we should test that transactions are -// always valid i.e. ValidateTx. We could potentially +// always valid i.e. ValidateTx. We could potentially // combine this with PrepareProposal // func TestProcessProposal(t *testing.T) { // app := orderbook.New(dbm.NewMemDB()) @@ -109,3 +186,84 @@ func TestCheckTx(t *testing.T) { // TODO: test that we can start from new // and from existing state // func TestNewStateMachine(t *testing.T) {} + +func asTxs(msgs ...*orderbook.Msg) [][]byte { + output := make([][]byte, len(msgs)) + for i, msg := range msgs { + bz, err := proto.Marshal(msg) + if err != nil { + panic(err) + } + output[i] = bz + } + return output +} + +func newRegisterPair(d1, d2 string) *orderbook.Msg { + return &orderbook.Msg{Sum: &orderbook.Msg_MsgRegisterPair{MsgRegisterPair: &orderbook.MsgRegisterPair{ + Pair: &orderbook.Pair{BuyersDenomination: d1, SellersDenomination: d2}, + }}} +} + +func newRegisterAccount(pubkey []byte, commodities []*orderbook.Commodity ) *orderbook.Msg { + return &orderbook.Msg{Sum: &orderbook.Msg_MsgCreateAccount{MsgCreateAccount: &orderbook.MsgCreateAccount{ + PublicKey: pubkey, + Commodities: commodities, + }}} +} + +func TestEndToEnd(t *testing.T) { + db := dbm.NewMemDB() + _, err := orderbook.New(db) + require.NoError(t, err) + + // registerPairMsg := newRegisterPair("NZD", "AUD") + // registerAccountMsg := newRegisterAccount() + + // app.ProcessProposal(types.RequestProcessProposal{Txs: asTxs(registerPairMsg, registerAccountMsg)}) + + // for _, tc := range testCases { + // t.Run(tc.name, func(t *testing.T) { + // bz, err := proto.Marshal(tc.msg) + // require.NoError(t, err) + // resp := app.DeliverTx(types.RequestDeliverTx{Tx: bz}) + // require.Equal(t, tc.responseCode, resp.Code, resp.Log) + // }) + // } + // name: "test create account", + // msg: &orderbook.Msg{ + // Sum: &orderbook.Msg_MsgAsk{ + // MsgAsk: &orderbook.MsgAsk{ + // Pair: testPair, + // AskOrder: &orderbook.OrderAsk{ + // Quantity: 10, + // AskPrice: 1, + // OwnerId: 1, + // Signature: crypto.CRandBytes(ed25519.SignatureSize), + // }, + // }, + // }, + // }, + // responseCode: orderbook.StatusOK, + // expOrderSize: 1, + // }, + // { + // name: "test add tradeset", + // msg: &orderbook.Msg{ + // Sum: &orderbook.Msg_MsgAsk{ + // MsgAsk: &orderbook.MsgAsk{ + // Pair: testPair, + // AskOrder: &orderbook.OrderAsk{ + // Quantity: 10, + // AskPrice: 1, + // OwnerId: 1, + // Signature: crypto.CRandBytes(ed25519.SignatureSize), + // }, + // }, + // }, + // }, + // responseCode: orderbook.StatusOK, + // expOrderSize: 1, + // } + +} diff --git a/abci/example/orderbook/cmd/orderbook.go b/abci/example/orderbook/cmd/orderbook.go new file mode 100644 index 000000000..8c77dd913 --- /dev/null +++ b/abci/example/orderbook/cmd/orderbook.go @@ -0,0 +1,238 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/tendermint/abci/example/orderbook" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +func main() { + NewCLI().Run() +} + +type CLI struct { + root *cobra.Command + config *cfg.Config +} + +func NewCLI() *CLI { + cli := &CLI{} + cli.root = &cobra.Command{ + Use: "orderbook", + Short: "orderbook abci++ example", + } + cli.root.AddCommand(&cobra.Command{ + Use: "init", + Short: "initialize the file system for an orderbook node", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + root, err := os.Getwd() + if err != nil { + return err + } + + viper.AddConfigPath(filepath.Join(root, "config")) + viper.SetConfigName("config") + + if err := viper.ReadInConfig(); err != nil { + // return err + } + + config := cfg.DefaultConfig() + + if err := viper.Unmarshal(config); err != nil { + return err + } + + config.SetRoot(root) + cli.config = config + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + privValKeyFile := cli.config.PrivValidatorKeyFile() + privValStateFile := cli.config.PrivValidatorStateFile() + var pv *privval.FilePV + if tmos.FileExists(privValKeyFile) { + pv = privval.LoadFilePV(privValKeyFile, privValStateFile) + fmt.Print("found private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } else { + pv = privval.GenFilePV(privValKeyFile, privValStateFile) + pv.Save() + fmt.Print("Generated private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } + + nodeKeyFile := cli.config.NodeKeyFile() + if tmos.FileExists(nodeKeyFile) { + fmt.Print("Found node key", "path", nodeKeyFile) + } else { + if _, err := p2p.LoadOrGenNodeKey(nodeKeyFile); err != nil { + return err + } + fmt.Print("Generated node key", "path", nodeKeyFile) + } + + // genesis file + genFile := cli.config.GenesisFile() + if tmos.FileExists(genFile) { + fmt.Print("Found genesis file", "path", genFile) + } else { + genDoc := types.GenesisDoc{ + ChainID: fmt.Sprintf("orderbook-chain-%v", tmrand.Int()), + GenesisTime: tmtime.Now(), + ConsensusParams: types.DefaultConsensusParams(), + } + pubKey, err := pv.GetPubKey() + if err != nil { + return fmt.Errorf("can't get pubkey: %w", err) + } + genDoc.Validators = []types.GenesisValidator{{ + Address: pubKey.Address(), + PubKey: pubKey, + Power: 10, + }} + + if err := genDoc.SaveAs(genFile); err != nil { + return err + } + fmt.Print("Generated genesis file", "path", genFile) + } + + return nil + }, + }) + cli.root.AddCommand(&cobra.Command{ + Use: "run", + Short: "runs an orderbook node", + RunE: func(cmd *cobra.Command, args []string) error { + dbProvider := node.DefaultDBProvider + appDB, err := dbProvider(&node.DBContext{"orderbook", cli.config}) + if err != nil { + return err + } + app, err := orderbook.New(appDB) + if err != nil { + return err + } + + nodeKey, err := p2p.LoadOrGenNodeKey(cli.config.NodeKeyFile()) + if err != nil { + return fmt.Errorf("failed to load or gen node key %s: %w", cli.config.NodeKeyFile(), err) + } + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + n, err := node.NewNode( + cli.config, + privval.LoadOrGenFilePV(cli.config.PrivValidatorKeyFile(), cli.config.PrivValidatorStateFile()), + nodeKey, + proxy.NewLocalClientCreator(app), + node.DefaultGenesisDocProviderFunc(cli.config), + dbProvider, + node.DefaultMetricsProvider(cli.config.Instrumentation), + logger, + ) + if err != nil { + return err + } + + if err := n.Start(); err != nil { + return err + } + + tmos.TrapSignal(logger, func() { + if err := n.Stop(); err != nil { + logger.Error("unable to stop the node", "error", err) + } + }) + + return nil + }, + }) + cli.root.AddCommand(&cobra.Command{ + Use: "create-account [commodities...]", + Short: "creates a new account message and submits it to the chain", + Example: "create-account 500BTC 10000USD", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + }) + cli.root.AddCommand(&cobra.Command{ + Use: "create-pair buyers-denomination sellers-denomination", + Short: "creates a new pair message and submits it to the chain", + Example: "create-pair BTC USD", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + }) + cli.root.AddCommand(&cobra.Command{ + Use: "bid buying-commodity price", + Short: "creates a bid message and submits it to the chain", + Example: "bid 10BTC 15000BTC/USD", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + }) + cli.root.AddCommand(&cobra.Command{ + Use: "ask selling-commodity price", + Short: "creates an ask message and submits it to the chain", + Example: "ask 5BTC 12000BTC/USD", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + }) + querySubcommand := &cobra.Command{ + Use: "query", + Short: "query the bal", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + } + querySubcommand.AddCommand(&cobra.Command{ + Use: "account pubkey|id", + Short: "query the balance of an account", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + }) + querySubcommand.AddCommand(&cobra.Command{ + Use: "pairs", + Short: "list all the trading pairs", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + }) + querySubcommand.AddCommand(&cobra.Command{ + Use: "orders pair", + Short: "list all current orders for a given pair", + Example: "orders BTC/USD", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + }) + cli.root.AddCommand(querySubcommand) + + return cli +} + +// Run runs the CLI. +func (cli *CLI) Run() { + if err := cli.root.Execute(); err != nil { + fmt.Print(err) + os.Exit(1) + } +} diff --git a/abci/example/orderbook/msgs.pb.go b/abci/example/orderbook/msgs.pb.go index 2a0b4cf5e..325ca3de0 100644 --- a/abci/example/orderbook/msgs.pb.go +++ b/abci/example/orderbook/msgs.pb.go @@ -5,7 +5,7 @@ package orderbook import ( fmt "fmt" - proto "github.com/cosmos/gogoproto/proto" + proto "github.com/gogo/protobuf/proto" io "io" math "math" math_bits "math/bits" diff --git a/abci/example/orderbook/msgs.proto b/abci/example/orderbook/msgs.proto index 95a28a148..a59e33e65 100644 --- a/abci/example/orderbook/msgs.proto +++ b/abci/example/orderbook/msgs.proto @@ -1,4 +1,4 @@ -syntax = "proto3"; +€syntax = "proto3"; package orderbook; option go_package = "github.com/tendermint/tendermint/abci/example/orderbook"; diff --git a/abci/example/orderbook/orderbook b/abci/example/orderbook/orderbook new file mode 100755 index 000000000..cf5de341b Binary files /dev/null and b/abci/example/orderbook/orderbook differ diff --git a/abci/example/orderbook/wire.pb.go b/abci/example/orderbook/wire.pb.go index 8b5f6457d..aa5ad0c46 100644 --- a/abci/example/orderbook/wire.pb.go +++ b/abci/example/orderbook/wire.pb.go @@ -6,7 +6,7 @@ package orderbook import ( encoding_binary "encoding/binary" fmt "fmt" - proto "github.com/cosmos/gogoproto/proto" + proto "github.com/gogo/protobuf/proto" io "io" math "math" math_bits "math/bits"