Merge branch 'develop' into cwgoes/revert-governance-bft-time
This commit is contained in:
commit
5494e3c2dc
|
@ -28,6 +28,7 @@ BREAKING CHANGES
|
||||||
* [x/stake] [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Validator operator type has now changed to `sdk.ValAddress`
|
* [x/stake] [\#2040](https://github.com/cosmos/cosmos-sdk/issues/2040) Validator operator type has now changed to `sdk.ValAddress`
|
||||||
* A new bech32 prefix has been introduced for Tendermint signing keys and
|
* A new bech32 prefix has been introduced for Tendermint signing keys and
|
||||||
addresses, `cosmosconspub` and `cosmoscons` respectively.
|
addresses, `cosmosconspub` and `cosmoscons` respectively.
|
||||||
|
* [x/gov] \#2195 Made governance use BFT Time instead of Block Heights for deposit and voting periods.
|
||||||
|
|
||||||
* SDK
|
* SDK
|
||||||
* [core] [\#1807](https://github.com/cosmos/cosmos-sdk/issues/1807) Switch from use of rational to decimal
|
* [core] [\#1807](https://github.com/cosmos/cosmos-sdk/issues/1807) Switch from use of rational to decimal
|
||||||
|
@ -47,6 +48,7 @@ FEATURES
|
||||||
* [lcd] Endpoints to query staking pool and params
|
* [lcd] Endpoints to query staking pool and params
|
||||||
* [lcd] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions
|
* [lcd] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions
|
||||||
* [lcd] [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add support for `generate_only=true` query argument to generate offline unsigned transactions
|
* [lcd] [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add support for `generate_only=true` query argument to generate offline unsigned transactions
|
||||||
|
* [lcd] [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) Add /sign endpoint to sign transactions generated with `generate_only=true`.
|
||||||
|
|
||||||
* Gaia CLI (`gaiacli`)
|
* Gaia CLI (`gaiacli`)
|
||||||
* [cli] Cmds to query staking pool and params
|
* [cli] Cmds to query staking pool and params
|
||||||
|
@ -56,7 +58,9 @@ FEATURES
|
||||||
* [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) Setting the --gas flag value to 0 triggers a simulation of the tx before the actual execution. The gas estimate obtained via the simulation will be used as gas limit in the actual execution.
|
* [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) Setting the --gas flag value to 0 triggers a simulation of the tx before the actual execution. The gas estimate obtained via the simulation will be used as gas limit in the actual execution.
|
||||||
* [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=0.
|
* [cli] [\#2047](https://github.com/cosmos/cosmos-sdk/issues/2047) The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=0.
|
||||||
* [cli] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated.
|
* [cli] [\#2110](https://github.com/cosmos/cosmos-sdk/issues/2110) Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated.
|
||||||
* [cli] [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT.
|
* [cli] [\#2204](https://github.com/cosmos/cosmos-sdk/issues/2204) Support generating and broadcasting messages with multiple signatures via command line:
|
||||||
|
* [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT.
|
||||||
|
* [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) New `sign` command to sign transactions generated with the --generate-only flag.
|
||||||
|
|
||||||
* Gaia
|
* Gaia
|
||||||
* [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address`
|
* [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address`
|
||||||
|
|
|
@ -24,7 +24,7 @@ func BufferStdin() *bufio.Reader {
|
||||||
// It enforces the password length
|
// It enforces the password length
|
||||||
func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
||||||
if inputIsTty() {
|
if inputIsTty() {
|
||||||
pass, err = speakeasy.Ask(prompt)
|
pass, err = speakeasy.FAsk(os.Stderr, prompt)
|
||||||
} else {
|
} else {
|
||||||
pass, err = readLineFromBuf(buf)
|
pass, err = readLineFromBuf(buf)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/wire"
|
"github.com/cosmos/cosmos-sdk/wire"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||||
|
@ -313,12 +314,13 @@ func TestIBCTransfer(t *testing.T) {
|
||||||
// TODO: query ibc egress packet state
|
// TODO: query ibc egress packet state
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCoinSendGenerateOnly(t *testing.T) {
|
func TestCoinSendGenerateAndSign(t *testing.T) {
|
||||||
name, password := "test", "1234567890"
|
name, password := "test", "1234567890"
|
||||||
addr, seed := CreateAddr(t, "test", password, GetKeyBase(t))
|
addr, seed := CreateAddr(t, "test", password, GetKeyBase(t))
|
||||||
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})
|
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
// create TX
|
|
||||||
|
// generate TX
|
||||||
res, body, _ := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?generate_only=true")
|
res, body, _ := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?generate_only=true")
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
var msg auth.StdTx
|
var msg auth.StdTx
|
||||||
|
@ -327,6 +329,30 @@ func TestCoinSendGenerateOnly(t *testing.T) {
|
||||||
require.Equal(t, msg.Msgs[0].Type(), "bank")
|
require.Equal(t, msg.Msgs[0].Type(), "bank")
|
||||||
require.Equal(t, msg.Msgs[0].GetSigners(), []sdk.AccAddress{addr})
|
require.Equal(t, msg.Msgs[0].GetSigners(), []sdk.AccAddress{addr})
|
||||||
require.Equal(t, 0, len(msg.Signatures))
|
require.Equal(t, 0, len(msg.Signatures))
|
||||||
|
|
||||||
|
// sign tx
|
||||||
|
var signedMsg auth.StdTx
|
||||||
|
acc := getAccount(t, port, addr)
|
||||||
|
accnum := acc.GetAccountNumber()
|
||||||
|
sequence := acc.GetSequence()
|
||||||
|
|
||||||
|
payload := authrest.SignBody{
|
||||||
|
Tx: msg,
|
||||||
|
LocalAccountName: name,
|
||||||
|
Password: password,
|
||||||
|
ChainID: viper.GetString(client.FlagChainID),
|
||||||
|
AccountNumber: accnum,
|
||||||
|
Sequence: sequence,
|
||||||
|
}
|
||||||
|
json, err := cdc.MarshalJSON(payload)
|
||||||
|
require.Nil(t, err)
|
||||||
|
res, body = Request(t, port, "POST", "/sign", json)
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||||
|
require.Nil(t, cdc.UnmarshalJSON([]byte(body), &signedMsg))
|
||||||
|
require.Equal(t, len(msg.Msgs), len(signedMsg.Msgs))
|
||||||
|
require.Equal(t, msg.Msgs[0].Type(), signedMsg.Msgs[0].Type())
|
||||||
|
require.Equal(t, msg.Msgs[0].GetSigners(), signedMsg.Msgs[0].GetSigners())
|
||||||
|
require.Equal(t, 1, len(signedMsg.Signatures))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTxs(t *testing.T) {
|
func TestTxs(t *testing.T) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
@ -99,6 +100,49 @@ func PrintUnsignedStdTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignStdTx appends a signature to a StdTx and returns a copy of a it. If appendSig
|
||||||
|
// is false, it replaces the signatures already attached with the new signature.
|
||||||
|
func SignStdTx(txCtx authctx.TxContext, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool) (auth.StdTx, error) {
|
||||||
|
var signedStdTx auth.StdTx
|
||||||
|
|
||||||
|
keybase, err := keys.GetKeyBase()
|
||||||
|
if err != nil {
|
||||||
|
return signedStdTx, err
|
||||||
|
}
|
||||||
|
info, err := keybase.Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return signedStdTx, err
|
||||||
|
}
|
||||||
|
addr := info.GetPubKey().Address()
|
||||||
|
|
||||||
|
// Check whether the address is a signer
|
||||||
|
if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) {
|
||||||
|
fmt.Fprintf(os.Stderr, "WARNING: The generated transaction's intended signer does not match the given signer: '%v'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if txCtx.AccountNumber == 0 {
|
||||||
|
accNum, err := cliCtx.GetAccountNumber(addr)
|
||||||
|
if err != nil {
|
||||||
|
return signedStdTx, err
|
||||||
|
}
|
||||||
|
txCtx = txCtx.WithAccountNumber(accNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if txCtx.Sequence == 0 {
|
||||||
|
accSeq, err := cliCtx.GetAccountSequence(addr)
|
||||||
|
if err != nil {
|
||||||
|
return signedStdTx, err
|
||||||
|
}
|
||||||
|
txCtx = txCtx.WithSequence(accSeq)
|
||||||
|
}
|
||||||
|
|
||||||
|
passphrase, err := keys.GetPassphrase(name)
|
||||||
|
if err != nil {
|
||||||
|
return signedStdTx, err
|
||||||
|
}
|
||||||
|
return txCtx.SignStdTx(name, passphrase, stdTx, appendSig)
|
||||||
|
}
|
||||||
|
|
||||||
func adjustGasEstimate(estimate int64, adjustment float64) int64 {
|
func adjustGasEstimate(estimate int64, adjustment float64) int64 {
|
||||||
return int64(adjustment * float64(estimate))
|
return int64(adjustment * float64(estimate))
|
||||||
}
|
}
|
||||||
|
@ -163,3 +207,12 @@ func buildUnsignedStdTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs
|
||||||
}
|
}
|
||||||
return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil
|
return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool {
|
||||||
|
for _, s := range signers {
|
||||||
|
if bytes.Equal(user.Bytes(), s.Bytes()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ package clitest
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -332,7 +333,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||||
require.Equal(t, " 2 - Apples", proposalsQuery)
|
require.Equal(t, " 2 - Apples", proposalsQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGaiaCLISendGenerateOnly(t *testing.T) {
|
func TestGaiaCLISendGenerateAndSign(t *testing.T) {
|
||||||
chainID, servAddr, port := initializeFixtures(t)
|
chainID, servAddr, port := initializeFixtures(t)
|
||||||
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
|
||||||
|
|
||||||
|
@ -343,6 +344,7 @@ func TestGaiaCLISendGenerateOnly(t *testing.T) {
|
||||||
tests.WaitForTMStart(port)
|
tests.WaitForTMStart(port)
|
||||||
tests.WaitForNextNBlocksTM(2, port)
|
tests.WaitForNextNBlocksTM(2, port)
|
||||||
|
|
||||||
|
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
|
||||||
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
|
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
|
||||||
|
|
||||||
// Test generate sendTx with default gas
|
// Test generate sendTx with default gas
|
||||||
|
@ -376,6 +378,35 @@ func TestGaiaCLISendGenerateOnly(t *testing.T) {
|
||||||
require.Equal(t, msg.Fee.Gas, int64(100))
|
require.Equal(t, msg.Fee.Gas, int64(100))
|
||||||
require.Equal(t, len(msg.Msgs), 1)
|
require.Equal(t, len(msg.Msgs), 1)
|
||||||
require.Equal(t, 0, len(msg.GetSignatures()))
|
require.Equal(t, 0, len(msg.GetSignatures()))
|
||||||
|
|
||||||
|
// Write the output to disk
|
||||||
|
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||||
|
defer os.Remove(unsignedTxFile.Name())
|
||||||
|
|
||||||
|
// Test sign --print-sigs
|
||||||
|
success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||||
|
"gaiacli sign %v --print-sigs %v", flags, unsignedTxFile.Name()))
|
||||||
|
require.True(t, success)
|
||||||
|
require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n", fooAddr.String()), stdout)
|
||||||
|
|
||||||
|
// Test sign
|
||||||
|
success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||||
|
"gaiacli sign %v --name=foo %v", flags, unsignedTxFile.Name()), app.DefaultKeyPass)
|
||||||
|
require.True(t, success)
|
||||||
|
msg = unmarshalStdTx(t, stdout)
|
||||||
|
require.Equal(t, len(msg.Msgs), 1)
|
||||||
|
require.Equal(t, 1, len(msg.GetSignatures()))
|
||||||
|
require.Equal(t, fooAddr.String(), msg.GetSigners()[0].String())
|
||||||
|
|
||||||
|
// Write the output to disk
|
||||||
|
signedTxFile := writeToNewTempFile(t, stdout)
|
||||||
|
defer os.Remove(signedTxFile.Name())
|
||||||
|
|
||||||
|
// Test sign --print-signatures
|
||||||
|
success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf(
|
||||||
|
"gaiacli sign %v --print-sigs %v", flags, signedTxFile.Name()))
|
||||||
|
require.True(t, success)
|
||||||
|
require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\n", fooAddr.String(), fooAddr.String()), stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
//___________________________________________________________________________________
|
//___________________________________________________________________________________
|
||||||
|
@ -408,6 +439,14 @@ func unmarshalStdTx(t *testing.T, s string) (stdTx auth.StdTx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeToNewTempFile(t *testing.T, s string) *os.File {
|
||||||
|
fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_")
|
||||||
|
require.Nil(t, err)
|
||||||
|
_, err = fp.WriteString(s)
|
||||||
|
require.Nil(t, err)
|
||||||
|
return fp
|
||||||
|
}
|
||||||
|
|
||||||
//___________________________________________________________________________________
|
//___________________________________________________________________________________
|
||||||
// executors
|
// executors
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,7 @@ func main() {
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
client.GetCommands(
|
client.GetCommands(
|
||||||
authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)),
|
authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)),
|
||||||
|
authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)),
|
||||||
)...)
|
)...)
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
client.PostCommands(
|
client.PostCommands(
|
||||||
|
|
|
@ -229,6 +229,75 @@ Returns on success:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### POST /auth/accounts/sign
|
||||||
|
|
||||||
|
- **URL**: `/auth/sign`
|
||||||
|
- **Functionality**: Sign a transaction without broadcasting it.
|
||||||
|
- Returns on success:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"rest api": "1.0",
|
||||||
|
"code": 200,
|
||||||
|
"error": "",
|
||||||
|
"result": {
|
||||||
|
"type": "auth/StdTx",
|
||||||
|
"value": {
|
||||||
|
"msg": [
|
||||||
|
{
|
||||||
|
"type": "cosmos-sdk/Send",
|
||||||
|
"value": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "cosmos1ql4ekxkujf3xllk8h5ldhhgh4ylpu7kwec6q3d",
|
||||||
|
"coins": [
|
||||||
|
{
|
||||||
|
"denom": "steak",
|
||||||
|
"amount": "1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"address": "cosmos1dhyqhg4px33ed3erqymls0hc7q2lxw9hhfwklj",
|
||||||
|
"coins": [
|
||||||
|
{
|
||||||
|
"denom": "steak",
|
||||||
|
"amount": "1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee": {
|
||||||
|
"amount": [
|
||||||
|
{
|
||||||
|
"denom": "",
|
||||||
|
"amount": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gas": "2742"
|
||||||
|
},
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "tendermint/PubKeySecp256k1",
|
||||||
|
"value": "A2A/f2IYnrPUMTMqhwN81oas9jurtfcsvxdeLlNw3gGy"
|
||||||
|
},
|
||||||
|
"signature": "MEQCIGVn73y9QLwBa3vmsAD1bs3ygX75Wo+lAFSAUDs431ZPAiBWAf2amyqTCDXE9J87rL9QF9sd5JvVMt7goGSuamPJwg==",
|
||||||
|
"account_number": "1",
|
||||||
|
"sequence": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"memo": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## ICS20 - TokenAPI
|
## ICS20 - TokenAPI
|
||||||
|
|
||||||
The TokenAPI exposes all functionality needed to query account balances and send transactions.
|
The TokenAPI exposes all functionality needed to query account balances and send transactions.
|
||||||
|
|
|
@ -147,7 +147,16 @@ gaiacli send \
|
||||||
--chain-id=<chain_id> \
|
--chain-id=<chain_id> \
|
||||||
--name=<key_name> \
|
--name=<key_name> \
|
||||||
--to=<destination_cosmosaccaddr> \
|
--to=<destination_cosmosaccaddr> \
|
||||||
--generate-only
|
--generate-only > unsignedSendTx.json
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now sign the transaction file generated through the `--generate-only` flag by providing your key to the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gaiacli sign \
|
||||||
|
--chain-id=<chain_id> \
|
||||||
|
--name=<key_name>
|
||||||
|
unsignedSendTx.json > signedSendTx.json
|
||||||
```
|
```
|
||||||
|
|
||||||
### Staking
|
### Staking
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
// Sign signs a transaction given a name, passphrase, and a single message to
|
||||||
// signed. An error is returned if signing fails.
|
// signed. An error is returned if signing fails.
|
||||||
func (ctx TxContext) Sign(name, passphrase string, msg auth.StdSignMsg) ([]byte, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return ctx.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, []auth.StdSignature{sig}, msg.Memo))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildAndSign builds a single message to be signed, and signs a transaction
|
// 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))
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,10 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, s
|
||||||
"/accounts/{address}",
|
"/accounts/{address}",
|
||||||
QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx),
|
QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx),
|
||||||
).Methods("GET")
|
).Methods("GET")
|
||||||
|
r.HandleFunc(
|
||||||
|
"/sign",
|
||||||
|
SignTxRequestHandlerFn(cdc, cliCtx),
|
||||||
|
).Methods("POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
// query accountREST Handler
|
// query accountREST Handler
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue