diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2cbcb1477a51..d8f051e60cb7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -188,6 +188,23 @@ jobs: name: "${{ github.sha }}-${{ matrix.part }}-race-output" path: ./${{ matrix.part }}-race-output.txt + test-rosetta: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v2 + - uses: technote-space/get-diff-action@v4 + id: git_diff + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - name: test rosetta + run: | + make test-rosetta + # if: env.GIT_DIFF + race-detector-report: runs-on: ubuntu-latest needs: [test-race, install-tparse] diff --git a/Makefile b/Makefile index 6f498b36b3c6..0f4cf9e80fb2 100644 --- a/Makefile +++ b/Makefile @@ -309,6 +309,11 @@ test-cover: @export VERSION=$(VERSION); bash -x contrib/test_cover.sh .PHONY: test-cover +test-rosetta: + docker build -t rosetta-ci:latest -f contrib/rosetta/node/Dockerfile . + docker-compose -f contrib/rosetta/docker-compose.yaml up --abort-on-container-exit --exit-code-from test_rosetta --build +.PHONY: test-rosetta + benchmark: @go test -mod=readonly -bench=. $(PACKAGES_NOSIMULATION) .PHONY: benchmark @@ -463,3 +468,15 @@ localnet-stop: docker-compose down .PHONY: localnet-start localnet-stop + +############################################################################### +### rosetta ### +############################################################################### +# builds rosetta test data dir +rosetta-data: + -docker container rm data_dir_build + docker build -t rosetta-ci:latest -f contrib/rosetta/node/Dockerfile . + docker run --name data_dir_build -t rosetta-ci:latest sh /rosetta/data.sh + docker cp data_dir_build:/tmp/data.tar.gz "$(CURDIR)/contrib/rosetta/node/data.tar.gz" + docker container rm data_dir_build +.PHONY: rosetta-data \ No newline at end of file diff --git a/contrib/rosetta/README.md b/contrib/rosetta/README.md new file mode 100644 index 000000000000..b1d33659fec8 --- /dev/null +++ b/contrib/rosetta/README.md @@ -0,0 +1,24 @@ +# rosetta + +This directory contains the files required to run the rosetta CI. It builds `simapp` based on the current codebase. + +## docker-compose.yaml + +Builds: +- cosmos-sdk simapp node, with prefixed data directory, keys etc. This is required to test historical balances. +- faucet is required so we can test construction API, it was literally impossible to put there a deterministic address to request funds for +- rosetta is the rosetta node used by rosetta-cli to interact with the cosmos-sdk app +- test_rosetta runs the rosetta-cli test against construction API and data API + +## configuration + +Contains the required files to set up rosetta cli and make it work against its workflows + +## node + +Contains the files for a deterministic network, with fixed keys and some actions on there, to test parsing of msgs and historical balances. + +## Notes + +- Keyring password is 12345678 +- data.sh creates node data, it's required in case consensus breaking changes are made to quickly recreate replicable node data for rosetta diff --git a/contrib/rosetta/configuration/bootstrap.json b/contrib/rosetta/configuration/bootstrap.json new file mode 100644 index 000000000000..15b75b550862 --- /dev/null +++ b/contrib/rosetta/configuration/bootstrap.json @@ -0,0 +1,12 @@ +[ + { + "account_identifier": { + "address":"cosmos1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjjqfl87e" + }, + "currency":{ + "symbol":"stake", + "decimals":0 + }, + "value": "999900000000" + } +] \ No newline at end of file diff --git a/contrib/rosetta/configuration/data.sh b/contrib/rosetta/configuration/data.sh new file mode 100644 index 000000000000..dc4f2cb59a86 --- /dev/null +++ b/contrib/rosetta/configuration/data.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +set -e + +wait_simd() { + timeout 30 sh -c 'until nc -z $0 $1; do sleep 1; done' localhost 9090 +} +# this script is used to recreate the data dir +echo clearing /root/.simapp +rm -rf /root/.simapp +echo initting new chain +# init config files +simd init simd --chain-id testing + +# create accounts +simd keys add fd --keyring-backend=test + +addr=$(simd keys show fd -a --keyring-backend=test) + +# give the accounts some money +simd add-genesis-account "$addr" 1000000000000stake --keyring-backend=test + +# save configs for the daemon +simd gentx fd --chain-id testing --keyring-backend=test + +# input genTx to the genesis file +simd collect-gentxs +# verify genesis file is fine +simd validate-genesis +echo changing network settings +sed -i 's/127.0.0.1/0.0.0.0/g' /root/.simapp/config/config.toml + +# start simd +echo starting simd... +simd start --pruning=nothing & +pid=$! +echo simd started with PID $pid + +echo awaiting for simd to be ready +wait_simd +echo simd is ready +sleep 10 + + +# send transaction to deterministic address +echo sending transaction with addr $addr +simd tx bank send "$addr" cosmos1wjmt63j4fv9nqda92nsrp2jp2vsukcke4va3pt 100stake --yes --keyring-backend=test --broadcast-mode=block --chain-id=testing + +sleep 10 + +echo stopping simd... +kill -9 $pid + +echo zipping data dir and saving to /tmp/data.tar.gz + +tar -czvf /tmp/data.tar.gz /root/.simapp + +echo new address for bootstrap.json "$addr" diff --git a/contrib/rosetta/configuration/faucet.py b/contrib/rosetta/configuration/faucet.py new file mode 100644 index 000000000000..44536a84bb3b --- /dev/null +++ b/contrib/rosetta/configuration/faucet.py @@ -0,0 +1,25 @@ +from http.server import HTTPServer, BaseHTTPRequestHandler +import subprocess + +import os + + +class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): + + def do_POST(self): + try: + content_len = int(self.headers.get('Content-Length')) + addr = self.rfile.read(content_len).decode("utf-8") + print("sending funds to " + addr) + subprocess.call(['sh', './send_funds.sh', addr]) + self.send_response(200) + self.end_headers() + except Exception as e: + print("failed " + str(e)) + os._exit(1) + + +if __name__ == "__main__": + print("starting faucet server...") + httpd = HTTPServer(('0.0.0.0', 8000), SimpleHTTPRequestHandler) + httpd.serve_forever() diff --git a/contrib/rosetta/configuration/rosetta.json b/contrib/rosetta/configuration/rosetta.json new file mode 100644 index 000000000000..39a0bb3811dd --- /dev/null +++ b/contrib/rosetta/configuration/rosetta.json @@ -0,0 +1,51 @@ +{ + "network": { + "blockchain": "app", + "network": "network" + }, + "online_url": "http://rosetta:8080", + "data_directory": "", + "http_timeout": 300, + "max_retries": 5, + "retry_elapsed_time": 0, + "max_online_connections": 0, + "max_sync_concurrency": 0, + "tip_delay": 60, + "log_configuration": true, + "construction": { + "offline_url": "http://rosetta:8080", + "max_offline_connections": 0, + "stale_depth": 0, + "broadcast_limit": 0, + "ignore_broadcast_failures": false, + "clear_broadcasts": false, + "broadcast_behind_tip": false, + "block_broadcast_limit": 0, + "rebroadcast_all": false, + "constructor_dsl_file": "transfer.ros", + "end_conditions": { + "create_account": 1, + "transfer": 3 + } + }, + "data": { + "active_reconciliation_concurrency": 0, + "inactive_reconciliation_concurrency": 0, + "inactive_reconciliation_frequency": 0, + "log_blocks": false, + "log_transactions": false, + "log_balance_changes": false, + "log_reconciliations": false, + "ignore_reconciliation_error": false, + "exempt_accounts": "", + "bootstrap_balances": "bootstrap.json", + "interesting_accounts": "", + "reconciliation_disabled": false, + "inactive_discrepency_search_disabled": false, + "balance_tracking_disabled": false, + "coin_tracking_disabled": false, + "end_conditions": { + "tip": true + } + } +} \ No newline at end of file diff --git a/contrib/rosetta/configuration/run_tests.sh b/contrib/rosetta/configuration/run_tests.sh new file mode 100755 index 000000000000..cd7af92acda2 --- /dev/null +++ b/contrib/rosetta/configuration/run_tests.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +set -e + +addr="abcd" + +send_tx() { + echo '12345678' | simd tx bank send $addr "$1" "$2" +} + +detect_account() { + line=$1 +} + +wait_for_rosetta() { + timeout 30 sh -c 'until nc -z $0 $1; do sleep 1; done' rosetta 8080 +} + +echo "waiting for rosetta instance to be up" +wait_for_rosetta + +echo "checking data API" +rosetta-cli check:data --configuration-file ./config/rosetta.json + +echo "checking construction API" +rosetta-cli check:construction --configuration-file ./config/rosetta.json + +echo "checking staking API" +rosetta-cli check:construction --configuration-file ./config/staking.json diff --git a/contrib/rosetta/configuration/send_funds.sh b/contrib/rosetta/configuration/send_funds.sh new file mode 100644 index 000000000000..3a897539d225 --- /dev/null +++ b/contrib/rosetta/configuration/send_funds.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e +addr=$(simd keys show fd -a --keyring-backend=test) +echo "12345678" | simd tx bank send "$addr" "$1" 100stake --chain-id="testing" --node tcp://cosmos:26657 --yes --keyring-backend=test \ No newline at end of file diff --git a/contrib/rosetta/configuration/staking.json b/contrib/rosetta/configuration/staking.json new file mode 100644 index 000000000000..9c5e5da3ba46 --- /dev/null +++ b/contrib/rosetta/configuration/staking.json @@ -0,0 +1,30 @@ +{ + "network": { + "blockchain": "app", + "network": "network" + }, + "online_url": "http://rosetta:8080", + "data_directory": "", + "http_timeout": 300, + "max_retries": 5, + "retry_elapsed_time": 0, + "max_online_connections": 0, + "max_sync_concurrency": 0, + "tip_delay": 60, + "log_configuration": true, + "construction": { + "offline_url": "http://rosetta:8080", + "max_offline_connections": 0, + "stale_depth": 0, + "broadcast_limit": 0, + "ignore_broadcast_failures": false, + "clear_broadcasts": false, + "broadcast_behind_tip": false, + "block_broadcast_limit": 0, + "rebroadcast_all": false, + "constructor_dsl_file": "staking.ros", + "end_conditions": { + "staking": 3 + } + } +} \ No newline at end of file diff --git a/contrib/rosetta/configuration/staking.ros b/contrib/rosetta/configuration/staking.ros new file mode 100644 index 000000000000..1d9de1d180fa --- /dev/null +++ b/contrib/rosetta/configuration/staking.ros @@ -0,0 +1,147 @@ +request_funds(1){ + find_account{ + currency = {"symbol":"stake", "decimals":0}; + random_account = find_balance({ + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit":1 + }); + }, + send_funds{ + account_identifier = {{random_account.account_identifier}}; + address = {{account_identifier.address}}; + idk = http_request({ + "method": "POST", + "url": "http:\/\/faucet:8000", + "timeout": 10, + "body": {{random_account.account_identifier.address}} + }); + }, + // Create a separate scenario to request funds so that + // the address we are using to request funds does not + // get rolled back if funds do not yet exist. + request{ + loaded_account = find_balance({ + "account_identifier": {{random_account.account_identifier}}, + "minimum_balance":{ + "value": "100", + "currency": {{currency}} + } + }); + } +} +create_account(1){ + create{ + network = {"network":"network", "blockchain":"app"}; + key = generate_key({"curve_type": "secp256k1"}); + account = derive({ + "network_identifier": {{network}}, + "public_key": {{key.public_key}} + }); + // If the account is not saved, the key will be lost! + save_account({ + "account_identifier": {{account.account_identifier}}, + "keypair": {{key}} + }); + } +} + +staking(1){ + stake{ + stake.network = {"network":"network", "blockchain":"app"}; + currency = {"symbol":"stake", "decimals":0}; + sender = find_balance({ + "minimum_balance":{ + "value": "100", + "currency": {{currency}} + } + }); + // Set the recipient_amount as some value <= sender.balance-max_fee + max_fee = "0"; + fee_amount = "1"; + fee_value = 0 - {{fee_amount}}; + available_amount = {{sender.balance.value}} - {{max_fee}}; + recipient_amount = "1"; + print_message({"recipient_amount":{{recipient_amount}}}); + // Find recipient and construct operations + recipient = {{sender.account_identifier}}; + sender_amount = 0 - {{recipient_amount}}; + stake.confirmation_depth = "1"; + stake.operations = [ + { + "operation_identifier":{"index":0}, + "type":"fee", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{fee_value}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":1}, + "type":"cosmos.staking.v1beta1.MsgDelegate", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{sender_amount}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":2}, + "type":"cosmos.staking.v1beta1.MsgDelegate", + "account": { + "address": "staking_account", + "sub_account": { + "address" : "cosmosvaloper1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjj9atjj2" + } + }, + "amount":{ + "value":{{recipient_amount}}, + "currency":{{currency}} + } + } + ]; + }, + undelegate{ + print_message({"undelegate":{{sender}}}); + + undelegate.network = {"network":"network", "blockchain":"app"}; + undelegate.confirmation_depth = "1"; + undelegate.operations = [ + { + "operation_identifier":{"index":0}, + "type":"fee", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{fee_value}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":1}, + "type":"cosmos.staking.v1beta1.MsgUndelegate", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{recipient_amount}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":2}, + "type":"cosmos.staking.v1beta1.MsgUndelegate", + "account": { + "address": "staking_account", + "sub_account": { + "address" : "cosmosvaloper1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjj9atjj2" + } + }, + "amount":{ + "value":{{sender_amount}}, + "currency":{{currency}} + } + } + ]; + } +} diff --git a/contrib/rosetta/configuration/transfer.ros b/contrib/rosetta/configuration/transfer.ros new file mode 100644 index 000000000000..a1cb3f8caf89 --- /dev/null +++ b/contrib/rosetta/configuration/transfer.ros @@ -0,0 +1,109 @@ +request_funds(1){ + find_account{ + currency = {"symbol":"stake", "decimals":0}; + random_account = find_balance({ + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit":1 + }); + }, + send_funds{ + account_identifier = {{random_account.account_identifier}}; + address = {{account_identifier.address}}; + idk = http_request({ + "method": "POST", + "url": "http:\/\/faucet:8000", + "timeout": 10, + "body": {{random_account.account_identifier.address}} + }); + }, + // Create a separate scenario to request funds so that + // the address we are using to request funds does not + // get rolled back if funds do not yet exist. + request{ + loaded_account = find_balance({ + "account_identifier": {{random_account.account_identifier}}, + "minimum_balance":{ + "value": "100", + "currency": {{currency}} + } + }); + } +} +create_account(1){ + create{ + network = {"network":"network", "blockchain":"app"}; + key = generate_key({"curve_type": "secp256k1"}); + account = derive({ + "network_identifier": {{network}}, + "public_key": {{key.public_key}} + }); + // If the account is not saved, the key will be lost! + save_account({ + "account_identifier": {{account.account_identifier}}, + "keypair": {{key}} + }); + } +} +transfer(3){ + transfer{ + transfer.network = {"network":"network", "blockchain":"app"}; + currency = {"symbol":"stake", "decimals":0}; + sender = find_balance({ + "minimum_balance":{ + "value": "100", + "currency": {{currency}} + } + }); + // Set the recipient_amount as some value <= sender.balance-max_fee + max_fee = "0"; + fee_amount = "1"; + fee_value = 0 - {{fee_amount}}; + available_amount = {{sender.balance.value}} - {{max_fee}}; + recipient_amount = random_number({"minimum": "1", "maximum": {{available_amount}}}); + print_message({"recipient_amount":{{recipient_amount}}}); + // Find recipient and construct operations + sender_amount = 0 - {{recipient_amount}}; + recipient = find_balance({ + "not_account_identifier":[{{sender.account_identifier}}], + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit": 100, + "create_probability": 50 + }); + transfer.confirmation_depth = "1"; + transfer.operations = [ + { + "operation_identifier":{"index":0}, + "type":"fee", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{fee_value}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":1}, + "type":"cosmos.bank.v1beta1.MsgSend", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{sender_amount}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":2}, + "type":"cosmos.bank.v1beta1.MsgSend", + "account":{{recipient.account_identifier}}, + "amount":{ + "value":{{recipient_amount}}, + "currency":{{currency}} + } + } + ]; + } +} diff --git a/contrib/rosetta/docker-compose.yaml b/contrib/rosetta/docker-compose.yaml new file mode 100644 index 000000000000..0a9e82de8a9e --- /dev/null +++ b/contrib/rosetta/docker-compose.yaml @@ -0,0 +1,39 @@ +version: "3" + +services: + cosmos: + image: rosetta-ci:latest + command: ["simd", "start", "--pruning", "nothing", "--grpc-web.enable", "true", "--grpc-web.address", "0.0.0.0:9091"] + ports: + - 9090:9090 + - 26657:26657 + logging: + driver: "none" + + rosetta: + image: rosetta-ci:latest + command: [ + "simd", + "rosetta", + "--blockchain", "app", + "--network", "network", + "--tendermint", "cosmos:26657", + "--grpc", "cosmos:9090", + "--addr", ":8080", + ] + ports: + - 8080:8080 + + faucet: + image: rosetta-ci:latest + working_dir: /rosetta + command: ["python3", "faucet.py"] + expose: + - 8080 + + test_rosetta: + image: tendermintdev/rosetta-cli:v0.6.6 + volumes: + - ./configuration:/rosetta/config:z + command: ["./config/run_tests.sh"] + working_dir: /rosetta diff --git a/contrib/rosetta/node/Dockerfile b/contrib/rosetta/node/Dockerfile new file mode 100644 index 000000000000..0887f522f656 --- /dev/null +++ b/contrib/rosetta/node/Dockerfile @@ -0,0 +1,32 @@ +FROM golang:1.15-alpine as build + +RUN apk add --no-cache tar + +# prepare node data +WORKDIR /node +COPY ./contrib/rosetta/node/data.tar.gz data.tar.gz +RUN tar -zxvf data.tar.gz -C . + +# build simd +WORKDIR /simd +COPY . ./ +RUN go build -o simd ./simapp/simd/ + +FROM alpine +RUN apk add gcc libc-dev python3 --no-cache + +ENV PATH=$PATH:/bin + +COPY --from=build /simd/simd /bin/simd + +WORKDIR /rosetta +COPY ./contrib/rosetta/configuration ./ +RUN chmod +x run_tests.sh +RUN chmod +x send_funds.sh +RUN chmod +x faucet.py + +COPY --from=build /node/root /root/ +WORKDIR /root/.simapp + +RUN chmod -R 0777 ./ + diff --git a/contrib/rosetta/node/data.tar.gz b/contrib/rosetta/node/data.tar.gz new file mode 100644 index 000000000000..a7bed60579ea Binary files /dev/null and b/contrib/rosetta/node/data.tar.gz differ diff --git a/contrib/rosetta/rosetta-cli/Dockerfile b/contrib/rosetta/rosetta-cli/Dockerfile new file mode 100644 index 000000000000..e0bc3c961ff3 --- /dev/null +++ b/contrib/rosetta/rosetta-cli/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.15-alpine as build + +RUN apk add git gcc libc-dev --no-cache + +ARG ROSETTA_VERSION="v0.5.23" + +# build rosetta CLI +WORKDIR /rosetta +RUN git clone https://github.com/coinbase/rosetta-cli . +RUN git checkout tags/$ROSETTA_VERSION +RUN go build -o rosetta-cli ./main.go + +FROM alpine +RUN apk add gcc libc-dev python3 --no-cache + +ENV PATH=$PATH:/bin + +COPY --from=build /rosetta/rosetta-cli /bin/rosetta-cli diff --git a/docs/architecture/adr-035-rosetta-api-support.md b/docs/architecture/adr-035-rosetta-api-support.md index 2da663f5729d..b7807ce1d24b 100644 --- a/docs/architecture/adr-035-rosetta-api-support.md +++ b/docs/architecture/adr-035-rosetta-api-support.md @@ -5,6 +5,7 @@ - Jonathan Gimeno (@jgimeno) - David Grierson (@senormonito) - Alessio Treglia (@alessio) +- Frojdy Dymylja (@fdymylja) ## Context @@ -35,9 +36,11 @@ We will achieve these delivering on these principles by the following: 1. There will be an external repo called [cosmos-rosetta-gateway](https://github.com/tendermint/cosmos-rosetta-gateway) for the implementation of the core Rosetta API features, particularly: - a. The types and interfaces. This separates design from implementation detail. - b. Some core implementations: specifically, the `Service` functionality as this is independent of the Cosmos SDK version. -2. Due to differences between the Cosmos release series, each series will have its own specific API implementations of `Network` struct and `Adapter` interface. + a. The types and interfaces (`Client`, `OfflineClient`...), this separates design from implementation detail. + b. The `Server` functionality as this is independent of the Cosmos SDK version. + c. The `Online/OfflineNetwork`, which is not exported, and implements the rosetta API using the `Client` interface to query the node, build tx and so on. + d. The `errors` package to extend rosetta errors. +2. Due to differences between the Cosmos release series, each series will have its own specific implementation of `Client` interface. 3. There will be two options for starting an API service in applications: a. API shares the application process b. API-specific process. @@ -49,143 +52,130 @@ We will achieve these delivering on these principles by the following: As section will describe the proposed external library, including the service implementation, plus the defined types and interfaces. -#### Service +#### Server -`Service` is a simple `struct` that is started and listens to the port specified in the options. This is meant to be used across all the Cosmos SDK versions that are actively supported. +`Server` is a simple `struct` that is started and listens to the port specified in the settings. This is meant to be used across all the Cosmos SDK versions that are actively supported. The constructor follows: -`func New(options Options, network Network) (*Service, error)` - -#### Types - -`Service` accepts an `Options` `struct` that holds service configuration values, such as the port the service would be listening to: - -```golang -type Options struct { - ListenAddress string -} -``` - -The `Network` type holds network-specific properties (i.e. configuration values) and adapters. Pre-configured concrete types will be available for each Cosmos SDK release. Applications can also create their own custom types. - -```golang -type Network struct { - Properties rosetta.NetworkProperties - Adapter rosetta.Adapter -} -``` - -A `NetworkProperties` `struct` comprises basic values that are required by a Rosetta API `Service`: - -```golang -type NetworkProperties struct { - // Mandatory properties - Blockchain string - Network string - SupportedOperations []string +`func NewServer(settings Settings) (Server, error)` + +`Settings`, which are used to construct a new server, are the following: +```go +// Settings define the rosetta server settings +type Settings struct { + // Network contains the information regarding the network + Network *types.NetworkIdentifier + // Client is the online API handler + Client crgtypes.Client + // Listen is the address the handler will listen at + Listen string + // Offline defines if the rosetta service should be exposed in offline mode + Offline bool + // Retries is the number of readiness checks that will be attempted when instantiating the handler + // valid only for online API + Retries int + // RetryWait is the time that will be waited between retries + RetryWait time.Duration } ``` -Rosetta API services use `Blockchain` and `Network` as identifiers, e.g. the developers of _gaia_, the application that powers the Cosmos Hub, may want to set those to `Cosmos Hub` and `cosmos-hub-3` respectively. - -`SupportedOperations` contains the transaction types that are supported by the library. At the present time, -only `cosmos-sdk/MsgSend` is supported in Launchpad. Additional operations will be added in due time. - -For Launchpad we will map the amino type name to the operation supported, in Stargate we will use the protoc one. - -#### Interfaces - -Every SDK version uses a different format to connect (rpc, gRpc, etc), we have abstracted this in what is called the -Adapter. This is an interface that defines the methods an adapter implementation must provide in order to be used -in the `Network` interface. - -Each Cosmos SDK release series will have their own Adapter implementations. -Developers can implement their own custom adapters as required. - -```golang -type Adapter interface { - DataAPI - ConstructionAPI -} +#### Types -type DataAPI interface { - server.NetworkAPIServicer - server.AccountAPIServicer - server.MempoolAPIServicer - server.BlockAPIServicer - server.ConstructionAPIServicer +Package types uses a mixture of rosetta types and custom defined type wrappers, that the client must parse and return while executing operations. + + +##### Interfaces + +Every SDK version uses a different format to connect (rpc, gRPC, etc), query and build transactions, we have abstracted this in what is the `Client` interface. +The client uses rosetta types, whilst the `Online/OfflineNetwork` takes care of returning correctly parsed rosetta responses and errors. + +Each Cosmos SDK release series will have their own `Client` implementations. +Developers can implement their own custom `Client`s as required. + +```go +// Client defines the API the client implementation should provide. +type Client interface { + // Needed if the client needs to perform some action before connecting. + Bootstrap() error + // Ready checks if the servicer constraints for queries are satisfied + // for example the node might still not be ready, it's useful in process + // when the rosetta instance might come up before the node itself + // the servicer must return nil if the node is ready + Ready() error + + // Data API + + // Balances fetches the balance of the given address + // if height is not nil, then the balance will be displayed + // at the provided height, otherwise last block balance will be returned + Balances(ctx context.Context, addr string, height *int64) ([]*types.Amount, error) + // BlockByHashAlt gets a block and its transaction at the provided height + BlockByHash(ctx context.Context, hash string) (BlockResponse, error) + // BlockByHeightAlt gets a block given its height, if height is nil then last block is returned + BlockByHeight(ctx context.Context, height *int64) (BlockResponse, error) + // BlockTransactionsByHash gets the block, parent block and transactions + // given the block hash. + BlockTransactionsByHash(ctx context.Context, hash string) (BlockTransactionsResponse, error) + // BlockTransactionsByHash gets the block, parent block and transactions + // given the block hash. + BlockTransactionsByHeight(ctx context.Context, height *int64) (BlockTransactionsResponse, error) + // GetTx gets a transaction given its hash + GetTx(ctx context.Context, hash string) (*types.Transaction, error) + // GetUnconfirmedTx gets an unconfirmed Tx given its hash + // NOTE(fdymylja): NOT IMPLEMENTED YET! + GetUnconfirmedTx(ctx context.Context, hash string) (*types.Transaction, error) + // Mempool returns the list of the current non confirmed transactions + Mempool(ctx context.Context) ([]*types.TransactionIdentifier, error) + // Peers gets the peers currently connected to the node + Peers(ctx context.Context) ([]*types.Peer, error) + // Status returns the node status, such as sync data, version etc + Status(ctx context.Context) (*types.SyncStatus, error) + + // Construction API + + // PostTx posts txBytes to the node and returns the transaction identifier plus metadata related + // to the transaction itself. + PostTx(txBytes []byte) (res *types.TransactionIdentifier, meta map[string]interface{}, err error) + // ConstructionMetadataFromOptions + ConstructionMetadataFromOptions(ctx context.Context, options map[string]interface{}) (meta map[string]interface{}, err error) + OfflineClient } -type ConstructionAPI interface { - server.ConstructionAPIServicer +// OfflineClient defines the functionalities supported without having access to the node +type OfflineClient interface { + NetworkInformationProvider + // SignedTx returns the signed transaction given the tx bytes (msgs) plus the signatures + SignedTx(ctx context.Context, txBytes []byte, sigs []*types.Signature) (signedTxBytes []byte, err error) + // TxOperationsAndSignersAccountIdentifiers returns the operations related to a transaction and the account + // identifiers if the transaction is signed + TxOperationsAndSignersAccountIdentifiers(signed bool, hexBytes []byte) (ops []*types.Operation, signers []*types.AccountIdentifier, err error) + // ConstructionPayload returns the construction payload given the request + ConstructionPayload(ctx context.Context, req *types.ConstructionPayloadsRequest) (resp *types.ConstructionPayloadsResponse, err error) + // PreprocessOperationsToOptions returns the options given the preprocess operations + PreprocessOperationsToOptions(ctx context.Context, req *types.ConstructionPreprocessRequest) (options map[string]interface{}, err error) + // AccountIdentifierFromPublicKey returns the account identifier given the public key + AccountIdentifierFromPublicKey(pubKey *types.PublicKey) (*types.AccountIdentifier, error) } ``` -Example in pseudo-code of an Adapter interface: - -```golang -type SomeAdapter struct { - cosmosClient client - tendermintClient client -} - -func NewSomeAdapter(cosmosClient client, tendermintClient client) rosetta.Adapter { - return &SomeAdapter{cosmosClient: cosmosClient, tendermintClient: tendermintClient} -} - -func (s SomeAdapter) NetworkStatus(ctx context.Context, request *types.NetworkRequest) (*types.NetworkStatusResponse, *types.Error) { - resp := s.tendermintClient.CallStatus() - // ... Parse status Response - // build NetworkStatusResponse - return networkStatusResp, nil -} - -func (s SomeAdapter) AccountBalance(ctx context.Context, request *types.AccountBalanceRequest) (*types.AccountBalanceResponse, *types.Error) { - resp := s.cosmosClient.Account() - // ... Parse cosmos specific account response - // build AccountBalanceResponse - return AccountBalanceResponse, nil -} - -// And we repeat for all the methods defined in the interface. -``` +### 2. Cosmos SDK Implementation -For further information about the `Servicer` interfaces, please refer to the [Coinbase's rosetta-sdk-go's documentation](https://pkg.go.dev/github.com/coinbase/rosetta-sdk-go@v0.5.9/server). +The cosmos sdk implementation, based on version, takes care of satisfying the `Client` interface. +In Stargate, Launchpad and 0.37, we have introduced the concept of rosetta.Msg, this message is not in the shared repository as the sdk.Msg type differs between cosmos-sdk versions. -### 2. Cosmos SDK Implementation +The rosetta.Msg interface follows: -As described, each Cosmos SDK release series will have version specific implementations of `Network` and `Adapter`, as -well as a `NewNetwork` constructor. - -Due to separation of interface and implementation, application developers have the option to override as needed, -using this code as reference. - -```golang -// NewNetwork returns the default application configuration. -func NewNetwork(options Options) service.Network { - cosmosClient := cosmos.NewClient(fmt.Sprintf("http://%s", options.CosmosEndpoint)) - tendermintClient := tendermint.NewClient(fmt.Sprintf("http://%s", options.TendermintEndpoint)) - - return service.Network{ - Properties: rosetta.NetworkProperties{ - Blockchain: options.Blockchain, - Network: options.Network, - SupportedOperations: []string{OperationTransfer}, - }, - Adapter: newAdapter( - cosmosClient, - tendermintClient, - properties{ - Blockchain: options.Blockchain, - Network: options.Network, - OfflineMode: options.OfflineMode, - }, - ), - } +```go +// Msg represents a cosmos-sdk message that can be converted from and to a rosetta operation. +type Msg interface { + sdk.Msg + ToOperations(withStatus, hasError bool) []*types.Operation + FromOperations(ops []*types.Operation) (sdk.Msg, error) } ``` +Hence developers who want to extend the rosetta set of supported operations just need to extend their module's sdk.Msgs with the `ToOperations` and `FromOperations` methods. ### 3. API service invocation As stated at the start, application developers will have two methods for invocation of the Rosetta API service: @@ -195,67 +185,13 @@ As stated at the start, application developers will have two methods for invocat #### Shared Process (Only Stargate) -Rosetta API service could run within the same execution process as the application. New configuration option and -command line flags would be provided to support this: - -```golang - if config.Rosetta.Enable { - .... - get contecxt, flags, etc - ... - - h, err := service.New( - service.Options{ListenAddress: config.Rosetta.ListenAddress}, - rosetta.NewNetwork(cdc, options), - ) - if err != nil { - } - - ... - - go func() { - if err := h.Start(config); err != nil { - errCh <- err - } - }() - } +Rosetta API service could run within the same execution process as the application. This would be enabled via app.toml settings, and if gRPC is not enabled the rosetta instance would be spinned in offline mode (tx building capabilities only). -``` #### Separate API service -Client application developers can write a new command to launch a Rosetta API server as a separate process too: - -```golang -func RosettaCommand(cdc *codec.Codec) *cobra.Command { - - ... - cmd := &cobra.Command{ - Use: "rosetta", - .... - - RunE: func(cmd *cobra.Command, args []string) error { - .... - get contecxt, flags, etc - ... - - h, err := service.New( - service.Options{Endpoint: endpoint}, - rosetta.NewNetwork(cdc, options), - ) - if err != nil { - return err - } - - ... - - h.Start() - } - } - ... +Client application developers can write a new command to launch a Rosetta API server as a separate process too, using the rosetta command contained in the `/server/rosetta` package. Construction of the command depends on cosmos sdk version. Examples can be found inside `simd` for stargate, and `contrib/rosetta/simapp` for other release series. -} -``` ## Status diff --git a/docs/run-node/README.md b/docs/run-node/README.md index 0a2f24b87939..27ee9726508a 100644 --- a/docs/run-node/README.md +++ b/docs/run-node/README.md @@ -13,3 +13,4 @@ This folder contains documentation on how to run a node and interact with it. 1. [Interacting with a Node](./interact-node.md) 1. [Generating, Signing and Broadcasting Transactions](./txs.md) 1. [Cosmos Upgrade Manager](./cosmovisor.md) +1. [Rosetta API](./rosetta.md) diff --git a/docs/run-node/rosetta.md b/docs/run-node/rosetta.md new file mode 100644 index 000000000000..b10bc21e7996 --- /dev/null +++ b/docs/run-node/rosetta.md @@ -0,0 +1,83 @@ +# Rosetta + +Package rosetta implements the rosetta API for the current cosmos sdk release series. + +The client satisfies [cosmos-rosetta-gateway](https://github.com/tendermint/cosmos-rosetta-gateway) `Client` interface implementation. + +## Extension + +There are two ways in which you can customize and extend the implementation with your custom settings. + +### Message extension + +In order to make an `sdk.Msg` understandable by rosetta the only thing which is required is adding the methods to your message that satisfy the `rosetta.Msg` interface. +Examples on how to do so can be found in the staking types such as `MsgDelegate`, or in bank types such as `MsgSend`. + +### Client interface override + +In case more customization is required, it's possible to embed the Client type and override the methods which require customizations. + +Example: +```go +package custom_client +import ( + +"context" +"github.com/coinbase/rosetta-sdk-go/types" +"github.com/cosmos/cosmos-sdk/server/rosetta" +) + +// CustomClient embeds the standard cosmos client +// which means that it implements the cosmos-rosetta-gateway Client +// interface while at the same time allowing to customize certain methods +type CustomClient struct { + *rosetta.Client +} + +func (c *CustomClient) ConstructionPayload(_ context.Context, request *types.ConstructionPayloadsRequest) (resp *types.ConstructionPayloadsResponse, err error) { + // provide custom signature bytes + panic("implement me") +} +``` + +### Error extension + +Since rosetta requires to provide 'returned' errors to network options. In order to declare a new rosetta error, we use the `errors` package in cosmos-rosetta-gateway. + +Example: + +```go +package custom_errors +import crgerrs "github.com/tendermint/cosmos-rosetta-gateway/errors" + +var customErrRetriable = true +var CustomError = crgerrs.RegisterError(100, "custom message", customErrRetriable, "description") +``` + +Note: errors must be registered before cosmos-rosetta-gateway's `Server`.`Start` method is called. Otherwise the registration will be ignored. Errors with same code will be ignored too. + +## Integration in app.go + +To integrate rosetta as a command in your application, in app.go, in your root command simply use the `server.RosettaCommand` method. + +Example: + +```go +package app +import ( + +"github.com/cosmos/cosmos-sdk/server" +"github.com/spf13/cobra" +) + +func buildAppCommand(rootCmd *cobra.Command) { + // more app.go init stuff + // ... + // add rosetta command + rootCmd.AddCommand(server.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler)) +} +``` + +A full implementation example can be found in `simapp` package. + +NOTE: when using a customized client, the command cannot be used as the constructors required **may** differ, so it's required to create a new one. We intend to provide a way to init a customized client without writing extra code in the future. \ No newline at end of file diff --git a/go.mod b/go.mod index 323f13ebc44d..3e2c956f3544 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,16 @@ module github.com/cosmos/cosmos-sdk require ( github.com/99designs/keyring v1.1.6 - github.com/DataDog/zstd v1.4.5 // indirect github.com/armon/go-metrics v0.3.6 github.com/bgentry/speakeasy v0.1.0 github.com/btcsuite/btcd v0.21.0-beta github.com/btcsuite/btcutil v1.0.2 + github.com/coinbase/rosetta-sdk-go v0.5.9 github.com/confio/ics23/go v0.6.3 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/iavl v0.15.3 github.com/cosmos/ledger-cosmos-go v0.11.1 github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect - github.com/dgraph-io/ristretto v0.0.3 // indirect - github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/enigmampc/btcutil v1.0.3-0.20200723161021-e2fb6adb2a25 github.com/gogo/gateway v1.1.0 github.com/gogo/protobuf v1.3.3 @@ -31,7 +29,7 @@ require ( github.com/magiconair/properties v1.8.4 github.com/mattn/go-isatty v0.0.12 github.com/otiai10/copy v1.4.2 - github.com/pelletier/go-toml v1.8.0 // indirect + github.com/pelletier/go-toml v1.8.1 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.8.0 github.com/prometheus/common v0.15.0 @@ -41,11 +39,12 @@ require ( github.com/spf13/afero v1.3.4 // indirect github.com/spf13/cast v1.3.1 github.com/spf13/cobra v1.1.1 - github.com/spf13/jwalterweatherman v1.1.0 // indirect; indirects + github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.7.0 github.com/tendermint/btcd v0.1.1 + github.com/tendermint/cosmos-rosetta-gateway v0.3.0-rc1 github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 github.com/tendermint/go-amino v0.16.0 github.com/tendermint/tendermint v0.34.3 @@ -54,6 +53,7 @@ require ( google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f google.golang.org/grpc v1.35.0 google.golang.org/protobuf v1.25.0 + gopkg.in/ini.v1 v1.61.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 21ae47248ee3..c99dd6b24c26 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,19 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/99designs/keyring v1.1.6 h1:kVDC2uCgVwecxCk+9zoCt2uEL6dt+dfVzMvGgnVcIuM= github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -26,6 +39,8 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/Workiva/go-datastructures v1.0.52 h1:PLSK6pwn8mYdaoaCZEMsXBpBotr4HHn9abU0yMQt0NI= @@ -37,9 +52,11 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -48,6 +65,7 @@ github.com/armon/go-metrics v0.3.6/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -57,6 +75,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= @@ -76,6 +95,7 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= @@ -83,9 +103,13 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coinbase/rosetta-sdk-go v0.5.8/go.mod h1:xd4wYUhV3LkY78SPH8BUhc88rXfn2jYgN9BfiSjbcvM= +github.com/coinbase/rosetta-sdk-go v0.5.9 h1:CuGQE3HFmYwdEACJnuOtVI9cofqPsGvq6FdFIzaOPKI= +github.com/coinbase/rosetta-sdk-go v0.5.9/go.mod h1:xd4wYUhV3LkY78SPH8BUhc88rXfn2jYgN9BfiSjbcvM= github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/confio/ics23/go v0.6.3 h1:PuGK2V1NJWZ8sSkNDq91jgT/cahFEW9RGp4Y5jxulf0= github.com/confio/ics23/go v0.6.3/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= @@ -120,6 +144,7 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= @@ -134,29 +159,39 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b h1:HBah4D48ypg3J7Np4N+HY/ZR76fx3HEUGxDU6Uk39oQ= github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/enigmampc/btcutil v1.0.3-0.20200723161021-e2fb6adb2a25 h1:2vLKys4RBU4pn2T/hjXMbvwTr1Cvy5THHrQkbeY9HRk= github.com/enigmampc/btcutil v1.0.3-0.20200723161021-e2fb6adb2a25/go.mod h1:hTr8+TLQmkUkgcuh3mcr5fjrT9c64ZzsBCdCEC6UppY= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.9.23 h1:SIKhg/z4Q7AbvqcxuPYvMxf36che/Rq/Pp0IdYEkbtw= +github.com/ethereum/go-ethereum v1.9.23/go.mod h1:JIfVb6esrqALTExdz9hRYvrP0xBDf6wCncIu1hNwHpM= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= @@ -164,6 +199,7 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -175,6 +211,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -207,6 +245,7 @@ github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -218,9 +257,13 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -241,8 +284,10 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.1/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= @@ -293,13 +338,18 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/improbable-eng/grpc-web v0.13.0 h1:7XqtaBWaOCH0cVGKHyvhtcuo6fgW32Y10yRKrDHFHOc= github.com/improbable-eng/grpc-web v0.13.0/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -317,8 +367,10 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -331,23 +383,36 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -365,6 +430,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -374,6 +441,8 @@ github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ib github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -389,6 +458,8 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -420,11 +491,13 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= -github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= @@ -472,6 +545,7 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= @@ -482,11 +556,14 @@ github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzy github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs= github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= @@ -497,6 +574,7 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y= github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -535,6 +613,9 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= +github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -545,6 +626,7 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -556,6 +638,8 @@ github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzH github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= +github.com/tendermint/cosmos-rosetta-gateway v0.3.0-rc1 h1:UEkXlFMcWdQWPOA/Rf8nV4WruMK7JO2U8iX7rMTuC24= +github.com/tendermint/cosmos-rosetta-gateway v0.3.0-rc1/go.mod h1:gBPw8WV2Erm4UGHlBRiM3zaEBst4bsuihmMCNQdgP/s= github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI= github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= @@ -568,13 +652,21 @@ github.com/tendermint/tendermint v0.34.3/go.mod h1:h57vnXeOlrdvvNFCqPBSaOrpOivl+ github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI= github.com/tendermint/tm-db v0.6.3 h1:ZkhQcKnB8/2jr5EaZwGndN4owkPsGezW2fSisS9zGbg= github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8= +github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.2/go.mod h1:SEzaDwxiPzKzNfUEO4HbYF/m4UCSJDsGgNqsS1LvdoY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vmihailenco/msgpack/v5 v5.0.0-beta.9/go.mod h1:HVxBVPUK/+fZMonk4bi1islLa8V3cfnBug0+4dykPzo= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -623,6 +715,7 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= @@ -636,15 +729,18 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -669,6 +765,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -681,6 +778,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -692,6 +790,7 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -713,12 +812,16 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -754,6 +857,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -818,9 +922,14 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.61.0 h1:LBCdW4FmFYL4s/vDZD1RQYX7oAR6IjujCYgMdbHBR10= +gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -834,6 +943,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/server/config/config.go b/server/config/config.go index 68f6cf4352f6..8f1c47e88d2c 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -101,6 +101,29 @@ type APIConfig struct { // Ref: https://github.com/cosmos/cosmos-sdk/issues/6420 } +// RosettaConfig defines the Rosetta API listener configuration. +type RosettaConfig struct { + // Address defines the API server to listen on + Address string `mapstructure:"address"` + + // Blockchain defines the blockchain name + // defaults to DefaultBlockchain + Blockchain string `mapstructure:"blockchain"` + + // Network defines the network name + Network string `mapstructure:"network"` + + // Retries defines the maximum number of retries + // rosetta will do before quitting + Retries int `mapstructure:"retries"` + + // Enable defines if the API server should be enabled. + Enable bool `mapstructure:"enable"` + + // Offline defines if the server must be run in offline mode + Offline bool `mapstructure:"offline"` +} + // GRPCConfig defines configuration for the gRPC server. type GRPCConfig struct { // Enable defines if the gRPC server should be enabled. @@ -138,6 +161,7 @@ type Config struct { Telemetry telemetry.Config `mapstructure:"telemetry"` API APIConfig `mapstructure:"api"` GRPC GRPCConfig `mapstructure:"grpc"` + Rosetta RosettaConfig `mapstructure:"rosetta"` GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` StateSync StateSyncConfig `mapstructure:"state-sync"` } @@ -198,6 +222,14 @@ func DefaultConfig() *Config { Enable: true, Address: DefaultGRPCAddress, }, + Rosetta: RosettaConfig{ + Enable: false, + Address: ":8080", + Blockchain: "app", + Network: "network", + Retries: 3, + Offline: false, + }, GRPCWeb: GRPCWebConfig{ Enable: true, Address: DefaultGRPCWebAddress, @@ -252,6 +284,14 @@ func GetConfig(v *viper.Viper) Config { RPCMaxBodyBytes: v.GetUint("api.rpc-max-body-bytes"), EnableUnsafeCORS: v.GetBool("api.enabled-unsafe-cors"), }, + Rosetta: RosettaConfig{ + Enable: v.GetBool("rosetta.enable"), + Address: v.GetString("rosetta.address"), + Blockchain: v.GetString("rosetta.blockchain"), + Network: v.GetString("rosetta.network"), + Retries: v.GetInt("rosetta.retries"), + Offline: v.GetBool("rosetta.offline"), + }, GRPC: GRPCConfig{ Enable: v.GetBool("grpc.enable"), Address: v.GetString("grpc.address"), diff --git a/server/config/toml.go b/server/config/toml.go index 58dc7bfed2b4..88197defe91b 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -135,6 +135,30 @@ rpc-max-body-bytes = {{ .API.RPCMaxBodyBytes }} # EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk). enabled-unsafe-cors = {{ .API.EnableUnsafeCORS }} +############################################################################### +### Rosetta Configuration ### +############################################################################### + +[rosetta] + +# Enable defines if the Rosetta API server should be enabled. +enable = {{ .Rosetta.Enable }} + +# Address defines the Rosetta API server to listen on. +address = "{{ .Rosetta.Address }}" + +# Network defines the name of the blockchain that will be returned by Rosetta. +blockchain = "{{ .Rosetta.Blockchain }}" + +# Network defines the name of the network that will be returned by Rosetta. +network = "{{ .Rosetta.Network }}" + +# Retries defines the number of retries when connecting to the node before failing. +retries = {{ .Rosetta.Retries }} + +# Offline defines if Rosetta server should run in offline mode. +offline = {{ .Rosetta.Offline }} + ############################################################################### ### gRPC Configuration ### ############################################################################### diff --git a/server/rosetta.go b/server/rosetta.go new file mode 100644 index 000000000000..c6a928d062d0 --- /dev/null +++ b/server/rosetta.go @@ -0,0 +1,42 @@ +package server + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/server/rosetta" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" +) + +// RosettaCommand builds the rosetta root command given +// a protocol buffers serializer/deserializer +func RosettaCommand(ir codectypes.InterfaceRegistry, cdc codec.Marshaler) *cobra.Command { + cmd := &cobra.Command{ + Use: "rosetta", + Short: "spin up a rosetta server", + RunE: func(cmd *cobra.Command, args []string) error { + conf, err := rosetta.FromFlags(cmd.Flags()) + if err != nil { + return err + } + + protoCodec, ok := cdc.(*codec.ProtoCodec) + if !ok { + return fmt.Errorf("exoected *codec.ProtoMarshaler, got: %T", cdc) + } + conf.WithCodec(ir, protoCodec) + + rosettaSrv, err := rosetta.ServerFromConfig(conf) + if err != nil { + return err + } + return rosettaSrv.Start() + }, + } + rosetta.SetFlags(cmd.Flags()) + + return cmd +} diff --git a/server/rosetta/client_offline.go b/server/rosetta/client_offline.go new file mode 100644 index 000000000000..f619bfc6d2cf --- /dev/null +++ b/server/rosetta/client_offline.go @@ -0,0 +1,221 @@ +package rosetta + +import ( + "context" + "encoding/hex" + "strings" + + "github.com/btcsuite/btcd/btcec" + "github.com/coinbase/rosetta-sdk-go/types" + crgerrs "github.com/tendermint/cosmos-rosetta-gateway/errors" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +func (c *Client) OperationStatuses() []*types.OperationStatus { + return []*types.OperationStatus{ + { + Status: StatusSuccess, + Successful: true, + }, + { + Status: StatusReverted, + Successful: false, + }, + } +} + +func (c *Client) Version() string { + return c.version +} + +func (c *Client) SupportedOperations() []string { + var supportedOperations []string + for _, ii := range c.ir.ListImplementations("cosmos.base.v1beta1.Msg") { + resolve, err := c.ir.Resolve(ii) + if err != nil { + continue + } + + if _, ok := resolve.(Msg); ok { + supportedOperations = append(supportedOperations, strings.TrimLeft(ii, "/")) + } + } + + supportedOperations = append(supportedOperations, OperationFee) + + return supportedOperations +} + +func (c *Client) SignedTx(ctx context.Context, txBytes []byte, signatures []*types.Signature) (signedTxBytes []byte, err error) { + TxConfig := c.getTxConfig() + rawTx, err := TxConfig.TxDecoder()(txBytes) + if err != nil { + return nil, err + } + + txBldr, err := TxConfig.WrapTxBuilder(rawTx) + if err != nil { + return nil, err + } + + var sigs = make([]signing.SignatureV2, len(signatures)) + for i, signature := range signatures { + if signature.PublicKey.CurveType != types.Secp256k1 { + return nil, crgerrs.ErrUnsupportedCurve + } + + cmp, err := btcec.ParsePubKey(signature.PublicKey.Bytes, btcec.S256()) + if err != nil { + return nil, err + } + + compressedPublicKey := make([]byte, secp256k1.PubKeySize) + copy(compressedPublicKey, cmp.SerializeCompressed()) + pubKey := &secp256k1.PubKey{Key: compressedPublicKey} + + accountInfo, err := c.accountInfo(ctx, sdk.AccAddress(pubKey.Address()).String(), nil) + if err != nil { + return nil, err + } + + sig := signing.SignatureV2{ + PubKey: pubKey, + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + Signature: signature.Bytes, + }, + Sequence: accountInfo.GetSequence(), + } + sigs[i] = sig + } + + if err = txBldr.SetSignatures(sigs...); err != nil { + return nil, err + } + + txBytes, err = c.getTxConfig().TxEncoder()(txBldr.GetTx()) + if err != nil { + return nil, err + } + + return txBytes, nil +} + +func (c *Client) ConstructionPayload(_ context.Context, request *types.ConstructionPayloadsRequest) (resp *types.ConstructionPayloadsResponse, err error) { + // check if there is at least one operation + if len(request.Operations) < 1 { + return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, "expected at least one operation") + } + + // convert rosetta operations to sdk msgs and fees (if present) + msgs, fee, err := opsToMsgsAndFees(c.ir, request.Operations) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, err.Error()) + } + + metadata, err := getMetadataFromPayloadReq(request) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error()) + } + + txFactory := tx.Factory{}.WithAccountNumber(metadata.AccountNumber).WithChainID(metadata.ChainID). + WithGas(metadata.Gas).WithSequence(metadata.Sequence).WithMemo(metadata.Memo).WithFees(fee.String()) + + TxConfig := c.getTxConfig() + txFactory = txFactory.WithTxConfig(TxConfig) + + txBldr, err := tx.BuildUnsignedTx(txFactory, msgs...) + if err != nil { + return nil, err + } + + // Sign_mode_legacy_amino is being used as default here, as sign_mode_direct + // needs the signer infos to be set before hand but rosetta doesn't have a way + // to do this yet. To be revisited in future versions of sdk and rosetta + if txFactory.SignMode() == signing.SignMode_SIGN_MODE_UNSPECIFIED { + txFactory = txFactory.WithSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + } + + signerData := authsigning.SignerData{ + ChainID: txFactory.ChainID(), + AccountNumber: txFactory.AccountNumber(), + Sequence: txFactory.Sequence(), + } + + signBytes, err := TxConfig.SignModeHandler().GetSignBytes(txFactory.SignMode(), signerData, txBldr.GetTx()) + if err != nil { + return nil, err + } + + txBytes, err := TxConfig.TxEncoder()(txBldr.GetTx()) + if err != nil { + return nil, err + } + + accIdentifiers := getAccountIdentifiersByMsgs(msgs) + + payloads := make([]*types.SigningPayload, len(accIdentifiers)) + for i, accID := range accIdentifiers { + payloads[i] = &types.SigningPayload{ + AccountIdentifier: accID, + Bytes: crypto.Sha256(signBytes), + SignatureType: types.Ecdsa, + } + } + + return &types.ConstructionPayloadsResponse{ + UnsignedTransaction: hex.EncodeToString(txBytes), + Payloads: payloads, + }, nil +} + +func getAccountIdentifiersByMsgs(msgs []sdk.Msg) []*types.AccountIdentifier { + var accIdentifiers []*types.AccountIdentifier + for _, msg := range msgs { + for _, signer := range msg.GetSigners() { + accIdentifiers = append(accIdentifiers, &types.AccountIdentifier{Address: signer.String()}) + } + } + + return accIdentifiers +} + +func (c *Client) PreprocessOperationsToOptions(_ context.Context, req *types.ConstructionPreprocessRequest) (options map[string]interface{}, err error) { + operations := req.Operations + if len(operations) < 1 { + return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, "invalid number of operations") + } + + msgs, err := opsToMsgs(c.ir, operations) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, err.Error()) + } + + if len(msgs) < 1 || len(msgs[0].GetSigners()) < 1 { + return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, "operation produced no msg or signers") + } + + memo, ok := req.Metadata["memo"] + if !ok { + memo = "" + } + + defaultGas := float64(200000) + + gas := req.SuggestedFeeMultiplier + if gas == nil { + gas = &defaultGas + } + + return map[string]interface{}{ + OptionAddress: msgs[0].GetSigners()[0], + OptionMemo: memo, + OptionGas: gas, + }, nil +} diff --git a/server/rosetta/client_online.go b/server/rosetta/client_online.go new file mode 100644 index 000000000000..110430bb1806 --- /dev/null +++ b/server/rosetta/client_online.go @@ -0,0 +1,447 @@ +package rosetta + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "strconv" + "time" + + "github.com/cosmos/cosmos-sdk/version" + + abcitypes "github.com/tendermint/tendermint/abci/types" + + "github.com/tendermint/btcd/btcec" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + + "github.com/coinbase/rosetta-sdk-go/types" + "google.golang.org/grpc/metadata" + + "github.com/tendermint/tendermint/rpc/client/http" + tmtypes "github.com/tendermint/tendermint/rpc/core/types" + "google.golang.org/grpc" + + crgerrs "github.com/tendermint/cosmos-rosetta-gateway/errors" + crgtypes "github.com/tendermint/cosmos-rosetta-gateway/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + auth "github.com/cosmos/cosmos-sdk/x/auth/types" + bank "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// interface assertion +var _ crgtypes.Client = (*Client)(nil) + +const tmWebsocketPath = "/websocket" +const defaultNodeTimeout = 15 * time.Second + +// Client implements a single network client to interact with cosmos based chains +type Client struct { + config *Config + + auth auth.QueryClient + bank bank.QueryClient + + ir codectypes.InterfaceRegistry + + clientCtx client.Context + + version string +} + +func (c *Client) AccountIdentifierFromPublicKey(pubKey *types.PublicKey) (*types.AccountIdentifier, error) { + if pubKey.CurveType != "secp256k1" { + return nil, crgerrs.WrapError(crgerrs.ErrUnsupportedCurve, "only secp256k1 supported") + } + + cmp, err := btcec.ParsePubKey(pubKey.Bytes, btcec.S256()) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error()) + } + + compressedPublicKey := make([]byte, secp256k1.PubKeySize) + copy(compressedPublicKey, cmp.SerializeCompressed()) + + pk := secp256k1.PubKey{Key: compressedPublicKey} + + return &types.AccountIdentifier{ + Address: sdk.AccAddress(pk.Address()).String(), + }, nil +} + +// NewClient instantiates a new online servicer +func NewClient(cfg *Config) (*Client, error) { + info := version.NewInfo() + + v := info.Version + if v == "" { + v = "unknown" + } + + return &Client{ + config: cfg, + ir: cfg.InterfaceRegistry, + version: fmt.Sprintf("%s/%s", info.AppName, v), + }, nil +} + +func (c *Client) accountInfo(ctx context.Context, addr string, height *int64) (auth.AccountI, error) { + if height != nil { + strHeight := strconv.FormatInt(*height, 10) + ctx = metadata.AppendToOutgoingContext(ctx, grpctypes.GRPCBlockHeightHeader, strHeight) + } + + accountInfo, err := c.auth.Account(ctx, &auth.QueryAccountRequest{ + Address: addr, + }) + if err != nil { + return nil, crgerrs.FromGRPCToRosettaError(err) + } + + var account auth.AccountI + err = c.ir.UnpackAny(accountInfo.Account, &account) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) + } + + return account, nil +} + +func (c *Client) Balances(ctx context.Context, addr string, height *int64) ([]*types.Amount, error) { + if height != nil { + strHeight := strconv.FormatInt(*height, 10) + ctx = metadata.AppendToOutgoingContext(ctx, grpctypes.GRPCBlockHeightHeader, strHeight) + } + + balance, err := c.bank.AllBalances(ctx, &bank.QueryAllBalancesRequest{ + Address: addr, + }) + if err != nil { + return nil, crgerrs.FromGRPCToRosettaError(err) + } + + availableCoins, err := c.coins(ctx) + if err != nil { + return nil, err + } + + return sdkCoinsToRosettaAmounts(balance.Balances, availableCoins), nil +} + +func (c *Client) BlockByHash(ctx context.Context, hash string) (crgtypes.BlockResponse, error) { + bHash, err := hex.DecodeString(hash) + if err != nil { + return crgtypes.BlockResponse{}, fmt.Errorf("invalid block hash: %s", err) + } + + block, err := c.clientCtx.Client.BlockByHash(ctx, bHash) + if err != nil { + return crgtypes.BlockResponse{}, err + } + + return buildBlockResponse(block), nil +} + +func (c *Client) BlockByHeight(ctx context.Context, height *int64) (crgtypes.BlockResponse, error) { + block, err := c.clientCtx.Client.Block(ctx, height) + if err != nil { + return crgtypes.BlockResponse{}, err + } + + return buildBlockResponse(block), nil +} + +func buildBlockResponse(block *tmtypes.ResultBlock) crgtypes.BlockResponse { + return crgtypes.BlockResponse{ + Block: TMBlockToRosettaBlockIdentifier(block), + ParentBlock: TMBlockToRosettaParentBlockIdentifier(block), + MillisecondTimestamp: timeToMilliseconds(block.Block.Time), + TxCount: int64(len(block.Block.Txs)), + } +} + +func (c *Client) BlockTransactionsByHash(ctx context.Context, hash string) (crgtypes.BlockTransactionsResponse, error) { + blockResp, err := c.BlockByHash(ctx, hash) + if err != nil { + return crgtypes.BlockTransactionsResponse{}, err + } + + txs, err := c.listTransactionsInBlock(ctx, blockResp.Block.Index) + if err != nil { + return crgtypes.BlockTransactionsResponse{}, err + } + + return crgtypes.BlockTransactionsResponse{ + BlockResponse: blockResp, + Transactions: sdkTxsWithHashToRosettaTxs(txs), + }, nil +} + +func (c *Client) BlockTransactionsByHeight(ctx context.Context, height *int64) (crgtypes.BlockTransactionsResponse, error) { + blockResp, err := c.BlockByHeight(ctx, height) + if err != nil { + return crgtypes.BlockTransactionsResponse{}, err + } + + txs, err := c.listTransactionsInBlock(ctx, blockResp.Block.Index) + if err != nil { + return crgtypes.BlockTransactionsResponse{}, err + } + + return crgtypes.BlockTransactionsResponse{ + BlockResponse: blockResp, + Transactions: sdkTxsWithHashToRosettaTxs(txs), + }, nil +} + +// Coins fetches the existing coins in the application +func (c *Client) coins(ctx context.Context) (sdk.Coins, error) { + supply, err := c.bank.TotalSupply(ctx, &bank.QueryTotalSupplyRequest{}) + if err != nil { + return nil, crgerrs.FromGRPCToRosettaError(err) + } + return supply.Supply, nil +} + +// listTransactionsInBlock returns the list of the transactions in a block given its height +func (c *Client) listTransactionsInBlock(ctx context.Context, height int64) ([]*sdkTxWithHash, error) { + txQuery := fmt.Sprintf(`tx.height=%d`, height) + txList, err := c.clientCtx.Client.TxSearch(ctx, txQuery, true, nil, nil, "") + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrUnknown, err.Error()) + } + + sdkTxs, err := tmResultTxsToSdkTxsWithHash(c.clientCtx.TxConfig.TxDecoder(), txList.Txs) + if err != nil { + return nil, err + } + return sdkTxs, nil +} + +func (c *Client) TxOperationsAndSignersAccountIdentifiers(signed bool, txBytes []byte) (ops []*types.Operation, signers []*types.AccountIdentifier, err error) { + txConfig := c.getTxConfig() + rawTx, err := txConfig.TxDecoder()(txBytes) + if err != nil { + return nil, nil, err + } + + txBldr, err := txConfig.WrapTxBuilder(rawTx) + if err != nil { + return nil, nil, err + } + + var accountIdentifierSigners []*types.AccountIdentifier + if signed { + addrs := txBldr.GetTx().GetSigners() + for _, addr := range addrs { + signer := &types.AccountIdentifier{ + Address: addr.String(), + } + accountIdentifierSigners = append(accountIdentifierSigners, signer) + } + } + + return sdkTxToOperations(txBldr.GetTx(), false, false), accountIdentifierSigners, nil +} + +// GetTx returns a transaction given its hash +func (c *Client) GetTx(_ context.Context, hash string) (*types.Transaction, error) { + txResp, err := authclient.QueryTx(c.clientCtx, hash) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrUnknown, err.Error()) + } + var sdkTx sdk.Tx + err = c.ir.UnpackAny(txResp.Tx, &sdkTx) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) + } + return sdkTxWithHashToOperations(&sdkTxWithHash{ + HexHash: txResp.TxHash, + Code: txResp.Code, + Log: txResp.RawLog, + Tx: sdkTx, + }), nil +} + +// GetUnconfirmedTx gets an unconfirmed transaction given its hash +func (c *Client) GetUnconfirmedTx(ctx context.Context, hash string) (*types.Transaction, error) { + res, err := c.clientCtx.Client.UnconfirmedTxs(ctx, nil) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrNotFound, "unconfirmed tx not found") + } + + hashAsBytes, err := hex.DecodeString(hash) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrInterpreting, "invalid hash") + } + + for _, tx := range res.Txs { + if bytes.Equal(tx.Hash(), hashAsBytes) { + sdkTx, err := tmTxToSdkTx(c.clientCtx.TxConfig.TxDecoder(), tx) + if err != nil { + return nil, err + } + + return &types.Transaction{ + TransactionIdentifier: TmTxToRosettaTxsIdentifier(tx), + Operations: sdkTxToOperations(sdkTx, false, false), + Metadata: nil, + }, nil + } + } + + return nil, crgerrs.WrapError(crgerrs.ErrNotFound, "transaction not found in mempool") +} + +// Mempool returns the unconfirmed transactions in the mempool +func (c *Client) Mempool(ctx context.Context) ([]*types.TransactionIdentifier, error) { + txs, err := c.clientCtx.Client.UnconfirmedTxs(ctx, nil) + if err != nil { + return nil, err + } + + return TMTxsToRosettaTxsIdentifiers(txs.Txs), nil +} + +// Peers gets the number of peers +func (c *Client) Peers(ctx context.Context) ([]*types.Peer, error) { + netInfo, err := c.clientCtx.Client.NetInfo(ctx) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrUnknown, err.Error()) + } + return TmPeersToRosettaPeers(netInfo.Peers), nil +} + +func (c *Client) Status(ctx context.Context) (*types.SyncStatus, error) { + status, err := c.clientCtx.Client.Status(ctx) + if err != nil { + return nil, crgerrs.WrapError(crgerrs.ErrUnknown, err.Error()) + } + return TMStatusToRosettaSyncStatus(status), err +} + +func (c *Client) getTxConfig() client.TxConfig { + return c.clientCtx.TxConfig +} + +func (c *Client) PostTx(txBytes []byte) (*types.TransactionIdentifier, map[string]interface{}, error) { + // sync ensures it will go through checkTx + res, err := c.clientCtx.BroadcastTxSync(txBytes) + if err != nil { + return nil, nil, crgerrs.WrapError(crgerrs.ErrUnknown, err.Error()) + } + // check if tx was broadcast successfully + if res.Code != abcitypes.CodeTypeOK { + return nil, nil, crgerrs.WrapError(crgerrs.ErrUnknown, fmt.Sprintf("transaction broadcast failure: (%d) %s ", res.Code, res.RawLog)) + } + + return &types.TransactionIdentifier{ + Hash: res.TxHash, + }, + map[string]interface{}{ + Log: res.RawLog, + }, nil +} + +func (c *Client) ConstructionMetadataFromOptions(ctx context.Context, options map[string]interface{}) (meta map[string]interface{}, err error) { + if len(options) == 0 { + return nil, crgerrs.ErrBadArgument + } + + addr, ok := options[OptionAddress] + if !ok { + return nil, crgerrs.WrapError(crgerrs.ErrInvalidAddress, "no address provided") + } + + addrString, ok := addr.(string) + if !ok { + return nil, crgerrs.WrapError(crgerrs.ErrInvalidAddress, "address is not a string") + } + + accountInfo, err := c.accountInfo(ctx, addrString, nil) + if err != nil { + return nil, err + } + + gas, ok := options[OptionGas] + if !ok { + return nil, crgerrs.WrapError(crgerrs.ErrInvalidAddress, "gas not set") + } + + memo, ok := options[OptionMemo] + if !ok { + return nil, crgerrs.WrapError(crgerrs.ErrInvalidMemo, "memo not set") + } + + status, err := c.clientCtx.Client.Status(ctx) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + OptionAccountNumber: accountInfo.GetAccountNumber(), + OptionSequence: accountInfo.GetSequence(), + OptionChainID: status.NodeInfo.Network, + OptionGas: gas, + OptionMemo: memo, + }, nil +} + +func (c *Client) Ready() error { + ctx, cancel := context.WithTimeout(context.Background(), defaultNodeTimeout) + defer cancel() + _, err := c.clientCtx.Client.Health(ctx) + if err != nil { + return err + } + _, err = c.bank.TotalSupply(ctx, &bank.QueryTotalSupplyRequest{}) + if err != nil { + return err + } + return nil +} + +func (c *Client) Bootstrap() error { + grpcConn, err := grpc.Dial(c.config.GRPCEndpoint, grpc.WithInsecure()) + if err != nil { + return err + } + + tmRPC, err := http.New(c.config.TendermintRPC, tmWebsocketPath) + if err != nil { + return err + } + + authClient := auth.NewQueryClient(grpcConn) + bankClient := bank.NewQueryClient(grpcConn) + + // NodeURI and Client are set from here otherwise + // WitNodeURI will require to create a new client + // it's done here because WithNodeURI panics if + // connection to tendermint node fails + clientCtx := client.Context{ + Client: tmRPC, + NodeURI: c.config.TendermintRPC, + } + clientCtx = clientCtx. + WithJSONMarshaler(c.config.Codec). + WithInterfaceRegistry(c.config.InterfaceRegistry). + WithTxConfig(authtx.NewTxConfig(c.config.Codec, authtx.DefaultSignModes)). + WithAccountRetriever(auth.AccountRetriever{}). + WithBroadcastMode(flags.BroadcastBlock) + + c.auth = authClient + c.bank = bankClient + c.clientCtx = clientCtx + c.ir = c.config.InterfaceRegistry + + return nil +} diff --git a/server/rosetta/codec.go b/server/rosetta/codec.go new file mode 100644 index 000000000000..96242a9e044a --- /dev/null +++ b/server/rosetta/codec.go @@ -0,0 +1,22 @@ +package rosetta + +import ( + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/types" + bankcodec "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// MakeCodec generates the codec required to interact +// with the cosmos APIs used by the rosetta gateway +func MakeCodec() (*codec.ProtoCodec, codectypes.InterfaceRegistry) { + ir := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(ir) + + authcodec.RegisterInterfaces(ir) + bankcodec.RegisterInterfaces(ir) + cryptocodec.RegisterInterfaces(ir) + + return cdc, ir +} diff --git a/server/rosetta/config.go b/server/rosetta/config.go new file mode 100644 index 000000000000..a6a81669398d --- /dev/null +++ b/server/rosetta/config.go @@ -0,0 +1,203 @@ +package rosetta + +import ( + "fmt" + "strings" + "time" + + "github.com/coinbase/rosetta-sdk-go/types" + "github.com/spf13/pflag" + crg "github.com/tendermint/cosmos-rosetta-gateway/server" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" +) + +// configuration defaults constants +const ( + // DefaultBlockchain defines the default blockchain identifier name + DefaultBlockchain = "app" + // DefaultAddr defines the default rosetta binding address + DefaultAddr = ":8080" + // DefaultRetries is the default number of retries + DefaultRetries = 5 + // DefaultTendermintEndpoint is the default value for the tendermint endpoint + DefaultTendermintEndpoint = "localhost:26657" + // DefaultGRPCEndpoint is the default value for the gRPC endpoint + DefaultGRPCEndpoint = "localhost:9090" + // DefaultNetwork defines the default network name + DefaultNetwork = "network" + // DefaultOffline defines the default offline value + DefaultOffline = false +) + +// configuration flags +const ( + FlagBlockchain = "blockchain" + FlagNetwork = "network" + FlagTendermintEndpoint = "tendermint" + FlagGRPCEndpoint = "grpc" + FlagAddr = "addr" + FlagRetries = "retries" + FlagOffline = "offline" +) + +// Config defines the configuration of the rosetta server +type Config struct { + // Blockchain defines the blockchain name + // defaults to DefaultBlockchain + Blockchain string + // Network defines the network name + Network string + // TendermintRPC defines the endpoint to connect to + // tendermint RPC, specifying 'tcp://' before is not + // required, usually it's at port 26657 of the + TendermintRPC string + // GRPCEndpoint defines the cosmos application gRPC endpoint + // usually it is located at 9090 port + GRPCEndpoint string + // Addr defines the default address to bind the rosetta server to + // defaults to DefaultAddr + Addr string + // Retries defines the maximum number of retries + // rosetta will do before quitting + Retries int + // Offline defines if the server must be run in offline mode + Offline bool + // Codec overrides the default data and construction api client codecs + Codec *codec.ProtoCodec + // InterfaceRegistry overrides the default data and construction api interface registry + InterfaceRegistry codectypes.InterfaceRegistry +} + +// NetworkIdentifier returns the network identifier given the configuration +func (c *Config) NetworkIdentifier() *types.NetworkIdentifier { + return &types.NetworkIdentifier{ + Blockchain: c.Blockchain, + Network: c.Network, + } +} + +// validate validates a configuration and sets +// its defaults in case they were not provided +func (c *Config) validate() error { + if (c.Codec == nil) != (c.InterfaceRegistry == nil) { + return fmt.Errorf("codec and interface registry must be both different from nil or nil") + } + + if c.Addr == "" { + c.Addr = DefaultAddr + } + if c.Blockchain == "" { + c.Blockchain = DefaultBlockchain + } + if c.Retries == 0 { + c.Retries = DefaultRetries + } + // these are must + if c.Network == "" { + return fmt.Errorf("network not provided") + } + if c.Offline { + return fmt.Errorf("offline mode is not supported for stargate implementation due to how sigv2 works") + } + + // these are optional but it must be online + if c.GRPCEndpoint == "" { + return fmt.Errorf("grpc endpoint not provided") + } + if c.TendermintRPC == "" { + return fmt.Errorf("tendermint rpc not provided") + } + if !strings.HasPrefix(c.TendermintRPC, "tcp://") { + c.TendermintRPC = fmt.Sprintf("tcp://%s", c.TendermintRPC) + } + + return nil +} + +// WithCodec extends the configuration with a predefined Codec +func (c *Config) WithCodec(ir codectypes.InterfaceRegistry, cdc *codec.ProtoCodec) { + c.Codec = cdc + c.InterfaceRegistry = ir +} + +// FromFlags gets the configuration from flags +func FromFlags(flags *pflag.FlagSet) (*Config, error) { + blockchain, err := flags.GetString(FlagBlockchain) + if err != nil { + return nil, err + } + network, err := flags.GetString(FlagNetwork) + if err != nil { + return nil, err + } + tendermintRPC, err := flags.GetString(FlagTendermintEndpoint) + if err != nil { + return nil, err + } + gRPCEndpoint, err := flags.GetString(FlagGRPCEndpoint) + if err != nil { + return nil, err + } + addr, err := flags.GetString(FlagAddr) + if err != nil { + return nil, err + } + retries, err := flags.GetInt(FlagRetries) + if err != nil { + return nil, err + } + offline, err := flags.GetBool(FlagOffline) + if err != nil { + return nil, err + } + conf := &Config{ + Blockchain: blockchain, + Network: network, + TendermintRPC: tendermintRPC, + GRPCEndpoint: gRPCEndpoint, + Addr: addr, + Retries: retries, + Offline: offline, + } + err = conf.validate() + if err != nil { + return nil, err + } + return conf, nil +} + +func ServerFromConfig(conf *Config) (crg.Server, error) { + err := conf.validate() + if err != nil { + return crg.Server{}, err + } + client, err := NewClient(conf) + if err != nil { + return crg.Server{}, err + } + return crg.NewServer( + crg.Settings{ + Network: &types.NetworkIdentifier{ + Blockchain: conf.Blockchain, + Network: conf.Network, + }, + Client: client, + Listen: conf.Addr, + Offline: conf.Offline, + Retries: conf.Retries, + RetryWait: 15 * time.Second, + }) +} + +// SetFlags sets the configuration flags to the given flagset +func SetFlags(flags *pflag.FlagSet) { + flags.String(FlagBlockchain, DefaultBlockchain, "the blockchain type") + flags.String(FlagNetwork, DefaultNetwork, "the network name") + flags.String(FlagTendermintEndpoint, DefaultTendermintEndpoint, "the tendermint rpc endpoint, without tcp://") + flags.String(FlagGRPCEndpoint, DefaultGRPCEndpoint, "the app gRPC endpoint") + flags.String(FlagAddr, DefaultAddr, "the address rosetta will bind to") + flags.Int(FlagRetries, DefaultRetries, "the number of retries that will be done before quitting") + flags.Bool(FlagOffline, DefaultOffline, "run rosetta only with construction API") +} diff --git a/server/rosetta/conv_from_rosetta.go b/server/rosetta/conv_from_rosetta.go new file mode 100644 index 000000000000..da9ea5b2ed6f --- /dev/null +++ b/server/rosetta/conv_from_rosetta.go @@ -0,0 +1,211 @@ +package rosetta + +import ( + "fmt" + "time" + + "github.com/coinbase/rosetta-sdk-go/types" + tmcoretypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// timeToMilliseconds converts time to milliseconds timestamp +func timeToMilliseconds(t time.Time) int64 { + return t.UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) +} + +// sdkCoinsToRosettaAmounts converts []sdk.Coin to rosetta amounts +// availableCoins keeps track of current available coins vs the coins +// owned by an address. This is required to support historical balances +// as rosetta expects them to be set to 0, if an address does not own them +func sdkCoinsToRosettaAmounts(ownedCoins []sdk.Coin, availableCoins sdk.Coins) []*types.Amount { + amounts := make([]*types.Amount, len(availableCoins)) + ownedCoinsMap := make(map[string]sdk.Int, len(availableCoins)) + + for _, ownedCoin := range ownedCoins { + ownedCoinsMap[ownedCoin.Denom] = ownedCoin.Amount + } + + for i, coin := range availableCoins { + value, owned := ownedCoinsMap[coin.Denom] + if !owned { + amounts[i] = &types.Amount{ + Value: sdk.NewInt(0).String(), + Currency: &types.Currency{ + Symbol: coin.Denom, + }, + } + continue + } + amounts[i] = &types.Amount{ + Value: value.String(), + Currency: &types.Currency{ + Symbol: coin.Denom, + }, + } + } + + return amounts +} + +// sdkTxsWithHashToRosettaTxs converts sdk transactions wrapped with their hash to rosetta transactions +func sdkTxsWithHashToRosettaTxs(txs []*sdkTxWithHash) []*types.Transaction { + converted := make([]*types.Transaction, len(txs)) + for i, tx := range txs { + converted[i] = sdkTxWithHashToOperations(tx) + } + + return converted +} + +func sdkTxWithHashToOperations(tx *sdkTxWithHash) *types.Transaction { + hasError := tx.Code != 0 + return &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{Hash: tx.HexHash}, + Operations: sdkTxToOperations(tx.Tx, true, hasError), + Metadata: map[string]interface{}{ + Log: tx.Log, + }, + } +} + +// sdkTxToOperations converts an sdk.Tx to rosetta operations +func sdkTxToOperations(tx sdk.Tx, withStatus, hasError bool) []*types.Operation { + var operations []*types.Operation + + msgOps := sdkMsgsToRosettaOperations(tx.GetMsgs(), withStatus, hasError) + operations = append(operations, msgOps...) + + feeTx := tx.(sdk.FeeTx) + feeOps := sdkFeeTxToOperations(feeTx, withStatus, len(msgOps)) + operations = append(operations, feeOps...) + + return operations +} + +// sdkFeeTxToOperations converts sdk.FeeTx to rosetta operations +func sdkFeeTxToOperations(feeTx sdk.FeeTx, withStatus bool, previousOps int) []*types.Operation { + feeCoins := feeTx.GetFee() + var ops []*types.Operation + if feeCoins != nil { + var feeOps = rosettaFeeOperationsFromCoins(feeCoins, feeTx.FeePayer().String(), withStatus, previousOps) + ops = append(ops, feeOps...) + } + + return ops +} + +// rosettaFeeOperationsFromCoins returns the list of rosetta fee operations given sdk coins +func rosettaFeeOperationsFromCoins(coins sdk.Coins, account string, withStatus bool, previousOps int) []*types.Operation { + feeOps := make([]*types.Operation, 0) + var status string + if withStatus { + status = StatusSuccess + } + + for i, coin := range coins { + op := &types.Operation{ + OperationIdentifier: &types.OperationIdentifier{ + Index: int64(previousOps + i), + }, + Type: OperationFee, + Status: status, + Account: &types.AccountIdentifier{ + Address: account, + }, + Amount: &types.Amount{ + Value: "-" + coin.Amount.String(), + Currency: &types.Currency{ + Symbol: coin.Denom, + }, + }, + } + + feeOps = append(feeOps, op) + } + + return feeOps +} + +// sdkMsgsToRosettaOperations converts sdk messages to rosetta operations +func sdkMsgsToRosettaOperations(msgs []sdk.Msg, withStatus bool, hasError bool) []*types.Operation { + var operations []*types.Operation + for _, msg := range msgs { + if rosettaMsg, ok := msg.(Msg); ok { + operations = append(operations, rosettaMsg.ToOperations(withStatus, hasError)...) + } + } + + return operations +} + +// TMTxsToRosettaTxsIdentifiers converts a tendermint raw transactions into an array of rosetta tx identifiers +func TMTxsToRosettaTxsIdentifiers(txs []tmtypes.Tx) []*types.TransactionIdentifier { + converted := make([]*types.TransactionIdentifier, len(txs)) + for i, tx := range txs { + converted[i] = TmTxToRosettaTxsIdentifier(tx) + } + + return converted +} + +// TmTxToRosettaTxsIdentifier converts a tendermint raw transaction into a rosetta tx identifier +func TmTxToRosettaTxsIdentifier(tx tmtypes.Tx) *types.TransactionIdentifier { + return &types.TransactionIdentifier{Hash: fmt.Sprintf("%x", tx.Hash())} +} + +// TMBlockToRosettaBlockIdentifier converts a tendermint result block to a rosetta block identifier +func TMBlockToRosettaBlockIdentifier(block *tmcoretypes.ResultBlock) *types.BlockIdentifier { + return &types.BlockIdentifier{ + Index: block.Block.Height, + Hash: block.Block.Hash().String(), + } +} + +// TmPeersToRosettaPeers converts tendermint peers to rosetta ones +func TmPeersToRosettaPeers(peers []tmcoretypes.Peer) []*types.Peer { + converted := make([]*types.Peer, len(peers)) + + for i, peer := range peers { + converted[i] = &types.Peer{ + PeerID: peer.NodeInfo.Moniker, + Metadata: map[string]interface{}{ + "addr": peer.NodeInfo.ListenAddr, + }, + } + } + + return converted +} + +// TMStatusToRosettaSyncStatus converts a tendermint status to rosetta sync status +func TMStatusToRosettaSyncStatus(status *tmcoretypes.ResultStatus) *types.SyncStatus { + // determine sync status + var stage = StageSynced + if status.SyncInfo.CatchingUp { + stage = StageSyncing + } + + return &types.SyncStatus{ + CurrentIndex: status.SyncInfo.LatestBlockHeight, + TargetIndex: nil, // sync info does not allow us to get target height + Stage: &stage, + } +} + +// TMBlockToRosettaParentBlockIdentifier returns the parent block identifier from the last block +func TMBlockToRosettaParentBlockIdentifier(block *tmcoretypes.ResultBlock) *types.BlockIdentifier { + if block.Block.Height == 1 { + return &types.BlockIdentifier{ + Index: 1, + Hash: fmt.Sprintf("%X", block.BlockID.Hash.Bytes()), + } + } + + return &types.BlockIdentifier{ + Index: block.Block.Height - 1, + Hash: fmt.Sprintf("%X", block.Block.LastBlockID.Hash.Bytes()), + } +} diff --git a/server/rosetta/conv_to_rosetta.go b/server/rosetta/conv_to_rosetta.go new file mode 100644 index 000000000000..09146eed4f3e --- /dev/null +++ b/server/rosetta/conv_to_rosetta.go @@ -0,0 +1,95 @@ +package rosetta + +import ( + "fmt" + "strconv" + "strings" + + "github.com/gogo/protobuf/jsonpb" + + "github.com/coinbase/rosetta-sdk-go/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// opsToMsgsAndFees converts rosetta operations to sdk.Msg and fees represented as sdk.Coins +func opsToMsgsAndFees(interfaceRegistry jsonpb.AnyResolver, ops []*types.Operation) ([]sdk.Msg, sdk.Coins, error) { + var feeAmnt []*types.Amount + var newOps []*types.Operation + var msgType string + // find the fee operation and put it aside + for _, op := range ops { + switch op.Type { + case OperationFee: + amount := op.Amount + feeAmnt = append(feeAmnt, amount) + default: + // check if operation matches the one already used + // as, at the moment, we only support operations + // that represent a single cosmos-sdk message + switch { + // if msgType was not set then set it + case msgType == "": + msgType = op.Type + // if msgType does not match op.Type then it means we're trying to send multiple messages in a single tx + case msgType != op.Type: + return nil, nil, fmt.Errorf("only single message operations are supported: %s - %s", msgType, op.Type) + } + // append operation to new ops list + newOps = append(newOps, op) + } + } + // convert all operations, except fee op to sdk.Msgs + msgs, err := opsToMsgs(interfaceRegistry, newOps) + if err != nil { + return nil, nil, err + } + + return msgs, amountsToCoins(feeAmnt), nil +} + +// amountsToCoins converts rosetta amounts to sdk coins +func amountsToCoins(amounts []*types.Amount) sdk.Coins { + var feeCoins sdk.Coins + + for _, amount := range amounts { + absValue := strings.Trim(amount.Value, "-") + value, err := strconv.ParseInt(absValue, 10, 64) + if err != nil { + return nil + } + coin := sdk.NewCoin(amount.Currency.Symbol, sdk.NewInt(value)) + feeCoins = append(feeCoins, coin) + } + + return feeCoins +} + +func opsToMsgs(interfaceRegistry jsonpb.AnyResolver, ops []*types.Operation) ([]sdk.Msg, error) { + var msgs []sdk.Msg + var operationsByType = make(map[string][]*types.Operation) + for _, op := range ops { + operationsByType[op.Type] = append(operationsByType[op.Type], op) + } + + for opName, operations := range operationsByType { + if opName == OperationFee { + continue + } + + msgType, err := interfaceRegistry.Resolve("/" + opName) // Types are registered as /proto-name in the interface registry. + if err != nil { + return nil, err + } + + if rosettaMsg, ok := msgType.(Msg); ok { + m, err := rosettaMsg.FromOperations(operations) + if err != nil { + return nil, err + } + msgs = append(msgs, m) + } + } + + return msgs, nil +} diff --git a/server/rosetta/types.go b/server/rosetta/types.go new file mode 100644 index 000000000000..626e7470ab91 --- /dev/null +++ b/server/rosetta/types.go @@ -0,0 +1,41 @@ +package rosetta + +import ( + "github.com/coinbase/rosetta-sdk-go/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// statuses +const ( + StatusSuccess = "Success" + StatusReverted = "Reverted" + StageSynced = "synced" + StageSyncing = "syncing" +) + +// misc +const ( + Log = "log" +) + +// operations +const ( + OperationFee = "fee" +) + +// options +const ( + OptionAccountNumber = "account_number" + OptionAddress = "address" + OptionChainID = "chain_id" + OptionSequence = "sequence" + OptionMemo = "memo" + OptionGas = "gas" +) + +type Msg interface { + sdk.Msg + ToOperations(withStatus, hasError bool) []*types.Operation + FromOperations(ops []*types.Operation) (sdk.Msg, error) +} diff --git a/server/rosetta/util.go b/server/rosetta/util.go new file mode 100644 index 000000000000..29e4a1587dc2 --- /dev/null +++ b/server/rosetta/util.go @@ -0,0 +1,112 @@ +package rosetta + +import ( + "fmt" + + "github.com/coinbase/rosetta-sdk-go/types" + + tmcoretypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// tmResultTxsToSdkTxsWithHash converts tendermint result txs to cosmos sdk.Tx +func tmResultTxsToSdkTxsWithHash(decode sdk.TxDecoder, txs []*tmcoretypes.ResultTx) ([]*sdkTxWithHash, error) { + converted := make([]*sdkTxWithHash, len(txs)) + for i, tx := range txs { + sdkTx, err := decode(tx.Tx) + if err != nil { + return nil, err + } + converted[i] = &sdkTxWithHash{ + HexHash: fmt.Sprintf("%X", tx.Tx.Hash()), + Code: tx.TxResult.Code, + Log: tx.TxResult.Log, + Tx: sdkTx, + } + } + + return converted, nil +} + +func tmTxToSdkTx(decode sdk.TxDecoder, tx tmtypes.Tx) (sdk.Tx, error) { + sdkTx, err := decode(tx) + if err != nil { + return nil, err + } + + return sdkTx, err +} + +type sdkTxWithHash struct { + HexHash string + Code uint32 + Log string + Tx sdk.Tx +} + +type PayloadReqMetadata struct { + ChainID string + Sequence uint64 + AccountNumber uint64 + Gas uint64 + Memo string +} + +// getMetadataFromPayloadReq obtains the metadata from the request to /construction/payloads endpoint. +func getMetadataFromPayloadReq(req *types.ConstructionPayloadsRequest) (*PayloadReqMetadata, error) { + chainID, ok := req.Metadata[OptionChainID].(string) + if !ok { + return nil, fmt.Errorf("chain_id metadata was not provided") + } + + sequence, ok := req.Metadata[OptionSequence] + if !ok { + return nil, fmt.Errorf("sequence metadata was not provided") + } + + seqNum, ok := sequence.(float64) + if !ok { + return nil, fmt.Errorf("invalid sequence value") + } + + accountNum, ok := req.Metadata[OptionAccountNumber] + if !ok { + return nil, fmt.Errorf("account_number metadata was not provided") + } + + accNum, ok := accountNum.(float64) + if !ok { + fmt.Printf("this is type %T", accountNum) + return nil, fmt.Errorf("invalid account_number value") + } + + gasNum, ok := req.Metadata[OptionGas] + if !ok { + return nil, fmt.Errorf("gas metadata was not provided") + } + + gasF64, ok := gasNum.(float64) + if !ok { + return nil, fmt.Errorf("invalid gas value") + } + + memo, ok := req.Metadata[OptionMemo] + if !ok { + memo = "" + } + + memoStr, ok := memo.(string) + if !ok { + return nil, fmt.Errorf("invalid memo") + } + + return &PayloadReqMetadata{ + ChainID: chainID, + Sequence: uint64(seqNum), + AccountNumber: uint64(accNum), + Gas: uint64(gasF64), + Memo: memoStr, + }, nil +} diff --git a/server/start.go b/server/start.go index 84c4dc858651..447497f08708 100644 --- a/server/start.go +++ b/server/start.go @@ -9,7 +9,14 @@ import ( "runtime/pprof" "time" + "github.com/cosmos/cosmos-sdk/server/rosetta" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/spf13/cobra" + "google.golang.org/grpc" + + crgserver "github.com/tendermint/cosmos-rosetta-gateway/server" "github.com/tendermint/tendermint/abci/server" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tmos "github.com/tendermint/tendermint/libs/os" @@ -18,7 +25,6 @@ import ( pvm "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/rpc/client/local" - "google.golang.org/grpc" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -280,7 +286,6 @@ func startInProcess(ctx *Context, clientCtx client.Context, appCreator types.App } var apiSrv *api.Server - if config.API.Enable { genDoc, err := genDocProvider() if err != nil { @@ -326,6 +331,42 @@ func startInProcess(ctx *Context, clientCtx client.Context, appCreator types.App } } + var rosettaSrv crgserver.Server + if config.Rosetta.Enable { + offlineMode := config.Rosetta.Offline + if !config.GRPC.Enable { // If GRPC is not enabled rosetta cannot work in online mode, so it works in offline mode. + offlineMode = true + } + + conf := &rosetta.Config{ + Blockchain: config.Rosetta.Blockchain, + Network: config.Rosetta.Network, + TendermintRPC: ctx.Config.RPC.ListenAddress, + GRPCEndpoint: config.GRPC.Address, + Addr: config.Rosetta.Address, + Retries: config.Rosetta.Retries, + Offline: offlineMode, + } + conf.WithCodec(clientCtx.InterfaceRegistry, clientCtx.JSONMarshaler.(*codec.ProtoCodec)) + + rosettaSrv, err = rosetta.ServerFromConfig(conf) + if err != nil { + return err + } + errCh := make(chan error) + go func() { + if err := rosettaSrv.Start(); err != nil { + errCh <- err + } + }() + + select { + case err := <-errCh: + return err + case <-time.After(5 * time.Second): // assume server started successfully + } + } + defer func() { if tmNode.IsRunning() { _ = tmNode.Stop() diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index e7713a284543..f76948d59cd5 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -90,6 +90,9 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { txCommand(), keys.Commands(simapp.DefaultNodeHome), ) + + // add rosetta + rootCmd.AddCommand(server.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler)) } func addModuleInitFlags(startCmd *cobra.Command) { diff --git a/types/tx/types.go b/types/tx/types.go index 0392b2fcfd08..a0b05b5dcea0 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -132,6 +132,37 @@ func (t *Tx) GetSigners() []sdk.AccAddress { return signers } +func (t *Tx) GetGas() uint64 { + return t.AuthInfo.Fee.GasLimit +} +func (t *Tx) GetFee() sdk.Coins { + return t.AuthInfo.Fee.Amount +} +func (t *Tx) FeePayer() sdk.AccAddress { + feePayer := t.AuthInfo.Fee.Payer + if feePayer != "" { + payerAddr, err := sdk.AccAddressFromBech32(feePayer) + if err != nil { + panic(err) + } + return payerAddr + } + // use first signer as default if no payer specified + return t.GetSigners()[0] +} + +func (t *Tx) FeeGranter() sdk.AccAddress { + feePayer := t.AuthInfo.Fee.Granter + if feePayer != "" { + granterAddr, err := sdk.AccAddressFromBech32(feePayer) + if err != nil { + panic(err) + } + return granterAddr + } + return nil +} + // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (t *Tx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { if t.Body != nil { diff --git a/x/auth/client/cli/cli_test.go b/x/auth/client/cli/cli_test.go index fdd558b7971f..d8913d1ac07e 100644 --- a/x/auth/client/cli/cli_test.go +++ b/x/auth/client/cli/cli_test.go @@ -435,7 +435,7 @@ func (s *IntegrationTestSuite) TestCLISendGenerateSignAndBroadcast() { // Write the output to disk signedTxFile := testutil.WriteToNewTempFile(s.T(), signedTx.String()) - // Validate Signature + // validate Signature res, err = authtest.TxValidateSignaturesExec(val1.ClientCtx, signedTxFile.Name()) s.Require().NoError(err) s.Require().True(strings.Contains(res.String(), "[OK]")) diff --git a/x/auth/client/cli/validate_sigs.go b/x/auth/client/cli/validate_sigs.go index 52290ed8e95f..d8d6283a7990 100644 --- a/x/auth/client/cli/validate_sigs.go +++ b/x/auth/client/cli/validate_sigs.go @@ -16,7 +16,7 @@ import ( func GetValidateSignaturesCommand() *cobra.Command { cmd := &cobra.Command{ Use: "validate-signatures [file]", - Short: "Validate transactions signatures", + Short: "validate transactions signatures", Long: `Print the addresses that must sign the transaction, those who have already signed it, and make sure that signatures are in the correct order. @@ -96,7 +96,7 @@ func printAndValidateSigs( success = false } - // Validate the actual signature over the transaction bytes since we can + // validate the actual signature over the transaction bytes since we can // reach out to a full node to query accounts. if !offline && success { accNum, accSeq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, sigAddr) diff --git a/x/bank/types/msgs.go b/x/bank/types/msgs.go index 22fdc30b3886..dd41975468ca 100644 --- a/x/bank/types/msgs.go +++ b/x/bank/types/msgs.go @@ -1,6 +1,13 @@ package types import ( + "fmt" + "strconv" + "strings" + + "github.com/coinbase/rosetta-sdk-go/types" + "github.com/gogo/protobuf/proto" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -62,6 +69,83 @@ func (msg MsgSend) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{from} } +// Rosetta interface +func (msg *MsgSend) ToOperations(withStatus bool, hasError bool) []*types.Operation { + var operations []*types.Operation + + fromAddress := msg.FromAddress + toAddress := msg.ToAddress + amounts := msg.Amount + if len(amounts) == 0 { + return []*types.Operation{} + } + + coin := amounts[0] + sendOp := func(account, amount string, index int) *types.Operation { + var status string + if withStatus { + status = "Success" + if hasError { + status = "Reverted" + } + } + return &types.Operation{ + OperationIdentifier: &types.OperationIdentifier{ + Index: int64(index), + }, + Type: proto.MessageName(msg), + Status: status, + Account: &types.AccountIdentifier{ + Address: account, + }, + Amount: &types.Amount{ + Value: amount, + Currency: &types.Currency{ + Symbol: coin.Denom, + }, + }, + } + } + operations = append(operations, + sendOp(fromAddress, "-"+coin.Amount.String(), 0), + sendOp(toAddress, coin.Amount.String(), 1), + ) + + return operations +} + +func (msg MsgSend) FromOperations(ops []*types.Operation) (sdk.Msg, error) { + var ( + from, to sdk.AccAddress + sendAmt sdk.Coin + err error + ) + + for _, op := range ops { + if strings.HasPrefix(op.Amount.Value, "-") { + from, err = sdk.AccAddressFromBech32(op.Account.Address) + if err != nil { + return nil, err + } + continue + } + + to, err = sdk.AccAddressFromBech32(op.Account.Address) + if err != nil { + return nil, err + } + + amount, err := strconv.ParseInt(op.Amount.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid amount") + } + + sendAmt = sdk.NewCoin(op.Amount.Currency.Symbol, sdk.NewInt(amount)) + } + + return NewMsgSend(from, to, sdk.NewCoins(sendAmt)), nil +} + var _ sdk.Msg = &MsgMultiSend{} // NewMsgMultiSend - construct arbitrary multi-in, multi-out send msg. diff --git a/x/distribution/types/msg.go b/x/distribution/types/msg.go index 8dc72081e9ac..62c8bfc2abbb 100644 --- a/x/distribution/types/msg.go +++ b/x/distribution/types/msg.go @@ -2,6 +2,12 @@ package types import ( + "fmt" + + rosettatypes "github.com/coinbase/rosetta-sdk-go/types" + "github.com/gogo/protobuf/proto" + + "github.com/cosmos/cosmos-sdk/server/rosetta" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -90,6 +96,50 @@ func (msg MsgWithdrawDelegatorReward) ValidateBasic() error { return nil } +func (msg *MsgWithdrawDelegatorReward) ToOperations(withStatus, hasError bool) []*rosettatypes.Operation { + + var status string + if withStatus { + status = rosetta.StatusSuccess + if hasError { + status = rosetta.StatusReverted + } + } + + op := &rosettatypes.Operation{ + OperationIdentifier: &rosettatypes.OperationIdentifier{ + Index: 0, + }, + RelatedOperations: nil, + Type: proto.MessageName(msg), + Status: status, + Account: &rosettatypes.AccountIdentifier{ + Address: msg.DelegatorAddress, + SubAccount: &rosettatypes.SubAccountIdentifier{ + Address: msg.ValidatorAddress, + }, + }, + } + return []*rosettatypes.Operation{op} +} + +func (msg *MsgWithdrawDelegatorReward) FromOperations(ops []*rosettatypes.Operation) (sdk.Msg, error) { + if len(ops) != 1 { + return nil, fmt.Errorf("expected one operation") + } + op := ops[0] + if op.Account == nil { + return nil, fmt.Errorf("account identifier must be specified") + } + if op.Account.SubAccount == nil { + return nil, fmt.Errorf("account identifier subaccount must be specified") + } + return &MsgWithdrawDelegatorReward{ + DelegatorAddress: op.Account.Address, + ValidatorAddress: op.Account.SubAccount.Address, + }, nil +} + func NewMsgWithdrawValidatorCommission(valAddr sdk.ValAddress) *MsgWithdrawValidatorCommission { return &MsgWithdrawValidatorCommission{ ValidatorAddress: valAddr.String(), diff --git a/x/genutil/client/cli/gentx_test.go b/x/genutil/client/cli/gentx_test.go index d15ee8896265..b5733675951a 100644 --- a/x/genutil/client/cli/gentx_test.go +++ b/x/genutil/client/cli/gentx_test.go @@ -72,7 +72,7 @@ func (s *IntegrationTestSuite) TestGenTxCmd() { err := cmd.ExecuteContext(ctx) s.Require().NoError(err) - // Validate generated transaction. + // validate generated transaction. open, err := os.Open(genTxFile) s.Require().NoError(err) diff --git a/x/ibc/core/03-connection/types/msgs_test.go b/x/ibc/core/03-connection/types/msgs_test.go index 57c1925f66db..6aff3b0904b3 100644 --- a/x/ibc/core/03-connection/types/msgs_test.go +++ b/x/ibc/core/03-connection/types/msgs_test.go @@ -138,7 +138,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { {"invalid counterparty client ID", types.NewMsgConnectionOpenTry(connectionID, "clienttotesta", "connectiontotest", "test/conn1", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, signer), false}, {"invalid nil counterparty client", types.NewMsgConnectionOpenTry(connectionID, "clienttotesta", "connectiontotest", "clienttotest", nil, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, signer), false}, {"invalid client unpacking", &types.MsgConnectionOpenTry{connectionID, "clienttotesta", invalidAny, counterparty, 500, []*types.Version{ibctesting.ConnectionVersion}, clientHeight, suite.proof, suite.proof, suite.proof, clientHeight, signer.String()}, false}, - {"counterparty failed Validate", types.NewMsgConnectionOpenTry(connectionID, "clienttotesta", "connectiontotest", "clienttotest", invalidClient, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, signer), false}, + {"counterparty failed validate", types.NewMsgConnectionOpenTry(connectionID, "clienttotesta", "connectiontotest", "clienttotest", invalidClient, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, signer), false}, {"empty counterparty prefix", types.NewMsgConnectionOpenTry(connectionID, "clienttotesta", "connectiontotest", "clienttotest", clientState, emptyPrefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, signer), false}, {"empty counterpartyVersions", types.NewMsgConnectionOpenTry(connectionID, "clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{}, 500, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, signer), false}, {"empty proofInit", types.NewMsgConnectionOpenTry(connectionID, "clienttotesta", "connectiontotest", "clienttotest", clientState, prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, emptyProof, suite.proof, suite.proof, clientHeight, clientHeight, signer), false}, @@ -188,7 +188,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenAck() { {"invalid counterparty connection ID", types.NewMsgConnectionOpenAck(connectionID, "test/conn1", clientState, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, {"invalid nil counterparty client", types.NewMsgConnectionOpenAck(connectionID, connectionID, nil, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, {"invalid unpacking counterparty client", &types.MsgConnectionOpenAck{connectionID, connectionID, ibctesting.ConnectionVersion, invalidAny, clientHeight, suite.proof, suite.proof, suite.proof, clientHeight, signer.String()}, false}, - {"counterparty client failed Validate", types.NewMsgConnectionOpenAck(connectionID, connectionID, invalidClient, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, + {"counterparty client failed validate", types.NewMsgConnectionOpenAck(connectionID, connectionID, invalidClient, suite.proof, suite.proof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, {"empty proofTry", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, emptyProof, suite.proof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, {"empty proofClient", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, suite.proof, emptyProof, suite.proof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, {"empty proofConsensus", types.NewMsgConnectionOpenAck(connectionID, connectionID, clientState, suite.proof, suite.proof, emptyProof, clientHeight, clientHeight, ibctesting.ConnectionVersion, signer), false}, diff --git a/x/slashing/client/rest/rest.go b/x/slashing/client/rest/rest.go index a3a88545900b..059cb17c6bdc 100644 --- a/x/slashing/client/rest/rest.go +++ b/x/slashing/client/rest/rest.go @@ -3,9 +3,8 @@ package rest import ( "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/rest" - "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/rest" ) func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) { diff --git a/x/staking/client/rest/rest.go b/x/staking/client/rest/rest.go index 35bb8da68139..6f9b7e1e2a60 100644 --- a/x/staking/client/rest/rest.go +++ b/x/staking/client/rest/rest.go @@ -3,9 +3,8 @@ package rest import ( "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/rest" - "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/rest" ) func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) { diff --git a/x/staking/types/msg.go b/x/staking/types/msg.go index 2525aa66468f..52ab1750b827 100644 --- a/x/staking/types/msg.go +++ b/x/staking/types/msg.go @@ -2,9 +2,17 @@ package types import ( "bytes" + "fmt" + "strconv" + "strings" + + "github.com/gogo/protobuf/proto" + + rosettatypes "github.com/coinbase/rosetta-sdk-go/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/server/rosetta" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -249,6 +257,90 @@ func (msg MsgDelegate) ValidateBasic() error { return nil } +// Rosetta Msg interface. +func (msg *MsgDelegate) ToOperations(withStatus bool, hasError bool) []*rosettatypes.Operation { + var operations []*rosettatypes.Operation + delAddr := msg.DelegatorAddress + valAddr := msg.ValidatorAddress + coin := msg.Amount + delOp := func(account *rosettatypes.AccountIdentifier, amount string, index int) *rosettatypes.Operation { + var status string + if withStatus { + status = rosetta.StatusSuccess + if hasError { + status = rosetta.StatusReverted + } + } + return &rosettatypes.Operation{ + OperationIdentifier: &rosettatypes.OperationIdentifier{ + Index: int64(index), + }, + Type: proto.MessageName(msg), + Status: status, + Account: account, + Amount: &rosettatypes.Amount{ + Value: amount, + Currency: &rosettatypes.Currency{ + Symbol: coin.Denom, + }, + }, + } + } + delAcc := &rosettatypes.AccountIdentifier{ + Address: delAddr, + } + valAcc := &rosettatypes.AccountIdentifier{ + Address: "staking_account", + SubAccount: &rosettatypes.SubAccountIdentifier{ + Address: valAddr, + }, + } + operations = append(operations, + delOp(delAcc, "-"+coin.Amount.String(), 0), + delOp(valAcc, coin.Amount.String(), 1), + ) + return operations +} + +func (msg *MsgDelegate) FromOperations(ops []*rosettatypes.Operation) (sdk.Msg, error) { + var ( + delAddr sdk.AccAddress + valAddr sdk.ValAddress + sendAmt sdk.Coin + err error + ) + + for _, op := range ops { + if strings.HasPrefix(op.Amount.Value, "-") { + if op.Account == nil { + return nil, fmt.Errorf("account identifier must be specified") + } + delAddr, err = sdk.AccAddressFromBech32(op.Account.Address) + if err != nil { + return nil, err + } + continue + } + + if op.Account.SubAccount == nil { + return nil, fmt.Errorf("account identifier subaccount must be specified") + } + valAddr, err = sdk.ValAddressFromBech32(op.Account.SubAccount.Address) + if err != nil { + return nil, err + } + + amount, err := strconv.ParseInt(op.Amount.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid amount: %w", err) + } + + sendAmt = sdk.NewCoin(op.Amount.Currency.Symbol, sdk.NewInt(amount)) + } + + return NewMsgDelegate(delAddr, valAddr, sendAmt), nil +} + // NewMsgBeginRedelegate creates a new MsgBeginRedelegate instance. //nolint:interfacer func NewMsgBeginRedelegate( @@ -304,6 +396,103 @@ func (msg MsgBeginRedelegate) ValidateBasic() error { return nil } +// Rosetta Msg interface. +func (msg *MsgBeginRedelegate) ToOperations(withStatus bool, hasError bool) []*rosettatypes.Operation { + var operations []*rosettatypes.Operation + delAddr := msg.DelegatorAddress + srcValAddr := msg.ValidatorSrcAddress + destValAddr := msg.ValidatorDstAddress + coin := msg.Amount + delOp := func(account *rosettatypes.AccountIdentifier, amount string, index int) *rosettatypes.Operation { + var status string + if withStatus { + status = rosetta.StatusSuccess + if hasError { + status = rosetta.StatusReverted + } + } + return &rosettatypes.Operation{ + OperationIdentifier: &rosettatypes.OperationIdentifier{ + Index: int64(index), + }, + Type: proto.MessageName(msg), + Status: status, + Account: account, + Amount: &rosettatypes.Amount{ + Value: amount, + Currency: &rosettatypes.Currency{ + Symbol: coin.Denom, + }, + }, + } + } + srcValAcc := &rosettatypes.AccountIdentifier{ + Address: delAddr, + SubAccount: &rosettatypes.SubAccountIdentifier{ + Address: srcValAddr, + }, + } + destValAcc := &rosettatypes.AccountIdentifier{ + Address: "staking_account", + SubAccount: &rosettatypes.SubAccountIdentifier{ + Address: destValAddr, + }, + } + operations = append(operations, + delOp(srcValAcc, "-"+coin.Amount.String(), 0), + delOp(destValAcc, coin.Amount.String(), 1), + ) + return operations +} + +func (msg *MsgBeginRedelegate) FromOperations(ops []*rosettatypes.Operation) (sdk.Msg, error) { + var ( + delAddr sdk.AccAddress + srcValAddr sdk.ValAddress + destValAddr sdk.ValAddress + sendAmt sdk.Coin + err error + ) + + for _, op := range ops { + if strings.HasPrefix(op.Amount.Value, "-") { + if op.Account == nil { + return nil, fmt.Errorf("account identifier must be specified") + } + delAddr, err = sdk.AccAddressFromBech32(op.Account.Address) + if err != nil { + return nil, err + } + + if op.Account.SubAccount == nil { + return nil, fmt.Errorf("account identifier subaccount must be specified") + } + srcValAddr, err = sdk.ValAddressFromBech32(op.Account.SubAccount.Address) + if err != nil { + return nil, err + } + continue + } + + if op.Account.SubAccount == nil { + return nil, fmt.Errorf("account identifier subaccount must be specified") + } + destValAddr, err = sdk.ValAddressFromBech32(op.Account.SubAccount.Address) + if err != nil { + return nil, err + } + + amount, err := strconv.ParseInt(op.Amount.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid amount: %w", err) + } + + sendAmt = sdk.NewCoin(op.Amount.Currency.Symbol, sdk.NewInt(amount)) + } + + return NewMsgBeginRedelegate(delAddr, srcValAddr, destValAddr, sendAmt), nil +} + // NewMsgUndelegate creates a new MsgUndelegate instance. //nolint:interfacer func NewMsgUndelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) *MsgUndelegate { @@ -351,3 +540,88 @@ func (msg MsgUndelegate) ValidateBasic() error { return nil } + +// Rosetta Msg interface. +func (msg *MsgUndelegate) ToOperations(withStatus bool, hasError bool) []*rosettatypes.Operation { + var operations []*rosettatypes.Operation + delAddr := msg.DelegatorAddress + valAddr := msg.ValidatorAddress + coin := msg.Amount + delOp := func(account *rosettatypes.AccountIdentifier, amount string, index int) *rosettatypes.Operation { + var status string + if withStatus { + status = rosetta.StatusSuccess + if hasError { + status = rosetta.StatusReverted + } + } + return &rosettatypes.Operation{ + OperationIdentifier: &rosettatypes.OperationIdentifier{ + Index: int64(index), + }, + Type: proto.MessageName(msg), + Status: status, + Account: account, + Amount: &rosettatypes.Amount{ + Value: amount, + Currency: &rosettatypes.Currency{ + Symbol: coin.Denom, + }, + }, + } + } + delAcc := &rosettatypes.AccountIdentifier{ + Address: delAddr, + } + valAcc := &rosettatypes.AccountIdentifier{ + Address: "staking_account", + SubAccount: &rosettatypes.SubAccountIdentifier{ + Address: valAddr, + }, + } + operations = append(operations, + delOp(valAcc, "-"+coin.Amount.String(), 0), + delOp(delAcc, coin.Amount.String(), 1), + ) + return operations +} + +func (msg *MsgUndelegate) FromOperations(ops []*rosettatypes.Operation) (sdk.Msg, error) { + var ( + delAddr sdk.AccAddress + valAddr sdk.ValAddress + undelAmt sdk.Coin + err error + ) + + for _, op := range ops { + if strings.HasPrefix(op.Amount.Value, "-") { + if op.Account.SubAccount == nil { + return nil, fmt.Errorf("account identifier subaccount must be specified") + } + valAddr, err = sdk.ValAddressFromBech32(op.Account.SubAccount.Address) + if err != nil { + return nil, err + } + continue + } + + if op.Account == nil { + return nil, fmt.Errorf("account identifier must be specified") + } + + delAddr, err = sdk.AccAddressFromBech32(op.Account.Address) + if err != nil { + return nil, err + } + + amount, err := strconv.ParseInt(op.Amount.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid amount") + } + + undelAmt = sdk.NewCoin(op.Amount.Currency.Symbol, sdk.NewInt(amount)) + } + + return NewMsgUndelegate(delAddr, valAddr, undelAmt), nil +} diff --git a/x/staking/types/tx.pb.go b/x/staking/types/tx.pb.go index 50e1765ff6c0..fad3a8c75e14 100644 --- a/x/staking/types/tx.pb.go +++ b/x/staking/types/tx.pb.go @@ -6,6 +6,11 @@ package types import ( context "context" fmt "fmt" + io "io" + math "math" + math_bits "math/bits" + time "time" + types "github.com/cosmos/cosmos-sdk/codec/types" github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" types1 "github.com/cosmos/cosmos-sdk/types" @@ -18,10 +23,6 @@ import ( grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - io "io" - math "math" - math_bits "math/bits" - time "time" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/upgrade/client/rest/rest.go b/x/upgrade/client/rest/rest.go index 3192083f8da4..8f104852496c 100644 --- a/x/upgrade/client/rest/rest.go +++ b/x/upgrade/client/rest/rest.go @@ -3,9 +3,8 @@ package rest import ( "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client/rest" - "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/rest" ) // RegisterRoutes registers REST routes for the upgrade module under the path specified by routeName.