Merge pull request #418 from cosmos/feature/remove_tmc_dep
Remove Tendermint Core as dependency to enable build
This commit is contained in:
commit
9ca0501a4b
3
Makefile
3
Makefile
@ -25,6 +25,9 @@ dist:
|
||||
check_tools:
|
||||
cd tools && $(MAKE) check
|
||||
|
||||
update_tools:
|
||||
cd tools && $(MAKE) glide_update
|
||||
|
||||
get_tools:
|
||||
cd tools && $(MAKE)
|
||||
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
# Roadmap for future basecoin development
|
||||
|
||||
Warning: there are current plans, they may change based on other developments, needs. The further in the future, the less clear, like all plans.
|
||||
|
||||
## 0.6.x - Testnet and Light Client (late June 2017)
|
||||
|
||||
The current release cycle is making sure the server is usable for deploying testnets (easy config, safe restarts, moving nodes). Also that we have a useable light client that does full cryptographic prooofs without syncing the entire block headers. See the [changelog](CHANGELOG.md).
|
||||
|
||||
Patch release here involve improving the usability of the cli tools, adding subcommands, more flags, helper functions, shell integrations, etc. Please add issues if you find the client tool difficult to use, or deployment troublesome.
|
||||
|
||||
## 0.7.x - Towards a modular framework (late July 2017)
|
||||
|
||||
**Breaking changes**
|
||||
|
||||
* Renaming likely: this release may well lead to a [renaming of the repository](https://github.com/tendermint/basecoin/issues/119) to emphasize that it is a generalized framework. `basecoin` and `basecli` executables will remain with generally unchanged usage.
|
||||
* This will also provide a tx structure that is very different than the current one, and a non-trivial upgrade of running chains.
|
||||
|
||||
The next release cycle involves a big upgrade to the core, especially how one can write modules (aka plugins) as well as configure a basecoin-based executable. The main goal is to leave us with basecoin as a single executable with a similar API, but create a new module/middleware system with a number of standard modules provided (and easy addition of third party modules), so developers can quickly mix-and-match pieces and add custom business logic for there chain.
|
||||
|
||||
The main goal here is to migrate from a basecoin with plugins for extra enhancements, to a proper app development framework, of which basecoin is one example app that can quickly be built.
|
||||
|
||||
Some ideas:
|
||||
|
||||
* Flexible fee/gas system (good for both public and private blockchains)
|
||||
* Flexible authentication systems (with multi-sig support)
|
||||
* Basic role permission system
|
||||
* Abstract IBC to support other transactions from various modules (not just sendtx)
|
||||
|
||||
This will be done in conjunction with some sample apps also building on this framework, where other logic is interesting and money transfers is not the central goal, like [trackomatron](https://github.com/tendermint/trackomatron)
|
||||
|
||||
## Next steps
|
||||
|
||||
The following are three planned steps, the order of which may change. At least one or two of these will most likely occur before any other developments. Clearly, any other feature that are urgent for cosmos can jump the list in priority, but all these pieces are part of the cosmos roadmap, especially the first two.
|
||||
|
||||
### 0.8.x??? - Local client API for UI
|
||||
|
||||
Beyond the CLI, we want to add more interfaces for easily building a UI on top of the basecoin client. One clear example is a local REST API, so you can easily integrate with an electron app, or a chrome app, just as if you wrote a normal Single-Page Application, but connecting to a local proxy to do full crypto-graphic proofs.
|
||||
|
||||
Another **possible** development is providing an SDK, which we can compile with [go-mobile](https://github.com/golang/go/wiki/Mobile) for both Android and iOS to support secure mobile applications. Progress on this front is contingent on participation of an experienced mobile developer.
|
||||
|
||||
Further, when the planned enhancements to real-time events happen in tendermint core, we should expose that via a simple subscriber/listener model in these local APIs.
|
||||
|
||||
### 0.9.x??? - Proof of Stake and Voting Modules
|
||||
|
||||
We should integrate developments on a [proof-of-stake module](https://github.com/tendermint/basecoin-stake) (currently a work-in-progress) and basic voting modules (currently planned) into properly supported for modules. These would provide the basis for dynamic validator set changes with bondign periods, and the basis for making governance decisions (eg. voting to change the block reward).
|
||||
|
||||
At this point we would have to give full support to these plugins, and third-party devs can build on them to add more complex delegation or governance logic.
|
||||
|
||||
### 0.10.x??? - Database enhancements
|
||||
|
||||
Depending on developments with merkleeyes, we would like to increase the expressiveness of the storage layer while maintaining provability of all queries. We would also add a number of new primatives to the key-value store, to allow some general data-structures.
|
||||
|
||||
Also, full support for historical queries and performance optimizations of the storage later. But this all depends on supporting developments of another repo, so timing here is unclear. Here are some example ideas:
|
||||
|
||||
Merkle proofs:
|
||||
|
||||
* **Proof of key-value**: only current proof
|
||||
* **Proof of missing key**: prove there is no data for that key
|
||||
* **Proof of range**: one proof for all key-values in a range of keys
|
||||
* **Proof of highest/lowest in range**: just get one key, for example, prove validator hash with highest height <= H
|
||||
|
||||
Data structures:
|
||||
|
||||
* **Queues**: provable push-pop operations, split over multiple keys, so it can scale to 1000s of entries without deserializing them all every time.
|
||||
* **Priority Queues**: as above, but ordered by priority instead of FIFO.
|
||||
* **Secondary Indexes**: add support for secondary indexes with proofs. So, I can not only prove my balance, but for example, the list of all accouns with a balance of > 1000000 atoms. These indexes would have to be created by the application and stored extra in the database, but if you have a common query that you want proofs/trust, it can be very useful.
|
||||
@ -1,194 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
sdkapp "github.com/cosmos/cosmos-sdk/app"
|
||||
"github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
"github.com/cosmos/cosmos-sdk/modules/base"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/cosmos/cosmos-sdk/modules/fee"
|
||||
"github.com/cosmos/cosmos-sdk/modules/nonce"
|
||||
"github.com/cosmos/cosmos-sdk/modules/roles"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
)
|
||||
|
||||
type BenchApp struct {
|
||||
App *sdkapp.BaseApp
|
||||
Accounts []*coin.AccountWithKey
|
||||
ChainID string
|
||||
}
|
||||
|
||||
// DefaultHandler - placeholder to just handle sendtx
|
||||
func DefaultHandler(feeDenom string) sdk.Handler {
|
||||
// use the default stack
|
||||
c := coin.NewHandler()
|
||||
r := roles.NewHandler()
|
||||
d := stack.NewDispatcher(
|
||||
c,
|
||||
stack.WrapHandler(r),
|
||||
)
|
||||
return stack.New(
|
||||
base.Logger{},
|
||||
stack.Recovery{},
|
||||
auth.Signatures{},
|
||||
base.Chain{},
|
||||
nonce.ReplayCheck{},
|
||||
roles.NewMiddleware(),
|
||||
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||
).Use(d)
|
||||
}
|
||||
|
||||
func NewBenchApp(h sdk.Handler, chainID string, n int,
|
||||
persist bool) BenchApp {
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
// logger := log.NewFilter(log.NewTMLogger(os.Stdout), log.AllowError())
|
||||
// logger = log.NewTracingLogger(logger)
|
||||
|
||||
dbDir, cache := "", 0
|
||||
if persist {
|
||||
dbDir, _ = ioutil.TempDir("", "bc-app-benchmark")
|
||||
cache = 500
|
||||
}
|
||||
|
||||
store, err := sdkapp.NewStoreApp("bench", dbDir, cache, logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
app := sdkapp.NewBaseApp(store, h, nil)
|
||||
|
||||
err = app.InitState("base", "chain_id", chainID)
|
||||
if err != nil {
|
||||
panic("cannot set chain")
|
||||
}
|
||||
|
||||
// make keys
|
||||
money := coin.Coins{{"mycoin", 1234567890}}
|
||||
accts := make([]*coin.AccountWithKey, n)
|
||||
for i := 0; i < n; i++ {
|
||||
accts[i] = coin.NewAccountWithKey(money)
|
||||
err = app.InitState("coin", "account", accts[i].MakeOption())
|
||||
if err != nil {
|
||||
panic("can't set account")
|
||||
}
|
||||
}
|
||||
|
||||
return BenchApp{
|
||||
App: app,
|
||||
Accounts: accts,
|
||||
ChainID: chainID,
|
||||
}
|
||||
}
|
||||
|
||||
// make a random tx...
|
||||
func (b BenchApp) makeTx(useFee bool) []byte {
|
||||
n := len(b.Accounts)
|
||||
sender := b.Accounts[cmn.RandInt()%n]
|
||||
recipient := b.Accounts[cmn.RandInt()%n]
|
||||
amount := coin.Coins{{"mycoin", 123}}
|
||||
tx := coin.NewSendOneTx(sender.Actor(), recipient.Actor(), amount)
|
||||
if useFee {
|
||||
toll := coin.Coin{"mycoin", 2}
|
||||
tx = fee.NewFee(tx, toll, sender.Actor())
|
||||
}
|
||||
sequence := sender.NextSequence()
|
||||
tx = nonce.NewTx(sequence, []sdk.Actor{sender.Actor()}, tx)
|
||||
tx = base.NewChainTx(b.ChainID, 0, tx)
|
||||
stx := auth.NewMulti(tx)
|
||||
auth.Sign(stx, sender.Key)
|
||||
res := wire.BinaryBytes(stx.Wrap())
|
||||
return res
|
||||
}
|
||||
|
||||
func BenchmarkMakeTx(b *testing.B) {
|
||||
h := DefaultHandler("mycoin")
|
||||
app := NewBenchApp(h, "bench-chain", 10, false)
|
||||
b.ResetTimer()
|
||||
for i := 1; i <= b.N; i++ {
|
||||
txBytes := app.makeTx(true)
|
||||
if len(txBytes) < 2 {
|
||||
panic("cannot commit")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkTransfers(b *testing.B, app BenchApp, blockSize int, useFee bool) {
|
||||
// prepare txs
|
||||
txs := make([][]byte, b.N)
|
||||
for i := 1; i <= b.N; i++ {
|
||||
txBytes := app.makeTx(useFee)
|
||||
if len(txBytes) < 2 {
|
||||
panic("cannot make bytes")
|
||||
}
|
||||
txs[i-1] = txBytes
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 1; i <= b.N; i++ {
|
||||
res := app.App.DeliverTx(txs[i-1])
|
||||
if res.IsErr() {
|
||||
panic(res.Error())
|
||||
}
|
||||
if i%blockSize == 0 {
|
||||
res := app.App.Commit()
|
||||
if res.IsErr() {
|
||||
panic("cannot commit")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSimpleTransfer(b *testing.B) {
|
||||
benchmarks := []struct {
|
||||
accounts int
|
||||
blockSize int
|
||||
useFee bool
|
||||
toDisk bool
|
||||
}{
|
||||
{100, 10, false, false},
|
||||
{100, 10, true, false},
|
||||
{100, 200, false, false},
|
||||
{100, 200, true, false},
|
||||
{10000, 10, false, false},
|
||||
{10000, 10, true, false},
|
||||
{10000, 200, false, false},
|
||||
{10000, 200, true, false},
|
||||
{100, 10, false, true},
|
||||
{100, 10, true, true},
|
||||
{100, 200, false, true},
|
||||
{100, 200, true, true},
|
||||
{10000, 10, false, true},
|
||||
{10000, 10, true, true},
|
||||
{10000, 200, false, true},
|
||||
{10000, 200, true, true},
|
||||
}
|
||||
|
||||
for _, bb := range benchmarks {
|
||||
prefix := fmt.Sprintf("%d-%d", bb.accounts, bb.blockSize)
|
||||
if bb.useFee {
|
||||
prefix += "-fee"
|
||||
} else {
|
||||
prefix += "-nofee"
|
||||
}
|
||||
if bb.toDisk {
|
||||
prefix += "-persist"
|
||||
} else {
|
||||
prefix += "-memdb"
|
||||
}
|
||||
|
||||
h := DefaultHandler("mycoin")
|
||||
app := NewBenchApp(h, "bench-chain", bb.accounts, bb.toDisk)
|
||||
b.Run(prefix, func(sub *testing.B) {
|
||||
benchmarkTransfers(sub, app, bb.blockSize, bb.useFee)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
BenchmarkMakeTx-4 2000 603153 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-nofee-memdb-4 5000 313154 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-fee-memdb-4 5000 366534 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-nofee-memdb-4 5000 296381 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-fee-memdb-4 5000 350973 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-nofee-memdb-4 5000 351425 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-fee-memdb-4 3000 410855 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-nofee-memdb-4 5000 344839 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-fee-memdb-4 5000 394080 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-nofee-persist-4 3000 433890 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-fee-persist-4 3000 496133 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-nofee-persist-4 5000 310174 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-fee-persist-4 5000 366868 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-nofee-persist-4 2000 815755 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-fee-persist-4 2000 874532 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-nofee-persist-4 5000 567349 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-fee-persist-4 5000 621833 ns/op
|
||||
PASS
|
||||
ok github.com/tendermint/basecoin/benchmarks 93.047s
|
||||
@ -1,19 +0,0 @@
|
||||
BenchmarkMakeTx-4 2000 648379 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-nofee-memdb-4 5000 356487 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-fee-memdb-4 5000 413435 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-nofee-memdb-4 5000 321859 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-fee-memdb-4 5000 393578 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-nofee-memdb-4 5000 379129 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-fee-memdb-4 3000 480334 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-nofee-memdb-4 5000 384398 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-fee-memdb-4 3000 443481 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-nofee-persist-4 3000 498460 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-fee-persist-4 3000 559034 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-nofee-persist-4 5000 314090 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-fee-persist-4 5000 397457 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-nofee-persist-4 2000 845872 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-fee-persist-4 2000 929205 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-nofee-persist-4 5000 596601 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-fee-persist-4 5000 667093 ns/op
|
||||
PASS
|
||||
ok github.com/tendermint/basecoin/benchmarks 97.097s
|
||||
@ -1,19 +0,0 @@
|
||||
BenchmarkMakeTx-4 2000 660064 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-nofee-memdb-4 5000 338378 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-fee-memdb-4 5000 380171 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-nofee-memdb-4 5000 306365 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-fee-memdb-4 5000 359344 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-nofee-memdb-4 5000 366057 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-fee-memdb-4 3000 433549 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-nofee-memdb-4 5000 351662 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-fee-memdb-4 3000 421573 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-nofee-persist-4 3000 479848 ns/op
|
||||
BenchmarkSimpleTransfer/100-10-fee-persist-4 3000 544164 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-nofee-persist-4 5000 327999 ns/op
|
||||
BenchmarkSimpleTransfer/100-200-fee-persist-4 5000 385751 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-nofee-persist-4 2000 852128 ns/op
|
||||
BenchmarkSimpleTransfer/10000-10-fee-persist-4 2000 1055130 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-nofee-persist-4 5000 642872 ns/op
|
||||
BenchmarkSimpleTransfer/10000-200-fee-persist-4 3000 686337 ns/op
|
||||
PASS
|
||||
ok github.com/tendermint/basecoin/benchmarks 91.717s
|
||||
@ -1,33 +0,0 @@
|
||||
package auto
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// AutoCompleteCmd - command to generate bash autocompletions
|
||||
var AutoCompleteCmd = &cobra.Command{
|
||||
Use: "complete",
|
||||
Short: "generate bash autocompletions",
|
||||
RunE: doAutoComplete,
|
||||
}
|
||||
|
||||
// nolint - flags
|
||||
const (
|
||||
FlagOutput = "file"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AutoCompleteCmd.Flags().String(FlagOutput, "", "file to output bash autocompletion")
|
||||
AutoCompleteCmd.MarkFlagFilename(FlagOutput)
|
||||
}
|
||||
|
||||
func doAutoComplete(cmd *cobra.Command, args []string) error {
|
||||
output := viper.GetString(FlagOutput)
|
||||
if output == "" {
|
||||
return cmd.Root().GenBashCompletion(os.Stdout)
|
||||
}
|
||||
return cmd.Root().GenBashCompletionFile(output)
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
var exportCmd = &cobra.Command{
|
||||
Use: "export <file>",
|
||||
Short: "Export selected commits to given file",
|
||||
Long: `Exports the most recent commit to a binary file.
|
||||
If desired, you can select by an older height or validator hash.
|
||||
`,
|
||||
RunE: commands.RequireInit(exportCommit),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportCmd.Flags().Int(heightFlag, 0, "Show the commit with closest height to this")
|
||||
exportCmd.Flags().String(hashFlag, "", "Show the commit matching the validator hash")
|
||||
RootCmd.AddCommand(exportCmd)
|
||||
}
|
||||
|
||||
func exportCommit(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide a filepath to output")
|
||||
}
|
||||
path := args[0]
|
||||
|
||||
// load the seed as specified
|
||||
trust, _ := commands.GetProviders()
|
||||
h := viper.GetInt(heightFlag)
|
||||
hash := viper.GetString(hashFlag)
|
||||
fc, err := loadCommit(trust, h, hash, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now get the output file and write it
|
||||
return files.SaveFullCommitJSON(fc, path)
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
const (
|
||||
dryFlag = "dry-run"
|
||||
)
|
||||
|
||||
var importCmd = &cobra.Command{
|
||||
Use: "import <file>",
|
||||
Short: "Imports a new commit from the given file",
|
||||
Long: `Validate this file and update to the given commit if secure.`,
|
||||
RunE: commands.RequireInit(importCommit),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
importCmd.Flags().Bool(dryFlag, false, "Test the import fully, but do not import")
|
||||
RootCmd.AddCommand(importCmd)
|
||||
}
|
||||
|
||||
func importCommit(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide an input file")
|
||||
}
|
||||
|
||||
// prepare the certifier
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse the input file
|
||||
path := args[0]
|
||||
fc, err := files.LoadFullCommitJSON(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// just do simple checks in --dry-run
|
||||
if viper.GetBool(dryFlag) {
|
||||
fmt.Printf("Testing commit %d/%X\n", fc.Height(), fc.ValidatorsHash())
|
||||
err = fc.ValidateBasic(cert.ChainID())
|
||||
} else {
|
||||
fmt.Printf("Importing commit %d/%X\n", fc.Height(), fc.ValidatorsHash())
|
||||
err = cert.Update(fc)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package commits
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "commits",
|
||||
Short: "Verify commits from your local store",
|
||||
Long: `Commits allows you to inspect and update the validator set for the chain.
|
||||
|
||||
Since all security in a PoS system is based on having the correct validator
|
||||
set, it is important to inspect the commits to maintain the security, which
|
||||
is used to verify all header and merkle proofs.
|
||||
`,
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
const (
|
||||
heightFlag = "height"
|
||||
hashFlag = "hash"
|
||||
fileFlag = "file"
|
||||
)
|
||||
|
||||
var showCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show the details of one selected commit",
|
||||
Long: `Shows the most recent downloaded key by default.
|
||||
If desired, you can select by height, validator hash, or a file.
|
||||
`,
|
||||
RunE: commands.RequireInit(showCommit),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
showCmd.Flags().Int(heightFlag, 0, "Show the commit with closest height to this")
|
||||
showCmd.Flags().String(hashFlag, "", "Show the commit matching the validator hash")
|
||||
showCmd.Flags().String(fileFlag, "", "Show the commit stored in the given file")
|
||||
RootCmd.AddCommand(showCmd)
|
||||
}
|
||||
|
||||
func loadCommit(p certifiers.Provider, h int, hash, file string) (fc certifiers.FullCommit, err error) {
|
||||
// load the commit from the proper place
|
||||
if h != 0 {
|
||||
fc, err = p.GetByHeight(h)
|
||||
} else if hash != "" {
|
||||
var vhash []byte
|
||||
vhash, err = hex.DecodeString(hash)
|
||||
if err == nil {
|
||||
fc, err = p.GetByHash(vhash)
|
||||
}
|
||||
} else if file != "" {
|
||||
fc, err = files.LoadFullCommitJSON(file)
|
||||
} else {
|
||||
// default is latest commit
|
||||
fc, err = p.LatestCommit()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func showCommit(cmd *cobra.Command, args []string) error {
|
||||
trust, _ := commands.GetProviders()
|
||||
|
||||
h := viper.GetInt(heightFlag)
|
||||
hash := viper.GetString(hashFlag)
|
||||
file := viper.GetString(fileFlag)
|
||||
fc, err := loadCommit(trust, h, hash, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now render it!
|
||||
data, err := json.MarshalIndent(fc, "", " ")
|
||||
fmt.Println(string(data))
|
||||
return err
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update commit to current height if possible",
|
||||
RunE: commands.RequireInit(updateCommit),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
updateCmd.Flags().Int(heightFlag, 0, "Update to this height, not latest")
|
||||
RootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
|
||||
func updateCommit(cmd *cobra.Command, args []string) error {
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := viper.GetInt(heightFlag)
|
||||
var fc certifiers.FullCommit
|
||||
if h <= 0 {
|
||||
// get the lastest from our source
|
||||
fc, err = cert.Source.LatestCommit()
|
||||
} else {
|
||||
fc, err = cert.Source.GetByHeight(h)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// let the certifier do it's magic to update....
|
||||
fmt.Printf("Trying to update to height: %d...\n", fc.Height())
|
||||
err = cert.Update(fc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Success!")
|
||||
return nil
|
||||
}
|
||||
@ -1,154 +0,0 @@
|
||||
/*
|
||||
Package commands contains any general setup/helpers valid for all subcommands
|
||||
*/
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
)
|
||||
|
||||
var (
|
||||
trustedProv certifiers.Provider
|
||||
sourceProv certifiers.Provider
|
||||
)
|
||||
|
||||
const (
|
||||
ChainFlag = "chain-id"
|
||||
NodeFlag = "node"
|
||||
)
|
||||
|
||||
// AddBasicFlags adds --node and --chain-id, which we need for everything
|
||||
func AddBasicFlags(cmd *cobra.Command) {
|
||||
cmd.PersistentFlags().String(ChainFlag, "", "Chain ID of tendermint node")
|
||||
cmd.PersistentFlags().String(NodeFlag, "", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
}
|
||||
|
||||
// GetChainID reads ChainID from the flags
|
||||
func GetChainID() string {
|
||||
return viper.GetString(ChainFlag)
|
||||
}
|
||||
|
||||
// GetNode prepares a simple rpc.Client from the flags
|
||||
func GetNode() rpcclient.Client {
|
||||
return client.GetNode(viper.GetString(NodeFlag))
|
||||
}
|
||||
|
||||
// GetSourceProvider returns a provider pointing to an rpc handler
|
||||
func GetSourceProvider() certifiers.Provider {
|
||||
if sourceProv == nil {
|
||||
node := viper.GetString(NodeFlag)
|
||||
sourceProv = client.GetRPCProvider(node)
|
||||
}
|
||||
return sourceProv
|
||||
}
|
||||
|
||||
// GetTrustedProvider returns a reference to a local store with cache
|
||||
func GetTrustedProvider() certifiers.Provider {
|
||||
if trustedProv == nil {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
trustedProv = client.GetLocalProvider(rootDir)
|
||||
}
|
||||
return trustedProv
|
||||
}
|
||||
|
||||
// GetProviders creates a trusted (local) seed provider and a remote
|
||||
// provider based on configuration.
|
||||
func GetProviders() (trusted certifiers.Provider, source certifiers.Provider) {
|
||||
return GetTrustedProvider(), GetSourceProvider()
|
||||
}
|
||||
|
||||
// GetCertifier constructs a dynamic certifier from the config info
|
||||
func GetCertifier() (*certifiers.Inquiring, error) {
|
||||
// load up the latest store....
|
||||
trust := GetTrustedProvider()
|
||||
source := GetSourceProvider()
|
||||
chainID := GetChainID()
|
||||
return client.GetCertifier(chainID, trust, source)
|
||||
}
|
||||
|
||||
// ParseActor parses an address of form:
|
||||
// [<chain>:][<app>:]<hex address>
|
||||
// into a sdk.Actor.
|
||||
// If app is not specified or "", then assume auth.NameSigs
|
||||
func ParseActor(input string) (res sdk.Actor, err error) {
|
||||
chain, app := "", auth.NameSigs
|
||||
input = strings.TrimSpace(input)
|
||||
spl := strings.SplitN(input, ":", 3)
|
||||
|
||||
if len(spl) == 3 {
|
||||
chain = spl[0]
|
||||
spl = spl[1:]
|
||||
}
|
||||
if len(spl) == 2 {
|
||||
if spl[0] != "" {
|
||||
app = spl[0]
|
||||
}
|
||||
spl = spl[1:]
|
||||
}
|
||||
|
||||
addr, err := hex.DecodeString(cmn.StripHex(spl[0]))
|
||||
if err != nil {
|
||||
return res, errors.Errorf("Address is invalid hex: %v\n", err)
|
||||
}
|
||||
res = sdk.Actor{
|
||||
ChainID: chain,
|
||||
App: app,
|
||||
Address: addr,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseActors takes a comma-separated list of actors and parses them into
|
||||
// a slice
|
||||
func ParseActors(key string) (signers []sdk.Actor, err error) {
|
||||
var act sdk.Actor
|
||||
for _, k := range strings.Split(key, ",") {
|
||||
act, err = ParseActor(k)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signers = append(signers, act)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOneArg makes sure there is exactly one positional argument
|
||||
func GetOneArg(args []string, argname string) (string, error) {
|
||||
if len(args) == 0 {
|
||||
return "", errors.Errorf("Missing required argument [%s]", argname)
|
||||
}
|
||||
if len(args) > 1 {
|
||||
return "", errors.Errorf("Only accepts one argument [%s]", argname)
|
||||
}
|
||||
return args[0], nil
|
||||
}
|
||||
|
||||
// ParseHexFlag takes a flag name and parses the viper contents as hex
|
||||
func ParseHexFlag(flag string) ([]byte, error) {
|
||||
arg := viper.GetString(flag)
|
||||
if arg == "" {
|
||||
return nil, errors.Errorf("No such flag: %s", flag)
|
||||
}
|
||||
value, err := hex.DecodeString(cmn.StripHex(arg))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("Cannot parse %s", flag))
|
||||
}
|
||||
return value, nil
|
||||
|
||||
}
|
||||
@ -1,352 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var (
|
||||
dirPerm = os.FileMode(0700)
|
||||
)
|
||||
|
||||
//nolint
|
||||
const (
|
||||
CommitFlag = "commit"
|
||||
HashFlag = "valhash"
|
||||
GenesisFlag = "genesis"
|
||||
FlagTrustNode = "trust-node"
|
||||
|
||||
ConfigFile = "config.toml"
|
||||
)
|
||||
|
||||
// InitCmd will initialize the basecli store
|
||||
var InitCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize the light client for a new chain",
|
||||
RunE: runInit,
|
||||
}
|
||||
|
||||
var ResetCmd = &cobra.Command{
|
||||
Use: "reset_all",
|
||||
Short: "DANGEROUS: Wipe out all client data, including keys",
|
||||
RunE: runResetAll,
|
||||
}
|
||||
|
||||
func init() {
|
||||
InitCmd.Flags().Bool("force-reset", false, "Wipe clean an existing client store, except for keys")
|
||||
InitCmd.Flags().String(CommitFlag, "", "Commit file to import (optional)")
|
||||
InitCmd.Flags().String(HashFlag, "", "Trusted validator hash (must match to accept)")
|
||||
InitCmd.Flags().String(GenesisFlag, "", "Genesis file with chainid and validators (optional)")
|
||||
}
|
||||
|
||||
func runInit(cmd *cobra.Command, args []string) error {
|
||||
root := viper.GetString(cli.HomeFlag)
|
||||
if viper.GetBool("force-reset") {
|
||||
resetRoot(root, true)
|
||||
}
|
||||
|
||||
// make sure we don't have an existing client initialized
|
||||
inited, err := WasInited(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if inited {
|
||||
return errors.Errorf("%s already is initialized, --force-reset if you really want to wipe it out", root)
|
||||
}
|
||||
|
||||
// clean up dir if init fails
|
||||
err = doInit(cmd, root)
|
||||
if err != nil {
|
||||
resetRoot(root, true)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// doInit actually creates all the files, on error, we should revert it all
|
||||
func doInit(cmd *cobra.Command, root string) error {
|
||||
// read the genesis file if present, and populate --chain-id and --valhash
|
||||
err := checkGenesis(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = initConfigFile(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = initTrust()
|
||||
return err
|
||||
}
|
||||
|
||||
func runResetAll(cmd *cobra.Command, args []string) error {
|
||||
root := viper.GetString(cli.HomeFlag)
|
||||
resetRoot(root, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetRoot(root string, saveKeys bool) {
|
||||
tmp := filepath.Join(os.TempDir(), cmn.RandStr(16))
|
||||
keys := filepath.Join(root, "keys")
|
||||
if saveKeys {
|
||||
os.Rename(keys, tmp)
|
||||
}
|
||||
os.RemoveAll(root)
|
||||
if saveKeys {
|
||||
os.Mkdir(root, 0700)
|
||||
os.Rename(tmp, keys)
|
||||
}
|
||||
}
|
||||
|
||||
type Runable func(cmd *cobra.Command, args []string) error
|
||||
|
||||
// Any commands that require and init'ed basecoin directory
|
||||
// should wrap their RunE command with RequireInit
|
||||
// to make sure that the client is initialized.
|
||||
//
|
||||
// This cannot be called during PersistentPreRun,
|
||||
// as they are called from the most specific command first, and root last,
|
||||
// and the root command sets up viper, which is needed to find the home dir.
|
||||
func RequireInit(run Runable) Runable {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
// otherwise, run the wrappped command
|
||||
if viper.GetBool(FlagTrustNode) {
|
||||
return run(cmd, args)
|
||||
}
|
||||
|
||||
// first check if we were Init'ed and if not, return an error
|
||||
root := viper.GetString(cli.HomeFlag)
|
||||
init, err := WasInited(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !init {
|
||||
return errors.Errorf("You must run '%s init' first", cmd.Root().Name())
|
||||
}
|
||||
|
||||
// otherwise, run the wrappped command
|
||||
return run(cmd, args)
|
||||
}
|
||||
}
|
||||
|
||||
// WasInited returns true if a basecoin was previously initialized
|
||||
// in this directory. Important to ensure proper behavior.
|
||||
//
|
||||
// Returns error if we have filesystem errors
|
||||
func WasInited(root string) (bool, error) {
|
||||
// make sure there is a directory here in any case
|
||||
os.MkdirAll(root, dirPerm)
|
||||
|
||||
// check if there is a config.toml file
|
||||
cfgFile := filepath.Join(root, "config.toml")
|
||||
_, err := os.Stat(cfgFile)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// check if there are non-empty checkpoints and validators dirs
|
||||
dirs := []string{
|
||||
filepath.Join(root, files.CheckDir),
|
||||
filepath.Join(root, files.ValDir),
|
||||
}
|
||||
// if any of these dirs is empty, then we have no data
|
||||
for _, d := range dirs {
|
||||
empty, err := isEmpty(d)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if empty {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// looks like we have everything
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func checkGenesis(cmd *cobra.Command) error {
|
||||
genesis := viper.GetString(GenesisFlag)
|
||||
if genesis == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
doc, err := types.GenesisDocFromFile(genesis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Set(ChainFlag, doc.ChainID)
|
||||
hash := doc.ValidatorHash()
|
||||
hexHash := hex.EncodeToString(hash)
|
||||
flags.Set(HashFlag, hexHash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isEmpty returns false if we can read files in this dir.
|
||||
// if it doesn't exist, read issues, etc... return true
|
||||
//
|
||||
// TODO: should we handle errors otherwise?
|
||||
func isEmpty(dir string) (bool, error) {
|
||||
// check if we can read the directory, missing is fine, other error is not
|
||||
d, err := os.Open(dir)
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
// read to see if any (at least one) files here...
|
||||
files, err := d.Readdirnames(1)
|
||||
if err == io.EOF {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
empty := len(files) == 0
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Chain string `toml:"chain-id,omitempty"`
|
||||
Node string `toml:"node,omitempty"`
|
||||
Output string `toml:"output,omitempty"`
|
||||
Encoding string `toml:"encoding,omitempty"`
|
||||
}
|
||||
|
||||
func setConfig(flags *pflag.FlagSet, f string, v *string) {
|
||||
if flags.Changed(f) {
|
||||
*v = viper.GetString(f)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfigFile(cmd *cobra.Command) error {
|
||||
flags := cmd.Flags()
|
||||
var cfg Config
|
||||
|
||||
required := []string{ChainFlag, NodeFlag}
|
||||
for _, f := range required {
|
||||
if !flags.Changed(f) {
|
||||
return errors.Errorf(`"--%s" required`, f)
|
||||
}
|
||||
}
|
||||
|
||||
setConfig(flags, ChainFlag, &cfg.Chain)
|
||||
setConfig(flags, NodeFlag, &cfg.Node)
|
||||
setConfig(flags, cli.OutputFlag, &cfg.Output)
|
||||
setConfig(flags, cli.EncodingFlag, &cfg.Encoding)
|
||||
|
||||
out, err := os.Create(filepath.Join(viper.GetString(cli.HomeFlag), ConfigFile))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// save the config file
|
||||
err = toml.NewEncoder(out).Encode(cfg)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initTrust() (err error) {
|
||||
// create a provider....
|
||||
trust, source := GetProviders()
|
||||
|
||||
// load a commit file, or get data from the provider
|
||||
var fc certifiers.FullCommit
|
||||
commitFile := viper.GetString(CommitFlag)
|
||||
if commitFile == "" {
|
||||
fmt.Println("Loading validator set from tendermint rpc...")
|
||||
fc, err = source.LatestCommit()
|
||||
} else {
|
||||
fmt.Printf("Loading validators from file %s\n", commitFile)
|
||||
fc, err = files.LoadFullCommit(commitFile)
|
||||
}
|
||||
// can't load the commit? abort!
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure it is a proper commit
|
||||
err = fc.ValidateBasic(viper.GetString(ChainFlag))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate hash interactively or not
|
||||
hash := viper.GetString(HashFlag)
|
||||
if hash != "" {
|
||||
var hashb []byte
|
||||
hashb, err = hex.DecodeString(hash)
|
||||
if err == nil && !bytes.Equal(hashb, fc.ValidatorsHash()) {
|
||||
err = errors.Errorf("Validator hash doesn't match expectation: %X", fc.ValidatorsHash())
|
||||
}
|
||||
} else {
|
||||
err = validateHash(fc)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if accepted, store commit as current state
|
||||
trust.StoreCommit(fc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHash(fc certifiers.FullCommit) error {
|
||||
// ask the user to verify the validator hash
|
||||
fmt.Println("\nImportant: if this is incorrect, all interaction with the chain will be insecure!")
|
||||
fmt.Printf(" Given validator hash valid: %X\n", fc.ValidatorsHash())
|
||||
fmt.Println("Is this valid (y/n)?")
|
||||
valid := askForConfirmation()
|
||||
if !valid {
|
||||
return errors.New("Invalid validator hash, try init with proper commit later")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func askForConfirmation() bool {
|
||||
var resp string
|
||||
_, err := fmt.Scanln(&resp)
|
||||
if err != nil {
|
||||
fmt.Println("Please type yes or no and then press enter:")
|
||||
return askForConfirmation()
|
||||
}
|
||||
resp = strings.ToLower(resp)
|
||||
if resp == "y" || resp == "yes" {
|
||||
return true
|
||||
} else if resp == "n" || resp == "no" {
|
||||
return false
|
||||
} else {
|
||||
fmt.Println("Please type yes or no and then press enter:")
|
||||
return askForConfirmation()
|
||||
}
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
# Keys CLI
|
||||
|
||||
This is as much an example how to expose cobra/viper, as for a cli itself
|
||||
(I think this code is overkill for what go-keys needs). But please look at
|
||||
the commands, and give feedback and changes.
|
||||
|
||||
`RootCmd` calls some initialization functions (`cobra.OnInitialize` and `RootCmd.PersistentPreRunE`) which serve to connect environmental variables and cobra flags, as well as load the config file. It also validates the flags registered on root and creates the cryptomanager, which will be used by all subcommands.
|
||||
|
||||
## Help info
|
||||
|
||||
```
|
||||
# keys help
|
||||
|
||||
Keys allows you to manage your local keystore for tendermint.
|
||||
|
||||
These keys may be in any format supported by go-crypto and can be
|
||||
used by light-clients, full nodes, or any other application that
|
||||
needs to sign with a private key.
|
||||
|
||||
Usage:
|
||||
keys [command]
|
||||
|
||||
Available Commands:
|
||||
get Get details of one key
|
||||
list List all keys
|
||||
new Create a new public/private key pair
|
||||
serve Run the key manager as an http server
|
||||
update Change the password for a private key
|
||||
|
||||
Flags:
|
||||
--keydir string Directory to store private keys (subdir of root) (default "keys")
|
||||
-o, --output string Output format (text|json) (default "text")
|
||||
-r, --root string root directory for config and data (default "/Users/ethan/.tlc")
|
||||
|
||||
Use "keys [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
## Getting the config file
|
||||
|
||||
The first step is to load in root, by checking the following in order:
|
||||
|
||||
* -r, --root command line flag
|
||||
* TM_ROOT environmental variable
|
||||
* default ($HOME/.tlc evaluated at runtime)
|
||||
|
||||
Once the `rootDir` is established, the script looks for a config file named `keys.{json,toml,yaml,hcl}` in that directory and parses it. These values will provide defaults for flags of the same name.
|
||||
|
||||
There is an example config file for testing out locally, which writes keys to `./.mykeys`. You can
|
||||
|
||||
## Getting/Setting variables
|
||||
|
||||
When we want to get the value of a user-defined variable (eg. `output`), we can call `viper.GetString("output")`, which will do the following checks, until it finds a match:
|
||||
|
||||
* Is `--output` command line flag present?
|
||||
* Is `TM_OUTPUT` environmental variable set?
|
||||
* Was a config file found and does it have an `output` variable?
|
||||
* Is there a default set on the command line flag?
|
||||
|
||||
If no variable is set and there was no default, we get back "".
|
||||
|
||||
This setup allows us to have powerful command line flags, but use env variables or config files (local or 12-factor style) to avoid passing these arguments every time.
|
||||
|
||||
## Nesting structures
|
||||
|
||||
Sometimes we don't just need key-value pairs, but actually a multi-level config file, like
|
||||
|
||||
```
|
||||
[mail]
|
||||
from = "no-reply@example.com"
|
||||
server = "mail.example.com"
|
||||
port = 567
|
||||
password = "XXXXXX"
|
||||
```
|
||||
|
||||
This CLI is too simple to warant such a structure, but I think eg. tendermint could benefit from such an approach. Here are some pointers:
|
||||
|
||||
* [Accessing nested keys from config files](https://github.com/spf13/viper#accessing-nested-keys)
|
||||
* [Overriding nested values with envvars](https://www.netlify.com/blog/2016/09/06/creating-a-microservice-boilerplate-in-go/#nested-config-values) - the mentioned outstanding PR is already merged into master!
|
||||
* Overriding nested values with cli flags? (use `--log_config.level=info` ??)
|
||||
|
||||
I'd love to see an example of this fully worked out in a more complex CLI.
|
||||
|
||||
## Have your cake and eat it too
|
||||
|
||||
It's easy to render data different ways. Some better for viewing, some better for importing to other programs. You can just add some global (persistent) flags to control the output formatting, and everyone gets what they want.
|
||||
|
||||
```
|
||||
# keys list -e hex
|
||||
All keys:
|
||||
betty d0789984492b1674e276b590d56b7ae077f81adc
|
||||
john b77f4720b220d1411a649b6c7f1151eb6b1c226a
|
||||
|
||||
# keys list -e btc
|
||||
All keys:
|
||||
betty 3uTF4r29CbtnzsNHZoPSYsE4BDwH
|
||||
john 3ZGp2Md35iw4XVtRvZDUaAEkCUZP
|
||||
|
||||
# keys list -e b64 -o json
|
||||
[
|
||||
{
|
||||
"name": "betty",
|
||||
"address": "0HiZhEkrFnTidrWQ1Wt64Hf4Gtw=",
|
||||
"pubkey": {
|
||||
"type": "secp256k1",
|
||||
"data": "F83WvhT0KwttSoqQqd_0_r2ztUUaQix5EXdO8AZyREoV31Og780NW59HsqTAb2O4hZ-w-j0Z-4b2IjfdqqfhVQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "john",
|
||||
"address": "t39HILIg0UEaZJtsfxFR62scImo=",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "t1LFmbg_8UTwj-n1wkqmnTp6NfaOivokEhlYySlGYCY="
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
@ -1,49 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// deleteCmd represents the delete command
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete [name]",
|
||||
Short: "DANGER: Delete a private key from your system",
|
||||
RunE: runDeleteCmd,
|
||||
}
|
||||
|
||||
func runDeleteCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide a name for the key")
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
oldpass, err := getPassword("DANGER - enter password to permanently delete key:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = GetKeyManager().Delete(name, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Password deleted forever (uh oh!)")
|
||||
return nil
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// getCmd represents the get command
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get [name]",
|
||||
Short: "Get details of one key",
|
||||
Long: `Return public details of one local key.`,
|
||||
RunE: runGetCmd,
|
||||
}
|
||||
|
||||
func runGetCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide a name for the key")
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
info, err := GetKeyManager().Get(name)
|
||||
if err == nil {
|
||||
printInfo(info)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// listCmd represents the list command
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all keys",
|
||||
Long: `Return a list of all public keys stored by this key manager
|
||||
along with their associated name and address.`,
|
||||
RunE: runListCmd,
|
||||
}
|
||||
|
||||
func runListCmd(cmd *cobra.Command, args []string) error {
|
||||
infos, err := GetKeyManager().List()
|
||||
if err == nil {
|
||||
printInfos(infos)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
flagType = "type"
|
||||
flagNoBackup = "no-backup"
|
||||
)
|
||||
|
||||
// newCmd represents the new command
|
||||
var newCmd = &cobra.Command{
|
||||
Use: "new [name]",
|
||||
Short: "Create a new public/private key pair",
|
||||
Long: `Add a public/private key pair to the key store.
|
||||
The password muts be entered in the terminal and not
|
||||
passed as a command line argument for security.`,
|
||||
RunE: runNewCmd,
|
||||
}
|
||||
|
||||
func init() {
|
||||
newCmd.Flags().StringP(flagType, "t", "ed25519", "Type of key (ed25519|secp256k1|ledger")
|
||||
newCmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
||||
}
|
||||
|
||||
func runNewCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide a name for the key")
|
||||
}
|
||||
name := args[0]
|
||||
algo := viper.GetString(flagType)
|
||||
|
||||
pass, err := getCheckPassword("Enter a passphrase:", "Repeat the passphrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, seed, err := GetKeyManager().Create(name, pass, algo)
|
||||
if err == nil {
|
||||
printCreate(info, seed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type NewOutput struct {
|
||||
Key keys.Info `json:"key"`
|
||||
Seed string `json:"seed"`
|
||||
}
|
||||
|
||||
func printCreate(info keys.Info, seed string) {
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
printInfo(info)
|
||||
// print seed unless requested not to.
|
||||
if !viper.GetBool(flagNoBackup) {
|
||||
fmt.Println("**Important** write this seed phrase in a safe place.")
|
||||
fmt.Println("It is the only way to recover your account if you ever forget your password.\n")
|
||||
fmt.Println(seed)
|
||||
}
|
||||
case "json":
|
||||
out := NewOutput{Key: info}
|
||||
if !viper.GetBool(flagNoBackup) {
|
||||
out.Seed = seed
|
||||
}
|
||||
json, err := data.ToJSON(out)
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// recoverCmd represents the recover command
|
||||
var recoverCmd = &cobra.Command{
|
||||
Use: "recover [name]",
|
||||
Short: "Recover a private key from a seed phrase",
|
||||
Long: `Recover a private key from a seed phrase.
|
||||
|
||||
I really hope you wrote this down when you created the new key.
|
||||
The seed is only displayed on creation, never again.
|
||||
|
||||
You can also use this to copy a key between multiple testnets,
|
||||
simply by "recovering" the key in the other nets you want to copy
|
||||
to. Of course, it has no coins on the other nets, just the same address.`,
|
||||
RunE: runRecoverCmd,
|
||||
}
|
||||
|
||||
func runRecoverCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide a name for the key")
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
pass, err := getPassword("Enter the new passphrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// not really a password... huh?
|
||||
seed, err := getSeed("Enter your recovery seed phrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := GetKeyManager().Recover(name, pass, seed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printInfo(info)
|
||||
return nil
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
)
|
||||
|
||||
var (
|
||||
manager keys.Manager
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "keys",
|
||||
Short: "Key manager for tendermint clients",
|
||||
Long: `Keys allows you to manage your local keystore for tendermint.
|
||||
|
||||
These keys may be in any format supported by go-crypto and can be
|
||||
used by light-clients, full nodes, or any other application that
|
||||
needs to sign with a private key.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(getCmd)
|
||||
RootCmd.AddCommand(listCmd)
|
||||
RootCmd.AddCommand(newCmd)
|
||||
RootCmd.AddCommand(updateCmd)
|
||||
RootCmd.AddCommand(deleteCmd)
|
||||
RootCmd.AddCommand(recoverCmd)
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// updateCmd represents the update command
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update [name]",
|
||||
Short: "Change the password for a private key",
|
||||
RunE: runUpdateCmd,
|
||||
}
|
||||
|
||||
func runUpdateCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide a name for the key")
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
oldpass, err := getPassword("Enter the current passphrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newpass, err := getCheckPassword("Enter the new passphrase:", "Repeat the new passphrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = GetKeyManager().Update(name, oldpass, newpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Password successfully updated!")
|
||||
return nil
|
||||
}
|
||||
@ -1,131 +0,0 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
data "github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
)
|
||||
|
||||
const MinPassLength = 10
|
||||
|
||||
// GetKeyManager initializes a key manager based on the configuration
|
||||
func GetKeyManager() keys.Manager {
|
||||
if manager == nil {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
manager = client.GetKeyManager(rootDir)
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
// if we read from non-tty, we just need to init the buffer reader once,
|
||||
// in case we try to read multiple passwords (eg. update)
|
||||
var buf *bufio.Reader
|
||||
|
||||
func inputIsTty() bool {
|
||||
return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
|
||||
}
|
||||
|
||||
func stdinPassword() (string, error) {
|
||||
if buf == nil {
|
||||
buf = bufio.NewReader(os.Stdin)
|
||||
}
|
||||
pass, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(pass), nil
|
||||
}
|
||||
|
||||
func getPassword(prompt string) (pass string, err error) {
|
||||
if inputIsTty() {
|
||||
pass, err = speakeasy.Ask(prompt)
|
||||
} else {
|
||||
pass, err = stdinPassword()
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(pass) < MinPassLength {
|
||||
return "", errors.Errorf("Password must be at least %d characters", MinPassLength)
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
|
||||
func getSeed(prompt string) (seed string, err error) {
|
||||
if inputIsTty() {
|
||||
fmt.Println(prompt)
|
||||
}
|
||||
seed, err = stdinPassword()
|
||||
seed = strings.TrimSpace(seed)
|
||||
return
|
||||
}
|
||||
|
||||
func getCheckPassword(prompt, prompt2 string) (string, error) {
|
||||
// simple read on no-tty
|
||||
if !inputIsTty() {
|
||||
return getPassword(prompt)
|
||||
}
|
||||
|
||||
// TODO: own function???
|
||||
pass, err := getPassword(prompt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pass2, err := getPassword(prompt2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if pass != pass2 {
|
||||
return "", errors.New("Passphrases don't match")
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
|
||||
func printInfo(info keys.Info) {
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
addr, err := data.ToText(info.Address)
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
}
|
||||
sep := "\t\t"
|
||||
if len(info.Name) > 7 {
|
||||
sep = "\t"
|
||||
}
|
||||
fmt.Printf("%s%s%s\n", info.Name, sep, addr)
|
||||
case "json":
|
||||
json, err := data.ToJSON(info)
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
}
|
||||
|
||||
func printInfos(infos keys.Infos) {
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
fmt.Println("All keys:")
|
||||
for _, i := range infos {
|
||||
printInfo(i)
|
||||
}
|
||||
case "json":
|
||||
json, err := data.ToJSON(infos)
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "proxy",
|
||||
Short: "Run proxy server, verifying tendermint rpc",
|
||||
Long: `This node will run a secure proxy to a tendermint rpc server.
|
||||
|
||||
All calls that can be tracked back to a block header by a proof
|
||||
will be verified before passing them back to the caller. Other that
|
||||
that it will present the same interface as a full tendermint node,
|
||||
just with added trust and running locally.`,
|
||||
RunE: commands.RequireInit(runProxy),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
const (
|
||||
bindFlag = "serve"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.Flags().String(bindFlag, ":8888", "Serve the proxy on the given port")
|
||||
}
|
||||
|
||||
// TODO: pass in a proper logger
|
||||
var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
func init() {
|
||||
logger = logger.With("module", "main")
|
||||
logger = log.NewFilter(logger, log.AllowInfo())
|
||||
}
|
||||
|
||||
func runProxy(cmd *cobra.Command, args []string) error {
|
||||
// First, connect a client
|
||||
node := commands.GetNode()
|
||||
bind := viper.GetString(bindFlag)
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sc := client.SecureClient(node, cert)
|
||||
|
||||
err = client.StartProxy(sc, bind, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmn.TrapSignal(func() {
|
||||
// TODO: close up shop
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/iavl"
|
||||
"github.com/tendermint/light-client/proofs"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// GetParsed does most of the work of the query commands, but is quite
|
||||
// opinionated, so if you want more control about parsing, call Get
|
||||
// directly.
|
||||
//
|
||||
// It will try to get the proof for the given key. If it is successful,
|
||||
// it will return the height and also unserialize proof.Data into the data
|
||||
// argument (so pass in a pointer to the appropriate struct)
|
||||
func GetParsed(key []byte, data interface{}, height int, prove bool) (uint64, error) {
|
||||
bs, h, err := Get(key, height, prove)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = wire.ReadBinaryBytes(bs, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Get queries the given key and returns the value stored there and the
|
||||
// height we checked at.
|
||||
//
|
||||
// If prove is true (and why shouldn't it be?),
|
||||
// the data is fully verified before returning. If prove is false,
|
||||
// we just repeat whatever any (potentially malicious) node gives us.
|
||||
// Only use that if you are running the full node yourself,
|
||||
// and it is localhost or you have a secure connection (not HTTP)
|
||||
func Get(key []byte, height int, prove bool) (data.Bytes, uint64, error) {
|
||||
if height < 0 {
|
||||
return nil, 0, fmt.Errorf("Height cannot be negative")
|
||||
}
|
||||
|
||||
if !prove {
|
||||
node := commands.GetNode()
|
||||
resp, err := node.ABCIQueryWithOptions("/key", key,
|
||||
rpcclient.ABCIQueryOptions{Trusted: true, Height: uint64(height)})
|
||||
return data.Bytes(resp.Value), resp.Height, err
|
||||
}
|
||||
val, h, _, err := GetWithProof(key, height)
|
||||
return val, h, err
|
||||
}
|
||||
|
||||
// GetWithProof returns the values stored under a given key at the named
|
||||
// height as in Get. Additionally, it will return a validated merkle
|
||||
// proof for the key-value pair if it exists, and all checks pass.
|
||||
func GetWithProof(key []byte, height int) (data.Bytes, uint64, iavl.KeyProof, error) {
|
||||
node := commands.GetNode()
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
return client.GetWithProof(key, height, node, cert)
|
||||
}
|
||||
|
||||
// ParseHexKey parses the key flag as hex and converts to bytes or returns error
|
||||
// argname is used to customize the error message
|
||||
func ParseHexKey(args []string, argname string) ([]byte, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, errors.Errorf("Missing required argument [%s]", argname)
|
||||
}
|
||||
if len(args) > 1 {
|
||||
return nil, errors.Errorf("Only accepts one argument [%s]", argname)
|
||||
}
|
||||
rawkey := args[0]
|
||||
if rawkey == "" {
|
||||
return nil, errors.Errorf("[%s] argument must be non-empty ", argname)
|
||||
}
|
||||
// with tx, we always just parse key as hex and use to lookup
|
||||
return proofs.ParseHexKey(rawkey)
|
||||
}
|
||||
|
||||
// GetHeight reads the viper config for the query height
|
||||
func GetHeight() int {
|
||||
return viper.GetInt(FlagHeight)
|
||||
}
|
||||
|
||||
type proof struct {
|
||||
Height uint64 `json:"height"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// FoutputProof writes the output of wrapping height and info
|
||||
// in the form {"data": <the_data>, "height": <the_height>}
|
||||
// to the provider io.Writer
|
||||
func FoutputProof(w io.Writer, v interface{}, height uint64) error {
|
||||
wrap := &proof{height, v}
|
||||
blob, err := data.ToJSON(wrap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(w, "%s\n", blob)
|
||||
return err
|
||||
}
|
||||
|
||||
// OutputProof prints the proof to stdout
|
||||
// reuse this for printing proofs and we should enhance this for text/json,
|
||||
// better presentation of height
|
||||
func OutputProof(data interface{}, height uint64) error {
|
||||
return FoutputProof(os.Stdout, data, height)
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
FlagHeight = "height"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "query",
|
||||
Short: "Get and store merkle proofs for blockchain data",
|
||||
Long: `Proofs allows you to validate data and merkle proofs.
|
||||
|
||||
These proofs tie the data to a checkpoint, which is managed by "seeds".
|
||||
Here we can validate these proofs and import/export them to prove specific
|
||||
data to other peers as needed.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.PersistentFlags().Int(FlagHeight, 0, "Height to query (skip to use latest block)")
|
||||
RootCmd.PersistentFlags().Bool(commands.FlagTrustNode, false,
|
||||
"DANGEROUS: blindly trust all results from the server")
|
||||
RootCmd.PersistentFlags().MarkHidden(commands.FlagTrustNode)
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// KeyQueryCmd - CLI command to query a state by key with proof
|
||||
var KeyQueryCmd = &cobra.Command{
|
||||
Use: "key [key]",
|
||||
Short: "Handle proofs for state of abci app",
|
||||
Long: `This will look up a given key in the abci app, verify the proof,
|
||||
and output it as hex.
|
||||
|
||||
If you want json output, use an app-specific command that knows key and value structure.`,
|
||||
RunE: commands.RequireInit(keyQueryCmd),
|
||||
}
|
||||
|
||||
// Note: we cannot yse GetAndParseAppProof here, as we don't use go-wire to
|
||||
// parse the object, but rather return the raw bytes
|
||||
func keyQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
// parse cli
|
||||
key, err := ParseHexKey(args, "key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
|
||||
val, h, err := Get(key, GetHeight(), prove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return OutputProof(val, h)
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// TxQueryCmd - CLI command to query a transaction with proof
|
||||
var TxQueryCmd = &cobra.Command{
|
||||
Use: "tx [txhash]",
|
||||
Short: "Handle proofs of commited txs",
|
||||
Long: `Proofs allows you to validate abci state with merkle proofs.
|
||||
|
||||
These proofs tie the data to a checkpoint, which is managed by "seeds".
|
||||
Here we can validate these proofs and import/export them to prove specific
|
||||
data to other peers as needed.
|
||||
`,
|
||||
RunE: commands.RequireInit(txQueryCmd),
|
||||
}
|
||||
|
||||
func txQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
// parse cli
|
||||
// TODO: when querying historical heights is allowed... pass it
|
||||
// height := GetHeight()
|
||||
bkey, err := ParseHexKey(args, "txhash")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the proof -> this will be used by all prover commands
|
||||
node := commands.GetNode()
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
res, err := node.Tx(bkey, prove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// no checks if we don't get a proof
|
||||
if !prove {
|
||||
return showTx(res.Height, res.Tx)
|
||||
}
|
||||
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
check, err := client.GetCertifiedCommit(res.Height, node, cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = res.Proof.Validate(check.Header.DataHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// note that we return res.Proof.Data, not res.Tx,
|
||||
// as res.Proof.Validate only verifies res.Proof.Data
|
||||
return showTx(res.Height, res.Proof.Data)
|
||||
}
|
||||
|
||||
// showTx parses anything that was previously registered as interface{}
|
||||
func showTx(h int, tx types.Tx) error {
|
||||
var info interface{}
|
||||
err := wire.ReadBinaryBytes(tx, &info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return OutputProof(info, uint64(h))
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
var waitCmd = &cobra.Command{
|
||||
Use: "wait",
|
||||
Short: "Wait until a given height, or number of new blocks",
|
||||
RunE: commands.RequireInit(runWait),
|
||||
}
|
||||
|
||||
func init() {
|
||||
waitCmd.Flags().Int(FlagHeight, -1, "wait for block height")
|
||||
waitCmd.Flags().Int(FlagDelta, -1, "wait for given number of nodes")
|
||||
}
|
||||
|
||||
func runWait(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
h := viper.GetInt(FlagHeight)
|
||||
if h == -1 {
|
||||
// read from delta
|
||||
d := viper.GetInt(FlagDelta)
|
||||
if d == -1 {
|
||||
return errors.New("Must set --height or --delta")
|
||||
}
|
||||
status, err := c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h = status.LatestBlockHeight + d
|
||||
}
|
||||
|
||||
// now wait
|
||||
err := client.WaitForHeight(c, h, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Chain now at height %d\n", h)
|
||||
return nil
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Query the status of the node",
|
||||
RunE: commands.RequireInit(runStatus),
|
||||
}
|
||||
|
||||
func runStatus(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
status, err := c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(status)
|
||||
}
|
||||
|
||||
var infoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Query info on the abci app",
|
||||
RunE: commands.RequireInit(runInfo),
|
||||
}
|
||||
|
||||
func runInfo(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
info, err := c.ABCIInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(info)
|
||||
}
|
||||
|
||||
var genesisCmd = &cobra.Command{
|
||||
Use: "genesis",
|
||||
Short: "Query the genesis of the node",
|
||||
RunE: commands.RequireInit(runGenesis),
|
||||
}
|
||||
|
||||
func runGenesis(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
genesis, err := c.Genesis()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(genesis)
|
||||
}
|
||||
|
||||
var validatorsCmd = &cobra.Command{
|
||||
Use: "validators",
|
||||
Short: "Query the validators of the node",
|
||||
RunE: commands.RequireInit(runValidators),
|
||||
}
|
||||
|
||||
func runValidators(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
validators, err := c.Validators(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(validators)
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/go-wire/data"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagDelta = "delta"
|
||||
FlagHeight = "height"
|
||||
FlagMax = "max"
|
||||
FlagMin = "min"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "rpc",
|
||||
Short: "Query the tendermint rpc, validating everything with a proof",
|
||||
}
|
||||
|
||||
// TODO: add support for subscribing to events????
|
||||
func init() {
|
||||
RootCmd.AddCommand(
|
||||
statusCmd,
|
||||
infoCmd,
|
||||
genesisCmd,
|
||||
validatorsCmd,
|
||||
blockCmd,
|
||||
commitCmd,
|
||||
headersCmd,
|
||||
waitCmd,
|
||||
)
|
||||
}
|
||||
|
||||
func getSecureNode() (rpcclient.Client, error) {
|
||||
// First, connect a client
|
||||
c := commands.GetNode()
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.SecureClient(c, cert), nil
|
||||
}
|
||||
|
||||
// printResult just writes the struct to the console, returns an error if it can't
|
||||
func printResult(res interface{}) error {
|
||||
// TODO: handle text mode
|
||||
// switch viper.Get(cli.OutputFlag) {
|
||||
// case "text":
|
||||
// case "json":
|
||||
json, err := data.ToJSON(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
return nil
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
func init() {
|
||||
blockCmd.Flags().Int(FlagHeight, -1, "block height")
|
||||
commitCmd.Flags().Int(FlagHeight, -1, "block height")
|
||||
headersCmd.Flags().Int(FlagMin, -1, "minimum block height")
|
||||
headersCmd.Flags().Int(FlagMax, -1, "maximum block height")
|
||||
}
|
||||
|
||||
var blockCmd = &cobra.Command{
|
||||
Use: "block",
|
||||
Short: "Get a validated block at a given height",
|
||||
RunE: commands.RequireInit(runBlock),
|
||||
}
|
||||
|
||||
func runBlock(cmd *cobra.Command, args []string) error {
|
||||
c, err := getSecureNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := viper.GetInt(FlagHeight)
|
||||
block, err := c.Block(&h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(block)
|
||||
}
|
||||
|
||||
var commitCmd = &cobra.Command{
|
||||
Use: "commit",
|
||||
Short: "Get the header and commit signature at a given height",
|
||||
RunE: commands.RequireInit(runCommit),
|
||||
}
|
||||
|
||||
func runCommit(cmd *cobra.Command, args []string) error {
|
||||
c, err := getSecureNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := viper.GetInt(FlagHeight)
|
||||
commit, err := c.Commit(&h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(commit)
|
||||
}
|
||||
|
||||
var headersCmd = &cobra.Command{
|
||||
Use: "headers",
|
||||
Short: "Get all headers in the given height range",
|
||||
RunE: commands.RequireInit(runHeaders),
|
||||
}
|
||||
|
||||
func runHeaders(cmd *cobra.Command, args []string) error {
|
||||
c, err := getSecureNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
min := viper.GetInt(FlagMin)
|
||||
max := viper.GetInt(FlagMax)
|
||||
headers, err := c.BlockchainInfo(min, max)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(headers)
|
||||
}
|
||||
@ -1,250 +0,0 @@
|
||||
package txs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
keycmd "github.com/cosmos/cosmos-sdk/client/commands/keys"
|
||||
"github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
)
|
||||
|
||||
// Validatable represents anything that can be Validated
|
||||
type Validatable interface {
|
||||
ValidateBasic() error
|
||||
}
|
||||
|
||||
// GetSigner returns the pub key that will sign the tx
|
||||
// returns empty key if no name provided
|
||||
func GetSigner() crypto.PubKey {
|
||||
name := viper.GetString(FlagName)
|
||||
manager := keycmd.GetKeyManager()
|
||||
info, _ := manager.Get(name) // error -> empty pubkey
|
||||
return info.PubKey
|
||||
}
|
||||
|
||||
// GetSignerAct returns the address of the signer of the tx
|
||||
// (as we still only support single sig)
|
||||
func GetSignerAct() (res sdk.Actor) {
|
||||
// this could be much cooler with multisig...
|
||||
signer := GetSigner()
|
||||
if !signer.Empty() {
|
||||
res = auth.SigPerm(signer.Address())
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// DoTx is a helper function for the lazy :)
|
||||
//
|
||||
// It uses only public functions and goes through the standard sequence of
|
||||
// wrapping the tx with middleware layers, signing it, either preparing it,
|
||||
// or posting it and displaying the result.
|
||||
//
|
||||
// If you want a non-standard flow, just call the various functions directly.
|
||||
// eg. if you already set the middleware layers in your code, or want to
|
||||
// output in another format.
|
||||
func DoTx(tx interface{}) (err error) {
|
||||
tx, err = Middleware.Wrap(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = SignTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bres, err := PrepareOrPostTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bres == nil {
|
||||
return nil // successful prep, nothing left to do
|
||||
}
|
||||
return OutputTx(bres) // print response of the post
|
||||
|
||||
}
|
||||
|
||||
// SignTx will validate the tx, and signs it if it is wrapping a Signable.
|
||||
// Modifies tx in place, and returns an error if it should sign but couldn't
|
||||
func SignTx(tx interface{}) (err error) {
|
||||
// TODO: validate tx client-side
|
||||
// err := tx.ValidateBasic()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// abort early if we don't want to sign
|
||||
if viper.GetBool(FlagNoSign) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := viper.GetString(FlagName)
|
||||
manager := keycmd.GetKeyManager()
|
||||
|
||||
if sign, ok := tx.(keys.Signable); ok {
|
||||
// TODO: allow us not to sign? if so then what use?
|
||||
if name == "" {
|
||||
return errors.New("--name is required to sign tx")
|
||||
}
|
||||
err = signTx(manager, sign, name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// PrepareOrPostTx checks the flags to decide to prepare the tx for future
|
||||
// multisig, or to post it to the node. Returns error on any failure.
|
||||
// If no error and the result is nil, it means it already wrote to file,
|
||||
// no post, no need to do more.
|
||||
func PrepareOrPostTx(tx interface{}) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
wrote, err := PrepareTx(tx)
|
||||
// error in prep
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// successfully wrote the tx!
|
||||
if wrote {
|
||||
return nil, nil
|
||||
}
|
||||
// or try to post it
|
||||
return PostTx(tx)
|
||||
}
|
||||
|
||||
// PrepareTx checks for FlagPrepare and if set, write the tx as json
|
||||
// to the specified location for later multi-sig. Returns true if it
|
||||
// handled the tx (no futher work required), false if it did nothing
|
||||
// (and we should post the tx)
|
||||
func PrepareTx(tx interface{}) (bool, error) {
|
||||
prep := viper.GetString(FlagPrepare)
|
||||
if prep == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
js, err := data.ToJSON(tx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = writeOutput(prep, js)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// PostTx does all work once we construct a proper struct
|
||||
// it validates the data, signs if needed, transforms to bytes,
|
||||
// and posts to the node.
|
||||
func PostTx(tx interface{}) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
packet := wire.BinaryBytes(tx)
|
||||
// post the bytes
|
||||
node := commands.GetNode()
|
||||
return node.BroadcastTxCommit(packet)
|
||||
}
|
||||
|
||||
// OutputTx validates if success and prints the tx result to stdout
|
||||
func OutputTx(res *ctypes.ResultBroadcastTxCommit) error {
|
||||
if res.CheckTx.IsErr() {
|
||||
return errors.Errorf("CheckTx: (%d): %s", res.CheckTx.Code, res.CheckTx.Log)
|
||||
}
|
||||
if res.DeliverTx.IsErr() {
|
||||
return errors.Errorf("DeliverTx: (%d): %s", res.DeliverTx.Code, res.DeliverTx.Log)
|
||||
}
|
||||
js, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(js))
|
||||
return nil
|
||||
}
|
||||
|
||||
func signTx(manager keys.Manager, tx keys.Signable, name string) error {
|
||||
prompt := fmt.Sprintf("Please enter passphrase for %s: ", name)
|
||||
pass, err := getPassword(prompt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return manager.Sign(name, pass, tx)
|
||||
}
|
||||
|
||||
// if we read from non-tty, we just need to init the buffer reader once,
|
||||
// in case we try to read multiple passwords
|
||||
var buf *bufio.Reader
|
||||
|
||||
func inputIsTty() bool {
|
||||
return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
|
||||
}
|
||||
|
||||
func stdinPassword() (string, error) {
|
||||
if buf == nil {
|
||||
buf = bufio.NewReader(os.Stdin)
|
||||
}
|
||||
pass, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(pass), nil
|
||||
}
|
||||
|
||||
func getPassword(prompt string) (pass string, err error) {
|
||||
if inputIsTty() {
|
||||
pass, err = speakeasy.Ask(prompt)
|
||||
} else {
|
||||
pass, err = stdinPassword()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeOutput(file string, d []byte) error {
|
||||
var writer io.Writer
|
||||
if file == "-" {
|
||||
writer = os.Stdout
|
||||
} else {
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
writer = f
|
||||
}
|
||||
|
||||
_, err := writer.Write(d)
|
||||
// this returns nil if err == nil
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func readInput(file string) ([]byte, error) {
|
||||
var reader io.Reader
|
||||
// get the input stream
|
||||
if file == "" || file == "-" {
|
||||
reader = os.Stdin
|
||||
} else {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
reader = f
|
||||
}
|
||||
|
||||
// and read it all!
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
package txs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
FlagName = "name"
|
||||
FlagNoSign = "no-sign"
|
||||
FlagIn = "in"
|
||||
FlagPrepare = "prepare"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "tx",
|
||||
Short: "Post tx from json input",
|
||||
RunE: doRawTx,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.PersistentFlags().String(FlagName, "", "name to sign the tx")
|
||||
RootCmd.PersistentFlags().Bool(FlagNoSign, false, "don't add a signature")
|
||||
RootCmd.PersistentFlags().String(FlagPrepare, "", "file to store prepared tx")
|
||||
RootCmd.Flags().String(FlagIn, "", "file with tx in json format")
|
||||
}
|
||||
|
||||
func doRawTx(cmd *cobra.Command, args []string) error {
|
||||
raw, err := readInput(viper.GetString(FlagIn))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse the input
|
||||
var tx interface{}
|
||||
err = json.Unmarshal(raw, &tx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// sign it
|
||||
err = SignTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// otherwise, post it and display response
|
||||
bres, err := PrepareOrPostTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bres == nil {
|
||||
return nil // successful prep, nothing left to do
|
||||
}
|
||||
return OutputTx(bres) // print response of the post
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package txs
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
// Middleware must be set in main.go to defined the wrappers we should apply
|
||||
Middleware Wrapper
|
||||
)
|
||||
|
||||
// Wrapper defines the information needed for each middleware package that
|
||||
// wraps the data. They should read all configuration out of bounds via viper.
|
||||
type Wrapper interface {
|
||||
Wrap(interface{}) (interface{}, error)
|
||||
Register(*pflag.FlagSet)
|
||||
}
|
||||
|
||||
// Wrappers combines a list of wrapper middlewares.
|
||||
// The first one is the inner-most layer, eg. Fee, Nonce, Chain, Auth
|
||||
type Wrappers []Wrapper
|
||||
|
||||
var _ Wrapper = Wrappers{}
|
||||
|
||||
// Wrap applies the wrappers to the passed in tx in order,
|
||||
// aborting on the first error
|
||||
func (ws Wrappers) Wrap(tx interface{}) (interface{}, error) {
|
||||
var err error
|
||||
for _, w := range ws {
|
||||
tx, err = w.Wrap(tx)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return tx, err
|
||||
}
|
||||
|
||||
// Register adds any needed flags to the command
|
||||
func (ws Wrappers) Register(fs *pflag.FlagSet) {
|
||||
for _, w := range ws {
|
||||
w.Register(fs)
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
// CommitHash should be filled by linker flags
|
||||
var CommitHash = ""
|
||||
|
||||
// VersionCmd - command to show the application version
|
||||
var VersionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show version info",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("%s-%s\n", version.Version, CommitHash)
|
||||
},
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
certclient "github.com/tendermint/light-client/certifiers/client"
|
||||
certerr "github.com/tendermint/light-client/certifiers/errors"
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
|
||||
"github.com/tendermint/light-client/proofs"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
// GetNode prepares a simple rpc.Client for the given endpoint
|
||||
func GetNode(url string) rpcclient.Client {
|
||||
return rpcclient.NewHTTP(url, "/websocket")
|
||||
}
|
||||
|
||||
// GetRPCProvider retuns a certifier compatible data source using
|
||||
// tendermint RPC
|
||||
func GetRPCProvider(url string) certifiers.Provider {
|
||||
return certclient.NewHTTPProvider(url)
|
||||
}
|
||||
|
||||
// GetLocalProvider returns a reference to a file store of headers
|
||||
// wrapped with an in-memory cache
|
||||
func GetLocalProvider(dir string) certifiers.Provider {
|
||||
return certifiers.NewCacheProvider(
|
||||
certifiers.NewMemStoreProvider(),
|
||||
files.NewProvider(dir),
|
||||
)
|
||||
}
|
||||
|
||||
// GetCertifier initializes an inquiring certifier given a fixed chainID
|
||||
// and a local source of trusted data with at least one seed
|
||||
func GetCertifier(chainID string, trust certifiers.Provider,
|
||||
source certifiers.Provider) (*certifiers.Inquiring, error) {
|
||||
|
||||
// this gets the most recent verified commit
|
||||
fc, err := trust.LatestCommit()
|
||||
if certerr.IsCommitNotFoundErr(err) {
|
||||
return nil, errors.New("Please run init first to establish a root of trust")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := certifiers.NewInquiring(chainID, fc, trust, source)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// SecureClient uses a given certifier to wrap an connection to an untrusted
|
||||
// host and return a cryptographically secure rpc client.
|
||||
func SecureClient(c rpcclient.Client, cert *certifiers.Inquiring) rpcclient.Client {
|
||||
return proofs.Wrap(c, cert)
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
//--------------------------------------------
|
||||
|
||||
var errNoData = fmt.Errorf("No data returned for query")
|
||||
|
||||
// IsNoDataErr checks whether an error is due to a query returning empty data
|
||||
func IsNoDataErr(err error) bool {
|
||||
return errors.Cause(err) == errNoData
|
||||
}
|
||||
|
||||
func ErrNoData() error {
|
||||
return errors.WithStack(errNoData)
|
||||
}
|
||||
|
||||
//--------------------------------------------
|
||||
@ -1,18 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestErrorNoData(t *testing.T) {
|
||||
e1 := ErrNoData()
|
||||
e1.Error()
|
||||
assert.True(t, IsNoDataErr(e1))
|
||||
|
||||
e2 := errors.New("foobar")
|
||||
assert.False(t, IsNoDataErr(e2))
|
||||
assert.False(t, IsNoDataErr(nil))
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package client
|
||||
|
||||
/*
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-crypto/keys/cryptostore"
|
||||
"github.com/tendermint/go-crypto/keys/storage/filestorage"
|
||||
)
|
||||
|
||||
// KeySubdir is the directory name under root where we store the keys
|
||||
const KeySubdir = "keys"
|
||||
|
||||
// GetKeyManager initializes a key manager based on the configuration
|
||||
func GetKeyManager(rootDir string) keys.Manager {
|
||||
keyDir := filepath.Join(rootDir, KeySubdir)
|
||||
// TODO: smarter loading??? with language and fallback?
|
||||
codec := keys.MustLoadCodec("english")
|
||||
|
||||
// and construct the key manager
|
||||
manager := cryptostore.New(
|
||||
cryptostore.SecretBox,
|
||||
filestorage.New(keyDir),
|
||||
codec,
|
||||
)
|
||||
return manager
|
||||
}
|
||||
*/
|
||||
@ -1,68 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
"github.com/tendermint/tendermint/rpc/core"
|
||||
rpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
)
|
||||
|
||||
const (
|
||||
wsEndpoint = "/websocket"
|
||||
)
|
||||
|
||||
// StartProxy will start the websocket manager on the client,
|
||||
// set up the rpc routes to proxy via the given client,
|
||||
// and start up an http/rpc server on the location given by bind (eg. :1234)
|
||||
func StartProxy(c rpcclient.Client, bind string, logger log.Logger) error {
|
||||
c.Start()
|
||||
r := RPCRoutes(c)
|
||||
|
||||
// build the handler...
|
||||
mux := http.NewServeMux()
|
||||
rpc.RegisterRPCFuncs(mux, r, logger)
|
||||
wm := rpc.NewWebsocketManager(r, c)
|
||||
wm.SetLogger(logger)
|
||||
core.SetLogger(logger)
|
||||
mux.HandleFunc(wsEndpoint, wm.WebsocketHandler)
|
||||
|
||||
_, err := rpc.StartHTTPServer(bind, mux, logger)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RPCRoutes just routes everything to the given client, as if it were
|
||||
// a tendermint fullnode.
|
||||
//
|
||||
// if we want security, the client must implement it as a secure client
|
||||
func RPCRoutes(c rpcclient.Client) map[string]*rpc.RPCFunc {
|
||||
|
||||
return map[string]*rpc.RPCFunc{
|
||||
// Subscribe/unsubscribe are reserved for websocket events.
|
||||
// We can just use the core tendermint impl, which uses the
|
||||
// EventSwitch we registered in NewWebsocketManager above
|
||||
"subscribe": rpc.NewWSRPCFunc(core.Subscribe, "event"),
|
||||
"unsubscribe": rpc.NewWSRPCFunc(core.Unsubscribe, "event"),
|
||||
|
||||
// info API
|
||||
"status": rpc.NewRPCFunc(c.Status, ""),
|
||||
"blockchain": rpc.NewRPCFunc(c.BlockchainInfo, "minHeight,maxHeight"),
|
||||
"genesis": rpc.NewRPCFunc(c.Genesis, ""),
|
||||
"block": rpc.NewRPCFunc(c.Block, "height"),
|
||||
"commit": rpc.NewRPCFunc(c.Commit, "height"),
|
||||
"tx": rpc.NewRPCFunc(c.Tx, "hash,prove"),
|
||||
"validators": rpc.NewRPCFunc(c.Validators, ""),
|
||||
|
||||
// broadcast API
|
||||
"broadcast_tx_commit": rpc.NewRPCFunc(c.BroadcastTxCommit, "tx"),
|
||||
"broadcast_tx_sync": rpc.NewRPCFunc(c.BroadcastTxSync, "tx"),
|
||||
"broadcast_tx_async": rpc.NewRPCFunc(c.BroadcastTxAsync, "tx"),
|
||||
|
||||
// abci API
|
||||
"abci_query": rpc.NewRPCFunc(c.ABCIQuery, "path,data,prove"),
|
||||
"abci_info": rpc.NewRPCFunc(c.ABCIInfo, ""),
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/iavl"
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
certerr "github.com/tendermint/light-client/certifiers/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
// GetWithProof will query the key on the given node, and verify it has
|
||||
// a valid proof, as defined by the certifier.
|
||||
//
|
||||
// If there is any error in checking, returns an error.
|
||||
// If val is non-empty, proof should be KeyExistsProof
|
||||
// If val is empty, proof should be KeyMissingProof
|
||||
func GetWithProof(key []byte, reqHeight int, node client.Client,
|
||||
cert certifiers.Certifier) (
|
||||
val data.Bytes, height uint64, proof iavl.KeyProof, err error) {
|
||||
|
||||
if reqHeight < 0 {
|
||||
err = errors.Errorf("Height cannot be negative")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := node.ABCIQueryWithOptions("/key", key,
|
||||
client.ABCIQueryOptions{Height: uint64(reqHeight)})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// make sure the proof is the proper height
|
||||
if !resp.Code.IsOK() {
|
||||
err = errors.Errorf("Query error %d: %s", resp.Code, resp.Code.String())
|
||||
return
|
||||
}
|
||||
if len(resp.Key) == 0 || len(resp.Proof) == 0 {
|
||||
err = ErrNoData()
|
||||
return
|
||||
}
|
||||
if resp.Height == 0 {
|
||||
err = errors.New("Height returned is zero")
|
||||
return
|
||||
}
|
||||
|
||||
// AppHash for height H is in header H+1
|
||||
var commit *certifiers.Commit
|
||||
commit, err = GetCertifiedCommit(int(resp.Height+1), node, cert)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(resp.Value) > 0 {
|
||||
// The key was found, construct a proof of existence.
|
||||
var eproof *iavl.KeyExistsProof
|
||||
eproof, err = iavl.ReadKeyExistsProof(resp.Proof)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Error reading proof")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the proof against the certified header to ensure data integrity.
|
||||
err = eproof.Verify(resp.Key, resp.Value, commit.Header.AppHash)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Couldn't verify proof")
|
||||
return
|
||||
}
|
||||
val = data.Bytes(resp.Value)
|
||||
proof = eproof
|
||||
} else {
|
||||
// The key wasn't found, construct a proof of non-existence.
|
||||
var aproof *iavl.KeyAbsentProof
|
||||
aproof, err = iavl.ReadKeyAbsentProof(resp.Proof)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Error reading proof")
|
||||
return
|
||||
}
|
||||
// Validate the proof against the certified header to ensure data integrity.
|
||||
err = aproof.Verify(resp.Key, nil, commit.Header.AppHash)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Couldn't verify proof")
|
||||
return
|
||||
}
|
||||
err = ErrNoData()
|
||||
proof = aproof
|
||||
}
|
||||
|
||||
height = resp.Height
|
||||
return
|
||||
}
|
||||
|
||||
// GetCertifiedCommit gets the signed header for a given height
|
||||
// and certifies it. Returns error if unable to get a proven header.
|
||||
func GetCertifiedCommit(h int, node client.Client,
|
||||
cert certifiers.Certifier) (empty *certifiers.Commit, err error) {
|
||||
|
||||
// FIXME: cannot use cert.GetByHeight for now, as it also requires
|
||||
// Validators and will fail on querying tendermint for non-current height.
|
||||
// When this is supported, we should use it instead...
|
||||
client.WaitForHeight(node, h, nil)
|
||||
cresp, err := node.Commit(&h)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
commit := certifiers.CommitFromResult(cresp)
|
||||
|
||||
// validate downloaded checkpoint with our request and trust store.
|
||||
if commit.Height() != h {
|
||||
return empty, certerr.ErrHeightMismatch(h, commit.Height())
|
||||
}
|
||||
err = cert.Certify(commit)
|
||||
return commit, nil
|
||||
}
|
||||
@ -1,148 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
certclient "github.com/tendermint/light-client/certifiers/client"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
sdkapp "github.com/cosmos/cosmos-sdk/app"
|
||||
"github.com/cosmos/cosmos-sdk/modules/eyes"
|
||||
)
|
||||
|
||||
var node *nm.Node
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
logger := log.TestingLogger()
|
||||
store, err := sdkapp.MockStoreApp("query", logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
app := sdkapp.NewBaseApp(store, eyes.NewHandler(), nil)
|
||||
|
||||
node = rpctest.StartTendermint(app)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestAppProofs(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
cl := client.NewLocal(node)
|
||||
client.WaitForHeight(cl, 1, nil)
|
||||
|
||||
k := []byte("my-key")
|
||||
v := []byte("my-value")
|
||||
|
||||
tx := eyes.SetTx{Key: k, Value: v}.Wrap()
|
||||
btx := wire.BinaryBytes(tx)
|
||||
br, err := cl.BroadcastTxCommit(btx)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx)
|
||||
require.EqualValues(0, br.DeliverTx.Code)
|
||||
brh := br.Height
|
||||
|
||||
// This sets up our trust on the node based on some past point.
|
||||
source := certclient.NewProvider(cl)
|
||||
seed, err := source.GetByHeight(br.Height - 2)
|
||||
require.NoError(err, "%+v", err)
|
||||
cert := certifiers.NewStatic("my-chain", seed.Validators)
|
||||
|
||||
client.WaitForHeight(cl, 3, nil)
|
||||
latest, err := source.LatestCommit()
|
||||
require.NoError(err, "%+v", err)
|
||||
rootHash := latest.Header.AppHash
|
||||
|
||||
// Test existing key.
|
||||
var data eyes.Data
|
||||
|
||||
// verify a query before the tx block has no data (and valid non-exist proof)
|
||||
bs, height, proof, err := GetWithProof(k, brh-1, cl, cert)
|
||||
require.NotNil(err)
|
||||
require.True(IsNoDataErr(err))
|
||||
require.Nil(bs)
|
||||
|
||||
// but given that block it is good
|
||||
bs, height, proof, err = GetWithProof(k, brh, cl, cert)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.NotNil(proof)
|
||||
require.True(height >= uint64(latest.Header.Height))
|
||||
|
||||
// Alexis there is a bug here, somehow the above code gives us rootHash = nil
|
||||
// and proof.Verify doesn't care, while proofNotExists.Verify fails.
|
||||
// I am hacking this in to make it pass, but please investigate further.
|
||||
rootHash = proof.Root()
|
||||
|
||||
err = wire.ReadBinaryBytes(bs, &data)
|
||||
require.NoError(err, "%+v", err)
|
||||
assert.EqualValues(v, data.Value)
|
||||
err = proof.Verify(k, bs, rootHash)
|
||||
assert.NoError(err, "%+v", err)
|
||||
|
||||
// Test non-existing key.
|
||||
missing := []byte("my-missing-key")
|
||||
bs, _, proof, err = GetWithProof(missing, 0, cl, cert)
|
||||
require.True(IsNoDataErr(err))
|
||||
require.Nil(bs)
|
||||
require.NotNil(proof)
|
||||
err = proof.Verify(missing, nil, rootHash)
|
||||
assert.NoError(err, "%+v", err)
|
||||
err = proof.Verify(k, nil, rootHash)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestTxProofs(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
cl := client.NewLocal(node)
|
||||
client.WaitForHeight(cl, 1, nil)
|
||||
|
||||
tx := eyes.NewSetTx([]byte("key-a"), []byte("value-a"))
|
||||
|
||||
btx := types.Tx(wire.BinaryBytes(tx))
|
||||
br, err := cl.BroadcastTxCommit(btx)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx)
|
||||
require.EqualValues(0, br.DeliverTx.Code)
|
||||
fmt.Printf("tx height: %d\n", br.Height)
|
||||
|
||||
source := certclient.NewProvider(cl)
|
||||
seed, err := source.GetByHeight(br.Height - 2)
|
||||
require.NoError(err, "%+v", err)
|
||||
cert := certifiers.NewStatic("my-chain", seed.Validators)
|
||||
|
||||
// First let's make sure a bogus transaction hash returns a valid non-existence proof.
|
||||
key := types.Tx([]byte("bogus")).Hash()
|
||||
res, err := cl.Tx(key, true)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), "not found")
|
||||
|
||||
// Now let's check with the real tx hash.
|
||||
key = btx.Hash()
|
||||
res, err = cl.Tx(key, true)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.NotNil(res)
|
||||
err = res.Proof.Validate(key)
|
||||
assert.NoError(err, "%+v", err)
|
||||
|
||||
commit, err := GetCertifiedCommit(int(br.Height), cl, cert)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(res.Proof.RootHash, commit.Header.DataHash)
|
||||
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
## basecoin-server
|
||||
|
||||
### Proxy server
|
||||
This package exposes access to key management i.e
|
||||
- creating
|
||||
- listing
|
||||
- updating
|
||||
- deleting
|
||||
|
||||
The HTTP handlers can be embedded in a larger server that
|
||||
does things like signing transactions and posting them to a
|
||||
Tendermint chain (which requires domain-knowledge of the transaction
|
||||
types and is out of scope of this generic app).
|
||||
|
||||
### Key Management
|
||||
We expose a couple of methods for safely managing your keychain.
|
||||
If you are embedding this in a larger server, you will typically
|
||||
want to mount all these paths /keys.
|
||||
|
||||
HTTP Method | Route | Description
|
||||
---|---|---
|
||||
POST|/|Requires a name and passphrase to create a brand new key
|
||||
GET|/|Retrieves the list of all available key names, along with their public key and address
|
||||
GET|/{name} | Updates the passphrase for the given key. It requires you to correctly provide the current passphrase, as well as a new one.
|
||||
DELETE|/{name} | Permanently delete this private key. It requires you to correctly provide the current passphrase.
|
||||
@ -1,192 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/tmlibs/common"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
keycmd "github.com/cosmos/cosmos-sdk/client/commands/keys"
|
||||
)
|
||||
|
||||
type Keys struct {
|
||||
algo string
|
||||
manager keys.Manager
|
||||
}
|
||||
|
||||
func DefaultKeysManager() keys.Manager {
|
||||
return keycmd.GetKeyManager()
|
||||
}
|
||||
|
||||
func NewDefaultKeysManager(algo string) *Keys {
|
||||
return New(DefaultKeysManager(), algo)
|
||||
}
|
||||
|
||||
func New(manager keys.Manager, algo string) *Keys {
|
||||
return &Keys{
|
||||
algo: algo,
|
||||
manager: manager,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keys) GenerateKey(w http.ResponseWriter, r *http.Request) {
|
||||
ckReq := &CreateKeyRequest{
|
||||
Algo: k.algo,
|
||||
}
|
||||
if err := common.ParseRequestAndValidateJSON(r, ckReq); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, seed, err := k.manager.Create(ckReq.Name, ckReq.Passphrase, ckReq.Algo)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
res := &CreateKeyResponse{Key: key, Seed: seed}
|
||||
common.WriteSuccess(w, res)
|
||||
}
|
||||
|
||||
func (k *Keys) GetKey(w http.ResponseWriter, r *http.Request) {
|
||||
query := mux.Vars(r)
|
||||
name := query["name"]
|
||||
key, err := k.manager.Get(name)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
common.WriteSuccess(w, &key)
|
||||
}
|
||||
|
||||
func (k *Keys) ListKeys(w http.ResponseWriter, r *http.Request) {
|
||||
keys, err := k.manager.List()
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
common.WriteSuccess(w, keys)
|
||||
}
|
||||
|
||||
var (
|
||||
errNonMatchingPathAndJSONKeyNames = errors.New("path and json key names don't match")
|
||||
)
|
||||
|
||||
func (k *Keys) UpdateKey(w http.ResponseWriter, r *http.Request) {
|
||||
uReq := new(UpdateKeyRequest)
|
||||
if err := common.ParseRequestAndValidateJSON(r, uReq); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := mux.Vars(r)
|
||||
name := query["name"]
|
||||
if name != uReq.Name {
|
||||
common.WriteError(w, errNonMatchingPathAndJSONKeyNames)
|
||||
return
|
||||
}
|
||||
|
||||
if err := k.manager.Update(uReq.Name, uReq.OldPass, uReq.NewPass); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := k.manager.Get(uReq.Name)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
common.WriteSuccess(w, &key)
|
||||
}
|
||||
|
||||
func (k *Keys) DeleteKey(w http.ResponseWriter, r *http.Request) {
|
||||
dReq := new(DeleteKeyRequest)
|
||||
if err := common.ParseRequestAndValidateJSON(r, dReq); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := mux.Vars(r)
|
||||
name := query["name"]
|
||||
if name != dReq.Name {
|
||||
common.WriteError(w, errNonMatchingPathAndJSONKeyNames)
|
||||
return
|
||||
}
|
||||
|
||||
if err := k.manager.Delete(dReq.Name, dReq.Passphrase); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &common.ErrorResponse{Success: true}
|
||||
common.WriteSuccess(w, resp)
|
||||
}
|
||||
|
||||
func doPostTx(w http.ResponseWriter, r *http.Request) {
|
||||
tx := new(sdk.Tx)
|
||||
if err := common.ParseRequestAndValidateJSON(r, tx); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
commit, err := PostTx(*tx)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
common.WriteSuccess(w, commit)
|
||||
}
|
||||
|
||||
func doSign(w http.ResponseWriter, r *http.Request) {
|
||||
sr := new(SignRequest)
|
||||
if err := common.ParseRequestAndValidateJSON(r, sr); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
tx := sr.Tx
|
||||
if err := SignTx(sr.Name, sr.Password, tx); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
common.WriteSuccess(w, tx)
|
||||
}
|
||||
|
||||
// mux.Router registrars
|
||||
|
||||
// RegisterPostTx is a mux.Router handler that exposes POST
|
||||
// method access to post a transaction to the blockchain.
|
||||
func RegisterPostTx(r *mux.Router) error {
|
||||
r.HandleFunc("/tx", doPostTx).Methods("POST")
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterAllCRUD is a convenience method to register all
|
||||
// CRUD for keys to allow access by methods and routes:
|
||||
// POST: /keys
|
||||
// GET: /keys
|
||||
// GET: /keys/{name}
|
||||
// POST, PUT: /keys/{name}
|
||||
// DELETE: /keys/{name}
|
||||
func (k *Keys) RegisterAllCRUD(r *mux.Router) error {
|
||||
r.HandleFunc("/keys", k.GenerateKey).Methods("POST")
|
||||
r.HandleFunc("/keys", k.ListKeys).Methods("GET")
|
||||
r.HandleFunc("/keys/{name}", k.GetKey).Methods("GET")
|
||||
r.HandleFunc("/keys/{name}", k.UpdateKey).Methods("POST", "PUT")
|
||||
r.HandleFunc("/keys/{name}", k.DeleteKey).Methods("DELETE")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterSignTx is a mux.Router handler that
|
||||
// exposes POST method access to sign a transaction.
|
||||
func RegisterSignTx(r *mux.Router) error {
|
||||
r.HandleFunc("/sign", doSign).Methods("POST")
|
||||
return nil
|
||||
}
|
||||
|
||||
// End of mux.Router registrars
|
||||
@ -1,29 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
keycmd "github.com/cosmos/cosmos-sdk/client/commands/keys"
|
||||
)
|
||||
|
||||
// PostTx is same as a tx
|
||||
func PostTx(tx sdk.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
packet := wire.BinaryBytes(tx)
|
||||
// post the bytes
|
||||
node := commands.GetNode()
|
||||
return node.BroadcastTxCommit(packet)
|
||||
}
|
||||
|
||||
// SignTx will modify the tx in-place, adding a signature if possible
|
||||
func SignTx(name, pass string, tx sdk.Tx) error {
|
||||
if sign, ok := tx.Unwrap().(keys.Signable); ok {
|
||||
manager := keycmd.GetKeyManager()
|
||||
return manager.Sign(name, pass, sign)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
)
|
||||
|
||||
type CreateKeyRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"required,min=3,printascii"`
|
||||
Passphrase string `json:"password,omitempty" validate:"required,min=10"`
|
||||
|
||||
// Algo is the requested algorithm to create the key
|
||||
Algo string `json:"algo,omitempty"`
|
||||
}
|
||||
|
||||
type DeleteKeyRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"required,min=3,printascii"`
|
||||
Passphrase string `json:"password,omitempty" validate:"required,min=10"`
|
||||
}
|
||||
|
||||
type UpdateKeyRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"required,min=3,printascii"`
|
||||
OldPass string `json:"password,omitempty" validate:"required,min=10"`
|
||||
NewPass string `json:"new_passphrase,omitempty" validate:"required,min=10"`
|
||||
}
|
||||
|
||||
type SignRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"required,min=3,printascii"`
|
||||
Password string `json:"password,omitempty" validate:"required,min=10"`
|
||||
|
||||
Tx sdk.Tx `json:"tx" validate:"required"`
|
||||
}
|
||||
|
||||
type CreateKeyResponse struct {
|
||||
Key keys.Info `json:"key,omitempty"`
|
||||
Seed string `json:"seed_phrase,omitempty"`
|
||||
}
|
||||
|
||||
// SendInput is the request to send an amount from one actor to another.
|
||||
// Note: Not using the `validator:""` tags here because SendInput has
|
||||
// many fields so it would be nice to figure out all the invalid
|
||||
// inputs and report them back to the caller, in one shot.
|
||||
type SendInput struct {
|
||||
Fees *coin.Coin `json:"fees"`
|
||||
Multi bool `json:"multi,omitempty"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
|
||||
To *sdk.Actor `json:"to"`
|
||||
From *sdk.Actor `json:"from"`
|
||||
Amount coin.Coins `json:"amount"`
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
func makeHandler() stack.Dispatchable {
|
||||
return NewHandler()
|
||||
}
|
||||
|
||||
func makeSimpleTx(from, to sdk.Actor, amount Coins) sdk.Tx {
|
||||
in := []TxInput{{Address: from, Coins: amount}}
|
||||
out := []TxOutput{{Address: to, Coins: amount}}
|
||||
return NewSendTx(in, out)
|
||||
}
|
||||
|
||||
func BenchmarkSimpleTransfer(b *testing.B) {
|
||||
h := makeHandler()
|
||||
store := state.NewMemKVStore()
|
||||
logger := log.NewNopLogger()
|
||||
|
||||
// set the initial account
|
||||
acct := NewAccountWithKey(Coins{{"mycoin", 1234567890}})
|
||||
h.InitState(logger, store, NameCoin, "account", acct.MakeOption(), nil)
|
||||
sender := acct.Actor()
|
||||
receiver := sdk.Actor{App: "foo", Address: cmn.RandBytes(20)}
|
||||
|
||||
// now, loop...
|
||||
for i := 1; i <= b.N; i++ {
|
||||
ctx := stack.MockContext("foo", 100).WithPermissions(sender)
|
||||
tx := makeSimpleTx(sender, receiver, Coins{{"mycoin", 2}})
|
||||
_, err := h.DeliverTx(ctx, store, tx, nil)
|
||||
// never should error
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
"github.com/cosmos/cosmos-sdk/x/coin"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
)
|
||||
|
||||
// AccountQueryCmd - command to query an account
|
||||
var AccountQueryCmd = &cobra.Command{
|
||||
Use: "account [address]",
|
||||
Short: "Get details of an account, with proof",
|
||||
RunE: commands.RequireInit(accountQueryCmd),
|
||||
}
|
||||
|
||||
func accountQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
addr, err := commands.GetOneArg(args, "address")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
act, err := commands.ParseActor(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
act = coin.ChainAddr(act)
|
||||
key := stack.PrefixedKey(coin.NameCoin, act.Bytes())
|
||||
|
||||
acc := coin.Account{}
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
height, err := query.GetParsed(key, &acc, query.GetHeight(), prove)
|
||||
if client.IsNoDataErr(err) {
|
||||
return errors.Errorf("Account bytes are empty for address %s ", addr)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return query.OutputProof(acc, height)
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
|
||||
"github.com/cosmos/cosmos-sdk/x/coin"
|
||||
)
|
||||
|
||||
// SendTxCmd is CLI command to send tokens between basecoin accounts
|
||||
var SendTxCmd = &cobra.Command{
|
||||
Use: "send",
|
||||
Short: "send tokens from one account to another",
|
||||
RunE: commands.RequireInit(sendTxCmd),
|
||||
}
|
||||
|
||||
// CreditTxCmd is CLI command to issue credit to one account
|
||||
var CreditTxCmd = &cobra.Command{
|
||||
Use: "credit",
|
||||
Short: "issue credit to one account",
|
||||
RunE: commands.RequireInit(creditTxCmd),
|
||||
}
|
||||
|
||||
//nolint
|
||||
const (
|
||||
FlagTo = "to"
|
||||
FlagAmount = "amount"
|
||||
FlagFrom = "from"
|
||||
)
|
||||
|
||||
func init() {
|
||||
flags := SendTxCmd.Flags()
|
||||
flags.String(FlagTo, "", "Destination address for the bits")
|
||||
flags.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
flags.String(FlagFrom, "", "Address sending coins, if not first signer")
|
||||
|
||||
fs2 := CreditTxCmd.Flags()
|
||||
fs2.String(FlagTo, "", "Destination address for the bits")
|
||||
fs2.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
}
|
||||
|
||||
func sendTxCmd(cmd *cobra.Command, args []string) error {
|
||||
tx, err := readSendTxFlags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txcmd.DoTx(tx)
|
||||
}
|
||||
|
||||
func readSendTxFlags() (tx sdk.Tx, err error) {
|
||||
// parse to address
|
||||
toAddr, err := commands.ParseActor(viper.GetString(FlagTo))
|
||||
if err != nil {
|
||||
return tx, err
|
||||
}
|
||||
|
||||
fromAddr, err := readFromAddr()
|
||||
if err != nil {
|
||||
return tx, err
|
||||
}
|
||||
|
||||
amountCoins, err := coin.ParseCoins(viper.GetString(FlagAmount))
|
||||
if err != nil {
|
||||
return tx, err
|
||||
}
|
||||
|
||||
// craft the inputs and outputs
|
||||
tx = coin.NewSendOneTx(fromAddr, toAddr, amountCoins)
|
||||
return
|
||||
}
|
||||
|
||||
func creditTxCmd(cmd *cobra.Command, args []string) error {
|
||||
tx, err := readCreditTxFlags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txcmd.DoTx(tx)
|
||||
}
|
||||
|
||||
func readCreditTxFlags() (tx sdk.Tx, err error) {
|
||||
// parse to address
|
||||
toAddr, err := commands.ParseActor(viper.GetString(FlagTo))
|
||||
if err != nil {
|
||||
return tx, err
|
||||
}
|
||||
|
||||
amount, err := coin.ParseCoins(viper.GetString(FlagAmount))
|
||||
if err != nil {
|
||||
return tx, err
|
||||
}
|
||||
|
||||
tx = coin.CreditTx{Debitor: toAddr, Credit: amount}.Wrap()
|
||||
return
|
||||
}
|
||||
|
||||
func readFromAddr() (sdk.Actor, error) {
|
||||
from := viper.GetString(FlagFrom)
|
||||
if from == "" {
|
||||
return txcmd.GetSignerAct(), nil
|
||||
}
|
||||
return commands.ParseActor(from)
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
)
|
||||
|
||||
/**** code to parse accounts from genesis docs ***/
|
||||
|
||||
// GenesisAccount - genesis account parameters
|
||||
type GenesisAccount struct {
|
||||
Address data.Bytes `json:"address"`
|
||||
// this from types.Account (don't know how to embed this properly)
|
||||
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
|
||||
Balance types.Coins `json:"coins"`
|
||||
}
|
||||
|
||||
// ToAccount - GenesisAccount struct to a basecoin Account
|
||||
func (g GenesisAccount) ToAccount() Account {
|
||||
return Account{
|
||||
Coins: g.Balance,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAddr - Get the address of the genesis account
|
||||
func (g GenesisAccount) GetAddr() ([]byte, error) {
|
||||
noAddr, noPk := len(g.Address) == 0, g.PubKey.Empty()
|
||||
|
||||
if noAddr {
|
||||
if noPk {
|
||||
return nil, errors.New("No address given")
|
||||
}
|
||||
return g.PubKey.Address(), nil
|
||||
}
|
||||
if noPk { // but is addr...
|
||||
return g.Address, nil
|
||||
}
|
||||
// now, we have both, make sure they check out
|
||||
if bytes.Equal(g.Address, g.PubKey.Address()) {
|
||||
return g.Address, nil
|
||||
}
|
||||
return nil, errors.New("Address and pubkey don't match")
|
||||
}
|
||||
@ -1,251 +0,0 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
// "github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
// "github.com/cosmos/cosmos-sdk/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
//NameCoin - name space of the coin module
|
||||
NameCoin = "coin"
|
||||
// CostSend is GasAllocation per input/output
|
||||
CostSend = uint64(10)
|
||||
// CostCredit is GasAllocation of a credit allocation
|
||||
CostCredit = uint64(20)
|
||||
)
|
||||
|
||||
// Handler includes an accountant
|
||||
type Handler struct {
|
||||
// stack.PassInitValidate
|
||||
}
|
||||
|
||||
// var _ stack.Dispatchable = Handler{}
|
||||
|
||||
// NewHandler - new accountant handler for the coin module
|
||||
func NewHandler() Handler {
|
||||
return Handler{}
|
||||
}
|
||||
|
||||
// Name - return name space
|
||||
func (Handler) Name() string {
|
||||
return NameCoin
|
||||
}
|
||||
|
||||
// AssertDispatcher - to fulfill Dispatchable interface
|
||||
func (Handler) AssertDispatcher() {}
|
||||
|
||||
// CheckTx checks if there is enough money in the account
|
||||
func (h Handler) CheckTx(ctx types.Context, store store.MultiStore,
|
||||
tx types.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch t := tx.Unwrap().(type) {
|
||||
case SendTx:
|
||||
// price based on inputs and outputs
|
||||
used := uint64(len(t.Inputs) + len(t.Outputs))
|
||||
return sdk.NewCheck(used*CostSend, ""), h.checkSendTx(ctx, store, t)
|
||||
case CreditTx:
|
||||
// default price of 20, constant work
|
||||
return sdk.NewCheck(CostCredit, ""), h.creditTx(ctx, store, t)
|
||||
}
|
||||
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||
}
|
||||
|
||||
// DeliverTx moves the money
|
||||
func (h Handler) DeliverTx(ctx types.Context, store store.MultiStore,
|
||||
tx types.Tx, cb sdk.Deliver) (res sdk.DeliverResult, err error) {
|
||||
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch t := tx.Unwrap().(type) {
|
||||
case SendTx:
|
||||
return res, h.sendTx(ctx, store, t, cb)
|
||||
case CreditTx:
|
||||
return res, h.creditTx(ctx, store, t)
|
||||
}
|
||||
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||
}
|
||||
|
||||
// InitState - sets the genesis account balance
|
||||
func (h Handler) InitState(l log.Logger, store store.MultiStore,
|
||||
module, key, value string, cb sdk.InitStater) (log string, err error) {
|
||||
if module != NameCoin {
|
||||
return "", errors.ErrUnknownModule(module)
|
||||
}
|
||||
switch key {
|
||||
case "account":
|
||||
return setAccount(store, value)
|
||||
case "issuer":
|
||||
return setIssuer(store, value)
|
||||
}
|
||||
return "", errors.ErrUnknownKey(key)
|
||||
}
|
||||
|
||||
func (h Handler) sendTx(ctx types.Context, store store.MultiStore,
|
||||
send SendTx, cb sdk.Deliver) error {
|
||||
|
||||
err := checkTx(ctx, send)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// deduct from all input accounts
|
||||
senders := sdk.Actors{}
|
||||
for _, in := range send.Inputs {
|
||||
_, err = ChangeCoins(store, in.Address, in.Coins.Negative())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
senders = append(senders, in.Address)
|
||||
}
|
||||
|
||||
// add to all output accounts
|
||||
for _, out := range send.Outputs {
|
||||
// TODO: cleaner way, this makes sure we don't consider
|
||||
// incoming ibc packets with our chain to be remote packets
|
||||
if out.Address.ChainID == ctx.ChainID() {
|
||||
out.Address.ChainID = ""
|
||||
}
|
||||
|
||||
_, err = ChangeCoins(store, out.Address, out.Coins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// now send ibc packet if needed...
|
||||
if out.Address.ChainID != "" {
|
||||
// FIXME: if there are many outputs, we need to adjust inputs
|
||||
// so the amounts in and out match. how?
|
||||
inputs := make([]TxInput, len(send.Inputs))
|
||||
for i := range send.Inputs {
|
||||
inputs[i] = send.Inputs[i]
|
||||
inputs[i].Address = inputs[i].Address.WithChain(ctx.ChainID())
|
||||
}
|
||||
|
||||
outTx := NewSendTx(inputs, []TxOutput{out})
|
||||
_ = outTx
|
||||
/* TODO
|
||||
packet := ibc.CreatePacketTx{
|
||||
DestChain: out.Address.ChainID,
|
||||
Permissions: senders,
|
||||
Tx: outTx,
|
||||
}
|
||||
ibcCtx := ctx.WithPermissions(ibc.AllowIBC(NameCoin))
|
||||
_, err := cb.DeliverTx(ibcCtx, store, packet.Wrap())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// a-ok!
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Handler) creditTx(ctx types.Context, store store.MultiStore,
|
||||
credit CreditTx) error {
|
||||
|
||||
// first check permissions!!
|
||||
info, err := loadHandlerInfo(store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Issuer.Empty() || !ctx.HasPermission(info.Issuer) {
|
||||
return errors.ErrUnauthorized()
|
||||
}
|
||||
|
||||
// load up the account
|
||||
addr := ChainAddr(credit.Debitor)
|
||||
acct, err := GetAccount(store, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make and check changes
|
||||
acct.Coins = acct.Coins.Plus(credit.Credit)
|
||||
if !acct.Coins.IsNonnegative() {
|
||||
return ErrInsufficientFunds()
|
||||
}
|
||||
acct.Credit = acct.Credit.Plus(credit.Credit)
|
||||
if !acct.Credit.IsNonnegative() {
|
||||
return ErrInsufficientCredit()
|
||||
}
|
||||
|
||||
err = storeAccount(store, addr.Bytes(), acct)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkTx(ctx types.Context, send SendTx) error {
|
||||
// check if all inputs have permission
|
||||
for _, in := range send.Inputs {
|
||||
if !ctx.HasPermission(in.Address) {
|
||||
return errors.ErrUnauthorized()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Handler) checkSendTx(ctx types.Context, store store.MultiStore, send SendTx) error {
|
||||
err := checkTx(ctx, send)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// now make sure there is money
|
||||
for _, in := range send.Inputs {
|
||||
_, err := CheckCoins(store, in.Address, in.Coins.Negative())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAccount(store store.MultiStore, value string) (log string, err error) {
|
||||
var acc GenesisAccount
|
||||
err = data.FromJSON([]byte(value), &acc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
acc.Balance.Sort()
|
||||
addr, err := acc.GetAddr()
|
||||
if err != nil {
|
||||
return "", ErrInvalidAddress()
|
||||
}
|
||||
// this sets the permission for a public key signature, use that app
|
||||
actor := auth.SigPerm(addr)
|
||||
err = storeAccount(store, actor.Bytes(), acc.ToAccount())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Success", nil
|
||||
}
|
||||
|
||||
// setIssuer sets a permission for some super-powerful account to
|
||||
// mint money
|
||||
func setIssuer(store store.MultiStore, value string) (log string, err error) {
|
||||
var issuer sdk.Actor
|
||||
err = data.FromJSON([]byte(value), &issuer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = storeIssuer(store, issuer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Success", nil
|
||||
}
|
||||
@ -1,381 +0,0 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
// this makes sure that txs are rejected with invalid data or permissions
|
||||
func TestHandlerValidation(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// these are all valid, except for minusCoins
|
||||
addr1 := sdk.Actor{App: "coin", Address: []byte{1, 2}}
|
||||
addr2 := sdk.Actor{App: "role", Address: []byte{7, 8}}
|
||||
someCoins := Coins{{"atom", 123}}
|
||||
doubleCoins := Coins{{"atom", 246}}
|
||||
minusCoins := Coins{{"eth", -34}}
|
||||
|
||||
cases := []struct {
|
||||
valid bool
|
||||
tx sdk.Tx
|
||||
perms []sdk.Actor
|
||||
}{
|
||||
// auth works with different apps
|
||||
{true,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr2, someCoins)}),
|
||||
[]sdk.Actor{addr1}},
|
||||
{true,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr2, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr1, someCoins)}),
|
||||
[]sdk.Actor{addr1, addr2}},
|
||||
// check multi-input with both sigs
|
||||
{true,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins), NewTxInput(addr2, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr1, doubleCoins)}),
|
||||
[]sdk.Actor{addr1, addr2}},
|
||||
// wrong permissions fail
|
||||
{false,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr2, someCoins)}),
|
||||
[]sdk.Actor{}},
|
||||
{false,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr2, someCoins)}),
|
||||
[]sdk.Actor{addr2}},
|
||||
{false,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins), NewTxInput(addr2, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr1, doubleCoins)}),
|
||||
[]sdk.Actor{addr1}},
|
||||
// invalid input fails
|
||||
{false,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, minusCoins)},
|
||||
[]TxOutput{NewTxOutput(addr2, minusCoins)}),
|
||||
[]sdk.Actor{addr2}},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
||||
err := checkTx(ctx, tc.tx.Unwrap().(SendTx))
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDeliverSendTx(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// some sample settings
|
||||
addr1 := sdk.Actor{App: "coin", Address: []byte{1, 2}}
|
||||
addr2 := sdk.Actor{App: "role", Address: []byte{7, 8}}
|
||||
addr3 := sdk.Actor{App: "coin", Address: []byte{6, 5, 4, 3}}
|
||||
|
||||
someCoins := Coins{{"atom", 123}}
|
||||
moreCoins := Coins{{"atom", 6487}}
|
||||
diffCoins := moreCoins.Minus(someCoins)
|
||||
otherCoins := Coins{{"eth", 11}}
|
||||
mixedCoins := someCoins.Plus(otherCoins)
|
||||
|
||||
type money struct {
|
||||
addr sdk.Actor
|
||||
coins Coins
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
init []money
|
||||
tx sdk.Tx
|
||||
perms []sdk.Actor
|
||||
final []money // nil for error
|
||||
cost uint64 // gas allocated (if not error)
|
||||
}{
|
||||
{
|
||||
[]money{{addr1, moreCoins}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr2, someCoins)}),
|
||||
[]sdk.Actor{addr1},
|
||||
[]money{{addr1, diffCoins}, {addr2, someCoins}},
|
||||
20,
|
||||
},
|
||||
// simple multi-sig 2 accounts to 1
|
||||
{
|
||||
[]money{{addr1, mixedCoins}, {addr2, moreCoins}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, otherCoins), NewTxInput(addr2, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr3, mixedCoins)}),
|
||||
[]sdk.Actor{addr1, addr2},
|
||||
[]money{{addr1, someCoins}, {addr2, diffCoins}, {addr3, mixedCoins}},
|
||||
30,
|
||||
},
|
||||
// multi-sig with one account sending many times
|
||||
{
|
||||
[]money{{addr1, moreCoins.Plus(otherCoins)}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, otherCoins), NewTxInput(addr1, someCoins)},
|
||||
[]TxOutput{NewTxOutput(addr2, mixedCoins)}),
|
||||
[]sdk.Actor{addr1},
|
||||
[]money{{addr1, diffCoins}, {addr2, mixedCoins}},
|
||||
30,
|
||||
},
|
||||
// invalid send (not enough money )
|
||||
{
|
||||
[]money{{addr1, moreCoins}, {addr2, someCoins}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr2, moreCoins)},
|
||||
[]TxOutput{NewTxOutput(addr1, moreCoins)}),
|
||||
[]sdk.Actor{addr1, addr2},
|
||||
nil,
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
h := NewHandler()
|
||||
for i, tc := range cases {
|
||||
// setup the cases....
|
||||
store := state.NewMemKVStore()
|
||||
for _, m := range tc.init {
|
||||
acct := Account{Coins: m.coins}
|
||||
err := storeAccount(store, m.addr.Bytes(), acct)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
}
|
||||
|
||||
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
||||
|
||||
// throw-away state for checktx
|
||||
cache := store.Checkpoint()
|
||||
cres, err := h.CheckTx(ctx, cache, tc.tx, nil)
|
||||
// real store for delivertx
|
||||
_, err2 := h.DeliverTx(ctx, store, tc.tx, nil)
|
||||
|
||||
if len(tc.final) > 0 { // valid
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
assert.Nil(err2, "%d: %+v", i, err2)
|
||||
// make sure proper gas is set
|
||||
assert.Equal(uint64(0), cres.GasPayment, "%d", i)
|
||||
assert.Equal(tc.cost, cres.GasAllocated, "%d", i)
|
||||
// make sure the final balances are correct
|
||||
for _, f := range tc.final {
|
||||
acct, err := loadAccount(store, f.addr.Bytes())
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(f.coins, acct.Coins)
|
||||
}
|
||||
} else {
|
||||
// both check and deliver should fail
|
||||
assert.NotNil(err, "%d", i)
|
||||
assert.NotNil(err2, "%d", i)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitState(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// some sample settings
|
||||
pk := crypto.GenPrivKeySecp256k1().Wrap()
|
||||
addr := pk.PubKey().Address()
|
||||
actor := auth.SigPerm(addr)
|
||||
|
||||
someCoins := Coins{{"atom", 123}}
|
||||
otherCoins := Coins{{"eth", 11}}
|
||||
mixedCoins := someCoins.Plus(otherCoins)
|
||||
|
||||
type money struct {
|
||||
addr sdk.Actor
|
||||
coins Coins
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
init []GenesisAccount
|
||||
expected []money
|
||||
}{
|
||||
{
|
||||
[]GenesisAccount{{Address: addr, Balance: mixedCoins}},
|
||||
[]money{{actor, mixedCoins}},
|
||||
},
|
||||
}
|
||||
|
||||
h := NewHandler()
|
||||
l := log.NewNopLogger()
|
||||
for i, tc := range cases {
|
||||
store := state.NewMemKVStore()
|
||||
key := "account"
|
||||
|
||||
// set the options
|
||||
for j, gen := range tc.init {
|
||||
value, err := json.Marshal(gen)
|
||||
require.Nil(err, "%d,%d: %+v", i, j, err)
|
||||
_, err = h.InitState(l, store, NameCoin, key, string(value), nil)
|
||||
require.Nil(err)
|
||||
}
|
||||
|
||||
// check state is proper
|
||||
for _, f := range tc.expected {
|
||||
acct, err := loadAccount(store, f.addr.Bytes())
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(f.coins, acct.Coins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetIssuer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cases := []struct {
|
||||
issuer sdk.Actor
|
||||
}{
|
||||
{sdk.Actor{App: "sig", Address: []byte("gwkfgk")}},
|
||||
// and set back to empty (nil is valid, but assert.Equals doesn't match)
|
||||
{sdk.Actor{}},
|
||||
{sdk.Actor{ChainID: "other", App: "role", Address: []byte("vote")}},
|
||||
}
|
||||
|
||||
h := NewHandler()
|
||||
l := log.NewNopLogger()
|
||||
for i, tc := range cases {
|
||||
store := state.NewMemKVStore()
|
||||
key := "issuer"
|
||||
|
||||
value, err := json.Marshal(tc.issuer)
|
||||
require.Nil(err, "%d,%d: %+v", i, err)
|
||||
_, err = h.InitState(l, store, NameCoin, key, string(value), nil)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// check state is proper
|
||||
info, err := loadHandlerInfo(store)
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(tc.issuer, info.Issuer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeliverCreditTx(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// sample coins
|
||||
someCoins := Coins{{"atom", 6570}}
|
||||
minusCoins := Coins{{"atom", -1234}}
|
||||
lessCoins := someCoins.Plus(minusCoins)
|
||||
otherCoins := Coins{{"eth", 11}}
|
||||
mixedCoins := someCoins.Plus(otherCoins)
|
||||
|
||||
// some sample addresses
|
||||
owner := sdk.Actor{App: "foo", Address: []byte("rocks")}
|
||||
addr1 := sdk.Actor{App: "coin", Address: []byte{1, 2}}
|
||||
key := NewAccountWithKey(someCoins)
|
||||
addr2 := key.Actor()
|
||||
addr3 := sdk.Actor{ChainID: "other", App: "sigs", Address: []byte{3, 9}}
|
||||
|
||||
h := NewHandler()
|
||||
store := state.NewMemKVStore()
|
||||
ctx := stack.MockContext("secret", 77)
|
||||
|
||||
// set the owner who can issue credit
|
||||
js, err := json.Marshal(owner)
|
||||
require.Nil(err, "%+v", err)
|
||||
_, err = h.InitState(log.NewNopLogger(), store, "coin", "issuer", string(js), nil)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// give addr2 some coins to start
|
||||
_, err = h.InitState(log.NewNopLogger(), store, "coin", "account", key.MakeOption(), nil)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
cases := []struct {
|
||||
tx sdk.Tx
|
||||
perm sdk.Actor
|
||||
check errors.CheckErr
|
||||
addr sdk.Actor
|
||||
expected Account
|
||||
}{
|
||||
// require permission
|
||||
{
|
||||
tx: NewCreditTx(addr1, someCoins),
|
||||
check: errors.IsUnauthorizedErr,
|
||||
},
|
||||
// add credit
|
||||
{
|
||||
tx: NewCreditTx(addr1, someCoins),
|
||||
perm: owner,
|
||||
check: errors.NoErr,
|
||||
addr: addr1,
|
||||
expected: Account{Coins: someCoins, Credit: someCoins},
|
||||
},
|
||||
// remove some
|
||||
{
|
||||
tx: NewCreditTx(addr1, minusCoins),
|
||||
perm: owner,
|
||||
check: errors.NoErr,
|
||||
addr: addr1,
|
||||
expected: Account{Coins: lessCoins, Credit: lessCoins},
|
||||
},
|
||||
// can't remove more cash than there is
|
||||
{
|
||||
tx: NewCreditTx(addr1, otherCoins.Negative()),
|
||||
perm: owner,
|
||||
check: IsInsufficientFundsErr,
|
||||
},
|
||||
// cumulative with initial state
|
||||
{
|
||||
tx: NewCreditTx(addr2, otherCoins),
|
||||
perm: owner,
|
||||
check: errors.NoErr,
|
||||
addr: addr2,
|
||||
expected: Account{Coins: mixedCoins, Credit: otherCoins},
|
||||
},
|
||||
// Even if there is cash, credit can't go negative
|
||||
{
|
||||
tx: NewCreditTx(addr2, minusCoins),
|
||||
perm: owner,
|
||||
check: IsInsufficientCreditErr,
|
||||
},
|
||||
// make sure it works for other chains
|
||||
{
|
||||
tx: NewCreditTx(addr3, mixedCoins),
|
||||
perm: owner,
|
||||
check: errors.NoErr,
|
||||
addr: ChainAddr(addr3),
|
||||
expected: Account{Coins: mixedCoins, Credit: mixedCoins},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
myStore := store.Checkpoint()
|
||||
|
||||
myCtx := ctx.WithPermissions(tc.perm)
|
||||
_, err = h.DeliverTx(myCtx, myStore, tc.tx, nil)
|
||||
assert.True(tc.check(err), "%d: %+v", i, err)
|
||||
|
||||
if err == nil {
|
||||
store.Commit(myStore)
|
||||
acct, err := GetAccount(store, tc.addr)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(tc.expected, acct, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
// AccountWithKey is a helper for tests, that includes and account
|
||||
// along with the private key to access it.
|
||||
type AccountWithKey struct {
|
||||
Key crypto.PrivKey
|
||||
Sequence uint32
|
||||
Account
|
||||
}
|
||||
|
||||
// NewAccountWithKey creates an account with the given balance
|
||||
// and a random private key
|
||||
func NewAccountWithKey(coins Coins) *AccountWithKey {
|
||||
return &AccountWithKey{
|
||||
Key: crypto.GenPrivKeyEd25519().Wrap(),
|
||||
Account: Account{Coins: coins},
|
||||
}
|
||||
}
|
||||
|
||||
// Address returns the public key address for this account
|
||||
func (a *AccountWithKey) Address() []byte {
|
||||
return a.Key.PubKey().Address()
|
||||
}
|
||||
|
||||
// Actor returns the basecoin actor associated with this account
|
||||
func (a *AccountWithKey) Actor() sdk.Actor {
|
||||
return auth.SigPerm(a.Key.PubKey().Address())
|
||||
}
|
||||
|
||||
// NextSequence returns the next sequence to sign with
|
||||
func (a *AccountWithKey) NextSequence() uint32 {
|
||||
a.Sequence++
|
||||
return a.Sequence
|
||||
}
|
||||
|
||||
// MakeOption returns a string to use with InitState to initialize this account
|
||||
//
|
||||
// This is intended for use in test cases
|
||||
func (a *AccountWithKey) MakeOption() string {
|
||||
info := GenesisAccount{
|
||||
Address: a.Address(),
|
||||
Balance: a.Coins,
|
||||
}
|
||||
js, err := data.ToJSON(info)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(js)
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
// TODO: other test making sure tx is output on send, balance is updated
|
||||
|
||||
// This makes sure we respond properly to posttx
|
||||
// TODO: set credit limit
|
||||
func TestIBCPostPacket(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
otherID := "chain-2"
|
||||
ourID := "dex"
|
||||
start := 200
|
||||
|
||||
// create the app and our chain
|
||||
app := stack.New().
|
||||
IBC(ibc.NewMiddleware()).
|
||||
Dispatch(
|
||||
NewHandler(),
|
||||
stack.WrapHandler(ibc.NewHandler()),
|
||||
)
|
||||
ourChain := ibc.NewAppChain(app, ourID)
|
||||
|
||||
// set up the other chain and register it with us
|
||||
otherChain := ibc.NewMockChain(otherID, 7)
|
||||
registerTx := otherChain.GetRegistrationTx(start).Wrap()
|
||||
_, err := ourChain.DeliverTx(registerTx)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// set up a rich guy on this chain
|
||||
wealth := Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}}
|
||||
rich := NewAccountWithKey(wealth)
|
||||
_, err = ourChain.InitState("coin", "account", rich.MakeOption())
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// sends money to another guy on a different chain, now other chain has credit
|
||||
buddy := sdk.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("dude")}
|
||||
outTx := NewSendOneTx(rich.Actor(), buddy, wealth)
|
||||
_, err = ourChain.DeliverTx(outTx, rich.Actor())
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// make sure the money moved to the other chain...
|
||||
cstore := ourChain.GetStore(NameCoin)
|
||||
acct, err := GetAccount(cstore, ChainAddr(buddy))
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(wealth, acct.Coins)
|
||||
|
||||
// make sure there is a proper packet for this....
|
||||
istore := ourChain.GetStore(ibc.NameIBC)
|
||||
assertPacket(t, istore, otherID, wealth)
|
||||
|
||||
// these are the people for testing incoming ibc from the other chain
|
||||
recipient := sdk.Actor{ChainID: ourID, App: auth.NameSigs, Address: []byte("bar")}
|
||||
sender := sdk.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("foo")}
|
||||
payment := Coins{{"eth", 100}, {"ltc", 300}}
|
||||
coinTx := NewSendOneTx(sender, recipient, payment)
|
||||
wrongCoin := NewSendOneTx(sender, recipient, Coins{{"missing", 20}})
|
||||
|
||||
p0 := ibc.NewPacket(coinTx, ourID, 0, sender)
|
||||
packet0, update0 := otherChain.MakePostPacket(p0, start+5)
|
||||
require.Nil(ourChain.Update(update0))
|
||||
|
||||
p1 := ibc.NewPacket(coinTx, ourID, 1, sender)
|
||||
packet1, update1 := otherChain.MakePostPacket(p1, start+25)
|
||||
require.Nil(ourChain.Update(update1))
|
||||
|
||||
p2 := ibc.NewPacket(wrongCoin, ourID, 2, sender)
|
||||
packet2, update2 := otherChain.MakePostPacket(p2, start+50)
|
||||
require.Nil(ourChain.Update(update2))
|
||||
|
||||
ibcPerm := sdk.Actors{ibc.AllowIBC(NameCoin)}
|
||||
cases := []struct {
|
||||
packet ibc.PostPacketTx
|
||||
permissions sdk.Actors
|
||||
checker errors.CheckErr
|
||||
}{
|
||||
// out of order -> error
|
||||
{packet1, ibcPerm, ibc.IsPacketOutOfOrderErr},
|
||||
|
||||
// all good -> execute tx
|
||||
{packet0, ibcPerm, errors.NoErr},
|
||||
|
||||
// all good -> execute tx (even if earlier attempt failed)
|
||||
{packet1, ibcPerm, errors.NoErr},
|
||||
|
||||
// packet 2 attempts to spend money this chain doesn't have
|
||||
{packet2, ibcPerm, IsInsufficientFundsErr},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
_, err := ourChain.DeliverTx(tc.packet.Wrap(), tc.permissions...)
|
||||
assert.True(tc.checker(err), "%d: %+v", i, err)
|
||||
}
|
||||
|
||||
// now, make sure the recipient got credited for the 2 successful sendtx
|
||||
cstore = ourChain.GetStore(NameCoin)
|
||||
// FIXME: we need to strip off this when it is local chain-id...
|
||||
// think this throw and handle this better
|
||||
local := recipient.WithChain("")
|
||||
acct, err = GetAccount(cstore, local)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(payment.Plus(payment), acct.Coins)
|
||||
|
||||
}
|
||||
|
||||
func assertPacket(t *testing.T, istore state.SimpleDB, destID string, amount Coins) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
iq := ibc.InputQueue(istore, destID)
|
||||
require.Equal(0, iq.Size())
|
||||
|
||||
q := ibc.OutputQueue(istore, destID)
|
||||
require.Equal(1, q.Size())
|
||||
d := q.Item(0)
|
||||
var res ibc.Packet
|
||||
err := wire.ReadBinaryBytes(d, &res)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(destID, res.DestChain)
|
||||
assert.EqualValues(0, res.Sequence)
|
||||
stx, ok := res.Tx.Unwrap().(SendTx)
|
||||
if assert.True(ok) {
|
||||
assert.Equal(1, len(stx.Outputs))
|
||||
assert.Equal(amount, stx.Outputs[0].Coins)
|
||||
}
|
||||
}
|
||||
@ -1,166 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/base"
|
||||
"github.com/cosmos/cosmos-sdk/x/coin"
|
||||
"github.com/cosmos/cosmos-sdk/x/fee"
|
||||
"github.com/cosmos/cosmos-sdk/x/nonce"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
// SendInput is the request to send an amount from one actor to another.
|
||||
// Note: Not using the `validator:""` tags here because SendInput has
|
||||
// many fields so it would be nice to figure out all the invalid
|
||||
// inputs and report them back to the caller, in one shot.
|
||||
type SendInput struct {
|
||||
Fees *coin.Coin `json:"fees"`
|
||||
Multi bool `json:"multi,omitempty"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
|
||||
To *sdk.Actor `json:"to"`
|
||||
From *sdk.Actor `json:"from"`
|
||||
Amount coin.Coins `json:"amount"`
|
||||
}
|
||||
|
||||
// doQueryAccount is the HTTP handlerfunc to query an account
|
||||
// It expects a query string with
|
||||
func doQueryAccount(w http.ResponseWriter, r *http.Request) {
|
||||
args := mux.Vars(r)
|
||||
signature := args["signature"]
|
||||
actor, err := commands.ParseActor(signature)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var h int
|
||||
qHeight := r.URL.Query().Get("height")
|
||||
if qHeight != "" {
|
||||
h, err = strconv.Atoi(qHeight)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
actor = coin.ChainAddr(actor)
|
||||
key := stack.PrefixedKey(coin.NameCoin, actor.Bytes())
|
||||
account := new(coin.Account)
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
height, err := query.GetParsed(key, account, h, prove)
|
||||
if client.IsNoDataErr(err) {
|
||||
err := fmt.Errorf("account bytes are empty for address: %q", signature)
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
} else if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := query.FoutputProof(w, account, height); err != nil {
|
||||
common.WriteError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func PrepareSendTx(si *SendInput) sdk.Tx {
|
||||
tx := coin.NewSendOneTx(*si.From, *si.To, si.Amount)
|
||||
// fees are optional
|
||||
if si.Fees != nil && !si.Fees.IsZero() {
|
||||
tx = fee.NewFee(tx, *si.Fees, *si.From)
|
||||
}
|
||||
// only add the actual signer to the nonce
|
||||
signers := []sdk.Actor{*si.From}
|
||||
tx = nonce.NewTx(si.Sequence, signers, tx)
|
||||
tx = base.NewChainTx(commands.GetChainID(), 0, tx)
|
||||
|
||||
if si.Multi {
|
||||
tx = auth.NewMulti(tx).Wrap()
|
||||
} else {
|
||||
tx = auth.NewSig(tx).Wrap()
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
func doSend(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
si := new(SendInput)
|
||||
if err := common.ParseRequestAndValidateJSON(r, si); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var errsList []string
|
||||
if si.From == nil {
|
||||
errsList = append(errsList, `"from" cannot be nil`)
|
||||
}
|
||||
if si.Sequence <= 0 {
|
||||
errsList = append(errsList, `"sequence" must be > 0`)
|
||||
}
|
||||
if si.To == nil {
|
||||
errsList = append(errsList, `"to" cannot be nil`)
|
||||
}
|
||||
if len(si.Amount) == 0 {
|
||||
errsList = append(errsList, `"amount" cannot be empty`)
|
||||
}
|
||||
if len(errsList) > 0 {
|
||||
code := http.StatusBadRequest
|
||||
err := &common.ErrorResponse{
|
||||
Err: strings.Join(errsList, ", "),
|
||||
Code: code,
|
||||
}
|
||||
common.WriteCode(w, err, code)
|
||||
return
|
||||
}
|
||||
|
||||
tx := PrepareSendTx(si)
|
||||
common.WriteSuccess(w, tx)
|
||||
}
|
||||
|
||||
// mux.Router registrars
|
||||
|
||||
// RegisterCoinSend is a mux.Router handler that exposes
|
||||
// POST method access on route /build/send to create a
|
||||
// transaction for sending money from one account to another.
|
||||
func RegisterCoinSend(r *mux.Router) error {
|
||||
r.HandleFunc("/build/send", doSend).Methods("POST")
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterQueryAccount is a mux.Router handler that exposes GET
|
||||
// method access on route /query/account/{signature} to query accounts
|
||||
func RegisterQueryAccount(r *mux.Router) error {
|
||||
r.HandleFunc("/query/account/{signature}", doQueryAccount).Methods("GET")
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterAll is a convenience function to
|
||||
// register all the handlers in this package.
|
||||
func RegisterAll(r *mux.Router) error {
|
||||
funcs := []func(*mux.Router) error{
|
||||
RegisterCoinSend,
|
||||
RegisterQueryAccount,
|
||||
}
|
||||
|
||||
for _, fn := range funcs {
|
||||
if err := fn(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// End of mux.Router registrars
|
||||
@ -1,145 +0,0 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
)
|
||||
|
||||
// GetAccount - Get account from store and address
|
||||
func GetAccount(store store.MultiStore, addr sdk.Actor) (Account, error) {
|
||||
// if the actor is another chain, we use one address for the chain....
|
||||
addr = ChainAddr(addr)
|
||||
acct, err := loadAccount(store, addr.Bytes())
|
||||
|
||||
// for empty accounts, don't return an error, but rather an empty account
|
||||
if IsNoAccountErr(err) {
|
||||
err = nil
|
||||
}
|
||||
return acct, err
|
||||
}
|
||||
|
||||
// CheckCoins makes sure there are funds, but doesn't change anything
|
||||
func CheckCoins(store store.MultiStore, addr sdk.Actor, coins Coins) (Coins, error) {
|
||||
// if the actor is another chain, we use one address for the chain....
|
||||
addr = ChainAddr(addr)
|
||||
|
||||
acct, err := updateCoins(store, addr, coins)
|
||||
return acct.Coins, err
|
||||
}
|
||||
|
||||
// ChangeCoins changes the money, returns error if it would be negative
|
||||
func ChangeCoins(store store.MultiStore, addr sdk.Actor, coins Coins) (Coins, error) {
|
||||
// if the actor is another chain, we use one address for the chain....
|
||||
addr = ChainAddr(addr)
|
||||
|
||||
acct, err := updateCoins(store, addr, coins)
|
||||
if err != nil {
|
||||
return acct.Coins, err
|
||||
}
|
||||
|
||||
err = storeAccount(store, addr.Bytes(), acct)
|
||||
return acct.Coins, err
|
||||
}
|
||||
|
||||
// ChainAddr collapses all addresses from another chain into one, so we can
|
||||
// keep an over-all balance
|
||||
//
|
||||
// TODO: is there a better way to do this?
|
||||
func ChainAddr(addr sdk.Actor) sdk.Actor {
|
||||
if addr.ChainID == "" {
|
||||
return addr
|
||||
}
|
||||
addr.App = ""
|
||||
addr.Address = nil
|
||||
return addr
|
||||
}
|
||||
|
||||
// updateCoins will load the account, make all checks, and return the updated account.
|
||||
//
|
||||
// it doesn't save anything, that is up to you to decide (Check/Change Coins)
|
||||
func updateCoins(store store.MultiStore, addr sdk.Actor, coins Coins) (acct Account, err error) {
|
||||
acct, err = loadAccount(store, addr.Bytes())
|
||||
// we can increase an empty account...
|
||||
if IsNoAccountErr(err) && coins.IsPositive() {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return acct, err
|
||||
}
|
||||
|
||||
// check amount
|
||||
final := acct.Coins.Plus(coins)
|
||||
if !final.IsNonnegative() {
|
||||
return acct, ErrInsufficientFunds()
|
||||
}
|
||||
|
||||
acct.Coins = final
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
// Account - coin account structure
|
||||
type Account struct {
|
||||
// Coins is how much is on the account
|
||||
Coins Coins `json:"coins"`
|
||||
// Credit is how much has been "fronted" to the account
|
||||
// (this is usually 0 except for trusted chains)
|
||||
Credit Coins `json:"credit"`
|
||||
}
|
||||
|
||||
func loadAccount(store store.MultiStore, key []byte) (acct Account, err error) {
|
||||
// fmt.Printf("load: %X\n", key)
|
||||
data := store.Get(key)
|
||||
if len(data) == 0 {
|
||||
return acct, ErrNoAccount()
|
||||
}
|
||||
err = wire.ReadBinaryBytes(data, &acct)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Error reading account %X", key)
|
||||
return acct, errors.ErrInternal(msg)
|
||||
}
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
func storeAccount(store store.MultiStore, key []byte, acct Account) error {
|
||||
// fmt.Printf("store: %X\n", key)
|
||||
bin := wire.BinaryBytes(acct)
|
||||
store.Set(key, bin)
|
||||
return nil // real stores can return error...
|
||||
}
|
||||
|
||||
// HandlerInfo - this is global info on the coin handler
|
||||
type HandlerInfo struct {
|
||||
Issuer sdk.Actor `json:"issuer"`
|
||||
}
|
||||
|
||||
// TODO: where to store these special pieces??
|
||||
var handlerKey = []byte{12, 34}
|
||||
|
||||
func loadHandlerInfo(store store.KVStore) (info HandlerInfo, err error) {
|
||||
data := store.Get(handlerKey)
|
||||
if len(data) == 0 {
|
||||
return info, nil
|
||||
}
|
||||
err = wire.ReadBinaryBytes(data, &info)
|
||||
if err != nil {
|
||||
msg := "Error reading handler info"
|
||||
return info, errors.ErrInternal(msg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func storeIssuer(store store.KVStore, issuer sdk.Actor) error {
|
||||
info, err := loadHandlerInfo(store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.Issuer = issuer
|
||||
d := wire.BinaryBytes(info)
|
||||
store.Set(handlerKey, d)
|
||||
return nil // real stores can return error...
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
LINKER_FLAGS:="-X github.com/cosmos/cosmos-sdk/client/commands.CommitHash=`git rev-parse --short HEAD`"
|
||||
|
||||
install:
|
||||
@go install -ldflags $(LINKER_FLAGS) ./cmd/...
|
||||
|
||||
test: test_unit test_cli
|
||||
|
||||
test_unit:
|
||||
@go test `glide novendor`
|
||||
|
||||
test_cli:
|
||||
./tests/cli/keys.sh
|
||||
./tests/cli/rpc.sh
|
||||
./tests/cli/init.sh
|
||||
./tests/cli/init-server.sh
|
||||
./tests/cli/basictx.sh
|
||||
./tests/cli/roles.sh
|
||||
./tests/cli/restart.sh
|
||||
./tests/cli/rest.sh
|
||||
./tests/cli/ibc.sh
|
||||
|
||||
.PHONY: install test test_unit test_cli
|
||||
@ -1,73 +0,0 @@
|
||||
// XXX Rename AppHandler to DefaultAppHandler.
|
||||
// XXX Register with a sdk.BaseApp instance to create Basecoin.
|
||||
// XXX Create TxParser in anotehr file.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
)
|
||||
|
||||
// AppHandler has no state for now, a more complex app could store state here
|
||||
type AppHandler struct{}
|
||||
|
||||
func NewAppHandler() sdk.Handler {
|
||||
return AppHandler{}
|
||||
}
|
||||
|
||||
// DeliverTx applies the tx
|
||||
func (h Handler) DeliverTx(ctx sdk.Context, store store.MultiStore,
|
||||
msg interface{}) (res sdk.DeliverResult, err error) {
|
||||
|
||||
db := store.Get("main").(sdk.KVStore)
|
||||
|
||||
// Here we switch on which implementation of tx we use,
|
||||
// and then take the appropriate action.
|
||||
switch tx := tx.(type) {
|
||||
case SendTx:
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
db.Set(tx.Key, tx.Value)
|
||||
res.Data = tx.Key
|
||||
case RemoveTx:
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
db.Remove(tx.Key)
|
||||
res.Data = tx.Key
|
||||
default:
|
||||
err = errors.ErrInvalidFormat(TxWrapper{}, msg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckTx verifies if it is legit and returns info on how
|
||||
// to prioritize it in the mempool
|
||||
func (h Handler) CheckTx(ctx sdk.Context, store store.MultiStore,
|
||||
msg interface{}) (res sdk.CheckResult, err error) {
|
||||
|
||||
// If we wanted to use the store,
|
||||
// it would look the same (as DeliverTx)
|
||||
// db := store.Get("main").(sdk.KVStore)
|
||||
|
||||
// Make sure it is something valid
|
||||
tx, ok := msg.(Tx)
|
||||
if !ok {
|
||||
return res, errors.ErrInvalidFormat(TxWrapper{}, msg)
|
||||
}
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Now return the costs (these should have meaning in your app)
|
||||
return sdk.CheckResult{
|
||||
GasAllocated: 50,
|
||||
GasPayment: 10,
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
# Run your own (super) lightweight node
|
||||
|
||||
In addition to providing command-line tooling that goes cryptographic verification
|
||||
on all the data your receive from the node, we have implemented a proxy mode, that
|
||||
allows you to run a super lightweight node. It does not follow the chain on
|
||||
every block or even every header, but only as needed. But still providing the
|
||||
same security as running a full non-validator node on your local machine.
|
||||
|
||||
Basically, it runs as a proxy that exposes the same rpc interface as the full node
|
||||
and connects to a (potentially untrusted) full node. Every response is cryptographically
|
||||
verified before being passed through, returning an error if it doesn't match.
|
||||
|
||||
You can expect 2 rpc calls for every query plus <= 1 query for each validator set
|
||||
change. Going offline for a while allows you to verify multiple validator set changes
|
||||
with one call. Cuz at 1 block/sec and 1000 tx/block, it just doesn't make sense
|
||||
to run a full node just to get security
|
||||
|
||||
## Setup
|
||||
|
||||
Just initialize your client with the proper validator set as in the [README](README.md)
|
||||
|
||||
```
|
||||
$ export BCHOME=~/.lightnode
|
||||
$ basecli init --node tcp://<host>:<port> --chain-id <chain>
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```
|
||||
$ basecli proxy --serve tcp://localhost:7890
|
||||
...
|
||||
curl localhost:7890/status
|
||||
curl localhost:7890/block\?height=20
|
||||
```
|
||||
|
||||
You can even subscribe to events over websockets and they are all verified
|
||||
before passing them though. Though if you want every block, you might as
|
||||
well run a full (nonvalidating) node.
|
||||
|
||||
## Seeds
|
||||
|
||||
Every time the validator set changes, the light node verifies if it is legal,
|
||||
and then creates a seed at that point. These "seeds" are verified checkpoints
|
||||
that we can trace any proof back to, starting with one on `init`.
|
||||
|
||||
To make sure you are based on the most recent header, you can run:
|
||||
|
||||
```
|
||||
basecli seeds update
|
||||
basecli seeds show
|
||||
```
|
||||
|
||||
## Feedback
|
||||
|
||||
This is the first release of basecli and the light-weight proxy. It is secure, but
|
||||
may not be useful for your workflow. Please try it out and open github issues
|
||||
for any enhancements or bugs you find. I am aiming to make this a very useful
|
||||
tool by tendermint 0.11, for which I need community feedback.
|
||||
@ -1,53 +0,0 @@
|
||||
# Basic run through of using basecli....
|
||||
|
||||
To keep things clear, let's have two shells...
|
||||
|
||||
`$` is for basecoin (server), `%` is for basecli (client)
|
||||
|
||||
## Set up your basecli with a new key
|
||||
|
||||
```
|
||||
% export BCHOME=~/.democli
|
||||
% basecli keys new demo
|
||||
% basecli keys get demo -o json
|
||||
```
|
||||
|
||||
And set up a few more keys for fun...
|
||||
|
||||
```
|
||||
% basecli keys new buddy
|
||||
% basecli keys list
|
||||
% ME=$(basecli keys get demo | awk '{print $2}')
|
||||
% YOU=$(basecli keys get buddy | awk '{print $2}')
|
||||
```
|
||||
|
||||
## Set up a clean basecoin, initialized with your account
|
||||
|
||||
```
|
||||
$ export BCHOME=~/.demoserve
|
||||
$ basecoin init $ME
|
||||
$ basecoin start
|
||||
```
|
||||
|
||||
## Connect your basecli the first time
|
||||
|
||||
```
|
||||
% basecli init --chain-id test_chain_id --node tcp://localhost:46657
|
||||
```
|
||||
|
||||
## Check your balances...
|
||||
|
||||
```
|
||||
% basecli query account $ME
|
||||
% basecli query account $YOU
|
||||
```
|
||||
|
||||
## Send the money
|
||||
|
||||
```
|
||||
% basecli tx send --name demo --amount 1000mycoin --sequence 1 --to $YOU
|
||||
-> copy hash to HASH
|
||||
% basecli query tx $HASH
|
||||
% basecli query account $YOU
|
||||
```
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/auto"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/commits"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/proxy"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
rpccmd "github.com/cosmos/cosmos-sdk/client/commands/rpc"
|
||||
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/modules/auth/commands"
|
||||
basecmd "github.com/cosmos/cosmos-sdk/modules/base/commands"
|
||||
coincmd "github.com/cosmos/cosmos-sdk/modules/coin/commands"
|
||||
feecmd "github.com/cosmos/cosmos-sdk/modules/fee/commands"
|
||||
ibccmd "github.com/cosmos/cosmos-sdk/modules/ibc/commands"
|
||||
noncecmd "github.com/cosmos/cosmos-sdk/modules/nonce/commands"
|
||||
rolecmd "github.com/cosmos/cosmos-sdk/modules/roles/commands"
|
||||
)
|
||||
|
||||
// BaseCli - main basecoin client command
|
||||
var BaseCli = &cobra.Command{
|
||||
Use: "basecli",
|
||||
Short: "Light client for Tendermint",
|
||||
Long: `Basecli is a certifying light client for the basecoin abci app.
|
||||
|
||||
It leverages the power of the tendermint consensus algorithm get full
|
||||
cryptographic proof of all queries while only syncing a fraction of the
|
||||
block headers.`,
|
||||
}
|
||||
|
||||
func main() {
|
||||
commands.AddBasicFlags(BaseCli)
|
||||
|
||||
// Prepare queries
|
||||
query.RootCmd.AddCommand(
|
||||
// These are default parsers, but optional in your app (you can remove key)
|
||||
query.TxQueryCmd,
|
||||
query.KeyQueryCmd,
|
||||
coincmd.AccountQueryCmd,
|
||||
noncecmd.NonceQueryCmd,
|
||||
rolecmd.RoleQueryCmd,
|
||||
ibccmd.IBCQueryCmd,
|
||||
)
|
||||
|
||||
// set up the middleware
|
||||
txcmd.Middleware = txcmd.Wrappers{
|
||||
feecmd.FeeWrapper{},
|
||||
rolecmd.RoleWrapper{},
|
||||
noncecmd.NonceWrapper{},
|
||||
basecmd.ChainWrapper{},
|
||||
authcmd.SigWrapper{},
|
||||
}
|
||||
txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags())
|
||||
|
||||
// you will always want this for the base send command
|
||||
txcmd.RootCmd.AddCommand(
|
||||
// This is the default transaction, optional in your app
|
||||
coincmd.SendTxCmd,
|
||||
coincmd.CreditTxCmd,
|
||||
// this enables creating roles
|
||||
rolecmd.CreateRoleTxCmd,
|
||||
// these are for handling ibc
|
||||
ibccmd.RegisterChainTxCmd,
|
||||
ibccmd.UpdateChainTxCmd,
|
||||
ibccmd.PostPacketTxCmd,
|
||||
)
|
||||
|
||||
// Set up the various commands to use
|
||||
BaseCli.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.ResetCmd,
|
||||
keys.RootCmd,
|
||||
commits.RootCmd,
|
||||
rpccmd.RootCmd,
|
||||
query.RootCmd,
|
||||
txcmd.RootCmd,
|
||||
proxy.RootCmd,
|
||||
commands.VersionCmd,
|
||||
auto.AutoCompleteCmd,
|
||||
)
|
||||
|
||||
cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli"))
|
||||
cmd.Execute()
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
client "github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
"github.com/cosmos/cosmos-sdk/modules/base"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/cosmos/cosmos-sdk/modules/eyes"
|
||||
"github.com/cosmos/cosmos-sdk/modules/fee"
|
||||
"github.com/cosmos/cosmos-sdk/modules/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/modules/nonce"
|
||||
"github.com/cosmos/cosmos-sdk/modules/roles"
|
||||
"github.com/cosmos/cosmos-sdk/server/commands"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
)
|
||||
|
||||
// RootCmd is the entry point for this binary
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "basecoin",
|
||||
Short: "A cryptocurrency framework in Golang based on Tendermint-Core",
|
||||
}
|
||||
|
||||
// BuildApp constructs the stack we want to use for this app
|
||||
func BuildApp(feeDenom string) sdk.Handler {
|
||||
return stack.New(
|
||||
base.Logger{},
|
||||
stack.Recovery{},
|
||||
auth.Signatures{},
|
||||
base.Chain{},
|
||||
stack.Checkpoint{OnCheck: true},
|
||||
nonce.ReplayCheck{},
|
||||
).
|
||||
IBC(ibc.NewMiddleware()).
|
||||
Apps(
|
||||
roles.NewMiddleware(),
|
||||
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||
stack.Checkpoint{OnDeliver: true},
|
||||
).
|
||||
Dispatch(
|
||||
coin.NewHandler(),
|
||||
stack.WrapHandler(roles.NewHandler()),
|
||||
stack.WrapHandler(ibc.NewHandler()),
|
||||
// and just for run, add eyes as well
|
||||
stack.WrapHandler(eyes.NewHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// require all fees in mycoin - change this in your app!
|
||||
commands.Handler = BuildApp("mycoin")
|
||||
|
||||
RootCmd.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.StartCmd,
|
||||
//commands.RelayCmd,
|
||||
commands.UnsafeResetAllCmd,
|
||||
client.VersionCmd,
|
||||
)
|
||||
commands.SetUpRoot(RootCmd)
|
||||
|
||||
cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin"))
|
||||
cmd.Execute()
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
# baseserver
|
||||
|
||||
baseserver is the REST counterpart to basecli
|
||||
|
||||
## Compiling and running it
|
||||
```shell
|
||||
$ go get -u -v github.com/tendermint/basecoin/cmd/baseserver
|
||||
$ baseserver init
|
||||
$ baseserver serve --port 8888
|
||||
```
|
||||
|
||||
to run the server at localhost:8888, otherwise if you don't specify --port,
|
||||
by default the server will be run on port 8998.
|
||||
|
||||
## Supported routes
|
||||
Route | Method | Completed | Description
|
||||
---|---|---|---
|
||||
/keys|GET|✔️|Lists all keys
|
||||
/keys|POST|✔️|Generate a new key. It expects fields: "name", "algo", "passphrase"
|
||||
/keys/{name}|GET|✔️|Retrieves the specific key
|
||||
/keys/{name}|POST/PUT|✔️|Updates the named key
|
||||
/keys/{name}|DELETE|✔️|Deletes the named key
|
||||
/build/send|POST|✔️|Send a transaction
|
||||
/sign|POST|✔️|Sign a transaction
|
||||
/tx|POST|✖️|Post a transaction to the blockchain
|
||||
/seeds/status|GET|✖️|Returns the information on the last seed
|
||||
/build/create_role|POST|✔️|Creates a role. Please note that the role MUST be valid hex for example instead of sending "role", send its hex encoded equivalent "726f6c65"
|
||||
|
||||
## Preamble:
|
||||
In the examples below, we assume that URL is set to `http://localhost:8889`
|
||||
which can be set for example
|
||||
URL=http://localhost:8889
|
||||
|
||||
## Sample usage
|
||||
- Generate a key
|
||||
```shell
|
||||
$ curl -X POST $URL/keys --data '{"algo": "ed25519", "name": "SampleX", "passphrase": "Say no more"}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"key": {
|
||||
"name": "SampleX",
|
||||
"address": "603EE63C41E322FC7A247864A9CD0181282EB458",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "C050948CFC087F5E1068C7E244DDC30E03702621CC9442A28E6C9EDA7771AA0C"
|
||||
}
|
||||
},
|
||||
"seed_phrase": "border almost future parade speak soccer bulk orange real brisk caution body river chapter"
|
||||
}
|
||||
```
|
||||
|
||||
- Sign a key
|
||||
```shell
|
||||
$ curl -X POST $URL/sign --data '{
|
||||
"name": "matt",
|
||||
"password": "Say no more",
|
||||
"tx": {
|
||||
"type": "sigs/multi",
|
||||
"data": {
|
||||
"tx": {"type":"coin/send","data":{"inputs":[{"address":{"chain":"","app":"role","addr":"62616E6B32"},"coins":[{"denom":"mycoin","amount":900000}]}],"outputs":[{"address":{"chain":"","app":"sigs","addr":"BDADF167E6CF2CDF2D621E590FF1FED2787A40E0"},"coins":[{"denom":"mycoin","amount":900000}]}]}},
|
||||
"signatures": null
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "sigs/multi",
|
||||
"data": {
|
||||
"tx": {
|
||||
"type": "coin/send",
|
||||
"data": {
|
||||
"inputs": [
|
||||
{
|
||||
"address": {
|
||||
"chain": "",
|
||||
"app": "role",
|
||||
"addr": "62616E6B32"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 900000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": {
|
||||
"chain": "",
|
||||
"app": "sigs",
|
||||
"addr": "BDADF167E6CF2CDF2D621E590FF1FED2787A40E0"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "mycoin",
|
||||
"amount": 900000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"Sig": {
|
||||
"type": "ed25519",
|
||||
"data": "F6FE3053F1E6C236F886A0D525C1AF840F7831B6E50F7E1108C345AA524303920F09945DA110AD5184B3F45717D7114E368B12AFE027FECECC2FC193D4906A0C"
|
||||
},
|
||||
"Pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "0D8D19E527BAE9D1256A3D03009E2708171CDCB71CCDEDA2DC52DD9AD23AEE25"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Create a role
|
||||
```shell
|
||||
$ curl -X POST $URL/build/create_role --data \
|
||||
'{
|
||||
"role": "deadbeef",
|
||||
"signers": [{
|
||||
"addr": "4FF759D47C81754D8F553DCCAC8651D0AF74C7F9",
|
||||
"app": "role"
|
||||
}],
|
||||
"min_sigs": 1,
|
||||
"seq": 1
|
||||
}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "chain/tx",
|
||||
"data": {
|
||||
"chain_id": "test_chain_id",
|
||||
"expires_at": 0,
|
||||
"tx": {
|
||||
"type": "role/create",
|
||||
"data": {
|
||||
"role": "DEADBEEF",
|
||||
"min_sigs": 1,
|
||||
"signers": [
|
||||
{
|
||||
"chain": "",
|
||||
"app": "role",
|
||||
"addr": "4FF759D47C81754D8F553DCCAC8651D0AF74C7F9"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1,95 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
rest "github.com/cosmos/cosmos-sdk/client/rest"
|
||||
coinrest "github.com/cosmos/cosmos-sdk/modules/coin/rest"
|
||||
noncerest "github.com/cosmos/cosmos-sdk/modules/nonce/rest"
|
||||
rolerest "github.com/cosmos/cosmos-sdk/modules/roles/rest"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
)
|
||||
|
||||
var srvCli = &cobra.Command{
|
||||
Use: "baseserver",
|
||||
Short: "Light REST client for tendermint",
|
||||
Long: `Baseserver presents a nice (not raw hex) interface to the basecoin blockchain structure.`,
|
||||
}
|
||||
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Serve the light REST client for tendermint",
|
||||
Long: "Access basecoin via REST",
|
||||
RunE: serve,
|
||||
}
|
||||
|
||||
const (
|
||||
envPortFlag = "port"
|
||||
defaultAlgo = "ed25519"
|
||||
)
|
||||
|
||||
func init() {
|
||||
_ = serveCmd.PersistentFlags().Int(envPortFlag, 8998, "the port to run the server on")
|
||||
}
|
||||
|
||||
func serve(cmd *cobra.Command, args []string) error {
|
||||
router := mux.NewRouter()
|
||||
|
||||
routeRegistrars := []func(*mux.Router) error{
|
||||
// rest.Keys handlers
|
||||
rest.NewDefaultKeysManager(defaultAlgo).RegisterAllCRUD,
|
||||
|
||||
// Coin send handler
|
||||
coinrest.RegisterCoinSend,
|
||||
// Coin query account handler
|
||||
coinrest.RegisterQueryAccount,
|
||||
|
||||
// Roles createRole handler
|
||||
rolerest.RegisterCreateRole,
|
||||
|
||||
// Basecoin sign transactions handler
|
||||
rest.RegisterSignTx,
|
||||
// Basecoin post transaction handler
|
||||
rest.RegisterPostTx,
|
||||
|
||||
// Nonce query handler
|
||||
noncerest.RegisterQueryNonce,
|
||||
}
|
||||
|
||||
for _, routeRegistrar := range routeRegistrars {
|
||||
if err := routeRegistrar(router); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
port := viper.GetInt(envPortFlag)
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
|
||||
log.Printf("Serving on %q", addr)
|
||||
return http.ListenAndServe(addr, router)
|
||||
}
|
||||
|
||||
func main() {
|
||||
commands.AddBasicFlags(srvCli)
|
||||
|
||||
srvCli.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.VersionCmd,
|
||||
serveCmd,
|
||||
)
|
||||
|
||||
// this should share the dir with basecli, so you can use the cli and
|
||||
// the api interchangeably
|
||||
cmd := cli.PrepareMainCmd(srvCli, "BC", os.ExpandEnv("$HOME/.basecli"))
|
||||
if err := cmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# These global variables are required for common.sh
|
||||
SERVER_EXE=basecoin
|
||||
CLIENT_EXE=basecli
|
||||
ACCOUNTS=(jae ethan bucky rigel igor)
|
||||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[4]}
|
||||
|
||||
oneTimeSetUp() {
|
||||
if ! quickSetup .basecoin_test_basictx basictx-chain; then
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
quickTearDown
|
||||
}
|
||||
|
||||
test00GetAccount() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account"
|
||||
|
||||
checkAccount $SENDER "9007199254740992"
|
||||
|
||||
ACCT2=$(${CLIENT_EXE} query account $RECV 2>/dev/null)
|
||||
assertFalse "line=${LINENO}, has no genesis account" $?
|
||||
}
|
||||
|
||||
test01SendTx() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1"
|
||||
assertFalse "line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH"
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH)
|
||||
txSucceeded $? "$TX" "$RECV"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkAccount $SENDER "9007199254740000" "$TX_HEIGHT"
|
||||
# make sure 0x prefix also works
|
||||
checkAccount "0x$SENDER" "9007199254740000" "$TX_HEIGHT"
|
||||
checkAccount $RECV "992" "$TX_HEIGHT"
|
||||
|
||||
# Make sure tx is indexed
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "992"
|
||||
}
|
||||
|
||||
test02SendTxWithFee() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
# Test to see if the auto-sequencing works, the sequence here should be calculated to be 2
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --to=$RECV --name=$RICH)
|
||||
txSucceeded $? "$TX" "$RECV"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# deduct 100 from sender, add 90 to receiver... fees "vanish"
|
||||
checkAccount $SENDER "9007199254739900" "$TX_HEIGHT"
|
||||
checkAccount $RECV "1082" "$TX_HEIGHT"
|
||||
|
||||
# Make sure tx is indexed
|
||||
checkSendFeeTx $HASH $TX_HEIGHT $SENDER "90" "10"
|
||||
|
||||
# assert replay protection
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=2 --to=$RECV --name=$RICH 2>/dev/null)
|
||||
assertFalse "line=${LINENO}, replay: $TX" $?
|
||||
|
||||
# checking normally
|
||||
checkAccount $SENDER "9007199254739900" "$TX_HEIGHT"
|
||||
checkAccount $RECV "1082" "$TX_HEIGHT"
|
||||
|
||||
# make sure we can query the proper nonce
|
||||
NONCE=$(${CLIENT_EXE} query nonce $SENDER)
|
||||
if [ -n "$DEBUG" ]; then echo $NONCE; echo; fi
|
||||
# TODO: note that cobra returns error code 0 on parse failure,
|
||||
# so currently this check passes even if there is no nonce query command
|
||||
if assertTrue "line=${LINENO}, no nonce query" $?; then
|
||||
assertEquals "line=${LINENO}, proper nonce" "2" $(echo $NONCE | jq .data)
|
||||
fi
|
||||
|
||||
# make sure this works without trust also
|
||||
OLD_BC_HOME=$BC_HOME
|
||||
export BC_HOME=/foo
|
||||
export BC_TRUST_NODE=1
|
||||
export BC_NODE=localhost:46657
|
||||
checkSendFeeTx $HASH $TX_HEIGHT $SENDER "90" "10"
|
||||
checkAccount $SENDER "9007199254739900" "$TX_HEIGHT"
|
||||
checkAccount $RECV "1082" "$TX_HEIGHT"
|
||||
unset BC_TRUST_NODE
|
||||
unset BC_NODE
|
||||
export BC_HOME=$OLD_BC_HOME
|
||||
}
|
||||
|
||||
|
||||
test03CreditTx() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
# make sure we are controlled by permissions (only rich can issue credit)
|
||||
assertFalse "line=${LINENO}, bad password" "echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=1000mycoin --sequence=1 --to=$RECV --name=$POOR"
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=1000mycoin --sequence=3 --to=$RECV --name=$RICH)
|
||||
txSucceeded $? "$TX" "$RECV"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# receiver got cash, sender didn't lose any (1000 more than last check)
|
||||
checkAccount $RECV "2082" "$TX_HEIGHT"
|
||||
checkAccount $SENDER "9007199254739900" "$TX_HEIGHT"
|
||||
}
|
||||
|
||||
|
||||
# Load common then run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/common.sh
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,284 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# These global variables are required for common.sh
|
||||
SERVER_EXE=basecoin
|
||||
CLIENT_EXE=basecli
|
||||
ACCOUNTS=(jae ethan bucky rigel igor)
|
||||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[4]}
|
||||
|
||||
# For full stack traces in error output, run
|
||||
# BC_TRACE=1 ./ibc.sh
|
||||
|
||||
oneTimeSetUp() {
|
||||
# These are passed in as args
|
||||
BASE_DIR_1=$HOME/.basecoin_test_ibc/chain1
|
||||
CHAIN_ID_1=test-chain-1
|
||||
CLIENT_1=${BASE_DIR_1}/client
|
||||
PREFIX_1=1234
|
||||
PORT_1=${PREFIX_1}7
|
||||
|
||||
BASE_DIR_2=$HOME/.basecoin_test_ibc/chain2
|
||||
CHAIN_ID_2=test-chain-2
|
||||
CLIENT_2=${BASE_DIR_2}/client
|
||||
PREFIX_2=2345
|
||||
PORT_2=${PREFIX_2}7
|
||||
|
||||
# Clean up and create the test dirs
|
||||
rm -rf $BASE_DIR_1 $BASE_DIR_2 2>/dev/null
|
||||
mkdir -p $BASE_DIR_1 $BASE_DIR_2
|
||||
|
||||
# Set up client for chain 1- make sure you use the proper prefix if you set
|
||||
# a custom CLIENT_EXE
|
||||
BC_HOME=${CLIENT_1} prepareClient
|
||||
BC_HOME=${CLIENT_2} prepareClient
|
||||
|
||||
# Start basecoin server, giving money to the key in the first client
|
||||
BC_HOME=${CLIENT_1} initServer $BASE_DIR_1 $CHAIN_ID_1 $PREFIX_1
|
||||
if [ $? != 0 ]; then exit 1; fi
|
||||
PID_SERVER_1=$PID_SERVER
|
||||
|
||||
# Start second basecoin server, giving money to the key in the second client
|
||||
BC_HOME=${CLIENT_2} initServer $BASE_DIR_2 $CHAIN_ID_2 $PREFIX_2
|
||||
if [ $? != 0 ]; then exit 1; fi
|
||||
PID_SERVER_2=$PID_SERVER
|
||||
|
||||
# Connect both clients
|
||||
BC_HOME=${CLIENT_1} initClient $CHAIN_ID_1 $PORT_1
|
||||
if [ $? != 0 ]; then exit 1; fi
|
||||
BC_HOME=${CLIENT_2} initClient $CHAIN_ID_2 $PORT_2
|
||||
if [ $? != 0 ]; then exit 1; fi
|
||||
|
||||
printf "...Testing may begin!\n\n\n"
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
printf "\n\nstopping both $SERVER_EXE test servers... $PID_SERVER_1 $PID_SERVER_2"
|
||||
kill -9 $PID_SERVER_1
|
||||
kill -9 $PID_SERVER_2
|
||||
sleep 1
|
||||
}
|
||||
|
||||
test00GetAccount() {
|
||||
export BC_HOME=${CLIENT_1}
|
||||
SENDER_1=$(getAddr $RICH)
|
||||
RECV_1=$(getAddr $POOR)
|
||||
|
||||
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null"
|
||||
assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_1 2>/dev/null"
|
||||
checkAccount $SENDER_1 "9007199254740992"
|
||||
|
||||
export BC_HOME=${CLIENT_2}
|
||||
SENDER_2=$(getAddr $RICH)
|
||||
RECV_2=$(getAddr $POOR)
|
||||
|
||||
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null"
|
||||
assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_2 2>/dev/null"
|
||||
checkAccount $SENDER_2 "9007199254740992"
|
||||
|
||||
# Make sure that they have different addresses on both chains (they are random keys)
|
||||
assertNotEquals "line=${LINENO}, sender keys must be different" "$SENDER_1" "$SENDER_2"
|
||||
assertNotEquals "line=${LINENO}, recipient keys must be different" "$RECV_1" "$RECV_2"
|
||||
}
|
||||
|
||||
test01RegisterChains() {
|
||||
# let's get the root commits to cross-register them
|
||||
ROOT_1="$BASE_DIR_1/root_commit.json"
|
||||
${CLIENT_EXE} commits export $ROOT_1 --home=${CLIENT_1}
|
||||
assertTrue "line=${LINENO}, export commit failed" $?
|
||||
|
||||
ROOT_2="$BASE_DIR_2/root_commit.json"
|
||||
${CLIENT_EXE} commits export $ROOT_2 --home=${CLIENT_2}
|
||||
assertTrue "line=${LINENO}, export commit failed" $?
|
||||
|
||||
# register chain2 on chain1
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-register \
|
||||
--sequence=1 --commit=${ROOT_2} --name=$POOR --home=${CLIENT_1})
|
||||
txSucceeded $? "$TX" "register chain2 on chain 1"
|
||||
# an example to quit early if there is no point in more tests
|
||||
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||
# this is used later to check data
|
||||
REG_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# register chain1 on chain2 (no money needed... yet)
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-register \
|
||||
--sequence=1 --commit=${ROOT_1} --name=$POOR --home=${CLIENT_2})
|
||||
txSucceeded $? "$TX" "register chain1 on chain 2"
|
||||
# an example to quit early if there is no point in more tests
|
||||
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||
}
|
||||
|
||||
test02UpdateChains() {
|
||||
# let's get the root commits to cross-register them
|
||||
UPDATE_1="$BASE_DIR_1/seed_1.json"
|
||||
${CLIENT_EXE} commits update --home=${CLIENT_1} > /dev/null
|
||||
${CLIENT_EXE} commits export $UPDATE_1 --home=${CLIENT_1}
|
||||
assertTrue "line=${LINENO}, export commit failed" $?
|
||||
# make sure it is newer than the other....
|
||||
assertNewHeight "line=${LINENO}" $ROOT_1 $UPDATE_1
|
||||
|
||||
UPDATE_2="$BASE_DIR_2/seed_2.json"
|
||||
${CLIENT_EXE} commits update --home=${CLIENT_2} > /dev/null
|
||||
${CLIENT_EXE} commits export $UPDATE_2 --home=${CLIENT_2}
|
||||
assertTrue "line=${LINENO}, export commit failed" $?
|
||||
assertNewHeight "line=${LINENO}" $ROOT_2 $UPDATE_2
|
||||
# this is used later to check query data
|
||||
REGISTER_2_HEIGHT=$(cat $ROOT_2 | jq .commit.header.height)
|
||||
UPDATE_2_HEIGHT=$(cat $UPDATE_2 | jq .commit.header.height)
|
||||
|
||||
# update chain2 on chain1
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \
|
||||
--sequence=2 --commit=${UPDATE_2} --name=$POOR --home=${CLIENT_1})
|
||||
txSucceeded $? "$TX" "update chain2 on chain 1"
|
||||
# an example to quit early if there is no point in more tests
|
||||
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||
|
||||
# update chain1 on chain2 (no money needed... yet)
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \
|
||||
--sequence=2 --commit=${UPDATE_1} --name=$POOR --home=${CLIENT_2})
|
||||
txSucceeded $? "$TX" "update chain1 on chain 2"
|
||||
# an example to quit early if there is no point in more tests
|
||||
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||
}
|
||||
|
||||
# make sure all query commands about ibc work...
|
||||
test03QueryIBC() {
|
||||
# just test on one chain, as they are all symetrical
|
||||
export BC_HOME=${CLIENT_1}
|
||||
|
||||
# make sure we can list all chains
|
||||
CHAINS=$(${CLIENT_EXE} query ibc chains)
|
||||
assertTrue "line=${LINENO}, cannot query chains" $?
|
||||
assertEquals "1" $(echo $CHAINS | jq '.data | length')
|
||||
assertEquals "line=${LINENO}" "\"$CHAIN_ID_2\"" $(echo $CHAINS | jq '.data[0]')
|
||||
|
||||
# error on unknown chain, data on proper chain
|
||||
assertFalse "line=${LINENO}, unknown chain" "${CLIENT_EXE} query ibc chain random 2>/dev/null"
|
||||
CHAIN_INFO=$(${CLIENT_EXE} query ibc chain $CHAIN_ID_2)
|
||||
assertTrue "line=${LINENO}, cannot query chain $CHAIN_ID_2" $?
|
||||
assertEquals "line=${LINENO}, register height" $REG_HEIGHT $(echo $CHAIN_INFO | jq .data.registered_at)
|
||||
assertEquals "line=${LINENO}, tracked height" $UPDATE_2_HEIGHT $(echo $CHAIN_INFO | jq .data.remote_block)
|
||||
}
|
||||
|
||||
# Trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2
|
||||
# we make sure the money was reduced, but nothing arrived
|
||||
test04SendIBCPacket() {
|
||||
export BC_HOME=${CLIENT_1}
|
||||
|
||||
# make sure there are no packets yet
|
||||
PACKETS=$(${CLIENT_EXE} query ibc packets --to=$CHAIN_ID_2 2>/dev/null)
|
||||
assertFalse "line=${LINENO}, packet query" $?
|
||||
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR)
|
||||
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin \
|
||||
--to=${CHAIN_ID_2}::${RECV} --name=$RICH)
|
||||
txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}"
|
||||
# quit early if there is no point in more tests
|
||||
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# Make sure balance went down and tx is indexed
|
||||
checkAccount $SENDER "9007199254720990" "$TX_HEIGHT"
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "20002"
|
||||
|
||||
# look, we wrote a packet
|
||||
PACKETS=$(${CLIENT_EXE} query ibc packets --to=$CHAIN_ID_2 --height=$TX_HEIGHT)
|
||||
assertTrue "line=${LINENO}, packets query" $?
|
||||
assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data)
|
||||
|
||||
# and look at the packet itself
|
||||
PACKET=$(${CLIENT_EXE} query ibc packet --to=$CHAIN_ID_2 --sequence=0 --height=$TX_HEIGHT)
|
||||
assertTrue "line=${LINENO}, packet query" $?
|
||||
assertEquals "line=${LINENO}, proper src" "\"$CHAIN_ID_1\"" $(echo $PACKET | jq .src_chain)
|
||||
assertEquals "line=${LINENO}, proper dest" "\"$CHAIN_ID_2\"" $(echo $PACKET | jq .packet.dest_chain)
|
||||
assertEquals "line=${LINENO}, proper sequence" "0" $(echo $PACKET | jq .packet.sequence)
|
||||
if [ -n "$DEBUG" ]; then echo $PACKET; echo; fi
|
||||
|
||||
# nothing arrived
|
||||
ARRIVED=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1 --home=$CLIENT_2 2>/dev/null)
|
||||
assertFalse "line=${LINENO}, packet query" $?
|
||||
assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV"
|
||||
}
|
||||
|
||||
test05ReceiveIBCPacket() {
|
||||
export BC_HOME=${CLIENT_2}
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
# make some credit, so we can accept the packet
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=60006mycoin --to=$CHAIN_ID_1:: --name=$RICH)
|
||||
txSucceeded $? "$TX" "${CHAIN_ID_1}::"
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# make sure there is enough credit
|
||||
checkAccount $CHAIN_ID_1:: "60006" "$TX_HEIGHT"
|
||||
# and the poor guy doesn't have a penny to his name
|
||||
ACCT2=$(${CLIENT_EXE} query account $RECV 2>/dev/null)
|
||||
assertFalse "line=${LINENO}, has no genesis account" $?
|
||||
|
||||
|
||||
# now, we try to post it.... (this is PACKET from last test)
|
||||
|
||||
# get the commit with the proof and post it
|
||||
SRC_HEIGHT=$(echo $PACKET | jq .src_height)
|
||||
PROOF_HEIGHT=$(expr $SRC_HEIGHT + 1)
|
||||
# FIXME: this should auto-update on proofs...
|
||||
${CLIENT_EXE} commits update --height=$PROOF_HEIGHT --home=${CLIENT_1} > /dev/null
|
||||
assertTrue "line=${LINENO}, update commit failed" $?
|
||||
|
||||
PACKET_COMMIT="$BASE_DIR_1/packet_commit.json"
|
||||
${CLIENT_EXE} commits export $PACKET_COMMIT --home=${CLIENT_1} --height=$PROOF_HEIGHT
|
||||
assertTrue "line=${LINENO}, export commit failed" $?
|
||||
if [ -n "$DEBUG" ]; then
|
||||
echo "**** SEED ****"
|
||||
cat $PACKET_COMMIT | jq .commit.header
|
||||
echo
|
||||
fi
|
||||
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \
|
||||
--commit=${PACKET_COMMIT} --name=$POOR --sequence=3)
|
||||
txSucceeded $? "$TX" "prepare packet chain1 on chain 2"
|
||||
# an example to quit early if there is no point in more tests
|
||||
if [ $? != 0 ]; then echo "aborting!"; return 1; fi
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# write the packet to the file
|
||||
POST_PACKET="$BASE_DIR_1/post_packet.json"
|
||||
echo $PACKET > $POST_PACKET
|
||||
|
||||
# post it as a tx (cross-fingers)
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-post \
|
||||
--packet=${POST_PACKET} --name=$POOR --sequence=4)
|
||||
txSucceeded $? "$TX" "post packet from chain1 on chain 2"
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# ensure $POOR balance was incremented, and credit for CHAIN_1 decremented
|
||||
checkAccount $CHAIN_ID_1:: "40004" "$TX_HEIGHT"
|
||||
checkAccount $RECV "20002" "$TX_HEIGHT"
|
||||
|
||||
# look, we wrote a packet
|
||||
PACKETS=$(${CLIENT_EXE} query ibc packets --height=$TX_HEIGHT --from=$CHAIN_ID_1)
|
||||
assertTrue "line=${LINENO}, packets query" $?
|
||||
assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data)
|
||||
}
|
||||
|
||||
# XXX Ex Usage: assertNewHeight $MSG $SEED_1 $SEED_2
|
||||
# Desc: Asserts that seed2 has a higher block height than commit 1
|
||||
assertNewHeight() {
|
||||
H1=$(cat $2 | jq .commit.header.height)
|
||||
H2=$(cat $3 | jq .commit.header.height)
|
||||
assertTrue "$1" "test $H2 -gt $H1"
|
||||
return $?
|
||||
}
|
||||
|
||||
# Load common then run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/common.sh
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,45 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
CLIENT_EXE=basecli
|
||||
SERVER_EXE=basecoin
|
||||
|
||||
test01initOption() {
|
||||
BASE=~/.bc_init_test
|
||||
rm -rf "$BASE"
|
||||
mkdir -p "$BASE"
|
||||
|
||||
SERVE_DIR="${BASE}/server"
|
||||
GENESIS_FILE=${SERVE_DIR}/genesis.json
|
||||
HEX="deadbeef1234deadbeef1234deadbeef1234aaaa"
|
||||
|
||||
${SERVER_EXE} init ${HEX} --home="$SERVE_DIR" -p=eyes/key1/val1 -p='"eyes/key2/{""name"": ""joe"", ""age"": ""100""}"' >/dev/null
|
||||
if ! assertTrue "line=${LINENO}" $?; then return 1; fi
|
||||
|
||||
OPTION1KEY=$(cat ${GENESIS_FILE} | jq '.app_options.plugin_options[2]')
|
||||
OPTION1VAL=$(cat ${GENESIS_FILE} | jq '.app_options.plugin_options[3]')
|
||||
OPTION2KEY=$(cat ${GENESIS_FILE} | jq '.app_options.plugin_options[4]')
|
||||
OPTION2VAL=$(cat ${GENESIS_FILE} | jq '.app_options.plugin_options[5]')
|
||||
OPTION2VALEXPECTED=$(echo '{"name": "joe", "age": "100"}' | jq '.')
|
||||
|
||||
assertEquals "line=${LINENO}" '"eyes/key1"' $OPTION1KEY
|
||||
assertEquals "line=${LINENO}" '"val1"' $OPTION1VAL
|
||||
assertEquals "line=${LINENO}" '"eyes/key2"' $OPTION2KEY
|
||||
assertEquals "line=${LINENO}" "$OPTION2VALEXPECTED" "$OPTION2VAL"
|
||||
}
|
||||
|
||||
test02runServer() {
|
||||
# Attempt to begin the server with the custom genesis
|
||||
SERVER_LOG=$BASE/${SERVER_EXE}.log
|
||||
startServer $SERVE_DIR $SERVER_LOG
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
quickTearDown
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/common.sh
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,110 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
CLIENT_EXE=basecli
|
||||
SERVER_EXE=basecoin
|
||||
|
||||
oneTimeSetUp() {
|
||||
BASE=~/.bc_init_test
|
||||
rm -rf "$BASE"
|
||||
mkdir -p "$BASE"
|
||||
|
||||
SERVER="${BASE}/server"
|
||||
SERVER_LOG="${BASE}/${SERVER_EXE}.log"
|
||||
|
||||
HEX="deadbeef1234deadbeef1234deadbeef1234aaaa"
|
||||
${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG"
|
||||
if ! assertTrue "line=${LINENO}" $?; then return 1; fi
|
||||
|
||||
GENESIS_FILE=${SERVER}/genesis.json
|
||||
CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \")
|
||||
|
||||
printf "starting ${SERVER_EXE}...\n"
|
||||
${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 &
|
||||
sleep 5
|
||||
PID_SERVER=$!
|
||||
disown
|
||||
if ! ps $PID_SERVER >/dev/null; then
|
||||
echo "**STARTUP FAILED**"
|
||||
cat $SERVER_LOG
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
printf "\nstopping ${SERVER_EXE}..."
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
test01goodInit() {
|
||||
export BCHOME=${BASE}/client-01
|
||||
assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2"
|
||||
|
||||
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null
|
||||
assertTrue "line=${LINENO}, initialized light-client" $?
|
||||
checkDir $BCHOME 3
|
||||
}
|
||||
|
||||
test02badInit() {
|
||||
export BCHOME=${BASE}/client-02
|
||||
assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2"
|
||||
|
||||
# no node where we go
|
||||
echo y | ${CLIENT_EXE} init --node=tcp://localhost:9999 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
|
||||
assertFalse "line=${LINENO}, invalid init" $?
|
||||
# dir there, but empty...
|
||||
checkDir $BCHOME 0
|
||||
|
||||
# try with invalid chain id
|
||||
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="bad-chain-id" > /dev/null 2>&1
|
||||
assertFalse "line=${LINENO}, invalid init" $?
|
||||
checkDir $BCHOME 0
|
||||
|
||||
# reject the response
|
||||
echo n | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
|
||||
assertFalse "line=${LINENO}, invalid init" $?
|
||||
checkDir $BCHOME 0
|
||||
}
|
||||
|
||||
test03noDoubleInit() {
|
||||
export BCHOME=${BASE}/client-03
|
||||
assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2"
|
||||
|
||||
# init properly
|
||||
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
|
||||
assertTrue "line=${LINENO}, initialized light-client" $?
|
||||
checkDir $BCHOME 3
|
||||
|
||||
# try again, and we get an error
|
||||
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
|
||||
assertFalse "line=${LINENO}, warning on re-init" $?
|
||||
checkDir $BCHOME 3
|
||||
|
||||
# unless we --force-reset
|
||||
echo y | ${CLIENT_EXE} init --force-reset --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
|
||||
assertTrue "line=${LINENO}, re-initialized light-client" $?
|
||||
checkDir $BCHOME 3
|
||||
}
|
||||
|
||||
test04acceptGenesisFile() {
|
||||
export BCHOME=${BASE}/client-04
|
||||
assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2"
|
||||
|
||||
# init properly
|
||||
${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1
|
||||
assertTrue "line=${LINENO}, initialized light-client" $?
|
||||
checkDir $BCHOME 3
|
||||
}
|
||||
|
||||
# XXX Ex: checkDir $DIR $FILES
|
||||
# Makes sure directory exists and has the given number of files
|
||||
checkDir() {
|
||||
assertTrue "line=${LINENO}" "ls ${1} 2>/dev/null >&2"
|
||||
assertEquals "line=${LINENO}, no files created" "$2" $(ls $1 | wc -l)
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,146 +0,0 @@
|
||||
#!/bin/bash
|
||||
EXE=basecli
|
||||
|
||||
|
||||
oneTimeSetUp() {
|
||||
PASS=qwertyuiop
|
||||
export BCHOME=$HOME/.bc_keys_test
|
||||
${EXE} reset_all
|
||||
assertTrue "line ${LINENO}" $?
|
||||
}
|
||||
|
||||
newKey(){
|
||||
assertNotNull "keyname required" "$1"
|
||||
KEYPASS=${2:-qwertyuiop}
|
||||
KEY=$(echo $KEYPASS | ${EXE} keys new $1 -o json)
|
||||
if ! assertTrue "line ${LINENO}: created $1" $?; then return 1; fi
|
||||
assertEquals "$1" $(echo $KEY | jq .key.name | tr -d \")
|
||||
return $?
|
||||
}
|
||||
|
||||
# updateKey <name> <oldkey> <newkey>
|
||||
updateKey() {
|
||||
(echo $2; echo $3) | ${EXE} keys update $1 > /dev/null
|
||||
return $?
|
||||
}
|
||||
|
||||
test00MakeKeys() {
|
||||
USER=demouser
|
||||
assertFalse "line ${LINENO}: already user $USER" "${EXE} keys get $USER"
|
||||
newKey $USER
|
||||
assertTrue "line ${LINENO}: no user $USER" "${EXE} keys get $USER"
|
||||
# make sure bad password not accepted
|
||||
assertFalse "accepts short password" "echo 123 | ${EXE} keys new badpass"
|
||||
}
|
||||
|
||||
test01ListKeys() {
|
||||
# one line plus the number of keys
|
||||
assertEquals "2" $(${EXE} keys list | wc -l)
|
||||
newKey foobar
|
||||
assertEquals "3" $(${EXE} keys list | wc -l)
|
||||
# we got the proper name here...
|
||||
assertEquals "foobar" $(${EXE} keys list -o json | jq .[1].name | tr -d \" )
|
||||
# we get all names in normal output
|
||||
EXPECTEDNAMES=$(echo demouser; echo foobar)
|
||||
TEXTNAMES=$(${EXE} keys list | tail -n +2 | cut -f1)
|
||||
assertEquals "$EXPECTEDNAMES" "$TEXTNAMES"
|
||||
# let's make sure the addresses match!
|
||||
assertEquals "line ${LINENO}: text and json addresses don't match" $(${EXE} keys list | tail -1 | cut -f3) $(${EXE} keys list -o json | jq .[1].address | tr -d \")
|
||||
}
|
||||
|
||||
test02updateKeys() {
|
||||
USER=changer
|
||||
PASS1=awsedrftgyhu
|
||||
PASS2=S4H.9j.D9S7hso
|
||||
PASS3=h8ybO7GY6d2
|
||||
|
||||
newKey $USER $PASS1
|
||||
assertFalse "line ${LINENO}: accepts invalid pass" "updateKey $USER $PASS2 $PASS2"
|
||||
assertTrue "line ${LINENO}: doesn't update" "updateKey $USER $PASS1 $PASS2"
|
||||
assertTrue "line ${LINENO}: takes new key after update" "updateKey $USER $PASS2 $PASS3"
|
||||
}
|
||||
|
||||
test03recoverKeys() {
|
||||
USER=sleepy
|
||||
PASS1=S4H.9j.D9S7hso
|
||||
|
||||
USER2=easy
|
||||
PASS2=1234567890
|
||||
|
||||
# make a user and check they exist
|
||||
KEY=$(echo $PASS1 | ${EXE} keys new $USER -o json)
|
||||
if ! assertTrue "created $USER" $?; then return 1; fi
|
||||
if [ -n "$DEBUG" ]; then echo $KEY; echo; fi
|
||||
|
||||
SEED=$(echo $KEY | jq .seed | tr -d \")
|
||||
ADDR=$(echo $KEY | jq .key.address | tr -d \")
|
||||
PUBKEY=$(echo $KEY | jq .key.pubkey | tr -d \")
|
||||
assertTrue "line ${LINENO}" "${EXE} keys get $USER > /dev/null"
|
||||
|
||||
# let's delete this key
|
||||
assertFalse "line ${LINENO}" "echo foo | ${EXE} keys delete $USER > /dev/null"
|
||||
assertTrue "line ${LINENO}" "echo $PASS1 | ${EXE} keys delete $USER > /dev/null"
|
||||
assertFalse "line ${LINENO}" "${EXE} keys get $USER > /dev/null"
|
||||
|
||||
# fails on short password
|
||||
assertFalse "line ${LINENO}" "echo foo; echo $SEED | ${EXE} keys recover $USER2 -o json > /dev/null"
|
||||
# fails on bad seed
|
||||
assertFalse "line ${LINENO}" "echo $PASS2; echo \"silly white whale tower bongo\" | ${EXE} keys recover $USER2 -o json > /dev/null"
|
||||
# now we got it
|
||||
KEY2=$((echo $PASS2; echo $SEED) | ${EXE} keys recover $USER2 -o json)
|
||||
if ! assertTrue "recovery failed: $KEY2" $?; then return 1; fi
|
||||
if [ -n "$DEBUG" ]; then echo $KEY2; echo; fi
|
||||
|
||||
# make sure it looks the same
|
||||
NAME2=$(echo $KEY2 | jq .name | tr -d \")
|
||||
ADDR2=$(echo $KEY2 | jq .address | tr -d \")
|
||||
PUBKEY2=$(echo $KEY2 | jq .pubkey | tr -d \")
|
||||
assertEquals "line ${LINENO}: wrong username" "$USER2" "$NAME2"
|
||||
assertEquals "line ${LINENO}: address doesn't match" "$ADDR" "$ADDR2"
|
||||
assertEquals "line ${LINENO}: pubkey doesn't match" "$PUBKEY" "$PUBKEY2"
|
||||
|
||||
# and we can find the info
|
||||
assertTrue "line ${LINENO}" "${EXE} keys get $USER2 > /dev/null"
|
||||
}
|
||||
|
||||
# try recovery with secp256k1 keys
|
||||
test03recoverSecp() {
|
||||
USER=dings
|
||||
PASS1=Sbub-U9byS7hso
|
||||
|
||||
USER2=booms
|
||||
PASS2=1234567890
|
||||
|
||||
KEY=$(echo $PASS1 | ${EXE} keys new $USER -o json -t secp256k1)
|
||||
if ! assertTrue "created $USER" $?; then return 1; fi
|
||||
if [ -n "$DEBUG" ]; then echo $KEY; echo; fi
|
||||
|
||||
SEED=$(echo $KEY | jq .seed | tr -d \")
|
||||
ADDR=$(echo $KEY | jq .key.address | tr -d \")
|
||||
PUBKEY=$(echo $KEY | jq .key.pubkey | tr -d \")
|
||||
assertTrue "line ${LINENO}" "${EXE} keys get $USER > /dev/null"
|
||||
|
||||
# now we got it
|
||||
KEY2=$((echo $PASS2; echo $SEED) | ${EXE} keys recover $USER2 -o json)
|
||||
if ! assertTrue "recovery failed: $KEY2" $?; then return 1; fi
|
||||
if [ -n "$DEBUG" ]; then echo $KEY2; echo; fi
|
||||
|
||||
# make sure it looks the same
|
||||
NAME2=$(echo $KEY2 | jq .name | tr -d \")
|
||||
ADDR2=$(echo $KEY2 | jq .address | tr -d \")
|
||||
PUBKEY2=$(echo $KEY2 | jq .pubkey | tr -d \")
|
||||
assertEquals "line ${LINENO}: wrong username" "$USER2" "$NAME2"
|
||||
assertEquals "line ${LINENO}: address doesn't match" "$ADDR" "$ADDR2"
|
||||
assertEquals "line ${LINENO}: pubkey doesn't match" "$PUBKEY" "$PUBKEY2"
|
||||
|
||||
# and we can find the info
|
||||
assertTrue "line ${LINENO}" "${EXE} keys get $USER2 > /dev/null"
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,161 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# These global variables are required for common.sh
|
||||
SERVER_EXE=basecoin
|
||||
CLIENT_EXE=basecli
|
||||
ACCOUNTS=(jae ethan bucky rigel igor)
|
||||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[4]}
|
||||
|
||||
BPORT=7000
|
||||
URL="localhost:${BPORT}"
|
||||
|
||||
oneTimeSetUp() {
|
||||
if ! quickSetup .basecoin_test_rest rest-chain; then
|
||||
exit 1;
|
||||
fi
|
||||
baseserver serve --port $BPORT >/dev/null &
|
||||
sleep 0.1 # for startup
|
||||
PID_PROXY=$!
|
||||
disown
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
quickTearDown
|
||||
kill -9 $PID_PROXY
|
||||
}
|
||||
|
||||
# XXX Ex Usage: restAddr $NAME
|
||||
# Desc: Gets the address for a key name via rest
|
||||
restAddr() {
|
||||
assertNotNull "line=${LINENO}, keyname required" "$1"
|
||||
ADDR=$(curl ${URL}/keys/${1} 2>/dev/null | jq .address | tr -d \")
|
||||
assertNotEquals "line=${LINENO}, null key" "null" "$ADDR"
|
||||
assertNotEquals "line=${LINENO}, no key" "" "$ADDR"
|
||||
echo $ADDR
|
||||
}
|
||||
|
||||
# XXX Ex Usage: restAccount $ADDR $AMOUNT [$HEIGHT]
|
||||
# Desc: Assumes just one coin, checks the balance of first coin in any case
|
||||
restAccount() {
|
||||
assertNotNull "line=${LINENO}, address required" "$1"
|
||||
QUERY=${URL}/query/account/sigs:$1
|
||||
if [ -n "$3" ]; then
|
||||
QUERY="${QUERY}?height=${3}"
|
||||
fi
|
||||
ACCT=$(curl ${QUERY} 2>/dev/null)
|
||||
if [ -n "$DEBUG" ]; then echo $QUERY; echo $ACCT; echo; fi
|
||||
assertEquals "line=${LINENO}, proper money" "$2" $(echo $ACCT | jq .data.coins[0].amount)
|
||||
return $?
|
||||
}
|
||||
|
||||
restNoAccount() {
|
||||
ERROR=$(curl ${URL}/query/account/sigs:$1 2>/dev/null)
|
||||
assertEquals "line=${LINENO}, should error" 400 $(echo $ERROR | jq .code)
|
||||
}
|
||||
|
||||
test00GetAccount() {
|
||||
RECV=$(restAddr $POOR)
|
||||
SENDER=$(restAddr $RICH)
|
||||
|
||||
restNoAccount $RECV
|
||||
restAccount $SENDER "9007199254740992"
|
||||
}
|
||||
|
||||
test01SendTx() {
|
||||
SENDER=$(restAddr $RICH)
|
||||
RECV=$(restAddr $POOR)
|
||||
|
||||
CMD="{\"from\": {\"app\": \"sigs\", \"addr\": \"$SENDER\"}, \"to\": {\"app\": \"sigs\", \"addr\": \"$RECV\"}, \"amount\": [{\"denom\": \"mycoin\", \"amount\": 992}], \"sequence\": 1}"
|
||||
|
||||
UNSIGNED=$(curl -XPOST ${URL}/build/send -d "$CMD" 2>/dev/null)
|
||||
if [ -n "$DEBUG" ]; then echo $UNSIGNED; echo; fi
|
||||
|
||||
TOSIGN="{\"name\": \"$RICH\", \"password\": \"qwertyuiop\", \"tx\": $UNSIGNED}"
|
||||
SIGNED=$(curl -XPOST ${URL}/sign -d "$TOSIGN" 2>/dev/null)
|
||||
TX=$(curl -XPOST ${URL}/tx -d "$SIGNED" 2>/dev/null)
|
||||
if [ -n "$DEBUG" ]; then echo $TX; echo; fi
|
||||
|
||||
txSucceeded $? "$TX" "$RECV"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
restAccount $SENDER "9007199254740000" "$TX_HEIGHT"
|
||||
restAccount $RECV "992" "$TX_HEIGHT"
|
||||
|
||||
# Make sure tx is indexed
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "992"
|
||||
}
|
||||
|
||||
|
||||
# XXX Ex Usage: restCreateRole $PAYLOAD $EXPECTED
|
||||
# Desc: Tests that the first returned signer.addr matches the expected
|
||||
restCreateRole() {
|
||||
assertNotNull "line=${LINENO}, data required" "$1"
|
||||
ROLE=$(curl ${URL}/build/create_role --data "$1" 2>/dev/null)
|
||||
if [ -n "$DEBUG" ]; then echo -e "$ROLE\n"; fi
|
||||
assertEquals "line=${LINENO}, role required" "$2" $(echo $ROLE | jq .data.tx.data.signers[0].addr)
|
||||
return $?
|
||||
}
|
||||
|
||||
test03CreateRole() {
|
||||
DATA="{\"role\": \"726f6c65\", \"seq\": 1, \"min_sigs\": 1, \"signers\": [{\"addr\": \"4FF759D47C81754D8F553DCCAC8651D0AF74C7F9\", \"app\": \"role\"}]}"
|
||||
restCreateRole "$DATA" \""4FF759D47C81754D8F553DCCAC8651D0AF74C7F9"\"
|
||||
}
|
||||
|
||||
test04CreateRoleInvalid() {
|
||||
ERROR=$(curl ${URL}/build/create_role --data '{}' 2>/dev/null)
|
||||
assertEquals "line=${LINENO}, should report validation failed" 0 $(echo $ERROR | grep "failed" > /dev/null && echo 0 || echo 1)
|
||||
|
||||
ERROR=$(curl ${URL}/build/create_role --data '{"role": "foo"}' 2>/dev/null)
|
||||
assertEquals "line=${LINENO}, should report validation failed" 0 $(echo $ERROR | grep "failed" > /dev/null && echo 0 || echo 1)
|
||||
|
||||
ERROR=$(curl ${URL}/build/create_role --data '{"min_sigs": 2, "role": "abcdef"}' 2>/dev/null)
|
||||
assertEquals "line=${LINENO}, should report validation failed" 0 $(echo $ERROR | grep "failed" > /dev/null && echo 0 || echo 1)
|
||||
|
||||
## Non-hex roles should be rejected
|
||||
ERROR=$(curl ${URL}/build/create_role --data "{\"role\": \"foobar\", \"seq\": 2, \"signers\": [{\"addr\": \"4FF759D47C81754D8F553DCCAC8651D0AF74C7F9\", \"app\": \"role\"}], \"min_sigs\": 1}" 2>/dev/null)
|
||||
assertEquals "line=${LINENO}, should report validation failed" 0 $(echo $ERROR | grep "invalid hex" > /dev/null && echo 0 || echo 1)
|
||||
}
|
||||
|
||||
|
||||
# test02SendTxWithFee() {
|
||||
# SENDER=$(getAddr $RICH)
|
||||
# RECV=$(getAddr $POOR)
|
||||
|
||||
# # Test to see if the auto-sequencing works, the sequence here should be calculated to be 2
|
||||
# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --to=$RECV --name=$RICH)
|
||||
# txSucceeded $? "$TX" "$RECV"
|
||||
# HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
# TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# # deduct 100 from sender, add 90 to receiver... fees "vanish"
|
||||
# checkAccount $SENDER "9007199254739900" "$TX_HEIGHT"
|
||||
# checkAccount $RECV "1082" "$TX_HEIGHT"
|
||||
|
||||
# # Make sure tx is indexed
|
||||
# checkSendFeeTx $HASH $TX_HEIGHT $SENDER "90" "10"
|
||||
|
||||
# # assert replay protection
|
||||
# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=2 --to=$RECV --name=$RICH 2>/dev/null)
|
||||
# assertFalse "line=${LINENO}, replay: $TX" $?
|
||||
# checkAccount $SENDER "9007199254739900" "$TX_HEIGHT"
|
||||
# checkAccount $RECV "1082" "$TX_HEIGHT"
|
||||
|
||||
# # make sure we can query the proper nonce
|
||||
# NONCE=$(${CLIENT_EXE} query nonce $SENDER)
|
||||
# if [ -n "$DEBUG" ]; then echo $NONCE; echo; fi
|
||||
# # TODO: note that cobra returns error code 0 on parse failure,
|
||||
# # so currently this check passes even if there is no nonce query command
|
||||
# if assertTrue "line=${LINENO}, no nonce query" $?; then
|
||||
# assertEquals "line=${LINENO}, proper nonce" "2" $(echo $NONCE | jq .data)
|
||||
# fi
|
||||
# }
|
||||
|
||||
|
||||
# Load common then run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/common.sh
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,85 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# these are two globals to control all scripts (can use eg. counter instead)
|
||||
SERVER_EXE=basecoin
|
||||
CLIENT_EXE=basecli
|
||||
ACCOUNTS=(jae ethan bucky rigel igor)
|
||||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[4]}
|
||||
|
||||
oneTimeSetUp() {
|
||||
if ! quickSetup .basecoin_test_restart restart-chain; then
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
quickTearDown
|
||||
}
|
||||
|
||||
test00PreRestart() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH)
|
||||
txSucceeded $? "$TX" "$RECV"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkAccount $SENDER "9007199254740000" "$TX_HEIGHT"
|
||||
checkAccount $RECV "992" "$TX_HEIGHT"
|
||||
|
||||
# make sure tx is indexed
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "992"
|
||||
|
||||
}
|
||||
|
||||
test01OnRestart() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=10000mycoin --sequence=2 --to=$RECV --name=$RICH)
|
||||
txSucceeded $? "$TX" "$RECV"
|
||||
if [ $? != 0 ]; then echo "can't make tx!"; return 1; fi
|
||||
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# wait til we have quite a few blocks... like at least 20,
|
||||
# so the query command won't just wait for the next eg. 7 blocks to verify the result
|
||||
echo "waiting to generate lots of blocks..."
|
||||
sleep 5
|
||||
echo "done waiting!"
|
||||
|
||||
# last minute tx just at the block cut-off...
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20000mycoin --sequence=3 --to=$RECV --name=$RICH)
|
||||
txSucceeded $? "$TX" "$RECV"
|
||||
if [ $? != 0 ]; then echo "can't make second tx!"; return 1; fi
|
||||
|
||||
# now we do a restart...
|
||||
quickTearDown
|
||||
startServer $BASE_DIR/server $BASE_DIR/${SERVER_EXE}.log
|
||||
if [ $? != 0 ]; then echo "can't restart server!"; return 1; fi
|
||||
|
||||
# make sure queries still work properly, with all 3 tx now executed
|
||||
echo "Checking state after restart..."
|
||||
checkAccount $SENDER "9007199254710000"
|
||||
checkAccount $RECV "30992"
|
||||
|
||||
# make sure tx is indexed
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "10000"
|
||||
|
||||
# for double-check of logs
|
||||
if [ -n "$DEBUG" ]; then
|
||||
cat $BASE_DIR/${SERVER_EXE}.log;
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Load common then run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/common.sh
|
||||
. $CLI_DIR/shunit2
|
||||
|
||||
@ -1,97 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# These global variables are required for common.sh
|
||||
SERVER_EXE=basecoin
|
||||
CLIENT_EXE=basecli
|
||||
ACCOUNTS=(jae ethan bucky rigel igor)
|
||||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[4]}
|
||||
DUDE=${ACCOUNTS[2]}
|
||||
ROLE="10CAFE4E"
|
||||
|
||||
oneTimeSetUp() {
|
||||
if ! quickSetup .basecoin_test_roles roles-chain; then
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
quickTearDown
|
||||
}
|
||||
|
||||
test01SetupRole() {
|
||||
ONE=$(getAddr $RICH)
|
||||
TWO=$(getAddr $POOR)
|
||||
THREE=$(getAddr $DUDE)
|
||||
MEMBERS=${ONE},${TWO},${THREE}
|
||||
|
||||
SIGS=2
|
||||
|
||||
assertFalse "line=${LINENO}, missing min-sigs" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=${ROLE} --members=${MEMBERS} --sequence=1 --name=$RICH"
|
||||
assertFalse "line=${LINENO}, missing members" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=${ROLE} --min-sigs=2 --sequence=1 --name=$RICH"
|
||||
assertFalse "line=${LINENO}, missing role" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --min-sigs=2 --members=${MEMBERS} --sequence=1 --name=$RICH"
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=${ROLE} --min-sigs=$SIGS --members=${MEMBERS} --sequence=1 --name=$RICH)
|
||||
txSucceeded $? "$TX" "${ROLE}"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkRole "${ROLE}" $SIGS 3 "$TX_HEIGHT"
|
||||
|
||||
# Make sure tx is indexed
|
||||
checkRoleTx $HASH $TX_HEIGHT "${ROLE}" 3
|
||||
}
|
||||
|
||||
test02SendTxToRole() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=role:${ROLE}
|
||||
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --fee=90mycoin --amount=10000mycoin --to=$RECV --sequence=2 --name=$RICH)
|
||||
txSucceeded $? "$TX" "${ROLE}"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# reduce by 10090
|
||||
checkAccount $SENDER "9007199254730902" "$TX_HEIGHT"
|
||||
checkAccount $RECV "10000" "$TX_HEIGHT"
|
||||
|
||||
checkSendFeeTx $HASH $TX_HEIGHT $SENDER "10000" "90"
|
||||
}
|
||||
|
||||
test03SendMultiFromRole() {
|
||||
ONE=$(getAddr $RICH)
|
||||
TWO=$(getAddr $POOR)
|
||||
THREE=$(getAddr $DUDE)
|
||||
BANK=role:${ROLE}
|
||||
|
||||
# no money to start mr. poor...
|
||||
assertFalse "line=${LINENO}, has no money yet" "${CLIENT_EXE} query account $TWO 2>/dev/null"
|
||||
|
||||
# let's try to send money from the role directly without multisig
|
||||
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --name=$POOR 2>/dev/null)
|
||||
assertFalse "need to assume role" $?
|
||||
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=2 --assume-role=${ROLE} --name=$POOR 2>/dev/null)
|
||||
assertFalse "need two signatures" $?
|
||||
|
||||
# okay, begin a multisig transaction mr. poor...
|
||||
TX_FILE=$BASE_DIR/tx.json
|
||||
echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=${ROLE} --name=$POOR --multi --prepare=$TX_FILE
|
||||
assertTrue "line=${LINENO}, successfully prepare tx" $?
|
||||
# and get some dude to sign it
|
||||
# FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$POOR 2>/dev/null)
|
||||
# assertFalse "line=${LINENO}, double signing doesn't get bank" $?
|
||||
# and get some dude to sign it for the full access
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$DUDE)
|
||||
txSucceeded $? "$TX" "multi-bank"
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkAccount $TWO "6000" "$TX_HEIGHT"
|
||||
checkAccount $BANK "4000" "$TX_HEIGHT"
|
||||
}
|
||||
|
||||
|
||||
# Load common then run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/common.sh
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,132 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
CLIENT_EXE=basecli
|
||||
SERVER_EXE=basecoin
|
||||
|
||||
oneTimeSetUp() {
|
||||
BASE=~/.bc_init_test
|
||||
rm -rf "$BASE"
|
||||
mkdir -p "$BASE"
|
||||
|
||||
SERVER="${BASE}/server"
|
||||
SERVER_LOG="${BASE}/${SERVER_EXE}.log"
|
||||
|
||||
HEX="deadbeef1234deadbeef1234deadbeef1234aaaa"
|
||||
${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG"
|
||||
if ! assertTrue "line=${LINENO}" $?; then return 1; fi
|
||||
|
||||
GENESIS_FILE=${SERVER}/genesis.json
|
||||
CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \")
|
||||
|
||||
printf "starting ${SERVER_EXE}...\n"
|
||||
${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 &
|
||||
sleep 5
|
||||
PID_SERVER=$!
|
||||
disown
|
||||
if ! ps $PID_SERVER >/dev/null; then
|
||||
echo "**STARTUP FAILED**"
|
||||
cat $SERVER_LOG
|
||||
return 1
|
||||
fi
|
||||
|
||||
# this sets the base for all client queries in the tests
|
||||
export BCHOME=${BASE}/client
|
||||
${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1
|
||||
if ! assertTrue "line=${LINENO}, initialized light-client" "$?"; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
printf "\nstopping ${SERVER_EXE}..."
|
||||
kill -9 $PID_SERVER >/dev/null 2>&1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
test01GetInsecure() {
|
||||
GENESIS=$(${CLIENT_EXE} rpc genesis)
|
||||
assertTrue "line=${LINENO}, get genesis" "$?"
|
||||
MYCHAIN=$(echo ${GENESIS} | jq .genesis.chain_id | tr -d \")
|
||||
assertEquals "line=${LINENO}, genesis chain matches" "${CHAIN_ID}" "${MYCHAIN}"
|
||||
|
||||
STATUS=$(${CLIENT_EXE} rpc status)
|
||||
assertTrue "line=${LINENO}, get status" "$?"
|
||||
SHEIGHT=$(echo ${STATUS} | jq .latest_block_height)
|
||||
assertTrue "line=${LINENO}, parsed status" "$?"
|
||||
assertNotNull "line=${LINENO}, has a height" "${SHEIGHT}"
|
||||
|
||||
VALS=$(${CLIENT_EXE} rpc validators)
|
||||
assertTrue "line=${LINENO}, get validators" "$?"
|
||||
VHEIGHT=$(echo ${VALS} | jq .block_height)
|
||||
assertTrue "line=${LINENO}, parsed validators" "$?"
|
||||
assertTrue "line=${LINENO}, sensible heights: $SHEIGHT / $VHEIGHT" "test $VHEIGHT -ge $SHEIGHT"
|
||||
VCNT=$(echo ${VALS} | jq '.validators | length')
|
||||
assertEquals "line=${LINENO}, one validator" "1" "$VCNT"
|
||||
|
||||
INFO=$(${CLIENT_EXE} rpc info)
|
||||
assertTrue "line=${LINENO}, get info" "$?"
|
||||
DATA=$(echo $INFO | jq .response.data)
|
||||
assertEquals "line=${LINENO}, basecoin info" '"basecoin v0.7.1"' "$DATA"
|
||||
}
|
||||
|
||||
test02GetSecure() {
|
||||
HEIGHT=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
|
||||
assertTrue "line=${LINENO}, get status" "$?"
|
||||
|
||||
# check block produces something reasonable
|
||||
assertFalse "line=${LINENO}, missing height" "${CLIENT_EXE} rpc block"
|
||||
BLOCK=$(${CLIENT_EXE} rpc block --height=$HEIGHT)
|
||||
assertTrue "line=${LINENO}, get block" "$?"
|
||||
MHEIGHT=$(echo $BLOCK | jq .block_meta.header.height)
|
||||
assertEquals "line=${LINENO}, meta height" "${HEIGHT}" "${MHEIGHT}"
|
||||
BHEIGHT=$(echo $BLOCK | jq .block.header.height)
|
||||
assertEquals "line=${LINENO}, meta height" "${HEIGHT}" "${BHEIGHT}"
|
||||
|
||||
# check commit produces something reasonable
|
||||
assertFalse "line=${LINENO}, missing height" "${CLIENT_EXE} rpc commit"
|
||||
let "CHEIGHT = $HEIGHT - 1"
|
||||
COMMIT=$(${CLIENT_EXE} rpc commit --height=$CHEIGHT)
|
||||
assertTrue "line=${LINENO}, get commit" "$?"
|
||||
HHEIGHT=$(echo $COMMIT | jq .header.height)
|
||||
assertEquals "line=${LINENO}, commit height" "${CHEIGHT}" "${HHEIGHT}"
|
||||
assertEquals "line=${LINENO}, canonical" "true" $(echo $COMMIT | jq .canonical)
|
||||
BSIG=$(echo $BLOCK | jq .block.last_commit)
|
||||
CSIG=$(echo $COMMIT | jq .commit)
|
||||
assertEquals "line=${LINENO}, block and commit" "$BSIG" "$CSIG"
|
||||
|
||||
# now let's get some headers
|
||||
# assertFalse "missing height" "${CLIENT_EXE} rpc headers"
|
||||
HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT)
|
||||
assertTrue "line=${LINENO}, get headers" "$?"
|
||||
assertEquals "line=${LINENO}, proper height" "$HEIGHT" $(echo $HEADERS | jq '.block_metas[0].header.height')
|
||||
assertEquals "line=${LINENO}, two headers" "2" $(echo $HEADERS | jq '.block_metas | length')
|
||||
# should we check these headers?
|
||||
CHEAD=$(echo $COMMIT | jq .header)
|
||||
# most recent first, so the commit header is second....
|
||||
HHEAD=$(echo $HEADERS | jq .block_metas[1].header)
|
||||
assertEquals "line=${LINENO}, commit and header" "$CHEAD" "$HHEAD"
|
||||
}
|
||||
|
||||
test03Waiting() {
|
||||
START=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
|
||||
assertTrue "line=${LINENO}, get status" "$?"
|
||||
|
||||
let "NEXT = $START + 5"
|
||||
assertFalse "line=${LINENO}, no args" "${CLIENT_EXE} rpc wait"
|
||||
assertFalse "line=${LINENO}, too long" "${CLIENT_EXE} rpc wait --height=1234"
|
||||
assertTrue "line=${LINENO}, normal wait" "${CLIENT_EXE} rpc wait --height=$NEXT"
|
||||
|
||||
STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
|
||||
assertEquals "line=${LINENO}, wait until height" "$NEXT" "$STEP"
|
||||
|
||||
let "NEXT = $STEP + 3"
|
||||
assertTrue "line=${LINENO}, ${CLIENT_EXE} rpc wait --delta=3"
|
||||
STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
|
||||
assertEquals "line=${LINENO}, wait for delta" "$NEXT" "$STEP"
|
||||
}
|
||||
|
||||
# load and run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,14 +0,0 @@
|
||||
LINKER_FLAGS:="-X github.com/cosmos/cosmos-sdk/client/commands.CommitHash=`git rev-parse --short HEAD`"
|
||||
|
||||
install:
|
||||
@go install -ldflags $(LINKER_FLAGS) ./cmd/...
|
||||
|
||||
test: test_unit test_cli
|
||||
|
||||
test_unit:
|
||||
@go test `glide novendor`
|
||||
|
||||
test_cli:
|
||||
./tests/cli/counter.sh
|
||||
|
||||
.PHONY: install test test_unit test_cli
|
||||
@ -1,36 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
client "github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/examples/counter/plugins/counter"
|
||||
"github.com/cosmos/cosmos-sdk/server/commands"
|
||||
)
|
||||
|
||||
// RootCmd is the entry point for this binary
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "counter",
|
||||
Short: "demo application for cosmos sdk",
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// TODO: register the counter here
|
||||
commands.Handler = counter.NewHandler("mycoin")
|
||||
|
||||
RootCmd.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.StartCmd,
|
||||
commands.UnsafeResetAllCmd,
|
||||
client.VersionCmd,
|
||||
)
|
||||
commands.SetUpRoot(RootCmd)
|
||||
|
||||
cmd := cli.PrepareMainCmd(RootCmd, "CT", os.ExpandEnv("$HOME/.counter"))
|
||||
cmd.Execute()
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
|
||||
"github.com/cosmos/cosmos-sdk/examples/counter/plugins/counter"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
)
|
||||
|
||||
//CounterTxCmd is the CLI command to execute the counter
|
||||
// through the appTx Command
|
||||
var CounterTxCmd = &cobra.Command{
|
||||
Use: "counter",
|
||||
Short: "add a vote to the counter",
|
||||
Long: `Add a vote to the counter.
|
||||
|
||||
You must pass --valid for it to count and the countfee will be added to the counter.`,
|
||||
RunE: counterTx,
|
||||
}
|
||||
|
||||
// nolint - flags names
|
||||
const (
|
||||
FlagCountFee = "countfee"
|
||||
FlagValid = "valid"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fs := CounterTxCmd.Flags()
|
||||
fs.String(FlagCountFee, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
|
||||
fs.Bool(FlagValid, false, "Is count valid?")
|
||||
}
|
||||
|
||||
func counterTx(cmd *cobra.Command, args []string) error {
|
||||
tx, err := readCounterTxFlags()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txcmd.DoTx(tx)
|
||||
}
|
||||
|
||||
func readCounterTxFlags() (tx sdk.Tx, err error) {
|
||||
feeCoins, err := coin.ParseCoins(viper.GetString(FlagCountFee))
|
||||
if err != nil {
|
||||
return tx, err
|
||||
}
|
||||
|
||||
tx = counter.NewTx(viper.GetBool(FlagValid), feeCoins)
|
||||
return tx, nil
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/counter/plugins/counter"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
)
|
||||
|
||||
//CounterQueryCmd - CLI command to query the counter state
|
||||
var CounterQueryCmd = &cobra.Command{
|
||||
Use: "counter",
|
||||
Short: "Query counter state, with proof",
|
||||
RunE: counterQueryCmd,
|
||||
}
|
||||
|
||||
func counterQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
var cp counter.State
|
||||
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
key := stack.PrefixedKey(counter.NameCounter, counter.StateKey())
|
||||
h, err := query.GetParsed(key, &cp, query.GetHeight(), prove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return query.OutputProof(cp, h)
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/commits"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/proxy"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
|
||||
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
|
||||
bcount "github.com/cosmos/cosmos-sdk/examples/counter/cmd/countercli/commands"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/modules/auth/commands"
|
||||
basecmd "github.com/cosmos/cosmos-sdk/modules/base/commands"
|
||||
coincmd "github.com/cosmos/cosmos-sdk/modules/coin/commands"
|
||||
feecmd "github.com/cosmos/cosmos-sdk/modules/fee/commands"
|
||||
noncecmd "github.com/cosmos/cosmos-sdk/modules/nonce/commands"
|
||||
)
|
||||
|
||||
// CounterCli represents the base command when called without any subcommands
|
||||
var CounterCli = &cobra.Command{
|
||||
Use: "countercli",
|
||||
Short: "Example app built using the Cosmos SDK",
|
||||
Long: `Countercli is a demo app that includes custom logic to
|
||||
present a formatted interface to a custom blockchain structure.
|
||||
|
||||
This is a useful tool and also serves to demonstrate how to configure
|
||||
the Cosmos SDK to work for any custom ABCI app, see:
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
func main() {
|
||||
commands.AddBasicFlags(CounterCli)
|
||||
|
||||
// Prepare queries
|
||||
query.RootCmd.AddCommand(
|
||||
// These are default parsers, optional in your app
|
||||
query.TxQueryCmd,
|
||||
query.KeyQueryCmd,
|
||||
coincmd.AccountQueryCmd,
|
||||
noncecmd.NonceQueryCmd,
|
||||
|
||||
// XXX IMPORTANT: here is how you add custom query commands in your app
|
||||
bcount.CounterQueryCmd,
|
||||
)
|
||||
|
||||
// set up the middleware
|
||||
txcmd.Middleware = txcmd.Wrappers{
|
||||
feecmd.FeeWrapper{},
|
||||
noncecmd.NonceWrapper{},
|
||||
basecmd.ChainWrapper{},
|
||||
authcmd.SigWrapper{},
|
||||
}
|
||||
txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags())
|
||||
|
||||
// Prepare transactions
|
||||
txcmd.RootCmd.AddCommand(
|
||||
// This is the default transaction, optional in your app
|
||||
coincmd.SendTxCmd,
|
||||
|
||||
// XXX IMPORTANT: here is how you add custom tx construction for your app
|
||||
bcount.CounterTxCmd,
|
||||
)
|
||||
|
||||
// Set up the various commands to use
|
||||
CounterCli.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.ResetCmd,
|
||||
commands.VersionCmd,
|
||||
keys.RootCmd,
|
||||
commits.RootCmd,
|
||||
query.RootCmd,
|
||||
txcmd.RootCmd,
|
||||
proxy.RootCmd,
|
||||
)
|
||||
|
||||
cmd := cli.PrepareMainCmd(CounterCli, "CTL", os.ExpandEnv("$HOME/.countercli"))
|
||||
cmd.Execute()
|
||||
}
|
||||
@ -1,226 +0,0 @@
|
||||
package counter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
"github.com/cosmos/cosmos-sdk/modules/base"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/cosmos/cosmos-sdk/modules/fee"
|
||||
"github.com/cosmos/cosmos-sdk/modules/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/modules/nonce"
|
||||
"github.com/cosmos/cosmos-sdk/modules/roles"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
// Tx
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// register the tx type with it's validation logic
|
||||
// make sure to use the name of the handler as the prefix in the tx type,
|
||||
// so it gets routed properly
|
||||
const (
|
||||
NameCounter = "cntr"
|
||||
ByteTx = 0x2F //TODO What does this byte represent should use typebytes probably
|
||||
TypeTx = NameCounter + "/count"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sdk.TxMapper.RegisterImplementation(Tx{}, TypeTx, ByteTx)
|
||||
}
|
||||
|
||||
// Tx - struct for all counter transactions
|
||||
type Tx struct {
|
||||
Valid bool `json:"valid"`
|
||||
Fee coin.Coins `json:"fee"`
|
||||
}
|
||||
|
||||
// NewTx - return a new counter transaction struct wrapped as a basecoin transaction
|
||||
func NewTx(valid bool, fee coin.Coins) sdk.Tx {
|
||||
return Tx{
|
||||
Valid: valid,
|
||||
Fee: fee,
|
||||
}.Wrap()
|
||||
}
|
||||
|
||||
// Wrap - Wrap a Tx as a Basecoin Tx, used to satisfy the XXX interface
|
||||
func (c Tx) Wrap() sdk.Tx {
|
||||
return sdk.Tx{TxInner: c}
|
||||
}
|
||||
|
||||
// ValidateBasic just makes sure the Fee is a valid, non-negative value
|
||||
func (c Tx) ValidateBasic() error {
|
||||
if !c.Fee.IsValid() {
|
||||
return coin.ErrInvalidCoins()
|
||||
}
|
||||
if !c.Fee.IsNonnegative() {
|
||||
return coin.ErrInvalidCoins()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Custom errors
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
errInvalidCounter = fmt.Errorf("Counter Tx marked invalid")
|
||||
)
|
||||
|
||||
// ErrInvalidCounter - custom error class
|
||||
func ErrInvalidCounter() error {
|
||||
return errors.WithCode(errInvalidCounter, abci.CodeType_BaseInvalidInput)
|
||||
}
|
||||
|
||||
// IsInvalidCounterErr - custom error class check
|
||||
func IsInvalidCounterErr(err error) bool {
|
||||
return errors.IsSameError(errInvalidCounter, err)
|
||||
}
|
||||
|
||||
// ErrDecoding - This is just a helper function to return a generic "internal error"
|
||||
func ErrDecoding() error {
|
||||
return errors.ErrInternal("Error decoding state")
|
||||
}
|
||||
|
||||
// Counter Handler
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// NewHandler returns a new counter transaction processing handler
|
||||
func NewHandler(feeDenom string) sdk.Handler {
|
||||
return stack.New(
|
||||
base.Logger{},
|
||||
stack.Recovery{},
|
||||
auth.Signatures{},
|
||||
base.Chain{},
|
||||
stack.Checkpoint{OnCheck: true},
|
||||
nonce.ReplayCheck{},
|
||||
).
|
||||
IBC(ibc.NewMiddleware()).
|
||||
Apps(
|
||||
roles.NewMiddleware(),
|
||||
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||
stack.Checkpoint{OnDeliver: true},
|
||||
).
|
||||
Dispatch(
|
||||
coin.NewHandler(),
|
||||
Handler{},
|
||||
)
|
||||
}
|
||||
|
||||
// Handler the counter transaction processing handler
|
||||
type Handler struct {
|
||||
stack.PassInitState
|
||||
stack.PassInitValidate
|
||||
}
|
||||
|
||||
var _ stack.Dispatchable = Handler{}
|
||||
|
||||
// Name - return counter namespace
|
||||
func (Handler) Name() string {
|
||||
return NameCounter
|
||||
}
|
||||
|
||||
// AssertDispatcher - placeholder to satisfy XXX
|
||||
func (Handler) AssertDispatcher() {}
|
||||
|
||||
// CheckTx checks if the tx is properly structured
|
||||
func (h Handler) CheckTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
_, err = checkTx(ctx, tx)
|
||||
return
|
||||
}
|
||||
|
||||
// DeliverTx executes the tx if valid
|
||||
func (h Handler) DeliverTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, dispatch sdk.Deliver) (res sdk.DeliverResult, err error) {
|
||||
ctr, err := checkTx(ctx, tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
// note that we don't assert this on CheckTx (ValidateBasic),
|
||||
// as we allow them to be writen to the chain
|
||||
if !ctr.Valid {
|
||||
return res, ErrInvalidCounter()
|
||||
}
|
||||
|
||||
// handle coin movement.... like, actually decrement the other account
|
||||
if !ctr.Fee.IsZero() {
|
||||
// take the coins and put them in out account!
|
||||
senders := ctx.GetPermissions("", auth.NameSigs)
|
||||
if len(senders) == 0 {
|
||||
return res, errors.ErrMissingSignature()
|
||||
}
|
||||
in := []coin.TxInput{{Address: senders[0], Coins: ctr.Fee}}
|
||||
out := []coin.TxOutput{{Address: StoreActor(), Coins: ctr.Fee}}
|
||||
send := coin.NewSendTx(in, out)
|
||||
// if the deduction fails (too high), abort the command
|
||||
_, err = dispatch.DeliverTx(ctx, store, send)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
// update the counter
|
||||
state, err := LoadState(store)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
state.Counter++
|
||||
state.TotalFees = state.TotalFees.Plus(ctr.Fee)
|
||||
err = SaveState(store, state)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func checkTx(ctx sdk.Context, tx sdk.Tx) (ctr Tx, err error) {
|
||||
ctr, ok := tx.Unwrap().(Tx)
|
||||
if !ok {
|
||||
return ctr, errors.ErrInvalidFormat(TypeTx, tx)
|
||||
}
|
||||
err = ctr.ValidateBasic()
|
||||
if err != nil {
|
||||
return ctr, err
|
||||
}
|
||||
return ctr, nil
|
||||
}
|
||||
|
||||
// CounterStore
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// StoreActor - return the basecoin actor for the account
|
||||
func StoreActor() sdk.Actor {
|
||||
return sdk.Actor{App: NameCounter, Address: []byte{0x04, 0x20}} //XXX what do these bytes represent? - should use typebyte variables
|
||||
}
|
||||
|
||||
// State - state of the counter applicaton
|
||||
type State struct {
|
||||
Counter int `json:"counter"`
|
||||
TotalFees coin.Coins `json:"total_fees"`
|
||||
}
|
||||
|
||||
// StateKey - store key for the counter state
|
||||
func StateKey() []byte {
|
||||
return []byte("state")
|
||||
}
|
||||
|
||||
// LoadState - retrieve the counter state from the store
|
||||
func LoadState(store state.SimpleDB) (state State, err error) {
|
||||
bytes := store.Get(StateKey())
|
||||
if len(bytes) > 0 {
|
||||
err = wire.ReadBinaryBytes(bytes, &state)
|
||||
if err != nil {
|
||||
return state, errors.ErrDecoding()
|
||||
}
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// SaveState - save the counter state to the provided store
|
||||
func SaveState(store state.SimpleDB, state State) error {
|
||||
bytes := wire.BinaryBytes(state)
|
||||
store.Set(StateKey(), bytes)
|
||||
return nil
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
package counter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/app"
|
||||
"github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
"github.com/cosmos/cosmos-sdk/modules/base"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/cosmos/cosmos-sdk/modules/nonce"
|
||||
)
|
||||
|
||||
func TestCounterPlugin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// Basecoin initialization
|
||||
chainID := "test_chain_id"
|
||||
logger := log.TestingLogger()
|
||||
// logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout))
|
||||
|
||||
h := NewHandler("gold")
|
||||
store, err := app.MockStoreApp("counter", logger)
|
||||
require.Nil(err, "%+v", err)
|
||||
bcApp := app.NewBaseApp(store, h, nil)
|
||||
err = bcApp.InitState("base", "chain_id", chainID)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// Account initialization
|
||||
bal := coin.Coins{{"", 1000}, {"gold", 1000}}
|
||||
acct := coin.NewAccountWithKey(bal)
|
||||
err = bcApp.InitState("coin", "account", acct.MakeOption())
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// Deliver a CounterTx
|
||||
DeliverCounterTx := func(valid bool, counterFee coin.Coins, sequence uint32) abci.Result {
|
||||
tx := NewTx(valid, counterFee)
|
||||
tx = nonce.NewTx(sequence, []sdk.Actor{acct.Actor()}, tx)
|
||||
tx = base.NewChainTx(chainID, 0, tx)
|
||||
stx := auth.NewSig(tx)
|
||||
auth.Sign(stx, acct.Key)
|
||||
txBytes := wire.BinaryBytes(stx.Wrap())
|
||||
return bcApp.DeliverTx(txBytes)
|
||||
}
|
||||
|
||||
// Test a basic send, no fee
|
||||
res := DeliverCounterTx(true, nil, 1)
|
||||
assert.True(res.IsOK(), res.String())
|
||||
|
||||
// Test an invalid send, no fee
|
||||
res = DeliverCounterTx(false, nil, 2)
|
||||
assert.True(res.IsErr(), res.String())
|
||||
|
||||
// Test an invalid sequence
|
||||
res = DeliverCounterTx(true, nil, 2)
|
||||
assert.True(res.IsErr(), res.String())
|
||||
|
||||
// Test an valid send, with supported fee
|
||||
res = DeliverCounterTx(true, coin.Coins{{"gold", 100}}, 3)
|
||||
assert.True(res.IsOK(), res.String())
|
||||
|
||||
// Test unsupported fee
|
||||
res = DeliverCounterTx(true, coin.Coins{{"silver", 100}}, 4)
|
||||
assert.True(res.IsErr(), res.String())
|
||||
}
|
||||
@ -1,124 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# These global variables are required for common.sh
|
||||
SERVER_EXE=counter
|
||||
CLIENT_EXE=countercli
|
||||
ACCOUNTS=(jae ethan bucky rigel igor)
|
||||
RICH=${ACCOUNTS[0]}
|
||||
POOR=${ACCOUNTS[4]}
|
||||
|
||||
oneTimeSetUp() {
|
||||
if ! quickSetup .basecoin_test_counter counter-chain; then
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
quickTearDown
|
||||
}
|
||||
|
||||
test00GetAccount() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
assertFalse "Line=${LINENO}, requires arg" "${CLIENT_EXE} query account"
|
||||
|
||||
checkAccount $SENDER "9007199254740992"
|
||||
|
||||
ACCT2=$(${CLIENT_EXE} query account $RECV 2>/dev/null)
|
||||
assertFalse "Line=${LINENO}, has no genesis account" $?
|
||||
}
|
||||
|
||||
test01SendTx() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
RECV=$(getAddr $POOR)
|
||||
|
||||
# sequence should work well for first time also
|
||||
assertFalse "Line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992mycoin 2>/dev/null"
|
||||
assertFalse "Line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --to=$RECV --name=$RICH 2>/dev/null"
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --to=$RECV --name=$RICH)
|
||||
txSucceeded $? "$TX" "$RECV"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkAccount $SENDER "9007199254740000" "$TX_HEIGHT"
|
||||
checkAccount $RECV "992" "$TX_HEIGHT"
|
||||
|
||||
# make sure tx is indexed
|
||||
checkSendTx $HASH $TX_HEIGHT $SENDER "992"
|
||||
}
|
||||
|
||||
test02GetCounter() {
|
||||
COUNT=$(${CLIENT_EXE} query counter 2>/dev/null)
|
||||
assertFalse "Line=${LINENO}, no default count" $?
|
||||
}
|
||||
|
||||
# checkCounter $COUNT $BALANCE [$HEIGHT]
|
||||
# Assumes just one coin, checks the balance of first coin in any case
|
||||
# pass optional height to query which block to query
|
||||
checkCounter() {
|
||||
# default height of 0, but accept an argument
|
||||
HEIGHT=${3:-0}
|
||||
|
||||
# make sure sender goes down
|
||||
ACCT=$(${CLIENT_EXE} query counter --height=$HEIGHT)
|
||||
if assertTrue "Line=${LINENO}, count is set" $?; then
|
||||
assertEquals "Line=${LINENO}, proper count" "$1" $(echo $ACCT | jq .data.counter)
|
||||
assertEquals "Line=${LINENO}, proper money" "$2" $(echo $ACCT | jq .data.total_fees[0].amount)
|
||||
fi
|
||||
}
|
||||
|
||||
test03AddCount() {
|
||||
SENDER=$(getAddr $RICH)
|
||||
assertFalse "Line=${LINENO}, bad password" "echo hi | ${CLIENT_EXE} tx counter --countfee=100mycoin --sequence=2 --name=${RICH} 2>/dev/null"
|
||||
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --countfee=10mycoin --sequence=2 --name=${RICH} --valid)
|
||||
txSucceeded $? "$TX" "counter"
|
||||
HASH=$(echo $TX | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# make sure the counter was updated
|
||||
checkCounter "1" "10" "$TX_HEIGHT"
|
||||
|
||||
# make sure the account was debited
|
||||
checkAccount $SENDER "9007199254739990" "$TX_HEIGHT"
|
||||
|
||||
# make sure tx is indexed
|
||||
TX=$(${CLIENT_EXE} query tx $HASH --trace)
|
||||
if assertTrue "Line=${LINENO}, found tx" $?; then
|
||||
assertEquals "Line=${LINENO}, proper height" $TX_HEIGHT $(echo $TX | jq .height)
|
||||
assertEquals "Line=${LINENO}, type=sigs/one" '"sigs/one"' $(echo $TX | jq .data.type)
|
||||
CTX=$(echo $TX | jq .data.data.tx)
|
||||
assertEquals "Line=${LINENO}, type=chain/tx" '"chain/tx"' $(echo $CTX | jq .type)
|
||||
NTX=$(echo $CTX | jq .data.tx)
|
||||
assertEquals "line=${LINENO}, type=nonce" '"nonce"' $(echo $NTX | jq .type)
|
||||
CNTX=$(echo $NTX | jq .data.tx)
|
||||
assertEquals "Line=${LINENO}, type=cntr/count" '"cntr/count"' $(echo $CNTX | jq .type)
|
||||
assertEquals "Line=${LINENO}, proper fee" "10" $(echo $CNTX | jq .data.fee[0].amount)
|
||||
fi
|
||||
|
||||
# test again with fees...
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --countfee=7mycoin --fee=4mycoin --sequence=3 --name=${RICH} --valid)
|
||||
txSucceeded $? "$TX" "counter"
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
# make sure the counter was updated, added 7
|
||||
checkCounter "2" "17" "$TX_HEIGHT"
|
||||
# make sure the account was debited 11
|
||||
checkAccount $SENDER "9007199254739979" "$TX_HEIGHT"
|
||||
|
||||
# make sure we cannot replay the counter, no state change
|
||||
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --countfee=10mycoin --sequence=2 --name=${RICH} --valid 2>/dev/null)
|
||||
assertFalse "line=${LINENO}, replay: $TX" $?
|
||||
TX_HEIGHT=$(echo $TX | jq .height)
|
||||
|
||||
checkCounter "2" "17" "$TX_HEIGHT"
|
||||
checkAccount $SENDER "9007199254739979" "$TX_HEIGHT"
|
||||
}
|
||||
|
||||
# Load common then run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/common.sh
|
||||
. $CLI_DIR/shunit2
|
||||
@ -1,14 +0,0 @@
|
||||
LINKER_FLAGS:="-X github.com/cosmos/cosmos-sdk/client/commands.CommitHash=`git rev-parse --short HEAD`"
|
||||
|
||||
install:
|
||||
@go install -ldflags $(LINKER_FLAGS) ./cmd/...
|
||||
|
||||
test: test_unit test_cli
|
||||
|
||||
test_unit:
|
||||
@go test `glide novendor`
|
||||
|
||||
test_cli:
|
||||
./tests/cli/eyes.sh
|
||||
|
||||
.PHONY: install test test_unit test_cli
|
||||
@ -1,58 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/server/commands"
|
||||
)
|
||||
|
||||
// InitCmd - node initialization command
|
||||
var InitCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize eyes abci server",
|
||||
RunE: initCmd,
|
||||
}
|
||||
|
||||
//nolint - flags
|
||||
var (
|
||||
FlagChainID = "chain-id" //TODO group with other flags or remove? is this already a flag here?
|
||||
)
|
||||
|
||||
func init() {
|
||||
InitCmd.Flags().String(FlagChainID, "eyes_test_id", "Chain ID")
|
||||
}
|
||||
|
||||
func initCmd(cmd *cobra.Command, args []string) error {
|
||||
// this will ensure that config.toml is there if not yet created, and create dir
|
||||
cfg, err := tcmd.ParseConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genesis := getGenesisJSON(viper.GetString(commands.FlagChainID))
|
||||
return commands.CreateGenesisValidatorFiles(cfg, genesis, commands.StaticPrivValJSON, cmd.Root().Name())
|
||||
}
|
||||
|
||||
// TODO: better, auto-generate validator...
|
||||
func getGenesisJSON(chainID string) string {
|
||||
return fmt.Sprintf(`{
|
||||
"app_hash": "",
|
||||
"chain_id": "%s",
|
||||
"genesis_time": "0001-01-01T00:00:00.000Z",
|
||||
"validators": [
|
||||
{
|
||||
"power": 10,
|
||||
"name": "",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, chainID)
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
client "github.com/cosmos/cosmos-sdk/client/commands"
|
||||
eyesmod "github.com/cosmos/cosmos-sdk/modules/eyes"
|
||||
"github.com/cosmos/cosmos-sdk/server/commands"
|
||||
"github.com/cosmos/cosmos-sdk/util"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/eyes"
|
||||
)
|
||||
|
||||
// RootCmd is the entry point for this binary
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "eyes",
|
||||
Short: "key-value store",
|
||||
Long: "A demo app to show key-value store with proofs over abci",
|
||||
}
|
||||
|
||||
// BuildApp constructs the stack we want to use for this app
|
||||
func BuildApp() sdk.Handler {
|
||||
return sdk.ChainDecorators(
|
||||
util.Logger{},
|
||||
util.Recovery{},
|
||||
eyes.Parser{},
|
||||
util.Chain{},
|
||||
).WithHandler(
|
||||
eyesmod.NewHandler(),
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
commands.Handler = BuildApp()
|
||||
|
||||
RootCmd.AddCommand(
|
||||
// out own init command to not require argument
|
||||
InitCmd,
|
||||
commands.StartCmd,
|
||||
commands.UnsafeResetAllCmd,
|
||||
client.VersionCmd,
|
||||
)
|
||||
commands.SetUpRoot(RootCmd)
|
||||
|
||||
cmd := cli.PrepareMainCmd(RootCmd, "EYE", os.ExpandEnv("$HOME/.eyes"))
|
||||
cmd.Execute()
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/auto"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/commits"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands/query"
|
||||
rpccmd "github.com/cosmos/cosmos-sdk/client/commands/rpc"
|
||||
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
|
||||
eyescmd "github.com/cosmos/cosmos-sdk/modules/eyes/commands"
|
||||
)
|
||||
|
||||
// EyesCli - main basecoin client command
|
||||
var EyesCli = &cobra.Command{
|
||||
Use: "eyescli",
|
||||
Short: "Light client for Tendermint",
|
||||
Long: `EyesCli is the light client for a merkle key-value store (eyes)`,
|
||||
}
|
||||
|
||||
func main() {
|
||||
commands.AddBasicFlags(EyesCli)
|
||||
|
||||
// Prepare queries
|
||||
query.RootCmd.AddCommand(
|
||||
// These are default parsers, but optional in your app (you can remove key)
|
||||
query.TxQueryCmd,
|
||||
query.KeyQueryCmd,
|
||||
// this is our custom parser
|
||||
eyescmd.EyesQueryCmd,
|
||||
)
|
||||
|
||||
// no middleware wrapers
|
||||
txcmd.Middleware = txcmd.Wrappers{}
|
||||
// txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags())
|
||||
|
||||
// just the etc commands
|
||||
txcmd.RootCmd.AddCommand(
|
||||
eyescmd.SetTxCmd,
|
||||
eyescmd.RemoveTxCmd,
|
||||
)
|
||||
|
||||
// Set up the various commands to use
|
||||
EyesCli.AddCommand(
|
||||
// we use out own init command to not require address arg
|
||||
commands.InitCmd,
|
||||
commands.ResetCmd,
|
||||
commits.RootCmd,
|
||||
rpccmd.RootCmd,
|
||||
query.RootCmd,
|
||||
txcmd.RootCmd,
|
||||
commands.VersionCmd,
|
||||
auto.AutoCompleteCmd,
|
||||
)
|
||||
|
||||
cmd := cli.PrepareMainCmd(EyesCli, "EYE", os.ExpandEnv("$HOME/.eyescli"))
|
||||
cmd.Execute()
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package eyes
|
||||
|
||||
import sdk "github.com/cosmos/cosmos-sdk"
|
||||
|
||||
// Parser converts bytes into a tx struct
|
||||
type Parser struct{}
|
||||
|
||||
var _ sdk.Decorator = Parser{}
|
||||
|
||||
// CheckTx makes sure we are on the proper chain
|
||||
// - fulfills Decorator interface
|
||||
func (c Parser) CheckTx(ctx sdk.Context, store sdk.SimpleDB,
|
||||
txBytes interface{}, next sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
|
||||
tx, err := LoadTx(txBytes.([]byte))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
return next.CheckTx(ctx, store, tx)
|
||||
}
|
||||
|
||||
// DeliverTx makes sure we are on the proper chain
|
||||
// - fulfills Decorator interface
|
||||
func (c Parser) DeliverTx(ctx sdk.Context, store sdk.SimpleDB,
|
||||
txBytes interface{}, next sdk.Deliverer) (res sdk.DeliverResult, err error) {
|
||||
|
||||
tx, err := LoadTx(txBytes.([]byte))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
return next.DeliverTx(ctx, store, tx)
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# These global variables are required for common.sh
|
||||
SERVER_EXE=eyes
|
||||
CLIENT_EXE=eyescli
|
||||
|
||||
oneTimeSetUp() {
|
||||
# These are passed in as args
|
||||
BASE_DIR=$HOME/.test_eyes
|
||||
CHAIN_ID="eyes-cli-test"
|
||||
|
||||
rm -rf $BASE_DIR 2>/dev/null
|
||||
mkdir -p $BASE_DIR
|
||||
|
||||
echo "Setting up genesis..."
|
||||
SERVE_DIR=${BASE_DIR}/server
|
||||
SERVER_LOG=${BASE_DIR}/${SERVER_EXE}.log
|
||||
|
||||
echo "Starting ${SERVER_EXE} server..."
|
||||
export EYE_HOME=${SERVE_DIR}
|
||||
${SERVER_EXE} init --chain-id=$CHAIN_ID >>$SERVER_LOG
|
||||
startServer $SERVE_DIR $SERVER_LOG
|
||||
[ $? = 0 ] || return 1
|
||||
|
||||
# Set up client - make sure you use the proper prefix if you set
|
||||
# a custom CLIENT_EXE
|
||||
export EYE_HOME=${BASE_DIR}/client
|
||||
|
||||
initClient $CHAIN_ID
|
||||
[ $? = 0 ] || return 1
|
||||
|
||||
printf "...Testing may begin!\n\n\n"
|
||||
}
|
||||
|
||||
oneTimeTearDown() {
|
||||
quickTearDown
|
||||
}
|
||||
|
||||
test00SetGetRemove() {
|
||||
KEY="CAFE6000"
|
||||
VALUE="F00D4200"
|
||||
|
||||
assertFalse "line=${LINENO} data present" "${CLIENT_EXE} query eyes ${KEY}"
|
||||
|
||||
# set data
|
||||
TXRES=$(${CLIENT_EXE} tx set --key=${KEY} --value=${VALUE})
|
||||
txSucceeded $? "$TXRES" "set cafe"
|
||||
HASH=$(echo $TXRES | jq .hash | tr -d \")
|
||||
TX_HEIGHT=$(echo $TXRES | jq .height)
|
||||
|
||||
# make sure it is set
|
||||
DATA=$(${CLIENT_EXE} query eyes ${KEY} --height=$TX_HEIGHT)
|
||||
assertTrue "line=${LINENO} data not set" $?
|
||||
assertEquals "line=${LINENO}" "\"${VALUE}\"" $(echo $DATA | jq .data.value)
|
||||
|
||||
# query the tx
|
||||
TX=$(${CLIENT_EXE} query tx $HASH)
|
||||
assertTrue "line=${LINENO}, found tx" $?
|
||||
if [ -n "$DEBUG" ]; then echo $TX; echo; fi
|
||||
|
||||
assertEquals "line=${LINENO}, proper type" "\"eyes/set\"" $(echo $TX | jq .data.type)
|
||||
assertEquals "line=${LINENO}, proper key" "\"${KEY}\"" $(echo $TX | jq .data.data.key)
|
||||
assertEquals "line=${LINENO}, proper value" "\"${VALUE}\"" $(echo $TX | jq .data.data.value)
|
||||
}
|
||||
|
||||
|
||||
# Load common then run these tests with shunit2!
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
|
||||
CLI_DIR=$GOPATH/src/github.com/cosmos/cosmos-sdk/tests/cli
|
||||
|
||||
. $CLI_DIR/common.sh
|
||||
. $CLI_DIR/shunit2
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
package eyes
|
||||
|
||||
import (
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
eyesmod "github.com/cosmos/cosmos-sdk/modules/eyes"
|
||||
"github.com/cosmos/cosmos-sdk/util"
|
||||
)
|
||||
|
||||
// Tx is what is submitted to the chain.
|
||||
// This embeds the tx data along with any info we want for
|
||||
// decorators (just chain for now to demo)
|
||||
type Tx struct {
|
||||
Tx eyesmod.EyesTx `json:"tx"`
|
||||
Chain util.ChainData `json:"chain"`
|
||||
}
|
||||
|
||||
// GetTx gets the tx info
|
||||
func (e Tx) GetTx() interface{} {
|
||||
return e.Tx
|
||||
}
|
||||
|
||||
// GetChain gets the chain we wish to perform the tx on
|
||||
// (info for decorators)
|
||||
func (e Tx) GetChain() util.ChainData {
|
||||
return e.Chain
|
||||
}
|
||||
|
||||
// LoadTx parses the input data into our blockchain tx structure
|
||||
func LoadTx(data []byte) (tx Tx, err error) {
|
||||
err = wire.ReadBinaryBytes(data, &tx)
|
||||
return
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
/*
|
||||
Package genesis provides some utility functions for parsing
|
||||
a standard genesis file to initialize your abci application.
|
||||
|
||||
We wish to support using one genesis file to initialize both
|
||||
tendermint and the application, so this file format is designed
|
||||
to be embedable in the tendermint genesis.json file. We reuse
|
||||
the same chain_id field for tendermint, ignore the other fields,
|
||||
and add a special app_options field that contains information just
|
||||
for the abci app (and ignored by tendermint).
|
||||
|
||||
The use of this file format for your application is not required by
|
||||
the sdk and is only used by default in the start command, if you wish
|
||||
to write your own start command, you can use any other method to
|
||||
store and parse options for your abci application. The important part is
|
||||
that the same data is available on every node.
|
||||
|
||||
Example file format:
|
||||
|
||||
{
|
||||
"chain_id": "foo_bar_chain",
|
||||
"app_options": {
|
||||
"accounts": [{
|
||||
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "6880DB93598E283A67C4D88FC67A8858AA2DE70F713FE94A5109E29C137100C2"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "ETH",
|
||||
"amount": 654321
|
||||
}
|
||||
]
|
||||
}],
|
||||
"plugin_options": [
|
||||
"plugin1/key1", "value1",
|
||||
"profile/set", {"name": "john", age: 37}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Note that there are two subfields under app_options. The first one "accounts"
|
||||
is a special case for the coin module, which is assumed to be used by most
|
||||
applications. It is simply a list of accounts with an identifier and their
|
||||
initial balance. The account must be identified by EITHER an address
|
||||
(20 bytes in hex) or a pubkey (in the go-crypto json format), not both as in
|
||||
this example. "coins" defines the initial balance of the account.
|
||||
|
||||
Configuration options for every other module should be placed under
|
||||
"plugin_options" as key value pairs (there must be an even number of items).
|
||||
The first value must be "<module>/<key>" to define the option to be set.
|
||||
The second value is parsed as raw json and is the value to pass to the
|
||||
application. This may be a string, an array, a map or any other valid json
|
||||
structure that the module can parse.
|
||||
|
||||
Note that we don't use a map for plugin_options, as we will often wish
|
||||
to have many values for the same key, to run this setup many times,
|
||||
just as we support setting many accounts.
|
||||
*/
|
||||
package genesis
|
||||
@ -1,153 +0,0 @@
|
||||
package genesis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
// KeyDelimiter is used to separate module and key in
|
||||
// the options
|
||||
const KeyDelimiter = "/"
|
||||
|
||||
// Option just holds module/key/value triples from
|
||||
// parsing the genesis file
|
||||
type Option struct {
|
||||
Module string
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// InitStater is anything that can handle app options
|
||||
// from genesis file. Setting the merkle store, config options,
|
||||
// or anything else
|
||||
type InitStater interface {
|
||||
InitState(module, key, value string) error
|
||||
}
|
||||
|
||||
// Load parses the genesis file and sets the initial
|
||||
// state based on that
|
||||
func Load(app InitStater, filePath string) error {
|
||||
opts, err := GetOptions(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// execute all the genesis init options
|
||||
// abort on any error
|
||||
for _, opt := range opts {
|
||||
err = app.InitState(opt.Module, opt.Key, opt.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOptions parses the genesis file in a format
|
||||
// that can easily be handed into InitStaters
|
||||
func GetOptions(path string) ([]Option, error) {
|
||||
genDoc, err := load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := genDoc.AppOptions
|
||||
cnt := 1 + len(opts.Accounts) + len(opts.pluginOptions)
|
||||
res := make([]Option, 0, cnt)
|
||||
res = append(res, Option{sdk.ModuleNameBase, sdk.ChainKey, genDoc.ChainID})
|
||||
|
||||
// set accounts
|
||||
for _, acct := range opts.Accounts {
|
||||
res = append(res, Option{"coin", "account", string(acct)})
|
||||
}
|
||||
|
||||
// set plugin options
|
||||
for _, kv := range opts.pluginOptions {
|
||||
module, key := splitKey(kv.Key)
|
||||
res = append(res, Option{module, key, kv.Value})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type keyValue struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// FullDoc - includes tendermint (in the json, we ignore here)
|
||||
type FullDoc struct {
|
||||
ChainID string `json:"chain_id"`
|
||||
AppOptions *Doc `json:"app_options"`
|
||||
}
|
||||
|
||||
// Doc - All genesis values
|
||||
type Doc struct {
|
||||
Accounts []json.RawMessage `json:"accounts"`
|
||||
PluginOptions []json.RawMessage `json:"plugin_options"`
|
||||
|
||||
pluginOptions []keyValue // unmarshaled rawmessages
|
||||
}
|
||||
|
||||
func load(filePath string) (*FullDoc, error) {
|
||||
bytes, err := cmn.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "loading genesis file")
|
||||
}
|
||||
|
||||
// the basecoin genesis go-wire/data :)
|
||||
genDoc := new(FullDoc)
|
||||
err = json.Unmarshal(bytes, genDoc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshaling genesis file")
|
||||
}
|
||||
|
||||
if genDoc.AppOptions == nil {
|
||||
genDoc.AppOptions = new(Doc)
|
||||
}
|
||||
|
||||
pluginOpts, err := parseList(genDoc.AppOptions.PluginOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
genDoc.AppOptions.pluginOptions = pluginOpts
|
||||
return genDoc, nil
|
||||
}
|
||||
|
||||
func parseList(kvzIn []json.RawMessage) (kvz []keyValue, err error) {
|
||||
if len(kvzIn)%2 != 0 {
|
||||
return nil, errors.New("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]")
|
||||
}
|
||||
|
||||
for i := 0; i < len(kvzIn); i += 2 {
|
||||
kv := keyValue{}
|
||||
rawK := []byte(kvzIn[i])
|
||||
err := json.Unmarshal(rawK, &(kv.Key))
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Non-string key: %s", string(rawK))
|
||||
}
|
||||
// convert value to string if possible (otherwise raw json)
|
||||
rawV := kvzIn[i+1]
|
||||
err = json.Unmarshal(rawV, &(kv.Value))
|
||||
if err != nil {
|
||||
kv.Value = string(rawV)
|
||||
}
|
||||
kvz = append(kvz, kv)
|
||||
}
|
||||
return kvz, nil
|
||||
}
|
||||
|
||||
// Splits the string at the first '/'.
|
||||
// if there are none, assign default module ("base").
|
||||
func splitKey(key string) (string, string) {
|
||||
if strings.Contains(key, KeyDelimiter) {
|
||||
keyParts := strings.SplitN(key, KeyDelimiter, 2)
|
||||
return keyParts[0], keyParts[1]
|
||||
}
|
||||
return sdk.ModuleNameBase, key
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
package genesis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
const genesisFilepath = "./testdata/genesis.json"
|
||||
|
||||
func TestParseList(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
bytes, err := cmn.ReadFile(genesisFilepath)
|
||||
require.Nil(err, "loading genesis file %+v", err)
|
||||
|
||||
// the basecoin genesis go-wire/data :)
|
||||
genDoc := new(FullDoc)
|
||||
err = json.Unmarshal(bytes, genDoc)
|
||||
require.Nil(err, "unmarshaling genesis file %+v", err)
|
||||
|
||||
pluginOpts, err := parseList(genDoc.AppOptions.PluginOptions)
|
||||
require.Nil(err, "%+v", err)
|
||||
genDoc.AppOptions.pluginOptions = pluginOpts
|
||||
|
||||
assert.Equal(genDoc.AppOptions.pluginOptions[0].Key, "plugin1/key1")
|
||||
assert.Equal(genDoc.AppOptions.pluginOptions[1].Key, "plugin1/key2")
|
||||
assert.Equal(genDoc.AppOptions.pluginOptions[0].Value, "value1")
|
||||
assert.Equal(genDoc.AppOptions.pluginOptions[1].Value, "value2")
|
||||
}
|
||||
|
||||
func TestGetOptions(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
opts, err := GetOptions(genesisFilepath)
|
||||
require.Nil(err, "loading genesis file %+v", err)
|
||||
|
||||
require.Equal(4, len(opts))
|
||||
chain := opts[0]
|
||||
assert.Equal(sdk.ModuleNameBase, chain.Module)
|
||||
assert.Equal(sdk.ChainKey, chain.Key)
|
||||
assert.Equal("foo_bar_chain", chain.Value)
|
||||
|
||||
acct := opts[1]
|
||||
assert.Equal("coin", acct.Module)
|
||||
assert.Equal("account", acct.Key)
|
||||
|
||||
p1 := opts[2]
|
||||
assert.Equal("plugin1", p1.Module)
|
||||
assert.Equal("key1", p1.Key)
|
||||
assert.Equal("value1", p1.Value)
|
||||
|
||||
p2 := opts[3]
|
||||
assert.Equal("plugin1", p2.Module)
|
||||
assert.Equal("key2", p2.Key)
|
||||
assert.Equal("value2", p2.Value)
|
||||
}
|
||||
|
||||
func TestSplitKey(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
prefix, suffix := splitKey("foo/bar")
|
||||
assert.EqualValues("foo", prefix)
|
||||
assert.EqualValues("bar", suffix)
|
||||
|
||||
prefix, suffix = splitKey("foobar")
|
||||
assert.EqualValues("base", prefix)
|
||||
assert.EqualValues("foobar", suffix)
|
||||
|
||||
prefix, suffix = splitKey("some/complex/issue")
|
||||
assert.EqualValues("some", prefix)
|
||||
assert.EqualValues("complex/issue", suffix)
|
||||
|
||||
}
|
||||
22
_attic/genesis/testdata/genesis.json
vendored
22
_attic/genesis/testdata/genesis.json
vendored
@ -1,22 +0,0 @@
|
||||
{
|
||||
"chain_id": "foo_bar_chain",
|
||||
"app_options": {
|
||||
"accounts": [{
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "6880db93598e283a67c4d88fc67a8858aa2de70f713fe94a5109e29c137100c2"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "blank",
|
||||
"amount": 12345
|
||||
},
|
||||
{
|
||||
"denom": "ETH",
|
||||
"amount": 654321
|
||||
}
|
||||
]
|
||||
}],
|
||||
"plugin_options": ["plugin1/key1", "value1", "plugin1/key2", "value2"]
|
||||
}
|
||||
}
|
||||
@ -1,292 +0,0 @@
|
||||
package app
|
||||
|
||||
// import (
|
||||
// "encoding/hex"
|
||||
// "testing"
|
||||
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// "github.com/stretchr/testify/require"
|
||||
|
||||
// sdk "github.com/cosmos/cosmos-sdk"
|
||||
// "github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
// "github.com/cosmos/cosmos-sdk/modules/base"
|
||||
// "github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
// "github.com/cosmos/cosmos-sdk/modules/fee"
|
||||
// "github.com/cosmos/cosmos-sdk/modules/nonce"
|
||||
// "github.com/cosmos/cosmos-sdk/stack"
|
||||
// "github.com/cosmos/cosmos-sdk/state"
|
||||
// "github.com/cosmos/cosmos-sdk/util"
|
||||
// abci "github.com/tendermint/abci/types"
|
||||
// wire "github.com/tendermint/go-wire"
|
||||
// "github.com/tendermint/tmlibs/log"
|
||||
// )
|
||||
|
||||
// // DefaultHandler for the tests (coin, roles, ibc)
|
||||
// func DefaultHandler(feeDenom string) sdk.Handler {
|
||||
// // use the default stack
|
||||
// // r := roles.NewHandler()
|
||||
// // i := ibc.NewHandler()
|
||||
|
||||
// return sdk.ChainDecorators(
|
||||
// util.Logger{},
|
||||
// util.Recovery{},
|
||||
// auth.Signatures{},
|
||||
// util.Chain{},
|
||||
// // stack.Checkpoint{OnCheck: true},
|
||||
// // nonce.ReplayCheck{},
|
||||
// ).
|
||||
// // IBC(ibc.NewMiddleware()).
|
||||
// // Apps(
|
||||
// // roles.NewMiddleware(),
|
||||
// // fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
|
||||
// // stack.Checkpoint{OnDeliver: true},
|
||||
// // ).
|
||||
// WithHandler(
|
||||
// coin.NewHandler(),
|
||||
// // stack.WrapHandler(r),
|
||||
// // stack.WrapHandler(i),
|
||||
// )
|
||||
// }
|
||||
|
||||
// //--------------------------------------------------------
|
||||
// // test environment is a list of input and output accounts
|
||||
|
||||
// type appTest struct {
|
||||
// t *testing.T
|
||||
// chainID string
|
||||
// app *BaseApp
|
||||
// acctIn *coin.AccountWithKey
|
||||
// acctOut *coin.AccountWithKey
|
||||
// }
|
||||
|
||||
// func newAppTest(t *testing.T) *appTest {
|
||||
// at := &appTest{
|
||||
// t: t,
|
||||
// chainID: "test_chain_id",
|
||||
// }
|
||||
// at.reset()
|
||||
// return at
|
||||
// }
|
||||
|
||||
// // baseTx is the
|
||||
// func (at *appTest) baseTx(coins coin.Coins) sdk.Tx {
|
||||
// in := []coin.TxInput{{Address: at.acctIn.Actor(), Coins: coins}}
|
||||
// out := []coin.TxOutput{{Address: at.acctOut.Actor(), Coins: coins}}
|
||||
// tx := coin.NewSendTx(in, out)
|
||||
// return tx
|
||||
// }
|
||||
|
||||
// func (at *appTest) signTx(tx sdk.Tx) sdk.Tx {
|
||||
// stx := auth.NewMulti(tx)
|
||||
// auth.Sign(stx, at.acctIn.Key)
|
||||
// return stx.Wrap()
|
||||
// }
|
||||
|
||||
// func (at *appTest) getTx(coins coin.Coins, sequence uint32) sdk.Tx {
|
||||
// tx := at.baseTx(coins)
|
||||
// tx = nonce.NewTx(sequence, []sdk.Actor{at.acctIn.Actor()}, tx)
|
||||
// tx = base.NewChainTx(at.chainID, 0, tx)
|
||||
// return at.signTx(tx)
|
||||
// }
|
||||
|
||||
// func (at *appTest) feeTx(coins coin.Coins, toll coin.Coin, sequence uint32) sdk.Tx {
|
||||
// tx := at.baseTx(coins)
|
||||
// tx = fee.NewFee(tx, toll, at.acctIn.Actor())
|
||||
// tx = nonce.NewTx(sequence, []sdk.Actor{at.acctIn.Actor()}, tx)
|
||||
// tx = base.NewChainTx(at.chainID, 0, tx)
|
||||
// return at.signTx(tx)
|
||||
// }
|
||||
|
||||
// // set the account on the app through InitState
|
||||
// func (at *appTest) initAccount(acct *coin.AccountWithKey) {
|
||||
// err := at.app.InitState("coin", "account", acct.MakeOption())
|
||||
// require.Nil(at.t, err, "%+v", err)
|
||||
// }
|
||||
|
||||
// // reset the in and out accs to be one account each with 7mycoin
|
||||
// func (at *appTest) reset() {
|
||||
// at.acctIn = coin.NewAccountWithKey(coin.Coins{{"mycoin", 7}})
|
||||
// at.acctOut = coin.NewAccountWithKey(coin.Coins{{"mycoin", 7}})
|
||||
|
||||
// // Note: switch logger if you want to get more info
|
||||
// logger := log.TestingLogger()
|
||||
// // logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout))
|
||||
|
||||
// store, err := NewStoreApp("app-test", "", 0, logger)
|
||||
// require.Nil(at.t, err, "%+v", err)
|
||||
// at.app = NewBaseApp(store, DefaultHandler("mycoin"), nil)
|
||||
|
||||
// err = at.app.InitState("base", "chain_id", at.chainID)
|
||||
// require.Nil(at.t, err, "%+v", err)
|
||||
|
||||
// at.initAccount(at.acctIn)
|
||||
// at.initAccount(at.acctOut)
|
||||
|
||||
// resabci := at.app.Commit()
|
||||
// require.True(at.t, resabci.IsOK(), resabci)
|
||||
// }
|
||||
|
||||
// func getBalance(key sdk.Actor, store state.SimpleDB) (coin.Coins, error) {
|
||||
// cspace := stack.PrefixedStore(coin.NameCoin, store)
|
||||
// acct, err := coin.GetAccount(cspace, key)
|
||||
// return acct.Coins, err
|
||||
// }
|
||||
|
||||
// func getAddr(addr []byte, state state.SimpleDB) (coin.Coins, error) {
|
||||
// actor := auth.SigPerm(addr)
|
||||
// return getBalance(actor, state)
|
||||
// }
|
||||
|
||||
// // returns the final balance and expected balance for input and output accounts
|
||||
// func (at *appTest) exec(t *testing.T, tx sdk.Tx, checkTx bool) (res abci.Result, diffIn, diffOut coin.Coins) {
|
||||
// require := require.New(t)
|
||||
|
||||
// initBalIn, err := getBalance(at.acctIn.Actor(), at.app.Append())
|
||||
// require.Nil(err, "%+v", err)
|
||||
// initBalOut, err := getBalance(at.acctOut.Actor(), at.app.Append())
|
||||
// require.Nil(err, "%+v", err)
|
||||
|
||||
// txBytes := wire.BinaryBytes(tx)
|
||||
// if checkTx {
|
||||
// res = at.app.CheckTx(txBytes)
|
||||
// } else {
|
||||
// res = at.app.DeliverTx(txBytes)
|
||||
// }
|
||||
|
||||
// endBalIn, err := getBalance(at.acctIn.Actor(), at.app.Append())
|
||||
// require.Nil(err, "%+v", err)
|
||||
// endBalOut, err := getBalance(at.acctOut.Actor(), at.app.Append())
|
||||
// require.Nil(err, "%+v", err)
|
||||
// return res, endBalIn.Minus(initBalIn), endBalOut.Minus(initBalOut)
|
||||
// }
|
||||
|
||||
// //--------------------------------------------------------
|
||||
|
||||
// func TestInitState(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
// require := require.New(t)
|
||||
|
||||
// logger := log.TestingLogger()
|
||||
// store, err := NewStoreApp("app-test", "", 0, logger)
|
||||
// require.Nil(err, "%+v", err)
|
||||
// app := NewBaseApp(store, DefaultHandler("atom"), nil)
|
||||
|
||||
// //testing ChainID
|
||||
// chainID := "testChain"
|
||||
// err = app.InitState("base", "chain_id", chainID)
|
||||
// require.Nil(err, "%+v", err)
|
||||
// assert.EqualValues(app.GetChainID(), chainID)
|
||||
|
||||
// // make a nice account...
|
||||
// bal := coin.Coins{{"atom", 77}, {"eth", 12}}
|
||||
// acct := coin.NewAccountWithKey(bal)
|
||||
// err = app.InitState("coin", "account", acct.MakeOption())
|
||||
// require.Nil(err, "%+v", err)
|
||||
|
||||
// // make sure it is set correctly, with some balance
|
||||
// coins, err := getBalance(acct.Actor(), app.Append())
|
||||
// require.Nil(err)
|
||||
// assert.Equal(bal, coins)
|
||||
|
||||
// // let's parse an account with badly sorted coins...
|
||||
// unsortAddr, err := hex.DecodeString("C471FB670E44D219EE6DF2FC284BE38793ACBCE1")
|
||||
// require.Nil(err)
|
||||
// unsortCoins := coin.Coins{{"BTC", 789}, {"eth", 123}}
|
||||
// unsortAcc := `{
|
||||
// "pub_key": {
|
||||
// "type": "ed25519",
|
||||
// "data": "AD084F0572C116D618B36F2EB08240D1BAB4B51716CCE0E7734B89C8936DCE9A"
|
||||
// },
|
||||
// "coins": [
|
||||
// {
|
||||
// "denom": "eth",
|
||||
// "amount": 123
|
||||
// },
|
||||
// {
|
||||
// "denom": "BTC",
|
||||
// "amount": 789
|
||||
// }
|
||||
// ]
|
||||
// }`
|
||||
// err = app.InitState("coin", "account", unsortAcc)
|
||||
// require.Nil(err, "%+v", err)
|
||||
|
||||
// coins, err = getAddr(unsortAddr, app.Append())
|
||||
// require.Nil(err)
|
||||
// assert.True(coins.IsValid())
|
||||
// assert.Equal(unsortCoins, coins)
|
||||
|
||||
// err = app.InitState("base", "dslfkgjdas", "")
|
||||
// require.Error(err)
|
||||
|
||||
// err = app.InitState("", "dslfkgjdas", "")
|
||||
// require.Error(err)
|
||||
|
||||
// err = app.InitState("dslfkgjdas", "szfdjzs", "")
|
||||
// require.Error(err)
|
||||
// }
|
||||
|
||||
// // Test CheckTx and DeliverTx with insufficient and sufficient balance
|
||||
// func TestTx(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
// at := newAppTest(t)
|
||||
|
||||
// //Bad Balance
|
||||
// at.acctIn.Coins = coin.Coins{{"mycoin", 2}}
|
||||
// at.initAccount(at.acctIn)
|
||||
// at.app.Commit()
|
||||
|
||||
// res, _, _ := at.exec(t, at.getTx(coin.Coins{{"mycoin", 5}}, 1), true)
|
||||
// assert.True(res.IsErr(), "ExecTx/Bad CheckTx: Expected error return from ExecTx, returned: %v", res)
|
||||
// res, diffIn, diffOut := at.exec(t, at.getTx(coin.Coins{{"mycoin", 5}}, 1), false)
|
||||
// assert.True(res.IsErr(), "ExecTx/Bad DeliverTx: Expected error return from ExecTx, returned: %v", res)
|
||||
// assert.True(diffIn.IsZero())
|
||||
// assert.True(diffOut.IsZero())
|
||||
|
||||
// //Regular CheckTx
|
||||
// at.reset()
|
||||
// res, _, _ = at.exec(t, at.getTx(coin.Coins{{"mycoin", 5}}, 1), true)
|
||||
// assert.True(res.IsOK(), "ExecTx/Good CheckTx: Expected OK return from ExecTx, Error: %v", res)
|
||||
|
||||
// //Regular DeliverTx
|
||||
// at.reset()
|
||||
// amt := coin.Coins{{"mycoin", 3}}
|
||||
// res, diffIn, diffOut = at.exec(t, at.getTx(amt, 1), false)
|
||||
// assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
|
||||
// assert.Equal(amt.Negative(), diffIn)
|
||||
// assert.Equal(amt, diffOut)
|
||||
|
||||
// //DeliverTx with fee.... 4 get to recipient, 1 extra taxed
|
||||
// at.reset()
|
||||
// amt = coin.Coins{{"mycoin", 4}}
|
||||
// toll := coin.Coin{"mycoin", 1}
|
||||
// res, diffIn, diffOut = at.exec(t, at.feeTx(amt, toll, 1), false)
|
||||
// assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
|
||||
// payment := amt.Plus(coin.Coins{toll}).Negative()
|
||||
// assert.Equal(payment, diffIn)
|
||||
// assert.Equal(amt, diffOut)
|
||||
|
||||
// }
|
||||
|
||||
// func TestQuery(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
// at := newAppTest(t)
|
||||
|
||||
// res, _, _ := at.exec(t, at.getTx(coin.Coins{{"mycoin", 5}}, 1), false)
|
||||
// assert.True(res.IsOK(), "Commit, DeliverTx: Expected OK return from DeliverTx, Error: %v", res)
|
||||
|
||||
// resQueryPreCommit := at.app.Query(abci.RequestQuery{
|
||||
// Path: "/account",
|
||||
// Data: at.acctIn.Address(),
|
||||
// })
|
||||
|
||||
// res = at.app.Commit()
|
||||
// assert.True(res.IsOK(), res)
|
||||
|
||||
// key := stack.PrefixedKey(coin.NameCoin, at.acctIn.Address())
|
||||
// resQueryPostCommit := at.app.Query(abci.RequestQuery{
|
||||
// Path: "/key",
|
||||
// Data: key,
|
||||
// })
|
||||
// assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit")
|
||||
// }
|
||||
@ -1,101 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/genesis"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
)
|
||||
|
||||
const genesisFilepath = "./testdata/genesis.json"
|
||||
const genesisAcctFilepath = "./testdata/genesis2.json"
|
||||
|
||||
// 2b is just like 2, but add carl who has inconsistent
|
||||
// pubkey and address
|
||||
const genesisBadAcctFilepath = "./testdata/genesis2b.json"
|
||||
|
||||
func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) {
|
||||
logger := log.TestingLogger()
|
||||
store, err := MockStoreApp("genesis", logger)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
app := NewBaseApp(store, DefaultHandler("mycoin"), nil)
|
||||
|
||||
err = genesis.Load(app, "./testdata/genesis3.json")
|
||||
require.Nil(t, err, "%+v", err)
|
||||
}
|
||||
|
||||
func TestLoadGenesisFailsWithUnknownOptions(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
logger := log.TestingLogger()
|
||||
store, err := MockStoreApp("genesis", logger)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
app := NewBaseApp(store, DefaultHandler("mycoin"), nil)
|
||||
err = genesis.Load(app, genesisFilepath)
|
||||
require.NotNil(err, "%+v", err)
|
||||
}
|
||||
|
||||
// Fix for issue #89, change the parse format for accounts in genesis.json
|
||||
func TestLoadGenesisAccountAddress(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
logger := log.TestingLogger()
|
||||
store, err := MockStoreApp("genesis", logger)
|
||||
require.Nil(err, "%+v", err)
|
||||
app := NewBaseApp(store, DefaultHandler("mycoin"), nil)
|
||||
|
||||
err = genesis.Load(app, genesisAcctFilepath)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// check the chain id
|
||||
assert.Equal("addr_accounts_chain", app.GetChainID())
|
||||
|
||||
// make sure the accounts were set properly
|
||||
cases := []struct {
|
||||
addr string
|
||||
exists bool
|
||||
hasPubkey bool
|
||||
coins coin.Coins
|
||||
}{
|
||||
// this comes from a public key, should be stored proper (alice)
|
||||
{"62035D628DE7543332544AA60D90D3693B6AD51B", true, true, coin.Coins{{"one", 111}}},
|
||||
// this comes from an address, should be stored proper (bob)
|
||||
{"C471FB670E44D219EE6DF2FC284BE38793ACBCE1", true, false, coin.Coins{{"two", 222}}},
|
||||
// this comes from a secp256k1 public key, should be stored proper (sam)
|
||||
{"979F080B1DD046C452C2A8A250D18646C6B669D4", true, true, coin.Coins{{"four", 444}}},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
addr, err := hex.DecodeString(tc.addr)
|
||||
require.Nil(err, tc.addr)
|
||||
coins, err := getAddr(addr, app.Append())
|
||||
require.Nil(err, "%+v", err)
|
||||
if !tc.exists {
|
||||
assert.True(coins.IsZero(), "%d", i)
|
||||
} else if assert.False(coins.IsZero(), "%d", i) {
|
||||
// it should and does exist...
|
||||
assert.True(coins.IsValid())
|
||||
assert.Equal(tc.coins, coins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When you define an account in genesis with address
|
||||
// and pubkey that don't match
|
||||
func TestLoadGenesisAccountInconsistentAddress(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
logger := log.TestingLogger()
|
||||
store, err := MockStoreApp("genesis", logger)
|
||||
require.Nil(err, "%+v", err)
|
||||
app := NewBaseApp(store, DefaultHandler("mycoin"), nil)
|
||||
err = genesis.Load(app, genesisBadAcctFilepath)
|
||||
require.NotNil(err)
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
/**
|
||||
Package bonus is a temporary home for various functionalities
|
||||
that were removed for complexity, but may be added back in
|
||||
later.
|
||||
**/
|
||||
package bonus
|
||||
@ -1,118 +0,0 @@
|
||||
package bonus
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
//nolint
|
||||
const (
|
||||
NameVal = "val"
|
||||
NamePrice = "price"
|
||||
|
||||
TypeValChange = NameVal + "/change"
|
||||
ByteValChange = 0xfe
|
||||
|
||||
TypePriceShow = NamePrice + "/show"
|
||||
BytePriceShow = 0xfd
|
||||
)
|
||||
|
||||
func init() {
|
||||
sdk.TxMapper.
|
||||
RegisterImplementation(ValChangeTx{}, TypeValChange, ByteValChange).
|
||||
RegisterImplementation(PriceShowTx{}, TypePriceShow, BytePriceShow)
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
// Setup tx and handler for validation test cases
|
||||
|
||||
type ValSetHandler struct {
|
||||
sdk.NopCheck
|
||||
sdk.NopInitState
|
||||
sdk.NopInitValidate
|
||||
}
|
||||
|
||||
var _ sdk.Handler = ValSetHandler{}
|
||||
|
||||
func (ValSetHandler) Name() string {
|
||||
return NameVal
|
||||
}
|
||||
|
||||
func (ValSetHandler) DeliverTx(ctx sdk.Context, store state.SimpleDB,
|
||||
tx sdk.Tx) (res sdk.DeliverResult, err error) {
|
||||
change, ok := tx.Unwrap().(ValChangeTx)
|
||||
if !ok {
|
||||
return res, errors.ErrUnknownTxType(tx)
|
||||
}
|
||||
res.Diff = change.Diff
|
||||
return
|
||||
}
|
||||
|
||||
type ValChangeTx struct {
|
||||
Diff []*abci.Validator
|
||||
}
|
||||
|
||||
func (v ValChangeTx) Wrap() sdk.Tx {
|
||||
return sdk.Tx{v}
|
||||
}
|
||||
|
||||
func (v ValChangeTx) ValidateBasic() error { return nil }
|
||||
|
||||
//--------------------------------
|
||||
// Setup tx and handler for testing checktx fees/gas
|
||||
|
||||
// PriceData is the data we ping back
|
||||
var PriceData = []byte{0xCA, 0xFE}
|
||||
|
||||
// PriceHandler returns checktx results based on the input
|
||||
type PriceHandler struct {
|
||||
sdk.NopInitState
|
||||
sdk.NopInitValidate
|
||||
}
|
||||
|
||||
var _ sdk.Handler = PriceHandler{}
|
||||
|
||||
func (PriceHandler) Name() string {
|
||||
return NamePrice
|
||||
}
|
||||
|
||||
func (PriceHandler) CheckTx(ctx sdk.Context, store state.SimpleDB,
|
||||
tx sdk.Tx) (res sdk.CheckResult, err error) {
|
||||
price, ok := tx.Unwrap().(PriceShowTx)
|
||||
if !ok {
|
||||
return res, errors.ErrUnknownTxType(tx)
|
||||
}
|
||||
res.GasAllocated = price.GasAllocated
|
||||
res.GasPayment = price.GasPayment
|
||||
res.Data = PriceData
|
||||
return
|
||||
}
|
||||
|
||||
func (PriceHandler) DeliverTx(ctx sdk.Context, store state.SimpleDB,
|
||||
tx sdk.Tx) (res sdk.DeliverResult, err error) {
|
||||
_, ok := tx.Unwrap().(PriceShowTx)
|
||||
if !ok {
|
||||
return res, errors.ErrUnknownTxType(tx)
|
||||
}
|
||||
res.Data = PriceData
|
||||
return
|
||||
}
|
||||
|
||||
// PriceShowTx lets us bounce back a given fee/gas on CheckTx
|
||||
type PriceShowTx struct {
|
||||
GasAllocated uint64
|
||||
GasPayment uint64
|
||||
}
|
||||
|
||||
func NewPriceShowTx(gasAllocated, gasPayment uint64) sdk.Tx {
|
||||
return PriceShowTx{GasAllocated: gasAllocated, GasPayment: gasPayment}.Wrap()
|
||||
}
|
||||
|
||||
func (p PriceShowTx) Wrap() sdk.Tx {
|
||||
return sdk.Tx{p}
|
||||
}
|
||||
|
||||
func (v PriceShowTx) ValidateBasic() error { return nil }
|
||||
@ -1,116 +0,0 @@
|
||||
package bonus
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
)
|
||||
|
||||
//nolint
|
||||
const (
|
||||
NameMultiplexer = "mplx"
|
||||
)
|
||||
|
||||
// Multiplexer grabs a MultiTx and sends them sequentially down the line
|
||||
type Multiplexer struct {
|
||||
stack.PassInitState
|
||||
stack.PassInitValidate
|
||||
}
|
||||
|
||||
// Name of the module - fulfills Middleware interface
|
||||
func (Multiplexer) Name() string {
|
||||
return NameMultiplexer
|
||||
}
|
||||
|
||||
var _ stack.Middleware = Multiplexer{}
|
||||
|
||||
// CheckTx splits the input tx and checks them all - fulfills Middlware interface
|
||||
func (Multiplexer) CheckTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, next sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
if mtx, ok := tx.Unwrap().(MultiTx); ok {
|
||||
return runAllChecks(ctx, store, mtx.Txs, next)
|
||||
}
|
||||
return next.CheckTx(ctx, store, tx)
|
||||
}
|
||||
|
||||
// DeliverTx splits the input tx and checks them all - fulfills Middlware interface
|
||||
func (Multiplexer) DeliverTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, next sdk.Deliver) (res sdk.DeliverResult, err error) {
|
||||
if mtx, ok := tx.Unwrap().(MultiTx); ok {
|
||||
return runAllDelivers(ctx, store, mtx.Txs, next)
|
||||
}
|
||||
return next.DeliverTx(ctx, store, tx)
|
||||
}
|
||||
|
||||
func runAllChecks(ctx sdk.Context, store state.SimpleDB, txs []sdk.Tx, next sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
// store all results, unless anything errors
|
||||
rs := make([]sdk.CheckResult, len(txs))
|
||||
for i, stx := range txs {
|
||||
rs[i], err = next.CheckTx(ctx, store, stx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// now combine the results into one...
|
||||
return combineChecks(rs), nil
|
||||
}
|
||||
|
||||
func runAllDelivers(ctx sdk.Context, store state.SimpleDB, txs []sdk.Tx, next sdk.Deliver) (res sdk.DeliverResult, err error) {
|
||||
// store all results, unless anything errors
|
||||
rs := make([]sdk.DeliverResult, len(txs))
|
||||
for i, stx := range txs {
|
||||
rs[i], err = next.DeliverTx(ctx, store, stx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// now combine the results into one...
|
||||
return combineDelivers(rs), nil
|
||||
}
|
||||
|
||||
// combines all data bytes as a go-wire array.
|
||||
// joins all log messages with \n
|
||||
func combineChecks(all []sdk.CheckResult) sdk.CheckResult {
|
||||
datas := make([]data.Bytes, len(all))
|
||||
logs := make([]string, len(all))
|
||||
var allocated, payments uint64
|
||||
for i, r := range all {
|
||||
datas[i] = r.Data
|
||||
logs[i] = r.Log
|
||||
allocated += r.GasAllocated
|
||||
payments += r.GasPayment
|
||||
}
|
||||
return sdk.CheckResult{
|
||||
Data: wire.BinaryBytes(datas),
|
||||
Log: strings.Join(logs, "\n"),
|
||||
GasAllocated: allocated,
|
||||
GasPayment: payments,
|
||||
}
|
||||
}
|
||||
|
||||
// combines all data bytes as a go-wire array.
|
||||
// joins all log messages with \n
|
||||
func combineDelivers(all []sdk.DeliverResult) sdk.DeliverResult {
|
||||
datas := make([]data.Bytes, len(all))
|
||||
logs := make([]string, len(all))
|
||||
var used uint64
|
||||
var diffs []*abci.Validator
|
||||
for i, r := range all {
|
||||
datas[i] = r.Data
|
||||
logs[i] = r.Log
|
||||
used += r.GasUsed
|
||||
if len(r.Diff) > 0 {
|
||||
diffs = append(diffs, r.Diff...)
|
||||
}
|
||||
}
|
||||
return sdk.DeliverResult{
|
||||
Data: wire.BinaryBytes(datas),
|
||||
Log: strings.Join(logs, "\n"),
|
||||
GasUsed: used,
|
||||
Diff: diffs,
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
package bonus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/stack"
|
||||
"github.com/cosmos/cosmos-sdk/state"
|
||||
"github.com/stretchr/testify/assert"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
)
|
||||
|
||||
func TestMultiplexer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
msg := "diddly"
|
||||
chainID := "multi-verse"
|
||||
height := uint64(100)
|
||||
|
||||
// Generic args here...
|
||||
store := state.NewMemKVStore()
|
||||
ctx := stack.NewContext(chainID, height, log.NewNopLogger())
|
||||
|
||||
// Build the stack
|
||||
app := stack.
|
||||
New(Multiplexer{}).
|
||||
Dispatch(
|
||||
stack.WrapHandler(stack.OKHandler{Log: msg}),
|
||||
stack.WrapHandler(PriceHandler{}),
|
||||
)
|
||||
|
||||
raw := stack.NewRawTx([]byte{1, 2, 3, 4})
|
||||
fail := stack.NewFailTx()
|
||||
price1 := NewPriceShowTx(123, 456)
|
||||
price2 := NewPriceShowTx(1000, 2000)
|
||||
price3 := NewPriceShowTx(11, 0)
|
||||
|
||||
join := func(data ...[]byte) []byte {
|
||||
return wire.BinaryBytes(data)
|
||||
}
|
||||
|
||||
cases := [...]struct {
|
||||
tx sdk.Tx
|
||||
valid bool
|
||||
gasAllocated uint64
|
||||
gasPayment uint64
|
||||
log string
|
||||
data data.Bytes
|
||||
}{
|
||||
// test the components without multiplexer (no effect)
|
||||
0: {raw, true, 0, 0, msg, nil},
|
||||
1: {price1, true, 123, 456, "", PriceData},
|
||||
2: {fail, false, 0, 0, "", nil},
|
||||
// test multiplexer on error
|
||||
3: {NewMultiTx(raw, fail, price1), false, 0, 0, "", nil},
|
||||
// test combining info on multiplexer
|
||||
4: {NewMultiTx(price1, raw), true, 123, 456, "\n" + msg, join(PriceData, nil)},
|
||||
// add lots of prices
|
||||
5: {NewMultiTx(price1, price2, price3), true, 1134, 2456, "\n\n", join(PriceData, PriceData, PriceData)},
|
||||
// combine multiple logs
|
||||
6: {NewMultiTx(raw, price3, raw), true, 11, 0, msg + "\n\n" + msg, join(nil, PriceData, nil)},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
cres, err := app.CheckTx(ctx, store, tc.tx)
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(tc.log, cres.Log, "%d", i)
|
||||
assert.Equal(tc.data, cres.Data, "%d", i)
|
||||
assert.Equal(tc.gasAllocated, cres.GasAllocated, "%d", i)
|
||||
assert.Equal(tc.gasPayment, cres.GasPayment, "%d", i)
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
}
|
||||
|
||||
// make sure deliver returns error, not a panic crash
|
||||
dres, err := app.DeliverTx(ctx, store, tc.tx)
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(tc.log, dres.Log, "%d", i)
|
||||
assert.Equal(tc.data, dres.Data, "%d", i)
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = python -msphinx
|
||||
SPHINXPROJ = Cosmos-SDK
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user