Merge branch 'master' into cwgoes/a-random-walk-down-proof-of-stake

This commit is contained in:
Christopher Goes 2018-07-14 02:09:41 +02:00
commit 405bb538fc
119 changed files with 8113 additions and 5207 deletions

View File

@ -103,7 +103,7 @@ jobs:
test_cover:
<<: *defaults
parallelism: 2
parallelism: 4
steps:
- attach_workspace:
at: /tmp/workspace
@ -119,7 +119,6 @@ jobs:
make install
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | circleci tests split --split-by=timings); do
id=$(basename "$pkg")
GOCACHE=off go test -v -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
done
- persist_to_workspace:

24
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@ -0,0 +1,24 @@
---
name: Bug Report
about: Create a report to help us squash bugs!
---
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v ✰ Thanks for opening an issue! ✰
v Before smashing the submit button please review the template.
v Please also ensure that this is not a duplicate issue :)
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
## Summary of Bug
<!-- Concisely describe the issue -->
## Steps to Reproduce
<!-- What commands in order should someone run to reproduce your problem -->
____
#### For Admin Use
- [ ] Not duplicate issue
- [ ] Appropriate labels applied
- [ ] Appropriate contributors tagged
- [ ] Contributor assigned/self-assigned

View File

@ -0,0 +1,31 @@
---
name: Feature Request
about: Create a proposal to request a feature
---
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v ✰ Thanks for opening an issue! ✰
v Before smashing the submit button please review the template.
v Word of caution: poorly thought-out proposals may be rejected
v without deliberation
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
## Summary
<!-- Short, concise description of the proposed feature -->
## Problem Definition
<!-- Why do we need this feature?
What problems may be addressed by introducing this feature?
What benefits does the SDK stand to gain by including this feature?
Are there any disadvantages of including this feature? -->
## Proposal
<!-- Detailed description of requirements of implementation -->
____
#### For Admin Use
- [ ] Not duplicate issue
- [ ] Appropriate labels applied
- [ ] Appropriate contributors tagged
- [ ] Contributor assigned/self-assigned

View File

@ -4,10 +4,13 @@ v Before smashing the submit button please review the checkboxes.
v If a checkbox is n/a - please still include it but + a little note why
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
* [ ] Updated all relevant documentation in docs
* [ ] Updated all code comments where relevant
* [ ] Updated all relevant documentation (`docs/`)
* [ ] Updated all relevant code comments
* [ ] Wrote tests
* [ ] Updated CHANGELOG.md
* [ ] Updated Gaia/Examples
* [ ] Squashed all commits, uses message "Merge pull request #XYZ: [title]" ([coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr))
* [ ] Updated `CHANGELOG.md`
* [ ] Updated `cmd/gaia` and `examples/`
___________________________________
For Admin Use:
* [ ] Added appropriate labels to PR (ex. wip, ready-for-review, docs)
* [ ] Reviewers Assigned
* [ ] Squashed all commits, uses message "Merge pull request #XYZ: [title]" ([coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr))

View File

@ -2,20 +2,28 @@
## 0.21.0
*TBD*
*July 13th, 2018*
BREAKING CHANGES
* [x/stake] Specify DelegatorAddress in MsgCreateValidator
* [x/stake] Remove the use of global shares in the pool
* Remove the use of `PoolShares` type in `x/stake/validator` type - replace with `Status` `Tokens` fields
* [x/auth] NewAccountMapper takes a constructor instead of a prototype
* [keys] Keybase.Update function now takes in a function to get the newpass, rather than the password itself
FEATURES
* [baseapp] NewBaseApp now takes option functions as parameters
IMPROVEMENTS
* Updated docs folder to accommodate cosmos.network docs project
* [store] Added support for tracing multi-store operations via `--trace-store`
* [store] Pruning strategy configurable with pruning flag on gaiad start
BUG FIXES
* \#1630 - redelegation nolonger removes tokens from the delegator liquid account
* [keys] \#1629 - updating password no longer asks for a new password when the first entered password was incorrect
* [lcd] importing an account would create a random account
* [server] 'gaiad init' command family now writes provided name as the moniker in `config.toml`
## 0.20.0

View File

@ -8,6 +8,12 @@ Please make sure to use `gofmt` before every commit - the easiest way to do this
Looking for a good place to start contributing? How about checking out some [good first issues](https://github.com/cosmos/cosmos-sdk/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
## Pull Requests
To accommodate review process we suggest that PRs are catagorically broken up.
Ideally each PR addresses only a single issue. Additionally, as much as possible
code refactoring and cleanup should be submitted as a seperate PRs from bugfixes/feature-additions.
## Forking
Please note that Go requires code to live under absolute paths, which complicates forking.

1
Gopkg.lock generated
View File

@ -636,6 +636,7 @@
"github.com/tendermint/tendermint/config",
"github.com/tendermint/tendermint/crypto",
"github.com/tendermint/tendermint/crypto/merkle",
"github.com/tendermint/tendermint/crypto/tmhash",
"github.com/tendermint/tendermint/libs/bech32",
"github.com/tendermint/tendermint/libs/cli",
"github.com/tendermint/tendermint/libs/cli/flags",

View File

@ -2,12 +2,14 @@ package baseapp
import (
"fmt"
"io"
"runtime/debug"
"strings"
"github.com/pkg/errors"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/tmhash"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
@ -37,7 +39,7 @@ const (
runTxModeDeliver runTxMode = iota
)
// The ABCI application
// BaseApp reflects the ABCI application implementation.
type BaseApp struct {
// initialized on creation
Logger log.Logger
@ -71,7 +73,12 @@ type BaseApp struct {
var _ abci.Application = (*BaseApp)(nil)
// Create and name new BaseApp
// NewBaseApp returns a reference to an initialized BaseApp.
//
// TODO: Determine how to use a flexible and robust configuration paradigm that
// allows for sensible defaults while being highly configurable
// (e.g. functional options).
//
// NOTE: The db is used to store the version number for now.
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices
func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB, options ...func(*BaseApp)) *BaseApp {
@ -85,7 +92,9 @@ func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB, opti
codespacer: sdk.NewCodespacer(),
txDecoder: defaultTxDecoder(cdc),
}
// Register the undefined & root codespaces, which should not be used by any modules
// Register the undefined & root codespaces, which should not be used by
// any modules.
app.codespacer.RegisterOrPanic(sdk.CodespaceRoot)
for _, option := range options {
option(app)
@ -98,6 +107,12 @@ func (app *BaseApp) Name() string {
return app.name
}
// SetCommitMultiStoreTracer sets the store tracer on the BaseApp's underlying
// CommitMultiStore.
func (app *BaseApp) SetCommitMultiStoreTracer(w io.Writer) {
app.cms.WithTracer(w)
}
// Register the next available codespace through the baseapp's codespacer, starting from a default
func (app *BaseApp) RegisterCodespace(codespace sdk.CodespaceType) sdk.CodespaceType {
return app.codespacer.RegisterNext(codespace)
@ -392,13 +407,18 @@ func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abc
return sdk.ErrUnknownRequest(msg).QueryResult()
}
// Implements ABCI
// BeginBlock implements the ABCI application interface.
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
// Initialize the DeliverTx state.
// If this is the first block, it should already
// be initialized in InitChain.
// Otherwise app.deliverState will be nil, since it
// is reset on Commit.
if app.cms.TracingEnabled() {
app.cms.ResetTraceContext()
app.cms.WithTracingContext(sdk.TraceContext(
map[string]interface{}{"blockHeight": req.Header.Height},
))
}
// Initialize the DeliverTx state. If this is the first block, it should
// already be initialized in InitChain. Otherwise app.deliverState will be
// nil, since it is reset on Commit.
if app.deliverState == nil {
app.setDeliverState(req.Header)
} else {
@ -406,9 +426,11 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
// by InitChain. Context is now updated with Header information.
app.deliverState.ctx = app.deliverState.ctx.WithBlockHeader(req.Header)
}
if app.beginBlocker != nil {
res = app.beginBlocker(app.deliverState.ctx, req)
}
// set the signed validators for addition to context in deliverTx
app.signedValidators = req.Validators
return
@ -548,25 +570,26 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg) (result sdk.Result)
return result
}
// Returns deliverState if app is in runTxModeDeliver, otherwhise returns checkstate
// Returns the applicantion's deliverState if app is in runTxModeDeliver,
// otherwise it returns the application's checkstate.
func getState(app *BaseApp, mode runTxMode) *state {
if mode == runTxModeCheck || mode == runTxModeSimulate {
return app.checkState
}
return app.deliverState
}
// txBytes may be nil in some cases, eg. in tests.
// Also, in the future we may support "internal" transactions.
// runTx processes a transaction. The transactions is proccessed via an
// anteHandler. txBytes may be nil in some cases, eg. in tests. Also, in the
// future we may support "internal" transactions.
func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) {
//NOTE: GasWanted should be returned by the AnteHandler.
// GasUsed is determined by the GasMeter.
// We need access to the context to get the gas meter so
// we initialize upfront
// NOTE: GasWanted should be returned by the AnteHandler. GasUsed is
// determined by the GasMeter. We need access to the context to get the gas
// meter so we initialize upfront.
var gasWanted int64
ctx := app.getContextForAnte(mode, txBytes)
// Handle any panics.
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
@ -578,11 +601,11 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
result = sdk.ErrInternal(log).Result()
}
}
result.GasWanted = gasWanted
result.GasUsed = ctx.GasMeter().GasConsumed()
}()
// Get the Msg.
var msgs = tx.GetMsgs()
err := validateBasicTxMsgs(msgs)
@ -590,7 +613,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
return err.Result()
}
// Run the ante handler.
// run the ante handler
if app.anteHandler != nil {
newCtx, anteResult, abort := app.anteHandler(ctx, tx)
if abort {
@ -599,17 +622,24 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
if !newCtx.IsZero() {
ctx = newCtx
}
gasWanted = result.GasWanted
}
// CacheWrap the state in case it fails.
// Keep the state in a transient CacheWrap in case processing the messages
// fails.
msCache := getState(app, mode).CacheMultiStore()
ctx = ctx.WithMultiStore(msCache)
if msCache.TracingEnabled() {
msCache = msCache.WithTracingContext(sdk.TraceContext(
map[string]interface{}{"txHash": cmn.HexBytes(tmhash.Sum(txBytes)).String()},
)).(sdk.CacheMultiStore)
}
ctx = ctx.WithMultiStore(msCache)
result = app.runMsgs(ctx, msgs)
result.GasWanted = gasWanted
// Only update state if all messages pass and we're not in a simulation.
// only update state if all messages pass and we're not in a simulation
if result.IsOK() && mode != runTxModeSimulate {
msCache.Write()
}
@ -617,11 +647,16 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
return
}
// Implements ABCI
// EndBlock implements the ABCI application interface.
func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
if app.deliverState.ms.TracingEnabled() {
app.deliverState.ms = app.deliverState.ms.ResetTraceContext().(sdk.CacheMultiStore)
}
if app.endBlocker != nil {
res = app.endBlocker(app.deliverState.ctx, req)
}
return
}

28
baseapp/options.go Normal file
View File

@ -0,0 +1,28 @@
package baseapp
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// File for storing in-package BaseApp optional functions,
// for options that need access to non-exported fields of the BaseApp
// SetPruning sets a pruning option on the multistore associated with the app
func SetPruning(pruning string) func(*BaseApp) {
var pruningEnum sdk.PruningStrategy
switch pruning {
case "nothing":
pruningEnum = sdk.PruneNothing
case "everything":
pruningEnum = sdk.PruneEverything
case "syncable":
pruningEnum = sdk.PruneSyncable
default:
panic(fmt.Sprintf("Invalid pruning strategy: %s", pruning))
}
return func(bap *BaseApp) {
bap.cms.SetPruning(pruningEnum)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -105,7 +105,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
privVal := pvm.LoadOrGenFilePV(privValidatorFile)
privVal.Reset()
db := dbm.NewMemDB()
app := gapp.NewGaiaApp(logger, db)
app := gapp.NewGaiaApp(logger, db, nil)
cdc = gapp.MakeCodec()
genesisFile := config.GenesisFile()
@ -146,7 +146,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
accAuth.Coins = sdk.Coins{sdk.NewCoin("steak", 100)}
acc := gapp.NewGenesisAccount(&accAuth)
genesisState.Accounts = append(genesisState.Accounts, acc)
genesisState.StakeData.Pool.LooseTokens += 100
genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewRat(100))
}
appState, err := wire.MarshalJSONIndent(cdc, genesisState)

View File

@ -2,6 +2,7 @@ package app
import (
"encoding/json"
"io"
"os"
abci "github.com/tendermint/tendermint/abci/types"
@ -55,12 +56,15 @@ type GaiaApp struct {
govKeeper gov.Keeper
}
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
// NewGaiaApp returns a reference to an initialized GaiaApp.
func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptions ...func(*bam.BaseApp)) *GaiaApp {
cdc := MakeCodec()
// create your application object
bApp := bam.NewBaseApp(appName, cdc, logger, db, baseAppOptions...)
bApp.SetCommitMultiStoreTracer(traceStore)
var app = &GaiaApp{
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
BaseApp: bApp,
cdc: cdc,
keyMain: sdk.NewKVStoreKey("main"),
keyAccount: sdk.NewKVStoreKey("acc"),

View File

@ -160,7 +160,7 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
}
acc := NewGenesisAccount(&accAuth)
genaccs[i] = acc
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionsAcc // increase the supply
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewRat(freeFermionsAcc)) // increase the supply
// add the validator
if len(genTx.Name) > 0 {
@ -168,7 +168,7 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
validator := stake.NewValidator(genTx.Address,
sdk.MustGetAccPubKeyBech32(genTx.PubKey), desc)
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionVal // increase the supply
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewRat(freeFermionVal)) // increase the supply
// add some new shares to the validator
var issuedDelShares sdk.Rat

View File

@ -131,7 +131,7 @@ func TestGaiaCLICreateValidator(t *testing.T) {
validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags))
require.Equal(t, validator.Owner, barAddr)
require.Equal(t, "2/1", validator.PoolShares.Amount.String())
require.True(sdk.RatEq(t, sdk.NewRat(2), validator.Tokens))
// unbond a single share
unbondStr := fmt.Sprintf("gaiacli stake unbond begin %v", flags)
@ -149,7 +149,7 @@ func TestGaiaCLICreateValidator(t *testing.T) {
require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc)
*/
validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags))
require.Equal(t, "1/1", validator.PoolShares.Amount.String())
require.Equal(t, "1/1", validator.Tokens.String())
}
func TestGaiaCLISubmitProposal(t *testing.T) {

View File

@ -2,8 +2,12 @@ package main
import (
"encoding/json"
"io"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/spf13/cobra"
"github.com/spf13/viper"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/cli"
@ -38,11 +42,13 @@ func main() {
}
}
func newApp(logger log.Logger, db dbm.DB) abci.Application {
return app.NewGaiaApp(logger, db)
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
return app.NewGaiaApp(logger, db, traceStore, baseapp.SetPruning(viper.GetString("pruning")))
}
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) {
gapp := app.NewGaiaApp(logger, db)
return gapp.ExportAppStateAndValidators()
func exportAppStateAndTMValidators(
logger log.Logger, db dbm.DB, traceStore io.Writer,
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
gApp := app.NewGaiaApp(logger, db, traceStore)
return gApp.ExportAppStateAndValidators()
}

View File

@ -7,7 +7,10 @@ import (
"os"
"path"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/spf13/cobra"
"github.com/spf13/viper"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tendermint/libs/common"
@ -44,7 +47,7 @@ func runHackCmd(cmd *cobra.Command, args []string) error {
fmt.Println(err)
os.Exit(1)
}
app := NewGaiaApp(logger, db)
app := NewGaiaApp(logger, db, baseapp.SetPruning(viper.GetString("pruning")))
// print some info
id := app.LastCommitID()
@ -140,12 +143,15 @@ type GaiaApp struct {
slashingKeeper slashing.Keeper
}
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseApp)) *GaiaApp {
cdc := MakeCodec()
bApp := bam.NewBaseApp(appName, cdc, logger, db, baseAppOptions...)
bApp.SetCommitMultiStoreTracer(os.Stdout)
// create your application object
var app = &GaiaApp{
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
BaseApp: bApp,
cdc: cdc,
keyMain: sdk.NewKVStoreKey("main"),
keyAccount: sdk.NewKVStoreKey("acc"),

View File

@ -1,66 +1,9 @@
# Cosmos SDK Documentation
# Welcome to the Cosmos Docs!
NOTE: This documentation is a work-in-progress!
![cosmonaut reading the cosmos docs in space](./graphics/cosmos-docs.jpg)
- [Overview](overview)
- [Overview](overview/overview.md) - An overview of the Cosmos-SDK
- [The Object-Capability Model](overview/capabilities.md) - Security by
least-privilege
- [Application Architecture](overview/apps.md) - Layers in the application architecture
- [Install](install.md) - Install the library and example applications
- [Core](core)
- [Introduction](core/intro.md) - Intro to the tutorial
- [App1 - The Basics](core/app1.md)
- [Messages](core/app1.md#messages) - Messages contain the content of a transaction
- [Stores](core/app1.md#kvstore) - KVStore is a Merkle Key-Value store.
- [Handlers](core/app1.md#handlers) - Handlers are the workhorse of the app!
- [Tx](core/app1.md#tx) - Transactions are the ultimate input to the
application
- [BaseApp](core/app1.md#baseapp) - BaseApp is the base layer of the application
- [App2 - Transactions](core/app2.md)
- [Amino](core/app2.md#amino) - Amino is the primary serialization library used in the SDK
- [Ante Handler](core/app2.md#antehandler) - The AnteHandler
authenticates transactions
- [App3 - Modules: Auth and Bank](core/app3.md)
- [auth.Account](core/app3.md#accounts) - Accounts are the prototypical object kept in the store
- [auth.AccountMapper](core/app3.md#account-mapper) - AccountMapper gets and sets Account on a KVStore
- [auth.StdTx](core/app3.md#stdtx) - `StdTx` is the default implementation of `Tx`
- [auth.StdSignBytes](core/app3.md#signing) - `StdTx` must be signed with certain
information
- [auth.AnteHandler](core/app3.md#antehandler) - The `AnteHandler`
verifies `StdTx`, manages accounts, and deducts fees
- [bank.CoinKeeper](core/app3.md#coinkeeper) - CoinKeeper allows for coin
transfers on an underlying AccountMapper
- [App4 - ABCI](core/app4.md)
- [ABCI](core/app4.md#abci) - ABCI is the interface between Tendermint
and the Cosmos-SDK
- [InitChain](core/app4.md#initchain) - Initialize the application
store
- [BeginBlock](core/app4.md#beginblock) - BeginBlock runs at the
beginning of every block and updates the app about validator behaviour
- [EndBlock](core/app4.md#endblock) - EndBlock runs at the
end of every block and lets the app change the validator set.
- [Query](core/app4.md#query) - Query the application store
- [CheckTx](core/app4.md#checktx) - CheckTx only runs the AnteHandler
- [App5 - Basecoin](core/app5.md) -
- [Directory Structure](core/app5.md#directory-structure) - Keep your
application code organized
- [Tendermint Node](core/app5.md#tendermint-node) - Run a full
blockchain node with your app
- [Clients](core/app5.md#clients) - Hook up your app to CLI and REST
interfaces for clients to use!
Cosmos is a decentralized network of independent parallel blockchains, each powered by classical BFT consensus algorithms like Tendermint.
- [Modules](modules)
- [Bank](modules/README.md#bank)
- [Staking](modules/README.md#stake)
- [Slashing](modules/README.md#slashing)
- [Provisions](modules/README.md#provisions)
- [Governance](modules/README.md#governance)
- [IBC](modules/README.md#ibc)
The first blockchain in the Cosmos Network is the Cosmos Hub, whose native token is the Atom. Cosmos is a permission-less network, meaning that anybody can build a blockchain on it.
- [Clients](clients)
- [Running a Node](clients/node.md) - Run a full node!
- [Key Management](clients/keys.md) - Managing user keys
- [CLI](clients/cli.md) - Queries and transactions via command line
- [REST Light Client Daemon](clients/rest.md) - Queries and transactions via REST
API
Cosmos can interoperate with multiple other applications and cryptocurrencies. By creating a new zone, you can plug any blockchain system into the Cosmos hub and pass tokens back and forth between those zones, without the need for an intermediary.

View File

@ -1,402 +0,0 @@
Using The Staking Module
========================
This project is a demonstration of the Cosmos Hub staking functionality; it is
designed to get validator acquianted with staking concepts and procedures.
Potential validators will be declaring their candidacy, after which users can
delegate and, if they so wish, unbond. This can be practiced using a local or
public testnet.
This example covers initial setup of a two-node testnet between a server in the cloud and a local machine. Begin this tutorial from a cloud machine that you've ``ssh``'d into.
Install
-------
The ``gaiad`` and ``gaiacli`` binaries:
::
go get github.com/cosmos/cosmos-sdk
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
make get_vendor_deps
make install
Let's jump right into it. First, we initialize some default files:
::
gaiad init
which will output:
::
I[03-30|11:20:13.365] Found private validator module=main path=/root/.gaiad/config/priv_validator.json
I[03-30|11:20:13.365] Found genesis file module=main path=/root/.gaiad/config/genesis.json
Secret phrase to access coins:
citizen hungry tennis noise park hire glory exercise link glow dolphin labor design grit apple abandon
This tell us we have a ``priv_validator.json`` and ``genesis.json`` in the ``~/.gaiad/config`` directory. A ``config.toml`` was also created in the same directory. It is a good idea to get familiar with those files. Write down the seed.
The next thing we'll need to is add the key from ``priv_validator.json`` to the ``gaiacli`` key manager. For this we need a seed and a password:
::
gaiacli keys add alice --recover
which will give you three prompts:
::
Enter a passphrase for your key:
Repeat the passphrase:
Enter your recovery seed phrase:
create a password and copy in your seed phrase. The name and address of the key will be output:
::
NAME: ADDRESS: PUBKEY:
alice 67997DD03D527EB439B7193F2B813B05B219CC02 1624DE6220BB89786C1D597050438C728202436552C6226AB67453CDB2A4D2703402FB52B6
You can see all available keys with:
::
gaiacli keys list
Setup Testnet
-------------
Next, we start the daemon (do this in another window):
::
gaiad start
and you'll see blocks start streaming through.
For this example, we're doing the above on a cloud machine. The next steps should be done on your local machine or another server in the cloud, which will join the running testnet then bond/unbond.
Accounts
--------
We have:
- ``alice`` the initial validator (in the cloud)
- ``bob`` receives tokens from ``alice`` then declares candidacy (from local machine)
- ``charlie`` will bond and unbond to ``bob`` (from local machine)
Remember that ``alice`` was already created. On your second machine, install the binaries and create two new keys:
::
gaiacli keys add bob
gaiacli keys add charlie
both of which will prompt you for a password. Now we need to copy the ``genesis.json`` and ``config.toml`` from the first machine (with ``alice``) to the second machine. This is a good time to look at both these files.
The ``genesis.json`` should look something like:
::
{
"app_state": {
"accounts": [
{
"address": "1D9B2356CAADF46D3EE3488E3CCE3028B4283DEE",
"coins": [
{
"denom": "steak",
"amount": 100000
}
]
}
],
"stake": {
"pool": {
"total_supply": 0,
"bonded_shares": {
"num": 0,
"denom": 1
},
"unbonded_shares": {
"num": 0,
"denom": 1
},
"bonded_pool": 0,
"unbonded_pool": 0,
"inflation_last_time": 0,
"inflation": {
"num": 7,
"denom": 100
}
},
"params": {
"inflation_rate_change": {
"num": 13,
"denom": 100
},
"inflation_max": {
"num": 20,
"denom": 100
},
"inflation_min": {
"num": 7,
"denom": 100
},
"goal_bonded": {
"num": 67,
"denom": 100
},
"max_validators": 100,
"bond_denom": "steak"
}
}
},
"validators": [
{
"pub_key": {
"type": "AC26791624DE60",
"value": "rgpc/ctVld6RpSfwN5yxGBF17R1PwMTdhQ9gKVUZp5g="
},
"power": 10,
"name": ""
}
],
"app_hash": "",
"genesis_time": "0001-01-01T00:00:00Z",
"chain_id": "test-chain-Uv1EVU"
}
To notice is that the ``accounts`` field has a an address and a whole bunch of "mycoin". This is ``alice``'s address (todo: dbl check). Under ``validators`` we see the ``pub_key.data`` field, which will match the same field in the ``priv_validator.json`` file.
The ``config.toml`` is long so let's focus on one field:
::
# Comma separated list of seed nodes to connect to
seeds = ""
On the ``alice`` cloud machine, we don't need to do anything here. Instead, we need its IP address. After copying this file (and the ``genesis.json`` to your local machine, you'll want to put the IP in the ``seeds = "138.197.161.74"`` field, in this case, we have a made-up IP. For joining testnets with many nodes, you can add more comma-seperated IPs to the list.
Now that your files are all setup, it's time to join the network. On your local machine, run:
::
gaiad start
and your new node will connect to the running validator (``alice``).
Sending Tokens
--------------
We'll have ``alice`` send some ``mycoin`` to ``bob``, who has now joined the network:
::
gaiacli send --amount=1000mycoin --sequence=0 --from=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU
where the ``--sequence`` flag is to be incremented for each transaction, the ``--from`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like:
::
Please enter passphrase for alice:
{
"check_tx": {
"gas": 30
},
"deliver_tx": {
"tags": [
{
"key": "height",
"value_type": 1,
"value_int": 2963
},
{
"key": "coin.sender",
"value_string": "5D93A6059B6592833CBC8FA3DA90EE0382198985"
},
{
"key": "coin.receiver",
"value_string": "5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6"
}
]
},
"hash": "423BD7EA3C4B36AF8AFCCA381C0771F8A698BA77",
"height": 2963
}
TODO: check the above with current actual output.
Check out ``bob``'s account, which should now have 1000 mycoin:
::
gaiacli account 5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6
Adding a Second Validator
-------------------------
**This section is wrong/needs to be updated**
Next, let's add the second node as a validator.
First, we need the pub_key data:
** need to make bob a priv_Val above?
::
cat $HOME/.gaia2/priv_validator.json
the first part will look like:
::
{"address":"7B78527942C831E16907F10C3263D5ED933F7E99","pub_key":{"type":"ed25519","data":"96864CE7085B2E342B0F96F2E92B54B18C6CC700186238810D5AA7DFDAFDD3B2"},
and you want the ``pub_key`` ``data`` that starts with ``96864CE``.
Now ``bob`` can create a validator with that pubkey.
::
gaiacli stake create-validator --amount=10mycoin --from=bob --address-validator=<address> --pub-key=<pubkey> --moniker=bobby
with an output like:
::
Please enter passphrase for bob:
{
"check_tx": {
"gas": 30
},
"deliver_tx": {},
"hash": "2A2A61FFBA1D7A59138E0068C82CC830E5103799",
"height": 4075
}
We should see ``bob``'s account balance decrease by 10 mycoin:
::
gaiacli account 5D93A6059B6592833CBC8FA3DA90EE0382198985
To confirm for certain the new validator is active, ask the tendermint node:
::
curl localhost:26657/validators
If you now kill either node, blocks will stop streaming in, because
there aren't enough validators online. Turn it back on and they will
start streaming again.
Now that ``bob`` has declared candidacy, which essentially bonded 10 mycoin and made him a validator, we're going to get ``charlie`` to delegate some coins to ``bob``.
Delegating
----------
First let's have ``alice`` send some coins to ``charlie``:
::
gaiacli send --amount=1000mycoin --sequence=2 --from=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF
Then ``charlie`` will delegate some mycoin to ``bob``:
::
gaiacli stake delegate --amount=10mycoin --address-delegator=<charlie's address> --address-validator=<bob's address> --from=charlie
You'll see output like:
::
Please enter passphrase for charlie:
{
"check_tx": {
"gas": 30
},
"deliver_tx": {},
"hash": "C3443BA30FCCC1F6E3A3D6AAAEE885244F8554F0",
"height": 51585
}
And that's it. You can query ``charlie``'s account to see the decrease in mycoin.
To get more information about the candidate, try:
::
gaiacli stake validator <address>
and you'll see output similar to:
::
{
"height": 51899,
"data": {
"pub_key": {
"type": "ed25519",
"data": "52D6FCD8C92A97F7CCB01205ADF310A18411EA8FDCC10E65BF2FCDB05AD1689B"
},
"owner": {
"chain": "",
"app": "sigs",
"addr": "5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6"
},
"shares": 20,
"voting_power": 20,
"description": {
"moniker": "bobby",
"identity": "",
"website": "",
"details": ""
}
}
}
It's also possible the query the delegator's bond like so:
::
gaiacli stake delegation --address-delegator=<address> --address-validator=<address>
with an output similar to:
::
{
"height": 325782,
"data": {
"PubKey": {
"type": "ed25519",
"data": "52D6FCD8C92A97F7CCB01205ADF310A18411EA8FDCC10E65BF2FCDB05AD1689B"
},
"Shares": 20
}
}
where the ``--address-delegator`` is ``charlie``'s address and the ``--address-validator`` is ``bob``'s address.
Unbonding
---------
Finally, to relinquish your voting power, unbond some coins. You should see
your VotingPower reduce and your account balance increase.
::
gaiacli stake unbond --amount=5mycoin --from=charlie --address-delegator=<address> --address-validator=<address>
gaiacli account 48F74F48281C89E5E4BE9092F735EA519768E8EF
See the bond decrease with ``gaiacli stake delegation`` like above.

View File

@ -1,216 +0,0 @@
//TODO update .rst
# Staking Module
## Overview
The Cosmos Hub is a Tendermint-based Delegated Proof of Stake (DPos) blockchain
system that serves as a backbone of the Cosmos ecosystem. It is operated and
secured by an open and globally decentralized set of validators. Tendermint is
a Byzantine fault-tolerant distributed protocol for consensus among distrusting
parties, in this case the group of validators which produce the blocks for the
Cosmos Hub. To avoid the nothing-at-stake problem, a validator in Tendermint
needs to lock up coins in a bond deposit. Each bond's atoms are illiquid, they
cannot be transferred - in order to become liquid, they must be unbonded, a
process which will take 3 weeks by default at Cosmos Hub launch. Tendermint
protocol messages are signed by the validator's private key and are therefor
attributable. Validators acting outside protocol specifications can be made
accountable through punishing by slashing (burning) their bonded Atoms. On the
other hand, validators are rewarded for their service of securing blockchain
network by the inflationary provisions and transactions fees. This incentivizes
correct behavior of the validators and provides the economic security of the
network.
The native token of the Cosmos Hub is called the Atom; becoming a validator of the
Cosmos Hub requires holding Atoms. However, not all Atom holders are validators
of the Cosmos Hub. More precisely, there is a selection process that determines
the validator set as a subset of all validators (Atom holders that
want to become a validator). The other option for Atom holders is to delegate
their atoms to validators, i.e., being a delegator. A delegator is an Atom
holder that has put its Atoms at stake by delegating it to a validator. By bonding
Atoms to secure the network (and taking a risk of being slashed in case of
misbehaviour), a user is rewarded with inflationary provisions and transaction
fees proportional to the amount of its bonded Atoms. The Cosmos Hub is
designed to efficiently facilitate a small numbers of validators (hundreds),
and large numbers of delegators (tens of thousands). More precisely, it is the
role of the Staking module of the Cosmos Hub to support various staking
functionality including validator set selection, delegating, bonding and
withdrawing Atoms, and the distribution of inflationary provisions and
transaction fees.
## Basic Terms and Definitions
* Cosmsos Hub - a Tendermint-based Delegated Proof of Stake (DPos)
blockchain system
* Atom - native token of the Cosmsos Hub
* Atom holder - an entity that holds some amount of Atoms
* Pool - Global object within the Cosmos Hub which accounts global state
including the total amount of bonded, unbonding, and unbonded atoms
* Validator Share - Share which a validator holds to represent its portion of
bonded, unbonding or unbonded atoms in the pool
* Delegation Share - Shares which a delegation bond holds to represent its
portion of bonded, unbonding or unbonded shares in a validator
* Bond Atoms - a process of locking Atoms in a delegation share which holds them
under protocol control.
* Slash Atoms - the process of burning atoms in the pool and assoiated
validator shares of a misbehaving validator, (not behaving according to the
protocol specification). This process devalues the worth of delegation shares
of the given validator
* Unbond Shares - Process of retrieving atoms from shares. If the shares are
bonded the shares must first remain in an inbetween unbonding state for the
duration of the unbonding period
* Redelegating Shares - Process of redelegating atoms from one validator to
another. This process is instantaneous, but the redelegated atoms are
retrospecively slashable if the old validator is found to misbehave for any
blocks before the redelegation. These atoms are simultaniously slashable
for any new blocks which the new validator misbehavess
* Validator - entity with atoms which is either actively validating the Tendermint
protocol (bonded validator) or vying to validate .
* Bonded Validator - a validator whose atoms are currently bonded and liable to
be slashed. These validators are to be able to sign protocol messages for
Tendermint consensus. At Cosmos Hub genesis there is a maximum of 100
bonded validator positions. Only Bonded Validators receive atom provisions
and fee rewards.
* Delegator - an Atom holder that has bonded Atoms to a validator
* Unbonding period - time required in the unbonding state when unbonding
shares. Time slashable to old validator after a redelegation. Time for which
validators can be slashed after an infraction. To provide the requisite
cryptoeconomic security guarantees, all of these must be equal.
* Atom provisions - The process of increasing the Atom supply. Atoms are
periodically created on the Cosmos Hub and issued to bonded Atom holders.
The goal of inflation is to incentize most of the Atoms in existence to be
bonded. Atoms are distributed unbonded and using the fee_distribution mechanism
* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub
transaction. The fees are collected by the current validator set and
distributed among validators and delegators in proportion to their bonded
Atom share
* Commission fee - a fee taken from the transaction fees by a validator for
their service
## The pool and the share
At the core of the Staking module is the concept of a pool which denotes a
collection of Atoms contributed by different Atom holders. There are three
pools in the Staking module: the bonded, unbonding, and unbonded pool. Bonded
Atoms are part of the global bonded pool. If a validator or delegator wants to
unbond its shares, these Shares are moved to the the unbonding pool for the
duration of the unbonding period. From here normally Atoms will be moved
directly into the delegators wallet, however under the situation thatn an
entire validator gets unbonded, the Atoms of the delegations will remain with
the validator and moved to the unbonded pool. For each pool, the total amount
of bonded, unbonding, or unbonded Atoms are tracked as well as the current
amount of issued pool-shares, the specific holdings of these shares by
validators are tracked in protocol by the validator object.
A share is a unit of Atom distribution and the value of the share
(share-to-atom exchange rate) can change during system execution. The
share-to-atom exchange rate can be computed as:
`share-to-atom-exchange-rate = size of the pool / ammount of issued shares`
Then for each validator (in a per validator data structure) the protocol keeps
track of the amount of shares the validator owns in a pool. At any point in
time, the exact amount of Atoms a validator has in the pool can be computed as
the number of shares it owns multiplied with the current share-to-atom exchange
rate:
`validator-coins = validator.Shares * share-to-atom-exchange-rate`
The benefit of such accounting of the pool resources is the fact that a
modification to the pool from bonding/unbonding/slashing of Atoms affects only
global data (size of the pool and the number of shares) and not the related
validator data structure, i.e., the data structure of other validators do not
need to be modified. This has the advantage that modifying global data is much
cheaper computationally than modifying data of every validator. Let's explain
this further with several small examples:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXX TODO make way less verbose lets use bullet points to describe the example
XXX Also need to update to not include bonded atom provisions all atoms are
XXX redistributed with the fee pool now
We consider initially 4 validators p1, p2, p3 and p4, and that each validator
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
issued initially 40 shares (note that the initial distribution of the shares,
i.e., share-to-atom exchange rate can be set to any meaningful value), i.e.,
share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we
have, the size of the pool is 40 Atoms, and the amount of issued shares is
equal to 40. And for each validator we store in their corresponding data
structure that each has 10 shares of the bonded pool. Now lets assume that the
validator p4 starts process of unbonding of 5 shares. Then the total size of
the pool is decreased and now it will be 35 shares and the amount of Atoms is
35 . Note that the only change in other data structures needed is reducing the
number of shares for a validator p4 from 10 to 5.
Let's consider now the case where a validator p1 wants to bond 15 more atoms to
the pool. Now the size of the pool is 50, and as the exchange rate hasn't
changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we
now have 50 shares in the pool in total. Validators p2, p3 and p4 still have
(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we
don't need to modify anything in their corresponding data structures. But p1
now has 25 shares, so we update the amount of shares owned by p1 in its
data structure. Note that apart from the size of the pool that is in Atoms, all
other data structures refer only to shares.
Finally, let's consider what happens when new Atoms are created and added to
the pool due to inflation. Let's assume that the inflation rate is 10 percent
and that it is applied to the current state of the pool. This means that 5
Atoms are created and added to the pool and that each validator now
proportionally increase it's Atom count. Let's analyse how this change is
reflected in the data structures. First, the size of the pool is increased and
is now 55 atoms. As a share of each validator in the pool hasn't changed, this
means that the total number of shares stay the same (50) and that the amount of
shares of each validator stays the same (correspondingly 25, 10, 10, 5). But
the exchange rate has changed and each share is now worth 55/50 Atoms per
share, so each validator has effectively increased amount of Atoms it has. So
validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms.
The concepts of the pool and its shares is at the core of the accounting in the
Staking module. It is used for managing the global pools (such as bonding and
unbonding pool), but also for distribution of Atoms between validator and its
delegators (we will explain this in section X).
#### Delegator shares
A validator is, depending on its status, contributing Atoms to either the
unbonding or unbonded pool - the validator in turn holds some amount of pool
shares. Not all of a validator's Atoms (and respective shares) are necessarily
owned by the validator, some may be owned by delegators to that validator. The
mechanism for distribution of Atoms (and shares) between a validator and its
delegators is based on a notion of delegator shares. More precisely, every
validator is issuing (local) delegator shares
(`Validator.IssuedDelegatorShares`) that represents some portion of global
shares managed by the validator (`Validator.GlobalStakeShares`). The principle
behind managing delegator shares is the same as described in [Section](#The
pool and the share). We now illustrate it with an example.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXX TODO make way less verbose lets use bullet points to describe the example
XXX Also need to update to not include bonded atom provisions all atoms are
XXX redistributed with the fee pool now
Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
issued initially 40 global shares, i.e., that
`share-to-atom-exchange-rate = 1 atom per share`. So we will set
`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the
Validator data structure of each validator `Validator.GlobalStakeShares = 10`.
Furthermore, each validator issued 10 delegator shares which are initially
owned by itself, i.e., `Validator.IssuedDelegatorShares = 10`, where
`delegator-share-to-global-share-ex-rate = 1 global share per delegator share`.
Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and
consider what are the updates we need to make to the data structures. First,
`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for
validator p1 we have `Validator.GlobalStakeShares = 15`, but we also need to
issue also additional delegator shares, i.e.,
`Validator.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator
shares of validator p1, where each delegator share is worth 1 global shares,
i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to
inflation. In that case, we only need to update `GlobalState.BondedPool` which
is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note
that the amount of global and delegator shares stay the same but they are now
worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share.
Therefore, a delegator d1 now owns:
`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms`

View File

@ -1,675 +0,0 @@
# Stake Module
## Overview
The stake module is tasked with various core staking functionality. Through the
stake module atoms may be bonded, delegated, and provisions/rewards are
distributed. Atom provisions are distributed to validators and their delegators
through share distribution of a collective pool of all staked atoms. As atoms
are created they are added to the common pool and each share become
proportionally worth more atoms. Fees are distributed through a similar pooling
mechanism but where each validator and delegator maintains an adjustment factor
to determine the true proportion of fees they are entitled too. This adjustment
factor is updated for each delegator and validator for each block where changes
to the voting power occurs in the network. Broken down, the stake module at a
high level is responsible for:
- Declaration of candidacy for becoming a validator
- Updating Tendermint validating power to reflect slashable stake
- Delegation and unbonding transactions
- Implementing unbonding period
- Provisioning Atoms
- Managing and distributing transaction fees
- Providing the framework for validator commission on delegators
### Transaction Overview
Available Transactions:
- TxDeclareCandidacy
- TxEditCandidacy
- TxLivelinessCheck
- TxProveLive
- TxDelegate
- TxUnbond
- TxRedelegate
## Global State
`Params` and `GlobalState` represent the global persistent state of Gaia.
`Params` is intended to remain static whereas `GlobalState` is anticipated to
change each block.
``` golang
type Params struct {
HoldBonded Address // account where all bonded coins are held
HoldUnbonded Address // account where all delegated but unbonded coins are held
InflationRateChange rational.Rational // maximum annual change in inflation rate
InflationMax rational.Rational // maximum inflation rate
InflationMin rational.Rational // minimum inflation rate
GoalBonded rational.Rational // Goal of percent bonded atoms
ReserveTax rational.Rational // Tax collected on all fees
MaxVals uint16 // maximum number of validators
AllowedBondDenom string // bondable coin denomination
// gas costs for txs
GasDeclareCandidacy int64
GasEditCandidacy int64
GasDelegate int64
GasRedelegate int64
GasUnbond int64
}
```
``` golang
type GlobalState struct {
TotalSupply int64 // total supply of atom tokens
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
BondedPool int64 // reserve of bonded tokens
UnbondedPool int64 // reserve of unbonded tokens held with candidates
InflationLastTime int64 // timestamp of last processing of inflation
Inflation rational.Rat // current annual inflation rate
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset
FeePool coin.Coins // fee pool for all the fee shares which have already been distributed
ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
Adjustment rational.Rat // Adjustment factor for calculating global fee accum
}
```
### The Queue
The queue is ordered so the next to unbond/re-delegate is at the head. Every
tick the head of the queue is checked and if the unbonding period has passed
since `InitHeight` commence with final settlement of the unbonding and pop the
queue. All queue elements used for unbonding share a common struct:
``` golang
type QueueElem struct {
Candidate crypto.PubKey
InitHeight int64 // when the queue was initiated
}
```
Each `QueueElem` is persisted in the store until it is popped from the queue.
## Validator-Candidate
The `Candidate` struct holds the current state and some historical actions of
validators or candidate-validators.
``` golang
type Candidate struct {
Status CandidateStatus
PubKey crypto.PubKey
GovernancePubKey crypto.PubKey
Owner Address
GlobalStakeShares rational.Rat
IssuedDelegatorShares rational.Rat
RedelegatingShares rational.Rat
VotingPower rational.Rat
Commission rational.Rat
CommissionMax rational.Rat
CommissionChangeRate rational.Rat
CommissionChangeToday rational.Rat
ProposerRewardPool coin.Coins
Adjustment rational.Rat
Description Description
}
type CandidateStatus byte
const (
VyingUnbonded CandidateStatus = 0x00
VyingUnbonding CandidateStatus = 0x01
Bonded CandidateStatus = 0x02
KickUnbonding CandidateStatus = 0x03
KickUnbonded CandidateStatus = 0x04
)
type Description struct {
Name string
DateBonded string
Identity string
Website string
Details string
}
```
Candidate parameters are described:
- Status: signal that the candidate is either vying for validator status
either unbonded or unbonding, an active validator, or a kicked validator
either unbonding or unbonded.
- PubKey: separated key from the owner of the candidate as is used strictly
for participating in consensus.
- Owner: Address where coins are bonded from and unbonded to
- GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
`Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` if
`Candidate.Status` is otherwise
- IssuedDelegatorShares: Sum of all shares issued to delegators (which
includes the candidate's self-bond) which represent each of their stake in
the Candidate's `GlobalStakeShares`
- RedelegatingShares: The portion of `IssuedDelegatorShares` which are
currently re-delegating to a new validator
- VotingPower: Proportional to the amount of bonded tokens which the validator
has if the validator is within the top 100 validators.
- Commission: The commission rate of fees charged to any delegators
- CommissionMax: The maximum commission rate which this candidate can charge
each day from the date `GlobalState.DateLastCommissionReset`
- CommissionChangeRate: The maximum daily increase of the candidate commission
- CommissionChangeToday: Counter for the amount of change to commission rate
which has occurred today, reset on the first block of each day (UTC time)
- ProposerRewardPool: reward pool for extra fees collected when this candidate
is the proposer of a block
- Adjustment factor used to passively calculate each validators entitled fees
from `GlobalState.FeePool`
- Description
- Name: moniker
- DateBonded: date determined which the validator was bonded
- Identity: optional field to provide a signature which verifies the
validators identity (ex. UPort or Keybase)
- Website: optional website link
- Details: optional details
validator candidacy can be declared using the `TxDeclareCandidacy` transaction.
During this transaction a self-delegation transaction is executed to bond
tokens which are sent in with the transaction.
``` golang
type TxDeclareCandidacy struct {
PubKey crypto.PubKey
Amount coin.Coin
GovernancePubKey crypto.PubKey
Commission rational.Rat
CommissionMax int64
CommissionMaxChange int64
Description Description
}
```
For all subsequent self-bonding, whether self-bonding or delegation the
`TxDelegate` function should be used. In this context `TxUnbond` is used to
unbond either delegation bonds or validator self-bonds.
If either the `Description` (excluding `DateBonded` which is constant),
`Commission`, or the `GovernancePubKey` need to be updated, the
`TxEditCandidacy` transaction should be sent from the owner account:
``` golang
type TxEditCandidacy struct {
GovernancePubKey crypto.PubKey
Commission int64
Description Description
}
```
### Persistent State
Within the store, each `Candidate` is stored by validator-pubkey.
- key: validator-pubkey
- value: `Candidate` object
A second key-value pair is also persisted in order to quickly sort though the
group of all candidates, this second index is however not persisted through the
merkle store.
- key: `Candidate.GlobalStakeShares`
- value: `Candidate.PubKey`
When the set of all validators needs to be determined from the group of all
candidates, the top candidates, sorted by GlobalStakeShares can be retrieved
from this sorting without the need to retrieve the entire group of candidates.
When validators are kicked from the validator set they are removed from this
list.
### New Validators
The validator set is updated in the first block of every hour. Validators are
taken as the first `GlobalState.MaxValidators` number of candidates with the
greatest amount of staked atoms who have not been kicked from the validator
set.
### Kicked Validators
Unbonding of an entire validator-candidate to a temporary liquid account occurs
under the scenarios:
- not enough stake to be within the validator set
- the owner unbonds all of their staked tokens
- validator liveliness issues
- crosses a self-imposed safety threshold
- minimum number of tokens staked by owner
- minimum ratio of tokens staked by owner to delegator tokens
When this occurs delegator's tokens do not unbond to their personal wallets but
begin the unbonding process to a pool where they must then transact in order to
withdraw to their respective wallets. The following unbonding will use the
following queue element
``` golang
type QueueElemUnbondCandidate struct {
QueueElem
}
```
If a delegator chooses to initiate an unbond or re-delegation of their shares
while a candidate-unbond is commencing, then that unbond/re-delegation is
subject to a reduced unbonding period based on how much time those funds have
already spent in the unbonding queue.
#### Liveliness issues
Liveliness issues are calculated by keeping track of the block precommits in
the block header. A queue is persisted which contains the block headers from
all recent blocks for the duration of the unbonding period. A validator is
defined as having livliness issues if they have not been included in more than
33% of the blocks over:
- The most recent 24 Hours if they have >= 20% of global stake
- The most recent week if they have = 0% of global stake
- Linear interpolation of the above two scenarios
Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is
submitted.
``` golang
type TxLivelinessCheck struct {
PubKey crypto.PubKey
RewardAccount Addresss
}
```
If the `TxLivelinessCheck is successful in kicking a validator, 5% of the
liveliness punishment is provided as a reward to `RewardAccount`.
#### Validator Liveliness Proof
If the validator was kicked for liveliness issues and is able to regain
liveliness then all delegators in the temporary unbonding pool which have not
transacted to move will be bonded back to the now-live validator and begin to
once again collect provisions and rewards. Regaining livliness is demonstrated
by sending in a `TxProveLive` transaction:
``` golang
type TxProveLive struct {
PubKey crypto.PubKey
}
```
## Delegator bond
Atom holders may delegate coins to validators, under this circumstance their
funds are held in a `DelegatorBond`. It is owned by one delegator, and is
associated with the shares for one validator. The sender of the transaction is
considered to be the owner of the bond,
``` golang
type DelegatorBond struct {
Candidate crypto.PubKey
Shares rational.Rat
AdjustmentFeePool coin.Coins
AdjustmentRewardPool coin.Coins
}
```
Description:
- Candidate: pubkey of the validator candidate: bonding too
- Shares: the number of shares received from the validator candidate
- AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
entitled fees from `GlobalState.FeePool`
- AdjustmentRewardPool: Adjustment factor used to passively calculate each
bonds entitled fees from `Candidate.ProposerRewardPool``
Each `DelegatorBond` is individually indexed within the store by delegator
address and candidate pubkey.
- key: Delegator and Candidate-Pubkey
- value: DelegatorBond
### Delegating
Delegator bonds are created using the TxDelegate transaction. Within this
transaction the validator candidate queried with an amount of coins, whereby
given the current exchange rate of candidate's delegator-shares-to-atoms the
candidate will return shares which are assigned in `DelegatorBond.Shares`.
``` golang
type TxDelegate struct {
PubKey crypto.PubKey
Amount coin.Coin
}
```
### Unbonding
Delegator unbonding is defined by the following transaction type:
``` golang
type TxUnbond struct {
PubKey crypto.PubKey
Shares rational.Rat
}
```
When unbonding is initiated, delegator shares are immediately removed from the
candidate and added to a queue object.
``` golang
type QueueElemUnbondDelegation struct {
QueueElem
Payout Address // account to pay out to
Shares rational.Rat // amount of shares which are unbonding
StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation
}
```
In the unbonding queue - the fraction of all historical slashings on
that validator are recorded (`StartSlashRatio`). When this queue reaches maturity
if that total slashing applied is greater on the validator then the
difference (amount that should have been slashed from the first validator) is
assigned to the amount being paid out.
### Re-Delegation
The re-delegation command allows delegators to switch validators while still
receiving equal reward to as if you had never unbonded.
``` golang
type TxRedelegate struct {
PubKeyFrom crypto.PubKey
PubKeyTo crypto.PubKey
Shares rational.Rat
}
```
When re-delegation is initiated, delegator shares remain accounted for within
the `Candidate.Shares`, the term `RedelegatingShares` is incremented and a
queue element is created.
``` golang
type QueueElemReDelegate struct {
QueueElem
Payout Address // account to pay out to
Shares rational.Rat // amount of shares which are unbonding
NewCandidate crypto.PubKey // validator to bond to after unbond
}
```
During the unbonding period all unbonding shares do not count towards the
voting power of a validator. Once the `QueueElemReDelegation` has reached
maturity, the appropriate unbonding shares are removed from the `Shares` and
`RedelegatingShares` term.
Note that with the current menchanism a delegator cannot redelegate funds which
are currently redelegating.
### Cancel Unbonding
A delegator who is in the process of unbonding from a validator may use the
re-delegate transaction to bond back to the original validator they're
currently unbonding from (and only that validator). If initiated, the delegator
will immediately begin to one again collect rewards from their validator.
## Provision Calculations
Every hour atom provisions are assigned proportionally to the each slashable
bonded token which includes re-delegating atoms but not unbonding tokens.
Validation provisions are payed directly to a global hold account
(`BondedTokenPool`) and proportions of that hold account owned by each
validator is defined as the `GlobalStakeBonded`. The tokens are payed as bonded
tokens.
Here, the bonded tokens that a candidate has can be calculated as:
```
globalStakeExRate = params.BondedTokenPool / params.IssuedGlobalStakeShares
candidateCoins = candidate.GlobalStakeShares * globalStakeExRate
```
If a delegator chooses to add more tokens to a validator then the amount of
validator shares distributed is calculated on exchange rate (aka every
delegators shares do not change value at that moment. The validator's
accounting of distributed shares to delegators must also increased at every
deposit.
```
delegatorExRate = validatorCoins / candidate.IssuedDelegatorShares
createShares = coinsDeposited / delegatorExRate
candidate.IssuedDelegatorShares += createShares
```
Whenever a validator has new tokens added to it, the `BondedTokenPool` is
increased and must be reflected in the global parameter as well as the
validators `GlobalStakeShares`. This calculation ensures that the worth of the
`GlobalStakeShares` of other validators remains worth a constant absolute
amount of the `BondedTokenPool`
```
createdGlobalStakeShares = coinsDeposited / globalStakeExRate
validator.GlobalStakeShares += createdGlobalStakeShares
params.IssuedGlobalStakeShares += createdGlobalStakeShares
params.BondedTokenPool += coinsDeposited
```
Similarly, if a delegator wanted to unbond coins:
```
coinsWithdrawn = withdrawlShares * delegatorExRate
destroyedGlobalStakeShares = coinsWithdrawn / globalStakeExRate
validator.GlobalStakeShares -= destroyedGlobalStakeShares
params.IssuedGlobalStakeShares -= destroyedGlobalStakeShares
params.BondedTokenPool -= coinsWithdrawn
```
Note that when an re-delegation occurs the shares to move are placed in an
re-delegation queue where they continue to collect validator provisions until
queue element matures. Although provisions are collected during re-delegation,
re-delegation tokens do not contribute to the voting power of a validator.
Validator provisions are minted on an hourly basis (the first block of a new
hour). The annual target of between 7% and 20%. The long-term target ratio of
bonded tokens to unbonded tokens is 67%.
The target annual inflation rate is recalculated for each previsions cycle. The
inflation is also subject to a rate change (positive of negative) depending or
the distance from the desired ratio (67%). The maximum rate change possible is
defined to be 13% per year, however the annual inflation is capped as between
7% and 20%.
```
inflationRateChange(0) = 0
annualInflation(0) = 0.07
bondedRatio = bondedTokenPool / totalTokenSupply
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
annualInflation += AnnualInflationRateChange
if annualInflation > 0.20 then annualInflation = 0.20
if annualInflation < 0.07 then annualInflation = 0.07
provisionTokensHourly = totalTokenSupply * annualInflation / (365.25*24)
```
Because the validators hold a relative bonded share (`GlobalStakeShare`), when
more bonded tokens are added proportionally to all validators the only term
which needs to be updated is the `BondedTokenPool`. So for each previsions
cycle:
```
params.BondedTokenPool += provisionTokensHourly
```
## Fee Calculations
Collected fees are pooled globally and divided out passively to validators and
delegators. Each validator has the opportunity to charge commission to the
delegators on the fees collected on behalf of the delegators by the validators.
Fees are paid directly into a global fee pool. Due to the nature of of passive
accounting whenever changes to parameters which affect the rate of fee
distribution occurs, withdrawal of fees must also occur.
- when withdrawing one must withdrawal the maximum amount they are entitled
too, leaving nothing in the pool,
- when bonding, unbonding, or re-delegating tokens to an existing account a
full withdrawal of the fees must occur (as the rules for lazy accounting
change),
- when a candidate chooses to change the commission on fees, all accumulated
commission fees must be simultaneously withdrawn.
When the validator is the proposer of the round, that validator (and their
delegators) receives between 1% and 5% of fee rewards, the reserve tax is then
charged, then the remainder is distributed socially by voting power to all
validators including the proposer validator. The amount of proposer reward is
calculated from pre-commits Tendermint messages. All provision rewards are
added to a provision reward pool which validator holds individually. Here note
that `BondedShares` represents the sum of all voting power saved in the
`GlobalState` (denoted `gs`).
```
proposerReward = feesCollected * (0.01 + 0.04
* sumOfVotingPowerOfPrecommitValidators / gs.BondedShares)
candidate.ProposerRewardPool += proposerReward
reserveTaxed = feesCollected * params.ReserveTax
gs.ReservePool += reserveTaxed
distributedReward = feesCollected - proposerReward - reserveTaxed
gs.FeePool += distributedReward
gs.SumFeesReceived += distributedReward
gs.RecentFee = distributedReward
```
The entitlement to the fee pool held by the each validator can be accounted for
lazily. First we must account for a candidate's `count` and `adjustment`. The
`count` represents a lazy accounting of what that candidates entitlement to the
fee pool would be if there `VotingPower` was to never change and they were to
never withdraw fees.
```
candidate.count = candidate.VotingPower * BlockHeight
```
Similarly the GlobalState count can be passively calculated whenever needed,
where `BondedShares` is the updated sum of voting powers from all validators.
```
gs.count = gs.BondedShares * BlockHeight
```
The `adjustment` term accounts for changes in voting power and withdrawals of
fees. The adjustment factor must be persisted with the candidate and modified
whenever fees are withdrawn from the candidate or the voting power of the
candidate changes. When the voting power of the candidate changes the
`Adjustment` factor is increased/decreased by the cumulative difference in the
voting power if the voting power has been the new voting power as opposed to
the old voting power for the entire duration of the blockchain up the previous
block. Each time there is an adjustment change the GlobalState (denoted `gs`)
`Adjustment` must also be updated.
```
simplePool = candidate.count / gs.count * gs.SumFeesReceived
projectedPool = candidate.PrevPower * (height-1)
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
+ candidate.Power / gs.Power * gs.RecentFee
AdjustmentChange = simplePool - projectedPool
candidate.AdjustmentRewardPool += AdjustmentChange
gs.Adjustment += AdjustmentChange
```
Every instance that the voting power changes, information about the state of
the validator set during the change must be recorded as a `powerChange` for
other validators to run through. Before any validator modifies its voting power
it must first run through the above calculation to determine the change in
their `caandidate.AdjustmentRewardPool` for all historical changes in the set
of `powerChange` which they have not yet synced to. The set of all
`powerChange` may be trimmed from its oldest members once all validators have
synced past the height of the oldest `powerChange`. This trim procedure will
occur on an epoch basis.
```golang
type powerChange struct {
height int64 // block height at change
power rational.Rat // total power at change
prevpower rational.Rat // total power at previous height-1
feesin coins.Coin // fees in at block height
prevFeePool coins.Coin // total fees in at previous block height
}
```
Note that the adjustment factor may result as negative if the voting power of a
different candidate has decreased.
```
candidate.AdjustmentRewardPool += withdrawn
gs.Adjustment += withdrawn
```
Now the entitled fee pool of each candidate can be lazily accounted for at
any given block:
```
candidate.feePool = candidate.simplePool - candidate.Adjustment
```
So far we have covered two sources fees which can be withdrawn from: Fees from
proposer rewards (`candidate.ProposerRewardPool`), and fees from the fee pool
(`candidate.feePool`). However we should note that all fees from fee pool are
subject to commission rate from the owner of the candidate. These next
calculations outline the math behind withdrawing fee rewards as either a
delegator to a candidate providing commission, or as the owner of a candidate
who is receiving commission.
### Calculations For Delegators and Candidates
The same mechanism described to calculate the fees which an entire validator is
entitled to is be applied to delegator level to determine the entitled fees for
each delegator and the candidates entitled commission from `gs.FeesPool` and
`candidate.ProposerRewardPool`.
The calculations are identical with a few modifications to the parameters:
- Delegator's entitlement to `gs.FeePool`:
- entitled party voting power should be taken as the effective voting power
after commission is retrieved,
`bond.Shares/candidate.TotalDelegatorShares * candidate.VotingPower * (1 - candidate.Commission)`
- Delegator's entitlement to `candidate.ProposerFeePool`
- global power in this context is actually shares
`candidate.TotalDelegatorShares`
- entitled party voting power should be taken as the effective shares after
commission is retrieved, `bond.Shares * (1 - candidate.Commission)`
- Candidate's commission entitlement to `gs.FeePool`
- entitled party voting power should be taken as the effective voting power
of commission portion of total voting power,
`candidate.VotingPower * candidate.Commission`
- Candidate's commission entitlement to `candidate.ProposerFeePool`
- global power in this context is actually shares
`candidate.TotalDelegatorShares`
- entitled party voting power should be taken as the of commission portion
of total delegators shares,
`candidate.TotalDelegatorShares * candidate.Commission`
For more implementation ideas see spreadsheet `spec/AbsoluteFeeDistrModel.xlsx`
As mentioned earlier, every time the voting power of a delegator bond is
changing either by unbonding or further bonding, all fees must be
simultaneously withdrawn. Similarly if the validator changes the commission
rate, all commission on fees must be simultaneously withdrawn.
### Other general notes on fees accounting
- When a delegator chooses to re-delegate shares, fees continue to accumulate
until the re-delegation queue reaches maturity. At the block which the queue
reaches maturity and shares are re-delegated all available fees are
simultaneously withdrawn.
- Whenever a totally new validator is added to the validator set, the `accum`
of the entire candidate must be 0, meaning that the initial value for
`candidate.Adjustment` must be set to the value of `canidate.Count` for the
height which the candidate is added on the validator set.
- The feePool of a new delegator bond will be 0 for the height at which the bond
was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to
the height which the bond was added.

View File

@ -1,698 +0,0 @@
# Stake Module
## Overview
The stake module is tasked with various core staking functionality,
including validator set rotation, unbonding periods, and the
distribution of inflationary provisions and transaction fees.
It is designed to efficiently facilitate small numbers of
validators (hundreds), and large numbers of delegators (tens of thousands).
Bonded Atoms are pooled globally and for each validator.
Validators have shares in the global pool, and delegators
have shares in the pool of every validator they delegate to.
Atom provisions simply accumulate in the global pool, making
each share worth proportionally more.
Validator shares can be redeemed for Atoms, but the Atoms will be locked in a queue
for an unbonding period before they can be withdrawn to an account.
Delegators can exchange one validator's shares for another immediately
(ie. they can re-delegate to another validator), but must then wait the
unbonding period before they can do it again.
Fees are pooled separately and withdrawn lazily, at any time.
They are not bonded, and can be paid in multiple tokens.
An adjustment factor is maintained for each validator
and delegator to determine the true proportion of fees in the pool they are entitled too.
Adjustment factors are updated every time a validator or delegator's voting power changes.
Validators and delegators must withdraw all fees they are entitled too before they can bond or
unbond Atoms.
## State
The staking module persists the following to the store:
- `GlobalState`, describing the global pools
- a `Candidate` for each candidate validator, indexed by public key
- a `Candidate` for each candidate validator, indexed by shares in the global pool (ie. ordered)
- a `DelegatorBond` for each delegation to a candidate by a delegator, indexed by delegator and candidate
public keys
- a `Queue` of unbonding delegations (TODO)
### Global State
``` golang
type GlobalState struct {
TotalSupply int64 // total supply of atom tokens
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
BondedPool int64 // reserve of bonded tokens
UnbondedPool int64 // reserve of unbonded tokens held with candidates
InflationLastTime int64 // timestamp of last processing of inflation
Inflation rational.Rat // current annual inflation rate
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset
FeePool coin.Coins // fee pool for all the fee shares which have already been distributed
ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
Adjustment rational.Rat // Adjustment factor for calculating global fee accum
}
```
### Candidate
The `Candidate` struct holds the current state and some historical actions of
validators or candidate-validators.
``` golang
type Candidate struct {
Status CandidateStatus
PubKey crypto.PubKey
GovernancePubKey crypto.PubKey
Owner Address
GlobalStakeShares rational.Rat
IssuedDelegatorShares rational.Rat
RedelegatingShares rational.Rat
VotingPower rational.Rat
Commission rational.Rat
CommissionMax rational.Rat
CommissionChangeRate rational.Rat
CommissionChangeToday rational.Rat
ProposerRewardPool coin.Coins
Adjustment rational.Rat
Description Description
}
type CandidateStatus byte
const (
VyingUnbonded CandidateStatus = 0x00
VyingUnbonding CandidateStatus = 0x01
Bonded CandidateStatus = 0x02
KickUnbonding CandidateStatus = 0x03
KickUnbonded CandidateStatus = 0x04
)
type Description struct {
Name string
DateBonded string
Identity string
Website string
Details string
}
```
Candidate parameters are described:
- Status: signal that the candidate is either vying for validator status
either unbonded or unbonding, an active validator, or a kicked validator
either unbonding or unbonded.
- PubKey: separated key from the owner of the candidate as is used strictly
for participating in consensus.
- Owner: Address where coins are bonded from and unbonded to
- GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
`Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` if
`Candidate.Status` is otherwise
- IssuedDelegatorShares: Sum of all shares issued to delegators (which
includes the candidate's self-bond) which represent each of their stake in
the Candidate's `GlobalStakeShares`
- RedelegatingShares: The portion of `IssuedDelegatorShares` which are
currently re-delegating to a new validator
- VotingPower: Proportional to the amount of bonded tokens which the validator
has if the validator is within the top 100 validators.
- Commission: The commission rate of fees charged to any delegators
- CommissionMax: The maximum commission rate which this candidate can charge
each day from the date `GlobalState.DateLastCommissionReset`
- CommissionChangeRate: The maximum daily increase of the candidate commission
- CommissionChangeToday: Counter for the amount of change to commission rate
which has occurred today, reset on the first block of each day (UTC time)
- ProposerRewardPool: reward pool for extra fees collected when this candidate
is the proposer of a block
- Adjustment factor used to passively calculate each validators entitled fees
from `GlobalState.FeePool`
- Description
- Name: moniker
- DateBonded: date determined which the validator was bonded
- Identity: optional field to provide a signature which verifies the
validators identity (ex. UPort or Keybase)
- Website: optional website link
- Details: optional details
Candidates are indexed by their `Candidate.PubKey`.
Additionally, we index empty values by the candidates global stake shares concatenated with the public key.
TODO: be more precise.
When the set of all validators needs to be determined from the group of all
candidates, the top candidates, sorted by GlobalStakeShares can be retrieved
from this sorting without the need to retrieve the entire group of candidates.
When validators are kicked from the validator set they are removed from this
list.
### DelegatorBond
Atom holders may delegate coins to validators, under this circumstance their
funds are held in a `DelegatorBond`. It is owned by one delegator, and is
associated with the shares for one validator. The sender of the transaction is
considered to be the owner of the bond,
``` golang
type DelegatorBond struct {
Candidate crypto.PubKey
Shares rational.Rat
AdjustmentFeePool coin.Coins
AdjustmentRewardPool coin.Coins
}
```
Description:
- Candidate: pubkey of the validator candidate: bonding too
- Shares: the number of shares received from the validator candidate
- AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
entitled fees from `GlobalState.FeePool`
- AdjustmentRewardPool: Adjustment factor used to passively calculate each
bonds entitled fees from `Candidate.ProposerRewardPool``
Each `DelegatorBond` is individually indexed within the store by delegator
address and candidate pubkey.
- key: Delegator and Candidate-Pubkey
- value: DelegatorBond
### Unbonding Queue
- main unbonding queue contains both UnbondElem and RedelegateElem
- "queue" + <i>
- new unbonding queue every time a val leaves the validator set
- "queue"+ <candidate.pubkey > + <i>
The queue is ordered so the next to unbond/re-delegate is at the head. Every
tick the head of the queue is checked and if the unbonding period has passed
since `InitHeight` commence with final settlement of the unbonding and pop the
queue. All queue elements used for unbonding share a common struct:
``` golang
type QueueElem struct {
Candidate crypto.PubKey
InitHeight int64 // when the queue was initiated
}
```
``` golang
type QueueElemUnbondCandidate struct {
QueueElem
}
```
``` golang
type QueueElemUnbondDelegation struct {
QueueElem
Payout Address // account to pay out to
Shares rational.Rat // amount of shares which are unbonding
StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation
}
```
``` golang
type QueueElemReDelegate struct {
QueueElem
Payout Address // account to pay out to
Shares rational.Rat // amount of shares which are unbonding
NewCandidate crypto.PubKey // validator to bond to after unbond
}
```
Each `QueueElem` is persisted in the store until it is popped from the queue.
## Transactions
### TxDeclareCandidacy
Validator candidacy can be declared using the `TxDeclareCandidacy` transaction.
During this transaction a self-delegation transaction is executed to bond
tokens which are sent in with the transaction.
``` golang
type TxDeclareCandidacy struct {
PubKey crypto.PubKey
Amount coin.Coin
GovernancePubKey crypto.PubKey
Commission rational.Rat
CommissionMax int64
CommissionMaxChange int64
Description Description
}
```
### TxEditCandidacy
If either the `Description` (excluding `DateBonded` which is constant),
`Commission`, or the `GovernancePubKey` need to be updated, the
`TxEditCandidacy` transaction should be sent from the owner account:
``` golang
type TxEditCandidacy struct {
GovernancePubKey crypto.PubKey
Commission int64
Description Description
}
```
### TxLivelinessCheck
Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is
submitted.
``` golang
type TxLivelinessCheck struct {
PubKey crypto.PubKey
RewardAccount Addresss
}
```
If the `TxLivelinessCheck is successful in kicking a validator, 5% of the
liveliness punishment is provided as a reward to `RewardAccount`.
### TxProveLive
If the validator was kicked for liveliness issues and is able to regain
liveliness then all delegators in the temporary unbonding pool which have not
transacted to move will be bonded back to the now-live validator and begin to
once again collect provisions and rewards. Regaining livliness is demonstrated
by sending in a `TxProveLive` transaction:
``` golang
type TxProveLive struct {
PubKey crypto.PubKey
}
```
### TxDelegate
All bonding, whether self-bonding or delegation, is done via
`TxDelegate`.
Delegator bonds are created using the TxDelegate transaction. Within this
transaction the validator candidate queried with an amount of coins, whereby
given the current exchange rate of candidate's delegator-shares-to-atoms the
candidate will return shares which are assigned in `DelegatorBond.Shares`.
``` golang
type TxDelegate struct {
PubKey crypto.PubKey
Amount coin.Coin
}
```
### TxUnbond
In this context `TxUnbond` is used to
unbond either delegation bonds or validator self-bonds.
Delegator unbonding is defined by the following transaction type:
``` golang
type TxUnbond struct {
PubKey crypto.PubKey
Shares rational.Rat
}
```
### TxRedelegate
The re-delegation command allows delegators to switch validators while still
receiving equal reward to as if you had never unbonded.
``` golang
type TxRedelegate struct {
PubKeyFrom crypto.PubKey
PubKeyTo crypto.PubKey
Shares rational.Rat
}
```
A delegator who is in the process of unbonding from a validator may use the
re-delegate transaction to bond back to the original validator they're
currently unbonding from (and only that validator). If initiated, the delegator
will immediately begin to one again collect rewards from their validator.
### TxWithdraw
....
## EndBlock
### Update Validators
The validator set is updated in the first block of every hour. Validators are
taken as the first `GlobalState.MaxValidators` number of candidates with the
greatest amount of staked atoms who have not been kicked from the validator
set.
Unbonding of an entire validator-candidate to a temporary liquid account occurs
under the scenarios:
- not enough stake to be within the validator set
- the owner unbonds all of their staked tokens
- validator liveliness issues
- crosses a self-imposed safety threshold
- minimum number of tokens staked by owner
- minimum ratio of tokens staked by owner to delegator tokens
When this occurs delegator's tokens do not unbond to their personal wallets but
begin the unbonding process to a pool where they must then transact in order to
withdraw to their respective wallets.
### Unbonding
When unbonding is initiated, delegator shares are immediately removed from the
candidate and added to a queue object.
In the unbonding queue - the fraction of all historical slashings on
that validator are recorded (`StartSlashRatio`). When this queue reaches maturity
if that total slashing applied is greater on the validator then the
difference (amount that should have been slashed from the first validator) is
assigned to the amount being paid out.
#### Liveliness issues
Liveliness issues are calculated by keeping track of the block precommits in
the block header. A queue is persisted which contains the block headers from
all recent blocks for the duration of the unbonding period.
A validator is defined as having livliness issues if they have not been included in more than
33% of the blocks over:
- The most recent 24 Hours if they have >= 20% of global stake
- The most recent week if they have = 0% of global stake
- Linear interpolation of the above two scenarios
## Invariants
-----------------------------
------------
If a delegator chooses to initiate an unbond or re-delegation of their shares
while a candidate-unbond is commencing, then that unbond/re-delegation is
subject to a reduced unbonding period based on how much time those funds have
already spent in the unbonding queue.
### Re-Delegation
When re-delegation is initiated, delegator shares remain accounted for within
the `Candidate.Shares`, the term `RedelegatingShares` is incremented and a
queue element is created.
During the unbonding period all unbonding shares do not count towards the
voting power of a validator. Once the `QueueElemReDelegation` has reached
maturity, the appropriate unbonding shares are removed from the `Shares` and
`RedelegatingShares` term.
Note that with the current menchanism a delegator cannot redelegate funds which
are currently redelegating.
----------------------------------------------
## Provision Calculations
Every hour atom provisions are assigned proportionally to the each slashable
bonded token which includes re-delegating atoms but not unbonding tokens.
Validation provisions are payed directly to a global hold account
(`BondedTokenPool`) and proportions of that hold account owned by each
validator is defined as the `GlobalStakeBonded`. The tokens are payed as bonded
tokens.
Here, the bonded tokens that a candidate has can be calculated as:
```
globalStakeExRate = params.BondedTokenPool / params.IssuedGlobalStakeShares
candidateCoins = candidate.GlobalStakeShares * globalStakeExRate
```
If a delegator chooses to add more tokens to a validator then the amount of
validator shares distributed is calculated on exchange rate (aka every
delegators shares do not change value at that moment. The validator's
accounting of distributed shares to delegators must also increased at every
deposit.
```
delegatorExRate = validatorCoins / candidate.IssuedDelegatorShares
createShares = coinsDeposited / delegatorExRate
candidate.IssuedDelegatorShares += createShares
```
Whenever a validator has new tokens added to it, the `BondedTokenPool` is
increased and must be reflected in the global parameter as well as the
validators `GlobalStakeShares`. This calculation ensures that the worth of the
`GlobalStakeShares` of other validators remains worth a constant absolute
amount of the `BondedTokenPool`
```
createdGlobalStakeShares = coinsDeposited / globalStakeExRate
validator.GlobalStakeShares += createdGlobalStakeShares
params.IssuedGlobalStakeShares += createdGlobalStakeShares
params.BondedTokenPool += coinsDeposited
```
Similarly, if a delegator wanted to unbond coins:
```
coinsWithdrawn = withdrawlShares * delegatorExRate
destroyedGlobalStakeShares = coinsWithdrawn / globalStakeExRate
validator.GlobalStakeShares -= destroyedGlobalStakeShares
params.IssuedGlobalStakeShares -= destroyedGlobalStakeShares
params.BondedTokenPool -= coinsWithdrawn
```
Note that when an re-delegation occurs the shares to move are placed in an
re-delegation queue where they continue to collect validator provisions until
queue element matures. Although provisions are collected during re-delegation,
re-delegation tokens do not contribute to the voting power of a validator.
Validator provisions are minted on an hourly basis (the first block of a new
hour). The annual target of between 7% and 20%. The long-term target ratio of
bonded tokens to unbonded tokens is 67%.
The target annual inflation rate is recalculated for each previsions cycle. The
inflation is also subject to a rate change (positive of negative) depending or
the distance from the desired ratio (67%). The maximum rate change possible is
defined to be 13% per year, however the annual inflation is capped as between
7% and 20%.
```
inflationRateChange(0) = 0
annualInflation(0) = 0.07
bondedRatio = bondedTokenPool / totalTokenSupply
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
annualInflation += AnnualInflationRateChange
if annualInflation > 0.20 then annualInflation = 0.20
if annualInflation < 0.07 then annualInflation = 0.07
provisionTokensHourly = totalTokenSupply * annualInflation / (365.25*24)
```
Because the validators hold a relative bonded share (`GlobalStakeShare`), when
more bonded tokens are added proportionally to all validators the only term
which needs to be updated is the `BondedTokenPool`. So for each previsions
cycle:
```
params.BondedTokenPool += provisionTokensHourly
```
## Fee Calculations
Collected fees are pooled globally and divided out passively to validators and
delegators. Each validator has the opportunity to charge commission to the
delegators on the fees collected on behalf of the delegators by the validators.
Fees are paid directly into a global fee pool. Due to the nature of of passive
accounting whenever changes to parameters which affect the rate of fee
distribution occurs, withdrawal of fees must also occur.
- when withdrawing one must withdrawal the maximum amount they are entitled
too, leaving nothing in the pool,
- when bonding, unbonding, or re-delegating tokens to an existing account a
full withdrawal of the fees must occur (as the rules for lazy accounting
change),
- when a candidate chooses to change the commission on fees, all accumulated
commission fees must be simultaneously withdrawn.
When the validator is the proposer of the round, that validator (and their
delegators) receives between 1% and 5% of fee rewards, the reserve tax is then
charged, then the remainder is distributed socially by voting power to all
validators including the proposer validator. The amount of proposer reward is
calculated from pre-commits Tendermint messages. All provision rewards are
added to a provision reward pool which validator holds individually. Here note
that `BondedShares` represents the sum of all voting power saved in the
`GlobalState` (denoted `gs`).
```
proposerReward = feesCollected * (0.01 + 0.04
* sumOfVotingPowerOfPrecommitValidators / gs.BondedShares)
candidate.ProposerRewardPool += proposerReward
reserveTaxed = feesCollected * params.ReserveTax
gs.ReservePool += reserveTaxed
distributedReward = feesCollected - proposerReward - reserveTaxed
gs.FeePool += distributedReward
gs.SumFeesReceived += distributedReward
gs.RecentFee = distributedReward
```
The entitlement to the fee pool held by the each validator can be accounted for
lazily. First we must account for a candidate's `count` and `adjustment`. The
`count` represents a lazy accounting of what that candidates entitlement to the
fee pool would be if there `VotingPower` was to never change and they were to
never withdraw fees.
```
candidate.count = candidate.VotingPower * BlockHeight
```
Similarly the GlobalState count can be passively calculated whenever needed,
where `BondedShares` is the updated sum of voting powers from all validators.
```
gs.count = gs.BondedShares * BlockHeight
```
The `adjustment` term accounts for changes in voting power and withdrawals of
fees. The adjustment factor must be persisted with the candidate and modified
whenever fees are withdrawn from the candidate or the voting power of the
candidate changes. When the voting power of the candidate changes the
`Adjustment` factor is increased/decreased by the cumulative difference in the
voting power if the voting power has been the new voting power as opposed to
the old voting power for the entire duration of the blockchain up the previous
block. Each time there is an adjustment change the GlobalState (denoted `gs`)
`Adjustment` must also be updated.
```
simplePool = candidate.count / gs.count * gs.SumFeesReceived
projectedPool = candidate.PrevPower * (height-1)
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
+ candidate.Power / gs.Power * gs.RecentFee
AdjustmentChange = simplePool - projectedPool
candidate.AdjustmentRewardPool += AdjustmentChange
gs.Adjustment += AdjustmentChange
```
Every instance that the voting power changes, information about the state of
the validator set during the change must be recorded as a `powerChange` for
other validators to run through. Before any validator modifies its voting power
it must first run through the above calculation to determine the change in
their `caandidate.AdjustmentRewardPool` for all historical changes in the set
of `powerChange` which they have not yet synced to. The set of all
`powerChange` may be trimmed from its oldest members once all validators have
synced past the height of the oldest `powerChange`. This trim procedure will
occur on an epoch basis.
```golang
type powerChange struct {
height int64 // block height at change
power rational.Rat // total power at change
prevpower rational.Rat // total power at previous height-1
feesin coins.Coin // fees in at block height
prevFeePool coins.Coin // total fees in at previous block height
}
```
Note that the adjustment factor may result as negative if the voting power of a
different candidate has decreased.
```
candidate.AdjustmentRewardPool += withdrawn
gs.Adjustment += withdrawn
```
Now the entitled fee pool of each candidate can be lazily accounted for at
any given block:
```
candidate.feePool = candidate.simplePool - candidate.Adjustment
```
So far we have covered two sources fees which can be withdrawn from: Fees from
proposer rewards (`candidate.ProposerRewardPool`), and fees from the fee pool
(`candidate.feePool`). However we should note that all fees from fee pool are
subject to commission rate from the owner of the candidate. These next
calculations outline the math behind withdrawing fee rewards as either a
delegator to a candidate providing commission, or as the owner of a candidate
who is receiving commission.
### Calculations For Delegators and Candidates
The same mechanism described to calculate the fees which an entire validator is
entitled to is be applied to delegator level to determine the entitled fees for
each delegator and the candidates entitled commission from `gs.FeesPool` and
`candidate.ProposerRewardPool`.
The calculations are identical with a few modifications to the parameters:
- Delegator's entitlement to `gs.FeePool`:
- entitled party voting power should be taken as the effective voting power
after commission is retrieved,
`bond.Shares/candidate.TotalDelegatorShares * candidate.VotingPower * (1 - candidate.Commission)`
- Delegator's entitlement to `candidate.ProposerFeePool`
- global power in this context is actually shares
`candidate.TotalDelegatorShares`
- entitled party voting power should be taken as the effective shares after
commission is retrieved, `bond.Shares * (1 - candidate.Commission)`
- Candidate's commission entitlement to `gs.FeePool`
- entitled party voting power should be taken as the effective voting power
of commission portion of total voting power,
`candidate.VotingPower * candidate.Commission`
- Candidate's commission entitlement to `candidate.ProposerFeePool`
- global power in this context is actually shares
`candidate.TotalDelegatorShares`
- entitled party voting power should be taken as the of commission portion
of total delegators shares,
`candidate.TotalDelegatorShares * candidate.Commission`
For more implementation ideas see spreadsheet `spec/AbsoluteFeeDistrModel.xlsx`
As mentioned earlier, every time the voting power of a delegator bond is
changing either by unbonding or further bonding, all fees must be
simultaneously withdrawn. Similarly if the validator changes the commission
rate, all commission on fees must be simultaneously withdrawn.
### Other general notes on fees accounting
- When a delegator chooses to re-delegate shares, fees continue to accumulate
until the re-delegation queue reaches maturity. At the block which the queue
reaches maturity and shares are re-delegated all available fees are
simultaneously withdrawn.
- Whenever a totally new validator is added to the validator set, the `accum`
of the entire candidate must be 0, meaning that the initial value for
`candidate.Adjustment` must be set to the value of `canidate.Count` for the
height which the candidate is added on the validator set.
- The feePool of a new delegator bond will be 0 for the height at which the bond
was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to
the height which the bond was added.

View File

@ -1,94 +0,0 @@
# Testnet Setup
**Note:** This document is incomplete and may not be up-to-date with the
state of the code.
See the [installation guide](../sdk/install.html) for details on
installation.
Here is a quick example to get you off your feet:
First, generate a couple of genesis transactions to be incorporated into
the genesis file, this will create two keys with the password
`1234567890`:
```
gaiad init gen-tx --name=foo --home=$HOME/.gaiad1
gaiad init gen-tx --name=bar --home=$HOME/.gaiad2
gaiacli keys list
```
**Note:** If you've already run these tests you may need to overwrite
keys using the `--owk` flag When you list the keys you should see two
addresses, we'll need these later so take note. Now let's actually
create the genesis files for both nodes:
```
cp -a ~/.gaiad2/config/gentx/. ~/.gaiad1/config/gentx/
cp -a ~/.gaiad1/config/gentx/. ~/.gaiad2/config/gentx/
gaiad init --gen-txs --home=$HOME/.gaiad1 --chain-id=test-chain
gaiad init --gen-txs --home=$HOME/.gaiad2 --chain-id=test-chain
```
**Note:** If you've already run these tests you may need to overwrite
genesis using the `-o` flag. What we just did is copy the genesis
transactions between each of the nodes so there is a common genesis
transaction set; then we created both genesis files independently from
each home directory. Importantly both nodes have independently created
their `genesis.json` and `config.toml` files, which should be identical
between nodes.
Great, now that we've initialized the chains, we can start both nodes in
the background:
```
gaiad start --home=$HOME/.gaiad1 &> gaia1.log &
NODE1_PID=$!
gaia start --home=$HOME/.gaiad2 &> gaia2.log &
NODE2_PID=$!
```
Note that we save the PID so we can later kill the processes. You can
peak at your logs with `tail gaia1.log`, or follow them for a bit with
`tail -f gaia1.log`.
Nice. We can also lookup the validator set:
```
gaiacli validatorset
```
Then, we try to transfer some `steak` to another account:
```
gaiacli account <FOO-ADDR>
gaiacli account <BAR-ADDR>
gaiacli send --amount=10steak --to=<BAR-ADDR> --from=foo --chain-id=test-chain
```
**Note:** We need to be careful with the `chain-id` and `sequence`
Check the balance & sequence with:
```
gaiacli account <BAR-ADDR>
```
To confirm for certain the new validator is active, check tendermint:
```
curl localhost:46657/validators
```
Finally, to relinquish all your power, unbond some coins. You should see
your VotingPower reduce and your account balance increase.
```
gaiacli unbond --chain-id=<chain-id> --from=test
```
That's it!
**Note:** TODO demonstrate edit-candidacy **Note:** TODO demonstrate
delegation **Note:** TODO demonstrate unbond of delegation **Note:**
TODO demonstrate unbond candidate

View File

@ -1,10 +1,45 @@
# Running a Node
TODO: document `gaiad`
> TODO: Improve documentation of `gaiad`
## Basics
To start a node:
```shell
$ gaiad start <flags>
```
Options for running the `gaiad` binary are effectively the same as for `tendermint`.
See `gaiad --help` and the
[guide to using Tendermint](https://github.com/tendermint/tendermint/blob/master/docs/using-tendermint.md)
for more details.
## Debugging
Optionally, you can run `gaiad` with `--trace-store` to trace all store operations
to a specified file.
```shell
$ gaiad start <flags> --trace-store=/path/to/trace.out
```
Key/value pairs will be base64 encoded. Additionally, the block number and any
correlated transaction hash will be included as metadata.
e.g.
```json
...
{"operation":"write","key":"ATW6Bu997eeuUeRBwv1EPGvXRfPR","value":"BggEEBYgFg==","metadata":{"blockHeight":12,"txHash":"5AAC197EC45E6C5DE0798C4A4E2F54BBB695CA9E"}}
{"operation":"write","key":"AjW6Bu997eeuUeRBwv1EPGvXRfPRCgAAAAAAAAA=","value":"AQE=","metadata":{"blockHeight":12,"txHash":"5AAC197EC45E6C5DE0798C4A4E2F54BBB695CA9E"}}
{"operation":"read","key":"ATW6Bu997eeuUeRBwv1EPGvXRfPR","value":"BggEEBYgFg==","metadata":{"blockHeight":13}}
{"operation":"read","key":"AjW6Bu997eeuUeRBwv1EPGvXRfPRCwAAAAAAAAA=","value":"","metadata":{"blockHeight":13}}
...
```
You can then query for the various traced operations using a tool like [jq](https://github.com/stedolan/jq).
```shell
$ jq -s '.[] | select((.key=="ATW6Bu997eeuUeRBwv1EPGvXRfPR") and .metadata.blockHeight==14)' /path/to/trace.out
```

View File

@ -0,0 +1,104 @@
# Join the Testnet
Please ensure you have the [Cosmos SDK](/getting-started/installation.md) installed. If you ran a full node on a previous testnet, please skip to [Upgrading From Previous Testnet](#upgrading-from-previous-testnet).
## Setting Up a New Node
These instructions are for setting up a brand new full node from scratch.
First, initialize the node and create the necessary config files:
```bash
gaiad init --name <your_custom_name>
```
::: warning Note
Only ASCII characters are supported for the `--name`. Using Unicode characters will render your node unreachable.
:::
You can edit this `name` later, in the `~/.gaiad/config/config.toml` file:
```toml
# A custom human readable name for this node
moniker = "<your_custom_name>"
```
Your full node has been initialized! Please skip to [Genesis & Seeds](#genesis-seeds).
## Upgrading From Previous Testnet
These instructions are for full nodes that have ran on previous testnets and would like to upgrade to the latest testnet.
### Reset Data
First, remove the outdated files and reset the data.
```bash
rm $HOME/.gaiad/config/addrbook.json $HOME/.gaiad/config/genesis.json
gaiad unsafe_reset_all
```
Your node is now in a pristine state while keeping the original `priv_validator.json` and `config.toml`. If you had any sentry nodes or full nodes setup before,
your node will still try to connect to them, but may fail if they haven't also
been upgraded.
::: danger Warning
Make sure that every node has a unique `priv_validator.json`. Do not copy the `priv_validator.json` from an old node to multiple new nodes. Running two nodes with the same `priv_validator.json` will cause you to double sign.
:::
### Software Upgrade
Now it is time to upgrade the software:
```bash
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
git fetch --all && git checkout v0.19.0
make update_tools && make get_vendor_deps && make install
```
Your full node has been cleanly upgraded!
## Genesis & Seeds
### Copy the Genesis File
Copy the testnet's `genesis.json` file and place it in `gaiad`'s config directory.
```bash
mkdir -p $HOME/.gaiad/config
cp -a $GOPATH/src/github.com/cosmos/cosmos-sdk/cmd/gaia/testnets/gaia-6002/genesis.json $HOME/.gaiad/config/genesis.json
```
### Add Seed Nodes
Your node needs to know how to find peers. You'll need to add healthy seed nodes to `$HOME/.gaiad/config/config.toml`. Here are some seed nodes you can use:
```toml
# Comma separated list of seed nodes to connect to
seeds = "38aa9bec3998f12ae9088b21a2d910d19d565c27@gaia-6002.coinculture.net:46656,80a35a46ce09cfb31ee220c8141a25e73e0b239b@seed.cosmos.cryptium.ch:46656,80a35a46ce09cfb31ee220c8141a25e73e0b239b@35.198.166.171:46656,032fa56301de335d835057fb6ad9f7ce2242a66d@165.227.236.213:46656"
```
If those seeds aren't working, you can find more seeds and persistent peers on the [Cosmos Explorer](https://explorecosmos.network/nodes). Open the the `Full Nodes` pane and select nodes that do not have private (`10.x.x.x`) or [local IP addresses](https://en.wikipedia.org/wiki/Private_network). The `Persistent Peer` field contains the connection string. For best results use 4-6.
For more information on seeds and peers, you can [read this](https://github.com/tendermint/tendermint/blob/develop/docs/using-tendermint.md#peers).
## Run a Full Node
Start the full node with this command:
```bash
gaiad start
```
Check that everything is running smoothly:
```bash
gaiacli status
```
View the status of the network with the [Cosmos Explorer](https://explorecosmos.network). Once your full node syncs up to the current block height, you should see it appear on the [list of full nodes](https://explorecosmos.network/validators). If it doesn't show up, that's ok--the Explorer does not connect to every node.
## Upgrade to Validator Node
You now have an active full node. What's the next step? You can upgrade your full node to become a Cosmos Validator. The top 100 validators have the ability to propose new blocks to the Cosmos Hub. Continue onto [the Validator Setup](../validators/validator-setup.md).

View File

@ -0,0 +1,44 @@
# Install the SDK
This guide will explain how to install the [Cosmos SDK](/sdk/overview.md) onto your system. With the SDK installed on a server, you can participate in the latest testnet as either a [Full Node](full-node.md) or a [Validator](/validators/validator-setup.md).
## Install Go
Install `go` by following the [official docs](https://golang.org/doc/install). Remember to set your `$GOPATH`, `$GOBIN`, and `$PATH` environment variables, for example:
```bash
mkdir -p $HOME/go/bin
echo "export GOPATH=$HOME/go" >> ~/.bash_profile
echo "export GOBIN=$GOPATH/bin" >> ~/.bash_profile
echo "export PATH=$PATH:$GOBIN" >> ~/.bash_profile
```
::: tip
**Go 1.10+** is required for the Cosmos SDK.
:::
## Install Cosmos SDK
Next, let's install the testnet's version of the Cosmos SDK.
```bash
mkdir -p $GOPATH/src/github.com/cosmos
cd $GOPATH/src/github.com/cosmos
git clone https://github.com/cosmos/cosmos-sdk
cd cosmos-sdk && git checkout v0.19.0
make get_tools && make get_vendor_deps && make install
```
That will install the `gaiad` and `gaiacli` binaries. Verify that everything is OK:
```bash
$ gaiad version
0.19.0-c6711810
$ gaiacli version
0.19.0-c6711810
```
## Run a Full Node
With Cosmos SDK installed, you can run [a full node on the latest testnet](full-node.md).

View File

@ -0,0 +1,9 @@
# Download Voyager
Voyager is the official desktop app for the Cosmos Network. It provides an intuitive interface for managing accounts, creating transactions, delegation, and governance.
Download the [latest Voyager release](https://github.com/cosmos/voyager/releases).
### Delving Deeper
If you're familar with the CLI, you should consider running a full node or validator for the latest Cosmos testnet. [Learn more](/getting-started/installation.md).

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

View File

@ -1,11 +0,0 @@
.. Cosmos-SDK documentation master file, created by
sphinx-quickstart on Fri Sep 1 21:37:02 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to the Cosmos SDK!
==========================
This location for our documentation has been deprecated, please see:
- https://cosmos.network/docs/

View File

@ -0,0 +1,32 @@
# The Cosmos Hub
The first blockchain in the Cosmos network is the Cosmos hub. The Cosmos hub connects to zones via the novel [IBC](/specs/ibc.md) (inter-blockchain communication) protocol and keeps a record of the total number of tokens in each zone. Because all inter-zone transfers go through the Cosmos Hub, you can send tokens from one zone to another, quickly and securely, without the need for a liquid exchange or trusted third party between zones.
The Cosmos Hub can connect to many different kinds of zones - public or private - as long as each zone speaks IBC. Tendermint-based Zones are natively compatible with IBC, but any fast-finality consensus algorithm can be used as a replacement. Cosmos can thus support a wide variety of currencies and scripting languages like those found in Bitcoin, Ethereum, ZeroCash, CryptoNote, and more. Atom's are the native token of the Cosmos Hub. It is a license for holders to stake and participate in governance.
## Proof-of-Stake
Blockchain networks are secured by a set of validators, who are responsible for committing new blocks in the blockchain. In Proof-Of-Work systems such as Bitcoin, validators are called miners, and the probability of a given miner to produce the next block is proportional to its computing power. In contrast, the Cosmos Hub is a public Proof-of-Stake blockchain. Proof-of-Stake is a category of consensus algorithm that relies on validators economic stake in the network.
## Atoms
In the case of the Cosmos Hub, the frequency at which a validator is selected to produce the next block is proportional to the number of Atoms locked up (i.e. bonded, or staked).
These Atoms can be locked up by validators themselves, or delegated to them by Atom holders that do not want or cannot run validator operations, called delegators. The sum of a validators self-bonded and delegated Atoms is called its stake. The Atom is the only staking token of the Cosmos Hub. In return for locking up their Atoms, delegators earn block provisions (in Atoms), block rewards (in Photons) and transaction fees (in whitelisted fee tokens). When a bonded Atom holder wants to retrieve its deposit, it must wait for a 3 week unbonding period.
## Photons
Atoms are designed to be bonded on the Hub. This means that they are not ideal to pay fees or to move on other Zones of the Cosmos Ecosystem. This is why Photons will be introduced. Photon is a fee token with much greater liquidity and velocity than Atom. It is the second whitelisted fee token on the Hub after Atom and can move on all the Zones that are connected to the Hub.
## Hard spoon
A hard spoon occurs when a new cryptocurrency is minted by replicating the account balances of an existing cryptocurrency. In our case, we are hard spooning Ethereum by taking the account balances of existing Ethereum holders and mirroring those values. This means that ETH holders will have their coins replicated in this EVM zone and will be redeemable as fee tokensPhotonswithin Ethermint.
After launch, Atom holders will be able to vote on the hard spoon, specifically:
- Whether the hard spoon should happen or not
- When the snapshot will occur
- How Photons are distributed (what goes to Ethereum holders, what goes to Atom holders and Photon inflation)
## Validators
Validators of the Cosmos Hub are responsible for creating new blocks of transactions that are added to the blockchain. Running a validator is non-trivial. It requires technical knowledge and hardware investment. Additionally, due to the way that Tendermint—the underlying consensus engine on which the Cosmos Hub is built—works, the number of validators must be limited. Initially, this limit is fixed to 100. This means that only the top 100 addresses with the most stake that declared their intention to become validator will be validators. As a result, most Atom holders will not be validators. Instead, they will become delegators, thereby participating in deciding who among the validator candidates actually become validators.
If you are interested in becoming a validator: learn more about validators [here](/validators/overview.md).
## Delegators
People that cannot, or do not want to run validator operations, can still participate in the staking process as delegators. Indeed, validators are not chosen based on their own stake but based on their total stake, which is the sum of their own stake and of the stake that is delegated to them. If you are interested in staking your atoms to a Validator to earn revenue, or just want to learn more about delegators, read the [Delegator FAQ](/resources/delegator-faq.md).

View File

@ -0,0 +1,13 @@
# Tendermint
Tendermint is software for securely and consistently replicating an application on many machines. By securely, we mean that Tendermint works even if up to 1/3 of machines fail in arbitrary ways. By consistently, we mean that every non-faulty machine sees the same transaction log and computes the same state. Secure and consistent replication is a fundamental problem in distributed systems; it plays a critical role in the fault tolerance of a broad range of applications, from currencies, to elections, to infrastructure orchestration, and beyond.
Tendermint is designed to be easy-to-use, simple-to-understand, highly performant, and useful for a wide variety of distributed applications.
## Byzantine Fault Tolerance
The ability to tolerate machines failing in arbitrary ways, including becoming malicious, is known as Byzantine fault tolerance (BFT). The theory of BFT is decades old, but software implementations have only became popular recently, due largely to the success of “blockchain technology” like Bitcoin and Ethereum. Blockchain technology is just a re-formalization of BFT in a more modern setting, with emphasis on peer-to-peer networking and cryptographic authentication. The name derives from the way transactions are batched in blocks, where each block contains a cryptographic hash of the previous one, forming a chain. In practice, the blockchain data structure actually optimizes BFT design.
## Application Blockchain Interface
Tendermint consists of two chief technical components: a blockchain consensus engine and a generic application interface. The consensus engine, called Tendermint Core, ensures that the same transactions are recorded on every machine in the same order. The application interface, called the Application Blockchain Interface (ABCI), enables the transactions to be processed in any programming language. Unlike other blockchain and consensus solutions developers can use Tendermint for BFT state machine replication in any programming language or development environment. Visit the [Tendermint docs](https://tendermint.readthedocs.io/projects/tools/en/master/introduction.html#abci-overview) for a deep dive into the ABCI.
The [Cosmos SDK](/sdk/overview.md) is an ABCI framework written in Go. [Lotion JS](/lotion/overview.md) is an ABCI framework written in JavaScript.

View File

@ -0,0 +1,7 @@
# What is Cosmos?
Cosmos is a decentralized network of independent parallel blockchains, each powered by classical BFT consensus algorithms like [Tendermint]().
The first blockchain in the Cosmos Network is the [Cosmos Hub](), whose native token is the Atom. Cosmos is a permission-less network, meaning that anybody can build a blockchain on it.
Cosmos can interoperate with multiple other applications and cryptocurrencies. By creating a new zone, you can plug any blockchain system into the Cosmos hub and pass tokens back and forth between those zones, without the need for an intermediary.

View File

@ -0,0 +1,46 @@
# Building an App
::: tip
Lotion requires __node v7.6.0__ or higher, and a mac or linux machine.
:::
## Installation
```
$ npm install lotion
```
## Simple App
`app.js`:
```js
let lotion = require('lotion')
let app = lotion({
initialState: {
count: 0
}
})
app.use(function (state, tx) {
if(state.count === tx.nonce) {
state.count++
}
})
app.listen(3000)
```
run `node app.js`, then:
```bash
$ curl http://localhost:3000/state
# { "count": 0 }
$ curl http://localhost:3000/txs -d '{ "nonce": 0 }'
# { "ok": true }
$ curl http://localhost:3000/state
# { "count": 1 }
```
## Learn More
You can learn more about Lotion JS by visiting Lotion on [Github](https://github.com/keppel/lotion).

5
docs/lotion/overview.md Normal file
View File

@ -0,0 +1,5 @@
# Lotion JS Overview
Lotion is a new way to create blockchain apps in JavaScript, which aims to make writing new blockchains fast and fun. It builds on top of Tendermint using the ABCI protocol. Lotion lets you write secure, scalable applications that can easily interoperate with other blockchains on the Cosmos Network using IBC.
Lotion itself is a tiny framework; its true power comes from the network of small, focused modules built upon it. Adding a fully-featured cryptocurrency to your blockchain, for example, takes only a few lines of code.

View File

@ -1,70 +0,0 @@
# Apps in the SDK
The SDK has multiple levels of "application": the ABCI app, the BaseApp, the BasecoinApp, and now your App.
## ABCI App
The basic ABCI interface allowing Tendermint to drive the applications state machine with transaction blocks.
## BaseApp
Implements an ABCI App using a MultiStore for persistence and a Router to handle transactions.
The goal is to provide a secure interface between the store and the extensible state machine
while defining as little about that state machine as possible (staying true to the ABCI).
BaseApp requires stores to be mounted via capabilities keys - handlers can only access
stores they're given the key for. The BaseApp ensures all stores are properly loaded, cached, and committed.
One mounted store is considered the "main" - it holds the latest block header, from which we can find and load the
most recent state ([TODO](https://github.com/cosmos/cosmos-sdk/issues/522)).
BaseApp distinguishes between two handler types - the `AnteHandler` and the `MsgHandler`.
The former is a global validity check (checking nonces, sigs and sufficient balances to pay fees,
e.g. things that apply to all transaction from all modules), the later is the full state transition function.
During CheckTx the state transition function is only applied to the checkTxState and should return
before any expensive state transitions are run (this is up to each developer). It also needs to return the estimated
gas cost.
During DeliverTx the state transition function is applied to the blockchain state and the transactions
need to be fully executed.
BaseApp is responsible for managing the context passed into handlers -
it makes the block header available and provides the right stores for CheckTx and DeliverTx.
BaseApp is completely agnostic to serialization formats.
## Basecoin
Basecoin is the first complete application in the stack.
Complete applications require extensions to the core modules of the SDK to actually implement handler functionality.
The native extensions of the SDK, useful for building Cosmos Zones, live under `x`.
Basecoin implements a `BaseApp` state machine using the `x/auth` and `x/bank` extensions,
which define how transaction signers are authenticated and how coins are transferred.
It should also use `x/ibc` and probably a simple staking extension.
Basecoin and the native `x` extensions use go-amino for all serialization needs,
including for transactions and accounts.
## Your Cosmos App
Your Cosmos App is a fork of Basecoin - copy the `examples/basecoin` directory and modify it to your needs.
You might want to:
- add fields to accounts
- copy and modify handlers
- add new handlers for new transaction types
- add new stores for better isolation across handlers
The Cosmos Hub takes Basecoin and adds more stores and extensions to handle additional
transaction types and logic, like the advanced staking logic and the governance process.
## Ethermint
Ethermint is a new implementation of `BaseApp` that does not depend on Basecoin.
Instead of `cosmos-sdk/x/` it has its own `ethermint/x` based on `go-ethereum`.
Ethermint uses a Patricia store for its accounts, and an IAVL store for IBC.
It has `x/ante`, which is quite similar to Basecoin's but uses RLP instead of go-amino.
Instead of `x/bank`, it has `x/eth`, which defines the single Ethereum transaction type
and all the semantics of the Ethereum state machine.
Within `x/eth`, transactions sent to particular addresses can be handled in unique ways,
for instance to handle IBC and staking.

View File

@ -1,118 +0,0 @@
Overview
========
The SDK design optimizes flexibility and security. The framework is
designed around a modular execution stack which allows applications to
mix and match elements as desired. In addition, all modules are
sandboxed for greater application security.
Framework Overview
------------------
### Object-Capability Model
When thinking about security, it's good to start with a specific threat
model. Our threat model is the following:
We assume that a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules.
The Cosmos-SDK is designed to address this threat by being the
foundation of an object capability system.
The structural properties of object capability systems favor
modularity in code design and ensure reliable encapsulation in
code implementation.
These structural properties facilitate the analysis of some
security properties of an object-capability program or operating
system. Some of these — in particular, information flow properties
— can be analyzed at the level of object references and
connectivity, independent of any knowledge or analysis of the code
that determines the behavior of the objects. As a consequence,
these security properties can be established and maintained in the
presence of new objects that contain unknown and possibly
malicious code.
These structural properties stem from the two rules governing
access to existing objects:
1) An object A can send a message to B only if object A holds a
reference to B.
2) An object A can obtain a reference to C only
if object A receives a message containing a reference to C. As a
consequence of these two rules, an object can obtain a reference
to another object only through a preexisting chain of references.
In short, "Only connectivity begets connectivity."
See the [wikipedia
article](https://en.wikipedia.org/wiki/Object-capability_model) for more
information.
Strictly speaking, Golang does not implement object capabilities
completely, because of several issues:
- pervasive ability to import primitive modules (e.g. "unsafe", "os")
- pervasive ability to override module vars
<https://github.com/golang/go/issues/23161>
- data-race vulnerability where 2+ goroutines can create illegal
interface values
The first is easy to catch by auditing imports and using a proper
dependency version control system like Dep. The second and third are
unfortunate but it can be audited with some cost.
Perhaps [Go2 will implement the object capability
model](https://github.com/golang/go/issues/23157).
#### What does it look like?
Only reveal what is necessary to get the work done.
For example, the following code snippet violates the object capabilities
principle:
type AppAccount struct {...}
var account := &AppAccount{
Address: pub.Address(),
Coins: sdk.Coins{{"ATM", 100}},
}
var sumValue := externalModule.ComputeSumValue(account)
The method "ComputeSumValue" implies a pure function, yet the implied
capability of accepting a pointer value is the capability to modify that
value. The preferred method signature should take a copy instead.
var sumValue := externalModule.ComputeSumValue(*account)
In the Cosmos SDK, you can see the application of this principle in the
basecoin examples folder.
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
package app
import (
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/sketchy"
)
func (app *BasecoinApp) initRouterHandlers() {
// All handlers must be added here.
// The order matters.
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
app.router.AddRoute("sketchy", sketchy.NewHandler())
}
In the Basecoin example, the sketchy handler isn't provided an account
mapper, which does provide the bank handler with the capability (in
conjunction with the context of a transaction run).
### Capabilities systems
TODO:
- Need for module isolation
- Capability is implied permission
- Link to thesis

View File

@ -1,34 +0,0 @@
# Overview
The Cosmos-SDK is a framework for building Tendermint ABCI applications in
Golang. It is designed to allow developers to easily create custom interoperable
blockchain applications within the Cosmos Network.
We envision the SDK as the `npm`-like framework to build secure blockchain applications on top of Tendermint.
To achieve its goals of flexibility and security, the SDK makes extensive use of
the [object-capability
model](https://en.wikipedia.org/wiki/Object-capability_model)
and the [principle of least
privelege](https://en.wikipedia.org/wiki/Principle_of_least_privilege).
For an introduction to object-capabilities, see this [article](http://habitatchronicles.com/2017/05/what-are-capabilities/).
## Languages
The Cosmos-SDK is currently writen in [Golang](https://golang.org/), though the
framework could be implemented similarly in other languages.
Contact us for information about funding an implementation in another language.
## Directory Structure
The SDK is laid out in the following directories:
- `baseapp`: Defines the template for a basic [ABCI](https://cosmos.network/whitepaper#abci) application so that your Cosmos-SDK application can communicate with the underlying Tendermint node.
- `client`: CLI and REST server tooling for interacting with SDK application.
- `examples`: Examples of how to build working standalone applications.
- `server`: The full node server for running an SDK application on top of
Tendermint.
- `store`: The database of the SDK - a Merkle multistore supporting multiple types of underling Merkle key-value stores.
- `types`: Common types in SDK applications.
- `x`: Extensions to the core, where all messages and handlers are defined.

View File

@ -0,0 +1,69 @@
# Delegator FAQ
## What is a delegator?
People that cannot, or do not want to run [validator](/validators/overview.md) operations, can still participate in the staking process as delegators. Indeed, validators are not chosen based on their own stake but based on their total stake, which is the sum of their own stake and of the stake that is delegated to them. This is an important property, as it makes delegators a safeguard against validators that exhibit bad behavior. If a validator misbehaves, its delegators will move their Atoms away from it, thereby reducing its stake. Eventually, if a validator's stake falls under the top 100 addresses with highest stake, it will exit the validator set.
Delegators share the revenue of their validators, but they also share the risks. In terms of revenue, validators and delegators differ in that validators can apply a commission on the revenue that goes to their delegator before it is distributed. This commission is known to delegators beforehand and can only change according to predefined constraints (see section below). In terms of risk, delegators' Atoms can be slashed if their validator misbehaves. For more, see Risks section.
To become delegators, Atom holders need to send a "Bond transaction" from [Cosmos Voyager](/getting-started/voyager.md) where they specify how many Atoms they want to bond and to which validator. A list of validator candidates will be displayed in Cosmos Voyager. Later, if a delegator wants to unbond part or all of its stake, it needs to send an "Unbond transaction". From there, the delegator will have to wait 3 weeks to retrieve its Atoms.
## Choosing a validator
In order to choose their validators, delegators have access to a range of information directly in Cosmos Voyager.
* Validator's name: Name that was chosen by the validator candidate when it declared candidacy.
* Validator's description: Description that was provided by the validator candidate when it declared candidacy.
* Validator's website: Link to the validator's website.
* Initial commission rate: The commission rate on revenue charged to any delegators (see below for more detail).
* Commission change rate: The maximum daily increase of the validator's commission
* Maximum commission: The maximum commission rate which this validator candidate can charge.
* Minimum self-bond amount: Minimum amount of Atoms the validator candidate need to have bonded at all time. If the validator's self-bonded stake falls below this limit, its entire staking pool (i.e. all its delegators) will unbond. This parameter exists as a safeguard for delegators. Indeed, when a validator misbehaves, part of its total stake gets slashed. This included the validator's own stake as well as its delegators' stake. Thus, a validator with a high amount of self-bonded Atoms has more skin-in-the-game than a validator with a low amount. The minimum self-bond amount parameter guarantees to delegators that a validator will never fall below a certain amount of self-bonded stake, thereby ensuring a minimum level of skin-in-the-game.
## Directives of delegators
Being a delegator is not a passive task. Here are the main directives of a delegator:
* Perform careful due diligence on validators before delegating. If a validator misbehaves, part of its total stake, which includes the stake of its delegators, can be slashed. Delegators should therefore carefully select validators they think will behave correctly.
* Actively monitor their validator after having delegated. Delegators should ensure that the validators they're delegating to behaves correctly, meaning that they have good uptime, do not get hacked and participate in governance. They should also monitor the commission rate that is applied. If a delegator is not satisfied with its validator, it can unbond or switch to another validator.
* Participate in governance. Delegators can and are expected to actively participate in governance. A delegator's voting power is proportional to the size of its stake. If a delegator does not vote, it will inherit the vote of its validator. Delegators therefore act as a counterbalance to their validators.
## Revenue
Validators and delegators earn revenue in exchange for their services. This revenue is given in three forms:
* Block provisions (Atoms): They are paid in newly created Atoms. Block provisions exist to incentivize Atom holders to stake. The yearly inflation rate fluctuates around a target of 2/3 bonded stake. If the total bonded stake is less than 2/3 of the total Atom supply, inflation increases until it reaches 20%. If the total bonded stake is more than 2/3 of the Atom supply, inflation decreases until it reaches 7%. This means that if total bonded stake stays less than 2/3 of the total Atom supply for a prolonged period of time, unbonded Atom holders can expect their Atom value to deflate by 20% per year.
* Block rewards ([Photons](https://blog.cosmos.network/cosmos-fee-token-introducing-the-photon-8a62b2f51aa): They are paid in Photons. Initial distribution of Photons will take the form of a hard spoon of the Ethereum chain. Atom holders will vote on the parameter of this hard spoon, like the date of the snapshot or the initial distribution. Additionally, bonded Atom holders will receive newly created Photons as block rewards. Photons will be distributed at a fixed rate in proportion to each bonded Atom holder's stake. This rate will be decided via governance.
* Transaction fees (various tokens): Each transfer on the Cosmos Hub comes with transactions fees. These fees can be paid in any currency that is whitelisted by the Hub's governance. Fees are distributed to bonded Atom holders in proportion to their stake. The first whitelisted tokens at launch are Atoms and Photons.
## Validator's commission
Each validator's staking pool receives revenue in proportion to its total stake. However, before this revenue is distributed to delegators inside the staking pool, the validator can apply a commission. In other words, delegators have to pay a commission to their validators on the revenue they earn. Let us look at a concrete example:
We consider a validator whose stake (i.e. self-bonded stake + delegated stake) is 10% of the total stake of all validators. This validator has 20% self-bonded stake and applies a commission of 10%. Now let us consider a block with the following revenue:
* 990 Atoms in block provisions
* 10 Photons in block reward
* 10 Atoms and 90 Photons in transaction fees.
This amounts to a total of 1000 Atoms and 100 Photons to be distributed among all staking pools.
Our validator's staking pool represents 10% of the total stake, which means the pool obtains 100 Atoms and 10 Photons. Now let us look at the internal distribution of revenue:
* Commission = `10% * 80% * 100` Atoms + `10% * 80% * 10` Photons = 8 Atoms + 0.8 Photons
* Validator's revenue = `20% * 100` Atoms + `20% * 10` Photons + Commission = 28 Atoms + 2.8 Photons
* Delegators' total revenue = `80% * 100` Atoms + `20% * 10` Photons - Commission = 72 Atoms + 7.2 Photons
Then, each delegator in the staking pool can claim its portion of the delegators' total revenue.
## Risks
Staking Atoms is not free of risk. First, staked Atoms are locked up, and retrieving them requires a 3 week waiting period called unbonding period. Additionally, if a validator misbehaves, a portion of its total stake can be slashed (i.e. destroyed). This includes the stake of their delegators.
There are 3 main slashing conditions:
* Double signing: If someone reports on chain A that a validator signed two blocks at the same height on chain A and chain B, this validator will get slashed on chain A
* Unavailability: If a validator's signature has not been included in the last X blocks, the validator will get slashed by a marginal amount proportional to X. If X is above a certain limit Y, then the validator will get unbonded
* Non-voting: If a validator did not vote on a proposal and once the fault is reported by a someone, its stake will receive a minor slash.
This is why Atom holders should perform careful due diligence on validators before delegating. It is also important that delegators actively monitor the activity of their validators. If a validator behaves suspiciously or is too often offline, delegators can choose to unbond from it or switch to another validator. Delegators can also mitigate risk by distributing their stake across multiple validators.

95
docs/resources/faq.md Normal file
View File

@ -0,0 +1,95 @@
# FAQ
## Overview
### How do I get Atoms?
If you participated in the fundraiser, you can check your suggested atom balance at [fundraiser.cosmos.network](https://fundraiser.cosmos.network).
If not, you must wait until the [Cosmos Network launches](/roadmap) and Atoms are traded on exchanges.
### Are Atoms listed on exchanges?
No. The Cosmos Network mainnet has not yet launched, which means Atoms are _not_ on exchanges. $CMOS and $ATOM tokens are _not_ Cosmos Network native tokens.
### How do I participate in the fundraiser?
The [fundraiser](https://fundraiser.cosmos.network) is closed. The Interchain Foundation raised funds from private individuals and has hosted a public fundraising event on which ended on April 6, 2017. Both $ETH and $BTC were accepted in the fundraiser. The security of the fundraising process has been vetted extremely carefully.
### What is the initial allocation of Atoms?
As a public, decentralized network, the allocation of Atoms is decided by those who run the software for the Cosmos Hub. To faciliate a decision, we are creating a Swiss non-profit, the [Interchain Foundation](https://interchain.io), which is responsible for co-ordinating fundraising and allocating funds to get the network off the ground. The foundation will suggest a allocation of Atoms according to the results of the fundraiser. Users will ultimately decide the distribution for themselves when they run the software.
The Interchain Foundation will suggest that 5% of the Atoms go to its initial donors, 10% go to the Interchain Foundation, 10% go to the company developing most of the software, and the remaining 75% to be distributed according to the results of the private and public fundraisers.
### What is the team developing the Cosmos Network?
The Cosmos Network is the first project being funded by the Interchain Foundation. Its development is led primarily by the [Tendermint team](/about/team).
### What's the difference between Tendermint, the Cosmos Network, and the Cosmos Hub?
- [Tendermint](https://tendermint.com) is a general purpose blockchain engine that uses a Byzantine-fault tolerant consensus protocol and allows applications to be written in any programming language.
- The Cosmos Network is a heterogenous network of Proof-of-Stake blockchains that can interoperate with one-another.
- The Cosmos Hub is the first Proof-of-Stake blockchain to be launched by the Cosmos Network; it uses Tendermint consensus, contains a built in governance protocol, and serves as co-ordinater for interoperability between other blockchains.
- Atoms: The native cryptocurrency on the Cosmos Hub. Atoms are necessary for participating in the consensus protocol and transacting on the network.
### When will the Cosmos Network launch?
Please check [our roadmap](https://cosmos.network/roadmap).
### What is the utility of Atoms?
Public, decentralized networks require high levels of security and spam-prevention that are best achieved by economic means: participants in the consensus must incur some economic cost, and all transactions processed by the network must pay a fee. Since we want to use Proof-of-Stake validators instead of Proof-of-Work miners, we require validators of the Cosmos Hub to make a large security deposit in Atoms - if they misbehave, their Atoms are revoked by the protocol!
The more Atoms in security deposits, the more stake on the line; the more skin-in-the-game; the greater the economic security. In this sense, the Atoms act like virtual miners.
To achieve spam-prevention, all transactions on the Cosmos Hub must pay a fee in Atoms. The fee may be proportional to the amount of computation required by the transaction, similar to Ethereum's concept of "gas". The fees are collected by the validators and distributed proportionately to the Atoms held in security deposits.
## Interoperability
### What's an IBC packet?
[IBC packets](https://blog.cosmos.network/developer-deep-dive-cosmos-ibc-5855aaf183fe) are packets of data that one blockchain wishes to send to another blockchain. But instead of literally sending a packet of bytes via the TCP/IP or UDP/IP protocol (which is designed for singular, physical, machines), IBC packets require cryptographic proof-of-existence. Since no single node or validator has the authority to speak on behalf of the entire blockchain, and, since we don't want to rely on the integrity of the IP internet infrastructure, instead we rely on a cryptographic proof of a blockchain hash commit (+2/3 of signatures for that blockchain hash) along with a Merkle-proof from the aforementioned blockhash to a packet in the blockchain's "application state", which proves that the blockchain validators agreed to publish this packet of information. So, anyone who sees an IBC packet (regardless of the source of this data) can verify its integrity.
### How does one exchange currencies in this system?
For tokens outside the Cosmos system, they can only be introduced via pegged
derivatives. Read about interoperating with existing blockchains here: [Peggy](https://blog.cosmos.network/the-internet-of-blockchains-how-cosmos-does-interoperability-starting-with-the-ethereum-peg-zone-8744d4d2bc3f).
```
_ peg smart contract
/
[ Ethereum ] <--> [ EtherCosmos Peg Zone ] <-IBC-> [ Cosmos Hub ] <-IBC-> (Bitcoin) [ PoW/Casper ]
[ Tendermint ] [ Tendermint ] <-IBC-> (exchange)
```
### How does Cosmos manage governance?
In Cosmos, the stakeholders are well defined, as is the prior social contract. Ethereum had a hard time with the fork because they had to ask the ether holders as well as the miners, but the ether holders had no prior social contract or obligation to partake in governance, so no quorum could be reached in time. Asking the miners is necessary to ensure that the hard-fork will have support, but after a while they tend to simply follow the money and incentives.
Cosmos is different because instead of anonymous miners we have social contract bound validators and delegators who have stake, and, they have the obligation to partake in governance.
## Validators
### What is the maximum number of validators in Cosmos? What about nodes?
We will start with 100 validators. Anyone else can be a node. To start, the validators will be the same across all shards - they will run the shards concurrently. Over time, these restrictions will be loosened. Misbehaviour in the consensus on any shard will result in security deposits being revoked.
### What will be the process for abandoning validators that misbehave?
If a validator misbehaves on its own by double-signing at the same height &amp; round, then the evidence is very short and simple -- it's just the two conflicting votes. This evidence can be included in the the Cosmos Hub as a Slash transaction, and the validator will immediately become inactive and slashed after the Slash transaction gets committed.
If there is a zone fork, either of the Cosmos Hub or any of the zones, the two conflicting commits also constitute evidence. This is a much more complicated data structure. It is guaranteed to slash at least 1/3 of the validators' atoms for that zone.
### What's the difference between a Delegator and a Validator?
A [validator](/staking/validators) has an active key involved in signing votes in the consensus protocol. A validator must also have some Atoms in a security deposit. Since there will only be a limitted number of validators, [other Atom holders can delegate](/staking/delegators) to the validators, thereby contributing to the economic security of the system by putting their funds on the line if the validator misbehaves. In return, they earn a share of the transaction fees and any inflationary rewards.
### Can delegators also be validators?
Delegators are never validators. If a validator wishes to delegate, they need to do so with their free and unbonded Atoms.
### How are validator voting powers determined and changed?
Validators are initially determined according to a public vote among Atom holders to be carried out before the launch of the Cosmos Hub. Atom holders delegate to the various candidates, and the top 100 candidates will be the initial validators. Once [the Hub launches](/roadmap), the vote will be a continuous process where users shuffle around their delegated Atoms, thereby changing the validator set.
Part of the purpose of the fundraiser is to distribute Atoms across a wide variety of individuals and organizations so that the validator set will be sufficiently decentralized for a robust network. In the event of attacks or mishaps, the blockchain may need to purge bad actors through socially co-ordinated hard-forks. The ability to account for misbehaviour and co-ordinate hardforks helps make the system antifragile.

View File

@ -0,0 +1,756 @@
# 코스모스
분산원장 네트워크
**저자:**
Jae Kwon <mailto:jae@tendermint.com><br>
Ethan Buchman <mailto:ethan@tendermint.com>
For discussions, [join our community chat](https://riot.im/app/#/room/#cosmos:matrix.org)!
**번역 및 제작:**
한승환, 이승민, 김기현, 윤승완, HJ Kim(금마), 김석현
\[[toc]]
## 서 론 (Introduction)
오픈 소스 생태계, 탈중앙화 파일 공유, 퍼블릭 암호화폐 등이 연달아 성공하면서 탈중앙화 인터넷 프로토콜들이 사회경제적 인프라를 근본적으로 개선하는데 사용될 수 있다는 것을 알게 되었다. 비트코인[\[1\]](https://bitcoin.org/bitcoin.pdf)(암호화폐)과 제로캐시[\[2\]](http://zerocash-project.org/paper)(프라이버시용 암호화폐) 같은 특화된 블록체인 어플리케이션들이 있었고, Augur(예측 시장)와 TheDAO[\[4\]](https://download.slock.it/public/DAO/WhitePaper.pdf)(투자 클럽) 같은 무수한 분산 애플리케이션들을 가진 이더리움(Ethereum)[\[3\]](https://github.com/ethereum/wiki/wiki/White-Paper) 같은 범용 스마트 컨트랙트 플랫폼들이 있었다.
그러나 지금까지 이런 블록체인들은 엄청난 에너지 비효율, 형편없거나 제한적인 성능, 미성숙한 거버넌스 메커니즘 등을 비롯해 많은 결함들을 경험해왔다. 세그위트(Segregated-Witness)[\[5\]](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)와 비트코인 NG(BitconNG)[\[6\]](https://arxiv.org/pdf/1510.02037v2.pdf) 같은 비트코인 이체 처리량을 확장하기 위한 여러 제안들이 있었지만, 완전 감사가능성(complete auditability)을 희생하지 않는 이상, 여전히 단일한 기계의 용량에 제한을 받는 수직적 스케일링 솔루션이다. 라이트닝 네트워크(Lightning Network)[\[7\]](https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf)는 원장에서 특정한 이체들을 완전히 제외하여 비트코인의 확장성에 도움을 주며, 소액결제와 프라이버시 보호형 지급 플랫폼(privacy preserving payment rails)에 적합하지만, 보다 범용적인 확장에는 적합하지 않을 수 있다.
가장 이상적인 해결책은 다수의 병렬 블록체인들이 각자의 보안 특성을 유지하면서 호환되는 것이다. 하지만 작업증명의 경우, 이것이 불가능은 아니더라도 상당히 어렵다는 것이 증명되었다. 예를 들어, 병합채굴(merged-mining)은 부모체인의 보안을 위해 수행된 작업을 자녀체인에서 재사용하는 것을 허용하지만, 이체들은 여전히 차례대로 각 노드에 의해 검증돼야 하고, 부모체인의 해시파워 대부분이 자녀체인에서도 병합채굴을 하고 있지 않을 경우, 병합 채굴된(merged mined) 블록체인이 공격에 취약해진다. 우리는 [대안 블록체인 네트워크 아키텍처들(alternative blockchain network architectures)](http://vukolic.com/iNetSec_2015.pdf)에 대한 학술적 고찰을 하였고, 그 중 일부에 대해서는 요약과 결점들을 아래의 [관련 연구(Related Work)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#related-work)에서 다루었다.
우리는 이 모든 문제들을 해결하는 새로운 블록체인 네트워크 아키텍처인 코스모스(Cosmos)를 제시하고자 한다. 코스모스는 존(zone)이라고 불리는 많은 독립된 블록체인들의 네트워크이다. 존은 텐더민트 코어(Tendermint Core)[\[8\]](https://github.com/tendermint/tendermint/wiki)를 통해 작동하는데, 텐더민트 코어는 일관적이고 안전한 고성능의 [유사](https://blog.cosmos.network/tendermint-vs-pbft-12e9f294c9ab?gi=7d54da26ffe)[PBFT](https://blog.cosmos.network/tendermint-vs-pbft-12e9f294c9ab?gi=c320a745ea23) 합의 엔진을 제공하며, 악의적인 공격자들에게 엄격한 [포크 책임(fork-accountability)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#fork-accountability)을 묻는다. 텐더민트 코어의 BFT 합의 알고리즘은 공개형 지분증명 블록체인들의 확장성을 확보하는데 적합하다.
코스모스 상의 첫 번째 존을 코스모스 허브(Cosmos Hub)라고 부른다. 코스모스 허브는 단순한 거버넌스 메커니즘을 통해 네트워크가 적응하고 업그레이드 할 수 있게 하는 ‘다중 자산 지분증명 암호화폐(multi-asset proof-of-stake cryptocurrency)’이다. 그에 더해서, 코스모스 허브는 다른 존들을 연결함으로 확장될 수 있다.
코스모스 네트워크의 허브와 존들은 블록체인 간 통신(IBC: inter-blockchain communication) 프로토콜을 통하여 상호 통신한다. 이 프로토콜은 블록체인들을 위한 일종의 가상 UDP 또는 TCP 역할을 한다. 토큰들은 존들 간 거래소의 유동성(exchange liquidity)없이도 안전하고 신속하게 하나의 존에서 다른 존으로 전송될 수 있다. 존들 간 토큰 전송은 코스모스 허브를 통과하며, 코스모스 허브는 각 존이 보유한 토큰 총액을 추적한다. 허브는 각 존을 다른 존들의 실패(failure)로부터 격리한다. 또한 누구든지 코스모스 허브에 새로운 존을 연결할 수 있기 때문에, 존들은 앞으로의 새로운 블록체인 혁신과의 장래 호환성도 확보한다.
## 텐더민트 (Tendermint)
본 절에서는 텐더민트 합의 프로토콜과 이를 통한 애플리케이션을 구축을 위해 사용되는 인터페이스를 다룬다. 상세한 내용은 [부록(appendix)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#appendix)참조
### 검증인 (Validators)
전형적인 비잔틴 장애 허용 알고리즘들(BFT: Byzantine fault-tolerant algorithms)에서는 각 노드가 동일한 가중치를 갖는다. 텐더민트에서는 노드들이 0 이상’의 *투표권(voting power)*을 가지며, 양(+)의 투표권을 갖는 노드들을 *검증인\*\*(validators)*라고 부른다. 검증인들은 다음 블록에 동의하는 암호서명(cryptographic signature), 즉 *투표(vote)*를 전파(broadcast)함으로써 합의 프로토콜에 참여한다.
검증인(validator)들의 투표권은 제네시스(genesis) 당시에 결정되거나 블록체인에 의해 결정론적으로(deterministically) 변경되기도 한다. 예를 들어, 코스모스 허브와 같은 지분증명 애플리케이션에서는 투표권이 담보물로서 본딩된(bonded) 지분 토큰(staking token)의 양에 의해 결정될 수도 있다.
_주: ⅔, ⅓ 과 같은 분수들은 모든 **검증인**들이 동등한 가중치를 갖지 않는 한, **검증인**들의 총수가 결코 아니라 총 투표권**에서 차지하는** **비중**을 가리킨다. 주: +⅔ 는 " ⅔ 초과"를 의미하**며**, ⅓+은 "⅓ 이상"을 의미한다._
### 합의 (Consensus)
텐더민트는 DLS 합의 알고리즘에서 파생되는, 부분적 동기 BFT 합의 프로토콜(synchronous BFT consensus protocol)이다[\[20\]](http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf). 텐더민트는 단순함, 성능 그리고 [포크 책임(fork-accountability)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#fork-accountability)이 특징이다. 이 프로토콜은 공개되있고 고정된 세트의 검증인들(a fixed, known set of validators)을 요구하는데, 각 검증인 공개 키에 의해 식별된다. 검증인들은 한 번에 하나의 블록, 즉 이체 목록(a list of transactions)에 대해 합의를 시도한다. 블록에 대한 합의는 라운드(round)를 통해 진행된다. 각 라운드에는 블록을 제안하는 라운드 리더(round-leader), 즉 제안자(proposer)가 있다. 그 다음, 검증인들은 제안된 블록을 받아들일 것인지 또는 다음 라운드로 넘어갈 것인지에 대한 단계별 투표를 진행한다. 각 라운드의 제안자는 검증인 리스트(ordered list)에서 투표권에 비례해 결정론적으로 선택된다.
프로토콜에 대한 세부정보는 [여기](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)를 참조
텐더민트 보안은 압도적 다수결(+⅔)과 잠금 메커니즘(locking mechanism)을 통해 최적의 비잔틴 장애 허용을 사용하는 것으로부터 유래한다. 다음을 보장한다.
- 안전성을 저해(violation of safety)하기 위해서는 ⅓+의 투표권이 비잔틴이어야 하고, 2 개를 초과하는 값들’이 커밋(commit)되어야 한다.
- 프로토콜은 임의의 검증인들 세트가 안전성을 저해하는 데 성공하거나 또는 시도만 하더라도, 이들을 식별할 수 있다. 충돌하는 블록들에 찬성 투표 하거나 정당하지 않은 투표들을 전파하는 이들 모두가 포함된다.
텐더민트는 강력한 보장들에 더해 성능면에서도 탁월하다. 5 개 대륙 7 개 데이터센터에 분산되어 있는 64 개 노드의 대중품 클라우드(commodity cloud)를 기준으로, 텐더민트 합의는 약 1~2 초의 커밋 지연속도(commit latencies)와 함께 초당 수천 개의 트랜잭션(transaction)를 처리한다. 특히, 검증인들이 실패하거나 악의적으로 조작된 투표를 전파하는 가혹한 공격 상황(adversarial conditions)에서도 초당 1,000 번 수준의 트랜잭션 성능은 가볍게 능가한다. _아래 그림 참조_
![Figure of Tendermint throughput performance](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/tendermint_throughput_blocksize.png)
### 라이트 클라이언트 (Light Clients)
텐더민트 합의 알고리즘의 특징은 단순화된 라이트 클라이언트 보안인데, 모바일 및 사물인터넷에 사용되기도 적합하다. 비트코인 라이트 클라이언트는 ‘블록헤더 체인’들을 동기화(sync)하여 가장 많은 작업증명을 가진 체인을 찾아야 하지만, 텐더민트 라이트 클라이언트는 ‘검증인 세트’의 변경을 추적해서, 최신 블록에서 +⅔ 프리커밋(PreCommit)을 검증하기만 하면 최신 상태를 결정할 수 있다.
간결한 라이트 클라이언트 증명들은 [블록체인 간 통신(inter-blockchain communication)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#/h)도 가능하게 한다.
### 공격 방지 (Preventing Attacks)
텐더민트는 [long-range-nothing-at-stake double spend](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#/h)’나 [검열(censorship)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#/h) 등을 방지하기 위한 다양한 보호장치를 가지고 있다. 세부사항은 [부록(appendix)](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#/h)참조
### TMSP
텐더민트 합의 알고리즘은 텐더민트 코어(Tendermint Core)라는 프로그램에서 구현된다. 텐더민트 코어는 모든 결정적 블랙박스 애플리케이션(deterministic blackbox application)을 분산 복제 블록체인(distributedly replicated blockchain)으로 전환시킬 수 있는 어플리케이션 어그노스틱(application-agnostic) "합의 엔진"이다. 아파치 웹 서버나 Nginx 가 CGI 나 FastCGI 를 통해 워드프레스 애플리케이션에 접속하는 것처럼, 텐더민트 코어는 텐더민트 소켓 프로토콜(TMSP: Tendermint Socket Protocol)을 통해 블록체인 애플리케이션들과 연결된다.
비트코인을 예로 들어보자면, 비트코인은 각 노드가 완전 감사되는 UTXO(fully audited Unspent Transaction Output) 데이터베이스를 유지하는 암호화폐 블록체인이다. 만일 누군가 비트코인과 같은 시스템(Bitcoin-like system)을 TMSP 상에 만들고자 하면, **텐더민트 코어**’는 아래와 같은 부분들을 책임진다.
- 노드 간 블록 및 이체 공유
- 정규(canomical)/변경배제(immutable) 이체순서의 확립(블록체인)
반면, **TMSP 의 어플리케이션**’은 다음을 책임진다.
- UTXO 데이터베이스 유지
- 이체의 암호서명 검증
- 존재하지 않는 이체를 지불방지
- 클라이언트들의 UTXO 데이터베이스에 대한 query 허용
텐더민트는 애플리케이션 프로세스와 합의 프로세스 사이에 매우 단순한 API 를 제공함으로써 블록체인 설계를 분해할 수 있다.
## 코스모스 개요 (Cosmos Overview)
코스모스는 독립 병렬 블록체인들의 네트워크이며, 해당 블록체인들은 각기 텐더민트와 같은 전형적인 BFT 합의 알고리즘에 기반한다[1].
코스모스의 최초 블록체인이 곧 코스모스 허브가 된다. 코스모스 허브는 블록체인 간 통신(IBC) 프로토콜을 통해 다른 많은 블록체인들(즉, _존들_)과 연결된다. 코스모스 허브는 수많은 유형의 토큰을 추적하고, 연결된 각 존의 토큰 총 수를 기록한다. 모든 존들 간의 코인 전송은 코스모스 허브를 거치기 때문에 토큰은 존들 간의 유동 거래소(liquid exchange) 방식 없이
이 아키텍처는 어플리케이션 상호운용성, 확장성 그리고 무결절 업그레이드 가능성(seamless upgradability)을 포함한 블록체인이 직면한 많은 문제들을 해결한다. 예를 들면, Bitcoind, Go-Ethereum, 크립토노트(CryptoNote), ZCash 등을 포함한 임의의 블록체인 시스템으로부터 파생된 존들이 코스모스 허브에 연결될 수 있다. 이러한 존들을 통해 코스모스는 전역 이체(global transaction) 요구를 충족시킬 때까지 무한히 확장할 수 있다. 그리고 존들은 탈중앙화 거래소(distributed exchange)에도 매우 적합하며, 실제로 지원될 것이다.
코스모스는 단일 분산 원장에 불과한 것이 아니며, 코스모스 허브도 폐쇄형 플랫폼(walled garden, 울타리 친 정원)이 아니다. 오히려 코스모스는 미래 금융 시스템의 새로운 기초가 될 수 있는 개방형 분산원장 네트워크를 위한 프로토콜이며, 암호학 원리, 잘 설계된 경제(sound economics), 합의 이론, 투명성, 책임(accountability) 등의 원칙에 기반하고 있다.
### Tendermint-BFT DPoS
코스모스 허브는 텐더민트 BFT 합의 알고리즘에 기반하는, 코스모스 네트워크 내 최초의 퍼블릭 블록체인이다. 텐더민트 오픈 소스 프로젝트는 비트코인의 작업증명(PoW) 합의 알고리즘이 지닌 속도, 확장성, 환경 등의 문제를 처리하기 위해서 2014 년에 탄생했다. 1988 년 MIT 에서 개발한 증명된 BFT 알고리즘들을 활용하고 개선해서, 텐더민트 팀은 제 1 세대 지분증명(PoS) 암호화폐들이 겪은 Nothing-at-stake 문제를 해결하는 지분증명 암호화폐를 최초로 개념증명하였다.
오늘날, 대부분의 비트코인 모바일 지갑들은 이체 검증(transaction verification)을 제공하는 신뢰할 수 있는 서버(trusted server)를 이용한다. 이는 PoS 가 여러번의 이체확인(confirmation)을 기다려야만, 이체확정된 것으로 간주되기 때문이다. 이중지불(double-spend) 공격이 이미 코인베이스(CoinBase) 같은 서비스들에서 일어난 바 있다.
다른 블록체인 합의 시스템들과는 달리 텐더민트는 즉각적이고 안전한(provably-secure) 모바일-클라이언트 지불 검증을 제공한다. 텐더민트는 분기(fork)할 수 없도록 설계되었기 때문에, 모바일 지갑들에서 즉시 이체확인이 가능하며 실제로 신뢰 없는(trustless) 지불을 실현한다. 이는 IoT 관련 어플리케이션들에도 큰 영향을 줄 것이다.
(비트코인 채굴자들과 유사한 역할이지만 그 대신에 암호서명을 통해 투표하는) 코스모스의 검증인들은 블록을 커밋 할 책임이 있는 안전한 전용 머신들이어야 한다. 검증인이 아닌 이들(non-validators)은 ‘아톰’이라고 부르는 지분 토큰(staking tokens)을 임의의 검증인에게 위임하여 일정한 블록 수수료(block fees)와 아톰 보상(atom rewards)을 얻을 수는 있으나, 위임 검증인(delegate validator)이 해킹당하거나 프로토콜을 위반할 경우, 처벌을 받게 되는 리스크가 있다. 텐터민트 BFT 합의의 입증된 안정성 보장과 (검증인 및 위임자) 주주들의 담보 보증(collateral deposit)은 노드들에 심지어는 라이트 클라이언트들에게 증명 가능하고 정량화 가능한 보안성(provable, quantifiable security)을 제공한다.
### 거버넌스 (Governance)
‘공개형 분산원장’은 헌법(constitution)과 거버넌스 시스템(governance system)을 가져야 한다. 비트코인은 업그레이드 등의 조정을 위해 일정 부분 비트코인 재단(Bitcoin Foundation)이나 채굴에 의존하지만, 진행속도가 느리다. 이더리움의 경우에는 The DAO 해킹의 처리를 위한 하드포크 후 ETH 와 ETC 로 분할되었는데, 이는 사전에 그런 의사결정을 하기 위한 사회적 계약이나 메커니즘이 존재하지 않았기 때문이다.
코스모스 허브의 검증인과 위임자는 코스모스 허브의 정책들을 위한 ‘코드가 아니라 평이한 언어로 된 헌법(규칙)’을 표결에 부쳐 수정할 뿐 아니라 ‘블록 가스 한계(block gas limit) 같은 시스템의 사전에 설정된 제한들을 업그레이드를 통해 자동으로 변경하는 프로포잘(proposals)도 표결에 부칠 수 있다. 헌법은 The DAO 사건 같은 절도 및 버그관련 문제들의 이해관계자들이 응집(cohesion)할 수 있도록 도우며, 보다 신속하고 깔끔한 해결책이 나올 수 있도록 한다.
각 존 역시 자체적인 헌법과 거버넌스 메커니즘을 가질 수 있다. 예를 들어, 코스모스 허브가 변경 배제(immutability) -코스모스 허브의 노드 실행 버그를 제외하고는 롤백 금지(no roll-backs)와 같은- 헌법을 적용한 경우라면, 각 존은 절도와 버그를 위한 롤백 관련 자체 정책을 설정할 수 있다.
코스모스 네트워크는 상이한 정책의 존들 간 상호운용성을 확보함으로 사용자들에게 궁극적 자유와 실험의 기회를 제공한다.
## 허브와 존 (The Hub and Zones)
여기서는 탈중앙화(decentralization)와 확장성(scalability)의 새로운 모델을 설명한다. 코스모스는 텐더민트에 기반한 복수의 블록체인들의 네트워크이다. 기존의 프로포잘들이 전역적 이체 순서(total global transaction ordering)를 가진 ‘단일 블록체인’을 목표로 하는 반면에, 코스모스는 수많은 블록체인들이 상호 병행 실행(run concurrently)하는 동시에 상호운용성을 확보하도록 한다.
기본적으로 코스모스 허브는 ‘존(zone)’이라고 부르는 여러 독립된 블록체인들을 관리한다(‘존’은 ‘샤드(shard)’로도 불림, 데이터베이스의 스케일링 기법인 ‘샤딩(sharding)’참조). 존들은 최근의 블록 커밋들을 끊임없이 허브로 전송하며, 허브는 이를 통해 항상 최신 상태를 유지한다. 마찬가지로, 각 존도 허브의 상태를 제공받는다(다만 허브를 통한 간접적인 경우를 제외하고는 존들끼리는 서로 이러한 작업을 하지 않는다). 존들은 정보의 송신과 수신의 증거인 머클 증명(Merkle-proofs)을 포스팅함으로 정보의 패킷들을 교환한다. 이 메커니즘을 ‘블록체인 간 통신(inter-blockchain communication) 또는 IBC 라고 칭한다.
![Figure of hub and zones acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/hub_and_zones.png)
어느 존이든 스스로 허브가 되어 비순환 그래프(acyclic graph)를 형성할 수 있으나 명료성을 위해 우리는 단지 하나의 허브와 많은 비-허브 존들이 존재하는 단순한 구성만 설명할 것이다.
### 허브 (The Hub)
코스모스 허브는 멀티자산 분산 원장(multi-asset distributed ledger)을 관리하는 블록체인인데, 여기서 토큰은 개인 사용자들이나 존 자체가 보유할 수 있다. 이 토큰들은 ‘코인 패킷(coin packet)’이라 부르는 특수 IBC 패킷을 통해 하나의 존에서 다른 존으로 이동할 수 있다. 허브는 존들에 걸쳐 각 토큰 총액의 전역적 불변성(global invariance)을 보존한다. IBC 코인 패킷 이체는 반드시 송신자, 허브 및 수신자 블록체인에 의해 커밋 되어야 한다.
코스모스 허브는 전체 시스템을 위한 토큰의 ‘중앙 원장’ 역할을 하기 때문에, 보안이 무엇보다도 중요하다. 각 존은 단 4 명(혹은 BFT 합의가 필요하지 않을 경우에는 훨씬 더 적은 수)의 검증인으로도 안전해지는 텐더민트 블록체인일 수 있는 반면, 허브는 대륙 네트워크 분할(continental network partition)이나 국가 주도적 공격(nation-state sponsored attack) 같은 가장 심각한 공격 시나리오들에도 견딜 수 있어야 한다. 따라서 전역적으로 탈중앙화된 검증인 세트로 보안되어야 한다.
### 존 (The Zones)
코스모스 존은 허브와 IBC 메시지를 교환하는 독립 블록체인이다. 허브의 관점에서, 존은 IBC 패킷을 사용하여 토큰을 송수신할 수 있는 ‘멀티자산 동적구성원 멀티시그너처 계정(multi-asset dynamic-membership multi-signature account)’이다. 암호화폐 계정과 마찬가지로 존은 보유 중인 토큰보다 더 많은 토큰을 전송할 수 없지만, 토큰을 보유한 다른 존으로부터 토큰을 수신할 수는 있다. 존은 토큰 유형들의 ‘소스(source)’로 지정되어, 해당 토큰을 추가 공급할 수도 있다.
코스모스 허브의 아톰들은 허브의 검증인이 아니라, ‘허브에 연결된 존의 검증인’들에 의해 스테이킹될 수도 있다. 이 존들에 대한 이중지불 공격은 텐더민트의 포크 책임인 아톰의 대폭 감소(slashing)라는 결과를 가져오게 되는 반면, 투표권의 +⅔ 이 비잔틴인 존이라면 무효한 상태(invalid state)를 커밋할 수 있다. 코스모스 허브는 다른 존들에 커밋 된 이체들은 검증하거나 실행하지 않으므로, 자신이 신뢰하는 존으로 토큰을 전송하는 것은 사용자들의 책임이다. 장래에는 코스모스 허브의 거버넌스 시스템이 존 실패(failures)를 위한 허브 개선 프로포잘을 만들수도 있다. 예를 들면, 공격이 탐지될 때, 일부(또는 전체) 존들의 아웃바운드 토큰 전송을 억제함으로 ‘긴급 서킷 브레이킹(토큰 전송 일시중단)’을 할 수도 있다.
## 블록체인 간 커뮤니케이션 (Inter-blockchain Communication (IBC))
여기서는 허브와 존 간의 상호 통신방법을 설명한다. 예를 들어, ‘존 1(Zone 1), ‘존 2(Zone 2)’와 ‘허브(Hub)’의 세 블록체인이 있고, ‘존 1이 ‘허브’를 통과하여 ‘존 2로 가는 패킷을 만들고자 한다. 패킷이 하나의 블록체인에서 다른 블록체인으로 이동하기 위해, 송신 체인(sending chain)이 수신지로 가는 패킷을 발행했다는 증거를 수신 체인(receiving chain)에 포스팅한다. 수신 체인이 이 증거를 확인하기 위해서는 송신자의 블록 헤더(block headers) 정보를 알아야 한다. 이 메커니즘은 사이드체인(sidechain)이 사용하는 메커니즘과 유사한데, 상호 작용하는 두 체인이 ‘존재 증명 데이터그램의 양방향 스트림(a bidirectional stream of proof-of-existence datagram)’을 통해 서로를 ‘인식’해야한다 (이체).
IBC 프로토콜을 두 가지 이체 방식을 통해 정의할 수 있다: 블록체인이 최근의 블록 해시를 임의의 관측자에게 증명하는 IBCBlockCommitTx 이체, 그리고 블록체인이 송신자의 어플리케이션이 패킷을 머클증명을 통해 최근 블록해시로 전파했다는 것을 증명하는 IBCPacketTx 이체
IBC 구동방식을 IBCBlockCommitTx 와 IBCPacketTx 로 분할함으로, 수신체인의 내부 수수료 시장 메커니즘(native fee market-mechanism)이 커밋될(승인될) 패킷을 결정하도록 하며, 송신체인이 얼마나 많은 아웃바운드 패킷들을 허용할 지 자유롭게 정할 수 있도록 한다.
![Figure of Zone1, Zone2, and Hub IBC without acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_without_ack.png)
위의 예에서, ‘존 1의 블록해시를 (또는 ‘존 2 상의 ‘허브’의 블록 해시를) 업데이트 하기 위해서는, IBCBlockCommitTx 이체가 ‘존 1의 블록해시와 함께 ‘허브’에 (또는 ‘허브’의 블록해시와 함께 ‘존 2에) 반드시 포스팅 되어야 한다.
_두 IBC 이체 유형에 관한 보다 상세한 정보는 [IBCBlockCommitTx](#ibcblockcommittx) 와 [IBCPacketTx](#ibcpacketcommit) 를 참조._
## 사용사례 (Use Cases)
### 분산거래소 (Distributed Exchange)
비트코인이 원장을 복사하고 분산하여 더 안전해지는 것처럼, 거래소도 블록체인 상에 올림으로 내/외부의 해킹으로부터 더욱 안전해질 수 있다.
현재 커뮤니티에서 칭하는 탈중앙화 거래소(decentralized exchange)는 ‘아토믹 크로스체인(Atomic Cross-Chain, AXC) 이체에 기반하고 있다. 이를 통해 두 명의 유저는 두 개의 서로 다른 원장(체인)에서 이체를 일으키고 해당 이체는 동시에 각 원장에 기록되거나 또는 그 어떤 원장에도 기록되지 않는다. 예를 들어, AXC 를 이용하면 두 명의 유저가 서로의 이더와 비트코인(또는 서로 다른 원장의 토큰)을 교환할 수 있다. AXC 기반 거래소의 장점은, 두 명의 유저 모두 서로 또는 거래소 서비스를 신뢰할 필요가 없다는 점이다. 그러나 거래를 위해서는 양 당사자가 반드시 온라인 상태여야만 한다는 점이 치명적인 단점이다.
또 다른 방식의 탈중앙화 거래소는 자체적인 분산원장에서 작동하는 ‘대규모의 복제 분산 거래소(a mass-replicated distributed exchange)’이다. 유저는 ‘지정가 주문(limit order)’을 내고 컴퓨터를 종료한다. 이 경우, 유저가 온라인 상태가 아니어도 거래가 성사된다. 블록체인이 해당 거래를 매칭시키고 완료시키는 것이다.
중앙화된 거래소의 경우, 이미 많은 지정가 주문이 걸려 있고 따라서 유저들을 더 쉽게 유치하게 된다. 거래소에서 유동성은 더 큰 유동성을 야기하고 강한 네트워크 효과(또는 승자독식)를 일으키게 된다. 현재 24 시간 거래량 기준, Poloniex 가 $20M 으로 가장 많은 거래량을 가지고 있고, Bitfinex 가 $5M 로 그 다음에 위치하고 있다. 이러한 사례를 생각해보면, AXC 기반의 새로운 탈중앙화 거래소가 중앙화된 거래소를 이길 가능성은 거의 없다. 탈중앙화된 거래소가 중앙화된 거래소와 경쟁하기 위해서는 많은 거래량을 확보해야 한다. 오직 ‘분산 거래소(distributed exchange)’만이 그것을 가능하게 할 수 있다.
텐더민트는 또한 매우 빠른 이체 커밋을 제공한다. 일관성의 훼손없이 빠른 완결성을 우선순위에 둠으로써 코스모스 상의 존(zone)들의 이체를 빠르게 완결한다. 이는 거래소의 이체나 IBC 를 통한 존들 간의 이체에 동일하게 적용된다.
현재 암호화폐 거래소들의 상황을 고려할 때, 코스모스를 이용해 분산거래소(일명 ‘코스모스 덱스’)를 만드는 것은 매우 적절하다. 코스모스 덱스(Cosmos DEX)의 이체 처리량과 커밋 속도는 중앙화된 거래소에 필적한다. 유저들은 지정가 주문을 올리고, 이 주문은 당사자의 온라인 상태여부에 관계없이 완결된다. 트레이더들은 텐더민트, 코스모스 허브, 그리고 IBC 를 통해, 빠른 속도로 자금을 존(zone)들이나 거래소로 이동시킬 수 있다.
### 다른 화폐에 가치고정 (Pegging to Other Cryptocurrencies)
특권 존(privileged zone)은 다른 암호화폐를 페깅한 토큰의 소스 역할을 할 수 있다. 페그(peg)는 코스모스 허브와 존의 관계와 본질적으로 유사한데, 코스모스 허브와 존 모두는 토큰이 한 쪽에서 다른 쪽으로 이동했다는 증명을 검증하기 위해 상대방의 최신 블록을 반드시 알고 있어야 한다. 코스모스 네트워크의 페그 존(peg-zone)은 다른 암호화폐뿐 아니라 허브도 추적한다. 페그 존을 통한 간접참조(indirection)는 허브의 로직이 단순하면서도 비트코인의 작업증명 채굴 같은 다른 블록체인 합의 전략들에 독립적(agnostic)인 상태를 유지할 수 있게 한다.
예를 들어, 허브의 검증인 세트와 동일한 어떤 검증인 세트를 갖는 존이 이더-페그(ether-peg) 역할을 할 수 있을 것이다. 여기서 ‘페그-존(peg-zone)’의 TMSP 애플리케이션은 ‘외부 이더리움 블록체인의 페그 컨트랙트(peg contract)’와 IBC 메시지를 교환할 수 있다. 이더 보유자들이 이더리움 상의 페그 컨트랙트로 이더를 전송함으로 코스모스의 페그 존에 이더를 전송하게 된다. 이렇게 이더가 일단 한번 수신되면, 페그 컨트랙트가 페그 존으로부터 IBC 패킷을 수신하지 않는 한 이더는 인출되지 못 한다. 페그 컨트랙트에서 특정 이더리움 계정으로부터 이더를 수신했다는 IBC 패킷을 페그 존으로 보내면, 이에 대응되는 계정이 동일한 잔고와 함께 페그 존 위에 생성된다. 페그 존 위의 (페깅된) 이더를 허브로 또는 허브로부터 전송할 수 있으며, 페깅 이더를 폐기하면서 이더리움 상의 특정 인출 주소로 그 이더를 도로 전송할 수 있고, 이더의 인출을 허용하기 위해 페그 존에서 이체가 발생했음을 증명하는 IBC 패킷을 이더리움 페그 컨트랙트에.
물론 그런 페깅 컨트랙트는 악의적 검증인 세트를 통한 리스크에 노출되어 있다. ⅓+ 비잔틴 투표권이 분기(fork)를 만들어 페깅된 이더를 페그 존에 유지하는 동시에 이더리움 위의 페그 컨트랙트를 통해 이더를 인출할 수도 있다. 더 좋지 않은 시나리오는 +⅔ 비잔틴 투표권이, 페그 존의 원래 페깅 로직으로부터 이탈하여, 페그 컨트랙트로 보내지는 모든 이더를 노골적으로 훔칠 수도 있다는 것이다.
이런 문제들은 페그를 ‘완전 책임적(totally accountable)’으로 설계하여 해결할 수 있다. 예를 들면, 허브나 원점(페깅될 토큰의 원래 블록체인)으로부터의 모든 IBC 패킷이 페그-존의 응답(acknowledgment)을 요구하도록 할 수도 있을 것이다. 허브와 원점은 페그-존 검증인들이 담보물(collateral)을 포스팅(post) 할 수 있게 하고, 독립 감사들(independent auditors)의 도전을 허용하기 위해 페그 컨트랙트로부터 나가는 토큰 전송은 지연되도록 하는 식이다. 그리고 담보 언본딩(unbonding) 기간은 충분히 길어야 한다. 이 부분은 미확정 상태로 두고, 누구든지 향후에 관련 제안을 해서 코스모스 거버넌스 시스템에 승인을 받아 적용할 수 있도록 한다.
정치사회적 분위기가 아직은 갖추어져 있지 않긴 하지만, 국가 통화의 책임기관들, 특히 은행들의 조합으로부터 검증인 세트를 형성함으로 국가의 법정통화에 페깅하는 존들을 허용할 수도 있다. 물론, 강력한 법체계에 기반하는 화폐만 허용하도록 충분한 고려가 되어야 할 것이며, 충분히 큰 규모의 공증인이나 기관들을 통해 감사시스템을 강제하여 은행들의 활동을 관리해야할 것이다.
이렇게 통합이 된다면, 참여 은행에 은행계좌가 있는 누구든지 존에 있는 은행계좌로부터 존에 있는 다른 계좌나 허브나 혹은 또 다른 존으로 법정화폐를 자유롭게 이동시킬 수 있을 것이다. 이 점에서 코스모스 허브는 법정화폐들과 암호화폐들 간의 전달자 역할을 하고, 지금까지 상호운용성을 거래소(exchanges)의 영역으로 한정시킨 장벽들을 제거할 수 있다.
### 이더리움 확장성 (Ethereum Scaling)
이더리움의 해결되지 않은 쟁점 하나는 스케일링 문제를 해결하는 방법이다. 현재는 각 이더리움 노드들이 모든 이체를 처리하고 또 모든 상태를 저장한다. [링크](https://docs.google.com/presentation/d/1CjD0W4l4-CwHKUvfF5Vlps76fKLEC6pIwu1a_kC_YRQ/mobilepresent?slide=id.gd284b9333_0_28)
텐더민트는 이더리움의 작업증명보다 훨씬 빠르게 블록들을 커밋 할 수 있기 때문에, 텐더민트 합의에 기반한 페깅된 이더(pegged-ether)를 운용하는 EVM 존들은 이더리움 블록체인에 보다 높은 성능을 제공할 수 있다. 뿐만 아니라, 비록 코스모스 허브 및 IBC 패킷 자체는 임의계약 로직 실행을 허용하지 않지만, 서로 다른 존들에서 실행되는 이더리움 컨트랙트 간 토큰이동의 조정을 위해 사용되며, 샤딩(sharding)을 통한 토큰 중심 이더리움 스케일링을 위한 기반을 제공할 수 있다.
### 멀티 어플리케이션 통합 (Multi-Application Integration)
코스모스 존들은 존의 생성 초기에 정해진 어플리케이션 로직에 따라 작동하며, 거버넌스를 통해 지속적으로 업데이트될 수 있다. 그런 유연성 덕분에 코스모스 존들이 이더리움이나 비트코인 같은 다른 암호화폐들의 페그 역할을 하고, 동일한 코드베이스를 활용하면서도 상이한 검증인 세트와 초기 분포(initial distribution)를 갖는 블록체인 파생상품(derivatives)을 만들 수 있다. 이를 통해 이더리움, 제로캐시, 비트코인, 크립토노트 등과 같은 기존의 암호화폐 프레임워크들이 ‘고성능 합의 엔진(텐더민트 코어)’을 통해 공통의 네트워크에서 사용될 수 있으며, 엄청난 상호운용성의 기회를 가질 수 있게 된다. 뿐만 아니라, 멀티 자산 블록체인으로서, 단일 이체는 다수의 인풋과 아웃풋을 포함할 수 있고, 어떤 토큰이든지 인풋이 될 수 있기 때문에, 주문들은 다른 플랫폼들을 통해 매칭되더라도 코스모스가 직접 탈중앙화 거래소(exchange)를 위한 플랫폼 역할을 할 수 있다. 또한 존은 오더북 기능이 있는 ‘분산형 장애-허용 거래소’ 역할을 할 수도 있는데, 해킹 공격에 당하곤 하는 기존의 중앙집중형 암호화폐 거래소들에 대한 강력한 개선책이 될 수 있다.
존은 또한 기업 및 정부 시스템들의 블록체인 버전(blockchain-backed versions)을 지원할 수도 있다. 전통적으로 조직에 의해 운영된 서비스에 대해서 기반이 된느 부분은 통제권을 유지하고, 특정 부분만을 존 위의 TMSP 애플리케이션으로 운영한다면, 퍼블릭 코스모스 네트워크의 보안과 상호운용성을 활용할 수 있게 된다. 따라서 코스모스는 블록체인 기술을 활용하고자 하지만 (분산된) 제 3 자에게 통제를 완전히 내주는 것은 경계하는 조직들에 일거양득을 제공할 수 있다.
### 네트워크 분할 완화 (Network Partition Mitigation)
일각에서는 텐더민트 같은 ‘일관성선호 합의 알고리즘(consistency-favouring consensus algorithm)’에 심각한 문제가 있다고 주장한다. +⅔ 투표권(⅓+은 오프라인)을 갖는 분할이 생기지 않도록 방지하는 것이 합의과정 자체를 멈추도록 할 수 있다는 것이다. 코스모스 아키텍처는 지역 자치 존(regional autonomous zone)을 갖는 전역 허브(global hub)를 사용함으로 이 문제를 완화할 수 있는데, 이 지역적 자치 존은 각 존의 투표권이 지리적 위치에 기초하여 분배되는 존을 가리킨다. 예를 들어, 개별 도시들이나 지역들이 자체적인 존을 운영하면서 공통의 허브(예. 코스모스 허브)를 공유하여, 일시적인 네트워크 분할로 허브가 중단되더라도 자치활동은 지속될 수 있도록 할 수 있다. 강인한 연합형 장애허용 시스템(robust federated fault-tolerant system)이 설계되기 위해서는 실제 지리, 정치 및 네트워크 토폴로지 관련 특징들이 고려되어야 할 것이다.
### 연합 명칭 결의 시스템 (Federated Name Resolution System)
네임코인(NameCoin)은 비트코인 블록체인을 이용하여 ‘명칭 결의(name-resolution) 문제를 해결하고자 한 최초의 블록체인 중 하나였다. 안타깝지만 이 접근법에는 몇 가지 문제들이 있었다.
네임코인으로 예를 들면, *@satoshi*라는 명칭이 과거 어느 시점에 특정 공개키로 등록되었다는 것은 검증할 수 있지만, 이후의 모든 블록들을 다운로드 하지 않는 한 그 공개키가 이후로 업데이트 된 적이 있는지는 알 수 없다. 이는 비트코인의 UTXO 머클화 모델의 한계 때문인데, 이 모델에서는 ‘상태’가 아닌 ‘이체의 기록’만 블록 해시로 머클화된다--이는 존재를 증명하지만, 이후로 업데이트가 없었다는 것을 증명하지는 못한다. 따라서 완전 노드를 신뢰하거나 전체 블록체인을 다운로드 하면서 상당한 비용을 지불해야만 가장 최근 값을 확실히 알 수 있다.
머클 탐색 트리(Merkle-ized search tree)가 네임코인에 구현되더라도, 작업증명에 대한 의존성 때문에 라이트 클라이언트 검증에서 문제가 생긴다. 라이트 클라이언트는 전체 블록체인의 블록헤더들(또는 적어도 마지막 업데이트 이후의 모든 헤더들)을 모두 다운로드 해야 한다. 이는 대역폭 사용이 소모시간과 함께 선형으로 증가한다는 것을 의미한다[\[21\]]][21]. 또한 작업증명 블록체인 위의 명칭 변경을 위해서는 추가적인 블록확인을 기다려야 하는데 이것이 비트코인에서는 최대 1 시간까지도 걸릴 수 있다.
텐더민트의 경우, 변경을 위해 필요한 것은 (투표권을 통해) 검증인 정족수가 서명한 최근의 블록 해시, 그리고 명칭에 대한 현재 값(current value)의 머클 증명이다. 이를 통해, ‘명칭 값’을 간결하고 신속하며 안전한 라이트 클라이언트 검증을 할 수 있다.
코스모스에서 이 개념을 확장시킬 수 있다. 코스모스의 각 명칭등록 존(name-registration zone)은 .com이나 .org와 같은 최상위 도메인(TLD: top-level-domain)을 가질 수 있고, 자체적인 거버넌스와 등록규칙을 설정할 수 있다.
## 발행과 인센티브 (Issuance and Incentives)
### 아톰 토큰 (The Atom Token)
코스모스 허브는 ‘다중자산 분산원장’이며 특별한 내부 토큰인 _아톰(atom)_을 가지고 있다. 아톰은 코스모스 허브의 유일한 지분 토큰(staking token)이다. 아톰은 보유자가 투표, 검증 또는 다른 검증인들에게 위임을 하기 위해 필요하다. 이더리움의 이더와 마찬가지로 아톰 역시 스팸공격 완화를 위한 이체수수료(transaction fees) 지불을 위해 사용될 수 있다. 추가되는 인플레이션 아톰(inflationary atoms)과 블록 이체수수료가 검증인들에게 그리고 검증인들에게 위임한 위임자들(delegators)에게 보상된다.
BurnAtomTx 이체 기능을 통해 ‘지급 준비금 풀(reserve pool)’에서 일정 비율의 토큰 금액(proportionate amount of tokens)을 회수(recover)할 수 있다.
#### 크라우드펀딩 (Fundraiser)
코스모스가 최초로 생성될 때 아톰 토큰과 검증인의 배분은 코스모스 크라우드세일의 자금제공자들(75%), 사전 자금 제공자들(5%)과 코스모스 주식회사(Cosmos Corp)(20%)로 가게 된다. 그 이후, 아톰 총액 중 1/3 이 본딩된(bonded) 검증인들과 위임자들에게 매년 보상될 것이다.
이 계획은 변경될 수 있으며 상세내용은 [Crowdfund Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md) 참조
### 검증인수의 제한 (Limitations on the Number of Validators)
비트코인 등의 작업증명 블록체인들과는 달리, 텐더민트 블록체인은 검증인의 수가 많아질수록 통신의 복잡도가 증가하여 속도가 느려진다. 물론 충분한 검증인들을 통해 블록체인의 전세계 분산, 매우 빠른 이체확인 속도를 제공할 수 있다. 또한 대역폭, 저장공간 및 병렬 컴퓨팅 용량이 증가함에 따라 장래에 더욱 많은 검증인들을 지원할 수 있을 것이다.
제네시스 시에는 최대 검증인 수가 100 명으로 설정될 것이고, 이 수치는 10 년 동안 13%의 비율로 증가하여 최종적으로는 총 300 명의 검증인을 가지게 될 것이다.
Year 0: 100
Year 1: 113
Year 2: 127
Year 3: 144
Year 4: 163
Year 5: 184
Year 6: 208
Year 7: 235
Year 8: 265
Year 9: 300
Year 10: 300
...
### 제네시스 일이후에 검증인되기 (Becoming a Validator After Genesis Day)
아톰 보유자들은 BondTx 이체’를 서명하여 제출함으로써 검증인이 될 수 있다. 담보로 제공된 아톰 액수는 0보다 많아야 한다. 현재 검증인 세트의 크기가 허용된 최대 검증인 수보다 많은 경우를 제외하고는 누구라도 언제든지 검증인이 될 수 있다. 만일 검증인의 수가 허용치 이상인 경우, 가장 적은 검증인이 보유한 유효 아톰의 액수보다 더 높은 아톰 액수(위임받은 아톰 포함)를 담보로 제공해야만 한다. 이러한 방식으로 새로운 검증인이 기존 검증인를 대체할 수 있으며, 기존 검증인은 비활성이 되고 모든 아톰과 위임 아톰(delegated atom)은 언본딩 상태(unbonding state)로 돌아간다.
### 검증인에 대한 처벌 (Penalties for Validators)
고의든 아니든 검증인이 정해진 프로토콜을 어길 때에는, 처벌이 주어져야 한다. 동일한 높이(height) 및 라운드에서의 이중 서명이나 "prevote-the-lock"(텐더민트 합의 프로토콜의 규칙)의 위반 같은 행위는 증거를 통해 즉시 인정된다. 이 경우, 검증인은 유효한 지위(good standing)를 상실하게 되며, 본딩된 아톰과 ‘지급준비금 풀(reserve pool)’내 토큰의 비례분 즉, 통칭하여 ‘지분(stake)’을 상당수 잃을 것이다.
때로는 지역 네트워크 단절, 전원 장애나 그 밖의 이유들로 인해 검증인이 단절될 수 있을 것이다. 만일 ‘과거 어느 시점의 ValidatorTimeoutWindow 블록’에서 검증인의 커밋 투표가 이루어지지 않은 횟수가 ValidatorTimeoutMax Absent 횟수 이상인 경우, 해당 검증인은 비활성화 되고 지분의 Validator TimeoutPenalty(디폴트 1%)’만큼 잃게 될 것이다.
어떤 ‘악의적’ 행위는 블록체인에 명확한 증거를 남기지 않을 수 있다. 이런 경우, 압도적 다수의 합의가 존재한다면, 검증인들이 외부에서 합의한 뒤 악의적인 검증인을 강제로 타임아웃(timeout) 시킬 수 있다.
‘투표권 ⅓+’이 악의적으로 연합하여 코스모스를 중단시키거나, 이들이 악의적 행동의 증거가 블록체인으로 들어오지 않게 검열하여 삭제하는 경우, 허브는 하드포크를 통한 블록재조정(reorg) 프로포잘로 복구(recover)되어야 한다.
### 이체 수수료 (Transaction Fees)
코스모스 허브 검증인들은 아톰 뿐 아니라, 어떠한 유형의 토큰이라도 이체수수료로 받을 수 있다. 각 검증인은, BlockGasLimit 을 초과하지 않는 한, 원하는 어떤 교환비율이든 주관적으로 정할 수 있고 원하는 어떤 이체든 선택할 수 있다. 징수된 수수료는 아래에 명시된 세금들을 제외하고, 매 ValidatorPayoutPeriod(검증인지불기간, 디폴트 1 시간)마다 본딩된 아톰(bonded atoms)에 비례하여 본딩된 주주들(bonded stakeholders)에게 재분배된다.
징수된 이체수수료 중 ‘지급준비금 세금(Reserve Tax, 디폴트 2%)’은 ‘지금준비금 풀’에 충당되며 이를 통해 ‘지금준비금 풀’을 늘리고 코스모스 네트워크의 보안과 가치를 높이는데 사용될 것이다. 또한 공유세(Commons Tax, 디폴트 3%)는 공유재의 자금으로 충당될 것이다. 이 자금들은 CustodianAddress 로 가게 되고, 거버넌스 시스템의 결정에 따라 분배된다.
투표권을 다른 검증인들에게 위임하는 아톰 보유자들은 위임 받은 검증인에게 커미션(commission)을 지불한다. 커미션은 각 검증인이 정할 수 있다.
### 해커에 대한 인센티브 제공 (Incentivizing Hackers)
코스모스 허브의 보안은 검증인들과 위임자들의 검증인 선택에 달려있다. 취약성 발견 및 조기 보고를 권장하기 위해 코스모스 허브는 해커들이 "이 검증인은 해킹 당했다. 본 주소로 포상금을 보내주기 바란다."라고 알릴 수 있는 ReporthackTx 이체’를 통해 해킹성공 발표를 권장한다. 해킹 즉시, 해당 검증인과 위임자들은 비활성화되고, 이들의 아톰 일부가 HackPunishmentRatio(해킹처벌비율, 디폴트 5%)’만큼 감소되고, 해커는 포상금 주소를 통해 HackRewardRatio(해크보상비율, 디폴트 5%)’만큼 보상받는다. 검증인은 백업 키(backup key)를 사용하여 나머지 아톰을 회수(recover)해야 한다.
이러한 방법을 이용해 ‘본딩이 완료되지 않은 비귀속(unvested) 아톰’을 전송하려는 악의적 시도를 막기위해, 감별자와 위임자들의 귀속과 비귀속 아톰 비율은 ReportHackTx 전후로 동일하게 유지될 것이다. 해커 포상금은, 존재할 경우, 얼마의 비귀속 아톰을 포함할 것이다.
### 거버넌스 (Governance Specification)
코스모스 허브는 소프트웨어 업그레이드와 헌법(규정) 수정뿐 아니라 시스템의 변수 파라미터들과 같은 블록체인의 다양한 변화를 조정하기 위해서 명확한 거버넌스 메커니즘을 가진 분산형 조직에 의해 운영된다.
모든 검증인들은 모든 프로포잘에 대한 투표책임이 있다. 적시에 투표하지 않는 경우, 해당 검증인은 ‘결석처벌기간(AbsenteeismPenaltyPeriod-디폴트 1 주) 동안 자동으로 비활성화된다.
위임자들(delegators)은 그들이 위임한 검증인의 투표를 자동으로 물려받는다. 이 투표는 수동으로 취소(overriden manually)될 수 있다. 언본딩된(unbonded) 아톰들은 어떤 투표권도 얻지 않는다.
각 프로포잘은 ‘최소 프로포잘 보증금(MinimumProposalDeposit) 토큰을 요구하는데, 이는 아톰을 포함한 하나 이상의 토큰들일 수 있다. 각 프로포잘에 대해 투표자들은 보증금을 사용하기로 투표할 수 있다. 투표자의 절반 이상이 보증금을 사용하기로 선택할 경우, 그 보증금은 지급보증금 풀에 충당된다. 다만, 프로포잘이 스팸이었다거나 기타의 경우라서 소각되는(burned) 아톰은 제외한다.
각 프로포잘에 대해, 투표자들은 다음의 옵션으로 투표할 수 있다:
- Yay(찬성)
- YayWithForce(강력히 찬성)
- Nay(반대)
- NayWithForce(강력히 반대)
- Abstain(기권)
프로포잘의 통과 여부를 결정시에는 과반수의 투표가 요구되지만, 1/3+이 "강력히(with force)" 반대 투표함으로써 과반수의 결정을 거부할 수 있다. 다만 이렇게 과반수가 거부될 경우, 모두가 ‘거부권패널티블록(VetoPenaltyFeeBlocks, 디폴트 1 일 가치의 블록)’을 통해 수수료를 상실함으로 처벌 받고, 과반수 결정을 거부한 당사자는 자신의 아톰 중 ‘거부권패널티아톰(VetoPenaltyAtoms, 디폴트 0.1%) 만큼을 추가로 상실한다.
### 파라미터 변경 프로포잘 (Parameter Change Proposal)
여기서 정의된 파라미터들 중 어느 것이든 ParameterChangeProposal 의 통과를 통해 변경될 수 있다.
### 텍스트 프로포잘 (Text Proposal)
다른 모든 프로포잘들(예. 업그레이드 프로포잘)은 일반적인 TextProposal 을 통해 조정된다.
## 로드맵 (Roadmap)
[Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md) 참조.
## 관련 연구 (Related Work)
지난 수십 년 동안 블록체인 합의구조와 확장성 부분에서 많은 혁신이 있었다. 아래에서 중요한 몇 가지를 간략히 개관한다.
### 합의 시스템 (Consensus Systems)
#### 고전적 비잔틴 장애 저항 (Classic Byzantine Fault Tolerance)
악의적인 참여자가 있는 합의는 80 년대 초로 거슬러 올라가는 문제인데, 이때 레슬리 램포트(Leslie Lamport)는 프로세스가 단순한 ‘충돌 장애(crash fault)’와는 대조되는 것으로, 의도된 행위로부터 벗어나는 임의의 프로세스 행위(process behavior)를 가리키는 ‘비잔틴 장애(Byzantine fault)'라는 용어를 만들었다. 초기의 솔루션들은 메시지 지연시간에 상한(upper bound)이 존재하는 동기식 네트워크들을 위한 것이었다. 하지만 실제 사용은 항공기 제어기와 원자시계를 통해 동기화되는 데이터센터 같은 고도로 통제된 환경들로 제한되었다. 임의의 프로세스 행위를 최대 ⅓ 까지 허용할 수 있는 효율적인 ‘부분 동기 합의 알고리즘’으로서 ‘실용적 비잔틴 장애 허용(PBFT: Practical Byzantine Fault Tolerance)[\[11\]](11)이 도입된 것은 90 년대 말이 되고 나서였다. PBFT 는 표준 알고리즘이 되었고, 많은 파생 변형들을 만들어내었다. 가장 최근에는 IBM 이 하이퍼레저(Hyperledger)에 기여를 위해 만든 변형이 있다.
PBFT 와 비교할때 텐더민트 합의구조의 중요한 장점은 텐더민트가 단순하고 개선된 기반 구조를 가진다는 것이고, 이 구조 중의 일부는 블록체인 패러다임을 수용한 결과이다. 텐더민트 블록들은 순서대로 커밋되는데, 이것이 PBFT 의 관점 변화(view-changes)와 관련 있는 복잡도와 통신 오버헤드를 제거한다. 코스모스를 포함한 많은 암호화폐에서는, 블록 _N_ 자체가 아직 커밋되지 않았을 때에는 블록 _N+i_(_i >= 1_)의 커밋을 고려할 필요가 없다. 블록 *N*이 코스모스 존에 커밋되지 않은 이유가 대역폭인 경우, _N+i_ 블록들에 대해 대역폭 공유 투표(bandwidth sharing votes)를 사용해도 의미가 없다. 만일 네트워크 분할이나 오프라인 노드들 때문에 블록 *N*이 커밋되지 않은 경우, 블록 *N+i*은 어차피 커밋되지 않는다.
뿐만 아니라, 블록들 속으로 이체를 배칭(batching)함으로 PBFT 의 체크포인팅(checkpointing) 기법 같은 주기적 다이제스트가 아닌, ‘어플리케이션 상태의 정규 머클 해싱’이 가능하다. 또한 이를 통해, 라이트 클라이언트들을 위한 보다 빠른 증명 가능한 이체 커밋과, 보다 빠른 블록체인 간 통신이 가능하다.
텐더민트 코어는 PBFT 에 명시된 것 이상의 많은 최적화 사항들과 기능들을 가지고 있다. 예를 들면, 검증인이 제안하는 블록들은 전파 성능을 개선하는 방식으로 부분화되고 머클화(Merkle-ized) 되고 가십화(gossipped)된다(영감은 LibSwift[\[19\]](19) 참조). 또한 텐더민트 코어는 점 대 점(point-to-point) 접속에 관한 어떤 가정도 하지 않으며, P2P 네트워크가 연결되어 있기만 하다면 작동한다.
#### 비트쉐어 위임 지분 (BitShares delegated stake)
비트쉐어(BitShares)[\[12\]](12)는 최초의 지분증명(PoS: proof-of-stake) 적용 사례는 아니지만, PoS 블록체인들, 특히 ‘위임(delegated) PoS 블록체인의 연구와 채택에 상당한 기여를 했다. 비트쉐어에서 지분 보유자들은 이체명령 및 커밋 책임이 있는 '증인(witnesses)'과 소프트웨어 업데이트와 패러미터 변경 책임이 있는 '델리게이트(delegates)'를 선출한다. 비트쉐어는 이상적인 상태에서 고성능(100k tx/s, 1 초 지연시간)을 달성하기는 하지만, 악의적인 증인들이 아무런 경제적 처벌 없이 블록체인을 분기해 이중지불 공격을 가할 수 있다. 즉 '무보증(Nothing-at-Stake)' 문제를 겪는다. 비트쉐어는 이체들이 최근의 블록-해시들을 참조하게 함으로써 이 문제를 완화하고자 한다. 물론 주주들은 부정행위 증인들을 매일 제거하거나 대체할 수 있다. 하지만 이것이 성공한 이중지불공격에 대한 분명한 처벌은 결코 아니다.
#### 스텔라 (Stellar)
리플(Ripple)이 개척한 방식을 토대로, 스텔라(Stellar)[\[13\]](13)는 ‘연합형 비잔틴 합의(Federated Byzantine Agreement) 모델을 개선했는데, 여기에서는 합의참여 프로세스에서 ‘전역적으로 알려진 고정된 집합’을 요구하지는 않는다. 오히려 프로세스 노드는 각기 신뢰할 수 있는 프로세스 집합을 구성하는 하나 이상의 '정족수 슬라이스(quorum slices)'를 배포한다. 스텔라에서 '정족수'는 노드들의 집합(set)이며, 적어도 각 노드당 하나 이상의 정족수 슬라이스를 포함하여 합의에 도달하도록 한다.
스텔라 메커니즘의 보안은 _임의의_ 두 정족수의 교집합이 비공(non-empty)이라는 가정에 의존한다. 또한 노드의 가용성을 위해서는, 정족수 슬라이스들 중 적어도 하나가 완전히 올바른(entirely correct) 노드들로 구성되어야 하며, 신뢰에 관한 중요한 가정 없이는 큰 정족수 슬라이스와 작은 정족수 슬라이스 사용 간에 발생하는 상호관계의 균형유지가 어려울 수 있다. 궁극적으로는 노드들은 충분한 장애허용이 가능한 ‘적절한 정족수 슬라이스’ 또는 ‘온전한 노드들(intact nodes)’을 어떻게든 선택해야 한다. 또한 그런 구성을 보장하는 유일한 전략은 계층적이고, 경계 경로 프로토콜(BGP: Border Gateway Protocol)과도 유사하다. 이는 전역 라우팅 테이블 확립을 위해 인터넷의 최상위 계층 ISP 들에 의해 사용되며, TLS 인증서를 관리하기 위해 브라우저들이 사용하기도 한다. 그러나 두 가지 모두 약한 보안성으로 악명높다.
스텔라 논문에서 비판한 ‘텐더민트 기반 지분 증명 시스템’들은 이곳에 기술된 토큰 전략을 통해 해명할 수 있다. 이 전략에서는 미래의 수수료 및 보상에 대한 권리인 *아톰*이라는 이름의 새로운 유형의 토큰이 발행된다. ‘텐더민트 기반 지분 증명’은 상대적으로 단순성을 유지하면서도, 충분한 그리고 입증 가능한 보안을 제공한다는 점에서 이점이 있다.
#### 비트코인 NG (BitcoinNG)
BitcoinNG 는 블록 크기 확장과 같은 수직 확장성을 제공하는 방법이다. 또한 이러한 확장이 초래하는 부정적 경제 요인들을 최소화하였다. 이러한 개선은 리더 선정(leader election)과 이체 전파를 분리시킴으로 가능하다: 리더들은 우선 ‘마이크로 블록(micro-block)’들에 있는 작업증명(PoW)을 통해 선정되며, 그 이후 새로운 마이크로블록이 발견될 때까지 커밋할 이체내역들을 전파한다. 이러한 방식은 PoW 경쟁을 이기기 위해 소요되는 대역폭 요구량을 줄여준다. 또한 소규모 채굴자들이 더욱 공정하게 경쟁하고 이체가 더 정기적으로 커밋될 수 있도록 한다.
#### 캐스퍼 (Casper)
캐스퍼[\[16\]](16)는 이더리움 용으로 제안된 지분증명(PoS) 합의 알고리즘이다. 주요 운용 방식은 '베팅에 의한 합의(consensus-by-bet)'이며, 검증인들은 지금까지 본 다른 베팅들(bets)에 기초해 블록체인에 커밋 될 것으로 생각되는 블록에 반복적으로 베팅하게 되며, 이러한 방식으로 결국 완결성(finality)이 달성된다는 논리를 전제한다. [링크](https://blog.ethereum.org/2015/12/28/understanding-serenity-part-2-casper/). 이러한 ‘합의 베팅’이 캐스퍼 팀의 주요 연구영역인데, 도전이 되는 부분은 베팅 메커니즘을 ‘진화적으로 안정된 전략(evolutionarily stable strategy)’이 되도록 설계해야 한다는 점이다. 텐더민트와 비교할 때 캐스퍼의 강점은 '일관성(consistency)보다 가용성(availability)'을 제공하는 것으로 볼 수 있다. 합의는 투표권의 +⅔ 정족수를 요구하지 않으며 커밋 속도나 구현 복잡도를 희생했다고 볼 수 있다.
### 수평 스케일링 (Horizontal Scaling)
#### 인터레저 프로토콜 (Interledger Protocol)
인터레저 프로토콜(Interledger protocol)[\[14\]](14)은 엄밀히는 확장성 솔루션이 아니다. 느슨하게 연결된 ‘쌍방 관계 네트워크’를 통해 상이한 원장 시스템들 간 ‘애드혹 상호운용성(ad hoc interoperation)’을 제공하는 방식이다. 라이트닝 네트워크(Lightning Network)의 경우처럼, ILP 는 지불을 용이하게 하는 프로토콜인데, 특히 서로 다른 유형의 원장들 간 지불에 초점을 맞추며 ‘아톰 이체 메커니즘(atomic transaction mechanism)’을 확장하여 ‘해시 잠금(hash-locks)’과 ‘공증인 정족수(quorum of notaries)’를 포함하로독 했으며, 이를 ‘아톰 전송 프로트콜(Atomic Transport Protocol)’로 칭한다. ‘원장 간 이체의 원자성(atomicity)’을 확보하기 위한 이러한 메커니즘은 텐더민트의 ‘라이트 클라이언트 SPV 작동방식과도 유사하다. 하단에서 ILP 와 코스모스/IBC 를 비교해본다.
1. ILP 의 커넥터(connector) 공증인들은 구성원(membership) 변경을 지원하지 않으며 공증인들 간 유연한 가중치 부여를 지원하지 않는다. 반면에 IBC 는 블록체인 전용으로 설계되었고, 검증인들이 상이한 가중치를 가질 수 있으며 블록체인을 통해 구성원들의 변경이 가능하다.
2. 라이트닝 네트워크나 ILP 에서는 지불 수신자가 송신자에게 ‘확인’을 되돌려 보내기 위해 반드시 온라인 상태여야 한다. IBC 를 통한 토큰 전송에서는, 수신자가 아니라, 수신자 블록체인의 검증인 세트가 ‘확인’을 제공할 책임이 있다.
3. 가장 근본적인 차이는 ILP 커넥터들은 지불에 대한 책임을 지거나 권한 상태를 유지하고 있지 않은 반면에, 코스모스에서는 허브의 검증인들이 IBC 토큰 전송과 각 존이 가진 토큰 총액에 대한 권한을 가진다는 점이다(그러나 존 내 각 계정이 보유한 토큰 금액에 대한 권한은 없다). 이는 존에서 존으로 비대칭 토큰 전송을 안전하게 실행하기 위한 근본적 혁신이다.
4. ILP 에서 원장 간 지불을 하기 위해서는 거래소 오더북(exchange orderbook)의 지원이 필요한데, 이는 하나의 원장에서 다른 원장으로의 ‘코인 비대칭 전송’이 없고 ‘가치나 시장 등가물(market equivalents)’의 전송만 있기 때문이다.
#### 사이드체인 (Sidechains)
사이드체인(sidechains)[\[15\]](15)은 비트코인 블록체인에 '페깅(pegged)'된 ‘대안 블록체인’을 통해 비트코인 네트워크를 확장하고자 하는 시도이다. 사이드체인은 비트코인이 비트코인 블록체인으로부터 사이드체인으로 효과적으로 이동할 수 있게 하고 사이드체인에서 각 체인의 특징을 이용한 실험들을 가능하게 한다. 코스모스 허브와 마찬가지로, 사이드체인과 비트코인은 상호 간 라이트 클라이언트 역할을 하며 코인 전송 시점을 결정하기 위해 SPV 증명을 사용한다. 물론 비트코인은 작업증명을 사용하기 때문에 비트코인 중심의 사이드체인들은 작업증명 합의 메커니즘으로부터 발생하는 여러 문제들과 리스크를 갖는다. 이는 ‘비트코인 극단주의자(Bitcoin-maximalist)’적 솔루션이며, 코스모스처럼 자체적으로 다양한 토큰들과 존 간 네트워크 위상(inter-zone network topology)을 지원하지는 않는다. 하지만, ‘양방향 페그(two way peg)’의 핵심 메커니즘 자체는 코스모스 네트워크의 방식과 원칙적으로 동일하다.
#### 이더리움 확장성 노력 (Ethereum Scalability Efforts)
이더리움은 확장성 확보를 위해 ‘이더리움 블록체인 상태’를 샤딩(sharding)하기 위한 여러 전략들을 연구 중이다. 현재의 이더리움 가상 머신(EVM)를 통해 ‘공유 상태 공간(shared state space)’에 ‘대한 추상적 계층(abstraction layer)’을 유지하는 방식이다. 또한 다수의 연구들이 진행되고 있다.[\[18\]](18)[\[22\]](22)
#### 코스모스 vs 이더리움 2.0 Mauve (Cosmos vs Ethereum 2.0 Mauve)
코스모스와 이더리움 2.0 Mauve[\[22\]](22)의 설계 목표에는 차이가 있다.
- 코스모스가 토큰들에 대한 것이라면 Mauve 는 일반 계산(general computation)의 스케일링에 관한 것이다.
- 코스모스는 EVM 에 구속되지 않으며, 심지어는 서로 다른 VM 들이 상호 운용될 수 있다.
- 코스모스는 존의 검증 책임자를 존의 생성자가 결정하도록 한다.
- (거버넌스 시스템의 결정과 상충되지만 않는다면) 누구든지 코스모스에서 새 존을 시작할 수 있다.
- 허브는 존 실패(zone failures)를 격리시키며, 이를 통해 ‘전역 토큰 불변성(global token invariants)’을 보증한다.
### 일반 스케일링 (General Scaling)
#### 라이트닝 네트워크 (Lightning Network)
라이트닝 네트워크는 비트코인 블록체인(그리고 그 밖의 퍼블릭 블록체인들)의 상위 계층에서 운용되도록 제안된 토큰 전송 네트워크로, 합의원장(consensus ledger) 외부의 대다수 이체들을 소위 ‘지불채널(payment channels)’로 이동시켜서 이체처리량(throughput)을 획기적으로 개선시킨다. 온 체인(on-chain) 암호화폐 스크립트를 통해 당사자들이 쌍무 상태기반 계약(bilateral stateful contracts)을 체결할 수 있도록 한다. 계약들의 상태는 디지털 서명에 의해 업데이트 되며, 또한 ‘크로스 체인 아토믹 스왑(cross-chain atomic swap)’를 통해 최초에 설정된 방식에 따라 합당한 증명을 블록체인에 전파함으로 계약이 마감된다. 다수의 당사자와 함께 지불 채널을 개방함으로, 라이트닝 네트워크의 참여자들은 당사자들이 지불을 라우팅하는 ‘포컬포인트(focal point)’의 역할을 할 수 있다. 또한 이러한 지불 채널에 실제 자본을 고정시킴으로 완전히 연결된 지불 채널을 개설하는 것도 가능하다.
라이트닝 네트워크는 다수의 블록체인들에 걸쳐 확장되면서 교환시장을 통해 *가치(value)*를 전송하도록 하지만, 하나의 블록체인에서 다른 블록체인으로 *토큰(token)*을 비대칭적으로 전송하는 것은 불가능하다. 이와 관련된 코스모스 네트워크의 주된 이점은 그러한 토큰의 직접 전송을 지원한다는 것이다. 물론, 비용 절감과 프라이버시 차원에서 토큰 전송 메커니즘이 지불 채널들과 라이트닝 네트워크와 함께 적용되기를 기대한다.
#### 세그위트 (Segregated Witness)
세그위트(Segregated Witness)는 블록 당 이체처리량을 2-3 배 가량 증가시키는 것을 목적으로 하며, 동시에 신규 노드들이 더욱 빠르게 블록 동기화(block syncing)를 하도록 돕는 비트코인 개선 제안이다([참조](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)). 이 솔루션은 비트코인의 현재 프로토콜 내에서 작동하면서 소프트 포크 업그레이드(soft-fork upgrade)를 허용하는 방식이다. 따라서, 이전의 소프트웨어 버전을 가진 클라이언트들도 업그레이드를 받아 정상적으로 기능할 수 있다. 텐더민트의 경우, 새로운 프로토콜을 만드는데 아무런 제약이 없기 때문에 확장성에 대한 새로운 우선순위를 가진다. 주로 텐더민트는 채굴 대신 암호 서명에 기반한 BFT 라운드 로빈 알고리즘(BFT round-robin algorithm)’을 사용한다. 이를 통해, 다중 병렬 블록체인을 이용한 수평 스케일링을 가능하게 하면서도, 보다 정기적이고 자주 발생하는 블록 커밋에는 수직 스케일링을 적용할 수도 있다.
<hr/>
## 부 록 (Appendix)
### 포크 책임 (Fork Accountability)
잘 설계된 합의 프로토콜이라면 허용 용량(tolerance capacity)이 초과되거나 합의가 실패할 경우에도 문제가 없어야 한다. 이는 ‘비잔틴 행동’에 상당한 금전적 보상이 있을 수 있는 경제시스템에서 특히 중요하다. 그러한 보장 중 가장 중요한 것이 _포크 책임(fork-accountability)_인데, 포킹과 같은 합의실패를 초래한 프로세스들이 프로토콜 규칙이나 법적 체계(가능하다면)에 의해 식별되고 처벌받을 수 있다. 법 체계를 신뢰할 수 없거나 적용하기에 과도한 비용이 들 경우, 검증인들이 담보 보증(security deposits)을 내고 참여하도록 하고 악영향을 주는 행위 시 해당 담보를 차감하거나 강제로 환수함으로 처벌할 수 있다[\[10\]](10).
‘포크 책임’에서 다루는 것은 비트코인에서처럼 ‘네트워크 비동시성’과 ‘부분적 해시충돌’ 특성으로 인해 일어나는 정상적 분기와는 큰 차이가 있다. 많은 경우, 비동시성 때문에 악의적 포킹을 가려내는 것이 거의 불가능하기 때문에 비트코인의 경우 채굴자들이 포킹을 시도하다가 고아블록이 되어버리는 기회비용을 제외하고는 특별히 ‘포크 책임’을 물게하는 것이 어렵다.
### 텐더민트 합의 (Tendermint Consensus)
투표 단계들은 *프리보트(PreVote)*와 *프리커밋(PreCommit)*으로 나뉜다. 투표는 특정 블록이나 _무효(Nil)_를 위해 행사될 수 있다. 동일 라운드에서 단일 블록에 대한 +⅔ PreVote 집합을 *폴카(Polka)*라고 부르며, 동일 라운드에서의 단일 블록에 대한 +⅔ PreCommit 집합을 *커밋(Commit)*이라고 부른다. 만일 동일 라운드에서 ‘무효(Nil)’에 +⅔ PreCommit 상태라면, 다음 라운드로 이동한다.
결함 있는 리더들을 검출하고 무시하기 위해 엄격한 결정성(determinism)을 사용하다가 오히려 약한 동시성(synchrony)을 초래할 수 있다. 따라서 검증인들은 무효(Nil)를 Prevote 하기 전에, 일정 시간(*TimeoutPropose)*을 기다리게 되며 이 TimeoutPropose 값은 매 라운드마다 점점 증가한다. 검증인이 네트워크의 +⅔ 로부터 전파받을 때에만 단 한번 진행이 되므로, 라운드의 나머지 부분 진행은 완전히 비동시적이다. 실제로, 약한 동시성 가정( weak synchrony assumption)을 계속해서 좌절시키고 결국 블록 커밋을 위한 합의를 실패시키기 위해서는 극단적으로 강한 공격자가 필요하다. 또한 각 검증인에게 서로 다른 TimeoutPropose 랜덤값을 적용한다면, 공격은 더욱 어려워진다.
‘추가 제약조건 집합’, 즉 ‘잠금 규칙(Locking Rules)’은 네트워크가 각 높이에서 단 하나의 블록만을 커밋하도록 보장한다. 특정한 높이에서 두 개 이상의 블록을 커밋되게 하려는 시도는 모두 발각될 수 있다. 첫째로, 블록에 대한 프리커밋(PreCommit)은 해당 블록에 대한 폴카의 형태로 정당화(justification)되어야 한다. 만일 검증인이 이미* R_1* 라운드에서 블록을 프리커밋했다면, 이는 해당 블록에 로킹*(locked)*되며, _R_2_ 라운드의 새로운 프리커밋은 반드시 *R_1 &lt; R_polka &lt;= R_2*인 R_polka 라운드’에서 발생해야한다. 둘째로, 검증인들은 자신들이 로킹되어 있는 블록을 제안(Propose)하거나 아니면 사전투표(PreVote)하거나 또는 둘을 함께 해야한다. 이를 통해 검증인은 충분한 증거 없이 프리커밋하지 못하며, 이미 프리커밋한 검증인의 경우 다른 블록을 동시에 프리커밋할 수 없게 된다. 이렇게 합의 알고리즘의 안전성(safety)과 라이브성(liveness)을 보장하게 된다.
프로토콜의 전체 세부내용은 [여기](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)를 참조
### 텐더민트 라이트 클라이언트 (Tendermint Light Clients)
대안 체인(포크)의 존재 덕분에 본딩된(bonded) 지분을 ⅓+이 대폭 삭감할(slashed) 수 있기 때문에, 텐더민트 PoS 에서는 모든 블록 헤더들을 동기화(sync)할 필요가 없다. 물론 이러한 삭감을 위해서는 누군가가 포킹의 증거를 공유해야 하기 때문에, 라이트 클라이언트는 전파받는 모든 블록해시 커밋을 저장해야 한다. 뿐만 아니라, 라이트 클라이언트들은 ‘검증인 세트의 변경’과 주기적으로 동기화하여 [장기 공격(long range attacks)](#preventing-long-range-attacks)을 피할 수 있을 것이다(물론 다른 방법도 적용가능).
이더리움과 유사한 의도로, 텐더민트는 애플리케이션들이 각 블록에 ‘전역 머클 루트 해시(global Merkle root hash)’를 임베딩(embed)하도록 하고, 계정 잔고, 컨트랙트내 저장 값, UTXO 등 각 어플리케이션에 적합한 쉽게 검증이 가능한 ‘상태 쿼리(state queries)’를 이용하도록 한다.
### 장기 공격 방지 (Preventing Long Range Attacks)
‘충분한 회복력(resilient)의 전파 네트워크 집합’과 ‘정적(static) 검증인 세트’가 있다면, 블록체인의 모든 포킹이 검출될 수 있으며 문제가 되는 검증인들의 보증금을 대폭 삭감시킬 수 있다. 2014 년 초에 비탈릭 뷰터린(Vitalik Buterin)은 처음 지분증명 암호화폐들의 ‘무처벌(nothing-at-stake) 문제에 대한 해결책을 제시한다([관련 연구](#related-work) 참조). 그러나 검증인 세트들이 항상 고정되어 있는 것이 아니기 때문에 오랜 기간에 걸쳐 원래의 검증인들은 모두 언본딩 될 수 있고(may all become unbonded) 묶어둔 보증금이 사라지면서, 어떤 비용도 치르지 않고 제네시스 블록으로부터 새로운 체인을 자유로이 생성할 수 있게 된다. 이 공격은 단기 공격(Short Range Attack)과 대조되는 개념으로 ‘장기 공격(Long Range Attack)’으로 알려지게 되었다. 텐더민트 합의와 같은 ‘포크 책임 BFT 알고리즘(folk-accountable BFT algorithm)’이 존재한다면 현재 본딩된 검증인들이 포크를 초래하는 단기공격은 처벌이 가능하다. 반면 장기 공격은 지분증명 방식에 치명적인 공격으로 간주된다.
다행히 ‘장기공격(LRA)’을 부분적으로 방지하는 것이 가능하다. 첫째로, 검증인이 보증금을 언본딩하여 보증금을 회수하고 동시에 더이상의 합의참여 수수료를 포기하기 위해서는 정해진 일정 기간, 즉 ‘언본딩 기간(unbonding period)’을 채워야 한다. 그 기간은 수주 또는 수개월이 된다. 둘째로, 라이트 클라이언트는 최초로 네트워크에 연결될 때 신뢰할 수 있는 한개 이상의 출처를 통해 네트워크의 최근 블록해시를 검증해야 한다. 이러한 조건을 ‘약한 주체성(weak subjectivity)’으로 부르기도 한다. 마지막으로, 보안성을 유지하기 위해서 적어도 매 ‘언본딩 기간’을 주기로 ‘최종 검증인 세트’와 동기화되어야 한다. 이를 통해, 라이트 클라이언트는 검증인이 보증금을 빼내고 ‘무보증 상태(Nothing at Stake)’가 되었는지 여부를 파악할 수 있게 된다. 이를 파악하지 못한다면, 검증인은 최초 본딩이 시작된 블록으로 돌아가 새로우 블록들을 다시 생성함으로 장기공격을 감행할 수 있다(물론 이를 위해서는 검증인이 충분히 많은 초기 개인키를 가지고 있어야 함).
이러한 방식의 LRA 대응 방안은 기존 ‘작업증명(Proof-of-Work) 방식의 보안 기법에 대한 완전한 분해정비를 필요로 한다. ‘작업증명’에서는, 라이트 클라이언트가 언제든 전체 블록헤더의 작업증명을 처리하기만 한다면 신뢰할 수 있는 제네시스 블록을 통해 최신 블록체인을 동기화할 수 있다. 그러나 LRA 에 대응하기 위해서는, 라이트 클라이언트가 정기적으로 온라인 상태가 되어 검증인 세트를 추적해야하며, 최초 온라인이 되었을 때 반드시 신뢰할 만한 출처를 통해 네트워크에서 제공하는 정보의 진실성을 확인해야만 한다. 물론 신뢰할만한 출처를 이용하는 방법은 비트코인에서도 동일한데, 프로토콜이나 소프트웨어를 반드시 신뢰할 수 있는 출처에서 얻어야 하기 때문이다.
LRA 를 방지하는 상기의 방법은, 검증인들과 텐더민트 기반 블록체인의 완전 노드들에 적합하다. 이 노드들은 네트워크에 계속 연결되어 있기 때문이다. 또한 이 방법은 네트워크에 자주 연결될 수 있는 라이트 클라이언트들에도 적합하다. 하지만 라이트 클라이언트들이 자주 연결될 수 없는 경우, 다른 방법이 사용될 수 있다. 검증인이 아닌 토큰 보유자들이 상당히 긴 기간동안 자신의 토큰을 담보로 제공하는 것이다. 그리고 라이트 클라이언트들에게 이차적인 방법으로 최근과 이전의 블록해시 값을 전달하는 것이다. 이러한 토큰들이 블록체인 합의와 관련된 보안에는 영향을 미치지 않으나 라이트 클라이언트에게는 강력한 보장을 제공할 수 있다. 역사적 블록-해시 질의가 이더리움에서 지원될 경우, 누구든지 특별 설계된 스마트 컨트랙트에서 자신의 토큰을 본딩하고 지불을 위한 인증 서비스(attestation services for pay)를 제공하여 라이트 클라이언트 LRA 보안 시장을 효과적으로 창출할 수 있을 것이다.
### 포크 및 검열 공격 극복 (Overcoming Forks and Censorship Attacks)
블록 커밋의 정의로 인해, ⅓+ 투표권 연합은 오프라인이 되거나 투표전파를 거부함으로 블록체인을 중단시킬 수 있다. 그러한 연합을 통해 특정한 이체를 포함한 블록을 검열하여 탈락시킬 수도 있으며, 이 경우 상당한 양의 프로포잘이 거부되고 블록커밋 속도를 늦추어 블록체인의 효용과 가치를 낮출수도 있다. 또한 연합은 소량의 투표만을 전파하여 블록체인의 블록커밋 속도를 서서히 늦출 수도 있다. 결정적으로 이들은 이중서명이나 잠금규정을 지키지 않음으로 블록체인을 분기(포킹)시킬 수도 있다.
전역적 능동 공격자(global active adversary)가 관여할 경우, 네트워크를 분할시키고 다른 이가 책임이 있는 것처럼 보이게 할 수도 있다. 이는 텐더민트만의 문제는 아니며 네트워크 상에 능동 공격자가 존재하는 모든 합의 프로토콜이 가진 한계이다.
이러한 유형의 공격이 가능해지려면, 발리데이터 부분집합(a subset of the validators)이 합동하여 외부수단을 이용해 포킹 목적의 블록재조정 프로포잘에 서명해야하고, 초기 발리데이터 부분집합도 이에 서명해야한다. 클라이언트들은 블록재조정 프로포잘의 서명을 유심히 검증하고, 이를 근거로 판단하거나 그 결정을 최종사용자에게 넘길 수 있다.
어떤 ‘비동시성 비잔틴 장애 저항 알고리즘(non-synchronous Byzantine fault-tolerant algorithm)’도 투표권의 ⅓+이 부정직할 경우, 합의에 도달할 수 없다. 하지만, 포크의 경우 투표권의 ⅓+이 이미 이중 서명이나 잠금 변경(lock-changing)에 의해 부정직한 것으로 가정한다. 따라서 블록재조정 프로포잘(reorg-proposal)에 서명하는 것은 어떤 비동시성 프로토콜에서도(즉, 자동적으로 그리고 기반 네트워크의 신뢰성에 관한 가정없이는) 해결할 수 없는 조정 문제이다. 현재로서는 ‘블록재조정 프로포잘 조정(reorg-proposal coordination) 문제를 인터넷 매체 상에서 일어나는 인간들의 사회적 합의에 맡기고 있다. 상충되는 두 블록재조정 프로포잘에 서명하는 것을 방지하기 위해, 검증인들은 서명에 앞서 남은 네트워크 분할이 없도록 주의해야 한다.
외부의 조정 수단 및 프로토콜이 강인하다면, 네트워크분기(포크)가 검열 공격보다는 덜 치명적이다.
⅓ 비잔틴 투표권을 요구하는 포크 및 검열 외에, +⅔ 투표권의 연합이 임의의 무효 상태를 커밋할 수도 있다. 이는 모든 BFT 합의 시스템의 특징이다. 쉽게 검증 가능한 포크가 생성되는 이중서명과는 달리, 무효 상태 커밋(commitment of an invalid state)을 검증하기 위해서는 발리데이터가 아닌 피어들(non-validating peers)도 전체 블록들을 검증해야한다. 이는 피어들이 상태의 지역 사본(local copy)을 가지고 각 이체를 실행하며, 상태 루트(state root)를 독립적으로 계산한다는 것을 의미한다. 또한 무효상태커밋이 검출되면, 사회적 합의만이 이를 처리하는 유일한 방법이다. 예를 들어, (2013 년 3 월처럼) 소프트웨어 버그로 인한 분기이든, 또는 (2015 년 7 월처럼) 채굴자들의 비잔틴 행동으로 인한 무효 상태 커밋이든 비트코인이 실패한 상황들에서, 비즈니스, 개발자, 채굴자 및 그 밖의 조직들의 잘 연결된 커뮤니티는 네트워크 회복을 위해서 참여자들의 무엇을 해야하는지에 대한 사회적 합의를 확립했다. 텐더민트의 경우, 블록체인의 검증인들이 식별될 수 있을 것으로 예상되기 때문에, 필요한 경우, 무효 상태의 커밋을 법률이나 어떤 외부 관할권을 통해 처벌하는 것까지도 가능할 수 있다.
### TMSP 명세 (TMSP specification)
TMSP 는 코어에서 애플리케이션으로 전달되는 3 가지 주요 메시지 유형으로 구성된다. 애플리케이션은 대응하는 응답 메시지로 응답한다.
AppendTx 메시지는 애플리케이션의 핵심 작업수단이다. 블록체인에서의 이체는 이 메시지와 함께 전달된다. 애플리케이션은 AppendTx 메시지와 함께 수신된 각 이체를 현재의 상태, 애플리케이션 프로토콜, 이체의 암호학적 요건 등과 비교해서 검증한다. 이후, 검증된 이체는 값을 키-값 저장장치(key values store)으로 결합하거나 UTXO 데이터베이스를 업데이트하여 애플리케이션 상태를 업데이트해야 한다.
CheckTx 메시지는 AppendTx 와 유사하지만 이체 검증에만 사용된다. 텐더민트 코어의 mempool(메모리 풀)은 CheckTx 로 이체의 유효성을 검사하고 유효한 이체만 피어들에 전파한다. 애플리케이션들은 이체의 증가 논스 (incrementing nonce)를 검사하고 논스가 오래된 경우, CheckTx 에 오류를 반환할 수도 있다.
Commit 메시지는 다음 블록 헤더에 포함될 현재 애플리케이션 상태에 대한 암호학적 커밋(cryptographic commitment)을 계산하기 위해 사용된다. 이를 통해 몇가지를 얻을 수 있다. 해당 상태 업데이트에서 불일치들이 포크의 형태로 나타나게 된다. 또한 머클-해시 증명들이 블록-해시와의 비교를 통해 검증될 수 있고 블록-해시는 (투표권에 의한) 검증인 정족수에 의해 서명되기 때문에, 단순한 방식으로 안전한 라이트 클라이언트 개발하는데 도움이 된다.
추가적 TMSP 메시지를 통해 애플리케이션이 검증인 세트를 추적(keep track of) 및 변경하고, 높이와 커밋 투표 같은 블록 정보를 수신할 수 있다.
TMSP 요구/응답(TMSP requests/responses)은 간단한 Protobuf 메시지들이다. [스키마 파일(schema file)](https://github.com/tendermint/abci/blob/master/types/types.proto) 확인
##### AppendTx
- **Arguments**:
- `Data ([]byte)`: The request transaction bytes
- **Returns**:
- `Code (uint32)`: Response code
- `Data ([]byte)`: Result bytes, if any
- `Log (string)`: Debug or error message
- **사용법**:<br/>
이체를 덧붙이고 실행한다. 이체가 유효할 경우, CodeType.OK 를 반환한다.
##### CheckTx
- **Arguments**:
- `Data ([]byte)`: The request transaction bytes
- **Returns**:
- `Code (uint32)`: Response code
- `Data ([]byte)`: Result bytes, if any
- `Log (string)`: Debug or error message
- **사용법**:<br/>
이체를 검증한다. 이 메시지가 상태를 변경(mutate)해서는 안 된다. 이체들은 mempool 계층의 피어들에게 전파되기 전, CheckTx 를 통해 먼저 실행된다. 동일 블록 내 이체의 발생 순서를 고려하기 위해 CheckTx 를 semi-stateful(준-상태기반)로 만들고 Commit 또는 BeginBlock 시에 상태를 클리어(clear) 할 수 있다.
##### 커밋 (Commit)
- **Returns**:
- `Data ([]byte)`: The Merkle root hash
- `Log (string)`: Debug or error message
- **사용법**:<br/>
애플리케이션 상태의 머클 루트 해시를 반환한다.
##### Query
- **Arguments**:
- `Data ([]byte)`: The query request bytes
- **Returns**:
- `Code (uint32)`: Response code
- `Data ([]byte)`: The query response bytes
- `Log (string)`: Debug or error message
##### Flush
- **사용법**:<br/>
응답 대기행렬(response queue)을 플러시(flush)한다. types.Application 을 구현하는 애플리케이션들은 이 메시지를 구현할 필요가 없다--프로젝트에 의해 처리되기 때문이다.
##### Info
- **Returns**:
- `Data ([]byte)`: The info bytes
- **사용법**:<br/>
애플리케이션 상태에 관한 정보를 반환한다. 애플리케이션 특정적이다.
##### SetOption
- **Arguments**:
- `Key (string)`: Key to set
- `Value (string)`: Value to set for key
- **Returns**:
- `Log (string)`: Debug or error message
- **사용법**:<br/>
애플리케이션 옵션을 설정한다. 예를 들어, mempool 연결을 위해서는 Key='mode', Value='mempool', 또는 합의 연결을 위해서는 Key='mode', Value='consensus'. 다른 옵션들은 각 애플리케이션마다 다르다.
##### InitChain
- **Arguments**:
- `Validators ([]Validator)`: Initial genesis-validators
- **사용법**:<br/>
제네시스 시, 1 회 호출됨.
##### BeginBlock
- **Arguments**:
- `Height (uint64)`: The block height that is starting
- **사용법**:<br/>
새 블록의 시작을 알린다. 모든 AppendTx 에 앞서 호출됨.
##### EndBlock
- **Arguments**:
- `Height (uint64)`: The block height that ended
- **Returns**:
- `Validators ([]Validator)`: Changed validators with new voting powers (0
to remove)
- **사용법**:<br/>
블록의 끝을 알린다. 이체 후, 각 커밋에 앞서 호출됨.
상세한 내용은 [TMSP 리포지토리(TMSP repository)](https://github.com/tendermint/abci) 참조.
### IBC 패킷 전송 확인응답 (IBC Packet Delivery Acknowledgement)
발신자가 수신 체인의 패킷전송 확인응답을 원할 여러 가지 경우가 존재한다. 예를 들어 장애가 있을 경우, 송신자는 수신 체인의 상태를 모를 수 있다. 또는 송신자는 (MaxHeight 패킷 필드로) 패킷에 타임아웃을 부여하기를 원하는 반면, 수신 체인이 갑작스런 수신 패킷 수 급증에 의한 서비스 거부(denial-of-service) 공격을 받을 수도 있다.
이런 경우, 송신자는 초기 패킷 상태를 AckPending 으로 설정하여 전송 확인응답을 요구할 수 있다. 그러면 앱 머클 해시에 abbreviatedIBCPacket 을 포함하여 전송을 확인해 주는 것이 수신 체인의 책임이다.
![Figure of Zone1, Zone2, and Hub IBC with acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack.png)
먼저, 'Zone 1'에 IBCPacket 이 존재함을 증명하는 '허브'가 있다. 그리고 이 허브 위에 IBCBlockCommit 과 IBCPacketTx 가 포스팅된다. 예를 들어 IBCPacketTx 는 다음과 같은 값을 갖는다:
- `FromChainID`: "Zone1"
- `FromBlockHeight`: 100 (say)
- `Packet`: an `IBCPacket`:
- `Header`: an `IBCPacketHeader`:
- `SrcChainID`: "Zone1"
- `DstChainID`: "Zone2"
- `Number`: 200 (say)
- `Status`: `AckPending`
- `Type`: "coin"
- `MaxHeight`: 350 (say "Hub" is currently at height 300)
- `Payload`: &lt;The bytes of a "coin" payload>
다음으로, '허브'에 IBCPacket 이 존재함을 증명하는 'Zone 2' 위에 IBCBlockCommit 과 IBCPacketTx 가 포스팅된다. 예를 들면, IBCPacketTx 는 다음과 같다:
- `FromChainID`: "Hub"
- `FromBlockHeight`: 300
- `Packet`: an `IBCPacket`:
- `Header`: an `IBCPacketHeader`:
- `SrcChainID`: "Zone1"
- `DstChainID`: "Zone2"
- `Number`: 200
- `Status`: `AckPending`
- `Type`: "coin"
- `MaxHeight`: 350
- `Payload`: &lt;The same bytes of a "coin" payload>
그 다음, 'Zone 2'는 앱-해시(app-hash)에 새로운 상태의 AckSent 를 보이는 생략형 패킷을 반드시 포함해야 한다. 생략형 IBCPacket 이 Zone 2' 위에 존재함을 증명하는 '허브' 위에 IBCBlockCommit 와 IBCPacketTx 가 다시 포스팅된다. 예를 들면 IBCPacketTx 는 다음과 같다:
- `FromChainID`: "Zone2"
- `FromBlockHeight`: 400 (say)
- `Packet`: an `IBCPacket`:
- `Header`: an `IBCPacketHeader`:
- `SrcChainID`: "Zone1"
- `DstChainID`: "Zone2"
- `Number`: 200
- `Status`: `AckSent`
- `Type`: "coin"
- `MaxHeight`: 350
- `PayloadHash`: &lt;The hash bytes of the same "coin" payload>
끝으로, '허브'는 패킷의 상태를 AckPending 에서 AckReceived 로 업데이트 해야 한다. 이 새로이 완결된 상태의 증거가 'Zone 2'로 되돌아간다. 예를 들면, IBCPacketTx 는 다음과 같은 값을 갖는다:
- `FromChainID`: "Hub"
- `FromBlockHeight`: 301
- `Packet`: an `IBCPacket`:
- `Header`: an `IBCPacketHeader`:
- `SrcChainID`: "Zone1"
- `DstChainID`: "Zone2"
- `Number`: 200
- `Status`: `AckReceived`
- `Type`: "coin"
- `MaxHeight`: 350
- `PayloadHash`: &lt;The hash bytes of the same "coin" payload>
한편, 'Zone 1'은 ‘코인’패킷 전송의 실패가 '허브' 상에서 증명되지 않는 한, 성공적으로 전송될 것이라고 가정할 것이다. 위의 예에서, '허브'는 ‘블록 350를 통해 'Zone 2'의 AckSent 상태를 수신하지 않는다면, 상태를 타임아웃(Timeout)으로 자동 설정했을 것이다. 이 타임아웃 증거가 'Zone 1'에 다시 포스팅 될 수 있고, 모든 토큰은 반환될 수 있다.
![Figure of Zone1, Zone2, and Hub IBC with acknowledgement and timeout](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack_timeout.png)
### 머클 트리와 증명 명세 (Merkle Tree & Proof Specification)
두 가지의 머클 트리 유형이 텐더민트/코스모스 생태계에서 지원된다: 단순 트리(Simple Tree)와 IAVL+ 트리.
#### 단순 트리 (Simple Tree)
단순트리는 요소들(elements)의 정적 리스트(static list)를 위한 머클 트리이다. 항목의 수가 2 의 거듭제곱이 아닐 경우, 잎들이 서로 다른 수준에 위치할 수 있다. 단순 트리는 트리의 양측면을 동일한 높이로 유지하고자 하지만, 좌측이 더 클 수도 있다. 이 머클 트리는 블록의 이체들을 그리고 애플리케이션 상태 루트의 최상위 요소들을 머클화 하기 위해 사용된다.
*
/ \
/ \
/ \
/ \
* *
/ \ / \
/ \ / \
/ \ / \
* * * h6
/ \ / \ / \
h0 h1 h2 h3 h4 h5
7요소의 단순트리
#### IAVL+ Tree
IAVL+ 데이터 구조의 목적은 애플리케이션 상태의 키-값 쌍들에 영구 저장(persistent storage)을 제공하는 것이다. 그렇게 하여 결정론적 머클 루트 해시를 효율적으로 계산한다. 트리는 [AVL 알고리즘](https://en.wikipedia.org/wiki/AVL_tree)의 변형(variant)을 사용하여 밸런싱되며(balancing), 모든 연산(operations)은 O(log(n))이다.
AVL 트리에서는 임의 노드의 2 개 ‘자식 서브트리(child subtree)’의 높이가 최대 1 차이가 난다. 업데이트 시, 이 조건이 위반될 때마다 트리는 이전 트리의 비수정 노드들을 가리키는 O(log(n))의 새 노드들을 만들어서 다시 균형을 잡는다. 기존 AVL 알고리즘에서는 내부 노드들이 키-값 쌍들도 보유할 수 있다. AVL+ 알고리즘(플러스에 주의한다)은 잎 노드 상의 모든 값을 유지하기 위해 AVL 알고리즘을 수정하며, 가지 노드들만 사용하여 키를 저장한다. 이를 통해 알고리즘을 단순화하고, 머클 해시 트레일(merkle hash trail)을 짧게 유지한다.
AVL+ 트리는 이더리움의 [패트리샤 트라이(Patricia tries)](https://en.wikipedia.org/wiki/Radix_tree)와 유사하다. 장단점이 존재한다. 키들은 IAVL+ 트리에 삽입되기 전에 해싱될 필요가 없고, 따라서 키 공간(key space)에서의 빠른 순서반복(ordered iteration)을 제공한다. 로직의 구현이 간편하고 내부 노드(inner nodes)와 잎 노드(leaf nodes)만 필요로 한다. 머클 증명은 일반적으로 짧고 균형 잡힌 이진 트리이다. 반면에 IAVL+ 트리의 머클 루트는 업데이트의 순서(order of updates)에 의존한다.
이진 변형(binary variant) 사용이 가능해지면, 이더리움의 패트리샤 트라이 같은 효율적 머클 트리들을 추가로 지원할 것이다.
### 이체 유형 (Transaction Types)
일반적으로, 이체들은 TMSP 인터페이스를 통해 코스모스 허브 애플리케이션으로 흘러간다.
코스모스 허브는 앞으로 SendTx, BondTx, UnbondTx, ReportHackTx, SlashTx, BurnAtomTx, ProposalCreateTx, ProposalVoteTx 등을 포함한 많은 주요 이체 유형들(primary transaction types)을 추가할 것이고 문서화할 것이다. 여기서는 IBC 의 두 주요 이체 유형인 IBCBlockCommitTx 와 IBCPacketTx 에 대해 설명한다.
#### IBCBlockCommitTx
IBCBlockCommitTx 이체는 다음으로 구성된다:
- `ChainID (string)`: The ID of the blockchain
- `BlockHash ([]byte)`: The block-hash bytes, the Merkle root which includes the
app-hash
- `BlockPartsHeader (PartSetHeader)`: The block part-set header bytes, only
needed to verify vote signatures
- `BlockHeight (int)`: The height of the commit
- `BlockRound (int)`: The round of the commit
- `Commit ([]Vote)`: The +⅔ Tendermint `Precommit` votes that comprise a block
commit
- `ValidatorsHash ([]byte)`: A Merkle-tree root hash of the new validator set
- `ValidatorsHashProof (SimpleProof)`: A SimpleTree Merkle-proof for proving the
`ValidatorsHash` against the `BlockHash`
- `AppHash ([]byte)`: A IAVLTree Merkle-tree root hash of the application state
- `AppHashProof (SimpleProof)`: A SimpleTree Merkle-proof for proving the
`AppHash` against the `BlockHash`
#### IBCPacketTx
IBCPacket 은 다음으로 구성된다:
- `Header (IBCPacketHeader)`: The packet header
- `Payload ([]byte)`: The bytes of the packet payload. _Optional_
- `PayloadHash ([]byte)`: The hash for the bytes of the packet. _Optional_
Payload 또는 PayloadHash 가 반드시 존재해야 한다. IBCPacket 의 해시는 Header 와 Payload 의 단순 머클 루트이다. 전체 페이로드(full payload)가 없는 IBCPacket 은 _생략형 패킷_(_abbreviated packet_)이라고 부른다.
IBCPacketHeader 는 다음으로 구성된다.
- `SrcChainID (string)`: The source blockchain ID
- `DstChainID (string)`: The destination blockchain ID
- `Number (int)`: A unique number for all packets
- `Status (enum)`: Can be one of `AckPending`, `AckSent`, `AckReceived`,
`NoAck`, or `Timeout`
- `Type (string)`: The types are application-dependent. Cosmos reserves the
"coin" packet type
- `MaxHeight (int)`: If status is not `NoAckWanted` or `AckReceived` by this
height, status becomes `Timeout`. _Optional_
IBCPacketTx 이체는 다음으로 구성된다:
- `FromChainID (string)`: The ID of the blockchain which is providing this
packet; not necessarily the source
- `FromBlockHeight (int)`: The blockchain height in which the following packet
is included (Merkle-ized) in the block-hash of the source chain
- `Packet (IBCPacket)`: A packet of data, whose status may be one of
`AckPending`, `AckSent`, `AckReceived`, `NoAck`, or `Timeout`
- `PacketProof (IAVLProof)`: A IAVLTree Merkle-proof for proving the packet's
hash against the `AppHash` of the source chain at given height
'허브'를 통해 '존 1'에서 '존 2'로 패킷을 송신하기 위한 순서가 {그림 X}에 도시되어 있다. 먼저, IBCPacketTx 가 패킷이 '존 1'의 앱-상태(app-state)에 포함되어 있음을 '허브'에 증명한다. 그 다음에 또 다른 IBCPacketTx 가 패킷이 '허브'의 앱-상태에 포함되어 있음을 '존 2'에 증명한다. 이 절차 동안, IBCPacket 필드들은 동일하다: SrcChainID 는 언제나 'Zone1(존 1)'이고 DstChainID 는 언제나 'Zone2(존 2)'이다.
PacketProof 은 반드시 다음과 같은 올바른 머클 증명 경로를 포함해야 한다:
IBC/<SrcChainID>/<DstChainID>/<Number>
'존 1'이 '허브'를 통해 '존 2'로 패킷을 전송할 경우, 패킷이 '존 1', '허브' 또는 '존 2'의 어디에서 머클화 되든지 상관 없이 IBCPacket 데이터는 동일하다. 변경 가능한 유일한 필드는 전송 추적을 위한 Status(상태)이다.
## 감사의 글 (Acknowledgements)
We thank our friends and peers for assistance in conceptualizing, reviewing, and
providing support for our work with Tendermint and Cosmos.
- [Zaki Manian](https://github.com/zmanian) of
[SkuChain](http://www.skuchain.com/) provided much help in formatting and
wording, especially under the TMSP section
- [Jehan Tremback](https://github.com/jtremback) of Althea and Dustin Byington
for helping with initial iterations
- [Andrew Miller](https://soc1024.com/) of [Honey
Badger](https://eprint.iacr.org/2016/199) for feedback on consensus
- [Greg Slepak](https://fixingtao.com/) for feedback on consensus and wording
- Also thanks to [Bill Gleim](https://github.com/gleim) and [Seunghwan
Han](http://www.seunghwanhan.com) for various contributions.
- **Your name and organization here for your contribution**
## 인용 (Citations)
- [1] Bitcoin: <https://bitcoin.org/bitcoin.pdf>
- [2] ZeroCash: <http://zerocash-project.org/paper>
- [3] Ethereum: <https://github.com/ethereum/wiki/wiki/White-Paper>
- [4] TheDAO: <https://download.slock.it/public/DAO/WhitePaper.pdf>
- [5] Segregated Witness: <https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki>
- [6] BitcoinNG: <https://arxiv.org/pdf/1510.02037v2.pdf>
- [7] Lightning Network: <https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf>
- [8] Tendermint: <https://github.com/tendermint/tendermint/wiki>
- [9] FLP Impossibility: <https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf>
- [10] Slasher: <https://blog.ethereum.org/2014/01/15/slasher-a-punitive-proof-of-stake-algorithm/>
- [11] PBFT: <http://pmg.csail.mit.edu/papers/osdi99.pdf>
- [12] BitShares: <https://bitshares.org/technology/delegated-proof-of-stake-consensus/>
- [13] Stellar: <https://www.stellar.org/papers/stellar-consensus-protocol.pdf>
- [14] Interledger: <https://interledger.org/rfcs/0001-interledger-architecture/>
- [15] Sidechains: <https://blockstream.com/sidechains.pdf>
- [16] Casper: <https://blog.ethereum.org/2015/08/01/introducing-casper-friendly-ghost/>
- [17] TMSP: <https://github.com/tendermint/abci>
- [18] Ethereum Sharding: <https://github.com/ethereum/EIPs/issues/53>
- [19] LibSwift: <http://www.ds.ewi.tudelft.nl/fileadmin/pds/papers/PerformanceAnalysisOfLibswift.pdf>
- [20] DLS: <http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf>
- [21] Thin Client Security: <https://en.bitcoin.it/wiki/Thin_Client_Security>
- [22] Ethereum 2.0 Mauve Paper: <https://cdn.hackaday.io/files/10879465447136/Mauve%20Paper%20Vitalik.pdf>
#### 기타 링크 (Unsorted links)
- [https://www.docdroid.net/ec7xGzs/314477721-ethereum-platform-review-opportunities-and-challenges-for-private-and-consortium-blockchains.pdf]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,726 @@
# Cosmos
分布式账本网络
Jae Kwon <mailto:jae@tendermint.com><br>
Ethan Buchman <mailto:ethan@tendermint.com>
加入我们的 [Matrix](https://riot.im/app/#/room/#cosmos:matrix.org)一起讨论吧!
_注意我们会对内容进行定期更新您可以随时进行查阅谢谢_
\[[toc]]
## 介绍
开源的生态系统、去中心化的文件共享、以及公共的加密货币,这一系列技术的成功让人们开始了解到,去中心化互联网协议是可以用来彻底改善社会经济基础架构的。我们见证了专业区块链应用的诞生,比如比特币 [\[1\]](1)加密货币Zerocash [\[2\]](2)(私有加密货币),也看到了大众化智能合约平台,比如以太坊 [\[3\]](3),此外还有其他无数针对 EVM以太坊虚拟机的分布式应用如 Augur预测市场以及 The DAO [\[4\]](4)(投资俱乐部)。
但是到目前为止这些区块链已经暴露了各种缺陷包括总能量低效、功能不佳或受限、并且缺乏成熟的管理机制。为了扩大比特币交易吞吐量已经研发了许多诸如隔离见证Segregated-Witness [\[5\]](5)和 BitcoinNG [\[6\]](6)这样的解决方案,但是这些垂直扩展方案都因单一物理机容量而受到限制,不然就得损害其可审核性这一特性。闪电网络 [\[7\]](7)可以通过让部分交易完全记录在账本外,来帮助扩大比特币交易额,这个方法非常适合微支付以及隐私保护支付轨道,但是可能无法满足更广泛的扩展需求。
理想的解决方案是在允许多个平行区块链互相操作的同时,保留安全特性。不过事实证明,采用工作量证明很难做到这一点,但也并非不可能。例如合并挖矿可以在完成工作的同时,让母链得以在子链上重复使用。不过这样还是需要通过每个节点,依次对交易进行验证,而且如果母链上大多数哈希力没有积极地对子链进行合并挖矿,那么就很容易遭到攻击。关于 [可替代区块链网络架构的学术回顾](http://vukolic.com/iNetSec_2015.pdf)将在辅助材料中呈现,我们会在 [相关作品](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#related-work)中对更多提议及其缺点进行概括。
这里我们要介绍的是 Cosmos这是一个全新区块链网络架构能够解决所有问题。Cosmos 是一个涵盖众多独立区块链的网络,叫做"空间"。空间在 Tendermint Core [\[8\]](8)支持下运行是一个类似实用拜占庭容错的安全共识引擎兼具高性能、一致性等特点而且在其严格的分叉责任制保证下能够防止怀有恶意的参与者做出不当操作。Tendermint Core 的拜占庭容错共识算法,非常适合用来扩展权益证明机制下的公共区块链。
Cosmos 上的第一个空间叫做"Cosmos Hub"Cosmos 中心。Cosmos 中心是一种多资产权益证明加密货币网络它通过简单的管理机制来实现网络的改动与更新。此外Cosmos 中心还可以通过连接其他空间来实现扩展。
Cosmos 网络的中心及各个空间可以通过区块链间通信IBC协议进行沟通这种协议就是针对区块链的虚拟用户数据报协议UDP或者传输控制协议TCP。代币可以安全快速地从一个空间传递到另一个空间两者之间无需体现汇兑流动性。相反空间内部所有代币的转移都会通过 Cosmos 中心,它会记录每个空间所持有的代币总量。这个中心会将每个空间与其他故障空间隔离开。因为每个人都将新空间连接到 Cosmos 中心,所以空间今后也可以兼容新的区块链技术。
## Tendermint
这一部分将对 Tendermint 共识协议及其用来创建应用程序的界面进行介绍。更多细节,详见 [附录](#appendix)。
### 验证人
在经典拜占庭容错BFT算法中每个节点都同样重要。在 Tendermint 网络里,节点的投票权不能为负,而拥有投票权的节点被称作"验证人"。验证人通过传播加密签名或选票,来参与共识协议并商定下一区块。
验证人的投票权是一开始就确定好的,或者根据应用程序由区块链来决定是否有改变。比如,在 Cosmos 中心这种权益证明类应用程序中,投票权可能就是通过绑定为保证金的代币数量来确定的。
_注意像 ⅔ 和 ⅓ 这样的分数指的是占总投票权的分数,而不是总验证人,除非所有验证人拥有相同币种。而+⅔ 的意思是"超过 ⅔ ",⅓+则是"⅓ 或者更多"的意思。_
### 共识
Tendermint 是部分同步运作的拜占庭容错共识协议,这种协议源自 DLS 共识算法 [\[20\]](20)。Tendermint 的特点就在于其简易性、高性能以及分叉责任制。协议要求有固定且熟知的一组验证人,其中每个验证人通过公钥进行身份验证。这些验证人会尝试在某个区块上同时达成共识(这里的区块是指一份交易列表)。每个区块的共识轮流进行,每一轮都会有个领头人,或者提议人,由他们来发起区块。之后验证人分阶段对是否接受该区块,或者是否进入下一轮做出投票。每轮的提议人会从验证人顺序列表中按照其选票比例来选择确定。
该协议全部细节请参考 [此处](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)。
Tendermint 采用由绝对多数的选票(+⅔)选定的最优拜占庭容错算法,以及一套锁定机制来确保安全性。对此他们保证:
- 想要违背安全必须有超过 ⅓ 的选票出现拜占庭问题,并且提交超过两个值。
- 如果有任何验证组引起了安全问题,或者说是企图这么做,那么就会被协议发现,一方面针对有冲突的区块进行投票,同时广播那些有问题的选票。
除了其超强安全保障外Tendermint 还具备其他功效。以商品型云平台为例Tendermint 共识以分布在五大洲七个数据中心的 64 位节点为基准,其每秒可以处理成千上万笔交易,提交顺序延迟时间为 1-2 秒。而值得关注的是,即使是在极其恶劣的敌对环境中,比如验证人崩溃了或者是遇到蓄谋已久的恶意选票,也能维持这种每秒千笔交易的高绩效。详见下图。
![Figure of Tendermint throughput performance](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/tendermint_throughput_blocksize.png)
### 轻客户端
Tendermint 共识算法的主要好处就是它具有安全简易的轻客戸端,这一点使其成为手机和物联网用例的理想工具。比特币轻客户端必须同步运行区块头组成的链,并且找到工作量证明最多的那一条,而 Tendermint 轻客戸端只需和验证组的变化保持一致,然后简单地验证最新区块中预先提交的+⅔,来确定最新情况。
这种简单的轻客戸端证明机制也可以实现 [区块链之间的通信](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc)。
### 防止攻击
Tendermint 有各种各样的防御措施来防止攻击,比如 [远程无利害关系双重花费](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#preventing-long-range-attacks)及 [审查制度](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#overcoming-forks-and-censorship-attacks)。这个在 [附录](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#appendix)中会进行完整讨论。
### TMSP
Tendermint 共识算法是在叫做 Tendermint Core 的程序中实现的。这个程序是一种与应用程序无关的"共识引擎",可以让任何命中注定的黑匣子软件变为分散复制的区块链。就像 Apache 网页服务器或者 Nginx 是通过通用网关接口CGI或快速通用网关接口FastCGI来连接 Wordpress一款博客系统应用程序一样Tendermint Core 通过 Tendermint Socket 协议TMSP来连接区块链应用程序。因此TMSP 允许区块链应用程序用任何语言进行编程而不仅仅是共识引擎写入的程序语言。此外TMSP 也让交换任何现有区块链堆栈的共识层成为可能。
我们将其与知名加密货币比特币进行了类比。在比特币这种加密币区块链中,每个节点都维持着完整的审核过的 UTXO未使用交易输出数据库。如果您想要在 TMSP 基础上,创建出类似比特币的系统,那么 Tendermint Core 可以做到:
- 在节点间共享区块及交易
- 创建规范或不可改变的交易顺序(区块链)
同时TMSP 应用程序会负责:
- 维护 UTXO 数据库
- 验证交易的加密签名
- 防止出现不存在的交易花费
- 允许客户访问 UTXO 数据库
Tendermint 能够通过为应用程序与共识的形成过程提供简单的应用程序界面API来分解区块设计。
## Cosmos 概述
Cosmos 是一种独立平行的区块链网络,其中每条区块链通过 Tendermint [1](https://github.com/tendermint/tendermint)这样的经典拜占庭容错共识算法来运行。
网络中第一条区块链将会是 Cosmos 中心。Cosmos 中心通过全新区块链间通信协议来连接其他众多区块链(或将其称之为空间)。中心可以追踪无数代币种类,并且在各个连接的空间里记录代币总数。代币可以安全快速地从一个空间传递到另一个空间,两者之间无需体现汇兑流动性,因为所有空间之间的代币传输都会经过 Cosmos 中心。
这一架构解决了当今区块链领域面临的许多问题,包括应用程序互操作性、可扩展性、以及无缝更新性。比如,从 Bitcoind、Go-Ethereum、CryptoNote、ZCash 或其他区块链系统中衍生出来的空间,都可以接入 Cosmos 中心。这些空间允许 Cosmos 实现无限扩展,从而满足全球交易的需求。此外,空间也完全适用于分布式交易所,反之交易所也支持空间运行。
Cosmos 不仅仅是单一的分布式账本,而 Cosmos 中心也不是封闭式花园或宇宙中心。我们正在为分布式账本的开放网络设计一套协议,这套协议会按照加密学、稳健经济学、共识理论、透明性及可追究制的原则,成为未来金融系统的全新基础。
### Tendermint 拜占庭容错股份授权证明机制(Tendermint-BFT DPoS)
Cosmos 中心是 Cosmos 网络中第一个公共区块链,通过 Tendermint 拜占庭共识算法运行。这个 Tendermint 开源项目于 2014 年开始旨在解决比特币工作量证明算法的速度、可扩展性以及环境问题。通过采用并提高已经过验证的拜占庭算法1988 年在麻省理工学院开发Tendermint 成为了首个在概念上演示加密货币权益证明的团队,这种机制可以解决 NXT 和 BitShares 这些第一代权益证明加密币面临的"无利害关系"nothing-at-stake的问题。
如今,实际上所有比特币移动钱包都要使用可靠的服务器来进行交易验证。这是因为工作量证明机制需要在交易被认定为无法逆转前进行多次确认。而在 CoinBase 之类的服务中也已经出现重复花费攻击。
和其他区块链共识系统不同Tendermint 提供的是即时、可证明安全的移动客户端支付验证方式。因为 Tendermint 的设计完全不支持分叉,所以移动钱包就可以实时接收交易确认,从而在智能手机上真正实现去信任的支付方式。这一点也大大影响了物联网应用程序。
Cosmos 中的验证人(其扮演的角色类似比特币矿工,但是与之不同的是,他们采用加密签名来进行投票)必须是专门用来提交区块的安全机器。非验证人可以将权益代币(也叫做"atom")委托给任何验证人来赚取一定的区块费用以及 atom 奖励但是如果验证人被黑客攻击或者违反协议规定那么就会面临被惩罚削减的风险。Tendermint 拜占庭共识的可证明安全机制,以及利益相关方(验证人和委托人)的抵押品保证,为节点甚至是轻客户端提供了可证明、可计量的安全性。
### 管理
分布式公共账本应该要有一套章程与管理体系。比特币依靠比特币基金会(在一定程度上)及挖矿来协调更新,但是这个过程很缓慢。以太坊在采用硬分叉措施解决 The DAO 黑客事件后,分裂成了 ETH 和 ETC这主要是因为之前设定社会契约或机制来进行这类决定。
Cosmos 中心的验证人与委托人可以对提案进行投票,从而自动改变预先设置好的系统参数(比如区块容量限制),协调更新,并对人们看得懂的章程进行修订投票,从而管理 Cosmos 中心。这个章程允许权益相关者聚集到一起,来解决盗窃及漏洞等相关问题(比如 The DAO 事件),并快速得出明确的解决方案。
每个空间也具备自己的一套章程及管理机制。比如Cosmos 中心的章程会强制实现中心的不可改变性(不能重新执行,除了 Cosmos 中心节点实现的漏洞),而每个空间则可自行设置与盗窃及漏洞相关的重新执行政策。
Cosmos 网络能够在政策不同的区块间实现互操作性,这一点可以让客户在无需许可的环境下进行实验,为客户带去了终极自由及潜力。
## 中心与空间
这里我们将描述一个全新的去中心化与可扩展性模型。Cosmos 网络通过 Tendermint 机制来运行众多区块链。虽然现存提案的目标是创建一个包含全球所有交易顺序的"单一区块链"Cosmos 允许众多区块链在相互运行的同时,维持互操作性。
在这个基础上Cosmos 中心负责管理众多独立区块链(称之为"空间",有时也叫做"碎片",根据数据库扩展技术"分片"得出。中心上的空间会源源不断地提交最新区块这一点可以让中心跟上每个空间状态的变化。同样地每个空间也会和中心的状态保持一致不过空间之间不会同彼此的步伐保持一致除非间接通过中心来实现。之后信息包就会从一个空间传递到另一个空间并通过发布梅克尔证明Merkle-proof来说明信息已经被传送或接收。这种机制叫做"区块链间通信",或者简称为"IBC"机制。
![Figure of hub and zones acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/images/hub_and_zones.png)
任何区块都可以自行成为中心,从而形成非循环图,但是有一点需要阐明,那就是我们只会对简单配置(只有一个中心)以及许多没有中心的空间进行描述。
### 中心Hub
Cosmos 中心区块链承载的是多资产分布式账本,其中代币可以由个体用户或空间本身持有。这些代币能够通过特殊的 IBC 包裹,即"代币包"coin packet从一个空间转移到另一个空间。中心负责保持空间中各类代币全球总量不变。IBC 代币宝交易必须由发送人、中心及接收人的区块链执行。
因为 Cosmos 中心在整个系统中扮演着中央代币账本的角色,其安全性极其重要。虽然每个空间可能都是一个 Tendermint 区块链——只需通过 4 个,或者在无需拜占庭容错共识的情况下更少的验证人来保证安全),但是 Cosmos 中心必须通过全球去中心化验证组来保证安全,而且这个验证组要能够承受最严重的攻击,比如大陆网络分割或者由国家发起的攻击。
### 空间Zones
Cosmos 空间是独立的区块链,能够和 Cosmos 中心进行 IBC 信息交换。从 Cosmos 中心的角度看,空间是一种多资产、多签名的动态会员制账户,它可以通过 IBC 包裹进行代币发送与接收。就像加密币账户一样,空间不能转移超出其持有量的代币,不过可以从其他拥有代币的人那里接收代币。空间可能会被指定为一种或多种代币的"来源",从而赋予其增加代币供应量的权力。
Cosmos 中心的 Atom 或可作为空间(连接到中心)验证人的筹码。虽然在 Tendermint 分叉责任制下,空间出现重复花费攻击会导致 atom 数量减少,但是如果空间中有超过 ⅔ 的选票都出现拜占庭问题的话那这个空间就可以提交无效状态。Cosmos 中心不会验证或执行提交到其他空间的交易,因此将代币传送到可靠空间就是用户的责任了。未来 Cosmos 中心的管理系统可能会通过改善提案,来解决空间故障问题。比如,在检测到袭击时,可以将有些空间(或全部空间)发起的代币转移输出压制下来,实现紧急断路(即暂时中止代币转移)。
## 区块链间通信IBC
现在我们来介绍下中心与空间之前通信的方法。假如现在有三个区块链,分别是"空间 1"、"空间 2"以及"中心",我们想要"空间 1"生成一个包裹,通过"中心"发送给"空间 2"。为了让包裹从一个区块链转移到另一个区块链,需要在接收方区块链上发布一个证明,来明确发送方已经发起了一个包裹到指定地点。接收方要验证的这个证明,必须和发送方区块头保持一致。这种机制就类似与侧链采用的机制,它需要两个相互作用的链,通过双向传送存在证明数据元(交易),来"知晓"另一方的情况。
IBC 协议可以自然定义为两种交易的使用:一种是 IBCBlockCommitTx 交易,这种交易可以让区块链向任何观察员证明其最新区块哈希值;另一种是 IBCPacketTx 交易这种交易则可以证明某个包裹确实由发送者的应用程序通过梅克尔证明机制Merkle-proof传送到了最新区块的哈希值上。
通过将 IBC 机制分裂成两个单独的交易,即 IBCBlockCommitTx 交易与 IBCPacketTx 交易,我们可以让接收链的本地费用市场机制,来决定承认哪个包裹,与此同时还能确保发送方的完全自由,让其自行决定能够传出的包裹数量。
![Figure of Zone1, Zone2, and Hub IBC without acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_without_ack.png)
在上述案例中,为了更新"中心"上"空间 1"的区块哈希(或者说"空间 2"上"中心"的区块哈希),必须将 IBCBlockCommitTx 交易的"空间 1"区块哈希值发布到"中心"上(或者将该交易的"中心"区块哈希值发布到"空间 2"中)。
_更多关于两种 IBC 交易的信息请参考_ [_IBCBlockCommitTx_](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#ibcblockcommittx)_ 以及 _ [_IBCPacketTx_](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#ibcpacketcommit)_。_
## 用例
### 分布式交易所
比特币借助批量复制的分布式账本技术来保证安全,同样的,我们也可以用这种方式,在区块链上运行,从而降低交易所受内外部攻击的可能性。我们称之为分布式交易所。
如今,加密币社区认为去中心化交易所是基于"原子交叉链"交易AXC 交易)的交易所。通过这类交易,不同链上的两位用户可以发起两笔传输交易,要么在两个账本上一起执行,要么两个账本都不执行(即原子级)。比如,两位用户可以通过 AXC 交易来进行比特币和以太币之间的交易(或不同账本上的任意两种代币),即使比特币和以太坊之间并没有相互连接。在 AXC 交易模式下的交易所,其好处在于用户双方都不需要相信彼此,也不用相信交易匹配服务。其坏处就是,双方都得在线才能进行交易。
另一种去中心化交易所是在交易所的区块链上运行批量复制的分布式账本。这种交易所的用户可以提交一份限价订单,在关机状态下执行交易。区块链会代表交易者匹配并完成交易。
去中心化交易所可以创建一份大范围限价订单簿,以此来吸引其他交易者。在交易所界,流动性需求越来越高,因此交易所业务界的网络效应也愈发强烈(或者说至少产生了"胜者得益"效应)。目前加密币交易所排名第一的是 Poloniex其 24 小时交易额为 2000 万美元,而 Bitfinex 以 24 小时 500 万位列第二。在这种强大的网络效应背景下,基于 AXC 的去中心化交易所的交易额是不可能超过中心化交易所的。去中心化交易所要想和中心化交易所一争高下,那么就需要支持大范围限价订单簿的运行。而只有基于区块链的去中心化交易所可以实现这一点。
Tendermint 的快速交易执行是另一大优势。Cosmos 的空间可以在不牺牲一致性的前提下,通过优先完善快速交易,来实现交易的快速完成——针对双向订单交易,及 IBC跨区块链通信代币与其他空间的交易。
根据如今加密币交易所的情况Cosmos 的一项重大应用就是分布式交易所(也就是 Cosmos DEX。其交易吞吐能力及提交延时情况可以和那些中心化交易所媲美。交易者可以在离线的状态下提交限价订单。并且在 TendermintCosmos 中心以及 IBC 的应用下,交易者可以快速地完成资金在交易所及其他空间的转出转入。
### 和其他加密货币挂钩
享有特权的空间可以作为和其他加密货币挂钩的代币来源。这种挂钩类似 Cosmos 中心与空间之间的关系两者都必须及时更新彼此最新的区块链从而验证代币已经从一方转移到另一方的证明。Cosmos 网络上挂钩的空间要和中心以及其他加密货币保持一致。这种间接挂钩的空间可以维持简单的中心逻辑,并且不用了解其他区块链共识战略(比如比特币工作量证明挖矿机制)。
比如,设置有特定验证组的 Cosmos 空间(可能和中心里的验证组一样)可以作为与以太币挂钩的空间,其中基于 Tendermint Socket 协议TMSP的应用即"挂钩空间"里的)有办法和外部以太坊区块链上的(即"起始点")挂钩合约交换 IBC 信息。通过这一合约,持币人可以先将以太币发送到以太坊的挂钩合约中,然后再将以太币传送到挂钩空间。挂钩合约接收到以太币后,除非同时从挂钩空间处接收到正确的 IBC 包裹,否则这些以太币是无法提取的。而当挂钩空间接收到 IBC 包裹,并证明以太币已被特定以太坊账户的挂钩合约接收后,挂钩空间就会生成存有余额的相关账户。之后,挂钩空间上的以太币(即"已挂钩的以太币"就可以转进或转出中心了完成传送到特定以太坊提取地址的交易后再彻底删除。IBC 包裹可以证明挂钩空间上的交易,这个包裹可以公布到以太坊挂钩合约中,来开放以太币的提取权。
当然,这类挂钩合约也存在风险,比如会出现恶劣的验证组。如果拜占庭投票权超过 ⅓,就会造成分叉,即从以太坊挂钩合约中提取以太币的同时,还能保持挂钩空间中的挂钩以太币不变。更有甚者,如果拜占庭投票权超过 ⅔,可能会有人直接对将以太币发送到挂钩合约中(通过脱离原始挂钩空间的挂钩逻辑)的人下手,盗取他们的以太币。
如果将这个挂钩方法完全设计成责任制,那么就有可能解决这一问题。比如,中心及起始点的全部 IBC 包裹可能需要先通过挂钩空间的认可,即让中心或起始点中的钩挂合约对挂钩空间的所有状态转变进行有效验证。中心及起始点要允许挂钩空间的验证人提供抵押品,而挂钩合约的代币转出需要有所延迟(并且抵押品解绑时间也要足够长),从而让单独的审计人有时间发起挑战。我们会以未来 Cosmos 改善提议的形式公开这一系统的设计说明及实现方式,以待 Cosmos 中心的管理系统审批通过。
虽然现在的社会政治环境还不够成熟,不过我们可以做一些延伸,比如让负责国家国币的一些机构(尤其是其银行)组成一个验证组,来实现空间同国家法定货币的挂钩。当然这必须布置好额外的预防措施,只接受法律系统下的货币,从而加强可靠的公证人或大型机构对银行活动的审计。
这一整合或可让空间中所有拥有银行账户的人,将自己银行账户里的美元传输到空间账户中,或者完整的转入中心或其他空间里。
这么看来Cosmos 中心就是法定货币和加密货币无缝对接的导管,从而解决困扰交易所至今的交互局限问题。
### 以太坊的扩展
扩展问题一直是以太坊的一个公开问题。目前以太坊节点会处理每笔交易,并且存储所有状态。
因为 Tendermint 提交区块的速度比以太坊工作量证明要快,所以由 Tendermint 共识推动且用于挂钩以太币运行的 EVM以太坊虚拟机空间能够强以太坊区块链的性能。此外虽然 Cosmos 中心及 IBC 包裹技术不能实现每秒合约逻辑的任意执行,但是它可以用来协调不同空间里以太坊合约间的代币变动,通过碎片化方式为以代币为中心的以太坊奠定基础。
### 多应用一体化
Cosmos 空间可以运行任意应用逻辑,这一点在空间运转初期就已经设定好,通过管理可以不断更新。这种灵活度让 Cosmos 空间得以成为其他加密货币的挂钩载体比如以太坊或比特币并且它还能和这些区块链的衍生品挂钩使用同样的代码库但是验证组及初始分配有所不同。这样一来就可以运行多种现有加密币框架比如以太坊、Zerocash、比特币、CryptoNote 等等,将其同 Tendermint Core 结合,成为通用网络中性能更优的共识引擎,为平台提供更多的交互机遇。此外,作为多资产区块链,每笔交易都有可能包含多个输入输出项,其中每个输入项都可以是任意代币,使 Cosmos 直接成为去中心化交易所,当然这里假设的是订单通过其他平台进行匹配。还有一种替代方案,即让空间作为分布式容错交易所(包含订单簿),这可以算是对现有中心化加密币交易所的严格改进——现有交易所时不时会受到攻击。
空间也可以作为区块链版的企业及政府系统,其原本由一个或多个组织运行的特定服务,现在作为 TMSP 应用在某个空间上运行,从而在不放弃对底层服务控制的前提下,维持公共 Cosmos 网络的安全性及交互性。所以Cosmos 或可为那些既想使用区块链技术,又不愿将控制权彻底放给分布式第三方的人,提供最佳的运行环境。
### 缓解网络分区问题
有人认为像 Tendermint 这种支持一致性的共识算法有一个重大问题,那就是网络分割会导致没有一个分区拥有超过 ⅔ 的投票权(比如超过 ⅓ 在线下),而任何这类网络分割都将中止整个共识。而 Cosmos 架构可以缓解这个问题,它可以使用全球中心,但是空间实行地区自治,然后让每个空间的投票权按照正常的地理位置进行分布。比如,某个一般范例就有可能是针对个别城市或地区的,让他们在运行自己空间的同时,还能共享共同的中心(比如 Cosmos 中心),并且可以在因网络分区导致的中断期间,继续维持地区自治活动。请注意,这样一来在设计稳健的联邦式容错系统过程中,就可以真正地去考虑地理、政治及网络拓扑的特征了。
### 联邦式名称解析系统
NameCoin 是首批试图通过比特币区块链解决名称解析问题的区块链之一。不幸的是,这个方案存在一些不足。
比如,我们可以通过 Namecoin 来验证*@satoshi*(中本聪)这个号是在过去某个时间点用特定公钥进行注册的。但是,该公约是否更新过我们就不得而知了,除非将该名称最后一次更新以来的全部区块都下载下来。这一点是因为比特币 UTXO 交易模式中梅克尔式模型的局限性所导致的,这类模型中只有交易(而非可变的应用程序状态)会以梅克尔形式加入到区块哈希中。它会让我们证明之后名称更新的存在,而非不存在。因此,我们必须依靠完整节点才能明确这个名称的最近价值,否则就要投入巨大成本来下载整个区块链。
即使在 NameCoin 运用了默克尔化的搜索树,其工作量证明的独立性还是会导致轻客戸端的验证出现问题。轻客戸端必须下载区块链中所有区块头的完整复件(或者至少是自其最后的名称更新后的所有区块头)。这意味着带宽需要会随着时间直线扩展。 [\[21\]](21)此外,在工作量证明制区块链上的名称更改需要等额外的工作量证明验证区块才能进行,这个在比特币上可能要花上一个小时。
有了 Tendermint我们只需用到由法定数量验证人签署通过投票权的区块哈希以及与名称相关的当前价值的默克尔证明。这点让简易、快速、安全的轻客戸端名称价值验证成为可能。
在 Cosmos 中我们可以借助这个概念对其进行延伸。Cosmos 中的每个名称注册空间都能有一个相关的最高级别域名TLD比如".com"或者".org"等,每个名称注册空间都有其本身的管理和登记规则。
## 发行与激励
### Atom 代币
Cosmos HubCosmos 中心)是多资产分布式账本,不过它也有本地代币,叫做 Atom。Atom 是 Cosmos Hub 唯一的权益代币。Atom 是持有人投票、验证或委托给其他验证人的许可证就像以太坊的以太币以太币一样Atom 也可以用来支付交易费以减少电子垃圾。额外的通胀 Atom 和区块交易费用就作为验证人及委托人(委托给其他验证人)的奖励。
BurnAtomTx 交易可以用来恢复储蓄池中任意比例的代币。
#### 众筹
创世块上的 Atom 代币及验证人的初次分布会是 Cosmos 众销资助人占 75%,预售资助人 5%Cosmos 公司占 20%。从创世块开始,总 Atom 总量的 1/3 将作为奖励发放给每年绑定的验证人以及委托人。
额外细节详见 [Crowdfund Plan](https://github.com/cosmos/cosmos/blob/master/PLAN.md)。
#### 归属
为了防止那些炒股诈骗的投机者借众筹来进行短期牟利,创世块的 Atom 必须有所归属才能用于转移。每个账户将在为期两年的时间里以每小时恒速授予 Atom这个速率由创世块 Atom 总量除以(2 \* 365 \* 24)小时得出。通胀区块获得的 Atom 奖励是预先授予的,可以立即进行转移,因此第一年绑定的验证人及委托人可以挣取比其创世块 Atom 一半还多的奖励。
### 验证人的数量上限
Tendermint 区块链和比特币之类的工作量证明区块链不同,由于通信复杂度提升,验证人增加,所以速度会更慢。所幸的是,我们可以支持足够多的验证人来实现全球稳健的分布式区块链,使其拥有较短交易验证时间,此外,在提升带宽、内存以及平行电脑计算能力的提升下,在未来支持更多验证人的参与。
在创世块诞生那天,验证人数量最多将设置为 100之后十年的增长率将在 13%,最终达到 300 位验证人。
Year 0: 100
Year 1: 113
Year 2: 127
Year 3: 144
Year 4: 163
Year 5: 184
Year 6: 208
Year 7: 235
Year 8: 265
Year 9: 300
Year 10: 300
...
### 成为创世日后首个验证人
如果 Atom 持有人还没有成为验证人,那么可以通过签署提交 BondTx 交易来成为验证人,其中作为抵押品的 Atom 数量不能为零。任何人在任何时候都可以作为验证人,除非当前验证组的数量超过了最大值。这样的话,除非 Atom 数量比最小验证人持有的有效 Atom包括受委托的 Atom还要多那么交易才算有效。如果新验证人通过这种方式取代了现有验证人那么现有验证人就被中止活动所有 Atom 和受委托的 Atom 都会进入解绑状态。
### 针对验证人的惩罚
针对验证人必须有一定的惩罚机制,防止他们有意无意地偏离已批准的协议。有些证据可以立即采纳,比如在同样高度和回合的双重签名,或者违反"预投票锁定"的(这一规则在 Tendermint 共识协议中有列出)。这类证据将导致验证人损失良好信誉,而且其绑定的 Atom 还有储备池内一定比例的代币份额——合起来称作其"权益"——也会减少。
有时因为地区网络中断、电力故障或者其他原因,验证人会无法连通。如果在过去随便什么时间点的 ValidatorTimeoutWindow 区块中,验证人在区块链中提交的投票没有超过 ValidatorTimeoutMaxAbsent 次那么验证人将会被中止活动并且从权益中共损失一定的验证人超时罚款ValidatorTimeoutPenalty ,默认为 1%)。有些劣行表露的没那么明显,这样的话,验证人就可以在带外协调,强制叫停这类恶意验证人,如果有绝对多数制共识的话。
如果 Cosmos 中心因为超过 ⅓ 的投票权在线下合并而出现了中止情况,或者说超过 ⅓ 的投票权合并来审查进入区块链的恶意行为,这时候中心就必须借助硬分叉重组协议来恢复。(详见"分叉与审查攻击"
### 交易费用
Cosmos Hub 验证人可以接受任何中共类的代币或组合作为处理交易的费用。每个验证人可以主观设置任意兑换率,并且选择它想要进行的交易,只要没有超过区块 Gas 限制BlockGasLimit。收集起来的费用剪去下面列出的任意税费后会再次根据权益相关人绑定的 Atom 比例进行分配周期是就是每次验证人支付的时间ValidatorPayoutPeriod默认为 1 小时)。
在所有交易费用中储存税ReserveTax默认为 2%)将存入储备池来增加储备量,来提高 Cosmos 网络的安全性及价值。普通税CommonsTax默认为 3%合并到普通商品的资金中。这些资金将进入托管人地址CustodianAddress根据管理熊进行分配。将投票权委托给其他验证人的 Atom 持有人会支付一定佣金给委托方,而这笔费用可以由每个验证人进行设置。
### 激励黑客
Cosmos Hub 的安全是一组函数涉及底层验证人的安全以及委托人的委托选择。为了鼓励发现并及时报告缺陷Cosmos Hub 允许黑客通过 ReportHackTx 交易来"邀功",主要就是说明,"这个加点已被攻击,请将奖金发到这个地址"。通过这类功绩,验证人和委托人的行为将被中止,而黑客赏金地址可以收到每个人 Atom 中攻击奖励比率HackRewardRatio默认为 5%)。而验证人必须通过使用备份密钥来恢复剩余的 Atom。
为了防止这个特征被滥用于转移未授权的 AtomReportHackTx黑客报告交易前后验证人和委托人手中的两类 Atom 的比例(授权的与未授权的)将保持不变,而黑客的赏金将包含未授权的 Atom如果有的话。
## 管理
Cosmos Hub 通过分布式组织来运行,这类组织要求有一套完备的管理机制,从而协调区块链上的各类变动,比如系统变量参数,以及软件更新、规章更改等。
所有验证人对所有提案的投票负责。如果没能及时对提案做出投票那么验证人就会在一段时间内自动失去活动权利这段时间叫做缺席惩罚期AbsenteeismPenaltyPeriod默认为一周
委托人自动继承委托验证人的投票权。这一投票可能会被手动覆盖掉。而未绑定的 Atom 是没有投票权的。
每个提案都需要一定的保证金即最低提案保证金MinimumProposalDeposit )代币,这个可以是代币组合也可以是更多代币包括 Atom。对每一个提案投票人可能会投票来取走保证金呢。如果超过一半的投票人选择取走保证金比如由于提案是垃圾信息之类那么保证金就会进去储备池除非有任何 Atom 被燃烧。
对于每一个提案,投票人可能会投以下选项:
- 同意
- 强烈同意
- 反对
- 强烈反对
- 弃权
决定采纳(或不采纳)提案需要严格的多数投"同意"或"强烈同意"(或者"反对"及"强烈反对"),但是超过 1/3 的人投"强烈反对"或"强烈支持"的话就可以否决大多数人的决定。如果大多数人的票都被否决,那么每个人都会得到惩罚,即损失否决惩罚费用块那一部分钱( VetoPenaltyFeeBlocks默认是一天的区块值 ,税费除外),而否决大多数决定的那一方也会受到额外的惩罚,即损失否决惩罚 AtomVetoPenaltyAtoms默认为 0.1%)。
### 参数改变提案
这里定义的任何参数都可以发生改变主要在参数改变提案ParameterChangeProposal的接受范围内。
### 文本提案
所有其他提案比如用来更新协议的提案都会通过通用的文本提案TextProposal来协调。
## 路线图
详见 [计划](https://github.com/cosmos/cosmos/blob/master/PLAN.md)。
## 相关工作
过去几年,关于区块链共识及可扩展性已经有过多次创新,这一部分将挑选一些重要的创新进行简短分析。
### 共识系统
#### 经典拜占庭容错
二十世纪八十年代早期的共识机制中就已经出现了恶意参与者,当时 Leslie Lamport 杜撰了"拜占庭容错"这个词,用来指那些图谋不轨参与者做出的恣意妄为的行径,这个词和"死机故障"相对死机故障就只是处理过程崩溃而已。早期针对同步网络也探索出了一些解决方案其信息滞后有一个上限尽管实际使用是在高度受控的环境下进行的比如飞机控制器以及原子钟同步的数据中心。直到九十年代后期实用拜占庭容错PBFT才被引用作为有效的、部分同步的共识算法以容忍处理过程中 ⅓ 的恣意行为。PBFT 成为标准算法,繁殖了各种衍生品,包括最近 IBM 用于超级账本中的。
和 PBFT 相比Tendermint 共识的主要好处在于其有一套经过改善且简化了的底层结构其中有些是拥抱区块链范例的结果。Tendermint 区块必须按顺序提交,这一点可以消除复杂性,节省与 PBFT 浏览变化相关的通信开支。在 Cosmos 和众多加密币中,如果区块 N 本身没有提交,那么就无需让区块 N+ii*>=1*)来提交。如果是带宽导致了区块 N 未提交到 Cosmos 空间,那么它就不会帮助使用带宽将选票共享给区块 N+i。如果是网络分区或者线下节点导致的区块 N 未提交,那么 N+i 就无论如何也不会提交。
此外,区块内交易的批量处理可以对应用程序的状态进行默克尔哈希,而不是用 PBFT 检查机制进行周期消化。这可以实现轻客戸端更快的证明交易提交,以及更快的跨区块链通信。
Tendermint Core 中有很多优化项和特点都超过了 PBFT 特定的性能。比如,验证人发起的区块被分割成部分,默克尔化然后散布开来,这种方式可以提高其广播性能(关于启发请查看 LibSwift [\[19\]](19)。而且Tendermint Core 不会对点对点连接做任何假设,只要点对点网络仍有微小连接,那么它就能正常运行。
#### BitShare 委托权益
BitShares [\[12\]](12)不是第一个部署权益证明机制PoS的区块链但是其对 PoS 区块链的研究与采纳做出了巨大的贡献,尤其是这些被认为是"受委托的" PoS。在 BitShares 中,股权持有者选择"见证"以及"委托",其中"见证"负责下单并提交交易,而"委托"负责协调软件更新与参数变化。尽管 BitShare 在理想环境下的性能很高100k tx/s1 秒的滞后),但是它也有可能受到恶意见证施加的重复使用攻击,这类攻击会导致区块链出现分叉而不用受到任何经济惩罚——它的惩罚来自于"没有任何权益"。BitShare 试图通过允许交易查看近期区块哈希来缓解这个问题。此外,权益相关者可以每天移除或替代行为不佳的见证,尽管这个对成功的重复使用攻击来说根本不算什么明确的惩罚。
#### Stellar
Stellar [\[13\]](13)是在 Ripple 的解决方案上创建的,它精炼了联邦拜占庭协议模型,其中参与共识的进程不包括固定且举世闻名的组件。相反,每个处理节点管理一个或多个"仲裁集碎片"每一个都组成一组可靠的进程。Stellar 中的"仲裁集"被定义为一组节点,其中每一节点包含(是一个超集合)至少一个仲裁集碎片,这样就可以达成协议。
机制的安全性依靠假设实现,即假设任意两个仲裁集的交集并非为空,而实现节点的可用性则需要至少一个仲裁集碎片来完整组成正确节点,这个在使用或大或小的仲裁集碎片间可能会产生一组权衡,因为要在不对信任施加重要假设的情况下来进行平衡是很困难的。最终,节点必须以某种办法来选择充足的仲裁集碎片,从而保证有足够多的容错(或任何"完整无损的节点",大部分文章里得出的结果就依靠这个来决定)。此外,唯一用来确保这类配置的战略是等级制的,且和 BGP 协议相似(边界网关协议),可用于互联网高层 ISP 来创建全球路由表,或者是用在浏览器中管理 TSL传输层安全证书不过这两者都因其不安全性而臭名昭著。
Stellar 文章中对基于 Tendermint 的权益证明系统的批判可以通过以下代币战略加以缓和,其中有一种新的代币叫做"atom"可以通过发布这一代币来代表未来的费用及奖励。所以Tendermint 权益证明机制的好处就是其简易性,在相对简易的同时,提供足够多且可证明的安全保障。
#### BitcoinNG
BitcoinNG 是针对比特币提出来的一种改善,或将允许垂直扩展形式,比如增加区块容量,并且不会带来负面经济影响(一般这类变动都会带来这类影响),比如越小的矿工受到的影响越大。这一改善可以通过将是首项选择从交易广播中分离来实现,即由"微区块"中的工作量证明先选择群首,之后可以对要提交的交易进行广播,直到新的微区块被发现。这个过程会减少赢得工作量证明比赛所必须的带宽需求,让小矿工可以更公平的参与竞争,然后让最后找到微区块的矿工来定期提交交易。
#### Casper
Casper [\[16\]](16)是针对以太坊提出的一个权益证明共识算法。其最初运行模式是"赌注共识",理念就是让验证人反复对其认为会提交到区块链的区块下赌注(根据它之前打赌的经验来),最后得出结论。 [链接](https://blog.ethereum.org/2015/12/28/understanding-serenity-part-2-casper/)。这是 Casper 团队活跃中的研究领域,其挑战就是搭建一套进化稳定的打赌机制。和 Tendermint 相比Casper 主要的优势就在于提供了"优于一致性的实用性"——其共识无需超过 ⅔ 的仲裁选票——可能其代价就是速度慢以及实现复杂。
### 水平扩展
#### Interledger 协议
Interledger 协议 [\[14\]](14)严格来说不算是可扩展解决方案。它通过一个松散耦合的双边关系网络为不同账本系统提供了特别的互操作性。比如闪电网络ILP 的目的就是促进支付,不过是以不同账本类型的支付为主,并且延展了原子交易机制,将哈希锁以及公证人的仲裁集(也叫做原子传输协议)都包括进去。用于维持账本间交易原子数的后面这种机制和 Tendermint 的轻客戸端 SPV 机制相似,因此就可以对 ILP 和 Cosmos/IBC 之间的区别加以解释了,具体如下:
1. 在 ILP 中连接器的公证人不支持成员变动,并且不允许在公证人间进行灵活的权重。而 IBC 是专门为区块链设计的,其验证人可以有不同的权重,而且成员也可以根据区块链进程进行变动。
2. 在闪电网络中ILP 付款接收人必须在线来发送确认函给发送者。而在 IBC 代币传输过程中,接收人区块链的验证组会负责提供确认,而非接收人。
3. 两者最大的不同就是 ILP 的连接器不充当支付状态的权威方,而在 Cosmos 中,一个中心的验证人就是 IBC 代币传输状态以及每个空间所持代币总量(不是空间每个账户所持代币总量)的权威见证人。这是一个根本性创新,允许代币在空间里进行安全且非对称的传输,而在 Cosmos 中,充当 ILP 连接器的角色是一个持久且安全的区块链账本——Cosmos 中心Cosmos Hub
4. ILP 账本间支付需要由一份交易订单簿做支持,因为其不包括代币从一个账本到另一个账本的非对称传输,而只有价值或市场等值在传输。
#### 侧链
侧链 [\[15\]](15)是针对比特币网络扩展提出的一个机制,通过与比特币区块链"挂钩"的可替代区块链实现。侧链可以让比特币有效从区块链转移到侧链及后台,还可以在侧链上进行新功能测试。在 Cosmos Hub 中,侧链和比特币是彼此的轻客戸端,使用 SPV安全协议验证工具证明来决定什么时候转移代币到侧链及后台。当然因为比特币采用工作量证明机制所以以比特币为中心的侧链也遇到很多工作量证明作为共识算法而带来的问题与风险。此外这个是比特币多数派解决方案它并不支持本地代币以及空间内网络拓扑学而 Cosmos 可以。也就是说,这种双向挂钩的核心机制在原则上是和 Cosmos 网络运用的机制一样的。
#### 以太坊在可扩展方面的努力
以太坊目前正在研究不同的方法来实现以太坊区块链状态的碎片化,从而解决可扩展问题。这些努力的目标就是在共享状态空间中维持当前以太坊虚拟机提供的抽象层。他们同时开展了多项研究。 [\[18\]](18) [\[22\]](22)
##### Cosmos vs 以太坊 2.0 Mauve
Cosmos 和以太坊 2.0 Mauve [\[22\]](22)有不同的设计目标。
- Cosmos 专注代币。而 Mauve 是和扩展普通计算相关。
- Cosmos 不一定是以太坊虚拟机,因此即使是不同的虚拟机也可以进行交互。
- Cosmos 可以让空间创建者决定谁来验证空间。
- 任何人都可以在 Cosmos 开创新空间(除非管理有其他决定)。
- 中心会隔离空间故障,这样全球代币的不变性就得到了维护。
### 普通扩展
#### 闪电网络
闪电网络是一种代币传输网络,在比特币区块链(及其他公共区块链)上一层运行,能够执行众多改善交易吞吐量的指令,通过将大多数交易从共式账本移出,转入所谓的"支付信道"内。这个举措通过链上的加密货币脚本或可实现,它可以让参与者进入双方有状态的合约,其中状态可以通过共享数字签名进行更新,并且最后将证据公布到区块链后可以关闭合约,这个机制最初是通过跨链原子掉期而普及的。通过开放多方支付信道,闪电网络的参与者可以成为他人支付的路由焦点,带领全面连接的支付信道网络,代价就是绑定在支付信道上的资本。
虽然闪电网络可以轻松在多个单独区块链间延伸以通过交易市场进行价值传输但是它无法用来进行区块链间的非对称代币传输。Cosmos 网络的主要好处就是能够进行这类直接代币传输。也就是说,我们可以期待支付信道与闪电网络在我们的代币传输机制下投入广泛应用,一方面为了节省成本,另一方面也可以保证隐私。
#### 隔离见证
隔离见证是一项比特币改善提议( [连接](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)),其目的是将每个区块的交易吞吐量提高 2-3 倍并且同时保证区块快速同步新节点。这个方案的出色就在于它可以在比特币当前协议的限制下运行并且允许进行软分叉更新也就是其他旧版本的客户端软件在更新后可以继续运行。Tendermint 是一种新的协议它没有设计限制因此可以有不同的扩展选择。首先Tendermint 采用拜占庭容错循环制算法,这个是在加密签字而非挖矿的基础上进行,可以简单地通过多平行区块链进行水平扩展,而定期频繁的区块提交还可以进行垂直扩展。
## 附录
### 分叉问责制
如果遇到超过容错能力以及共识出错的情况下,设计周到的共识协议应该要对此提供一定的保障。这个在经济系统中尤为必要,经济系统中的拜占庭行为可以获取大量资金奖励。而针对上述情况有一个最为重要的保证就是分叉问责制,这个形式下,导致共识出错的进程(也就是导致协议客户端开始接受不同值——即分叉出现)可以被识别出并根据协议规定进行生发,或者也有可能受到法律制裁。当法律系统变得不可靠或者调用代价过大,那么验证人可能要强制支付安全保证金来参与,而一旦检测到恶意行为,那么这些保证金就会被吊销或者减少。 [\[10\]](10)
注意这个和比特币有很大区别,由于网络同步及其发现哈希冲突的概率特性,比特币的分叉是定期出现的。在很多案例中,很难将恶意分叉与同步导致的分叉区分开来,所以比特币无法可靠地实施分叉问责制,除了让矿工为孤行区块挖矿支付隐形机会成本。
### Tendermint 共识
我们将投票阶段分为预投票及预提交阶段。一个选票可以用于特定区块或*Nil。*我们把同一轮超过 ⅔ 的单个区块的预投票总和称为*Polka*,把同一轮超过 ⅔ 的单个区块的预提交总和称为*Commit*。如果同一轮针对 Nil 的预提交超过 ⅔,那么它们就进入下一轮。
注意,协议中严格的决定论会招致较弱的同步假设,因为默认的首项必须被扣除且略过。因此,验证人在对 Nil 进行预投票前会等候一段时间(即*TimeoutPropose* ,超时提议),而*TimeoutPropose* 的值会随着每一轮的进行而增加。每一轮剩下的进程是完全同步的,在过程中只有验证人收到超过 ⅔ 的网络投票才会进入下一步。在实践中,这么做将需要超级强大的对手无限阻挠较弱的同步假设(导致共识无法提交区块),而且通过使用每个验证人的*TimeoutPropose* 随机值还可加大其难度。
另一套约束,或者锁定规则会确保最终在每个高度只提交一个区块,任何试图提交超过一个区块到指定高度的恶意行为都会被识别出来。首先,每个区块的预提交必须正当,并且以 Polka 的形式提交。如果验证人已经在*R_1*轮预提交了一个区块,那么我们就认为它们被锁定在了这个区块,然后用于验证*R_2*轮新预提交动作的 Polka 必须进入 R_polka 轮,其中 `R_1 < R_polka <= R_2`。第二,验证人必须提出并且/或者预投票它们被锁定的区块。这两步合起来,就可以确保验证人不会在没有充足证据证明正当性的前提下进行预提交,并且保证已经完成预提交的验证人不能再为其他东西的预提交贡献证明。这样不但可以保证安全,还能保证共识算法的活跃。
关于这一协议的全部细节参考 [这里](https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm)。
### Tendermint 轻客戸端
本来需要同步所有区块头,现在在 Tendermint-PoS 里取消了这一需求,因为我们有替代链(分叉),也就是说可以削减超过 ⅓ 的绑定权益。当然,削减也是需要有人共享分叉证据的,所以轻客戸端就要存储任何它见证的区块哈希提交。此外,轻客戸端可以定期同步验证组的变动,以避免出现 [远距离攻击](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#preventing-long-range-attacks)(除了其他可能实现的解决方案)。
秉着和以太坊类似的想法Tendermint 能够让应用程序在每个区块中嵌入全球梅克尔根哈希,可以进行简单且可验证的状态查询,比如查询账户余额、合约存放价值,或者未使用交易输入的存在等,具体由应用程序的特性决定。
### 预防远距离攻击
假设有一套充足的可复原的广播网络集合以及一组静态验证组,那么任何区块链分叉都可以被检测到,而且发起攻击的验证人提交的保证金会被扣除。这一创新是由 Vitalik Buterin 于 2014 年首次提出的,可以解决其他权益证明加密货币"没有任何权益"的问题(详见 [相关工作](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#related-work)部分。但是由于验证组必须要可以更改所以可能在很长一段时内最初的验证人可能会解除绑定因此就可以自由从创世区块中创建新链并且不需要任何费用因为他们不再有锁定的保证金。这类攻击被称为远距离攻击LRA和短距离攻击相反短距离攻击是当前处于绑定中的验证人导致的分叉并且会因此受到惩罚假设有类似 Tendermint 共识这样的分叉问责制拜占庭容错算法)。长距离攻击经常被认为是对权益证明机制的重要打击。
所幸的是LRA 可以通过以下方法得以缓解。第一,针对解绑(来恢复抵押的保证金并且不再挣取参与共识的费用)的验证人,保证金在一定时间内必须是不可转移的,可以称作"解绑期",可能长达数周或数月。第二,针对轻客戸端的安全,其首次连接到网络必须根据可信源验证最近的一个或者最好是多个区块哈希。这种情况有时也被称作"薄弱主观性"。最后,为了保证安全,必须频繁同步最新验证组(每次间隔时间和解绑期一样)。这一点可以保证轻客戸端在验证人解绑资金(从而没有任何权益)前知道验证组的变化情况,否则解绑的验证人就会通过实施远距离攻击来欺骗客户端,在其绑定的高度创建新区块起始返回点(假设它可以控制足够多的早期私钥)。
注意,用这种方式解决 LRA 问题需要对工作量证明模型原始的安全问题进行彻底检查。在 PoW 中,轻客戸端被认为可以在任何时候从可靠的创世区块同步到当前高度,这个过程可以简单的在每个区块头上处理工作量证明来完成。但是,为了解决 LRA我们需要轻客戸端上线定期追踪验证组的变动并且其首次上线必须尤其要注意根据可靠源来验证其从网络收集的情报。当然后面这个要求和比特币的类似在比特币中协议和软件也必须从可靠源获得。
上述预防 LRA 的方法正好适合 Tendermint 驱动下的区块链验证人及全部节点,因为这些节点的任务就是保持连接到网络。这个方法也适合那些想要经常进行网络同步的轻客戸端。但是,对不希望频繁接入互联网或区块链网络的轻客戸端来说,还有另一种方法可以用来解决 LRA 问题。非验证人的代币持有者额可以在很长的解绑期内(比如比验证人的解绑期久)发布代币作为抵押品,并且为轻客戸端提供第二级解决方案来证实当前及过去区块哈希的有效性。就算这些节点没有计入区块链共识的安全性时,他们还是可以为轻客戸端提供强大的保障。如果历史区块哈希查询在以太坊中得到过支持,那么任何人都可以用特定的智能合约来绑定他们的代币,并且提供付费证明服务,从而有效地针对轻客戸端 LRA 安全问题开发出一个市场。
### 克服分叉及审查攻击
由于区块提交的定义如此,所以任何超过 ⅓ 的投票联合都可以通过下线或者不广播选票来中止区块链运行。这样的联合也可以通过拒绝包含这类交易的区块来检查特定交易,尽管这或将导致大多数区块提案被拒绝,从而减缓区块提交速率,降低实用性及价值。恶意联合或许会陆陆续续地广播选票,以阻挠区块链的区块提交,将其逼停,或者就是参与到这些攻击的组合攻击中。最后,它会通过双重签名或者违反锁定规则来造成区块链分叉。
如果一个全球活跃的对手参与进来,那么就会使网络分割,导致验证组子集出现拉低速率。这不单单是 Tendermint 面临的限制,也是所有共识协议(其网络可能由活跃对手控制)的限制。
对这几类攻击,验证人子集应该在外部进行协调,签署重组提议来选择分叉(及牵扯到的任何证据)以及验证人的初始子集。签署这份重组提议的验证人会放弃他们在其他分叉上的所有抵押品。客户端要验证重组提议上的签名,验证任何证据并且做出判断或者给端用户提示来做决定。比如,一个手机钱包 APP 可能会提示用户安全警告,而电冰箱可能会接受签署超过 ½ 的初始验证人提出的重组提议。
任何非同步的拜占庭容错算反都不能进入共识,如果超过 ⅓ 的投票不诚实的话,而且出现分叉就说明已经有超过 ⅓ 的投票权因为无正当理由的双重签名或改锁而失信。因此,签署重组提议是一个协调性问题,无法通过任何非同步协议(即自动的,不对底层网络可靠性做假设的)来解决。目前,我们认为重组提议的协调问题,可以通过互联网媒体的社会共识来实现人为协调。验证人必须确保在签订重组提议前不会出现网络分割,以此来避免有两个相冲突的重组提议被强叔的情况出现。
加入外部条件媒介以及协议足够稳健的话,那么分叉的问题就没有检查攻击问题来的严重。
分叉和检查需要有超过 ⅓ 的拜占庭投票权,此外超过 ⅔ 的投票权联合可能会恣意提交无效状态。这个是任何拜占庭容错共识系统的特点。和双重签名不同,双重签名会利用简单的可验证的证据来创建分叉,而要检测无效状态的提交则需要非验证对等节点来验证整个区块,也就是说它们会保留一份本地状态副本,执行每笔交易,然后独立计算状态根。一旦检测出来,处理这类故障的唯一方法就是社会共识。比如,如果比特币出现问题,那么无论是软件漏洞造成的分叉,还是矿工拜占庭行为提交的无效状态(正如 2015 年 7 月),连接紧密的商户、开发者、矿工以及其他组织组成的社区按照所需分工来参与修复网络。此外,因为 Tendermint 区块链的验证人或可进行身份认证,所以如果有这个想法,那么无效状态的提交也可能会受到法律或其他外部法律体系的惩罚。
### TMSP 具体说明
TMSPTendermint Socket 协议)由三个主要信息类型组成,这三类信息从核心传递到应用程序上,然后应用程序用相关回复信息做出应答。
AppendTx 信息是应用程序的主力。区块链上的每笔交易都通过这个信息来传递。应用程序需要借助 AppendTx 信息在当前状态、应用程序协议以及交易加密证书中进行验证,验证每笔接收的交易。验证过的交易之后需要更新应用状态——通过绑定一个值到关键价值存储库中,或者更新 UTXO 数据库。
CheckTx 信息和 AppendTx 信息类似但是只针对交易验证。Tendermint Core 的内存池会先用 CheckTx 检查交易有效性,并且只会将有效的交易分程传递给对等节点。应用程序可能会检查交易中的递增新鲜值,如果新鲜值过期就会借助 CheckTx 返回一个错误。
Commit 信息是用来计算当前应用状态上的加密提交项的——之后会存入下一区块头。这包含一些方便的特性。状态更新的矛盾性将以区块链分叉的形式出现,从而捕捉整个阶段的程序错误。这也简化了安全轻客戸端的开发,因为梅克尔哈希证明可以通过检查区块哈希来加以验证,而区块哈希是通过验证人仲裁集加以签署的(通过投票权)。
额外的 TMSP 信息允许应用程序追踪改变验证组,并让应用程序接收高度、提交的选票之类的区块信息。
TMSP 的要求/回应是简单的 Protobuf 信息。请参考这里的 [模式文件](https://github.com/tendermint/abci/blob/master/types/types.proto)。
#### AppendTx附加交易
- **命令行参数** :
- Data (\[]byte): 所需交易的字节
- **返回** :
- Code (uint32): 回复代码
- Data (\[]byte): 结果字节,如果有的话
- Log (string): 调试或出错信息
- **使用** :
附加并运行一笔交易。如果交易有效,那返回 CodeType.OK
#### CheckTx检查交易
- **命令行参数** :
- Data (\[]byte): 所需交易的字节
- **返回** :
- Code (uint32): 回复代码
- Data (\[]byte): 结果字节,如果有的话
- Log (string): 调试或出错信息
- **使用** :
验证一笔交易。这个信息不应该改变状态。交易在广播给内存池层对等节点前,首先通过 CheckTx 运行。你可以发起半状态化 CheckTx并在 Commit or BeginBlock 上清算状态,以允许执行同一区块中相关的交易序列。
#### Commit提交
- **返回** :
- Data (\[]byte): 梅克尔根哈希
- Log (string): 调试或出错信息
- **使用** :
返回应用程序状态梅克尔根哈希。
#### Query查询
- **命令行参数** :
- Data (\[]byte): 查询需要的字节
- **返回** :
- Code (uint32): 回复代码
- Data (\[]byte): 查询回复字节
- Log (string): 调试或出错信息
#### Flush划掉
- **使用** :
划掉回复队列。使用 types.Application 的应用程序无需实施这条信息——这个由项目进行处理。
#### Info信息
- **返回** :
- Data (\[]byte): 信息的字节
- **使用** :
返回关于应用程序状态的信息。Application specific.
#### SetOption设置选项
- **命令行参数** :
- Key (string): 设置密钥
- Value (string): 密钥设置的值
- **返回** :
- Log (string): 调试或出错信息
- **使用** :
设置应用选项。比如,针对内存池的连接可以将密钥设置为"mode"(模式),价值为"mempool"(内存池)。或者针对共识连接,将密钥设置为"mode",价值设置为"consensus"(共识)。其他选项根据可具体应用进行专门设置。
#### InitChain初始链
- **命令行参数** :
- Validators (\[]Validator): 初始创世验证人
- **使用** :
在创世块生成后进行调用
#### BeginBlock起始块
- **命令行参数** :
- Height (uint64): 区块刚开始的高度
- **使用** :
为新区块的开始提供信号。在任何附加交易AppendTxs前进行调用。
#### EndBlock结束区块
- **命令行参数** :
- Height (uint64): 结束时的区块高度
- **返回** :
- Validators (\[]Validator): 具有新选票的变动后的验证人(归零就去除)
- **使用** :
为区块结束提供信号。在每次提交前所有交易后调用。
更多细节请参考 [TMSP 知识库](https://github.com/tendermint/abci)。
### IBC 包交付确认
发送人可能会需要接收链提供包交付确认,这个原因有很多。比如,发送人可能不了解目的链的状态,如果有问题的话也不得而知。或者,发送者可能会想要向包强加一次超时(借助 MaxHeight 即最大值包域),而目的链可能会遇到拒绝服务攻击,比如接受包数量突然暴增。
在这些案例中,发送人可以通过在 AckPending 上设置初始包状态来要求提供交付确认。接着就由接收链通过在应用程序的梅克尔哈希中包括一个缩写的 IBCPacket 来确认交付。
![Figure of Zone1, Zone2, and Hub IBC with acknowledgement](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack.png)
首先IBCBlockCommit 以及 IBCPacketTx 是公布在"Hub"(中心)上用来证明"Zone1"(空间 1上的 IBCPacket 的存在的。假如 IBCPacketTx 的值如下:
- FromChainID: "Zone1"
- FromBlockHeight: 100 (假如)
- Packet: an IBCPacket:
- Header: an IBCPacketHeader:
- SrcChainID: "Zone1"
- DstChainID: "Zone2"
- Number: 200 (假如)
- Status: AckPending
- Type: "coin"
- MaxHeight: 350 (假如"Hub"目前的高度是 300)
- Payload: &lt;The bytes of a "coin" payload>(一个"代币"的有效负荷字节)
第二步IBCBlockCommit 和 IBCPacketTx 被公布到"Zone2"(空间 2来证明 IBCPacket 存在于"Hub"上。假如 IBCPacketTx 的值如下:
- FromChainID: "Hub"
- FromBlockHeight: 300
- Packet: an IBCPacket:
- Header: an IBCPacketHeader:
- SrcChainID: "Zone1"
- DstChainID: "Zone2"
- Number: 200
- Status: AckPending
- Type: "coin"
- MaxHeight: 350
- Payload: &lt;The same bytes of a "coin" payload>(一个"代币"相同的有效负荷字节)
接下来,"Zone2"必须将缩写的包放入其应用程序哈希中来显示 AckSent 的最新状态。
IBCBlockCommitand 和 IBCPacketTx 会公布到"Hub"上来证明缩写的 IBCPacket 存在于"Zone2"上。假如 IBCPacketTx 的值如下:
- FromChainID: "Zone2"
- FromBlockHeight: 400 (say)
- Packet: an IBCPacket:
- Header: an IBCPacketHeader:
- SrcChainID: "Zone1"
- DstChainID: "Zone2"
- Number: 200
- Status: AckSent
- Type: "coin"
- MaxHeight: 350
- PayloadHash: &lt;The hash bytes of the same "coin" payload>(相同"代币"有效负荷的哈希字节)
最后,"Hub"必须更新从 AckPending 到 AckReceived 的包的状态。这个最新状态的证据要回到"Zone2"。假如 IBCPacketTx 的值如下:
- FromChainID: "Hub"
- FromBlockHeight: 301
- Packet: an IBCPacket:
- Header: an IBCPacketHeader:
- SrcChainID: "Zone1"
- DstChainID: "Zone2"
- Number: 200
- Status: AckReceived
- Type: "coin"
- MaxHeight: 350
- PayloadHash: &lt;The hash bytes of the same "coin" payload>(相同"代币"有效负荷的哈希字节)
同时,"Zone1"可能会积极地假设"代币"包的交付是成功的,除非"Hub"上有证据给出相反的证明。在上述例子中,如果"Hub"没有从"Zone2"接收到区块 350 的 AckSent 状态,那么它就会自动将这个设置到 Timeout超时。这个超时证据可以贴回到"Zone1"上,然后就可以返回任意代币。
![Figure of Zone1, Zone2, and Hub IBC with acknowledgement and timeout](https://raw.githubusercontent.com/gnuclear/atom-whitepaper/master/msc/ibc_with_ack_timeout.png)
### 梅克尔树及梅克尔证明说明
Tendermint/Cosmos 生态支持的梅克尔树有两种:简单的和 IAVL+的。
#### 简易版梅克尔树
简易版梅克尔树针对的元素的静态列表。如果项的数目不是 2 的次方,那么有些树叶就会在不同的层。简易树试图让树的两侧在同一高度,但是左边可能会稍大一点。这种梅克尔树就是用于一个区块交易的梅克尔化的,而顶层元素就是应用状态根。
*
/ \
/ \
/ \
/ \
* *
/ \ / \
/ \ / \
/ \ / \
* * * h6
/ \ / \ / \
h0 h1 h2 h3 h4 h5
A SimpleTree with 7 elements
#### IAVL+梅克尔树
IAVL+数据结构的目的是永久储存应用状态中的密钥-值,这样具有决定功能的梅克尔根哈希就可以有效进行运算了。这个树的平衡通过 [AVL 算法](http://en.wikipedia.org/wiki/AVL_tree)的一个变量来达到,所有运行都是 O(log(n))。
在 AVL 树中,任意节点两个子树的高度至少有一处不同。无论什么时候出现更新来打破这种情况,这个树都会通过创造 O(log(n))新节点(指向旧树上未修改的节点)来再次达到平衡。在初始 AVL 算法中,内部节点也可以维持密钥-值。AVL+算法(注意这里有个"+"号)对 AVL 算法进行了修改,来维持所有树叶节点上的值,同时还只需采用分支-节点来存储密钥。这一点在简化算法的同时,还能维持较短的梅克尔哈希轨迹。
AVL+树类似于以太坊的 [Patricia tries](https://en.wikipedia.org/wiki/Radix_tree)(帕氏树)。其中也有一定的折中。密钥不需要在嵌入到 IAVL+树前生成哈希而这个就为密钥空间里提供了较快的命令迭代或许为很多应用程序都带去了好处。逻辑实现很简单只需要两种节点——内部节点和树叶节点。作为一个平衡的二进制树梅克尔证明算是短的。而另一方面IAVL+树有取决于命令的更新。
我们将支持额外有效的梅克尔树,比如以太坊的帕氏树,同时实现二进制变量的可用性。
### 交易类型
在标准实现中,交易通过 TMSP 界面流入 Cosmos Hub 的应用程序。
Cosmos Hub 将接受几类主要交易,包括 SendTxBondTxUnbondTxReportHackTxSlashTxBurnAtomTxProposalCreateTx以及 ProposalVoteTx即发送交易、绑定交易、解绑交易、攻击报告交易、削减交易、Atom 燃烧交易,创建提议交易),这些都不需要加以寿命,会在之后文章更新中进行归档。这里我们主要列举两个主要的 IBC 交易类型IBCBlockCommitTx 以及 IBCPacketTx即 IBC 区块提交交易以及 IBC 包交易)
#### IBCBlockCommitTxIBC 区块提交交易)
IBCBlockCommitTx 交易主要由这些组成:
- ChainID (string): 区块链 ID
- BlockHash (\[]byte): 区块哈希字节,就是梅克尔根(包括应用程序哈希)
- BlockPartsHeader (PartSetHeader): 区块部分设置的头字节,只用于验证投票签名
- BlockHeight (int): 提交高度
- BlockRound (int): 提交回合
- Commit (\[]Vote): 超过 ⅔ 的 Tendermint 预提交投票,以组成区块提交项
- ValidatorsHash (\[]byte): 新验证组的梅克尔树根哈希
- ValidatorsHashProof (SimpleProof): 简易版梅克尔树证明,在区块哈希中证明验证人哈希
- AppHash (\[]byte): IAVL 树,应用程序状态的梅克尔树根哈希
- AppHashProof (SimpleProof): 简易版梅克尔树证明,在区块哈希中验证应用程序哈希
#### IBCPacketTxIBC 包交易)
IBCPacket 由下列项组成:
- Header (IBCPacketHeader): 包头
- Payload (\[]byte): 包有效负荷字节。_可选择_。
- PayloadHash (\[]byte): 包字节哈希。_可选择。_
有效负荷或有效负荷哈希必须存在一个。IBCPacket 的哈希就是两个项的简易版梅克尔根,即头和有效负荷。没有完整有效负荷的 IBCPacket 被称作缩写版包。
IBCPacketHeader 由下列项组成:
- SrcChainID (string): 源区块链 ID
- DstChainID (string): 目标区块链 ID
- Number (int): 所有包特定数量
- Status (enum): 可以是 AckPendingAckSentAckReceivedNoAck或 Timeout 任意一个
- Type (string): 种类根据应用程序决定。Cosmos 保留"coin"(币)包种类。
- MaxHeight (int): 如果状态不是这个高度给出的 NoAckWanted 或者 AckReceived 那么状态就算超时。_可选择。_
IBCPacketTx 交易有下列项组成:
- FromChainID (string): 区块链 ID用于提供这个包不是必要的来源
- FromBlockHeight (int): 区块链高度,其中接下来的包会包含在源链的区块哈希中
- Packet (IBCPacket): 数据包,其状态可以是 AckPendingAckSentAckReceivedNoAck或 Timeout 任意一个
- PacketProof (IAVLProof): IAVL 树梅克尔证明,用于一定高度的源链中的应用哈希中验证包的哈希
通过"Hub",从"Zone1"发送到"Zone2"的包的序列会用{Figure X}函数进行描述。首先一次 IBCPacketTx 会向"Hub"证明包是包含在"Zone1"的应用程序状态中。然后,另一次 IBCPacketTx 会向"Zone2"证明包包含在"Hub"的应用程序状态中。在这个过程中IBCPacketTx 的域是一样的SrcChainID 永远是"Zone1"而 DstChainID 永远是"Zone2"。
PacketProof 必须有正确的梅克尔证明路径,如下:
IBC/<SrcChainID>/<DstChainID>/<Number>
当"Zone1"想要向"Zone2"通过"Hub"发送证明,那么 IBCPacket 的数据是相同的,无论这个包是在"Zone1"、 "Hub"还是"Zone2"上梅克尔化的。唯一可变的域只有追踪交付的 Status (状态),如下所示。
## 鸣谢
感谢朋友及同行在概念成型与检查方面提供的帮助,以及对我们同 Tendermint 及 Cosmos 工作的支持。
- [SkuChain](http://www.skuchain.com/)的 [Zaki Manian](https://github.com/zmanian)在 格式和措辞方面提供了很多支持,尤其是 TMSP 部分。
- Althea and Dustin Byington 的 [Jehan Trembac](https://github.com/jtremback) [k](https://github.com/jtremback) 在初始迭代方面帮助。
- [Honey Badger](https://eprint.iacr.org/2016/199)的 [Andrew Miller](https://soc1024.com/) 对共识部分给予的反馈。
- [Greg Slepak](https://fixingtao.com/)对共识及措辞给予的反馈。
- 同时还要感谢 [Bill Gleim](https://github.com/gleim)和 [Seunghwan Han](http://www.seunghwanhan.com/)的各种支持与贡献。
- \*\*此处还有您及您的组织对本文的贡献。
## 引用
- [1] Bitcoin: <https://bitcoin.org/bitcoin.pdf>
- [2] ZeroCash: <http://zerocash-project.org/paper>
- [3] Ethereum: <https://github.com/ethereum/wiki/wiki/White-Paper>
- [4] TheDAO: <https://download.slock.it/public/DAO/WhitePaper.pdf>
- [5] Segregated Witness: <https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki>
- [6] BitcoinNG: <https://arxiv.org/pdf/1510.02037v2.pdf>
- [7] Lightning Network: <https://lightning.network/lightning-network-paper-DRAFT-0.5.pdf>
- [8] Tendermint: <https://github.com/tendermint/tendermint/wiki>
- [9] FLP Impossibility: <https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf>
- [10] Slasher: <https://blog.ethereum.org/2014/01/15/slasher-a-punitive-proof-of-stake-algorithm/>
- [11] PBFT: <http://pmg.csail.mit.edu/papers/osdi99.pdf>
- [12] BitShares: <https://bitshares.org/technology/delegated-proof-of-stake-consensus/>
- [13] Stellar: <https://www.stellar.org/papers/stellar-consensus-protocol.pdf>
- [14] Interledger: <https://interledger.org/rfcs/0001-interledger-architecture/>
- [15] Sidechains: <https://blockstream.com/sidechains.pdf>
- [16] Casper: <https://blog.ethereum.org/2015/08/01/introducing-casper-friendly-ghost/>
- [17] TMSP: <https://github.com/tendermint/abci>
- [18] Ethereum Sharding: <https://github.com/ethereum/EIPs/issues/53>
- [19] LibSwift: <http://www.ds.ewi.tudelft.nl/fileadmin/pds/papers/PerformanceAnalysisOfLibswift.pdf>
- [20] DLS: <http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf>
- [21] Thin Client Security: <https://en.bitcoin.it/wiki/Thin_Client_Security>
- [22] Ethereum 2.0 Mauve Paper: <https://cdn.hackaday.io/files/10879465447136/Mauve%20Paper%20Vitalik.pdf>
#### 未分类链接
- <https://www.docdroid.net/ec7xGzs/314477721-ethereum-platform-review-opportunities-and-challenges-for-private-and-consortium-blockchains.pdf>

1590
docs/resources/whitepaper.md Normal file

File diff suppressed because it is too large Load Diff

162
docs/sdk/clients.md Normal file
View File

@ -0,0 +1,162 @@
# Clients
::: tip Note
🚧 We are actively working on documentation for SDK clients.
:::
## Gaia CLI
::: tip Note
🚧 We are actively working on improving documentation for Gaiacli and Gaiad.
:::
`gaiacli` is the command line interface to manage accounts and transactions on Cosmos testnets. Here is a list of useful `gaiacli` commands, including usage examples.
### Key Types
There are three types of key representations that are used:
- `cosmosaccaddr`
* Derived from account keys generated by `gaiacli keys add`
* Used to receive funds
* e.g. `cosmosaccaddr15h6vd5f0wqps26zjlwrc6chah08ryu4hzzdwhc`
- `cosmosaccpub`
* Derived from account keys generated by `gaiacli keys add`
* e.g. `cosmosaccpub1zcjduc3q7fu03jnlu2xpl75s2nkt7krm6grh4cc5aqth73v0zwmea25wj2hsqhlqzm`
- `cosmosvalpub`
* Generated when the node is created with `gaiad init`.
* Get this value with `gaiad tendermint show_validator`
* e.g. `cosmosvalpub1zcjduc3qcyj09qc03elte23zwshdx92jm6ce88fgc90rtqhjx8v0608qh5ssp0w94c`
### Generate Keys
You'll need an account private and public key pair \(a.k.a. `sk, pk` respectively\) to be able to receive funds, send txs, bond tx, etc.
To generate a new key \(default _ed25519_ elliptic curve\):
```bash
gaiacli keys add <account_name>
```
Next, you will have to create a passphrase to protect the key on disk. The output of the above command will contain a _seed phrase_. Save the _seed phrase_ in a safe place in case you forget the password!
If you check your private keys, you'll now see `<account_name>`:
```bash
gaiacli keys show <account_name>
```
You can see all your available keys by typing:
```bash
gaiacli keys list
```
View the validator pubkey for your node by typing:
```bash
gaiad tendermint show_validator
```
::: danger Warning
We strongly recommend *NOT* using the same passphrase for multiple keys. The Tendermint team and the Interchain Foundation will not be responsible for the loss of funds.
:::
### Get Tokens
The best way to get tokens is from the [Cosmos Testnet Faucet](https://faucetcosmos.network). If the faucet is not working for you, try asking [#cosmos-validators](https://riot.im/app/#/room/#cosmos-validators:matrix.org). The faucet needs the `cosmosaccaddr` from the account you wish to use for staking.
After receiving tokens to your address, you can view your account's balance by typing:
```bash
gaiacli account <account_cosmosaccaddr>
```
::: warning Note
When you query an account balance with zero tokens, you will get this error: `No account with address <account_cosmosaccaddr> was found in the state.` This can also happen if you fund the account before your node has fully synced with the chain. These are both normal.
We're working on improving our error messages!
:::
### Send Tokens
```bash
gaiacli send \
--amount=10faucetToken \
--chain-id=gaia-6002 \
--name=<key_name> \
--to=<destination_cosmosaccaddr>
```
::: warning Note
The `--amount` flag accepts the format `--amount=<value|coin_name>`.
:::
Now, view the updated balances of the origin and destination accounts:
```bash
gaiacli account <account_cosmosaccaddr>
gaiacli account <destination_cosmosaccaddr>
```
You can also check your balance at a given block by using the `--block` flag:
```bash
gaiacli account <account_cosmosaccaddr> --block=<block_height>
```
### Delegate
On the upcoming mainnet, you can delegate `atom` to a validator. These [delegators](/resources/delegators-faq) can receive part of the validator's fee revenue. Read more about the [Cosmos Token Model](https://github.com/cosmos/cosmos/raw/master/Cosmos_Token_Model.pdf).
### Bond Tokens
On the testnet, we delegate `steak` instead of `atom`. Here's how you can bond tokens to a testnet validator:
```bash
gaiacli stake delegate \
--amount=10steak \
--address-delegator=<account_cosmosaccaddr> \
--address-validator=$(gaiad tendermint show_validator) \
--name=<key_name> \
--chain-id=gaia-6002
```
While tokens are bonded, they are pooled with all the other bonded tokens in the network. Validators and delegators obtain a percentage of shares that equal their stake in this pool.
::: tip Note
Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)!
:::
### Unbond Tokens
If for any reason the validator misbehaves, or you want to unbond a certain amount of tokens, use this following command. You can unbond a specific amount of`shares`\(eg:`12.1`\) or all of them \(`MAX`\).
```bash
gaiacli stake unbond \
--address-delegator=<account_cosmosaccaddr> \
--address-validator=$(gaiad tendermint show_validator) \
--shares=MAX \
--name=<key_name> \
--chain-id=gaia-6002
```
You can check your balance and your stake delegation to see that the unbonding went through successfully.
```bash
gaiacli account <account_cosmosaccaddr>
gaiacli stake delegation \
--address-delegator=<account_cosmosaccaddr> \
--address-validator=$(gaiad tendermint show_validator) \
--chain-id=gaia-6002
```
## Light Client Daemon
::: tip Note
🚧 We are actively working on documentation for the LCD.
:::

View File

@ -1,7 +1,7 @@
# App5 - Basecoin
# Basecoin
As we've seen, the SDK provides a flexible yet comprehensive framework for building state
machines and defining their transitions, including authenticating transactions,
machines and defining their transitions, including authenticating transactions,
executing messages, controlling access to stores, and updating the validator set.
Until now, we have focused on building only isolated ABCI applications to
@ -17,11 +17,11 @@ TODO
## Tendermint Node
Since the Cosmos-SDK is written in Go, Cosmos-SDK applications can be compiled
Since the Cosmos-SDK is written in Go, Cosmos-SDK applications can be compiled
with Tendermint into a single binary. Of course, like any ABCI application, they
can also run as separate processes that communicate with Tendermint via socket.
For more details on what's involved in starting a Tendermint full node, see the
For more details on what's involved in starting a Tendermint full node, see the
[NewNode](https://godoc.org/github.com/tendermint/tendermint/node#NewNode)
function in `github.com/tendermint/tendermint/node`.
@ -57,14 +57,14 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application {
}
```
Note we utilize the popular [cobra library](https://github.com/spf13/cobra)
for the CLI, in concert with the [viper library](https://github.com/spf13/library)
Note we utilize the popular [cobra library](https://github.com/spf13/cobra)
for the CLI, in concert with the [viper library](https://github.com/spf13/library)
for managing configuration. See our [cli library](https://github.com/tendermint/blob/master/tmlibs/cli/setup.go)
for more details.
TODO: compile and run the binary
Options for running the `basecoind` binary are effectively the same as for `tendermint`.
Options for running the `basecoind` binary are effectively the same as for `tendermint`.
See [Using Tendermint](TODO) for more details.
## Clients

145
docs/sdk/gaiacli.md Normal file
View File

@ -0,0 +1,145 @@
# Gaia CLI
`gaiacli` is the command line interface to manage accounts and transactions on Cosmos testnets. Here is a list of useful `gaiacli` commands, including usage examples.
## Key Types
There are three types of key representations that are used:
- `cosmosaccaddr`
* Derived from account keys generated by `gaiacli keys add`
* Used to receive funds
* e.g. `cosmosaccaddr15h6vd5f0wqps26zjlwrc6chah08ryu4hzzdwhc`
- `cosmosaccpub`
* Derived from account keys generated by `gaiacli keys add`
* e.g. `cosmosaccpub1zcjduc3q7fu03jnlu2xpl75s2nkt7krm6grh4cc5aqth73v0zwmea25wj2hsqhlqzm`
- `cosmosvalpub`
* Generated when the node is created with `gaiad init`.
* Get this value with `gaiad tendermint show_validator`
* e.g. `cosmosvalpub1zcjduc3qcyj09qc03elte23zwshdx92jm6ce88fgc90rtqhjx8v0608qh5ssp0w94c`
## Generate Keys
You'll need an account private and public key pair \(a.k.a. `sk, pk` respectively\) to be able to receive funds, send txs, bond tx, etc.
To generate a new key \(default _ed25519_ elliptic curve\):
```bash
gaiacli keys add <account_name>
```
Next, you will have to create a passphrase to protect the key on disk. The output of the above command will contain a _seed phrase_. Save the _seed phrase_ in a safe place in case you forget the password!
If you check your private keys, you'll now see `<account_name>`:
```bash
gaiacli keys show <account_name>
```
You can see all your available keys by typing:
```bash
gaiacli keys list
```
View the validator pubkey for your node by typing:
```bash
gaiad tendermint show_validator
```
::: danger Warning
We strongly recommend *NOT* using the same passphrase for multiple keys. The Tendermint team and the Interchain Foundation will not be responsible for the loss of funds.
:::
## Get Tokens
The best way to get tokens is from the [Cosmos Testnet Faucet](https://faucetcosmos.network). If the faucet is not working for you, try asking [#cosmos-validators](https://riot.im/app/#/room/#cosmos-validators:matrix.org). The faucet needs the `cosmosaccaddr` from the account you wish to use for staking.
After receiving tokens to your address, you can view your account's balance by typing:
```bash
gaiacli account <account_cosmosaccaddr>
```
::: warning Note
When you query an account balance with zero tokens, you will get this error: `No account with address <account_cosmosaccaddr> was found in the state.` This can also happen if you fund the account before your node has fully synced with the chain. These are both normal.
We're working on improving our error messages!
:::
## Send Tokens
```bash
gaiacli send \
--amount=10faucetToken \
--chain-id=gaia-6002 \
--name=<key_name> \
--to=<destination_cosmosaccaddr>
```
::: warning Note
The `--amount` flag accepts the format `--amount=<value|coin_name>`.
:::
Now, view the updated balances of the origin and destination accounts:
```bash
gaiacli account <account_cosmosaccaddr>
gaiacli account <destination_cosmosaccaddr>
```
You can also check your balance at a given block by using the `--block` flag:
```bash
gaiacli account <account_cosmosaccaddr> --block=<block_height>
```
## Delegate
On the upcoming mainnet, you can delegate `atom` to a validator. These [delegators](/resources/delegators-faq) can receive part of the validator's fee revenue. Read more about the [Cosmos Token Model](https://github.com/cosmos/cosmos/raw/master/Cosmos_Token_Model.pdf).
### Bond Tokens
On the testnet, we delegate `steak` instead of `atom`. Here's how you can bond tokens to a testnet validator:
```bash
gaiacli stake delegate \
--amount=10steak \
--address-delegator=<account_cosmosaccaddr> \
--address-validator=$(gaiad tendermint show_validator) \
--name=<key_name> \
--chain-id=gaia-6002
```
While tokens are bonded, they are pooled with all the other bonded tokens in the network. Validators and delegators obtain a percentage of shares that equal their stake in this pool.
::: tip Note
Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)!
:::
### Unbond Tokens
If for any reason the validator misbehaves, or you want to unbond a certain amount of tokens, use this following command. You can unbond a specific amount of`shares`\(eg:`12.1`\) or all of them \(`MAX`\).
```bash
gaiacli stake unbond \
--address-delegator=<account_cosmosaccaddr> \
--address-validator=$(gaiad tendermint show_validator) \
--shares=MAX \
--name=<key_name> \
--chain-id=gaia-6002
```
You can check your balance and your stake delegation to see that the unbonding went through successfully.
```bash
gaiacli account <account_cosmosaccaddr>
gaiacli stake delegation \
--address-delegator=<account_cosmosaccaddr> \
--address-validator=$(gaiad tendermint show_validator) \
--chain-id=gaia-6002
```

19
docs/sdk/modules.md Normal file
View File

@ -0,0 +1,19 @@
# Modules
::: tip Note
🚧 We are actively working on documentation for SDK modules.
:::
The Cosmos SDK has all the necessary pre-built modules to add functionality on top of a `BaseApp`, which is the template to build a blockchain dApp in Cosmos. In this context, a `module` is a fundamental unit in the Cosmos SDK.
Each module is an extension of the `BaseApp`'s functionalities that defines transactions, handles application state and manages the state transition logic. Each module also contains handlers for messages and transactions, as well as REST and CLI for secure user interactions.
Some of the most important modules in the SDK are:
- `Auth`: Defines a standard account structure (`BaseAccount`) and how transaction signers are authenticated.
- `Bank`: Defines how coins or tokens (i.e cryptocurrencies) are transferred.
- `Fees`:
- `Governance`: Governance related implementation including proposals and voting.
- `IBC`: Defines the intereoperability of blockchain zones according to the specifications of the [IBC Protocol](https://cosmos.network/whitepaper#inter-blockchain-communication-ibc).
- `Slashing`:
- `Staking`: Proof of Stake related implementation including bonding and delegation transactions, inflation, fees, unbonding, etc.

203
docs/sdk/overview.md Normal file
View File

@ -0,0 +1,203 @@
# Cosmos SDK Overview
The Cosmos-SDK is a framework for building Tendermint ABCI applications in
Golang. It is designed to allow developers to easily create custom interoperable
blockchain applications within the Cosmos Network.
To achieve its goals of flexibility and security, the SDK makes extensive use of
the [object-capability
model](https://en.wikipedia.org/wiki/Object-capability_model)
and the [principle of least
privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege).
For an introduction to object-capabilities, see this [article](http://habitatchronicles.com/2017/05/what-are-capabilities/).
## Languages
The Cosmos-SDK is currently written in [Golang](https://golang.org/), though the
framework could be implemented similarly in other languages.
Contact us for information about funding an implementation in another language.
## Directory Structure
The SDK is laid out in the following directories:
- `baseapp`: Defines the template for a basic [ABCI](https://cosmos.network/whitepaper#abci) application so that your Cosmos-SDK application can communicate with the underlying Tendermint node.
- `client`: CLI and REST server tooling for interacting with SDK application.
- `examples`: Examples of how to build working standalone applications.
- `server`: The full node server for running an SDK application on top of
Tendermint.
- `store`: The database of the SDK - a Merkle multistore supporting multiple types of underling Merkle key-value stores.
- `types`: Common types in SDK applications.
- `x`: Extensions to the core, where all messages and handlers are defined.
## Object-Capability Model
When thinking about security, it's good to start with a specific threat
model. Our threat model is the following:
> We assume that a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules.
The Cosmos-SDK is designed to address this threat by being the
foundation of an object capability system.
> The structural properties of object capability systems favor
> modularity in code design and ensure reliable encapsulation in
> code implementation.
>
> These structural properties facilitate the analysis of some
> security properties of an object-capability program or operating
> system. Some of these — in particular, information flow properties
> — can be analyzed at the level of object references and
> connectivity, independent of any knowledge or analysis of the code
> that determines the behavior of the objects.
>
> As a consequence, these security properties can be established
> and maintained in the presence of new objects that contain unknown
> and possibly malicious code.
>
> These structural properties stem from the two rules governing
> access to existing objects:
>
> 1. An object A can send a message to B only if object A holds a
> reference to B.
> 2. An object A can obtain a reference to C only
> if object A receives a message containing a reference to C. As a
> consequence of these two rules, an object can obtain a reference
> to another object only through a preexisting chain of references.
> In short, "Only connectivity begets connectivity."
See the [wikipedia
article](https://en.wikipedia.org/wiki/Object-capability_model) on the Object Capability Model for more
information.
Strictly speaking, Golang does not implement object capabilities
completely, because of several issues:
- pervasive ability to import primitive modules (e.g. "unsafe", "os")
- pervasive ability to override module vars <https://github.com/golang/go/issues/23161>
- data-race vulnerability where 2+ goroutines can create illegal interface values
The first is easy to catch by auditing imports and using a proper
dependency version control system like Dep. The second and third are
unfortunate but it can be audited with some cost.
Perhaps [Go2 will implement the object capability
model](https://github.com/golang/go/issues/23157).
### What does it look like?
Only reveal what is necessary to get the work done.
For example, the following code snippet violates the object capabilities
principle:
```go
type AppAccount struct {...}
var account := &AppAccount{
Address: pub.Address(),
Coins: sdk.Coins{{"ATM", 100}},
}
var sumValue := externalModule.ComputeSumValue(account)
```
The method `ComputeSumValue` implies a pure function, yet the implied
capability of accepting a pointer value is the capability to modify that
value. The preferred method signature should take a copy instead.
```go
var sumValue := externalModule.ComputeSumValue(*account)
```
In the Cosmos SDK, you can see the application of this principle in the
basecoin examples folder.
```go
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
package app
import (
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/sketchy"
)
func (app *BasecoinApp) initRouterHandlers() {
// All handlers must be added here.
// The order matters.
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
app.router.AddRoute("sketchy", sketchy.NewHandler())
}
```
In the Basecoin example, the sketchy handler isn't provided an account
mapper, which does provide the bank handler with the capability (in
conjunction with the context of a transaction run).
## Application Architecture
The SDK has multiple levels of "application": the ABCI app, the BaseApp, the BasecoinApp, and now your App.
### ABCI App
The basic ABCI interface allowing Tendermint to drive the applications state machine with transaction blocks.
### BaseApp
Implements an ABCI App using a MultiStore for persistence and a Router to handle transactions.
The goal is to provide a secure interface between the store and the extensible state machine
while defining as little about that state machine as possible (staying true to the ABCI).
BaseApp requires stores to be mounted via capabilities keys - handlers can only access
stores they're given the key for. The BaseApp ensures all stores are properly loaded, cached, and committed.
One mounted store is considered the "main" - it holds the latest block header, from which we can find and load the most recent state.
BaseApp distinguishes between two handler types - the `AnteHandler` and the `MsgHandler`.
The former is a global validity check (checking nonces, sigs and sufficient balances to pay fees,
e.g. things that apply to all transaction from all modules), the later is the full state transition function.
During CheckTx the state transition function is only applied to the checkTxState and should return
before any expensive state transitions are run (this is up to each developer). It also needs to return the estimated
gas cost.
During DeliverTx the state transition function is applied to the blockchain state and the transactions
need to be fully executed.
BaseApp is responsible for managing the context passed into handlers - it makes the block header available and provides the right stores for CheckTx and DeliverTx. BaseApp is completely agnostic to serialization formats.
### Basecoin
Basecoin is the first complete application in the stack. Complete applications require extensions to the core modules of the SDK to actually implement handler functionality.
The native extensions of the SDK, useful for building Cosmos Zones, live under `x`.
Basecoin implements a `BaseApp` state machine using the `x/auth` and `x/bank` extensions,
which define how transaction signers are authenticated and how coins are transferred.
It should also use `x/ibc` and probably a simple staking extension.
Basecoin and the native `x` extensions use go-amino for all serialization needs,
including for transactions and accounts.
### Your Cosmos App
Your Cosmos App is a fork of Basecoin - copy the `examples/basecoin` directory and modify it to your needs.
You may want to:
- add fields to accounts
- copy and modify handlers
- add new handlers for new transaction types
- add new stores for better isolation across handlers
The Cosmos Hub takes Basecoin and adds more stores and extensions to handle additional
transaction types and logic, like the advanced staking logic and the governance process.
## Ethermint
Ethermint is a new implementation of `BaseApp` that does not depend on Basecoin.
Instead of `cosmos-sdk/x/` it has its own `ethermint/x` based on `go-ethereum`.
Ethermint uses a Patricia store for its accounts, and an IAVL store for IBC.
It has `x/ante`, which is quite similar to Basecoin's but uses RLP instead of go-amino.
Instead of `x/bank`, it has `x/eth`, which defines the single Ethereum transaction type
and all the semantics of the Ethereum state machine.
Within `x/eth`, transactions sent to particular addresses can be handled in unique ways,
for instance to handle IBC and staking.

View File

@ -2,46 +2,69 @@
## State
### Procedures
### Procedures and base types
`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 DepositProcedure struct {
MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period.
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
}
```
```go
type VotingProcedure struct {
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
}
```
```go
type TallyingProcedure struct {
Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto rational.Rational // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
GovernancePenalty sdk.Rat // Penalty if validator does not vote
GracePeriod int64 // If validator entered validator set in this period of blocks before vote ended, governance penalty does not apply
}
```
Procedures are stored in a global `GlobalParams` KVStore.
Additionally, we introduce some basic types:
```go
type VoteType byte
type Vote byte
const (
VoteTypeYes = 0x1
VoteTypeNo = 0x2
VoteTypeNoWithVeto = 0x3
VoteTypeAbstain = 0x4
VoteYes = 0x1
VoteNo = 0x2
VoteNoWithVeto = 0x3
VoteAbstain = 0x4
)
type ProposalType byte
const (
ProposalTypePlainText = 0x1
ProposalTypeSoftwareUpgrade = 0x2
ProposalTypePlainText = 0x1 // Plain text proposals
ProposalTypeSoftwareUpgrade = 0x2 // Text proposal inducing a software upgrade
)
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.
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.
}
```
type ProposalStatus byte
The current active procedure is stored in a global `params` KVStore.
const (
ProposalStatusOpen = 0x1 // Proposal is submitted. Participants can deposit on it but not vote
ProposalStatusActive = 0x2 // MinDeposit is reachhed, participants can vote
ProposalStatusAccepted = 0x3 // Proposal has been accepted
ProposalStatusRejected = 0x4 // Proposal has been rejected
ProposalStatusClosed. = 0x5 // Proposal never reached MinDeposit
)
```
### Deposit
@ -52,18 +75,17 @@ The current active procedure is stored in a global `params` KVStore.
}
```
### Votes
### ValidatorGovInfo
This type is used in a temp map when tallying
```go
type Votes struct {
Yes int64
No int64
NoWithVeto int64
Abstain int64
type ValidatorGovInfo struct {
Minus sdk.Rat
Vote Vote
}
```
### Proposals
`Proposals` are an item to be voted on.
@ -76,37 +98,33 @@ type Proposal struct {
TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit
Deposits []Deposit // List of deposits on the proposal
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
Submitter crypto.Address // Address of the submitter
Submitter sdk.Address // Address of the submitter
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)
InitProcedure Procedure // Active Procedure when proposal enters voting period
CurrentStatus ProposalStatus // Current status of the proposal
Votes Votes // Total votes for each option
YesVotes sdk.Rat
NoVotes sdk.Rat
NoWithVetoVotes sdk.Rat
AbstainVotes sdk.Rat
}
```
We also introduce a type `ValidatorGovInfo`
We also mention a method to update the tally for a given proposal:
```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
}
func (proposal Proposal) updateTally(vote byte, amount sdk.Rat)
```
### Stores
*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list*
*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list*`
We will use one KVStore `Governance` to store two mappings:
* A mapping from `proposalID|'proposal'` to `Proposal`
* A mapping from `proposalID|'addresses'|address` to `Vote`. This mapping allows us to query all addresses that voted on the proposal along with their vote by doing a range query on `proposalID:addresses`
* `Proposals: int64 => Proposal` maps `proposalID` to the `Proposal`
`proposalID`
* `Options: <proposalID | voterAddress | Address> => VoteType`: maps to the vote of the `voterAddress` for `proposalID` re its delegation to `Address`.
Returns 0x0 If `voterAddress` has not voted under this validator.
* `ValidatorGovInfos: <proposalID | Address> => ValidatorGovInfo`: maps to the gov info for the `Address` and `proposalID`.
Returns `nil` if proposal has not entered voting period or if `address` was not the
address of a validator when proposal entered voting period.
For pseudocode purposes, here are the two function we will use to read or write in stores:
@ -119,17 +137,15 @@ For pseudocode purposes, here are the two function we will use to read or write
* `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
`CurrentBlock == VotingStartBlock + activeProcedure.VotingPeriod`. If it is,
then the application tallies the votes, compute the votes of each validator and checks if every validator in the valdiator set have voted
and, if not, applies `GovernancePenalty`. If the proposal is accepted, deposits are refunded.
After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated.
Note that if a proposal is accepted under the special condition,
its `ProposalID` must be ejected from `ProposalProcessingQueue`.
And the pseudocode for the `ProposalProcessingQueue`:
```go
in BeginBlock do
in EndBlock do
checkProposal() // First call of the recursive function
@ -140,58 +156,66 @@ And the pseudocode for the `ProposalProcessingQueue`:
if (proposalID == nil)
return
proposal = load(Proposals, proposalID)
proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key
votingProcedure = load(GlobalParams, 'VotingProcedure')
if (proposal.Votes.YesVotes/proposal.InitTotalVotingPower > 2/3)
if (CurrentBlock == proposal.VotingStartBlock + votingProcedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive)
// proposal accepted early by super-majority
// no punishments; refund deposits
// End of voting period, tally
ProposalProcessingQueue.pop()
var newDeposits []Deposits
// XXX: why do we need to reset deposits? cant we just clear it ?
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
proposal.Deposits = newDeposits
store(Proposals, proposalID, proposal)
checkProposal()
else if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod)
ProposalProcessingQueue.pop()
activeProcedure = load(params, 'ActiveProcedure')
for each validator in CurrentBondedValidators
validatorGovInfo = load(ValidatorGovInfos, <proposalID | validator.Address>)
if (validatorGovInfo.InitVotingPower != nil)
// validator was bonded when vote started
validatorOption = load(Options, <proposalID | validator.Address>)
if (validatorOption == nil)
// validator did not vote
slash validator by activeProcedure.GovernancePenalty
validators =
totalNonAbstain = proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes
if( proposal.Votes.YesVotes/totalNonAbstain > 0.5 AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < 1/3)
Keeper.getAllValidators()
tmpValMap := map(sdk.Address)ValidatorGovInfo
// Initiate mapping at 0. Validators that remain at 0 at the end of tally will be punished
for each validator in validators
tmpValMap(validator).Minus = 0
// Tally
voterIterator = rangeQuery(Governance, <proposalID|'addresses'>) //return all the addresses that voted on the proposal
for each (voterAddress, vote) in voterIterator
delegations = stakeKeeper.getDelegations(voterAddress) // get all delegations for current voter
for each delegation in delegations
// make sure delegation.Shares does NOT include shares being unbonded
tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares
proposal.updateTally(vote, delegation.Shares)
_, isVal = stakeKeeper.getValidator(voterAddress)
if (isVal)
tmpValMap(voterAddress).Vote = vote
tallyingProcedure = load(GlobalParams, 'TallyingProcedure')
// Slash validators that did not vote, or update tally if they voted
for each validator in validators
if (validator.bondHeight < CurrentBlock - tallyingProcedure.GracePeriod)
// only slash if validator entered validator set before grace period
if (!tmpValMap(validator).HasVoted)
slash validator by tallyingProcedure.GovernancePenalty
else
proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus))
// Check if proposal is accepted or rejected
totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes
if (proposal.Votes.YesVotes/totalNonAbstain > tallyingProcedure.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingProcedure.Veto)
// proposal was accepted at the end of the voting period
// refund deposits (non-voters already punished)
var newDeposits []Deposits
proposal.CurrentStatus = ProposalStatusAccepted
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
proposal.Deposits = newDeposits
store(Proposals, proposalID, proposal)
else
// proposal was rejected
proposal.CurrentStatus = ProposalStatusRejected
checkProposal()
store(Governance, <proposalID|'proposal'>, proposal)
checkProposal()
```

View File

@ -12,7 +12,7 @@ type TxGovSubmitProposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type ProposalType // Type of proposal
InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive.
InitialDeposit sdk.Coins // Initial deposit paid by sender. Must be strictly positive.
}
```
@ -22,8 +22,7 @@ type TxGovSubmitProposal struct {
* Initialise `Proposals` attributes
* Decrease balance of sender by `InitialDeposit`
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueueEnd`
* Store each validator's voting power in `ValidatorGovInfos`
* Push `proposalID` in `ProposalProcessingQueue`
A `TxGovSubmitProposal` transaction can be handled according to the following
pseudocode.
@ -34,16 +33,18 @@ pseudocode.
upon receiving txGovSubmitProposal from sender do
if !correctlyFormatted(txGovSubmitProposal) then
if !correctlyFormatted(txGovSubmitProposal)
// check if proposal is correctly formatted. Includes fee payment.
throw
initialDeposit = txGovSubmitProposal.InitialDeposit
if (initialDeposit <= 0) OR (sender.AtomBalance < initialDeposit) then
if (initialDeposit.Atoms <= 0) OR (sender.AtomBalance < initialDeposit.Atoms)
// InitialDeposit is negative or null OR sender has insufficient funds
throw
if (txGovSubmitProposal.Type != ProposalTypePlainText) OR (txGovSubmitProposal.Type != ProposalTypeSoftwareUpgrade)
sender.AtomBalance -= initialDeposit
sender.AtomBalance -= initialDeposit.Atoms
proposalID = generate new proposalID
proposal = NewProposal()
@ -55,38 +56,26 @@ upon receiving txGovSubmitProposal from sender do
proposal.SubmitBlock = CurrentBlock
proposal.Deposits.append({initialDeposit, sender})
proposal.Submitter = sender
proposal.Votes.Yes = 0
proposal.Votes.No = 0
proposal.Votes.NoWithVeto = 0
proposal.Votes.Abstain = 0
proposal.YesVotes = 0
proposal.NoVotes = 0
proposal.NoWithVetoVotes = 0
proposal.AbstainVotes = 0
activeProcedure = load(params, 'ActiveProcedure')
depositProcedure = load(GlobalParams, 'DepositProcedure')
if (initialDeposit < activeProcedure.MinDeposit) then
if (initialDeposit < depositProcedure.MinDeposit)
// MinDeposit is not reached
proposal.VotingStartBlock = -1
proposal.InitTotalVotingPower = 0
proposal.CurrentStatus = ProposalStatusOpen
else
// MinDeposit is reached
proposal.CurrentStatus = ProposalStatusActive
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = new ValidatorGovInfo
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID | validator.Address>, validatorGovInfo)
ProposalProcessingQueue.push(proposalID)
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
store(Proposals, <proposalID|'proposal'>, proposal) // Store proposal in Proposals mapping
return proposalID
```
@ -98,8 +87,8 @@ Once a proposal is submitted, if
```go
type TxGovDeposit struct {
ProposalID int64 // ID of the proposal
Deposit int64 // Number of Atoms to add to the proposal's deposit
ProposalID int64 // ID of the proposal
Deposit sdk.Coins // Number of Atoms to add to the proposal's deposit
}
```
@ -109,7 +98,6 @@ type TxGovDeposit struct {
* Increase `proposal.TotalDeposit` 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.
@ -121,57 +109,43 @@ These checks are outlined in the following pseudocode.
upon receiving txGovDeposit from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit) then
if !correctlyFormatted(txGovDeposit)
throw
proposal = load(Proposals, txGovDeposit.ProposalID)
proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>) // proposal is a const key, proposalID is variable
if (proposal == nil) then
if (proposal == nil)
// There is no proposal for this proposalID
throw
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
// deposit is negative or null OR sender has insufficient funds
throw
activeProcedure = load(params, 'ActiveProcedure')
if (txGovDeposit.Deposit.Atoms <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit.Atoms) OR (proposal.CurrentStatus != ProposalStatusOpen)
// deposit is negative or null
// OR sender has insufficient funds
// OR proposal is not open for deposit anymore
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit was reached
// TODO: shouldnt we do something here ?
throw
depositProcedure = load(GlobalParams, 'DepositProcedure')
if (CurrentBlock >= proposal.SubmitBlock + depositProcedure.MaxDepositPeriod)
proposal.CurrentStatus = ProposalStatusClosed
else
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
// Maximum deposit period reached
throw
// sender can deposit
sender.AtomBalance -= txGovDeposit.Deposit
sender.AtomBalance -= txGovDeposit.Deposit.Atoms
proposal.Deposits.append({txGovVote.Deposit, sender})
proposal.TotalDeposit += txGovDeposit.Deposit
proposal.TotalDeposit.Plus(txGovDeposit.Deposit)
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
if (proposal.TotalDeposit >= depositProcedure.MinDeposit)
// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
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.Address>, validatorGovInfo)
proposal.CurrentStatus = ProposalStatusActive
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
store(Proposals, txGovVote.ProposalID, proposal)
store(Proposals, <txGovVote.ProposalID|'proposal'>, proposal)
```
### Vote
@ -182,27 +156,16 @@ vote on the proposal.
```go
type TxGovVote struct {
ProposalID int64 // proposalID of the proposal
Option string // option chosen by the voter
ValidatorAddress crypto.address // Address of the validator voter wants to tie its vote to
ProposalID int64 // proposalID of the proposal
Vote byte // option from OptionSet chosen by the voter
}
```
**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
votes of `validatorOption` by sender's `voting power`
* If sender is not a validator, increase votes of `txGovVote.Option`
by sender's `voting power`
* If sender is a validator, increase votes of `txGovVote.Option` by
validator's `InitVotingPower - minus` (`minus` can be equal to 0)
* Record `Vote` of sender
*Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker*
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 address as `Address`
Next is a pseudocode proposal of the way `TxGovVote` transactions are
handled:
@ -214,89 +177,22 @@ handled:
upon receiving txGovVote from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit) then
if !correctlyFormatted(txGovDeposit)
throw
proposal = load(Proposals, txGovDeposit.ProposalID)
proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>)
if (proposal == nil) then
if (proposal == nil)
// There is no proposal for this proposalID
throw
validator = load(CurrentValidators, txGovVote.Address)
if (validator == nil) then
// Throws if
// ValidatorAddress is not the address of a current validator
throw
else
option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (option != nil)
// sender has already voted with the Atoms bonded to Address
throw
if (proposal.CurrentStatus == ProposalStatusActive)
if (proposal.VotingStartBlock < 0) OR
(CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.Address) OR
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR
(proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then
// Throws if
// Vote has not started OR if
// Vote had ended OR if
// sender bonded Atoms to Address after start of vote OR if
// sender unbonded Atoms from Address after start of vote OR if
// special condition is met, i.e. proposal is accepted and closed
// Sender can vote if
// Proposal is active
// Sender has some bonds
throw
validatorGovInfo = load(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.Address>)
if (validatorGovInfo == nil)
// validator became validator after proposal entered voting period
throw
// sender can vote, check if sender == validator and store sender's option in Options
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.Address>, txGovVote.Option)
if (sender != validator.address)
// Here, sender is not the Address of the validator whose Address is txGovVote.Address
if sender does not have bonded Atoms to txGovVote.Address then
// check in Staking module
throw
validatorOption = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.Address>)
if (validatorOption == nil)
// Validator has not voted already
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.Address)
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.Address>, validatorGovInfo)
else
// Validator has already voted
// Reduce votes of option chosen by validator by sender's bonded Amount
proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.Address)
// increase votes of option chosen by sender by bonded Amount
senderOption = txGovVote.Option
propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.Address)
store(Proposals, txGovVote.ProposalID, proposal)
else
// sender is the address of the validator whose main Address is txGovVote.Address
// i.e. sender == validator
proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
store(Proposals, txGovVote.ProposalID, proposal)
store(Governance, <txGovVote.ProposalID|'addresses'|sender>, txGovVote.Vote) // Voters can vote multiple times. Re-voting overrides previous vote. This is ok because tallying is done once at the end.
```

View File

@ -6,29 +6,18 @@
- value: `amino(pool)`
The pool is a space for all dynamic global state of the Cosmos Hub. It tracks
information about the total amounts of Atoms in all states, representative
validator shares for stake in the global pools, moving Atom inflation
information, etc.
information about the total amounts of Atoms in all states, moving Atom
inflation information, etc.
```golang
type Pool struct {
LooseTokens int64 // tokens not associated with any validator
UnbondedTokens int64 // reserve of unbonded tokens held with validators
UnbondingTokens int64 // tokens moving from bonded to unbonded pool
LooseTokens int64 // tokens not associated with any bonded validator
BondedTokens int64 // reserve of bonded tokens
UnbondedShares sdk.Rat // sum of all shares distributed for the Unbonded Pool
UnbondingShares sdk.Rat // shares moving from Bonded to Unbonded Pool
BondedShares sdk.Rat // sum of all shares distributed for the Bonded Pool
InflationLastTime int64 // block which the last inflation was processed // TODO make time
Inflation sdk.Rat // current annual inflation rate
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily)
}
type PoolShares struct {
Status sdk.BondStatus // either: unbonded, unbonding, or bonded
Amount sdk.Rat // total shares of type ShareKind
}
```
### Params
@ -85,7 +74,8 @@ type Validator struct {
ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator
Revoked bool // has the validator been revoked?
PoolShares PoolShares // total shares for tokens held in the pool
Status sdk.BondStatus // validator status (bonded/unbonding/unbonded)
Tokens sdk.Rat // delegated tokens (incl. self-delegation)
DelegatorShares sdk.Rat // total shares issued to a validator's delegators
SlashRatio sdk.Rat // increases each time the validator is slashed
@ -100,7 +90,7 @@ type Validator struct {
ProposerRewardPool sdk.Coins // reward pool collected from being the proposer
// TODO: maybe this belongs in distribution module ?
PrevPoolShares PoolShares // total shares of a global hold pools
LastBondedTokens sdk.Rat // last bonded token amount
}
type CommissionInfo struct {

View File

@ -0,0 +1,34 @@
# Validators Overview
## Introduction
The [Cosmos Hub](/introduction/cosmos-hub.md) is based on [Tendermint](/introduction/tendermint.md), which relies on a set of validators that are responsible for committing new blocks in the blockchain. These validators participate in the consensus protocol by broadcasting votes which contain cryptographic signatures signed by each validator's private key.
Validator candidates can bond their own Atoms and have Atoms ["delegated"](/staking/delegators), or staked, to them by token holders. The Cosmos Hub will have 100 validators, but over time this will increase to 300 validators according to a predefined schedule. The validators are determined by who has the most stake delegated to themthe top 100 validator candidates with the most stake will become Cosmos validators.
Validators and their delegators will earn Atoms as block provisions and tokens as transaction fees through execution of the Tendermint consensus protocol. Initially, transaction fees will be paid in Atoms but in the future, any token in the Cosmos ecosystem will be valid as fee tender if it is whitelisted by governance. Note that validators can set commission on the fees their delegators receive as additional incentive.
If validators double sign, are frequently offline or do not participate in governance, their staked Atoms (including Atoms of users that delegated to them) can be slashed. The penalty depends on the severity of the violation.
## Hardware
There currently exists no appropriate cloud solution for validator key management. This may change in 2018 when cloud SGX becomes more widely available. For this reason, validators must set up a physical operation secured with restricted access. A good starting place, for example, would be co-locating in secure data centers.
Validators should expect to equip their datacenter location with redundant power, connectivity, and storage backups. Expect to have several redundant networking boxes for fiber, firewall and switching and then small servers with redundant hard drive and failover. Hardware can be on the low end of datacenter gear to start out with.
We anticipate that network requirements will be low initially. The current testnet requires minimal resources. Then bandwidth, CPU and memory requirements will rise as the network grows. Large hard drives are recommended for storing years of blockchain history.
## Set Up a Website
Set up a dedicated validator's website and signal your intention to become a validator on our [forum](https://forum.cosmos.network/t/validator-candidates-websites/127/3). This is important since delegators will want to have information about the entity they are delegating their Atoms to.
## Seek Legal Advice
Seek legal advice if you intend to run a Validator.
## Community
Discuss the finer details of being a validator on our community chat and forum:
* [Validator Chat](https://riot.im/app/#/room/#cosmos_validators:matrix.org)
* [Validator Forum](https://forum.cosmos.network/c/validating)

View File

@ -0,0 +1,41 @@
## Overview
Each validator candidate is encouraged to run its operations independently, as diverse setups increase the resilience of the network. Validator candidates should commence their setup phase now in order to be on time for launch.
## Key management - HSM
It is mission critical that an attacker cannot steal a validator's key. If this is possible, it puts the entire stake delegated to the compromised validator at risk. Hardware security modules are an important strategy for mitigating this risk.
HSM modules must support `ed25519` signatures for the hub. The YubiHSM2 supports `ed25519` and we expect to have an adapter library available in December 2017. The YubiHSM can protect a private key but cannot ensure in a secure setting that it won't sign the same block twice.
The Tendermint team is also working on extending our Ledger Nano S application to support validator signing. This app can store recent blocks and mitigate double signing attacks.
We will update this page when more key storage solutions become available.
## Sentry Nodes (DDOS Protection)
Validators are responsible for ensuring that the network can sustain denial of service attacks.
One recommended way to mitigate these risks is for validators to carefully structure their network topology in a so-called sentry node architecture.
Validator nodes should only connect to full-nodes they trust because they operate them themselves or are run by other validators they know socially. A validator node will typically run in a data center. Most data centers provide direct links the networks of major cloud providers. The validator can use those links to connect to sentry nodes in the cloud. This shifts the burden of denial-of-service from the validator's node directly to its sentry nodes, and may require new sentry nodes be spun up or activated to mitigate attacks on existing ones.
Sentry nodes can be quickly spun up or change their IP addresses. Because the links to the sentry nodes are in private IP space, an internet based attacked cannot disturb them directly. This will ensure validator block proposals and votes always make it to the rest of the network.
To setup your sentry node architecture you can follow the instructions below:
Validators nodes should edit their config.toml:
```bash
# Comma separated list of nodes to keep persistent connections to
# Do not add private peers to this list if you don't want them advertised
persistent_peers =[list of sentry nodes]
# Set true to enable the peer-exchange reactor
pex = false
```
Sentry Nodes should edit their config.toml:
```bash
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
private_peer_ids = "ipaddress of validator nodes"
```

View File

@ -0,0 +1,278 @@
# Validator FAQ
::: warning Disclaimer
This is work in progress. Mechanisms and values are susceptible to change.
:::
## General Concepts
### What is a validator?
The [Cosmos Hub](/introduction/cosmos-hub.md) is based on [Tendermint](/introduction/tendermint.md), which relies on a set of [validators](/validators/overview.md) to secure the network. The role of validators is to run a full-node and participate in consensus by broadcasting votes which contain cryptographic signatures signed by their private key. Validators commit new blocks in the blockchain and receive revenue in exchange for their work. They must also participate in governance by voting on proposals. Validators are weighted according to their total stake.
### What is 'staking'?
The Cosmos Hub is a public Proof-Of-Stake (PoS) blockchain, meaning that validator's weight is determined by the amount of staking tokens (Atoms) bonded as collateral. These Atoms can be staked directly by the validator or delegated to them by Atom holders.
Any user in the system can declare its intention to become a validator by sending a "declare-candidacy" transaction. From there, they become validator candidates.
The weight (i.e. total stake) of a candidate determines wether or not it is a validator, and also how frequently this node will have to propose a block and how much revenue it will obtain. Initially, only the top 100 validator candidates with the most weight will be validators. If validators double sign, are frequently offline or do not participate in governance, their staked Atoms (including Atoms of users that delegated to them) can be destroyed, or 'slashed'.
### What is a full-node?
A full-node is a program that fully validates transactions and blocks of a blockchain. It is distinct from a light-node that only processes block headers and a small subset of transactions. Running a full-node requires more resources than a light-node but is necessary in order to be a validator. In practice, running a full-node only implies running a non-compromised and up-to-date version of the software with low network latency and without downtime.
Of course, it is possible and encouraged for any user to run full-nodes even if they do not plan to be validators.
### What is a delegator?
Delegators are Atom holders who cannot, or do not want to run validator operations themselves. Through [Cosmos Voyager](/getting-started/voyager.md), a user can delegate Atoms to a validator and obtain a part of its revenue in exchange (for more detail on how revenue is distributed, see **What is the incentive to stake?** and **What is a validator's commission?** sections below).
Because they share revenue with their validators, delegators also share responsibility. Should a validator misbehave, each of its delegators will be partially slashed in proportion to their stake. This is why delegators should perform due diligence on validator candidates before delegating, as well as spreading their stake over multiple validators.
Delegators play a critical role in the system, as they are responsible for choosing validators. Being a delegator is not a passive role: Delegators should actively monitor the actions of their validators and participate in governance.
## Becoming a Validator
### How to become a validator?
Any participant in the network can signal that they want to become a validator by sending a "declare-candidacy" transaction, where they must fill out the following parameters:
* Validator's PubKey: The validator must signal an account with which it will perform its validator duties. The private key associated with PubKey is used to sign _prevotes_ and _precommits_. This way, validators can have different accounts for validating and holding liquid funds.
* Validator's name
* Validator's website (Optional)
* Validator's description (Optional)
* Initial commission rate: The commission rate on block provisions, block rewards and fees charged to delegators
* Maximum commission: The maximum commission rate which this validator candidate can charge
* Commission change rate: The maximum daily increase of the validator candidate commission
* Minimum self-bond amount: Minimum amount of Atoms the validator candidate need to have bonded at all time. If the validator's self-bonded stake falls below this limit, its entire staking pool will unbond.
* Initial self-bond amount: Initial amount Atoms the validator candidate wants to self-bond
Once a PubKey has declared candidacy, Atom holders can delegate atoms to it, effectively adding stake to this pool. The total stake of an address is the combination of Atoms bonded by delegators and Atoms self-bonded by the entity which designated itself.
Out of all the candidates that signaled themselves, the 100 with the most stake are the ones who are designated as validators. If a validator's total stake falls below the top 100 then that validator loses its validator privileges. Over time, the maximum number of validators will increase, according to a predefined schedule:
* **Year 0:** 100
* **Year 1:** 113
* **Year 2:** 127
* **Year 3:** 144
* **Year 4:** 163
* **Year 5:** 184
* **Year 6:** 208
* **Year 7:** 235
* **Year 8:** 265
* **Year 9:** 300
* **Year 10:** 300
### How can I join the testnet?
The Testnet is a great environment to test your validator setup before launch.
We view testnet participation as a great way to signal to the community that you are ready and able to operate a validator. You can find all relevant information about the [testnet and more here](/getting-started/full-node.md).
### Is there a faucet?
If you want to obtain coins for the testnet, you can do so by using [this faucet](https://faucetcosmos.network)
### Is there a minimum amount of Atoms that must be staked to be a validator?
There is no minimum. The top 100 validator candidates with the highest total stake (where total stake = self-bonded stake + delegators stake) are the validators.
### How will delegators choose their validators?
Delegators are free to choose validators according to their own subjective criteria. This said, criteria anticipated to be important include:
* **Amount of self-bonded Atoms:** Number of Atoms a validator self-bonded to its staking pool. A validator with higher amount of self-bonded Atoms has more skin in the game, making it more liable for its actions.
* **Amount of delegated Atoms:** Total number of Atoms delegated to a validator. A high stake shows that the community trusts this validator, but it also means that this validator is a bigger target for hackers. Indeed, hackers are incentivized to hack bigger validators as they receive a reward proportionate to the stake of the validator they can prove to have compromised. Validators are expected to become less and less attractive as their amount of delegated Atoms grows.
* **Commission rate:** Commission applied on revenue by validators before it is distributed to their delegators
* **Track record:** Delegators will likely look at the track record of the validators they plan to delegate to. This includes seniority, past votes on proposals, historical average uptime and how often the node was compromised.
Apart from these criteria that will be displayed in Cosmos Voyager, there will be a possibility for validators to signal a website address to complete their resume. Validators will need to build reputation one way or another to attract delegators. For example, it would be a good practice for validators to have their setup audited by third parties. Note though, that the Tendermint team will not approve or conduct any audit itself.
## Responsibilites
### Do validators need to be publicly identified?
No, they do not. Each delegator will value validators based on their own criteria. Validators will be able (and are advised) to register a website address when they nominate themselves so that they can advertise their operation as they see fit. Some delegators may prefer a website that clearly displays the team running the validator and their resume, while others might prefer anonymous validators with positive track records. Most likely both identified and anonymous validators will coexist in the validator set.
### What are the responsiblities of a validator?
Validators have two main responsibilities:
* **Be able to constantly run a correct version of the software:** validators need to make sure that their servers are always online and their private keys are not compromised.
* **Actively participate in governance:** validators are required to vote on every proposal.
Additionally, validators are expected to be active members of the community. They should always be up-to-date with the current state of the ecosystem so that they can easily adapt to any change.
### What does 'participate in governance' entail?
Validators and delegators on the Cosmos Hub can vote on proposals to change operational parameters (such as the block gas limit), coordinate upgrades, as well as vote on amendments to the human-readable constitution that govern the Cosmos Hub.
Validators play a special role in the governance system. Being the pillars of the system, they are required to vote on every proposal. It is especially important since delegators who do not vote will inherit the vote of their validator. Each time a validator does not vote on a proposal, it will get slashed by a minimal amount.
### What does staking imply?
Staking Atoms can be thought of as a safety deposit on validation activities. When a validator or a delegator wants to retrieve part or all of their deposit, they send an unbonding transaction. Then, Atoms undergo a _three weeks unbonding period_ during which they are liable to being slashed for potential misbehaviors committed by the validator before the unbonding process started.
Validators, and by association delegators, receive block provisions, block rewards, fee rewards, and the right to participate in governance. If a validator misbehaves, a certain portion of its total stake is slashed (the severity of the penalty depends on the type of misbehavior). This means that every user that bonded Atoms to this validator gets penalized in proportion to its stake. Delegators are therefore incentivized to delegate to validators that they anticipate will function safely.
### Can a validator run away with its delegators' Atoms?
By delegating to a validator, a user delegates staking power. The more staking power a validator has, the more weight it has in the consensus and governance processes. This does not mean that the validator has custody of its delegators' Atoms. _By no means can a validator run away with its delegator's funds_.
Even though delegated funds cannot be stolen by their validators, delegators are still liable if their validators misbehave. In such case, each delegators' stake will be partially slashed in proportion to their relative stake.
### How often will a validator be chosen to propose the next block? Does it go up with the quantity of Atoms staked?
The validator that is selected to propose the next block is called proposer. Each proposer is selected deterministically, and the frequency of being chosen is equal to the relative total stake (where total stake = self-bonded stake + delegators stake) of the validator. For example, if the total bonded stake across all validators is 100 Atoms and a validator's total stake is 10 Atoms, then this validator will be chosen 10% of the time as the next proposer.
### Will validators of the Cosmos Hub ever be required to validate other zones in the Cosmos ecosystem?
Yes, they will. Initially, validators of the Cosmos hub will also validate the first public Ethermint zone. If governance decides so, validators of the Cosmos hub may be required to validate additional zones in the Cosmos ecosystem. As the case with the Ethermint Zone, for each additional zone compensation is to be provided in the form of block rewards and transaction fees.
## Incentives
### What is the incentive to stake?
Each member of a validator's staking pool earns different types of revenue:
* **Block provisions:** Native tokens of applications run by validators (e.g. Atoms on the Cosmos Hub) are inflated to produce block provisions. These provisions exist to incentivize Atom holders to bond their stake, as non-bonded Atom will be diluted over time.
* **Block rewards:** For the Ethermint zone, block rewards are paid in Photons. Initial distribution of Photons will be hard spooned from Ethereum. This means Photons will be emitted 1:1 to Ether.
* **Transaction fees:** The Cosmos Hub maintains a whitelist of token that are accepted as fee payment.
This total revenue is divided among validators' staking pools according to each validator's weight. Then, within each validator's staking pool the revenue is divided among delegators in proportion to each delegator's stake. Note that a commission on delegators' revenue is applied by the validator before it is distributed.
### What is the incentive to run a validator ?
Validators earn proportionally more revenue than their delegators because of commissions.
Validators also play a major role in governance. If a delegator does not vote, it inherits the vote from its validator. This gives validators a major responsibility in the ecosystem.
### What is a validator's commission?
Revenue received by a validator's pool is split between the validator and its delegators. The validator can apply a commission on the part of the revenue that goes to its delegators. This commission is set as a percentage. Each validator is free to set its initial commission, maximum daily commission change rate and maximum commission. The Cosmos Hub enforces the parameter that each validator sets. These parameters can only be defined when initially declaring candidacy, and may only be constrained further after being declared.
### How are block provisions distributed?
Block provisions are distributed proportionally to all validators relative to their total stake. This means that even though each validator gains atoms with each provision, all validators will still maintain equal weight.
Let us take an example where we have 10 validators with equal staking power and a commission rate of 1%. Let us also assume that the provision for a block is 1000 Atoms and that each validator has 20% of self-bonded Atoms. These tokens do not go directly to the proposer. Instead, they are evenly spread among validators. So now each validator's pool has 100 Atoms. These 100 Atoms will be distributed according to each participant's stake:
* Commission: `100*80%*1% = 0.8 Atoms`
* Validator gets: `100\*20% + Commission = 20.8 Atoms`
* All delegators get: `100\*80% - Commission = 79.2 Atoms`
Then, each delegator can claim its part of the 79.2 Atoms in proportion to their stake in the validator's staking pool. Note that the validator's commission is not applied on block provisions. Note that block rewards (paid in Photons) are distributed according to the same mechanism.
### How are fees distributed?
Fees are similarly distributed with the exception that the block proposer can get a bonus on the fees of the block it proposes if it includes more than the strict minimum of required precommits.
When a validator is selected to propose the next block, it must include at least 2/3 precommits for the previous block in the form of validator signatures. However, there is an incentive to include more than 2/3 precommits in the form of a bonus. The bonus is linear: it ranges from 1% if the proposer includes 2/3rd precommits (minimum for the block to be valid) to 5% if the proposer includes 100% precommits. Of course the proposer should not wait too long or other validators may timeout and move on to the next proposer. As such, validators have to find a balance between wait-time to get the most signatures and risk of losing out on proposing the next block. This mechanism aims to incentivize non-empty block proposals, better networking between validators as well as to mitigate censorship.
Let's take a concrete example to illustrate the aforementioned concept. In this example, there are 10 validators with equal stake. Each of them applies a 1% commission and has 20% of self-bonded Atoms. Now comes a successful block that collects a total of 1025.51020408 Atoms in fees.
First, a 2% tax is applied. The corresponding Atoms go to the reserve pool. Reserve pool's funds can be allocated through governance to fund bounties and upgrades.
* `2% \* 1025.51020408 = 20.51020408` Atoms go to the reserve pool.
1005 Atoms now remain. Let's assume that the proposer included 100% of the signatures in its block. It thus obtains the full bonus of 5%.
We have to solve this simple equation to find the reward R for each validator:
`9*R + R + R*5% = 1005 ⇔ R = 1005/10.05 = 100`
* For the proposer validator:
* The pool obtains `R + R * 5%`: 105 Atoms
* Commission: `105 * 80% * 1%` = 0.84 Atoms
* Validator's reward: `100 * 20% + Commission` = 21.84 Atoms
* Delegators' rewards: `105 * 80% - Commission` = 83.16 Atoms (each delegator will be able to claim its portion of these rewards in proportion to their stake)
* For each non-proposer validator:
* The pool obtains R: 100 Atoms
* Commission: `100 * 80% * 1%` = 0.8 Atoms
* Validator's reward: `105 * 20% + Commission` = 20.8 Atoms
* Delegators' rewards: `100 * 80% - Commission` = 79.2 Atoms (each delegator will be able to claim its portion of these rewards in proportion to their stake)
### What are the slashing conditions?
If a validator misbehaves, its bonded stake along with its delegators' stake and will be slashed. The severity of the punishment depends on the type of fault. There are 3 main faults that can result in slashing of funds for a validator and its delegators:
* **Double signing:** If someone reports on chain A that a validator signed two blocks at the same height on chain A and chain B, this validator will get slashed on chain A
* **Unavailability:** If a validator's signature has not been included in the last X blocks, the validator will get slashed by a marginal amount proportional to X. If X is above a certain limit Y, then the validator will get unbonded
* **Non-voting:** If a validator did not vote on a proposal and once the fault is reported by a someone, its stake will receive a minor slash.
Note that even if a validator does not intentionally misbehave, it can still be slashed if its node crashes, looses connectivity, gets DDOSed, or if its private key is compromised. A complete document on the economics of the network will be published soon.
### Do validators need to self-bond Atoms?
No, they do not. A validators total stake is equal to the sum of its own self-bonded stake and of its delegated stake. This means that a validator can compensate its low amount of self-bonded stake by attracting more delegators. This is why reputation is very important for validators.
Even though there is no obligation for validators to self-bond Atoms, delegators should want their validator to have self-bonded Atoms in their staking pool. In other words, validators should have skin in the game.
In order for delegators to have some guarantee about how much skin-in-the-game their validator has, the latter can signal a minimum amount of self-bonded Atoms. If a validator's self-bond goes below the limit that it predefined, this validator and all of its delegators will unbond.
### How to prevent concentration of stake in the hands of a few top validators?
For now the community is expected to behave in a smart and self-preserving way. When a mining pool in Bitcoin gets too much mining power the community usually stops contributing to that pool. The Cosmos Hub will rely on the same effect initially. In the future, other mechanisms will be deployed to smoothen this process as much as possible:
* **Penalty-free re-delegation:** This is to allow delegators to easily switch from one validator to another, in order to reduce validator stickiness.
* **Hack bounty:** This is an incentive for the community to hack validators. There will be bounties proportionate to the size of the validator, so that a validator becomes a bigger target as its stake grows.
* **UI warning:** Users will be warned by Cosmos Voyager if they want to delegate to a validator that already has a significant amount of staking power.
## Technical Requirements
### What are hardware requirements?
Validators should expect to provision one or more data center locations with redundant power, networking, firewalls, HSMs and servers.
We expect that a modest level of hardware specifications will be needed initially and that they might rise as network use increases. Participating in the testnet is the best way to learn more.
### What are software requirements?
In addition to running a Cosmos Hub node, validators should develop monitoring, alerting and management solutions.
### What are bandwidth requirements?
The Cosmos network has the capacity for very high throughput relative to chains like Ethereum or Bitcoin.
We recommend that the data center nodes only connect to trusted full-nodes in the cloud or other validators that know each other socially. This relieves the data center node from the burden of mitigating denial-of-service attacks.
Ultimately, as the network becomes more heavily used, multigigabyte per day bandwidth is very realistic.
### What does running a validator imply in terms of logistics?
A successful validator operation will require the efforts of multiple highly skilled individuals and continuous operational attention. This will be considerably more involved than running a bitcoin miner for instance.
### How to handle key management?
Validators should expect to run an HSM that supports ed25519 keys. Here are potential options:
* YubiHSM 2
* Ledger Nano S
* Ledger BOLOS SGX enclave
* Thales nShield support
* Tendermint SGX enclave
The Tendermint team does not recommend one solution above the other. The community is encouraged to bolster the effort to improve HSMs and the security of key management.
### What can validators expect in terms of operations?
Running effective operation is the key to avoiding unexpectedly unbonding or being slashed. This includes being able to respond to attacks, outages, as well as to maintain security and isolation in your data center.
### What are the maintenance requirements?
Validators should expect to perform regular software updates to accommodate upgrades and bug fixes. There will inevitably be issues with the network early in its bootstrapping phase that will require substantial vigilance.
### How can validators protect themselves from denial-of-service attacks?
Denial-of-service attacks occur when an attacker sends a flood of internet traffic to an IP address to prevent the server at the IP address from connecting to the internet.
An attacker scans the network, tries to learn the IP address of various validator nodes and disconnect them from communication by flooding them with traffic.
One recommended way to mitigate these risks is for validators to carefully structure their network topology in a so-called sentry node architecture.
Validator nodes should only connect to full-nodes they trust because they operate them themselves or are run by other validators they know socially. A validator node will typically run in a data center. Most data centers provide direct links the networks of major cloud providers. The validator can use those links to connect to sentry nodes in the cloud. This shifts the burden of denial-of-service from the validator's node directly to its sentry nodes, and may require new sentry nodes be spun up or activated to mitigate attacks on existing ones.
Sentry nodes can be quickly spun up or change their IP addresses. Because the links to the sentry nodes are in private IP space, an internet based attacked cannot disturb them directly. This will ensure validator block proposals and votes always make it to the rest of the network.
It is expected that good operating procedures on that part of validators will completely mitigate these threats.

View File

@ -0,0 +1,129 @@
# Validator Setup
Before setting up your validator node, make sure you've already gone through the [Full Node Setup](/getting-started/full-node.md) guide.
## Running a Validator Node
[Validators](/validators/overview.md) are responsible for committing new blocks to the blockchain through voting. A validator's stake is slashed if they become unavailable, double sign a transaction, or don't cast their votes. Please read about [Sentry Node Architecture](/validators/validator-faq.md#how-can-validators-protect-themselves-from-denial-of-service-attacks) to protect your node from DDOS attacks and to ensure high-availability.
::: danger Warning
If you want to become a validator for the Hub's `mainnet`, you should [research security](/validators/security.md).
:::
### Create Your Validator
Your `cosmosvalpub` can be used to create a new validator by staking tokens. You can find your validator pubkey by running:
```bash
gaiad tendermint show_validator
```
Next, craft your `gaiacli stake create-validator` command:
::: warning Note
Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)!
:::
```bash
gaiacli stake create-validator \
--amount=5steak \
--pubkey=$(gaiad tendermint show_validator) \
--address-validator=<account_cosmosaccaddr>
--moniker="choose a moniker" \
--chain-id=gaia-6002 \
--name=<key_name>
```
### Edit Validator Description
You can edit your validator's public description. This info is to identify your validator, and will be relied on by delegators to decide which validators to stake to. Make sure to provide input for every flag below, otherwise the field will default to empty (`--moniker` defaults to the machine name).
The `--keybase-sig` is a 16-digit string that is generated with a [keybase.io](https://keybase.io) account. It's a cryptographically secure method of verifying your identity across multiple online networks. The Keybase API allows us to retrieve your Keybase avatar. This is how you can add a logo to your validator profile.
```bash
gaiacli stake edit-validator
--address-validator=<account_cosmosaccaddr>
--moniker="choose a moniker" \
--website="https://cosmos.network" \
--keybase-sig="6A0D65E29A4CBC8E"
--details="To infinity and beyond!"
--chain-id=gaia-6002 \
--name=<key_name>
```
### View Validator Description
View the validator's information with this command:
```bash
gaiacli stake validator \
--address-validator=<account_cosmosaccaddr> \
--chain-id=gaia-6002
```
### Confirm Your Validator is Running
Your validator is active if the following command returns anything:
```bash
gaiacli advanced tendermint validator-set | grep "$(gaiad tendermint show_validator)"
```
You should also be able to see your validator on the [Explorer](https://explorecosmos.network/validators). You are looking for the `bech32` encoded `address` in the `~/.gaiad/config/priv_validator.json` file.
::: warning Note
To be in the validator set, you need to have more total voting power than the 100th validator.
:::
## Common Problems
### Problem #1: My validator has `voting_power: 0`
Your validator has become auto-unbonded. In `gaia-6002`, we unbond validators if they do not vote on `50` of the last `100` blocks. Since blocks are proposed every ~2 seconds, a validator unresponsive for ~100 seconds will become unbonded. This usually happens when your `gaiad` process crashes.
Here's how you can return the voting power back to your validator. First, if `gaiad` is not running, start it up again:
```bash
gaiad start
```
Wait for your full node to catch up to the latest block. Next, run the following command. Note that `<cosmosaccaddr>` is the address of your validator account, and `<name>` is the name of the validator account. You can find this info by running `gaiacli keys list`.
```bash
gaiacli stake unrevoke <cosmosaccaddr> --chain-id=gaia-6002 --name=<name>
```
::: danger Warning
If you don't wait for `gaiad` to sync before running `unrevoke`, you will receive an error message telling you your validator is still jailed.
:::
Lastly, check your validator again to see if your voting power is back.
```bash
gaiacli status
```
You may notice that your voting power is less than it used to be. That's because you got slashed for downtime!
### Problem #2: My `gaiad` crashes because of `too many open files`
The default number of files Linux can open (per-process) is `1024`. `gaiad` is known to open more than `1024` files. This causes the process to crash. A quick fix is to run `ulimit -n 4096` (increase the number of open files allowed) and then restart the process with `gaiad start`. If you are using `systemd` or another process manager to launch `gaiad` this may require some configuration at that level. A sample `systemd` file to fix this issue is below:
```toml
# /etc/systemd/system/gaiad.service
[Unit]
Description=Cosmos Gaia Node
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu
ExecStart=/home/ubuntu/go/bin/gaiad start
Restart=on-failure
RestartSec=3
LimitNOFILE=4096
[Install]
WantedBy=multi-user.target
```

View File

@ -46,14 +46,14 @@ type BasecoinApp struct {
// In addition, all necessary mappers and keepers are created, routes
// registered, and finally the stores being mounted along with any necessary
// chain initialization.
func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
func NewBasecoinApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseApp)) *BasecoinApp {
// create and register app-level codec for TXs and accounts
cdc := MakeCodec()
// create your application type
var app = &BasecoinApp{
cdc: cdc,
BaseApp: bam.NewBaseApp(appName, cdc, logger, db),
BaseApp: bam.NewBaseApp(appName, cdc, logger, db, baseAppOptions...),
keyMain: sdk.NewKVStoreKey("main"),
keyAccount: sdk.NewKVStoreKey("acc"),
keyIBC: sdk.NewKVStoreKey("ibc"),

View File

@ -2,11 +2,15 @@ package main
import (
"encoding/json"
"io"
"os"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/spf13/cobra"
"github.com/spf13/viper"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/cli"
dbm "github.com/tendermint/tendermint/libs/db"
@ -39,11 +43,11 @@ func main() {
}
}
func newApp(logger log.Logger, db dbm.DB) abci.Application {
return app.NewBasecoinApp(logger, db)
func newApp(logger log.Logger, db dbm.DB, storeTracer io.Writer) abci.Application {
return app.NewBasecoinApp(logger, db, baseapp.SetPruning(viper.GetString("pruning")))
}
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) {
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, storeTracer io.Writer) (json.RawMessage, []tmtypes.GenesisValidator, error) {
bapp := app.NewBasecoinApp(logger, db)
return bapp.ExportAppStateAndValidators()
}

View File

@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"io"
"os"
"github.com/spf13/cobra"
@ -50,11 +51,11 @@ func CoolAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState jso
return
}
func newApp(logger log.Logger, db dbm.DB) abci.Application {
func newApp(logger log.Logger, db dbm.DB, _ io.Writer) abci.Application {
return app.NewDemocoinApp(logger, db)
}
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) {
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer) (json.RawMessage, []tmtypes.GenesisValidator, error) {
dapp := app.NewDemocoinApp(logger, db)
return dapp.ExportAppStateAndValidators()
}

View File

@ -2,7 +2,7 @@
## Requirements
- [Install gaia](/docs/index.rst)
- [Install gaia](https://cosmos.network/docs/getting-started/installation.html)
- [Install docker](https://docs.docker.com/engine/installation/)
- [Install docker-compose](https://docs.docker.com/compose/install/)
@ -23,7 +23,6 @@ make build-linux
make build-docker-gaiadnode
```
## Run a testnet
To start a 4 node testnet run:
@ -66,7 +65,6 @@ docker run -v `pwd`/build:/gaiad tendermint/gaiadnode testnet --o . --v 1
#Run the node
docker run -v `pwd`/build:/gaiad tendermint/gaiadnode
```
## Logging
@ -76,4 +74,3 @@ Log is saved under the attached volume, in the `gaiad.log` file and written on t
## Special binaries
If you have multiple binaries with different names, you can specify which one to run with the BINARY environment variable. The path of the binary is relative to the attached volume.

View File

@ -2,6 +2,8 @@ package server
import (
"encoding/json"
"io"
"os"
"path/filepath"
abci "github.com/tendermint/tendermint/abci/types"
@ -10,34 +12,73 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
)
// AppCreator lets us lazily initialize app, using home dir
// and other flags (?) to start
type AppCreator func(string, log.Logger) (abci.Application, error)
type (
// AppCreator reflects a function that allows us to lazily initialize an
// application using various configurations.
AppCreator func(home string, logger log.Logger, traceStore string) (abci.Application, error)
// AppExporter dumps all app state to JSON-serializable structure and returns the current validator set
type AppExporter func(home string, log log.Logger) (json.RawMessage, []tmtypes.GenesisValidator, error)
// AppExporter reflects a function that dumps all app state to
// JSON-serializable structure and returns the current validator set.
AppExporter func(home string, logger log.Logger, traceStore string) (json.RawMessage, []tmtypes.GenesisValidator, error)
// ConstructAppCreator returns an application generation function
func ConstructAppCreator(appFn func(log.Logger, dbm.DB) abci.Application, name string) AppCreator {
return func(rootDir string, logger log.Logger) (abci.Application, error) {
// AppCreatorInit reflects a function that performs initialization of an
// AppCreator.
AppCreatorInit func(log.Logger, dbm.DB, io.Writer) abci.Application
// AppExporterInit reflects a function that performs initialization of an
// AppExporter.
AppExporterInit func(log.Logger, dbm.DB, io.Writer) (json.RawMessage, []tmtypes.GenesisValidator, error)
)
// ConstructAppCreator returns an application generation function.
func ConstructAppCreator(appFn AppCreatorInit, name string) AppCreator {
return func(rootDir string, logger log.Logger, traceStore string) (abci.Application, error) {
dataDir := filepath.Join(rootDir, "data")
db, err := dbm.NewGoLevelDB(name, dataDir)
if err != nil {
return nil, err
}
app := appFn(logger, db)
var traceStoreWriter io.Writer
if traceStore != "" {
traceStoreWriter, err = os.OpenFile(
traceStore,
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
if err != nil {
return nil, err
}
}
app := appFn(logger, db, traceStoreWriter)
return app, nil
}
}
// ConstructAppExporter returns an application export function
func ConstructAppExporter(appFn func(log.Logger, dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error), name string) AppExporter {
return func(rootDir string, logger log.Logger) (json.RawMessage, []tmtypes.GenesisValidator, error) {
// ConstructAppExporter returns an application export function.
func ConstructAppExporter(appFn AppExporterInit, name string) AppExporter {
return func(rootDir string, logger log.Logger, traceStore string) (json.RawMessage, []tmtypes.GenesisValidator, error) {
dataDir := filepath.Join(rootDir, "data")
db, err := dbm.NewGoLevelDB(name, dataDir)
if err != nil {
return nil, nil, err
}
return appFn(logger, db)
var traceStoreWriter io.Writer
if traceStore != "" {
traceStoreWriter, err = os.OpenFile(
traceStore,
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
if err != nil {
return nil, nil, err
}
}
return appFn(logger, db, traceStoreWriter)
}
}

View File

@ -11,27 +11,33 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
)
// ExportCmd dumps app state to JSON
// ExportCmd dumps app state to JSON.
func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Command {
return &cobra.Command{
Use: "export",
Short: "Export state to JSON",
RunE: func(cmd *cobra.Command, args []string) error {
home := viper.GetString("home")
appState, validators, err := appExporter(home, ctx.Logger)
traceStore := viper.GetString(flagTraceStore)
appState, validators, err := appExporter(home, ctx.Logger, traceStore)
if err != nil {
return errors.Errorf("error exporting state: %v\n", err)
}
doc, err := tmtypes.GenesisDocFromFile(ctx.Config.GenesisFile())
if err != nil {
return err
}
doc.AppStateJSON = appState
doc.Validators = validators
encoded, err := wire.MarshalJSONIndent(cdc, doc)
if err != nil {
return err
}
fmt.Println(string(encoded))
return nil
},

View File

@ -149,6 +149,11 @@ func gentxWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, genTx
return
}
// Write updated config with moniker
config.Moniker = genTxConfig.Name
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
return
}
@ -240,6 +245,11 @@ func initWithConfig(cdc *wire.Codec, appInit AppInit, config *cfg.Config, initCo
viper.GetBool(FlagOWK),
"127.0.0.1",
}
// Write updated config with moniker
config.Moniker = genTxConfig.Name
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig)
appMessage = am
if err != nil {

View File

@ -1,6 +1,8 @@
package mock
import (
"io"
dbm "github.com/tendermint/tendermint/libs/db"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -18,6 +20,26 @@ func (ms multiStore) CacheWrap() sdk.CacheWrap {
panic("not implemented")
}
func (ms multiStore) CacheWrapWithTrace(_ io.Writer, _ sdk.TraceContext) sdk.CacheWrap {
panic("not implemented")
}
func (ms multiStore) ResetTraceContext() sdk.MultiStore {
panic("not implemented")
}
func (ms multiStore) TracingEnabled() bool {
panic("not implemented")
}
func (ms multiStore) WithTracingContext(tc sdk.TraceContext) sdk.MultiStore {
panic("not implemented")
}
func (ms multiStore) WithTracer(w io.Writer) sdk.MultiStore {
panic("not implemented")
}
func (ms multiStore) Commit() sdk.CommitID {
panic("not implemented")
}
@ -26,6 +48,10 @@ func (ms multiStore) LastCommitID() sdk.CommitID {
panic("not implemented")
}
func (ms multiStore) SetPruning(s sdk.PruningStrategy) {
panic("not implemented")
}
func (ms multiStore) GetCommitKVStore(key sdk.StoreKey) sdk.CommitKVStore {
panic("not implemented")
}
@ -70,6 +96,10 @@ func (kv kvStore) CacheWrap() sdk.CacheWrap {
panic("not implemented")
}
func (kv kvStore) CacheWrapWithTrace(w io.Writer, tc sdk.TraceContext) sdk.CacheWrap {
panic("not implemented")
}
func (kv kvStore) GetStoreType() sdk.StoreType {
panic("not implemented")
}

View File

@ -17,10 +17,12 @@ import (
const (
flagWithTendermint = "with-tendermint"
flagAddress = "address"
flagTraceStore = "trace-store"
flagPruning = "pruning"
)
// StartCmd runs the service passed in, either
// stand-alone, or in-process with tendermint
// StartCmd runs the service passed in, either stand-alone or in-process with
// Tendermint.
func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command {
cmd := &cobra.Command{
Use: "start",
@ -30,26 +32,31 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command {
ctx.Logger.Info("Starting ABCI without Tendermint")
return startStandAlone(ctx, appCreator)
}
ctx.Logger.Info("Starting ABCI with Tendermint")
_, err := startInProcess(ctx, appCreator)
return err
},
}
// basic flags for abci app
cmd.Flags().Bool(flagWithTendermint, true, "run abci app embedded in-process with tendermint")
// core flags for the ABCI application
cmd.Flags().Bool(flagWithTendermint, true, "Run abci app embedded in-process with tendermint")
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address")
cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
cmd.Flags().String(flagPruning, "syncable", "Pruning strategy: syncable, nothing, everything")
// AddNodeFlags adds support for all tendermint-specific command line options
// add support for all Tendermint-specific command line options
tcmd.AddNodeFlags(cmd)
return cmd
}
func startStandAlone(ctx *Context, appCreator AppCreator) error {
// Generate the app in the proper dir
addr := viper.GetString(flagAddress)
home := viper.GetString("home")
app, err := appCreator(home, ctx.Logger)
traceStore := viper.GetString(flagTraceStore)
app, err := appCreator(home, ctx.Logger, traceStore)
if err != nil {
return err
}
@ -58,15 +65,17 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error {
if err != nil {
return errors.Errorf("error creating listener: %v\n", err)
}
svr.SetLogger(ctx.Logger.With("module", "abci-server"))
err = svr.Start()
if err != nil {
cmn.Exit(err.Error())
}
// Wait forever
// wait forever
cmn.TrapSignal(func() {
// Cleanup
// cleanup
err = svr.Stop()
if err != nil {
cmn.Exit(err.Error())
@ -78,29 +87,33 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error {
func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) {
cfg := ctx.Config
home := cfg.RootDir
app, err := appCreator(home, ctx.Logger)
traceStore := viper.GetString(flagTraceStore)
app, err := appCreator(home, ctx.Logger, traceStore)
if err != nil {
return nil, err
}
// Create & start tendermint node
n, err := node.NewNode(cfg,
// create & start tendermint node
tmNode, err := node.NewNode(
cfg,
pvm.LoadOrGenFilePV(cfg.PrivValidatorFile()),
proxy.NewLocalClientCreator(app),
node.DefaultGenesisDocProviderFunc(cfg),
node.DefaultDBProvider,
node.DefaultMetricsProvider,
ctx.Logger.With("module", "node"))
ctx.Logger.With("module", "node"),
)
if err != nil {
return nil, err
}
err = n.Start()
err = tmNode.Start()
if err != nil {
return nil, err
}
// Trap signal, run forever.
n.RunForever()
return n, nil
// trap signal (run forever)
tmNode.RunForever()
return tmNode, nil
}

View File

@ -2,6 +2,7 @@ package store
import (
"bytes"
"io"
"sort"
"sync"
@ -27,11 +28,10 @@ var _ CacheKVStore = (*cacheKVStore)(nil)
// nolint
func NewCacheKVStore(parent KVStore) *cacheKVStore {
ci := &cacheKVStore{
return &cacheKVStore{
cache: make(map[string]cValue),
parent: parent,
}
return ci
}
// Implements Store.
@ -98,6 +98,7 @@ func (ci *cacheKVStore) Write() {
keys = append(keys, key)
}
}
sort.Strings(keys)
// TODO: Consider allowing usage of Batch, which would allow the write to
@ -125,6 +126,11 @@ func (ci *cacheKVStore) CacheWrap() CacheWrap {
return NewCacheKVStore(ci)
}
// CacheWrapWithTrace implements the CacheWrapper interface.
func (ci *cacheKVStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap {
return NewCacheKVStore(NewTraceKVStore(ci, w, tc))
}
//----------------------------------------
// Iteration
@ -140,32 +146,39 @@ func (ci *cacheKVStore) ReverseIterator(start, end []byte) Iterator {
func (ci *cacheKVStore) iterator(start, end []byte, ascending bool) Iterator {
var parent, cache Iterator
if ascending {
parent = ci.parent.Iterator(start, end)
} else {
parent = ci.parent.ReverseIterator(start, end)
}
items := ci.dirtyItems(ascending)
cache = newMemIterator(start, end, items)
return newCacheMergeIterator(parent, cache, ascending)
}
// Constructs a slice of dirty items, to use w/ memIterator.
func (ci *cacheKVStore) dirtyItems(ascending bool) []cmn.KVPair {
items := make([]cmn.KVPair, 0, len(ci.cache))
for key, cacheValue := range ci.cache {
if !cacheValue.dirty {
continue
}
items = append(items,
cmn.KVPair{[]byte(key), cacheValue.value})
items = append(items, cmn.KVPair{Key: []byte(key), Value: cacheValue.value})
}
sort.Slice(items, func(i, j int) bool {
if ascending {
return bytes.Compare(items[i].Key, items[j].Key) < 0
}
return bytes.Compare(items[i].Key, items[j].Key) > 0
})
return items
}
@ -180,10 +193,9 @@ func (ci *cacheKVStore) assertValidKey(key []byte) {
// Only entrypoint to mutate ci.cache.
func (ci *cacheKVStore) setCacheValue(key, value []byte, deleted bool, dirty bool) {
cacheValue := cValue{
ci.cache[string(key)] = cValue{
value: value,
deleted: deleted,
dirty: dirty,
}
ci.cache[string(key)] = cacheValue
}

View File

@ -1,6 +1,8 @@
package store
import (
"io"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -13,33 +15,86 @@ type cacheMultiStore struct {
db CacheKVStore
stores map[StoreKey]CacheWrap
keysByName map[string]StoreKey
traceWriter io.Writer
traceContext TraceContext
}
var _ CacheMultiStore = cacheMultiStore{}
func newCacheMultiStoreFromRMS(rms *rootMultiStore) cacheMultiStore {
cms := cacheMultiStore{
db: NewCacheKVStore(dbStoreAdapter{rms.db}),
stores: make(map[StoreKey]CacheWrap, len(rms.stores)),
keysByName: rms.keysByName,
db: NewCacheKVStore(dbStoreAdapter{rms.db}),
stores: make(map[StoreKey]CacheWrap, len(rms.stores)),
keysByName: rms.keysByName,
traceWriter: rms.traceWriter,
traceContext: rms.traceContext,
}
for key, store := range rms.stores {
cms.stores[key] = store.CacheWrap()
if cms.TracingEnabled() {
cms.stores[key] = store.CacheWrapWithTrace(cms.traceWriter, cms.traceContext)
} else {
cms.stores[key] = store.CacheWrap()
}
}
return cms
}
func newCacheMultiStoreFromCMS(cms cacheMultiStore) cacheMultiStore {
cms2 := cacheMultiStore{
db: NewCacheKVStore(cms.db),
stores: make(map[StoreKey]CacheWrap, len(cms.stores)),
db: NewCacheKVStore(cms.db),
stores: make(map[StoreKey]CacheWrap, len(cms.stores)),
traceWriter: cms.traceWriter,
traceContext: cms.traceContext,
}
for key, store := range cms.stores {
cms2.stores[key] = store.CacheWrap()
if cms2.TracingEnabled() {
cms2.stores[key] = store.CacheWrapWithTrace(cms2.traceWriter, cms2.traceContext)
} else {
cms2.stores[key] = store.CacheWrap()
}
}
return cms2
}
// WithTracer sets the tracer for the MultiStore that the underlying
// stores will utilize to trace operations. A MultiStore is returned.
func (cms cacheMultiStore) WithTracer(w io.Writer) MultiStore {
cms.traceWriter = w
return cms
}
// WithTracingContext updates the tracing context for the MultiStore by merging
// the given context with the existing context by key. Any existing keys will
// be overwritten. It is implied that the caller should update the context when
// necessary between tracing operations. It returns a modified MultiStore.
func (cms cacheMultiStore) WithTracingContext(tc TraceContext) MultiStore {
if cms.traceContext != nil {
for k, v := range tc {
cms.traceContext[k] = v
}
} else {
cms.traceContext = tc
}
return cms
}
// TracingEnabled returns if tracing is enabled for the MultiStore.
func (cms cacheMultiStore) TracingEnabled() bool {
return cms.traceWriter != nil
}
// ResetTraceContext resets the current tracing context.
func (cms cacheMultiStore) ResetTraceContext() MultiStore {
cms.traceContext = nil
return cms
}
// Implements Store.
func (cms cacheMultiStore) GetStoreType() StoreType {
return sdk.StoreTypeMulti
@ -58,6 +113,11 @@ func (cms cacheMultiStore) CacheWrap() CacheWrap {
return cms.CacheMultiStore().(CacheWrap)
}
// CacheWrapWithTrace implements the CacheWrapper interface.
func (cms cacheMultiStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap {
return cms.CacheWrap()
}
// Implements MultiStore.
func (cms cacheMultiStore) CacheMultiStore() CacheMultiStore {
return newCacheMultiStoreFromCMS(cms)

View File

@ -1,6 +1,8 @@
package store
import (
"io"
sdk "github.com/cosmos/cosmos-sdk/types"
dbm "github.com/tendermint/tendermint/libs/db"
)
@ -19,6 +21,11 @@ func (dsa dbStoreAdapter) CacheWrap() CacheWrap {
return NewCacheKVStore(dsa)
}
// CacheWrapWithTrace implements the KVStore interface.
func (dsa dbStoreAdapter) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap {
return NewCacheKVStore(NewTraceKVStore(dsa, w, tc))
}
// Implements KVStore
func (dsa dbStoreAdapter) Prefix(prefix []byte) KVStore {
return prefixStore{dsa, prefix}

View File

@ -1,6 +1,8 @@
package store
import (
"io"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -82,7 +84,12 @@ func (gi *gasKVStore) ReverseIterator(start, end []byte) sdk.Iterator {
// Implements KVStore.
func (gi *gasKVStore) CacheWrap() sdk.CacheWrap {
panic("you cannot CacheWrap a GasKVStore")
panic("cannot CacheWrap a GasKVStore")
}
// CacheWrapWithTrace implements the KVStore interface.
func (gi *gasKVStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap {
panic("cannot CacheWrapWithTrace a GasKVStore")
}
func (gi *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator {

View File

@ -2,6 +2,7 @@ package store
import (
"fmt"
"io"
"sync"
"github.com/tendermint/go-amino"
@ -14,20 +15,19 @@ import (
)
const (
defaultIAVLCacheSize = 10000
defaultIAVLNumRecent = 100
defaultIAVLStoreEvery = 1
defaultIAVLCacheSize = 10000
)
// load the iavl store
func LoadIAVLStore(db dbm.DB, id CommitID) (CommitStore, error) {
func LoadIAVLStore(db dbm.DB, id CommitID, pruning sdk.PruningStrategy) (CommitStore, error) {
tree := iavl.NewVersionedTree(db, defaultIAVLCacheSize)
_, err := tree.LoadVersion(id.Version)
if err != nil {
return nil, err
}
store := newIAVLStore(tree, defaultIAVLNumRecent, defaultIAVLStoreEvery)
return store, nil
iavl := newIAVLStore(tree, int64(0), int64(0))
iavl.SetPruning(pruning)
return iavl, nil
}
//----------------------------------------
@ -43,16 +43,15 @@ type iavlStore struct {
tree *iavl.VersionedTree
// How many old versions we hold onto.
// A value of 0 means keep no recent states
// A value of 0 means keep no recent states.
numRecent int64
// Distance between state-sync waypoint states to be stored
// This is the distance between state-sync waypoint states to be stored.
// See https://github.com/tendermint/tendermint/issues/828
// A value of 1 means store every state
// A value of 0 means store no waypoints (node cannot assist in state-sync)
// A value of 1 means store every state.
// A value of 0 means store no waypoints. (node cannot assist in state-sync)
// By default this value should be set the same across all nodes,
// so that nodes can know the waypoints their peers store
// TODO if set to non-default, signal to peers that the node is not suitable as a state sync source
// so that nodes can know the waypoints their peers store.
storeEvery int64
}
@ -76,13 +75,13 @@ func (st *iavlStore) Commit() CommitID {
panic(err)
}
// Release an old version of history, if not a sync waypoint
// Release an old version of history, if not a sync waypoint.
previous := version - 1
if st.numRecent < previous {
toRelease := previous - st.numRecent
if st.storeEvery == 0 || toRelease%st.storeEvery != 0 {
err := st.tree.DeleteVersion(toRelease)
if err != nil {
if err != nil && err.(cmn.Error).Data() != iavl.ErrVersionDoesNotExist {
panic(err)
}
}
@ -102,7 +101,21 @@ func (st *iavlStore) LastCommitID() CommitID {
}
}
// VersionExists returns whether or not a given version is stored
// Implements Committer.
func (st *iavlStore) SetPruning(pruning sdk.PruningStrategy) {
switch pruning {
case sdk.PruneEverything:
st.numRecent = 0
st.storeEvery = 0
case sdk.PruneNothing:
st.storeEvery = 1
case sdk.PruneSyncable:
st.numRecent = 100
st.storeEvery = 10000
}
}
// VersionExists returns whether or not a given version is stored.
func (st *iavlStore) VersionExists(version int64) bool {
return st.tree.VersionExists(version)
}
@ -117,6 +130,11 @@ func (st *iavlStore) CacheWrap() CacheWrap {
return NewCacheKVStore(st)
}
// CacheWrapWithTrace implements the Store interface.
func (st *iavlStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap {
return NewCacheKVStore(NewTraceKVStore(st, w, tc))
}
// Implements KVStore.
func (st *iavlStore) Set(key, value []byte) {
st.tree.Set(key, value)
@ -190,6 +208,10 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
case "/store", "/key": // Get by key
key := req.Data // Data holds the key bytes
res.Key = key
if !st.VersionExists(res.Height) {
res.Log = cmn.ErrorWrap(iavl.ErrVersionDoesNotExist, "").Error()
break
}
if req.Prove {
value, proof, err := tree.GetVersionedWithProof(key, res.Height)
if err != nil {

View File

@ -266,13 +266,11 @@ func nextVersion(iavl *iavlStore) {
iavl.Set(key, value)
iavl.Commit()
}
func TestIAVLDefaultPruning(t *testing.T) {
//Expected stored / deleted version numbers for:
//numRecent = 5, storeEvery = 3
var states = []struct {
stored []int64
deleted []int64
}{
var states = []pruneState{
{[]int64{}, []int64{}},
{[]int64{1}, []int64{}},
{[]int64{1, 2}, []int64{}},
@ -290,6 +288,39 @@ func TestIAVLDefaultPruning(t *testing.T) {
{[]int64{3, 6, 9, 10, 11, 12, 13, 14}, []int64{1, 2, 4, 5, 7, 8}},
{[]int64{3, 6, 9, 10, 11, 12, 13, 14, 15}, []int64{1, 2, 4, 5, 7, 8}},
}
testPruning(t, int64(5), int64(3), states)
}
func TestIAVLAlternativePruning(t *testing.T) {
//Expected stored / deleted version numbers for:
//numRecent = 3, storeEvery = 5
var states = []pruneState{
{[]int64{}, []int64{}},
{[]int64{1}, []int64{}},
{[]int64{1, 2}, []int64{}},
{[]int64{1, 2, 3}, []int64{}},
{[]int64{1, 2, 3, 4}, []int64{}},
{[]int64{2, 3, 4, 5}, []int64{1}},
{[]int64{3, 4, 5, 6}, []int64{1, 2}},
{[]int64{4, 5, 6, 7}, []int64{1, 2, 3}},
{[]int64{5, 6, 7, 8}, []int64{1, 2, 3, 4}},
{[]int64{5, 6, 7, 8, 9}, []int64{1, 2, 3, 4}},
{[]int64{5, 7, 8, 9, 10}, []int64{1, 2, 3, 4, 6}},
{[]int64{5, 8, 9, 10, 11}, []int64{1, 2, 3, 4, 6, 7}},
{[]int64{5, 9, 10, 11, 12}, []int64{1, 2, 3, 4, 6, 7, 8}},
{[]int64{5, 10, 11, 12, 13}, []int64{1, 2, 3, 4, 6, 7, 8, 9}},
{[]int64{5, 10, 11, 12, 13, 14}, []int64{1, 2, 3, 4, 6, 7, 8, 9}},
{[]int64{5, 10, 12, 13, 14, 15}, []int64{1, 2, 3, 4, 6, 7, 8, 9, 11}},
}
testPruning(t, int64(3), int64(5), states)
}
type pruneState struct {
stored []int64
deleted []int64
}
func testPruning(t *testing.T, numRecent int64, storeEvery int64, states []pruneState) {
db := dbm.NewMemDB()
tree := iavl.NewVersionedTree(db, cacheSize)
iavlStore := newIAVLStore(tree, numRecent, storeEvery)
@ -307,6 +338,7 @@ func TestIAVLDefaultPruning(t *testing.T) {
nextVersion(iavlStore)
}
}
func TestIAVLNoPrune(t *testing.T) {
db := dbm.NewMemDB()
tree := iavl.NewVersionedTree(db, cacheSize)
@ -321,6 +353,7 @@ func TestIAVLNoPrune(t *testing.T) {
nextVersion(iavlStore)
}
}
func TestIAVLPruneEverything(t *testing.T) {
db := dbm.NewMemDB()
tree := iavl.NewVersionedTree(db, cacheSize)

View File

@ -1,6 +1,8 @@
package store
import (
"io"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -19,6 +21,11 @@ func (s prefixStore) CacheWrap() CacheWrap {
return NewCacheKVStore(s)
}
// CacheWrapWithTrace implements the KVStore interface.
func (s prefixStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap {
return NewCacheKVStore(NewTraceKVStore(s, w, tc))
}
// Implements KVStore
func (s prefixStore) Get(key []byte) []byte {
return s.store.Get(append(s.prefix, key...))

View File

@ -2,6 +2,7 @@ package store
import (
"fmt"
"io"
"strings"
"golang.org/x/crypto/ripemd160"
@ -18,16 +19,19 @@ const (
commitInfoKeyFmt = "s/%d" // s/<version>
)
// rootMultiStore is composed of many CommitStores.
// Name contrasts with cacheMultiStore which is for cache-wrapping
// other MultiStores.
// Implements MultiStore.
// rootMultiStore is composed of many CommitStores. Name contrasts with
// cacheMultiStore which is for cache-wrapping other MultiStores. It implements
// the CommitMultiStore interface.
type rootMultiStore struct {
db dbm.DB
lastCommitID CommitID
pruning sdk.PruningStrategy
storesParams map[StoreKey]storeParams
stores map[StoreKey]CommitStore
keysByName map[string]StoreKey
traceWriter io.Writer
traceContext TraceContext
}
var _ CommitMultiStore = (*rootMultiStore)(nil)
@ -43,6 +47,14 @@ func NewCommitMultiStore(db dbm.DB) *rootMultiStore {
}
}
// Implements CommitMultiStore
func (rs *rootMultiStore) SetPruning(pruning sdk.PruningStrategy) {
rs.pruning = pruning
for _, substore := range rs.stores {
substore.SetPruning(pruning)
}
}
// Implements Store.
func (rs *rootMultiStore) GetStoreType() StoreType {
return sdk.StoreTypeMulti
@ -130,6 +142,40 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error {
return nil
}
// WithTracer sets the tracer for the MultiStore that the underlying
// stores will utilize to trace operations. A MultiStore is returned.
func (rs *rootMultiStore) WithTracer(w io.Writer) MultiStore {
rs.traceWriter = w
return rs
}
// WithTracingContext updates the tracing context for the MultiStore by merging
// the given context with the existing context by key. Any existing keys will
// be overwritten. It is implied that the caller should update the context when
// necessary between tracing operations. It returns a modified MultiStore.
func (rs *rootMultiStore) WithTracingContext(tc TraceContext) MultiStore {
if rs.traceContext != nil {
for k, v := range tc {
rs.traceContext[k] = v
}
} else {
rs.traceContext = tc
}
return rs
}
// TracingEnabled returns if tracing is enabled for the MultiStore.
func (rs *rootMultiStore) TracingEnabled() bool {
return rs.traceWriter != nil
}
// ResetTraceContext resets the current tracing context.
func (rs *rootMultiStore) ResetTraceContext() MultiStore {
rs.traceContext = nil
return rs
}
//----------------------------------------
// +CommitStore
@ -165,6 +211,11 @@ func (rs *rootMultiStore) CacheWrap() CacheWrap {
return rs.CacheMultiStore().(CacheWrap)
}
// CacheWrapWithTrace implements the CacheWrapper interface.
func (rs *rootMultiStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap {
return rs.CacheWrap()
}
//----------------------------------------
// +MultiStore
@ -178,9 +229,17 @@ func (rs *rootMultiStore) GetStore(key StoreKey) Store {
return rs.stores[key]
}
// Implements MultiStore.
// GetKVStore implements the MultiStore interface. If tracing is enabled on the
// rootMultiStore, a wrapped TraceKVStore will be returned with the given
// tracer, otherwise, the original KVStore will be returned.
func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore {
return rs.stores[key].(KVStore)
store := rs.stores[key].(KVStore)
if rs.TracingEnabled() {
store = NewTraceKVStore(store, rs.traceWriter, rs.traceContext)
}
return store
}
// Implements MultiStore.
@ -263,7 +322,7 @@ func (rs *rootMultiStore) loadCommitStoreFromParams(id CommitID, params storePar
// TODO: id?
// return NewCommitMultiStore(db, id)
case sdk.StoreTypeIAVL:
store, err = LoadIAVLStore(db, id)
store, err = LoadIAVLStore(db, id, rs.pruning)
return
case sdk.StoreTypeDB:
panic("dbm.DB is not a CommitStore")

198
store/tracekvstore.go Normal file
View File

@ -0,0 +1,198 @@
package store
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
writeOp operation = "write"
readOp operation = "read"
deleteOp operation = "delete"
iterKeyOp operation = "iterKey"
iterValueOp operation = "iterValue"
)
type (
// TraceKVStore implements the KVStore interface with tracing enabled.
// Operations are traced on each core KVStore call and written to the
// underlying io.writer.
//
// TODO: Should we use a buffered writer and implement Commit on
// TraceKVStore?
TraceKVStore struct {
parent sdk.KVStore
writer io.Writer
context TraceContext
}
// operation represents an IO operation
operation string
// traceOperation implements a traced KVStore operation
traceOperation struct {
Operation operation `json:"operation"`
Key string `json:"key"`
Value string `json:"value"`
Metadata map[string]interface{} `json:"metadata"`
}
)
// NewTraceKVStore returns a reference to a new traceKVStore given a parent
// KVStore implementation and a buffered writer.
func NewTraceKVStore(parent sdk.KVStore, writer io.Writer, tc TraceContext) *TraceKVStore {
return &TraceKVStore{parent: parent, writer: writer, context: tc}
}
// Get implements the KVStore interface. It traces a read operation and
// delegates a Get call to the parent KVStore.
func (tkv *TraceKVStore) Get(key []byte) []byte {
value := tkv.parent.Get(key)
writeOperation(tkv.writer, readOp, tkv.context, key, value)
return value
}
// Set implements the KVStore interface. It traces a write operation and
// delegates the Set call to the parent KVStore.
func (tkv *TraceKVStore) Set(key []byte, value []byte) {
writeOperation(tkv.writer, writeOp, tkv.context, key, value)
tkv.parent.Set(key, value)
}
// Delete implements the KVStore interface. It traces a write operation and
// delegates the Delete call to the parent KVStore.
func (tkv *TraceKVStore) Delete(key []byte) {
writeOperation(tkv.writer, deleteOp, tkv.context, key, nil)
tkv.parent.Delete(key)
}
// Has implements the KVStore interface. It delegates the Has call to the
// parent KVStore.
func (tkv *TraceKVStore) Has(key []byte) bool {
return tkv.parent.Has(key)
}
// Prefix implements the KVStore interface.
func (tkv *TraceKVStore) Prefix(prefix []byte) KVStore {
return prefixStore{tkv, prefix}
}
// Iterator implements the KVStore interface. It delegates the Iterator call
// the to the parent KVStore.
func (tkv *TraceKVStore) Iterator(start, end []byte) sdk.Iterator {
return tkv.iterator(start, end, true)
}
// ReverseIterator implements the KVStore interface. It delegates the
// ReverseIterator call the to the parent KVStore.
func (tkv *TraceKVStore) ReverseIterator(start, end []byte) sdk.Iterator {
return tkv.iterator(start, end, false)
}
// iterator facilitates iteration over a KVStore. It delegates the necessary
// calls to it's parent KVStore.
func (tkv *TraceKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator {
var parent sdk.Iterator
if ascending {
parent = tkv.parent.Iterator(start, end)
} else {
parent = tkv.parent.ReverseIterator(start, end)
}
return newTraceIterator(tkv.writer, parent, tkv.context)
}
type traceIterator struct {
parent sdk.Iterator
writer io.Writer
context TraceContext
}
func newTraceIterator(w io.Writer, parent sdk.Iterator, tc TraceContext) sdk.Iterator {
return &traceIterator{writer: w, parent: parent, context: tc}
}
// Domain implements the Iterator interface.
func (ti *traceIterator) Domain() (start []byte, end []byte) {
return ti.parent.Domain()
}
// Valid implements the Iterator interface.
func (ti *traceIterator) Valid() bool {
return ti.parent.Valid()
}
// Next implements the Iterator interface.
func (ti *traceIterator) Next() {
ti.parent.Next()
}
// Key implements the Iterator interface.
func (ti *traceIterator) Key() []byte {
key := ti.parent.Key()
writeOperation(ti.writer, iterKeyOp, ti.context, key, nil)
return key
}
// Value implements the Iterator interface.
func (ti *traceIterator) Value() []byte {
value := ti.parent.Value()
writeOperation(ti.writer, iterValueOp, ti.context, nil, value)
return value
}
// Close implements the Iterator interface.
func (ti *traceIterator) Close() {
ti.parent.Close()
}
// GetStoreType implements the KVStore interface. It returns the underlying
// KVStore type.
func (tkv *TraceKVStore) GetStoreType() sdk.StoreType {
return tkv.parent.GetStoreType()
}
// CacheWrap implements the KVStore interface. It panics as a TraceKVStore
// cannot be cache wrapped.
func (tkv *TraceKVStore) CacheWrap() sdk.CacheWrap {
panic("cannot CacheWrap a TraceKVStore")
}
// CacheWrapWithTrace implements the KVStore interface. It panics as a
// TraceKVStore cannot be cache wrapped.
func (tkv *TraceKVStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap {
panic("cannot CacheWrapWithTrace a TraceKVStore")
}
// writeOperation writes a KVStore operation to the underlying io.Writer as
// JSON-encoded data where the key/value pair is base64 encoded.
func writeOperation(w io.Writer, op operation, tc TraceContext, key, value []byte) {
traceOp := traceOperation{
Operation: op,
Key: base64.StdEncoding.EncodeToString(key),
Value: base64.StdEncoding.EncodeToString(value),
}
if tc != nil {
traceOp.Metadata = tc
}
raw, err := json.Marshal(traceOp)
if err != nil {
panic(fmt.Sprintf("failed to serialize trace operation: %v", err))
}
if _, err := w.Write(raw); err != nil {
panic(fmt.Sprintf("failed to write trace operation: %v", err))
}
io.WriteString(w, "\n")
}

283
store/tracekvstore_test.go Normal file
View File

@ -0,0 +1,283 @@
package store
import (
"bytes"
"io"
"testing"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tendermint/libs/db"
)
var kvPairs = []KVPair{
{Key: keyFmt(1), Value: valFmt(1)},
{Key: keyFmt(2), Value: valFmt(2)},
{Key: keyFmt(3), Value: valFmt(3)},
}
func newTraceKVStore(w io.Writer) *TraceKVStore {
store := newEmptyTraceKVStore(w)
for _, kvPair := range kvPairs {
store.Set(kvPair.Key, kvPair.Value)
}
return store
}
func newEmptyTraceKVStore(w io.Writer) *TraceKVStore {
memDB := dbStoreAdapter{dbm.NewMemDB()}
tc := TraceContext(map[string]interface{}{"blockHeight": 64})
return NewTraceKVStore(memDB, w, tc)
}
func TestTraceKVStoreGet(t *testing.T) {
testCases := []struct {
key []byte
expectedValue []byte
expectedOut string
}{
{
key: []byte{},
expectedValue: nil,
expectedOut: "{\"operation\":\"read\",\"key\":\"\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
},
{
key: kvPairs[0].Key,
expectedValue: kvPairs[0].Value,
expectedOut: "{\"operation\":\"read\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"dmFsdWUwMDAwMDAwMQ==\",\"metadata\":{\"blockHeight\":64}}\n",
},
{
key: []byte("does-not-exist"),
expectedValue: nil,
expectedOut: "{\"operation\":\"read\",\"key\":\"ZG9lcy1ub3QtZXhpc3Q=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
},
}
for _, tc := range testCases {
var buf bytes.Buffer
store := newTraceKVStore(&buf)
buf.Reset()
value := store.Get(tc.key)
require.Equal(t, tc.expectedValue, value)
require.Equal(t, tc.expectedOut, buf.String())
}
}
func TestTraceKVStoreSet(t *testing.T) {
testCases := []struct {
key []byte
value []byte
expectedOut string
}{
{
key: []byte{},
value: nil,
expectedOut: "{\"operation\":\"write\",\"key\":\"\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
},
{
key: kvPairs[0].Key,
value: kvPairs[0].Value,
expectedOut: "{\"operation\":\"write\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"dmFsdWUwMDAwMDAwMQ==\",\"metadata\":{\"blockHeight\":64}}\n",
},
}
for _, tc := range testCases {
var buf bytes.Buffer
store := newEmptyTraceKVStore(&buf)
buf.Reset()
store.Set(tc.key, tc.value)
require.Equal(t, tc.expectedOut, buf.String())
}
}
func TestTraceKVStoreDelete(t *testing.T) {
testCases := []struct {
key []byte
expectedOut string
}{
{
key: []byte{},
expectedOut: "{\"operation\":\"delete\",\"key\":\"\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
},
{
key: kvPairs[0].Key,
expectedOut: "{\"operation\":\"delete\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
},
}
for _, tc := range testCases {
var buf bytes.Buffer
store := newTraceKVStore(&buf)
buf.Reset()
store.Delete(tc.key)
require.Equal(t, tc.expectedOut, buf.String())
}
}
func TestTraceKVStoreHas(t *testing.T) {
testCases := []struct {
key []byte
expected bool
}{
{
key: []byte{},
expected: false,
},
{
key: kvPairs[0].Key,
expected: true,
},
}
for _, tc := range testCases {
var buf bytes.Buffer
store := newTraceKVStore(&buf)
buf.Reset()
ok := store.Has(tc.key)
require.Equal(t, tc.expected, ok)
}
}
func TestTestTraceKVStoreIterator(t *testing.T) {
var buf bytes.Buffer
store := newTraceKVStore(&buf)
iterator := store.Iterator(nil, nil)
s, e := iterator.Domain()
require.Equal(t, []uint8([]byte(nil)), s)
require.Equal(t, []uint8([]byte(nil)), e)
testCases := []struct {
expectedKey []byte
expectedValue []byte
expectedKeyOut string
expectedvalueOut string
}{
{
expectedKey: kvPairs[0].Key,
expectedValue: kvPairs[0].Value,
expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMQ==\",\"metadata\":{\"blockHeight\":64}}\n",
},
{
expectedKey: kvPairs[1].Key,
expectedValue: kvPairs[1].Value,
expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDI=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMg==\",\"metadata\":{\"blockHeight\":64}}\n",
},
{
expectedKey: kvPairs[2].Key,
expectedValue: kvPairs[2].Value,
expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDM=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMw==\",\"metadata\":{\"blockHeight\":64}}\n",
},
}
for _, tc := range testCases {
buf.Reset()
ka := iterator.Key()
require.Equal(t, tc.expectedKeyOut, buf.String())
buf.Reset()
va := iterator.Value()
require.Equal(t, tc.expectedvalueOut, buf.String())
require.Equal(t, tc.expectedKey, ka)
require.Equal(t, tc.expectedValue, va)
iterator.Next()
}
require.False(t, iterator.Valid())
require.Panics(t, iterator.Next)
require.NotPanics(t, iterator.Close)
}
func TestTestTraceKVStoreReverseIterator(t *testing.T) {
var buf bytes.Buffer
store := newTraceKVStore(&buf)
iterator := store.ReverseIterator(nil, nil)
s, e := iterator.Domain()
require.Equal(t, []uint8([]byte(nil)), s)
require.Equal(t, []uint8([]byte(nil)), e)
testCases := []struct {
expectedKey []byte
expectedValue []byte
expectedKeyOut string
expectedvalueOut string
}{
{
expectedKey: kvPairs[2].Key,
expectedValue: kvPairs[2].Value,
expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDM=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMw==\",\"metadata\":{\"blockHeight\":64}}\n",
},
{
expectedKey: kvPairs[1].Key,
expectedValue: kvPairs[1].Value,
expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDI=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMg==\",\"metadata\":{\"blockHeight\":64}}\n",
},
{
expectedKey: kvPairs[0].Key,
expectedValue: kvPairs[0].Value,
expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n",
expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMQ==\",\"metadata\":{\"blockHeight\":64}}\n",
},
}
for _, tc := range testCases {
buf.Reset()
ka := iterator.Key()
require.Equal(t, tc.expectedKeyOut, buf.String())
buf.Reset()
va := iterator.Value()
require.Equal(t, tc.expectedvalueOut, buf.String())
require.Equal(t, tc.expectedKey, ka)
require.Equal(t, tc.expectedValue, va)
iterator.Next()
}
require.False(t, iterator.Valid())
require.Panics(t, iterator.Next)
require.NotPanics(t, iterator.Close)
}
func TestTraceKVStorePrefix(t *testing.T) {
store := newEmptyTraceKVStore(nil)
pStore := store.Prefix([]byte("trace_prefix"))
require.IsType(t, prefixStore{}, pStore)
}
func TestTraceKVStoreGetStoreType(t *testing.T) {
memDB := dbStoreAdapter{dbm.NewMemDB()}
store := newEmptyTraceKVStore(nil)
require.Equal(t, memDB.GetStoreType(), store.GetStoreType())
}
func TestTraceKVStoreCacheWrap(t *testing.T) {
store := newEmptyTraceKVStore(nil)
require.Panics(t, func() { store.CacheWrap() })
}
func TestTraceKVStoreCacheWrapWithTrace(t *testing.T) {
store := newEmptyTraceKVStore(nil)
require.Panics(t, func() { store.CacheWrapWithTrace(nil, nil) })
}

View File

@ -6,20 +6,23 @@ import (
// Import cosmos-sdk/types/store.go for convenience.
// nolint
type Store = types.Store
type Committer = types.Committer
type CommitStore = types.CommitStore
type MultiStore = types.MultiStore
type CacheMultiStore = types.CacheMultiStore
type CommitMultiStore = types.CommitMultiStore
type KVStore = types.KVStore
type KVPair = types.KVPair
type Iterator = types.Iterator
type CacheKVStore = types.CacheKVStore
type CommitKVStore = types.CommitKVStore
type CacheWrapper = types.CacheWrapper
type CacheWrap = types.CacheWrap
type CommitID = types.CommitID
type StoreKey = types.StoreKey
type StoreType = types.StoreType
type Queryable = types.Queryable
type (
Store = types.Store
Committer = types.Committer
CommitStore = types.CommitStore
MultiStore = types.MultiStore
CacheMultiStore = types.CacheMultiStore
CommitMultiStore = types.CommitMultiStore
KVStore = types.KVStore
KVPair = types.KVPair
Iterator = types.Iterator
CacheKVStore = types.CacheKVStore
CommitKVStore = types.CommitKVStore
CacheWrapper = types.CacheWrapper
CacheWrap = types.CacheWrap
CommitID = types.CommitID
StoreKey = types.StoreKey
StoreType = types.StoreType
Queryable = types.Queryable
TraceContext = types.TraceContext
)

View File

@ -252,3 +252,11 @@ func RatsEqual(r1s, r2s []Rat) bool {
func RatEq(t *testing.T, exp, got Rat) (*testing.T, bool, string, Rat, Rat) {
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
}
// minimum rational between two
func MinRat(r1, r2 Rat) Rat {
if r1.LT(r2) {
return r1
}
return r2
}

View File

@ -26,10 +26,15 @@ func BondStatusToString(b BondStatus) string {
case 0x02:
return "Bonded"
default:
return ""
panic("improper use of BondStatusToString")
}
}
// nolint
func (b BondStatus) Equal(b2 BondStatus) bool {
return byte(b) == byte(b2)
}
// validator for a delegated proof of stake system
type Validator interface {
GetRevoked() bool // whether the validator is revoked

View File

@ -2,6 +2,7 @@ package types
import (
"fmt"
"io"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
@ -10,6 +11,20 @@ import (
// NOTE: These are implemented in cosmos-sdk/store.
// PruningStrategy specfies how old states will be deleted over time
type PruningStrategy uint8
const (
// PruneSyncable means only those states not needed for state syncing will be deleted (keeps last 100 + every 10000th)
PruneSyncable PruningStrategy = iota
// PruneEverything means all saved states will be deleted, storing only the current state
PruneEverything PruningStrategy = iota
// PruneNothing means all historic states will be saved, nothing will be deleted
PruneNothing PruningStrategy = iota
)
type Store interface { //nolint
GetStoreType() StoreType
CacheWrapper
@ -19,6 +34,7 @@ type Store interface { //nolint
type Committer interface {
Commit() CommitID
LastCommitID() CommitID
SetPruning(PruningStrategy)
}
// Stores of MultiStore must implement CommitStore.
@ -50,6 +66,21 @@ type MultiStore interface { //nolint
GetStore(StoreKey) Store
GetKVStore(StoreKey) KVStore
GetKVStoreWithGas(GasMeter, StoreKey) KVStore
// TracingEnabled returns if tracing is enabled for the MultiStore.
TracingEnabled() bool
// WithTracer sets the tracer for the MultiStore that the underlying
// stores will utilize to trace operations. A MultiStore is returned.
WithTracer(w io.Writer) MultiStore
// WithTracingContext sets the tracing context for a MultiStore. It is
// implied that the caller should update the context when necessary between
// tracing operations. A MultiStore is returned.
WithTracingContext(TraceContext) MultiStore
// ResetTraceContext resets the current tracing context.
ResetTraceContext() MultiStore
}
// From MultiStore.CacheMultiStore()....
@ -163,25 +194,27 @@ type KVStoreGetter interface {
//----------------------------------------
// CacheWrap
/*
CacheWrap() makes the most appropriate cache-wrap. For example,
IAVLStore.CacheWrap() returns a CacheKVStore.
CacheWrap() should not return a Committer, since Commit() on
cache-wraps make no sense. It can return KVStore, HeapStore,
SpaceStore, etc.
*/
// CacheWrap makes the most appropriate cache-wrap. For example,
// IAVLStore.CacheWrap() returns a CacheKVStore. CacheWrap should not return
// a Committer, since Commit cache-wraps make no sense. It can return KVStore,
// HeapStore, SpaceStore, etc.
type CacheWrap interface {
// Write syncs with the underlying store.
Write()
// CacheWrap recursively wraps again.
CacheWrap() CacheWrap
// CacheWrapWithTrace recursively wraps again with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap
}
type CacheWrapper interface { //nolint
// CacheWrap cache wraps.
CacheWrap() CacheWrap
// CacheWrapWithTrace cache wraps with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap
}
//----------------------------------------
@ -298,3 +331,7 @@ func (getter PrefixStoreGetter) KVStore(ctx Context) KVStore {
type KVPair cmn.KVPair
//----------------------------------------
// TraceContext contains TraceKVStore context data. It will be written with
// every trace operation.
type TraceContext map[string]interface{}

View File

@ -2,10 +2,10 @@
package version
const Maj = "0"
const Min = "20"
const Min = "21"
const Fix = "0"
const Version = "0.20.0"
const Version = "0.21.0"
// GitCommit set by build flags
var GitCommit = ""

View File

@ -59,7 +59,7 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk
mapp.InitChainer(ctx, req)
stakeGenesis := stake.DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
stakeGenesis.Pool.LooseTokens = sdk.NewRat(100000)
err := stake.InitGenesis(ctx, stakeKeeper, stakeGenesis)
if err != nil {

View File

@ -54,7 +54,7 @@ func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
mapp.InitChainer(ctx, req)
stakeGenesis := stake.DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
stakeGenesis.Pool.LooseTokens = sdk.NewRat(100000)
err := stake.InitGenesis(ctx, keeper, stakeGenesis)
if err != nil {
panic(err)
@ -101,8 +101,8 @@ func TestSlashingMsgs(t *testing.T) {
validator := checkValidator(t, mapp, stakeKeeper, addr1, true)
require.Equal(t, addr1, validator.Owner)
require.Equal(t, sdk.Bonded, validator.Status())
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
require.Equal(t, sdk.Bonded, validator.Status)
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.BondedTokens()))
unrevokeMsg := MsgUnrevoke{ValidatorAddr: sdk.AccAddress(validator.PubKey.Address())}
// no signing info yet

View File

@ -100,7 +100,7 @@ func TestHandleAbsentValidator(t *testing.T) {
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
pool := sk.GetPool(ctx)
require.Equal(t, int64(amtInt), pool.BondedTokens)
require.Equal(t, int64(amtInt), pool.BondedTokens.RoundInt64())
// 501st block missed
ctx = ctx.WithBlockHeight(height)
@ -129,7 +129,7 @@ func TestHandleAbsentValidator(t *testing.T) {
// validator should have been slashed
pool = sk.GetPool(ctx)
require.Equal(t, int64(amtInt-1), pool.BondedTokens)
require.Equal(t, int64(amtInt-1), pool.BondedTokens.RoundInt64())
// validator start height should have been changed
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address()))
@ -194,5 +194,5 @@ func TestHandleNewValidator(t *testing.T) {
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
pool := sk.GetPool(ctx)
require.Equal(t, int64(100), pool.BondedTokens)
require.Equal(t, int64(100), pool.BondedTokens.RoundInt64())
}

View File

@ -63,7 +63,9 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep
ck := bank.NewKeeper(accountMapper)
sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace)
genesis := stake.DefaultGenesisState()
genesis.Pool.LooseTokens = initCoins.MulRaw(int64(len(addrs))).Int64()
genesis.Pool.LooseTokens = sdk.NewRat(initCoins.MulRaw(int64(len(addrs))).Int64())
err = stake.InitGenesis(ctx, sk, genesis)
require.Nil(t, err)

View File

@ -63,7 +63,7 @@ func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer {
mapp.InitChainer(ctx, req)
stakeGenesis := DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
stakeGenesis.Pool.LooseTokens = sdk.NewRat(100000)
err := InitGenesis(ctx, keeper, stakeGenesis)
if err != nil {
@ -135,8 +135,8 @@ func TestStakeMsgs(t *testing.T) {
validator := checkValidator(t, mApp, keeper, addr1, true)
require.Equal(t, addr1, validator.Owner)
require.Equal(t, sdk.Bonded, validator.Status())
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
require.Equal(t, sdk.Bonded, validator.Status)
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.BondedTokens()))
// addr1 create validator on behalf of addr2
createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf(addr1, addr2, priv2.PubKey(), bondCoin, description)
@ -147,8 +147,8 @@ func TestStakeMsgs(t *testing.T) {
validator = checkValidator(t, mApp, keeper, addr2, true)
require.Equal(t, addr2, validator.Owner)
require.Equal(t, sdk.Bonded, validator.Status())
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
require.Equal(t, sdk.Bonded, validator.Status)
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
// check the bond that should have been created as well
checkDelegation(t, mApp, keeper, addr1, addr1, true, sdk.NewRat(10))

View File

@ -215,57 +215,6 @@ func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc {
}
}
// TODO move exist next to validator struct for maintainability
type StakeValidatorOutput struct {
Owner sdk.AccAddress `json:"owner"` // in bech32
PubKey string `json:"pub_key"` // in bech32
Revoked bool `json:"revoked"` // has the validator been revoked from bonded status?
PoolShares stake.PoolShares `json:"pool_shares"` // total shares for tokens held in the pool
DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators
Description stake.Description `json:"description"` // description terms for the validator
BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator
BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change
ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer
Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators
CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge
CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission
CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time)
// fee related
PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools
}
func bech32StakeValidatorOutput(validator stake.Validator) (StakeValidatorOutput, error) {
bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey)
if err != nil {
return StakeValidatorOutput{}, err
}
return StakeValidatorOutput{
Owner: validator.Owner,
PubKey: bechValPubkey,
Revoked: validator.Revoked,
PoolShares: validator.PoolShares,
DelegatorShares: validator.DelegatorShares,
Description: validator.Description,
BondHeight: validator.BondHeight,
BondIntraTxCounter: validator.BondIntraTxCounter,
ProposerRewardPool: validator.ProposerRewardPool,
Commission: validator.Commission,
CommissionMax: validator.CommissionMax,
CommissionChangeRate: validator.CommissionChangeRate,
CommissionChangeToday: validator.CommissionChangeToday,
PrevBondedShares: validator.PrevBondedShares,
}, nil
}
// TODO bech32
// http request handler to query list of validators
func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc {
@ -284,7 +233,7 @@ func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF
}
// parse out the validators
validators := make([]StakeValidatorOutput, len(kvs))
validators := make([]types.BechValidator, len(kvs))
for i, kv := range kvs {
addr := kv.Key[1:]
@ -295,7 +244,7 @@ func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF
return
}
bech32Validator, err := bech32StakeValidatorOutput(validator)
bech32Validator, err := validator.Bech32Validator()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))

View File

@ -20,7 +20,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error
for _, validator := range data.Validators {
keeper.SetValidator(ctx, validator)
if validator.PoolShares.Amount.IsZero() {
if validator.Tokens.IsZero() {
return errors.Errorf("genesis validator cannot have zero pool shares, validator: %v", validator)
}
if validator.DelegatorShares.IsZero() {
@ -31,7 +31,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error
keeper.SetValidatorByPubKeyIndex(ctx, validator)
keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool)
if validator.Status() == sdk.Bonded {
if validator.Status == sdk.Bonded {
keeper.SetValidatorBondedIndex(ctx, validator)
}
}

View File

@ -14,8 +14,7 @@ func TestInitGenesis(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
pool := keeper.GetPool(ctx)
pool.UnbondedTokens = 1
pool.UnbondedShares = sdk.OneRat()
pool.LooseTokens = sdk.OneRat()
params := keeper.GetParams(ctx)
var delegations []Delegation
@ -28,7 +27,7 @@ func TestInitGenesis(t *testing.T) {
err := InitGenesis(ctx, keeper, genesisState)
require.Error(t, err)
validators[0].PoolShares.Amount = sdk.OneRat()
validators[0].Tokens = sdk.OneRat()
validators[0].DelegatorShares = sdk.OneRat()
genesisState = types.NewGenesisState(pool, params, validators, delegations)

View File

@ -35,12 +35,13 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
// Called every block, process inflation, update validator set
func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Validator) {
pool := k.GetPool(ctx)
params := k.GetParams(ctx)
// Process types.Validator Provisions
blockTime := ctx.BlockHeader().Time
if pool.InflationLastTime+blockTime >= 3600 {
pool.InflationLastTime = blockTime
pool = k.ProcessProvisions(ctx)
pool = pool.ProcessProvisions(params)
}
// save the params

View File

@ -84,8 +84,8 @@ func TestValidatorByPowerIndex(t *testing.T) {
keeper.Revoke(ctx, keep.PKs[0])
validator, found = keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Unbonded, validator.PoolShares.Status) // ensure is unbonded
require.Equal(t, int64(500000), validator.PoolShares.Amount.RoundInt64()) // ensure is unbonded
require.Equal(t, sdk.Unbonded, validator.Status) // ensure is unbonded
require.Equal(t, int64(500000), validator.Tokens.RoundInt64()) // ensure is unbonded
// the old power record should have been deleted as the power changed
require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power))
@ -98,8 +98,9 @@ func TestValidatorByPowerIndex(t *testing.T) {
require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2))
// inflate a bunch
for i := 0; i < 20000; i++ {
pool = keeper.ProcessProvisions(ctx)
params := keeper.GetParams(ctx)
for i := 0; i < 200; i++ {
pool = pool.ProcessProvisions(params)
keeper.SetPool(ctx, pool)
}
@ -133,10 +134,10 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) {
validator, found := keeper.GetValidator(ctx, addr1)
require.True(t, found)
assert.Equal(t, sdk.Bonded, validator.Status())
assert.Equal(t, sdk.Bonded, validator.Status)
assert.Equal(t, addr1, validator.Owner)
assert.Equal(t, pk1, validator.PubKey)
assert.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded())
assert.Equal(t, sdk.NewRat(10), validator.BondedTokens())
assert.Equal(t, sdk.NewRat(10), validator.DelegatorShares)
assert.Equal(t, Description{}, validator.Description)
@ -157,11 +158,11 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) {
validator, found = keeper.GetValidator(ctx, addr2)
require.True(t, found)
assert.Equal(t, sdk.Bonded, validator.Status())
assert.Equal(t, sdk.Bonded, validator.Status)
assert.Equal(t, addr2, validator.Owner)
assert.Equal(t, pk2, validator.PubKey)
assert.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded())
assert.Equal(t, sdk.NewRat(10), validator.DelegatorShares)
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
assert.Equal(t, Description{}, validator.Description)
}
@ -177,12 +178,12 @@ func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
require.Equal(t, validatorAddr, validator.Owner)
require.Equal(t, pk, validator.PubKey)
require.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded())
require.Equal(t, sdk.NewRat(10), validator.DelegatorShares)
require.Equal(t, Description{}, validator.Description)
assert.Equal(t, sdk.Bonded, validator.Status)
assert.Equal(t, validatorAddr, validator.Owner)
assert.Equal(t, pk, validator.PubKey)
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
assert.Equal(t, Description{}, validator.Description)
// one validator cannot be created twice even from different delegator
msgCreateValidatorOnBehalfOf.DelegatorAddr = keep.Addrs[2]
@ -206,9 +207,9 @@ func TestIncrementsMsgDelegate(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
require.Equal(t, sdk.Bonded, validator.Status)
require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64())
require.Equal(t, bondAmount, validator.PoolShares.Bonded().RoundInt64(), "validator: %v", validator)
require.Equal(t, bondAmount, validator.BondedTokens().RoundInt64(), "validator: %v", validator)
_, found = keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
require.False(t, found)
@ -218,10 +219,9 @@ func TestIncrementsMsgDelegate(t *testing.T) {
require.Equal(t, bondAmount, bond.Shares.RoundInt64())
pool := keeper.GetPool(ctx)
exRate := validator.DelegatorShareExRate(pool)
exRate := validator.DelegatorShareExRate()
require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v", exRate)
require.Equal(t, bondAmount, pool.BondedShares.RoundInt64())
require.Equal(t, bondAmount, pool.BondedTokens)
require.Equal(t, bondAmount, pool.BondedTokens.RoundInt64())
// just send the same msgbond multiple times
msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount)
@ -238,8 +238,7 @@ func TestIncrementsMsgDelegate(t *testing.T) {
bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
require.True(t, found)
pool := keeper.GetPool(ctx)
exRate := validator.DelegatorShareExRate(pool)
exRate := validator.DelegatorShareExRate()
require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v, i = %v", exRate, i)
expBond := int64(i+1) * bondAmount
@ -291,7 +290,7 @@ func TestIncrementsMsgUnbond(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, initBond*2, validator.DelegatorShares.RoundInt64())
require.Equal(t, initBond*2, validator.PoolShares.Bonded().RoundInt64())
require.Equal(t, initBond*2, validator.BondedTokens().RoundInt64())
// just send the same msgUnbond multiple times
// TODO use decimals here
@ -674,7 +673,7 @@ func TestUnbondingWhenExcessValidators(t *testing.T) {
require.Equal(t, 2, len(vals), "vals %v", vals)
val1, found := keeper.GetValidator(ctx, validatorAddr1)
require.True(t, found)
require.Equal(t, sdk.Bonded, val1.Status(), "%v", val1)
require.Equal(t, sdk.Bonded, val1.Status, "%v", val1)
}
func TestJoiningAsCliffValidator(t *testing.T) {

View File

@ -238,7 +238,7 @@ func (k Keeper) Delegate(ctx sdk.Context, delegatorAddr sdk.AccAddress, bondAmt
// unbond the the delegation return
func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress,
shares sdk.Rat) (amount int64, err sdk.Error) {
shares sdk.Rat) (amount sdk.Rat, err sdk.Error) {
// check if delegation has any shares in it unbond
delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr)
@ -306,7 +306,7 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk
// create the unbonding delegation
params := k.GetParams(ctx)
minTime := ctx.BlockHeader().Time + params.UnbondingTime
balance := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)}
balance := sdk.Coin{params.BondDenom, returnAmount.RoundInt()}
ubd := types.UnbondingDelegation{
DelegatorAddr: delegatorAddr,
@ -356,7 +356,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd
}
params := k.GetParams(ctx)
returnCoin := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)}
returnCoin := sdk.Coin{params.BondDenom, returnAmount.RoundInt()}
dstValidator, found := k.GetValidator(ctx, validatorDstAddr)
if !found {
return types.ErrBadRedelegationDst(k.Codespace())

View File

@ -141,7 +141,7 @@ func TestUnbondingDelegation(t *testing.T) {
func TestUnbondDelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = 10
pool.LooseTokens = sdk.NewRat(10)
//create a validator and a delegator to that validator
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
@ -151,8 +151,8 @@ func TestUnbondDelegation(t *testing.T) {
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
require.Equal(t, int64(10), pool.BondedTokens)
require.Equal(t, int64(10), validator.PoolShares.Bonded().RoundInt64())
require.Equal(t, int64(10), pool.BondedTokens.RoundInt64())
require.Equal(t, int64(10), validator.BondedTokens().RoundInt64())
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
@ -162,10 +162,10 @@ func TestUnbondDelegation(t *testing.T) {
keeper.SetDelegation(ctx, delegation)
var err error
var amount int64
var amount sdk.Rat
amount, err = keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewRat(6))
require.NoError(t, err)
require.Equal(t, int64(6), amount) // shares to be added to an unbonding delegation / redelegation
require.Equal(t, int64(6), amount.RoundInt64()) // shares to be added to an unbonding delegation / redelegation
delegation, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
@ -174,9 +174,9 @@ func TestUnbondDelegation(t *testing.T) {
pool = keeper.GetPool(ctx)
require.Equal(t, int64(4), delegation.Shares.RoundInt64())
require.Equal(t, int64(4), validator.PoolShares.Bonded().RoundInt64())
require.Equal(t, int64(6), pool.LooseTokens, "%v", pool)
require.Equal(t, int64(4), pool.BondedTokens)
require.Equal(t, int64(4), validator.BondedTokens().RoundInt64())
require.Equal(t, int64(6), pool.LooseTokens.RoundInt64(), "%v", pool)
require.Equal(t, int64(4), pool.BondedTokens.RoundInt64())
}
// Make sure that that the retrieving the delegations doesn't affect the state

Some files were not shown because too many files have changed in this diff Show More