Documentation Structure Change and Cleanup (#2808)

* Update docs/sdk/clients.md
* organize ADR directory like tendermint
* docs: move spec-proposals into spec/
* remove lotion, moved to website repo
* move getting-started to cosmos-hub, and voyager to website
* docs: move lite/ into clients/lite/
* move introduction/ content to website repo
* move resources/ content to website repo
* mv sdk/clients.md to clients/clients.md
* mv validators to cosmos-hub/validators
* move deprecated sdk/ content to _attic
* sdk/modules.md is duplicate with modules/README.md
* consolidate remianing sdk/ files into a single sdk.md
* move examples/ to docs/examples/
* mv docs/cosmos-hub to docs/gaia
* Add keys/accounts section to localnet docs
This commit is contained in:
gamarin2
2018-11-14 11:44:17 -08:00
committed by Jack Zampolin
parent 6feab55e1e
commit addcfbf5cb
156 changed files with 664 additions and 5897 deletions
+20 -86
View File
@@ -12,105 +12,39 @@ module.exports = {
nav: [{ text: "Back to Cosmos", link: "https://cosmos.network" }],
sidebar: [
{
title: "Introduction",
title: "Overview",
collapsable: false,
children: [
"/introduction/cosmos-hub",
"/introduction/tendermint-cosmos",
"/introduction/tendermint"
"/intro/README",
"/intro/sdk-app-architecture",
"/intro/ocap"
]
},
{
title: "Getting Started",
title: "Gaia",
collapsable: false,
children: [
"/getting-started/voyager",
"/getting-started/installation",
"/getting-started/join-testnet",
"/getting-started/networks"
]
},
{
title: "Cosmos SDK",
collapsable: false,
children: [
["/sdk/overview", "Overview"],
["/sdk/core/intro", "Core"],
"/sdk/core/app1",
"/sdk/core/app2",
"/sdk/core/app3",
"/sdk/core/app4",
"/sdk/core/app5",
// "/sdk/modules",
"/sdk/clients"
]
},
// {
// title: "Specifications",
// collapsable: false,
// children: [
// ["/specs/overview", "Overview"],
// "/specs/governance",
// "/specs/ibc",
// "/specs/staking",
// "/specs/icts",
// ]
// },
{
title: "SDK by Examples - Simple Governance",
collapsable: false,
children: [
["/sdk/sdk-by-examples/simple-governance/intro", "Intro"],
"/sdk/sdk-by-examples/simple-governance/setup-and-design",
"/sdk/sdk-by-examples/simple-governance/app-init",
"/sdk/sdk-by-examples/simple-governance/simple-gov-module",
"/sdk/sdk-by-examples/simple-governance/bridging-it-all",
"/sdk/sdk-by-examples/simple-governance/running-the-application"
]
},
{
title: "Light Client",
collapsable: false,
children: [
"/lite/",
"/lite/getting_started"
]
},
{
title: "Lotion JS",
collapsable: false,
children: [
["/lotion/overview", "Overview"]
]
},
{
title: "Validators",
collapsable: false,
children: [
["/validators/overview", "Overview"],
["/validators/security", "Security"],
["/validators/validator-setup", "Validator Setup"],
"/validators/validator-faq"
"/gaia/installation",
"/gaia/join-testnet",
"/gaia/networks",
"/gaia/validators/overview",
"/gaia/validators/security",
"/gaia/validators/validator-faq",
"/gaia/validators/validator-setup",
"/gaia/ledger"
]
},
{
title: "Clients",
collapsable: false,
children: [
["/clients/service-providers", "Service Providers"]
]
},
{
title: "Resources",
collapsable: false,
children: [
// ["/resources/faq" "General"],
"/resources/delegator-faq",
["/resources/whitepaper", "Whitepaper - English"],
["/resources/whitepaper-ko", "Whitepaper - 한국어"],
["/resources/whitepaper-zh-CN", "Whitepaper - 中文"],
["/resources/whitepaper-pt", "Whitepaper - Português"]
]
"/clients/README",
"/lite/", // this renders the readme
"/lite/getting_started",
"/lite/specification",
"/clients/cli",
"/clients/service-providers"
]
}
]
}
+15 -11
View File
@@ -1,4 +1,8 @@
# Docs Build Workflow
## Updating the docs
If you want to open a PR on the Cosmos SDK to update the documentation, please follow the guidelines in the [`CONTRIBUTING.md`](https://github.com/cosmos/cosmos-sdk/tree/master/CONTRIBUTING.md)
## Docs Build Workflow
The documentation for the Cosmos SDK is hosted at:
@@ -15,34 +19,34 @@ Besides, gaia-lite API docs are also provided by gaia-lite. The default API docs
https://localhost:1317/swagger-ui/
```
## How It Works
### How It Works
There is a Jenkins job listening for changes in the `/docs` directory, on both
the `master` and `develop` branches. Any updates to files in this directory
on those branches will automatically trigger a website deployment. Under the hood,
a private website repository has make targets consumed by a standard Jenkins task.
## README
### README
The [README.md](./README.md) is also the landing page for the documentation
on the website. During the Jenkins build, the current commit is added to the bottom
of the README.
## Config.js
### Config.js
The [config.js](./.vuepress/config.js) generates the sidebar and Table of Contents
on the website docs. Note the use of relative links and the omission of
file extensions. Additional features are available to improve the look
of the sidebar.
## Links
### Links
**NOTE:** Strongly consider the existing links - both within this directory
and to the website docs - when moving or deleting files.
Relative links should be used nearly everywhere, having discovered and weighed the following:
### Relative
#### Relative
Where is the other file, relative to the current one?
@@ -50,7 +54,7 @@ Where is the other file, relative to the current one?
- confusing / annoying to have things like: `../../../../myfile.md`
- requires more updates when files are re-shuffled
### Absolute
#### Absolute
Where is the other file, given the root of the repo?
@@ -58,12 +62,12 @@ Where is the other file, given the root of the repo?
- this is much nicer: `/docs/hereitis/myfile.md`
- if you move that file around, the links inside it are preserved (but not to it, of course)
### Full
#### Full
The full GitHub URL to a file or directory. Used occasionally when it makes sense
to send users to the GitHub.
## Building Locally
### Building Locally
To build and serve the documentation locally, run:
@@ -94,12 +98,12 @@ python -m SimpleHTTPServer 8080
then navigate to localhost:8080 in your browser.
## Consistency
### Consistency
Because the build processes are identical (as is the information contained herein), this file should be kept in sync as
much as possible with its [counterpart in the Tendermint Core repo](https://github.com/tendermint/tendermint/blob/develop/docs/DOCS_README.md).
## Update and Build the RPC docs
### Update and Build the RPC docs
1. Execute the following command at the root directory to install the swagger-ui generate tool.
```
+27 -13
View File
@@ -1,24 +1,38 @@
# Welcome to the Cosmos Docs!
# Welcome to the SDK Docs!
![cosmonaut reading the cosmos docs in space](./graphics/cosmos-docs.jpg)
Welcome to the SDK docs!
Cosmos is a decentralized network of independent parallel blockchains, each powered by classical BFT consensus algorithms like Tendermint.
The first blockchain in the Cosmos Network is the Cosmos Hub, whose native token is the Atom. Cosmos is a permission-less network, meaning that anybody can build a blockchain on it.
![cosmonaut reading the cosmos docs in space](./cosmos-docs.jpg)
Cosmos can interoperate with multiple other applications and cryptocurrencies. By creating a new zone, you can plug any blockchain system into the Cosmos hub and pass tokens back and forth between those zones, without the need for an intermediary.
## Learn the SDK
## Quick Start
### SDK Intro
- [Getting started with the SDK](./sdk/core/intro.md)
- [SDK Examples](../examples)
- [Join the testnet](./getting-started/join-testnet.md#run-a-full-node)
If you are a newcomer and would like to learn more about the Cosmos SDK, this **[intro](./intro/README.md)** is a good starting place.
### SDK tutorial
If you like to learn by doing, you can follow the **[SDK application tutorial](https://github.com/cosmos/sdk-application-tutorial)**. It showcases how to build an SDK-based blockchain from scratch, and teaches you about the basic principles the SDK in the process.
## Use the SDK
The following sections contain the information you need if you want to build a fully-functional SDK-based blockchain:
>*NOTE*: We are currently working on improving the docs. Some info might be missing. If that is the case, try the Cosmos [Forum](https://forum.cosmos.network). Failing that, [open an issue](https://github.com/cosmos/cosmos-sdk/issues/new).
- [Introduction](./intro/README.md): Contains introductory high-level material on the Cosmos SDK.
- [Gaia](./gaia/README.md): Contains all documentation related to the gaia application (current name for the Cosmos-Hub).
- [Clients](./clients/README.md): Documentation about SDK clients like the SDK Command-Line interface and the SDK Light-client.
- [Specifications](./spec/README.md): Contains SDK and modules specifications.
If you are reading this on the Cosmos Website, please know that you can find more information on [github](https://github.com/cosmos/cosmos-sdk/tree/develop/docs). Also if you find any issues with the documentation please [*open a Pull Request*](https://github.com/cosmos/cosmos-sdk/compare?expand=1), or at least [*open an Issue*](https://github.com/cosmos/cosmos-sdk/issues/new) to update the docs!
## Join the public testnet for the Cosmos Hub
To install the latest version of the `gaia` application and join the public testnet, **click [here](./gaia/README.md#join-the-cosmos-hub-public-testnet)**
## Edit the Documentation
See [this file](./DOCS_README.md) for details of the build process and
considerations when making changes.
## Version
This documentation is built from the following commit:
@@ -0,0 +1,94 @@
# ADR 002: SDK Documentation Structure
## Context
There is a need for a scalable structure of the SDK documentation. Current documentation includes a lot of non-related SDK material, is difficult to maintain and hard to follow as a user.
Ideally, we would have:
- All docs related to dev frameworks or tools live in their respective github repos (sdk repo would contain sdk docs, hub repo would contain hub docs, lotion repo would contain lotion docs, etc.)
- All other docs (faqs, whitepaper, high-level material about Cosmos) would live on the website.
## Decision
Re-structure the `/docs` folder of the SDK github repo as follows:
```
docs/
├── README
├── intro/
├── gaia/
│ ├── overview.md
│ ├── install.md
│ ├── join-testnet.md
│ ├── validator-node.md
│ ├── validator-faq.md
│ └── delegator-faq.md
├── reference/
│ ├── baseapp
│ ├── types
│ ├── store
│ ├── server
│ ├── modules/
│ │ ├── keeper
│ │ ├── handler
│ │ ├── cli
│ ├── gas
│ └── commands
├── examples/
│ ├── basecoin/
│ └── democoin/
├── clients/
│ ├── lite/
│ ├── service-providers
├── spec/
└── architecture/
```
The files in each sub-folders do not matter and will likely change. What matters is the sectioning:
- `README`: Landing page of the docs.
- `into`: Introductory material. Goal is to have a short explainer of the SDK and then channel people to the resource they need. The [sdk-tutorial](https://github.com/cosmos/sdk-application-tutorial/) will be highlighted, as well as the `godocs`.
- `gaia`: Contains all docs related to the `gaia` application. Will later be renamed to `cosmos-hub` or `chub` and probably moved to its own repository.
- `reference`: Contains high-level explanations of the abstractions of the SDK. It does not contain specific code implementation and does not need to be updated often. **It is not an API specification of the interfaces**. API spec is the `godoc`.
- `examples`: Contain a couple examples of sdk application like `basecoin` and `democoin`. Developers need to maintain them up-to-date and make sure they compile as the SDK gets upgraded.
- `clients`: Contains specs and info about the various SDK clients.
- `spec`: Contains specs of modules, and others.
- `architecture`: Contains architecture-related docs like the present one.
Website docs sidebar will only include the following sections:
- `README`
- `intro`
- `gaia`
- `reference`
- `clients`
`architecture` and `examples` need not be displayed on the website. As for `modules`, we might need to think about creating a modules manager, but this is out of scope for this document.
## Status
Proposed
## Consequences
### Positive
- Much clearer organisation of the SDK docs.
- The `/docs` folder now only contains SDK and gaia related material. Later, it will only contain SDK related material.
- Developers only have to update `/docs` folder when they open a PR (and not `/examples` for example).
- Easier for developers to find what they need to update in the docs thanks to reworked architecture.
- Cleaner vuepress build for website docs.
- Will help build an executable doc (cf https://github.com/cosmos/cosmos-sdk/issues/2611)
### Neutral
- We need to move a bunch of deprecated stuff to `/_attic` folder.
- We need to integrate content in `docs/sdk/docs/core` in `reference`.
- We need to move all the content that currently lives in `docs` and does not fit in new structure (like `lotion`, intro material, whitepaper) to the website repository.
- Update `DOCS_README.md`
## References
- https://github.com/cosmos/cosmos-sdk/issues/1460
- https://github.com/cosmos/cosmos-sdk/pull/2695
- https://github.com/cosmos/cosmos-sdk/issues/2611
@@ -1,22 +0,0 @@
# Architecture Decision Records (ADR)
This is a location to record all high-level architecture decisions in the cosmos-sdk project.
You can read more about the ADR concept in this [blog post](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t).
An ADR should provide:
- Context on the relevant goals and the current state
- Proposed changes to achieve the goals
- Summary of pros and cons
- References
- Changelog
Note the distinction between an ADR and a spec. The ADR provides the context, intuition, reasoning, and
justification for a change in architecture, or for the architecture of something
new. The spec is much more compressed and streamlined summary of everything as
it stands today.
If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match.
Note the context/background should be written in the present tense.
@@ -1,54 +0,0 @@
# ADR 001: Global Message Counter
## Context
There is a desire for modules to have a concept of orderings between messages.
One such example is in staking, we currently use an "intra bond tx counter" and
bond height.
The purpose these two serve is to providing an ordering for validators with equal stake,
for usage in the power-ranking of validators.
We can't use address here, as that would create a bad incentive to grind
addresses that optimized the sort function, which lowers the private key's
security.
Instead we order by whose transaction appeared first, as tracked by bondHeight
and intra bond tx counter.
This logic however should not be unique to staking.
It is very conceivable that many modules in the future will want to be able to
know the ordering of messages / objects after they were initially created.
## Decision
Create a global message counter field of type int64.
Note that with int64's, there is no fear of overflow under normal use,
as it is only getting incremented by one,
and thus has a space of 9 quintillion values to go through.
This counter must be persisted in state, but can just be read and written on
begin/end block respectively.
This field will get incremented upon every DeliverTx,
regardless if the transaction succeeds or not.
It must also be incremented within the check state for CheckTx.
The global message ordering field should be set within the context
so that modules can access it.
## Corollary - Intra block ordering
In the event that there is desire to just have an intra block msg counter,
this can easily be derived from the global message counter.
Simply subtract current counter from first global message counter in the block.
Thus the relevant module could easily implement this.
## Status
Proposed
## Consequences
### Positive
* Moves message ordering out of the set of things staking must keep track of
* Abstracts the logic well so other modules can use it
### Negative
* Another thing to implement prelaunch. (Though this should be easy to implement)
### Neutral
@@ -1,32 +0,0 @@
# ADR {ADR-NUMBER}: {TITLE}
## Changelog
* {date}: {changelog}
## Context
> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution.
## Decision
> This section explains all of the details of the proposed solution, including implementation details.
It should also describe affects / corollary items that may need to be changed as a part of this.
If the proposed change will be large, please also indicate a way to do the change to maximize ease of review.
(e.g. the optimal split of things to do between separate PR's)
## Status
> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement.
{Deprecated|Proposed|Accepted}
## Consequences
> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones.
### Positive
### Negative
### Neutral
## References
> Are there any relevant PR comments, issues that led up to this, or articles referrenced for why we made the given design choice? If so link them here!
* {reference link}
+18
View File
@@ -0,0 +1,18 @@
# Clients
This section explains contains information on clients for SDK based blockchain.
>*NOTE*: This section is a WIP.
## Light-client
Light-clients enable users to interact with your application without having to download the entire state history but with a good level of security.
- [Overview of light clients](./lite/README.md)
- [Starting a light-client server](./lite/getting_started.md)
- [Light-client specification](./lite/specification.md)
## Other clients
- [Command-Line interface for SDK-based blockchain](./cli.md)
- [Service provider doc](./service-providers.md)
+1 -6
View File
@@ -1,8 +1,3 @@
# CLI
See `gaiacli --help` for more details.
Also see the [testnet
tutorial](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets).
TODO: cleanup the UX and document this properly.
> TODO: Rewrite this section to explain how CLI works for a generic SDK app.
@@ -13,7 +13,7 @@ To start a REST server, we need to specify the following parameters:
For example::
```bash
gaiacli rest-server --chain-id=test \
gaiacli advanced rest-server --chain-id=test \
--laddr=tcp://localhost:1317 \
--node tcp://localhost:26657 \
--trust-node=false
@@ -22,7 +22,7 @@ gaiacli rest-server --chain-id=test \
The server listens on HTTPS by default. You can set the SSL certificate to be used by the server with these additional flags:
```bash
gaiacli rest-server --chain-id=test \
gaiacli advanced rest-server --chain-id=test \
--laddr=tcp://localhost:1317 \
--node tcp://localhost:26657 \
--trust-node=false \

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

@@ -16,65 +16,61 @@ maintaining any full blockchain nodes.
### What is a lite Client
The LCD is split into two separate components. The first component is generic for any Tendermint
The Cosmos SDK Light Client (Gaia-lite) is split into two separate components. The first component is generic for any Tendermint
based application. It handles the security and connectivity aspects of following the header chain
and verify proofs from full nodes against locally trusted validator set. Furthermore it exposes
exactly the same API as any Tendermint Core node. The second component is specific for the Cosmos
Hub (Gaiad). It works as a query endpoint and exposes the application specific functionality, which
Hub (`gaiad`). It works as a query endpoint and exposes the application specific functionality, which
can be arbitrary. All queries against the application state have to go through the query endpoint.
The advantage of the query endpoint is that it can verify the proofs that the application returns.
### High-Level Architecture
An application developer that would like to build a third party integration can ship his application
with the LCD for the Cosmos Hub (or any other zone) and only needs to initialise it. Afterwards his
application can interact with the zone as if it was running against a full node.
![high-level](pics/high-level.png)
An application developer that wants to build an third party application for the Cosmos Hub (or any
other zone) should build it against it's canonical API. That API is a combination of multiple parts.
An application developer that wants to build a third party client application for the Cosmos Hub (or any
other zone) should build it against its canonical API. That API is a combination of multiple parts.
All zones have to expose ICS0 (TendermintAPI). Beyond that any zone is free to choose any
combination of module APIs, depending on which modules the state machine uses. The Cosmos Hub will
initially support ICS0 (TendermintAPI), ICS1 (KeyAPI), ICS20 (TokenAPI), ICS21 (StakingAPI) and
ICS22 (GovernanceAPI).
initially support [ICS0](https://cosmos.network/rpc/#/ICS0) (TendermintAPI), [ICS1](https://cosmos.network/rpc/#/ICS1) (KeyAPI), [ICS20](https://cosmos.network/rpc/#/ICS20) (TokenAPI), [ICS21](https://cosmos.network/rpc/#/ICS21) (StakingAPI),
[ICS22](https://cosmos.network/rpc/#/ICS22) (GovernanceAPI) and [ICS23](https://cosmos.network/rpc/#/ICS23) (SlashingAPI).
All applications are expected to only run against the LCD. The LCD is the only piece of software
![high-level](./pics/high-level.png)
All applications are expected to only run against Gaia-lite. Gaia-lite is the only piece of software
that offers stability guarantees around the zone API.
### Comparision
A full node of ABCI is different from its lite client in the following ways:
|| Full Node | LCD | Description|
|| Full Node | Gaia-lite | Description|
|-| ------------- | ----- | -------------- |
| Execute and verify transactions|Yes|No|Full node will execute and verify all transactions while LCD won't|
| Verify and save blocks|Yes|No|Full node will verify and save all blocks while LCD won't|
| Participate consensus| Yes|No|Only when the full node is a validtor, it will participate consensus. LCD nodes never participate consensus|
| Bandwidth cost|Huge|Little|Full node will receive all blocks. if the bandwidth is limited, it will fall behind the main network. What's more, if it happens to be a validator,it will slow down the consensus process. LCD requires little bandwidth. Only when serving local request, it will cost bandwidth|
| Execute and verify transactions|Yes|No|Full node will execute and verify all transactions while Gaia-lite won't|
| Verify and save blocks|Yes|No|Full node will verify and save all blocks while Gaia-lite won't|
| Participate consensus| Yes|No|Only when the full node is a validtor, it will participate consensus. Lite nodes never participate consensus|
| Bandwidth cost|Huge|Little|Full node will receive all blocks. if the bandwidth is limited, it will fall behind the main network. What's more, if it happens to be a validator,it will slow down the consensus process. Light clients requires little bandwidth. Only when serving local request, it will cost bandwidth|
| Computing resource|Huge|Little|Full node will execute all transactions and verify all blocks which require much computing resource|
| Storage resource|Huge|Little|Full node will save all blocks and ABCI states. LCD just saves validator sets and some checkpoints|
| Power consume|Huge|Little|Full nodes have to be deployed on machines which have high performance and will be running all the time. So power consume will be huge. LCD can be deployed on the same machines as users' applications, or on independent machines but with poor performance. Besides, LCD can be shutdown anytime when necessary. So LCD only consume very little power, even mobile devices can meet the power requirement|
| Provide APIs|All cosmos APIs|Modular APIs|Full node supports all cosmos APIs. LCD provides modular APIs according to users' configuration|
| Secuity level| High|High|Full node will verify all transactions and blocks by itself. LCD can't do this, but it can query any data from other full nodes and verify the data independently. So both full node and LCD don't need to trust any third nodes, they all can achieve high security|
| Storage resource|Huge|Little|Full node will save all blocks and ABCI states. Gaia-lite just saves validator sets and some checkpoints|
| Power consume|Huge|Little|Full nodes have to be deployed on machines which have high performance and will be running all the time. So power consume will be huge. Gaia-lite can be deployed on the same machines as users' applications, or on independent machines but with poor performance. Besides, lite clients can be shutdown anytime when necessary. So Gaia-lite only consume very little power, even mobile devices can meet the power requirement|
| Provide APIs|All cosmos APIs|Modular APIs|Full node supports all cosmos APIs. Gaia-lite provides modular APIs according to users' configuration|
| Secuity level| High|High|Full node will verify all transactions and blocks by itself. A light client can't do this, but it can query any data from other full nodes and verify the data independently. So both full nodes and light clients don't need to trust any third nodes, they all can achieve high security|
According to the above table, LCD can meet all users' functionality and security requirements, but
According to the above table, Gaia-lite can meet all users' functionality and security requirements, but
only requires little resource on bandwidth, computing, storage and power.
## Achieving Security
### Trusted Validator Set
The base design philosophy of the LCD follows two rules:
The base design philosophy of Gaia-lite follows two rules:
1. **Doesn't trust any blockchain nodes, including validator nodes and other full nodes**
2. **Only trusts the whole validator set**
The original trusted validator set should be prepositioned into its trust store, usually this
validator set comes from genesis file. During runtime, if LCD detects a different validator set,
validator set comes from genesis file. During runtime, if Gaia-lite detects a different validator set,
it will verify it and save new validated validator set to the trust store.
![validator-set-change](pics/validatorSetChange.png)
![validator-set-change](./pics/validatorSetChange.png)
### Trust Propagation
@@ -83,8 +79,8 @@ validator set evolution. Validator set is the foundation of trust, and the trust
other blockchain data, such as block and transaction. The propagate architecture is shown as
follows:
![change-process](pics/trustPropagate.png)
![change-process](./pics/trustPropagate.png)
In general, by trusted validator set, LCD can verify each block commit which contains all pre-commit
In general, by trusted validator set, a light client can verify each block commit which contains all pre-commit
data and block header data. Then the block hash, data hash and appHash are trusted. Based on this
and merkle proof, all transactions data and ABCI states can be verified too.
@@ -12,7 +12,7 @@ we need to extract name, height and store root hash from these substores to buil
Merkle leaf nodes, then calculate hash from leaf nodes to root. The root hash of the simple Merkle
tree is the AppHash which will be included in block header.
![Simple Merkle Tree](pics/simpleMerkleTree.png)
![Simple Merkle Tree](./pics/simpleMerkleTree.png)
As we have discussed in [LCD trust-propagation](https://github.com/irisnet/cosmos-sdk/tree/bianjie/lcd_spec/docs/spec/lcd#trust-propagation),
the AppHash can be verified by checking voting power against a trusted validator set. Here we just
@@ -65,7 +65,7 @@ type KeyExistsProof struct {
The data structure of exist proof is shown as above. The process to build and verify existance proof
is shown as follows:
![Exist Proof](pics/existProof.png)
![Exist Proof](./pics/existProof.png)
Steps to build proof:
@@ -92,12 +92,12 @@ the postition of the target key in the whole key set of this IAVL tree. As shown
out the left key and the right key. If we can demonstrate that both left key and right key
definitely exist, and they are adjacent nodes. Thus the target key definitely doesn't exist.
![Absence Proof1](pics/absence1.png)
![Absence Proof1](./pics/absence1.png)
If the target key is larger than the right most leaf node or less than the left most key, then the
target key definitely doesn't exist.
![Absence Proof2](pics/absence2.png)![Absence Proof3](pics/absence3.png)
![Absence Proof2](./pics/absence2.png)![Absence Proof3](./pics/absence3.png)
```go
type proofLeafNode struct {
@@ -147,7 +147,7 @@ in commitID equals to proof RootHash. If not, the proof is invalid. Then sort th
commitInfo array by the hash of substore name. Finally, build the simple Merkle tree with all
substore commitInfo array and verify if the Merkle root hash equal to appHash.
![substore proof](pics/substoreProof.png)
![substore proof](./pics/substoreProof.png)
```go
func SimpleHashFromTwoHashes(left []byte, right []byte) []byte {
@@ -187,7 +187,7 @@ Above sections refer appHash frequently. But where does the trusted appHash come
appHash exist in block header, so next we need to verify blocks header at specific height against
LCD trusted validator set. The validation flow is shown as follows:
![commit verification](pics/commitValidation.png)
![commit verification](./pics/commitValidation.png)
When the trusted validator set doesn't match the block header, we need to try to update our
validator set to the height of this block. LCD have a rule that each validator set change should not
@@ -198,7 +198,7 @@ validator set update be accomplished.
For instance:
![Update validator set to height](pics/updateValidatorToHeight.png)
![Update validator set to height](./pics/updateValidatorToHeight.png)
* Update to 10000, tooMuchChangeErr
* Update to 5050, tooMuchChangeErr
-6
View File
@@ -1,6 +0,0 @@
# REST
See `gaiacli rest-server --help` for more.
Also see the
[work in progress API specification](https://github.com/cosmos/cosmos-sdk/pull/1314)
+4 -2
View File
@@ -83,7 +83,7 @@ It will display all the available commands. For each command, you can use the `-
## Setting up the Rest Server
The Rest Server acts as an intermediary between the front-end and the full-node. You don't need to run the Rest Server on the same machine as the full-node. If you intend to run the Rest Server on another machine, you need to go through the [Installation and configuration](#installation-and-configuration) again on this machine.
The Rest Server acts as an intermediary between the front-end and the full-node. You don't need to run the Rest Server on the same machine as the full-node.
To start the Rest server:
@@ -109,4 +109,6 @@ The Rest API documents all the available endpoints that you can use to interract
The API is divided into ICS standards for each category of endpoints. For example, the [ICS20](https://cosmos.network/rpc/#/ICS20/) describes the API to interact with tokens.
To give more flexibility to implementers, we have separated the different steps that are involved in the process of sending transactions. You will be able to generate unsigned transactions (example with [coin transfer](https://cosmos.network/rpc/#/ICS20/post_bank_accounts__address__transfers)), [sign](https://cosmos.network/rpc/#/ICS20/post_tx_sign) and [broadcast](https://cosmos.network/rpc/#/ICS20/post_tx_broadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance.
To give more flexibility to implementers, we have included the ability to generate unsigned transactions, [sign](https://cosmos.network/rpc/#/ICS20/post_tx_sign) and [broadcast](https://cosmos.network/rpc/#/ICS20/post_tx_broadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance.
In order to generate an unsigned transaction (example with [coin transfer](https://cosmos.network/rpc/#/ICS20/post_bank_accounts__address__transfers)), you need to use the flag `?generate_only`.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 283 KiB

+320
View File
@@ -0,0 +1,320 @@
# Basecoin Example
Here we explain how to get started with a basic Basecoin blockchain, how
to send transactions between accounts using the ``basecli`` tool, and
what is happening under the hood.
## Setup and Install
You will need to have go installed on your computer. Please refer to the [cosmos testnet tutorial](https://cosmos.network/validators/tutorial), which will always have the most updated instructions on how to get setup with go and the cosmos repository.
Once you have go installed, run the command:
```
go get github.com/cosmos/cosmos-sdk
```
There will be an error stating `can't load package: package github.com/cosmos/cosmos-sdk: no Go files`, however you can ignore this error, it doesn't affect us. Now change directories to:
```
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
```
And run :
```
make get_tools // run make update_tools if you already had it installed
make get_vendor_deps
make install_examples
```
Then run `make install_examples`, which creates binaries for `basecli` and `basecoind`. You can look at the Makefile if you want to see the details on what these make commands are doing.
## Using basecli and basecoind
Check the versions by running:
```
basecli version
basecoind version
```
They should read something like `0.17.1-5d18d5f`, but the versions will be constantly updating so don't worry if your version is higher that 0.17.1. That's a good thing.
Note that you can always check help in the terminal by running `basecli -h` or `basecoind -h`. It is good to check these out if you are stuck, because updates to the code base might slightly change the commands, and you might find the correct command in there.
Let's start by initializing the basecoind daemon. Run the command
```
basecoind init
```
And you should see something like this:
```
{
"chain_id": "test-chain-z77iHG",
"node_id": "e14c5056212b5736e201dd1d64c89246f3288129",
"app_message": {
"secret": "pluck life bracket worry guilt wink upgrade olive tilt output reform census member trouble around abandon"
}
}
```
This creates the `~/.basecoind folder`, which has config.toml, genesis.json, node_key.json, priv_validator.json. Take some time to review what is contained in these files if you want to understand what is going on at a deeper level.
## Generating keys
The next thing we'll need to do is add the key from priv_validator.json to the gaiacli key manager. For this we need the 16 word seed that represents the private key, and a password. You can also get the 16 word seed from the output seen above, under `"secret"`. Then run the command:
```
basecli keys add alice --recover
```
Which will give you three prompts:
```
Enter a passphrase for your key:
Repeat the passphrase:
Enter your recovery seed phrase:
```
You just created your first locally stored key, under the name alice, and this account is linked to the private key that is running the basecoind validator node. Once you do this, the ~/.basecli folder is created, which will hold the alice key and any other keys you make. Now that you have the key for alice, you can start up the blockchain by running
```
basecoind start
```
You should see blocks being created at a fast rate, with a lot of output in the terminal.
Next we need to make some more keys so we can use the send transaction functionality of basecoin. Open a new terminal, and run the following commands, to make two new accounts, and give each account a password you can remember:
```
basecli keys add bob
basecli keys add charlie
```
You can see your keys with the command:
```
basecli keys list
```
You should now see alice, bob and charlie's account all show up.
```
NAME: ADDRESS: PUBKEY:
alice cosmos1khygs0qh7gz3p4m39u00mjhvgvc2dcpxhsuh5f cosmospub1addwnpepq0w037u5g7y7lvdvsred2dehg90j84k0weyss5ynysf0nnnax74agrsxns6
bob cosmos18se8tz6kwwfga6k2yjsu7n64e9z52nen29rhzz cosmospub1addwnpepqwe97n8lryxrzvamrvjfj24jys3uzf8wndfvqa2l7mh5nsv4jrvdznvyeg6
charlie cosmos13wq5mklhn03ljpd4dkph5rflk5a3ssma2ag07q cosmospub1addwnpepqdmtxv35rrmv2dvcr3yhfyxj7dzrd4z4rnhmclksq4g55a4wpl54clvx33l
```
## Send transactions
Lets send bob and charlie some tokens. First, lets query alice's account so we can see what kind of tokens she has:
```
basecli account cosmos1khygs0qh7gz3p4m39u00mjhvgvc2dcpxhsuh5f
```
Where `cosmos1khygs0qh7gz3p4m39u00mjhvgvc2dcpxhsuh5f` is alice's address we got from running `basecli keys list`. You should see a large amount of "mycoin" there. If you search for bob's or charlie's address, the command will fail, because they haven't been added into the blockchain database yet since they have no coins. We need to send them some!
The following command will send coins from alice, to bob:
```
basecli send --from=alice --amount=10000mycoin --to=cosmos18se8tz6kwwfga6k2yjsu7n64e9z52nen29rhzz
--sequence=0 --chain-id=test-chain-AE4XQo
```
Flag Descriptions:
- `from` is the name you gave your key
- `mycoin` is the name of the token for this basecoin demo, initialized in the genesis.json file
- `sequence` is a tally of how many transactions have been made by this account. Since this is the first tx on this account, it is 0
- `chain-id` is the unique ID that helps tendermint identify which network to connect to. You can find it in the terminal output from the gaiad daemon in the header block , or in the genesis.json file at `~/.basecoind/config/genesis.json`
Now if we check bobs account, it should have `10000 mycoin`. You can do so by running :
```
basecli account cosmos18se8tz6kwwfga6k2yjsu7n64e9z52nen29rhzz
```
Now lets send some from bob to charlie. Make sure you send less than bob has, otherwise the transaction will fail:
```
basecli send --from=bob --amount=5000mycoin --to=cosmos13wq5mklhn03ljpd4dkph5rflk5a3ssma2ag07q
--sequence=0 --chain-id=test-chain-AE4XQo
```
Note how we use the ``--from`` flag to select a different account to send from.
Lets now try to send from bob back to alice:
```
basecli send --from=bob --amount=3000mycoin --to=cosmos1khygs0qh7gz3p4m39u00mjhvgvc2dcpxhsuh5f
--sequence=1 --chain-id=test-chain-AE4XQo
```
Notice that the sequence is now 1, since we have already recorded bobs 1st transaction as `sequence 0`. Also note the ``hash`` value in the response in the terminal - this is the hash of the transaction. We can query for the transaction with this command:
```
basecli tx <INSERT HASH HERE>
```
It will return the details of the transaction hash, such as how many coins were send and to which address, and on what block it occurred.
That is the basic implementation of basecoin!
## Reset the basecoind blockchain and basecli data
**WARNING:** Running these commands will wipe out any existing
information in both the ``~/.basecli`` and ``~/.basecoind`` directories,
including private keys. This should be no problem considering that basecoin
is just an example, but it is always good to pay extra attention when
you are removing private keys, in any scenario involving a blockchain.
To remove all the files created and refresh your environment (e.g., if
starting this tutorial again or trying something new), the following
commands are run:
```
basecoind unsafe-reset-all
rm -rf ~/.basecoind
rm -rf ~/.basecli
```
## Technical Details on how Basecoin Works
This section describes some of the more technical aspects for what is going on under the hood of Basecoin.
## Proof
Even if you don't see it in the UI, the result of every query comes with
a proof. This is a Merkle proof that the result of the query is actually
contained in the state. And the state's Merkle root is contained in a
recent block header. Behind the scenes, ``basecli`` will not only
verify that this state matches the header, but also that the header is
properly signed by the known validator set. It will even update the
validator set as needed, so long as there have not been major changes
and it is secure to do so. So, if you wonder why the query may take a
second... there is a lot of work going on in the background to make sure
even a lying full node can't trick your client.
## Accounts and Transactions
For a better understanding of how to further use the tools, it helps to
understand the underlying data structures, so lets look at accounts and transactions.
### Accounts
The Basecoin state consists entirely of a set of accounts. Each account
contains an address, a public key, a balance in many different coin denominations,
and a strictly increasing sequence number for replay protection. This
type of account was directly inspired by accounts in Ethereum, and is
unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs).
```
type BaseAccount struct {
Address sdk.Address `json:"address"`
Coins sdk.Coins `json:"coins"`
PubKey crypto.PubKey `json:"public_key"`
Sequence int64 `json:"sequence"`
}
```
You can also add more fields to accounts, and basecoin actually does so. Basecoin
adds a Name field in order to show how easily the base account structure can be
modified to suit any applications needs. It takes the `auth.BaseAccount` we see above,
and extends it with `Name`.
```
type AppAccount struct {
auth.BaseAccount
Name string `json:"name"`
}
```
Within accounts, coin balances are stored. Basecoin is a multi-asset cryptocurrency, so each account can have many
different kinds of tokens, which are held in an array.
```
type Coins []Coin
type Coin struct {
Denom string `json:"denom"`
Amount int64 `json:"amount"`
}
```
If you want to add more coins to a blockchain, you can do so manually in
the ``~/.basecoin/genesis.json`` before you start the blockchain for the
first time.
Accounts are serialized and stored in a Merkle tree under the key
``base/a/<address>``, where ``<address>`` is the address of the account.
Typically, the address of the account is the first 20-bytes of the ``sha256`` hash
of the public key, but other formats are acceptable as well, as defined
in the `Tendermint crypto
library <https://github.com/tendermint/tendermint/tree/master/crypto>`__. The Merkle tree
used in Basecoin is a balanced, binary search tree, which we call an
`IAVL tree <https://github.com/tendermint/iavl>`__.
### Transactions
Basecoin defines a transaction type, the `SendTx`, which allows tokens
to be sent to other accounts. The `SendTx` takes a list of inputs and
a list of outputs, and transfers all the tokens listed in the inputs
from their corresponding accounts to the accounts listed in the output.
The `SendTx` is structured as follows:
```
type SendTx struct {
Gas int64 `json:"gas"`
Fee Coin `json:"fee"`
Inputs []TxInput `json:"inputs"`
Outputs []TxOutput `json:"outputs"`
}
type TxInput struct {
Address []byte `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` //
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
Signature []byte `json:"signature"` // Depends on the PubKey type and the whole Tx
PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0
}
type TxOutput struct {
Address []byte `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` //
}
```
Note the `SendTx` includes a field for `Gas` and `Fee`. The
`Gas` limits the total amount of computation that can be done by the
transaction, while the `Fee` refers to the total amount paid in fees.
This is slightly different from Ethereum's concept of `Gas` and
`GasPrice`, where `Fee = Gas x GasPrice`. In Basecoin, the `Gas`
and `Fee` are independent, and the `GasPrice` is implicit.
In Basecoin, the `Fee` is meant to be used by the validators to inform
the ordering of transactions, like in Bitcoin. And the `Gas` is meant
to be used by the application plugin to control its execution. There is
currently no means to pass `Fee` information to the Tendermint
validators, but it will come soon... so this version of Basecoin does
not actually fully implement fees and gas, but it still allows us
to send transactions between accounts.
Note also that the `PubKey` only needs to be sent for
`Sequence == 0`. After that, it is stored under the account in the
Merkle tree and subsequent transactions can exclude it, using only the
`Address` to refer to the sender. Ethereum does not require public
keys to be sent in transactions as it uses a different elliptic curve
scheme which enables the public key to be derived from the signature
itself.
Finally, note that the use of multiple inputs and multiple outputs
allows us to send many different types of tokens between many different
accounts at once in an atomic transaction. Thus, the `SendTx` can
serve as a basic unit of decentralized exchange. When using multiple
inputs and outputs, you must make sure that the sum of coins of the
inputs equals the sum of coins of the outputs (no creating money), and
that all accounts that provide inputs have signed the transaction.
+189
View File
@@ -0,0 +1,189 @@
package app
import (
"encoding/json"
"os"
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/basecoin/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/ibc"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
)
const (
appName = "BasecoinApp"
)
// default home directories for expected binaries
var (
DefaultCLIHome = os.ExpandEnv("$HOME/.basecli")
DefaultNodeHome = os.ExpandEnv("$HOME/.basecoind")
)
// BasecoinApp implements an extended ABCI application. It contains a BaseApp,
// a codec for serialization, KVStore keys for multistore state management, and
// various mappers and keepers to manage getting, setting, and serializing the
// integral app types.
type BasecoinApp struct {
*bam.BaseApp
cdc *codec.Codec
// keys to access the multistore
keyMain *sdk.KVStoreKey
keyAccount *sdk.KVStoreKey
keyIBC *sdk.KVStoreKey
// manage getting and setting accounts
accountKeeper auth.AccountKeeper
feeCollectionKeeper auth.FeeCollectionKeeper
bankKeeper bank.Keeper
ibcMapper ibc.Mapper
}
// NewBasecoinApp returns a reference to a new BasecoinApp given a logger and
// database. Internally, a codec is created along with all the necessary keys.
// In addition, all necessary mappers and keepers are created, routes
// registered, and finally the stores being mounted along with any necessary
// chain initialization.
func NewBasecoinApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseApp)) *BasecoinApp {
// create and register app-level codec for TXs and accounts
cdc := MakeCodec()
// create your application type
var app = &BasecoinApp{
cdc: cdc,
BaseApp: bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...),
keyMain: sdk.NewKVStoreKey("main"),
keyAccount: sdk.NewKVStoreKey("acc"),
keyIBC: sdk.NewKVStoreKey("ibc"),
}
// define and attach the mappers and keepers
app.accountKeeper = auth.NewAccountKeeper(
cdc,
app.keyAccount, // target store
func() auth.Account {
return &types.AppAccount{}
},
)
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper)
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
// register message routes
app.Router().
AddRoute("bank", bank.NewHandler(app.bankKeeper)).
AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.bankKeeper))
// perform initialization logic
app.SetInitChainer(app.initChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
// mount the multistore and load the latest state
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC)
err := app.LoadLatestVersion(app.keyMain)
if err != nil {
cmn.Exit(err.Error())
}
app.Seal()
return app
}
// MakeCodec creates a new codec codec and registers all the necessary types
// with the codec.
func MakeCodec() *codec.Codec {
cdc := codec.New()
codec.RegisterCrypto(cdc)
sdk.RegisterCodec(cdc)
bank.RegisterCodec(cdc)
ibc.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
// register custom type
cdc.RegisterConcrete(&types.AppAccount{}, "basecoin/Account", nil)
cdc.Seal()
return cdc
}
// BeginBlocker reflects logic to run before any TXs application are processed
// by the application.
func (app *BasecoinApp) BeginBlocker(_ sdk.Context, _ abci.RequestBeginBlock) abci.ResponseBeginBlock {
return abci.ResponseBeginBlock{}
}
// EndBlocker reflects logic to run after all TXs are processed by the
// application.
func (app *BasecoinApp) EndBlocker(_ sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock {
return abci.ResponseEndBlock{}
}
// initChainer implements the custom application logic that the BaseApp will
// invoke upon initialization. In this case, it will take the application's
// state provided by 'req' and attempt to deserialize said state. The state
// should contain all the genesis accounts. These accounts will be added to the
// application's account mapper.
func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes
genesisState := new(types.GenesisState)
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
if err != nil {
// TODO: https://github.com/cosmos/cosmos-sdk/issues/468
panic(err)
}
for _, gacc := range genesisState.Accounts {
acc, err := gacc.ToAppAccount()
if err != nil {
// TODO: https://github.com/cosmos/cosmos-sdk/issues/468
panic(err)
}
acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx)
app.accountKeeper.SetAccount(ctx, acc)
}
return abci.ResponseInitChain{}
}
// ExportAppStateAndValidators implements custom application logic that exposes
// various parts of the application's state and set of validators. An error is
// returned if any step getting the state or set of validators fails.
func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
ctx := app.NewContext(true, abci.Header{})
accounts := []*types.GenesisAccount{}
appendAccountsFn := func(acc auth.Account) bool {
account := &types.GenesisAccount{
Address: acc.GetAddress(),
Coins: acc.GetCoins(),
}
accounts = append(accounts, account)
return false
}
app.accountKeeper.IterateAccounts(ctx, appendAccountsFn)
genState := types.GenesisState{Accounts: accounts}
appState, err = codec.MarshalJSONIndent(app.cdc, genState)
if err != nil {
return nil, nil, err
}
return appState, validators, err
}
+81
View File
@@ -0,0 +1,81 @@
package app
import (
"os"
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/basecoin/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
)
func setGenesis(baseApp *BasecoinApp, accounts ...*types.AppAccount) (types.GenesisState, error) {
genAccts := make([]*types.GenesisAccount, len(accounts))
for i, appAct := range accounts {
genAccts[i] = types.NewGenesisAccount(appAct)
}
genesisState := types.GenesisState{Accounts: genAccts}
stateBytes, err := codec.MarshalJSONIndent(baseApp.cdc, genesisState)
if err != nil {
return types.GenesisState{}, err
}
// initialize and commit the chain
baseApp.InitChain(abci.RequestInitChain{
Validators: []abci.ValidatorUpdate{}, AppStateBytes: stateBytes,
})
baseApp.Commit()
return genesisState, nil
}
func TestGenesis(t *testing.T) {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
db := dbm.NewMemDB()
baseApp := NewBasecoinApp(logger, db)
// construct a pubkey and an address for the test account
pubkey := ed25519.GenPrivKey().PubKey()
addr := sdk.AccAddress(pubkey.Address())
// construct some test coins
coins, err := sdk.ParseCoins("77foocoin,99barcoin")
require.Nil(t, err)
// create an auth.BaseAccount for the given test account and set it's coins
baseAcct := auth.NewBaseAccountWithAddress(addr)
err = baseAcct.SetCoins(coins)
require.Nil(t, err)
// create a new test AppAccount with the given auth.BaseAccount
appAcct := types.NewAppAccount("foobar", baseAcct)
genState, err := setGenesis(baseApp, appAcct)
require.Nil(t, err)
// create a context for the BaseApp
ctx := baseApp.BaseApp.NewContext(true, abci.Header{})
res := baseApp.accountKeeper.GetAccount(ctx, baseAcct.Address)
require.Equal(t, appAcct, res)
// reload app and ensure the account is still there
baseApp = NewBasecoinApp(logger, db)
stateBytes, err := codec.MarshalJSONIndent(baseApp.cdc, genState)
require.Nil(t, err)
// initialize the chain with the expected genesis state
baseApp.InitChain(abci.RequestInitChain{
Validators: []abci.ValidatorUpdate{}, AppStateBytes: stateBytes,
})
ctx = baseApp.BaseApp.NewContext(true, abci.Header{})
res = baseApp.accountKeeper.GetAccount(ctx, baseAcct.Address)
require.Equal(t, appAcct, res)
}
@@ -0,0 +1,55 @@
package clitest
import (
"encoding/json"
"fmt"
"os"
"testing"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/stretchr/testify/require"
)
var (
basecoindHome = ""
basecliHome = ""
)
func init() {
basecoindHome, basecliHome = getTestingHomeDirs()
}
func TestInitStartSequence(t *testing.T) {
os.RemoveAll(basecoindHome)
servAddr, port, err := server.FreeTCPAddr()
require.NoError(t, err)
executeInit(t)
executeStart(t, servAddr, port)
}
func executeInit(t *testing.T) {
var (
chainID string
initRes map[string]json.RawMessage
)
_, stderr := tests.ExecuteT(t, fmt.Sprintf("basecoind --home=%s --home-client=%s init --name=test", basecoindHome, basecliHome), app.DefaultKeyPass)
err := json.Unmarshal([]byte(stderr), &initRes)
require.NoError(t, err)
err = json.Unmarshal(initRes["chain_id"], &chainID)
require.NoError(t, err)
}
func executeStart(t *testing.T, servAddr, port string) {
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("basecoind start --home=%s --rpc.laddr=%v", basecoindHome, servAddr))
defer proc.Stop(false)
tests.WaitForTMStart(port)
}
func getTestingHomeDirs() (string, string) {
tmpDir := os.TempDir()
basecoindHome := fmt.Sprintf("%s%s.test_basecoind", tmpDir, string(os.PathSeparator))
basecliHome := fmt.Sprintf("%s%s.test_basecli", tmpDir, string(os.PathSeparator))
return basecoindHome, basecliHome
}
@@ -0,0 +1,99 @@
package main
import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/lcd"
_ "github.com/cosmos/cosmos-sdk/client/lcd/statik"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/docs/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/docs/examples/basecoin/types"
"github.com/cosmos/cosmos-sdk/version"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli"
slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli"
stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/cli"
)
// rootCmd is the entry point for this binary
var (
rootCmd = &cobra.Command{
Use: "basecli",
Short: "Basecoin light-client",
}
)
func main() {
// disable sorting
cobra.EnableCommandSorting = false
// get the codec
cdc := app.MakeCodec()
// TODO: Setup keybase, viper object, etc. to be passed into
// the below functions and eliminate global vars, like we do
// with the cdc.
// add standard rpc, and tx commands
rootCmd.AddCommand(
rpc.InitClientCommand(),
rpc.StatusCommand(),
client.LineBreak,
tx.SearchTxCmd(cdc),
tx.QueryTxCmd(cdc),
client.LineBreak,
)
// add query/post commands (custom to binary)
rootCmd.AddCommand(
client.GetCommands(
stakecmd.GetCmdQueryValidator("stake", cdc),
stakecmd.GetCmdQueryValidators("stake", cdc),
stakecmd.GetCmdQueryValidatorUnbondingDelegations("stake", cdc),
stakecmd.GetCmdQueryValidatorRedelegations("stake", cdc),
stakecmd.GetCmdQueryDelegation("stake", cdc),
stakecmd.GetCmdQueryDelegations("stake", cdc),
stakecmd.GetCmdQueryPool("stake", cdc),
stakecmd.GetCmdQueryParams("stake", cdc),
stakecmd.GetCmdQueryUnbondingDelegation("stake", cdc),
stakecmd.GetCmdQueryUnbondingDelegations("stake", cdc),
stakecmd.GetCmdQueryRedelegation("stake", cdc),
stakecmd.GetCmdQueryRedelegations("stake", cdc),
slashingcmd.GetCmdQuerySigningInfo("slashing", cdc),
authcmd.GetAccountCmd("acc", cdc, types.GetAccountDecoder(cdc)),
)...)
rootCmd.AddCommand(
client.PostCommands(
bankcmd.SendTxCmd(cdc),
ibccmd.IBCTransferCmd(cdc),
ibccmd.IBCRelayCmd(cdc),
stakecmd.GetCmdCreateValidator(cdc),
stakecmd.GetCmdEditValidator(cdc),
stakecmd.GetCmdDelegate(cdc),
stakecmd.GetCmdUnbond("stake", cdc),
stakecmd.GetCmdRedelegate("stake", cdc),
slashingcmd.GetCmdUnjail(cdc),
)...)
// add proxy, version and key info
rootCmd.AddCommand(
client.LineBreak,
lcd.ServeCommand(cdc),
keys.Commands(),
client.LineBreak,
version.VersionCmd,
)
// prepare and add flags
executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome)
err := executor.Execute()
if err != nil {
// Note: Handle with #870
panic(err)
}
}
@@ -0,0 +1,133 @@
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/tendermint/tendermint/p2p"
"github.com/cosmos/cosmos-sdk/baseapp"
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/spf13/cobra"
"github.com/spf13/viper"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
)
const (
flagClientHome = "home-client"
)
func main() {
cdc := app.MakeCodec()
ctx := server.NewDefaultContext()
rootCmd := &cobra.Command{
Use: "basecoind",
Short: "Basecoin Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}
appInit := server.DefaultAppInit
rootCmd.AddCommand(InitCmd(ctx, cdc, appInit))
server.AddCommands(ctx, cdc, rootCmd, appInit,
newApp, exportAppStateAndTMValidators)
// prepare and add flags
rootDir := os.ExpandEnv("$HOME/.basecoind")
executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir)
err := executor.Execute()
if err != nil {
// Note: Handle with #870
panic(err)
}
}
// get cmd to initialize all files for tendermint and application
// nolint: errcheck
func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize genesis config, priv-validator file, and p2p-node file",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
config := ctx.Config
config.SetRoot(viper.GetString(cli.HomeFlag))
chainID := viper.GetString(client.FlagChainID)
if chainID == "" {
chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6))
}
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return err
}
nodeID := string(nodeKey.ID())
pk := gaiaInit.ReadOrCreatePrivValidator(config.PrivValidatorFile())
genTx, appMessage, validator, err := server.SimpleAppGenTx(cdc, pk)
if err != nil {
return err
}
appState, err := appInit.AppGenState(
cdc, tmtypes.GenesisDoc{}, []json.RawMessage{genTx})
if err != nil {
return err
}
appStateJSON, err := cdc.MarshalJSON(appState)
if err != nil {
return err
}
toPrint := struct {
ChainID string `json:"chain_id"`
NodeID string `json:"node_id"`
AppMessage json.RawMessage `json:"app_message"`
}{
chainID,
nodeID,
appMessage,
}
out, err := codec.MarshalJSONIndent(cdc, toPrint)
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "%s\n", string(out))
return gaiaInit.ExportGenesisFile(config.GenesisFile(), chainID,
[]tmtypes.GenesisValidator{validator}, appStateJSON)
},
}
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
cmd.Flags().String(client.FlagChainID, "",
"genesis file chain-id, if left blank will be randomly created")
cmd.Flags().String(client.FlagName, "", "validator's moniker")
cmd.MarkFlagRequired(client.FlagName)
return cmd
}
func newApp(logger log.Logger, db dbm.DB, storeTracer io.Writer) abci.Application {
return app.NewBasecoinApp(logger, db, baseapp.SetPruning(viper.GetString("pruning")))
}
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, storeTracer io.Writer) (
json.RawMessage, []tmtypes.GenesisValidator, error) {
bapp := app.NewBasecoinApp(logger, db)
return bapp.ExportAppStateAndValidators()
}
+81
View File
@@ -0,0 +1,81 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
)
var _ auth.Account = (*AppAccount)(nil)
// AppAccount is a custom extension for this application. It is an example of
// extending auth.BaseAccount with custom fields. It is compatible with the
// stock auth.AccountKeeper, since auth.AccountKeeper uses the flexible go-amino
// library.
type AppAccount struct {
auth.BaseAccount
Name string `json:"name"`
}
// nolint
func (acc AppAccount) GetName() string { return acc.Name }
func (acc *AppAccount) SetName(name string) { acc.Name = name }
// NewAppAccount returns a reference to a new AppAccount given a name and an
// auth.BaseAccount.
func NewAppAccount(name string, baseAcct auth.BaseAccount) *AppAccount {
return &AppAccount{BaseAccount: baseAcct, Name: name}
}
// GetAccountDecoder returns the AccountDecoder function for the custom
// AppAccount.
func GetAccountDecoder(cdc *codec.Codec) auth.AccountDecoder {
return func(accBytes []byte) (auth.Account, error) {
if len(accBytes) == 0 {
return nil, sdk.ErrTxDecode("accBytes are empty")
}
acct := new(AppAccount)
err := cdc.UnmarshalBinaryBare(accBytes, &acct)
if err != nil {
panic(err)
}
return acct, err
}
}
// GenesisState reflects the genesis state of the application.
type GenesisState struct {
Accounts []*GenesisAccount `json:"accounts"`
}
// GenesisAccount reflects a genesis account the application expects in it's
// genesis state.
type GenesisAccount struct {
Name string `json:"name"`
Address sdk.AccAddress `json:"address"`
Coins sdk.Coins `json:"coins"`
}
// NewGenesisAccount returns a reference to a new GenesisAccount given an
// AppAccount.
func NewGenesisAccount(aa *AppAccount) *GenesisAccount {
return &GenesisAccount{
Name: aa.Name,
Address: aa.Address,
Coins: aa.Coins.Sort(),
}
}
// ToAppAccount converts a GenesisAccount to an AppAccount.
func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) {
return &AppAccount{
Name: ga.Name,
BaseAccount: auth.BaseAccount{
Address: ga.Address,
Coins: ga.Coins.Sort(),
},
}, nil
}
+197
View File
@@ -0,0 +1,197 @@
package app
import (
"encoding/json"
"os"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/ibc"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/types"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/simplestake"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/sketchy"
)
const (
appName = "DemocoinApp"
)
// default home directories for expected binaries
var (
DefaultCLIHome = os.ExpandEnv("$HOME/.democli")
DefaultNodeHome = os.ExpandEnv("$HOME/.democoind")
)
// Extended ABCI application
type DemocoinApp struct {
*bam.BaseApp
cdc *codec.Codec
// keys to access the substores
capKeyMainStore *sdk.KVStoreKey
capKeyAccountStore *sdk.KVStoreKey
capKeyPowStore *sdk.KVStoreKey
capKeyIBCStore *sdk.KVStoreKey
capKeyStakingStore *sdk.KVStoreKey
// keepers
feeCollectionKeeper auth.FeeCollectionKeeper
bankKeeper bank.Keeper
coolKeeper cool.Keeper
powKeeper pow.Keeper
ibcMapper ibc.Mapper
stakeKeeper simplestake.Keeper
// Manage getting and setting accounts
accountKeeper auth.AccountKeeper
}
func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp {
// Create app-level codec for txs and accounts.
var cdc = MakeCodec()
// Create your application object.
var app = &DemocoinApp{
BaseApp: bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc)),
cdc: cdc,
capKeyMainStore: sdk.NewKVStoreKey("main"),
capKeyAccountStore: sdk.NewKVStoreKey("acc"),
capKeyPowStore: sdk.NewKVStoreKey("pow"),
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
capKeyStakingStore: sdk.NewKVStoreKey("stake"),
}
// Define the accountKeeper.
app.accountKeeper = auth.NewAccountKeeper(
cdc,
app.capKeyAccountStore, // target store
types.ProtoAppAccount, // prototype
)
// Add handlers.
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper)
app.coolKeeper = cool.NewKeeper(app.capKeyMainStore, app.bankKeeper, app.RegisterCodespace(cool.DefaultCodespace))
app.powKeeper = pow.NewKeeper(app.capKeyPowStore, pow.NewConfig("pow", int64(1)), app.bankKeeper, app.RegisterCodespace(pow.DefaultCodespace))
app.ibcMapper = ibc.NewMapper(app.cdc, app.capKeyIBCStore, app.RegisterCodespace(ibc.DefaultCodespace))
app.stakeKeeper = simplestake.NewKeeper(app.capKeyStakingStore, app.bankKeeper, app.RegisterCodespace(simplestake.DefaultCodespace))
app.Router().
AddRoute("bank", bank.NewHandler(app.bankKeeper)).
AddRoute("cool", cool.NewHandler(app.coolKeeper)).
AddRoute("pow", app.powKeeper.Handler).
AddRoute("sketchy", sketchy.NewHandler()).
AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.bankKeeper)).
AddRoute("simplestake", simplestake.NewHandler(app.stakeKeeper))
// Initialize BaseApp.
app.SetInitChainer(app.initChainerFn(app.coolKeeper, app.powKeeper))
app.MountStoresIAVL(app.capKeyMainStore, app.capKeyAccountStore, app.capKeyPowStore, app.capKeyIBCStore, app.capKeyStakingStore)
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
err := app.LoadLatestVersion(app.capKeyMainStore)
if err != nil {
cmn.Exit(err.Error())
}
app.Seal()
return app
}
// custom tx codec
func MakeCodec() *codec.Codec {
var cdc = codec.New()
codec.RegisterCrypto(cdc) // Register crypto.
sdk.RegisterCodec(cdc) // Register Msgs
cool.RegisterCodec(cdc)
pow.RegisterCodec(cdc)
bank.RegisterCodec(cdc)
ibc.RegisterCodec(cdc)
simplestake.RegisterCodec(cdc)
// Register AppAccount
cdc.RegisterInterface((*auth.Account)(nil), nil)
cdc.RegisterConcrete(&types.AppAccount{}, "democoin/Account", nil)
cdc.Seal()
return cdc
}
// custom logic for democoin initialization
// nolint: unparam
func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes
genesisState := new(types.GenesisState)
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
for _, gacc := range genesisState.Accounts {
acc, err := gacc.ToAppAccount()
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
app.accountKeeper.SetAccount(ctx, acc)
}
// Application specific genesis handling
err = cool.InitGenesis(ctx, app.coolKeeper, genesisState.CoolGenesis)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
err = pow.InitGenesis(ctx, app.powKeeper, genesisState.POWGenesis)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
return abci.ResponseInitChain{}
}
}
// Custom logic for state export
func (app *DemocoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
ctx := app.NewContext(true, abci.Header{})
// iterate to get the accounts
accounts := []*types.GenesisAccount{}
appendAccount := func(acc auth.Account) (stop bool) {
account := &types.GenesisAccount{
Address: acc.GetAddress(),
Coins: acc.GetCoins(),
}
accounts = append(accounts, account)
return false
}
app.accountKeeper.IterateAccounts(ctx, appendAccount)
genState := types.GenesisState{
Accounts: accounts,
POWGenesis: pow.ExportGenesis(ctx, app.powKeeper),
CoolGenesis: cool.ExportGenesis(ctx, app.coolKeeper),
}
appState, err = codec.MarshalJSONIndent(app.cdc, genState)
if err != nil {
return nil, nil, err
}
return appState, validators, nil
}
+72
View File
@@ -0,0 +1,72 @@
package app
import (
"os"
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/types"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
)
func setGenesis(bapp *DemocoinApp, trend string, accs ...auth.BaseAccount) error {
genaccs := make([]*types.GenesisAccount, len(accs))
for i, acc := range accs {
genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, "foobart"})
}
genesisState := types.GenesisState{
Accounts: genaccs,
CoolGenesis: cool.Genesis{trend},
}
stateBytes, err := codec.MarshalJSONIndent(bapp.cdc, genesisState)
if err != nil {
return err
}
// Initialize the chain
vals := []abci.ValidatorUpdate{}
bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes})
bapp.Commit()
return nil
}
func TestGenesis(t *testing.T) {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
db := dbm.NewMemDB()
bapp := NewDemocoinApp(logger, db)
// Construct some genesis bytes to reflect democoin/types/AppAccount
pk := ed25519.GenPrivKey().PubKey()
addr := sdk.AccAddress(pk.Address())
coins, err := sdk.ParseCoins("77foocoin,99barcoin")
require.Nil(t, err)
baseAcc := auth.BaseAccount{
Address: addr,
Coins: coins,
}
acc := &types.AppAccount{baseAcc, "foobart"}
err = setGenesis(bapp, "ice-cold", baseAcc)
require.Nil(t, err)
// A checkTx context
ctx := bapp.BaseApp.NewContext(true, abci.Header{})
res1 := bapp.accountKeeper.GetAccount(ctx, baseAcc.Address)
require.Equal(t, acc, res1)
// reload app and ensure the account is still there
bapp = NewDemocoinApp(logger, db)
bapp.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")})
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
res1 = bapp.accountKeeper.GetAccount(ctx, baseAcc.Address)
require.Equal(t, acc, res1)
}
@@ -0,0 +1,56 @@
package clitest
import (
"encoding/json"
"fmt"
"os"
"testing"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/stretchr/testify/require"
)
var (
democoindHome = ""
democliHome = ""
)
func init() {
democoindHome, democliHome = getTestingHomeDirs()
}
func TestInitStartSequence(t *testing.T) {
os.RemoveAll(democoindHome)
servAddr, port, err := server.FreeTCPAddr()
require.NoError(t, err)
executeInit(t)
executeStart(t, servAddr, port)
}
func executeInit(t *testing.T) {
var (
chainID string
initRes map[string]json.RawMessage
)
_, stderr := tests.ExecuteT(t, fmt.Sprintf("democoind --home=%s --home-client=%s init --name=test", democoindHome, democliHome), app.DefaultKeyPass)
err := json.Unmarshal([]byte(stderr), &initRes)
require.NoError(t, err)
err = json.Unmarshal(initRes["chain_id"], &chainID)
require.NoError(t, err)
}
func executeStart(t *testing.T, servAddr, port string) {
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("democoind start --home=%s --rpc.laddr=%v", democoindHome, servAddr))
defer proc.Stop(false)
tests.WaitForTMStart(port)
}
func getTestingHomeDirs() (string, string) {
tmpDir := os.TempDir()
democoindHome := fmt.Sprintf("%s%s.test_democoind", tmpDir, string(os.PathSeparator))
democliHome := fmt.Sprintf("%s%s.test_democli", tmpDir, string(os.PathSeparator))
return democoindHome, democliHome
}
+112
View File
@@ -0,0 +1,112 @@
package main
import (
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/lcd"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/version"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/app"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/types"
coolcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool/client/cli"
powcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow/client/cli"
simplestakingcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/simplestake/client/cli"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// rootCmd is the entry point for this binary
var (
rootCmd = &cobra.Command{
Use: "democli",
Short: "Democoin light-client",
}
)
func main() {
// disable sorting
cobra.EnableCommandSorting = false
// get the codec
cdc := app.MakeCodec()
// Setup certain SDK config
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("demoacc", "demopub")
config.SetBech32PrefixForValidator("demoval", "demovalpub")
config.SetBech32PrefixForConsensusNode("democons", "democonspub")
config.Seal()
// TODO: setup keybase, viper object, etc. to be passed into
// the below functions and eliminate global vars, like we do
// with the cdc
// add standard rpc, and tx commands
rootCmd.AddCommand(
rpc.InitClientCommand(),
rpc.StatusCommand(),
client.LineBreak,
tx.SearchTxCmd(cdc),
tx.QueryTxCmd(cdc),
client.LineBreak,
)
// add query/post commands (custom to binary)
// start with commands common to basecoin
rootCmd.AddCommand(
client.GetCommands(
authcmd.GetAccountCmd("acc", cdc, types.GetAccountDecoder(cdc)),
)...)
rootCmd.AddCommand(
client.PostCommands(
bankcmd.SendTxCmd(cdc),
)...)
rootCmd.AddCommand(
client.PostCommands(
ibccmd.IBCTransferCmd(cdc),
)...)
rootCmd.AddCommand(
client.PostCommands(
ibccmd.IBCRelayCmd(cdc),
simplestakingcmd.BondTxCmd(cdc),
)...)
rootCmd.AddCommand(
client.PostCommands(
simplestakingcmd.UnbondTxCmd(cdc),
)...)
// and now democoin specific commands
rootCmd.AddCommand(
client.PostCommands(
coolcmd.QuizTxCmd(cdc),
coolcmd.SetTrendTxCmd(cdc),
powcmd.MineCmd(cdc),
)...)
// add proxy, version and key info
rootCmd.AddCommand(
client.LineBreak,
lcd.ServeCommand(cdc),
keys.Commands(),
client.LineBreak,
version.VersionCmd,
)
// prepare and add flags
executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome)
err := executor.Execute()
if err != nil {
// handle with #870
panic(err)
}
}
@@ -0,0 +1,174 @@
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/p2p"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/cli"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/app"
"github.com/cosmos/cosmos-sdk/server"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
flagClientHome = "home-client"
)
// init parameters
var CoolAppInit = server.AppInit{
AppGenState: CoolAppGenState,
}
// coolGenAppParams sets up the app_state and appends the cool app state
func CoolAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) (
appState json.RawMessage, err error) {
appState, err = server.SimpleAppGenState(cdc, tmtypes.GenesisDoc{}, appGenTxs)
if err != nil {
return
}
key := "cool"
value := json.RawMessage(`{
"trend": "ice-cold"
}`)
appState, err = server.InsertKeyJSON(cdc, appState, key, value)
if err != nil {
return
}
key = "pow"
value = json.RawMessage(`{
"difficulty": "1",
"count": "0"
}`)
appState, err = server.InsertKeyJSON(cdc, appState, key, value)
return
}
// get cmd to initialize all files for tendermint and application
// nolint: errcheck
func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize genesis config, priv-validator file, and p2p-node file",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
config := ctx.Config
config.SetRoot(viper.GetString(cli.HomeFlag))
chainID := viper.GetString(client.FlagChainID)
if chainID == "" {
chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6))
}
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return err
}
nodeID := string(nodeKey.ID())
pk := gaiaInit.ReadOrCreatePrivValidator(config.PrivValidatorFile())
genTx, appMessage, validator, err := server.SimpleAppGenTx(cdc, pk)
if err != nil {
return err
}
appState, err := appInit.AppGenState(cdc, tmtypes.GenesisDoc{},
[]json.RawMessage{genTx})
if err != nil {
return err
}
appStateJSON, err := cdc.MarshalJSON(appState)
if err != nil {
return err
}
toPrint := struct {
ChainID string `json:"chain_id"`
NodeID string `json:"node_id"`
AppMessage json.RawMessage `json:"app_message"`
}{
chainID,
nodeID,
appMessage,
}
out, err := codec.MarshalJSONIndent(cdc, toPrint)
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "%s\n", string(out))
return gaiaInit.ExportGenesisFile(config.GenesisFile(), chainID,
[]tmtypes.GenesisValidator{validator}, appStateJSON)
},
}
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
cmd.Flags().String(client.FlagChainID, "",
"genesis file chain-id, if left blank will be randomly created")
cmd.Flags().String(client.FlagName, "", "validator's moniker")
cmd.MarkFlagRequired(client.FlagName)
return cmd
}
func newApp(logger log.Logger, db dbm.DB, _ io.Writer) abci.Application {
return app.NewDemocoinApp(logger, db)
}
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer) (
json.RawMessage, []tmtypes.GenesisValidator, error) {
dapp := app.NewDemocoinApp(logger, db)
return dapp.ExportAppStateAndValidators()
}
func main() {
cdc := app.MakeCodec()
// Setup certain SDK config
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("demoacc", "demopub")
config.SetBech32PrefixForValidator("demoval", "demovalpub")
config.SetBech32PrefixForConsensusNode("democons", "democonspub")
config.Seal()
ctx := server.NewDefaultContext()
rootCmd := &cobra.Command{
Use: "democoind",
Short: "Democoin Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}
rootCmd.AddCommand(InitCmd(ctx, cdc, CoolAppInit))
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, CoolAppInit))
server.AddCommands(ctx, cdc, rootCmd, CoolAppInit,
newApp, exportAppStateAndTMValidators)
// prepare and add flags
rootDir := os.ExpandEnv("$HOME/.democoind")
executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir)
err := executor.Execute()
if err != nil {
// handle with #870
panic(err)
}
}
+162
View File
@@ -0,0 +1,162 @@
package mock
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto"
)
// Validator implements sdk.Validator
type Validator struct {
Address sdk.ValAddress
Power sdk.Dec
}
// Implements sdk.Validator
func (v Validator) GetStatus() sdk.BondStatus {
return sdk.Bonded
}
// Implements sdk.Validator
func (v Validator) GetOperator() sdk.ValAddress {
return v.Address
}
// Implements sdk.Validator
func (v Validator) GetConsPubKey() crypto.PubKey {
return nil
}
// Implements sdk.Validator
func (v Validator) GetConsAddr() sdk.ConsAddress {
return nil
}
// Implements sdk.Validator
func (v Validator) GetTokens() sdk.Dec {
return sdk.ZeroDec()
}
// Implements sdk.Validator
func (v Validator) GetPower() sdk.Dec {
return v.Power
}
// Implements sdk.Validator
func (v Validator) GetDelegatorShares() sdk.Dec {
return sdk.ZeroDec()
}
// Implements sdk.Validator
func (v Validator) GetCommission() sdk.Dec {
return sdk.ZeroDec()
}
// Implements sdk.Validator
func (v Validator) GetJailed() bool {
return false
}
// Implements sdk.Validator
func (v Validator) GetBondHeight() int64 {
return 0
}
// Implements sdk.Validator
func (v Validator) GetMoniker() string {
return ""
}
// Implements sdk.Validator
type ValidatorSet struct {
Validators []Validator
}
// IterateValidators implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
for i, val := range vs.Validators {
if fn(int64(i), val) {
break
}
}
}
// IterateBondedValidatorsByPower implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
vs.IterateValidators(ctx, fn)
}
// IterateLastValidators implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateLastValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
vs.IterateValidators(ctx, fn)
}
// Validator implements sdk.ValidatorSet
func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.ValAddress) sdk.Validator {
for _, val := range vs.Validators {
if bytes.Equal(val.Address.Bytes(), addr.Bytes()) {
return val
}
}
return nil
}
// ValidatorByPubKey implements sdk.ValidatorSet
func (vs *ValidatorSet) ValidatorByConsPubKey(_ sdk.Context, _ crypto.PubKey) sdk.Validator {
panic("not implemented")
}
// ValidatorByPubKey implements sdk.ValidatorSet
func (vs *ValidatorSet) ValidatorByConsAddr(_ sdk.Context, _ sdk.ConsAddress) sdk.Validator {
panic("not implemented")
}
// TotalPower implements sdk.ValidatorSet
func (vs *ValidatorSet) TotalPower(ctx sdk.Context) sdk.Dec {
res := sdk.ZeroDec()
for _, val := range vs.Validators {
res = res.Add(val.Power)
}
return res
}
// Helper function for adding new validator
func (vs *ValidatorSet) AddValidator(val Validator) {
vs.Validators = append(vs.Validators, val)
}
// Helper function for removing exsting validator
func (vs *ValidatorSet) RemoveValidator(addr sdk.AccAddress) {
pos := -1
for i, val := range vs.Validators {
if bytes.Equal(val.Address, addr) {
pos = i
break
}
}
if pos == -1 {
return
}
vs.Validators = append(vs.Validators[:pos], vs.Validators[pos+1:]...)
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Slash(_ sdk.Context, _ sdk.ConsAddress, _ int64, _ int64, _ sdk.Dec) {
panic("not implemented")
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Jail(_ sdk.Context, _ sdk.ConsAddress) {
panic("not implemented")
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Unjail(_ sdk.Context, _ sdk.ConsAddress) {
panic("not implemented")
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Delegation(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) sdk.Delegation {
panic("not implemented")
}
+82
View File
@@ -0,0 +1,82 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow"
)
var _ auth.Account = (*AppAccount)(nil)
// Custom extensions for this application. This is just an example of
// extending auth.BaseAccount with custom fields.
//
// This is compatible with the stock auth.AccountStore, since
// auth.AccountStore uses the flexible go-amino library.
type AppAccount struct {
auth.BaseAccount
Name string `json:"name"`
}
// Constructor for AppAccount
func ProtoAppAccount() auth.Account {
return &AppAccount{}
}
// nolint
func (acc AppAccount) GetName() string { return acc.Name }
func (acc *AppAccount) SetName(name string) { acc.Name = name }
// Get the AccountDecoder function for the custom AppAccount
func GetAccountDecoder(cdc *codec.Codec) auth.AccountDecoder {
return func(accBytes []byte) (res auth.Account, err error) {
if len(accBytes) == 0 {
return nil, sdk.ErrTxDecode("accBytes are empty")
}
acct := new(AppAccount)
err = cdc.UnmarshalBinaryBare(accBytes, &acct)
if err != nil {
panic(err)
}
return acct, err
}
}
//___________________________________________________________________________________
// State to Unmarshal
type GenesisState struct {
Accounts []*GenesisAccount `json:"accounts"`
POWGenesis pow.Genesis `json:"pow"`
CoolGenesis cool.Genesis `json:"cool"`
}
// GenesisAccount doesn't need pubkey or sequence
type GenesisAccount struct {
Name string `json:"name"`
Address sdk.AccAddress `json:"address"`
Coins sdk.Coins `json:"coins"`
}
func NewGenesisAccount(aa *AppAccount) *GenesisAccount {
return &GenesisAccount{
Name: aa.Name,
Address: aa.Address,
Coins: aa.Coins.Sort(),
}
}
// convert GenesisAccount to AppAccount
func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) {
baseAcc := auth.BaseAccount{
Address: ga.Address,
Coins: ga.Coins.Sort(),
}
return &AppAccount{
BaseAccount: baseAcc,
Name: ga.Name,
}, nil
}
@@ -0,0 +1,107 @@
package assoc
import (
"bytes"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// ValidatorSet defines
type ValidatorSet struct {
sdk.ValidatorSet
store sdk.KVStore
cdc *codec.Codec
maxAssoc int
addrLen int
}
var _ sdk.ValidatorSet = ValidatorSet{}
// NewValidatorSet returns new ValidatorSet with underlying ValidatorSet
func NewValidatorSet(cdc *codec.Codec, store sdk.KVStore, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet {
if maxAssoc < 0 || addrLen < 0 {
panic("Cannot use negative integer for NewValidatorSet")
}
return ValidatorSet{
ValidatorSet: valset,
store: store,
cdc: cdc,
maxAssoc: maxAssoc,
addrLen: addrLen,
}
}
// Implements sdk.ValidatorSet
func (valset ValidatorSet) Validator(ctx sdk.Context, addr sdk.ValAddress) (res sdk.Validator) {
base := valset.store.Get(GetBaseKey(addr))
res = valset.ValidatorSet.Validator(ctx, base)
if res == nil {
res = valset.ValidatorSet.Validator(ctx, addr)
}
return
}
// GetBaseKey :: sdk.ValAddress -> sdk.ValAddress
func GetBaseKey(addr sdk.ValAddress) []byte {
return append([]byte{0x00}, addr...)
}
// GetAssocPrefix :: sdk.ValAddress -> (sdk.ValAddress -> byte)
func GetAssocPrefix(base sdk.ValAddress) []byte {
return append([]byte{0x01}, base...)
}
// GetAssocKey :: (sdk.ValAddress, sdk.ValAddress) -> byte
func GetAssocKey(base sdk.ValAddress, assoc sdk.ValAddress) []byte {
return append(append([]byte{0x01}, base...), assoc...)
}
// Associate associates new address with validator address
// nolint: unparam
func (valset ValidatorSet) Associate(ctx sdk.Context, base sdk.ValAddress, assoc sdk.ValAddress) bool {
if len(base) != valset.addrLen || len(assoc) != valset.addrLen {
return false
}
// If someone already owns the associated address
if valset.store.Get(GetBaseKey(assoc)) != nil {
return false
}
valset.store.Set(GetBaseKey(assoc), base)
valset.store.Set(GetAssocKey(base, assoc), []byte{0x00})
return true
}
// Dissociate removes association between addresses
// nolint: unparam
func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.ValAddress, assoc sdk.ValAddress) bool {
if len(base) != valset.addrLen || len(assoc) != valset.addrLen {
return false
}
// No associated address found for given validator
if !bytes.Equal(valset.store.Get(GetBaseKey(assoc)), base) {
return false
}
valset.store.Delete(GetBaseKey(assoc))
valset.store.Delete(GetAssocKey(base, assoc))
return true
}
// Associations returns all associated addresses with a validator
// nolint: unparam
func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.ValAddress) (res []sdk.ValAddress) {
res = make([]sdk.ValAddress, valset.maxAssoc)
iter := sdk.KVStorePrefixIterator(valset.store, GetAssocPrefix(base))
defer iter.Close()
i := 0
for ; iter.Valid(); iter.Next() {
key := iter.Key()
res[i] = key[len(key)-valset.addrLen:]
i++
}
return res[:i]
}
@@ -0,0 +1,71 @@
package assoc
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/mock"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func defaultContext(key sdk.StoreKey) sdk.Context {
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
cms.LoadLatestVersion()
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
return ctx
}
func TestValidatorSet(t *testing.T) {
key := sdk.NewKVStoreKey("test")
ctx := defaultContext(key)
addr1 := []byte("addr1")
addr2 := []byte("addr2")
base := &mock.ValidatorSet{[]mock.Validator{
{addr1, sdk.NewDec(1)},
{addr2, sdk.NewDec(2)},
}}
valset := NewValidatorSet(codec.New(), ctx.KVStore(key).Prefix([]byte("assoc")), base, 1, 5)
require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
assoc1 := []byte("asso1")
assoc2 := []byte("asso2")
require.True(t, valset.Associate(ctx, addr1, assoc1))
require.True(t, valset.Associate(ctx, addr2, assoc2))
require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, assoc1))
require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, assoc2))
require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
assocs := valset.Associations(ctx, addr1)
require.Equal(t, 1, len(assocs))
require.True(t, bytes.Equal(assoc1, assocs[0]))
require.False(t, valset.Associate(ctx, addr1, assoc2))
require.False(t, valset.Associate(ctx, addr2, assoc1))
valset.Dissociate(ctx, addr1, assoc1)
valset.Dissociate(ctx, addr2, assoc2)
require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
require.Nil(t, valset.Validator(ctx, assoc1))
require.Nil(t, valset.Validator(ctx, assoc2))
}
+104
View File
@@ -0,0 +1,104 @@
package cool
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
bank "github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
)
var (
priv1 = ed25519.GenPrivKey()
pubKey = priv1.PubKey()
addr1 = sdk.AccAddress(pubKey.Address())
quizMsg1 = MsgQuiz{
Sender: addr1,
CoolAnswer: "icecold",
}
quizMsg2 = MsgQuiz{
Sender: addr1,
CoolAnswer: "badvibesonly",
}
setTrendMsg1 = MsgSetTrend{
Sender: addr1,
Cool: "icecold",
}
setTrendMsg2 = MsgSetTrend{
Sender: addr1,
Cool: "badvibesonly",
}
setTrendMsg3 = MsgSetTrend{
Sender: addr1,
Cool: "warmandkind",
}
)
// initialize the mock application for this module
func getMockApp(t *testing.T) *mock.App {
mapp := mock.NewApp()
RegisterCodec(mapp.Cdc)
keyCool := sdk.NewKVStoreKey("cool")
bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper)
keeper := NewKeeper(keyCool, bankKeeper, mapp.RegisterCodespace(DefaultCodespace))
mapp.Router().AddRoute("cool", NewHandler(keeper))
mapp.SetInitChainer(getInitChainer(mapp, keeper, "ice-cold"))
require.NoError(t, mapp.CompleteSetup(keyCool))
return mapp
}
// overwrite the mock init chainer
func getInitChainer(mapp *mock.App, keeper Keeper, newTrend string) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
mapp.InitChainer(ctx, req)
keeper.setTrend(ctx, newTrend)
return abci.ResponseInitChain{}
}
}
func TestMsgQuiz(t *testing.T) {
mapp := getMockApp(t)
// Construct genesis state
acc1 := &auth.BaseAccount{
Address: addr1,
Coins: nil,
}
accs := []auth.Account{acc1}
// Initialize the chain (nil)
mock.SetGenesis(mapp, accs)
// A checkTx context (true)
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1)
require.Equal(t, acc1, res1)
// Set the trend, submit a really cool quiz and check for reward
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, true, priv1)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, false, priv1) // result without reward
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, true, priv1) // reset the trend
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, false, priv1) // the same answer will nolonger do!
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, true, priv1) // earlier answer now relevant again
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", sdk.NewInt(69)}, {"icecold", sdk.NewInt(138)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, false, priv1) // expect to fail to set the trend to something which is not cool
}
@@ -0,0 +1,61 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool"
sdk "github.com/cosmos/cosmos-sdk/types"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
)
// QuizTxCmd invokes the coolness quiz transaction.
func QuizTxCmd(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "cool [answer]",
Short: "What's cooler than being cool?",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
cliCtx := context.NewCLIContext().
WithCodec(cdc).
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
from, err := cliCtx.GetFromAddress()
if err != nil {
return err
}
msg := cool.NewMsgQuiz(from, args[0])
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
},
}
}
// SetTrendTxCmd sends a new cool trend transaction.
func SetTrendTxCmd(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "setcool [answer]",
Short: "You're so cool, tell us what is cool!",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
cliCtx := context.NewCLIContext().
WithCodec(cdc).
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
from, err := cliCtx.GetFromAddress()
if err != nil {
return err
}
msg := cool.NewMsgSetTrend(from, args[0])
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
},
}
}
+11
View File
@@ -0,0 +1,11 @@
package cool
import (
"github.com/cosmos/cosmos-sdk/codec"
)
// Register concrete types on codec codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgQuiz{}, "cool/Quiz", nil)
cdc.RegisterConcrete(MsgSetTrend{}, "cool/SetTrend", nil)
}
+20
View File
@@ -0,0 +1,20 @@
package cool
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Cool errors reserve 400 ~ 499.
const (
DefaultCodespace sdk.CodespaceType = 6
// Cool module reserves error 400-499 lawl
CodeIncorrectCoolAnswer sdk.CodeType = 400
)
// ErrIncorrectCoolAnswer - Error returned upon an incorrect guess
func ErrIncorrectCoolAnswer(codespace sdk.CodespaceType, answer string) sdk.Error {
return sdk.NewError(codespace, CodeIncorrectCoolAnswer, fmt.Sprintf("incorrect cool answer: %v", answer))
}
+57
View File
@@ -0,0 +1,57 @@
package cool
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// This is just an example to demonstrate a functional custom module
// with full feature set functionality.
//
// /$$$$$$$ /$$$$$$ /$$$$$$ /$$
// /$$_____/ /$$__ $$ /$$__ $$| $$
//| $$ | $$ \ $$| $$ \ $$| $$
//| $$ | $$ | $$| $$ | $$| $$
//| $$$$$$$| $$$$$$/| $$$$$$/| $$$$$$$
// \_______/ \______/ \______/ |______/
// NewHandler returns a handler for "cool" type messages.
func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgSetTrend:
return handleMsgSetTrend(ctx, k, msg)
case MsgQuiz:
return handleMsgQuiz(ctx, k, msg)
default:
errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", msg.Type())
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
// Handle MsgQuiz This is the engine of your module
func handleMsgSetTrend(ctx sdk.Context, k Keeper, msg MsgSetTrend) sdk.Result {
k.setTrend(ctx, msg.Cool)
return sdk.Result{}
}
// Handle MsgQuiz This is the engine of your module
func handleMsgQuiz(ctx sdk.Context, k Keeper, msg MsgQuiz) sdk.Result {
correct := k.CheckTrend(ctx, msg.CoolAnswer)
if !correct {
return ErrIncorrectCoolAnswer(k.codespace, msg.CoolAnswer).Result()
}
bonusCoins := sdk.Coins{sdk.NewInt64Coin(msg.CoolAnswer, 69)}
_, _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins)
if err != nil {
return err.Result()
}
return sdk.Result{}
}
+56
View File
@@ -0,0 +1,56 @@
package cool
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
)
// Keeper - handlers sets/gets of custom variables for your module
type Keeper struct {
ck bank.Keeper
storeKey sdk.StoreKey // The (unexposed) key used to access the store from the Context.
codespace sdk.CodespaceType
}
// NewKeeper - Returns the Keeper
func NewKeeper(key sdk.StoreKey, bankKeeper bank.Keeper, codespace sdk.CodespaceType) Keeper {
return Keeper{bankKeeper, key, codespace}
}
// Key to knowing the trend on the streets!
var trendKey = []byte("TrendKey")
// GetTrend - returns the current cool trend
func (k Keeper) GetTrend(ctx sdk.Context) string {
store := ctx.KVStore(k.storeKey)
bz := store.Get(trendKey)
return string(bz)
}
// Implements sdk.AccountKeeper.
func (k Keeper) setTrend(ctx sdk.Context, newTrend string) {
store := ctx.KVStore(k.storeKey)
store.Set(trendKey, []byte(newTrend))
}
// CheckTrend - Returns true or false based on whether guessedTrend is currently cool or not
func (k Keeper) CheckTrend(ctx sdk.Context, guessedTrend string) bool {
if guessedTrend == k.GetTrend(ctx) {
return true
}
return false
}
// InitGenesis - store the genesis trend
func InitGenesis(ctx sdk.Context, k Keeper, data Genesis) error {
k.setTrend(ctx, data.Trend)
return nil
}
// ExportGenesis - output the genesis trend
func ExportGenesis(ctx sdk.Context, k Keeper) Genesis {
trend := k.GetTrend(ctx)
return Genesis{trend}
}
@@ -0,0 +1,50 @@
package cool
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth"
bank "github.com/cosmos/cosmos-sdk/x/bank"
)
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
db := dbm.NewMemDB()
capKey := sdk.NewKVStoreKey("capkey")
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
ms.LoadLatestVersion()
return ms, capKey
}
func TestCoolKeeper(t *testing.T) {
ms, capKey := setupMultiStore()
cdc := codec.New()
auth.RegisterBaseAccount(cdc)
am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
ck := bank.NewBaseKeeper(am)
keeper := NewKeeper(capKey, ck, DefaultCodespace)
err := InitGenesis(ctx, keeper, Genesis{"icy"})
require.Nil(t, err)
genesis := ExportGenesis(ctx, keeper)
require.Nil(t, err)
require.Equal(t, genesis, Genesis{"icy"})
res := keeper.GetTrend(ctx)
require.Equal(t, res, "icy")
keeper.setTrend(ctx, "fiery")
res = keeper.GetTrend(ctx)
require.Equal(t, res, "fiery")
}
+108
View File
@@ -0,0 +1,108 @@
package cool
import (
"encoding/json"
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// a really cool msg type, these fields are can be entirely arbitrary and
// custom to your message
type MsgSetTrend struct {
Sender sdk.AccAddress
Cool string
}
// genesis state - specify genesis trend
type Genesis struct {
Trend string `json:"trend"`
}
// new cool message
func NewMsgSetTrend(sender sdk.AccAddress, cool string) MsgSetTrend {
return MsgSetTrend{
Sender: sender,
Cool: cool,
}
}
// enforce the msg type at compile time
var _ sdk.Msg = MsgSetTrend{}
// nolint
func (msg MsgSetTrend) Route() string { return "cool" }
func (msg MsgSetTrend) Type() string { return "set_trend" }
func (msg MsgSetTrend) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} }
func (msg MsgSetTrend) String() string {
return fmt.Sprintf("MsgSetTrend{Sender: %v, Cool: %v}", msg.Sender, msg.Cool)
}
// Validate Basic is used to quickly disqualify obviously invalid messages quickly
func (msg MsgSetTrend) ValidateBasic() sdk.Error {
if len(msg.Sender) == 0 {
return sdk.ErrUnknownAddress(msg.Sender.String()).TraceSDK("")
}
if strings.Contains(msg.Cool, "hot") {
return sdk.ErrUnauthorized("").TraceSDK("hot is not cool")
}
if strings.Contains(msg.Cool, "warm") {
return sdk.ErrUnauthorized("").TraceSDK("warm is not very cool")
}
return nil
}
// Get the bytes for the message signer to sign on
func (msg MsgSetTrend) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(b)
}
//_______________________________________________________________________
// A message type to quiz how cool you are. these fields are can be entirely
// arbitrary and custom to your message
type MsgQuiz struct {
Sender sdk.AccAddress
CoolAnswer string
}
// New cool message
func NewMsgQuiz(sender sdk.AccAddress, coolerthancool string) MsgQuiz {
return MsgQuiz{
Sender: sender,
CoolAnswer: coolerthancool,
}
}
// enforce the msg type at compile time
var _ sdk.Msg = MsgQuiz{}
// nolint
func (msg MsgQuiz) Route() string { return "cool" }
func (msg MsgQuiz) Type() string { return "quiz" }
func (msg MsgQuiz) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} }
func (msg MsgQuiz) String() string {
return fmt.Sprintf("MsgQuiz{Sender: %v, CoolAnswer: %v}", msg.Sender, msg.CoolAnswer)
}
// Validate Basic is used to quickly disqualify obviously invalid messages quickly
func (msg MsgQuiz) ValidateBasic() sdk.Error {
if len(msg.Sender) == 0 {
return sdk.ErrUnknownAddress(msg.Sender.String()).TraceSDK("")
}
return nil
}
// Get the bytes for the message signer to sign on
func (msg MsgQuiz) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(b)
}
+58
View File
@@ -0,0 +1,58 @@
# Oracle Module
`x/oracle` provides a way to receive external information(real world price, events from other chains, etc.) with validators' vote. Each validator make transaction which contains those informations, and Oracle aggregates them until the supermajority signed on it. After then, Oracle sends the information to the actual module that processes the information, and prune the votes from the state.
## Integration
See `x/oracle/oracle_test.go` for the code that using Oracle
To use Oracle in your module, first define a `payload`. It should implement `oracle.Payload` and contain nessesary information for your module. Including nonce is recommended.
```go
type MyPayload struct {
Data int
Nonce int
}
```
When you write a payload, its `.Route()` should return same route with your module is registered on the router. It is because `oracle.Msg` inherits `.Route()` from its embedded payload and it should be handled on the user modules.
Then route every incoming `oracle.Msg` to `oracle.Keeper.Handler()` with the function that implements `oracle.Handler`.
```go
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case oracle.Msg:
return keeper.oracle.Handle(ctx sdk.Context, p oracle.Payload) sdk.Error {
switch p := p.(type) {
case MyPayload:
return handleMyPayload(ctx, keeper, p)
}
}
}
}
}
```
In the previous example, the keeper has an `oracle.Keeper`. `oracle.Keeper`s are generated by `NewKeeper`.
```go
func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper {
return Keeper {
cdc: cdc,
key: key,
// ValidatorSet to get validators infor
valset: valset,
// The keeper will pass payload
// when more than 2/3 signed on it
supermaj: supermaj,
// The keeper will prune votes after 100 blocks from last sign
timeout: timeout,
}
}
```
Now the validators can send `oracle.Msg`s with `MyPayload` when they want to witness external events.
+31
View File
@@ -0,0 +1,31 @@
package oracle
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Oracle errors reserve 1101-1199
const (
CodeNotValidator sdk.CodeType = 1101
CodeAlreadyProcessed sdk.CodeType = 1102
CodeAlreadySigned sdk.CodeType = 1103
CodeUnknownRequest sdk.CodeType = sdk.CodeUnknownRequest
)
// ----------------------------------------
// Error constructors
// ErrNotValidator called when the signer of a Msg is not a validator
func ErrNotValidator(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error {
return sdk.NewError(codespace, CodeNotValidator, address.String())
}
// ErrAlreadyProcessed called when a payload is already processed
func ErrAlreadyProcessed(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeAlreadyProcessed, "")
}
// ErrAlreadySigned called when the signer is trying to double signing
func ErrAlreadySigned(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeAlreadySigned, "")
}
+106
View File
@@ -0,0 +1,106 @@
package oracle
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Handler handles payload after it passes voting process
type Handler func(ctx sdk.Context, p Payload) sdk.Error
func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.ValidatorSet, p Payload, info Info) Info {
info.Power = info.Power.Add(val.GetPower())
// Return if the voted power is not bigger than required power
totalPower := valset.TotalPower(ctx)
requiredPower := totalPower.Mul(keeper.supermaj)
if !info.Power.GT(requiredPower) {
return info
}
// Check if the validators hash has been changed during the vote process
// and recalculate voted power
hash := ctx.BlockHeader().ValidatorsHash
if !bytes.Equal(hash, info.Hash) {
info.Power = sdk.ZeroDec()
info.Hash = hash
prefix := GetSignPrefix(p, keeper.cdc)
store := ctx.KVStore(keeper.key)
iter := sdk.KVStorePrefixIterator(store, prefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
if valset.Validator(ctx, iter.Value()) != nil {
store.Delete(iter.Key())
continue
}
info.Power = info.Power.Add(val.GetPower())
}
if !info.Power.GT(totalPower.Mul(keeper.supermaj)) {
return info
}
}
info.Status = Processed
return info
}
// Handle is used by other modules to handle Msg
func (keeper Keeper) Handle(h Handler, ctx sdk.Context, o Msg, codespace sdk.CodespaceType) sdk.Result {
valset := keeper.valset
signer := o.Signer
payload := o.Payload
// Check the oracle is not in process
info := keeper.Info(ctx, payload)
if info.Status != Pending {
return ErrAlreadyProcessed(codespace).Result()
}
// Check if it is reporting timeout
now := ctx.BlockHeight()
if now > info.LastSigned+keeper.timeout {
info = Info{Status: Timeout}
keeper.setInfo(ctx, payload, info)
keeper.clearSigns(ctx, payload)
return sdk.Result{}
}
info.LastSigned = ctx.BlockHeight()
// check the signer is a validator
val := valset.Validator(ctx, sdk.ValAddress(signer))
if val == nil {
return ErrNotValidator(codespace, signer).Result()
}
// Check double signing
if keeper.signed(ctx, payload, signer) {
return ErrAlreadySigned(codespace).Result()
}
keeper.sign(ctx, payload, signer)
info = keeper.update(ctx, val, valset, payload, info)
if info.Status == Processed {
info = Info{Status: Processed}
}
keeper.setInfo(ctx, payload, info)
if info.Status == Processed {
keeper.clearSigns(ctx, payload)
cctx, write := ctx.CacheContext()
err := h(cctx, payload)
if err != nil {
return sdk.Result{
Code: sdk.ABCICodeOK,
Log: err.ABCILog(),
}
}
write()
}
return sdk.Result{}
}
+111
View File
@@ -0,0 +1,111 @@
package oracle
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Keeper of the oracle store
type Keeper struct {
key sdk.StoreKey
cdc *codec.Codec
valset sdk.ValidatorSet
supermaj sdk.Dec
timeout int64
}
// NewKeeper constructs a new keeper
func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper {
if timeout < 0 {
panic("Timeout should not be negative")
}
return Keeper{
key: key,
cdc: cdc,
valset: valset,
supermaj: supermaj,
timeout: timeout,
}
}
// InfoStatus - current status of an Info
type InfoStatus int8
// Define InfoStatus
const (
Pending = InfoStatus(iota)
Processed
Timeout
)
// Info for each payload
type Info struct {
Power sdk.Dec
Hash []byte
LastSigned int64
Status InfoStatus
}
// EmptyInfo construct an empty Info
func EmptyInfo(ctx sdk.Context) Info {
return Info{
Power: sdk.ZeroDec(),
Hash: ctx.BlockHeader().ValidatorsHash,
LastSigned: ctx.BlockHeight(),
Status: Pending,
}
}
// Info returns the information about a payload
func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) {
store := ctx.KVStore(keeper.key)
key := GetInfoKey(p, keeper.cdc)
bz := store.Get(key)
if bz == nil {
return EmptyInfo(ctx)
}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res)
return
}
func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) {
store := ctx.KVStore(keeper.key)
key := GetInfoKey(p, keeper.cdc)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(info)
store.Set(key, bz)
}
func (keeper Keeper) sign(ctx sdk.Context, p Payload, signer sdk.AccAddress) {
store := ctx.KVStore(keeper.key)
key := GetSignKey(p, signer, keeper.cdc)
store.Set(key, signer)
}
func (keeper Keeper) signed(ctx sdk.Context, p Payload, signer sdk.AccAddress) bool {
store := ctx.KVStore(keeper.key)
key := GetSignKey(p, signer, keeper.cdc)
return store.Has(key)
}
func (keeper Keeper) clearSigns(ctx sdk.Context, p Payload) {
store := ctx.KVStore(keeper.key)
prefix := GetSignPrefix(p, keeper.cdc)
iter := sdk.KVStorePrefixIterator(store, prefix)
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
iter.Close()
}
@@ -0,0 +1,23 @@
package oracle
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// GetInfoKey returns the key for OracleInfo
func GetInfoKey(p Payload, cdc *codec.Codec) []byte {
bz := cdc.MustMarshalBinaryLengthPrefixed(p)
return append([]byte{0x00}, bz...)
}
// GetSignPrefix returns the prefix for signs
func GetSignPrefix(p Payload, cdc *codec.Codec) []byte {
bz := cdc.MustMarshalBinaryLengthPrefixed(p)
return append([]byte{0x01}, bz...)
}
// GetSignKey returns the key for sign
func GetSignKey(p Payload, signer sdk.AccAddress, cdc *codec.Codec) []byte {
return append(GetSignPrefix(p, cdc), signer...)
}
@@ -0,0 +1,225 @@
package oracle
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/mock"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func defaultContext(keys ...sdk.StoreKey) sdk.Context {
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
for _, key := range keys {
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
}
cms.LoadLatestVersion()
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
return ctx
}
type seqOracle struct {
Seq int
Nonce int
}
func (o seqOracle) Route() string {
return "seq"
}
func (o seqOracle) Type() string {
return "seq"
}
func (o seqOracle) ValidateBasic() sdk.Error {
return nil
}
func makeCodec() *codec.Codec {
var cdc = codec.New()
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
cdc.RegisterConcrete(Msg{}, "test/Oracle", nil)
cdc.RegisterInterface((*Payload)(nil), nil)
cdc.RegisterConcrete(seqOracle{}, "test/oracle/seqOracle", nil)
cdc.Seal()
return cdc
}
func seqHandler(ork Keeper, key sdk.StoreKey, codespace sdk.CodespaceType) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case Msg:
return ork.Handle(func(ctx sdk.Context, p Payload) sdk.Error {
switch p := p.(type) {
case seqOracle:
return handleSeqOracle(ctx, key, p)
default:
return sdk.ErrUnknownRequest("")
}
}, ctx, msg, codespace)
default:
return sdk.ErrUnknownRequest("").Result()
}
}
}
func getSequence(ctx sdk.Context, key sdk.StoreKey) int {
store := ctx.KVStore(key)
seqbz := store.Get([]byte("seq"))
var seq int
if seqbz == nil {
seq = 0
} else {
codec.New().MustUnmarshalBinaryLengthPrefixed(seqbz, &seq)
}
return seq
}
func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error {
store := ctx.KVStore(key)
seq := getSequence(ctx, key)
if seq != o.Seq {
return sdk.NewError(sdk.CodespaceRoot, 1, "")
}
bz := codec.New().MustMarshalBinaryLengthPrefixed(seq + 1)
store.Set([]byte("seq"), bz)
return nil
}
func TestOracle(t *testing.T) {
cdc := makeCodec()
addr1 := []byte("addr1")
addr2 := []byte("addr2")
addr3 := []byte("addr3")
addr4 := []byte("addr4")
valset := &mock.ValidatorSet{[]mock.Validator{
{addr1, sdk.NewDec(7)},
{addr2, sdk.NewDec(7)},
{addr3, sdk.NewDec(1)},
}}
key := sdk.NewKVStoreKey("testkey")
ctx := defaultContext(key)
bz, err := json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
ork := NewKeeper(key, cdc, valset, sdk.NewDecWithPrec(667, 3), 100) // 66.7%
h := seqHandler(ork, key, sdk.CodespaceRoot)
// Nonmock.Validator signed, transaction failed
msg := Msg{seqOracle{0, 0}, []byte("randomguy")}
res := h(ctx, msg)
require.False(t, res.IsOK())
require.Equal(t, 0, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg.Signer = addr1
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 0, getSequence(ctx, key))
// Double signed, transaction failed
res = h(ctx, msg)
require.False(t, res.IsOK())
require.Equal(t, 0, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr2
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 1, getSequence(ctx, key))
// Already processed, transaction failed
msg.Signer = addr3
res = h(ctx, msg)
require.False(t, res.IsOK())
require.Equal(t, 1, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg = Msg{seqOracle{100, 1}, addr1}
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 1, getSequence(ctx, key))
// More than 2/3 signed but payload is invalid
msg.Signer = addr2
res = h(ctx, msg)
require.True(t, res.IsOK())
require.NotEqual(t, "", res.Log)
require.Equal(t, 1, getSequence(ctx, key))
// Already processed, transaction failed
msg.Signer = addr3
res = h(ctx, msg)
require.False(t, res.IsOK())
require.Equal(t, 1, getSequence(ctx, key))
// Should handle mock.Validator set change
valset.AddValidator(mock.Validator{addr4, sdk.NewDec(12)})
bz, err = json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
// Less than 2/3 signed, msg not processed
msg = Msg{seqOracle{1, 2}, addr1}
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 1, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg.Signer = addr2
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 1, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr4
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 2, getSequence(ctx, key))
// Should handle mock.Validator set change while oracle process is happening
msg = Msg{seqOracle{2, 3}, addr4}
// Less than 2/3 signed, msg not processed
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 2, getSequence(ctx, key))
// Signed mock.Validator is kicked out
valset.RemoveValidator(addr4)
bz, err = json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
// Less than 2/3 signed, msg not processed
msg.Signer = addr1
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 2, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr2
res = h(ctx, msg)
require.True(t, res.IsOK())
require.Equal(t, 3, getSequence(ctx, key))
}
+34
View File
@@ -0,0 +1,34 @@
package oracle
import (
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Msg - struct for voting on payloads
type Msg struct {
Payload
Signer sdk.AccAddress
}
// GetSignBytes implements sdk.Msg
func (msg Msg) GetSignBytes() []byte {
bz, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(bz)
}
// GetSigners implements sdk.Msg
func (msg Msg) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Signer}
}
// Payload defines inner data for actual execution
type Payload interface {
Route() string
Type() string
ValidateBasic() sdk.Error
}
+86
View File
@@ -0,0 +1,86 @@
package pow
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/mock"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
)
var (
priv1 = ed25519.GenPrivKey()
addr1 = sdk.AccAddress(priv1.PubKey().Address())
)
// initialize the mock application for this module
func getMockApp(t *testing.T) *mock.App {
mapp := mock.NewApp()
RegisterCodec(mapp.Cdc)
keyPOW := sdk.NewKVStoreKey("pow")
bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper)
config := Config{"pow", 1}
keeper := NewKeeper(keyPOW, config, bankKeeper, mapp.RegisterCodespace(DefaultCodespace))
mapp.Router().AddRoute("pow", keeper.Handler)
mapp.SetInitChainer(getInitChainer(mapp, keeper))
require.NoError(t, mapp.CompleteSetup(keyPOW))
mapp.Seal()
return mapp
}
// overwrite the mock init chainer
func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
mapp.InitChainer(ctx, req)
genesis := Genesis{
Difficulty: 1,
Count: 0,
}
InitGenesis(ctx, keeper, genesis)
return abci.ResponseInitChain{}
}
}
func TestMsgMine(t *testing.T) {
mapp := getMockApp(t)
// Construct genesis state
acc1 := &auth.BaseAccount{
Address: addr1,
Coins: nil,
}
accs := []auth.Account{acc1}
// Initialize the chain (nil)
mock.SetGenesis(mapp, accs)
// A checkTx context (true)
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1)
require.Equal(t, acc1, res1)
// Mine and check for reward
mineMsg1 := GenerateMsgMine(addr1, 1, 2)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(1)}})
// Mine again and check for reward
mineMsg2 := GenerateMsgMine(addr1, 2, 3)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}})
// Mine again - should be invalid
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, false, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}})
}
@@ -0,0 +1,57 @@
package cli
import (
"strconv"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow"
sdk "github.com/cosmos/cosmos-sdk/types"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
"github.com/spf13/cobra"
)
// command to mine some pow!
func MineCmd(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "mine [difficulty] [count] [nonce] [solution]",
Short: "Mine some coins with proof-of-work!",
Args: cobra.ExactArgs(4),
RunE: func(cmd *cobra.Command, args []string) error {
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
cliCtx := context.NewCLIContext().
WithCodec(cdc).
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
from, err := cliCtx.GetFromAddress()
if err != nil {
return err
}
difficulty, err := strconv.ParseUint(args[0], 0, 64)
if err != nil {
return err
}
count, err := strconv.ParseUint(args[1], 0, 64)
if err != nil {
return err
}
nonce, err := strconv.ParseUint(args[2], 0, 64)
if err != nil {
return err
}
solution := []byte(args[3])
msg := pow.NewMsgMine(from, difficulty, count, nonce, solution)
// Build and sign the transaction, then broadcast to a Tendermint
// node.
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
},
}
}
+10
View File
@@ -0,0 +1,10 @@
package pow
import (
"github.com/cosmos/cosmos-sdk/codec"
)
// Register concrete types on codec codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgMine{}, "pow/Mine", nil)
}
+79
View File
@@ -0,0 +1,79 @@
package pow
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// TODO remove, seems hacky
type CodeType = sdk.CodeType
// POW errors reserve 200 ~ 299
const (
DefaultCodespace sdk.CodespaceType = 5
CodeInvalidDifficulty CodeType = 201
CodeNonexistentDifficulty CodeType = 202
CodeNonexistentReward CodeType = 203
CodeNonexistentCount CodeType = 204
CodeInvalidProof CodeType = 205
CodeNotBelowTarget CodeType = 206
CodeInvalidCount CodeType = 207
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
)
func codeToDefaultMsg(code CodeType) string {
switch code {
case CodeInvalidDifficulty:
return "insuffient difficulty"
case CodeNonexistentDifficulty:
return "nonexistent difficulty"
case CodeNonexistentReward:
return "nonexistent reward"
case CodeNonexistentCount:
return "nonexistent count"
case CodeInvalidProof:
return "invalid proof"
case CodeNotBelowTarget:
return "not below target"
case CodeInvalidCount:
return "invalid count"
case CodeUnknownRequest:
return "unknown request"
default:
return sdk.CodeToDefaultMsg(code)
}
}
// nolint
func ErrInvalidDifficulty(codespace sdk.CodespaceType, msg string) sdk.Error {
return newError(codespace, CodeInvalidDifficulty, msg)
}
func ErrNonexistentDifficulty(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeNonexistentDifficulty, "")
}
func ErrNonexistentReward(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeNonexistentReward, "")
}
func ErrNonexistentCount(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeNonexistentCount, "")
}
func ErrInvalidProof(codespace sdk.CodespaceType, msg string) sdk.Error {
return newError(codespace, CodeInvalidProof, msg)
}
func ErrNotBelowTarget(codespace sdk.CodespaceType, msg string) sdk.Error {
return newError(codespace, CodeNotBelowTarget, msg)
}
func ErrInvalidCount(codespace sdk.CodespaceType, msg string) sdk.Error {
return newError(codespace, CodeInvalidCount, msg)
}
func msgOrDefaultMsg(msg string, code CodeType) string {
if msg != "" {
return msg
}
return codeToDefaultMsg(code)
}
func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error {
msg = msgOrDefaultMsg(msg, code)
return sdk.NewError(codespace, code, msg)
}
+33
View File
@@ -0,0 +1,33 @@
package pow
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// POW handler
func (pk Keeper) Handler(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgMine:
return handleMsgMine(ctx, pk, msg)
default:
errMsg := "Unrecognized pow Msg type: " + msg.Type()
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
func handleMsgMine(ctx sdk.Context, pk Keeper, msg MsgMine) sdk.Result {
// precondition: msg has passed ValidateBasic
newDiff, newCount, err := pk.CheckValid(ctx, msg.Difficulty, msg.Count)
if err != nil {
return err.Result()
}
err = pk.ApplyValid(ctx, msg.Sender, newDiff, newCount)
if err != nil {
return err.Result()
}
return sdk.Result{}
}
@@ -0,0 +1,59 @@
package pow
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
codec "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth"
bank "github.com/cosmos/cosmos-sdk/x/bank"
)
func TestPowHandler(t *testing.T) {
ms, capKey := setupMultiStore()
cdc := codec.New()
auth.RegisterBaseAccount(cdc)
am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger())
config := NewConfig("pow", int64(1))
ck := bank.NewBaseKeeper(am)
keeper := NewKeeper(capKey, config, ck, DefaultCodespace)
handler := keeper.Handler
addr := sdk.AccAddress([]byte("sender"))
count := uint64(1)
difficulty := uint64(2)
err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)})
require.Nil(t, err)
nonce, proof := mine(addr, count, difficulty)
msg := NewMsgMine(addr, difficulty, count, nonce, proof)
result := handler(ctx, msg)
require.Equal(t, result, sdk.Result{})
newDiff, err := keeper.GetLastDifficulty(ctx)
require.Nil(t, err)
require.Equal(t, newDiff, uint64(2))
newCount, err := keeper.GetLastCount(ctx)
require.Nil(t, err)
require.Equal(t, newCount, uint64(1))
// todo assert correct coin change, awaiting https://github.com/cosmos/cosmos-sdk/pull/691
difficulty = uint64(4)
nonce, proof = mine(addr, count, difficulty)
msg = NewMsgMine(addr, difficulty, count, nonce, proof)
result = handler(ctx, msg)
require.NotEqual(t, result, sdk.Result{})
}
+135
View File
@@ -0,0 +1,135 @@
package pow
import (
"fmt"
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
bank "github.com/cosmos/cosmos-sdk/x/bank"
)
// module users must specify coin denomination and reward (constant) per PoW solution
type Config struct {
Denomination string
Reward int64
}
// genesis info must specify starting difficulty and starting count
type Genesis struct {
Difficulty uint64 `json:"difficulty"`
Count uint64 `json:"count"`
}
// POW Keeper
type Keeper struct {
key sdk.StoreKey
config Config
ck bank.Keeper
codespace sdk.CodespaceType
}
func NewConfig(denomination string, reward int64) Config {
return Config{denomination, reward}
}
func NewKeeper(key sdk.StoreKey, config Config, ck bank.Keeper, codespace sdk.CodespaceType) Keeper {
return Keeper{key, config, ck, codespace}
}
// InitGenesis for the POW module
func InitGenesis(ctx sdk.Context, k Keeper, genesis Genesis) error {
k.SetLastDifficulty(ctx, genesis.Difficulty)
k.SetLastCount(ctx, genesis.Count)
return nil
}
// ExportGenesis for the PoW module
func ExportGenesis(ctx sdk.Context, k Keeper) Genesis {
difficulty, err := k.GetLastDifficulty(ctx)
if err != nil {
panic(err)
}
count, err := k.GetLastCount(ctx)
if err != nil {
panic(err)
}
return Genesis{
difficulty,
count,
}
}
var lastDifficultyKey = []byte("lastDifficultyKey")
// get the last mining difficulty
func (k Keeper) GetLastDifficulty(ctx sdk.Context) (uint64, error) {
store := ctx.KVStore(k.key)
stored := store.Get(lastDifficultyKey)
if stored == nil {
panic("no stored difficulty")
} else {
return strconv.ParseUint(string(stored), 0, 64)
}
}
// set the last mining difficulty
func (k Keeper) SetLastDifficulty(ctx sdk.Context, diff uint64) {
store := ctx.KVStore(k.key)
store.Set(lastDifficultyKey, []byte(strconv.FormatUint(diff, 16)))
}
var countKey = []byte("count")
// get the last count
func (k Keeper) GetLastCount(ctx sdk.Context) (uint64, error) {
store := ctx.KVStore(k.key)
stored := store.Get(countKey)
if stored == nil {
panic("no stored count")
} else {
return strconv.ParseUint(string(stored), 0, 64)
}
}
// set the last count
func (k Keeper) SetLastCount(ctx sdk.Context, count uint64) {
store := ctx.KVStore(k.key)
store.Set(countKey, []byte(strconv.FormatUint(count, 16)))
}
// Is the keeper state valid?
func (k Keeper) CheckValid(ctx sdk.Context, difficulty uint64, count uint64) (uint64, uint64, sdk.Error) {
lastDifficulty, err := k.GetLastDifficulty(ctx)
if err != nil {
return 0, 0, ErrNonexistentDifficulty(k.codespace)
}
newDifficulty := lastDifficulty + 1
lastCount, err := k.GetLastCount(ctx)
if err != nil {
return 0, 0, ErrNonexistentCount(k.codespace)
}
newCount := lastCount + 1
if count != newCount {
return 0, 0, ErrInvalidCount(k.codespace, fmt.Sprintf("invalid count: was %d, should have been %d", count, newCount))
}
if difficulty != newDifficulty {
return 0, 0, ErrInvalidDifficulty(k.codespace, fmt.Sprintf("invalid difficulty: was %d, should have been %d", difficulty, newDifficulty))
}
return newDifficulty, newCount, nil
}
// Add some coins for a POW well done
func (k Keeper) ApplyValid(ctx sdk.Context, sender sdk.AccAddress, newDifficulty uint64, newCount uint64) sdk.Error {
_, _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.NewInt64Coin(k.config.Denomination, k.config.Reward)})
if ckErr != nil {
return ckErr
}
k.SetLastDifficulty(ctx, newDifficulty)
k.SetLastCount(ctx, newCount)
return nil
}
@@ -0,0 +1,57 @@
package pow
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth"
bank "github.com/cosmos/cosmos-sdk/x/bank"
)
// possibly share this kind of setup functionality between module testsuites?
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
db := dbm.NewMemDB()
capKey := sdk.NewKVStoreKey("capkey")
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
ms.LoadLatestVersion()
return ms, capKey
}
func TestPowKeeperGetSet(t *testing.T) {
ms, capKey := setupMultiStore()
cdc := codec.New()
auth.RegisterBaseAccount(cdc)
am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger())
config := NewConfig("pow", int64(1))
ck := bank.NewBaseKeeper(am)
keeper := NewKeeper(capKey, config, ck, DefaultCodespace)
err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)})
require.Nil(t, err)
genesis := ExportGenesis(ctx, keeper)
require.Nil(t, err)
require.Equal(t, genesis, Genesis{uint64(1), uint64(0)})
res, err := keeper.GetLastDifficulty(ctx)
require.Nil(t, err)
require.Equal(t, res, uint64(1))
keeper.SetLastDifficulty(ctx, 2)
res, err = keeper.GetLastDifficulty(ctx)
require.Nil(t, err)
require.Equal(t, res, uint64(2))
}
+45
View File
@@ -0,0 +1,45 @@
package pow
import (
"encoding/hex"
"math"
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto"
)
// generate the mine message
func GenerateMsgMine(sender sdk.AccAddress, count uint64, difficulty uint64) MsgMine {
nonce, hash := mine(sender, count, difficulty)
return NewMsgMine(sender, difficulty, count, nonce, hash)
}
func hash(sender sdk.AccAddress, count uint64, nonce uint64) []byte {
var bytes []byte
bytes = append(bytes, []byte(sender)...)
countBytes := strconv.FormatUint(count, 16)
bytes = append(bytes, countBytes...)
nonceBytes := strconv.FormatUint(nonce, 16)
bytes = append(bytes, nonceBytes...)
hash := crypto.Sha256(bytes)
// uint64, so we just use the first 8 bytes of the hash
// this limits the range of possible difficulty values (as compared to uint256), but fine for proof-of-concept
ret := make([]byte, hex.EncodedLen(len(hash)))
hex.Encode(ret, hash)
return ret[:16]
}
func mine(sender sdk.AccAddress, count uint64, difficulty uint64) (uint64, []byte) {
target := math.MaxUint64 / difficulty
for nonce := uint64(0); ; nonce++ {
hash := hash(sender, count, nonce)
hashuint, err := strconv.ParseUint(string(hash), 16, 64)
if err != nil {
panic(err)
}
if hashuint < target {
return nonce, hash
}
}
}
+81
View File
@@ -0,0 +1,81 @@
package pow
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"math"
"strconv"
"github.com/tendermint/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// MsgMine - mine some coins with PoW
type MsgMine struct {
Sender sdk.AccAddress `json:"sender"`
Difficulty uint64 `json:"difficulty"`
Count uint64 `json:"count"`
Nonce uint64 `json:"nonce"`
Proof []byte `json:"proof"`
}
// enforce the msg type at compile time
var _ sdk.Msg = MsgMine{}
// NewMsgMine - construct mine message
func NewMsgMine(sender sdk.AccAddress, difficulty uint64, count uint64, nonce uint64, proof []byte) MsgMine {
return MsgMine{sender, difficulty, count, nonce, proof}
}
// nolint
func (msg MsgMine) Route() string { return "pow" }
func (msg MsgMine) Type() string { return "mine" }
func (msg MsgMine) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} }
func (msg MsgMine) String() string {
return fmt.Sprintf("MsgMine{Sender: %s, Difficulty: %d, Count: %d, Nonce: %d, Proof: %s}", msg.Sender, msg.Difficulty, msg.Count, msg.Nonce, msg.Proof)
}
// validate the mine message
func (msg MsgMine) ValidateBasic() sdk.Error {
// check hash
var data []byte
// hash must include sender, so no other users can race the tx
data = append(data, []byte(msg.Sender)...)
countBytes := strconv.FormatUint(msg.Count, 16)
// hash must include count so proof-of-work solutions cannot be replayed
data = append(data, countBytes...)
nonceBytes := strconv.FormatUint(msg.Nonce, 16)
data = append(data, nonceBytes...)
hash := crypto.Sha256(data)
hashHex := make([]byte, hex.EncodedLen(len(hash)))
hex.Encode(hashHex, hash)
hashHex = hashHex[:16]
if !bytes.Equal(hashHex, msg.Proof) {
return ErrInvalidProof(DefaultCodespace, fmt.Sprintf("hashHex: %s, proof: %s", hashHex, msg.Proof))
}
// check proof below difficulty
// difficulty is linear - 1 = all hashes, 2 = half of hashes, 3 = third of hashes, etc
target := math.MaxUint64 / msg.Difficulty
hashUint, err := strconv.ParseUint(string(msg.Proof), 16, 64)
if err != nil {
return ErrInvalidProof(DefaultCodespace, fmt.Sprintf("proof: %s", msg.Proof))
}
if hashUint >= target {
return ErrNotBelowTarget(DefaultCodespace, fmt.Sprintf("hashuint: %d, target: %d", hashUint, target))
}
return nil
}
// get the mine message sign bytes
func (msg MsgMine) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(b)
}

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