Moved basecoin server commands out of examples dir to share them

This commit is contained in:
Ethan Frey
2017-09-04 16:50:09 +02:00
parent 23a539ae6f
commit ec6843928e
11 changed files with 4 additions and 12 deletions
+147
View File
@@ -0,0 +1,147 @@
package commands
import (
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/config"
cmn "github.com/tendermint/tmlibs/common"
)
// InitCmd - node initialization command
var InitCmd = &cobra.Command{
Use: "init [address]",
Short: "Initialize a basecoin blockchain",
RunE: initCmd,
}
//nolint - flags
var (
FlagChainID = "chain-id" //TODO group with other flags or remove? is this already a flag here?
)
func init() {
InitCmd.Flags().String(FlagChainID, "test_chain_id", "Chain ID")
}
// returns 1 iff it set a file, otherwise 0 (so we can add them)
func setupFile(path, data string, perm os.FileMode) (int, error) {
_, err := os.Stat(path)
if !os.IsNotExist(err) { //note, os.IsExist(err) != !os.IsNotExist(err)
return 0, nil
}
err = ioutil.WriteFile(path, []byte(data), perm)
if err != nil {
return 0, err
}
return 1, nil
}
func initCmd(cmd *cobra.Command, args []string) error {
// this will ensure that config.toml is there if not yet created, and create dir
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
if len(args) != 1 {
return fmt.Errorf("`init` takes one argument, a basecoin account address. Generate one using `basecli keys new mykey`")
}
userAddr := args[0]
// verify this account is correct
data, err := hex.DecodeString(cmn.StripHex(userAddr))
if err != nil {
return errors.Wrap(err, "Invalid address")
}
if len(data) != 20 {
return errors.New("Address must be 20-bytes in hex")
}
genesis := GetGenesisJSON(viper.GetString(FlagChainID), userAddr)
return CreateGenesisValidatorFiles(cfg, genesis, cmd.Root().Name())
}
// CreateGenesisValidatorFiles creates a genesis file with these
// contents and a private validator file
func CreateGenesisValidatorFiles(cfg *config.Config, genesis, appName string) error {
genesisFile := cfg.GenesisFile()
privValFile := cfg.PrivValidatorFile()
mod1, err := setupFile(genesisFile, genesis, 0644)
if err != nil {
return err
}
mod2, err := setupFile(privValFile, PrivValJSON, 0400)
if err != nil {
return err
}
if (mod1 + mod2) > 0 {
msg := fmt.Sprintf("Initialized %s", appName)
logger.Info(msg, "genesis", genesisFile, "priv_validator", privValFile)
} else {
logger.Info("Already initialized", "priv_validator", privValFile)
}
return nil
}
// PrivValJSON - validator private key file contents in json
var PrivValJSON = `{
"address": "7A956FADD20D3A5B2375042B2959F8AB172A058F",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": {
"type": "ed25519",
"data": "D07ABE82A8B15559A983B2DB5D4842B2B6E4D6AF58B080005662F424F17D68C17B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
},
"pub_key": {
"type": "ed25519",
"data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
}
}`
// GetGenesisJSON returns a new tendermint genesis with Basecoin app_options
// that grant a large amount of "mycoin" to a single address
// TODO: A better UX for generating genesis files
func GetGenesisJSON(chainID, addr string) string {
return fmt.Sprintf(`{
"app_hash": "",
"chain_id": "%s",
"genesis_time": "0001-01-01T00:00:00.000Z",
"validators": [
{
"amount": 10,
"name": "",
"pub_key": {
"type": "ed25519",
"data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
}
}
],
"app_options": {
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}],
"plugin_options": [
"coin/issuer", {"app": "sigs", "addr": "%s"}
]
}
}`, chainID, addr, addr)
}
+71
View File
@@ -0,0 +1,71 @@
package commands
import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"path"
"strings"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/cli"
)
//---------------------------------------------
// simple implementation of a key
// Address - public address for a key
type Address [20]byte
// MarshalJSON - marshal the json bytes of the address
func (a Address) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%x"`, a[:])), nil
}
// UnmarshalJSON - unmarshal the json bytes of the address
func (a *Address) UnmarshalJSON(addrHex []byte) error {
addr, err := hex.DecodeString(strings.Trim(string(addrHex), `"`))
if err != nil {
return err
}
copy(a[:], addr)
return nil
}
// Key - full private key
type Key struct {
Address Address `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
PrivKey crypto.PrivKey `json:"priv_key"`
}
// Sign - Implements Signer
func (k *Key) Sign(msg []byte) crypto.Signature {
return k.PrivKey.Sign(msg)
}
// LoadKey - load key from json file
func LoadKey(keyFile string) (*Key, error) {
filePath := keyFile
if !strings.HasPrefix(keyFile, "/") && !strings.HasPrefix(keyFile, ".") {
rootDir := viper.GetString(cli.HomeFlag)
filePath = path.Join(rootDir, keyFile)
}
keyJSONBytes, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
key := new(Key)
err = json.Unmarshal(keyJSONBytes, key)
if err != nil {
return nil, fmt.Errorf("Error reading key from %v: %v", filePath, err) //never stack trace
}
return key, nil
}
+298
View File
@@ -0,0 +1,298 @@
package commands
// import (
// "fmt"
// "io/ioutil"
// "strconv"
// "time"
// "github.com/pkg/errors"
// "github.com/spf13/cobra"
// // "github.com/spf13/viper"
// // "github.com/tendermint/tmlibs/cli"
// // "github.com/tendermint/tmlibs/log"
// "github.com/tendermint/go-wire"
// "github.com/tendermint/merkleeyes/iavl"
// cmn "github.com/tendermint/tmlibs/common"
// "github.com/cosmos/cosmos-sdk/plugins/ibc"
// "github.com/cosmos/cosmos-sdk/types"
// "github.com/tendermint/tendermint/rpc/client"
// tmtypes "github.com/tendermint/tendermint/types"
// )
// var RelayCmd = &cobra.Command{
// Use: "relay",
// Short: "Relay ibc packets between two chains",
// }
// var RelayStartCmd = &cobra.Command{
// Use: "start",
// Short: "Start basecoin relayer to relay IBC packets between chains",
// RunE: relayStartCmd,
// }
// var RelayInitCmd = &cobra.Command{
// Use: "init",
// Short: "Register both chains with each other, to prepare the relayer to run",
// RunE: relayInitCmd,
// }
// //flags
// var (
// chain1AddrFlag string
// chain2AddrFlag string
// chain1IDFlag string
// chain2IDFlag string
// fromFileFlag string
// genesisFile1Flag string
// genesisFile2Flag string
// )
// func init() {
// flags := []Flag2Register{
// {&chain1AddrFlag, "chain1-addr", "tcp://localhost:46657", "Node address for chain1"},
// {&chain2AddrFlag, "chain2-addr", "tcp://localhost:36657", "Node address for chain2"},
// {&chain1IDFlag, "chain1-id", "test_chain_1", "ChainID for chain1"},
// {&chain2IDFlag, "chain2-id", "test_chain_2", "ChainID for chain2"},
// {&fromFileFlag, "from", "key.json", "Path to a private key to sign the transaction"},
// }
// RegisterPersistentFlags(RelayCmd, flags)
// initFlags := []Flag2Register{
// {&genesisFile1Flag, "genesis1", "", "Path to genesis file for chain1"},
// {&genesisFile2Flag, "genesis2", "", "Path to genesis file for chain2"},
// }
// RegisterFlags(RelayInitCmd, initFlags)
// RelayCmd.AddCommand(RelayStartCmd)
// RelayCmd.AddCommand(RelayInitCmd)
// }
// func relayStartCmd(cmd *cobra.Command, args []string) error {
// go loop(chain1AddrFlag, chain2AddrFlag, chain1IDFlag, chain2IDFlag)
// go loop(chain2AddrFlag, chain1AddrFlag, chain2IDFlag, chain1IDFlag)
// cmn.TrapSignal(func() {
// // TODO: Cleanup
// })
// return nil
// }
// func relayInitCmd(cmd *cobra.Command, args []string) error {
// err := registerChain(chain1IDFlag, chain1AddrFlag, chain2IDFlag, genesisFile2Flag, fromFileFlag)
// if err != nil {
// return err
// }
// err = registerChain(chain2IDFlag, chain2AddrFlag, chain1IDFlag, genesisFile1Flag, fromFileFlag)
// return err
// }
// func registerChain(chainID, node, registerChainID, registerGenesis, keyFile string) error {
// genesisBytes, err := ioutil.ReadFile(registerGenesis)
// if err != nil {
// return errors.Errorf("Error reading genesis file %v: %v\n", registerGenesis, err)
// }
// ibcTx := ibc.IBCRegisterChainTx{
// ibc.BlockchainGenesis{
// ChainID: registerChainID,
// Genesis: string(genesisBytes),
// },
// }
// privKey, err := LoadKey(keyFile)
// if err != nil {
// return err
// }
// relay := newRelayer(privKey, chainID, node)
// return relay.appTx(ibcTx)
// }
// func loop(addr1, addr2, id1, id2 string) {
// nextSeq := 0
// // load the priv key
// privKey, err := LoadKey(fromFileFlag)
// if err != nil {
// logger.Error(err.Error())
// cmn.PanicCrisis(err.Error())
// }
// // relay from chain1 to chain2
// thisRelayer := newRelayer(privKey, id2, addr2)
// logger.Info(fmt.Sprintf("Relaying from chain %v on %v to chain %v on %v", id1, addr1, id2, addr2))
// httpClient := client.NewHTTP(addr1, "/websocket")
// OUTER:
// for {
// time.Sleep(time.Second)
// // get the latest ibc packet sequence number
// key := fmt.Sprintf("ibc,egress,%v,%v", id1, id2)
// query, err := queryWithClient(httpClient, []byte(key))
// if err != nil {
// logger.Error("Error querying for latest sequence", "key", key, "error", err.Error())
// continue OUTER
// }
// if len(query.Value) == 0 {
// // nothing yet
// continue OUTER
// }
// seq, err := strconv.ParseUint(string(query.Value), 10, 64)
// if err != nil {
// logger.Error("Error parsing sequence number from query", "query.Value", query.Value, "error", err.Error())
// continue OUTER
// }
// seq -= 1 // seq is the packet count. -1 because 0-indexed
// if nextSeq <= int(seq) {
// logger.Info("Got new packets", "last-sequence", nextSeq-1, "new-sequence", seq)
// }
// // get all packets since the last one we relayed
// for ; nextSeq <= int(seq); nextSeq++ {
// key := fmt.Sprintf("ibc,egress,%v,%v,%d", id1, id2, nextSeq)
// query, err := queryWithClient(httpClient, []byte(key))
// if err != nil {
// logger.Error("Error querying for packet", "seqeuence", nextSeq, "key", key, "error", err.Error())
// continue OUTER
// }
// var packet ibc.Packet
// err = wire.ReadBinaryBytes(query.Value, &packet)
// if err != nil {
// logger.Error("Error unmarshalling packet", "key", key, "query.Value", query.Value, "error", err.Error())
// continue OUTER
// }
// proof := new(iavl.IAVLProof)
// err = wire.ReadBinaryBytes(query.Proof, &proof)
// if err != nil {
// logger.Error("Error unmarshalling proof", "query.Proof", query.Proof, "error", err.Error())
// continue OUTER
// }
// // query.Height is actually for the next block,
// // so wait a block before we fetch the header & commit
// if err := waitForBlock(httpClient); err != nil {
// logger.Error("Error waiting for a block", "addr", addr1, "error", err.Error())
// continue OUTER
// }
// // get the header and commit from the height the query was done at
// res, err := httpClient.Commit(int(query.Height))
// if err != nil {
// logger.Error("Error fetching header and commits", "height", query.Height, "error", err.Error())
// continue OUTER
// }
// // update the chain state on the other chain
// updateTx := ibc.IBCUpdateChainTx{
// Header: *res.Header,
// Commit: *res.Commit,
// }
// logger.Info("Updating chain", "src-chain", id1, "height", res.Header.Height, "appHash", res.Header.AppHash)
// if err := thisRelayer.appTx(updateTx); err != nil {
// logger.Error("Error creating/sending IBCUpdateChainTx", "error", err.Error())
// continue OUTER
// }
// // relay the packet and proof
// logger.Info("Relaying packet", "src-chain", id1, "height", query.Height, "sequence", nextSeq)
// postTx := ibc.IBCPacketPostTx{
// FromChainID: id1,
// FromChainHeight: query.Height,
// Packet: packet,
// Proof: proof,
// }
// if err := thisRelayer.appTx(postTx); err != nil {
// logger.Error("Error creating/sending IBCPacketPostTx", "error", err.Error())
// // dont `continue OUTER` here. the error might be eg. Already exists
// // TODO: catch this programmatically ?
// }
// }
// }
// }
// type relayer struct {
// privKey *Key
// chainID string
// nodeAddr string
// client *client.HTTP
// }
// func newRelayer(privKey *Key, chainID, nodeAddr string) *relayer {
// httpClient := client.NewHTTP(nodeAddr, "/websocket")
// return &relayer{
// privKey: privKey,
// chainID: chainID,
// nodeAddr: nodeAddr,
// client: httpClient,
// }
// }
// func (r *relayer) appTx(ibcTx ibc.IBCTx) error {
// acc, err := getAccWithClient(r.client, r.privKey.Address[:])
// if err != nil {
// return err
// }
// sequence := acc.Sequence + 1
// data := []byte(wire.BinaryBytes(struct {
// ibc.IBCTx `json:"unwrap"`
// }{ibcTx}))
// smallCoins := coin.Coin{"mycoin", 1}
// input := types.NewTxInput(r.privKey.PubKey, coin.Coins{smallCoins}, sequence)
// tx := &types.AppTx{
// Gas: 0,
// Fee: smallCoins,
// Name: "IBC",
// Input: input,
// Data: data,
// }
// tx.Input.Signature = r.privKey.Sign(tx.SignBytes(r.chainID))
// txBytes := []byte(wire.BinaryBytes(struct {
// types.Tx `json:"unwrap"`
// }{tx}))
// data, log, err := broadcastTxWithClient(r.client, txBytes)
// if err != nil {
// return err
// }
// _, _ = data, log
// return nil
// }
// // broadcast the transaction to tendermint
// func broadcastTxWithClient(httpClient *client.HTTP, tx tmtypes.Tx) ([]byte, string, error) {
// res, err := httpClient.BroadcastTxCommit(tx)
// if err != nil {
// return nil, "", errors.Errorf("Error on broadcast tx: %v", err)
// }
// if !res.CheckTx.Code.IsOK() {
// r := res.CheckTx
// return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
// }
// if !res.DeliverTx.Code.IsOK() {
// r := res.DeliverTx
// return nil, "", errors.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
// }
// return res.DeliverTx.Data, res.DeliverTx.Log, nil
// }
+23
View File
@@ -0,0 +1,23 @@
package commands
import (
"github.com/spf13/cobra"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
)
// UnsafeResetAllCmd - extension of the tendermint command, resets initialization
var UnsafeResetAllCmd = &cobra.Command{
Use: "unsafe_reset_all",
Short: "Reset all blockchain data",
RunE: unsafeResetAllCmd,
}
func unsafeResetAllCmd(cmd *cobra.Command, args []string) error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
tcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), logger)
return nil
}
+43
View File
@@ -0,0 +1,43 @@
package commands
import (
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tmlibs/cli"
tmflags "github.com/tendermint/tmlibs/cli/flags"
"github.com/tendermint/tmlibs/log"
)
//nolint
const (
defaultLogLevel = "error"
FlagLogLevel = "log_level"
)
var (
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main")
)
// RootCmd - main node command
var RootCmd = &cobra.Command{
Use: "basecoin",
Short: "A cryptocurrency framework in Golang based on Tendermint-Core",
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
level := viper.GetString(FlagLogLevel)
logger, err = tmflags.ParseLogLevel(level, logger, defaultLogLevel)
if err != nil {
return err
}
if viper.GetBool(cli.TraceFlag) {
logger = log.NewTracingLogger(logger)
}
return nil
},
}
func init() {
RootCmd.PersistentFlags().String(FlagLogLevel, defaultLogLevel, "Log level")
}
+132
View File
@@ -0,0 +1,132 @@
package commands
import (
"fmt"
"os"
"path"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/abci/server"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/proxy"
"github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/app"
)
// StartCmd - command to start running the basecoin node!
var StartCmd = &cobra.Command{
Use: "start",
Short: "Start basecoin",
RunE: startCmd,
}
// nolint TODO: move to config file
const EyesCacheSize = 10000
//nolint
const (
FlagAddress = "address"
FlagWithoutTendermint = "without-tendermint"
)
var (
// Handler - use a global to store the handler, so we can set it in main.
// TODO: figure out a cleaner way to register plugins
Handler sdk.Handler
)
func init() {
flags := StartCmd.Flags()
flags.String(FlagAddress, "tcp://0.0.0.0:46658", "Listen address")
flags.Bool(FlagWithoutTendermint, false, "Only run basecoin abci app, assume external tendermint process")
// add all standard 'tendermint node' flags
tcmd.AddNodeFlags(StartCmd)
}
func startCmd(cmd *cobra.Command, args []string) error {
rootDir := viper.GetString(cli.HomeFlag)
store, err := app.NewStore(
path.Join(rootDir, "data", "merkleeyes.db"),
EyesCacheSize,
logger.With("module", "store"),
)
if err != nil {
return err
}
// Create Basecoin app
basecoinApp := app.NewBasecoin(Handler, store, logger.With("module", "app"))
// if chain_id has not been set yet, load the genesis.
// else, assume it's been loaded
if basecoinApp.GetChainID() == "" {
// If genesis file exists, set key-value options
genesisFile := path.Join(rootDir, "genesis.json")
if _, err := os.Stat(genesisFile); err == nil {
err := basecoinApp.LoadGenesis(genesisFile)
if err != nil {
return errors.Errorf("Error in LoadGenesis: %v\n", err)
}
} else {
fmt.Printf("No genesis file at %s, skipping...\n", genesisFile)
}
}
chainID := basecoinApp.GetChainID()
if viper.GetBool(FlagWithoutTendermint) {
logger.Info("Starting Basecoin without Tendermint", "chain_id", chainID)
// run just the abci app/server
return startBasecoinABCI(basecoinApp)
}
logger.Info("Starting Basecoin with Tendermint", "chain_id", chainID)
// start the app with tendermint in-process
return startTendermint(rootDir, basecoinApp)
}
func startBasecoinABCI(basecoinApp *app.Basecoin) error {
// Start the ABCI listener
addr := viper.GetString(FlagAddress)
svr, err := server.NewServer(addr, "socket", basecoinApp)
if err != nil {
return errors.Errorf("Error creating listener: %v\n", err)
}
svr.SetLogger(logger.With("module", "abci-server"))
svr.Start()
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
svr.Stop()
})
return nil
}
func startTendermint(dir string, basecoinApp *app.Basecoin) error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
// Create & start tendermint node
privValidator := types.LoadOrGenPrivValidator(cfg.PrivValidatorFile(), logger)
n := node.NewNode(cfg, privValidator, proxy.NewLocalClientCreator(basecoinApp), logger.With("module", "node"))
_, err = n.Start()
if err != nil {
return err
}
// Trap signal, run forever.
n.RunForever()
return nil
}