diff --git a/abci/example/orderbook/app.go b/abci/example/orderbook/app.go index 19f4a2fbe..184700ac7 100644 --- a/abci/example/orderbook/app.go +++ b/abci/example/orderbook/app.go @@ -132,6 +132,8 @@ func New(db dbm.DB) (*StateMachine, error) { lastHeight: int64(lastHeight), lastHash: lastHash, db: db, + touchedAccounts: make(map[uint64]struct{}), + newPairs: make([]*Pair, 0), }, nil } @@ -276,11 +278,15 @@ func (sm *StateMachine) PrepareProposal(req types.RequestPrepareProposal) types. continue } + fmt.Println("we have a tradeset") + tradeSet = sm.validateTradeSetAgainstState(tradeSet) if tradeSet == nil || len(tradeSet.MatchedOrders) == 0 { continue } + fmt.Println("we have a valid tradeset") + // wrap this as a message typ msgTradeSet := &MsgTradeSet{TradeSet: tradeSet} bz, err := proto.Marshal(msgTradeSet) @@ -311,6 +317,7 @@ func (sm *StateMachine) ProcessProposal(req types.RequestProcessProposal) types. } if status := sm.ValidateTx(msg); status != StatusOK { + fmt.Printf("tx failed validation, status: %d\n", status) return rejectProposal() } } @@ -349,11 +356,11 @@ func (sm *StateMachine) DeliverTx(req types.RequestDeliverTx) types.ResponseDeli case *Msg_MsgCreateAccount: nextAccountID := uint64(len(sm.accounts)) - sm.accounts[nextAccountID] = &Account{ + sm.accounts = append(sm.accounts, &Account{ Index: nextAccountID, PublicKey: m.MsgCreateAccount.PublicKey, Commodities: m.MsgCreateAccount.Commodities, - } + }) sm.touchedAccounts[nextAccountID] = struct{}{} sm.publicKeys[string(m.MsgCreateAccount.PublicKey)] = struct{}{} @@ -452,7 +459,7 @@ func (sm *StateMachine) hash() []byte { func (sm *StateMachine) updateState(batch dbm.Batch, height int64, hash []byte) error { sm.lastHash = hash sm.lastHeight = height - var heightBytes []byte + heightBytes := make([]byte, 8) binary.BigEndian.PutUint64(heightBytes, uint64(height)) return batch.Set(stateKey, append(heightBytes, hash...)) } diff --git a/abci/example/orderbook/app_test.go b/abci/example/orderbook/app_test.go index fd15ce5c2..c53702c1d 100644 --- a/abci/example/orderbook/app_test.go +++ b/abci/example/orderbook/app_test.go @@ -1,7 +1,7 @@ package orderbook_test import ( - "crypto/ed25519" + fmt "fmt" "testing" "github.com/cosmos/gogoproto/proto" @@ -11,6 +11,8 @@ import ( "github.com/tendermint/tendermint/abci/example/orderbook" "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + params "github.com/tendermint/tendermint/types" ) // TODO: we should also check that CheckTx adds bids and asks to the app-side mempool @@ -187,6 +189,117 @@ func TestCheckTx(t *testing.T) { // and from existing state // func TestNewStateMachine(t *testing.T) {} +func TestEndToEnd(t *testing.T) { + db := dbm.NewMemDB() + app, err := orderbook.New(db) + require.NoError(t, err) + + + var ( + maxBytes = params.DefaultConsensusParams().Block.MaxBytes + commodityNZD = &orderbook.Commodity{Denom: "NZD", Quantity: 100} + commodityAUD = &orderbook.Commodity{Denom: "AUD", Quantity: 100} + registerPairMsg = newRegisterPair("NZD", "AUD") + pair = registerPairMsg.GetMsgRegisterPair().Pair + pkAlice = ed25519.GenPrivKey() + pkBob = ed25519.GenPrivKey() + pubKeyAlice = pkAlice.PubKey().Bytes() + pubKeyBob = pkBob.PubKey().Bytes() + registerAlice = newRegisterAccount(pubKeyAlice, []*orderbook.Commodity{commodityAUD}) + registerBob = newRegisterAccount(pubKeyBob, []*orderbook.Commodity{commodityNZD}) + // bob is asking for 25 AUD for 5 NZD + ask = &orderbook.Msg{Sum: &orderbook.Msg_MsgAsk{MsgAsk: orderbook.NewMsgAsk(pair, 5, 5, 1)}} + // alice is bidding for 5 NZD for 25 AUD + bid = &orderbook.Msg{Sum: &orderbook.Msg_MsgBid{MsgBid: orderbook.NewMsgBid(pair, 5, 5, 0)}} + ) + + require.NoError(t, ask.GetMsgAsk().Sign(pkBob)) + require.NoError(t, bid.GetMsgBid().Sign(pkAlice)) + + testCases := []struct { + txs [][]byte + accepted bool + // assertions to be made about the state of the application + // after each block + assertions func(t *testing.T, app *orderbook.StateMachine) + }{ + { + // block 1 sets up the trading pair + txs: asTxs(registerPairMsg), + accepted: true, + assertions: func(t *testing.T, app *orderbook.StateMachine) { + pairs := app.Pairs() + require.Len(t, pairs, 1) + require.Equal(t, pair, &pairs[0]) + }, + }, + { + // block 2 registers two accounts: alice and bob + txs: asTxs(registerAlice, registerBob), + accepted: true, + assertions: func(t *testing.T, app *orderbook.StateMachine) { + alice := app.Account(0) + require.False(t, alice.IsEmpty(), alice) + require.Equal(t, pubKeyAlice, alice.PublicKey) + require.Len(t, alice.Commodities, 1) + require.Equal(t, alice.Commodities[0], commodityAUD) + bob := app.Account(1) + require.False(t, bob.IsEmpty(), bob) + require.Equal(t, pubKeyBob, bob.PublicKey) + require.Len(t, bob.Commodities, 1) + require.Equal(t, bob.Commodities[0], commodityNZD) + require.True(t, app.Account(2).IsEmpty()) + }, + }, + { + // block 3 performs a trade between alice and bob + txs: asTxs(ask, bid), + accepted: true, + assertions: func(t *testing.T, app *orderbook.StateMachine) { + alice := app.Account(0) + require.Equal(t, alice.Commodities[0].Quantity, 75) // 75 AUD + require.Equal(t, alice.Commodities[1].Quantity, 5) // 5 NZD + bob := app.Account(1) + require.Equal(t, bob.Commodities[0].Quantity, 95) // 95 NZD + require.Equal(t, bob.Commodities[0].Quantity, 5) // 5 AUD + }, + }, + } + + for idx, tc := range testCases { + for _, tx := range tc.txs { + resp := app.CheckTx(types.RequestCheckTx{Tx: tx}) + require.EqualValues(t, orderbook.StatusOK, resp.Code) + } + txs := app.PrepareProposal(types.RequestPrepareProposal{MaxTxBytes: maxBytes, Txs: tc.txs}).Txs + require.Equal(t, txs, tc.txs) + if idx == 2 { + fmt.Print(tc.txs) + fmt.Println() + fmt.Print(txs) + } + + result := app.ProcessProposal(types.RequestProcessProposal{Txs: txs}) + if tc.accepted { + require.Equal(t, types.ResponseProcessProposal_ACCEPT, result.Status) + } else { + require.Equal(t, types.ResponseProcessProposal_REJECT, result.Status) + continue + } + + app.BeginBlock(types.RequestBeginBlock{}) + for _, tx := range txs { + app.DeliverTx(types.RequestDeliverTx{Tx: tx}) + } + app.EndBlock(types.RequestEndBlock{}) + app.Commit() + + if tc.assertions != nil { + tc.assertions(t, app) + } + } +} + func asTxs(msgs ...*orderbook.Msg) [][]byte { output := make([][]byte, len(msgs)) for i, msg := range msgs { @@ -205,65 +318,10 @@ func newRegisterPair(d1, d2 string) *orderbook.Msg { }}} } -func newRegisterAccount(pubkey []byte, commodities []*orderbook.Commodity ) *orderbook.Msg { +func newRegisterAccount(pubkey []byte, commodities []*orderbook.Commodity) *orderbook.Msg { return &orderbook.Msg{Sum: &orderbook.Msg_MsgCreateAccount{MsgCreateAccount: &orderbook.MsgCreateAccount{ - PublicKey: pubkey, + 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 index 8c77dd913..87636c2b0 100644 --- a/abci/example/orderbook/cmd/orderbook.go +++ b/abci/example/orderbook/cmd/orderbook.go @@ -47,15 +47,20 @@ func NewCLI() *CLI { 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 + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // Config file not found; use default + // This often happens when initializing a config for the first time + } else { + return err + } + } else { + if err := viper.Unmarshal(config); err != nil { + return err + } } config.SetRoot(root) diff --git a/abci/example/orderbook/types.go b/abci/example/orderbook/types.go index b49680d02..58297d7ee 100644 --- a/abci/example/orderbook/types.go +++ b/abci/example/orderbook/types.go @@ -167,8 +167,8 @@ func (o *OrderBid) DeterministicSignatureBytes(pair *Pair) []byte { buf.WriteString(pair.SellersDenomination) buf.WriteString(pair.BuyersDenomination) bz := buf.Bytes() - binary.BigEndian.PutUint64(bz, math.Float64bits(o.MaxQuantity)) - binary.BigEndian.PutUint64(bz, math.Float64bits(o.MaxPrice)) + bz = binary.BigEndian.AppendUint64(bz, math.Float64bits(o.MaxQuantity)) + bz = binary.BigEndian.AppendUint64(bz, math.Float64bits(o.MaxPrice)) return bz } @@ -227,11 +227,15 @@ func (o *OrderAsk) DeterministicSignatureBytes(pair *Pair) []byte { buf.WriteString(pair.BuyersDenomination) buf.WriteString(pair.SellersDenomination) bz := buf.Bytes() - binary.BigEndian.PutUint64(bz, math.Float64bits(o.Quantity)) - binary.BigEndian.PutUint64(bz, math.Float64bits(o.AskPrice)) + bz = binary.BigEndian.AppendUint64(bz, math.Float64bits(o.Quantity)) + bz = binary.BigEndian.AppendUint64(bz, math.Float64bits(o.AskPrice)) return bz } +func (a Account) IsEmpty() bool { + return len(a.PublicKey) == 0 +} + func (a *Account) FindCommidity(denom string) *Commodity { for _, c := range a.Commodities { if c.Denom == denom {