bump version

added rest server and status endpoint

added get block endpoint

added latest block endpoint

add 404 if height is out of bounds

add version endpoint

add validators endpoint

export GetBlockHeight

add keys endpoints

add txs endpoints

added verb limiters to ednpoints

only output node info + json structure improvement

fixed wrong body parsing

github PR template

crypto.Address -> sdk.Address

revert to old go-wire

update glide

remove print statement and update glide

fix #554

add .DS_Store to .gitignore

Massive consolidation: queue, data storage struct, store, logic, ...

Small fixes
This commit is contained in:
Ethan Buchman 2018-03-01 02:16:54 -05:00
parent 7c3213fa00
commit ad705fdea1
18 changed files with 1662 additions and 93 deletions

21
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${fileDirname}",
"env": {},
"args": [],
"showLog": true
}
]
}

View File

@ -1,9 +1,12 @@
package keys
import (
"encoding/json"
"fmt"
"net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -120,3 +123,76 @@ func printCreate(info keys.Info, seed string) {
panic(fmt.Sprintf("I can't speak: %s", output))
}
}
// REST
type NewKeyBody struct {
Name string `json:"name"`
Password string `json:"password"`
// TODO make seed mandatory
// Seed string `json="seed"`
Type string `json:"type"`
}
func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
var kb keys.Keybase
var m NewKeyBody
kb, err := GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
if m.Name == "" {
w.WriteHeader(400)
w.Write([]byte("You have to specify a name for the locally stored account."))
return
}
// algo type defaults to ed25519
if m.Type == "" {
m.Type = "ed25519"
}
algo := keys.CryptoAlgo(m.Type)
_, _, err = kb.Create(m.Name, m.Password, algo)
// TODO handle different errors
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(200)
}
// function to just a new seed to display in the UI before actually persisting it in the keybase
func getSeed(algo keys.CryptoAlgo) string {
kb := client.MockKeyBase()
pass := "throwing-this-key-away"
name := "inmemorykey"
_, seed, _ := kb.Create(name, pass, algo)
return seed
}
func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
algoType := vars["type"]
// algo type defaults to ed25519
if algoType == "" {
algoType = "ed25519"
}
algo := keys.CryptoAlgo(algoType)
seed := getSeed(algo)
w.Write([]byte(seed))
}

View File

@ -1,10 +1,14 @@
package keys
import (
"encoding/json"
"fmt"
"net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/gorilla/mux"
"github.com/pkg/errors"
keys "github.com/tendermint/go-crypto/keys"
"github.com/spf13/cobra"
)
@ -43,3 +47,41 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error {
fmt.Println("Password deleted forever (uh oh!)")
return nil
}
// REST
type DeleteKeyBody struct {
Password string `json:"password"`
}
func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
var kb keys.Keybase
var m DeleteKeyBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
kb, err = GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
// TODO handle error if key is not available or pass is wrong
err = kb.Delete(name, m.Password)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(200)
}

View File

@ -1,6 +1,13 @@
package keys
import "github.com/spf13/cobra"
import (
"encoding/json"
"net/http"
"github.com/spf13/cobra"
)
// CMD
// listKeysCmd represents the list command
var listKeysCmd = &cobra.Command{
@ -23,3 +30,36 @@ func runListCmd(cmd *cobra.Command, args []string) error {
}
return err
}
//REST
func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) {
kb, err := GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
infos, err := kb.List()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
// an empty list will be JSONized as null, but we want to keep the empty list
if len(infos) == 0 {
w.Write([]byte("[]"))
return
}
keysOutput := make([]KeyOutput, len(infos))
for i, info := range infos {
keysOutput[i] = KeyOutput{Name: info.Name, Address: info.PubKey.Address().String()}
}
output, err := json.MarshalIndent(keysOutput, "", " ")
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

View File

@ -1,7 +1,12 @@
package keys
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/pkg/errors"
keys "github.com/tendermint/go-crypto/keys"
"github.com/spf13/cobra"
)
@ -13,20 +18,51 @@ var showKeysCmd = &cobra.Command{
RunE: runShowCmd,
}
func getKey(name string) (keys.Info, error) {
kb, err := GetKeyBase()
if err != nil {
return keys.Info{}, err
}
return kb.Get(name)
}
// CMD
func runShowCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
kb, err := GetKeyBase()
if err != nil {
return err
}
info, err := kb.Get(name)
info, err := getKey(name)
if err == nil {
printInfo(info)
}
return err
}
// REST
func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
info, err := getKey(name)
// TODO check for the error if key actually does not exist, instead of assuming this as the reason
if err != nil {
w.WriteHeader(404)
w.Write([]byte(err.Error()))
return
}
keyOutput := KeyOutput{Name: info.Name, Address: info.PubKey.Address().String()}
output, err := json.MarshalIndent(keyOutput, "", " ")
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

View File

@ -1,10 +1,14 @@
package keys
import (
"encoding/json"
"fmt"
"net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/gorilla/mux"
"github.com/pkg/errors"
keys "github.com/tendermint/go-crypto/keys"
"github.com/spf13/cobra"
)
@ -48,3 +52,42 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error {
fmt.Println("Password successfully updated!")
return nil
}
// REST
type UpdateKeyBody struct {
NewPassword string `json:"new_password"`
OldPassword string `json:"old_password"`
}
func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
var kb keys.Keybase
var m UpdateKeyBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
kb, err = GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
// TODO check if account exists and if password is correct
err = kb.Update(name, m.OldPassword, m.NewPassword)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(200)
}

View File

@ -20,6 +20,14 @@ var (
keybase keys.Keybase
)
// used for outputting keys.Info over REST
type KeyOutput struct {
Name string `json:"name"`
Address string `json:"address"`
// TODO add pubkey?
// Pubkey string `json:"pubkey"`
}
// GetKeyBase initializes a keybase based on the configuration
func GetKeyBase() (keys.Keybase, error) {
if keybase == nil {

View File

@ -1,11 +1,18 @@
package lcd
import (
"errors"
"net/http"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/spf13/viper"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
client "github.com/cosmos/cosmos-sdk/client"
keys "github.com/cosmos/cosmos-sdk/client/keys"
rpc "github.com/cosmos/cosmos-sdk/client/rpc"
tx "github.com/cosmos/cosmos-sdk/client/tx"
version "github.com/cosmos/cosmos-sdk/version"
)
const (
@ -13,19 +20,14 @@ const (
flagCORS = "cors"
)
// XXX: remove this when not needed
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
// ServeCommand will generate a long-running rest server
// (aka Light Client Daemon) that exposes functionality similar
// to the cli, but over rest
func ServeCommand() *cobra.Command {
func ServeCommand(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "serve",
Use: "rest-server",
Short: "Start LCD (light-client daemon), a local REST server",
RunE: todoNotImplemented,
RunE: startRESTServer(cdc),
}
// TODO: handle unix sockets also?
cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to")
@ -34,3 +36,32 @@ func ServeCommand() *cobra.Command {
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
return cmd
}
func startRESTServer(cdc *wire.Codec) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
bind := viper.GetString(flagBind)
r := initRouter(cdc)
return http.ListenAndServe(bind, r)
}
}
func initRouter(cdc *wire.Codec) http.Handler {
r := mux.NewRouter()
r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET")
r.HandleFunc("/node_info", rpc.NodeStatusRequestHandler).Methods("GET")
r.HandleFunc("/keys", keys.QueryKeysRequestHandler).Methods("GET")
r.HandleFunc("/keys", keys.AddNewKeyRequestHandler).Methods("POST")
r.HandleFunc("/keys/seed", keys.SeedRequestHandler).Methods("GET")
r.HandleFunc("/keys/{name}", keys.GetKeyRequestHandler).Methods("GET")
r.HandleFunc("/keys/{name}", keys.UpdateKeyRequestHandler).Methods("PUT")
r.HandleFunc("/keys/{name}", keys.DeleteKeyRequestHandler).Methods("DELETE")
r.HandleFunc("/txs", tx.SearchTxRequestHandler(cdc)).Methods("GET")
r.HandleFunc("/txs/{hash}", tx.QueryTxRequestHandler(cdc)).Methods("GET")
r.HandleFunc("/txs/sign", tx.SignTxRequstHandler).Methods("POST")
r.HandleFunc("/txs/broadcast", tx.BroadcastTxRequestHandler).Methods("POST")
r.HandleFunc("/blocks/latest", rpc.LatestBlockRequestHandler).Methods("GET")
r.HandleFunc("/blocks/{height}", rpc.BlockRequestHandler).Methods("GET")
r.HandleFunc("/validatorsets/latest", rpc.LatestValidatorsetRequestHandler).Methods("GET")
r.HandleFunc("/validatorsets/{height}", rpc.ValidatorsetRequestHandler).Methods("GET")
return r
}

View File

@ -1,11 +1,13 @@
package rpc
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
)
@ -18,7 +20,7 @@ func blockCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "block [height]",
Short: "Get verified data for a the block at given height",
RunE: getBlock,
RunE: printBlock,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
@ -27,7 +29,47 @@ func blockCommand() *cobra.Command {
return cmd
}
func getBlock(cmd *cobra.Command, args []string) error {
func getBlock(height *int64) ([]byte, error) {
// get the node
node, err := client.GetNode()
if err != nil {
return nil, err
}
// TODO: actually honor the --select flag!
// header -> BlockchainInfo
// header, tx -> Block
// results -> BlockResults
res, err := node.Block(height)
if err != nil {
return nil, err
}
// TODO move maarshalling into cmd/rest functions
// output, err := tmwire.MarshalJSON(res)
output, err := json.MarshalIndent(res, "", " ")
if err != nil {
return nil, err
}
return output, nil
}
func GetChainHeight() (int64, error) {
node, err := client.GetNode()
if err != nil {
return -1, err
}
status, err := node.Status()
if err != nil {
return -1, err
}
height := status.LatestBlockHeight
return height, nil
}
// CMD
func printBlock(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
@ -41,26 +83,51 @@ func getBlock(cmd *cobra.Command, args []string) error {
}
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
// TODO: actually honor the --select flag!
// header -> BlockchainInfo
// header, tx -> Block
// results -> BlockResults
res, err := node.Block(height)
if err != nil {
return err
}
output, err := wire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
output, err := getBlock(height)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func BlockRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
height, err := strconv.ParseInt(vars["height"], 10, 64)
if err != nil {
w.WriteHeader(400)
w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'."))
return
}
chainHeight, err := GetChainHeight()
if height > chainHeight {
w.WriteHeader(404)
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
return
}
output, err := getBlock(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
func LatestBlockRequestHandler(w http.ResponseWriter, r *http.Request) {
height, err := GetChainHeight()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
output, err := getBlock(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

View File

@ -1,31 +1,40 @@
package rpc
import (
"encoding/json"
"fmt"
"net/http"
"github.com/spf13/cobra"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
func statusCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Short: "Query remote node for status",
RunE: checkStatus,
RunE: printNodeStatus,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
return cmd
}
func checkStatus(cmd *cobra.Command, args []string) error {
func getNodeStatus() (*ctypes.ResultStatus, error) {
// get the node
node, err := client.GetNode()
if err != nil {
return err
return &ctypes.ResultStatus{}, err
}
res, err := node.Status()
return node.Status()
}
// CMD
func printNodeStatus(cmd *cobra.Command, args []string) error {
status, err := getNodeStatus()
if err != nil {
return err
}
@ -35,6 +44,28 @@ func checkStatus(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
// TODO match desired spec output
func NodeStatusRequestHandler(w http.ResponseWriter, r *http.Request) {
status, err := getNodeStatus()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
nodeInfo := status.NodeInfo
output, err := json.MarshalIndent(nodeInfo, "", " ")
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

View File

@ -1,11 +1,13 @@
package rpc
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
)
@ -14,7 +16,7 @@ func validatorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "validatorset <height>",
Short: "Get the full validator set at given height",
RunE: getValidators,
RunE: printValidators,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
@ -22,7 +24,28 @@ func validatorCommand() *cobra.Command {
return cmd
}
func getValidators(cmd *cobra.Command, args []string) error {
func getValidators(height *int64) ([]byte, error) {
// get the node
node, err := client.GetNode()
if err != nil {
return nil, err
}
res, err := node.Validators(height)
if err != nil {
return nil, err
}
output, err := json.MarshalIndent(res, "", " ")
if err != nil {
return nil, err
}
return output, nil
}
// CMD
func printValidators(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
@ -36,22 +59,52 @@ func getValidators(cmd *cobra.Command, args []string) error {
}
}
// get the node
node, err := client.GetNode()
output, err := getValidators(height)
if err != nil {
return err
}
res, err := node.Validators(height)
if err != nil {
return err
}
output, err := wire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func ValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
height, err := strconv.ParseInt(vars["height"], 10, 64)
if err != nil {
w.WriteHeader(400)
w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'."))
return
}
chainHeight, err := GetChainHeight()
if height > chainHeight {
w.WriteHeader(404)
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
return
}
output, err := getValidators(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
func LatestValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) {
height, err := GetChainHeight()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
output, err := getValidators(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}

View File

@ -3,6 +3,7 @@ package tx
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/spf13/cobra"
@ -24,7 +25,7 @@ func SearchTxCmd(cmdr commander) *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Search for all transactions that match the given tags",
RunE: cmdr.searchTxCmd,
RunE: cmdr.searchAndPrintTx,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false once proofs built in
@ -34,10 +35,9 @@ func SearchTxCmd(cmdr commander) *cobra.Command {
return cmd
}
func (c commander) searchTxCmd(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
func (c commander) searchTx(tags []string) ([]byte, error) {
if len(tags) == 0 {
return errors.New("Must declare at least one tag to search")
return nil, errors.New("Must declare at least one tag to search")
}
// XXX: implement ANY
query := strings.Join(tags, " AND ")
@ -45,27 +45,25 @@ func (c commander) searchTxCmd(cmd *cobra.Command, args []string) error {
// get the node
node, err := client.GetNode()
if err != nil {
return err
return nil, err
}
prove := !viper.GetBool(client.FlagTrustNode)
res, err := node.TxSearch(query, prove)
if err != nil {
return err
return nil, err
}
info, err := formatTxResults(c.cdc, res)
if err != nil {
return err
return nil, err
}
output, err := c.cdc.MarshalJSON(info)
if err != nil {
return err
return nil, err
}
fmt.Println(string(output))
return nil
return output, nil
}
func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error) {
@ -79,3 +77,34 @@ func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error)
}
return out, nil
}
// CMD
func (c commander) searchAndPrintTx(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
output, err := c.searchTx(tags)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func SearchTxRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) {
c := commander{cdc}
return func(w http.ResponseWriter, r *http.Request) {
tag := r.FormValue("tag")
tags := []string{tag}
output, err := c.searchTx(tags)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
}

View File

@ -4,12 +4,20 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
keybase "github.com/cosmos/cosmos-sdk/client/keys"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types"
keys "github.com/tendermint/go-crypto/keys"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/client"
@ -22,7 +30,7 @@ func QueryTxCmd(cmdr commander) *cobra.Command {
cmd := &cobra.Command{
Use: "tx [hash]",
Short: "Matches this txhash over all committed blocks",
RunE: cmdr.queryTxCmd,
RunE: cmdr.queryAndPrintTx,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
@ -30,42 +38,28 @@ func QueryTxCmd(cmdr commander) *cobra.Command {
return cmd
}
// command to query for a transaction
func (c commander) queryTxCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a tx hash")
}
// find the key to look up the account
hexStr := args[0]
hash, err := hex.DecodeString(hexStr)
func (c commander) queryTx(hashHexStr string, trustNode bool) ([]byte, error) {
hash, err := hex.DecodeString(hashHexStr)
if err != nil {
return err
return nil, err
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
return nil, err
}
prove := !viper.GetBool(client.FlagTrustNode)
res, err := node.Tx(hash, prove)
res, err := node.Tx(hash, !trustNode)
if err != nil {
return err
return nil, err
}
info, err := formatTxResult(c.cdc, res)
if err != nil {
return err
return nil, err
}
output, err := json.MarshalIndent(info, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
return json.MarshalIndent(info, "", " ")
}
func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) {
@ -98,3 +92,110 @@ func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) {
}
return tx, nil
}
// CMD
// command to query for a transaction
func (c commander) queryAndPrintTx(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a tx hash")
}
// find the key to look up the account
hashHexStr := args[0]
trustNode := viper.GetBool(client.FlagTrustNode)
output, err := c.queryTx(hashHexStr, trustNode)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
// REST
func QueryTxRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) {
c := commander{cdc}
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
hashHexStr := vars["hash"]
trustNode, err := strconv.ParseBool(r.FormValue("trust_node"))
// trustNode defaults to true
if err != nil {
trustNode = true
}
output, err := c.queryTx(hashHexStr, trustNode)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
}
// TODO refactor into different files show, sign, broadcast
type SignTxBody struct {
Name string `json="name"`
Password string `json="password"`
TxBytes string `json="tx"`
}
func SignTxRequstHandler(w http.ResponseWriter, r *http.Request) {
var kb keys.Keybase
var m SignTxBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
kb, err = keybase.GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
//TODO check if account exists
sig, _, err := kb.Sign(m.Name, m.Password, []byte(m.TxBytes))
if err != nil {
w.WriteHeader(403)
w.Write([]byte(err.Error()))
return
}
w.Write(sig.Bytes())
}
type BroadcastTxBody struct {
TxBytes string `json="tx"`
}
func BroadcastTxRequestHandler(w http.ResponseWriter, r *http.Request) {
var m BroadcastTxBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
res, err := client.BroadcastTx([]byte(m.TxBytes))
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write([]byte(string(res.Height)))
}

View File

@ -0,0 +1,659 @@
# Governance documentation
*Disclaimer: This is work in progress. Mechanisms are susceptible to change.*
This document describes the high-level architecture of the governance module. The governance module allows bonded Atom holders to vote on proposals on a 1 bonded Atom 1 vote basis.
## Design overview
The governance process is divided in a few steps that are outlined below:
- **Proposal submission:** Proposal is submitted to the blockchain with a deposit
- **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` transactions to vote on the proposal
- If the proposal involves a software upgrade:
- **Signal:** Validators start signaling that they are ready to switch to the new version
- **Switch:** Once more than 75% of validators have signaled that they are ready to switch, their software automatically flips to the new version
## Proposal submission
### Right to submit a proposal
Any Atom holder, whether bonded or unbonded, can submit proposals by sending a `TxGovProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`.
### Proposal filter (minimum deposit)
To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit `MinDeposit`.
When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `MinDeposit`, it enters voting period.
### Deposit refund
There are two instances where Atom holders that deposited can claim back their deposit:
- If the proposal is accepted
- If the proposal's deposit does not reach `MinDeposit` for a period longer than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore.
In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` transaction to retrieve their share of the deposit.
### Proposal types
In the initial version of the governance module, there are two types of proposal:
- `PlainTextProposal`. All the proposals that do not involve a modification of the source code go under this type. For example, an opinion poll would use a proposal of type `PlainTextProposal`
- `SoftwareUpgradeProposal`. If accepted, validators are expected to update their software in accordance with the proposal. They must do so by following a 2-steps process described in the [Software Upgrade](#software-upgrade) section below. Software upgrade roadmap may be discussed and agreed on via `PlainTextProposals`, but actual software upgrades must be performed via `SoftwareUpgradeProposals`.
### Proposal categories
There are two categories of proposal:
- `Regular`
- `Urgent`
These two categories are strictly identical except that `Urgent` proposals can be accepted faster if a certain condition is met. For more information, see [Threshold](#threshold) section.
## Vote
### Participants
*Participants* are users that have the right to vote on proposals. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals.
Note that some *participants* can be forbidden to vote on a proposal under a certain validator if:
- *participant* bonded or unbonded Atoms to said validator after proposal entered voting period
- *participant* became validator after proposal entered voting period
This does not prevent *participant* to vote with Atoms bonded to other validators. For example, if a *participant* bonded some Atoms to validator A before a proposal entered voting period and other Atoms to validator B after proposal entered voting period, only the vote under validator B will be forbidden.
### Voting period
Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We define `Voting period` as the interval between the moment the vote opens and the moment the vote closes. `Voting period` should always be shorter than `Unbonding period` to prevent double voting. The initial value of `Voting period` is 2 weeks.
### Option set
The option set of a proposal refers to the set of choices a participant can choose from when casting its vote.
The initial option set includes the following options:
- `Yes`
- `No`
- `NoWithVeto`
- `Abstain`
`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote.
*Note: from the UI, for urgent proposals we should maybe add a Not Urgent option that casts a `NoWithVeto` vote.*
### Quorum
Quorum is defined as the minimum percentage of voting power that needs to be casted on a proposal for the result to be valid.
In the initial version of the governance module, there will be no quorum enforced by the protocol. Participation is ensured via the combination of inheritance and validator's punishment for non-voting.
### Threshold
Threshold is defined as the minimum proportion of `Yes` votes (excluding `Abstain` votes) for the proposal to be accepted.
Initially, the threshold is set at 50% with a possibility to veto if more than 1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means that proposals are accepted if the proportion of `Yes` votes (excluding `Abstain` votes) at the end of the voting period is superior to 50% and if the proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` votes).
`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, `UrgentProposal` will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens.
### Inheritance
If a delegator does not vote, it will inherit its validator vote.
- If the delegator votes before its validator, it will not inherit from the validator's vote.
- If the delegator votes after its validator, it will override its validator vote with its own. If the proposal is a `Urgent` proposal, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as `Urgent` proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway.
### Validators punishment for non-voting
Validators are required to vote on all proposals to ensure that results have legitimacy. Voting is part of validators' directives and failure to do it will result in a penalty.
If a validators address is not in the list of addresses that voted on a proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then the validator will automatically be partially slashed of `GovernancePenalty`.
*Note: Need to define values for `GovernancePenalty`*
**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2:3, making it mechanically impossible for some validators to vote on it.
### Governance key and governance address
Validators can make use of a slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign votes using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment.
## Software Upgrade
If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade their software to the new version that was voted. This process is divided in two steps.
### Signal
After a `SoftwareUpgradeProposal` is accepted, validators are expected to download and install the new version of the software while continuing to run the previous version. Once a validator has downloaded and installed the upgrade, it will start signaling to the network that it is ready to switch by including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation that we want it in the precommit?*)
Note: There is only one signal slot per *precommit*. If several `SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will form and they will be implemented one after the other in the order that they were accepted.
### Switch
Once a block contains more than 2/3rd *precommits* where a common `SoftwareUpgradeProposal` is signaled, all the nodes (including validator nodes, non-validating full nodes and light-nodes) are expected to switch to the new version of the software.
*Note: Not clear how the flip is handled programatically*
## Implementation
*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic and implementation might widely differ*
### State
#### Procedures
`Procedures` define the rule according to which votes are run. There can only be one active procedure at any given time. If governance wants to change a procedure, either to modify a value or add/remove a parameter, a new procedure has to be created and the previous one rendered inactive.
```Go
type Procedure struct {
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
MinDeposit int64 // Minimum deposit for a proposal to enter voting period.
OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain}
ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal}
Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
GovernancePenalty int64 // Penalty if validator does not vote
IsActive bool // If true, procedure is active. Only one procedure can have isActive true.
}
```
**Store**:
- `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their `ProcedureNumber`
- `ActiveProcedureNumber`: returns current procedure number
#### Proposals
`Proposals` are item to be voted on.
```Go
type Proposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Category bool // false=regular, true=urgent
Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0)
InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1)
Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain)
}
```
We also introduce a type `ValidatorGovInfo`
```Go
type ValidatorGovInfo struct {
InitVotingPower int64 // Voting power of validator when proposal enters voting period
Minus int64 // Minus of validator, used to compute validator's voting power
}
```
**Store:**
- `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID`
- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `<proposalID>:<depositorPubKey>` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the proposal)
- `Options`: A mapping `map[[]byte]string` of options indexed by `<proposalID>:<voterPubKey>:<validatorPubKey>` as `[]byte`. Given a `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by this `PubKey` for this validator (`nil` if `PubKey` has not voted under this validator)
- `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's governance infos indexed by `<proposalID>:<validatorGovPubKey>`. Returns `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key of a validator when proposal entered voting period.
#### Proposal Processing Queue
**Store:**
- `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the `ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if `CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`.
And the pseudocode for the `ProposalProcessingQueue`:
```
in BeginBlock do
checkProposal() // First call of the recursive function
// Recursive function. First call in BeginBlock
func checkProposal()
if (ProposalProcessingQueue.Peek() == nil)
return
else
proposalID = ProposalProcessingQueue.Peek()
proposal = load(store, Proposals, proposalID)
initProcedure = load(store, Procedures, proposal.InitProcedureNumber)
if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3)
// proposal was urgent and accepted under the special condition
// no punishment
ProposalProcessingQueue.pop()
checkProposal()
else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod)
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
for each validator in CurrentBondedValidators
validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey)
if (validatorGovInfo.InitVotingPower != nil)
// validator was bonded when vote started
validatorOption = load(store, Options, validator.GovPubKey)
if (validatorOption == nil)
// validator did not vote
slash validator by activeProcedure.GovernancePenalty
ProposalProcessingQueue.pop()
checkProposal()
```
### Transactions
#### Proposal Submission
Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction.
```Go
type TxGovSubmitProposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Category bool // false=regular, true=urgent
InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive.
}
```
**State modifications:**
- Generate new `proposalID`
- Create new `Proposal`
- Initialise `Proposals` attributes
- Store sender's deposit in `Deposits`
- Decrease balance of sender by `InitialDeposit`
- If `MinDeposit` is reached:
- Push `proposalID` in `ProposalProcessingQueueEnd`
- Store each validator's voting power in `ValidatorGovInfos`
A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode
```
// PSEUDOCODE //
// Check if TxGovSubmitProposal is valid. If it is, create proposal //
upon receiving txGovSubmitProposal from sender do
if !correctlyFormatted(txGovSubmitProposal) then
// check if proposal is correctly formatted. Includes fee payment.
throw
else
if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then
// InitialDeposit is negative or null OR sender has insufficient funds
throw
else
sender.AtomBalance -= txGovSubmitProposal.InitialDeposit
proposalID = generate new proposalID
proposal = NewProposal()
proposal.Title = txGovSubmitProposal.Title
proposal.Description = txGovSubmitProposal.Description
proposal.Type = txGovSubmitProposal.Type
proposal.Category = txGovSubmitProposal.Category
proposal.Deposit = txGovSubmitProposal.InitialDeposit
proposal.SubmitBlock = CurrentBlock
store(Deposits, <proposalID>:<sender>, txGovSubmitProposal.InitialDeposit)
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then
// MinDeposit is not reached
proposal.VotingStartBlock = -1
proposal.InitTotalVotingPower = 0
proposal.InitProcedureNumber = -1
else
// MinDeposit is reached
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedureNumber = ActiveProcedureNumber
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.GovPubKey>, validatorGovInfo)
ProposalProcessingQueue.push(proposalID)
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
return proposalID
```
#### Deposit
Once a proposal is submitted, if `Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send `TxGovDeposit` transactions to increase the proposal's deposit.
```Go
type TxGovDeposit struct {
ProposalID int64 // ID of the proposal
Deposit int64 // Number of Atoms to add to the proposal's deposit
}
```
**State modifications:**
- Decrease balance of sender by `deposit`
- Initialize or increase `deposit` of sender in `Deposits`
- Increase `proposal.Deposit` by sender's `deposit`
- If `MinDeposit` is reached:
- Push `proposalID` in `ProposalProcessingQueueEnd`
- Store each validator's voting power in `ValidatorGovInfos`
A `TxGovDeposit` transaction has to go through a number of checks to be valid. These checks are outlined in the following pseudocode.
```
// PSEUDOCODE //
// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached
upon receiving txGovDeposit from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit) then
throw
else
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
// deposit is negative or null OR sender has insufficient funds
throw
else
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (proposal.Deposit >= activeProcedure.MinDeposit) then
// MinDeposit was reached
throw
else
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
// Maximum deposit period reached
throw
else
// sender can deposit
sender.AtomBalance -= txGovDeposit.Deposit
deposit = load(store, Deposits, <txGovDeposit.ProposalID>:<sender>)
if (deposit == nil)
// sender has never deposited on this proposal
store(Deposits, <txGovDeposit.ProposalID>:<sender>, deposit)
else
// sender has already deposited on this proposal
newDeposit = deposit + txGovDeposit.Deposit
store(Deposits, <txGovDeposit.ProposalID>:<sender>, newDeposit)
proposal.Deposit += txGovDeposit.Deposit
if (proposal.Deposit >= activeProcedure.MinDeposit) then
// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedureNumber = ActiveProcedureNumber
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.GovPubKey>, validatorGovInfo)
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
```
#### Claiming deposit
Finally, if the proposal is accepted or `MinDeposit` was not reached before the end of the `MaximumDepositPeriod`, then Atom holders can send `TxGovClaimDeposit` transaction to claim their deposits.
```Go
type TxGovClaimDeposit struct {
ProposalID int64
}
```
**State modifications:**
If conditions are met, reimburse the deposit, i.e.
- Increase `AtomBalance` of sender by `deposit`
- Set `deposit` of sender in `DepositorsList` to 0
And the associated pseudocode
```
// PSEUDOCODE //
/* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */
upon receiving txGovClaimDeposit from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovClaimDeposit) then
throw
else
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
deposit = load(store, Deposits, <txGovClaimDeposit.ProposalID>:<sender>)
if (deposit == nil)
// sender has not deposited on this proposal
throw
else
if (deposit <= 0)
// deposit has already been claimed
throw
else
if (proposal.VotingStartBlock <= 0)
// Vote never started
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod)
// MaxDepositPeriod is not reached
throw
else
// MaxDepositPeriod is reached
// Set sender's deposit to 0 and refund
store(Deposits, <txGovClaimDeposit.ProposalID>:<sender>, 0)
sender.AtomBalance += deposit
else
// Vote started
initProcedure = load(store, Procedures, proposal.InitProcedureNumber)
if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR
((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then
// Proposal was accepted either because
// Proposal was urgent and special condition was met
// Voting period ended and vote satisfies threshold
store(Deposits, <txGovClaimDeposit.ProposalID>:<sender>, 0)
sender.AtomBalance += deposit
```
#### Vote
Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, bonded Atom holders are able to send `TxGovVote` transactions to cast their vote on the proposal.
```Go
type TxGovVote struct {
ProposalID int64 // proposalID of the proposal
Option string // option from OptionSet chosen by the voter
ValidatorPubKey crypto.PubKey // PubKey of the validator voter wants to tie its vote to
}
```
**State modifications:**
- If sender is not a validator and validator has not voted, initialize or increase minus of validator by sender's `voting power`
- If sender is not a validator and validator has voted, decrease `proposal.Votes['validatorOption']` by sender's `voting power`
- If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` by sender's `voting power`
- If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by validator's `InitialVotingPower - minus` (`minus` can be equal to 0)
Votes need to be tied to a validator in order to compute validator's voting power. If a delegator is bonded to multiple validators, it will have to send one transaction per validator (the UI should facilitate this so that multiple transactions can be sent in one "vote flow").
If the sender is the validator itself, then it will input its own GovernancePubKey as `ValidatorPubKey`
Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled:
```
// PSEUDOCODE //
// Check if TxGovVote is valid. If it is, count vote//
upon receiving txGovVote from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit) then
throw
else
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened
validator = load(store, Validators, txGovVote.ValidatorPubKey)
if !initProcedure.OptionSet.includes(txGovVote.Option) OR
(validator == nil) then
// Throws if
// Option is not in Option Set of procedure that was active when vote opened OR if
// ValidatorPubKey is not the GovPubKey of a current validator
throw
else
option = load(store, Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey>)
if (option != nil)
// sender has already voted with the Atoms bonded to ValidatorPubKey
throw
else
if (proposal.VotingStartBlock < 0) OR
(CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR
(proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then
// Throws if
// Vote has not started OR if
// Vote had ended OR if
// sender bonded Atoms to ValidatorPubKey after start of vote OR if
// sender unbonded Atoms from ValidatorPubKey after start of vote OR if
// proposal is urgent and special condition is met, i.e. proposal is accepted and closed
throw
else
validatorGovInfo = load(store, ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorGovPubKey>)
if (validatorGovInfo == nil)
// validator became validator after proposal entered voting period
throw
else
// sender can vote, check if sender == validator and store sender's option in Options
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey>, txGovVote.Option)
if (sender != validator.GovPubKey)
// Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey
if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then
// check in Staking module
throw
else
validatorOption = load(store, Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey)
if (validatorOption == nil)
// Validator has not voted already
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorPubKey)
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorGovPubKey>, validatorGovInfo)
else
// Validator has already voted
// Reduce votes of option chosen by validator by sender's bonded Amount
proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey)
// increase votes of option chosen by sender by bonded Amount
proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey)
else
// sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey
// i.e. sender == validator
proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
```
## Future improvements (not in scope for MVP)
The current documentation only describes the minimum viable product for the governance module. Future improvements may include:
- **`BountyProposals`:** If accepted, a `BountyProposal` creates an open bounty. The `BountyProposal` specifies how many Atoms will be given upon completion. These Atoms will be taken from the `reserve pool`. After a `BountyProposal` is accepted by governance, anybody can submit a `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a `BountyProposal` is accepted, the corresponding funds in the `reserve pool` are locked so that payment can always be honored. In order to link a `SoftwareUpgradeProposal` to an open bounty, the submitter of the `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by governance, the funds that were reserved are automatically transferred to the submitter.
- **Complex delegation:** Delegators could choose other representatives than their validators. Ultimately, the chain of representatives would always end up to a validator, but delegators could inherit the vote of their chosen representative before they inherit the vote of their validator. In other words, they would only inherit the vote of their validator if their other appointed representative did not vote.
- **`ParameterProposals` and `WhitelistProposals`:** These proposals would automatically change pre-defined parameters and whitelists. Upon acceptance, these proposals would not require validators to do the signal and switch process.
- **Better process for proposal review:** There would be two parts to `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to reward third party auditors.

View File

@ -68,7 +68,7 @@ func main() {
// add proxy, version and key info
basecliCmd.AddCommand(
client.LineBreak,
lcd.ServeCommand(),
lcd.ServeCommand(cdc),
keys.Commands(),
client.LineBreak,
version.VersionCmd,

263
glide.lock generated Normal file
View File

@ -0,0 +1,263 @@
hash: bff8e6213ad8494602f2095adde9bdbab0fd891345675920175cf05c65702e07
updated: 2018-03-02T12:01:38.719098766-05:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
- name: github.com/btcsuite/btcd
version: 2be2f12b358dc57d70b8f501b00be450192efbc3
subpackages:
- btcec
- name: github.com/ebuchman/fail-test
version: 95f809107225be108efcf10a3509e4ea6ceef3c4
- name: github.com/fsnotify/fsnotify
version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
- name: github.com/go-kit/kit
version: 4dc7be5d2d12881735283bcab7352178e190fc71
subpackages:
- log
- log/level
- log/term
- name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-stack/stack
version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc
- name: github.com/gogo/protobuf
version: 1adfc126b41513cc696b209667c8656ea7aac67c
subpackages:
- gogoproto
- jsonpb
- proto
- protoc-gen-gogo/descriptor
- sortkeys
- types
- name: github.com/golang/protobuf
version: 925541529c1fa6821df4e44ce2723319eb2be768
subpackages:
- proto
- ptypes
- ptypes/any
- ptypes/duration
- ptypes/timestamp
- name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9
- name: github.com/gorilla/websocket
version: 0647012449a1878977514a346b26637dd022446c
- name: github.com/hashicorp/hcl
version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8
subpackages:
- hcl/ast
- hcl/parser
- hcl/scanner
- hcl/strconv
- hcl/token
- json/parser
- json/scanner
- json/token
- name: github.com/howeyc/crc16
version: 2b2a61e366a66d3efb279e46176e7291001e0354
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/jmhodges/levigo
version: c42d9e0ca023e2198120196f842701bb4c55d7b9
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/magiconair/properties
version: 2c9e9502788518c97fe44e8955cd069417ee89df
- name: github.com/mattn/go-isatty
version: 0360b2af4f38e8d38c7fce2a9f4e702702d73a39
- name: github.com/mitchellh/mapstructure
version: 00c29f56e2386353d58c599509e8dc3801b0d716
- name: github.com/pelletier/go-toml
version: 05bcc0fb0d3e60da4b8dd5bd7e0ea563eb4ca943
- name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/rcrowley/go-metrics
version: 8732c616f52954686704c8645fe1a9d59e9df7c1
- name: github.com/rigelrozanski/common
version: f691f115798593d783b9999b1263c2f4ffecc439
- name: github.com/spf13/afero
version: bbf41cb36dffe15dff5bf7e18c447801e7ffe163
subpackages:
- mem
- name: github.com/spf13/cast
version: 8965335b8c7107321228e3e3702cab9832751bac
- name: github.com/spf13/cobra
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
- name: github.com/spf13/jwalterweatherman
version: 7c0cea34c8ece3fbeb2b27ab9b59511d360fb394
- name: github.com/spf13/pflag
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
- name: github.com/spf13/viper
version: 25b30aa063fc18e48662b86996252eabdcf2f0c7
- name: github.com/syndtr/goleveldb
version: c7a14d4b00e222eab6111b4cd1af829c13f53ec2
subpackages:
- leveldb
- leveldb/cache
- leveldb/comparer
- leveldb/errors
- leveldb/filter
- leveldb/iterator
- leveldb/journal
- leveldb/memdb
- leveldb/opt
- leveldb/storage
- leveldb/table
- leveldb/util
- name: github.com/tendermint/abci
version: 9e0e00bef42aebf6b402f66bf0f3dc607de8a6f3
subpackages:
- client
- example/code
- example/kvstore
- server
- types
- name: github.com/tendermint/ed25519
version: d8387025d2b9d158cf4efb07e7ebf814bcce2057
subpackages:
- edwards25519
- extra25519
- name: github.com/tendermint/go-crypto
version: c3e19f3ea26f5c3357e0bcbb799b0761ef923755
subpackages:
- keys
- keys/bcrypt
- keys/words
- keys/words/wordlist
- name: github.com/tendermint/go-wire
version: fa721242b042ecd4c6ed1a934ee740db4f74e45c
subpackages:
- data
- name: github.com/tendermint/iavl
version: 669ff61054a14c4542dbd657ab438800d5630e45
- name: github.com/tendermint/tendermint
version: 3cedd8cf070ef120964ac99367cd69414665604b
subpackages:
- blockchain
- cmd/tendermint/commands
- config
- consensus
- consensus/types
- evidence
- lite
- lite/client
- lite/errors
- lite/files
- lite/proxy
- mempool
- node
- p2p
- p2p/conn
- p2p/pex
- p2p/trust
- p2p/upnp
- proxy
- rpc/client
- rpc/core
- rpc/core/types
- rpc/grpc
- rpc/lib
- rpc/lib/client
- rpc/lib/server
- rpc/lib/types
- state
- state/txindex
- state/txindex/kv
- state/txindex/null
- types
- types/priv_validator
- version
- wire
- name: github.com/tendermint/tmlibs
version: 26f2ab65f82cfc6873c312e8030104c47c05f10e
subpackages:
- autofile
- cli
- cli/flags
- clist
- common
- db
- flowrate
- log
- merkle
- pubsub
- pubsub/query
- name: golang.org/x/crypto
version: 91a49db82a88618983a78a06c1cbd4e00ab749ab
subpackages:
- blowfish
- curve25519
- nacl/box
- nacl/secretbox
- openpgp/armor
- openpgp/errors
- poly1305
- ripemd160
- salsa20/salsa
- name: golang.org/x/net
version: 22ae77b79946ea320088417e4d50825671d82d57
subpackages:
- context
- http2
- http2/hpack
- idna
- internal/timeseries
- lex/httplex
- netutil
- trace
- name: golang.org/x/sys
version: dd2ff4accc098aceecb86b36eaa7829b2a17b1c9
subpackages:
- unix
- name: golang.org/x/text
version: 0b0b1f509072617b86d90971b51da23cc52694f2
subpackages:
- secure/bidirule
- transform
- unicode/bidi
- unicode/norm
- name: google.golang.org/genproto
version: 2c5e7ac708aaa719366570dd82bda44541ca2a63
subpackages:
- googleapis/rpc/status
- name: google.golang.org/grpc
version: f0a1202acdc5c4702be05098d5ff8e9b3b444442
subpackages:
- balancer
- balancer/base
- balancer/roundrobin
- codes
- connectivity
- credentials
- encoding
- encoding/proto
- grpclb/grpc_lb_v1/messages
- grpclog
- internal
- keepalive
- metadata
- naming
- peer
- resolver
- resolver/dns
- resolver/passthrough
- stats
- status
- tap
- transport
- name: gopkg.in/yaml.v2
version: 7f97868eec74b32b0982dd158a51a446d1da7eb5
testImports:
- name: github.com/davecgh/go-spew
version: 8991bc29aa16c548c550c7ff78260e27b9ab7c73
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: 792786c7400a136282c1664665ae0a8db921c6c2
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 12b6f73e6084dad08a7c6e575284b177ecafbc71
subpackages:
- assert
- require

54
glide.yaml Normal file
View File

@ -0,0 +1,54 @@
package: github.com/cosmos/cosmos-sdk
import:
- package: github.com/golang/protobuf
version: ^1.0.0
subpackages:
- proto
- package: github.com/bgentry/speakeasy
version: ^0.1.0
- package: github.com/mattn/go-isatty
version: ~0.0.3
- package: github.com/pkg/errors
version: ^0.8.0
- package: github.com/rigelrozanski/common
- package: github.com/tendermint/abci
version: develop
subpackages:
- server
- types
- package: github.com/tendermint/go-crypto
version: v0.5.0
- package: github.com/tendermint/go-wire
version: v0.7.3
- package: github.com/tendermint/iavl
version: v0.6.1
- package: github.com/tendermint/tmlibs
version: develop
subpackages:
- common
- db
- log
- merkle
- package: github.com/tendermint/tendermint
version: develop
subpackages:
- cmd/tendermint/commands
- config
- lite
- rpc/client
- types
- package: golang.org/x/crypto
subpackages:
- ripemd160
- package: github.com/spf13/pflag
version: v1.0.0
- package: github.com/spf13/cobra
version: v0.0.1
- package: github.com/spf13/viper
version: ^1.0.0
testImport:
- package: github.com/stretchr/testify
version: ^1.2.1
subpackages:
- assert
- require

View File

@ -2,6 +2,7 @@ package version
import (
"fmt"
"net/http"
"github.com/spf13/cobra"
)
@ -11,14 +12,28 @@ var (
VersionCmd = &cobra.Command{
Use: "version",
Short: "Print the app version",
Run: doVersionCmd,
Run: printVersion,
}
)
func doVersionCmd(cmd *cobra.Command, args []string) {
func getVersion() string {
v := Version
if GitCommit != "" {
v = v + " " + GitCommit
}
return v
}
// CMD
func printVersion(cmd *cobra.Command, args []string) {
v := getVersion()
fmt.Println(v)
}
// REST
func VersionRequestHandler(w http.ResponseWriter, r *http.Request) {
v := getVersion()
w.Write([]byte(v))
}