From c974475378cc45be4a8e216e2632c715dd86a2a9 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 18 Jan 2017 14:05:25 -0800 Subject: [PATCH 01/64] Updating the comments to match the blog post --- glide.lock | 2 +- types/plugin.go | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/glide.lock b/glide.lock index 948a5da3ba..6d606a6d63 100644 --- a/glide.lock +++ b/glide.lock @@ -90,7 +90,7 @@ imports: - app - client - name: github.com/tendermint/tendermint - version: cf0cb9558aaecbf3ddb071eb863df77e55d828ed + version: 9a2dd8bc9279ed2a1a4d4f31cc151f8a621cceb3 subpackages: - rpc/core/types - types diff --git a/types/plugin.go b/types/plugin.go index 09d68d4de0..ab65452cce 100644 --- a/types/plugin.go +++ b/types/plugin.go @@ -6,9 +6,15 @@ import ( ) type Plugin interface { + + // Name of this plugin, should be short. Name() string - SetOption(store KVStore, key string, value string) (log string) + + // Run a transaction from ABCI DeliverTx RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) + + // Other ABCI message handlers + SetOption(store KVStore, key string, value string) (log string) InitChain(store KVStore, vals []*abci.Validator) BeginBlock(store KVStore, height uint64) EndBlock(store KVStore, height uint64) []*abci.Validator @@ -16,12 +22,10 @@ type Plugin interface { //---------------------------------------- -// CallContext.Caller's coins have been deducted by CallContext.Coins -// Caller's Sequence has been incremented. type CallContext struct { - CallerAddress []byte - CallerAccount *Account - Coins Coins + CallerAddress []byte // Caller's Address (hash of PubKey) + CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted + TxInput Coins // The coins that the caller wishes to spend, excluding fees } func NewCallContext(callerAddress []byte, callerAccount *Account, coins Coins) CallContext { From cf33596bb1b8006217346abdbfd6dddaa7fcac60 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 27 Jan 2017 10:46:01 -0800 Subject: [PATCH 02/64] Fix Context field --- types/plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/plugin.go b/types/plugin.go index ab65452cce..55d3bb969f 100644 --- a/types/plugin.go +++ b/types/plugin.go @@ -25,7 +25,7 @@ type Plugin interface { type CallContext struct { CallerAddress []byte // Caller's Address (hash of PubKey) CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted - TxInput Coins // The coins that the caller wishes to spend, excluding fees + Coins Coins // The coins that the caller wishes to spend, excluding fees } func NewCallContext(callerAddress []byte, callerAccount *Account, coins Coins) CallContext { From b8374f4a9c6d98fbf7816fc56f143615be3c6052 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sat, 28 Jan 2017 09:29:32 -0800 Subject: [PATCH 03/64] Merge abci_proof --- Makefile | 9 ++-- app/app.go | 16 ++++++-- app/genesis.go | 63 ++++++++++++++++++++++++++++ cmd/basecoin/main.go | 73 ++++++++------------------------- cmd/paytovote/main.go | 53 ++++++++++++++++++++++++ glide.lock | 14 ++++--- glide.yaml | 1 - plugins/counter/counter.go | 3 +- plugins/counter/counter_test.go | 4 +- tests/tmsp/tmsp_test.go | 4 +- 10 files changed, 164 insertions(+), 76 deletions(-) create mode 100644 app/genesis.go create mode 100644 cmd/paytovote/main.go diff --git a/Makefile b/Makefile index d22c6561df..42ccfc69c9 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,11 @@ all: test install NOVENDOR = go list github.com/tendermint/basecoin/... | grep -v /vendor/ - -install: + +build: + go build github.com/tendermint/basecoin/cmd/... + +install: go install github.com/tendermint/basecoin/cmd/... test: @@ -20,4 +23,4 @@ update_deps: get_vendor_deps: go get github.com/Masterminds/glide glide install - + diff --git a/app/app.go b/app/app.go index a2a824814f..be618ae723 100644 --- a/app/app.go +++ b/app/app.go @@ -118,12 +118,20 @@ func (app *Basecoin) CheckTx(txBytes []byte) (res abci.Result) { } // TMSP::Query -func (app *Basecoin) Query(query []byte) (res abci.Result) { - if len(query) == 0 { - return abci.ErrEncodingError.SetLog("Query cannot be zero length") +func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { + if len(reqQuery.Data) == 0 { + resQuery.Log = "Query cannot be zero length" + resQuery.Code = abci.CodeType_EncodingError + return } - return app.eyesCli.QuerySync(query) + resQuery, err := app.eyesCli.QuerySync(reqQuery) + if err != nil { + resQuery.Log = "Failed to query MerkleEyes: " + err.Error() + resQuery.Code = abci.CodeType_InternalError + return + } + return } // TMSP::Commit diff --git a/app/genesis.go b/app/genesis.go new file mode 100644 index 0000000000..93848c893a --- /dev/null +++ b/app/genesis.go @@ -0,0 +1,63 @@ +package app + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/pkg/errors" + cmn "github.com/tendermint/go-common" +) + +func (app *Basecoin) LoadGenesis(path string) error { + kvz, err := loadGenesis(path) + if err != nil { + return err + } + for _, kv := range kvz { + log := app.SetOption(kv.Key, kv.Value) + // TODO: remove debug output + fmt.Printf("Set %v=%v. Log: %v", kv.Key, kv.Value, log) + } + return nil +} + +type keyValue struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func loadGenesis(filePath string) (kvz []keyValue, err error) { + kvz_ := []interface{}{} + bytes, err := cmn.ReadFile(filePath) + if err != nil { + return nil, errors.Wrap(err, "loading genesis file") + } + err = json.Unmarshal(bytes, &kvz_) + if err != nil { + return nil, errors.Wrap(err, "parsing genesis file") + } + if len(kvz_)%2 != 0 { + return nil, errors.New("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]") + } + for i := 0; i < len(kvz_); i += 2 { + keyIfc := kvz_[i] + valueIfc := kvz_[i+1] + var key, value string + key, ok := keyIfc.(string) + if !ok { + return nil, errors.Errorf("genesis had invalid key %v of type %v", keyIfc, reflect.TypeOf(keyIfc)) + } + if value_, ok := valueIfc.(string); ok { + value = value_ + } else { + valueBytes, err := json.Marshal(valueIfc) + if err != nil { + return nil, errors.Errorf("genesis had invalid value %v: %v", value_, err.Error()) + } + value = string(valueBytes) + } + kvz = append(kvz, keyValue{key, value}) + } + return kvz, nil +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 8d67c2d45d..5dc536b917 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -1,28 +1,32 @@ package main import ( - "encoding/json" "flag" - "fmt" - "reflect" "github.com/tendermint/abci/server" "github.com/tendermint/basecoin/app" - . "github.com/tendermint/go-common" + cmn "github.com/tendermint/go-common" eyes "github.com/tendermint/merkleeyes/client" ) func main() { - addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address") eyesPtr := flag.String("eyes", "local", "MerkleEyes address, or 'local' for embedded") + eyesDBNamePtr := flag.String("eyes-db-name", "local.db", "MerkleEyes db name, for embedded") + eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") genFilePath := flag.String("genesis", "", "Genesis file, if any") flag.Parse() // Connect to MerkleEyes - eyesCli, err := eyes.NewClient(*eyesPtr, "socket") - if err != nil { - Exit("connect to MerkleEyes: " + err.Error()) + var eyesCli *eyes.Client + if *eyesPtr == "local" { + eyesCli = eyes.NewLocalClient(*eyesDBNamePtr, *eyesCacheSizePtr) + } else { + var err error + eyesCli, err = eyes.NewClient(*eyesPtr) + if err != nil { + cmn.Exit("connect to MerkleEyes: " + err.Error()) + } } // Create Basecoin app @@ -30,65 +34,22 @@ func main() { // If genesis file was specified, set key-value options if *genFilePath != "" { - kvz := loadGenesis(*genFilePath) - for _, kv := range kvz { - log := app.SetOption(kv.Key, kv.Value) - fmt.Println(Fmt("Set %v=%v. Log: %v", kv.Key, kv.Value, log)) + err := app.LoadGenesis(*genFilePath) + if err != nil { + cmn.Exit(cmn.Fmt("%+v", err)) } } // Start the listener svr, err := server.NewServer(*addrPtr, "socket", app) if err != nil { - Exit("create listener: " + err.Error()) + cmn.Exit("create listener: " + err.Error()) } // Wait forever - TrapSignal(func() { + cmn.TrapSignal(func() { // Cleanup svr.Stop() }) } - -//---------------------------------------- - -type KeyValue struct { - Key string `json:"key"` - Value string `json:"value"` -} - -func loadGenesis(filePath string) (kvz []KeyValue) { - kvz_ := []interface{}{} - bytes, err := ReadFile(filePath) - if err != nil { - Exit("loading genesis file: " + err.Error()) - } - err = json.Unmarshal(bytes, &kvz_) - if err != nil { - Exit("parsing genesis file: " + err.Error()) - } - if len(kvz_)%2 != 0 { - Exit("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]") - } - for i := 0; i < len(kvz_); i += 2 { - keyIfc := kvz_[i] - valueIfc := kvz_[i+1] - var key, value string - key, ok := keyIfc.(string) - if !ok { - Exit(Fmt("genesis had invalid key %v of type %v", keyIfc, reflect.TypeOf(keyIfc))) - } - if value_, ok := valueIfc.(string); ok { - value = value_ - } else { - valueBytes, err := json.Marshal(valueIfc) - if err != nil { - Exit(Fmt("genesis had invalid value %v: %v", value_, err.Error())) - } - value = string(valueBytes) - } - kvz = append(kvz, KeyValue{key, value}) - } - return kvz -} diff --git a/cmd/paytovote/main.go b/cmd/paytovote/main.go new file mode 100644 index 0000000000..7c7715d992 --- /dev/null +++ b/cmd/paytovote/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "flag" + + "github.com/tendermint/abci/server" + "github.com/tendermint/basecoin/app" + "github.com/tendermint/basecoin/plugins/counter" + cmn "github.com/tendermint/go-common" + eyes "github.com/tendermint/merkleeyes/client" +) + +func main() { + addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address") + eyesPtr := flag.String("eyes", "local", "MerkleEyes address, or 'local' for embedded") + genFilePath := flag.String("genesis", "", "Genesis file, if any") + flag.Parse() + + // Connect to MerkleEyes + eyesCli, err := eyes.NewClient(*eyesPtr) + if err != nil { + cmn.Exit("connect to MerkleEyes: " + err.Error()) + } + + // Create Basecoin app + app := app.NewBasecoin(eyesCli) + + // add plugins + // TODO: add some more, like the cool voting app + counter := counter.New("counter") + app.RegisterPlugin(counter) + + // If genesis file was specified, set key-value options + if *genFilePath != "" { + err := app.LoadGenesis(*genFilePath) + if err != nil { + cmn.Exit(cmn.Fmt("%+v", err)) + } + } + + // Start the listener + svr, err := server.NewServer(*addrPtr, "socket", app) + if err != nil { + cmn.Exit("create listener: " + err.Error()) + } + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + svr.Stop() + }) + +} diff --git a/glide.lock b/glide.lock index 6d606a6d63..00754a8ad8 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: 3869944d14a8df914ffcad02c2ef3548173daba51c5ea697767f8af77c07b348 -updated: 2017-01-15T14:45:40.368426139-08:00 +updated: 2017-01-28T09:14:54.898268931-08:00 imports: - name: github.com/btcsuite/btcd version: afec1bd1245a4a19e6dfe1306974b733e7cbb9b8 @@ -25,6 +25,8 @@ imports: version: ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8 - name: github.com/mattn/go-isatty version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 +- name: github.com/pkg/errors + version: 248dadf4e9068a0b3e79f02ed0a610d935de5302 - name: github.com/syndtr/goleveldb version: 6ae1797c0b42b9323fc27ff7dcf568df88f2f33d subpackages: @@ -41,7 +43,7 @@ imports: - leveldb/table - leveldb/util - name: github.com/tendermint/abci - version: 05096de3687ac582bec63860b3dd384acd9149aa + version: 8df0bc3a40ccad0d2be10e33c62c404e65c92502 subpackages: - client - server @@ -52,7 +54,7 @@ imports: - edwards25519 - extra25519 - name: github.com/tendermint/go-common - version: 70e694ee76f09058ea38c9ba81b4aa621bd54df1 + version: 339e135776142939d82bc8e699db0bf391fd938d - name: github.com/tendermint/go-config version: e64b424499acd0eb9856b88e10c0dff41628c0d6 - name: github.com/tendermint/go-crypto @@ -68,7 +70,7 @@ imports: - name: github.com/tendermint/go-logger version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2 - name: github.com/tendermint/go-merkle - version: 2979c7eb8aa020fa1cf203654907dbb889703888 + version: 653cb1f631528351ddbc359b994eb0c96f0341cd - name: github.com/tendermint/go-p2p version: 67c9086b7458eb45b1970483decd01cd744c477a subpackages: @@ -85,12 +87,12 @@ imports: subpackages: - term - name: github.com/tendermint/merkleeyes - version: 2cf87e5f049ab6131aa4ea188c1b5b629d9b3bf9 + version: 00d915af3e425cf57c10afe502fd9e0a6a70acd4 subpackages: - app - client - name: github.com/tendermint/tendermint - version: 9a2dd8bc9279ed2a1a4d4f31cc151f8a621cceb3 + version: 7c15b54cccac574cfe673c473d4edff01c2503ec subpackages: - rpc/core/types - types diff --git a/glide.yaml b/glide.yaml index b3fddf0747..ecf4f151b4 100644 --- a/glide.yaml +++ b/glide.yaml @@ -18,6 +18,5 @@ import: version: develop - package: github.com/tendermint/abci version: develop - - package: github.com/gorilla/websocket version: v1.1.0 diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 61945f20c1..5dd42d1238 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -32,7 +32,7 @@ func (cp *CounterPlugin) StateKey() []byte { return []byte(fmt.Sprintf("CounterPlugin{name=%v}.State", cp.name)) } -func NewCounterPlugin(name string) *CounterPlugin { +func New(name string) *CounterPlugin { return &CounterPlugin{ name: name, } @@ -43,7 +43,6 @@ func (cp *CounterPlugin) SetOption(store types.KVStore, key string, value string } func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { - // Decode tx var tx CounterTx err := wire.ReadBinaryBytes(txBytes, &tx) diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index b9aa889464..7f07b4d319 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -15,7 +15,7 @@ import ( func TestCounterPlugin(t *testing.T) { // Basecoin initialization - eyesCli := eyescli.NewLocalClient() + eyesCli := eyescli.NewLocalClient("", 0) chainID := "test_chain_id" bcApp := app.NewBasecoin(eyesCli) bcApp.SetOption("base/chainID", chainID) @@ -23,7 +23,7 @@ func TestCounterPlugin(t *testing.T) { // Add Counter plugin counterPluginName := "testcounter" - counterPlugin := NewCounterPlugin(counterPluginName) + counterPlugin := New(counterPluginName) bcApp.RegisterPlugin(counterPlugin) // Account initialization diff --git a/tests/tmsp/tmsp_test.go b/tests/tmsp/tmsp_test.go index a6a03686a2..c00cadedbe 100644 --- a/tests/tmsp/tmsp_test.go +++ b/tests/tmsp/tmsp_test.go @@ -12,7 +12,7 @@ import ( ) func TestSendTx(t *testing.T) { - eyesCli := eyescli.NewLocalClient() + eyesCli := eyescli.NewLocalClient("", 0) chainID := "test_chain_id" bcApp := app.NewBasecoin(eyesCli) bcApp.SetOption("base/chainID", chainID) @@ -58,7 +58,7 @@ func TestSendTx(t *testing.T) { } func TestSequence(t *testing.T) { - eyesCli := eyescli.NewLocalClient() + eyesCli := eyescli.NewLocalClient("", 0) chainID := "test_chain_id" bcApp := app.NewBasecoin(eyesCli) bcApp.SetOption("base/chainID", chainID) From 9da85942c0bd71bd7596111937fc35bb95ee25c3 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sat, 28 Jan 2017 12:19:30 -0800 Subject: [PATCH 04/64] Move GoBasics --- README.md | 8 +++++--- GoBasics.md => docs/go_basics.md | 0 2 files changed, 5 insertions(+), 3 deletions(-) rename GoBasics.md => docs/go_basics.md (100%) diff --git a/README.md b/README.md index 794fc97ef4..dffeb26ae3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi ## Contents 1. [Installation](#installation) - 1. [(Advice for go novices)](./GoBasics.md) + 1. [Learn Go](#learn_go) 1. [Using the plugin system](#plugins) 1. [Forking the codebase](#forking) 1. [Tutorials and other reading](#tutorials) @@ -28,6 +28,10 @@ make install This will create the `basecoin` binary. +## Learn Go + +Check out our [guide to programming in Go](/docs/go_basics.md). + ## Plugins Basecoin handles public-key authentication of transaction, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), sending currency (one-to-one or n-to-n multisig), and providing merkle-proofs of the state. These are common factors that many people wish to have in a crypto-currency system, so instead of trying to start from scratch, you can take advantage of the basecoin plugin system. @@ -70,5 +74,3 @@ If you don't have much experience forking in go, there are a few tricks you want ## Tutorials We are working on some tutorials that will show you how to set up the genesis block, build a plugin to add custom logic, deploy to a tendermint testnet, and connect a UI to your blockchain. They should be published during the course of February 2017, so stay tuned.... - - diff --git a/GoBasics.md b/docs/go_basics.md similarity index 100% rename from GoBasics.md rename to docs/go_basics.md From 0b583ec97bf92df0364a2a9f0eb5691df3edea08 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sat, 28 Jan 2017 12:21:07 -0800 Subject: [PATCH 05/64] Update README markdown --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index dffeb26ae3..980ff07575 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi ## Contents 1. [Installation](#installation) - 1. [Learn Go](#learn_go) - 1. [Using the plugin system](#plugins) - 1. [Forking the codebase](#forking) - 1. [Tutorials and other reading](#tutorials) + 1. [Learn Go](#learn-go) + 1. [Using the plugin system](#using-the-plugin-system) + 1. [Forking the codebase](#forking-the-codebase) + 1. [Tutorials and other reading](#tutorials-and-other-reading) ## Installation @@ -32,7 +32,7 @@ This will create the `basecoin` binary. Check out our [guide to programming in Go](/docs/go_basics.md). -## Plugins +## Using the Plugin System Basecoin handles public-key authentication of transaction, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), sending currency (one-to-one or n-to-n multisig), and providing merkle-proofs of the state. These are common factors that many people wish to have in a crypto-currency system, so instead of trying to start from scratch, you can take advantage of the basecoin plugin system. @@ -55,7 +55,7 @@ An example is worth a 1000 words, so please take a look [at this example](https: There are a lot of changes on the dev branch, which should be merged in my early February, so experiment, but things will change soon.... -## Forking +## Forking the Codebase If you do want to fork basecoin, we would be happy if this was done in a public repo and any enhancements made as PRs on github. However, this is under the Apache license and you are free to keep the code private if you wish. @@ -71,6 +71,6 @@ If you don't have much experience forking in go, there are a few tricks you want * `git fetch upstream` * `git rebase upstream/master` (or whatever branch you want) -## Tutorials +## Tutorials and Other Reading We are working on some tutorials that will show you how to set up the genesis block, build a plugin to add custom logic, deploy to a tendermint testnet, and connect a UI to your blockchain. They should be published during the course of February 2017, so stay tuned.... From 1ff535d8834b092be3c4a9a9b126714d232f2b1a Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sat, 28 Jan 2017 17:01:07 -0800 Subject: [PATCH 06/64] Cost -> Fee --- plugins/counter/counter.go | 20 ++++++++++---------- plugins/counter/counter_test.go | 14 +++++++------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 5dd42d1238..8f3526818d 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -10,12 +10,12 @@ import ( type CounterPluginState struct { Counter int - TotalCost types.Coins + TotalFees types.Coins } type CounterTx struct { Valid bool - Cost types.Coins + Fee types.Coins } //-------------------------------------------------------------------------------- @@ -54,20 +54,20 @@ func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt if !tx.Valid { return abci.ErrInternalError.AppendLog("CounterTx.Valid must be true") } - if !tx.Cost.IsValid() { - return abci.ErrInternalError.AppendLog("CounterTx.Cost is not sorted or has zero amounts") + if !tx.Fee.IsValid() { + return abci.ErrInternalError.AppendLog("CounterTx.Fee is not sorted or has zero amounts") } - if !tx.Cost.IsNonnegative() { - return abci.ErrInternalError.AppendLog("CounterTx.Cost must be nonnegative") + if !tx.Fee.IsNonnegative() { + return abci.ErrInternalError.AppendLog("CounterTx.Fee must be nonnegative") } // Did the caller provide enough coins? - if !ctx.Coins.IsGTE(tx.Cost) { - return abci.ErrInsufficientFunds.AppendLog("CounterTx.Cost was not provided") + if !ctx.Coins.IsGTE(tx.Fee) { + return abci.ErrInsufficientFunds.AppendLog("CounterTx.Fee was not provided") } // TODO If there are any funds left over, return funds. - // e.g. !ctx.Coins.Minus(tx.Cost).IsZero() + // e.g. !ctx.Coins.Minus(tx.Fee).IsZero() // ctx.CallerAccount is synced w/ store, so just modify that and store it. // Load CounterPluginState @@ -82,7 +82,7 @@ func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt // Update CounterPluginState cpState.Counter += 1 - cpState.TotalCost = cpState.TotalCost.Plus(tx.Cost) + cpState.TotalFees = cpState.TotalFees.Plus(tx.Fee) // Save CounterPluginState store.Set(cp.StateKey(), wire.BinaryBytes(cpState)) diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index 7f07b4d319..6cc99bd3a3 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -35,14 +35,14 @@ func TestCounterPlugin(t *testing.T) { bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc))) // Deliver a CounterTx - DeliverCounterTx := func(gas int64, fee types.Coin, inputCoins types.Coins, inputSequence int, cost types.Coins) abci.Result { + DeliverCounterTx := func(gas int64, fee types.Coin, inputCoins types.Coins, inputSequence int, appFee types.Coins) abci.Result { // Construct an AppTx signature tx := &types.AppTx{ Gas: gas, Fee: fee, Name: counterPluginName, Input: types.NewTxInput(test1Acc.PubKey, inputCoins, inputSequence), - Data: wire.BinaryBytes(CounterTx{Valid: true, Cost: cost}), + Data: wire.BinaryBytes(CounterTx{Valid: true, Fee: appFee}), } // Sign request @@ -57,7 +57,7 @@ func TestCounterPlugin(t *testing.T) { return bcApp.DeliverTx(txBytes) } - // REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, cost) { + // REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, appFee) { // Test a basic send, no fee res := DeliverCounterTx(0, types.Coin{}, types.Coins{{"", 1}}, 1, types.Coins{}) @@ -75,15 +75,15 @@ func TestCounterPlugin(t *testing.T) { res = DeliverCounterTx(0, types.Coin{"", 2}, types.Coins{{"", 3}}, 3, types.Coins{}) assert.True(t, res.IsOK(), res.String()) - // Test input equals fee+cost + // Test input equals fee+appFee res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 1}}, 4, types.Coins{{"", 2}, {"gold", 1}}) assert.True(t, res.IsOK(), res.String()) - // Test fee+cost prevented transaction, not enough "" + // Test fee+appFee prevented transaction, not enough "" res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 2}, {"gold", 1}}, 5, types.Coins{{"", 2}, {"gold", 1}}) assert.True(t, res.IsErr(), res.String()) - // Test fee+cost prevented transaction, not enough "gold" + // Test fee+appFee prevented transaction, not enough "gold" res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 1}}, 5, types.Coins{{"", 2}, {"gold", 2}}) assert.True(t, res.IsErr(), res.String()) @@ -95,5 +95,5 @@ func TestCounterPlugin(t *testing.T) { res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 2}}, 7, types.Coins{{"", 2}, {"gold", 1}}) assert.True(t, res.IsOK(), res.String()) - // REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, cost) { + // REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, appFee) { } From 665b39e3309b230030dff570c0df7c6d496cc2b6 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 28 Jan 2017 21:12:58 -0500 Subject: [PATCH 07/64] new cli --- cmd/basecoin/main.go | 195 ++++++++++++++++++++++++++++++++---------- cmd/basecoin/start.go | 54 ++++++++++++ cmd/basecoin/tx.go | 101 ++++++++++++++++++++++ 3 files changed, 305 insertions(+), 45 deletions(-) create mode 100644 cmd/basecoin/start.go create mode 100644 cmd/basecoin/tx.go diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 5dc536b917..dc940a370f 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -1,55 +1,160 @@ package main import ( - "flag" + "os" - "github.com/tendermint/abci/server" - "github.com/tendermint/basecoin/app" - cmn "github.com/tendermint/go-common" - eyes "github.com/tendermint/merkleeyes/client" + "github.com/urfave/cli" +) + +// start flags +var ( + addrFlag = cli.StringFlag{ + Name: "address", + Value: "tcp://0.0.0.0:46658", + Usage: "Listen address", + } + + eyesFlag = cli.StringFlag{ + Name: "eyes", + Value: "local", + Usage: "MerkleEyes address, or 'local' for embedded", + } + + eyesDBFlag = cli.StringFlag{ + Name: "eyes-db", + Value: "merkleeyes.db", + Usage: "MerkleEyes db name for embedded", + } + + // TODO: move to config file + // eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") + + genesisFlag = cli.StringFlag{ + Name: "genesis", + Value: "", + Usage: "Path to genesis file, if it exists", + } + + inProcTMFlag = cli.BoolFlag{ + Name: "in-proc", + Usage: "Run Tendermint in-process with the App", + } +) + +// tx flags + +var ( + toFlag = cli.StringFlag{ + Name: "to", + Value: "", + Usage: "Destination address for the transaction", + } + + amountFlag = cli.IntFlag{ + Name: "amount", + Value: 0, + Usage: "Amount of coins to send in the transaction", + } + + fromFlag = cli.StringFlag{ + Name: "from", + Value: "priv_validator.json", + Usage: "Path to a private key to sign the transaction", + } + + seqFlag = cli.IntFlag{ + Name: "sequence", + Value: 0, + Usage: "Sequence number for the account", + } + + coinFlag = cli.StringFlag{ + Name: "coin", + Value: "blank", + Usage: "Specify a coin denomination", + } + + gasFlag = cli.IntFlag{ + Name: "gas", + Value: 0, + Usage: "The amount of gas for the transaction", + } + + feeFlag = cli.IntFlag{ + Name: "fee", + Value: 0, + Usage: "The transaction fee", + } + + dataFlag = cli.StringFlag{ + Name: "data", + Value: "", + Usage: "Data to send with the transaction", + } + + nameFlag = cli.StringFlag{ + Name: "name", + Value: "", + Usage: "Plugin to send the transaction to", + } ) func main() { - addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address") - eyesPtr := flag.String("eyes", "local", "MerkleEyes address, or 'local' for embedded") - eyesDBNamePtr := flag.String("eyes-db-name", "local.db", "MerkleEyes db name, for embedded") - eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") - genFilePath := flag.String("genesis", "", "Genesis file, if any") - flag.Parse() + app := cli.NewApp() + app.Name = "basecoin" + app.Usage = "basecoin [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + { + Name: "start", + Usage: "Start basecoin", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdStart(c) + }, + Flags: []cli.Flag{ + addrFlag, + eyesFlag, + eyesDBFlag, + genesisFlag, + inProcTMFlag, + }, + }, - // Connect to MerkleEyes - var eyesCli *eyes.Client - if *eyesPtr == "local" { - eyesCli = eyes.NewLocalClient(*eyesDBNamePtr, *eyesCacheSizePtr) - } else { - var err error - eyesCli, err = eyes.NewClient(*eyesPtr) - if err != nil { - cmn.Exit("connect to MerkleEyes: " + err.Error()) - } + { + Name: "sendtx", + Usage: "Broadcast a basecoin SendTx", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdSendTx(c) + }, + Flags: []cli.Flag{ + toFlag, + fromFlag, + amountFlag, + coinFlag, + gasFlag, + feeFlag, + }, + }, + + { + Name: "apptx", + Usage: "Broadcast a basecoin AppTx", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdAppTx(c) + }, + Flags: []cli.Flag{ + nameFlag, + fromFlag, + amountFlag, + coinFlag, + gasFlag, + feeFlag, + dataFlag, + }, + }, } - - // Create Basecoin app - app := app.NewBasecoin(eyesCli) - - // If genesis file was specified, set key-value options - if *genFilePath != "" { - err := app.LoadGenesis(*genFilePath) - if err != nil { - cmn.Exit(cmn.Fmt("%+v", err)) - } - } - - // Start the listener - svr, err := server.NewServer(*addrPtr, "socket", app) - if err != nil { - cmn.Exit("create listener: " + err.Error()) - } - - // Wait forever - cmn.TrapSignal(func() { - // Cleanup - svr.Stop() - }) - + app.Run(os.Args) } diff --git a/cmd/basecoin/start.go b/cmd/basecoin/start.go new file mode 100644 index 0000000000..fde62e3d62 --- /dev/null +++ b/cmd/basecoin/start.go @@ -0,0 +1,54 @@ +package main + +import ( + "errors" + + "github.com/urfave/cli" + + "github.com/tendermint/abci/server" + "github.com/tendermint/basecoin/app" + cmn "github.com/tendermint/go-common" + eyes "github.com/tendermint/merkleeyes/client" +) + +const EyesCacheSize = 10000 + +func cmdStart(c *cli.Context) error { + + // Connect to MerkleEyes + var eyesCli *eyes.Client + if c.String("eyes") == "local" { + eyesCli = eyes.NewLocalClient(c.String("eyes-db"), EyesCacheSize) + } else { + var err error + eyesCli, err = eyes.NewClient(c.String("eyes")) + if err != nil { + return errors.New("connect to MerkleEyes: " + err.Error()) + } + } + + // Create Basecoin app + app := app.NewBasecoin(eyesCli) + + // If genesis file was specified, set key-value options + if c.String("genesis") != "" { + err := app.LoadGenesis(c.String("genesis")) + if err != nil { + return errors.New(cmn.Fmt("%+v", err)) + } + } + + // Start the listener + svr, err := server.NewServer(c.String("address"), "socket", app) + if err != nil { + return errors.New("create listener: " + err.Error()) + } + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + svr.Stop() + }) + + return nil +} diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go new file mode 100644 index 0000000000..3f902963a1 --- /dev/null +++ b/cmd/basecoin/tx.go @@ -0,0 +1,101 @@ +package main + +import ( + "encoding/hex" + "errors" + "fmt" + + "github.com/urfave/cli" + + "github.com/tendermint/basecoin/types" + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-wire" + tmtypes "github.com/tendermint/tendermint/types" +) + +func cmdSendTx(c *cli.Context) error { + toHex := c.String("to") + fromFile := c.String("from") + amount := int64(c.Int("amount")) + coin := c.String("coin") + gas, fee := c.Int("gas"), int64(c.Int("fee")) + chainID := c.String("chain_id") + + to, err := hex.DecodeString(toHex) + if err != nil { + return errors.New("To address is invalid hex: " + err.Error()) + } + + privVal := tmtypes.LoadPrivValidator(fromFile) + + sequence := getSeq(c) + + input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) + output := newOutput(to, coin, amount) + + tx := types.SendTx{ + Gas: int64(gas), + Fee: types.Coin{coin, fee}, + Inputs: []types.TxInput{input}, + Outputs: []types.TxOutput{output}, + } + + tx.Inputs[0].Signature = privVal.Sign(tx.SignBytes(chainID)) + fmt.Println(string(wire.JSONBytes(tx))) + + return nil +} + +func cmdAppTx(c *cli.Context) error { + name := c.String("name") + fromFile := c.String("from") + amount := int64(c.Int("amount")) + coin := c.String("coin") + gas, fee := c.Int("gas"), int64(c.Int("fee")) + chainID := c.String("chain_id") + dataString := c.String("data") + + data := []byte(dataString) + if cmn.IsHex(dataString) { + data, _ = hex.DecodeString(dataString) + } + + privVal := tmtypes.LoadPrivValidator(fromFile) + + sequence := getSeq(c) + + input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) + + tx := types.AppTx{ + Gas: int64(gas), + Fee: types.Coin{coin, fee}, + Name: name, + Input: input, + Data: data, + } + + tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID)) + fmt.Println(string(wire.JSONBytes(tx))) + return nil +} + +func getSeq(c *cli.Context) int { + if c.IsSet("sequence") { + return c.Int("sequence") + } + // TODO: get from query + return 0 +} + +func newOutput(to []byte, coin string, amount int64) types.TxOutput { + return types.TxOutput{ + Address: to, + Coins: types.Coins{ + types.Coin{ + Denom: coin, + Amount: amount, + }, + }, + } + +} From 8262d0cc718ec0baaba8361bd784b5ee58dadb55 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 11:41:21 -0800 Subject: [PATCH 08/64] cli: working txs and account fetching --- cmd/basecoin/account.go | 69 +++++++++++++++++++++++++++ cmd/basecoin/main.go | 30 ++++++++++++ cmd/basecoin/start.go | 54 ++++++++++++++++++--- cmd/basecoin/tx.go | 103 +++++++++++++++++++++++++++++++++++----- 4 files changed, 237 insertions(+), 19 deletions(-) create mode 100644 cmd/basecoin/account.go diff --git a/cmd/basecoin/account.go b/cmd/basecoin/account.go new file mode 100644 index 0000000000..f3d3a4a8d7 --- /dev/null +++ b/cmd/basecoin/account.go @@ -0,0 +1,69 @@ +package main + +import ( + "encoding/hex" + "errors" + "fmt" + + "github.com/urfave/cli" + + "github.com/tendermint/basecoin/types" + cmn "github.com/tendermint/go-common" + client "github.com/tendermint/go-rpc/client" + "github.com/tendermint/go-wire" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +func cmdAccount(c *cli.Context) error { + if len(c.Args()) != 1 { + return errors.New("account command requires an argument ([address])") + } + addrHex := c.Args()[0] + + // convert destination address to bytes + addr, err := hex.DecodeString(addrHex) + if err != nil { + return errors.New("Account address is invalid hex: " + err.Error()) + } + + acc, err := getAcc(c, addr) + if err != nil { + return err + } + fmt.Println(string(wire.JSONBytes(acc))) + return nil +} + +// fetch the account by querying the app +func getAcc(c *cli.Context, address []byte) (*types.Account, error) { + tmAddr := c.String("tendermint") + clientURI := client.NewClientURI(tmAddr) + tmResult := new(ctypes.TMResult) + + params := map[string]interface{}{ + "path": "/key", + "data": append([]byte("base/a/"), address...), + "prove": false, + } + _, err := clientURI.Call("abci_query", params, tmResult) + if err != nil { + return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + } + res := (*tmResult).(*ctypes.ResultABCIQuery) + if !res.Response.Code.IsOK() { + return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) + } + accountBytes := res.Response.Value + + if len(accountBytes) == 0 { + return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) + } + var acc *types.Account + err = wire.ReadBinaryBytes(accountBytes, &acc) + if err != nil { + return nil, errors.New(cmn.Fmt("Error reading account %X error: %v", + accountBytes, err.Error())) + } + + return acc, nil +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index dc940a370f..6c4707f811 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -44,6 +44,12 @@ var ( // tx flags var ( + tmAddrFlag = cli.StringFlag{ + Name: "tendermint", + Value: "tcp://localhost:46657", + Usage: "Tendermint RPC address", + } + toFlag = cli.StringFlag{ Name: "to", Value: "", @@ -97,6 +103,12 @@ var ( Value: "", Usage: "Plugin to send the transaction to", } + + chainIDFlag = cli.StringFlag{ + Name: "chain_id", + Value: "test_chain_id", + Usage: "ID of the chain for replay protection", + } ) func main() { @@ -118,6 +130,7 @@ func main() { eyesDBFlag, genesisFlag, inProcTMFlag, + chainIDFlag, }, }, @@ -129,12 +142,15 @@ func main() { return cmdSendTx(c) }, Flags: []cli.Flag{ + tmAddrFlag, toFlag, fromFlag, amountFlag, coinFlag, gasFlag, feeFlag, + chainIDFlag, + seqFlag, }, }, @@ -146,6 +162,7 @@ func main() { return cmdAppTx(c) }, Flags: []cli.Flag{ + tmAddrFlag, nameFlag, fromFlag, amountFlag, @@ -153,6 +170,19 @@ func main() { gasFlag, feeFlag, dataFlag, + seqFlag, + }, + }, + + { + Name: "account", + Usage: "Get details of an account", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdAccount(c) + }, + Flags: []cli.Flag{ + tmAddrFlag, }, }, } diff --git a/cmd/basecoin/start.go b/cmd/basecoin/start.go index fde62e3d62..5d5c2de542 100644 --- a/cmd/basecoin/start.go +++ b/cmd/basecoin/start.go @@ -6,11 +6,21 @@ import ( "github.com/urfave/cli" "github.com/tendermint/abci/server" - "github.com/tendermint/basecoin/app" cmn "github.com/tendermint/go-common" + cfg "github.com/tendermint/go-config" + //logger "github.com/tendermint/go-logger" eyes "github.com/tendermint/merkleeyes/client" + + tmcfg "github.com/tendermint/tendermint/config/tendermint" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/proxy" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/tendermint/basecoin/app" ) +var config cfg.Config + const EyesCacheSize = 10000 func cmdStart(c *cli.Context) error { @@ -28,27 +38,57 @@ func cmdStart(c *cli.Context) error { } // Create Basecoin app - app := app.NewBasecoin(eyesCli) + basecoinApp := app.NewBasecoin(eyesCli) // If genesis file was specified, set key-value options if c.String("genesis") != "" { - err := app.LoadGenesis(c.String("genesis")) + err := basecoinApp.LoadGenesis(c.String("genesis")) if err != nil { return errors.New(cmn.Fmt("%+v", err)) } } - // Start the listener - svr, err := server.NewServer(c.String("address"), "socket", app) + if c.Bool("in-proc") { + startTendermint(c, basecoinApp) + } else { + startBasecoinABCI(c, basecoinApp) + } + + return nil +} + +func startBasecoinABCI(c *cli.Context, basecoinApp *app.Basecoin) error { + // Start the ABCI listener + svr, err := server.NewServer(c.String("address"), "socket", basecoinApp) if err != nil { return errors.New("create listener: " + err.Error()) } - // Wait forever cmn.TrapSignal(func() { // Cleanup svr.Stop() }) - return nil + +} + +func startTendermint(c *cli.Context, basecoinApp *app.Basecoin) { + // Get configuration + config = tmcfg.GetConfig("") + // logger.SetLogLevel("notice") //config.GetString("log_level")) + + // parseFlags(config, args[1:]) // Command line overrides + + // Create & start tendermint node + privValidatorFile := config.GetString("priv_validator_file") + privValidator := tmtypes.LoadOrGenPrivValidator(privValidatorFile) + n := node.NewNode(config, privValidator, proxy.NewLocalClientCreator(basecoinApp)) + + n.Start() + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + n.Stop() + }) } diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index 3f902963a1..e70d9a9ffa 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -9,7 +9,9 @@ import ( "github.com/tendermint/basecoin/types" cmn "github.com/tendermint/go-common" + client "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" + ctypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" ) @@ -21,28 +23,44 @@ func cmdSendTx(c *cli.Context) error { gas, fee := c.Int("gas"), int64(c.Int("fee")) chainID := c.String("chain_id") + // convert destination address to bytes to, err := hex.DecodeString(toHex) if err != nil { return errors.New("To address is invalid hex: " + err.Error()) } + // load the priv validator + // XXX: this is overkill for now, we need a keys solution privVal := tmtypes.LoadPrivValidator(fromFile) - sequence := getSeq(c) + // get the sequence number for the tx + sequence, err := getSeq(c, privVal.Address) + if err != nil { + return err + } + // craft the tx input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) output := newOutput(to, coin, amount) - - tx := types.SendTx{ + tx := &types.SendTx{ Gas: int64(gas), Fee: types.Coin{coin, fee}, Inputs: []types.TxInput{input}, Outputs: []types.TxOutput{output}, } - tx.Inputs[0].Signature = privVal.Sign(tx.SignBytes(chainID)) + // sign that puppy + signBytes := tx.SignBytes(chainID) + tx.Inputs[0].Signature = privVal.Sign(signBytes) + + fmt.Println("Signed SendTx:") fmt.Println(string(wire.JSONBytes(tx))) + // broadcast the transaction to tendermint + if err := broadcastTx(c, tx); err != nil { + return err + } + return nil } @@ -55,6 +73,7 @@ func cmdAppTx(c *cli.Context) error { chainID := c.String("chain_id") dataString := c.String("data") + // convert data to bytes data := []byte(dataString) if cmn.IsHex(dataString) { data, _ = hex.DecodeString(dataString) @@ -62,11 +81,13 @@ func cmdAppTx(c *cli.Context) error { privVal := tmtypes.LoadPrivValidator(fromFile) - sequence := getSeq(c) + sequence, err := getSeq(c, privVal.Address) + if err != nil { + return err + } input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) - - tx := types.AppTx{ + tx := &types.AppTx{ Gas: int64(gas), Fee: types.Coin{coin, fee}, Name: name, @@ -75,16 +96,74 @@ func cmdAppTx(c *cli.Context) error { } tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID)) + + fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) + + if err := broadcastTx(c, tx); err != nil { + return err + } + return nil } -func getSeq(c *cli.Context) int { - if c.IsSet("sequence") { - return c.Int("sequence") +// broadcast the transaction to tendermint +func broadcastTx(c *cli.Context, tx types.Tx) error { + tmResult := new(ctypes.TMResult) + tmAddr := c.String("tendermint") + clientURI := client.NewClientURI(tmAddr) + + /*txBytes := []byte(wire.JSONBytes(struct { + types.Tx `json:"unwrap"` + }{tx}))*/ + txBytes := wire.BinaryBytes(tx) + _, err := clientURI.Call("broadcast_tx_sync", map[string]interface{}{"tx": txBytes}, tmResult) + if err != nil { + return errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) } - // TODO: get from query - return 0 + res := (*tmResult).(*ctypes.ResultBroadcastTx) + if !res.Code.IsOK() { + return errors.New(cmn.Fmt("BroadcastTxSync got non-zero exit code: %v. %X; %s", res.Code, res.Data, res.Log)) + } + return nil +} + +// if the sequence flag is set, return it; +// else, fetch the account by querying the app and return the sequence number +func getSeq(c *cli.Context, address []byte) (int, error) { + if c.IsSet("sequence") { + return c.Int("sequence"), nil + } + tmAddr := c.String("tendermint") + clientURI := client.NewClientURI(tmAddr) + tmResult := new(ctypes.TMResult) + + params := map[string]interface{}{ + "path": "/key", + "data": append([]byte("base/a/"), address...), + "prove": false, + } + _, err := clientURI.Call("abci_query", params, tmResult) + if err != nil { + return 0, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + } + res := (*tmResult).(*ctypes.ResultABCIQuery) + if !res.Response.Code.IsOK() { + return 0, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) + } + accountBytes := res.Response.Value + + if len(accountBytes) == 0 { + return 0, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) + } + var acc *types.Account + err = wire.ReadBinaryBytes(accountBytes, &acc) + if err != nil { + return 0, errors.New(cmn.Fmt("Error reading account %X error: %v", + accountBytes, err.Error())) + } + + return acc.Sequence + 1, nil } func newOutput(to []byte, coin string, amount int64) types.TxOutput { From 7bb21c4795ffda6770d24b2170f2613387e0de34 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 12:43:30 -0800 Subject: [PATCH 09/64] cleanup readme --- README.md | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 980ff07575..6f93f0398a 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,7 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi ## Contents 1. [Installation](#installation) - 1. [Learn Go](#learn-go) 1. [Using the plugin system](#using-the-plugin-system) - 1. [Forking the codebase](#forking-the-codebase) 1. [Tutorials and other reading](#tutorials-and-other-reading) ## Installation @@ -26,15 +24,15 @@ make get_vendor_deps make install ``` -This will create the `basecoin` binary. - -## Learn Go - -Check out our [guide to programming in Go](/docs/go_basics.md). +This will create the `basecoin` binary in `$GOPATH/bin`. ## Using the Plugin System -Basecoin handles public-key authentication of transaction, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), sending currency (one-to-one or n-to-n multisig), and providing merkle-proofs of the state. These are common factors that many people wish to have in a crypto-currency system, so instead of trying to start from scratch, you can take advantage of the basecoin plugin system. +Basecoin is designed to serve as a common base layer for developers building cryptocurrency applications. +It handles public-key authentication of transactions, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), +sending currency (one-to-one or n-to-m multisig), and providing merkle-proofs of the state. +These are common factors that many people wish to have in a crypto-currency system, +so instead of trying to start from scratch, developers can extend the functionality of Basecoin using the plugin system! The Plugin interface is defined in `types/plugin.go`: @@ -49,28 +47,17 @@ type Plugin interface { } ``` -`RunTx` is where you can handle any special transactions directed to your application. To see a very simple implementation, look at the demo [counter plugin](./plugins/counter/counter.go). If you want to create your own currency using a plugin, you don't have to fork basecoin at all. Just make your own repo, add the implementation of your custom plugin, and then build your own main script that instatiates BaseCoin and registers your plugin. +`RunTx` is where you can handle any special transactions directed to your application. +To see a very simple implementation, look at the demo [counter plugin](./plugins/counter/counter.go). +If you want to create your own currency using a plugin, you don't have to fork basecoin at all. +Just make your own repo, add the implementation of your custom plugin, and then build your own main script that instatiates Basecoin and registers your plugin. -An example is worth a 1000 words, so please take a look [at this example](https://github.com/tendermint/basecoin/blob/abci_proof/cmd/paytovote/main.go#L25-L31), in a dev branch for now. You can use the same technique in your own repo. - -There are a lot of changes on the dev branch, which should be merged in my early February, so experiment, but things will change soon.... - -## Forking the Codebase - -If you do want to fork basecoin, we would be happy if this was done in a public repo and any enhancements made as PRs on github. However, this is under the Apache license and you are free to keep the code private if you wish. - -If you don't have much experience forking in go, there are a few tricks you want to keep in mind to avoid headaches. Basically, all imports in go are absolute from GOPATH, so if you fork a repo with more than one directory, and you put it under github.com/MYNAME/repo, all the code will start caling github.com/ORIGINAL/repo, which is very confusing. My prefered solution to this is as follows: - - * Create your own fork on github, using the fork button. - * Go to the original repo checked out locally (from `go get`) - * `git remote rename origin upstream` - * `git remote add origin git@github.com:YOUR-NAME/basecoin.git` - * `git push -u origin master` - * You can now push all changes to your fork and all code compiles, all other code referencing the original repo, now references your fork. - * If you want to pull in updates from the original repo: - * `git fetch upstream` - * `git rebase upstream/master` (or whatever branch you want) +An example is worth a 1000 words, so please take a look [at this example](https://github.com/tendermint/basecoin/blob/develop/cmd/paytovote/main.go#L25-L31). +Note for now it is in a dev branch. +You can use the same technique in your own repo. ## Tutorials and Other Reading +See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. + We are working on some tutorials that will show you how to set up the genesis block, build a plugin to add custom logic, deploy to a tendermint testnet, and connect a UI to your blockchain. They should be published during the course of February 2017, so stay tuned.... From 241c1638762256ad1d0ce1f107486fc8d44830f9 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 12:48:28 -0800 Subject: [PATCH 10/64] update README, add data --- README.md | 8 ++++++++ cmd/basecoin/main.go | 2 +- data/genesis.json | 12 ++++++++++++ data/priv_validator.json | 17 +++++++++++++++++ data/priv_validator2.json | 16 ++++++++++++++++ genesis.json | 7 ------- 6 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 data/genesis.json create mode 100644 data/priv_validator.json create mode 100644 data/priv_validator2.json delete mode 100644 genesis.json diff --git a/README.md b/README.md index 6f93f0398a..5cbb2e526e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi 1. [Installation](#installation) 1. [Using the plugin system](#using-the-plugin-system) + 1. [Using the cli](#using-the-cli) 1. [Tutorials and other reading](#tutorials-and-other-reading) ## Installation @@ -56,6 +57,13 @@ An example is worth a 1000 words, so please take a look [at this example](https: Note for now it is in a dev branch. You can use the same technique in your own repo. +## Using the CLI + +The basecoin cli can be used to start a stand-alone basecoin instance (`basecoin start`), +or to start basecoin with tendermint in the same process (`basecoin start --in-proc`). +It can also be used to send transactions, eg. `basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` +See `basecoin --help` and `basecoin [cmd] --help` for more details`. + ## Tutorials and Other Reading See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 6c4707f811..741c94f1f5 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -177,7 +177,7 @@ func main() { { Name: "account", Usage: "Get details of an account", - ArgsUsage: "", + ArgsUsage: "[address]", Action: func(c *cli.Context) error { return cmdAccount(c) }, diff --git a/data/genesis.json b/data/genesis.json new file mode 100644 index 0000000000..7aea6cb9bc --- /dev/null +++ b/data/genesis.json @@ -0,0 +1,12 @@ +[ + "base/chainID", "test_chain_id", + "base/account", { + "pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"], + "coins": [ + { + "denom": "blank", + "amount": 9007199254740992 + } + ] + } +] diff --git a/data/priv_validator.json b/data/priv_validator.json new file mode 100644 index 0000000000..15d7919240 --- /dev/null +++ b/data/priv_validator.json @@ -0,0 +1,17 @@ +{ + "address": "D397BC62B435F3CF50570FBAB4340FE52C60858F", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": [ + 1, + "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" + ], + "pub_key": [ + 1, + "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" + ] +} + diff --git a/data/priv_validator2.json b/data/priv_validator2.json new file mode 100644 index 0000000000..08256d1fd8 --- /dev/null +++ b/data/priv_validator2.json @@ -0,0 +1,16 @@ +{ + "address": "4793A333846E5104C46DD9AB9A00E31821B2F301", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": [ + 1, + "13A04A552ABAA2CCFA1F618CF9C97F1FD59FC3EE4968FE87DF3637C9B0F2FAAA93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E" + ], + "pub_key": [ + 1, + "93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E" + ] +} diff --git a/genesis.json b/genesis.json deleted file mode 100644 index 49b7b8605e..0000000000 --- a/genesis.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - "base/chainID", "test_chain_id", - "base/account", { - "pub_key": [1, "67D3B5EAF0C0BF6B5A602D359DAECC86A7A74053490EC37AE08E71360587C870"], - "balance": 9007199254740992 - } -] From 4ff02fd681fd322fca3d2d12bbb42526b67813fd Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 13:34:48 -0800 Subject: [PATCH 11/64] cmd: utils.go --- cmd/basecoin/account.go | 42 ++------------------------ cmd/basecoin/tx.go | 32 +++----------------- cmd/basecoin/utils.go | 65 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 68 deletions(-) create mode 100644 cmd/basecoin/utils.go diff --git a/cmd/basecoin/account.go b/cmd/basecoin/account.go index f3d3a4a8d7..22b4e076fc 100644 --- a/cmd/basecoin/account.go +++ b/cmd/basecoin/account.go @@ -7,18 +7,14 @@ import ( "github.com/urfave/cli" - "github.com/tendermint/basecoin/types" - cmn "github.com/tendermint/go-common" - client "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" - ctypes "github.com/tendermint/tendermint/rpc/core/types" ) func cmdAccount(c *cli.Context) error { if len(c.Args()) != 1 { return errors.New("account command requires an argument ([address])") } - addrHex := c.Args()[0] + addrHex := stripHex(c.Args()[0]) // convert destination address to bytes addr, err := hex.DecodeString(addrHex) @@ -26,44 +22,10 @@ func cmdAccount(c *cli.Context) error { return errors.New("Account address is invalid hex: " + err.Error()) } - acc, err := getAcc(c, addr) + acc, err := getAcc(c.String("tendermint"), addr) if err != nil { return err } fmt.Println(string(wire.JSONBytes(acc))) return nil } - -// fetch the account by querying the app -func getAcc(c *cli.Context, address []byte) (*types.Account, error) { - tmAddr := c.String("tendermint") - clientURI := client.NewClientURI(tmAddr) - tmResult := new(ctypes.TMResult) - - params := map[string]interface{}{ - "path": "/key", - "data": append([]byte("base/a/"), address...), - "prove": false, - } - _, err := clientURI.Call("abci_query", params, tmResult) - if err != nil { - return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) - } - res := (*tmResult).(*ctypes.ResultABCIQuery) - if !res.Response.Code.IsOK() { - return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) - } - accountBytes := res.Response.Value - - if len(accountBytes) == 0 { - return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) - } - var acc *types.Account - err = wire.ReadBinaryBytes(accountBytes, &acc) - if err != nil { - return nil, errors.New(cmn.Fmt("Error reading account %X error: %v", - accountBytes, err.Error())) - } - - return acc, nil -} diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index e70d9a9ffa..4b0bd83845 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -24,7 +24,7 @@ func cmdSendTx(c *cli.Context) error { chainID := c.String("chain_id") // convert destination address to bytes - to, err := hex.DecodeString(toHex) + to, err := hex.DecodeString(stripHex(toHex)) if err != nil { return errors.New("To address is invalid hex: " + err.Error()) } @@ -75,7 +75,7 @@ func cmdAppTx(c *cli.Context) error { // convert data to bytes data := []byte(dataString) - if cmn.IsHex(dataString) { + if isHex(dataString) { data, _ = hex.DecodeString(dataString) } @@ -135,34 +135,10 @@ func getSeq(c *cli.Context, address []byte) (int, error) { return c.Int("sequence"), nil } tmAddr := c.String("tendermint") - clientURI := client.NewClientURI(tmAddr) - tmResult := new(ctypes.TMResult) - - params := map[string]interface{}{ - "path": "/key", - "data": append([]byte("base/a/"), address...), - "prove": false, - } - _, err := clientURI.Call("abci_query", params, tmResult) + acc, err := getAcc(tmAddr, address) if err != nil { - return 0, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + return 0, err } - res := (*tmResult).(*ctypes.ResultABCIQuery) - if !res.Response.Code.IsOK() { - return 0, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) - } - accountBytes := res.Response.Value - - if len(accountBytes) == 0 { - return 0, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) - } - var acc *types.Account - err = wire.ReadBinaryBytes(accountBytes, &acc) - if err != nil { - return 0, errors.New(cmn.Fmt("Error reading account %X error: %v", - accountBytes, err.Error())) - } - return acc.Sequence + 1, nil } diff --git a/cmd/basecoin/utils.go b/cmd/basecoin/utils.go new file mode 100644 index 0000000000..a944c5bd10 --- /dev/null +++ b/cmd/basecoin/utils.go @@ -0,0 +1,65 @@ +package main + +import ( + "encoding/hex" + "errors" + + "github.com/tendermint/basecoin/types" + + cmn "github.com/tendermint/go-common" + client "github.com/tendermint/go-rpc/client" + "github.com/tendermint/go-wire" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// Returns true for non-empty hex-string prefixed with "0x" +func isHex(s string) bool { + if len(s) > 2 && s[:2] == "0x" { + _, err := hex.DecodeString(s[2:]) + if err != nil { + return false + } + return true + } + return false +} + +func stripHex(s string) string { + if isHex(s) { + return s[2:] + } + return s +} + +// fetch the account by querying the app +func getAcc(tmAddr string, address []byte) (*types.Account, error) { + clientURI := client.NewClientURI(tmAddr) + tmResult := new(ctypes.TMResult) + + params := map[string]interface{}{ + "path": "/key", + "data": append([]byte("base/a/"), address...), + "prove": false, + } + _, err := clientURI.Call("abci_query", params, tmResult) + if err != nil { + return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err)) + } + res := (*tmResult).(*ctypes.ResultABCIQuery) + if !res.Response.Code.IsOK() { + return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) + } + accountBytes := res.Response.Value + + if len(accountBytes) == 0 { + return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) + } + var acc *types.Account + err = wire.ReadBinaryBytes(accountBytes, &acc) + if err != nil { + return nil, errors.New(cmn.Fmt("Error reading account %X error: %v", + accountBytes, err.Error())) + } + + return acc, nil +} From 8d17dda2a606703a06c638b00b9f3d3db5e7db2f Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sat, 28 Jan 2017 21:26:43 -0800 Subject: [PATCH 12/64] Begin implementing IBC plugin --- plugins/ibc/ibc.go | 350 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 plugins/ibc/ibc.go diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go new file mode 100644 index 0000000000..8a8d4d9fcb --- /dev/null +++ b/plugins/ibc/ibc.go @@ -0,0 +1,350 @@ +package ibc + +import ( + "errors" + "net/url" + "strings" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/types" + cmn "github.com/tendermint/go-common" + merkle "github.com/tendermint/go-merkle" + "github.com/tendermint/go-wire" + tm "github.com/tendermint/tendermint/types" +) + +const ( + // Key parts + _IBC = "ibc" + _BLOCKCHAIN = "blockchain" + _GENESIS = "genesis" + _STATE = "state" + _HEADER = "header" + _EGRESS = "egress" + _CONNECTION = "connection" +) + +type IBCPluginState struct { + // @[:ibc, :blockchain, :genesis, ChainID] <~ BlockchainGenesis + // @[:ibc, :blockchain, :state, ChainID] <~ BlockchainState + // @[:ibc, :blockchain, :header, ChainID, Height] <~ tm.Header + // @[:ibc, :egress, Src, Dst, Sequence] <~ Packet + // @[:ibc, :connection, Src, Dst] <~ Connection # TODO - keep connection state +} + +type BlockchainGenesis struct { + ChainID string + Genesis string +} + +type BlockchainState struct { + ChainID string + Validators []*tm.Validator + LastBlockHash []byte + LastBlockHeight uint64 +} + +type Packet struct { + SrcChainID string + DstChainID string + Sequence uint64 + Type string + Payload []byte +} + +//-------------------------------------------------------------------------------- + +const ( + IBCTxTypeRegisterChain = byte(0x01) + IBCTxTypeUpdateChain = byte(0x02) + IBCTxTypePacket = byte(0x03) +) + +var _ = wire.RegisterInterface( + struct{ IBCTx }{}, + wire.ConcreteType{IBCRegisterChainTx{}, IBCTxTypeRegisterChain}, + wire.ConcreteType{IBCUpdateChainTx{}, IBCTxTypeUpdateChain}, + wire.ConcreteType{IBCPacketTx{}, IBCTxTypePacket}, +) + +type IBCTx interface { + AssertIsIBCTx() + ValidateBasic() abci.Result +} + +func (IBCRegisterChainTx) AssertIsIBCTx() {} +func (IBCUpdateChainTx) AssertIsIBCTx() {} +func (IBCPacketTx) AssertIsIBCTx() {} + +type IBCRegisterChainTx struct { + BlockchainGenesis +} + +func (IBCRegisterChainTx) ValidateBasic() (res abci.Result) { + // TODO - validate + return +} + +type IBCUpdateChainTx struct { + Header tm.Header + Commit tm.Commit + // TODO: NextValidators +} + +func (IBCUpdateChainTx) ValidateBasic() (res abci.Result) { + // TODO - validate + return +} + +type IBCPacketTx struct { + FromChainID string // The immediate source of the packet, not always Packet.SrcChainID + FromChainHeight uint64 // The block height in which Packet was committed, to check Proof + Packet + Proof merkle.IAVLProof +} + +func (IBCPacketTx) ValidateBasic() (res abci.Result) { + // TODO - validate + return +} + +//-------------------------------------------------------------------------------- + +type IBCPlugin struct { +} + +func (ibc *IBCPlugin) Name() string { + return "IBC" +} + +func (ibc *IBCPlugin) StateKey() []byte { + return []byte("IBCPlugin.State") +} + +func New() *IBCPlugin { + return &IBCPlugin{} +} + +func (ibc *IBCPlugin) SetOption(store types.KVStore, key string, value string) (log string) { + return "" +} + +func (ibc *IBCPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { + // Decode tx + var tx IBCTx + err := wire.ReadBinaryBytes(txBytes, &tx) + if err != nil { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + } + + // Validate tx + res = tx.ValidateBasic() + if res.IsErr() { + return res.PrependLog("ValidateBasic Failed: ") + } + + // TODO - Check whether sufficient funds + + defer func() { + // TODO - Refund any remaining funds left over + // e.g. !ctx.Coins.Minus(tx.Fee).IsZero() + // ctx.CallerAccount is synced w/ store, so just modify that and store it. + // NOTE: We should use the CallContext to store fund/refund information. + }() + + sm := &IBCStateMachine{store, ctx, abci.OK} + + switch tx := tx.(type) { + case IBCRegisterChainTx: + sm.runRegisterChainTx(tx) + case IBCUpdateChainTx: + sm.runUpdateChainTx(tx) + case IBCPacketTx: + sm.runPacketTx(tx) + } + + return sm.res +} + +type IBCStateMachine struct { + store types.KVStore + ctx types.CallContext + res abci.Result +} + +func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { + chainGenKey := toKey(_IBC, _BLOCKCHAIN, _GENESIS, tx.ChainID) + chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, tx.ChainID) + chainGen := tx.BlockchainGenesis + + // Parse genesis + var chainGenDoc = &tm.GenesisDoc{} + var err error + wire.ReadJSONPtr(&chainGenDoc, []byte(chainGen.Genesis), &err) + if err != nil { + sm.res.AppendLog("Genesis doc couldn't be parsed: " + err.Error()) + return + } + + // Make sure chainGen doesn't already exist + if exists(sm.store, chainGenKey) { + sm.res.AppendLog("Already exists") + return + } + + // Save new BlockchainGenesis + save(sm.store, chainGenKey, chainGen) + + // Create new BlockchainState + chainState := BlockchainState{ + ChainID: chainGenDoc.ChainID, + Validators: make([]*tm.Validator, len(chainGenDoc.Validators)), + LastBlockHash: nil, + LastBlockHeight: 0, + } + // Make validators slice + for i, val := range chainGenDoc.Validators { + pubKey := val.PubKey + address := pubKey.Address() + chainState.Validators[i] = &tm.Validator{ + Address: address, + PubKey: pubKey, + VotingPower: val.Amount, + } + } + + // Save new BlockchainState + save(sm.store, chainStateKey, chainState) +} + +func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) { + chainID := tx.Header.ChainID + chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, chainID) + + // Make sure chainState exists + if !exists(sm.store, chainStateKey) { + return // Chain does not exist, do nothing + } + + // Load latest chainState + var chainState BlockchainState + exists, err := load(sm.store, chainStateKey, &chainState) + if err != nil { + sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading ChainState: %v", err.Error())) + return + } + if !exists { + sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Missing ChainState")) + return + } + + // Check commit against last known state & validators + err = verifyCommit(chainState, &tx.Header, &tx.Commit) + if err != nil { + sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Invalid Commit: %v", err.Error())) + return + } + + // Store header + headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER, chainID, cmn.Fmt("%v", tx.Header.Height)) + save(sm.store, headerKey, tx.Header) + + // Update chainState + chainState.LastBlockHash = tx.Header.Hash() + chainState.LastBlockHeight = uint64(tx.Header.Height) + + // Store chainState + save(sm.store, chainStateKey, chainState) +} + +func (sm *IBCStateMachine) runPacketTx(tx IBCPacketTx) { + // TODO Make sure packat doesn't already exist + // TODO Load associated blockHash and make sure it exists + // TODO compute packet key + // TODO Make sure packet's proof matches given (packet, key, blockhash) + // TODO Store packet +} + +func (ibc *IBCPlugin) InitChain(store types.KVStore, vals []*abci.Validator) { +} + +func (ibc *IBCPlugin) BeginBlock(store types.KVStore, height uint64) { +} + +func (ibc *IBCPlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator { + return nil +} + +//-------------------------------------------------------------------------------- +// TODO: move to utils + +// Returns true if exists, false if nil. +func exists(store types.KVStore, key []byte) (exists bool) { + value := store.Get(key) + return len(value) > 0 +} + +// Load bytes from store by reading value for key and read into ptr. +// Returns true if exists, false if nil. +// Returns err if decoding error. +func load(store types.KVStore, key []byte, ptr interface{}) (exists bool, err error) { + value := store.Get(key) + if len(value) > 0 { + err = wire.ReadBinaryBytes(value, ptr) + if err != nil { + return true, errors.New( + cmn.Fmt("Error decoding key 0x%X = 0x%X: %v", key, value, err.Error()), + ) + } + return true, nil + } else { + return false, nil + } +} + +// Save bytes to store by writing obj's go-wire binary bytes. +func save(store types.KVStore, key []byte, obj interface{}) { + store.Set(key, wire.BinaryBytes(obj)) +} + +// Key parts are URL escaped and joined with ',' +func toKey(parts ...string) []byte { + escParts := make([]string, len(parts)) + for i, part := range parts { + escParts[i] = url.QueryEscape(part) + } + return []byte(strings.Join(escParts, ",")) +} + +// NOTE: Commit's votes include ValidatorAddress, so can be matched up +// against chainState.Validators, even if the validator set had changed. +// For the purpose of the demo, we assume that the validator set hadn't changed, +// though we should check that explicitly. +func verifyCommit(chainState BlockchainState, header *tm.Header, commit *tm.Commit) error { + + // Ensure that chainState and header ChainID match. + if chainState.ChainID != header.ChainID { + return errors.New(cmn.Fmt("Expected header.ChainID %v, got %v", chainState.ChainID, header.ChainID)) + } + if len(chainState.Validators) == 0 { + return errors.New(cmn.Fmt("Blockchain has no validators")) // NOTE: Why would this happen? + } + if len(commit.Precommits) == 0 { + return errors.New(cmn.Fmt("Commit has no signatures")) + } + chainID := chainState.ChainID + vote0 := commit.Precommits[0] + vals := chainState.Validators + valSet := tm.NewValidatorSet(vals) + + // NOTE: Currently this only works with the exact same validator set. + // Not this, but perhaps "ValidatorSet.VerifyCommitAny" should expose + // the functionality to verify commits even after validator changes. + err := valSet.VerifyCommit(chainID, vote0.BlockID, vote0.Height, commit) + if err != nil { + return err + } + + // All ok! + return nil +} From 1fea9501d1b858bf3d3a1ff172cf387a0c72fcc6 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 15:23:50 -0800 Subject: [PATCH 13/64] Added Contributing --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 5cbb2e526e..cfa31cdb4c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi 1. [Using the plugin system](#using-the-plugin-system) 1. [Using the cli](#using-the-cli) 1. [Tutorials and other reading](#tutorials-and-other-reading) + 1. [Contributing](#contributing) ## Installation @@ -69,3 +70,19 @@ See `basecoin --help` and `basecoin [cmd] --help` for more details`. See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. We are working on some tutorials that will show you how to set up the genesis block, build a plugin to add custom logic, deploy to a tendermint testnet, and connect a UI to your blockchain. They should be published during the course of February 2017, so stay tuned.... + +## Contributing + +We will merge in interesting plugin implementations and improvements to Basecoin. + +If you don't have much experience forking in go, there are a few tricks you want to keep in mind to avoid headaches. Basically, all imports in go are absolute from GOPATH, so if you fork a repo with more than one directory, and you put it under github.com/MYNAME/repo, all the code will start caling github.com/ORIGINAL/repo, which is very confusing. My prefered solution to this is as follows: + + * Create your own fork on github, using the fork button. + * Go to the original repo checked out locally (from `go get`) + * `git remote rename origin upstream` + * `git remote add origin git@github.com:YOUR-NAME/basecoin.git` + * `git push -u origin master` + * You can now push all changes to your fork and all code compiles, all other code referencing the original repo, now references your fork. + * If you want to pull in updates from the original repo: + * `git fetch upstream` + * `git rebase upstream/master` (or whatever branch you want) From f8115028266cea72bfc222579662e90a2ff166ca Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 15:32:38 -0800 Subject: [PATCH 14/64] cmd: counter tx --- app/app.go | 3 + cmd/basecoin/cmd.go | 98 ++++++++++++++++++++ cmd/basecoin/flags.go | 121 +++++++++++++++++++++++++ cmd/basecoin/main.go | 177 +------------------------------------ cmd/basecoin/start.go | 10 +++ cmd/basecoin/tx.go | 49 +++++++--- cmd/paytovote/main.go | 53 ----------- plugins/counter/counter.go | 2 +- 8 files changed, 275 insertions(+), 238 deletions(-) create mode 100644 cmd/basecoin/cmd.go create mode 100644 cmd/basecoin/flags.go delete mode 100644 cmd/paytovote/main.go diff --git a/app/app.go b/app/app.go index be618ae723..a6ad90424d 100644 --- a/app/app.go +++ b/app/app.go @@ -1,6 +1,7 @@ package app import ( + "fmt" "strings" abci "github.com/tendermint/abci/types" @@ -102,6 +103,8 @@ func (app *Basecoin) CheckTx(txBytes []byte) (res abci.Result) { return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum") } + fmt.Printf("%X\n", txBytes) + // Decode tx var tx types.Tx err := wire.ReadBinaryBytes(txBytes, &tx) diff --git a/cmd/basecoin/cmd.go b/cmd/basecoin/cmd.go new file mode 100644 index 0000000000..7fe5fb3dbb --- /dev/null +++ b/cmd/basecoin/cmd.go @@ -0,0 +1,98 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + startCmd = cli.Command{ + Name: "start", + Usage: "Start basecoin", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdStart(c) + }, + Flags: []cli.Flag{ + addrFlag, + eyesFlag, + eyesDBFlag, + genesisFlag, + inProcTMFlag, + chainIDFlag, + pluginFlag, + }, + } + + sendTxCmd = cli.Command{ + Name: "sendtx", + Usage: "Broadcast a basecoin SendTx", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdSendTx(c) + }, + Flags: []cli.Flag{ + tmAddrFlag, + chainIDFlag, + + fromFlag, + + amountFlag, + coinFlag, + gasFlag, + feeFlag, + seqFlag, + + toFlag, + }, + } + + appTxCmd = cli.Command{ + Name: "apptx", + Usage: "Broadcast a basecoin AppTx", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdAppTx(c) + }, + Flags: []cli.Flag{ + tmAddrFlag, + chainIDFlag, + + fromFlag, + + amountFlag, + coinFlag, + gasFlag, + feeFlag, + seqFlag, + + nameFlag, + dataFlag, + }, + Subcommands: []cli.Command{ + counterTxCmd, + }, + } + + counterTxCmd = cli.Command{ + Name: "counter", + Usage: "Craft a transaction to the counter plugin", + Action: func(c *cli.Context) error { + return cmdCounterTx(c) + }, + Flags: []cli.Flag{ + validFlag, + }, + } + + accountCmd = cli.Command{ + Name: "account", + Usage: "Get details of an account", + ArgsUsage: "[address]", + Action: func(c *cli.Context) error { + return cmdAccount(c) + }, + Flags: []cli.Flag{ + tmAddrFlag, + }, + } +) diff --git a/cmd/basecoin/flags.go b/cmd/basecoin/flags.go new file mode 100644 index 0000000000..c4b4c9f62c --- /dev/null +++ b/cmd/basecoin/flags.go @@ -0,0 +1,121 @@ +package main + +import ( + "github.com/urfave/cli" +) + +// start flags +var ( + addrFlag = cli.StringFlag{ + Name: "address", + Value: "tcp://0.0.0.0:46658", + Usage: "Listen address", + } + + eyesFlag = cli.StringFlag{ + Name: "eyes", + Value: "local", + Usage: "MerkleEyes address, or 'local' for embedded", + } + + eyesDBFlag = cli.StringFlag{ + Name: "eyes-db", + Value: "merkleeyes.db", + Usage: "MerkleEyes db name for embedded", + } + + // TODO: move to config file + // eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") + + genesisFlag = cli.StringFlag{ + Name: "genesis", + Value: "", + Usage: "Path to genesis file, if it exists", + } + + inProcTMFlag = cli.BoolFlag{ + Name: "in-proc", + Usage: "Run Tendermint in-process with the App", + } + + pluginFlag = cli.StringFlag{ + Name: "plugin", + Value: "counter", // load the counter by default + Usage: "Plugin to enable", + } +) + +// tx flags + +var ( + tmAddrFlag = cli.StringFlag{ + Name: "tendermint", + Value: "tcp://localhost:46657", + Usage: "Tendermint RPC address", + } + + toFlag = cli.StringFlag{ + Name: "to", + Value: "", + Usage: "Destination address for the transaction", + } + + amountFlag = cli.IntFlag{ + Name: "amount", + Value: 0, + Usage: "Amount of coins to send in the transaction", + } + + fromFlag = cli.StringFlag{ + Name: "from", + Value: "priv_validator.json", + Usage: "Path to a private key to sign the transaction", + } + + seqFlag = cli.IntFlag{ + Name: "sequence", + Value: 0, + Usage: "Sequence number for the account", + } + + coinFlag = cli.StringFlag{ + Name: "coin", + Value: "blank", + Usage: "Specify a coin denomination", + } + + gasFlag = cli.IntFlag{ + Name: "gas", + Value: 0, + Usage: "The amount of gas for the transaction", + } + + feeFlag = cli.IntFlag{ + Name: "fee", + Value: 0, + Usage: "The transaction fee", + } + + dataFlag = cli.StringFlag{ + Name: "data", + Value: "", + Usage: "Data to send with the transaction", + } + + nameFlag = cli.StringFlag{ + Name: "name", + Value: "", + Usage: "Plugin to send the transaction to", + } + + chainIDFlag = cli.StringFlag{ + Name: "chain_id", + Value: "test_chain_id", + Usage: "ID of the chain for replay protection", + } + + validFlag = cli.BoolFlag{ + Name: "valid", + Usage: "Set valid field in CounterTx", + } +) diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 741c94f1f5..1fd2c79eb4 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -6,185 +6,16 @@ import ( "github.com/urfave/cli" ) -// start flags -var ( - addrFlag = cli.StringFlag{ - Name: "address", - Value: "tcp://0.0.0.0:46658", - Usage: "Listen address", - } - - eyesFlag = cli.StringFlag{ - Name: "eyes", - Value: "local", - Usage: "MerkleEyes address, or 'local' for embedded", - } - - eyesDBFlag = cli.StringFlag{ - Name: "eyes-db", - Value: "merkleeyes.db", - Usage: "MerkleEyes db name for embedded", - } - - // TODO: move to config file - // eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") - - genesisFlag = cli.StringFlag{ - Name: "genesis", - Value: "", - Usage: "Path to genesis file, if it exists", - } - - inProcTMFlag = cli.BoolFlag{ - Name: "in-proc", - Usage: "Run Tendermint in-process with the App", - } -) - -// tx flags - -var ( - tmAddrFlag = cli.StringFlag{ - Name: "tendermint", - Value: "tcp://localhost:46657", - Usage: "Tendermint RPC address", - } - - toFlag = cli.StringFlag{ - Name: "to", - Value: "", - Usage: "Destination address for the transaction", - } - - amountFlag = cli.IntFlag{ - Name: "amount", - Value: 0, - Usage: "Amount of coins to send in the transaction", - } - - fromFlag = cli.StringFlag{ - Name: "from", - Value: "priv_validator.json", - Usage: "Path to a private key to sign the transaction", - } - - seqFlag = cli.IntFlag{ - Name: "sequence", - Value: 0, - Usage: "Sequence number for the account", - } - - coinFlag = cli.StringFlag{ - Name: "coin", - Value: "blank", - Usage: "Specify a coin denomination", - } - - gasFlag = cli.IntFlag{ - Name: "gas", - Value: 0, - Usage: "The amount of gas for the transaction", - } - - feeFlag = cli.IntFlag{ - Name: "fee", - Value: 0, - Usage: "The transaction fee", - } - - dataFlag = cli.StringFlag{ - Name: "data", - Value: "", - Usage: "Data to send with the transaction", - } - - nameFlag = cli.StringFlag{ - Name: "name", - Value: "", - Usage: "Plugin to send the transaction to", - } - - chainIDFlag = cli.StringFlag{ - Name: "chain_id", - Value: "test_chain_id", - Usage: "ID of the chain for replay protection", - } -) - func main() { app := cli.NewApp() app.Name = "basecoin" app.Usage = "basecoin [command] [args...]" app.Version = "0.1.0" app.Commands = []cli.Command{ - { - Name: "start", - Usage: "Start basecoin", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdStart(c) - }, - Flags: []cli.Flag{ - addrFlag, - eyesFlag, - eyesDBFlag, - genesisFlag, - inProcTMFlag, - chainIDFlag, - }, - }, - - { - Name: "sendtx", - Usage: "Broadcast a basecoin SendTx", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdSendTx(c) - }, - Flags: []cli.Flag{ - tmAddrFlag, - toFlag, - fromFlag, - amountFlag, - coinFlag, - gasFlag, - feeFlag, - chainIDFlag, - seqFlag, - }, - }, - - { - Name: "apptx", - Usage: "Broadcast a basecoin AppTx", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdAppTx(c) - }, - Flags: []cli.Flag{ - tmAddrFlag, - nameFlag, - fromFlag, - amountFlag, - coinFlag, - gasFlag, - feeFlag, - dataFlag, - seqFlag, - }, - }, - - { - Name: "account", - Usage: "Get details of an account", - ArgsUsage: "[address]", - Action: func(c *cli.Context) error { - return cmdAccount(c) - }, - Flags: []cli.Flag{ - tmAddrFlag, - }, - }, + startCmd, + sendTxCmd, + appTxCmd, + accountCmd, } app.Run(os.Args) } diff --git a/cmd/basecoin/start.go b/cmd/basecoin/start.go index 5d5c2de542..e483737a4d 100644 --- a/cmd/basecoin/start.go +++ b/cmd/basecoin/start.go @@ -17,6 +17,7 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/basecoin/app" + "github.com/tendermint/basecoin/plugins/counter" ) var config cfg.Config @@ -40,6 +41,15 @@ func cmdStart(c *cli.Context) error { // Create Basecoin app basecoinApp := app.NewBasecoin(eyesCli) + switch c.String("plugin") { + case "counter": + basecoinApp.RegisterPlugin(counter.New("counter")) + case "": + // no plugins to register + default: + return errors.New(cmn.Fmt("Unknown plugin: %v", c.String("plugin"))) + } + // If genesis file was specified, set key-value options if c.String("genesis") != "" { err := basecoinApp.LoadGenesis(c.String("genesis")) diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index 4b0bd83845..e80d1a77a8 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -7,7 +7,9 @@ import ( "github.com/urfave/cli" + "github.com/tendermint/basecoin/plugins/counter" "github.com/tendermint/basecoin/types" + cmn "github.com/tendermint/go-common" client "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" @@ -60,24 +62,26 @@ func cmdSendTx(c *cli.Context) error { if err := broadcastTx(c, tx); err != nil { return err } - return nil } func cmdAppTx(c *cli.Context) error { + // convert data to bytes + dataString := c.String("data") + data := []byte(dataString) + if isHex(dataString) { + data, _ = hex.DecodeString(dataString) + } name := c.String("name") + return appTx(c, name, data) +} + +func appTx(c *cli.Context, name string, data []byte) error { fromFile := c.String("from") amount := int64(c.Int("amount")) coin := c.String("coin") gas, fee := c.Int("gas"), int64(c.Int("fee")) chainID := c.String("chain_id") - dataString := c.String("data") - - // convert data to bytes - data := []byte(dataString) - if isHex(dataString) { - data, _ = hex.DecodeString(dataString) - } privVal := tmtypes.LoadPrivValidator(fromFile) @@ -107,16 +111,39 @@ func cmdAppTx(c *cli.Context) error { return nil } +func cmdCounterTx(c *cli.Context) error { + valid := c.Bool("valid") + parent := c.Parent() + + counterTx := counter.CounterTx{ + Valid: valid, + Fee: types.Coins{ + { + Denom: parent.String("coin"), + Amount: int64(parent.Int("fee")), + }, + }, + } + + fmt.Println("CounterTx:", string(wire.JSONBytes(counterTx))) + + data := wire.BinaryBytes(counterTx) + name := "counter" + + return appTx(parent, name, data) +} + // broadcast the transaction to tendermint func broadcastTx(c *cli.Context, tx types.Tx) error { tmResult := new(ctypes.TMResult) tmAddr := c.String("tendermint") clientURI := client.NewClientURI(tmAddr) - /*txBytes := []byte(wire.JSONBytes(struct { + // Don't you hate having to do this? + // How many times have I lost an hour over this trick?! + txBytes := []byte(wire.BinaryBytes(struct { types.Tx `json:"unwrap"` - }{tx}))*/ - txBytes := wire.BinaryBytes(tx) + }{tx})) _, err := clientURI.Call("broadcast_tx_sync", map[string]interface{}{"tx": txBytes}, tmResult) if err != nil { return errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) diff --git a/cmd/paytovote/main.go b/cmd/paytovote/main.go deleted file mode 100644 index 7c7715d992..0000000000 --- a/cmd/paytovote/main.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "flag" - - "github.com/tendermint/abci/server" - "github.com/tendermint/basecoin/app" - "github.com/tendermint/basecoin/plugins/counter" - cmn "github.com/tendermint/go-common" - eyes "github.com/tendermint/merkleeyes/client" -) - -func main() { - addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address") - eyesPtr := flag.String("eyes", "local", "MerkleEyes address, or 'local' for embedded") - genFilePath := flag.String("genesis", "", "Genesis file, if any") - flag.Parse() - - // Connect to MerkleEyes - eyesCli, err := eyes.NewClient(*eyesPtr) - if err != nil { - cmn.Exit("connect to MerkleEyes: " + err.Error()) - } - - // Create Basecoin app - app := app.NewBasecoin(eyesCli) - - // add plugins - // TODO: add some more, like the cool voting app - counter := counter.New("counter") - app.RegisterPlugin(counter) - - // If genesis file was specified, set key-value options - if *genFilePath != "" { - err := app.LoadGenesis(*genFilePath) - if err != nil { - cmn.Exit(cmn.Fmt("%+v", err)) - } - } - - // Start the listener - svr, err := server.NewServer(*addrPtr, "socket", app) - if err != nil { - cmn.Exit("create listener: " + err.Error()) - } - - // Wait forever - cmn.TrapSignal(func() { - // Cleanup - svr.Stop() - }) - -} diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 8f3526818d..9c115089b5 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -47,7 +47,7 @@ func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt var tx CounterTx err := wire.ReadBinaryBytes(txBytes, &tx) if err != nil { - return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()).PrependLog("CounterTx Error: ") } // Validate tx From b5e3a11347a690f5c74b2cea584a90cbbee45d79 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 18:42:25 -0800 Subject: [PATCH 15/64] Add test for IBCRegisterChainTx --- plugins/ibc/ibc.go | 5 +++ plugins/ibc/ibc_test.go | 87 +++++++++++++++++++++++++++++++++++++++++ types/kvstore.go | 43 ++++++++++++++++---- 3 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 plugins/ibc/ibc_test.go diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index 8a8d4d9fcb..a21b34d337 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -58,6 +58,9 @@ const ( IBCTxTypeRegisterChain = byte(0x01) IBCTxTypeUpdateChain = byte(0x02) IBCTxTypePacket = byte(0x03) + + IBCCodeEncodingError = abci.CodeType(1001) + IBCCodeChainAlreadyExists = abci.CodeType(1002) ) var _ = wire.RegisterInterface( @@ -182,12 +185,14 @@ func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { var err error wire.ReadJSONPtr(&chainGenDoc, []byte(chainGen.Genesis), &err) if err != nil { + sm.res.Code = IBCCodeEncodingError sm.res.AppendLog("Genesis doc couldn't be parsed: " + err.Error()) return } // Make sure chainGen doesn't already exist if exists(sm.store, chainGenKey) { + sm.res.Code = IBCCodeChainAlreadyExists sm.res.AppendLog("Already exists") return } diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go new file mode 100644 index 0000000000..1de57e541d --- /dev/null +++ b/plugins/ibc/ibc_test.go @@ -0,0 +1,87 @@ +package ibc + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/basecoin/testutils" + "github.com/tendermint/basecoin/types" + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-wire" + tm "github.com/tendermint/tendermint/types" +) + +func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []*tm.Validator) { + var vals []*tm.Validator + genDoc := &tm.GenesisDoc{ + ChainID: chainID, + Validators: nil, + } + + for i := 0; i < numVals; i++ { + name := cmn.Fmt("%v_val_%v", chainID, i) + valPrivAcc := testutils.PrivAccountFromSecret(name) + val := tm.NewValidator(valPrivAcc.Account.PubKey, 1) + genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{ + PubKey: val.PubKey, + Amount: 1, + Name: name, + }) + vals = append(vals, val) + } + + return genDoc, vals +} + +func TestIBCPlugin(t *testing.T) { + + store := types.NewKVCache(nil) + store.SetLogging() // Log all activity + + ibcPlugin := New() + ctx := types.CallContext{ + CallerAddress: nil, + CallerAccount: nil, + Coins: types.Coins{}, + } + + chainID_1 := "test_chain" + genDoc_1, vals_1 := genGenesisDoc(chainID_1, 4) + genDocJSON_1 := wire.JSONBytesPretty(genDoc_1) + + // Register a malformed chain + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ + BlockchainGenesis{ + ChainID: "test_chain", + Genesis: "", + }, + }})) + assert.Equal(t, res.Code, IBCCodeEncodingError) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Successfully register a chain + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ + BlockchainGenesis{ + ChainID: "test_chain", + Genesis: string(genDocJSON_1), + }, + }})) + assert.True(t, res.IsOK(), res) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Duplicate request fails + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ + BlockchainGenesis{ + ChainID: "test_chain", + Genesis: string(genDocJSON_1), + }, + }})) + assert.Equal(t, res.Code, IBCCodeChainAlreadyExists, res) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + t.Log(">>", vals_1) +} diff --git a/types/kvstore.go b/types/kvstore.go index 5ba8271f03..96f8c7d0ff 100644 --- a/types/kvstore.go +++ b/types/kvstore.go @@ -36,9 +36,11 @@ func (mkv *MemKVStore) Get(key []byte) (value []byte) { // A Cache that enforces deterministic sync order. type KVCache struct { - store KVStore - cache map[string]kvCacheValue - keys *list.List + store KVStore + cache map[string]kvCacheValue + keys *list.List + logging bool + logLines []string } type kvCacheValue struct { @@ -46,12 +48,28 @@ type kvCacheValue struct { e *list.Element // The KVCache.keys element } +// NOTE: If store is nil, creates a new MemKVStore func NewKVCache(store KVStore) *KVCache { + if store == nil { + store = NewMemKVStore() + } return (&KVCache{ store: store, }).Reset() } +func (kvc *KVCache) SetLogging() { + kvc.logging = true +} + +func (kvc *KVCache) GetLogLines() []string { + return kvc.logLines +} + +func (kvc *KVCache) ClearLogLines() { + kvc.logLines = nil +} + func (kvc *KVCache) Reset() *KVCache { kvc.cache = make(map[string]kvCacheValue) kvc.keys = list.New() @@ -59,7 +77,10 @@ func (kvc *KVCache) Reset() *KVCache { } func (kvc *KVCache) Set(key []byte, value []byte) { - fmt.Println("Set [KVCache]", formatBytes(key), "=", formatBytes(value)) + if kvc.logging { + line := fmt.Sprintf("Set %v = %v", LegibleBytes(key), LegibleBytes(value)) + kvc.logLines = append(kvc.logLines, line) + } cacheValue, ok := kvc.cache[string(key)] if ok { kvc.keys.MoveToBack(cacheValue.e) @@ -73,7 +94,10 @@ func (kvc *KVCache) Set(key []byte, value []byte) { func (kvc *KVCache) Get(key []byte) (value []byte) { cacheValue, ok := kvc.cache[string(key)] if ok { - fmt.Println("GET [KVCache, hit]", formatBytes(key), "=", formatBytes(cacheValue.v)) + if kvc.logging { + line := fmt.Sprintf("Get (hit) %v = %v", LegibleBytes(key), LegibleBytes(cacheValue.v)) + kvc.logLines = append(kvc.logLines, line) + } return cacheValue.v } else { value := kvc.store.Get(key) @@ -81,7 +105,10 @@ func (kvc *KVCache) Get(key []byte) (value []byte) { v: value, e: kvc.keys.PushBack(key), } - fmt.Println("GET [KVCache, miss]", formatBytes(key), "=", formatBytes(value)) + if kvc.logging { + line := fmt.Sprintf("Get (miss) %v = %v", LegibleBytes(key), LegibleBytes(value)) + kvc.logLines = append(kvc.logLines, line) + } return value } } @@ -97,13 +124,13 @@ func (kvc *KVCache) Sync() { //---------------------------------------- -func formatBytes(data []byte) string { +func LegibleBytes(data []byte) string { s := "" for _, b := range data { if 0x21 <= b && b < 0x7F { s += Green(string(b)) } else { - s += Blue(Fmt("%X", b)) + s += Blue(Fmt("%02X", b)) } } return s From 90d0b53a2fd64b976f859e8dcc1990ca4501e56f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 18:41:37 -0800 Subject: [PATCH 16/64] cmd: ibc --- cmd/basecoin/account.go | 31 ----------- cmd/basecoin/cmd.go | 107 ++++++++++++++++++++++++++++++++++-- cmd/basecoin/flags.go | 67 ++++++++++++++++++++++- cmd/basecoin/ibc.go | 114 +++++++++++++++++++++++++++++++++++++++ cmd/basecoin/main.go | 3 ++ cmd/basecoin/query.go | 117 ++++++++++++++++++++++++++++++++++++++++ cmd/basecoin/tx.go | 4 +- cmd/basecoin/utils.go | 41 +++++++++++--- 8 files changed, 439 insertions(+), 45 deletions(-) delete mode 100644 cmd/basecoin/account.go create mode 100644 cmd/basecoin/ibc.go create mode 100644 cmd/basecoin/query.go diff --git a/cmd/basecoin/account.go b/cmd/basecoin/account.go deleted file mode 100644 index 22b4e076fc..0000000000 --- a/cmd/basecoin/account.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "encoding/hex" - "errors" - "fmt" - - "github.com/urfave/cli" - - "github.com/tendermint/go-wire" -) - -func cmdAccount(c *cli.Context) error { - if len(c.Args()) != 1 { - return errors.New("account command requires an argument ([address])") - } - addrHex := stripHex(c.Args()[0]) - - // convert destination address to bytes - addr, err := hex.DecodeString(addrHex) - if err != nil { - return errors.New("Account address is invalid hex: " + err.Error()) - } - - acc, err := getAcc(c.String("tendermint"), addr) - if err != nil { - return err - } - fmt.Println(string(wire.JSONBytes(acc))) - return nil -} diff --git a/cmd/basecoin/cmd.go b/cmd/basecoin/cmd.go index 7fe5fb3dbb..890f390d00 100644 --- a/cmd/basecoin/cmd.go +++ b/cmd/basecoin/cmd.go @@ -31,7 +31,7 @@ var ( return cmdSendTx(c) }, Flags: []cli.Flag{ - tmAddrFlag, + nodeFlag, chainIDFlag, fromFlag, @@ -54,7 +54,7 @@ var ( return cmdAppTx(c) }, Flags: []cli.Flag{ - tmAddrFlag, + nodeFlag, chainIDFlag, fromFlag, @@ -84,15 +84,114 @@ var ( }, } + ibcCmd = cli.Command{ + Name: "ibc", + Usage: "Send a transaction to the interblockchain (ibc) plugin", + Flags: []cli.Flag{ + nodeFlag, + }, + Subcommands: []cli.Command{ + ibcRegisterTxCmd, + ibcUpdateTxCmd, + ibcPacketTxCmd, + }, + } + + ibcRegisterTxCmd = cli.Command{ + Name: "register", + Usage: "Register a blockchain via IBC", + Action: func(c *cli.Context) error { + return cmdIBCRegisterTx(c) + }, + Flags: []cli.Flag{ + ibcChainIDFlag, + ibcGenesisFlag, + }, + } + + ibcUpdateTxCmd = cli.Command{ + Name: "update", + Usage: "Update the latest state of a blockchain via IBC", + Action: func(c *cli.Context) error { + return cmdIBCUpdateTx(c) + }, + Flags: []cli.Flag{ + ibcHeaderFlag, + ibcCommitFlag, + }, + } + + ibcPacketTxCmd = cli.Command{ + Name: "packet", + Usage: "Send a new packet via IBC", + Flags: []cli.Flag{ + // + }, + Subcommands: []cli.Command{ + ibcPacketCreateTx, + ibcPacketPostTx, + }, + } + + ibcPacketCreateTx = cli.Command{ + Name: "create", + Usage: "Create an egress IBC packet", + Action: func(c *cli.Context) error { + return cmdIBCPacketCreateTx(c) + }, + Flags: []cli.Flag{ + ibcFromFlag, + ibcToFlag, + ibcTypeFlag, + ibcPayloadFlag, + }, + } + + ibcPacketPostTx = cli.Command{ + Name: "post", + Usage: "Deliver an IBC packet to another chain", + Action: func(c *cli.Context) error { + return cmdIBCPacketPostTx(c) + }, + Flags: []cli.Flag{ + ibcPacketFlag, + ibcProofFlag, + }, + } + + queryCmd = cli.Command{ + Name: "query", + Usage: "Query the merkle tree", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdQuery(c) + }, + Flags: []cli.Flag{ + nodeFlag, + }, + } + accountCmd = cli.Command{ Name: "account", Usage: "Get details of an account", - ArgsUsage: "[address]", + ArgsUsage: "
", Action: func(c *cli.Context) error { return cmdAccount(c) }, Flags: []cli.Flag{ - tmAddrFlag, + nodeFlag, + }, + } + + blockCmd = cli.Command{ + Name: "block", + Usage: "Get the header and commit of a block", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdBlock(c) + }, + Flags: []cli.Flag{ + nodeFlag, }, } ) diff --git a/cmd/basecoin/flags.go b/cmd/basecoin/flags.go index c4b4c9f62c..34b3f7c2e7 100644 --- a/cmd/basecoin/flags.go +++ b/cmd/basecoin/flags.go @@ -48,8 +48,8 @@ var ( // tx flags var ( - tmAddrFlag = cli.StringFlag{ - Name: "tendermint", + nodeFlag = cli.StringFlag{ + Name: "node", Value: "tcp://localhost:46657", Usage: "Tendermint RPC address", } @@ -119,3 +119,66 @@ var ( Usage: "Set valid field in CounterTx", } ) + +// ibc flags +var ( + ibcChainIDFlag = cli.StringFlag{ + Name: "chain_id", + Usage: "ChainID for the new blockchain", + Value: "", + } + + ibcGenesisFlag = cli.StringFlag{ + Name: "genesis", + Usage: "Genesis file for the new blockchain", + Value: "", + } + + ibcHeaderFlag = cli.StringFlag{ + Name: "header", + Usage: "Block header for an ibc update", + Value: "", + } + + ibcCommitFlag = cli.StringFlag{ + Name: "commit", + Usage: "Block commit for an ibc update", + Value: "", + } + + ibcFromFlag = cli.StringFlag{ + Name: "from", + Usage: "Source ChainID", + Value: "", + } + + ibcToFlag = cli.StringFlag{ + Name: "to", + Usage: "Destination ChainID", + Value: "", + } + + ibcTypeFlag = cli.StringFlag{ + Name: "type", + Usage: "IBC packet type (eg. coin)", + Value: "", + } + + ibcPayloadFlag = cli.StringFlag{ + Name: "payload", + Usage: "IBC packet payload", + Value: "", + } + + ibcPacketFlag = cli.StringFlag{ + Name: "packet", + Usage: "hex-encoded IBC packet", + Value: "", + } + + ibcProofFlag = cli.StringFlag{ + Name: "proof", + Usage: "hex-encoded proof of IBC packet from source chain", + Value: "", + } +) diff --git a/cmd/basecoin/ibc.go b/cmd/basecoin/ibc.go new file mode 100644 index 0000000000..9a4446a3c8 --- /dev/null +++ b/cmd/basecoin/ibc.go @@ -0,0 +1,114 @@ +package main + +import ( + "encoding/hex" + "errors" + "fmt" + "io/ioutil" + + "github.com/urfave/cli" + + "github.com/tendermint/basecoin/plugins/ibc" + + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-merkle" + "github.com/tendermint/go-wire" + tmtypes "github.com/tendermint/tendermint/types" +) + +func cmdIBCRegisterTx(c *cli.Context) error { + chainID := c.String("chain_id") + genesisFile := c.String("genesis") + parent := c.Parent() + + genesisBytes, err := ioutil.ReadFile(genesisFile) + if err != nil { + return errors.New(cmn.Fmt("Error reading genesis file %v: %v", genesisFile, err)) + } + + ibcTx := ibc.IBCRegisterChainTx{ + ibc.BlockchainGenesis{ + ChainID: chainID, + Genesis: string(genesisBytes), + }, + } + + fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) + + data := wire.BinaryBytes(ibcTx) + name := "ibc" + + return appTx(parent, name, data) +} + +func cmdIBCUpdateTx(c *cli.Context) error { + parent := c.Parent() + + headerBytes, err := hex.DecodeString(stripHex(c.String("header"))) + if err != nil { + return errors.New(cmn.Fmt("Header (%v) is invalid hex: %v", c.String("header"), err)) + } + commitBytes, err := hex.DecodeString(stripHex(c.String("commit"))) + if err != nil { + return errors.New(cmn.Fmt("Commit (%v) is invalid hex: %v", c.String("commit"), err)) + } + + var header tmtypes.Header + var commit tmtypes.Commit + + if err := wire.ReadBinaryBytes(headerBytes, &header); err != nil { + return errors.New(cmn.Fmt("Error unmarshalling header: %v", err)) + } + if err := wire.ReadBinaryBytes(commitBytes, &commit); err != nil { + return errors.New(cmn.Fmt("Error unmarshalling commit: %v", err)) + } + + ibcTx := ibc.IBCUpdateChainTx{ + Header: header, + Commit: commit, + } + + fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) + + data := wire.BinaryBytes(ibcTx) + name := "ibc" + + return appTx(parent, name, data) +} + +func cmdIBCPacketCreateTx(c *cli.Context) error { + return nil +} + +func cmdIBCPacketPostTx(c *cli.Context) error { + parent := c.Parent() + + var fromChain string + var fromHeight uint64 + var proof merkle.IAVLProof + + var srcChain, dstChain string + var sequence uint64 + var packetType string + var payload []byte + + ibcTx := ibc.IBCPacketTx{ + FromChainID: fromChain, + FromChainHeight: fromHeight, + Packet: ibc.Packet{ + SrcChainID: srcChain, + DstChainID: dstChain, + Sequence: sequence, + Type: packetType, + Payload: payload, + }, + Proof: proof, + } + + fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) + + data := wire.BinaryBytes(ibcTx) + name := "ibc" + + return appTx(parent, name, data) +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 1fd2c79eb4..7bc44b99ac 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -15,6 +15,9 @@ func main() { startCmd, sendTxCmd, appTxCmd, + ibcCmd, + queryCmd, + blockCmd, accountCmd, } app.Run(os.Args) diff --git a/cmd/basecoin/query.go b/cmd/basecoin/query.go new file mode 100644 index 0000000000..c42507e44d --- /dev/null +++ b/cmd/basecoin/query.go @@ -0,0 +1,117 @@ +package main + +import ( + "encoding/hex" + "errors" + "fmt" + "strconv" + + "github.com/urfave/cli" + + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-wire" + tmtypes "github.com/tendermint/tendermint/types" +) + +func cmdQuery(c *cli.Context) error { + if len(c.Args()) != 1 { + return errors.New("query command requires an argument ([key])") + } + keyString := c.Args()[0] + key := []byte(keyString) + if isHex(keyString) { + // convert key to bytes + var err error + key, err = hex.DecodeString(stripHex(keyString)) + if err != nil { + return errors.New(cmn.Fmt("Query key (%v) is invalid hex: %v", keyString, err)) + } + } + + resp, err := query(c.String("node"), key) + if err != nil { + return err + } + + if !resp.Code.IsOK() { + return errors.New(cmn.Fmt("Query for key (%v) returned non-zero code (%v): %v", keyString, resp.Code, resp.Log)) + } + + val := resp.Value + proof := resp.Proof + height := resp.Height + + fmt.Println(string(wire.JSONBytes(struct { + Value []byte `json:"value"` + Proof []byte `json:"proof"` + Height uint64 `json:"height"` + }{val, proof, height}))) + + return nil +} + +func cmdAccount(c *cli.Context) error { + if len(c.Args()) != 1 { + return errors.New("account command requires an argument ([address])") + } + addrHex := stripHex(c.Args()[0]) + + // convert destination address to bytes + addr, err := hex.DecodeString(addrHex) + if err != nil { + return errors.New(cmn.Fmt("Account address (%v) is invalid hex: %v", addrHex, err)) + } + + acc, err := getAcc(c.String("node"), addr) + if err != nil { + return err + } + fmt.Println(string(wire.JSONBytes(acc))) + return nil +} + +func cmdBlock(c *cli.Context) error { + if len(c.Args()) != 1 { + return errors.New("block command requires an argument ([height])") + } + heightString := c.Args()[0] + height, err := strconv.Atoi(heightString) + if err != nil { + return errors.New(cmn.Fmt("Height must be an int, got %v: %v", heightString, err)) + } + + block, err := getBlock(c, height) + if err != nil { + return err + } + nextBlock, err := getBlock(c, height+1) + if err != nil { + return err + } + + fmt.Println(string(wire.JSONBytes(struct { + Hex BlockHex `json:"hex"` + JSON BlockJSON `json:"json"` + }{ + BlockHex{ + Header: wire.BinaryBytes(block.Header), + Commit: wire.BinaryBytes(nextBlock.LastCommit), + }, + BlockJSON{ + Header: block.Header, + Commit: nextBlock.LastCommit, + }, + }))) + + return nil +} + +type BlockHex struct { + Header []byte `json:"header"` + Commit []byte `json:"commit"` +} + +type BlockJSON struct { + Header *tmtypes.Header `json:"header"` + Commit *tmtypes.Commit `json:"commit"` +} diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index e80d1a77a8..43e5a8e2e2 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -136,7 +136,7 @@ func cmdCounterTx(c *cli.Context) error { // broadcast the transaction to tendermint func broadcastTx(c *cli.Context, tx types.Tx) error { tmResult := new(ctypes.TMResult) - tmAddr := c.String("tendermint") + tmAddr := c.String("node") clientURI := client.NewClientURI(tmAddr) // Don't you hate having to do this? @@ -161,7 +161,7 @@ func getSeq(c *cli.Context, address []byte) (int, error) { if c.IsSet("sequence") { return c.Int("sequence"), nil } - tmAddr := c.String("tendermint") + tmAddr := c.String("node") acc, err := getAcc(tmAddr, address) if err != nil { return 0, err diff --git a/cmd/basecoin/utils.go b/cmd/basecoin/utils.go index a944c5bd10..b005f3306c 100644 --- a/cmd/basecoin/utils.go +++ b/cmd/basecoin/utils.go @@ -4,12 +4,16 @@ import ( "encoding/hex" "errors" + "github.com/urfave/cli" + "github.com/tendermint/basecoin/types" + abci "github.com/tendermint/abci/types" cmn "github.com/tendermint/go-common" client "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" ctypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" ) // Returns true for non-empty hex-string prefixed with "0x" @@ -31,15 +35,14 @@ func stripHex(s string) string { return s } -// fetch the account by querying the app -func getAcc(tmAddr string, address []byte) (*types.Account, error) { +func query(tmAddr string, key []byte) (*abci.ResponseQuery, error) { clientURI := client.NewClientURI(tmAddr) tmResult := new(ctypes.TMResult) params := map[string]interface{}{ "path": "/key", - "data": append([]byte("base/a/"), address...), - "prove": false, + "data": key, + "prove": true, } _, err := clientURI.Call("abci_query", params, tmResult) if err != nil { @@ -49,11 +52,24 @@ func getAcc(tmAddr string, address []byte) (*types.Account, error) { if !res.Response.Code.IsOK() { return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)) } - accountBytes := res.Response.Value + return &res.Response, nil +} + +// fetch the account by querying the app +func getAcc(tmAddr string, address []byte) (*types.Account, error) { + + key := append([]byte("base/a/"), address...) + response, err := query(tmAddr, key) + if err != nil { + return nil, err + } + + accountBytes := response.Value if len(accountBytes) == 0 { - return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address)) + return nil, errors.New(cmn.Fmt("Account bytes are empty for address: %X ", address)) } + var acc *types.Account err = wire.ReadBinaryBytes(accountBytes, &acc) if err != nil { @@ -63,3 +79,16 @@ func getAcc(tmAddr string, address []byte) (*types.Account, error) { return acc, nil } + +func getBlock(c *cli.Context, height int) (*tmtypes.Block, error) { + tmResult := new(ctypes.TMResult) + tmAddr := c.String("node") + clientURI := client.NewClientURI(tmAddr) + + _, err := clientURI.Call("block", map[string]interface{}{"height": height}, tmResult) + if err != nil { + return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) + } + res := (*tmResult).(*ctypes.ResultBlock) + return res.Block, nil +} From 3d0c6d07bd1a4fc13d76f6108498bb5a727a0dca Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 19:14:58 -0800 Subject: [PATCH 17/64] ibc: PacketCreate and PacketPost --- plugins/ibc/ibc.go | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index a21b34d337..1fa3727f4b 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -57,7 +57,8 @@ type Packet struct { const ( IBCTxTypeRegisterChain = byte(0x01) IBCTxTypeUpdateChain = byte(0x02) - IBCTxTypePacket = byte(0x03) + IBCTxTypePacketCreate = byte(0x03) + IBCTxTypePacketPost = byte(0x04) IBCCodeEncodingError = abci.CodeType(1001) IBCCodeChainAlreadyExists = abci.CodeType(1002) @@ -67,7 +68,8 @@ var _ = wire.RegisterInterface( struct{ IBCTx }{}, wire.ConcreteType{IBCRegisterChainTx{}, IBCTxTypeRegisterChain}, wire.ConcreteType{IBCUpdateChainTx{}, IBCTxTypeUpdateChain}, - wire.ConcreteType{IBCPacketTx{}, IBCTxTypePacket}, + wire.ConcreteType{IBCPacketCreateTx{}, IBCTxTypePacketCreate}, + wire.ConcreteType{IBCPacketPostTx{}, IBCTxTypePacketPost}, ) type IBCTx interface { @@ -77,7 +79,8 @@ type IBCTx interface { func (IBCRegisterChainTx) AssertIsIBCTx() {} func (IBCUpdateChainTx) AssertIsIBCTx() {} -func (IBCPacketTx) AssertIsIBCTx() {} +func (IBCPacketCreateTx) AssertIsIBCTx() {} +func (IBCPacketPostTx) AssertIsIBCTx() {} type IBCRegisterChainTx struct { BlockchainGenesis @@ -99,14 +102,23 @@ func (IBCUpdateChainTx) ValidateBasic() (res abci.Result) { return } -type IBCPacketTx struct { +type IBCPacketCreateTx struct { + Packet +} + +func (IBCPacketCreateTx) ValidateBasic() (res abci.Result) { + // TODO - validate + return +} + +type IBCPacketPostTx struct { FromChainID string // The immediate source of the packet, not always Packet.SrcChainID FromChainHeight uint64 // The block height in which Packet was committed, to check Proof Packet Proof merkle.IAVLProof } -func (IBCPacketTx) ValidateBasic() (res abci.Result) { +func (IBCPacketPostTx) ValidateBasic() (res abci.Result) { // TODO - validate return } @@ -162,8 +174,10 @@ func (ibc *IBCPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes sm.runRegisterChainTx(tx) case IBCUpdateChainTx: sm.runUpdateChainTx(tx) - case IBCPacketTx: - sm.runPacketTx(tx) + case IBCPacketCreateTx: + sm.runPacketCreateTx(tx) + case IBCPacketPostTx: + sm.runPacketPostTx(tx) } return sm.res @@ -262,7 +276,11 @@ func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) { save(sm.store, chainStateKey, chainState) } -func (sm *IBCStateMachine) runPacketTx(tx IBCPacketTx) { +func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) { + // TODO Store packet in egress +} + +func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { // TODO Make sure packat doesn't already exist // TODO Load associated blockHash and make sure it exists // TODO compute packet key From d3518213c64984dcb75a8ea9d44b894967f2952a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 19:18:50 -0800 Subject: [PATCH 18/64] cmd: ibc packet --- cmd/basecoin/cmd.go | 3 ++ cmd/basecoin/flags.go | 12 +++++++ cmd/basecoin/ibc.go | 81 ++++++++++++++++++++++++++++++++----------- 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/cmd/basecoin/cmd.go b/cmd/basecoin/cmd.go index 890f390d00..e8873de6ca 100644 --- a/cmd/basecoin/cmd.go +++ b/cmd/basecoin/cmd.go @@ -144,6 +144,7 @@ var ( ibcToFlag, ibcTypeFlag, ibcPayloadFlag, + ibcSequenceFlag, }, } @@ -154,6 +155,8 @@ var ( return cmdIBCPacketPostTx(c) }, Flags: []cli.Flag{ + ibcFromFlag, + ibcHeightFlag, ibcPacketFlag, ibcProofFlag, }, diff --git a/cmd/basecoin/flags.go b/cmd/basecoin/flags.go index 34b3f7c2e7..5b36d854e6 100644 --- a/cmd/basecoin/flags.go +++ b/cmd/basecoin/flags.go @@ -181,4 +181,16 @@ var ( Usage: "hex-encoded proof of IBC packet from source chain", Value: "", } + + ibcSequenceFlag = cli.IntFlag{ + Name: "sequence", + Usage: "sequence number for IBC packet", + Value: 0, + } + + ibcHeightFlag = cli.IntFlag{ + Name: "height", + Usage: "Height the packet became egress in source chain", + Value: 0, + } ) diff --git a/cmd/basecoin/ibc.go b/cmd/basecoin/ibc.go index 9a4446a3c8..39c32b2c8b 100644 --- a/cmd/basecoin/ibc.go +++ b/cmd/basecoin/ibc.go @@ -77,38 +77,77 @@ func cmdIBCUpdateTx(c *cli.Context) error { } func cmdIBCPacketCreateTx(c *cli.Context) error { - return nil -} + fromChain, toChain := c.String("from"), c.String("to") + packetType := c.String("type") -func cmdIBCPacketPostTx(c *cli.Context) error { - parent := c.Parent() + payloadBytes, err := hex.DecodeString(stripHex(c.String("payload"))) + if err != nil { + return errors.New(cmn.Fmt("Payload (%v) is invalid hex: %v", c.String("payload"), err)) + } - var fromChain string - var fromHeight uint64 - var proof merkle.IAVLProof + sequence, err := getIBCSequence(c) + if err != nil { + return err + } - var srcChain, dstChain string - var sequence uint64 - var packetType string - var payload []byte - - ibcTx := ibc.IBCPacketTx{ - FromChainID: fromChain, - FromChainHeight: fromHeight, + ibcTx := ibc.IBCPacketCreateTx{ Packet: ibc.Packet{ - SrcChainID: srcChain, - DstChainID: dstChain, + SrcChainID: fromChain, + DstChainID: toChain, Sequence: sequence, Type: packetType, - Payload: payload, + Payload: payloadBytes, }, - Proof: proof, } fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) data := wire.BinaryBytes(ibcTx) - name := "ibc" - return appTx(parent, name, data) + return appTx(c.Parent(), "ibc", data) +} + +func cmdIBCPacketPostTx(c *cli.Context) error { + fromChain, fromHeight := c.String("from"), c.Int("height") + + packetBytes, err := hex.DecodeString(stripHex(c.String("packet"))) + if err != nil { + return errors.New(cmn.Fmt("Packet (%v) is invalid hex: %v", c.String("packet"), err)) + } + proofBytes, err := hex.DecodeString(stripHex(c.String("proof"))) + if err != nil { + return errors.New(cmn.Fmt("Proof (%v) is invalid hex: %v", c.String("proof"), err)) + } + + var packet ibc.Packet + var proof merkle.IAVLProof + + if err := wire.ReadBinaryBytes(packetBytes, &packet); err != nil { + return errors.New(cmn.Fmt("Error unmarshalling packet: %v", err)) + } + if err := wire.ReadBinaryBytes(proofBytes, &proof); err != nil { + return errors.New(cmn.Fmt("Error unmarshalling proof: %v", err)) + } + + ibcTx := ibc.IBCPacketPostTx{ + FromChainID: fromChain, + FromChainHeight: uint64(fromHeight), + Packet: packet, + Proof: proof, + } + + fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) + + data := wire.BinaryBytes(ibcTx) + + return appTx(c.Parent(), "ibc", data) +} + +func getIBCSequence(c *cli.Context) (uint64, error) { + if c.IsSet("sequence") { + return uint64(c.Int("sequence")), nil + } + + // TODO: get sequence + return 0, nil } From a5eefe12faef274c2c479ada130bf7a6e088c1ad Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 19:45:21 -0800 Subject: [PATCH 19/64] cmd: verify iavl proof --- cmd/basecoin/cmd.go | 14 ++++++++++++++ cmd/basecoin/flags.go | 27 ++++++++++++++++++++++++++ cmd/basecoin/main.go | 1 + cmd/basecoin/query.go | 44 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/cmd/basecoin/cmd.go b/cmd/basecoin/cmd.go index e8873de6ca..6cb56d30e8 100644 --- a/cmd/basecoin/cmd.go +++ b/cmd/basecoin/cmd.go @@ -197,4 +197,18 @@ var ( nodeFlag, }, } + + verifyCmd = cli.Command{ + Name: "verify", + Usage: "Verify the IAVL proof", + Action: func(c *cli.Context) error { + return cmdVerify(c) + }, + Flags: []cli.Flag{ + proofFlag, + keyFlag, + valueFlag, + rootFlag, + }, + } ) diff --git a/cmd/basecoin/flags.go b/cmd/basecoin/flags.go index 5b36d854e6..dd454c53eb 100644 --- a/cmd/basecoin/flags.go +++ b/cmd/basecoin/flags.go @@ -194,3 +194,30 @@ var ( Value: 0, } ) + +// proof flags +var ( + proofFlag = cli.StringFlag{ + Name: "proof", + Usage: "hex-encoded IAVL proof", + Value: "", + } + + keyFlag = cli.StringFlag{ + Name: "key", + Usage: "key to the IAVL tree", + Value: "", + } + + valueFlag = cli.StringFlag{ + Name: "value", + Usage: "value in the IAVL tree", + Value: "", + } + + rootFlag = cli.StringFlag{ + Name: "root", + Usage: "root hash of the IAVL tree", + Value: "", + } +) diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 7bc44b99ac..31dcb83525 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -17,6 +17,7 @@ func main() { appTxCmd, ibcCmd, queryCmd, + verifyCmd, blockCmd, accountCmd, } diff --git a/cmd/basecoin/query.go b/cmd/basecoin/query.go index c42507e44d..2aba1b8f58 100644 --- a/cmd/basecoin/query.go +++ b/cmd/basecoin/query.go @@ -9,6 +9,7 @@ import ( "github.com/urfave/cli" cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" tmtypes "github.com/tendermint/tendermint/types" ) @@ -115,3 +116,46 @@ type BlockJSON struct { Header *tmtypes.Header `json:"header"` Commit *tmtypes.Commit `json:"commit"` } + +func cmdVerify(c *cli.Context) error { + keyString, valueString := c.String("key"), c.String("value") + + var err error + key := []byte(keyString) + if isHex(keyString) { + key, err = hex.DecodeString(stripHex(keyString)) + if err != nil { + return errors.New(cmn.Fmt("Key (%v) is invalid hex: %v", keyString, err)) + } + } + + value := []byte(valueString) + if isHex(valueString) { + value, err = hex.DecodeString(stripHex(valueString)) + if err != nil { + return errors.New(cmn.Fmt("Value (%v) is invalid hex: %v", valueString, err)) + } + } + + root, err := hex.DecodeString(stripHex(c.String("root"))) + if err != nil { + return errors.New(cmn.Fmt("Root (%v) is invalid hex: %v", c.String("root"), err)) + } + + proofBytes, err := hex.DecodeString(stripHex(c.String("proof"))) + if err != nil { + return errors.New(cmn.Fmt("Proof (%v) is invalid hex: %v", c.String("proof"), err)) + } + + proof, err := merkle.ReadProof(proofBytes) + if err != nil { + return errors.New(cmn.Fmt("Error unmarshalling proof: %v", err)) + } + + if proof.Verify(key, value, root) { + fmt.Println("OK") + } else { + return errors.New("Proof does not verify") + } + return nil +} From 530694f93cb5e53f3f8905157dccad3f1f926e71 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 19:54:38 -0800 Subject: [PATCH 20/64] Fill remaining IBC run* methods --- plugins/ibc/ibc.go | 91 +++++++++++++++++++++++++++++++++++++---- plugins/ibc/ibc_test.go | 44 ++++++++++++++++++-- 2 files changed, 123 insertions(+), 12 deletions(-) diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index 1fa3727f4b..087f336d91 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -21,6 +21,7 @@ const ( _STATE = "state" _HEADER = "header" _EGRESS = "egress" + _INGRESS = "ingress" _CONNECTION = "connection" ) @@ -29,6 +30,7 @@ type IBCPluginState struct { // @[:ibc, :blockchain, :state, ChainID] <~ BlockchainState // @[:ibc, :blockchain, :header, ChainID, Height] <~ tm.Header // @[:ibc, :egress, Src, Dst, Sequence] <~ Packet + // @[:ibc, :ingress, Dst, Src, Sequence] <~ Packet // @[:ibc, :connection, Src, Dst] <~ Connection # TODO - keep connection state } @@ -60,8 +62,11 @@ const ( IBCTxTypePacketCreate = byte(0x03) IBCTxTypePacketPost = byte(0x04) - IBCCodeEncodingError = abci.CodeType(1001) - IBCCodeChainAlreadyExists = abci.CodeType(1002) + IBCCodeEncodingError = abci.CodeType(1001) + IBCCodeChainAlreadyExists = abci.CodeType(1002) + IBCCodePacketAlreadyExists = abci.CodeType(1003) + IBCCodeUnknownHeight = abci.CodeType(1004) + IBCCodeInvalidProof = abci.CodeType(1005) ) var _ = wire.RegisterInterface( @@ -277,15 +282,85 @@ func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) { } func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) { - // TODO Store packet in egress + packet := tx.Packet + packetKey := toKey(_IBC, _EGRESS, + packet.SrcChainID, + packet.DstChainID, + cmn.Fmt("%v", packet.Sequence), + ) + // Make sure packet doesn't already exist + if exists(sm.store, packetKey) { + sm.res.Code = IBCCodePacketAlreadyExists + sm.res.AppendLog("Already exists") + return + } + // Save new Packet + save(sm.store, packetKey, wire.BinaryBytes(packet)) } func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { - // TODO Make sure packat doesn't already exist - // TODO Load associated blockHash and make sure it exists - // TODO compute packet key - // TODO Make sure packet's proof matches given (packet, key, blockhash) - // TODO Store packet + packet := tx.Packet + packetKeyEgress := toKey(_IBC, _EGRESS, + packet.SrcChainID, + packet.DstChainID, + cmn.Fmt("%v", packet.Sequence), + ) + packetKeyIngress := toKey(_IBC, _INGRESS, + packet.DstChainID, + packet.SrcChainID, + cmn.Fmt("%v", packet.Sequence), + ) + headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER, + tx.FromChainID, + cmn.Fmt("%v", tx.FromChainHeight), + ) + + // Make sure packet doesn't already exist + if exists(sm.store, packetKeyIngress) { + sm.res.Code = IBCCodePacketAlreadyExists + sm.res.AppendLog("Already exists") + return + } + + // Save new Packet + save(sm.store, packetKeyIngress, wire.BinaryBytes(packet)) + + // Load Header and make sure it exists + var header tm.Header + exists, err := load(sm.store, headerKey, &header) + if err != nil { + sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading Header: %v", err.Error())) + return + } + if !exists { + sm.res.Code = IBCCodeUnknownHeight + sm.res.AppendLog(cmn.Fmt("Loading Header: %v", err.Error())) + return + } + + /* + // Read Proof + var proof *merkle.IAVLProof + err = wire.ReadBinaryBytes(tx.Proof, &proof) + if err != nil { + sm.res.Code = IBCEncodingError + sm.res.AppendLog(cmn.Fmt("Reading Proof: %v", err.Error())) + return + } + */ + proof := tx.Proof + packetBytes := wire.BinaryBytes(packet) + + // Make sure packet's proof matches given (packet, key, blockhash) + ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash) + if !ok { + sm.res.Code = IBCCodeInvalidProof + sm.res.AppendLog("Proof is invalid") + return + } + + return + } func (ibc *IBCPlugin) InitChain(store types.KVStore, vals []*abci.Validator) { diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go index 1de57e541d..393f5003f3 100644 --- a/plugins/ibc/ibc_test.go +++ b/plugins/ibc/ibc_test.go @@ -5,10 +5,12 @@ import ( "testing" "github.com/stretchr/testify/assert" + abci "github.com/tendermint/abci/types" "github.com/tendermint/basecoin/testutils" "github.com/tendermint/basecoin/types" cmn "github.com/tendermint/go-common" "github.com/tendermint/go-wire" + eyes "github.com/tendermint/merkleeyes/client" tm "github.com/tendermint/tendermint/types" ) @@ -36,7 +38,8 @@ func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []*tm.Validator func TestIBCPlugin(t *testing.T) { - store := types.NewKVCache(nil) + tree := eyes.NewLocalClient("", 0) + store := types.NewKVCache(tree) store.SetLogging() // Log all activity ibcPlugin := New() @@ -68,7 +71,7 @@ func TestIBCPlugin(t *testing.T) { Genesis: string(genDocJSON_1), }, }})) - assert.True(t, res.IsOK(), res) + assert.True(t, res.IsOK(), res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() @@ -79,9 +82,42 @@ func TestIBCPlugin(t *testing.T) { Genesis: string(genDocJSON_1), }, }})) - assert.Equal(t, res.Code, IBCCodeChainAlreadyExists, res) + assert.Equal(t, res.Code, IBCCodeChainAlreadyExists, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() - t.Log(">>", vals_1) + // Create a new packet (for testing) + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ + Packet{ + SrcChainID: "test_chain", + DstChainID: "dst_chain", + Sequence: 0, + Type: "data", + Payload: []byte("hello world"), + }, + }})) + assert.Equal(t, res.Code, abci.CodeType(0), res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Post a duplicate packet + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ + Packet{ + SrcChainID: "test_chain", + DstChainID: "dst_chain", + Sequence: 0, + Type: "data", + Payload: []byte("hello world"), + }, + }})) + assert.Equal(t, res.Code, IBCCodePacketAlreadyExists, res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Update a chain + //header, commit := + + store.Sync() + resCommit := tree.CommitSync() + t.Log(">>", vals_1, tree, resCommit.Data) } From e69395c01fec38e174d11aec3718e7a5831a075f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 20:06:14 -0800 Subject: [PATCH 21/64] cmd: fix some serialization --- cmd/basecoin/cmd.go | 15 ++++++++++++++- cmd/basecoin/flags.go | 12 ++++++++---- cmd/basecoin/ibc.go | 32 ++++++++++++++++++++------------ cmd/basecoin/start.go | 13 +++++++------ plugins/ibc/ibc.go | 2 +- 5 files changed, 50 insertions(+), 24 deletions(-) diff --git a/cmd/basecoin/cmd.go b/cmd/basecoin/cmd.go index 6cb56d30e8..e5fb7d2a42 100644 --- a/cmd/basecoin/cmd.go +++ b/cmd/basecoin/cmd.go @@ -19,7 +19,8 @@ var ( genesisFlag, inProcTMFlag, chainIDFlag, - pluginFlag, + ibcPluginFlag, + counterPluginFlag, }, } @@ -89,6 +90,18 @@ var ( Usage: "Send a transaction to the interblockchain (ibc) plugin", Flags: []cli.Flag{ nodeFlag, + chainIDFlag, + + fromFlag, + + amountFlag, + coinFlag, + gasFlag, + feeFlag, + seqFlag, + + nameFlag, + dataFlag, }, Subcommands: []cli.Command{ ibcRegisterTxCmd, diff --git a/cmd/basecoin/flags.go b/cmd/basecoin/flags.go index dd454c53eb..7e349933f9 100644 --- a/cmd/basecoin/flags.go +++ b/cmd/basecoin/flags.go @@ -38,10 +38,14 @@ var ( Usage: "Run Tendermint in-process with the App", } - pluginFlag = cli.StringFlag{ - Name: "plugin", - Value: "counter", // load the counter by default - Usage: "Plugin to enable", + ibcPluginFlag = cli.BoolFlag{ + Name: "ibc-plugin", + Usage: "Enable the ibc plugin", + } + + counterPluginFlag = cli.BoolFlag{ + Name: "counter-plugin", + Usage: "Enable the counter plugin", } ) diff --git a/cmd/basecoin/ibc.go b/cmd/basecoin/ibc.go index 39c32b2c8b..a991e28fd1 100644 --- a/cmd/basecoin/ibc.go +++ b/cmd/basecoin/ibc.go @@ -35,8 +35,10 @@ func cmdIBCRegisterTx(c *cli.Context) error { fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) - data := wire.BinaryBytes(ibcTx) - name := "ibc" + data := []byte(wire.BinaryBytes(struct { + ibc.IBCTx `json:"unwrap"` + }{ibcTx})) + name := "IBC" return appTx(parent, name, data) } @@ -53,8 +55,8 @@ func cmdIBCUpdateTx(c *cli.Context) error { return errors.New(cmn.Fmt("Commit (%v) is invalid hex: %v", c.String("commit"), err)) } - var header tmtypes.Header - var commit tmtypes.Commit + header := new(tmtypes.Header) + commit := new(tmtypes.Commit) if err := wire.ReadBinaryBytes(headerBytes, &header); err != nil { return errors.New(cmn.Fmt("Error unmarshalling header: %v", err)) @@ -64,14 +66,16 @@ func cmdIBCUpdateTx(c *cli.Context) error { } ibcTx := ibc.IBCUpdateChainTx{ - Header: header, - Commit: commit, + Header: *header, + Commit: *commit, } fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) - data := wire.BinaryBytes(ibcTx) - name := "ibc" + data := []byte(wire.BinaryBytes(struct { + ibc.IBCTx `json:"unwrap"` + }{ibcTx})) + name := "IBC" return appTx(parent, name, data) } @@ -102,9 +106,11 @@ func cmdIBCPacketCreateTx(c *cli.Context) error { fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) - data := wire.BinaryBytes(ibcTx) + data := []byte(wire.BinaryBytes(struct { + ibc.IBCTx `json:"unwrap"` + }{ibcTx})) - return appTx(c.Parent(), "ibc", data) + return appTx(c.Parent(), "IBC", data) } func cmdIBCPacketPostTx(c *cli.Context) error { @@ -138,9 +144,11 @@ func cmdIBCPacketPostTx(c *cli.Context) error { fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx))) - data := wire.BinaryBytes(ibcTx) + data := []byte(wire.BinaryBytes(struct { + ibc.IBCTx `json:"unwrap"` + }{ibcTx})) - return appTx(c.Parent(), "ibc", data) + return appTx(c.Parent(), "IBC", data) } func getIBCSequence(c *cli.Context) (uint64, error) { diff --git a/cmd/basecoin/start.go b/cmd/basecoin/start.go index e483737a4d..687685d12b 100644 --- a/cmd/basecoin/start.go +++ b/cmd/basecoin/start.go @@ -18,6 +18,7 @@ import ( "github.com/tendermint/basecoin/app" "github.com/tendermint/basecoin/plugins/counter" + "github.com/tendermint/basecoin/plugins/ibc" ) var config cfg.Config @@ -41,13 +42,13 @@ func cmdStart(c *cli.Context) error { // Create Basecoin app basecoinApp := app.NewBasecoin(eyesCli) - switch c.String("plugin") { - case "counter": + if c.Bool("counter-plugin") { basecoinApp.RegisterPlugin(counter.New("counter")) - case "": - // no plugins to register - default: - return errors.New(cmn.Fmt("Unknown plugin: %v", c.String("plugin"))) + } + + if c.Bool("ibc-plugin") { + basecoinApp.RegisterPlugin(ibc.New()) + } // If genesis file was specified, set key-value options diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index 087f336d91..ce4bf3ccde 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -154,7 +154,7 @@ func (ibc *IBCPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes var tx IBCTx err := wire.ReadBinaryBytes(txBytes, &tx) if err != nil { - return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()).PrependLog("IBCTx Error: ") } // Validate tx From 7bbd21f7ee72d2dd7e66ef7ae2eda4802cc6a8ad Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 20:46:01 -0800 Subject: [PATCH 22/64] Fix res.Log; Update chain test --- plugins/ibc/ibc.go | 15 ++++++----- plugins/ibc/ibc_test.go | 59 ++++++++++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index ce4bf3ccde..0c203eb97d 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -205,14 +205,14 @@ func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { wire.ReadJSONPtr(&chainGenDoc, []byte(chainGen.Genesis), &err) if err != nil { sm.res.Code = IBCCodeEncodingError - sm.res.AppendLog("Genesis doc couldn't be parsed: " + err.Error()) + sm.res.Log = "Genesis doc couldn't be parsed: " + err.Error() return } // Make sure chainGen doesn't already exist if exists(sm.store, chainGenKey) { sm.res.Code = IBCCodeChainAlreadyExists - sm.res.AppendLog("Already exists") + sm.res.Log = "Already exists" return } @@ -291,7 +291,8 @@ func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) { // Make sure packet doesn't already exist if exists(sm.store, packetKey) { sm.res.Code = IBCCodePacketAlreadyExists - sm.res.AppendLog("Already exists") + // TODO: .AppendLog() does not update sm.res + sm.res.Log = "Already exists" return } // Save new Packet @@ -318,7 +319,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { // Make sure packet doesn't already exist if exists(sm.store, packetKeyIngress) { sm.res.Code = IBCCodePacketAlreadyExists - sm.res.AppendLog("Already exists") + sm.res.Log = "Already exists" return } @@ -334,7 +335,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { } if !exists { sm.res.Code = IBCCodeUnknownHeight - sm.res.AppendLog(cmn.Fmt("Loading Header: %v", err.Error())) + sm.res.Log = cmn.Fmt("Loading Header: %v", err.Error()) return } @@ -344,7 +345,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { err = wire.ReadBinaryBytes(tx.Proof, &proof) if err != nil { sm.res.Code = IBCEncodingError - sm.res.AppendLog(cmn.Fmt("Reading Proof: %v", err.Error())) + sm.res.Log = cmn.Fmt("Reading Proof: %v", err.Error()) return } */ @@ -355,7 +356,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash) if !ok { sm.res.Code = IBCCodeInvalidProof - sm.res.AppendLog("Proof is invalid") + sm.res.Log = "Proof is invalid" return } diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go index 393f5003f3..a5307740cd 100644 --- a/plugins/ibc/ibc_test.go +++ b/plugins/ibc/ibc_test.go @@ -1,6 +1,7 @@ package ibc import ( + "fmt" "strings" "testing" @@ -14,8 +15,8 @@ import ( tm "github.com/tendermint/tendermint/types" ) -func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []*tm.Validator) { - var vals []*tm.Validator +func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAccount) { + var privAccs []types.PrivAccount genDoc := &tm.GenesisDoc{ ChainID: chainID, Validators: nil, @@ -23,17 +24,16 @@ func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []*tm.Validator for i := 0; i < numVals; i++ { name := cmn.Fmt("%v_val_%v", chainID, i) - valPrivAcc := testutils.PrivAccountFromSecret(name) - val := tm.NewValidator(valPrivAcc.Account.PubKey, 1) + privAcc := testutils.PrivAccountFromSecret(name) genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{ - PubKey: val.PubKey, + PubKey: privAcc.Account.PubKey, Amount: 1, Name: name, }) - vals = append(vals, val) + privAccs = append(privAccs, privAcc) } - return genDoc, vals + return genDoc, privAccs } func TestIBCPlugin(t *testing.T) { @@ -50,7 +50,7 @@ func TestIBCPlugin(t *testing.T) { } chainID_1 := "test_chain" - genDoc_1, vals_1 := genGenesisDoc(chainID_1, 4) + genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) genDocJSON_1 := wire.JSONBytesPretty(genDoc_1) // Register a malformed chain @@ -114,10 +114,45 @@ func TestIBCPlugin(t *testing.T) { t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() - // Update a chain - //header, commit := - + // Construct a Header that includes the above packet. store.Sync() resCommit := tree.CommitSync() - t.Log(">>", vals_1, tree, resCommit.Data) + appHash := resCommit.Data + header := tm.Header{ + ChainID: "test_chain", + Height: 999, + AppHash: appHash, + } + + // Construct a Commit that signs above header + blockHash := header.Hash() + blockID := tm.BlockID{Hash: blockHash} + commit := tm.Commit{ + BlockID: blockID, + Precommits: make([]*tm.Vote, len(privAccs_1)), + } + for i, privAcc := range privAccs_1 { + vote := &tm.Vote{ + ValidatorAddress: privAcc.Account.PubKey.Address(), + ValidatorIndex: i, + Height: 999, + Round: 0, + Type: tm.VoteTypePrecommit, + BlockID: tm.BlockID{}, + } + vote.Signature = privAcc.PrivKey.Sign( + tm.SignBytes("test_chain", vote), + ) + fmt.Println(">>", i, privAcc, vote) + commit.Precommits[i] = vote + } + + // Update a chain + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ + Header: header, + Commit: commit, + }})) + assert.Equal(t, res.Code, abci.CodeType(0), res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() } From 28667a9a9fc0b742edb0501f82224834f231f095 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 21:07:29 -0800 Subject: [PATCH 23/64] Sort PrivAccounts so Commit signatures are sorted --- plugins/ibc/ibc_test.go | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go index a5307740cd..3074c93443 100644 --- a/plugins/ibc/ibc_test.go +++ b/plugins/ibc/ibc_test.go @@ -1,7 +1,8 @@ package ibc import ( - "fmt" + "bytes" + "sort" "strings" "testing" @@ -15,6 +16,8 @@ import ( tm "github.com/tendermint/tendermint/types" ) +// NOTE: PrivAccounts are sorted by Address, +// GenesisDoc, not necessarily. func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAccount) { var privAccs []types.PrivAccount genDoc := &tm.GenesisDoc{ @@ -33,9 +36,33 @@ func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAcc privAccs = append(privAccs, privAcc) } + // Sort PrivAccounts + sort.Sort(PrivAccountsByAddress(privAccs)) + return genDoc, privAccs } +//------------------------------------- +// Implements sort for sorting PrivAccount by address. + +type PrivAccountsByAddress []types.PrivAccount + +func (pas PrivAccountsByAddress) Len() int { + return len(pas) +} + +func (pas PrivAccountsByAddress) Less(i, j int) bool { + return bytes.Compare(pas[i].Account.PubKey.Address(), pas[j].Account.PubKey.Address()) == -1 +} + +func (pas PrivAccountsByAddress) Swap(i, j int) { + it := pas[i] + pas[i] = pas[j] + pas[j] = it +} + +//-------------------------------------------------------------------------------- + func TestIBCPlugin(t *testing.T) { tree := eyes.NewLocalClient("", 0) @@ -119,9 +146,10 @@ func TestIBCPlugin(t *testing.T) { resCommit := tree.CommitSync() appHash := resCommit.Data header := tm.Header{ - ChainID: "test_chain", - Height: 999, - AppHash: appHash, + ChainID: "test_chain", + Height: 999, + AppHash: appHash, + ValidatorsHash: []byte("must_exist"), // TODO make optional } // Construct a Commit that signs above header @@ -138,12 +166,11 @@ func TestIBCPlugin(t *testing.T) { Height: 999, Round: 0, Type: tm.VoteTypePrecommit, - BlockID: tm.BlockID{}, + BlockID: tm.BlockID{Hash: blockHash}, } vote.Signature = privAcc.PrivKey.Sign( tm.SignBytes("test_chain", vote), ) - fmt.Println(">>", i, privAcc, vote) commit.Precommits[i] = vote } From 0e94253d43248d4c4b30f300e323a6637dfb236b Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 21:16:51 -0800 Subject: [PATCH 24/64] Test update with bad commit --- plugins/ibc/ibc.go | 8 +++++--- plugins/ibc/ibc_test.go | 24 +++++++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index 0c203eb97d..7662e9ec91 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -66,7 +66,8 @@ const ( IBCCodeChainAlreadyExists = abci.CodeType(1002) IBCCodePacketAlreadyExists = abci.CodeType(1003) IBCCodeUnknownHeight = abci.CodeType(1004) - IBCCodeInvalidProof = abci.CodeType(1005) + IBCCodeInvalidCommit = abci.CodeType(1005) + IBCCodeInvalidProof = abci.CodeType(1006) ) var _ = wire.RegisterInterface( @@ -154,7 +155,7 @@ func (ibc *IBCPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes var tx IBCTx err := wire.ReadBinaryBytes(txBytes, &tx) if err != nil { - return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()).PrependLog("IBCTx Error: ") + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) } // Validate tx @@ -265,7 +266,8 @@ func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) { // Check commit against last known state & validators err = verifyCommit(chainState, &tx.Header, &tx.Commit) if err != nil { - sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Invalid Commit: %v", err.Error())) + sm.res.Code = IBCCodeInvalidCommit + sm.res.Log = cmn.Fmt("Invalid Commit: %v", err.Error()) return } diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go index 3074c93443..0b1a71fb01 100644 --- a/plugins/ibc/ibc_test.go +++ b/plugins/ibc/ibc_test.go @@ -11,6 +11,7 @@ import ( "github.com/tendermint/basecoin/testutils" "github.com/tendermint/basecoin/types" cmn "github.com/tendermint/go-common" + crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" tm "github.com/tendermint/tendermint/types" @@ -87,7 +88,7 @@ func TestIBCPlugin(t *testing.T) { Genesis: "", }, }})) - assert.Equal(t, res.Code, IBCCodeEncodingError) + assert.Equal(t, IBCCodeEncodingError, res.Code) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() @@ -109,7 +110,7 @@ func TestIBCPlugin(t *testing.T) { Genesis: string(genDocJSON_1), }, }})) - assert.Equal(t, res.Code, IBCCodeChainAlreadyExists, res.Log) + assert.Equal(t, IBCCodeChainAlreadyExists, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() @@ -123,7 +124,7 @@ func TestIBCPlugin(t *testing.T) { Payload: []byte("hello world"), }, }})) - assert.Equal(t, res.Code, abci.CodeType(0), res.Log) + assert.Equal(t, abci.CodeType_OK, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() @@ -137,7 +138,7 @@ func TestIBCPlugin(t *testing.T) { Payload: []byte("hello world"), }, }})) - assert.Equal(t, res.Code, IBCCodePacketAlreadyExists, res.Log) + assert.Equal(t, IBCCodePacketAlreadyExists, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() @@ -179,7 +180,20 @@ func TestIBCPlugin(t *testing.T) { Header: header, Commit: commit, }})) - assert.Equal(t, res.Code, abci.CodeType(0), res.Log) + assert.Equal(t, abci.CodeType_OK, res.Code, res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Update a chain with a broken commit + // Modify the first byte of the first signature + sig := commit.Precommits[0].Signature.(crypto.SignatureEd25519) + sig[0] += 1 + commit.Precommits[0].Signature = sig + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ + Header: header, + Commit: commit, + }})) + assert.Equal(t, IBCCodeInvalidCommit, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() } From 1ffe00def628b00034b952dffb9a8a860edea387 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 21:27:57 -0800 Subject: [PATCH 25/64] cmd: fixes --- cmd/basecoin/cmd.go | 3 +-- cmd/basecoin/flags.go | 14 ++++---------- cmd/basecoin/ibc.go | 4 ++-- cmd/basecoin/start.go | 11 +++++++---- cmd/basecoin/tx.go | 19 ++++++++++--------- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/cmd/basecoin/cmd.go b/cmd/basecoin/cmd.go index e5fb7d2a42..750b44b174 100644 --- a/cmd/basecoin/cmd.go +++ b/cmd/basecoin/cmd.go @@ -15,8 +15,7 @@ var ( Flags: []cli.Flag{ addrFlag, eyesFlag, - eyesDBFlag, - genesisFlag, + dirFlag, inProcTMFlag, chainIDFlag, ibcPluginFlag, diff --git a/cmd/basecoin/flags.go b/cmd/basecoin/flags.go index 7e349933f9..c724d8be48 100644 --- a/cmd/basecoin/flags.go +++ b/cmd/basecoin/flags.go @@ -18,19 +18,13 @@ var ( Usage: "MerkleEyes address, or 'local' for embedded", } - eyesDBFlag = cli.StringFlag{ - Name: "eyes-db", - Value: "merkleeyes.db", - Usage: "MerkleEyes db name for embedded", - } - // TODO: move to config file // eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") - genesisFlag = cli.StringFlag{ - Name: "genesis", - Value: "", - Usage: "Path to genesis file, if it exists", + dirFlag = cli.StringFlag{ + Name: "dir", + Value: ".", + Usage: "Root directory", } inProcTMFlag = cli.BoolFlag{ diff --git a/cmd/basecoin/ibc.go b/cmd/basecoin/ibc.go index a991e28fd1..a0bab12997 100644 --- a/cmd/basecoin/ibc.go +++ b/cmd/basecoin/ibc.go @@ -110,7 +110,7 @@ func cmdIBCPacketCreateTx(c *cli.Context) error { ibc.IBCTx `json:"unwrap"` }{ibcTx})) - return appTx(c.Parent(), "IBC", data) + return appTx(c.Parent().Parent(), "IBC", data) } func cmdIBCPacketPostTx(c *cli.Context) error { @@ -148,7 +148,7 @@ func cmdIBCPacketPostTx(c *cli.Context) error { ibc.IBCTx `json:"unwrap"` }{ibcTx})) - return appTx(c.Parent(), "IBC", data) + return appTx(c.Parent().Parent(), "IBC", data) } func getIBCSequence(c *cli.Context) (uint64, error) { diff --git a/cmd/basecoin/start.go b/cmd/basecoin/start.go index 687685d12b..4a68a0c175 100644 --- a/cmd/basecoin/start.go +++ b/cmd/basecoin/start.go @@ -2,6 +2,8 @@ package main import ( "errors" + "os" + "path" "github.com/urfave/cli" @@ -30,7 +32,7 @@ func cmdStart(c *cli.Context) error { // Connect to MerkleEyes var eyesCli *eyes.Client if c.String("eyes") == "local" { - eyesCli = eyes.NewLocalClient(c.String("eyes-db"), EyesCacheSize) + eyesCli = eyes.NewLocalClient(path.Join(c.String("dir"), "merkleeyes.db"), EyesCacheSize) } else { var err error eyesCli, err = eyes.NewClient(c.String("eyes")) @@ -51,9 +53,10 @@ func cmdStart(c *cli.Context) error { } - // If genesis file was specified, set key-value options - if c.String("genesis") != "" { - err := basecoinApp.LoadGenesis(c.String("genesis")) + // If genesis file exists, set key-value options + genesisFile := path.Join(c.String("dir"), "genesis.json") + if _, err := os.Stat(genesisFile); err == nil { + err := basecoinApp.LoadGenesis(genesisFile) if err != nil { return errors.New(cmn.Fmt("%+v", err)) } diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index 43e5a8e2e2..3057572086 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -59,7 +59,7 @@ func cmdSendTx(c *cli.Context) error { fmt.Println(string(wire.JSONBytes(tx))) // broadcast the transaction to tendermint - if err := broadcastTx(c, tx); err != nil { + if _, err := broadcastTx(c, tx); err != nil { return err } return nil @@ -104,7 +104,7 @@ func appTx(c *cli.Context, name string, data []byte) error { fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) - if err := broadcastTx(c, tx); err != nil { + if _, err := broadcastTx(c, tx); err != nil { return err } @@ -134,7 +134,7 @@ func cmdCounterTx(c *cli.Context) error { } // broadcast the transaction to tendermint -func broadcastTx(c *cli.Context, tx types.Tx) error { +func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, error) { tmResult := new(ctypes.TMResult) tmAddr := c.String("node") clientURI := client.NewClientURI(tmAddr) @@ -144,15 +144,16 @@ func broadcastTx(c *cli.Context, tx types.Tx) error { txBytes := []byte(wire.BinaryBytes(struct { types.Tx `json:"unwrap"` }{tx})) - _, err := clientURI.Call("broadcast_tx_sync", map[string]interface{}{"tx": txBytes}, tmResult) + _, err := clientURI.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult) if err != nil { - return errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) + return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) } - res := (*tmResult).(*ctypes.ResultBroadcastTx) - if !res.Code.IsOK() { - return errors.New(cmn.Fmt("BroadcastTxSync got non-zero exit code: %v. %X; %s", res.Code, res.Data, res.Log)) + res := (*tmResult).(*ctypes.ResultBroadcastTxCommit) + if !res.DeliverTx.Code.IsOK() { + r := res.DeliverTx + return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)) } - return nil + return res.DeliverTx.Data, nil } // if the sequence flag is set, return it; From a9408aa3b1c478d85118af01770c3d34d33668aa Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 21:29:43 -0800 Subject: [PATCH 26/64] ibc demo --- demo/clean.sh | 13 +++ demo/data/chain1/basecoin/genesis.json | 12 +++ demo/data/chain1/basecoin/priv_validator.json | 17 ++++ demo/data/chain1/tendermint/config.toml | 11 +++ demo/data/chain1/tendermint/genesis.json | 15 ++++ .../chain1/tendermint/priv_validator.json | 16 ++++ demo/data/chain2/basecoin/genesis.json | 12 +++ demo/data/chain2/basecoin/priv_validator.json | 16 ++++ demo/data/chain2/tendermint/config.toml | 11 +++ demo/data/chain2/tendermint/genesis.json | 15 ++++ .../chain2/tendermint/priv_validator.json | 16 ++++ demo/start.sh | 86 +++++++++++++++++++ 12 files changed, 240 insertions(+) create mode 100644 demo/clean.sh create mode 100644 demo/data/chain1/basecoin/genesis.json create mode 100644 demo/data/chain1/basecoin/priv_validator.json create mode 100644 demo/data/chain1/tendermint/config.toml create mode 100644 demo/data/chain1/tendermint/genesis.json create mode 100644 demo/data/chain1/tendermint/priv_validator.json create mode 100644 demo/data/chain2/basecoin/genesis.json create mode 100644 demo/data/chain2/basecoin/priv_validator.json create mode 100644 demo/data/chain2/tendermint/config.toml create mode 100644 demo/data/chain2/tendermint/genesis.json create mode 100644 demo/data/chain2/tendermint/priv_validator.json create mode 100644 demo/start.sh diff --git a/demo/clean.sh b/demo/clean.sh new file mode 100644 index 0000000000..e2d519337d --- /dev/null +++ b/demo/clean.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +killall -9 basecoin tendermint +TMROOT=./data/chain1/tendermint tendermint unsafe_reset_all +TMROOT=./data/chain2/tendermint tendermint unsafe_reset_all + +rm -rf ./data/chain1/basecoin/merkleeyes.db +rm -rf ./data/chain2/basecoin/merkleeyes.db + +rm ./*.log + +rm ./data/chain1/tendermint/*.bak +rm ./data/chain2/tendermint/*.bak diff --git a/demo/data/chain1/basecoin/genesis.json b/demo/data/chain1/basecoin/genesis.json new file mode 100644 index 0000000000..717a6345a0 --- /dev/null +++ b/demo/data/chain1/basecoin/genesis.json @@ -0,0 +1,12 @@ +[ + "base/chainID", "test_chain_1", + "base/account", { + "pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"], + "coins": [ + { + "denom": "blank", + "amount": 9007199254740992 + } + ] + } +] diff --git a/demo/data/chain1/basecoin/priv_validator.json b/demo/data/chain1/basecoin/priv_validator.json new file mode 100644 index 0000000000..15d7919240 --- /dev/null +++ b/demo/data/chain1/basecoin/priv_validator.json @@ -0,0 +1,17 @@ +{ + "address": "D397BC62B435F3CF50570FBAB4340FE52C60858F", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": [ + 1, + "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" + ], + "pub_key": [ + 1, + "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" + ] +} + diff --git a/demo/data/chain1/tendermint/config.toml b/demo/data/chain1/tendermint/config.toml new file mode 100644 index 0000000000..9b97202d12 --- /dev/null +++ b/demo/data/chain1/tendermint/config.toml @@ -0,0 +1,11 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "tcp://127.0.0.1:46658" +moniker = "anonymous" +node_laddr = "tcp://0.0.0.0:46656" +seeds = "" +fast_sync = true +db_backend = "leveldb" +log_level = "notice" +rpc_laddr = "tcp://0.0.0.0:46657" diff --git a/demo/data/chain1/tendermint/genesis.json b/demo/data/chain1/tendermint/genesis.json new file mode 100644 index 0000000000..27beb1b826 --- /dev/null +++ b/demo/data/chain1/tendermint/genesis.json @@ -0,0 +1,15 @@ +{ + "app_hash": "", + "chain_id": "test-chain-AtzVUw", + "genesis_time": "0001-01-01T00:00:00.000Z", + "validators": [ + { + "amount": 10, + "name": "", + "pub_key": [ + 1, + "D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" + ] + } + ] +} \ No newline at end of file diff --git a/demo/data/chain1/tendermint/priv_validator.json b/demo/data/chain1/tendermint/priv_validator.json new file mode 100644 index 0000000000..1ea10c12da --- /dev/null +++ b/demo/data/chain1/tendermint/priv_validator.json @@ -0,0 +1,16 @@ +{ + "address": "EBB0B4A899973C524A6BB18A161056A55F590F41", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": [ + 1, + "5FFDC1EA5FA2CA4A0A5503C86D2D348C5B401AD80FAA1899508F1ED00D8982E8D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" + ], + "pub_key": [ + 1, + "D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" + ] +} \ No newline at end of file diff --git a/demo/data/chain2/basecoin/genesis.json b/demo/data/chain2/basecoin/genesis.json new file mode 100644 index 0000000000..1dcc0d658f --- /dev/null +++ b/demo/data/chain2/basecoin/genesis.json @@ -0,0 +1,12 @@ +[ + "base/chainID", "test_chain_2", + "base/account", { + "pub_key": [1, "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"], + "coins": [ + { + "denom": "blank", + "amount": 9007199254740992 + } + ] + } +] diff --git a/demo/data/chain2/basecoin/priv_validator.json b/demo/data/chain2/basecoin/priv_validator.json new file mode 100644 index 0000000000..8f2eccadeb --- /dev/null +++ b/demo/data/chain2/basecoin/priv_validator.json @@ -0,0 +1,16 @@ +{ + "address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": [ + 1, + "22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D" + ], + "pub_key": [ + 1, + "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D" + ] +} diff --git a/demo/data/chain2/tendermint/config.toml b/demo/data/chain2/tendermint/config.toml new file mode 100644 index 0000000000..9b97202d12 --- /dev/null +++ b/demo/data/chain2/tendermint/config.toml @@ -0,0 +1,11 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "tcp://127.0.0.1:46658" +moniker = "anonymous" +node_laddr = "tcp://0.0.0.0:46656" +seeds = "" +fast_sync = true +db_backend = "leveldb" +log_level = "notice" +rpc_laddr = "tcp://0.0.0.0:46657" diff --git a/demo/data/chain2/tendermint/genesis.json b/demo/data/chain2/tendermint/genesis.json new file mode 100644 index 0000000000..b61008669a --- /dev/null +++ b/demo/data/chain2/tendermint/genesis.json @@ -0,0 +1,15 @@ +{ + "app_hash": "", + "chain_id": "test-chain-cLhwLM", + "genesis_time": "0001-01-01T00:00:00.000Z", + "validators": [ + { + "amount": 10, + "name": "", + "pub_key": [ + 1, + "9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F" + ] + } + ] +} \ No newline at end of file diff --git a/demo/data/chain2/tendermint/priv_validator.json b/demo/data/chain2/tendermint/priv_validator.json new file mode 100644 index 0000000000..8b3eb7e348 --- /dev/null +++ b/demo/data/chain2/tendermint/priv_validator.json @@ -0,0 +1,16 @@ +{ + "address": "D42CFCB9C42DF9A73143EEA89255D1DF027B6240", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": [ + 1, + "6353FAF4ADEB03EA496A9EAE5BE56C4C6A851CB705401788184FDC9198413C2C9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F" + ], + "pub_key": [ + 1, + "9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F" + ] +} \ No newline at end of file diff --git a/demo/start.sh b/demo/start.sh new file mode 100644 index 0000000000..f552f36a38 --- /dev/null +++ b/demo/start.sh @@ -0,0 +1,86 @@ +#! /bin/bash +set -eu + +cd $GOPATH/src/github.com/tendermint/basecoin/demo + +function removeQuotes() { + temp="${1%\"}" + temp="${temp#\"}" + echo "$temp" +} + +# grab the chain ids +CHAIN_ID1=$(cat ./data/chain1/basecoin/genesis.json | jq .[1]) +CHAIN_ID1=$(removeQuotes $CHAIN_ID1) +CHAIN_ID2=$(cat ./data/chain2/basecoin/genesis.json | jq .[1]) +CHAIN_ID2=$(removeQuotes $CHAIN_ID2) +echo "CHAIN_ID1: $CHAIN_ID1" +echo "CHAIN_ID2: $CHAIN_ID2" + +# make reusable chain flags +CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json" +CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657" + +echo "" +echo "... starting chains" +echo "" +# start the first node +TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & +basecoin start --ibc-plugin --dir ./data/chain1/basecoin &> chain1_basecoin.log & + +# start the second node +TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & +basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/basecoin &> chain2_basecoin.log & + +echo "" +echo "... waiting for chains to start" +echo "" +sleep 5 + +echo "... registering chain1 on chain2" +echo "" +# register chain1 on chain2 +basecoin ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json + +echo "" +echo "... creating egress packet on chain1" +echo "" +# create a packet on chain1 destined for chain2 +PAYLOAD="DEADBEEF" #TODO +basecoin ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1 + +echo "" +echo "... querying for packet data" +echo "" +# query for the packet data and proof +QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) +HEIGHT=$(echo $QUERY_RESULT | jq .height) +PACKET=$(echo $QUERY_RESULT | jq .value) +PROOF=$(echo $QUERY_RESULT | jq .proof) +echo "QUERY_RESULT: $QUERY_RESULT" +echo "HEIGHT: $HEIGHT" +echo "PACKET: $PACKET" +echo "PROOF: $PROOF" + +echo "" +echo "... querying for block data" +echo "" +# get the header and commit for the height +HEADER_AND_COMMIT=$(basecoin block $HEIGHT) +HEADER=$(echo $HEADER_AND_COMMIT | jq.hex.header) +COMMIT=$(echo $HEADER_AND_COMMIT | jq.hex.commit) +echo "HEADER_AND_COMMIT: $HEADER_AND_COMMIT" +echo "HEADER: $HEADER" +echo "COMMIT: $COMMIT" + +echo "" +echo "... updating state of chain1 on chain2" +echo "" +# update the state of chain1 on chain2 +basecoin ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT + +echo "" +echo "... posting packet from chain1 on chain2" +echo "" +# post the packet from chain1 to chain2 +basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $HEIGHT --packet $PACKET --proof $PROOF From 15aa84fd4cdf64d8810dafa1caf4d62ea1c4d35a Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 21:52:26 -0800 Subject: [PATCH 27/64] Test bad commit --- plugins/ibc/ibc_test.go | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go index 0b1a71fb01..b0d9b62d5d 100644 --- a/plugins/ibc/ibc_test.go +++ b/plugins/ibc/ibc_test.go @@ -183,6 +183,64 @@ func TestIBCPlugin(t *testing.T) { assert.Equal(t, abci.CodeType_OK, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() +} + +func TestIBCPluginBadCommit(t *testing.T) { + + tree := eyes.NewLocalClient("", 0) + store := types.NewKVCache(tree) + store.SetLogging() // Log all activity + + ibcPlugin := New() + ctx := types.CallContext{ + CallerAddress: nil, + CallerAccount: nil, + Coins: types.Coins{}, + } + + chainID_1 := "test_chain" + genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) + genDocJSON_1 := wire.JSONBytesPretty(genDoc_1) + + // Successfully register a chain + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ + BlockchainGenesis{ + ChainID: "test_chain", + Genesis: string(genDocJSON_1), + }, + }})) + assert.True(t, res.IsOK(), res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Construct a Header + header := tm.Header{ + ChainID: "test_chain", + Height: 999, + ValidatorsHash: []byte("must_exist"), // TODO make optional + } + + // Construct a Commit that signs above header + blockHash := header.Hash() + blockID := tm.BlockID{Hash: blockHash} + commit := tm.Commit{ + BlockID: blockID, + Precommits: make([]*tm.Vote, len(privAccs_1)), + } + for i, privAcc := range privAccs_1 { + vote := &tm.Vote{ + ValidatorAddress: privAcc.Account.PubKey.Address(), + ValidatorIndex: i, + Height: 999, + Round: 0, + Type: tm.VoteTypePrecommit, + BlockID: tm.BlockID{Hash: blockHash}, + } + vote.Signature = privAcc.PrivKey.Sign( + tm.SignBytes("test_chain", vote), + ) + commit.Precommits[i] = vote + } // Update a chain with a broken commit // Modify the first byte of the first signature @@ -196,4 +254,5 @@ func TestIBCPlugin(t *testing.T) { assert.Equal(t, IBCCodeInvalidCommit, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() + } From 8636946f805c651ce4956a6d6ffc3dcaedbcc8fa Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Jan 2017 22:31:05 -0800 Subject: [PATCH 28/64] Add test for bad proof --- glide.lock | 29 ++++++- plugins/ibc/ibc.go | 11 ++- plugins/ibc/ibc_test.go | 177 +++++++++++++++++++++++++++++++++++----- 3 files changed, 191 insertions(+), 26 deletions(-) diff --git a/glide.lock b/glide.lock index 00754a8ad8..e14238be1f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: 3869944d14a8df914ffcad02c2ef3548173daba51c5ea697767f8af77c07b348 -updated: 2017-01-28T09:14:54.898268931-08:00 +updated: 2017-01-29T22:09:11.408245895-08:00 imports: - name: github.com/btcsuite/btcd version: afec1bd1245a4a19e6dfe1306974b733e7cbb9b8 @@ -9,6 +9,8 @@ imports: version: 637e656429416087660c84436a2a035d69d54e2e - name: github.com/BurntSushi/toml version: 99064174e013895bbd9b025c31100bd1d9b590ca +- name: github.com/ebuchman/fail-test + version: c1eddaa09da2b4017351245b0d43234955276798 - name: github.com/go-stack/stack version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 - name: github.com/golang/protobuf @@ -46,6 +48,8 @@ imports: version: 8df0bc3a40ccad0d2be10e33c62c404e65c92502 subpackages: - client + - example/dummy + - example/nil - server - types - name: github.com/tendermint/ed25519 @@ -53,6 +57,10 @@ imports: subpackages: - edwards25519 - extra25519 +- name: github.com/tendermint/go-autofile + version: 0416e0aa9c68205aa44844096f9f151ada9d0405 +- name: github.com/tendermint/go-clist + version: 3baa390bbaf7634251c42ad69a8682e7e3990552 - name: github.com/tendermint/go-common version: 339e135776142939d82bc8e699db0bf391fd938d - name: github.com/tendermint/go-config @@ -79,23 +87,36 @@ imports: version: 6177eb8398ebd4613fbecb71fd96d7c7d97303ec subpackages: - client + - server - types - name: github.com/tendermint/go-wire - version: 2f3b7aafe21c80b19b6ee3210ecb3e3d07c7a471 + version: 3216ec9d47bbdf8d4fc27d22169ea86a6688bc15 - name: github.com/tendermint/log15 version: 9545b249b3aacafa97f79e0838b02b274adc6f5f subpackages: - term - name: github.com/tendermint/merkleeyes - version: 00d915af3e425cf57c10afe502fd9e0a6a70acd4 + version: 7c1ec0ef86c42b7a461e3967efb6c35bd5652101 subpackages: - app - client - name: github.com/tendermint/tendermint - version: 7c15b54cccac574cfe673c473d4edff01c2503ec + version: 67ab574e9889c0641ae959296d391e3cadec55e3 subpackages: + - blockchain + - config/tendermint + - consensus + - mempool + - node + - proxy + - rpc/core - rpc/core/types + - rpc/grpc + - state - types + - version +- name: github.com/urfave/cli + version: 8ef3805c9de2519805c3f060524b695bba2cd715 - name: golang.org/x/crypto version: aa2481cbfe81d911eb62b642b7a6b5ec58bbea71 subpackages: diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index 7662e9ec91..c337538744 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -121,7 +121,7 @@ type IBCPacketPostTx struct { FromChainID string // The immediate source of the packet, not always Packet.SrcChainID FromChainHeight uint64 // The block height in which Packet was committed, to check Proof Packet - Proof merkle.IAVLProof + Proof *merkle.IAVLProof } func (IBCPacketPostTx) ValidateBasic() (res abci.Result) { @@ -298,7 +298,7 @@ func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) { return } // Save new Packet - save(sm.store, packetKey, wire.BinaryBytes(packet)) + save(sm.store, packetKey, packet) } func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { @@ -326,7 +326,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { } // Save new Packet - save(sm.store, packetKeyIngress, wire.BinaryBytes(packet)) + save(sm.store, packetKeyIngress, packet) // Load Header and make sure it exists var header tm.Header @@ -352,6 +352,11 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { } */ proof := tx.Proof + if proof == nil { + sm.res.Code = IBCCodeInvalidProof + sm.res.Log = "Proof is nil" + return + } packetBytes := wire.BinaryBytes(packet) // Make sure packet's proof matches given (packet, key, blockhash) diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go index b0d9b62d5d..6bc3f0706e 100644 --- a/plugins/ibc/ibc_test.go +++ b/plugins/ibc/ibc_test.go @@ -12,6 +12,7 @@ import ( "github.com/tendermint/basecoin/types" cmn "github.com/tendermint/go-common" crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" tm "github.com/tendermint/tendermint/types" @@ -66,8 +67,8 @@ func (pas PrivAccountsByAddress) Swap(i, j int) { func TestIBCPlugin(t *testing.T) { - tree := eyes.NewLocalClient("", 0) - store := types.NewKVCache(tree) + eyesClient := eyes.NewLocalClient("", 0) + store := types.NewKVCache(eyesClient) store.SetLogging() // Log all activity ibcPlugin := New() @@ -115,14 +116,15 @@ func TestIBCPlugin(t *testing.T) { store.ClearLogLines() // Create a new packet (for testing) + packet := Packet{ + SrcChainID: "test_chain", + DstChainID: "dst_chain", + Sequence: 0, + Type: "data", + Payload: []byte("hello world"), + } res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ - Packet{ - SrcChainID: "test_chain", - DstChainID: "dst_chain", - Sequence: 0, - Type: "data", - Payload: []byte("hello world"), - }, + Packet: packet, }})) assert.Equal(t, abci.CodeType_OK, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) @@ -130,13 +132,7 @@ func TestIBCPlugin(t *testing.T) { // Post a duplicate packet res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ - Packet{ - SrcChainID: "test_chain", - DstChainID: "dst_chain", - Sequence: 0, - Type: "data", - Payload: []byte("hello world"), - }, + Packet: packet, }})) assert.Equal(t, IBCCodePacketAlreadyExists, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) @@ -144,7 +140,7 @@ func TestIBCPlugin(t *testing.T) { // Construct a Header that includes the above packet. store.Sync() - resCommit := tree.CommitSync() + resCommit := eyesClient.CommitSync() appHash := resCommit.Data header := tm.Header{ ChainID: "test_chain", @@ -183,12 +179,39 @@ func TestIBCPlugin(t *testing.T) { assert.Equal(t, abci.CodeType_OK, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() + + // Get proof for the packet + packetKey := toKey(_IBC, _EGRESS, + packet.SrcChainID, + packet.DstChainID, + cmn.Fmt("%v", packet.Sequence), + ) + resQuery, err := eyesClient.QuerySync(abci.RequestQuery{ + Path: "/store", + Data: packetKey, + Prove: true, + }) + assert.Nil(t, err) + var proof *merkle.IAVLProof + err = wire.ReadBinaryBytes(resQuery.Proof, &proof) + assert.Nil(t, err) + + // Post a packet + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{ + FromChainID: "test_chain", + FromChainHeight: 999, + Packet: packet, + Proof: proof, + }})) + assert.Equal(t, abci.CodeType_OK, res.Code, res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() } func TestIBCPluginBadCommit(t *testing.T) { - tree := eyes.NewLocalClient("", 0) - store := types.NewKVCache(tree) + eyesClient := eyes.NewLocalClient("", 0) + store := types.NewKVCache(eyesClient) store.SetLogging() // Log all activity ibcPlugin := New() @@ -256,3 +279,119 @@ func TestIBCPluginBadCommit(t *testing.T) { store.ClearLogLines() } + +func TestIBCPluginBadProof(t *testing.T) { + + eyesClient := eyes.NewLocalClient("", 0) + store := types.NewKVCache(eyesClient) + store.SetLogging() // Log all activity + + ibcPlugin := New() + ctx := types.CallContext{ + CallerAddress: nil, + CallerAccount: nil, + Coins: types.Coins{}, + } + + chainID_1 := "test_chain" + genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) + genDocJSON_1 := wire.JSONBytesPretty(genDoc_1) + + // Successfully register a chain + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ + BlockchainGenesis{ + ChainID: "test_chain", + Genesis: string(genDocJSON_1), + }, + }})) + assert.True(t, res.IsOK(), res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Create a new packet (for testing) + packet := Packet{ + SrcChainID: "test_chain", + DstChainID: "dst_chain", + Sequence: 0, + Type: "data", + Payload: []byte("hello world"), + } + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ + Packet: packet, + }})) + assert.Equal(t, abci.CodeType_OK, res.Code, res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Construct a Header that includes the above packet. + store.Sync() + resCommit := eyesClient.CommitSync() + appHash := resCommit.Data + header := tm.Header{ + ChainID: "test_chain", + Height: 999, + AppHash: appHash, + ValidatorsHash: []byte("must_exist"), // TODO make optional + } + + // Construct a Commit that signs above header + blockHash := header.Hash() + blockID := tm.BlockID{Hash: blockHash} + commit := tm.Commit{ + BlockID: blockID, + Precommits: make([]*tm.Vote, len(privAccs_1)), + } + for i, privAcc := range privAccs_1 { + vote := &tm.Vote{ + ValidatorAddress: privAcc.Account.PubKey.Address(), + ValidatorIndex: i, + Height: 999, + Round: 0, + Type: tm.VoteTypePrecommit, + BlockID: tm.BlockID{Hash: blockHash}, + } + vote.Signature = privAcc.PrivKey.Sign( + tm.SignBytes("test_chain", vote), + ) + commit.Precommits[i] = vote + } + + // Update a chain + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ + Header: header, + Commit: commit, + }})) + assert.Equal(t, abci.CodeType_OK, res.Code, res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() + + // Get proof for the packet + packetKey := toKey(_IBC, _EGRESS, + packet.SrcChainID, + packet.DstChainID, + cmn.Fmt("%v", packet.Sequence), + ) + resQuery, err := eyesClient.QuerySync(abci.RequestQuery{ + Path: "/store", + Data: packetKey, + Prove: true, + }) + assert.Nil(t, err) + var proof *merkle.IAVLProof + err = wire.ReadBinaryBytes(resQuery.Proof, &proof) + assert.Nil(t, err) + + // Mutate the proof + proof.InnerNodes[0].Height += 1 + + // Post a packet + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{ + FromChainID: "test_chain", + FromChainHeight: 999, + Packet: packet, + Proof: proof, + }})) + assert.Equal(t, IBCCodeInvalidProof, res.Code, res.Log) + t.Log(">>", strings.Join(store.GetLogLines(), "\n")) + store.ClearLogLines() +} From e578d1f07babbe7460bd15824413375fe8502e80 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 22:43:07 -0800 Subject: [PATCH 29/64] fixes to make demo work --- cmd/basecoin/ibc.go | 6 ++---- cmd/basecoin/query.go | 8 ++++---- demo/data/chain1/tendermint/genesis.json | 4 ++-- demo/data/chain2/tendermint/genesis.json | 4 ++-- demo/start.sh | 26 ++++++++++++++++++++---- plugins/ibc/ibc.go | 2 +- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/cmd/basecoin/ibc.go b/cmd/basecoin/ibc.go index a0bab12997..83d27d0bfd 100644 --- a/cmd/basecoin/ibc.go +++ b/cmd/basecoin/ibc.go @@ -44,8 +44,6 @@ func cmdIBCRegisterTx(c *cli.Context) error { } func cmdIBCUpdateTx(c *cli.Context) error { - parent := c.Parent() - headerBytes, err := hex.DecodeString(stripHex(c.String("header"))) if err != nil { return errors.New(cmn.Fmt("Header (%v) is invalid hex: %v", c.String("header"), err)) @@ -77,7 +75,7 @@ func cmdIBCUpdateTx(c *cli.Context) error { }{ibcTx})) name := "IBC" - return appTx(parent, name, data) + return appTx(c.Parent(), name, data) } func cmdIBCPacketCreateTx(c *cli.Context) error { @@ -126,7 +124,7 @@ func cmdIBCPacketPostTx(c *cli.Context) error { } var packet ibc.Packet - var proof merkle.IAVLProof + proof := new(merkle.IAVLProof) if err := wire.ReadBinaryBytes(packetBytes, &packet); err != nil { return errors.New(cmn.Fmt("Error unmarshalling packet: %v", err)) diff --git a/cmd/basecoin/query.go b/cmd/basecoin/query.go index 2aba1b8f58..01b293d118 100644 --- a/cmd/basecoin/query.go +++ b/cmd/basecoin/query.go @@ -81,10 +81,10 @@ func cmdBlock(c *cli.Context) error { return errors.New(cmn.Fmt("Height must be an int, got %v: %v", heightString, err)) } - block, err := getBlock(c, height) + /*block, err := getBlock(c, height) if err != nil { return err - } + }*/ nextBlock, err := getBlock(c, height+1) if err != nil { return err @@ -95,11 +95,11 @@ func cmdBlock(c *cli.Context) error { JSON BlockJSON `json:"json"` }{ BlockHex{ - Header: wire.BinaryBytes(block.Header), + Header: wire.BinaryBytes(nextBlock.Header), Commit: wire.BinaryBytes(nextBlock.LastCommit), }, BlockJSON{ - Header: block.Header, + Header: nextBlock.Header, Commit: nextBlock.LastCommit, }, }))) diff --git a/demo/data/chain1/tendermint/genesis.json b/demo/data/chain1/tendermint/genesis.json index 27beb1b826..91830dd23f 100644 --- a/demo/data/chain1/tendermint/genesis.json +++ b/demo/data/chain1/tendermint/genesis.json @@ -1,6 +1,6 @@ { "app_hash": "", - "chain_id": "test-chain-AtzVUw", + "chain_id": "test_chain_1", "genesis_time": "0001-01-01T00:00:00.000Z", "validators": [ { @@ -12,4 +12,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/demo/data/chain2/tendermint/genesis.json b/demo/data/chain2/tendermint/genesis.json index b61008669a..6c9f17c952 100644 --- a/demo/data/chain2/tendermint/genesis.json +++ b/demo/data/chain2/tendermint/genesis.json @@ -1,6 +1,6 @@ { "app_hash": "", - "chain_id": "test-chain-cLhwLM", + "chain_id": "test_chain_2", "genesis_time": "0001-01-01T00:00:00.000Z", "validators": [ { @@ -12,4 +12,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/demo/start.sh b/demo/start.sh index f552f36a38..da617910fd 100644 --- a/demo/start.sh +++ b/demo/start.sh @@ -35,7 +35,7 @@ basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/ echo "" echo "... waiting for chains to start" echo "" -sleep 5 +sleep 10 echo "... registering chain1 on chain2" echo "" @@ -57,18 +57,30 @@ QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) HEIGHT=$(echo $QUERY_RESULT | jq .height) PACKET=$(echo $QUERY_RESULT | jq .value) PROOF=$(echo $QUERY_RESULT | jq .proof) +PACKET=$(removeQuotes $PACKET) +PROOF=$(removeQuotes $PROOF) +echo "" echo "QUERY_RESULT: $QUERY_RESULT" echo "HEIGHT: $HEIGHT" echo "PACKET: $PACKET" echo "PROOF: $PROOF" + +echo "" +echo "... waiting for some blocks to be mined" +echo "" +sleep 5 + echo "" echo "... querying for block data" echo "" # get the header and commit for the height HEADER_AND_COMMIT=$(basecoin block $HEIGHT) -HEADER=$(echo $HEADER_AND_COMMIT | jq.hex.header) -COMMIT=$(echo $HEADER_AND_COMMIT | jq.hex.commit) +HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header) +HEADER=$(removeQuotes $HEADER) +COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit) +COMMIT=$(removeQuotes $COMMIT) +echo "" echo "HEADER_AND_COMMIT: $HEADER_AND_COMMIT" echo "HEADER: $HEADER" echo "COMMIT: $COMMIT" @@ -83,4 +95,10 @@ echo "" echo "... posting packet from chain1 on chain2" echo "" # post the packet from chain1 to chain2 -basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $HEIGHT --packet $PACKET --proof $PROOF +basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF + +echo "" +echo "... checking if the packet is present on chain2" +echo "" +# query for the packet on chain2 ! +basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index c337538744..8d6e0c3893 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -337,7 +337,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { } if !exists { sm.res.Code = IBCCodeUnknownHeight - sm.res.Log = cmn.Fmt("Loading Header: %v", err.Error()) + sm.res.Log = cmn.Fmt("Loading Header: Unknown height") return } From 699e0f6ae40a93a74a5d3975c3742c730fc4a034 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Jan 2017 23:35:37 -0800 Subject: [PATCH 30/64] bring back paytovote --- cmd/paytovote/main.go | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 cmd/paytovote/main.go diff --git a/cmd/paytovote/main.go b/cmd/paytovote/main.go new file mode 100644 index 0000000000..7c7715d992 --- /dev/null +++ b/cmd/paytovote/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "flag" + + "github.com/tendermint/abci/server" + "github.com/tendermint/basecoin/app" + "github.com/tendermint/basecoin/plugins/counter" + cmn "github.com/tendermint/go-common" + eyes "github.com/tendermint/merkleeyes/client" +) + +func main() { + addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address") + eyesPtr := flag.String("eyes", "local", "MerkleEyes address, or 'local' for embedded") + genFilePath := flag.String("genesis", "", "Genesis file, if any") + flag.Parse() + + // Connect to MerkleEyes + eyesCli, err := eyes.NewClient(*eyesPtr) + if err != nil { + cmn.Exit("connect to MerkleEyes: " + err.Error()) + } + + // Create Basecoin app + app := app.NewBasecoin(eyesCli) + + // add plugins + // TODO: add some more, like the cool voting app + counter := counter.New("counter") + app.RegisterPlugin(counter) + + // If genesis file was specified, set key-value options + if *genFilePath != "" { + err := app.LoadGenesis(*genFilePath) + if err != nil { + cmn.Exit(cmn.Fmt("%+v", err)) + } + } + + // Start the listener + svr, err := server.NewServer(*addrPtr, "socket", app) + if err != nil { + cmn.Exit("create listener: " + err.Error()) + } + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + svr.Stop() + }) + +} From 6a3c91a7f208d13fe674bae0dddd43267ca3be17 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 30 Jan 2017 15:22:41 +0400 Subject: [PATCH 31/64] fix path for `go get` also `-d` flag to not install the package --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cfa31cdb4c..f34af746dd 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi We use glide for dependency management. The prefered way of compiling from source is the following: ``` -go get github.com/tendermint/basecoin +go get -d github.com/tendermint/basecoin/cmd/basecoin cd $GOPATH/src/github.com/tendermint/basecoin make get_vendor_deps make install From 28f6a20a989a4853f899fc8123bddeef4f2ab71d Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 30 Jan 2017 15:16:51 +0100 Subject: [PATCH 32/64] Moved all commands into a subdir, for easier importing --- cmd/basecoin/cmd.go | 226 --------------------------- cmd/basecoin/{ => commands}/flags.go | 2 +- cmd/basecoin/{ => commands}/ibc.go | 94 ++++++++++- cmd/basecoin/{ => commands}/query.go | 54 ++++++- cmd/basecoin/{ => commands}/start.go | 20 ++- cmd/basecoin/{ => commands}/tx.go | 65 +++++++- cmd/basecoin/{ => commands}/utils.go | 2 +- cmd/basecoin/main.go | 17 +- 8 files changed, 240 insertions(+), 240 deletions(-) delete mode 100644 cmd/basecoin/cmd.go rename cmd/basecoin/{ => commands}/flags.go (99%) rename cmd/basecoin/{ => commands}/ibc.go (70%) rename cmd/basecoin/{ => commands}/query.go (79%) rename cmd/basecoin/{ => commands}/start.go (89%) rename cmd/basecoin/{ => commands}/tx.go (82%) rename cmd/basecoin/{ => commands}/utils.go (99%) diff --git a/cmd/basecoin/cmd.go b/cmd/basecoin/cmd.go deleted file mode 100644 index 750b44b174..0000000000 --- a/cmd/basecoin/cmd.go +++ /dev/null @@ -1,226 +0,0 @@ -package main - -import ( - "github.com/urfave/cli" -) - -var ( - startCmd = cli.Command{ - Name: "start", - Usage: "Start basecoin", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdStart(c) - }, - Flags: []cli.Flag{ - addrFlag, - eyesFlag, - dirFlag, - inProcTMFlag, - chainIDFlag, - ibcPluginFlag, - counterPluginFlag, - }, - } - - sendTxCmd = cli.Command{ - Name: "sendtx", - Usage: "Broadcast a basecoin SendTx", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdSendTx(c) - }, - Flags: []cli.Flag{ - nodeFlag, - chainIDFlag, - - fromFlag, - - amountFlag, - coinFlag, - gasFlag, - feeFlag, - seqFlag, - - toFlag, - }, - } - - appTxCmd = cli.Command{ - Name: "apptx", - Usage: "Broadcast a basecoin AppTx", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdAppTx(c) - }, - Flags: []cli.Flag{ - nodeFlag, - chainIDFlag, - - fromFlag, - - amountFlag, - coinFlag, - gasFlag, - feeFlag, - seqFlag, - - nameFlag, - dataFlag, - }, - Subcommands: []cli.Command{ - counterTxCmd, - }, - } - - counterTxCmd = cli.Command{ - Name: "counter", - Usage: "Craft a transaction to the counter plugin", - Action: func(c *cli.Context) error { - return cmdCounterTx(c) - }, - Flags: []cli.Flag{ - validFlag, - }, - } - - ibcCmd = cli.Command{ - Name: "ibc", - Usage: "Send a transaction to the interblockchain (ibc) plugin", - Flags: []cli.Flag{ - nodeFlag, - chainIDFlag, - - fromFlag, - - amountFlag, - coinFlag, - gasFlag, - feeFlag, - seqFlag, - - nameFlag, - dataFlag, - }, - Subcommands: []cli.Command{ - ibcRegisterTxCmd, - ibcUpdateTxCmd, - ibcPacketTxCmd, - }, - } - - ibcRegisterTxCmd = cli.Command{ - Name: "register", - Usage: "Register a blockchain via IBC", - Action: func(c *cli.Context) error { - return cmdIBCRegisterTx(c) - }, - Flags: []cli.Flag{ - ibcChainIDFlag, - ibcGenesisFlag, - }, - } - - ibcUpdateTxCmd = cli.Command{ - Name: "update", - Usage: "Update the latest state of a blockchain via IBC", - Action: func(c *cli.Context) error { - return cmdIBCUpdateTx(c) - }, - Flags: []cli.Flag{ - ibcHeaderFlag, - ibcCommitFlag, - }, - } - - ibcPacketTxCmd = cli.Command{ - Name: "packet", - Usage: "Send a new packet via IBC", - Flags: []cli.Flag{ - // - }, - Subcommands: []cli.Command{ - ibcPacketCreateTx, - ibcPacketPostTx, - }, - } - - ibcPacketCreateTx = cli.Command{ - Name: "create", - Usage: "Create an egress IBC packet", - Action: func(c *cli.Context) error { - return cmdIBCPacketCreateTx(c) - }, - Flags: []cli.Flag{ - ibcFromFlag, - ibcToFlag, - ibcTypeFlag, - ibcPayloadFlag, - ibcSequenceFlag, - }, - } - - ibcPacketPostTx = cli.Command{ - Name: "post", - Usage: "Deliver an IBC packet to another chain", - Action: func(c *cli.Context) error { - return cmdIBCPacketPostTx(c) - }, - Flags: []cli.Flag{ - ibcFromFlag, - ibcHeightFlag, - ibcPacketFlag, - ibcProofFlag, - }, - } - - queryCmd = cli.Command{ - Name: "query", - Usage: "Query the merkle tree", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdQuery(c) - }, - Flags: []cli.Flag{ - nodeFlag, - }, - } - - accountCmd = cli.Command{ - Name: "account", - Usage: "Get details of an account", - ArgsUsage: "
", - Action: func(c *cli.Context) error { - return cmdAccount(c) - }, - Flags: []cli.Flag{ - nodeFlag, - }, - } - - blockCmd = cli.Command{ - Name: "block", - Usage: "Get the header and commit of a block", - ArgsUsage: "", - Action: func(c *cli.Context) error { - return cmdBlock(c) - }, - Flags: []cli.Flag{ - nodeFlag, - }, - } - - verifyCmd = cli.Command{ - Name: "verify", - Usage: "Verify the IAVL proof", - Action: func(c *cli.Context) error { - return cmdVerify(c) - }, - Flags: []cli.Flag{ - proofFlag, - keyFlag, - valueFlag, - rootFlag, - }, - } -) diff --git a/cmd/basecoin/flags.go b/cmd/basecoin/commands/flags.go similarity index 99% rename from cmd/basecoin/flags.go rename to cmd/basecoin/commands/flags.go index c724d8be48..622aa61b9e 100644 --- a/cmd/basecoin/flags.go +++ b/cmd/basecoin/commands/flags.go @@ -1,4 +1,4 @@ -package main +package commands import ( "github.com/urfave/cli" diff --git a/cmd/basecoin/ibc.go b/cmd/basecoin/commands/ibc.go similarity index 70% rename from cmd/basecoin/ibc.go rename to cmd/basecoin/commands/ibc.go index 83d27d0bfd..23511c5151 100644 --- a/cmd/basecoin/ibc.go +++ b/cmd/basecoin/commands/ibc.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" @@ -16,6 +16,98 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +var ( + IbcCmd = cli.Command{ + Name: "ibc", + Usage: "Send a transaction to the interblockchain (ibc) plugin", + Flags: []cli.Flag{ + nodeFlag, + chainIDFlag, + + fromFlag, + + amountFlag, + coinFlag, + gasFlag, + feeFlag, + seqFlag, + + nameFlag, + dataFlag, + }, + Subcommands: []cli.Command{ + IbcRegisterTxCmd, + IbcUpdateTxCmd, + IbcPacketTxCmd, + }, + } + + IbcRegisterTxCmd = cli.Command{ + Name: "register", + Usage: "Register a blockchain via IBC", + Action: func(c *cli.Context) error { + return cmdIBCRegisterTx(c) + }, + Flags: []cli.Flag{ + ibcChainIDFlag, + ibcGenesisFlag, + }, + } + + IbcUpdateTxCmd = cli.Command{ + Name: "update", + Usage: "Update the latest state of a blockchain via IBC", + Action: func(c *cli.Context) error { + return cmdIBCUpdateTx(c) + }, + Flags: []cli.Flag{ + ibcHeaderFlag, + ibcCommitFlag, + }, + } + + IbcPacketTxCmd = cli.Command{ + Name: "packet", + Usage: "Send a new packet via IBC", + Flags: []cli.Flag{ + // + }, + Subcommands: []cli.Command{ + IbcPacketCreateTx, + IbcPacketPostTx, + }, + } + + IbcPacketCreateTx = cli.Command{ + Name: "create", + Usage: "Create an egress IBC packet", + Action: func(c *cli.Context) error { + return cmdIBCPacketCreateTx(c) + }, + Flags: []cli.Flag{ + ibcFromFlag, + ibcToFlag, + ibcTypeFlag, + ibcPayloadFlag, + ibcSequenceFlag, + }, + } + + IbcPacketPostTx = cli.Command{ + Name: "post", + Usage: "Deliver an IBC packet to another chain", + Action: func(c *cli.Context) error { + return cmdIBCPacketPostTx(c) + }, + Flags: []cli.Flag{ + ibcFromFlag, + ibcHeightFlag, + ibcPacketFlag, + ibcProofFlag, + }, + } +) + func cmdIBCRegisterTx(c *cli.Context) error { chainID := c.String("chain_id") genesisFile := c.String("genesis") diff --git a/cmd/basecoin/query.go b/cmd/basecoin/commands/query.go similarity index 79% rename from cmd/basecoin/query.go rename to cmd/basecoin/commands/query.go index 01b293d118..a2b22a0f37 100644 --- a/cmd/basecoin/query.go +++ b/cmd/basecoin/commands/query.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" @@ -14,6 +14,58 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +var ( + QueryCmd = cli.Command{ + Name: "query", + Usage: "Query the merkle tree", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdQuery(c) + }, + Flags: []cli.Flag{ + nodeFlag, + }, + } + + AccountCmd = cli.Command{ + Name: "account", + Usage: "Get details of an account", + ArgsUsage: "
", + Action: func(c *cli.Context) error { + return cmdAccount(c) + }, + Flags: []cli.Flag{ + nodeFlag, + }, + } + + BlockCmd = cli.Command{ + Name: "block", + Usage: "Get the header and commit of a block", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdBlock(c) + }, + Flags: []cli.Flag{ + nodeFlag, + }, + } + + VerifyCmd = cli.Command{ + Name: "verify", + Usage: "Verify the IAVL proof", + Action: func(c *cli.Context) error { + return cmdVerify(c) + }, + Flags: []cli.Flag{ + proofFlag, + keyFlag, + valueFlag, + rootFlag, + }, + } +) + func cmdQuery(c *cli.Context) error { if len(c.Args()) != 1 { return errors.New("query command requires an argument ([key])") diff --git a/cmd/basecoin/start.go b/cmd/basecoin/commands/start.go similarity index 89% rename from cmd/basecoin/start.go rename to cmd/basecoin/commands/start.go index 4a68a0c175..f442557a7a 100644 --- a/cmd/basecoin/start.go +++ b/cmd/basecoin/commands/start.go @@ -1,4 +1,4 @@ -package main +package commands import ( "errors" @@ -27,6 +27,24 @@ var config cfg.Config const EyesCacheSize = 10000 +var StartCmd = cli.Command{ + Name: "start", + Usage: "Start basecoin", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdStart(c) + }, + Flags: []cli.Flag{ + addrFlag, + eyesFlag, + dirFlag, + inProcTMFlag, + chainIDFlag, + ibcPluginFlag, + counterPluginFlag, + }, +} + func cmdStart(c *cli.Context) error { // Connect to MerkleEyes diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/commands/tx.go similarity index 82% rename from cmd/basecoin/tx.go rename to cmd/basecoin/commands/tx.go index 3057572086..5c37ecaee2 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/commands/tx.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" @@ -17,6 +17,69 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +var ( + SendTxCmd = cli.Command{ + Name: "sendtx", + Usage: "Broadcast a basecoin SendTx", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdSendTx(c) + }, + Flags: []cli.Flag{ + nodeFlag, + chainIDFlag, + + fromFlag, + + amountFlag, + coinFlag, + gasFlag, + feeFlag, + seqFlag, + + toFlag, + }, + } + + AppTxCmd = cli.Command{ + Name: "apptx", + Usage: "Broadcast a basecoin AppTx", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdAppTx(c) + }, + Flags: []cli.Flag{ + nodeFlag, + chainIDFlag, + + fromFlag, + + amountFlag, + coinFlag, + gasFlag, + feeFlag, + seqFlag, + + nameFlag, + dataFlag, + }, + Subcommands: []cli.Command{ + CounterTxCmd, + }, + } + + CounterTxCmd = cli.Command{ + Name: "counter", + Usage: "Craft a transaction to the counter plugin", + Action: func(c *cli.Context) error { + return cmdCounterTx(c) + }, + Flags: []cli.Flag{ + validFlag, + }, + } +) + func cmdSendTx(c *cli.Context) error { toHex := c.String("to") fromFile := c.String("from") diff --git a/cmd/basecoin/utils.go b/cmd/basecoin/commands/utils.go similarity index 99% rename from cmd/basecoin/utils.go rename to cmd/basecoin/commands/utils.go index b005f3306c..db9f2821c4 100644 --- a/cmd/basecoin/utils.go +++ b/cmd/basecoin/commands/utils.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 31dcb83525..536dc61879 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -3,6 +3,7 @@ package main import ( "os" + "github.com/tendermint/basecoin/cmd/basecoin/commands" "github.com/urfave/cli" ) @@ -12,14 +13,14 @@ func main() { app.Usage = "basecoin [command] [args...]" app.Version = "0.1.0" app.Commands = []cli.Command{ - startCmd, - sendTxCmd, - appTxCmd, - ibcCmd, - queryCmd, - verifyCmd, - blockCmd, - accountCmd, + commands.StartCmd, + commands.SendTxCmd, + commands.AppTxCmd, + commands.IbcCmd, + commands.QueryCmd, + commands.VerifyCmd, + commands.BlockCmd, + commands.AccountCmd, } app.Run(os.Args) } From cb4f6a4bca54d20e9e5b1efdf9959a4313f1243c Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 30 Jan 2017 15:27:02 +0100 Subject: [PATCH 33/64] Made all flag names public --- cmd/basecoin/commands/flags.go | 68 +++++++++++++++++----------------- cmd/basecoin/commands/ibc.go | 46 +++++++++++------------ cmd/basecoin/commands/query.go | 14 +++---- cmd/basecoin/commands/start.go | 14 +++---- cmd/basecoin/commands/tx.go | 40 ++++++++++---------- 5 files changed, 91 insertions(+), 91 deletions(-) diff --git a/cmd/basecoin/commands/flags.go b/cmd/basecoin/commands/flags.go index 622aa61b9e..6bbf69e7bb 100644 --- a/cmd/basecoin/commands/flags.go +++ b/cmd/basecoin/commands/flags.go @@ -6,13 +6,13 @@ import ( // start flags var ( - addrFlag = cli.StringFlag{ + AddrFlag = cli.StringFlag{ Name: "address", Value: "tcp://0.0.0.0:46658", Usage: "Listen address", } - eyesFlag = cli.StringFlag{ + EyesFlag = cli.StringFlag{ Name: "eyes", Value: "local", Usage: "MerkleEyes address, or 'local' for embedded", @@ -21,23 +21,23 @@ var ( // TODO: move to config file // eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded") - dirFlag = cli.StringFlag{ + DirFlag = cli.StringFlag{ Name: "dir", Value: ".", Usage: "Root directory", } - inProcTMFlag = cli.BoolFlag{ + InProcTMFlag = cli.BoolFlag{ Name: "in-proc", Usage: "Run Tendermint in-process with the App", } - ibcPluginFlag = cli.BoolFlag{ + IbcPluginFlag = cli.BoolFlag{ Name: "ibc-plugin", Usage: "Enable the ibc plugin", } - counterPluginFlag = cli.BoolFlag{ + CounterPluginFlag = cli.BoolFlag{ Name: "counter-plugin", Usage: "Enable the counter plugin", } @@ -46,73 +46,73 @@ var ( // tx flags var ( - nodeFlag = cli.StringFlag{ + NodeFlag = cli.StringFlag{ Name: "node", Value: "tcp://localhost:46657", Usage: "Tendermint RPC address", } - toFlag = cli.StringFlag{ + ToFlag = cli.StringFlag{ Name: "to", Value: "", Usage: "Destination address for the transaction", } - amountFlag = cli.IntFlag{ + AmountFlag = cli.IntFlag{ Name: "amount", Value: 0, Usage: "Amount of coins to send in the transaction", } - fromFlag = cli.StringFlag{ + FromFlag = cli.StringFlag{ Name: "from", Value: "priv_validator.json", Usage: "Path to a private key to sign the transaction", } - seqFlag = cli.IntFlag{ + SeqFlag = cli.IntFlag{ Name: "sequence", Value: 0, Usage: "Sequence number for the account", } - coinFlag = cli.StringFlag{ + CoinFlag = cli.StringFlag{ Name: "coin", Value: "blank", Usage: "Specify a coin denomination", } - gasFlag = cli.IntFlag{ + GasFlag = cli.IntFlag{ Name: "gas", Value: 0, Usage: "The amount of gas for the transaction", } - feeFlag = cli.IntFlag{ + FeeFlag = cli.IntFlag{ Name: "fee", Value: 0, Usage: "The transaction fee", } - dataFlag = cli.StringFlag{ + DataFlag = cli.StringFlag{ Name: "data", Value: "", Usage: "Data to send with the transaction", } - nameFlag = cli.StringFlag{ + NameFlag = cli.StringFlag{ Name: "name", Value: "", Usage: "Plugin to send the transaction to", } - chainIDFlag = cli.StringFlag{ + ChainIDFlag = cli.StringFlag{ Name: "chain_id", Value: "test_chain_id", Usage: "ID of the chain for replay protection", } - validFlag = cli.BoolFlag{ + ValidFlag = cli.BoolFlag{ Name: "valid", Usage: "Set valid field in CounterTx", } @@ -120,73 +120,73 @@ var ( // ibc flags var ( - ibcChainIDFlag = cli.StringFlag{ + IbcChainIDFlag = cli.StringFlag{ Name: "chain_id", Usage: "ChainID for the new blockchain", Value: "", } - ibcGenesisFlag = cli.StringFlag{ + IbcGenesisFlag = cli.StringFlag{ Name: "genesis", Usage: "Genesis file for the new blockchain", Value: "", } - ibcHeaderFlag = cli.StringFlag{ + IbcHeaderFlag = cli.StringFlag{ Name: "header", Usage: "Block header for an ibc update", Value: "", } - ibcCommitFlag = cli.StringFlag{ + IbcCommitFlag = cli.StringFlag{ Name: "commit", Usage: "Block commit for an ibc update", Value: "", } - ibcFromFlag = cli.StringFlag{ + IbcFromFlag = cli.StringFlag{ Name: "from", Usage: "Source ChainID", Value: "", } - ibcToFlag = cli.StringFlag{ + IbcToFlag = cli.StringFlag{ Name: "to", Usage: "Destination ChainID", Value: "", } - ibcTypeFlag = cli.StringFlag{ + IbcTypeFlag = cli.StringFlag{ Name: "type", Usage: "IBC packet type (eg. coin)", Value: "", } - ibcPayloadFlag = cli.StringFlag{ + IbcPayloadFlag = cli.StringFlag{ Name: "payload", Usage: "IBC packet payload", Value: "", } - ibcPacketFlag = cli.StringFlag{ + IbcPacketFlag = cli.StringFlag{ Name: "packet", Usage: "hex-encoded IBC packet", Value: "", } - ibcProofFlag = cli.StringFlag{ + IbcProofFlag = cli.StringFlag{ Name: "proof", Usage: "hex-encoded proof of IBC packet from source chain", Value: "", } - ibcSequenceFlag = cli.IntFlag{ + IbcSequenceFlag = cli.IntFlag{ Name: "sequence", Usage: "sequence number for IBC packet", Value: 0, } - ibcHeightFlag = cli.IntFlag{ + IbcHeightFlag = cli.IntFlag{ Name: "height", Usage: "Height the packet became egress in source chain", Value: 0, @@ -195,25 +195,25 @@ var ( // proof flags var ( - proofFlag = cli.StringFlag{ + ProofFlag = cli.StringFlag{ Name: "proof", Usage: "hex-encoded IAVL proof", Value: "", } - keyFlag = cli.StringFlag{ + KeyFlag = cli.StringFlag{ Name: "key", Usage: "key to the IAVL tree", Value: "", } - valueFlag = cli.StringFlag{ + ValueFlag = cli.StringFlag{ Name: "value", Usage: "value in the IAVL tree", Value: "", } - rootFlag = cli.StringFlag{ + RootFlag = cli.StringFlag{ Name: "root", Usage: "root hash of the IAVL tree", Value: "", diff --git a/cmd/basecoin/commands/ibc.go b/cmd/basecoin/commands/ibc.go index 23511c5151..68ad4feba9 100644 --- a/cmd/basecoin/commands/ibc.go +++ b/cmd/basecoin/commands/ibc.go @@ -21,19 +21,19 @@ var ( Name: "ibc", Usage: "Send a transaction to the interblockchain (ibc) plugin", Flags: []cli.Flag{ - nodeFlag, - chainIDFlag, + NodeFlag, + ChainIDFlag, - fromFlag, + FromFlag, - amountFlag, - coinFlag, - gasFlag, - feeFlag, - seqFlag, + AmountFlag, + CoinFlag, + GasFlag, + FeeFlag, + SeqFlag, - nameFlag, - dataFlag, + NameFlag, + DataFlag, }, Subcommands: []cli.Command{ IbcRegisterTxCmd, @@ -49,8 +49,8 @@ var ( return cmdIBCRegisterTx(c) }, Flags: []cli.Flag{ - ibcChainIDFlag, - ibcGenesisFlag, + IbcChainIDFlag, + IbcGenesisFlag, }, } @@ -61,8 +61,8 @@ var ( return cmdIBCUpdateTx(c) }, Flags: []cli.Flag{ - ibcHeaderFlag, - ibcCommitFlag, + IbcHeaderFlag, + IbcCommitFlag, }, } @@ -85,11 +85,11 @@ var ( return cmdIBCPacketCreateTx(c) }, Flags: []cli.Flag{ - ibcFromFlag, - ibcToFlag, - ibcTypeFlag, - ibcPayloadFlag, - ibcSequenceFlag, + IbcFromFlag, + IbcToFlag, + IbcTypeFlag, + IbcPayloadFlag, + IbcSequenceFlag, }, } @@ -100,10 +100,10 @@ var ( return cmdIBCPacketPostTx(c) }, Flags: []cli.Flag{ - ibcFromFlag, - ibcHeightFlag, - ibcPacketFlag, - ibcProofFlag, + IbcFromFlag, + IbcHeightFlag, + IbcPacketFlag, + IbcProofFlag, }, } ) diff --git a/cmd/basecoin/commands/query.go b/cmd/basecoin/commands/query.go index a2b22a0f37..3c12e04107 100644 --- a/cmd/basecoin/commands/query.go +++ b/cmd/basecoin/commands/query.go @@ -23,7 +23,7 @@ var ( return cmdQuery(c) }, Flags: []cli.Flag{ - nodeFlag, + NodeFlag, }, } @@ -35,7 +35,7 @@ var ( return cmdAccount(c) }, Flags: []cli.Flag{ - nodeFlag, + NodeFlag, }, } @@ -47,7 +47,7 @@ var ( return cmdBlock(c) }, Flags: []cli.Flag{ - nodeFlag, + NodeFlag, }, } @@ -58,10 +58,10 @@ var ( return cmdVerify(c) }, Flags: []cli.Flag{ - proofFlag, - keyFlag, - valueFlag, - rootFlag, + ProofFlag, + KeyFlag, + ValueFlag, + RootFlag, }, } ) diff --git a/cmd/basecoin/commands/start.go b/cmd/basecoin/commands/start.go index f442557a7a..52238f797e 100644 --- a/cmd/basecoin/commands/start.go +++ b/cmd/basecoin/commands/start.go @@ -35,13 +35,13 @@ var StartCmd = cli.Command{ return cmdStart(c) }, Flags: []cli.Flag{ - addrFlag, - eyesFlag, - dirFlag, - inProcTMFlag, - chainIDFlag, - ibcPluginFlag, - counterPluginFlag, + AddrFlag, + EyesFlag, + DirFlag, + InProcTMFlag, + ChainIDFlag, + IbcPluginFlag, + CounterPluginFlag, }, } diff --git a/cmd/basecoin/commands/tx.go b/cmd/basecoin/commands/tx.go index 5c37ecaee2..239d2c842d 100644 --- a/cmd/basecoin/commands/tx.go +++ b/cmd/basecoin/commands/tx.go @@ -26,18 +26,18 @@ var ( return cmdSendTx(c) }, Flags: []cli.Flag{ - nodeFlag, - chainIDFlag, + NodeFlag, + ChainIDFlag, - fromFlag, + FromFlag, - amountFlag, - coinFlag, - gasFlag, - feeFlag, - seqFlag, + AmountFlag, + CoinFlag, + GasFlag, + FeeFlag, + SeqFlag, - toFlag, + ToFlag, }, } @@ -49,19 +49,19 @@ var ( return cmdAppTx(c) }, Flags: []cli.Flag{ - nodeFlag, - chainIDFlag, + NodeFlag, + ChainIDFlag, - fromFlag, + FromFlag, - amountFlag, - coinFlag, - gasFlag, - feeFlag, - seqFlag, + AmountFlag, + CoinFlag, + GasFlag, + FeeFlag, + SeqFlag, - nameFlag, - dataFlag, + NameFlag, + DataFlag, }, Subcommands: []cli.Command{ CounterTxCmd, @@ -75,7 +75,7 @@ var ( return cmdCounterTx(c) }, Flags: []cli.Flag{ - validFlag, + ValidFlag, }, } ) From 3fbd282f2ed2c1091750ada28f9ace52e4a0e8c6 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 30 Jan 2017 15:56:47 +0100 Subject: [PATCH 34/64] Allow registering plugin subcommand to apptx --- cmd/basecoin/commands/counter.go | 49 ++++++++++++++++++++++++++++++++ cmd/basecoin/commands/ibc.go | 8 +++--- cmd/basecoin/commands/tx.go | 49 +++++++------------------------- 3 files changed, 63 insertions(+), 43 deletions(-) create mode 100644 cmd/basecoin/commands/counter.go diff --git a/cmd/basecoin/commands/counter.go b/cmd/basecoin/commands/counter.go new file mode 100644 index 0000000000..d05d08c424 --- /dev/null +++ b/cmd/basecoin/commands/counter.go @@ -0,0 +1,49 @@ +package commands + +import ( + "fmt" + + "github.com/tendermint/basecoin/plugins/counter" + "github.com/tendermint/basecoin/types" + wire "github.com/tendermint/go-wire" + "github.com/urfave/cli" +) + +var ( + CounterTxCmd = cli.Command{ + Name: "counter", + Usage: "Craft a transaction to the counter plugin", + Action: func(c *cli.Context) error { + return cmdCounterTx(c) + }, + Flags: []cli.Flag{ + ValidFlag, + }, + } +) + +func init() { + RegisterPlugin(CounterTxCmd) +} + +func cmdCounterTx(c *cli.Context) error { + valid := c.Bool("valid") + parent := c.Parent() + + counterTx := counter.CounterTx{ + Valid: valid, + Fee: types.Coins{ + { + Denom: parent.String("coin"), + Amount: int64(parent.Int("fee")), + }, + }, + } + + fmt.Println("CounterTx:", string(wire.JSONBytes(counterTx))) + + data := wire.BinaryBytes(counterTx) + name := "counter" + + return AppTx(parent, name, data) +} diff --git a/cmd/basecoin/commands/ibc.go b/cmd/basecoin/commands/ibc.go index 68ad4feba9..bdb7ccc0e7 100644 --- a/cmd/basecoin/commands/ibc.go +++ b/cmd/basecoin/commands/ibc.go @@ -132,7 +132,7 @@ func cmdIBCRegisterTx(c *cli.Context) error { }{ibcTx})) name := "IBC" - return appTx(parent, name, data) + return AppTx(parent, name, data) } func cmdIBCUpdateTx(c *cli.Context) error { @@ -167,7 +167,7 @@ func cmdIBCUpdateTx(c *cli.Context) error { }{ibcTx})) name := "IBC" - return appTx(c.Parent(), name, data) + return AppTx(c.Parent(), name, data) } func cmdIBCPacketCreateTx(c *cli.Context) error { @@ -200,7 +200,7 @@ func cmdIBCPacketCreateTx(c *cli.Context) error { ibc.IBCTx `json:"unwrap"` }{ibcTx})) - return appTx(c.Parent().Parent(), "IBC", data) + return AppTx(c.Parent().Parent(), "IBC", data) } func cmdIBCPacketPostTx(c *cli.Context) error { @@ -238,7 +238,7 @@ func cmdIBCPacketPostTx(c *cli.Context) error { ibc.IBCTx `json:"unwrap"` }{ibcTx})) - return appTx(c.Parent().Parent(), "IBC", data) + return AppTx(c.Parent().Parent(), "IBC", data) } func getIBCSequence(c *cli.Context) (uint64, error) { diff --git a/cmd/basecoin/commands/tx.go b/cmd/basecoin/commands/tx.go index 239d2c842d..745c450323 100644 --- a/cmd/basecoin/commands/tx.go +++ b/cmd/basecoin/commands/tx.go @@ -7,7 +7,6 @@ import ( "github.com/urfave/cli" - "github.com/tendermint/basecoin/plugins/counter" "github.com/tendermint/basecoin/types" cmn "github.com/tendermint/go-common" @@ -63,23 +62,17 @@ var ( NameFlag, DataFlag, }, - Subcommands: []cli.Command{ - CounterTxCmd, - }, - } - - CounterTxCmd = cli.Command{ - Name: "counter", - Usage: "Craft a transaction to the counter plugin", - Action: func(c *cli.Context) error { - return cmdCounterTx(c) - }, - Flags: []cli.Flag{ - ValidFlag, - }, + // Subcommands are dynamically registered with plugins as needed + Subcommands: []cli.Command{}, } ) +// RegisterPlugin is used to add another subcommand and create a custom +// apptx encoding. Look at counter.go for an example +func RegisterPlugin(cmd cli.Command) { + AppTxCmd.Subcommands = append(AppTxCmd.Subcommands, cmd) +} + func cmdSendTx(c *cli.Context) error { toHex := c.String("to") fromFile := c.String("from") @@ -136,10 +129,10 @@ func cmdAppTx(c *cli.Context) error { data, _ = hex.DecodeString(dataString) } name := c.String("name") - return appTx(c, name, data) + return AppTx(c, name, data) } -func appTx(c *cli.Context, name string, data []byte) error { +func AppTx(c *cli.Context, name string, data []byte) error { fromFile := c.String("from") amount := int64(c.Int("amount")) coin := c.String("coin") @@ -174,28 +167,6 @@ func appTx(c *cli.Context, name string, data []byte) error { return nil } -func cmdCounterTx(c *cli.Context) error { - valid := c.Bool("valid") - parent := c.Parent() - - counterTx := counter.CounterTx{ - Valid: valid, - Fee: types.Coins{ - { - Denom: parent.String("coin"), - Amount: int64(parent.Int("fee")), - }, - }, - } - - fmt.Println("CounterTx:", string(wire.JSONBytes(counterTx))) - - data := wire.BinaryBytes(counterTx) - name := "counter" - - return appTx(parent, name, data) -} - // broadcast the transaction to tendermint func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, error) { tmResult := new(ctypes.TMResult) From 53786ab4ce1fbe36d09cdedc82808b91b772cc3e Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 30 Jan 2017 15:57:37 +0100 Subject: [PATCH 35/64] Remove obsolete paytovote cmd --- cmd/paytovote/main.go | 53 ------------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 cmd/paytovote/main.go diff --git a/cmd/paytovote/main.go b/cmd/paytovote/main.go deleted file mode 100644 index 7c7715d992..0000000000 --- a/cmd/paytovote/main.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "flag" - - "github.com/tendermint/abci/server" - "github.com/tendermint/basecoin/app" - "github.com/tendermint/basecoin/plugins/counter" - cmn "github.com/tendermint/go-common" - eyes "github.com/tendermint/merkleeyes/client" -) - -func main() { - addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address") - eyesPtr := flag.String("eyes", "local", "MerkleEyes address, or 'local' for embedded") - genFilePath := flag.String("genesis", "", "Genesis file, if any") - flag.Parse() - - // Connect to MerkleEyes - eyesCli, err := eyes.NewClient(*eyesPtr) - if err != nil { - cmn.Exit("connect to MerkleEyes: " + err.Error()) - } - - // Create Basecoin app - app := app.NewBasecoin(eyesCli) - - // add plugins - // TODO: add some more, like the cool voting app - counter := counter.New("counter") - app.RegisterPlugin(counter) - - // If genesis file was specified, set key-value options - if *genFilePath != "" { - err := app.LoadGenesis(*genFilePath) - if err != nil { - cmn.Exit(cmn.Fmt("%+v", err)) - } - } - - // Start the listener - svr, err := server.NewServer(*addrPtr, "socket", app) - if err != nil { - cmn.Exit("create listener: " + err.Error()) - } - - // Wait forever - cmn.TrapSignal(func() { - // Cleanup - svr.Stop() - }) - -} From 15904ea7a72ab5288db8e7b6dc6b50d033f54685 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 30 Jan 2017 17:16:00 +0100 Subject: [PATCH 36/64] cmd start allows plugin registration --- cmd/basecoin/commands/counter.go | 9 ++++++++- cmd/basecoin/commands/flags.go | 5 ----- cmd/basecoin/commands/ibc.go | 10 +++++----- cmd/basecoin/commands/query.go | 12 ++++++------ cmd/basecoin/commands/start.go | 28 +++++++++++++++++++++------- cmd/basecoin/commands/tx.go | 6 +++--- cmd/basecoin/commands/utils.go | 2 +- 7 files changed, 44 insertions(+), 28 deletions(-) diff --git a/cmd/basecoin/commands/counter.go b/cmd/basecoin/commands/counter.go index d05d08c424..83f98218cf 100644 --- a/cmd/basecoin/commands/counter.go +++ b/cmd/basecoin/commands/counter.go @@ -20,10 +20,17 @@ var ( ValidFlag, }, } + + CounterPluginFlag = cli.BoolFlag{ + Name: "counter-plugin", + Usage: "Enable the counter plugin", + } ) func init() { - RegisterPlugin(CounterTxCmd) + RegisterTxPlugin(CounterTxCmd) + RegisterStartPlugin(CounterPluginFlag, + func() types.Plugin { return counter.New("counter") }) } func cmdCounterTx(c *cli.Context) error { diff --git a/cmd/basecoin/commands/flags.go b/cmd/basecoin/commands/flags.go index 6bbf69e7bb..0ca74962ce 100644 --- a/cmd/basecoin/commands/flags.go +++ b/cmd/basecoin/commands/flags.go @@ -36,11 +36,6 @@ var ( Name: "ibc-plugin", Usage: "Enable the ibc plugin", } - - CounterPluginFlag = cli.BoolFlag{ - Name: "counter-plugin", - Usage: "Enable the counter plugin", - } ) // tx flags diff --git a/cmd/basecoin/commands/ibc.go b/cmd/basecoin/commands/ibc.go index bdb7ccc0e7..39f5aef8d4 100644 --- a/cmd/basecoin/commands/ibc.go +++ b/cmd/basecoin/commands/ibc.go @@ -136,11 +136,11 @@ func cmdIBCRegisterTx(c *cli.Context) error { } func cmdIBCUpdateTx(c *cli.Context) error { - headerBytes, err := hex.DecodeString(stripHex(c.String("header"))) + headerBytes, err := hex.DecodeString(StripHex(c.String("header"))) if err != nil { return errors.New(cmn.Fmt("Header (%v) is invalid hex: %v", c.String("header"), err)) } - commitBytes, err := hex.DecodeString(stripHex(c.String("commit"))) + commitBytes, err := hex.DecodeString(StripHex(c.String("commit"))) if err != nil { return errors.New(cmn.Fmt("Commit (%v) is invalid hex: %v", c.String("commit"), err)) } @@ -174,7 +174,7 @@ func cmdIBCPacketCreateTx(c *cli.Context) error { fromChain, toChain := c.String("from"), c.String("to") packetType := c.String("type") - payloadBytes, err := hex.DecodeString(stripHex(c.String("payload"))) + payloadBytes, err := hex.DecodeString(StripHex(c.String("payload"))) if err != nil { return errors.New(cmn.Fmt("Payload (%v) is invalid hex: %v", c.String("payload"), err)) } @@ -206,11 +206,11 @@ func cmdIBCPacketCreateTx(c *cli.Context) error { func cmdIBCPacketPostTx(c *cli.Context) error { fromChain, fromHeight := c.String("from"), c.Int("height") - packetBytes, err := hex.DecodeString(stripHex(c.String("packet"))) + packetBytes, err := hex.DecodeString(StripHex(c.String("packet"))) if err != nil { return errors.New(cmn.Fmt("Packet (%v) is invalid hex: %v", c.String("packet"), err)) } - proofBytes, err := hex.DecodeString(stripHex(c.String("proof"))) + proofBytes, err := hex.DecodeString(StripHex(c.String("proof"))) if err != nil { return errors.New(cmn.Fmt("Proof (%v) is invalid hex: %v", c.String("proof"), err)) } diff --git a/cmd/basecoin/commands/query.go b/cmd/basecoin/commands/query.go index 3c12e04107..4237075267 100644 --- a/cmd/basecoin/commands/query.go +++ b/cmd/basecoin/commands/query.go @@ -75,7 +75,7 @@ func cmdQuery(c *cli.Context) error { if isHex(keyString) { // convert key to bytes var err error - key, err = hex.DecodeString(stripHex(keyString)) + key, err = hex.DecodeString(StripHex(keyString)) if err != nil { return errors.New(cmn.Fmt("Query key (%v) is invalid hex: %v", keyString, err)) } @@ -107,7 +107,7 @@ func cmdAccount(c *cli.Context) error { if len(c.Args()) != 1 { return errors.New("account command requires an argument ([address])") } - addrHex := stripHex(c.Args()[0]) + addrHex := StripHex(c.Args()[0]) // convert destination address to bytes addr, err := hex.DecodeString(addrHex) @@ -175,7 +175,7 @@ func cmdVerify(c *cli.Context) error { var err error key := []byte(keyString) if isHex(keyString) { - key, err = hex.DecodeString(stripHex(keyString)) + key, err = hex.DecodeString(StripHex(keyString)) if err != nil { return errors.New(cmn.Fmt("Key (%v) is invalid hex: %v", keyString, err)) } @@ -183,18 +183,18 @@ func cmdVerify(c *cli.Context) error { value := []byte(valueString) if isHex(valueString) { - value, err = hex.DecodeString(stripHex(valueString)) + value, err = hex.DecodeString(StripHex(valueString)) if err != nil { return errors.New(cmn.Fmt("Value (%v) is invalid hex: %v", valueString, err)) } } - root, err := hex.DecodeString(stripHex(c.String("root"))) + root, err := hex.DecodeString(StripHex(c.String("root"))) if err != nil { return errors.New(cmn.Fmt("Root (%v) is invalid hex: %v", c.String("root"), err)) } - proofBytes, err := hex.DecodeString(stripHex(c.String("proof"))) + proofBytes, err := hex.DecodeString(StripHex(c.String("proof"))) if err != nil { return errors.New(cmn.Fmt("Proof (%v) is invalid hex: %v", c.String("proof"), err)) } diff --git a/cmd/basecoin/commands/start.go b/cmd/basecoin/commands/start.go index 52238f797e..90e701afd8 100644 --- a/cmd/basecoin/commands/start.go +++ b/cmd/basecoin/commands/start.go @@ -19,8 +19,8 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/basecoin/app" - "github.com/tendermint/basecoin/plugins/counter" "github.com/tendermint/basecoin/plugins/ibc" + "github.com/tendermint/basecoin/types" ) var config cfg.Config @@ -41,10 +41,23 @@ var StartCmd = cli.Command{ InProcTMFlag, ChainIDFlag, IbcPluginFlag, - CounterPluginFlag, + // CounterPluginFlag, }, } +type plugin struct { + name string + init func() types.Plugin +} + +var plugins = []plugin{} + +// RegisterStartPlugin is used to add another +func RegisterStartPlugin(flag cli.BoolFlag, init func() types.Plugin) { + StartCmd.Flags = append(StartCmd.Flags, flag) + plugins = append(plugins, plugin{name: flag.GetName(), init: init}) +} + func cmdStart(c *cli.Context) error { // Connect to MerkleEyes @@ -61,14 +74,15 @@ func cmdStart(c *cli.Context) error { // Create Basecoin app basecoinApp := app.NewBasecoin(eyesCli) - - if c.Bool("counter-plugin") { - basecoinApp.RegisterPlugin(counter.New("counter")) - } - if c.Bool("ibc-plugin") { basecoinApp.RegisterPlugin(ibc.New()) + } + // loop through all registered plugins and enable if desired + for _, p := range plugins { + if c.Bool(p.name) { + basecoinApp.RegisterPlugin(p.init()) + } } // If genesis file exists, set key-value options diff --git a/cmd/basecoin/commands/tx.go b/cmd/basecoin/commands/tx.go index 745c450323..43d36c81d7 100644 --- a/cmd/basecoin/commands/tx.go +++ b/cmd/basecoin/commands/tx.go @@ -67,9 +67,9 @@ var ( } ) -// RegisterPlugin is used to add another subcommand and create a custom +// RegisterTxPlugin is used to add another subcommand and create a custom // apptx encoding. Look at counter.go for an example -func RegisterPlugin(cmd cli.Command) { +func RegisterTxPlugin(cmd cli.Command) { AppTxCmd.Subcommands = append(AppTxCmd.Subcommands, cmd) } @@ -82,7 +82,7 @@ func cmdSendTx(c *cli.Context) error { chainID := c.String("chain_id") // convert destination address to bytes - to, err := hex.DecodeString(stripHex(toHex)) + to, err := hex.DecodeString(StripHex(toHex)) if err != nil { return errors.New("To address is invalid hex: " + err.Error()) } diff --git a/cmd/basecoin/commands/utils.go b/cmd/basecoin/commands/utils.go index db9f2821c4..04ac83ae26 100644 --- a/cmd/basecoin/commands/utils.go +++ b/cmd/basecoin/commands/utils.go @@ -28,7 +28,7 @@ func isHex(s string) bool { return false } -func stripHex(s string) string { +func StripHex(s string) string { if isHex(s) { return s[2:] } From 9e9a098d931d41a9025077f97239a09a346d17dc Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 30 Jan 2017 18:26:59 +0100 Subject: [PATCH 37/64] Added Bucky's plugin docs into it's own md file --- Plugins.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 30 +++--------------- 2 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 Plugins.md diff --git a/Plugins.md b/Plugins.md new file mode 100644 index 0000000000..7562aceacc --- /dev/null +++ b/Plugins.md @@ -0,0 +1,93 @@ +# Basecoin Plugins + +Basecoin is an extensible cryptocurrency module. +Each Basecoin account contains a ED25519 public key, +a balance in many different coin denominations, +and a strictly increasing sequence number for replay protection (like in Ethereum). +Accounts are serialized and stored in a merkle tree using the account's address as the key, +where the address is the RIPEMD160 hash of the public key. + +Sending tokens around is done via the `SendTx`, which takes a list of inputs and a list of outputs, +and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output. +The `SendTx` is structured as follows: + +``` +type SendTx struct { + Gas int64 `json:"gas"` // Gas + Fee Coin `json:"fee"` // Fee + Inputs []TxInput `json:"inputs"` + Outputs []TxOutput `json:"outputs"` +} + +type TxInput struct { + Address []byte `json:"address"` // Hash of the PubKey + Coins Coins `json:"coins"` // + Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput + Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx + PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 +} + +type TxOutput struct { + Address []byte `json:"address"` // Hash of the PubKey + Coins Coins `json:"coins"` // +} + +type Coins []Coin + +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} + +``` + +Note it also includes a field for `Gas` and `Fee`. The `Gas` limits the total amount of computation that can be done by the transaction, +while the `Fee` refers to the total amount paid in fees. This is slightly different from Ethereum's concept of `Gas` and `GasPrice`, +where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent. + + +Basecoin also defines another transaction type, the `AppTx`: + +``` +type AppTx struct { + Gas int64 `json:"gas"` // Gas + Fee Coin `json:"fee"` // Fee + Name string `json:"type"` // Which plugin + Input TxInput `json:"input"` + Data []byte `json:"data"` +} +``` + +The `AppTx` enables arbitrary additional functionality through the use of plugins. +A plugin is simply a Go package that implements the `Plugin` interface: + +``` +type Plugin interface { + + // Name of this plugin, should be short. + Name() string + + // Run a transaction from ABCI DeliverTx + RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) + + // Other ABCI message handlers + SetOption(store KVStore, key string, value string) (log string) + InitChain(store KVStore, vals []*abci.Validator) + BeginBlock(store KVStore, height uint64) + EndBlock(store KVStore, height uint64) []*abci.Validator +} + +type CallContext struct { + CallerAddress []byte // Caller's Address (hash of PubKey) + CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted + Coins Coins // The coins that the caller wishes to spend, excluding fees +} +``` + +The workhorse of the plugin is `RunTx`, which is called when an `AppTx` is processed. +The `Name` field in the `AppTx` refers to the plugin name, and the `Data` field of the `AppTx` is +forward to the `RunTx` function. + +You can look at some example plugins in the [basecoin repo](https://github.com/tendermint/basecoin/tree/develop/plugins). + +If you want to see how you can write a plugin in your own repo, and make use of all the basecoin tooling, cli, etc. please take a look at the [mintcoin example](https://github.com/tendermint/basecoin-examples/tree/master/mintcoin) for inspiration, not just the plugin itself, but also the `cmd/mintcoin` directory to create the custom command. diff --git a/README.md b/README.md index f34af746dd..816761505f 100644 --- a/README.md +++ b/README.md @@ -31,32 +31,12 @@ This will create the `basecoin` binary in `$GOPATH/bin`. ## Using the Plugin System Basecoin is designed to serve as a common base layer for developers building cryptocurrency applications. -It handles public-key authentication of transactions, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), -sending currency (one-to-one or n-to-m multisig), and providing merkle-proofs of the state. -These are common factors that many people wish to have in a crypto-currency system, -so instead of trying to start from scratch, developers can extend the functionality of Basecoin using the plugin system! +It handles public-key authentication of transactions, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), +sending currency (one-to-one or n-to-m multisig), and providing merkle-proofs of the state. +These are common factors that many people wish to have in a crypto-currency system, +so instead of trying to start from scratch, developers can extend the functionality of Basecoin using the plugin system, just writing the custom business logic they need, and leaving the rest to the basecoin system. -The Plugin interface is defined in `types/plugin.go`: - -``` -type Plugin interface { - Name() string - SetOption(store KVStore, key string, value string) (log string) - RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) - InitChain(store KVStore, vals []*abci.Validator) - BeginBlock(store KVStore, height uint64) - EndBlock(store KVStore, height uint64) []*abci.Validator -} -``` - -`RunTx` is where you can handle any special transactions directed to your application. -To see a very simple implementation, look at the demo [counter plugin](./plugins/counter/counter.go). -If you want to create your own currency using a plugin, you don't have to fork basecoin at all. -Just make your own repo, add the implementation of your custom plugin, and then build your own main script that instatiates Basecoin and registers your plugin. - -An example is worth a 1000 words, so please take a look [at this example](https://github.com/tendermint/basecoin/blob/develop/cmd/paytovote/main.go#L25-L31). -Note for now it is in a dev branch. -You can use the same technique in your own repo. +Interested in building a plugin? Then [read more details here](./Plugins.md) ## Using the CLI From 23dad6d0e3226ef2a6ca2b430d00e8c3a7740635 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 30 Jan 2017 18:47:30 +0100 Subject: [PATCH 38/64] Add links to more info in basecoin-examples --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 816761505f..7920f6dd0b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ sending currency (one-to-one or n-to-m multisig), and providing merkle-proofs of These are common factors that many people wish to have in a crypto-currency system, so instead of trying to start from scratch, developers can extend the functionality of Basecoin using the plugin system, just writing the custom business logic they need, and leaving the rest to the basecoin system. -Interested in building a plugin? Then [read more details here](./Plugins.md) +Interested in building a plugin? Then [read more details here](./Plugins.md) and then you can follow a [simple tutorial](https://github.com/tendermint/basecoin-examples/blob/master/pluginDev/tutorial.md) to get your first plugin working. ## Using the CLI @@ -45,6 +45,8 @@ or to start basecoin with tendermint in the same process (`basecoin start --in-p It can also be used to send transactions, eg. `basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` See `basecoin --help` and `basecoin [cmd] --help` for more details`. +Or follow through a [step-by-step introduction](https://github.com/tendermint/basecoin-examples/blob/master/tutorial.md) to testing basecoin locally. + ## Tutorials and Other Reading See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. From 6e3a199f0921509f86da2a508e39e95a1158bf2b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 30 Jan 2017 19:11:44 +0100 Subject: [PATCH 39/64] Fix sendtx to not panic on error --- .gitignore | 1 + cmd/basecoin/commands/start.go | 1 - cmd/basecoin/commands/tx.go | 5 +++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fbf50087e3..100b0cae2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.swp vendor +merkleeyes.db diff --git a/cmd/basecoin/commands/start.go b/cmd/basecoin/commands/start.go index 90e701afd8..225619399f 100644 --- a/cmd/basecoin/commands/start.go +++ b/cmd/basecoin/commands/start.go @@ -41,7 +41,6 @@ var StartCmd = cli.Command{ InProcTMFlag, ChainIDFlag, IbcPluginFlag, - // CounterPluginFlag, }, } diff --git a/cmd/basecoin/commands/tx.go b/cmd/basecoin/commands/tx.go index 43d36c81d7..2e7d3028dc 100644 --- a/cmd/basecoin/commands/tx.go +++ b/cmd/basecoin/commands/tx.go @@ -183,6 +183,11 @@ func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, error) { return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err)) } res := (*tmResult).(*ctypes.ResultBroadcastTxCommit) + // if it fails check, we don't even get a delivertx back! + if !res.CheckTx.Code.IsOK() { + r := res.CheckTx + return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)) + } if !res.DeliverTx.Code.IsOK() { r := res.DeliverTx return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)) From 2f7875dec03201de8121dd93c225078f282015ab Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 31 Jan 2017 12:24:49 +0100 Subject: [PATCH 40/64] Fixed Coins IsValid, issue #8 --- types/coin.go | 2 ++ types/coin_test.go | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/types/coin.go b/types/coin.go index 94e38b7011..23dc8f0868 100644 --- a/types/coin.go +++ b/types/coin.go @@ -35,6 +35,8 @@ func (coins Coins) IsValid() bool { if coin.Amount == 0 { return false } + // we compare each coin against the last denom + lowDenom = coin.Denom } return true } diff --git a/types/coin_test.go b/types/coin_test.go index 00a07a6f36..158f159c50 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -42,6 +42,19 @@ func TestCoinsBadSort(t *testing.T) { } } +func TestCoinsBadSort2(t *testing.T) { + // both are after the first one, but the second and third are in the wrong order + coins := Coins{ + Coin{"GAS", 1}, + Coin{"TREE", 1}, + Coin{"MINERAL", 1}, + } + + if coins.IsValid() { + t.Fatal("Coins are not sorted") + } +} + func TestCoinsBadAmount(t *testing.T) { coins := Coins{ Coin{"GAS", 1}, From 63612f820466c02b7a8172ac11640ca04ae01f87 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 31 Jan 2017 15:33:34 +0100 Subject: [PATCH 41/64] Expose Query globally, to use from other packages --- cmd/basecoin/commands/query.go | 2 +- cmd/basecoin/commands/utils.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/basecoin/commands/query.go b/cmd/basecoin/commands/query.go index 4237075267..41635e2882 100644 --- a/cmd/basecoin/commands/query.go +++ b/cmd/basecoin/commands/query.go @@ -81,7 +81,7 @@ func cmdQuery(c *cli.Context) error { } } - resp, err := query(c.String("node"), key) + resp, err := Query(c.String("node"), key) if err != nil { return err } diff --git a/cmd/basecoin/commands/utils.go b/cmd/basecoin/commands/utils.go index 04ac83ae26..92a805734a 100644 --- a/cmd/basecoin/commands/utils.go +++ b/cmd/basecoin/commands/utils.go @@ -35,7 +35,7 @@ func StripHex(s string) string { return s } -func query(tmAddr string, key []byte) (*abci.ResponseQuery, error) { +func Query(tmAddr string, key []byte) (*abci.ResponseQuery, error) { clientURI := client.NewClientURI(tmAddr) tmResult := new(ctypes.TMResult) @@ -59,7 +59,7 @@ func query(tmAddr string, key []byte) (*abci.ResponseQuery, error) { func getAcc(tmAddr string, address []byte) (*types.Account, error) { key := append([]byte("base/a/"), address...) - response, err := query(tmAddr, key) + response, err := Query(tmAddr, key) if err != nil { return nil, err } From 215c377fae00a004a8edd3ee71a643ed0c3dc377 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 31 Jan 2017 17:00:23 +0100 Subject: [PATCH 42/64] Show result from AppTx --- cmd/basecoin/commands/tx.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/basecoin/commands/tx.go b/cmd/basecoin/commands/tx.go index 2e7d3028dc..e68c3754d1 100644 --- a/cmd/basecoin/commands/tx.go +++ b/cmd/basecoin/commands/tx.go @@ -160,9 +160,11 @@ func AppTx(c *cli.Context, name string, data []byte) error { fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) - if _, err := broadcastTx(c, tx); err != nil { + res, err := broadcastTx(c, tx) + if err != nil { return err } + fmt.Printf("Response: %X\n", res) return nil } From 66697774dd8e5194169c7eec8db439c29e33799f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 2 Feb 2017 21:26:46 +0100 Subject: [PATCH 43/64] Actually return result on successful tx --- app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app.go b/app/app.go index a6ad90424d..cbf85c123a 100644 --- a/app/app.go +++ b/app/app.go @@ -94,7 +94,7 @@ func (app *Basecoin) DeliverTx(txBytes []byte) (res abci.Result) { if res.IsErr() { return res.PrependLog("Error in DeliverTx") } - return abci.OK + return res } // TMSP::CheckTx From a76b453d199063ee075ffebf10bedb24d969de60 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 3 Feb 2017 20:01:43 +0100 Subject: [PATCH 44/64] Clarified README using basecoin as framework --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7920f6dd0b..f67075ca9b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) desi 1. As an example for anyone wishing to build a custom application using tendermint. 2. As a framework for anyone wishing to build a tendermint-based currency, extensible using the plugin system. +If you wish to use basecoin as a framework to build your application, you most likely do not need to fork basecoin or modify it in any way. In fact, even the cli tool is designed to be easily extended by third party repos with almost no copying of code. You just need to add basecoin as a dependency in the `vendor` dir and take a look at [some examples](https://github.com/tendermint/basecoin-examples/blob/master/README.md) of how to customize it without modifying the code. + ## Contents 1. [Installation](#installation) @@ -51,13 +53,15 @@ Or follow through a [step-by-step introduction](https://github.com/tendermint/ba See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. -We are working on some tutorials that will show you how to set up the genesis block, build a plugin to add custom logic, deploy to a tendermint testnet, and connect a UI to your blockchain. They should be published during the course of February 2017, so stay tuned.... +There are a [number of examples](https://github.com/tendermint/basecoin-examples/blob/master/README.md) along with some tutorials and introductory texts, that should give you some pointers on how to wirte you own plugins and integrate them into your own custom app. + +We are working on extending these examples, as well as documenting (and automating) setting up a testnet, and providing an example GUI for viewing basecoin, which can all be used as a starting point for your application. They should be published during the course of February 2017, so stay tuned.... ## Contributing We will merge in interesting plugin implementations and improvements to Basecoin. -If you don't have much experience forking in go, there are a few tricks you want to keep in mind to avoid headaches. Basically, all imports in go are absolute from GOPATH, so if you fork a repo with more than one directory, and you put it under github.com/MYNAME/repo, all the code will start caling github.com/ORIGINAL/repo, which is very confusing. My prefered solution to this is as follows: +If you don't have much experience forking in go, there are a few tricks you want to keep in mind to avoid headaches. Basically, all imports in go are absolute from GOPATH, so if you fork a repo with more than one directory, and you put it under github.com/MYNAME/repo, all the code will start calling github.com/ORIGINAL/repo, which is very confusing. My preferred solution to this is as follows: * Create your own fork on github, using the fork button. * Go to the original repo checked out locally (from `go get`) From a3f9a5338e63d22a32b159ddad3b8ae6a405b631 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 3 Feb 2017 20:39:35 +0100 Subject: [PATCH 45/64] Add link to "good practices" in main README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f67075ca9b..a44a40b976 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ so instead of trying to start from scratch, developers can extend the functional Interested in building a plugin? Then [read more details here](./Plugins.md) and then you can follow a [simple tutorial](https://github.com/tendermint/basecoin-examples/blob/master/pluginDev/tutorial.md) to get your first plugin working. +### Best Practices + +We are still trying out sort out the best practices for basecoin plugins, and ABCi apps in general. Flexibility is very powerful once one has mastered a system, but when starting out, it is nice to have a set of guidelines to follow (and then expand beyond when no longer needed). I have attempted to gather some [good design practices](https://github.com/tendermint/basecoin-examples/tree/master/trader#code-design) I have discovered/invented while building progress. These are not hard rules, but should give you a good start. And please give feedback to improve and extend them. + ## Using the CLI The basecoin cli can be used to start a stand-alone basecoin instance (`basecoin start`), From 5c9d63c6e0ecb462aaa4c5a3257f681cb7049161 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 3 Feb 2017 16:06:12 -0500 Subject: [PATCH 46/64] app.GetState() for testing --- app/app.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/app.go b/app/app.go index cbf85c123a..56f4447cd4 100644 --- a/app/app.go +++ b/app/app.go @@ -37,6 +37,11 @@ func NewBasecoin(eyesCli *eyes.Client) *Basecoin { } } +// For testing, not thread safe! +func (app *Basecoin) GetState() *sm.State { + return app.state.CacheWrap() +} + // TMSP::Info func (app *Basecoin) Info() abci.ResponseInfo { return abci.ResponseInfo{Data: Fmt("Basecoin v%v", version)} From 8fda43384745200b9cabe3c17ce486575a38e957 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 3 Feb 2017 21:17:11 -0500 Subject: [PATCH 47/64] major docs upgrade --- README.md | 68 ++--- docs/guide/basecoin-basics.md | 145 +++++++++++ docs/guide/basecoin-design.md | 0 docs/guide/deployment.md | 7 + docs/guide/example-counter.md | 1 + docs/guide/ibc.md | 288 ++++++++++++++++++++++ docs/guide/install.md | 13 + docs/guide/more-examples.md | 16 ++ Plugins.md => docs/guide/plugin-design.md | 43 ---- 9 files changed, 488 insertions(+), 93 deletions(-) create mode 100644 docs/guide/basecoin-basics.md create mode 100644 docs/guide/basecoin-design.md create mode 100644 docs/guide/deployment.md create mode 100644 docs/guide/example-counter.md create mode 100644 docs/guide/ibc.md create mode 100644 docs/guide/install.md create mode 100644 docs/guide/more-examples.md rename Plugins.md => docs/guide/plugin-design.md (51%) diff --git a/README.md b/README.md index a44a40b976..6c5e917cd2 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,16 @@ DISCLAIMER: Basecoin is not associated with Coinbase.com, an excellent Bitcoin/Ethereum service. -Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. This project has two main purposes: +Basecoin is an [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. +It also provides a general purpose framework for extending the feature-set of the cryptocurrency +by implementing plugins. - 1. As an example for anyone wishing to build a custom application using tendermint. - 2. As a framework for anyone wishing to build a tendermint-based currency, extensible using the plugin system. +Basecoin serves as a reference implementation for how we build ABCI applications in Go, +and is the framework in which we implement the [Cosmos Hub](https://cosmos.network). +It's easy to use, and doesn't require any forking - just implement your plugin, import the basecoin libraries, +and away you go with a full-stack blockchain and command line tool for transacting. -If you wish to use basecoin as a framework to build your application, you most likely do not need to fork basecoin or modify it in any way. In fact, even the cli tool is designed to be easily extended by third party repos with almost no copying of code. You just need to add basecoin as a dependency in the `vendor` dir and take a look at [some examples](https://github.com/tendermint/basecoin-examples/blob/master/README.md) of how to customize it without modifying the code. - -## Contents - - 1. [Installation](#installation) - 1. [Using the plugin system](#using-the-plugin-system) - 1. [Using the cli](#using-the-cli) - 1. [Tutorials and other reading](#tutorials-and-other-reading) - 1. [Contributing](#contributing) +WARNING: Currently uses plain-text private keys for transactions and is otherwise not production ready. ## Installation @@ -30,49 +26,21 @@ make install This will create the `basecoin` binary in `$GOPATH/bin`. -## Using the Plugin System +## Command Line Interface -Basecoin is designed to serve as a common base layer for developers building cryptocurrency applications. -It handles public-key authentication of transactions, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), -sending currency (one-to-one or n-to-m multisig), and providing merkle-proofs of the state. -These are common factors that many people wish to have in a crypto-currency system, -so instead of trying to start from scratch, developers can extend the functionality of Basecoin using the plugin system, just writing the custom business logic they need, and leaving the rest to the basecoin system. - -Interested in building a plugin? Then [read more details here](./Plugins.md) and then you can follow a [simple tutorial](https://github.com/tendermint/basecoin-examples/blob/master/pluginDev/tutorial.md) to get your first plugin working. - -### Best Practices - -We are still trying out sort out the best practices for basecoin plugins, and ABCi apps in general. Flexibility is very powerful once one has mastered a system, but when starting out, it is nice to have a set of guidelines to follow (and then expand beyond when no longer needed). I have attempted to gather some [good design practices](https://github.com/tendermint/basecoin-examples/tree/master/trader#code-design) I have discovered/invented while building progress. These are not hard rules, but should give you a good start. And please give feedback to improve and extend them. - -## Using the CLI - -The basecoin cli can be used to start a stand-alone basecoin instance (`basecoin start`), +The basecoin CLI can be used to start a stand-alone basecoin instance (`basecoin start`), or to start basecoin with tendermint in the same process (`basecoin start --in-proc`). It can also be used to send transactions, eg. `basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` See `basecoin --help` and `basecoin [cmd] --help` for more details`. -Or follow through a [step-by-step introduction](https://github.com/tendermint/basecoin-examples/blob/master/tutorial.md) to testing basecoin locally. +## Learn more -## Tutorials and Other Reading +1. Getting started with the [Basecoin tool](/docs/guide/basecoin-basics.md) +1. Learn more about [Basecoin's design](/docs/guide/basecoin-design.md) +1. Make your own [cryptocurrency using Basecoin plugins](/docs/guide/example-counter.md) +1. Learn more about [plugin design](/docs/guide/plugin-design.md) +1. See some [more example applications](/docs/guide/more-examples.md) +1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. [Deploy testnets](deployment.md) running your basecoin application. -See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. -There are a [number of examples](https://github.com/tendermint/basecoin-examples/blob/master/README.md) along with some tutorials and introductory texts, that should give you some pointers on how to wirte you own plugins and integrate them into your own custom app. - -We are working on extending these examples, as well as documenting (and automating) setting up a testnet, and providing an example GUI for viewing basecoin, which can all be used as a starting point for your application. They should be published during the course of February 2017, so stay tuned.... - -## Contributing - -We will merge in interesting plugin implementations and improvements to Basecoin. - -If you don't have much experience forking in go, there are a few tricks you want to keep in mind to avoid headaches. Basically, all imports in go are absolute from GOPATH, so if you fork a repo with more than one directory, and you put it under github.com/MYNAME/repo, all the code will start calling github.com/ORIGINAL/repo, which is very confusing. My preferred solution to this is as follows: - - * Create your own fork on github, using the fork button. - * Go to the original repo checked out locally (from `go get`) - * `git remote rename origin upstream` - * `git remote add origin git@github.com:YOUR-NAME/basecoin.git` - * `git push -u origin master` - * You can now push all changes to your fork and all code compiles, all other code referencing the original repo, now references your fork. - * If you want to pull in updates from the original repo: - * `git fetch upstream` - * `git rebase upstream/master` (or whatever branch you want) diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md new file mode 100644 index 0000000000..34aaae4046 --- /dev/null +++ b/docs/guide/basecoin-basics.md @@ -0,0 +1,145 @@ +# Basecoin Basics + +Here we explain how to get started with a simple Basecoin blockchain, and how to send transactions between accounts using the `basecoin` tool. + +## Install + +Make sure you have [basecoin installed](install.md). +You will also need to [install tendermint](https://tendermint.com/intro/getting-started/download). + +## Initialization + +Basecoin is an ABCI application that runs on Tendermint, so we first need to initialize Tendermint: + +``` +tendermint init +``` + +This will create the necessary files for a single Tendermint node in `~/.tendermint`. +If you had previously run tendermint, make sure you reset the chain +(note this will delete all chain data, so back it up if you need it): + +``` +tendermint unsafe_reset_all +``` + +Now we need some initialization files for basecoin. +We have included some defaults in the basecoin directory, under `data`. +For purposes of convenience, change to that directory: + +``` +cd $GOPATH/src/github.com/tendermint/basecoin/data +``` + +The directory contains a genesis file and two private keys. + +You can generate your own private keys with `tendermint gen_validator`, +and construct the `genesis.json` as you like. + +## Start + +Now we can start basecoin: + +``` +basecoin start --in-proc +``` + +This will initialize the chain with the `genesis.json` file from the current directory. If you want to specify another location, you can run: + +``` +basecoin start --in-proc --dir PATH/TO/CUSTOM/DATA +``` + +Note that `--in-proc` stands for "in process", which means +basecoin will be started with the Tendermint node running in the same process. +To start Tendermint in a separate process instead, use: + +``` +basecoin start +``` + +and in another window: + +``` +tendermint node +``` + +In either case, you should see blocks start streaming in! + +## Send transactions + +Now we are ready to send some transactions. +If you take a look at the `genesis.json` file, you will see one account listed there. +This account corresponds to the private key in `priv_validator.json`. +We also included the private key for another account, in `priv_validator2.json`. + +Let's check the balance of these two accounts: + +``` +basecoin account 0xD397BC62B435F3CF50570FBAB4340FE52C60858F +basecoin account 0x4793A333846E5104C46DD9AB9A00E31821B2F301 +``` + +The first account is flush with cash, while the second account doesn't exist. +Let's send funds from the first account to the second: + +``` +basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 10 +``` + +By default, the CLI looks for a `priv_validator.json` to sign the transaction with, +so this will only work if you are in the `$GOPATH/src/github.com/tendermint/basecoin/data`. +To specify a different key, we can use the `--from` flag. + +Now if we check the second account, it should have `10` coins! + +``` +basecoin account 0x4793A333846E5104C46DD9AB9A00E31821B2F301 +``` + +We can send some of these coins back like so: + +``` +basecoin sendtx --to 0xD397BC62B435F3CF50570FBAB4340FE52C60858F --from priv_validator2.json --amount 5 +``` + +Note how we use the `--from` flag to select a different account to send from. + +If we try to send too much, we'll get an error: + +``` +basecoin sendtx --to 0xD397BC62B435F3CF50570FBAB4340FE52C60858F --from priv_validator2.json --amount 100 +``` + +See `basecoin sendtx --help` for additional details. + +## Plugins + + +The `sendtx` command creates and broadcasts a transaction of type `SendTx`, +which is only useful for moving tokens around. +Fortunately, Basecoin supports another transaction type, the `AppTx`, +which can trigger code registered via a plugin system. + +For instance, we implemented a simple plugin called `counter`, +which just counts the number of transactions it processed. +To run it, kill the other processes, run `tendermint unsafe_reset_all`, and then + +``` +basecoin start --in-proc --counter-plugin +``` + +Now in another window, we can send transactions with: + +``` +TODO +``` + +## Next steps + +1. Learn more about [Basecoin's design](basecoin-design.md) +1. Make your own [cryptocurrency using Basecoin plugins](example-counter.md) +1. Learn more about [plugin design](plugin-design.md) +1. See some [more example applications](more-examples.md) +1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. [Deploy testnets](deployment.md) running your basecoin application. diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/guide/deployment.md b/docs/guide/deployment.md new file mode 100644 index 0000000000..af3f7feb12 --- /dev/null +++ b/docs/guide/deployment.md @@ -0,0 +1,7 @@ +## Deployment + +Up until this point, we have only been testing the code as a stand-alone abci app, which is nice for developing, but it is no blockchain. Just a blockchain-ready application. + +This section will demonstrate how to launch your basecoin-based application along with a tendermint testnet and initialize the genesis block for fun and profit. + +**TODO** Maybe we link to a blog post for this??? diff --git a/docs/guide/example-counter.md b/docs/guide/example-counter.md new file mode 100644 index 0000000000..d97b73f4ff --- /dev/null +++ b/docs/guide/example-counter.md @@ -0,0 +1 @@ +Rigel explains how to build your own basecoin-based app diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md new file mode 100644 index 0000000000..96022310f3 --- /dev/null +++ b/docs/guide/ibc.md @@ -0,0 +1,288 @@ +# InterBlockchain Communication with Basecoin + +One of the most exciting elements of the Cosmos Network is the InterBlockchain Communication (IBC) protocol, +which enables interoperability across different blockchains. +The simplest example of using the IBC protocol is to send a data packet from one blockchain to another. + +We implemented IBC as a basecoin plugin. +and here we'll show you how to use the Basecoin IBC-plugin to send a packet of data across blockchains! + +Please note, this tutorial assumes you are familiar with [Basecoin plugins](/docs/guide/plugin-design.md) +and with the [Basecoin CLI](/docs/guide/basecoin-basics), but we'll explain how IBC works. + +The IBC plugin defines a new set of transactions as subtypes of the `AppTx`. +The plugin's functionality is accessed by setting the `AppTx.Name` field to `"IBC"`, and setting the `Data` field to the serialized IBC transaction type. + +We'll demonstrate exactly how this works below. + +## IBC + +Let's review the IBC protocol. +The purpose of IBC is to enable one blockchain to function as a light-client of another. +Since we are using a classical Byzantine Fault Tolerant consensus algorithm, +light-client verification is cheap and easy: +all we have to do is check validator signatures on the latest block, +and verify a merkle proof of the state. + +In Tendermint, validators agree on a block before processing it. This means +that the signatures and state root for that block aren't included until the +next block. Thus, each block contains a field called `LastCommit`, which +contains the votes responsible for committing the previous block, and a field +in the block header called `AppHash`, which refers to the merkle root hash of +the application after processing the transactions from the previous block. So, +if we want to verify some state from height H, we need the signatures and root +hash from the header at height H+1. + +Unlike Proof-of-Work, the light-client protocol does not need to download and +check all the headers in the blockchain - the client can always jump straight +to the latest header available, so long as the validator set has not changed +much. If the validator set is changing, the client needs to track these +changes, which requires downloading headers for each block in which there is a +significant change. Here, we will assume the validator set is constant, and +postpone handling validator set changes for another time. + +Now we can describe exactly how IBC works. +Suppose we have two blockchains, `chain1` and `chain2`, and we want to send some data from `chain1` to `chain2`. +We need to do the following: + +``` +1. Register the details (ie. chain ID and genesis configuration) of `chain1` on `chain2` +2. Within `chain1`, broadcast a transaction that creates an outgoing IBC packet destined for `chain2` +3. Broadcast a transaction to `chain2` informing it of the latest state (ie. header and commit signatures) of `chain1` +4. Post the outgoing packet from `chain1` to `chain2`, including the proof that +it was indeed committed on `chain1`. Note `chain2` can only verify this proof +because it has a recent header and commit. +``` + +Each of these steps involves a separate IBC transaction type. Let's take them up in turn. + +### IBCRegisterChainTx + +The `IBCRegisterChainTx` is used to register one chain on another. +It contains the chain ID and genesis configuration of the chain to register: + +``` +type IBCRegisterChainTx struct { + BlockchainGenesis +} + +type BlockchainGenesis struct { + ChainID string + Genesis string +} +``` + +This transaction should only be sent once for a given chain ID, and successive sends will return an error. + + +### IBCUpdateChainTx + +The `IBCUpdateChainTx` is used to update the state of one chain on another. +It contains the header and commit signatures for some block in the chain: + +``` +type IBCUpdateChainTx struct { + Header tm.Header + Commit tm.Commit +} +``` + +In the future, it needs to be updated to include changes to the validator set as well. +Anyone can relay an `IBCUpdateChainTx`, and they only need to do so as frequently as packets are being sent or the validator set is changing. + +### IBCPacketCreateTx + +The `IBCPacketCreateTx` is used to create an outgoing packet on one chain. +The packet itself contains the source and destination chain IDs, +a sequence number (ie. an integer that increments with every message sent between this pair of chains), +a packet type (eg. coin, data, etc.), +and a payload. + +``` +type IBCPacketCreateTx struct { + Packet +} + +type Packet struct { + SrcChainID string + DstChainID string + Sequence uint64 + Type string + Payload []byte +} +``` + +We have yet to define the format for the payload, so, for now, it's just arbitrary bytes. + +One way to think about this is that `chain2` has an account on `chain1`. +With a `IBCPacketCreateTx` on `chain1`, we send funds to that account. +Then we can prove to `chain2` that there are funds locked up for it in it's +account on `chain1`. +Those funds can only be unlocked with corresponding IBC messages back from +`chain2` to `chain1` sending the locked funds to another account on +`chain1`. + +### IBCPacketPostTx + +The `IBCPacketPostTx` is used to post an outgoing packet from one chain to another. +It contains the packet and a proof that the packet was committed into the state of the sending chain: + +``` +type IBCPacketPostTx struct { + FromChainID string // The immediate source of the packet, not always Packet.SrcChainID + FromChainHeight uint64 // The block height in which Packet was committed, to check Proof + Packet + Proof *merkle.IAVLProof +} +``` + +The proof is a merkle proof in an IAVL tree, our implementation of a balanced, Merklized binary search tree. +It contains a list of nodes in the tree, which can be hashed together to get the Merkle root hash. +This hash must match the `AppHash` contained in the header at `FromChainHeight + 1` +- note the `+ 1` is necessary since `FromChainHeight` is the height in which the packet was committed, +and the resulting state root is not included until the next block. + +### IBC State + +Now that we've seen all the transaction types, let's talk about the state. +Each chain stores some IBC state in its merkle tree. +For each chain being tracked by our chain, we store: + +``` +- Genesis configuration +- Latest state +- Headers for recent heights +``` + +We also store all incoming (ingress) and outgoing (egress) packets. + +The state of a chain is updated every time an `IBCUpdateChainTx` is committed. +New packets are added to the egress state upon `IBCPacketCreateTx`. +New packets are added to the ingress state upon `IBCPacketPostTx`, +assuming the proof checks out. + +## Merkle Queries + +The Basecoin application uses a single Merkle tree that is shared across all its state, +including the built-in accounts state and all plugin state. For this reason, +it's important to use explicit key names and/or hashes to ensure there are no collisions. + +We can query the Merkle tree using the ABCI Query method. +If we pass in the correct key, it will return the corresponding value, +as well as a proof that the key and value are contained in the Merkle tree. + +The results of a query can thus be used as proof in an `IBCPacketPostTx`. + +## Try it out + +Now that we have all the background knowledge, let's actually walk through the tutorial. + +Make sure you have installed +[tendermint](https://tendermint.com/intro/getting-started/download) and +[basecoin](/docs/guide/install.md). + +Now let's start the two blockchains. +In this tutorial, each chain will have only a single validator, +where the initial configuration files are already generated. +Let's change directory so these files are easily accessible: + +``` +cd $GOPATH/src/github.com/tendermint/basecoin/demo +``` + +The relevant data is now in the `data` directory. + +We can start the two chains as follows: + +``` +TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & +basecoin start --ibc-plugin --dir ./data/chain1/basecoin &> chain1_basecoin.log & +``` + +and + +``` +TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & +basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/basecoin &> chain2_basecoin.log & +``` + +Note how we refer to the relevant data directories. Also note how we have to set the various addresses for the second node so as not to conflict with the first. + +We can now check on the status of the two chains: + +``` +curl localhost:46657/status +curl localhost:36657/status +``` + +If either command fails, the nodes may not have finished starting up. Wait a couple seconds and try again. +Once you see the status of both chains, it's time to move on. + +In this tutorial, we're going to send some data from `test_chain_1` to `test_chain_2`. +For the sake of convenience, let's first set some environment variables: + +``` +export CHAIN_ID1=test_chain_1 +export CHAIN_ID2=test_chain_2 + +export CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json" +export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657" +``` + +Let's start by registering `test_chain_1` on `test_chain_2`: + +``` +basecoin ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json +``` + +Now we can create the outgoing packet on `test_chain_1`: + +``` +basecoin ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 +``` + +Note our payload is just `DEADBEEF`. +Now that the packet is committed in the chain, let's get some proof by querying: + +``` +basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1 +``` + +The result contains the latest height, a value (ie. the hex-encoded binary serialization of our packet), +and a proof (ie. hex-encoded binary serialization of a list of nodes from the Merkle tree) that the value is in the Merkle tree. + +If we want to send this data to `test_chain_2`, we first have to update what it knows about `test_chain_1`. +We'll need a recent block header and a set of commit signatures. +Fortunately, we can get them with the `block` command: + +``` +basecoin block +``` + +where `` is the height returned in the previous query. +Note the result contains both a hex-encoded and json-encoded version of the header and the commit. +The former is used as input for later commands; the latter is human-readable, so you know what's going on! + +Let's send this updated information about `test_chain_1` to `test_chain_2`: + +``` +basecoin ibc --amount 10 $CHAIN_FLAGS2 update --header 0x
--commit 0x +``` + +where `
` and `` are the hex-encoded header and commit returned by the previous `block` command. + +Now that `test_chain_2` knows about some recent state of `test_chain_1`, we can post the packet to `test_chain_2`, +along with proof the packet was committed on `test_chain_1`. Since `test_chain_2` knows about some recent state +of `test_chain_1`, it will be able to verify the proof! + +``` +basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x +``` + +Here, `` is one greater than the height retuned by the previous `query` command, and `` and `` are the +`value` and `proof` returned in that same query. + +Tada! + + +## Conclusion diff --git a/docs/guide/install.md b/docs/guide/install.md new file mode 100644 index 0000000000..4b77255b7d --- /dev/null +++ b/docs/guide/install.md @@ -0,0 +1,13 @@ +# Install + +We use glide for dependency management. The prefered way of compiling from source is the following: + +``` +go get -d github.com/tendermint/basecoin/cmd/basecoin +cd $GOPATH/src/github.com/tendermint/basecoin +make get_vendor_deps +make install +``` + +This will create the `basecoin` binary in `$GOPATH/bin`. + diff --git a/docs/guide/more-examples.md b/docs/guide/more-examples.md new file mode 100644 index 0000000000..ba1d94b134 --- /dev/null +++ b/docs/guide/more-examples.md @@ -0,0 +1,16 @@ + +## Mintcoin + + +You just read about the amazing [plugin system](https://github.com/tendermint/basecoin/blob/develop/Plugins.md), and want to use it to print your own money. Me too! Let's get started with a simple plugin extension to basecoin, called [mintcoin](./mintcoin/README.md). This plugin lets you register one or more accounts as "central bankers", who can unilaterally issue more currency into the system. It also serves as a simple test-bed to see how one can not just build a plugin, but also take advantage of existing codebases to provide a simple cli to use it. + +## Financial Instruments + +Sure, printing money and sending it is nice, but sometimes I don't fully trust the guy at the other end. Maybe we could add an escrow service? Or how about options for currency trading, since we support multiple currencies? No problem, this is also just a plugin away. Checkout our [trader application](./trader). + +**Running code, still WIP** + +## IBC + +Now, let's hook up your personal crypto-currency with the wide world of other currencies, in a distributed, proof-of-stake based exchange. Hard, you say? Well half the work is already done for you with the [IBC, InterBlockchain Communication, plugin](./ibc.md). Now, we just need to get cosmos up and running and time to go and trade. + diff --git a/Plugins.md b/docs/guide/plugin-design.md similarity index 51% rename from Plugins.md rename to docs/guide/plugin-design.md index 7562aceacc..6be526a8ae 100644 --- a/Plugins.md +++ b/docs/guide/plugin-design.md @@ -48,46 +48,3 @@ where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent. Basecoin also defines another transaction type, the `AppTx`: -``` -type AppTx struct { - Gas int64 `json:"gas"` // Gas - Fee Coin `json:"fee"` // Fee - Name string `json:"type"` // Which plugin - Input TxInput `json:"input"` - Data []byte `json:"data"` -} -``` - -The `AppTx` enables arbitrary additional functionality through the use of plugins. -A plugin is simply a Go package that implements the `Plugin` interface: - -``` -type Plugin interface { - - // Name of this plugin, should be short. - Name() string - - // Run a transaction from ABCI DeliverTx - RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) - - // Other ABCI message handlers - SetOption(store KVStore, key string, value string) (log string) - InitChain(store KVStore, vals []*abci.Validator) - BeginBlock(store KVStore, height uint64) - EndBlock(store KVStore, height uint64) []*abci.Validator -} - -type CallContext struct { - CallerAddress []byte // Caller's Address (hash of PubKey) - CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted - Coins Coins // The coins that the caller wishes to spend, excluding fees -} -``` - -The workhorse of the plugin is `RunTx`, which is called when an `AppTx` is processed. -The `Name` field in the `AppTx` refers to the plugin name, and the `Data` field of the `AppTx` is -forward to the `RunTx` function. - -You can look at some example plugins in the [basecoin repo](https://github.com/tendermint/basecoin/tree/develop/plugins). - -If you want to see how you can write a plugin in your own repo, and make use of all the basecoin tooling, cli, etc. please take a look at the [mintcoin example](https://github.com/tendermint/basecoin-examples/tree/master/mintcoin) for inspiration, not just the plugin itself, but also the `cmd/mintcoin` directory to create the custom command. From 4ea03bc9dd47296a9ca170841dcc5d26f26dc2d4 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Sat, 4 Feb 2017 14:47:36 -0800 Subject: [PATCH 48/64] Change ABCI app to implement abci.BlockchainAware interface --- app/app.go | 31 ++++++++++++++----------------- types/plugin.go | 5 +++-- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/app/app.go b/app/app.go index 56f4447cd4..21fe2e3d08 100644 --- a/app/app.go +++ b/app/app.go @@ -1,7 +1,6 @@ package app import ( - "fmt" "strings" abci "github.com/tendermint/abci/types" @@ -42,7 +41,7 @@ func (app *Basecoin) GetState() *sm.State { return app.state.CacheWrap() } -// TMSP::Info +// ABCI::Info func (app *Basecoin) Info() abci.ResponseInfo { return abci.ResponseInfo{Data: Fmt("Basecoin v%v", version)} } @@ -51,7 +50,7 @@ func (app *Basecoin) RegisterPlugin(plugin types.Plugin) { app.plugins.RegisterPlugin(plugin) } -// TMSP::SetOption +// ABCI::SetOption func (app *Basecoin) SetOption(key string, value string) (log string) { PluginName, key := splitKey(key) if PluginName != PluginNameBase { @@ -81,7 +80,7 @@ func (app *Basecoin) SetOption(key string, value string) (log string) { } } -// TMSP::DeliverTx +// ABCI::DeliverTx func (app *Basecoin) DeliverTx(txBytes []byte) (res abci.Result) { if len(txBytes) > maxTxSize { return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum") @@ -102,14 +101,12 @@ func (app *Basecoin) DeliverTx(txBytes []byte) (res abci.Result) { return res } -// TMSP::CheckTx +// ABCI::CheckTx func (app *Basecoin) CheckTx(txBytes []byte) (res abci.Result) { if len(txBytes) > maxTxSize { return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum") } - fmt.Printf("%X\n", txBytes) - // Decode tx var tx types.Tx err := wire.ReadBinaryBytes(txBytes, &tx) @@ -125,7 +122,7 @@ func (app *Basecoin) CheckTx(txBytes []byte) (res abci.Result) { return abci.OK } -// TMSP::Query +// ABCI::Query func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { if len(reqQuery.Data) == 0 { resQuery.Log = "Query cannot be zero length" @@ -142,7 +139,7 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu return } -// TMSP::Commit +// ABCI::Commit func (app *Basecoin) Commit() (res abci.Result) { // Commit state @@ -157,25 +154,25 @@ func (app *Basecoin) Commit() (res abci.Result) { return res } -// TMSP::InitChain +// ABCI::InitChain func (app *Basecoin) InitChain(validators []*abci.Validator) { for _, plugin := range app.plugins.GetList() { plugin.InitChain(app.state, validators) } } -// TMSP::BeginBlock -func (app *Basecoin) BeginBlock(height uint64) { +// ABCI::BeginBlock +func (app *Basecoin) BeginBlock(hash []byte, header *abci.Header) { for _, plugin := range app.plugins.GetList() { - plugin.BeginBlock(app.state, height) + plugin.BeginBlock(app.state, hash, header) } } -// TMSP::EndBlock -func (app *Basecoin) EndBlock(height uint64) (diffs []*abci.Validator) { +// ABCI::EndBlock +func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) { for _, plugin := range app.plugins.GetList() { - moreDiffs := plugin.EndBlock(app.state, height) - diffs = append(diffs, moreDiffs...) + pluginRes := plugin.EndBlock(app.state, height) + res.Diffs = append(res.Diffs, pluginRes.Diffs...) } return } diff --git a/types/plugin.go b/types/plugin.go index 55d3bb969f..ac95cac679 100644 --- a/types/plugin.go +++ b/types/plugin.go @@ -2,6 +2,7 @@ package types import ( "fmt" + abci "github.com/tendermint/abci/types" ) @@ -16,8 +17,8 @@ type Plugin interface { // Other ABCI message handlers SetOption(store KVStore, key string, value string) (log string) InitChain(store KVStore, vals []*abci.Validator) - BeginBlock(store KVStore, height uint64) - EndBlock(store KVStore, height uint64) []*abci.Validator + BeginBlock(store KVStore, hash []byte, header *abci.Header) + EndBlock(store KVStore, height uint64) abci.ResponseEndBlock } //---------------------------------------- From 8af20facc3a5e4b764fec4b8ba529d2d2cc7285f Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Sat, 4 Feb 2017 14:47:51 -0800 Subject: [PATCH 49/64] Update plugins for new interface --- plugins/counter/counter.go | 6 +++--- plugins/ibc/ibc.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 9c115089b5..8d78d9b545 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -93,9 +93,9 @@ func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt func (cp *CounterPlugin) InitChain(store types.KVStore, vals []*abci.Validator) { } -func (cp *CounterPlugin) BeginBlock(store types.KVStore, height uint64) { +func (cp *CounterPlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) { } -func (cp *CounterPlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator { - return nil +func (cp *CounterPlugin) EndBlock(store types.KVStore, height uint64) (res abci.ResponseEndBlock) { + return } diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index 8d6e0c3893..91b8c54985 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -374,11 +374,11 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { func (ibc *IBCPlugin) InitChain(store types.KVStore, vals []*abci.Validator) { } -func (ibc *IBCPlugin) BeginBlock(store types.KVStore, height uint64) { +func (cp *IBCPlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) { } -func (ibc *IBCPlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator { - return nil +func (cp *IBCPlugin) EndBlock(store types.KVStore, height uint64) (res abci.ResponseEndBlock) { + return } //-------------------------------------------------------------------------------- From c23c01882499a9bbdfd58b334fe924f8862659f5 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Sat, 4 Feb 2017 15:02:51 -0800 Subject: [PATCH 50/64] Assert that Basecoin ABCI app implements abci.BlockchainAware --- app/app.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/app.go b/app/app.go index 21fe2e3d08..621d17a6ec 100644 --- a/app/app.go +++ b/app/app.go @@ -188,3 +188,9 @@ func splitKey(key string) (prefix string, suffix string) { } return key, "" } + +// (not meant to be called) +// assert that Basecoin implements `abci.BlockchainAware` at compile-time +func _assertABCIBlockchainAware(basecoin *Basecoin) abci.BlockchainAware { + return basecoin +} From cb253cbdf4ea30ccec62e7cfee0bb1ae2a2c0d11 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 5 Feb 2017 00:43:55 -0500 Subject: [PATCH 51/64] docs: design, examples --- README.md | 2 +- docs/guide/basecoin-design.md | 81 +++++++++++++++++++++++++++ docs/guide/more-examples.md | 11 +++- docs/guide/plugin-design.md | 100 +++++++++++++++++++++------------- 4 files changed, 153 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 6c5e917cd2..b6c468aa60 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ See `basecoin --help` and `basecoin [cmd] --help` for more details`. 1. Make your own [cryptocurrency using Basecoin plugins](/docs/guide/example-counter.md) 1. Learn more about [plugin design](/docs/guide/plugin-design.md) 1. See some [more example applications](/docs/guide/more-examples.md) -1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. Learn how to use [InterBlockchain Communication (IBC)](/docs/guide/ibc.md) 1. [Deploy testnets](deployment.md) running your basecoin application. diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md index e69de29bb2..8240106c11 100644 --- a/docs/guide/basecoin-design.md +++ b/docs/guide/basecoin-design.md @@ -0,0 +1,81 @@ +# Basecoin Design + +Basecoin is designed to be a simple cryptocurrency application with limitted built-in functionality, +but with the capacity to be extended by arbitrary plugins. +Its basic data structures are inspired by Ethereum, but it is much simpler, as there is no built in virtual machine. + +## Accounts + +The Basecoin state consists entirely of a set of accounts. +Each account contains an ED25519 public key, +a balance in many different coin denominations, +and a strictly increasing sequence number for replay protection. +This type of account was directly inspired by accounts in Ethereum, +and is unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). +Note Basecoin is a multi-asset cryptocurrency, so each account can have many different kinds of tokens. + +Accounts are serialized and stored in a Merkle tree using the account's address as the key, +where the address is the RIPEMD160 hash of the public key. +In particular, an account is stored in the Merkle tree under the key `base/a/
`, +where `
` is the 20-byte address of the account. +We use an implementation of a Merkle, balanced, binary search tree, also known as an [IAVL tree](https://github.com/tendermint/go-merkle). + +## Transactions + +Basecoin defines a simple transaction type, the `SendTx`, which allows tokens to be sent to other accounts. +The `SendTx` takes a list of inputs and a list of outputs, +and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output. +The `SendTx` is structured as follows: + +``` +type SendTx struct { + Gas int64 `json:"gas"` + Fee Coin `json:"fee"` + Inputs []TxInput `json:"inputs"` + Outputs []TxOutput `json:"outputs"` +} + +type TxInput struct { + Address []byte `json:"address"` // Hash of the PubKey + Coins Coins `json:"coins"` // + Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput + Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx + PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 +} + +type TxOutput struct { + Address []byte `json:"address"` // Hash of the PubKey + Coins Coins `json:"coins"` // +} + +type Coins []Coin + +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} + +``` + +There are a few things to note. First, the `SendTx` includes a field for `Gas` and `Fee`. +The `Gas` limits the total amount of computation that can be done by the transaction, +while the `Fee` refers to the total amount paid in fees. +This is slightly different from Ethereum's concept of `Gas` and `GasPrice`, +where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent, +and the `GasPrice` is implicit. + +Second, notice that the `PubKey` only needs to be sent for `Sequence == 0`. +After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it, +using only the `Address` to refer to the sender. Ethereum does not require public keys to be sent in transactions +as it uses a different elliptic curve scheme which enables the public key to be derrived from the signature itself. + +Finally, note that the use of multiple inputs and multiple outputs allows us to send many different types of tokens between many different accounts +at once in an atomic transaction. Thus, the `SendTx` can serve as a basic unit of decentralized exchange. + +## Next steps + +1. Make your own [cryptocurrency using Basecoin plugins](example-counter.md) +1. Learn more about [plugin design](plugin-design.md) +1. See some [more example applications](more-examples.md) +1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. [Deploy testnets](deployment.md) running your basecoin application. diff --git a/docs/guide/more-examples.md b/docs/guide/more-examples.md index ba1d94b134..ad17c77a66 100644 --- a/docs/guide/more-examples.md +++ b/docs/guide/more-examples.md @@ -1,8 +1,17 @@ +# Plugin Examples + +Now that we've seen how to use Basecoin, talked about the design, +and looked at how to implement a simple plugin, let's take a look at some more interesting examples. ## Mintcoin +Basecoin does not provide any functionality for adding new tokens to the system. +The state is endowed with tokens by a `genesis.json` file which is read once when the system is first started. +From there, tokens can be sent to other accounts, even new accounts, but it's impossible to add more tokens to the system. +For this, we need a plugin. -You just read about the amazing [plugin system](https://github.com/tendermint/basecoin/blob/develop/Plugins.md), and want to use it to print your own money. Me too! Let's get started with a simple plugin extension to basecoin, called [mintcoin](./mintcoin/README.md). This plugin lets you register one or more accounts as "central bankers", who can unilaterally issue more currency into the system. It also serves as a simple test-bed to see how one can not just build a plugin, but also take advantage of existing codebases to provide a simple cli to use it. +The `mintcoin` plugin lets you register one or more accounts as "central bankers", +who can unilaterally issue more currency into the system. ## Financial Instruments diff --git a/docs/guide/plugin-design.md b/docs/guide/plugin-design.md index 6be526a8ae..127c51960b 100644 --- a/docs/guide/plugin-design.md +++ b/docs/guide/plugin-design.md @@ -1,50 +1,72 @@ # Basecoin Plugins -Basecoin is an extensible cryptocurrency module. -Each Basecoin account contains a ED25519 public key, -a balance in many different coin denominations, -and a strictly increasing sequence number for replay protection (like in Ethereum). -Accounts are serialized and stored in a merkle tree using the account's address as the key, -where the address is the RIPEMD160 hash of the public key. +Basecoin implements a simple cryptocurrency, which is useful in and of itself, +but is far more useful if it can support additional functionality. +Here we describe how that functionality can be achieved through a plugin system. -Sending tokens around is done via the `SendTx`, which takes a list of inputs and a list of outputs, -and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output. -The `SendTx` is structured as follows: + +## AppTx + +In addition to the `SendTx`, Basecoin also defines another transaction type, the `AppTx`: ``` -type SendTx struct { - Gas int64 `json:"gas"` // Gas - Fee Coin `json:"fee"` // Fee - Inputs []TxInput `json:"inputs"` - Outputs []TxOutput `json:"outputs"` +type AppTx struct { + Gas int64 `json:"gas"` + Fee Coin `json:"fee"` + Input TxInput `json:"input"` + Name string `json:"type"` // Name of the plugin + Data []byte `json:"data"` // Data for the plugin to process } - -type TxInput struct { - Address []byte `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // - Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput - Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx - PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 -} - -type TxOutput struct { - Address []byte `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // -} - -type Coins []Coin - -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - ``` -Note it also includes a field for `Gas` and `Fee`. The `Gas` limits the total amount of computation that can be done by the transaction, -while the `Fee` refers to the total amount paid in fees. This is slightly different from Ethereum's concept of `Gas` and `GasPrice`, -where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent. +The `AppTx` enables Basecoin to be extended with arbitrary additional functionality through the use of plugins. +The `Name` field in the `AppTx` refers to the particular plugin which should process the transasaction, +and the `Data` field of the `AppTx` is the data to be forwarded to the plugin for processing. + +Note the `AppTx` also has a `Gas` and `Fee`, with the same meaning as for the `SendTx`. +It also includes a single `TxInput`, which specifies the sender of the transaction, +and some coins that can be forwarded to the plugin as well. + +## Plugins + +A plugin is simply a Go package that implements the `Plugin` interface: + +``` +type Plugin interface { + + // Name of this plugin, should be short. + Name() string + + // Run a transaction from ABCI DeliverTx + RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) + + // Other ABCI message handlers + SetOption(store KVStore, key string, value string) (log string) + InitChain(store KVStore, vals []*abci.Validator) + BeginBlock(store KVStore, height uint64) + EndBlock(store KVStore, height uint64) []*abci.Validator +} + +type CallContext struct { + CallerAddress []byte // Caller's Address (hash of PubKey) + CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted + Coins Coins // The coins that the caller wishes to spend, excluding fees +} +``` + +The workhorse of the plugin is `RunTx`, which is called when an `AppTx` is processed. +The `Data` from the `AppTx` is passed in as the `txBytes`, +while the `Input` from the `AppTx` is used to populate the `CallContext`. + +Note that `RunTx` also takes a `KVStore` - this is an abstraction for the underlying Merkle tree which stores the account data. +By passing this to the plugin, we enable plugins to update accounts in the Basecoin state directly, +and also to store arbitrary other information in the state. +In this way, the functionality and state of a Basecoin-derrived cryptocurrency can be greatly extended. +One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin! -Basecoin also defines another transaction type, the `AppTx`: +## Next steps +1. Examples of [Basecoin plugins](more-examples.md) +1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. [Deploy testnets](deployment.md) running your basecoin application. From f77d43040b56175533519c34eaba01822bf3bce6 Mon Sep 17 00:00:00 2001 From: hcopperm Date: Mon, 6 Feb 2017 11:57:35 -0800 Subject: [PATCH 52/64] Fix typos --- docs/guide/basecoin-design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md index 8240106c11..344bb62bb9 100644 --- a/docs/guide/basecoin-design.md +++ b/docs/guide/basecoin-design.md @@ -1,8 +1,8 @@ # Basecoin Design -Basecoin is designed to be a simple cryptocurrency application with limitted built-in functionality, +Basecoin is designed to be a simple cryptocurrency application with limited built-in functionality, but with the capacity to be extended by arbitrary plugins. -Its basic data structures are inspired by Ethereum, but it is much simpler, as there is no built in virtual machine. +Its basic data structures are inspired by Ethereum, but it is much simpler, as there is no built-in virtual machine. ## Accounts From abac65bacc3fe86c19a587680b08a2a6922626ec Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 13:16:41 -0500 Subject: [PATCH 53/64] cli: refactor ibc and counter into own binaries --- cmd/adam/main.go | 29 +++++ cmd/basecoin/main.go | 10 +- cmd/{basecoin => }/commands/flags.go | 85 -------------- cmd/{basecoin => }/commands/ibc.go | 108 +++++++++++++++--- cmd/{basecoin => }/commands/query.go | 0 cmd/{basecoin => }/commands/start.go | 18 +-- cmd/{basecoin => }/commands/tx.go | 69 ++++++----- cmd/{basecoin => }/commands/utils.go | 0 .../commands/counter.go => counter/cmd.go} | 54 ++++----- cmd/counter/main.go | 22 ++++ 10 files changed, 212 insertions(+), 183 deletions(-) create mode 100644 cmd/adam/main.go rename cmd/{basecoin => }/commands/flags.go (60%) rename cmd/{basecoin => }/commands/ibc.go (74%) rename cmd/{basecoin => }/commands/query.go (100%) rename cmd/{basecoin => }/commands/start.go (84%) rename cmd/{basecoin => }/commands/tx.go (86%) rename cmd/{basecoin => }/commands/utils.go (100%) rename cmd/{basecoin/commands/counter.go => counter/cmd.go} (54%) create mode 100644 cmd/counter/main.go diff --git a/cmd/adam/main.go b/cmd/adam/main.go new file mode 100644 index 0000000000..ef55d7aced --- /dev/null +++ b/cmd/adam/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "os" + + "github.com/tendermint/basecoin/cmd/commands" + + "github.com/urfave/cli" +) + +func init() { + commands.RegisterIBC() +} + +func main() { + app := cli.NewApp() + app.Name = "adam" + app.Usage = "adam [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + commands.StartCmd, + commands.TxCmd, + commands.QueryCmd, + commands.VerifyCmd, // TODO: move to merkleeyes? + commands.BlockCmd, + commands.AccountCmd, + } + app.Run(os.Args) +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 536dc61879..3c31ae38fd 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/tendermint/basecoin/cmd/basecoin/commands" + "github.com/tendermint/basecoin/cmd/commands" "github.com/urfave/cli" ) @@ -14,12 +14,10 @@ func main() { app.Version = "0.1.0" app.Commands = []cli.Command{ commands.StartCmd, - commands.SendTxCmd, - commands.AppTxCmd, - commands.IbcCmd, + commands.TxCmd, commands.QueryCmd, - commands.VerifyCmd, - commands.BlockCmd, + commands.VerifyCmd, // TODO: move to merkleeyes? + commands.BlockCmd, // TODO: move to adam? commands.AccountCmd, } app.Run(os.Args) diff --git a/cmd/basecoin/commands/flags.go b/cmd/commands/flags.go similarity index 60% rename from cmd/basecoin/commands/flags.go rename to cmd/commands/flags.go index 0ca74962ce..dc91754687 100644 --- a/cmd/basecoin/commands/flags.go +++ b/cmd/commands/flags.go @@ -31,11 +31,6 @@ var ( Name: "in-proc", Usage: "Run Tendermint in-process with the App", } - - IbcPluginFlag = cli.BoolFlag{ - Name: "ibc-plugin", - Usage: "Enable the ibc plugin", - } ) // tx flags @@ -106,86 +101,6 @@ var ( Value: "test_chain_id", Usage: "ID of the chain for replay protection", } - - ValidFlag = cli.BoolFlag{ - Name: "valid", - Usage: "Set valid field in CounterTx", - } -) - -// ibc flags -var ( - IbcChainIDFlag = cli.StringFlag{ - Name: "chain_id", - Usage: "ChainID for the new blockchain", - Value: "", - } - - IbcGenesisFlag = cli.StringFlag{ - Name: "genesis", - Usage: "Genesis file for the new blockchain", - Value: "", - } - - IbcHeaderFlag = cli.StringFlag{ - Name: "header", - Usage: "Block header for an ibc update", - Value: "", - } - - IbcCommitFlag = cli.StringFlag{ - Name: "commit", - Usage: "Block commit for an ibc update", - Value: "", - } - - IbcFromFlag = cli.StringFlag{ - Name: "from", - Usage: "Source ChainID", - Value: "", - } - - IbcToFlag = cli.StringFlag{ - Name: "to", - Usage: "Destination ChainID", - Value: "", - } - - IbcTypeFlag = cli.StringFlag{ - Name: "type", - Usage: "IBC packet type (eg. coin)", - Value: "", - } - - IbcPayloadFlag = cli.StringFlag{ - Name: "payload", - Usage: "IBC packet payload", - Value: "", - } - - IbcPacketFlag = cli.StringFlag{ - Name: "packet", - Usage: "hex-encoded IBC packet", - Value: "", - } - - IbcProofFlag = cli.StringFlag{ - Name: "proof", - Usage: "hex-encoded proof of IBC packet from source chain", - Value: "", - } - - IbcSequenceFlag = cli.IntFlag{ - Name: "sequence", - Usage: "sequence number for IBC packet", - Value: 0, - } - - IbcHeightFlag = cli.IntFlag{ - Name: "height", - Usage: "Height the packet became egress in source chain", - Value: 0, - } ) // proof flags diff --git a/cmd/basecoin/commands/ibc.go b/cmd/commands/ibc.go similarity index 74% rename from cmd/basecoin/commands/ibc.go rename to cmd/commands/ibc.go index 39f5aef8d4..1b513d4047 100644 --- a/cmd/basecoin/commands/ibc.go +++ b/cmd/commands/ibc.go @@ -9,6 +9,7 @@ import ( "github.com/urfave/cli" "github.com/tendermint/basecoin/plugins/ibc" + "github.com/tendermint/basecoin/types" cmn "github.com/tendermint/go-common" "github.com/tendermint/go-merkle" @@ -16,25 +17,99 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +// Register the IBC plugin at start and for transactions +func RegisterIBC() { + RegisterTxSubcommand(IbcCmd) + RegisterStartPlugin("ibc", func() types.Plugin { + return ibc.New() + }) +} + +//--------------------------------------------------------------------- +// ibc flags + +var ( + IbcChainIDFlag = cli.StringFlag{ + Name: "chain_id", + Usage: "ChainID for the new blockchain", + Value: "", + } + + IbcGenesisFlag = cli.StringFlag{ + Name: "genesis", + Usage: "Genesis file for the new blockchain", + Value: "", + } + + IbcHeaderFlag = cli.StringFlag{ + Name: "header", + Usage: "Block header for an ibc update", + Value: "", + } + + IbcCommitFlag = cli.StringFlag{ + Name: "commit", + Usage: "Block commit for an ibc update", + Value: "", + } + + IbcFromFlag = cli.StringFlag{ + Name: "from", + Usage: "Source ChainID", + Value: "", + } + + IbcToFlag = cli.StringFlag{ + Name: "to", + Usage: "Destination ChainID", + Value: "", + } + + IbcTypeFlag = cli.StringFlag{ + Name: "type", + Usage: "IBC packet type (eg. coin)", + Value: "", + } + + IbcPayloadFlag = cli.StringFlag{ + Name: "payload", + Usage: "IBC packet payload", + Value: "", + } + + IbcPacketFlag = cli.StringFlag{ + Name: "packet", + Usage: "hex-encoded IBC packet", + Value: "", + } + + IbcProofFlag = cli.StringFlag{ + Name: "proof", + Usage: "hex-encoded proof of IBC packet from source chain", + Value: "", + } + + IbcSequenceFlag = cli.IntFlag{ + Name: "sequence", + Usage: "sequence number for IBC packet", + Value: 0, + } + + IbcHeightFlag = cli.IntFlag{ + Name: "height", + Usage: "Height the packet became egress in source chain", + Value: 0, + } +) + +//--------------------------------------------------------------------- +// ibc commands + var ( IbcCmd = cli.Command{ Name: "ibc", Usage: "Send a transaction to the interblockchain (ibc) plugin", - Flags: []cli.Flag{ - NodeFlag, - ChainIDFlag, - - FromFlag, - - AmountFlag, - CoinFlag, - GasFlag, - FeeFlag, - SeqFlag, - - NameFlag, - DataFlag, - }, + Flags: TxFlags, Subcommands: []cli.Command{ IbcRegisterTxCmd, IbcUpdateTxCmd, @@ -108,6 +183,9 @@ var ( } ) +//--------------------------------------------------------------------- +// ibc command implementations + func cmdIBCRegisterTx(c *cli.Context) error { chainID := c.String("chain_id") genesisFile := c.String("genesis") diff --git a/cmd/basecoin/commands/query.go b/cmd/commands/query.go similarity index 100% rename from cmd/basecoin/commands/query.go rename to cmd/commands/query.go diff --git a/cmd/basecoin/commands/start.go b/cmd/commands/start.go similarity index 84% rename from cmd/basecoin/commands/start.go rename to cmd/commands/start.go index 225619399f..9ce201a108 100644 --- a/cmd/basecoin/commands/start.go +++ b/cmd/commands/start.go @@ -19,7 +19,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/basecoin/app" - "github.com/tendermint/basecoin/plugins/ibc" "github.com/tendermint/basecoin/types" ) @@ -40,7 +39,6 @@ var StartCmd = cli.Command{ DirFlag, InProcTMFlag, ChainIDFlag, - IbcPluginFlag, }, } @@ -51,10 +49,9 @@ type plugin struct { var plugins = []plugin{} -// RegisterStartPlugin is used to add another -func RegisterStartPlugin(flag cli.BoolFlag, init func() types.Plugin) { - StartCmd.Flags = append(StartCmd.Flags, flag) - plugins = append(plugins, plugin{name: flag.GetName(), init: init}) +// RegisterStartPlugin is used to enable a plugin +func RegisterStartPlugin(name string, initFunc func() types.Plugin) { + plugins = append(plugins, plugin{name: name, init: initFunc}) } func cmdStart(c *cli.Context) error { @@ -73,15 +70,10 @@ func cmdStart(c *cli.Context) error { // Create Basecoin app basecoinApp := app.NewBasecoin(eyesCli) - if c.Bool("ibc-plugin") { - basecoinApp.RegisterPlugin(ibc.New()) - } - // loop through all registered plugins and enable if desired + // register all plugins for _, p := range plugins { - if c.Bool(p.name) { - basecoinApp.RegisterPlugin(p.init()) - } + basecoinApp.RegisterPlugin(p.init()) } // If genesis file exists, set key-value options diff --git a/cmd/basecoin/commands/tx.go b/cmd/commands/tx.go similarity index 86% rename from cmd/basecoin/commands/tx.go rename to cmd/commands/tx.go index e68c3754d1..cba6e70639 100644 --- a/cmd/basecoin/commands/tx.go +++ b/cmd/commands/tx.go @@ -16,61 +16,56 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +var TxFlags = []cli.Flag{ + NodeFlag, + ChainIDFlag, + + FromFlag, + + AmountFlag, + CoinFlag, + GasFlag, + FeeFlag, + SeqFlag, +} + var ( + TxCmd = cli.Command{ + Name: "tx", + Usage: "Create, sign, and broadcast a transaction", + ArgsUsage: "", + Subcommands: []cli.Command{ + SendTxCmd, + AppTxCmd, + }, + } + SendTxCmd = cli.Command{ - Name: "sendtx", - Usage: "Broadcast a basecoin SendTx", + Name: "send", + Usage: "Create, sign, and broadcast a SendTx transaction", ArgsUsage: "", Action: func(c *cli.Context) error { return cmdSendTx(c) }, - Flags: []cli.Flag{ - NodeFlag, - ChainIDFlag, - - FromFlag, - - AmountFlag, - CoinFlag, - GasFlag, - FeeFlag, - SeqFlag, - - ToFlag, - }, + Flags: append(TxFlags, ToFlag), } AppTxCmd = cli.Command{ - Name: "apptx", - Usage: "Broadcast a basecoin AppTx", + Name: "app", + Usage: "Create, sign, and broadcast a raw AppTx transaction", ArgsUsage: "", Action: func(c *cli.Context) error { return cmdAppTx(c) }, - Flags: []cli.Flag{ - NodeFlag, - ChainIDFlag, - - FromFlag, - - AmountFlag, - CoinFlag, - GasFlag, - FeeFlag, - SeqFlag, - - NameFlag, - DataFlag, - }, + Flags: append(TxFlags, NameFlag, DataFlag), // Subcommands are dynamically registered with plugins as needed Subcommands: []cli.Command{}, } ) -// RegisterTxPlugin is used to add another subcommand and create a custom -// apptx encoding. Look at counter.go for an example -func RegisterTxPlugin(cmd cli.Command) { - AppTxCmd.Subcommands = append(AppTxCmd.Subcommands, cmd) +// Register a subcommand of TxCmd to craft transactions for plugins +func RegisterTxSubcommand(cmd cli.Command) { + TxCmd.Subcommands = append(TxCmd.Subcommands, cmd) } func cmdSendTx(c *cli.Context) error { diff --git a/cmd/basecoin/commands/utils.go b/cmd/commands/utils.go similarity index 100% rename from cmd/basecoin/commands/utils.go rename to cmd/commands/utils.go diff --git a/cmd/basecoin/commands/counter.go b/cmd/counter/cmd.go similarity index 54% rename from cmd/basecoin/commands/counter.go rename to cmd/counter/cmd.go index 83f98218cf..6b5e02ab55 100644 --- a/cmd/basecoin/commands/counter.go +++ b/cmd/counter/cmd.go @@ -1,48 +1,48 @@ -package commands +package main import ( "fmt" - "github.com/tendermint/basecoin/plugins/counter" - "github.com/tendermint/basecoin/types" wire "github.com/tendermint/go-wire" "github.com/urfave/cli" -) -var ( - CounterTxCmd = cli.Command{ - Name: "counter", - Usage: "Craft a transaction to the counter plugin", - Action: func(c *cli.Context) error { - return cmdCounterTx(c) - }, - Flags: []cli.Flag{ - ValidFlag, - }, - } - - CounterPluginFlag = cli.BoolFlag{ - Name: "counter-plugin", - Usage: "Enable the counter plugin", - } + "github.com/tendermint/basecoin/cmd/commands" + "github.com/tendermint/basecoin/plugins/counter" + "github.com/tendermint/basecoin/types" ) func init() { - RegisterTxPlugin(CounterTxCmd) - RegisterStartPlugin(CounterPluginFlag, - func() types.Plugin { return counter.New("counter") }) + commands.RegisterTxSubcommand(CounterTxCmd) + commands.RegisterStartPlugin("counter", func() types.Plugin { + return counter.New("counter") + }) } +var ( + ValidFlag = cli.BoolFlag{ + Name: "valid", + Usage: "Set valid field in CounterTx", + } + + CounterTxCmd = cli.Command{ + Name: "counter", + Usage: "Create, sign, and broadcast a transaction to the counter plugin", + Action: func(c *cli.Context) error { + return cmdCounterTx(c) + }, + Flags: append(commands.TxFlags, ValidFlag), + } +) + func cmdCounterTx(c *cli.Context) error { valid := c.Bool("valid") - parent := c.Parent() counterTx := counter.CounterTx{ Valid: valid, Fee: types.Coins{ { - Denom: parent.String("coin"), - Amount: int64(parent.Int("fee")), + Denom: c.String("coin"), + Amount: int64(c.Int("fee")), }, }, } @@ -52,5 +52,5 @@ func cmdCounterTx(c *cli.Context) error { data := wire.BinaryBytes(counterTx) name := "counter" - return AppTx(parent, name, data) + return commands.AppTx(c, name, data) } diff --git a/cmd/counter/main.go b/cmd/counter/main.go new file mode 100644 index 0000000000..11967841c1 --- /dev/null +++ b/cmd/counter/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "os" + + "github.com/tendermint/basecoin/cmd/commands" + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "counter" + app.Usage = "counter [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + commands.StartCmd, + commands.TxCmd, + commands.QueryCmd, + commands.AccountCmd, + } + app.Run(os.Args) +} From d54763965ea70bde0c22bac7cb579f2a09fba819 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 16:10:17 -0500 Subject: [PATCH 54/64] cli: key command --- cmd/adam/main.go | 1 + cmd/basecoin/main.go | 1 + cmd/commands/flags.go | 2 +- cmd/commands/ibc.go | 4 +- cmd/commands/key.go | 73 +++++++++++++++++++++++++++++++++++ cmd/commands/start.go | 10 ++--- cmd/commands/tx.go | 19 +++++---- cmd/counter/cmd.go | 4 +- cmd/counter/main.go | 1 + data/genesis.json | 2 +- data/key.json | 11 ++++++ data/key2.json | 11 ++++++ data/priv_validator.json | 17 -------- data/priv_validator2.json | 16 -------- docs/guide/basecoin-basics.md | 52 +++++++++---------------- 15 files changed, 135 insertions(+), 89 deletions(-) create mode 100644 cmd/commands/key.go create mode 100644 data/key.json create mode 100644 data/key2.json delete mode 100644 data/priv_validator.json delete mode 100644 data/priv_validator2.json diff --git a/cmd/adam/main.go b/cmd/adam/main.go index ef55d7aced..9be7255e7c 100644 --- a/cmd/adam/main.go +++ b/cmd/adam/main.go @@ -20,6 +20,7 @@ func main() { app.Commands = []cli.Command{ commands.StartCmd, commands.TxCmd, + commands.KeyCmd, commands.QueryCmd, commands.VerifyCmd, // TODO: move to merkleeyes? commands.BlockCmd, diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 3c31ae38fd..77f9827fdd 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -16,6 +16,7 @@ func main() { commands.StartCmd, commands.TxCmd, commands.QueryCmd, + commands.KeyCmd, commands.VerifyCmd, // TODO: move to merkleeyes? commands.BlockCmd, // TODO: move to adam? commands.AccountCmd, diff --git a/cmd/commands/flags.go b/cmd/commands/flags.go index dc91754687..4670ee9fcd 100644 --- a/cmd/commands/flags.go +++ b/cmd/commands/flags.go @@ -56,7 +56,7 @@ var ( FromFlag = cli.StringFlag{ Name: "from", - Value: "priv_validator.json", + Value: "key.json", Usage: "Path to a private key to sign the transaction", } diff --git a/cmd/commands/ibc.go b/cmd/commands/ibc.go index 1b513d4047..2427433195 100644 --- a/cmd/commands/ibc.go +++ b/cmd/commands/ibc.go @@ -20,9 +20,7 @@ import ( // Register the IBC plugin at start and for transactions func RegisterIBC() { RegisterTxSubcommand(IbcCmd) - RegisterStartPlugin("ibc", func() types.Plugin { - return ibc.New() - }) + RegisterStartPlugin("ibc", func() types.Plugin { return ibc.New() }) } //--------------------------------------------------------------------- diff --git a/cmd/commands/key.go b/cmd/commands/key.go new file mode 100644 index 0000000000..2ebd74afd4 --- /dev/null +++ b/cmd/commands/key.go @@ -0,0 +1,73 @@ +package commands + +import ( + "fmt" + "io/ioutil" + + "github.com/urfave/cli" + + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" +) + +var ( + KeyCmd = cli.Command{ + Name: "key", + Usage: "Manage keys", + ArgsUsage: "", + Subcommands: []cli.Command{NewKeyCmd}, + } + + NewKeyCmd = cli.Command{ + Name: "new", + Usage: "Create a new private key", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdNewKey(c) + }, + } +) + +func cmdNewKey(c *cli.Context) error { + key := genKey() + keyJSON := wire.JSONBytesPretty(key) + fmt.Println(string(keyJSON)) + return nil +} + +//--------------------------------------------- +// simple implementation of a key + +type Key struct { + Address []byte `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` +} + +// Implements Signer +func (k *Key) Sign(msg []byte) crypto.Signature { + return k.PrivKey.Sign(msg) +} + +// Generates a new validator with private key. +func genKey() *Key { + privKey := crypto.GenPrivKeyEd25519() + return &Key{ + Address: privKey.PubKey().Address(), + PubKey: privKey.PubKey(), + PrivKey: privKey, + } +} + +func LoadKey(filePath string) *Key { + keyJSONBytes, err := ioutil.ReadFile(filePath) + if err != nil { + cmn.Exit(err.Error()) + } + key := wire.ReadJSON(&Key{}, keyJSONBytes, &err).(*Key) + if err != nil { + cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) + } + return key +} diff --git a/cmd/commands/start.go b/cmd/commands/start.go index 9ce201a108..9996e551c9 100644 --- a/cmd/commands/start.go +++ b/cmd/commands/start.go @@ -43,15 +43,15 @@ var StartCmd = cli.Command{ } type plugin struct { - name string - init func() types.Plugin + name string + newPlugin func() types.Plugin } var plugins = []plugin{} // RegisterStartPlugin is used to enable a plugin -func RegisterStartPlugin(name string, initFunc func() types.Plugin) { - plugins = append(plugins, plugin{name: name, init: initFunc}) +func RegisterStartPlugin(name string, newPlugin func() types.Plugin) { + plugins = append(plugins, plugin{name: name, newPlugin: newPlugin}) } func cmdStart(c *cli.Context) error { @@ -73,7 +73,7 @@ func cmdStart(c *cli.Context) error { // register all plugins for _, p := range plugins { - basecoinApp.RegisterPlugin(p.init()) + basecoinApp.RegisterPlugin(p.newPlugin()) } // If genesis file exists, set key-value options diff --git a/cmd/commands/tx.go b/cmd/commands/tx.go index cba6e70639..9b8ce21cd8 100644 --- a/cmd/commands/tx.go +++ b/cmd/commands/tx.go @@ -82,18 +82,17 @@ func cmdSendTx(c *cli.Context) error { return errors.New("To address is invalid hex: " + err.Error()) } - // load the priv validator - // XXX: this is overkill for now, we need a keys solution - privVal := tmtypes.LoadPrivValidator(fromFile) + // load the priv key + privKey := LoadKey(fromFile) // get the sequence number for the tx - sequence, err := getSeq(c, privVal.Address) + sequence, err := getSeq(c, privKey.Address) if err != nil { return err } // craft the tx - input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) + input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) output := newOutput(to, coin, amount) tx := &types.SendTx{ Gas: int64(gas), @@ -104,7 +103,7 @@ func cmdSendTx(c *cli.Context) error { // sign that puppy signBytes := tx.SignBytes(chainID) - tx.Inputs[0].Signature = privVal.Sign(signBytes) + tx.Inputs[0].Signature = privKey.Sign(signBytes) fmt.Println("Signed SendTx:") fmt.Println(string(wire.JSONBytes(tx))) @@ -134,14 +133,14 @@ func AppTx(c *cli.Context, name string, data []byte) error { gas, fee := c.Int("gas"), int64(c.Int("fee")) chainID := c.String("chain_id") - privVal := tmtypes.LoadPrivValidator(fromFile) + privKey := tmtypes.LoadPrivValidator(fromFile) - sequence, err := getSeq(c, privVal.Address) + sequence, err := getSeq(c, privKey.Address) if err != nil { return err } - input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) + input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) tx := &types.AppTx{ Gas: int64(gas), Fee: types.Coin{coin, fee}, @@ -150,7 +149,7 @@ func AppTx(c *cli.Context, name string, data []byte) error { Data: data, } - tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID)) + tx.Input.Signature = privKey.Sign(tx.SignBytes(chainID)) fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) diff --git a/cmd/counter/cmd.go b/cmd/counter/cmd.go index 6b5e02ab55..c125feb4f6 100644 --- a/cmd/counter/cmd.go +++ b/cmd/counter/cmd.go @@ -13,9 +13,7 @@ import ( func init() { commands.RegisterTxSubcommand(CounterTxCmd) - commands.RegisterStartPlugin("counter", func() types.Plugin { - return counter.New("counter") - }) + commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() }) } var ( diff --git a/cmd/counter/main.go b/cmd/counter/main.go index 11967841c1..72395a9b07 100644 --- a/cmd/counter/main.go +++ b/cmd/counter/main.go @@ -15,6 +15,7 @@ func main() { app.Commands = []cli.Command{ commands.StartCmd, commands.TxCmd, + commands.KeyCmd, commands.QueryCmd, commands.AccountCmd, } diff --git a/data/genesis.json b/data/genesis.json index 7aea6cb9bc..3a4c177526 100644 --- a/data/genesis.json +++ b/data/genesis.json @@ -1,7 +1,7 @@ [ "base/chainID", "test_chain_id", "base/account", { - "pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"], + "pub_key": [1, "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"], "coins": [ { "denom": "blank", diff --git a/data/key.json b/data/key.json new file mode 100644 index 0000000000..7a8c075604 --- /dev/null +++ b/data/key.json @@ -0,0 +1,11 @@ +{ + "address": "1B1BE55F969F54064628A63B9559E7C21C925165", + "priv_key": [ + 1, + "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D10000000000000000000000000000000000000000000000000000000000000000" + ], + "pub_key": [ + 1, + "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" + ] +} diff --git a/data/key2.json b/data/key2.json new file mode 100644 index 0000000000..f6f8d3693a --- /dev/null +++ b/data/key2.json @@ -0,0 +1,11 @@ +{ + "address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", + "priv_key": [ + 1, + "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A80000000000000000000000000000000000000000000000000000000000000000" + ], + "pub_key": [ + 1, + "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" + ] +} diff --git a/data/priv_validator.json b/data/priv_validator.json deleted file mode 100644 index 15d7919240..0000000000 --- a/data/priv_validator.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "address": "D397BC62B435F3CF50570FBAB4340FE52C60858F", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, - "priv_key": [ - 1, - "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" - ], - "pub_key": [ - 1, - "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" - ] -} - diff --git a/data/priv_validator2.json b/data/priv_validator2.json deleted file mode 100644 index 08256d1fd8..0000000000 --- a/data/priv_validator2.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "address": "4793A333846E5104C46DD9AB9A00E31821B2F301", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, - "priv_key": [ - 1, - "13A04A552ABAA2CCFA1F618CF9C97F1FD59FC3EE4968FE87DF3637C9B0F2FAAA93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E" - ], - "pub_key": [ - 1, - "93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E" - ] -} diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index 34aaae4046..c60480ed4f 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -35,6 +35,9 @@ The directory contains a genesis file and two private keys. You can generate your own private keys with `tendermint gen_validator`, and construct the `genesis.json` as you like. +Note, however, that you must be careful with the `chain_id` field, +as every transaction must contain the correct `chain_id` +(default is `test_chain_id`). ## Start @@ -65,26 +68,28 @@ tendermint node ``` In either case, you should see blocks start streaming in! +Note, however, that currently basecoin currently requires the +`develop` branch of tendermint for this to work. ## Send transactions Now we are ready to send some transactions. If you take a look at the `genesis.json` file, you will see one account listed there. -This account corresponds to the private key in `priv_validator.json`. -We also included the private key for another account, in `priv_validator2.json`. +This account corresponds to the private key in `key.json`. +We also included the private key for another account, in `key2.json`. Let's check the balance of these two accounts: ``` -basecoin account 0xD397BC62B435F3CF50570FBAB4340FE52C60858F -basecoin account 0x4793A333846E5104C46DD9AB9A00E31821B2F301 +basecoin account 0x1B1BE55F969F54064628A63B9559E7C21C925165 +basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 ``` The first account is flush with cash, while the second account doesn't exist. Let's send funds from the first account to the second: ``` -basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 10 +basecoin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 10 ``` By default, the CLI looks for a `priv_validator.json` to sign the transaction with, @@ -94,13 +99,13 @@ To specify a different key, we can use the `--from` flag. Now if we check the second account, it should have `10` coins! ``` -basecoin account 0x4793A333846E5104C46DD9AB9A00E31821B2F301 +basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 ``` We can send some of these coins back like so: ``` -basecoin sendtx --to 0xD397BC62B435F3CF50570FBAB4340FE52C60858F --from priv_validator2.json --amount 5 +basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 5 ``` Note how we use the `--from` flag to select a different account to send from. @@ -108,38 +113,19 @@ Note how we use the `--from` flag to select a different account to send from. If we try to send too much, we'll get an error: ``` -basecoin sendtx --to 0xD397BC62B435F3CF50570FBAB4340FE52C60858F --from priv_validator2.json --amount 100 +basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 100 ``` -See `basecoin sendtx --help` for additional details. +See `basecoin tx send --help` for additional details. ## Plugins - -The `sendtx` command creates and broadcasts a transaction of type `SendTx`, +The `tx send` command creates and broadcasts a transaction of type `SendTx`, which is only useful for moving tokens around. Fortunately, Basecoin supports another transaction type, the `AppTx`, which can trigger code registered via a plugin system. -For instance, we implemented a simple plugin called `counter`, -which just counts the number of transactions it processed. -To run it, kill the other processes, run `tendermint unsafe_reset_all`, and then - -``` -basecoin start --in-proc --counter-plugin -``` - -Now in another window, we can send transactions with: - -``` -TODO -``` - -## Next steps - -1. Learn more about [Basecoin's design](basecoin-design.md) -1. Make your own [cryptocurrency using Basecoin plugins](example-counter.md) -1. Learn more about [plugin design](plugin-design.md) -1. See some [more example applications](more-examples.md) -1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) -1. [Deploy testnets](deployment.md) running your basecoin application. +In the [next tutorial](example-counter.md), +we demonstrate how to implement a plugin +and extend the CLI to support new transaction types! +But first, you may want to learn a bit more about [Basecoin's design](basecoin-design.md) From 7335c8287c3fcc6d907a2695d3bd49e2fba27868 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 16:12:18 -0500 Subject: [PATCH 55/64] docs: example-plugin --- README.md | 4 +- docs/guide/example-counter.md | 1 - docs/guide/example-plugin.md | 419 ++++++++++++++++++++++++ docs/guide/src/example-plugin/cmd.go | 36 ++ docs/guide/src/example-plugin/main.go | 23 ++ docs/guide/src/example-plugin/plugin.go | 80 +++++ plugins/counter/counter.go | 4 +- 7 files changed, 562 insertions(+), 5 deletions(-) delete mode 100644 docs/guide/example-counter.md create mode 100644 docs/guide/example-plugin.md create mode 100644 docs/guide/src/example-plugin/cmd.go create mode 100644 docs/guide/src/example-plugin/main.go create mode 100644 docs/guide/src/example-plugin/plugin.go diff --git a/README.md b/README.md index b6c468aa60..a43c45d8a7 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ This will create the `basecoin` binary in `$GOPATH/bin`. The basecoin CLI can be used to start a stand-alone basecoin instance (`basecoin start`), or to start basecoin with tendermint in the same process (`basecoin start --in-proc`). -It can also be used to send transactions, eg. `basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` +It can also be used to send transactions, eg. `basecoin tx send --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` See `basecoin --help` and `basecoin [cmd] --help` for more details`. ## Learn more 1. Getting started with the [Basecoin tool](/docs/guide/basecoin-basics.md) 1. Learn more about [Basecoin's design](/docs/guide/basecoin-design.md) -1. Make your own [cryptocurrency using Basecoin plugins](/docs/guide/example-counter.md) +1. Extend Basecoin [using the plugin system](/docs/guide/example-plugin.md) 1. Learn more about [plugin design](/docs/guide/plugin-design.md) 1. See some [more example applications](/docs/guide/more-examples.md) 1. Learn how to use [InterBlockchain Communication (IBC)](/docs/guide/ibc.md) diff --git a/docs/guide/example-counter.md b/docs/guide/example-counter.md deleted file mode 100644 index d97b73f4ff..0000000000 --- a/docs/guide/example-counter.md +++ /dev/null @@ -1 +0,0 @@ -Rigel explains how to build your own basecoin-based app diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md new file mode 100644 index 0000000000..1db3feed6e --- /dev/null +++ b/docs/guide/example-plugin.md @@ -0,0 +1,419 @@ +# Basecoin Example Plugin + +In the [previous tutorial](basecoin-basics.md), +we saw how to start a Basecoin blockchain and use the CLI to send transactions. +Here, we will demonstrate how to extend the blockchain and CLI to support a simple plugin. + +## Overview + +Creating a new plugin and CLI to support it requires a little bit of boilerplate, but not much. +For convenience, we've implemented an extremely simple example plugin that can be easily modified. +The example is under `docs/guide/src/example-plugin`. +To build your own plugin, copy this folder to a new location and start modifying it there. + +Let's take a look at the files in `docs/guide/src/example-plugin`: + +``` +cmd.go +main.go +plugin.go +``` + +The `main.go` is very simple and does not need to be changed: + +``` +func main() { + app := cli.NewApp() + app.Name = "example-plugin" + app.Usage = "example-plugin [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + commands.StartCmd, + commands.TxCmd, + commands.KeyCmd, + commands.QueryCmd, + commands.AccountCmd, + } + app.Run(os.Args) +} +``` + +It creates the CLI, exactly like the `basecoin` one. +However, if we want our plugin to be active, +we need to make sure it is registered with the application. +In addition, if we want to send transactions to our plugin, +we need to add a new command to the CLI. +This is where the `cmd.go` comes in. + +## Commands + +First, we register the plugin: + + +``` +func init() { + commands.RegisterTxSubcommand(ExamplePluginTxCmd) + commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() }) +} +``` + +This creates a new subcommand under `tx` (defined below), +and ensures the plugin is activated when we start the app. +Now we actually define the new command: + +``` +var ( + ExampleFlag = cli.BoolFlag{ + Name: "valid", + Usage: "Set this to make the transaction valid", + } + + ExamplePluginTxCmd = cli.Command{ + Name: "example", + Usage: "Create, sign, and broadcast a transaction to the example plugin", + Action: func(c *cli.Context) error { + return cmdExamplePluginTx(c) + }, + Flags: append(commands.TxFlags, ExampleFlag), + } +) + +func cmdExamplePluginTx(c *cli.Context) error { + exampleFlag := c.Bool("valid") + exampleTx := ExamplePluginTx{exampleFlag} + return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx)) +} +``` + +It's a simple command with one flag, which is just a boolean. +However, it actually inherits more flags from the Basecoin framework: + +``` +Flags: append(commands.TxFlags, ExampleFlag), +``` + +The `commands.TxFlags` is defined in `cmd/commands/tx.go`: + +``` +var TxFlags = []cli.Flag{ + NodeFlag, + ChainIDFlag, + + FromFlag, + + AmountFlag, + CoinFlag, + GasFlag, + FeeFlag, + SeqFlag, +} +``` + +It adds all the default flags for a Basecoin transaction. + +If we now compile and run our program, we can see all the options: + +``` +cd $GOPATH/src/github.com/tendermint/basecoin +go install ./docs/guide/src/example-plugin +example-plugin tx example --help +``` + +The output: + +``` +NAME: + example-plugin tx example - Create, sign, and broadcast a transaction to the example plugin + +USAGE: + example-plugin tx example [command options] [arguments...] + +OPTIONS: + --node value Tendermint RPC address (default: "tcp://localhost:46657") + --chain_id value ID of the chain for replay protection (default: "test_chain_id") + --from value Path to a private key to sign the transaction (default: "key.json") + --amount value Amount of coins to send in the transaction (default: 0) + --coin value Specify a coin denomination (default: "blank") + --gas value The amount of gas for the transaction (default: 0) + --fee value The transaction fee (default: 0) + --sequence value Sequence number for the account (default: 0) + --valid Set this to make the transaction valid +``` + +Cool, eh? + +Before we move on to `plugin.go`, let's look at the `cmdExamplePluginTx` function in `cmd.go`: + +``` +func cmdExamplePluginTx(c *cli.Context) error { + exampleFlag := c.Bool("valid") + exampleTx := ExamplePluginTx{exampleFlag} + return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx)) +} +``` + +We read the flag from the CLI library, and then create the example transaction. +Remember that Basecoin itself only knows about two transaction types, `SendTx` and `AppTx`. +All plugin data must be serialized (ie. encoded as a byte-array) +and sent as data in an `AppTx`. The `commands.AppTx` function does this for us - +it creates an `AppTx` with the corresponding data, signs it, and sends it on to the blockchain. + +## RunTx + +Ok, now we're ready to actually look at the implementation of the plugin in `plugin.go`. +Note I'll leave out some of the methods as they don't serve any purpose for this example, +but are necessary boilerplate. +Your plugin may have additional requirements that utilize these other plugins. +Here's what's relevant for us: + +``` +type ExamplePluginState struct { + Counter int +} + +type ExamplePluginTx struct { + Valid bool +} + +type ExamplePlugin struct { + name string +} + +func (ep *ExamplePlugin) Name() string { + return ep.name +} + +func (ep *ExamplePlugin) StateKey() []byte { + return []byte("ExamplePlugin.State") +} + +func NewExamplePlugin() *ExamplePlugin { + return &ExamplePlugin{ + name: "example-plugin", + } +} + +func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string) (log string) { + return "" +} + +func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { + + // Decode tx + var tx ExamplePluginTx + err := wire.ReadBinaryBytes(txBytes, &tx) + if err != nil { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + } + + // Validate tx + if !tx.Valid { + return abci.ErrInternalError.AppendLog("Valid must be true") + } + + // Load PluginState + var pluginState ExamplePluginState + stateBytes := store.Get(ep.StateKey()) + if len(stateBytes) > 0 { + err = wire.ReadBinaryBytes(stateBytes, &pluginState) + if err != nil { + return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error()) + } + } + + //App Logic + pluginState.Counter += 1 + + // Save PluginState + store.Set(ep.StateKey(), wire.BinaryBytes(pluginState)) + + return abci.OK +} +``` + +All we're doing here is defining a state and transaction type for our plugin, +and then using the `RunTx` method to define how the transaction updates the state. +Let's break down `RunTx` in parts. First, we deserialize the transaction: + + +``` +// Decode tx +var tx ExamplePluginTx +err := wire.ReadBinaryBytes(txBytes, &tx) +if err != nil { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) +} +``` + +The transaction is expected to be serialized according to Tendermint's "wire" format, +as defined in the `github.com/tendermint/go-wire` package. +If it's not encoded properly, we return an error. + + +If the transaction deserializes currectly, we can now check if it's valid: + +``` +// Validate tx +if !tx.Valid { + return abci.ErrInternalError.AppendLog("Valid must be true") +} +``` + +The transaction is valid if the `Valid` field is set, otherwise it's not - simple as that. +Finally, we can update the state. In this example, the state simply counts how many valid transactions +we've processed. But the state itself is serialized and kept in some `store`, which is typically a Merkle tree. +So first we have to load the state from the store and deserialize it: + +``` +// Load PluginState +var pluginState ExamplePluginState +stateBytes := store.Get(ep.StateKey()) +if len(stateBytes) > 0 { + err = wire.ReadBinaryBytes(stateBytes, &pluginState) + if err != nil { + return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error()) + } +} +``` + +Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`. +Finally, we can update the state's `Counter`, and save the state back to the store: + +``` +//App Logic +pluginState.Counter += 1 + +// Save PluginState +store.Set(ep.StateKey(), wire.BinaryBytes(pluginState)) + +return abci.OK +``` + +And that's it! Now that we have a simple plugin, let's see how to run it. + +## Running your plugin + +In the [previous tutorial](basecoin-basics.md), +we used a pre-generated `genesis.json` and `priv_validator.json` for the application. +This time, let's make our own. + +First, let's create a new directory and change into it: + +``` +mkdir example-data +cd example-data +``` + +Now, let's create a new private key: + +``` +example-plugin key new > key.json +``` + +Here's what my `key.json looks like: + +``` +{ + "address": "15F591CA434CFCCBDEC1D206F3ED3EBA207BFE7D", + "priv_key": [ + 1, + "737C629667A9EAADBB8E7CF792D5A8F63AA4BB51E06457DDD7FDCC6D7412AAAD43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3" + ], + "pub_key": [ + 1, + "43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3" + ] +} +``` + +Now we can make a `genesis.json` file and add an account with out public key: + +``` +[ + "base/chainID", "example-chain", + "base/account", { + "pub_key": [1, "43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3"], + "coins": [ + { + "denom": "gold", + "amount": 1000000000, + } + ] + } +] +``` + +Here we've granted ourselves `1000000000` units of the `gold` token. + +Before we can start the blockchain, we must initialize and/or reset the tendermint state for a new blockchain: + +``` +tendermint init +tendermint unsafe_reset_all +``` + +Great, now we're ready to go. +To start the blockchain, simply run + +``` +example-plugin start --in-proc +``` + +In another window, we can try sending some transactions: + +``` +example-plugin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --amount 100 --coin gold --chain_id example-chain +``` + +Note the `--coin` and `--chain_id` flags. In the [previous tutorial](basecoin-basics.md), +we didn't need them because we were using the default coin type ("blank") and chain ID ("test_chain_id"). +Now that we're using custom values, we need to specify them explicitly on the command line. + +Ok, so that's how we can send a `SendTx` transaction using our `example-plugin` CLI, +but we were already able to do that with the `basecoin` CLI. +With our new CLI, however, we can also send an `ExamplePluginTx`: + +``` +example-plugin tx example --amount 1 --coin gold --chain_id example-chain +``` + +The transaction is invalid! That's because we didn't specify the `--valid` flag: + +``` +example-plugin tx example --valid --amount 1 --coin gold --chain_id example-chain +``` + +Tada! We successfuly created, signed, broadcast, and processed our custom transaction type. + +## Query + +Now that we've sent a transaction to update the state, let's query for the state. +Recall that the state is stored under the key `ExamplePlugin.State`: + + +``` +example-plugin query ExamplePlugin.State +``` + +Note the `"value":"0101"` piece. This is the serialized form of the state, +which contains only an integer. +If we send another transaction, and then query again, we'll see the value increment: + +``` +example-plugin tx example --valid --amount 1 --coin gold --chain_id example-chain +example-plugin query ExamplePlugin.State +``` + +Neat, right? Notice how the result of the query comes with a proof. +This is a Merkle proof that the state is what we say it is. +In a latter [tutorial on Interblockchain Communication](ibc.md), +we'll put this proof to work! + +## Conclusion + +In this tutorial we demonstrated how to create a new plugin and how to extend the +basecoin CLI to activate the plugin on the blockchain and to send transactions to it. +Hopefully by now you have some ideas for your own plugin, and feel comfortable implementing them. +In the [next tutorial](more-examples.md), we tour through some other plugin examples, +adding features for minting new coins, voting, and changin the Tendermint validator set. +But first, you may want to learn a bit more about [the design of plugins](plugin-design.md) diff --git a/docs/guide/src/example-plugin/cmd.go b/docs/guide/src/example-plugin/cmd.go new file mode 100644 index 0000000000..b2fb3ecdae --- /dev/null +++ b/docs/guide/src/example-plugin/cmd.go @@ -0,0 +1,36 @@ +package main + +import ( + wire "github.com/tendermint/go-wire" + "github.com/urfave/cli" + + "github.com/tendermint/basecoin/cmd/commands" + "github.com/tendermint/basecoin/types" +) + +func init() { + commands.RegisterTxSubcommand(ExamplePluginTxCmd) + commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() }) +} + +var ( + ExampleFlag = cli.BoolFlag{ + Name: "valid", + Usage: "Set this to make the transaction valid", + } + + ExamplePluginTxCmd = cli.Command{ + Name: "example", + Usage: "Create, sign, and broadcast a transaction to the example plugin", + Action: func(c *cli.Context) error { + return cmdExamplePluginTx(c) + }, + Flags: append(commands.TxFlags, ExampleFlag), + } +) + +func cmdExamplePluginTx(c *cli.Context) error { + exampleFlag := c.Bool("valid") + exampleTx := ExamplePluginTx{exampleFlag} + return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx)) +} diff --git a/docs/guide/src/example-plugin/main.go b/docs/guide/src/example-plugin/main.go new file mode 100644 index 0000000000..e1347334c1 --- /dev/null +++ b/docs/guide/src/example-plugin/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "os" + + "github.com/tendermint/basecoin/cmd/commands" + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "example-plugin" + app.Usage = "example-plugin [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + commands.StartCmd, + commands.TxCmd, + commands.KeyCmd, + commands.QueryCmd, + commands.AccountCmd, + } + app.Run(os.Args) +} diff --git a/docs/guide/src/example-plugin/plugin.go b/docs/guide/src/example-plugin/plugin.go new file mode 100644 index 0000000000..81bb365e50 --- /dev/null +++ b/docs/guide/src/example-plugin/plugin.go @@ -0,0 +1,80 @@ +package main + +import ( + abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/types" + "github.com/tendermint/go-wire" +) + +type ExamplePluginState struct { + Counter int +} + +type ExamplePluginTx struct { + Valid bool +} + +type ExamplePlugin struct { + name string +} + +func (ep *ExamplePlugin) Name() string { + return ep.name +} + +func (ep *ExamplePlugin) StateKey() []byte { + return []byte("ExamplePlugin.State") +} + +func NewExamplePlugin() *ExamplePlugin { + return &ExamplePlugin{ + name: "example-plugin", + } +} + +func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string) (log string) { + return "" +} + +func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { + + // Decode tx + var tx ExamplePluginTx + err := wire.ReadBinaryBytes(txBytes, &tx) + if err != nil { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + } + + // Validate tx + if !tx.Valid { + return abci.ErrInternalError.AppendLog("Valid must be true") + } + + // Load PluginState + var pluginState ExamplePluginState + stateBytes := store.Get(ep.StateKey()) + if len(stateBytes) > 0 { + err = wire.ReadBinaryBytes(stateBytes, &pluginState) + if err != nil { + return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error()) + } + } + + //App Logic + pluginState.Counter += 1 + + // Save PluginState + store.Set(ep.StateKey(), wire.BinaryBytes(pluginState)) + + return abci.OK +} + +func (ep *ExamplePlugin) InitChain(store types.KVStore, vals []*abci.Validator) { +} + +func (ep *ExamplePlugin) BeginBlock(store types.KVStore, height uint64) { +} + +func (ep *ExamplePlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator { + return nil +} diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 9c115089b5..e5d86a56cf 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -32,9 +32,9 @@ func (cp *CounterPlugin) StateKey() []byte { return []byte(fmt.Sprintf("CounterPlugin{name=%v}.State", cp.name)) } -func New(name string) *CounterPlugin { +func New() *CounterPlugin { return &CounterPlugin{ - name: name, + name: "counter", } } From b7ef4652ed98179f9ee0377571bf723584bfbf58 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 16:28:41 -0500 Subject: [PATCH 56/64] docs: update links and flow --- docs/guide/basecoin-basics.md | 2 +- docs/guide/basecoin-design.md | 40 ++++++++++++++++++++--------------- docs/guide/example-plugin.md | 5 +++-- docs/guide/plugin-design.md | 9 ++++---- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index c60480ed4f..fc6e62bb29 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -125,7 +125,7 @@ which is only useful for moving tokens around. Fortunately, Basecoin supports another transaction type, the `AppTx`, which can trigger code registered via a plugin system. -In the [next tutorial](example-counter.md), +In the [next tutorial](example-plugin.md), we demonstrate how to implement a plugin and extend the CLI to support new transaction types! But first, you may want to learn a bit more about [Basecoin's design](basecoin-design.md) diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md index 344bb62bb9..fd675e0a57 100644 --- a/docs/guide/basecoin-design.md +++ b/docs/guide/basecoin-design.md @@ -14,11 +14,26 @@ This type of account was directly inspired by accounts in Ethereum, and is unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). Note Basecoin is a multi-asset cryptocurrency, so each account can have many different kinds of tokens. +``` +type Account struct { + PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known. + Sequence int `json:"sequence"` + Balance Coins `json:"coins"` +} + +type Coins []Coin + +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} +``` + Accounts are serialized and stored in a Merkle tree using the account's address as the key, -where the address is the RIPEMD160 hash of the public key. In particular, an account is stored in the Merkle tree under the key `base/a/
`, -where `
` is the 20-byte address of the account. -We use an implementation of a Merkle, balanced, binary search tree, also known as an [IAVL tree](https://github.com/tendermint/go-merkle). +where `
` is the address of the account. +In Basecoin, the address of an account is the 20-byte `RIPEMD160` hash of the public key. +The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an [IAVL tree](https://github.com/tendermint/go-merkle). ## Transactions @@ -47,14 +62,6 @@ type TxOutput struct { Address []byte `json:"address"` // Hash of the PubKey Coins Coins `json:"coins"` // } - -type Coins []Coin - -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - ``` There are a few things to note. First, the `SendTx` includes a field for `Gas` and `Fee`. @@ -72,10 +79,9 @@ as it uses a different elliptic curve scheme which enables the public key to be Finally, note that the use of multiple inputs and multiple outputs allows us to send many different types of tokens between many different accounts at once in an atomic transaction. Thus, the `SendTx` can serve as a basic unit of decentralized exchange. -## Next steps +## Plugins -1. Make your own [cryptocurrency using Basecoin plugins](example-counter.md) -1. Learn more about [plugin design](plugin-design.md) -1. See some [more example applications](more-examples.md) -1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) -1. [Deploy testnets](deployment.md) running your basecoin application. +Basecoin actually defines a second transaction type, the `AppTx`, +which enables the functionality to be extended via custom plugins. +To learn more about the `AppTx` and plugin system, see the [plugin design document](plugin-design.md). +To implement your first plugin, see [plugin tutorial](example-plugin.md). diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md index 1db3feed6e..ec31890194 100644 --- a/docs/guide/example-plugin.md +++ b/docs/guide/example-plugin.md @@ -409,11 +409,12 @@ This is a Merkle proof that the state is what we say it is. In a latter [tutorial on Interblockchain Communication](ibc.md), we'll put this proof to work! -## Conclusion +## Next Stpes In this tutorial we demonstrated how to create a new plugin and how to extend the basecoin CLI to activate the plugin on the blockchain and to send transactions to it. Hopefully by now you have some ideas for your own plugin, and feel comfortable implementing them. + In the [next tutorial](more-examples.md), we tour through some other plugin examples, adding features for minting new coins, voting, and changin the Tendermint validator set. -But first, you may want to learn a bit more about [the design of plugins](plugin-design.md) +But first, you may want to learn a bit more about [the design of the plugin system](plugin-design.md) diff --git a/docs/guide/plugin-design.md b/docs/guide/plugin-design.md index 127c51960b..ff88bca77b 100644 --- a/docs/guide/plugin-design.md +++ b/docs/guide/plugin-design.md @@ -64,9 +64,8 @@ and also to store arbitrary other information in the state. In this way, the functionality and state of a Basecoin-derrived cryptocurrency can be greatly extended. One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin! +## Examples -## Next steps - -1. Examples of [Basecoin plugins](more-examples.md) -1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) -1. [Deploy testnets](deployment.md) running your basecoin application. +To get started with plugins, see [the example-plugin tutorial](example-plugin.md). +For more examples, see [the advanced plugin tutorial](more-examples.md). +If you're really brave, see the tutorial on [implementing Interblockchain Communication as a plugin](ibc.md). From 53f34f45ffc884aa39facae1181809d96a866551 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 19:54:28 -0500 Subject: [PATCH 57/64] docs: more examples --- docs/guide/more-examples.md | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/docs/guide/more-examples.md b/docs/guide/more-examples.md index ad17c77a66..7df37ca0be 100644 --- a/docs/guide/more-examples.md +++ b/docs/guide/more-examples.md @@ -1,25 +1,19 @@ # Plugin Examples -Now that we've seen how to use Basecoin, talked about the design, -and looked at how to implement a simple plugin, let's take a look at some more interesting examples. +Now that we've seen [how to write a simple plugin](example-plugin.md) +and taken a look at [how the plugin system is designed](plugin-design.md), +it's time for some more advanced examples. -## Mintcoin - -Basecoin does not provide any functionality for adding new tokens to the system. -The state is endowed with tokens by a `genesis.json` file which is read once when the system is first started. -From there, tokens can be sent to other accounts, even new accounts, but it's impossible to add more tokens to the system. -For this, we need a plugin. - -The `mintcoin` plugin lets you register one or more accounts as "central bankers", -who can unilaterally issue more currency into the system. - -## Financial Instruments - -Sure, printing money and sending it is nice, but sometimes I don't fully trust the guy at the other end. Maybe we could add an escrow service? Or how about options for currency trading, since we support multiple currencies? No problem, this is also just a plugin away. Checkout our [trader application](./trader). - -**Running code, still WIP** - -## IBC - -Now, let's hook up your personal crypto-currency with the wide world of other currencies, in a distributed, proof-of-stake based exchange. Hard, you say? Well half the work is already done for you with the [IBC, InterBlockchain Communication, plugin](./ibc.md). Now, we just need to get cosmos up and running and time to go and trade. +For now, most examples are contained in the `github.com/tendermint/basecoin-examples` repository. +In particular, we have the following: +1. [Mintcoin][0] - a plugin for issuing new Basecoin tokens +2. [Trader][1] - a plugin for adding escrow and options features to Basecoin +3. [Stakecoin][2] - a plugin for bonding and unbonding Tendermint validators and updating the validator set accordingly +4. [PayToVote][3] - a plugin for creating issues and voting on them +5. [IBC][4] - a plugin for facilitating InterBlockchain Communication +[0]: https://github.com/tendermint/basecoin-examples/tree/develop/mintcoin +[1]: https://github.com/tendermint/basecoin-examples/tree/develop/trader +[2]: https://github.com/tendermint/basecoin-examples/tree/develop/stake +[3]: https://github.com/tendermint/basecoin-examples/tree/develop/paytovote +[4]: ibc.md From b3834bc5d079a941070e5b7927825fb976bfbf27 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 20:35:43 -0500 Subject: [PATCH 58/64] docs: update ibc --- cmd/commands/ibc.go | 3 -- demo/clean.sh | 2 +- .../{priv_validator.json => key.json} | 5 --- .../{priv_validator.json => key.json} | 5 --- demo/start.sh | 25 +++++++------ docs/guide/ibc.md | 35 +++++++++++++------ 6 files changed, 39 insertions(+), 36 deletions(-) rename demo/data/chain1/basecoin/{priv_validator.json => key.json} (74%) rename demo/data/chain2/basecoin/{priv_validator.json => key.json} (74%) diff --git a/cmd/commands/ibc.go b/cmd/commands/ibc.go index 2427433195..f8d0e0b619 100644 --- a/cmd/commands/ibc.go +++ b/cmd/commands/ibc.go @@ -142,9 +142,6 @@ var ( IbcPacketTxCmd = cli.Command{ Name: "packet", Usage: "Send a new packet via IBC", - Flags: []cli.Flag{ - // - }, Subcommands: []cli.Command{ IbcPacketCreateTx, IbcPacketPostTx, diff --git a/demo/clean.sh b/demo/clean.sh index e2d519337d..7d18923212 100644 --- a/demo/clean.sh +++ b/demo/clean.sh @@ -1,6 +1,6 @@ #! /bin/bash -killall -9 basecoin tendermint +killall -9 adam tendermint TMROOT=./data/chain1/tendermint tendermint unsafe_reset_all TMROOT=./data/chain2/tendermint tendermint unsafe_reset_all diff --git a/demo/data/chain1/basecoin/priv_validator.json b/demo/data/chain1/basecoin/key.json similarity index 74% rename from demo/data/chain1/basecoin/priv_validator.json rename to demo/data/chain1/basecoin/key.json index 15d7919240..e610ba89d2 100644 --- a/demo/data/chain1/basecoin/priv_validator.json +++ b/demo/data/chain1/basecoin/key.json @@ -1,10 +1,5 @@ { "address": "D397BC62B435F3CF50570FBAB4340FE52C60858F", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, "priv_key": [ 1, "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" diff --git a/demo/data/chain2/basecoin/priv_validator.json b/demo/data/chain2/basecoin/key.json similarity index 74% rename from demo/data/chain2/basecoin/priv_validator.json rename to demo/data/chain2/basecoin/key.json index 8f2eccadeb..90761696de 100644 --- a/demo/data/chain2/basecoin/priv_validator.json +++ b/demo/data/chain2/basecoin/key.json @@ -1,10 +1,5 @@ { "address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, "priv_key": [ 1, "22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D" diff --git a/demo/start.sh b/demo/start.sh index da617910fd..b856852dea 100644 --- a/demo/start.sh +++ b/demo/start.sh @@ -18,19 +18,19 @@ echo "CHAIN_ID1: $CHAIN_ID1" echo "CHAIN_ID2: $CHAIN_ID2" # make reusable chain flags -CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json" -CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657" +CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/key.json" +CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.json --node tcp://localhost:36657" echo "" echo "... starting chains" echo "" # start the first node TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & -basecoin start --ibc-plugin --dir ./data/chain1/basecoin &> chain1_basecoin.log & +adam start --dir ./data/chain1/basecoin &> chain1_basecoin.log & # start the second node TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & -basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/basecoin &> chain2_basecoin.log & +adam start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log & echo "" echo "... waiting for chains to start" @@ -40,20 +40,20 @@ sleep 10 echo "... registering chain1 on chain2" echo "" # register chain1 on chain2 -basecoin ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json +adam tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json echo "" echo "... creating egress packet on chain1" echo "" # create a packet on chain1 destined for chain2 PAYLOAD="DEADBEEF" #TODO -basecoin ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1 +adam tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1 echo "" echo "... querying for packet data" echo "" # query for the packet data and proof -QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) +QUERY_RESULT=$(adam query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) HEIGHT=$(echo $QUERY_RESULT | jq .height) PACKET=$(echo $QUERY_RESULT | jq .value) PROOF=$(echo $QUERY_RESULT | jq .proof) @@ -75,7 +75,7 @@ echo "" echo "... querying for block data" echo "" # get the header and commit for the height -HEADER_AND_COMMIT=$(basecoin block $HEIGHT) +HEADER_AND_COMMIT=$(adam block $HEIGHT) HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header) HEADER=$(removeQuotes $HEADER) COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit) @@ -89,16 +89,19 @@ echo "" echo "... updating state of chain1 on chain2" echo "" # update the state of chain1 on chain2 -basecoin ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT +adam tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT echo "" echo "... posting packet from chain1 on chain2" echo "" # post the packet from chain1 to chain2 -basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF +adam tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF echo "" echo "... checking if the packet is present on chain2" echo "" # query for the packet on chain2 ! -basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 +adam query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 + +echo "" +echo "DONE!" diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md index 96022310f3..fe98941c17 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -9,6 +9,8 @@ and here we'll show you how to use the Basecoin IBC-plugin to send a packet of d Please note, this tutorial assumes you are familiar with [Basecoin plugins](/docs/guide/plugin-design.md) and with the [Basecoin CLI](/docs/guide/basecoin-basics), but we'll explain how IBC works. +You may also want to see the tutorials on [a simple example plugin](example-plugin.md) +and the list of [more advanced plugins](more-examples.md). The IBC plugin defines a new set of transactions as subtypes of the `AppTx`. The plugin's functionality is accessed by setting the `AppTx.Name` field to `"IBC"`, and setting the `Data` field to the serialized IBC transaction type. @@ -179,7 +181,10 @@ Now that we have all the background knowledge, let's actually walk through the t Make sure you have installed [tendermint](https://tendermint.com/intro/getting-started/download) and -[basecoin](/docs/guide/install.md). +[adam](/docs/guide/install.md). + +`adam` is the name for the program that will become the Cosmos Hub. +We call it Adam because it's the first blockchain in [the Cosmos Network](https://cosmos.network). Now let's start the two blockchains. In this tutorial, each chain will have only a single validator, @@ -196,14 +201,14 @@ We can start the two chains as follows: ``` TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & -basecoin start --ibc-plugin --dir ./data/chain1/basecoin &> chain1_basecoin.log & +adam start --dir ./data/chain1/basecoin &> chain1_adam.log & ``` and ``` TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & -basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/basecoin &> chain2_basecoin.log & +adam start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log & ``` Note how we refer to the relevant data directories. Also note how we have to set the various addresses for the second node so as not to conflict with the first. @@ -225,27 +230,27 @@ For the sake of convenience, let's first set some environment variables: export CHAIN_ID1=test_chain_1 export CHAIN_ID2=test_chain_2 -export CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json" -export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657" +export CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/key.json" +export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.json --node tcp://localhost:36657" ``` Let's start by registering `test_chain_1` on `test_chain_2`: ``` -basecoin ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json +adam tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json ``` Now we can create the outgoing packet on `test_chain_1`: ``` -basecoin ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 +adam tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 ``` Note our payload is just `DEADBEEF`. Now that the packet is committed in the chain, let's get some proof by querying: ``` -basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1 +adam query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1 ``` The result contains the latest height, a value (ie. the hex-encoded binary serialization of our packet), @@ -256,7 +261,7 @@ We'll need a recent block header and a set of commit signatures. Fortunately, we can get them with the `block` command: ``` -basecoin block +adam block ``` where `` is the height returned in the previous query. @@ -266,7 +271,7 @@ The former is used as input for later commands; the latter is human-readable, so Let's send this updated information about `test_chain_1` to `test_chain_2`: ``` -basecoin ibc --amount 10 $CHAIN_FLAGS2 update --header 0x
--commit 0x +adam tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x
--commit 0x ``` where `
` and `` are the hex-encoded header and commit returned by the previous `block` command. @@ -276,7 +281,7 @@ along with proof the packet was committed on `test_chain_1`. Since `test_chain_2 of `test_chain_1`, it will be able to verify the proof! ``` -basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x +adam tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x ``` Here, `` is one greater than the height retuned by the previous `query` command, and `` and `` are the @@ -286,3 +291,11 @@ Tada! ## Conclusion + +In this tutorial we explained how IBC works, and demonstrated how to use it to communicate between two chains. +We did the simplest communciation possible: a one way transfer of data from chain1 to chain2. +The most important part was that we updated chain2 with the latest state (ie. header and commit) of chain1, +and then were able to post a proof to chain2 that a packet was committed to the outgoing state of chain1. + +In a future tutorial, we will demonstrate how to use IBC to actually transfer tokens between two blockchains, +but we'll do it with real testnets deployed across multiple nodes on the network. Stay tuned! From 6f173a44a97984d18690390a253da268386c9352 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 8 Feb 2017 13:18:26 +0100 Subject: [PATCH 59/64] Added some notes on basecoin intro --- README.md | 7 ++++--- docs/guide/basecoin-basics.md | 18 ++++++++++-------- docs/guide/basecoin-design.md | 20 +++++++++++--------- docs/guide/example-plugin.md | 23 +++++++++++++---------- docs/guide/ibc.md | 12 ++++++------ docs/guide/install.md | 3 ++- 6 files changed, 46 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index a43c45d8a7..5028d6ef3c 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ DISCLAIMER: Basecoin is not associated with Coinbase.com, an excellent Bitcoin/Ethereum service. -Basecoin is an [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. +Basecoin is an [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. It also provides a general purpose framework for extending the feature-set of the cryptocurrency by implementing plugins. Basecoin serves as a reference implementation for how we build ABCI applications in Go, -and is the framework in which we implement the [Cosmos Hub](https://cosmos.network). +and is the framework in which we implement the [Cosmos Hub](https://cosmos.network). It's easy to use, and doesn't require any forking - just implement your plugin, import the basecoin libraries, and away you go with a full-stack blockchain and command line tool for transacting. @@ -18,8 +18,9 @@ WARNING: Currently uses plain-text private keys for transactions and is otherwis We use glide for dependency management. The prefered way of compiling from source is the following: ``` -go get -d github.com/tendermint/basecoin/cmd/basecoin +go get -u github.com/tendermint/basecoin cd $GOPATH/src/github.com/tendermint/basecoin +git checkout develop # (until we release v0.9) make get_vendor_deps make install ``` diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index fc6e62bb29..400136b496 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -7,6 +7,8 @@ Here we explain how to get started with a simple Basecoin blockchain, and how to Make sure you have [basecoin installed](install.md). You will also need to [install tendermint](https://tendermint.com/intro/getting-started/download). +**Note** All code is on the 0.9 pre-release branch, you may have to [install tendermint from source](https://tendermint.com/docs/guides/install) until 0.9 is released. (Make sure to add `git checkout develop` to the linked install instructions) + ## Initialization Basecoin is an ABCI application that runs on Tendermint, so we first need to initialize Tendermint: @@ -33,10 +35,10 @@ cd $GOPATH/src/github.com/tendermint/basecoin/data The directory contains a genesis file and two private keys. -You can generate your own private keys with `tendermint gen_validator`, +You can generate your own private keys with `tendermint gen_validator`, and construct the `genesis.json` as you like. -Note, however, that you must be careful with the `chain_id` field, -as every transaction must contain the correct `chain_id` +Note, however, that you must be careful with the `chain_id` field, +as every transaction must contain the correct `chain_id` (default is `test_chain_id`). ## Start @@ -53,7 +55,7 @@ This will initialize the chain with the `genesis.json` file from the current dir basecoin start --in-proc --dir PATH/TO/CUSTOM/DATA ``` -Note that `--in-proc` stands for "in process", which means +Note that `--in-proc` stands for "in process", which means basecoin will be started with the Tendermint node running in the same process. To start Tendermint in a separate process instead, use: @@ -68,7 +70,7 @@ tendermint node ``` In either case, you should see blocks start streaming in! -Note, however, that currently basecoin currently requires the +Note, however, that currently basecoin currently requires the `develop` branch of tendermint for this to work. ## Send transactions @@ -122,10 +124,10 @@ See `basecoin tx send --help` for additional details. The `tx send` command creates and broadcasts a transaction of type `SendTx`, which is only useful for moving tokens around. -Fortunately, Basecoin supports another transaction type, the `AppTx`, +Fortunately, Basecoin supports another transaction type, the `AppTx`, which can trigger code registered via a plugin system. -In the [next tutorial](example-plugin.md), -we demonstrate how to implement a plugin +In the [next tutorial](example-plugin.md), +we demonstrate how to implement a plugin and extend the CLI to support new transaction types! But first, you may want to learn a bit more about [Basecoin's design](basecoin-design.md) diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md index fd675e0a57..e91ab9ac7e 100644 --- a/docs/guide/basecoin-design.md +++ b/docs/guide/basecoin-design.md @@ -30,7 +30,7 @@ type Coin struct { ``` Accounts are serialized and stored in a Merkle tree using the account's address as the key, -In particular, an account is stored in the Merkle tree under the key `base/a/
`, +In particular, an account is stored in the Merkle tree under the key `base/a/
`, where `
` is the address of the account. In Basecoin, the address of an account is the 20-byte `RIPEMD160` hash of the public key. The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an [IAVL tree](https://github.com/tendermint/go-merkle). @@ -44,8 +44,8 @@ The `SendTx` is structured as follows: ``` type SendTx struct { - Gas int64 `json:"gas"` - Fee Coin `json:"fee"` + Gas int64 `json:"gas"` + Fee Coin `json:"fee"` Inputs []TxInput `json:"inputs"` Outputs []TxOutput `json:"outputs"` } @@ -64,24 +64,26 @@ type TxOutput struct { } ``` -There are a few things to note. First, the `SendTx` includes a field for `Gas` and `Fee`. +There are a few things to note. First, the `SendTx` includes a field for `Gas` and `Fee`. The `Gas` limits the total amount of computation that can be done by the transaction, -while the `Fee` refers to the total amount paid in fees. +while the `Fee` refers to the total amount paid in fees. This is slightly different from Ethereum's concept of `Gas` and `GasPrice`, where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent, and the `GasPrice` is implicit. -Second, notice that the `PubKey` only needs to be sent for `Sequence == 0`. -After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it, +In tendermint, the `Fee` is meant to be used by the validators to inform the ordering of transactions, like in bitcoin. And the `Gas` is meant to be used by the application plugin to control its execution. There is currently no means to pass `Fee` information to the tendermint validators, but it will come soon... + +Second, notice that the `PubKey` only needs to be sent for `Sequence == 0`. +After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it, using only the `Address` to refer to the sender. Ethereum does not require public keys to be sent in transactions as it uses a different elliptic curve scheme which enables the public key to be derrived from the signature itself. Finally, note that the use of multiple inputs and multiple outputs allows us to send many different types of tokens between many different accounts -at once in an atomic transaction. Thus, the `SendTx` can serve as a basic unit of decentralized exchange. +at once in an atomic transaction. Thus, the `SendTx` can serve as a basic unit of decentralized exchange. When using multiple inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction. ## Plugins -Basecoin actually defines a second transaction type, the `AppTx`, +Basecoin actually defines a second transaction type, the `AppTx`, which enables the functionality to be extended via custom plugins. To learn more about the `AppTx` and plugin system, see the [plugin design document](plugin-design.md). To implement your first plugin, see [plugin tutorial](example-plugin.md). diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md index ec31890194..ff9e77a62e 100644 --- a/docs/guide/example-plugin.md +++ b/docs/guide/example-plugin.md @@ -41,9 +41,9 @@ func main() { It creates the CLI, exactly like the `basecoin` one. However, if we want our plugin to be active, we need to make sure it is registered with the application. -In addition, if we want to send transactions to our plugin, +In addition, if we want to send transactions to our plugin, we need to add a new command to the CLI. -This is where the `cmd.go` comes in. +This is where the `cmd.go` comes in. ## Commands @@ -154,7 +154,7 @@ func cmdExamplePluginTx(c *cli.Context) error { We read the flag from the CLI library, and then create the example transaction. Remember that Basecoin itself only knows about two transaction types, `SendTx` and `AppTx`. -All plugin data must be serialized (ie. encoded as a byte-array) +All plugin data must be serialized (ie. encoded as a byte-array) and sent as data in an `AppTx`. The `commands.AppTx` function does this for us - it creates an `AppTx` with the corresponding data, signs it, and sends it on to the blockchain. @@ -162,10 +162,12 @@ it creates an `AppTx` with the corresponding data, signs it, and sends it on to Ok, now we're ready to actually look at the implementation of the plugin in `plugin.go`. Note I'll leave out some of the methods as they don't serve any purpose for this example, -but are necessary boilerplate. -Your plugin may have additional requirements that utilize these other plugins. +but are necessary boilerplate. +Your plugin may have additional requirements that utilize these other methods. Here's what's relevant for us: +**TODO** make `StateKey` `stateKey`? No need to expose this outside the package. + ``` type ExamplePluginState struct { Counter int @@ -276,7 +278,8 @@ if len(stateBytes) > 0 { } ``` -Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`. +Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`. Also note, that we do nothing if there is no existing state data. Is that a bug? No, we just make use of go's variable initialization, that `pluginState` will contain a `Counter` value of 0. If your app needs more initialization than empty variables, then do this logic here in an `else` block. + Finally, we can update the state's `Counter`, and save the state back to the store: ``` @@ -353,7 +356,7 @@ tendermint unsafe_reset_all ``` Great, now we're ready to go. -To start the blockchain, simply run +To start the blockchain, simply run ``` example-plugin start --in-proc @@ -396,7 +399,7 @@ example-plugin query ExamplePlugin.State ``` Note the `"value":"0101"` piece. This is the serialized form of the state, -which contains only an integer. +which contains only an integer. If we send another transaction, and then query again, we'll see the value increment: ``` @@ -404,14 +407,14 @@ example-plugin tx example --valid --amount 1 --coin gold --chain_id example-chai example-plugin query ExamplePlugin.State ``` -Neat, right? Notice how the result of the query comes with a proof. +Neat, right? Notice how the result of the query comes with a proof. This is a Merkle proof that the state is what we say it is. In a latter [tutorial on Interblockchain Communication](ibc.md), we'll put this proof to work! ## Next Stpes -In this tutorial we demonstrated how to create a new plugin and how to extend the +In this tutorial we demonstrated how to create a new plugin and how to extend the basecoin CLI to activate the plugin on the blockchain and to send transactions to it. Hopefully by now you have some ideas for your own plugin, and feel comfortable implementing them. diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md index fe98941c17..66f874d3a0 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -4,12 +4,12 @@ One of the most exciting elements of the Cosmos Network is the InterBlockchain C which enables interoperability across different blockchains. The simplest example of using the IBC protocol is to send a data packet from one blockchain to another. -We implemented IBC as a basecoin plugin. +We implemented IBC as a basecoin plugin. and here we'll show you how to use the Basecoin IBC-plugin to send a packet of data across blockchains! Please note, this tutorial assumes you are familiar with [Basecoin plugins](/docs/guide/plugin-design.md) and with the [Basecoin CLI](/docs/guide/basecoin-basics), but we'll explain how IBC works. -You may also want to see the tutorials on [a simple example plugin](example-plugin.md) +You may also want to see the tutorials on [a simple example plugin](example-plugin.md) and the list of [more advanced plugins](more-examples.md). The IBC plugin defines a new set of transactions as subtypes of the `AppTx`. @@ -32,8 +32,8 @@ next block. Thus, each block contains a field called `LastCommit`, which contains the votes responsible for committing the previous block, and a field in the block header called `AppHash`, which refers to the merkle root hash of the application after processing the transactions from the previous block. So, -if we want to verify some state from height H, we need the signatures and root -hash from the header at height H+1. +if we want to verify the `AppHash` from height H, we need the signatures from `LastCommit` at height H+1. (And remember that this `AppHash` only contains the results from all transactions up to and including block H-1) + Unlike Proof-of-Work, the light-client protocol does not need to download and check all the headers in the blockchain - the client can always jump straight @@ -147,7 +147,7 @@ and the resulting state root is not included until the next block. ### IBC State Now that we've seen all the transaction types, let's talk about the state. -Each chain stores some IBC state in its merkle tree. +Each chain stores some IBC state in its merkle tree. For each chain being tracked by our chain, we store: ``` @@ -179,7 +179,7 @@ The results of a query can thus be used as proof in an `IBCPacketPostTx`. Now that we have all the background knowledge, let's actually walk through the tutorial. -Make sure you have installed +Make sure you have installed [tendermint](https://tendermint.com/intro/getting-started/download) and [adam](/docs/guide/install.md). diff --git a/docs/guide/install.md b/docs/guide/install.md index 4b77255b7d..b9acb5455b 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -3,8 +3,9 @@ We use glide for dependency management. The prefered way of compiling from source is the following: ``` -go get -d github.com/tendermint/basecoin/cmd/basecoin +go get -u github.com/tendermint/basecoin cd $GOPATH/src/github.com/tendermint/basecoin +git checkout develop # (until we release v0.9) make get_vendor_deps make install ``` From 5cfc96676aa648fe3ff19b0ae944f7b9cc7d8bae Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 10 Feb 2017 16:40:20 -0500 Subject: [PATCH 60/64] docs: little fixes --- README.md | 16 +++++++++++++--- docs/guide/example-plugin.md | 4 +--- docs/guide/install.md | 13 +++++++++++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5028d6ef3c..047bca7093 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,28 @@ WARNING: Currently uses plain-text private keys for transactions and is otherwis ## Installation -We use glide for dependency management. The prefered way of compiling from source is the following: +On a good day, basecoin can be installed like a normal Go program: + +``` +go get -u github.com/tendermint/basecoin/cmd/basecoin +``` + +In some cases, if that fails, or if another branch is required, +we use `glide` for dependency management. + +The guaranteed correct way of compiling from source, assuming you've already +run `go get` or otherwise cloned the repo, is: ``` -go get -u github.com/tendermint/basecoin cd $GOPATH/src/github.com/tendermint/basecoin -git checkout develop # (until we release v0.9) +git checkout develop # (until we release tendermint v0.9) make get_vendor_deps make install ``` This will create the `basecoin` binary in `$GOPATH/bin`. + ## Command Line Interface The basecoin CLI can be used to start a stand-alone basecoin instance (`basecoin start`), diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md index ff9e77a62e..e8a431ea5a 100644 --- a/docs/guide/example-plugin.md +++ b/docs/guide/example-plugin.md @@ -166,8 +166,6 @@ but are necessary boilerplate. Your plugin may have additional requirements that utilize these other methods. Here's what's relevant for us: -**TODO** make `StateKey` `stateKey`? No need to expose this outside the package. - ``` type ExamplePluginState struct { Counter int @@ -278,7 +276,7 @@ if len(stateBytes) > 0 { } ``` -Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`. Also note, that we do nothing if there is no existing state data. Is that a bug? No, we just make use of go's variable initialization, that `pluginState` will contain a `Counter` value of 0. If your app needs more initialization than empty variables, then do this logic here in an `else` block. +Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`. Also note, that we do nothing if there is no existing state data. Is that a bug? No, we just make use of Go's variable initialization, that `pluginState` will contain a `Counter` value of 0. If your app needs more initialization than empty variables, then do this logic here in an `else` block. Finally, we can update the state's `Counter`, and save the state back to the store: diff --git a/docs/guide/install.md b/docs/guide/install.md index b9acb5455b..2a04abe73b 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -1,9 +1,18 @@ # Install -We use glide for dependency management. The prefered way of compiling from source is the following: +On a good day, basecoin can be installed like a normal Go program: + +``` +go get -u github.com/tendermint/basecoin/cmd/basecoin +``` + +In some cases, if that fails, or if another branch is required, +we use `glide` for dependency management. + +The correct way of compiling from source, assuming you've already +run `go get` or otherwise cloned the repo, is: ``` -go get -u github.com/tendermint/basecoin cd $GOPATH/src/github.com/tendermint/basecoin git checkout develop # (until we release v0.9) make get_vendor_deps From b189018090d445b02c0b75fe28e721e56af3bb05 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 12 Feb 2017 19:01:17 -0800 Subject: [PATCH 61/64] s/adam/basecoin/g; fix tests --- cmd/adam/main.go | 30 ------------------------- demo/clean.sh | 2 +- demo/start.sh | 18 +++++++-------- docs/guide/ibc.md | 21 +++++++++-------- docs/guide/src/example-plugin/plugin.go | 6 ++--- plugins/counter/counter.go | 9 +++----- plugins/counter/counter_test.go | 5 ++--- 7 files changed, 28 insertions(+), 63 deletions(-) delete mode 100644 cmd/adam/main.go diff --git a/cmd/adam/main.go b/cmd/adam/main.go deleted file mode 100644 index 9be7255e7c..0000000000 --- a/cmd/adam/main.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "os" - - "github.com/tendermint/basecoin/cmd/commands" - - "github.com/urfave/cli" -) - -func init() { - commands.RegisterIBC() -} - -func main() { - app := cli.NewApp() - app.Name = "adam" - app.Usage = "adam [command] [args...]" - app.Version = "0.1.0" - app.Commands = []cli.Command{ - commands.StartCmd, - commands.TxCmd, - commands.KeyCmd, - commands.QueryCmd, - commands.VerifyCmd, // TODO: move to merkleeyes? - commands.BlockCmd, - commands.AccountCmd, - } - app.Run(os.Args) -} diff --git a/demo/clean.sh b/demo/clean.sh index 7d18923212..e2d519337d 100644 --- a/demo/clean.sh +++ b/demo/clean.sh @@ -1,6 +1,6 @@ #! /bin/bash -killall -9 adam tendermint +killall -9 basecoin tendermint TMROOT=./data/chain1/tendermint tendermint unsafe_reset_all TMROOT=./data/chain2/tendermint tendermint unsafe_reset_all diff --git a/demo/start.sh b/demo/start.sh index b856852dea..ad5da68d1c 100644 --- a/demo/start.sh +++ b/demo/start.sh @@ -26,11 +26,11 @@ echo "... starting chains" echo "" # start the first node TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & -adam start --dir ./data/chain1/basecoin &> chain1_basecoin.log & +basecoin start --dir ./data/chain1/basecoin &> chain1_basecoin.log & # start the second node TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & -adam start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log & +basecoin start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log & echo "" echo "... waiting for chains to start" @@ -40,20 +40,20 @@ sleep 10 echo "... registering chain1 on chain2" echo "" # register chain1 on chain2 -adam tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json +basecoin tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json echo "" echo "... creating egress packet on chain1" echo "" # create a packet on chain1 destined for chain2 PAYLOAD="DEADBEEF" #TODO -adam tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1 +basecoin tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1 echo "" echo "... querying for packet data" echo "" # query for the packet data and proof -QUERY_RESULT=$(adam query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) +QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) HEIGHT=$(echo $QUERY_RESULT | jq .height) PACKET=$(echo $QUERY_RESULT | jq .value) PROOF=$(echo $QUERY_RESULT | jq .proof) @@ -75,7 +75,7 @@ echo "" echo "... querying for block data" echo "" # get the header and commit for the height -HEADER_AND_COMMIT=$(adam block $HEIGHT) +HEADER_AND_COMMIT=$(basecoin block $HEIGHT) HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header) HEADER=$(removeQuotes $HEADER) COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit) @@ -89,19 +89,19 @@ echo "" echo "... updating state of chain1 on chain2" echo "" # update the state of chain1 on chain2 -adam tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT +basecoin tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT echo "" echo "... posting packet from chain1 on chain2" echo "" # post the packet from chain1 to chain2 -adam tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF +basecoin tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF echo "" echo "... checking if the packet is present on chain2" echo "" # query for the packet on chain2 ! -adam query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 +basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 echo "" echo "DONE!" diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md index 66f874d3a0..4bd3228140 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -181,10 +181,9 @@ Now that we have all the background knowledge, let's actually walk through the t Make sure you have installed [tendermint](https://tendermint.com/intro/getting-started/download) and -[adam](/docs/guide/install.md). +[basecoin](/docs/guide/install.md). -`adam` is the name for the program that will become the Cosmos Hub. -We call it Adam because it's the first blockchain in [the Cosmos Network](https://cosmos.network). +`basecoin` is a framework for creating new cryptocurrency applications. Now let's start the two blockchains. In this tutorial, each chain will have only a single validator, @@ -201,14 +200,14 @@ We can start the two chains as follows: ``` TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & -adam start --dir ./data/chain1/basecoin &> chain1_adam.log & +basecoin start --dir ./data/chain1/basecoin &> chain1_basecoin.log & ``` and ``` TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & -adam start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log & +basecoin start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log & ``` Note how we refer to the relevant data directories. Also note how we have to set the various addresses for the second node so as not to conflict with the first. @@ -237,20 +236,20 @@ export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.jso Let's start by registering `test_chain_1` on `test_chain_2`: ``` -adam tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json +basecoin tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json ``` Now we can create the outgoing packet on `test_chain_1`: ``` -adam tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 +basecoin tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 ``` Note our payload is just `DEADBEEF`. Now that the packet is committed in the chain, let's get some proof by querying: ``` -adam query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1 +basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1 ``` The result contains the latest height, a value (ie. the hex-encoded binary serialization of our packet), @@ -261,7 +260,7 @@ We'll need a recent block header and a set of commit signatures. Fortunately, we can get them with the `block` command: ``` -adam block +basecoin block ``` where `` is the height returned in the previous query. @@ -271,7 +270,7 @@ The former is used as input for later commands; the latter is human-readable, so Let's send this updated information about `test_chain_1` to `test_chain_2`: ``` -adam tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x
--commit 0x +basecoin tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x
--commit 0x ``` where `
` and `` are the hex-encoded header and commit returned by the previous `block` command. @@ -281,7 +280,7 @@ along with proof the packet was committed on `test_chain_1`. Since `test_chain_2 of `test_chain_1`, it will be able to verify the proof! ``` -adam tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x +basecoin tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x ``` Here, `` is one greater than the height retuned by the previous `query` command, and `` and `` are the diff --git a/docs/guide/src/example-plugin/plugin.go b/docs/guide/src/example-plugin/plugin.go index 81bb365e50..e930ffbf90 100644 --- a/docs/guide/src/example-plugin/plugin.go +++ b/docs/guide/src/example-plugin/plugin.go @@ -72,9 +72,9 @@ func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt func (ep *ExamplePlugin) InitChain(store types.KVStore, vals []*abci.Validator) { } -func (ep *ExamplePlugin) BeginBlock(store types.KVStore, height uint64) { +func (ep *ExamplePlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) { } -func (ep *ExamplePlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator { - return nil +func (ep *ExamplePlugin) EndBlock(store types.KVStore, height uint64) abci.ResponseEndBlock { + return abci.ResponseEndBlock{} } diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index be9ec4041f..9d849d1d05 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -21,21 +21,18 @@ type CounterTx struct { //-------------------------------------------------------------------------------- type CounterPlugin struct { - name string } func (cp *CounterPlugin) Name() string { - return cp.name + return "counter" } func (cp *CounterPlugin) StateKey() []byte { - return []byte(fmt.Sprintf("CounterPlugin{name=%v}.State", cp.name)) + return []byte(fmt.Sprintf("CounterPlugin.State")) } func New() *CounterPlugin { - return &CounterPlugin{ - name: "counter", - } + return &CounterPlugin{} } func (cp *CounterPlugin) SetOption(store types.KVStore, key string, value string) (log string) { diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index 6cc99bd3a3..0b8ee4a866 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -22,8 +22,7 @@ func TestCounterPlugin(t *testing.T) { t.Log(bcApp.Info()) // Add Counter plugin - counterPluginName := "testcounter" - counterPlugin := New(counterPluginName) + counterPlugin := New() bcApp.RegisterPlugin(counterPlugin) // Account initialization @@ -40,7 +39,7 @@ func TestCounterPlugin(t *testing.T) { tx := &types.AppTx{ Gas: gas, Fee: fee, - Name: counterPluginName, + Name: "counter", Input: types.NewTxInput(test1Acc.PubKey, inputCoins, inputSequence), Data: wire.BinaryBytes(CounterTx{Valid: true, Fee: appFee}), } From 5be9db68dbd6a69ba886c5a6e55b90f2cecd2ca8 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 12 Feb 2017 19:05:27 -0800 Subject: [PATCH 62/64] Minor README fixes --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 047bca7093..c6303458aa 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Basecoin -DISCLAIMER: Basecoin is not associated with Coinbase.com, an excellent Bitcoin/Ethereum service. +_DISCLAIMER: Basecoin is not associated with Coinbase.com, an excellent Bitcoin/Ethereum service._ -Basecoin is an [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. +Basecoin is an [ABCI application](https://github.com/tendermint/abci) designed to be used with the [Tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. It also provides a general purpose framework for extending the feature-set of the cryptocurrency by implementing plugins. @@ -40,7 +40,7 @@ This will create the `basecoin` binary in `$GOPATH/bin`. ## Command Line Interface The basecoin CLI can be used to start a stand-alone basecoin instance (`basecoin start`), -or to start basecoin with tendermint in the same process (`basecoin start --in-proc`). +or to start basecoin with Tendermint in the same process (`basecoin start --in-proc`). It can also be used to send transactions, eg. `basecoin tx send --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` See `basecoin --help` and `basecoin [cmd] --help` for more details`. From 62a61e8b7d8a04728be076b3fb0bd475d06a8408 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 12 Feb 2017 22:15:01 -0800 Subject: [PATCH 63/64] s/blank/mycoin/g --- cmd/commands/flags.go | 2 +- data/genesis.json | 2 +- demo/data/chain1/basecoin/genesis.json | 2 +- demo/data/chain2/basecoin/genesis.json | 2 +- docs/guide/example-plugin.md | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/commands/flags.go b/cmd/commands/flags.go index 4670ee9fcd..00aae351cd 100644 --- a/cmd/commands/flags.go +++ b/cmd/commands/flags.go @@ -68,7 +68,7 @@ var ( CoinFlag = cli.StringFlag{ Name: "coin", - Value: "blank", + Value: "mycoin", Usage: "Specify a coin denomination", } diff --git a/data/genesis.json b/data/genesis.json index 3a4c177526..2936a0e4d6 100644 --- a/data/genesis.json +++ b/data/genesis.json @@ -4,7 +4,7 @@ "pub_key": [1, "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"], "coins": [ { - "denom": "blank", + "denom": "mycoin", "amount": 9007199254740992 } ] diff --git a/demo/data/chain1/basecoin/genesis.json b/demo/data/chain1/basecoin/genesis.json index 717a6345a0..b060121774 100644 --- a/demo/data/chain1/basecoin/genesis.json +++ b/demo/data/chain1/basecoin/genesis.json @@ -4,7 +4,7 @@ "pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"], "coins": [ { - "denom": "blank", + "denom": "mycoin", "amount": 9007199254740992 } ] diff --git a/demo/data/chain2/basecoin/genesis.json b/demo/data/chain2/basecoin/genesis.json index 1dcc0d658f..ca690c2cdb 100644 --- a/demo/data/chain2/basecoin/genesis.json +++ b/demo/data/chain2/basecoin/genesis.json @@ -4,7 +4,7 @@ "pub_key": [1, "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"], "coins": [ { - "denom": "blank", + "denom": "mycoin", "amount": 9007199254740992 } ] diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md index e8a431ea5a..b08f336fb7 100644 --- a/docs/guide/example-plugin.md +++ b/docs/guide/example-plugin.md @@ -133,7 +133,7 @@ OPTIONS: --chain_id value ID of the chain for replay protection (default: "test_chain_id") --from value Path to a private key to sign the transaction (default: "key.json") --amount value Amount of coins to send in the transaction (default: 0) - --coin value Specify a coin denomination (default: "blank") + --coin value Specify a coin denomination (default: "mycoin") --gas value The amount of gas for the transaction (default: 0) --fee value The transaction fee (default: 0) --sequence value Sequence number for the account (default: 0) @@ -367,7 +367,7 @@ example-plugin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --amount ``` Note the `--coin` and `--chain_id` flags. In the [previous tutorial](basecoin-basics.md), -we didn't need them because we were using the default coin type ("blank") and chain ID ("test_chain_id"). +we didn't need them because we were using the default coin type ("mycoin") and chain ID ("test_chain_id"). Now that we're using custom values, we need to specify them explicitly on the command line. Ok, so that's how we can send a `SendTx` transaction using our `example-plugin` CLI, From 1e21e8cfbec93df533b23c18b506117bcf0d1aef Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 13 Feb 2017 12:25:50 -0500 Subject: [PATCH 64/64] circle.yml --- circle.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000000..ef59921583 --- /dev/null +++ b/circle.yml @@ -0,0 +1,26 @@ +machine: + environment: + GOPATH: /home/ubuntu/.go_workspace + REPO: $GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME + hosts: + circlehost: 127.0.0.1 + localhost: 127.0.0.1 + +checkout: + post: + - rm -rf $REPO + - mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME + - mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO + +dependencies: + override: + - go get github.com/Masterminds/glide + - go version + - glide --version + - "cd $REPO && glide install" + +test: + override: + - "cd $REPO && make test" + +