Merge remote-tracking branch 'origin/develop' into rigel/stake-cons-addr

This commit is contained in:
rigelrozanski 2018-09-22 13:42:37 -04:00
commit a2b2ef92f2
13 changed files with 519 additions and 21 deletions

View File

@ -4,6 +4,7 @@ BREAKING CHANGES
* Gaia REST API (`gaiacli advanced rest-server`)
* [x/stake] Validator.Owner renamed to Validator.Operator
* [\#595](https://github.com/cosmos/cosmos-sdk/issues/595) Connections to the REST server are now secured using Transport Layer Security by default. The --insecure flag is provided to switch back to insecure HTTP.
* Gaia CLI (`gaiacli`)
* [x/stake] Validator.Owner renamed to Validator.Operator
@ -49,6 +50,7 @@ BREAKING CHANGES
* [store] Change storeInfo within the root multistore to use tmhash instead of ripemd160 \#2308
* [codec] \#2324 All referrences to wire have been renamed to codec. Additionally, wire.NewCodec is now codec.New().
* [types] \#2343 Make sdk.Msg have a names field, to facilitate automatic tagging.
* [baseapp] \#2366 Automatically add action tags to all messages
* [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index
* Tendermint

View File

@ -539,6 +539,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
if mode != runTxModeCheck {
msgResult = handler(ctx, msg)
}
msgResult.Tags = append(msgResult.Tags, sdk.MakeTag("action", []byte(msg.Name())))
// NOTE: GasWanted is determined by ante handler and
// GasUsed by the GasMeter

View File

@ -94,13 +94,15 @@ func createCertifier() tmlite.Certifier {
errMsg.WriteString("--node ")
}
if errMsg.Len() != 0 {
fmt.Printf("must specify these options: %s when --trust-node is false\n", errMsg.String())
fmt.Printf("Must specify these options: %s when --trust-node is false\n", errMsg.String())
os.Exit(1)
}
certifier, err := tmliteProxy.GetCertifier(chainID, home, nodeURI)
if err != nil {
panic(err)
fmt.Printf("Create certifier failed: %s\n", err.Error())
fmt.Printf("Please check network connection and verify the address of the node to connect to\n")
os.Exit(1)
}
return certifier

174
client/lcd/certificates.go Normal file
View File

@ -0,0 +1,174 @@
package lcd
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
"strings"
"time"
)
// default: 30 days
const defaultValidFor = 30 * 24 * time.Hour
func generateSelfSignedCert(host string) (certBytes []byte, priv *ecdsa.PrivateKey, err error) {
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
notBefore := time.Now()
notAfter := notBefore.Add(defaultValidFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
err = fmt.Errorf("failed to generate serial number: %s", err)
return
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Gaia Lite"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
hosts := strings.Split(host, ",")
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
certBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
err = fmt.Errorf("couldn't create certificate: %s", err)
return
}
return
}
func writeCertAndPrivKey(certBytes []byte, priv *ecdsa.PrivateKey) (certFile string, keyFile string, err error) {
if priv == nil {
err = errors.New("private key is nil")
return
}
certFile, err = writeCertificateFile(certBytes)
if err != nil {
return
}
keyFile, err = writeKeyFile(priv)
return
}
func writeCertificateFile(certBytes []byte) (filename string, err error) {
f, err := ioutil.TempFile("", "cert_")
if err != nil {
return
}
defer f.Close()
filename = f.Name()
if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
return filename, fmt.Errorf("failed to write data to %s: %s", filename, err)
}
return
}
func writeKeyFile(priv *ecdsa.PrivateKey) (filename string, err error) {
f, err := ioutil.TempFile("", "key_")
if err != nil {
return
}
defer f.Close()
filename = f.Name()
block, err := pemBlockForKey(priv)
if err != nil {
return
}
if err := pem.Encode(f, block); err != nil {
return filename, fmt.Errorf("failed to write data to %s: %s", filename, err)
}
return
}
func pemBlockForKey(priv *ecdsa.PrivateKey) (*pem.Block, error) {
b, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("unable to marshal ECDSA private key: %v", err)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
}
func genCertKeyFilesAndReturnFingerprint(sslHosts string) (certFile, keyFile string, fingerprint string, err error) {
certBytes, priv, err := generateSelfSignedCert(sslHosts)
if err != nil {
return
}
certFile, keyFile, err = writeCertAndPrivKey(certBytes, priv)
cleanupFunc := func() {
os.Remove(certFile)
os.Remove(keyFile)
}
// Either of the files could have been written already,
// thus clean up regardless of the error.
if err != nil {
defer cleanupFunc()
return
}
fingerprint, err = fingerprintForCertificate(certBytes)
if err != nil {
defer cleanupFunc()
return
}
return
}
func fingerprintForCertificate(certBytes []byte) (string, error) {
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return "", err
}
h := sha256.New()
h.Write(cert.Raw)
fingerprintBytes := h.Sum(nil)
var buf bytes.Buffer
for i, b := range fingerprintBytes {
if i > 0 {
fmt.Fprintf(&buf, ":")
}
fmt.Fprintf(&buf, "%02X", b)
}
return fmt.Sprintf("SHA256 Fingerprint=%s", buf.String()), nil
}
func fingerprintFromFile(certFile string) (string, error) {
f, err := os.Open(certFile)
if err != nil {
return "", err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return "", err
}
block, _ := pem.Decode(data)
if block == nil {
return "", fmt.Errorf("couldn't find PEM data in %s", certFile)
}
return fingerprintForCertificate(block.Bytes)
}

View File

@ -0,0 +1,93 @@
package lcd
import (
"crypto/ecdsa"
"crypto/x509"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestGenerateSelfSignedCert(t *testing.T) {
host := "127.0.0.1,localhost,::1"
certBytes, _, err := generateSelfSignedCert(host)
require.Nil(t, err)
cert, err := x509.ParseCertificate(certBytes)
require.Nil(t, err)
require.Equal(t, 2, len(cert.IPAddresses))
require.Equal(t, 1, len(cert.DNSNames))
require.True(t, cert.IsCA)
}
func TestWriteCertAndPrivKey(t *testing.T) {
expectedPerm := "-rw-------"
derBytes, priv, err := generateSelfSignedCert("localhost")
require.Nil(t, err)
type args struct {
certBytes []byte
priv *ecdsa.PrivateKey
}
tests := []struct {
name string
args args
wantErr bool
}{
{"valid certificate", args{derBytes, priv}, false},
{"garbage", args{[]byte("some garbage"), nil}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCertFile, gotKeyFile, err := writeCertAndPrivKey(tt.args.certBytes, tt.args.priv)
defer os.Remove(gotCertFile)
defer os.Remove(gotKeyFile)
if tt.wantErr {
require.NotNil(t, err)
return
}
require.Nil(t, err)
info, err := os.Stat(gotCertFile)
require.Nil(t, err)
require.True(t, info.Mode().IsRegular())
require.Equal(t, expectedPerm, info.Mode().String())
info, err = os.Stat(gotKeyFile)
require.Nil(t, err)
require.True(t, info.Mode().IsRegular())
require.Equal(t, expectedPerm, info.Mode().String())
})
}
}
func TestFingerprintFromFile(t *testing.T) {
cert := `-----BEGIN CERTIFICATE-----
MIIBbDCCARGgAwIBAgIQSuFKYv/22v+cxtVgMUrQADAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE4MDkyMDIzNDQyNloXDTE5MDkyMDIzNDQyNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDIo
ujAesRczcPVAWiLhpeV1B7hS/RI2LJaGj3QjyJ8hiUthJTPIamr8m7LuS/U5fS0o
hY297YeTIGo9YkxClICjSTBHMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZI
zj0EAwIDSQAwRgIhAKnwbhX9FrGG1otCVLwhClQ3RaLxnNpCgIGTqSimb34cAiEA
stMN+IqMCKWlZyGqxGIiyksMLMEU3lRqKNQn2EoAZJY=
-----END CERTIFICATE-----`
wantFingerprint := `SHA256 Fingerprint=0B:ED:9A:AA:A2:D1:7E:B2:53:56:F6:FC:C0:E6:1A:69:70:21:A2:B0:90:FC:AF:BB:EF:AE:2C:78:52:AB:68:40`
certFile, err := ioutil.TempFile("", "test_cert_")
require.Nil(t, err)
_, err = certFile.Write([]byte(cert))
require.Nil(t, err)
err = certFile.Close()
require.Nil(t, err)
defer os.Remove(certFile.Name())
fingerprint, err := fingerprintFromFile(certFile.Name())
require.Nil(t, err)
require.Equal(t, wantFingerprint, fingerprint)
// test failure
emptyFile, err := ioutil.TempFile("", "test_cert_")
require.Nil(t, err)
err = emptyFile.Close()
require.Nil(t, err)
defer os.Remove(emptyFile.Name())
_, err = fingerprintFromFile(emptyFile.Name())
require.NotNil(t, err)
}

View File

@ -1,6 +1,8 @@
package lcd
import (
"errors"
"net"
"net/http"
"os"
@ -23,35 +25,84 @@ import (
tmserver "github.com/tendermint/tendermint/rpc/lib/server"
)
const (
flagListenAddr = "laddr"
flagCORS = "cors"
flagMaxOpenConnections = "max-open"
flagInsecure = "insecure"
flagSSLHosts = "ssl-hosts"
flagSSLCertFile = "ssl-certfile"
flagSSLKeyFile = "ssl-keyfile"
)
// ServeCommand will generate a long-running rest server
// (aka Light Client Daemon) that exposes functionality similar
// to the cli, but over rest
func ServeCommand(cdc *codec.Codec) *cobra.Command {
flagListenAddr := "laddr"
flagCORS := "cors"
flagMaxOpenConnections := "max-open"
cmd := &cobra.Command{
Use: "rest-server",
Short: "Start LCD (light-client daemon), a local REST server",
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) (err error) {
listenAddr := viper.GetString(flagListenAddr)
handler := createHandler(cdc)
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server")
maxOpen := viper.GetInt(flagMaxOpenConnections)
sslHosts := viper.GetString(flagSSLHosts)
certFile := viper.GetString(flagSSLCertFile)
keyFile := viper.GetString(flagSSLKeyFile)
cleanupFunc := func() {}
listener, err := tmserver.StartHTTPServer(
listenAddr, handler, logger,
tmserver.Config{MaxOpenConnections: maxOpen},
)
if err != nil {
return err
var listener net.Listener
var fingerprint string
if viper.GetBool(flagInsecure) {
listener, err = tmserver.StartHTTPServer(
listenAddr, handler, logger,
tmserver.Config{MaxOpenConnections: maxOpen},
)
if err != nil {
return
}
} else {
if certFile != "" {
// validateCertKeyFiles() is needed to work around tendermint/tendermint#2460
err = validateCertKeyFiles(certFile, keyFile)
if err != nil {
return err
}
// cert/key pair is provided, read the fingerprint
fingerprint, err = fingerprintFromFile(certFile)
if err != nil {
return err
}
} else {
// if certificate is not supplied, generate a self-signed one
certFile, keyFile, fingerprint, err = genCertKeyFilesAndReturnFingerprint(sslHosts)
if err != nil {
return err
}
cleanupFunc = func() {
os.Remove(certFile)
os.Remove(keyFile)
}
defer cleanupFunc()
}
listener, err = tmserver.StartHTTPAndTLSServer(
listenAddr, handler,
certFile, keyFile,
logger,
tmserver.Config{MaxOpenConnections: maxOpen},
)
if err != nil {
return
}
logger.Info(fingerprint)
}
logger.Info("REST server started")
// wait forever and cleanup
cmn.TrapSignal(func() {
defer cleanupFunc()
err := listener.Close()
logger.Error("error closing listener", "err", err)
})
@ -61,6 +112,10 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command {
}
cmd.Flags().String(flagListenAddr, "tcp://localhost:1317", "The address for the server to listen on")
cmd.Flags().Bool(flagInsecure, false, "Do not set up SSL/TLS layer")
cmd.Flags().String(flagSSLHosts, "", "Comma-separated hostnames and IPs to generate a certificate for")
cmd.Flags().String(flagSSLCertFile, "", "Path to a SSL certificate file. If not supplied, a self-signed certificate will be generated.")
cmd.Flags().String(flagSSLKeyFile, "", "Path to a key file; ignored if a certificate file is not supplied.")
cmd.Flags().String(flagCORS, "", "Set the domains that can make CORS requests (* for all)")
cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node")
cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to")
@ -95,3 +150,16 @@ func createHandler(cdc *codec.Codec) http.Handler {
return r
}
func validateCertKeyFiles(certFile, keyFile string) error {
if keyFile == "" {
return errors.New("a key file is required")
}
if _, err := os.Stat(certFile); err != nil {
return err
}
if _, err := os.Stat(keyFile); err != nil {
return err
}
return nil
}

View File

@ -12,7 +12,6 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
tmTypes "github.com/tendermint/tendermint/types"
tmtypes "github.com/tendermint/tendermint/types"
)
@ -78,7 +77,7 @@ func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) {
return nil, err
}
if !bytes.Equal(check.ValidatorsHash(), tmTypes.NewValidatorSet(validatorsRes.Validators).Hash()) {
if !bytes.Equal(check.ValidatorsHash(), tmtypes.NewValidatorSet(validatorsRes.Validators).Hash()) {
return nil, fmt.Errorf("got invalid validatorset")
}
}

View File

@ -0,0 +1,3 @@
export default ({ router }) => {
router.addRoutes([{ path: "/testnet/", redirect: "/" }])
}

View File

@ -0,0 +1,4 @@
$accentColor = #304DE9
$textColor = #15192C
$borderColor = #eaecef
$codeBgColor = #282c34

View File

@ -24,7 +24,7 @@ on the website.
## Config.js
The [config.js](./config.js) generates the sidebar and Table of Contents
The [config.js](./.vuepress/config.js) generates the sidebar and Table of Contents
on the website docs. Note the use of relative links and the omission of
file extensions. Additional features are available to improve the look
of the sidebar.
@ -59,9 +59,34 @@ to send users to the GitHub.
## Building Locally
Not currently possible but coming soon! Doing so requires
assets held in the (private) website repo, installing
[VuePress](https://vuepress.vuejs.org/), and modifying the `config.js`.
To build and serve the documentation locally, run:
```
npm install -g vuepress
```
then change the following line in the `config.js`:
```
base: "/docs/",
```
to:
```
base: "/",
```
Finally, go up one directory to the root of the repo and run:
```
# from root of repo
vuepress build docs
cd dist/docs
python -m SimpleHTTPServer 8080
```
then navigate to localhost:8080 in your browser.
## Consistency

View File

@ -0,0 +1,111 @@
# Integrate a Cosmos-SDK based blockchain as a Service Provider
We define 'service providers' as entities providing services for end-users that involve some form of interaction with a Cosmos-SDK based blockchain (this includes the Cosmos Hub). More specifically, this document will be focused around interactions with tokens.
This section does not concern wallet builders that want to provide [Light-Client](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/light) functionalities. Service providers are expected to act as trusted point of contact to the blockchain for their end-users.
## High-level description of the architecture
There are three main pieces to consider:
- Full-nodes: To interact with the blockchain.
- Rest Server: This acts as a relayer for HTTP calls.
- Rest API: Define available endpoints for the Rest Server.
## Running a Full-Node
### Installation and configuration
We will describe the steps to run and interract with a full-node for the Cosmos Hub. For other SDK-based blockchain, the process should be similar.
First, you need to [install the software](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/getting-started/installation.md).
Then, you can start [running a full-node](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/getting-started/full-node.md).
### Command-Line interface
Next you will find a few useful CLI commands to interact with the Full-Node.
#### Creating a key-pair
To generate a new key (default ed25519 elliptic curve):
```bash
gaiacli keys add <your_key_name>
```
You will be asked to create a passwords (at least 8 characters) for this key-pair. The command returns 4 informations:
- `NAME`: Name of your key
- `ADDRESS`: Your address. Used to receive funds.
- `PUBKEY`: Your public key. Useful for validators.
- `Seed phrase`: 12-words phrase. **Save this seed phrase somewhere safe**. It is used to recover your private key in case you forget the password.
You can see all your available keys by typing:
```bash
gaiacli keys list
```
#### Checking your balance
After receiving tokens to your address, you can view your account's balance by typing:
```bash
gaiacli account <YOUR_ADDRESS>
```
*Note: When you query an account balance with zero tokens, you will get this error: No account with address <YOUR_ADDRESS> was found in the state. This is expected! We're working on improving our error messages.*
#### Sending coins via the CLI
Here is the command to send coins via the CLI:
```bash
gaiacli send --amount=10faucetToken --chain-id=<name_of_testnet_chain> --name=<key_name> --to=<destination_address>
```
Flags:
- `--amount`: This flag accepts the format `<value|coinName>`.
- `--chain-id`: This flag allows you to specify the id of the chain. There will be different ids for different testnet chains and main chain.
- `--name`: Name of the key of the sending account.
- `--to`: Address of the recipient.
#### Help
If you need to do something else, the best command you can run is:
```bash
gaiacli
```
It will display all the available commands. For each command, you can use the `--help` flag to get further information.
## Setting up the Rest Server
The Rest Server acts as an intermediary between the front-end and the full-node. You don't need to run the Rest Server on the same machine as the full-node. If you intend to run the Rest Server on another machine, you need to go through the [Installation and configuration](#installation-and-configuration) again on this machine.
To start the Rest server:
```bash
gaiacli advanced rest-server --trust-node=false --node=<full_node_address:full_node_port>
```
Flags:
- `--trust-node`: A boolean. If `true`, light-client verification is enabled. If `false`, it is disabled. For service providers, this should be set to `false`.
- `--node`: This is where you indicate the address and the port of your full-node. The format is <full_node_address:full_node_port>. If the full-node is on the same machine, the address should be "tcp://localhost".
- `--laddr`: This flag allows you to specify the address and port for the Rest Server. You will mostly use this flag only to specify the port, in which case just input "localhost" for the address. The format is <rest_server_address:port>.
### Listening for incoming transaction
The recommended way to listen for incoming transaction is to periodically query the blockchain through the following endpoint of the LCD:
[`/bank/balance/{account}`](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#bankbalanceaccount---get)
## Rest API
The Rest API documents all the available endpoints that you can use to interract with your full node. It can be found [here](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md).
The API is divided into ICS standards for each category of endpoints. For example, the [ICS20](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#ics20---tokenapi) describes the API to interact with tokens.
To give more flexibility to implementers, we have separated the different steps that are involved in the process of sending transactions. You will be able to generate unsigned transactions (example with [coin transfer](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-banktransfers)), [sign](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxsign) and [broadcast](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxbroadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance.

View File

@ -1,6 +1,6 @@
# Getting Started
To start a rest server, we need to specify the following parameters:
To start a REST server, we need to specify the following parameters:
| Parameter | Type | Default | Required | Description |
| ----------- | --------- | ----------------------- | -------- | ---------------------------------------------------- |
| chain-id | string | null | true | chain id of the full node to connect |
@ -12,9 +12,25 @@ To start a rest server, we need to specify the following parameters:
Sample command:
```bash
gaiacli light-client --chain-id=test --laddr=tcp://localhost:1317 --node tcp://localhost:46657 --trust-node=false
gaiacli rest-server --chain-id=test \
--laddr=tcp://localhost:1317 \
--node tcp://localhost:46657 \
--trust-node=false
```
The server listens on HTTPS by default. You can set the SSL certificate to be used by the server with these additional flags:
```bash
gaiacli rest-server --chain-id=test \
--laddr=tcp://localhost:1317 \
--node tcp://localhost:46657 \
--trust-node=false \
--certfile=mycert.pem --keyfile=mykey.key
```
If no certificate/keyfile pair is supplied, a self-signed certificate will be generated and its fingerprint printed out.
Append `--insecure` to the command line if you want to disable the secure layer and listen on an insecure HTTP port.
## Gaia Light Use Cases
LCD could be very helpful for related service providers. For a wallet service provider, LCD could