This commit is contained in:
Sam Ricotta
2022-11-25 16:15:04 +01:00
parent f5bfacd2cf
commit 28a7cbe97e
4 changed files with 146 additions and 72 deletions
+10 -3
View File
@@ -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...))
}
+116 -58
View File
@@ -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,
// }
}
+12 -7
View File
@@ -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)
+8 -4
View File
@@ -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 {