Merge branch 'develop' into cwgoes/update-tendermint-upstream

This commit is contained in:
Christopher Goes
2018-09-08 01:09:24 +08:00
21 changed files with 531 additions and 58 deletions
+91
View File
@@ -0,0 +1,91 @@
package cli
import (
"fmt"
"io/ioutil"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context"
"github.com/spf13/cobra"
amino "github.com/tendermint/go-amino"
)
const (
flagAppend = "append"
flagPrintSigs = "print-sigs"
)
// GetSignCommand returns the sign command
func GetSignCommand(codec *amino.Codec, decoder auth.AccountDecoder) *cobra.Command {
cmd := &cobra.Command{
Use: "sign <file>",
Short: "Sign transactions",
Long: `Sign transactions created with the --generate-only flag.
Read a transaction from <file>, sign it, and print its JSON encoding.`,
RunE: makeSignCmd(codec, decoder),
Args: cobra.ExactArgs(1),
}
cmd.Flags().String(client.FlagName, "", "Name of private key with which to sign")
cmd.Flags().Bool(flagAppend, true, "Append the signature to the existing ones. If disabled, old signatures would be overwritten")
cmd.Flags().Bool(flagPrintSigs, false, "Print the addresses that must sign the transaction and those who have already signed it, then exit")
return cmd
}
func makeSignCmd(cdc *amino.Codec, decoder auth.AccountDecoder) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) (err error) {
stdTx, err := readAndUnmarshalStdTx(cdc, args[0])
if err != nil {
return
}
if viper.GetBool(flagPrintSigs) {
printSignatures(stdTx)
return nil
}
name := viper.GetString(client.FlagName)
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(decoder)
txCtx := authctx.NewTxContextFromCLI()
newTx, err := utils.SignStdTx(txCtx, cliCtx, name, stdTx, viper.GetBool(flagAppend))
if err != nil {
return err
}
json, err := cdc.MarshalJSON(newTx)
if err != nil {
return err
}
fmt.Printf("%s\n", json)
return
}
}
func printSignatures(stdTx auth.StdTx) {
fmt.Println("Signers:")
for i, signer := range stdTx.GetSigners() {
fmt.Printf(" %v: %v\n", i, signer.String())
}
fmt.Println("")
fmt.Println("Signatures:")
for i, sig := range stdTx.GetSignatures() {
fmt.Printf(" %v: %v\n", i, sdk.AccAddress(sig.Address()).String())
}
return
}
func readAndUnmarshalStdTx(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err error) {
var bytes []byte
if bytes, err = ioutil.ReadFile(filename); err != nil {
return
}
if err = cdc.UnmarshalJSON(bytes, &stdTx); err != nil {
return
}
return
}
+45 -15
View File
@@ -117,24 +117,11 @@ func (ctx TxContext) Build(msgs []sdk.Msg) (auth.StdSignMsg, error) {
// Sign signs a transaction given a name, passphrase, and a single message to
// signed. An error is returned if signing fails.
func (ctx TxContext) Sign(name, passphrase string, msg auth.StdSignMsg) ([]byte, error) {
keybase, err := keys.GetKeyBase()
sig, err := MakeSignature(name, passphrase, msg)
if err != nil {
return nil, err
}
sig, pubkey, err := keybase.Sign(name, passphrase, msg.Bytes())
if err != nil {
return nil, err
}
sigs := []auth.StdSignature{{
AccountNumber: msg.AccountNumber,
Sequence: msg.Sequence,
PubKey: pubkey,
Signature: sig,
}}
return ctx.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo))
return ctx.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, []auth.StdSignature{sig}, msg.Memo))
}
// BuildAndSign builds a single message to be signed, and signs a transaction
@@ -177,3 +164,46 @@ func (ctx TxContext) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, error
return ctx.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo))
}
// SignStdTx appends a signature to a StdTx and returns a copy of a it. If append
// is false, it replaces the signatures already attached with the new signature.
func (ctx TxContext) SignStdTx(name, passphrase string, stdTx auth.StdTx, appendSig bool) (signedStdTx auth.StdTx, err error) {
stdSignature, err := MakeSignature(name, passphrase, auth.StdSignMsg{
ChainID: ctx.ChainID,
AccountNumber: ctx.AccountNumber,
Sequence: ctx.Sequence,
Fee: stdTx.Fee,
Msgs: stdTx.GetMsgs(),
Memo: stdTx.GetMemo(),
})
if err != nil {
return
}
sigs := stdTx.GetSignatures()
if len(sigs) == 0 || !appendSig {
sigs = []auth.StdSignature{stdSignature}
} else {
sigs = append(sigs, stdSignature)
}
signedStdTx = auth.NewStdTx(stdTx.GetMsgs(), stdTx.Fee, sigs, stdTx.GetMemo())
return
}
// MakeSignature builds a StdSignature given key name, passphrase, and a StdSignMsg.
func MakeSignature(name, passphrase string, msg auth.StdSignMsg) (sig auth.StdSignature, err error) {
keybase, err := keys.GetKeyBase()
if err != nil {
return
}
sigBytes, pubkey, err := keybase.Sign(name, passphrase, msg.Bytes())
if err != nil {
return
}
return auth.StdSignature{
AccountNumber: msg.AccountNumber,
Sequence: msg.Sequence,
PubKey: pubkey,
Signature: sigBytes,
}, nil
}
+4
View File
@@ -20,6 +20,10 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, s
"/accounts/{address}",
QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx),
).Methods("GET")
r.HandleFunc(
"/sign",
SignTxRequestHandlerFn(cdc, cliCtx),
).Methods("POST")
}
// query accountREST Handler
+61
View File
@@ -0,0 +1,61 @@
package rest
import (
"io/ioutil"
"net/http"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context"
)
// SignBody defines the properties of a sign request's body.
type SignBody struct {
Tx auth.StdTx `json:"tx"`
LocalAccountName string `json:"name"`
Password string `json:"password"`
ChainID string `json:"chain_id"`
AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"`
AppendSig bool `json:"append_sig"`
}
// sign tx REST handler
func SignTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var m SignBody
body, err := ioutil.ReadAll(r.Body)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
err = cdc.UnmarshalJSON(body, &m)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
txCtx := authctx.TxContext{
ChainID: m.ChainID,
AccountNumber: m.AccountNumber,
Sequence: m.Sequence,
}
signedTx, err := txCtx.SignStdTx(m.LocalAccountName, m.Password, m.Tx, m.AppendSig)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
output, err := wire.MarshalJSONIndent(cdc, signedTx)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
w.Write(output)
}
}
+89 -20
View File
@@ -2,6 +2,7 @@ package simulation
import (
"fmt"
"math"
"math/rand"
"testing"
@@ -18,30 +19,89 @@ const (
denom = "steak"
)
// SimulateSubmittingVotingAndSlashingForProposal simulates creating a msg Submit Proposal
// voting on the proposal, and subsequently slashing the proposal. It is implemented using
// future operations.
// TODO: Vote more intelligently, so we can actually do some checks regarding votes passing or failing
// TODO: Actually check that validator slashings happened
func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation {
handler := gov.NewHandler(k)
// The states are:
// column 1: All validators vote
// column 2: 90% vote
// column 3: 75% vote
// column 4: 40% vote
// column 5: 15% vote
// column 6: noone votes
// All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily,
// feel free to change.
numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{
{20, 10, 0, 0, 0, 0},
{55, 50, 20, 10, 0, 0},
{25, 25, 30, 25, 30, 15},
{0, 15, 30, 25, 30, 30},
{0, 0, 20, 30, 30, 30},
{0, 0, 0, 10, 10, 25},
})
statePercentageArray := []float64{1, .9, .75, .4, .15, 0}
curNumVotesState := 1
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) {
// 1) submit proposal now
sender := simulation.RandomKey(r, keys)
msg := simulationCreateMsgSubmitProposal(tb, r, sender, log)
action = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event)
proposalID := k.GetLastProposalID(ctx)
// 2) Schedule operations for votes
// 2.1) first pick a number of people to vote.
curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState)
numVotes := int(math.Ceil(float64(len(keys)) * statePercentageArray[curNumVotesState]))
// 2.2) select who votes and when
whoVotes := r.Perm(len(keys))
// didntVote := whoVotes[numVotes:]
whoVotes = whoVotes[:numVotes]
votingPeriod := k.GetVotingProcedure(ctx).VotingPeriod
fops := make([]simulation.FutureOperation, numVotes+1)
for i := 0; i < numVotes; i++ {
whenVote := ctx.BlockHeight() + r.Int63n(votingPeriod)
fops[i] = simulation.FutureOperation{int(whenVote), operationSimulateMsgVote(k, sk, keys[whoVotes[i]], proposalID)}
}
// 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant)
// TODO: Find a way to check if a validator was slashed other than just checking their balance a block
// before and after.
return action, fops, nil
}
}
// SimulateMsgSubmitProposal simulates a msg Submit Proposal
// Note: Currently doesn't ensure that the proposal txt is in JSON form
func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation {
handler := gov.NewHandler(k)
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) {
msg := simulationCreateMsgSubmitProposal(tb, r, keys, log)
ctx, write := ctx.CacheContext()
result := handler(ctx, msg)
if result.IsOK() {
// Update pool to keep invariants
pool := sk.GetPool(ctx)
pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(msg.InitialDeposit.AmountOf(denom)))
sk.SetPool(ctx, pool)
write()
}
event(fmt.Sprintf("gov/MsgSubmitProposal/%v", result.IsOK()))
action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
sender := simulation.RandomKey(r, keys)
msg := simulationCreateMsgSubmitProposal(tb, r, sender, log)
action = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event)
return action, nil, nil
}
}
func simulationCreateMsgSubmitProposal(tb testing.TB, r *rand.Rand, keys []crypto.PrivKey, log string) gov.MsgSubmitProposal {
key := simulation.RandomKey(r, keys)
addr := sdk.AccAddress(key.PubKey().Address())
func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, sk stake.Keeper, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string) {
ctx, write := ctx.CacheContext()
result := handler(ctx, msg)
if result.IsOK() {
// Update pool to keep invariants
pool := sk.GetPool(ctx)
pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(msg.InitialDeposit.AmountOf(denom)))
sk.SetPool(ctx, pool)
write()
}
event(fmt.Sprintf("gov/MsgSubmitProposal/%v", result.IsOK()))
action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
return action
}
func simulationCreateMsgSubmitProposal(tb testing.TB, r *rand.Rand, sender crypto.PrivKey, log string) gov.MsgSubmitProposal {
addr := sdk.AccAddress(sender.PubKey().Address())
deposit := randomDeposit(r)
msg := gov.NewMsgSubmitProposal(
simulation.RandStringOfLength(r, 5),
@@ -88,13 +148,22 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation {
// SimulateMsgVote
// nolint: unparam
func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation {
return operationSimulateMsgVote(k, sk, nil, -1)
}
func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey, proposalID int64) simulation.Operation {
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
key := simulation.RandomKey(r, keys)
addr := sdk.AccAddress(key.PubKey().Address())
proposalID, ok := randomProposalID(r, k, ctx)
if !ok {
return "no-operation", nil, nil
if key == nil {
key = simulation.RandomKey(r, keys)
}
var ok bool
if proposalID < 0 {
proposalID, ok = randomProposalID(r, k, ctx)
if !ok {
return "no-operation", nil, nil
}
}
addr := sdk.AccAddress(key.PubKey().Address())
option := randomVotingOption(r)
msg := gov.NewMsgVote(addr, proposalID, option)
if msg.ValidateBasic() != nil {
+15
View File
@@ -53,6 +53,7 @@ func TestGovWithRandomMessages(t *testing.T) {
gov.InitGenesis(ctx, govKeeper, gov.DefaultGenesisState())
}
// Test with unscheduled votes
simulation.Simulate(
t, mapp.BaseApp, appStateFn,
[]simulation.Operation{
@@ -66,4 +67,18 @@ func TestGovWithRandomMessages(t *testing.T) {
}, 10, 100,
false,
)
// Test with scheduled votes
simulation.Simulate(
t, mapp.BaseApp, appStateFn,
[]simulation.Operation{
SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper),
SimulateMsgDeposit(govKeeper, stakeKeeper),
}, []simulation.RandSetup{
setup,
}, []simulation.Invariant{
AllInvariants(),
}, 10, 100,
false,
)
}