diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c55365cf..7f73eaa84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ FEATURES * Supported proposal types: just binary (pass/fail) TextProposals for now * Proposals need deposits to be votable; deposits are burned if proposal fails * Delegators delegate votes to validator by default but can override (for their stake) +* Add benchmarks for signing and delivering a block with a single bank transaction + * Run with `cd x/bank && go test --bench=.` * [tools] make get_tools installs tendermint's linter, and gometalinter * [tools] Switch gometalinter to the stable version * [tools] Add checking for misspellings and for incorrectly formatted files in circle CI diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index c939996c7..dc46b38d1 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -3,7 +3,6 @@ package baseapp import ( "encoding/json" "fmt" - "github.com/cosmos/cosmos-sdk/x/bank" "os" "testing" @@ -20,6 +19,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" ) func defaultLogger() log.Logger { diff --git a/x/auth/mock/auth_app_test.go b/x/auth/mock/auth_app_test.go index 4442200f1..8da93c967 100644 --- a/x/auth/mock/auth_app_test.go +++ b/x/auth/mock/auth_app_test.go @@ -3,14 +3,38 @@ package mock import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/auth" + abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" ) +// A mock transaction that has a validation which can fail. +type testMsg struct { + signers []sdk.Address + positiveNum int64 +} + +// TODO: Clean this up, make it public +const msgType = "testMsg" + +func (tx testMsg) Type() string { return msgType } +func (tx testMsg) GetMsg() sdk.Msg { return tx } +func (tx testMsg) GetMemo() string { return "" } +func (tx testMsg) GetSignBytes() []byte { return nil } +func (tx testMsg) GetSigners() []sdk.Address { return tx.signers } +func (tx testMsg) GetSignatures() []auth.StdSignature { return nil } +func (tx testMsg) ValidateBasic() sdk.Error { + if tx.positiveNum >= 0 { + return nil + } + return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") +} + // test auth module messages var ( @@ -20,19 +44,50 @@ var ( addr2 = priv2.PubKey().Address() coins = sdk.Coins{sdk.NewCoin("foocoin", 10)} - sendMsg1 = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(addr1, coins)}, - Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, - } + testMsg1 = testMsg{signers: []sdk.Address{addr1}, positiveNum: 1} ) // initialize the mock application for this module func getMockApp(t *testing.T) *App { mapp := NewApp() - coinKeeper := bank.NewKeeper(mapp.AccountMapper) - mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper)) - + mapp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{})) return mapp } + +func TestMsgPrivKeys(t *testing.T) { + mapp := getMockApp(t) + mapp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) + + // Construct some genesis bytes to reflect basecoin/types/AppAccount + // Give 77 foocoin to the first key + coins := sdk.Coins{sdk.NewCoin("foocoin", 77)} + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: coins, + } + accs := []auth.Account{acc1} + + // Construct genesis state + SetGenesis(mapp, accs) + + // A checkTx context (true) + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + assert.Equal(t, acc1, res1.(*auth.BaseAccount)) + + // Run a CheckDeliver + SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{0}, true, priv1) + + // signing a SendMsg with the wrong privKey should be an auth error + mapp.BeginBlock(abci.RequestBeginBlock{}) + tx := GenTx([]sdk.Msg{testMsg1}, []int64{0}, []int64{1}, priv2) + res := mapp.Deliver(tx) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // resigning the tx with the correct priv key should still work + res = SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{1}, true, priv1) + + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), res.Code, res.Log) +} diff --git a/x/auth/mock/simulate_block.go b/x/auth/mock/simulate_block.go index e2e67b4cf..7e8f50843 100644 --- a/x/auth/mock/simulate_block.go +++ b/x/auth/mock/simulate_block.go @@ -54,6 +54,23 @@ func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyE return auth.NewStdTx(msgs, fee, sigs, memo) } +// generate a set of signed transactions a msg, that differ only by having the +// sequence numbers incremented between every transaction. +func GenSequenceOfTxs(msgs []sdk.Msg, accnums []int64, initSeqNums []int64, numToGenerate int, priv ...crypto.PrivKeyEd25519) []auth.StdTx { + txs := make([]auth.StdTx, numToGenerate, numToGenerate) + for i := 0; i < numToGenerate; i++ { + txs[i] = GenTx(msgs, accnums, initSeqNums, priv...) + incrementAllSequenceNumbers(initSeqNums) + } + return txs +} + +func incrementAllSequenceNumbers(initSeqNums []int64) { + for i := 0; i < len(initSeqNums); i++ { + initSeqNums[i]++ + } +} + // check a transaction result func SignCheck(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) sdk.Result { tx := GenTx(msgs, accnums, seq, priv...) @@ -62,7 +79,7 @@ func SignCheck(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int } // simulate a block -func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { +func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) sdk.Result { // Sign the tx tx := GenTx(msgs, accnums, seq, priv...) @@ -86,4 +103,5 @@ func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnum app.EndBlock(abci.RequestEndBlock{}) app.Commit() + return res } diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 6107126b4..b38ef4a45 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -77,14 +77,22 @@ var ( // initialize the mock application for this module func getMockApp(t *testing.T) *mock.App { + mapp, err := getBenchmarkMockApp() + require.NoError(t, err) + return mapp +} + +// getBenchmarkMockApp initializes a mock application for this module, for purposes of benchmarking +// Any long term API support commitments do not apply to this function. +func getBenchmarkMockApp() (*mock.App, error) { mapp := mock.NewApp() RegisterWire(mapp.Cdc) coinKeeper := NewKeeper(mapp.AccountMapper) mapp.Router().AddRoute("bank", NewHandler(coinKeeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{})) - return mapp + err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + return mapp, err } func TestMsgSendWithAccounts(t *testing.T) { diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go new file mode 100644 index 000000000..97a5c4526 --- /dev/null +++ b/x/bank/bench_test.go @@ -0,0 +1,41 @@ +package bank + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/mock" + + abci "github.com/tendermint/abci/types" +) + +func BenchmarkOneBankSendTxPerBlock(b *testing.B) { + benchmarkApp, _ := getBenchmarkMockApp() + + // Add an account at genesis + acc := &auth.BaseAccount{ + Address: addr1, + // Some value conceivably higher than the benchmarks would ever go + Coins: sdk.Coins{sdk.NewCoin("foocoin", 100000000000)}, + } + accs := []auth.Account{acc} + + // Construct genesis state + mock.SetGenesis(benchmarkApp, accs) + // Precompute all txs + txs := mock.GenSequenceOfTxs([]sdk.Msg{sendMsg1}, []int64{0}, []int64{int64(0)}, b.N, priv1) + b.ResetTimer() + // Run this with a profiler, so its easy to distinguish what time comes from + // Committing, and what time comes from Check/Deliver Tx. + for i := 0; i < b.N; i++ { + benchmarkApp.BeginBlock(abci.RequestBeginBlock{}) + x := benchmarkApp.Check(txs[i]) + if !x.IsOK() { + panic("something is broken in checking transaction") + } + benchmarkApp.Deliver(txs[i]) + benchmarkApp.EndBlock(abci.RequestEndBlock{}) + benchmarkApp.Commit() + } +}