diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1b6c09cb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +matrix: + include: + - language: go + go: 1.13.1 + env: + - GO111MODULE=on + before_script: + - cd golang + - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.20.0 + - make install + script: + - make lint + - go test ./x/relay/keeper + - go test ./x/relay/types + - go test -mod=readonly `go list ./cli_test/...` + + - language: node_js + node_js: 10.14.2 + before_script: + - cd solidity + - npm install -g truffle + - npm install + script: + - npm run lint + - npm run test diff --git a/README.md b/README.md index 234bcc14..34e59c9e 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ This is a Bitcoin Relay. It uses 1 + 1/n slots per header relayed (n is currently 4), and 2 slots to externalize useful information (best chain tip and best shared ancestor of latest reorg). -At present, only a Solidity implementation is available, but we intend to add -more implementations soon :) +Implementations are available in Solidity (for EVM chains) and Golang using the +cosmos-sdk framework. ### How does it work? @@ -43,11 +43,8 @@ reducing calldata gas costs. ### Project Notes -The Python relay mainter in `./relay/` is not thoroughly tested, and does not -yet support the cosmos-sdk relay. +Complete relays are available in Solidity, for EVM-based chains (like Ethereum) +and Golang using the cosmos-sdk framework. -### How do I develop for it? - -install `pipenv` and `pyenv` - -$ pipenv install --python=$(pyenv which python3.7) +The Python relay mainter in `./maintainer/` is not thoroughly tested, and does +not yet support the cosmos-sdk relay. diff --git a/golang/.gitignore b/golang/.gitignore new file mode 100644 index 00000000..d75238a0 --- /dev/null +++ b/golang/.gitignore @@ -0,0 +1,3 @@ +*.test + +/x/relay/keeper/coverage.out \ No newline at end of file diff --git a/golang/EXTENDING.md b/golang/EXTENDING.md new file mode 100644 index 00000000..f50825c7 --- /dev/null +++ b/golang/EXTENDING.md @@ -0,0 +1,110 @@ +## Adding new functionality + +This is a cosmos-sdk module. It can be extended with new messages and/or +queries. Generally, this module is feature-complete, and should not be +extended. The main exception is the WIP hooks system on proof validation. All +other functionality should likely be put into a separate module. + +### Integrating with other modules + +The relay keeper keeps a reference to an object that implements the following +interface (found in `x/types/types.go`). + +```go +type ProofHandler interface { + HandleValidProof(ctx sdk.Context, filled FilledRequests, requests []ProofRequest) +} +``` + +The `FilledRequests` struct contains an `SPVProof` and supporting information +about the transaction that fulfills the request. +It can be found in `x/types/validator.go`. `requests []ProofRequest` is a slice +of `ProofRequest`s that have been filled. + +When the keeper validates a proof, it will call the `HandleValidProof` function +with the valid `FilledRequests` struct and the `ProofRequests` that have been +filled. + +First, instantiate a `handler` that fulfills the `ProofHandler` interface. Then +add an instance of `relay.Keeper` to your app in `app.go`. It can be +instantiated as follows: + +```go +handler = types.NewNullHandler() // or your preferred handler + +app.relayKeeper = relay.NewKeeper( + keys[relay.StoreKey], + app.cdc, + true, + handler +) +``` + +After that, the relay can be accessed via the Keeper's public interface. + +### Extending this module + +In order to extend this module, follow these steps: + +## How to add a view function (queries) +1. Add necessary getter(s) in `x/relay/keeper/keeper.go` +1. Add response type to `x/relay/types/querier.go` + 1. Add new string tag for the new query + 1. Response type is a struct with the return values + 1. Implement `String()` for the response type +1. Add function to querier `x/relay/keeper/querier.go` + 1. Add new `query___` function + 1. Add new case block to `switch` in `NewQuerier()` +1. Add to CLI + 1. add to `x/relay/client/cli/query.go` + 1. `func GetCmd______` + 1. returns a `cobra.Command` object + 1. define `Use` `Example` `Short` `Long` `Args` and `RunE` + 1. `RunE` parses args, returns errors, and calls `cliCtx.QueryWithData` + 1. parses the output and returns it with `cliCtx.PrintOutput` +1. Add to REST + 1. add to `x/relay/client/rest/query.go` + 1. new function `_____Handler` + 1. parse args and build structs + 1. cliCtx.QueryWithData + 1. return errors with `rest.WriteErrorResponse` + 1. return query result with `rest.PostProcessResponse` + 1. add GET route to `x/relay/client/rest/rest.go` + 1. new `s.HandleFunc` with the route and arguments + 1. `.Methods("GET")` + 1. duplicate for optional args (see `isancestor` for example) + + +## How to add a non-view function (messages) +1. Add necessary getters/setters in `x/relay/keeper/keeper.go` +1. Add msg type in `x/relay/types/msgs.go` + 1. Message type is a struct with the arguments + 1. Implement `New___()` + 1. Implement `GetSigners()` <--- Ask me about this later + 1. Implement `Type()` + 1. Implement `ValidateBasic()` + 1. Implement `GetSignBytes()` + 1. Implement `Route()` +1. Add to handler + 1. Add new `handle____` function + 1. Add new case block to `switch` in `NewHandler()` +1. Add aliases in `x/relay/alias.go` + 1. Add alias in `var` block + 1. Add alias in `type` block +1. Add to CLI + 1. add to `x/relay/client/cli/tx.go` + 1. `func GetCmd______` + 1. returns a `cobra.Command` object + 1. define `Use` `Example` `Short` `Long` `Args` and `RunE` + 1. `RunE` parses args, returns errors, and calls `utils.GenerateOrBroadcastMsgs` +1. Add to REST + 1. add to `x/relay/client/rest/tx.go` + 1. new http request struct `______Req` + 1. `BaseReq` + the struct from `x/relay/types/msgs.go` + 1. new function `_____Handler` + 1. parse args and build structs + 1. return errors with `rest.WriteErrorResponse` + 1. make the tx with `utils.WriteGenerateStdTxResponse` + 1. add POST route to `x/relay/client/rest/rest.go` + 1. new `s.HandleFunc` with the route and arguments + 1. `.Methods("POST")` diff --git a/golang/Makefile b/golang/Makefile new file mode 100644 index 00000000..97a095fb --- /dev/null +++ b/golang/Makefile @@ -0,0 +1,33 @@ +PACKAGES=$(shell go list ./... | grep -v '/simulation') + +VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') +COMMIT := $(shell git log -1 --format='%H') + +ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=Relay \ + -X github.com/cosmos/cosmos-sdk/version.ServerName=relayd \ + -X github.com/cosmos/cosmos-sdk/version.ClientName=relaycli \ + -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) + +BUILD_FLAGS := -ldflags '$(ldflags)' + +all: lint install + +install: go.sum + go install -mod=readonly $(BUILD_FLAGS) ./cmd/relayd + go install -mod=readonly $(BUILD_FLAGS) ./cmd/relaycli + +go.sum: go.mod + @echo "--> Ensure dependencies have not been modified" + GO111MODULE=on go mod verify + +lint: + golangci-lint run + @find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s + go mod verify + +test: + @go test -mod=readonly $(PACKAGES) + +init: + ./scripts/init_chain.sh diff --git a/golang/README.md b/golang/README.md new file mode 100644 index 00000000..c59c1c97 --- /dev/null +++ b/golang/README.md @@ -0,0 +1,114 @@ +## cosmos-sdk Bitcoin Relay + +This is a full-featured Bitcoin relay module for cosmos-sdk chains. It indexes +Bitcoin headers, provides information about the latest-known state of the +Bitcoin chain, and validates SPV Proofs against its view of the chain. It is a +critical component for many Cosmos applications to interact with Bitcoin. + +For more information about the relay's architecture see the `README.md` in the +repo's root directory. + +## Building the daemon and cli + +```sh +$ make +# To additionally install them in your `$GOPATH/bin` directory: +$ make install +``` + +## Running tests + +Run the unit tests as follows: + +```sh +$ go test ./x/... +``` + +See the README in `./cli_test` for instructions on running the integration +tests. + +Instructions for setting up manual testing can be found in the README in +`./scripts`. + +## Project Status + +- [X] Milestone 1 +- [X] Milestone 2 +- - [X] Expose best-known digest +- - [X] Expose LCA of reorg +- - [X] Follow API of existing Solidity Relay +- - [X] Validate SPV Proofs +- - [X] `ProvideProof` message +- [ ] Milestone 3 +- - [X] Provide tooling for manual testing (scripts, docs, json test files) +- - [X] Integration Tests +- - [ ] Document relay design & architecture +- - [X] Document public interface +- - [X] Provide hooks to execute tasks + dispatch messages +- - [ ] Add a basic web dashboard with Relay health + + +## API + +Cosmos modules expose messages, which modify state, and queries, which read +state. + +### Queries + +Queries are available via CLI or REST. For more information, see the +descriptions in the CLI. + +| Query | Description | +| ----- | ----------- | +| IsAncestor | Deteremine if a block is an ancestor of another | +| GetRelayGenesis | Get the trusted root of the relay | +| GetLastReorgLCA | Get the LCA of the latest reorg | +| GetLastReorgLCA | Get the best digest known to the relay | +| FindAncestor | Find the nth ancestor of a block| +| IsMostRecentCommonAncestor | Determine if a block is the LCA of two headers| +| HeaviestFromAncestor | Check which of two descendents is heaviest from the LCA | +| GetRequest | Get details of an SPV Proof Request| +| CheckProof | Check the syntactic validity of an SPV Proof | +| CheckRequests | Perform CheckProof and check the SPV Proof against a set of Requests | + +### Messages + +Messages are available via CLI or REST. For more information, see the +descriptions in the CLI. + +| Message | Description | +| ------- | ----------- | +| IngestHeaderChain | Add a chain of headers to the relay | +| IngestDifficultyChange | Add a chain of headers to the relay with a difficulty change| +| MarkNewHeaviest | Mark a new best-known chain tip | +| NewRequest | Register a new SPV Proof request | +| ProvideProof | Provide a proof that satisfies 1 or more requests | + +## Project Overview + +### Keeper +High-level overview of the project structure within the `keeper` file. + +#### Keeper.go +Instantiates a `keeper` (what handles interaction with the store and contains most of the core functionality of the module). It also handles the genesis state for the relay. + +#### Headers.go +Handles the storage and validation of Bitcoin Headers and Header Chains. + +#### Chain.go +Checks and updates information about the chain. Provides functionality to ensure we are using the heaviest chain. + +#### Links.go +Sets and retrieves data about each link in the chain. This is most commonly used to check information about ancestors. + +#### Requests.go +Stores, retrieves, and validates requests. + +#### Validator.go +Contains validation functions. Currently, this can validate SPV Proofs and Requests. + +#### Handler.go +Handles messages. + +#### Querier.go +Handles queries. diff --git a/golang/cli_test/README.md b/golang/cli_test/README.md new file mode 100644 index 00000000..57c11753 --- /dev/null +++ b/golang/cli_test/README.md @@ -0,0 +1,56 @@ +# Relay CLI Integration tests + +The relay cli integration tests live in this folder. You can run the full suite by running: + +```bash +go test -mod=readonly -p 4 `go list ./cli_test/...` +``` + + To run a single test run: + ```bash + go test -mod=readonly -p 4 `go list ./cli_test/...` -testify.m TestName + ``` + +> NOTE: While the full suite runs in parallel, some of the tests can take up to a minute to complete + +### Test Structure + +This integration suite [uses a thin wrapper](https://godoc.org/github.com/cosmos/cosmos-sdk/tests) over the [`os/exec`](https://golang.org/pkg/os/exec/) package. This allows the integration test to run against built binaries (both `relayd` and `relaycli` are used) while being written in golang. This allows tests to take advantage of the various golang code we have for operations like marshal/unmarshal, crypto, etc... + +> NOTE: The tests will use whatever `relayd` or `relaycli` binaries are available in your `$GOPATH/bin`. You can check which binary will be run by the suite by running `which relayd` or `which relaycli`. If you have your `$GOPATH` properly setup they should be in `$GOPATH/bin/relay*`. This will ensure that your test uses the latest binary you have built + +Tests generally follow this structure: + +```go +func (suite *UtilsSuite) TestMyNewCommand() { + suite.T().Parallel() + f := InitFixtures(suite.T()) + + // start relayd server + proc := f.GDStart() + defer proc.Stop(false) + + // Your test code goes here... + + f.Cleanup() +} +``` + +This boilerplate above: + +- Ensures the tests run in parallel. Because the tests are calling out to `os/exec` for many operations these tests can take a long time to run. +- Creates `.relayd` and `.relaycli` folders in a new temp folder. +- Uses `relaycli` to create test account for use in testing: `foo` +- Creates a genesis file with coins (`1000footoken,1000feetoken,150stake`) controlled by the `foo` key +- Generates an initial bonding transaction (`gentx`) to make the `foo` key a validator at genesis +- Starts `relayd` and stops it once the test exits +- Cleans up test state on a successful run + +### Notes when adding/running tests + +- Because the tests run against a built binary, you should make sure you build every time the code changes and you want to test again, otherwise you will be testing against an older version. If you are adding new tests this can easily lead to confusing test results. +- The [`test_helpers.go`](./test_helpers.go) file is organized according to the format of `relaycli` and `relayd` commands. There are comments with section headers describing the different areas. Helper functions to call CLI functionality are generally named after the command (e.g. `relaycli query bestknowndigest` would be `QueryBestKnownDigest`). Try to keep functions grouped by their position in the command tree. +- Test state that is needed by `tx` and `query` commands (`home`, `chain_id`, etc...) is stored on the `Fixtures` object. This makes constructing your new tests almost trivial. Each test needs unique Fixture to run in parallel +- Sometimes if you exit a test early there can be still running `relayd` and `relaycli` processes that will interrupt subsequent runs. Still running `relayd` processes will block ports and prevent new tests from spinning up. You can ensure new tests spin up clean by running `pkill -9 relayd && pkill -9 relaycli` before each test run. +- Most `query` and `tx` commands take a variadic `flags` argument. This pattern allows for the creation of a general function which is easily modified by adding flags. +- `Tx*` functions follow a general pattern and return `(success bool, stdout string, stderr string)`. This allows for easy testing of multiple different flag configurations. diff --git a/golang/cli_test/cli_test.go b/golang/cli_test/cli_test.go new file mode 100644 index 00000000..aaad1118 --- /dev/null +++ b/golang/cli_test/cli_test.go @@ -0,0 +1,457 @@ +package clitest + +import ( + "encoding/hex" + "github.com/stretchr/testify/suite" + "testing" +) + +type UtilsSuite struct { + suite.Suite + TestData TestData +} + +// Runs the whole test suite +func TestRelay(t *testing.T) { + + utilsSuite := new(UtilsSuite) + utilsSuite.TestData = GrabTestData(t) + + suite.Run(t, utilsSuite) +} + +func (suite *UtilsSuite) TestRelayCLIIsAncestor() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + newDiffHeaders := suite.TestData.NewDiffHeaders + + // Initialize CHain + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + + // define param values + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + validAncestor := hex.EncodeToString(genesisHeaders[1].Hash[:]) + invalidAncestor := hex.EncodeToString(genesisHeaders[0].Hash[:]) + digest := hex.EncodeToString(newDiffHeaders[1].Hash[:]) + limit := "5" + + // must ingest headers in order to perform query + success, _, stderr := f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + + // query chain for actual ancestor value + isancestor := f.QueryIsAncestor(digest, validAncestor, limit) + actual := isancestor.Res + + // True Condition + expected := true + suite.Equal(expected, actual) + + // False Condition + expected = false + isancestor = f.QueryIsAncestor(digest, invalidAncestor, limit) + actual = isancestor.Res + suite.Equal(expected, actual) + + //Cleanup + f.Cleanup() +} + +func (suite *UtilsSuite) TestRelayCLIGetRelayGenesis() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + + // Query Chain for Actual Value + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + fooAddr := f.KeyAddress(keyFoo) + genesisRelay := f.QueryGetRelayGenesis(fooAddr) + actual := genesisRelay.Res + + // Condition + expected := genesisHeaders[1].Hash + suite.Equal(expected, actual) + + //Cleanup + f.Cleanup() +} + +func (suite *UtilsSuite) TestRelayCLIGetLastReorgLCA() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + + // Query Chain for Actual Value + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + fooAddr := f.KeyAddress(keyFoo) + lastReorgLCA := f.QueryGetLastReorgLCA(fooAddr) + actual := lastReorgLCA.Res + + // Condition + expected := genesisHeaders[1].Hash + suite.Equal(expected, actual) + + //Cleanup + f.Cleanup() +} + +func (suite *UtilsSuite) TestRelayCLIGetBestDigest() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + + // Query Chain for Actual Value + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + fooAddr := f.KeyAddress(keyFoo) + bestDigest := f.QueryGetBestDigest(fooAddr) + actual := bestDigest.Res + + // Condition + expected := genesisHeaders[1].Hash + suite.Equal(expected, actual) + + //Cleanup + f.Cleanup() +} + +func (suite *UtilsSuite) TestRelayCLIQueryFindAncestor() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + newDiffHeaders := suite.TestData.NewDiffHeaders + + // Initialize chain + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + + // define paramater values + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + digest := hex.EncodeToString(newDiffHeaders[1].Hash[:]) + invalidOffset := "5" + validOffset := "1" + + // ingest headers + success, stdout, stderr := f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // Require findancestor fails if ancestor does not exist on relay + f.QueryFindAncestorInvalid("could not find ancestor", digest, invalidOffset) + + // Require findancestor returns ancestor if valid query + findancestor := f.QueryFindAncestor(digest, validOffset) + actual := hex.EncodeToString(findancestor.Res[:]) + expected := hex.EncodeToString(newDiffHeaders[0].Hash[:]) + suite.Equal(expected, actual) +} + +func (suite *UtilsSuite) TestRelayCLIIsMostRecentCommonAncestor() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + newDiffHeaders := suite.TestData.NewDiffHeaders + + // Initialize chain + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + + // must ingest headers in order to perform query + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + success, _, stderr := f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + + //perform query + ancestor := hex.EncodeToString(newDiffHeaders[0].Hash[:]) + left := hex.EncodeToString(newDiffHeaders[0].Hash[:]) + right := hex.EncodeToString(newDiffHeaders[1].Hash[:]) + limit := "3" + commonancestor := f.QueryIsMostRecentCommonAncestor(ancestor, left, right, limit) + actual := commonancestor.Res + + // True Condition + expected := true + suite.Equal(expected, actual) + + // False Condition + invalidAncestor := hex.EncodeToString(newDiffHeaders[1].Hash[:]) + commonancestor = f.QueryIsMostRecentCommonAncestor(invalidAncestor, left, right, limit) + expected = false + actual = commonancestor.Res + suite.Equal(expected, actual) + + //Cleanup + f.Cleanup() +} + +func (suite *UtilsSuite) TestRelayCLIQueryHeaviestFromAncestor() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + newDiffHeaders := suite.TestData.NewDiffHeaders + + // Transact with Chain for Actual Value + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + + // define paramteer values + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + ancestor := hex.EncodeToString(genesisHeaders[1].Hash[:]) + currentBest := hex.EncodeToString(genesisHeaders[1].Hash[:]) + validNewBest := hex.EncodeToString(newDiffHeaders[1].Hash[:]) + invalidNewBest := hex.EncodeToString(genesisHeaders[0].Hash[:]) + limit := "10" + + success, stdout, stderr := f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // Query chain + heaviestfromancestor := f.QueryHeaviestFromAncestor(ancestor, currentBest, validNewBest, limit) + + // Condition + actual := hex.EncodeToString(heaviestfromancestor.Res[:]) + suite.Equal(validNewBest, actual) + + // Require heaviestfromancestor fails with invalid params + f.QueryHeaviestFromAncestorInvalid("could not determine", ancestor, currentBest, invalidNewBest, limit) +} + +func (suite *UtilsSuite) TestRelayCLIQueryCheckProof() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + + // Initialize chain + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + + // Ingest headers + success, stdout, stderr := f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // Require checkproof fails without headers associated with proof + checkProof := f.QueryCheckProof("1_check_proof.json", "--inputfile") + actual := checkProof.Valid + expected := false + suite.Equal(expected, actual) + + // Ingest associated header + success, stdout, stderr = f.TxIngestHeaders(fooAddr, "2_ingest_headers.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // Require proof is valid when associated header exists with valid transaction + checkProof = f.QueryCheckProof("1_check_proof.json", "--inputfile") + expected = true + actual = checkProof.Valid + suite.Equal(expected, actual) +} + +func (suite *UtilsSuite) TestRelayCLITXIngestHeaders() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + + // Initialize chain + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + + // define parameter valuse + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + + // Require IngestDiffChange fails with invalid headers + success, stdout, stderr := f.TxIngestHeaders(fooAddr, "2_ingest_headers.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":false`) + + //Ingest Difficulty Change Headers + success, stdout, stderr = f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // Require successful IngestDiffChange with valid headers + success, stdout, stderr = f.TxIngestHeaders(fooAddr, "2_ingest_headers.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + //Cleanup + f.Cleanup() +} + +func (suite *UtilsSuite) TestRelayCLITXIngestDiffChange() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + + // Initialize chain + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + + // Define parameter values + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + + // Require IngestDiffChange fails with invalid headers + success, stdout, stderr := f.TxIngestDiffChange(fooAddr, prevEpochStart, "2_ingest_headers.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":false`) + + // Require successful IngestDiffChange with valid headers + success, stdout, stderr = f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + //Cleanup + f.Cleanup() +} + +func (suite *UtilsSuite) TestRelayCLITXProvideProof() { + suite.T().Parallel() + genesisHeaders := suite.TestData.GenesisHeaders + + // Initialize chain + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + + // Define parameter values + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + + // Ingest Headers w/ Diff Change + success, stdout, stderr := f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // Ingest New Headers + success, stdout, stderr = f.TxIngestHeaders(fooAddr, "2_ingest_headers.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // require checkproof fails given invalid proof requests + _, stdout, _ = f.TxProvideProof(fooAddr, "1_check_proof.json", "3_filled_requests.json", "--inputfile -y") + suite.Contains(stdout, `"Request not found`) + + // submit proof request + spends := "0x" + pays := "0x17a91423737cd98bb6b2da5a11bcd82e5de36591d69f9f87" + value := "0" + numConfs := "1" + success, stdout, stderr = f.TxNewRequest(fooAddr, spends, pays, value, numConfs, "-y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // checkproof succeeds given valid proof and requests + success, stdout, stderr = f.TxProvideProof(fooAddr, "1_check_proof.json", "3_filled_requests.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + //Cleanup + f.Cleanup() +} + +func (suite *UtilsSuite) TestRelayCLITxMarkNewHeaviest() { + suite.T().Parallel() + + genesisHeaders := suite.TestData.GenesisHeaders + newDiffHeaders := suite.TestData.NewDiffHeaders + newHeaders := suite.TestData.NewHeaders + + // Initialize chain + f := InitFixtures(suite.T()) + proc := f.RelayDStart() + defer func() { + err := proc.Stop(false) + suite.NoError(err) + }() + + // Define parameter values + fooAddr := f.KeyAddress(keyFoo) + prevEpochStart := hex.EncodeToString(genesisHeaders[0].Hash[:]) + ancestor := hex.EncodeToString(genesisHeaders[1].Hash[:]) + bestKnown := hex.EncodeToString(genesisHeaders[1].Raw[:]) + invalidBestKnown := hex.EncodeToString(newHeaders[1].Raw[:]) + newBest := hex.EncodeToString(newDiffHeaders[1].Raw[:]) + limit := "10" + + // Ingest new headers + success, stdout, stderr := f.TxIngestDiffChange(fooAddr, prevEpochStart, "0_new_difficulty.json", "--inputfile -y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + // MarkNewHeaviest fails given invalid block + success, stdout, stderr = f.TxMarkNewHeaviest(fooAddr, ancestor, invalidBestKnown, newBest, limit, "-y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":false`) + + // Mark new heaviest digest + success, stdout, stderr = f.TxMarkNewHeaviest(fooAddr, ancestor, bestKnown, newBest, limit, "-y") + suite.True(success, stderr) + suite.Contains(stdout, `"success":true`) + + bestDigest := f.QueryGetBestDigest(fooAddr) + + // Condition + expected := hex.EncodeToString(newDiffHeaders[1].Hash[:]) + actual := hex.EncodeToString(bestDigest.Res[:]) + suite.Equal(expected, actual) + + //Cleanup + f.Cleanup() +} diff --git a/golang/cli_test/test_helpers.go b/golang/cli_test/test_helpers.go new file mode 100644 index 00000000..e68e4c8d --- /dev/null +++ b/golang/cli_test/test_helpers.go @@ -0,0 +1,451 @@ +package clitest + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" + app "github.com/summa-tx/relays/golang" + rtypes "github.com/summa-tx/relays/golang/x/relay/types" + + clientkeys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + keyFoo = "foo" + stakeDenom = "stake" + fooDenom = "footoken" +) + +var startCoins = sdk.NewCoins( + sdk.NewCoin(stakeDenom, sdk.TokensFromConsensusPower(1000000)), + sdk.NewCoin(fooDenom, sdk.TokensFromConsensusPower(1000000)), +) + +type TestData struct { + GenesisHeaders []rtypes.BitcoinHeader + NewDiffHeaders []rtypes.BitcoinHeader + NewHeaders []rtypes.BitcoinHeader +} + +// Fixtures is used to setup the testing environment +type Fixtures struct { + BinDir string + RootDir string + RelaydBinary string + RelaycliBinary string + ChainID string + RPCAddr string + Port string + RelaydHome string + RelaycliHome string + P2PAddr string + T *testing.T +} + +////////////////////////////////////////////////////////////////////////////////////// +// Instantiation ///////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + +// NewFixtures creates a new instance of Fixtures with many vars set +func NewFixtures(t *testing.T) *Fixtures { + tmpDir, err := ioutil.TempDir("", "relay_integration_"+strings.TrimPrefix(t.Name(), "TestRelay/")+"_") + require.NoError(t, err) + servAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) + + p2pAddr, _, err := server.FreeTCPAddr() + require.NoError(t, err) + + binDir := os.Getenv("GOPATH") + "/bin/" + require.True(t, fileExists(binDir+"relayd"), "relayd binary does not exist") + require.True(t, fileExists(binDir+"relaycli"), "relaycli binary does not exist") + + return &Fixtures{ + T: t, + BinDir: binDir, + RootDir: tmpDir, + RelaydBinary: filepath.Join(binDir, "relayd"), + RelaycliBinary: filepath.Join(binDir, "relaycli"), + RelaydHome: filepath.Join(tmpDir, ".relayd"), + RelaycliHome: filepath.Join(tmpDir, ".relaycli"), + RPCAddr: servAddr, + P2PAddr: p2pAddr, + Port: port, + } +} + +// InitFixtures is called at the beginning of a test and initializes a chain +// with 1 validator. +func InitFixtures(t *testing.T) (f *Fixtures) { + f = NewFixtures(t) + + // reset test state + f.UnsafeResetAll() + + f.KeysAdd(keyFoo) + + // ensure that CLI output is in JSON format + f.CLIConfig("output", "json") + + // NOTE: RelayDInit sets the ChainID + f.RelayDInit(keyFoo) + + f.CLIConfig("chain-id", f.ChainID) + f.CLIConfig("broadcast-mode", "block") + f.CLIConfig("trust-node", "true") + + // start an account with tokens + f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) + + f.GenTx(keyFoo) + f.CollectGenTxs() + + return f +} + +func GrabTestData(t *testing.T) TestData { + testData := TestData{} + genesisJSON := readJSONFile(t, "genesis") + err := json.Unmarshal([]byte(genesisJSON), &testData.GenesisHeaders) + require.NoError(t, err) + + newDiffJSON := readJSONFile(t, "0_new_difficulty") + err = json.Unmarshal([]byte(newDiffJSON), &testData.NewDiffHeaders) + require.NoError(t, err) + + newHeadersJSON := readJSONFile(t, "2_ingest_headers") + err = json.Unmarshal([]byte(newHeadersJSON), &testData.NewHeaders) + require.NoError(t, err) + + return testData +} + +////////////////////////////////////////////////////////////////////////////////////// +// Fixtures Interface //////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + +// RelayDInit is relayd init +// NOTE: RelayDInit sets the ChainID for the Fixtures instance +func (f *Fixtures) RelayDInit(moniker string, flags ...string) { + cmd := fmt.Sprintf("%s init -o --home=%s %s", f.RelaydBinary, f.RelaydHome, moniker) + _, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) + + var chainID string + var initRes map[string]json.RawMessage + + err := json.Unmarshal([]byte(stderr), &initRes) + require.NoError(f.T, err) + + err = json.Unmarshal(initRes["chain_id"], &chainID) + require.NoError(f.T, err) + + f.ChainID = chainID +} + +// RelaydStart runs relayd start with the appropriate flags and returns a process +func (f *Fixtures) RelayDStart(flags ...string) *tests.Process { + cmd := fmt.Sprintf("%s start --home=%s --rpc.laddr=%v --p2p.laddr=%v", f.RelaydBinary, f.RelaydHome, f.RPCAddr, f.P2PAddr) + proc := tests.GoExecuteTWithStdout(f.T, addFlags(cmd, flags)) + tests.WaitForTMStart(f.Port) + tests.WaitForNextNBlocksTM(1, f.Port) + return proc +} + +// Cleanup is meant to be run at the end of a test to clean up an remaining test state +func (f *Fixtures) Cleanup(dirs ...string) { + clean := append(dirs, f.RootDir) + for _, d := range clean { + require.NoError(f.T, os.RemoveAll(d)) + } +} + +// AddGenesisAccount is relayd add-genesis-account +func (f *Fixtures) AddGenesisAccount(address sdk.AccAddress, coins sdk.Coins, flags ...string) { + cmd := fmt.Sprintf("%s add-genesis-account %s %s --home=%s", f.RelaydBinary, address, coins, f.RelaydHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags)) +} + +// GenesisFile returns the path of the generated genesis file +func (f Fixtures) GenesisFile() string { + return filepath.Join(f.RelaydHome, "config", "genesis.json") +} + +// Flags returns the flags necessary for making most CLI calls +func (f *Fixtures) Flags() string { + return fmt.Sprintf("--home=%s --node=%s", f.RelaycliHome, f.RPCAddr) +} + +// KeysAdd is relaycli keys add +func (f *Fixtures) KeysAdd(name string, flags ...string) { + cmd := fmt.Sprintf("%s keys add --home=%s %s", f.RelaycliBinary, f.RelaycliHome, name) + executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// CLIConfig is relaycli config +func (f *Fixtures) CLIConfig(key, value string, flags ...string) { + cmd := fmt.Sprintf("%s config --home=%s %s %s", f.RelaycliBinary, f.RelaycliHome, key, value) + executeWriteCheckErr(f.T, addFlags(cmd, flags)) +} + +// CollectGenTxs is relayd collect-gentxs +func (f *Fixtures) CollectGenTxs(flags ...string) { + cmd := fmt.Sprintf("%s collect-gentxs --home=%s", f.RelaydBinary, f.RelaydHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// KeysShow is relaycli keys show +func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput { + cmd := fmt.Sprintf("%s keys show --home=%s %s", f.RelaycliBinary, + f.RelaycliHome, name) + out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + var ko keys.KeyOutput + err := clientkeys.UnmarshalJSON([]byte(out), &ko) + require.NoError(f.T, err) + return ko +} + +// UnsafeResetAll is relayd unsafe-reset-all +func (f *Fixtures) UnsafeResetAll(flags ...string) { + cmd := fmt.Sprintf("%s --home=%s unsafe-reset-all", f.RelaydBinary, f.RelaydHome) + executeWrite(f.T, addFlags(cmd, flags)) + err := os.RemoveAll(filepath.Join(f.RelaydHome, "config", "gentx")) + require.NoError(f.T, err) +} + +// KeyAddress returns the SDK account address from the key +func (f *Fixtures) KeyAddress(name string) sdk.AccAddress { + ko := f.KeysShow(name) + accAddr, err := sdk.AccAddressFromBech32(ko.Address) + require.NoError(f.T, err) + return accAddr +} + +// GenTx is relayd gentx +func (f *Fixtures) GenTx(name string, flags ...string) { + cmd := fmt.Sprintf("%s gentx --name=%s --home=%s --home-client=%s", f.RelaydBinary, name, f.RelaydHome, f.RelaycliHome) + executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +////////////////////////////////////////////////////////////////////////////////////// +// CLI Queries /////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + +// QueryGetRelayGenesis returns the relay genesis block Hash +func (f *Fixtures) QueryGetRelayGenesis(delAddr sdk.AccAddress) rtypes.QueryResGetRelayGenesis { + cmd := fmt.Sprintf("%s query relay getrelaygenesis %s %s", f.RelaycliBinary, delAddr, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var relaygenesis rtypes.QueryResGetRelayGenesis + err := cdc.UnmarshalJSON([]byte(res), &relaygenesis) + require.NoError(f.T, err) + return relaygenesis +} + +// QueryGetLastReorgLCA returns Last Common Anscestor +func (f *Fixtures) QueryGetLastReorgLCA(delAddr sdk.AccAddress) rtypes.QueryResGetLastReorgLCA { + cmd := fmt.Sprintf("%s query relay getlastreorglca %s %s", f.RelaycliBinary, delAddr, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var lastreorglca rtypes.QueryResGetLastReorgLCA + err := cdc.UnmarshalJSON([]byte(res), &lastreorglca) + require.NoError(f.T, err) + return lastreorglca +} + +// QueryGetBestDigest returns the Best Known Digest +func (f *Fixtures) QueryGetBestDigest(delAddr sdk.AccAddress) rtypes.QueryResGetBestDigest { + cmd := fmt.Sprintf("%s query relay getbestdigest %s %s", f.RelaycliBinary, delAddr, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var bestknowndigest rtypes.QueryResGetBestDigest + err := cdc.UnmarshalJSON([]byte(res), &bestknowndigest) + require.NoError(f.T, err) + return bestknowndigest +} + +// QueryIsAncestor returns the Boolean +func (f *Fixtures) QueryIsAncestor(digest, ancestor, limit string) rtypes.QueryResIsAncestor { + cmd := fmt.Sprintf("%s query relay isancestor %s %s %s %s", f.RelaycliBinary, digest, ancestor, limit, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var isancestor rtypes.QueryResIsAncestor + err := cdc.UnmarshalJSON([]byte(res), &isancestor) + require.NoError(f.T, err) + return isancestor +} + +// QueryFindAncestor returns the Boolean +func (f *Fixtures) QueryFindAncestor(digest, offset string) rtypes.QueryResFindAncestor { + cmd := fmt.Sprintf("%s query relay findancestor %s %s %s", f.RelaycliBinary, digest, offset, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var findancestor rtypes.QueryResFindAncestor + err := cdc.UnmarshalJSON([]byte(res), &findancestor) + require.NoError(f.T, err) + return findancestor +} + +// QueryFindAncestorInvalid require proper response for invalid query +func (f *Fixtures) QueryFindAncestorInvalid(errStr, digest, offset string) { + cmd := fmt.Sprintf("%s query relay findancestor %s %s %s", f.RelaycliBinary, digest, offset, f.Flags()) + res, _ := tests.ExecuteT(f.T, cmd, "") + require.Contains(f.T, res, errStr) +} + +// QueryIsMostRecentCommonAncestor returns a Boolean +func (f *Fixtures) QueryIsMostRecentCommonAncestor(ancestor, left, right, limit string) rtypes.QueryResIsMostRecentCommonAncestor { + cmd := fmt.Sprintf("%s query relay ismostrecentcommonancestor %s %s %s %s %s", f.RelaycliBinary, ancestor, left, right, limit, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var ismostrecentcommonancestor rtypes.QueryResIsMostRecentCommonAncestor + err := cdc.UnmarshalJSON([]byte(res), &ismostrecentcommonancestor) + require.NoError(f.T, err) + return ismostrecentcommonancestor +} + +// QueryHeaviestFromAncestor returns a Boolean +func (f *Fixtures) QueryHeaviestFromAncestor(ancestor, currentBest, newBest, limit string) rtypes.QueryResHeaviestFromAncestor { + cmd := fmt.Sprintf("%s query relay heaviestfromancestor %s %s %s %s %s", f.RelaycliBinary, ancestor, currentBest, newBest, limit, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var heaviestfromancestor rtypes.QueryResHeaviestFromAncestor + err := cdc.UnmarshalJSON([]byte(res), &heaviestfromancestor) + require.NoError(f.T, err) + return heaviestfromancestor +} + +// QueryHeaviestFromAncestorInvalid require proper response for invalid query +func (f *Fixtures) QueryHeaviestFromAncestorInvalid(errStr, ancestor, currentBest, newBest, limit string) { + cmd := fmt.Sprintf("%s query relay heaviestfromancestor %s %s %s %s %s", f.RelaycliBinary, ancestor, currentBest, newBest, limit, f.Flags()) + res, _ := tests.ExecuteT(f.T, cmd, "") + require.Contains(f.T, res, errStr) +} + +// QueryCheckProof returns the Boolean +func (f *Fixtures) QueryCheckProof(proof string, flags ...string) rtypes.QueryResCheckProof { + cmd := fmt.Sprintf("%s query relay checkproof %s %s", f.RelaycliBinary, proof, f.Flags()) + res, errStr := tests.ExecuteT(f.T, addFlags(cmd, flags), "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var checkproof rtypes.QueryResCheckProof + err := cdc.UnmarshalJSON([]byte(res), &checkproof) + require.NoError(f.T, err) + return checkproof +} + +///////////////////////////////////////////////////////////////////// +// CLI Transactions ///////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// + +// TxIngestDiffChange is relaycli tx that ingests headers with new difficulty +func (f *Fixtures) TxIngestDiffChange(delAddr sdk.AccAddress, prevEpochStart, jsonHeaders string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx relay ingestdiffchange %s %s --from %s %s", f.RelaycliBinary, prevEpochStart, jsonHeaders, delAddr, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// TxIngestHeaders is a relaycli tx that ingests headers with same difficulty as previous headers +func (f *Fixtures) TxIngestHeaders(delAddr sdk.AccAddress, headers string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx relay ingestheaders %s --from %s %s", f.RelaycliBinary, headers, delAddr, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// TxNewRequest is a relaycli tx that submits a new Proof Request +func (f *Fixtures) TxNewRequest(delAddr sdk.AccAddress, spends, pays, value, numConfs string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx relay newrequest %s %s %s %s --from %s %s", f.RelaycliBinary, spends, pays, value, numConfs, delAddr, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// TxProvideProof is a relaycli tx that submits a new Proof Request +func (f *Fixtures) TxProvideProof(delAddr sdk.AccAddress, proof, listofrequests string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx relay provideproof %s %s --from %s %s", f.RelaycliBinary, proof, listofrequests, delAddr, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +// TxMarkNewHeaviest returns Last Common Anscestor +func (f *Fixtures) TxMarkNewHeaviest(delAddr sdk.AccAddress, ancestor, currentBest, newBest, limit string, flags ...string) (bool, string, string) { + cmd := fmt.Sprintf("%s tx relay marknewheaviest %s %s %s %s --from %s %s", f.RelaycliBinary, ancestor, currentBest, newBest, limit, delAddr, f.Flags()) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) +} + +////////////////////////////////////////////////////////////////////////////////////// +// Executors ///////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + +func executeWriteCheckErr(t *testing.T, cmdStr string, writes ...string) { + require.True(t, executeWrite(t, cmdStr, writes...)) +} + +func executeWrite(t *testing.T, cmdStr string, writes ...string) (exitSuccess bool) { + exitSuccess, _, _ = executeWriteRetStdStreams(t, cmdStr, writes...) + return +} + +func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (bool, string, string) { + proc := tests.GoExecuteT(t, cmdStr) + + // Enables use of interactive commands + for _, write := range writes { + _, err := proc.StdinPipe.Write([]byte(write + "\n")) + require.NoError(t, err) + } + + // Read both stdout and stderr from the process + stdout, stderr, err := proc.ReadAll() + if err != nil { + fmt.Println("Err on proc.ReadAll()", err, cmdStr) + } + + // Log output. + if len(stdout) > 0 { + t.Log("Stdout:", string(stdout)) + } + if len(stderr) > 0 { + t.Log("Stderr:", string(stderr)) + } + + // Wait for process to exit + proc.Wait() + // Return succes, stdout, stderr + return proc.ExitState.Success(), string(stdout), string(stderr) +} + +////////////////////////////////////////////////////////////////////////////////////// +// utils ///////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +func addFlags(cmd string, flags []string) string { + for _, f := range flags { + cmd += " " + f + } + return strings.TrimSpace(cmd) +} + +func readJSONFile(t *testing.T, filename string) []byte { + headerJSON, jsonErr := ioutil.ReadFile("../scripts/json_data/" + filename + ".json") + require.NoError(t, jsonErr) + return headerJSON +} + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} diff --git a/golang/cmd/relaycli/main.go b/golang/cmd/relaycli/main.go new file mode 100644 index 00000000..935a26c2 --- /dev/null +++ b/golang/cmd/relaycli/main.go @@ -0,0 +1,139 @@ +package main + +import ( + "os" + "path" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/lcd" + "github.com/cosmos/cosmos-sdk/client/rpc" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + "github.com/spf13/cobra" + "github.com/spf13/viper" + app "github.com/summa-tx/relays/golang" + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/libs/cli" +) + +func main() { + cobra.EnableCommandSorting = false + + cdc := app.MakeCodec() + + // Read in the configuration file for the sdk + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + rootCmd := &cobra.Command{ + Use: "relaycli", + Short: "relay Client", + } + + // Add --chain-id to persistent flags and mark it required + rootCmd.PersistentFlags().String(client.FlagChainID, "", "Chain ID of tendermint node") + rootCmd.PersistentPreRunE = func(_ *cobra.Command, _ []string) error { + return initConfig(rootCmd) + } + + // Construct Root Command + rootCmd.AddCommand( + rpc.StatusCommand(), + client.ConfigCmd(app.DefaultCLIHome), + queryCmd(cdc), + txCmd(cdc), + client.LineBreak, + lcd.ServeCommand(cdc, registerRoutes), + client.LineBreak, + keys.Commands(), + client.LineBreak, + version.Cmd, + client.NewCompletionCmd(rootCmd, true), + ) + + executor := cli.PrepareMainCmd(rootCmd, "NS", app.DefaultCLIHome) + err := executor.Execute() + if err != nil { + panic(err) + } +} + +func registerRoutes(rs *lcd.RestServer) { + client.RegisterRoutes(rs.CliCtx, rs.Mux) + app.ModuleBasics.RegisterRESTRoutes(rs.CliCtx, rs.Mux) +} + +func queryCmd(cdc *amino.Codec) *cobra.Command { + queryCmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + } + + queryCmd.AddCommand( + authcmd.GetAccountCmd(cdc), + client.LineBreak, + rpc.ValidatorCommand(cdc), + rpc.BlockCommand(), + authcmd.QueryTxsByEventsCmd(cdc), + authcmd.QueryTxCmd(cdc), + client.LineBreak, + ) + + // add modules' query commands + app.ModuleBasics.AddQueryCommands(queryCmd, cdc) + + return queryCmd +} + +func txCmd(cdc *amino.Codec) *cobra.Command { + txCmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + } + + txCmd.AddCommand( + bankcmd.SendTxCmd(cdc), + client.LineBreak, + authcmd.GetSignCommand(cdc), + authcmd.GetMultiSignCommand(cdc), + client.LineBreak, + authcmd.GetBroadcastCommand(cdc), + authcmd.GetEncodeCommand(cdc), + client.LineBreak, + ) + + // add modules' tx commands + app.ModuleBasics.AddTxCommands(txCmd, cdc) + + return txCmd +} + +func initConfig(cmd *cobra.Command) error { + home, err := cmd.PersistentFlags().GetString(cli.HomeFlag) + if err != nil { + return err + } + + cfgFile := path.Join(home, "config", "config.toml") + if _, err := os.Stat(cfgFile); err == nil { + viper.SetConfigFile(cfgFile) + + if err := viper.ReadInConfig(); err != nil { + return err + } + } + if err := viper.BindPFlag(client.FlagChainID, cmd.PersistentFlags().Lookup(client.FlagChainID)); err != nil { + return err + } + if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { + return err + } + return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag)) +} diff --git a/golang/cmd/relayd/main.go b/golang/cmd/relayd/main.go new file mode 100644 index 00000000..fa5aad6a --- /dev/null +++ b/golang/cmd/relayd/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "encoding/json" + "io" + + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/x/genaccounts" + genaccscli "github.com/cosmos/cosmos-sdk/x/genaccounts/client/cli" + "github.com/cosmos/cosmos-sdk/x/staking" + + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" + + app "github.com/summa-tx/relays/golang" +) + +func main() { + cobra.EnableCommandSorting = false + + cdc := app.MakeCodec() + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + ctx := server.NewDefaultContext() + + rootCmd := &cobra.Command{ + Use: "relayd", + Short: "relay App Daemon (server)", + PersistentPreRunE: server.PersistentPreRunEFn(ctx), + } + // CLI commands to initialize the chain + rootCmd.AddCommand( + genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome), + genutilcli.CollectGenTxsCmd(ctx, cdc, genaccounts.AppModuleBasic{}, app.DefaultNodeHome), + genutilcli.GenTxCmd(ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{}, genaccounts.AppModuleBasic{}, app.DefaultNodeHome, app.DefaultCLIHome), + genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics), + // AddGenesisAccountCmd allows users to add accounts to the genesis file + genaccscli.AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome), + ) + + server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) + + // prepare and add flags + executor := cli.PrepareBaseCmd(rootCmd, "RE", app.DefaultNodeHome) + err := executor.Execute() + if err != nil { + panic(err) + } +} + +func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { + return app.NewRelayApp(logger, db) +} + +func exportAppStateAndTMValidators( + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string, +) (json.RawMessage, []tmtypes.GenesisValidator, error) { + + if height != -1 { + relayApp := app.NewRelayApp(logger, db) + err := relayApp.LoadHeight(height) + if err != nil { + return nil, nil, err + } + return relayApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) + } + + relayApp := app.NewRelayApp(logger, db) + + return relayApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) +} diff --git a/golang/fake_app.go b/golang/fake_app.go new file mode 100644 index 00000000..f05ce6f7 --- /dev/null +++ b/golang/fake_app.go @@ -0,0 +1,316 @@ +package app + +import ( + "encoding/json" + "os" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/genaccounts" + "github.com/cosmos/cosmos-sdk/x/genutil" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" + "github.com/tendermint/tendermint/libs/log" + + "github.com/summa-tx/relays/golang/x/relay" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" +) + +const appName = "relay" + +var ( + // DefaultCLIHome is default home directories for the application CLI + DefaultCLIHome = os.ExpandEnv("$HOME/.summa/cosmosrelay") + + // DefaultNodeHome sets the folder where the applcation data and configuration will be stored + DefaultNodeHome = os.ExpandEnv("$HOME/.summa/cosmosrelay/config") + + // ModuleBasics is basic module elemnets + ModuleBasics = module.NewBasicManager( + genaccounts.AppModuleBasic{}, + genutil.AppModuleBasic{}, + auth.AppModuleBasic{}, + bank.AppModuleBasic{}, + staking.AppModuleBasic{}, + distr.AppModuleBasic{}, + params.AppModuleBasic{}, + slashing.AppModuleBasic{}, + supply.AppModuleBasic{}, + + relay.AppModule{}, + ) + + // account permissions + maccPerms = map[string][]string{ + auth.FeeCollectorName: nil, + distr.ModuleName: nil, + staking.BondedPoolName: {supply.Burner, supply.Staking}, + staking.NotBondedPoolName: {supply.Burner, supply.Staking}, + } +) + +type relayApp struct { + *bam.BaseApp + cdc *codec.Codec + + // keys to access the substores + keys map[string]*sdk.KVStoreKey + tkeys map[string]*sdk.TransientStoreKey + + // Keepers + accountKeeper auth.AccountKeeper + bankKeeper bank.Keeper + stakingKeeper staking.Keeper + slashingKeeper slashing.Keeper + distrKeeper distr.Keeper + supplyKeeper supply.Keeper + paramsKeeper params.Keeper + + relayKeeper relay.Keeper + + // Module Manager + mm *module.Manager +} + +// MakeCodec generates the necessary codecs for Amino +func MakeCodec() *codec.Codec { + var cdc = codec.New() + ModuleBasics.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + return cdc +} + +// NewRelayApp instantiates a new Bitcoin relay app +func NewRelayApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseApp)) *relayApp { + // First define the top level codec that will be shared by the different modules. Note: Codec will be explained later + cdc := MakeCodec() + + // BaseApp handles interactions with Tendermint through the ABCI protocol + bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) + + keys := sdk.NewKVStoreKeys( + bam.MainStoreKey, + auth.StoreKey, + staking.StoreKey, + supply.StoreKey, + distr.StoreKey, + slashing.StoreKey, + params.StoreKey, + relay.StoreKey) + tkeys := sdk.NewTransientStoreKeys(staking.TStoreKey, params.TStoreKey) + + // Here you initialize your application with the store keys it requires + var app = &relayApp{ + BaseApp: bApp, + cdc: cdc, + keys: keys, + tkeys: tkeys, + } + + // The ParamsKeeper handles parameter storage for the application + app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace) + // Set specific supspaces + authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace) + bankSubspace := app.paramsKeeper.Subspace(bank.DefaultParamspace) + stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace) + distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) + slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) + + // The AccountKeeper handles address -> account lookups + app.accountKeeper = auth.NewAccountKeeper( + app.cdc, + keys[auth.StoreKey], + authSubspace, + auth.ProtoBaseAccount, + ) + + // The BankKeeper allows you perform sdk.Coins interactions + app.bankKeeper = bank.NewBaseKeeper( + app.accountKeeper, + bankSubspace, + bank.DefaultCodespace, + app.ModuleAccountAddrs(), + ) + + // The SupplyKeeper collects transaction fees and renders them to the fee distribution module + app.supplyKeeper = supply.NewKeeper( + app.cdc, + keys[supply.StoreKey], + app.accountKeeper, + app.bankKeeper, + maccPerms, + ) + + // The staking keeper + stakingKeeper := staking.NewKeeper( + app.cdc, + keys[staking.StoreKey], + tkeys[staking.TStoreKey], + app.supplyKeeper, + stakingSubspace, + staking.DefaultCodespace, + ) + + app.distrKeeper = distr.NewKeeper( + app.cdc, + keys[distr.StoreKey], + distrSubspace, + &stakingKeeper, + app.supplyKeeper, + distr.DefaultCodespace, + auth.FeeCollectorName, + app.ModuleAccountAddrs(), + ) + + app.slashingKeeper = slashing.NewKeeper( + app.cdc, + keys[slashing.StoreKey], + &stakingKeeper, + slashingSubspace, + slashing.DefaultCodespace, + ) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.stakingKeeper = *stakingKeeper.SetHooks( + staking.NewMultiStakingHooks( + app.distrKeeper.Hooks(), + app.slashingKeeper.Hooks()), + ) + + // The RelayKeeper is the Keeper from the module for this tutorial + // It handles interactions with the store + app.relayKeeper = relay.NewKeeper( + keys[relay.StoreKey], + app.cdc, + true, // Mainnet // TODO: pass this in somehow + relay.NullHandler{}, // Proof Handler. real apps should fill this in + ) + + app.mm = module.NewManager( + genaccounts.NewAppModule(app.accountKeeper), + genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), + auth.NewAppModule(app.accountKeeper), + bank.NewAppModule(app.bankKeeper, app.accountKeeper), + relay.NewAppModule(app.relayKeeper), + supply.NewAppModule(app.supplyKeeper, app.accountKeeper), + distr.NewAppModule(app.distrKeeper, app.supplyKeeper), + slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), + staking.NewAppModule(app.stakingKeeper, app.distrKeeper, app.accountKeeper, app.supplyKeeper), + ) + + app.mm.SetOrderBeginBlockers(distr.ModuleName, slashing.ModuleName) + app.mm.SetOrderEndBlockers(staking.ModuleName) + + // Sets the order of Genesis - Order matters, genutil is to always come last + app.mm.SetOrderInitGenesis( + genaccounts.ModuleName, + distr.ModuleName, + staking.ModuleName, + auth.ModuleName, + bank.ModuleName, + slashing.ModuleName, + relay.ModuleName, + genutil.ModuleName, + ) + + // register all module routes and module queriers + app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) + + // The initChainer handles translating the genesis.json file into initial state for the network + app.SetInitChainer(app.InitChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + + // The AnteHandler handles signature verification and transaction pre-processing + app.SetAnteHandler( + auth.NewAnteHandler( + app.accountKeeper, + app.supplyKeeper, + auth.DefaultSigVerificationGasConsumer, + ), + ) + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + + err := app.LoadLatestVersion(app.keys[bam.MainStoreKey]) + if err != nil { + cmn.Exit(err.Error()) + } + + return app +} + +// GenesisState represents chain state at the start of the chain. Any initial state (account balances) are stored here. +type GenesisState map[string]json.RawMessage + +// NewDefaultGenesisState returns a new default genesis +func NewDefaultGenesisState() GenesisState { + return ModuleBasics.DefaultGenesis() +} + +func (app *relayApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + var genesisState GenesisState + + err := app.cdc.UnmarshalJSON(req.AppStateBytes, &genesisState) + if err != nil { + panic(err) + } + + return app.mm.InitGenesis(ctx, genesisState) +} + +func (app *relayApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + return app.mm.BeginBlock(ctx, req) +} + +func (app *relayApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + return app.mm.EndBlock(ctx, req) +} + +func (app *relayApp) LoadHeight(height int64) error { + return app.LoadVersion(height, app.keys[bam.MainStoreKey]) +} + +// ModuleAccountAddrs returns all the app's module account addresses. +func (app *relayApp) ModuleAccountAddrs() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range maccPerms { + modAccAddrs[supply.NewModuleAddress(acc).String()] = true + } + + return modAccAddrs +} + +//_________________________________________________________ + +func (app *relayApp) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string, +) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { + + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) + + genState := app.mm.ExportGenesis(ctx) + appState, err = codec.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + + validators = staking.WriteValidators(ctx, app.stakingKeeper) + + return appState, validators, nil +} diff --git a/golang/go.mod b/golang/go.mod new file mode 100644 index 00000000..1a854840 --- /dev/null +++ b/golang/go.mod @@ -0,0 +1,33 @@ +module github.com/summa-tx/relays/golang + +go 1.12 + +require ( + github.com/bombsimon/wsl v1.2.8 // indirect + github.com/cosmos/cosmos-sdk v0.37.0 + github.com/go-critic/go-critic v0.4.0 // indirect + github.com/gogo/protobuf v1.3.1 + github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d // indirect + github.com/golangci/golangci-lint v1.21.0 // indirect + github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 // indirect + github.com/gorilla/mux v1.7.0 + github.com/gostaticanalysis/analysisutil v0.0.3 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/mattn/go-isatty v0.0.10 // indirect + github.com/pelletier/go-toml v1.6.0 // indirect + github.com/securego/gosec v0.0.0-20191119104125-df484bfa9e9f // indirect + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cobra v0.0.5 + github.com/spf13/viper v1.5.0 + github.com/stretchr/testify v1.4.0 + github.com/summa-tx/bitcoin-spv/golang v1.4.0 + github.com/tendermint/go-amino v0.15.0 + github.com/tendermint/tendermint v0.32.2 + github.com/tendermint/tm-db v0.1.1 + github.com/uudashr/gocognit v1.0.0 // indirect + golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect + golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d // indirect + gopkg.in/yaml.v2 v2.2.7 // indirect + mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2 // indirect + sourcegraph.com/sqs/pbtypes v1.0.0 // indirect +) diff --git a/golang/go.sum b/golang/go.sum new file mode 100644 index 00000000..1dc3a02e --- /dev/null +++ b/golang/go.sum @@ -0,0 +1,552 @@ +bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +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/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d h1:1aAija9gr0Hyv4KfQcRcwlmFIrhkDmIj2dz5bkg/s/8= +github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d/go.mod h1:icNx/6QdFblhsEjZehARqbNumymUT/ydwlLojFdv7Sk= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bombsimon/wsl v1.2.5 h1:9gTOkIwVtoDZywvX802SDHokeX4kW1cKnV8ZTVAPkRs= +github.com/bombsimon/wsl v1.2.5/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM= +github.com/bombsimon/wsl v1.2.8 h1:b+E/W7koicKBZDU+vEsw/hnQTN8026Gv1eMZDLUU/Wc= +github.com/bombsimon/wsl v1.2.8/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM= +github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw= +github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE= +github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosmos/cosmos-sdk v0.35.0 h1:EPeie1aKHwnXtTzKggvabG7aAPN+DDmju2xquvjFwao= +github.com/cosmos/cosmos-sdk v0.35.0/go.mod h1:ruF+G4D7hRf34uzZQvf/SIja9fsIThU5D7GirwTMQ9I= +github.com/cosmos/cosmos-sdk v0.37.0 h1:S2I3NDGN2wqfGlY5KqkAHTpfezjhgeqDxrCxhlhd528= +github.com/cosmos/cosmos-sdk v0.37.0/go.mod h1:3b/k/Zd+YDuttSmEJdNkxga1H5EIiDUhSYeErAHQN7A= +github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI= +github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= +github.com/cosmos/ledger-cosmos-go v0.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc= +github.com/cosmos/ledger-cosmos-go v0.10.3/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= +github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= +github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db h1:GYXWx7Vr3+zv833u+8IoXbNnQY0AdXsxAgI0kX7xcwA= +github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= +github.com/go-critic/go-critic v0.4.0 h1:sXD3pix0wDemuPuSlrXpJNNYXlUiKiysLrtPVQmxkzI= +github.com/go-critic/go-critic v0.4.0/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= +github.com/go-kit/kit v0.6.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +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= +github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1-0.20190508161146-9fa652df1129 h1:tT8iWCYw4uOem71yYA3htfH+LNopJvcqZQshm56G5L4= +github.com/golang/mock v1.3.1-0.20190508161146-9fa652df1129/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d h1:pXTK/gkVNs7Zyy7WKgLXmpQ5bHTrq5GDsp8R9Qs67g0= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.21.0 h1:HxAxpR8Z0M8omihvQdsD3PF0qPjlqYqp2vMJzstoKeI= +github.com/golangci/golangci-lint v1.21.0/go.mod h1:phxpHK52q7SE+5KpPnti4oZTdFCEsn/tKN+nFvCKXfk= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 h1:XQKc8IYQOeRwVs36tDrEmTgDgP88d5iEURwpmtiAlOM= +github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3 h1:iwp+5/UAyzQSFgQ4uR2sni99sJ8Eo9DEacKWM5pekIg= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/libp2p/go-buffer-pool v0.0.1 h1:9Rrn/H46cXjaA2HQ5Y8lyhOS1NhTkZ4yuEs2r3Eechg= +github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA= +github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +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/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/otiai10/copy v0.0.0-20180813032824-7e9a647135a1/go.mod h1:pXzZSDlN+HPzSdyIBnKNN9ptD9Hx7iZMWIJPTwo4FPE= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137 h1:3l8oligPtjd4JuM+OZ+U8sjtwFGJs98cdWsqs6QZRWs= +github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/rakyll/statik v0.1.4/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rakyll/statik v0.1.5 h1:Ly2UjURzxnsSYS0zI50fZ+srA+Fu7EbpV5hglvJvJG0= +github.com/rakyll/statik v0.1.5/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= +github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d h1:BzRvVq1EHuIjxpijCEKpAxzKUUMurOQ4sknehIATRh8= +github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do= +github.com/securego/gosec v0.0.0-20191119104125-df484bfa9e9f h1:1egnuKwFhkqg8hXU5huGkxz9iPFu4dLINWRSIPjAM+M= +github.com/securego/gosec v0.0.0-20191119104125-df484bfa9e9f/go.mod h1:H5UrtKXL5BGF4FgRa7p2fyqU/lddaTSCLjexYIfS0Bk= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= +github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/viper v1.0.3 h1:z5LPUc2iz8VLT5Cw1UyrESG6FUUnOGecYGY08BLKSuc= +github.com/spf13/viper v1.0.3/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4= +github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/summa-tx/bitcoin-spv v2.0.0+incompatible h1:so6fH4hvyZ7LI8mC9mLOiV5u+8gxFpsXG84g1dGyK7M= +github.com/summa-tx/bitcoin-spv/golang v1.1.0 h1:fpnC/LvOtMFCLrzjCmoTZyCGZr1/skgkJromSk4nBBU= +github.com/summa-tx/bitcoin-spv/golang v1.1.0/go.mod h1:8VS5yAT8po0RU1YM86UIr1klFm0/JBf5bZHL0eHpYLs= +github.com/summa-tx/bitcoin-spv/golang v1.1.1-0.20191112011044-15eeb47227b4 h1:ygtjTMYRNTyJvZb2DlN5xnbc20Zw6XSAry4br43XiW0= +github.com/summa-tx/bitcoin-spv/golang v1.1.1-0.20191112011044-15eeb47227b4/go.mod h1:8VS5yAT8po0RU1YM86UIr1klFm0/JBf5bZHL0eHpYLs= +github.com/summa-tx/bitcoin-spv/golang v1.1.1-0.20191112210231-284f474dd1a8 h1:M9kZdivYUXYQ2MkZsax89tW54oKXE0/hM3BCb6LMtR0= +github.com/summa-tx/bitcoin-spv/golang v1.1.1-0.20191112210231-284f474dd1a8/go.mod h1:8VS5yAT8po0RU1YM86UIr1klFm0/JBf5bZHL0eHpYLs= +github.com/summa-tx/bitcoin-spv/golang v1.1.1-0.20191113003027-995ff9fb2d07 h1:5znRqPaGWwJTgI7eb8yJdHekr6rREwAp9/fdLjgZyD8= +github.com/summa-tx/bitcoin-spv/golang v1.1.1-0.20191113003027-995ff9fb2d07/go.mod h1:8VS5yAT8po0RU1YM86UIr1klFm0/JBf5bZHL0eHpYLs= +github.com/summa-tx/bitcoin-spv/golang v1.2.0 h1:d9VSHSb4HMQOYshbOTRh26g5yU4tUiBIn4cr9hlB2CE= +github.com/summa-tx/bitcoin-spv/golang v1.2.0/go.mod h1:8VS5yAT8po0RU1YM86UIr1klFm0/JBf5bZHL0eHpYLs= +github.com/summa-tx/bitcoin-spv/golang v1.3.0 h1:BHs+LZsCYd/UhHssiGuj6TZ3h8eBytIOVtiBTfVOiLc= +github.com/summa-tx/bitcoin-spv/golang v1.3.0/go.mod h1:8VS5yAT8po0RU1YM86UIr1klFm0/JBf5bZHL0eHpYLs= +github.com/summa-tx/bitcoin-spv/golang v1.4.0 h1:LQ/AWlf/jbkivtvZtEot3/nzUt4/Ll8/9IXOtNN0xAs= +github.com/summa-tx/bitcoin-spv/golang v1.4.0/go.mod h1:8VS5yAT8po0RU1YM86UIr1klFm0/JBf5bZHL0eHpYLs= +github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3 h1:sAlSBRDl4psFR3ysKXRSE8ss6Mt90+ma1zRTroTNBJA= +github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw= +github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +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/crypto v0.0.0-20180820045704-3764759f34a5 h1:u8i49c+BxloX3XQ55cvzFNXplizZP/q00i+IlttUjAU= +github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= +github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6offJMk= +github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= +github.com/tendermint/go-amino v0.15.0 h1:TC4e66P59W7ML9+bxio17CPKnxW3nKIRAYskntMAoRk= +github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tendermint/iavl v0.12.1 h1:JDfyhM/Hhrumu1CL1Nxrypm8sNTPYqmeHo1IZLiJoXM= +github.com/tendermint/iavl v0.12.1/go.mod h1:EoKMMv++tDOL5qKKVnoIqtVPshRrEPeJ0WsgDOLAauM= +github.com/tendermint/iavl v0.12.4 h1:hd1woxUGISKkfUWBA4mmmTwOua6PQZTJM/F0FDrmMV8= +github.com/tendermint/iavl v0.12.4/go.mod h1:8LHakzt8/0G3/I8FUU0ReNx98S/EP6eyPJkAUvEXT/o= +github.com/tendermint/tendermint v0.31.5 h1:vTet8tCq3B9/J9Yo11dNZ8pOB7NtSy++bVSfkP4KzR4= +github.com/tendermint/tendermint v0.31.5/go.mod h1:ymcPyWblXCplCPQjbOYbrF1fWnpslATMVqiGgWbZrlc= +github.com/tendermint/tendermint v0.32.1/go.mod h1:jmPDAKuNkev9793/ivn/fTBnfpA9mGBww8MPRNPNxnU= +github.com/tendermint/tendermint v0.32.2 h1:FvZWdksfDg/65vKKr5Lgo57keARFnmhrUEXHwyrV1QY= +github.com/tendermint/tendermint v0.32.2/go.mod h1:NwMyx58S8VJ7tEpFKqRVlVWKO9N9zjTHu+Dx96VsnOE= +github.com/tendermint/tm-db v0.1.1 h1:G3Xezy3sOk9+ekhjZ/kjArYIs1SmwV+1OUgNkj7RgV0= +github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +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/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517 h1:ChMKTho2hWKpks/nD/FL2KqM1wuVt62oJeiE8+eFpGs= +github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/uudashr/gocognit v1.0.0 h1:NST9SQhYHpFiyKdXn8UAiogdB1xkt5RnA1/rR/oBcGw= +github.com/uudashr/gocognit v1.0.0/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +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/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= +github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20180911220305-26e67e76b6c3/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= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +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= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191101200257-8dbcdeb83d3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d h1:/iIZNFGxc/a7C3yWjGcnboV+Tkc7mxr+p6fDztwoxuM= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2 h1:67iHsV9djwGdZpdZNbLuQj6FOzCaZe3w+vhLjn5AcFA= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +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/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= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2 h1:K7wru2CfJGumS5hkiguQ0Rb9ebKM2Jo8s5d4Jm9lFaM= +mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2/go.mod h1:rCqoQrfAmpTX/h2APczwM7UymU/uvaOluiVPIYCSY/k= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o= +sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= diff --git a/golang/scripts/README.md b/golang/scripts/README.md new file mode 100644 index 00000000..afd0b060 --- /dev/null +++ b/golang/scripts/README.md @@ -0,0 +1,83 @@ +# Build and Run App + +## Setup +If you have never used the `go mod` before, you must add some parameters to your environment. + +```bash +mkdir -p $HOME/go/bin +echo "export GOBIN=\$GOPATH/bin" >> ~/.bash_profile +echo "export PATH=\$PATH:\$GOBIN" >> ~/.bash_profile +source ~/.bash_profile +``` + +Now, you can install and run the application. + +```bash +# Clone repository +git clone https://github.com/summa-tx/relays.git +cd relays/golang + +# Install the app into your $GOBIN +make install + +# Now you should be able to run the following commands: +relayd help +relaycli help +``` +## Running the CLI +To run the CLI for manual testing, you can run `make init` to initialize a new chain.

+All chain related data lives in `scripts/json_data`. Edit `scripts/json_data/genesis.json` to generate a customized genesis state. This JSON must be a list of block headers pertaining to one epoch. The first header must be the first block of the epoch. The remaining headers must be ordered headers beginning at any height in the epoch. +```bash +# Set the executable rights if not done already +chmod +x scripts/init_chain.sh + +# initialize chain with data from scripts/json_data/genesis.json +make init +``` +Open up a new terminal tab in the same directory to begin interacting with the chain. As per the setup script, you can now interact via username/password `me / 12345678` such that when submitting transactions using flag `--from me` when prompted for the password enter: `12345678` + +### Query CLI +Querying neither requires the `--from` flag nor a password. +```bash +# Retrieve the first digest of the relay +relaycli query relay getrelaygenesis + +# Retrieve the best known digest +relaycli query relay getlastreorglca + +# List other query options +relaycli query relay +``` + +### Transact with CLI +Transactions require the `--from` flag and password.

+JSON parameters can be accepted as either raw json or json files. including the `--inputfile` flag will interpret all json parameters as json files from directory `scripts/json_data`

+use the flag ` --broadcast-mode block` to get errors synchronously upon transactions. Otherwise errors could get swallowed resulting in false positive success

+Here are some transactions and queries you can run upon initializing the chain with the default genesis state: + +```bash +# Add the following bitcoin headers which also correspond with a difficulty change in the bitcoin change +relaycli tx relay ingestdiffchange ef8248820b277b542ac2a726ccd293e8f2a3ea24c1fe04000000000000000000 0_new_difficulty.json --inputfile --from me --broadcast-mode block + +# Submit Proof Request +relaycli tx relay newrequest 0x 0x17a91423737cd98bb6b2da5a11bcd82e5de36591d69f9f87 0 1 --broadcast-mode block --from me + +# Check whether given proof is valid: It will not because block with transaction has not been ingested yet +relaycli query relay checkproof 1_check_proof.json --inputfile + +# Ingest new headers to relay (without any change in difficulty) +relaycli tx relay ingestheaders 2_ingest_headers.json --from me --inputfile --broadcast-mode block + +# Check whether given proof is valid: It will will be valid with new headers from previous tx +relaycli query relay checkproof 1_check_proof.json --inputfile + +# Provide valid proof that fulfils a proof request +relaycli tx relay provideproof 1_check_proof.json 3_filled_requests.json --from me --inputfile --broadcast-mode block + +# Ingest remaining headers to relay (without any change in difficulty) +relaycli tx relay ingestheaders 4_ingest_headers.json --from me --inputfile --broadcast-mode block + +# Mark new heaviest to update the best known digest +relaycli tx relay marknewheaviest 0x4c2078d0388e3844fe6241723e9543074bd3a974c16611000000000000000000 0x0000c020954ea1d980abc34fd5c260205e025a405f59cdf510960c000000000000000000ad864d04a6ca14e597da45c4936dd3a07946e7d72aab72a3ed7444f0f6da618dd150425eff3212173f0c982d 0x0000c020bc00d40ffb1b0e8850475b0ff71d990080bb0e8203d1090000000000000000008a317b377cc53010ed4c741bd6bcea5fe6748665a6a9374510ff77e5cdfac7e3b971425ed41a12174334a315 0 --broadcast-mode block --from me + +``` diff --git a/golang/scripts/init_chain.sh b/golang/scripts/init_chain.sh new file mode 100755 index 00000000..0a43b35f --- /dev/null +++ b/golang/scripts/init_chain.sh @@ -0,0 +1,26 @@ +#!/bin/bash +rm -rf ~/.summa/cosmosrelay + +if make install +then + echo SUCCESSFULLY BUILT +else + echo ERROR: CANNOT BUILD + exit 1 +fi + +relayd init mynode --chain-id relay + +echo "12345678" | relaycli keys add me + +relayd add-genesis-account $(relaycli keys show me -a) 1000cbtc,100000000stake + +relaycli config chain-id relay +relaycli config output json +relaycli config indent true +relaycli config trust-node true + +echo "12345678" | relayd gentx --name me +relayd collect-gentxs + +relayd start diff --git a/golang/scripts/json_data/0_new_difficulty.json b/golang/scripts/json_data/0_new_difficulty.json new file mode 100644 index 00000000..179f7d66 --- /dev/null +++ b/golang/scripts/json_data/0_new_difficulty.json @@ -0,0 +1,16 @@ +[ + { + "raw": "0000ff3f4c2078d0388e3844fe6241723e9543074bd3a974c166110000000000000000000b43965e06a087b47726f35aa886df5630057215bb46a58216243e236565ac296053425ed41a121781b98236", + "hash": "9be6406d5311123b6212b14b1a070276157364a5d5f004000000000000000000", + "height": 616896, + "prevhash": "4c2078d0388e3844fe6241723e9543074bd3a974c16611000000000000000000", + "merkle_root": "0b43965e06a087b47726f35aa886df5630057215bb46a58216243e236565ac29" + }, + { + "raw": "00c0ff3f9be6406d5311123b6212b14b1a070276157364a5d5f004000000000000000000f19f16556e7952046ea684eb6e8aeebe5fc1bb7fca09a0633fd3da5ee9e2dbb80c56425ed41a12172fdc7d2c", + "hash": "0641238051855d1759da9b6603b156684a68a146d36a09000000000000000000", + "height": 616897, + "prevhash": "9be6406d5311123b6212b14b1a070276157364a5d5f004000000000000000000", + "merkle_root": "f19f16556e7952046ea684eb6e8aeebe5fc1bb7fca09a0633fd3da5ee9e2dbb8" + } + ] diff --git a/golang/scripts/json_data/1_check_proof.json b/golang/scripts/json_data/1_check_proof.json new file mode 100644 index 00000000..ecd77920 --- /dev/null +++ b/golang/scripts/json_data/1_check_proof.json @@ -0,0 +1,16 @@ +{ + "version": "02000000", + "vin": "01bbfe169156ef6922af60eced842fd588db83b1803d75f94b3054ab20c36729c20000000017160014761503a7b541c34ab017fe95d4e56b872af45319feffffff", + "vout": "023a44090a0000000017a91423737cd98bb6b2da5a11bcd82e5de36591d69f9f87b0c412000000000017a9143b77f8e86b4b65b4173a6a489b067dc2d14e49ce87", + "locktime": "c1690900", + "tx_id": "f2147d83f9b048ebbffa04ebaf76341c56908e44aeb9bce27e727328bb00fc7d", + "index": 13, + "confirming_header": { + "raw": "00e000200641238051855d1759da9b6603b156684a68a146d36a09000000000000000000f1f5d7f347aea286b805162817fca959edba51b5bf025a3b745d7da5683faba4be58425ed41a1217d632cdcc", + "hash": "7f9923db1d3ad6a08054b4a80a5cd7478b57a9650eaf09000000000000000000", + "height": 616898, + "prevhash": "0641238051855d1759da9b6603b156684a68a146d36a09000000000000000000", + "merkle_root": "f1f5d7f347aea286b805162817fca959edba51b5bf025a3b745d7da5683faba4" + }, + "intermediate_nodes": "58088fd963c83b6f8605a2e36230039f2f0327838984a439fbd02a747068f1394e0592da283da5fec881377e14a6a4952b8aafe69eef0f79249228b8d3774b21047d13d55d8565a84c7ec5bb66e677a322a197bd582eb8033163560a63423e919ecfe60a2c3d5a16e82faf72b1c5318a1b4c9fbc33945980b45b0b703d8c3cf4892ee4da7182112658c372a27adafae0588bf12242c7ee10eac9e2ca406b92cfd23133c8b957b6f52ff2c25b836b34c3587ea0c756cb994072b44199a718d913ab51c5c74aaab591cc846370692b17a7378c8aee6e1d46020d04b00208ab9fe0a545ef7072d6304c15005a2c3d4d77c94f95d2c2fa8bc00894d0eea0b7dda08333e53e219616c479448c43bf7df9463f600eac9380d257fdb85b64d3a627f335a535867e0f5d20a39a9ae95c55ab6ffddecc94530a95bd877b6583a37d635f2f8ff007761bc8623ce3a1bb93cbd1f65acaadbf419a56abd5ce00a94305ea8b5fcfa5f5e0bb8ae134a445e4ff3c650be2bdc2d07f8ad65670ccf6b0077e90f5b7" +} diff --git a/golang/scripts/json_data/2_ingest_headers.json b/golang/scripts/json_data/2_ingest_headers.json new file mode 100644 index 00000000..879a145b --- /dev/null +++ b/golang/scripts/json_data/2_ingest_headers.json @@ -0,0 +1,79 @@ +[ + { + "raw": "00e000200641238051855d1759da9b6603b156684a68a146d36a09000000000000000000f1f5d7f347aea286b805162817fca959edba51b5bf025a3b745d7da5683faba4be58425ed41a1217d632cdcc", + "hash": "7f9923db1d3ad6a08054b4a80a5cd7478b57a9650eaf09000000000000000000", + "height": 616898, + "prevhash": "0641238051855d1759da9b6603b156684a68a146d36a09000000000000000000", + "merkle_root": "f1f5d7f347aea286b805162817fca959edba51b5bf025a3b745d7da5683faba4" + }, + { + "raw": "0000ff3f7f9923db1d3ad6a08054b4a80a5cd7478b57a9650eaf090000000000000000000c2aeb41cdea44a03b437fcd294805092c294d613f7f7192e6ffc9c6ec9130cddf58425ed41a121783dd5b80", + "hash": "396b3248c6b8dd11d0f02c6e688dce408dc4fe80b0c702000000000000000000", + "height": 616899, + "prevhash": "7f9923db1d3ad6a08054b4a80a5cd7478b57a9650eaf09000000000000000000", + "merkle_root": "0c2aeb41cdea44a03b437fcd294805092c294d613f7f7192e6ffc9c6ec9130cd" + }, + { + "raw": "00e0ff3f396b3248c6b8dd11d0f02c6e688dce408dc4fe80b0c70200000000000000000033b53cacf908eb0763918b833b80936d88c3bfa6999c280450d03855208791691f5a425ed41a1217940afef1", + "hash": "13249a1f8fbfe42e8e8ec1340f4fc01df17762b4dcb80c000000000000000000", + "height": 616900, + "prevhash": "396b3248c6b8dd11d0f02c6e688dce408dc4fe80b0c702000000000000000000", + "merkle_root": "33b53cacf908eb0763918b833b80936d88c3bfa6999c280450d0385520879169" + }, + { + "raw": "00e0002013249a1f8fbfe42e8e8ec1340f4fc01df17762b4dcb80c000000000000000000a9a61a4be4289169e82e9ba5ac35b9680cb6f9cb4bea0ae5b1b2ab6cda7eaac6745b425ed41a1217ddcacc90", + "hash": "8888ba6537f453b6ed11c01d1b9670e09271ccdcd65a02000000000000000000", + "height": 616901, + "prevhash": "13249a1f8fbfe42e8e8ec1340f4fc01df17762b4dcb80c000000000000000000", + "merkle_root": "a9a61a4be4289169e82e9ba5ac35b9680cb6f9cb4bea0ae5b1b2ab6cda7eaac6" + }, + { + "raw": "00e0ff3f8888ba6537f453b6ed11c01d1b9670e09271ccdcd65a02000000000000000000b2988e5fd8ef3af6aa08e1724147bd371c572bf80d86ad78a2bb3350f675c3a27d62425ed41a1217d68e19fd", + "hash": "ea87aa3b099679015eb8fd21268e9295a9fa0d568edb0c000000000000000000", + "height": 616902, + "prevhash": "8888ba6537f453b6ed11c01d1b9670e09271ccdcd65a02000000000000000000", + "merkle_root": "b2988e5fd8ef3af6aa08e1724147bd371c572bf80d86ad78a2bb3350f675c3a2" + }, + { + "raw": "00008020ea87aa3b099679015eb8fd21268e9295a9fa0d568edb0c0000000000000000008ebb3bf274b7ab7f299279f3086dd59f5d45e4dc116db4d470c38dd2ff2b2c8f5b65425ed41a12174bd195f8", + "hash": "4755b42cb9dcc5106d4ec4dcc930d1ec68816e1169830b000000000000000000", + "height": 616903, + "prevhash": "ea87aa3b099679015eb8fd21268e9295a9fa0d568edb0c000000000000000000", + "merkle_root": "8ebb3bf274b7ab7f299279f3086dd59f5d45e4dc116db4d470c38dd2ff2b2c8f" + }, + { + "raw": "0000c0204755b42cb9dcc5106d4ec4dcc930d1ec68816e1169830b00000000000000000040fd126a7fac3d39cd65b7aa87df4785fe3e585eac589a56578cb4e6a42824bdae68425ed41a1217155cc584", + "hash": "ab02a0a506ed429e9d40e3a7c58f80dcdf744318c98f0d000000000000000000", + "height": 616904, + "prevhash": "4755b42cb9dcc5106d4ec4dcc930d1ec68816e1169830b000000000000000000", + "merkle_root": "40fd126a7fac3d39cd65b7aa87df4785fe3e585eac589a56578cb4e6a42824bd" + }, + { + "raw": "00e0ff7fab02a0a506ed429e9d40e3a7c58f80dcdf744318c98f0d000000000000000000d338bb7242ca738d8cb0ef0f05c4d888c52eb5a075041ffafbabc3268f041544166b425ed41a12178cd64a96", + "hash": "0eb5e17dccf81445c88645f95f7ea9dd9e26d651d56209000000000000000000", + "height": 616905, + "prevhash": "ab02a0a506ed429e9d40e3a7c58f80dcdf744318c98f0d000000000000000000", + "merkle_root": "d338bb7242ca738d8cb0ef0f05c4d888c52eb5a075041ffafbabc3268f041544" + }, + { + "raw": "00c0ff3f0eb5e17dccf81445c88645f95f7ea9dd9e26d651d5620900000000000000000064b53b8a3e102a1b7e7cbb245dd1339e87d34abcea730009bd2a2dca93683a71426f425ed41a12177b11d4dd", + "hash": "ddfa3cf805f8de7e520b4ebaa6f18ac58d8d7a462ade11000000000000000000", + "height": 616906, + "prevhash": "0eb5e17dccf81445c88645f95f7ea9dd9e26d651d56209000000000000000000", + "merkle_root": "64b53b8a3e102a1b7e7cbb245dd1339e87d34abcea730009bd2a2dca93683a71" + }, + { + "raw": "00e00020ddfa3cf805f8de7e520b4ebaa6f18ac58d8d7a462ade110000000000000000000d3e06e5e25b12623a9d6cd9f2c0b6df31315b64934a1e9b61edcc90e9f4ae275870425ed41a1217a1371395", + "hash": "bc00d40ffb1b0e8850475b0ff71d990080bb0e8203d109000000000000000000", + "height": 616907, + "prevhash": "ddfa3cf805f8de7e520b4ebaa6f18ac58d8d7a462ade11000000000000000000", + "merkle_root": "0d3e06e5e25b12623a9d6cd9f2c0b6df31315b64934a1e9b61edcc90e9f4ae27" + }, + { + "raw": "0000c020bc00d40ffb1b0e8850475b0ff71d990080bb0e8203d1090000000000000000008a317b377cc53010ed4c741bd6bcea5fe6748665a6a9374510ff77e5cdfac7e3b971425ed41a12174334a315", + "hash": "f8d0a038bfe4027e5de3b6bf07262122636fd2916d7503000000000000000000", + "height": 616908, + "prevhash": "bc00d40ffb1b0e8850475b0ff71d990080bb0e8203d109000000000000000000", + "merkle_root": "8a317b377cc53010ed4c741bd6bcea5fe6748665a6a9374510ff77e5cdfac7e3" + } +] diff --git a/golang/scripts/json_data/3_filled_requests.json b/golang/scripts/json_data/3_filled_requests.json new file mode 100644 index 00000000..79aaaaa5 --- /dev/null +++ b/golang/scripts/json_data/3_filled_requests.json @@ -0,0 +1,7 @@ +[ + { + "inputIndex": 0, + "outputIndex": 0, + "id": "0x0000000000000000" + } +] diff --git a/golang/scripts/json_data/4_ingest_headers.json b/golang/scripts/json_data/4_ingest_headers.json new file mode 100644 index 00000000..5d1e9f6e --- /dev/null +++ b/golang/scripts/json_data/4_ingest_headers.json @@ -0,0 +1,44 @@ +[ + { + "raw": "00e0ff2ff8d0a038bfe4027e5de3b6bf07262122636fd2916d75030000000000000000009c66fd29c230fbc348ba962f1c2ca8c6c9bca2cd01fd55006f31d65d0ed139016572425ed41a1217c6c10ed1", + "hash": "5eda4c9ca8947f4f4f9848c55af2ab4ad62758c45d4002000000000000000000", + "height": 616909, + "prevhash": "f8d0a038bfe4027e5de3b6bf07262122636fd2916d7503000000000000000000", + "merkle_root": "9c66fd29c230fbc348ba962f1c2ca8c6c9bca2cd01fd55006f31d65d0ed13901" + }, + { + "raw": "000000205eda4c9ca8947f4f4f9848c55af2ab4ad62758c45d40020000000000000000002af75f1c0581dfaf0aa7007deec24ef232fd280d60a16a2ab26ee8dd509c746bcd74425ed41a1217f47350e8", + "hash": "f444030e5d30968f330377646dc657817a35657e6d2907000000000000000000", + "height": 616910, + "prevhash": "5eda4c9ca8947f4f4f9848c55af2ab4ad62758c45d4002000000000000000000", + "merkle_root": "2af75f1c0581dfaf0aa7007deec24ef232fd280d60a16a2ab26ee8dd509c746b" + }, + { + "raw": "00e00020f444030e5d30968f330377646dc657817a35657e6d29070000000000000000007ec6b452207ab96612f2b3231325877fa83b814a03e2069bac136f2f0ac42435e176425ed41a12173c2750a5", + "hash": "0efecebfb6c77fa2ee92e5b132b9b5f9653fe7ba64ff07000000000000000000", + "height": 616911, + "prevhash": "f444030e5d30968f330377646dc657817a35657e6d2907000000000000000000", + "merkle_root": "7ec6b452207ab96612f2b3231325877fa83b814a03e2069bac136f2f0ac42435" + }, + { + "raw": "00e000200efecebfb6c77fa2ee92e5b132b9b5f9653fe7ba64ff070000000000000000007d1740590d7b907cec752812c3202fed4f1c39ab565d27867981bc6b4d4a20b28578425ed41a12170984291f", + "hash": "f1b67a58e98576479c7ba7202174ade860e3afff6d8110000000000000000000", + "height": 616912, + "prevhash": "0efecebfb6c77fa2ee92e5b132b9b5f9653fe7ba64ff07000000000000000000", + "merkle_root": "7d1740590d7b907cec752812c3202fed4f1c39ab565d27867981bc6b4d4a20b2" + }, + { + "raw": "00000020f1b67a58e98576479c7ba7202174ade860e3afff6d81100000000000000000009293b2d0c8f430a39cc8346c5478df790027176b5d26b0893a088ff6179b63108a7a425ed41a12173041448e", + "hash": "51677bf39dd3318f95bc79d821e936013f717528adbe0b000000000000000000", + "height": 616913, + "prevhash": "f1b67a58e98576479c7ba7202174ade860e3afff6d8110000000000000000000", + "merkle_root": "9293b2d0c8f430a39cc8346c5478df790027176b5d26b0893a088ff6179b6310" + }, + { + "raw": "0000002051677bf39dd3318f95bc79d821e936013f717528adbe0b0000000000000000008bdd1ff50a88a852d497061c51542db436818bd3b6b2455c0f6333d7533cffd1387c425ed41a12175f902567", + "hash": "e6f0334bf990fe5172f3b15575347f4269f4e420614f10000000000000000000", + "height": 616914, + "prevhash": "51677bf39dd3318f95bc79d821e936013f717528adbe0b000000000000000000", + "merkle_root": "8bdd1ff50a88a852d497061c51542db436818bd3b6b2455c0f6333d7533cffd1" + } +] diff --git a/golang/scripts/json_data/genesis.json b/golang/scripts/json_data/genesis.json new file mode 100644 index 00000000..6def766a --- /dev/null +++ b/golang/scripts/json_data/genesis.json @@ -0,0 +1,15 @@ +[ + { + "raw": "00c0ff3fcc235a9993cbdfe938b54b9850485c46b6a408dd23fe100000000000000000006d79472dd0cf809ff632565b6de2fb051f5f87a06a5daf79bb95412f357f434353f42f5eff32121761139949", + "hash": "ef8248820b277b542ac2a726ccd293e8f2a3ea24c1fe04000000000000000000", + "height": 614880, + "prevhash": "cc235a9993cbdfe938b54b9850485c46b6a408dd23fe10000000000000000000", + "merkle_root": "6d79472dd0cf809ff632565b6de2fb051f5f87a06a5daf79bb95412f357f4343"}, + { + "raw": "0000c020954ea1d980abc34fd5c260205e025a405f59cdf510960c000000000000000000ad864d04a6ca14e597da45c4936dd3a07946e7d72aab72a3ed7444f0f6da618dd150425eff3212173f0c982d", + "hash": "4c2078d0388e3844fe6241723e9543074bd3a974c16611000000000000000000", + "height": 616895, + "prevhash": "954ea1d980abc34fd5c260205e025a405f59cdf510960c000000000000000000", + "merkle_root": "ad864d04a6ca14e597da45c4936dd3a07946e7d72aab72a3ed7444f0f6da618d" + } +] diff --git a/golang/x/relay/alias.go b/golang/x/relay/alias.go new file mode 100644 index 00000000..cd42d9ff --- /dev/null +++ b/golang/x/relay/alias.go @@ -0,0 +1,67 @@ +package relay + +import ( + "github.com/summa-tx/relays/golang/x/relay/keeper" + "github.com/summa-tx/relays/golang/x/relay/types" +) + +const ( + // ModuleName is what it says on the tin + ModuleName = types.ModuleName + // RouterKey is what it says on the tin + RouterKey = types.RouterKey + //StoreKey is what it says on the tin + StoreKey = types.StoreKey +) + +var ( + // NewKeeper is what is says on the tin + NewKeeper = keeper.NewKeeper + // NewQuerier is what is says on the tin + NewQuerier = keeper.NewQuerier + // NewMsgIngestHeaderChain is what is says on the tin + NewMsgIngestHeaderChain = types.NewMsgIngestHeaderChain + // NewMsgIngestDifficultyChange is what is says on the tin + NewMsgIngestDifficultyChange = types.NewMsgIngestDifficultyChange + // NewMsgMarkNewHeaviest is what is says on the tin + NewMsgMarkNewHeaviest = types.NewMsgMarkNewHeaviest + // NewMsgNewRequest is what is says on the tin + NewMsgNewRequest = types.NewMsgNewRequest + // NewMsgProvideProof is what is says on the tin + NewMsgProvideProof = types.NewMsgProvideProof + // RegisterCodec is what is says on the tin + RegisterCodec = types.RegisterCodec + // ModuleCdc is what is says on the tin + ModuleCdc = types.ModuleCdc +) + +type ( + // Keeper is what is says on the tin + Keeper = keeper.Keeper + + // TODO: add query structs here + + // Hash256Digest 32-byte double-sha2 digest + Hash256Digest = types.Hash256Digest + + // Hash160Digest is a 20-byte ripemd160+sha2 hash + Hash160Digest = types.Hash160Digest + + // RawHeader is an 80-byte raw header + RawHeader = types.RawHeader + + // HexBytes is a type alias to make JSON hex ser/deser easier + HexBytes = types.HexBytes + + // BitcoinHeader is a parsed Bitcoin header + BitcoinHeader = types.BitcoinHeader + + // SPVProof is the base struct for an SPV proof + SPVProof = types.SPVProof + + // ProofHandler is an interface to which the keepers dispatches valid proofs + ProofHandler = types.ProofHandler + + // NullHandler does nothing + NullHandler = types.NullHandler +) diff --git a/golang/x/relay/client/cli/query.go b/golang/x/relay/client/cli/query.go new file mode 100644 index 00000000..cf104316 --- /dev/null +++ b/golang/x/relay/client/cli/query.go @@ -0,0 +1,511 @@ +package cli + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/summa-tx/relays/golang/x/relay/types" +) + +// GetQueryCmd sets up query CLI commands +func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { + relayQueryCommand := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the relay module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + relayQueryCommand.AddCommand(client.GetCommands( + GetCmdIsAncestor(queryRoute, cdc), + GetCmdGetRelayGenesis(queryRoute, cdc), + GetCmdGetLastReorgLCA(queryRoute, cdc), + GetCmdGetBestDigest(queryRoute, cdc), + GetCmdFindAncestor(queryRoute, cdc), + GetCmdIsMostRecentCommonAncestor(queryRoute, cdc), + GetCmdHeaviestFromAncestor(queryRoute, cdc), + GetCmdCheckProof(queryRoute, cdc), + )...) + return relayQueryCommand +} + +// GetCmdIsAncestor returns the CLI command struct for IsAncestor +func GetCmdIsAncestor(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + // what are the arguments. <> for required, [] for optional + Use: "isancestor [limit]", + Example: "isancestor 12..ab 34..cd 200", // how do you use it? + // a help message. shows on `help isancestor` + Long: "Check if the second argument is an ancestor of the the argument. Optionally set a limit on block traversal", + // how many arguments does it take? + // also useful: cobra.ExactArgs(3) + Args: cobra.RangeArgs(2, 3), + // what does it do when run? + RunE: func(cmd *cobra.Command, args []string) error { + // spin up a context + cliCtx := context.NewCLIContext().WithCodec(cdc) + + var limit uint32 + if len(args) == 3 { + lim, err := strconv.ParseUint(args[2], 10, 32) + if err != nil { + fmt.Print(err.Error()) + return nil + } + limit = uint32(lim) + } + + digestLE, sdkErr := types.Hash256DigestFromHex(args[0]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + ancestor, sdkErr := types.Hash256DigestFromHex(args[1]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + + params := types.QueryParamsIsAncestor{ + DigestLE: digestLE, + ProspectiveAncestor: ancestor, + Limit: limit, + } + + queryData, err := cdc.MarshalJSON(params) + if err != nil { + fmt.Print(err.Error()) + return nil + } + + // run the query. the routeString is passed as strings to our querier switch/case in `keeper/querier.go` + res, _, err := cliCtx.QueryWithData("custom/relay/isancestor", queryData) + + if err != nil { + fmt.Printf("could not check if %s... is ancestor of %s... \n", args[1][:8], args[0][:8]) + return nil + } + + var out types.QueryResIsAncestor + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } +} + +// GetCmdGetRelayGenesis returns the CLI command struct for GetRelayGenesis +func GetCmdGetRelayGenesis(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "getrelaygenesis", + Example: "getrelaygenesis", + Long: "Get the first digest in the relay", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, _, err := cliCtx.QueryWithData("custom/relay/getrelaygenesis", nil) + + if err != nil { + fmt.Println("could not get the first digest in the relay") + return nil + } + + var out types.QueryResGetRelayGenesis + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } +} + +// GetCmdGetLastReorgLCA returns the CLI command struct for GetLastReorgLCA +func GetCmdGetLastReorgLCA(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "getlastreorglca", + Example: "getlastreorglca", + Long: "Returns the latest common ancestor of the last-known reorg", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, _, err := cliCtx.QueryWithData("custom/relay/getlastreorglca", nil) + + if err != nil { + fmt.Println("could not get the last Reorg LCA") + return nil + } + + var out types.QueryResGetLastReorgLCA + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } +} + +// GetCmdGetBestDigest returns the CLI command struct for GetBestDigest +func GetCmdGetBestDigest(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "getbestdigest", + Example: "getbestdigest", + Long: "Returns the best known digest in the relay", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, _, err := cliCtx.QueryWithData("custom/relay/getbestdigest", nil) + + if err != nil { + fmt.Println("could not get best known digest") + return nil + } + + var out types.QueryResGetBestDigest + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } +} + +// GetCmdFindAncestor returns the CLI command struct for FindAncestor +func GetCmdFindAncestor(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "findancestor ", + Example: "findancestor 12..ab 2", + Long: "Finds the digest blocks before . Errors if digest or the ancestor is unknown", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + digest, sdkErr := types.Hash256DigestFromHex(args[0]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + + var offset uint32 + off, err := strconv.ParseUint(args[1], 10, 32) + if err != nil { + fmt.Print(err.Error()) + return nil + } + offset = uint32(off) + + params := types.QueryParamsFindAncestor{ + DigestLE: digest, + Offset: offset, + } + + queryData, err := cdc.MarshalJSON(params) + if err != nil { + fmt.Print(err.Error()) + return nil + } + + res, _, err := cliCtx.QueryWithData("custom/relay/findancestor", queryData) + + if err != nil { + fmt.Printf("could not find ancestor of %s... \n", args[0][:8]) + return nil + } + + var out types.QueryResFindAncestor + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } +} + +// GetCmdIsMostRecentCommonAncestor returns the CLI command struct for IsMostRecentCommonAncestor +func GetCmdIsMostRecentCommonAncestor(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "ismostrecentcommonancestor [limit]", + Example: "ismostrecentcommonancestor 12..ab 34..cd 56..ef 200", // how do you use it? + Long: "Checks if is the LCA of and digests", + Args: cobra.RangeArgs(3, 4), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + ancestor, sdkErr := types.Hash256DigestFromHex(args[0]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + + left, sdkErr := types.Hash256DigestFromHex(args[1]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + + right, sdkErr := types.Hash256DigestFromHex(args[2]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + + var limit uint32 + if len(args) == 4 { + lim, err := strconv.ParseUint(args[3], 10, 32) + if err != nil { + fmt.Print(err.Error()) + return nil + } + limit = uint32(lim) + } + + params := types.QueryParamsIsMostRecentCommonAncestor{ + Ancestor: ancestor, + Left: left, + Right: right, + Limit: limit, + } + + queryData, err := cdc.MarshalJSON(params) + if err != nil { + fmt.Print(err.Error()) + return nil + } + + res, _, err := cliCtx.QueryWithData("custom/relay/ismostrecentcommonancestor", queryData) + + if err != nil { + fmt.Printf("could not check if %s... is the LCA of %s... and %s... \n", args[0][:8], args[1][:8], args[2][:8]) + return nil + } + + var out types.QueryResIsMostRecentCommonAncestor + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } +} + +// GetCmdHeaviestFromAncestor returns the CLI command struct for HeaviestFromAncestor +func GetCmdHeaviestFromAncestor(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "heaviestfromancestor [limit]", + Example: "heaviestFromancestor 12..ab 34..cd 56..ef 200", // how do you use it? + Long: "Determines the heavier descendant of a common ancestor", + Args: cobra.RangeArgs(3, 4), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + ancestor, sdkErr := types.Hash256DigestFromHex(args[0]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + + currentBest, sdkErr := types.Hash256DigestFromHex(args[1]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + + newBest, sdkErr := types.Hash256DigestFromHex(args[2]) + if sdkErr != nil { + fmt.Print(sdkErr.Error()) + return nil + } + + var limit uint32 + if len(args) == 4 { + lim, err := strconv.ParseUint(args[3], 10, 32) + if err != nil { + fmt.Print(err.Error()) + return nil + } + limit = uint32(lim) + } + + params := types.QueryParamsHeaviestFromAncestor{ + Ancestor: ancestor, + CurrentBest: currentBest, + NewBest: newBest, + Limit: limit, + } + + queryData, err := cdc.MarshalJSON(params) + if err != nil { + fmt.Print(err.Error()) + return nil + } + + res, _, err := cliCtx.QueryWithData("custom/relay/heaviestfromancestor", queryData) + + if err != nil { + fmt.Printf("could not determine if %s... or %s... is heaviest decendant of %s... \n", args[1][:8], args[2][:8], args[0][:8]) + return nil + } + + var out types.QueryResHeaviestFromAncestor + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } +} + +// GetCmdGetRequest returns the CLI command struct for getRequest +func GetCmdGetRequest(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "getrequest ", + Example: "getrequest 12", + Long: "Get a proof request using the associated ID. ID can be an\n\"0x\" prepended hexbyte string or an integer", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + id, idErr := types.RequestIDFromString(args[0]) + if idErr != nil { + return idErr + } + + params := types.QueryParamsGetRequest{ + ID: id, + } + + queryData, err := cdc.MarshalJSON(params) + if err != nil { + fmt.Print(err.Error()) + return nil + } + + res, _, err := cliCtx.QueryWithData("custom/relay/getrequest", queryData) + + if err != nil { + fmt.Printf("could not find request associated with id: %s... \n", args[0]) + return nil + } + + var out types.QueryResGetRequest + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } +} + +// GetCmdCheckRequests returns the CLI command struct for checkRequests +func GetCmdCheckRequests(queryRoute string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "checkrequests ", + Long: "check whether proof successfully validates a set of requests", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + var proof types.SPVProof + var requests []types.FilledRequestInfo + if viper.GetBool("inputfile") { + jsonFileProof, err := readJSONFromFile(args[0]) + if err != nil { + return err + } + jsonFileReq, err := readJSONFromFile(args[1]) + if err != nil { + return err + } + jsonErr := json.Unmarshal([]byte(jsonFileProof), &proof) + if jsonErr != nil { + return jsonErr + } + jsonErr = json.Unmarshal([]byte(jsonFileReq), &requests) + if jsonErr != nil { + return jsonErr + } + } else { + jsonErr := json.Unmarshal([]byte(args[0]), &proof) + if jsonErr != nil { + return jsonErr + } + jsonErr = json.Unmarshal([]byte(args[1]), &requests) + if jsonErr != nil { + return jsonErr + } + } + + filledRequests := types.NewFilledRequests( + proof, + requests, + ) + + params := types.QueryParamsCheckRequests{ + Filled: filledRequests, + } + + queryData, err := cdc.MarshalJSON(params) + if err != nil { + fmt.Print(err.Error()) + return nil + } + + res, _, err := cliCtx.QueryWithData("custom/relay/checkrequests", queryData) + + if err != nil { + fmt.Printf("error processing checkrequests: %s \n", err) + return nil + } + + var out types.QueryResCheckRequests + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } + + attachFlagFileinput(cmd) + return cmd +} + +// GetCmdCheckProof returns the CLI command struct for checkProof +func GetCmdCheckProof(queryRoute string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "checkproof ", + Long: "check proof has valid parameters", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + var proof types.SPVProof + if viper.GetBool("inputfile") { + jsonFile, err := readJSONFromFile(args[0]) + if err != nil { + return err + } + jsonErr := json.Unmarshal([]byte(jsonFile), &proof) + if jsonErr != nil { + return jsonErr + } + } else { + jsonErr := json.Unmarshal([]byte(args[0]), &proof) + if jsonErr != nil { + return jsonErr + } + } + + params := types.QueryParamsCheckProof{ + Proof: proof, + } + + queryData, err := cdc.MarshalJSON(params) + if err != nil { + fmt.Print(err.Error()) + return nil + } + + res, _, err := cliCtx.QueryWithData("custom/relay/checkproof", queryData) + + if err != nil { + fmt.Printf("error processing checkproof: %s \n", err) + return nil + } + + var out types.QueryResCheckProof + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(&out) + }, + } + + attachFlagFileinput(cmd) + return cmd +} diff --git a/golang/x/relay/client/cli/tx.go b/golang/x/relay/client/cli/tx.go new file mode 100644 index 00000000..c89efafa --- /dev/null +++ b/golang/x/relay/client/cli/tx.go @@ -0,0 +1,301 @@ +package cli + +import ( + "encoding/json" + "strconv" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + + "github.com/summa-tx/relays/golang/x/relay/types" + + btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" +) + +// GetTxCmd sets up transaction CLI commands +func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + relayTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Relay transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + relayTxCmd.AddCommand(client.PostCommands( + GetCmdIngestHeaderChain(cdc), + GetCmdIngestDifficultyChange(cdc), + GetCmdNewRequest(cdc), + GetCmdProvideProof(cdc), + GetCmdMarkNewHeaviest(cdc), + )...) + + return relayTxCmd +} + +// GetCmdIngestHeaderChain creates a CLI command to ingest a header chain +func GetCmdIngestHeaderChain(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "ingestheaders ", + Short: "Ingest a set of headers", + Long: "Ingest a set of headers. The headers must be in order, and the header immediately before the first must already be known to the relay.\nUse flag --inputfile to submit a json filename as input from scripts/seed_data directory", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + var headers = make([]types.BitcoinHeader, 0) + + if viper.GetBool("inputfile") { + jsonFile, err := readJSONFromFile(args[0]) + if err != nil { + return err + } + jsonErr := json.Unmarshal([]byte(jsonFile), &headers) + if jsonErr != nil { + return jsonErr + } + } else { + jsonErr := json.Unmarshal([]byte(args[0]), &headers) + if jsonErr != nil { + return jsonErr + } + } + + msg := types.NewMsgIngestHeaderChain( + cliCtx.GetFromAddress(), + headers, + ) + err := msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + + }, + } + + attachFlagFileinput(cmd) + return cmd +} + +// GetCmdIngestDifficultyChange creates a CLI command to ingest a difficulty change +func GetCmdIngestDifficultyChange(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "ingestdiffchange ", + Short: "Ingest a difficulty change.", + Long: "Ingest a difficulty change. Prev Epoch Start is a hex digest.", + Args: cobra.ExactArgs(2), + + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + prevEpochStart, err := btcspv.NewHash256Digest(btcspv.DecodeIfHex(args[0])) + if err != nil { + return types.FromBTCSPVError(types.DefaultCodespace, err) + } + + var headers []types.BitcoinHeader + if viper.GetBool("inputfile") { + jsonFile, err := readJSONFromFile(args[1]) + if err != nil { + return err + } + jsonErr := json.Unmarshal([]byte(jsonFile), &headers) + if jsonErr != nil { + return jsonErr + } + } else { + jsonErr := json.Unmarshal([]byte(args[1]), &headers) + if jsonErr != nil { + return jsonErr + } + } + + msg := types.NewMsgIngestDifficultyChange( + cliCtx.GetFromAddress(), + prevEpochStart, + headers, + ) + err = msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + + }, + } + + attachFlagFileinput(cmd) + return cmd +} + +// GetCmdNewRequest stores a new proof request +func GetCmdNewRequest(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "newrequest ", + Short: "Stores a new proof request", + Long: "Stores a new proof request", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + spends := btcspv.DecodeIfHex(args[0]) + pays := btcspv.DecodeIfHex(args[1]) + paysValue, valueErr := strconv.ParseUint(args[2], 10, 64) + if valueErr != nil { + return valueErr + } + numConfs, confsErr := strconv.ParseUint(args[3], 10, 8) + if confsErr != nil { + return confsErr + } + + msg := types.NewMsgNewRequest( + cliCtx.GetFromAddress(), + spends, + pays, + paysValue, + uint8(numConfs), + types.Local, + nil, + ) + err := msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + + }, + } +} + +// GetCmdProvideProof stores a new proof request +func GetCmdProvideProof(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "provideproof ", + Short: "validates proof of given requests", + Long: "validates proof of given requests", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + var proof types.SPVProof + var requests []types.FilledRequestInfo + if viper.GetBool("inputfile") { + jsonFileProof, err := readJSONFromFile(args[0]) + if err != nil { + return err + } + jsonFileReq, err := readJSONFromFile(args[1]) + if err != nil { + return err + } + jsonErr := json.Unmarshal([]byte(jsonFileProof), &proof) + if jsonErr != nil { + return jsonErr + } + jsonErr = json.Unmarshal([]byte(jsonFileReq), &requests) + if jsonErr != nil { + return jsonErr + } + } else { + jsonErr := json.Unmarshal([]byte(args[0]), &proof) + if jsonErr != nil { + return jsonErr + } + jsonErr = json.Unmarshal([]byte(args[1]), &requests) + if jsonErr != nil { + return jsonErr + } + } + + filledRequests := types.NewFilledRequests( + proof, + requests, + ) + + msg := types.NewMsgProvideProof( + cliCtx.GetFromAddress(), + filledRequests, + ) + + err := msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + + }, + } + + attachFlagFileinput(cmd) + return cmd +} + +// GetCmdMarkNewHeaviest creates a CLI command to update best known digest and LCA +func GetCmdMarkNewHeaviest(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "marknewheaviest [limit]", + Short: "Updates best known digest and LCA", + Long: "Updates best known digest and LCA.\nAncestor, current best, and new best are hex.\nLimit is an integer.", + Args: cobra.RangeArgs(3, 4), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + // TODO: Set default limit if limit is not provided? + + ancestor, err := btcspv.NewHash256Digest(btcspv.DecodeIfHex(args[0])) + if err != nil { + return types.FromBTCSPVError(types.DefaultCodespace, err) + } + + currentBest, curBestErr := btcspv.NewRawHeader(btcspv.DecodeIfHex(args[1])) + if curBestErr != nil { + return types.FromBTCSPVError(types.DefaultCodespace, curBestErr) + } + + newBest, newBestErr := btcspv.NewRawHeader(btcspv.DecodeIfHex(args[2])) + if newBestErr != nil { + return types.FromBTCSPVError(types.DefaultCodespace, newBestErr) + } + + limit, err := strconv.ParseUint(args[3], 10, 32) + if err != nil { + return err + } + + msg := types.NewMsgMarkNewHeaviest( + cliCtx.GetFromAddress(), + ancestor, + currentBest, + newBest, + uint32(limit), + ) + err = msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} diff --git a/golang/x/relay/client/cli/utils.go b/golang/x/relay/client/cli/utils.go new file mode 100644 index 00000000..0f0c1806 --- /dev/null +++ b/golang/x/relay/client/cli/utils.go @@ -0,0 +1,23 @@ +package cli + +import ( + "github.com/spf13/cobra" + "io/ioutil" + "os" + "strings" +) + +func readJSONFromFile(filename string) ([]byte, error) { + // get path to root directory + path, err := os.Getwd() + if err != nil { + return nil, err + } + // if running this function from cli_test directory do not include it in golang path + path = strings.TrimSuffix(path, "/cli_test") + return ioutil.ReadFile("/" + path + "/scripts/json_data/" + filename) +} + +func attachFlagFileinput(cmd *cobra.Command) { + cmd.Flags().Bool("inputfile", false, "Accepts a file as input for each json parameter") +} diff --git a/golang/x/relay/client/rest/query.go b/golang/x/relay/client/rest/query.go new file mode 100644 index 00000000..18a89dbe --- /dev/null +++ b/golang/x/relay/client/rest/query.go @@ -0,0 +1,385 @@ +package rest + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/gorilla/mux" + + "github.com/summa-tx/relays/golang/x/relay/types" +) + +// handler function for isAncestor queries. parses arguments from url string, and passes them through +// as a QueryParamsIsAncestor struct +func isAncestorHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // mux.Vars holds the variable elements of the URL from rest.go + vars := mux.Vars(r) + + digestLE, sdkErr := types.Hash256DigestFromHex(vars["digest"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + ancestor, sdkErr := types.Hash256DigestFromHex(vars["ancestor"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + var limit uint32 + if val, ok := vars["limit"]; ok { + lim, err := strconv.ParseUint(val, 10, 32) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + limit = uint32(lim) + } + + params := types.QueryParamsIsAncestor{ + DigestLE: digestLE, + ProspectiveAncestor: ancestor, + Limit: limit, + } + + queryData, err := json.Marshal(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // run the query. the routeString is passed as strings to our querier switch/case in `keeper/querier.go` + res, _, err := cliCtx.QueryWithData("custom/relay/isancestor", queryData) + + // below this is boilerplate + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// handler function for getRelayGenesis queries +func getRelayGenesisHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + res, _, err := cliCtx.QueryWithData("custom/relay/getrelaygenesis", nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// handler function for getLastReorgLCA queries +func getLastReorgLCAHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + res, _, err := cliCtx.QueryWithData("custom/relay/getlastreorglca", nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// handler function for getBestDigest queries +func getBestDigest(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + res, _, err := cliCtx.QueryWithData("custom/relay/getbestdigest", nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// handler function for findAncestor queries. parses arguments from url string, and passes them through +// as a QueryParamsFindAncestor struct +func findAncestorHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // mux.Vars holds the variable elements of the URL from rest.go + vars := mux.Vars(r) + + digestLE, sdkErr := types.Hash256DigestFromHex(vars["digest"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + off, err := strconv.ParseUint(vars["offset"], 10, 32) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + offset := uint32(off) + + params := types.QueryParamsFindAncestor{ + DigestLE: digestLE, + Offset: offset, + } + + queryData, err := json.Marshal(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // run the query. the routeString is passed as strings to our querier switch/case in `keeper/querier.go` + res, _, err := cliCtx.QueryWithData("custom/relay/findancestor", queryData) + + // below this is boilerplate + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// handler function for IsMostRecentCommonAncestor queries. parses arguments from url string, and passes them through +// as a QueryParamsIsMostRecentCommonAncestor struct +func isMostRecentCommonAncestorHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // mux.Vars holds the variable elements of the URL from rest.go + vars := mux.Vars(r) + + ancestor, sdkErr := types.Hash256DigestFromHex(vars["ancestor"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + left, sdkErr := types.Hash256DigestFromHex(vars["left"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + right, sdkErr := types.Hash256DigestFromHex(vars["right"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + var limit uint32 + if val, ok := vars["limit"]; ok { + lim, err := strconv.ParseUint(val, 10, 32) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + limit = uint32(lim) + } + + params := types.QueryParamsIsMostRecentCommonAncestor{ + Ancestor: ancestor, + Left: left, + Right: right, + Limit: limit, + } + + queryData, err := json.Marshal(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // run the query. the routeString is passed as strings to our querier switch/case in `keeper/querier.go` + res, _, err := cliCtx.QueryWithData("custom/relay/ismostrecentcommonancestor", queryData) + + // below this is boilerplate + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// handler function for heaviestFromAncestor queries. parses arguments from url string, and passes them +// through as a QueryParamsHeaviestFromAncestor struct +func heaviestFromAncestorHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // mux.Vars holds the variable elements of the URL from rest.go + vars := mux.Vars(r) + + ancestor, sdkErr := types.Hash256DigestFromHex(vars["ancestor"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + currentBest, sdkErr := types.Hash256DigestFromHex(vars["currentBest"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + newBest, sdkErr := types.Hash256DigestFromHex(vars["newBest"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + var limit uint32 + if val, ok := vars["limit"]; ok { + lim, err := strconv.ParseUint(val, 10, 32) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + limit = uint32(lim) + } + + params := types.QueryParamsHeaviestFromAncestor{ + Ancestor: ancestor, + CurrentBest: currentBest, + NewBest: newBest, + Limit: limit, + } + + queryData, err := json.Marshal(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // run the query. the routeString is passed as strings to our querier switch/case in `keeper/querier.go` + res, _, err := cliCtx.QueryWithData("custom/relay/heaviestfromancestor", queryData) + + // below this is boilerplate + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// handler function for getRequest queries. parses arguments from url string, and passes them through +// as a QueryParamsGetRequest struct +func getRequestHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + id, sdkErr := types.RequestIDFromString(vars["id"]) + if sdkErr != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, sdkErr.Error()) + return + } + + params := types.QueryParamsGetRequest{ + ID: id, + } + + queryData, err := json.Marshal(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err := cliCtx.QueryWithData("custom/relay/getRequest", queryData) + + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// struct to help parse json parameters since checkRequests has params more complex than +// other view functions and hence technically comes in as a POST request w/ json params +type checkRequestsReq struct { + Proof types.SPVProof `json:"proof"` + Requests []types.FilledRequestInfo `json:"filled_requests"` +} + +// handler function for checkRequests queries. parses arguments from url string, and passes them through +// as a QueryParamsCheckRequests struct +// Comes in as POST request will proceed to treat it as a GET +func checkRequestsHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req checkRequestsReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + filledRequests := types.NewFilledRequests(req.Proof, req.Requests) + + params := types.QueryParamsCheckRequests{ + Filled: filledRequests, + } + + queryData, err := json.Marshal(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err := cliCtx.QueryWithData("custom/relay/checkRequests", queryData) + + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// struct to help parse json parameters since checkProof has params more complex than +// other view functions and hence technically comes in as a POST request w/ json +type checkProofReq struct { + Proof types.SPVProof `json:"proof"` +} + +// handler function for checkProof queries. parses arguments from url string, and passes them through +// as a QueryParamsCheckProof struct +// Comes in as POST request will proceed to treat it as a GET +func checkProofHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req checkProofReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + params := types.QueryParamsCheckProof{ + Proof: req.Proof, + } + + queryData, err := json.Marshal(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err := cliCtx.QueryWithData("custom/relay/checkProof", queryData) + + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} diff --git a/golang/x/relay/client/rest/rest.go b/golang/x/relay/client/rest/rest.go new file mode 100644 index 00000000..8806d080 --- /dev/null +++ b/golang/x/relay/client/rest/rest.go @@ -0,0 +1,38 @@ +package rest + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client/context" + + "github.com/gorilla/mux" +) + +// RegisterRoutes - Central function to define routes that get registered by the main application +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, storeName string) { + s := r.PathPrefix(fmt.Sprintf("/%s", storeName)).Subrouter() + + // add new tx msg routes here + s.HandleFunc("/ingestheaderchain", ingestHeaderChainHandler(cliCtx)).Methods("POST") + s.HandleFunc("/ingestdiffchange", ingestDifficultyChangeHandler(cliCtx)).Methods("POST") + s.HandleFunc("/marknewheaviest", markNewHeaviestHandler(cliCtx)).Methods("POST") + s.HandleFunc("/newrequest", newRequestHandler(cliCtx)).Methods("POST") + s.HandleFunc("/provideproof", provideProofHandler(cliCtx)).Methods("POST") + + // add new query routes below + // {} denotes variable parts of the url route + // These are our function arguments + s.HandleFunc("/isancestor/{digest}/{ancestor}/", isAncestorHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/isancestor/{digest}/{ancestor}/{limit}", isAncestorHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/getrelaygenesis", getRelayGenesisHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/getlastreorglca", getLastReorgLCAHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/getbestdigest", getBestDigest(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/findancestor/{digest}/{offset}", findAncestorHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/ismostrecentcommonancestor/{ancestor}/{left}/{right}/", isMostRecentCommonAncestorHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/ismostrecentcommonancestor/{ancestor}/{left}/{right}/{limit}", isMostRecentCommonAncestorHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/heaviestfromancestor/{ancestor}/{currentbest}/{newbest}/", heaviestFromAncestorHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/heaviestfromancestor/{ancestor}/{currentbest}/{newbest}/{limit}", heaviestFromAncestorHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/getrequest/{id}", getRequestHandler(cliCtx, storeName)).Methods("GET") + s.HandleFunc("/checkrequests", checkRequestsHandler(cliCtx, storeName)).Methods("POST") // technically a view only query, POST is due to complex params + s.HandleFunc("/checkproof", checkProofHandler(cliCtx, storeName)).Methods("POST") // technically a view only query, POST is due to complex params +} diff --git a/golang/x/relay/client/rest/tx.go b/golang/x/relay/client/rest/tx.go new file mode 100644 index 00000000..de6f7aa9 --- /dev/null +++ b/golang/x/relay/client/rest/tx.go @@ -0,0 +1,212 @@ +package rest + +import ( + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/summa-tx/relays/golang/x/relay/types" +) + +// IngestHeaderChainReq is the request struct for ingest header chain +type IngestHeaderChainReq struct { + BaseReq rest.BaseReq `json:"base_req"` + Headers []types.BitcoinHeader `json:"headers"` + Sender string `json:"sender"` +} + +func ingestHeaderChainHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req IngestHeaderChainReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + addr, err := sdk.AccAddressFromBech32(req.Sender) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + msg := types.NewMsgIngestHeaderChain(addr, req.Headers) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) + } +} + +// IngestDifficultyChangeReq is the request struct for ingest difficulty change +type IngestDifficultyChangeReq struct { + BaseReq rest.BaseReq `json:"base_req"` + Start types.Hash256Digest `json:"prevEpochStart"` + Headers []types.BitcoinHeader `json:"headers"` + Sender string `json:"sender"` +} + +func ingestDifficultyChangeHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req IngestDifficultyChangeReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + addr, err := sdk.AccAddressFromBech32(req.Sender) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + msg := types.NewMsgIngestDifficultyChange(addr, req.Start, req.Headers) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) + } +} + +// MarkNewHeaviestReq is the request struct for mark new heaviest +type MarkNewHeaviestReq struct { + BaseReq rest.BaseReq `json:"base_req"` + Ancestor types.Hash256Digest `json:"ancestor"` + CurrentBest types.RawHeader `json:"currentBest"` + NewBest types.RawHeader `json:"newBest"` + Limit uint32 `json:"limit"` + Sender string `json:"sender"` +} + +func markNewHeaviestHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req MarkNewHeaviestReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + addr, err := sdk.AccAddressFromBech32(req.Sender) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + msg := types.NewMsgMarkNewHeaviest(addr, req.Ancestor, req.CurrentBest, req.NewBest, req.Limit) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) + } +} + +// NewRequestReq is the request struct for a new proof request +type NewRequestReq struct { + BaseReq rest.BaseReq `json:"base_req"` + Spends []byte `json:"spends"` + Pays []byte `json:"pays"` + PaysValue uint64 `json:"paysValue"` + NumConfs uint8 `json:"numConfs"` + Sender string `json:"sender"` +} + +func newRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req NewRequestReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + addr, err := sdk.AccAddressFromBech32(req.Sender) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + msg := types.NewMsgNewRequest(addr, req.Spends, req.Pays, req.PaysValue, req.NumConfs, types.Local, nil) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) + } +} + +// ProvideProofReq is the request struct for a new provide proof message +type ProvideProofReq struct { + BaseReq rest.BaseReq `json:"base_req"` + Proof types.SPVProof `json:"proof"` + Requests []types.FilledRequestInfo `json:"filled_requests"` + Sender string `json:"sender"` +} + +func provideProofHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req ProvideProofReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + addr, err := sdk.AccAddressFromBech32(req.Sender) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + filledRequests := types.NewFilledRequests(req.Proof, req.Requests) + + msg := types.NewMsgProvideProof(addr, filledRequests) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) + } +} diff --git a/golang/x/relay/gen_state.go b/golang/x/relay/gen_state.go new file mode 100644 index 00000000..c72e641f --- /dev/null +++ b/golang/x/relay/gen_state.go @@ -0,0 +1,30 @@ +package relay + +import ( + "encoding/json" + "io/ioutil" + "os" + "strings" +) + +func getGenesisHeaders() (BitcoinHeader, []BitcoinHeader) { + // get path to root directory + path, err := os.Getwd() + if err != nil { + panic(err.Error()) + } + // if running this function from cli_test directory do not include it in golang path + path = strings.TrimSuffix(path, "/cli_test") + + headerJSON, jsonErr := ioutil.ReadFile("/" + path + "/scripts/json_data/genesis.json") + if jsonErr != nil { + panic("could not retreive data in gen_state: " + jsonErr.Error()) + } + + var genesisHeaders []BitcoinHeader + err = json.Unmarshal([]byte(headerJSON), &genesisHeaders) + if err != nil { + panic("bad json in gen_state: " + err.Error()) + } + return genesisHeaders[0], genesisHeaders[1:] +} diff --git a/golang/x/relay/genesis.go b/golang/x/relay/genesis.go new file mode 100644 index 00000000..b0dd00fa --- /dev/null +++ b/golang/x/relay/genesis.go @@ -0,0 +1,75 @@ +package relay + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" + abci "github.com/tendermint/tendermint/abci/types" +) + +// GenesisState is the genesis state +type GenesisState struct { + Headers []BitcoinHeader `json:"headers"` + PeriodStart BitcoinHeader `json:"periodStart"` +} + +// NewGenesisState instantiates a genesis state +func NewGenesisState(headers []BitcoinHeader, periodStart BitcoinHeader) GenesisState { + return GenesisState{Headers: headers, PeriodStart: periodStart} +} + +// ValidateGenesis validates a genesis state +func ValidateGenesis(data GenesisState) error { + raw := []byte{} + for _, header := range data.Headers { + _, err := header.Validate() + if err != nil { + return err + } + raw = append(raw, header.Raw[:]...) + } + + _, err := btcspv.ValidateHeaderChain(raw) + if err != nil { + return err + } + + // Genesis state must include first block of an epoch plus another block belonging to that same epoch + if data.PeriodStart.Height != (data.Headers[0].Height - (data.Headers[0].Height % 2016)) { + return errors.New("period start has incorrect height") + } + + return nil +} + +// DefaultGenesisState sets block 606210 as genesis +func DefaultGenesisState() GenesisState { + periodStart, headers := getGenesisHeaders() + return GenesisState{ + Headers: headers, + PeriodStart: periodStart, + } +} + +// InitGenesis inits the app state based on the genesis state +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) []abci.ValidatorUpdate { + err := keeper.SetGenesisState(ctx, data.Headers[0], data.PeriodStart) + if err != nil { + panic("already init!") + } + if len(data.Headers) > 1 { + err = keeper.IngestHeaderChain(ctx, data.Headers[1:]) + if err != nil { + panic("Bad header chain in genesis state! " + err.Error()) + } + } + return []abci.ValidatorUpdate{} +} + +// ExportGenesis exports the genesis state +// TODO: export GenesisState +// May need special store keys for it +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + panic("Not implemented") +} diff --git a/golang/x/relay/keeper/chain.go b/golang/x/relay/keeper/chain.go new file mode 100644 index 00000000..450966c5 --- /dev/null +++ b/golang/x/relay/keeper/chain.go @@ -0,0 +1,199 @@ +package keeper + +import ( + "github.com/summa-tx/relays/golang/x/relay/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/summa-tx/bitcoin-spv/golang/btcspv" +) + +func (k Keeper) getChainStore(ctx sdk.Context) sdk.KVStore { + return k.getPrefixStore(ctx, types.ChainStorePrefix) +} + +func (k Keeper) emitReorg(ctx sdk.Context, prev, new, lca types.Hash256Digest) { + ctx.EventManager().EmitEvent(types.NewReorgEvent(prev, new, lca)) +} + +func (k Keeper) getDigestByStoreKey(ctx sdk.Context, key string) (types.Hash256Digest, sdk.Error) { + store := k.getChainStore(ctx) + result := store.Get([]byte(key)) + + digest, err := btcspv.NewHash256Digest(result) + if err != nil { + return types.Hash256Digest{}, types.ErrBadHash256Digest(types.DefaultCodespace, key) + } + return digest, nil +} + +func (k Keeper) setDigestByStoreKey(ctx sdk.Context, key string, digest types.Hash256Digest) { + store := k.getChainStore(ctx) + store.Set([]byte(key), digest[:]) +} + +// setBestKnownDigest sets the best known chain tip +func (k Keeper) setBestKnownDigest(ctx sdk.Context, bestKnown types.Hash256Digest) { + k.setDigestByStoreKey(ctx, types.BestKnownDigestStorage, bestKnown) +} + +// GetBestKnownDigest returns the best known digest in the relay +func (k Keeper) GetBestKnownDigest(ctx sdk.Context) (types.Hash256Digest, sdk.Error) { + return k.getDigestByStoreKey(ctx, types.BestKnownDigestStorage) +} + +// setLastReorgLCA sets the latest common ancestor of the last reorg +func (k Keeper) setLastReorgLCA(ctx sdk.Context, lca types.Hash256Digest) { + k.setDigestByStoreKey(ctx, types.LastReorgLCAStorage, lca) +} + +// GetLastReorgLCA returns the best known digest in the relay +func (k Keeper) GetLastReorgLCA(ctx sdk.Context) (types.Hash256Digest, sdk.Error) { + return k.getDigestByStoreKey(ctx, types.LastReorgLCAStorage) +} + +// IsMostRecentCommonAncestor checks if a proposed ancestor is the LCA of two digests +func (k Keeper) IsMostRecentCommonAncestor(ctx sdk.Context, ancestor, left, right types.Hash256Digest, limit uint32) bool { + if ancestor == left && ancestor == right { + return true + } + + leftCurrent := left + leftPrev := left + + rightCurrent := right + rightPrev := right + + for i := uint32(0); i < limit; i++ { + if leftPrev != ancestor { + leftCurrent = leftPrev + leftPrev = k.getLink(ctx, leftPrev) + } + if rightPrev != ancestor { + rightCurrent = rightPrev + rightPrev = k.getLink(ctx, rightPrev) + } + if leftPrev == rightPrev { + break + } + } + + if leftCurrent == rightCurrent { + return false + } + + if leftPrev != rightPrev { + return false + } + + return true +} + +// HeaviestFromAncestor determines the heavier descendant of a common ancestor +func (k Keeper) HeaviestFromAncestor(ctx sdk.Context, ancestor, currentBest, newBest types.Hash256Digest, limit uint32) (types.Hash256Digest, sdk.Error) { + ancestorBlock, err := k.GetHeader(ctx, ancestor) + if err != nil { + return types.Hash256Digest{}, types.ErrUnknownBlock(types.DefaultCodespace, "ancestor", ancestor) + } + leftBlock, err := k.GetHeader(ctx, currentBest) + if err != nil { + return types.Hash256Digest{}, types.ErrUnknownBlock(types.DefaultCodespace, "currentBest", currentBest) + } + rightBlock, err := k.GetHeader(ctx, newBest) + if err != nil { + return types.Hash256Digest{}, types.ErrUnknownBlock(types.DefaultCodespace, "newBest", newBest) + } + + if leftBlock.Height < ancestorBlock.Height { + return types.Hash256Digest{}, types.ErrBadHeight(types.DefaultCodespace, "currentBest", currentBest) + } + + if rightBlock.Height < ancestorBlock.Height { + return types.Hash256Digest{}, types.ErrBadHeight(types.DefaultCodespace, "newBest", newBest) + } + + nextPeriodStartHeight := ancestorBlock.Height + 2016 - (ancestorBlock.Height % 2016) + leftInPeriod := leftBlock.Height < nextPeriodStartHeight + rightInPeriod := rightBlock.Height < nextPeriodStartHeight + + /* + NB: + 1. Left is in a new window, right is in the old window. Left is heavier + 2. Right is in a new window, left is in the old window. Right is heavier + 3. Both are in the same window, choose the higher one + 4. They're in different new windows. Choose the heavier one + */ + if !leftInPeriod && rightInPeriod { + return leftBlock.Hash, nil + } + if leftInPeriod && !rightInPeriod { + return rightBlock.Hash, nil + } + if leftInPeriod && rightInPeriod { + if leftBlock.Height >= rightBlock.Height { + return leftBlock.Hash, nil + } + return rightBlock.Hash, nil + } + + // if !leftInPeriod && !rightInPeriod + leftDiff := btcspv.ExtractDifficulty(leftBlock.Raw) + leftAccDiff := leftDiff.Mul(sdk.NewUint(uint64(leftBlock.Height % 2016))) + + rightDiff := btcspv.ExtractDifficulty(rightBlock.Raw) + rightAccDiff := rightDiff.Mul(sdk.NewUint(uint64(rightBlock.Height % 2016))) + + if leftAccDiff.GTE(rightAccDiff) { + return leftBlock.Hash, nil + } + return rightBlock.Hash, nil +} + +// MarkNewHeaviest updates the best known digest and LCA +func (k Keeper) MarkNewHeaviest(ctx sdk.Context, ancestor types.Hash256Digest, currentBest, newBest types.RawHeader, limit uint32) sdk.Error { + newBestDigest := btcspv.Hash256(newBest[:]) + currentBestDigest := btcspv.Hash256(currentBest[:]) + + if !k.HasHeader(ctx, newBestDigest) { + return types.ErrUnknownBlock(types.DefaultCodespace, "newBest", newBestDigest) + } + + knownBestDigest, err := k.GetBestKnownDigest(ctx) + if err != nil || currentBestDigest != knownBestDigest { + return types.ErrNotBestKnown(types.DefaultCodespace, currentBestDigest, knownBestDigest) + } + + if !k.IsMostRecentCommonAncestor(ctx, ancestor, knownBestDigest, newBestDigest, limit) { + return types.ErrNotHeaviestAncestor(types.DefaultCodespace, ancestor) + } + + better, err := k.HeaviestFromAncestor(ctx, ancestor, knownBestDigest, newBestDigest, limit) + if err != nil { + return err + } + + if newBestDigest != better { + return types.ErrNotHeavier(types.DefaultCodespace, newBestDigest, better) + } + + // get newBestHeader + newBestHeader, getHeaderErr := k.GetHeader(ctx, newBestDigest) + if getHeaderErr != nil { + return getHeaderErr + } + // extract difficulty + newDiff := btcspv.ExtractDifficulty(newBestHeader.Raw) + // get currentEpochDifficulty + currentEpochDiff := k.getCurrentEpochDifficulty(ctx) + if newDiff != currentEpochDiff { + err := k.setCurrentEpochDifficulty(ctx, newDiff) + if err != nil { + return err + } + } + + k.setLastReorgLCA(ctx, ancestor) + k.setBestKnownDigest(ctx, newBestDigest) + k.emitReorg(ctx, knownBestDigest, newBestDigest, ancestor) + + return nil +} diff --git a/golang/x/relay/keeper/chain_test.go b/golang/x/relay/keeper/chain_test.go new file mode 100644 index 00000000..c7ebb85d --- /dev/null +++ b/golang/x/relay/keeper/chain_test.go @@ -0,0 +1,171 @@ +package keeper + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/summa-tx/relays/golang/x/relay/types" +) + +func (s *KeeperSuite) TestEmitReorg() { + headers := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers + s.Keeper.emitReorg(s.Context, headers[0].Hash, headers[1].Hash, headers[2].Hash) + + events := s.Context.EventManager().Events() + e := events[0] + s.Equal("reorg", e.Type) +} + +func (s *KeeperSuite) TestGetDigestByStoreKey() { + wrongLenDigest := bytes.Repeat([]byte{0}, 31) + key := "bad-digest" + + store := s.Keeper.getChainStore(s.Context) + store.Set([]byte(key), wrongLenDigest) + + _, err := s.Keeper.getDigestByStoreKey(s.Context, key) + s.Equal(sdk.CodeType(types.BadHash256Digest), err.Code()) +} + +func (s *KeeperSuite) TestGetBestKnownDigest() { + digest := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers[0].Hash + s.Keeper.setBestKnownDigest(s.Context, digest) + bestKnown, _ := s.Keeper.GetBestKnownDigest(s.Context) + s.Equal(digest, bestKnown) +} + +func (s *KeeperSuite) TestGetLastReorgLCA() { + digest := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers[0].Hash + s.Keeper.setLastReorgLCA(s.Context, digest) + lca, _ := s.Keeper.GetLastReorgLCA(s.Context) + s.Equal(digest, lca) +} + +func (s *KeeperSuite) TestIsMostRecentCommonAncestor() { + tv := s.Fixtures.ChainTestCases.IsMostRecentCA + pre := tv.PreRetargetChain + post := tv.PostRetargetChain + + var postWithOrphan []types.BitcoinHeader + postWithOrphan = append(postWithOrphan, post[:len(post)-2]...) + postWithOrphan = append(postWithOrphan, tv.Orphan) + + err := s.Keeper.SetGenesisState(s.Context, tv.Genesis, tv.OldPeriodStart) + s.SDKNil(err) + + err = s.Keeper.IngestHeaderChain(s.Context, pre) + s.SDKNil(err) + err = s.Keeper.IngestDifficultyChange(s.Context, tv.OldPeriodStart.Hash, post) + s.SDKNil(err) + err = s.Keeper.IngestDifficultyChange(s.Context, tv.OldPeriodStart.Hash, postWithOrphan) + s.SDKNil(err) + + for i := range tv.TestCases { + isMostRecent := s.Keeper.IsMostRecentCommonAncestor( + s.Context, + tv.TestCases[i].Ancestor, + tv.TestCases[i].Left, + tv.TestCases[i].Right, + tv.TestCases[i].Limit) + s.Equal(tv.TestCases[i].Output, isMostRecent) + } +} + +func (s *KeeperSuite) TestHeaviestFromAncestor() { + tv := s.Fixtures.ChainTestCases.HeaviestFromAncestor + headers := tv.Headers[0:8] + headersWithMain := tv.Headers[0:9] + + var headersWithOrphan []types.BitcoinHeader + headersWithOrphan = append(headersWithOrphan, headers...) + headersWithOrphan = append(headersWithOrphan, tv.Orphan) + + s.Keeper.ingestHeader(s.Context, tv.Genesis) + err := s.Keeper.IngestHeaderChain(s.Context, headersWithMain) + s.SDKNil(err) + err = s.Keeper.IngestHeaderChain(s.Context, headersWithOrphan) + s.SDKNil(err) + + for i := range tv.TestCases { + heaviest, err := s.Keeper.HeaviestFromAncestor( + s.Context, + tv.TestCases[i].Ancestor, + tv.TestCases[i].CurrentBest, + tv.TestCases[i].NewBest, + tv.TestCases[i].Limit) + if tv.TestCases[i].Error == 0 { + s.SDKNil(err) + s.Equal(heaviest, tv.TestCases[i].Output) + } else { + s.Equal(sdk.CodeType(tv.TestCases[i].Error), err.Code()) + } + } +} + +func (s *KeeperSuite) TestMarkNewHeaviest() { + tv := s.Fixtures.ChainTestCases.IsMostRecentCA + tc := s.Fixtures.ChainTestCases.MarkNewHeaviest + pre := tv.PreRetargetChain + post := tv.PostRetargetChain + var postWithOrphan []types.BitcoinHeader + postWithOrphan = append(postWithOrphan, post[:len(post)-2]...) + postWithOrphan = append(postWithOrphan, tv.Orphan) + + err := s.Keeper.SetGenesisState(s.Context, tv.Genesis, tv.OldPeriodStart) + s.SDKNil(err) + + err = s.Keeper.MarkNewHeaviest( + s.Context, + tv.Genesis.Hash, + pre[0].Raw, + pre[1].Raw, + 10, + ) + s.Equal(sdk.CodeType(types.UnknownBlock), err.Code()) + + err = s.Keeper.IngestHeaderChain(s.Context, pre) + s.SDKNil(err) + err = s.Keeper.IngestDifficultyChange(s.Context, tv.OldPeriodStart.Hash, post) + s.SDKNil(err) + err = s.Keeper.IngestDifficultyChange(s.Context, tv.OldPeriodStart.Hash, postWithOrphan) + s.SDKNil(err) + + // errors if the ancestor is not the heaviest common ancestor + err = s.Keeper.MarkNewHeaviest( + s.Context, + tv.Genesis.Hash, + tv.Genesis.Raw, + pre[0].Raw, + 10, + ) + s.SDKNil(err) + err = s.Keeper.MarkNewHeaviest( + s.Context, + tv.Genesis.Hash, + pre[0].Raw, + pre[1].Raw, + 10, + ) + s.Equal(sdk.CodeType(types.NotHeaviestAncestor), err.Code()) + + for i := range tc { + s.Keeper.setBestKnownDigest(s.Context, tc[i].BestKnownDigest) + // updates the best known and emits an event + err = s.Keeper.MarkNewHeaviest( + s.Context, + tc[i].Ancestor, + tc[i].CurrentBest, + tc[i].NewBest, + tc[i].Limit, + ) + + if tc[i].Error == 0 { + s.SDKNil(err) + events := s.Context.EventManager().Events() + e := events[i] + s.Equal(tc[i].Output, e.Type) + } else { + s.Equal(sdk.CodeType(tc[i].Error), err.Code()) + } + } +} diff --git a/golang/x/relay/keeper/handler.go b/golang/x/relay/keeper/handler.go new file mode 100644 index 00000000..60150993 --- /dev/null +++ b/golang/x/relay/keeper/handler.go @@ -0,0 +1,92 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/summa-tx/relays/golang/x/relay/types" +) + +// NewHandler returns a handler for relay type messages. +func NewHandler(keeper Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case types.MsgIngestHeaderChain: + return handleMsgIngestHeaderChain(ctx, keeper, msg) + case types.MsgIngestDifficultyChange: + return handleMsgIngestDifficultyChange(ctx, keeper, msg) + case types.MsgMarkNewHeaviest: + return handleMsgMarkNewHeaviest(ctx, keeper, msg) + case types.MsgNewRequest: + return handleMsgNewRequest(ctx, keeper, msg) + case types.MsgProvideProof: + return handleMsgProvideProof(ctx, keeper, msg) + default: + errMsg := fmt.Sprintf("Unrecognized relay Msg type: %v", msg.Type()) + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +func handleMsgIngestHeaderChain(ctx sdk.Context, keeper Keeper, msg types.MsgIngestHeaderChain) sdk.Result { + err := keeper.IngestHeaderChain(ctx, msg.Headers) + if err != nil { + return err.Result() + } + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +func handleMsgIngestDifficultyChange(ctx sdk.Context, keeper Keeper, msg types.MsgIngestDifficultyChange) sdk.Result { + err := keeper.IngestDifficultyChange(ctx, msg.Start, msg.Headers) + if err != nil { + return err.Result() + } + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +func handleMsgMarkNewHeaviest(ctx sdk.Context, keeper Keeper, msg types.MsgMarkNewHeaviest) sdk.Result { + err := keeper.MarkNewHeaviest(ctx, msg.Ancestor, msg.CurrentBest, msg.NewBest, msg.Limit) + if err != nil { + return err.Result() + } + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +func handleMsgNewRequest(ctx sdk.Context, keeper Keeper, msg types.MsgNewRequest) sdk.Result { + // Validate message + err := msg.ValidateBasic() + if err != nil { + return err.Result() + } + + // TODO: Add more complex permissioning + // Set request + err = keeper.setRequest(ctx, msg.Spends, msg.Pays, msg.PaysValue, msg.NumConfs, msg.Origin, msg.Action) + if err != nil { + return err.Result() + } + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +func handleMsgProvideProof(ctx sdk.Context, keeper Keeper, msg types.MsgProvideProof) sdk.Result { + filled, err := keeper.checkRequestsFilled(ctx, msg.Filled) + if err != nil { + return err.Result() + } + + // Dispatch the proof to the keeper's proof handler + keeper.ProofHandler.HandleValidProof(ctx, msg.Filled, filled) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} diff --git a/golang/x/relay/keeper/handler_test.go b/golang/x/relay/keeper/handler_test.go new file mode 100644 index 00000000..0291719e --- /dev/null +++ b/golang/x/relay/keeper/handler_test.go @@ -0,0 +1,134 @@ +package keeper + +import ( + "bytes" + + "github.com/summa-tx/relays/golang/x/relay/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func getAccAddress() sdk.AccAddress { + address, _ := sdk.AccAddressFromBech32("cosmos1ay37rp2pc3kjarg7a322vu3sa8j9puah8msyfw") + return address +} + +// Create a bad sdk.msg to pass into TestNewHandler +type MsgBadMessage struct { + Signer sdk.AccAddress `json:"signer"` +} + +func (msg MsgBadMessage) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} +func (msg MsgBadMessage) Type() string { return "bad_message" } +func (msg MsgBadMessage) ValidateBasic() sdk.Error { return nil } +func (msg MsgBadMessage) GetSignBytes() []byte { + return sdk.MustSortJSON(types.ModuleCdc.MustMarshalJSON(msg)) +} +func (msg MsgBadMessage) Route() string { return types.RouterKey } + +func (s *KeeperSuite) TestNewHandler() { + handler := NewHandler(s.Keeper) + + badMsg := MsgBadMessage{ + Signer: getAccAddress(), + } + + res := handler(s.Context, badMsg) + s.Equal("{\"codespace\":\"sdk\",\"code\":6,\"message\":\"Unrecognized relay Msg type: bad_message\"}", res.Log) +} + +func (s *KeeperSuite) TestHandleMsgIngestHeaderChain() { + testCases := s.Fixtures.HeaderTestCases.ValidateChain + handler := NewHandler(s.Keeper) + + newMsg := types.NewMsgIngestHeaderChain(getAccAddress(), testCases[0].Headers) + + res := handler(s.Context, newMsg) + s.Equal(sdk.CodeType(types.UnknownBlock), res.Code) + + s.Keeper.ingestHeader(s.Context, testCases[0].Anchor) + res = handler(s.Context, newMsg) + s.Equal("extension", res.Events[0].Type) +} + +func (s *KeeperSuite) TestHandleMsgIngestDifficultyChange() { + testCases := s.Fixtures.HeaderTestCases.ValidateDiffChange + handler := NewHandler(s.Keeper) + + newMsg := types.NewMsgIngestDifficultyChange(getAccAddress(), testCases[0].PrevEpochStart.Hash, testCases[0].Headers) + + res := handler(s.Context, newMsg) + s.Equal(sdk.CodeType(types.UnknownBlock), res.Code) + + s.Keeper.ingestHeader(s.Context, testCases[0].PrevEpochStart) + s.Keeper.ingestHeader(s.Context, testCases[0].Anchor) + res = handler(s.Context, newMsg) + s.Equal("extension", res.Events[0].Type) +} + +func (s *KeeperSuite) TestHandleMsgMarkNewHeaviest() { + testCases := s.Fixtures.HeaderTestCases.ValidateDiffChange + handler := NewHandler(s.Keeper) + + s.Keeper.ingestHeader(s.Context, testCases[0].PrevEpochStart) + s.Keeper.ingestHeader(s.Context, testCases[0].Anchor) + newMsg := types.NewMsgIngestDifficultyChange(getAccAddress(), testCases[0].PrevEpochStart.Hash, testCases[0].Headers) + res := handler(s.Context, newMsg) + s.Equal("extension", res.Events[0].Type) +} + +func (s *KeeperSuite) TestHandleMarkNewHeaviest() { + tv := s.Fixtures.ChainTestCases.IsMostRecentCA + pre := tv.PreRetargetChain + post := tv.PostRetargetChain + handler := NewHandler(s.Keeper) + + var postWithOrphan []types.BitcoinHeader + postWithOrphan = append(postWithOrphan, post[:len(post)-2]...) + postWithOrphan = append(postWithOrphan, tv.Orphan) + + err := s.Keeper.SetGenesisState(s.Context, tv.Genesis, tv.OldPeriodStart) + s.SDKNil(err) + + err = s.Keeper.IngestHeaderChain(s.Context, pre) + s.SDKNil(err) + err = s.Keeper.IngestDifficultyChange(s.Context, tv.OldPeriodStart.Hash, post) + s.SDKNil(err) + err = s.Keeper.IngestDifficultyChange(s.Context, tv.OldPeriodStart.Hash, postWithOrphan) + s.SDKNil(err) + + // returns correct error + newMsg := types.NewMsgMarkNewHeaviest(getAccAddress(), tv.OldPeriodStart.Hash, tv.OldPeriodStart.Raw, tv.OldPeriodStart.Raw, 10) + res := handler(s.Context, newMsg) + s.Equal(sdk.CodeType(types.NotBestKnown), res.Code) + + // Successfully marks new heaviest + newMsg = types.NewMsgMarkNewHeaviest(getAccAddress(), tv.Genesis.Hash, tv.Genesis.Raw, pre[0].Raw, 10) + res = handler(s.Context, newMsg) + s.Equal("extension", res.Events[0].Type) +} + +func (s *KeeperSuite) TestHandleNewRequest() { + handler := NewHandler(s.Keeper) + + // Success + newRequest := types.NewMsgNewRequest(getAccAddress(), bytes.Repeat([]byte{0}, 36), []byte{0}, 0, 0, types.Local, nil) + res := handler(s.Context, newRequest) + hasRequest := s.Keeper.hasRequest(s.Context, types.RequestID{}) + s.Equal(true, hasRequest) + s.Equal("proof_request", res.Events[0].Type) + + // Msg validation failed + newRequest = types.NewMsgNewRequest(getAccAddress(), []byte{0}, []byte{0}, 0, 0, types.Local, nil) + res = handler(s.Context, newRequest) + s.Equal(sdk.CodeType(types.SpendsLength), res.Code) + + // setRequest error + store := s.Keeper.getRequestStore(s.Context) + store.Set([]byte(types.RequestIDTag), []byte("badID")) + + newRequest = types.NewMsgNewRequest(getAccAddress(), bytes.Repeat([]byte{0}, 36), []byte{0}, 0, 0, types.Local, nil) + res = handler(s.Context, newRequest) + s.Equal(sdk.CodeType(types.BadHexLen), res.Code) +} diff --git a/golang/x/relay/keeper/headers.go b/golang/x/relay/keeper/headers.go new file mode 100644 index 00000000..8aeb0a6f --- /dev/null +++ b/golang/x/relay/keeper/headers.go @@ -0,0 +1,258 @@ +package keeper + +import ( + "math/big" + + "github.com/summa-tx/bitcoin-spv/golang/btcspv" + "github.com/summa-tx/relays/golang/x/relay/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) getHeaderStore(ctx sdk.Context) sdk.KVStore { + return k.getPrefixStore(ctx, types.HeaderStorePrefix) +} + +func (k Keeper) emitExtension(ctx sdk.Context, first, last types.BitcoinHeader) { + ctx.EventManager().EmitEvent(types.NewExtensionEvent(first, last)) +} + +// HasHeader checks if a header is in the store +func (k Keeper) HasHeader(ctx sdk.Context, digestLE types.Hash256Digest) bool { + return k.getHeaderStore(ctx).Has(digestLE[:]) +} + +// GetHeader retrieves a header from the store using its LE diges +func (k Keeper) GetHeader(ctx sdk.Context, digestLE types.Hash256Digest) (types.BitcoinHeader, sdk.Error) { + var header types.BitcoinHeader + store := k.getHeaderStore(ctx) + + if !store.Has(digestLE[:]) { + return types.BitcoinHeader{}, types.ErrUnknownBlock(types.DefaultCodespace, "digest", digestLE) + } + + buf := store.Get(digestLE[:]) + k.cdc.MustUnmarshalBinaryBare(buf, &header) + + return header, nil +} + +// getCurrentEpochDifficulty gets the current epoch's difficulty +func (k Keeper) getCurrentEpochDifficulty(ctx sdk.Context) sdk.Uint { + store := k.getHeaderStore(ctx) + result := store.Get([]byte(types.CurrentEpochDiffStorage)) + + var diff sdk.Uint + // This will only fail if the store is corrupted + _ = diff.UnmarshalJSON(result) + + return diff +} + +// setCurrentEpochDifficulty sets the current epoch's difficulty +func (k Keeper) setCurrentEpochDifficulty(ctx sdk.Context, diff sdk.Uint) sdk.Error { + store := k.getHeaderStore(ctx) + + b, err := diff.MarshalJSON() + if err != nil { + return types.ErrExternal(types.DefaultCodespace, err) + } + + store.Set([]byte(types.CurrentEpochDiffStorage), b) + return nil +} + +// getPrevEpochDifficulty gets the previous epoch's difficulty +func (k Keeper) getPrevEpochDifficulty(ctx sdk.Context) sdk.Uint { + store := k.getHeaderStore(ctx) + result := store.Get([]byte(types.PrevEpochDiffStorage)) + + var diff sdk.Uint + // This will only fail if the store is corrupted + _ = diff.UnmarshalJSON(result) + + return diff +} + +// setPrevEpochDifficulty sets the previous epoch's difficulty +func (k Keeper) setPrevEpochDifficulty(ctx sdk.Context, diff sdk.Uint) sdk.Error { + store := k.getHeaderStore(ctx) + + b, err := diff.MarshalJSON() + if err != nil { + return types.ErrExternal(types.DefaultCodespace, err) + } + + store.Set([]byte(types.PrevEpochDiffStorage), b) + return nil +} + +// updatePrevEpochDifficulty checks if there is a change in difficulty and updates +// the previous epoch's difficulty accordingly +func (k Keeper) updatePrevEpochDifficulty(ctx sdk.Context, oldDiff sdk.Uint) sdk.Error { + prevEpochDiff := k.getPrevEpochDifficulty(ctx) + if prevEpochDiff != oldDiff { + err := k.setPrevEpochDifficulty(ctx, oldDiff) + if err != nil { + return err + } + } + return nil +} + +// compareTargets compares Bitcoin truncated and full-length targets +func compareTargets(full, truncated sdk.Uint) bool { + // dirty hacks. sdk.Uint doesn't give us easy access to the underlying + // will be fixed in future sdk version + f, _ := full.MarshalAmino() + t, _ := truncated.MarshalAmino() + fullBI := new(big.Int) + fullBI.SetString(f, 0) + truncatedBI := new(big.Int) + truncatedBI.SetString(t, 0) + + res := new(big.Int) + res.And(fullBI, truncatedBI) + + return truncatedBI.Cmp(res) == 0 +} + +// ingestHeader stores a Bitcoin Header +func (k Keeper) ingestHeader(ctx sdk.Context, header types.BitcoinHeader) { + store := k.getHeaderStore(ctx) + + buf := k.cdc.MustMarshalBinaryBare(header) + store.Set(header.Hash[:], buf) +} + +// validateHeaderChain validates a chain of Bitcoin Headers +func validateHeaderChain(anchor types.BitcoinHeader, headers []types.BitcoinHeader, internal, isMainnet bool) sdk.Error { + prev := anchor // scratchpad, we change this later + + // On internal call, use the header chain target + expectedTarget := btcspv.ExtractTarget(anchor.Raw) + if internal { + expectedTarget = btcspv.ExtractTarget(headers[0].Raw) + } + // allocate memory for raw anchor + all headers + raw := make([]byte, 80*(len(headers)+1)) + copy(raw[0:80], anchor.Raw[:]) + + // Make the raw chain + for i, header := range headers { + _, err := header.Validate() + if err != nil { + return types.FromBTCSPVError(types.DefaultCodespace, err) + } + + // ensure height changes as expected + if prev.Height != header.Height-1 { + return types.ErrHeightMismatch(types.DefaultCodespace, prev.Hash, header.Hash) + } + + // ensure expectedTarget doesn't change + // it's allowed to change if the relay is in testnet mode + if isMainnet && !btcspv.ExtractTarget(header.Raw).Equal(expectedTarget) { + return types.ErrUnexpectedRetarget(types.DefaultCodespace, header.Raw) + } + + // copy header raw into a bytearray + offset := 80 * (i + 1) + copy(raw[offset:offset+80], header.Raw[:]) + prev = header + } + + // Then validate the chain + _, err := btcspv.ValidateHeaderChain(raw) + if err != nil { + return types.FromBTCSPVError(types.DefaultCodespace, err) + } + + return nil +} + +// ingestHeaders validates and stores a chain of Bitcoin Headers +func (k Keeper) ingestHeaders(ctx sdk.Context, headers []types.BitcoinHeader, internal bool) sdk.Error { + anchor, err := k.GetHeader(ctx, headers[0].PrevHash) + if err != nil { + return err + } + + err = validateHeaderChain(anchor, headers, internal, k.IsMainNet) + if err != nil { + return err + } + + for _, header := range headers { + k.setLink(ctx, header) + k.ingestHeader(ctx, header) + } + + k.emitExtension(ctx, anchor, headers[len(headers)-1]) + + return nil +} + +// validateDifficultyChange validates a Header Chain with a difficulty change +func validateDifficultyChange(headers []types.BitcoinHeader, prevEpochStart, anchor types.BitcoinHeader) sdk.Error { + if anchor.Height%2016 != 2015 { + return types.ErrWrongEnd(types.DefaultCodespace) + } + if anchor.Height != prevEpochStart.Height+2015 || anchor.Height < prevEpochStart.Height { + return types.ErrWrongStart(types.DefaultCodespace) + } + if !btcspv.ExtractDifficulty(anchor.Raw).Equal(btcspv.ExtractDifficulty(prevEpochStart.Raw)) { + return types.ErrPeriodMismatch(types.DefaultCodespace) + } + + // calculated target + expectedTarget := btcspv.RetargetAlgorithm( + btcspv.ExtractTarget(prevEpochStart.Raw), + btcspv.ExtractTimestamp(prevEpochStart.Raw), + btcspv.ExtractTimestamp(anchor.Raw)) + + // Observed target in the new period start header + actualTarget := btcspv.ExtractTarget(headers[0].Raw) + + if !compareTargets(expectedTarget, actualTarget) { + return types.ErrBadRetarget(types.DefaultCodespace) + } + + return nil +} + +// ingestDifficultyChange validates and stores a Header Chain with a difficulty change +func (k Keeper) ingestDifficultyChange(ctx sdk.Context, prevEpochStartLE types.Hash256Digest, headers []types.BitcoinHeader) sdk.Error { + // Find the anchor in our store + prevEpochStart, err := k.GetHeader(ctx, prevEpochStartLE) + if err != nil { + return err + } + anchor, err := k.GetHeader(ctx, headers[0].PrevHash) + if err != nil { + return err + } + + err = validateDifficultyChange(headers, prevEpochStart, anchor) + if err != nil { + return err + } + + oldDiff := btcspv.ExtractDifficulty(prevEpochStart.Raw) + err = k.updatePrevEpochDifficulty(ctx, oldDiff) + if err != nil { + return err + } + + return k.ingestHeaders(ctx, headers, true) +} + +// IngestHeaderChain ingests a chain of headers +func (k Keeper) IngestHeaderChain(ctx sdk.Context, headers []types.BitcoinHeader) sdk.Error { + return k.ingestHeaders(ctx, headers, false) +} + +// IngestDifficultyChange ingests a chain of headers +func (k Keeper) IngestDifficultyChange(ctx sdk.Context, prevEpochStartLE types.Hash256Digest, headers []types.BitcoinHeader) sdk.Error { + return k.ingestDifficultyChange(ctx, prevEpochStartLE, headers) +} diff --git a/golang/x/relay/keeper/headers_test.go b/golang/x/relay/keeper/headers_test.go new file mode 100644 index 00000000..8b2e5e0e --- /dev/null +++ b/golang/x/relay/keeper/headers_test.go @@ -0,0 +1,162 @@ +package keeper + +import ( + "github.com/summa-tx/relays/golang/x/relay/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (s *KeeperSuite) TestGetHeader() { + // errors if header is not found + header := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers[0] + _, err := s.Keeper.GetHeader(s.Context, header.Hash) + s.Equal(sdk.CodeType(types.UnknownBlock), err.Code()) +} + +func (s *KeeperSuite) TestEmitExtension() { + // tests extension was emitted successfully + headers := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers + s.Keeper.emitExtension(s.Context, headers[0], headers[1]) + + events := s.Context.EventManager().Events() + e := events[0] + s.Equal("extension", e.Type) +} + +func (s *KeeperSuite) TestValidateHeaderChain() { + cases := s.Fixtures.HeaderTestCases.ValidateChain + + for _, tc := range cases { + err := validateHeaderChain(tc.Anchor, tc.Headers, tc.Internal, tc.IsMainnet) + if tc.Output == 0 { + logIfTestCaseError(tc, err) + s.SDKNil(err) + } else { + s.NotNil(err) + s.Equal(tc.Output, err.Code()) + } + } +} + +func (s *KeeperSuite) TestIngestHeaders() { + cases := s.Fixtures.HeaderTestCases.ValidateChain + + // errors if anchor is not found + err := s.Keeper.ingestHeaders(s.Context, cases[0].Headers, cases[0].Internal) + s.Equal(sdk.CodeType(types.UnknownBlock), err.Code()) + + for _, tc := range cases { + s.InitTestContext(tc.IsMainnet, false) + s.Keeper.ingestHeader(s.Context, tc.Anchor) + err := s.Keeper.ingestHeaders(s.Context, tc.Headers, tc.Internal) + if tc.Output == 0 { + logIfTestCaseError(tc, err) + s.SDKNil(err) + } else { + s.NotNil(err) + s.Equal(tc.Output, err.Code()) + } + } +} + +func (s *KeeperSuite) TestIngestHeaderChain() { + cases := s.Fixtures.HeaderTestCases.ValidateChain + + for _, tc := range cases { + if tc.Internal == false { + s.InitTestContext(tc.IsMainnet, false) + s.Keeper.ingestHeader(s.Context, tc.Anchor) + err := s.Keeper.IngestHeaderChain(s.Context, tc.Headers) + if tc.Output == 0 { + logIfTestCaseError(tc, err) + s.SDKNil(err) + } else { + s.NotNil(err) + s.Equal(tc.Output, err.Code()) + } + } + } +} + +// TestIngestHeader tests ingestHeader, HasHeader, and GetHeader +func (s *KeeperSuite) TestIngestHeader() { + cases := s.Fixtures.HeaderTestCases.ValidateChain + + for _, tc := range cases { + s.Keeper.ingestHeader(s.Context, tc.Headers[0]) + hasHeader := s.Keeper.HasHeader(s.Context, tc.Headers[0].Hash) + s.Equal(true, hasHeader) + header, err := s.Keeper.GetHeader(s.Context, tc.Headers[0].Hash) + s.SDKNil(err) + s.Equal(tc.Headers[0], header) + } +} + +func (s *KeeperSuite) TestValidateDifficultyChange() { + cases := s.Fixtures.HeaderTestCases.ValidateDiffChange + + for _, tc := range cases { + err := validateDifficultyChange(tc.Headers, tc.PrevEpochStart, tc.Anchor) + if tc.Output == 0 { + logIfTestCaseError(tc, err) + s.SDKNil(err) + } else { + s.NotNil(err) + s.Equal(tc.Output, err.Code()) + } + } +} + +func (s *KeeperSuite) TestIngestDifficultyChange() { + cases := s.Fixtures.HeaderTestCases.ValidateDiffChange + + // errors if PrevEpochStart is not found + err := s.Keeper.IngestDifficultyChange(s.Context, cases[0].PrevEpochStart.Hash, cases[0].Headers) + s.Equal(sdk.CodeType(types.UnknownBlock), err.Code()) + + // errors if anchor is not found + s.Keeper.ingestHeader(s.Context, cases[0].PrevEpochStart) + err = s.Keeper.IngestDifficultyChange(s.Context, cases[0].PrevEpochStart.Hash, cases[0].Headers) + s.Equal(sdk.CodeType(types.UnknownBlock), err.Code()) + + for _, tc := range cases { + s.Keeper.ingestHeader(s.Context, tc.PrevEpochStart) + s.Keeper.ingestHeader(s.Context, tc.Anchor) + err := s.Keeper.IngestDifficultyChange(s.Context, tc.PrevEpochStart.Hash, tc.Headers) + if tc.Output == 0 { + logIfTestCaseError(tc, err) + s.SDKNil(err) + } else { + s.NotNil(err) + s.Equal(tc.Output, err.Code()) + } + } +} + +func (s *KeeperSuite) TestCompareTargets() { + cases := s.Fixtures.HeaderTestCases.CompareTargets + + for _, tc := range cases { + result := compareTargets(tc.Full, tc.Truncated) + s.Equal(tc.Output, result) + } +} + +func (s *KeeperSuite) TestSetCurrentEpochDiff() { + val := sdk.NewUint(1000) + err := s.Keeper.setCurrentEpochDifficulty(s.Context, val) + s.SDKNil(err) + + d := s.Keeper.getCurrentEpochDifficulty(s.Context) + + s.Equal(d, val) +} + +func (s *KeeperSuite) TestSetPrevEpochDiff() { + val := sdk.NewUint(1000) + err := s.Keeper.setPrevEpochDifficulty(s.Context, val) + s.SDKNil(err) + + d := s.Keeper.getPrevEpochDifficulty(s.Context) + + s.Equal(d, val) +} diff --git a/golang/x/relay/keeper/keeper.go b/golang/x/relay/keeper/keeper.go new file mode 100644 index 00000000..e49e4b29 --- /dev/null +++ b/golang/x/relay/keeper/keeper.go @@ -0,0 +1,66 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/summa-tx/bitcoin-spv/golang/btcspv" + + "github.com/summa-tx/relays/golang/x/relay/types" +) + +// Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine +type Keeper struct { + storeKey sdk.StoreKey // Unexposed key to access store from sdk.Context + cdc *codec.Codec // The wire codec for binary encoding/decoding. + IsMainNet bool + ProofHandler types.ProofHandler +} + +// NewKeeper instantiates a new keeper +func NewKeeper(storeKey sdk.StoreKey, cdc *codec.Codec, mainnet bool, handler types.ProofHandler) Keeper { + return Keeper{ + storeKey: storeKey, + cdc: cdc, + IsMainNet: mainnet, + ProofHandler: handler, + } +} + +func (k Keeper) getPrefixStore(ctx sdk.Context, namespace string) sdk.KVStore { + return prefix.NewStore(ctx.KVStore(k.storeKey), []byte(namespace)) +} + +func (k Keeper) hasRelayGenesis(ctx sdk.Context) bool { + store := k.getChainStore(ctx) + return store.Has([]byte(types.RelayGenesisStorage)) +} + +// setRelayGenesis sets the first digest in the relay +func (k Keeper) setRelayGenesis(ctx sdk.Context, relayGenesis types.Hash256Digest) { + k.setDigestByStoreKey(ctx, types.RelayGenesisStorage, relayGenesis) +} + +// GetRelayGenesis returns the first digest in the relay +func (k Keeper) GetRelayGenesis(ctx sdk.Context) (types.Hash256Digest, sdk.Error) { + return k.getDigestByStoreKey(ctx, types.RelayGenesisStorage) +} + +// SetGenesisState sets the genesis state +func (k Keeper) SetGenesisState(ctx sdk.Context, genesis, epochStart btcspv.BitcoinHeader) sdk.Error { + if k.hasRelayGenesis(ctx) { + return types.ErrAlreadyInit(types.DefaultCodespace) + } + + k.ingestHeader(ctx, genesis) + k.ingestHeader(ctx, epochStart) + + k.setRelayGenesis(ctx, genesis.Hash) + k.setBestKnownDigest(ctx, genesis.Hash) + k.setLastReorgLCA(ctx, genesis.Hash) + + // this will only fail if the genesis state is corrupt + _ = k.setCurrentEpochDifficulty(ctx, btcspv.ExtractDifficulty(genesis.Raw)) + + return nil +} diff --git a/golang/x/relay/keeper/keeper_test.go b/golang/x/relay/keeper/keeper_test.go new file mode 100644 index 00000000..ddca5c1b --- /dev/null +++ b/golang/x/relay/keeper/keeper_test.go @@ -0,0 +1,317 @@ +package keeper + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "testing" + + "github.com/summa-tx/relays/golang/x/relay/types" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" + abci "github.com/tendermint/tendermint/abci/types" + tmlog "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + "github.com/stretchr/testify/suite" + + "github.com/summa-tx/bitcoin-spv/golang/btcspv" +) + +type NamedCase interface { + Name() string +} + +type Case struct { + NamedCase + Comment string `json:"comment"` +} + +/***** LINK TEST CASES *****/ +type IsAncestorTestCase struct { + Digest types.Hash256Digest `json:"digest"` + Ancestor types.Hash256Digest `json:"ancestor"` + Limit uint32 `json:"limit"` + Output bool `json:"output"` +} + +type IsAncestor struct { + TestCases []IsAncestorTestCase `json:"testCases"` +} + +type FindAncestorTestCase struct { + Digest types.Hash256Digest `json:"digest"` + Offset uint32 `json:"offset"` + Error int `json:"error"` + Output types.Hash256Digest `json:"output"` +} + +type FindAncestor struct { + TestCases []FindAncestorTestCase `json:"testCases"` +} + +type LinkTestCases struct { + IsAncestor IsAncestor `json:"isAncestor"` + FindAncestor FindAncestor `json:"findAncestor"` +} + +/***** CHAIN TEST CASES *****/ +type MostRecentCATestCase struct { + Ancestor types.Hash256Digest `json:"ancestor"` + Left types.Hash256Digest `json:"left"` + Right types.Hash256Digest `json:"right"` + Limit uint32 `json:"limit"` + Output bool `json:"output"` +} + +type IsMostRecentCA struct { + Orphan types.BitcoinHeader `json:"orphan"` + OldPeriodStart types.BitcoinHeader `json:"oldPeriodStart"` + Genesis types.BitcoinHeader `json:"genesis"` + PreRetargetChain []types.BitcoinHeader `json:"preRetargetChain"` + PostRetargetChain []types.BitcoinHeader `json:"postRetargetChain"` + TestCases []MostRecentCATestCase `json:"testCases"` +} + +type HeaviestTestCase struct { + Ancestor types.Hash256Digest `json:"ancestor"` + CurrentBest types.Hash256Digest `json:"currentBest"` + NewBest types.Hash256Digest `json:"newBest"` + Limit uint32 `json:"limit"` + Error int `json:"error"` + Output types.Hash256Digest `json:"output"` +} + +type HeaviestFromAncestor struct { + Orphan types.BitcoinHeader `json:"orphan"` + BadHeader types.BitcoinHeader `json:"badHeader"` + Genesis types.BitcoinHeader `json:"genesis"` + Headers []types.BitcoinHeader `json:"headers"` + TestCases []HeaviestTestCase `json:"testCases"` +} + +type NewHeaviestTestCase struct { + BestKnownDigest types.Hash256Digest `json:"bestKnownDigest"` + Ancestor types.Hash256Digest `json:"ancestor"` + CurrentBest types.RawHeader `json:"currentBest"` + NewBest types.RawHeader `json:"newBest"` + Limit uint32 `json:"limit"` + Error int `json:"error"` + Output string `json:"output"` +} + +type ChainTestCases struct { + IsMostRecentCA IsMostRecentCA `json:"isMostRecentCommonAncestor"` + HeaviestFromAncestor HeaviestFromAncestor `json:"heaviestFromAncestor"` + MarkNewHeaviest []NewHeaviestTestCase `json:"markNewHeaviest"` +} + +/***** HEADER TEST CASES *****/ +type IngestCase struct { + Case + Headers []types.BitcoinHeader `json:"headers"` + Anchor types.BitcoinHeader `json:"anchor"` + Internal bool `json:"internal"` + IsMainnet bool `json:"isMainnet"` + Output sdk.CodeType `json:"output"` +} + +type DiffChangeCase struct { + Case + Headers []types.BitcoinHeader `json:"headers"` + PrevEpochStart types.BitcoinHeader `json:"prevEpochStart"` + Anchor types.BitcoinHeader `json:"anchor"` + Output sdk.CodeType `json:"output"` +} + +type CompareCase struct { + Case + Full sdk.Uint `json:"full"` + Truncated sdk.Uint `json:"truncated"` + Output bool `json:"output"` +} + +type HeaderTestCases struct { + ValidateDiffChange []DiffChangeCase `json:"validateDifficultyChange"` + ValidateChain []IngestCase `json:"validateHeaderChain"` + CompareTargets []CompareCase `json:"compareTargets"` +} + +/***** Validator TEST CASES *****/ +type ValidateProofTestCase struct { + Proof types.SPVProof `json:"proof"` + BestKnown types.BitcoinHeader `json:"bestKnown"` + LCA types.Hash256Digest `json:"lca"` + Error int `json:"error"` +} + +type CheckRequestsFilledTestCase struct { + FilledRequests types.FilledRequests `json:"filledRequest"` + Error int `json:"error"` +} + +type ValidatorTestCases struct { + ValidateProof []ValidateProofTestCase `json:"validateProof"` + CheckRequestsFilled []CheckRequestsFilledTestCase `json:"checkRequestsFilled"` +} + +/***** Request TEST CASES *****/ +type CheckRequestTestCase struct { + InputIdx uint32 `json:"inputIndex"` + OutputIdx uint32 `json:"outputIndex"` + Vin types.HexBytes `json:"vin"` + Vout types.HexBytes `json:"vout"` + RequestID types.RequestID `json:"requestID"` + Error int `json:"error"` +} + +type RequestTestCases struct { + EmptyRequest types.ProofRequest `json:"emptyRequest"` + CheckRequests []CheckRequestTestCase `json:"checkRequests"` +} + +/***** KEEPER TEST CASES *****/ +type KeeperTestCases struct { + LinkTestCases LinkTestCases `json:"link"` + HeaderTestCases HeaderTestCases `json:"header"` + ChainTestCases ChainTestCases `json:"chain"` + ValidatorTestCases ValidatorTestCases `json:"validator"` + RequestTestCases RequestTestCases `json:"requests"` +} + +type KeeperSuite struct { + suite.Suite + Fixtures KeeperTestCases + Context sdk.Context + Keeper Keeper +} + +func (c Case) Name() string { + return c.Comment +} + +func logIfError(err error) { + if err != nil { + log.Fatal(err) + } +} + +func logIfTestCaseError(tc NamedCase, err sdk.Error) { + if err != nil { + log.Printf("Unexpected Error\nIn case: %s\n%s\n", tc.Name(), err.Error()) + } +} + +func (s *KeeperSuite) InitTestContext(mainnet, isCheckTx bool) { + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) + + relayKey := sdk.NewKVStoreKey(types.StoreKey) + + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(relayKey, sdk.StoreTypeIAVL, db) + err := ms.LoadLatestVersion() + if err != nil { + panic(err.Error()) + } + + cdc := codec.New() + + ctx := sdk.NewContext(ms, abci.Header{ChainID: "relayTestChain"}, isCheckTx, tmlog.NewNopLogger()) + keeper := NewKeeper(relayKey, cdc, mainnet, types.NewNullHandler()) + + s.Context = ctx + s.Keeper = keeper +} + +func (s *KeeperSuite) SetupTest() { + s.InitTestContext(true, false) +} + +// Runs the whole test suite +func TestKeeper(t *testing.T) { + jsonFile, err := os.Open("../../../../testVectors.json") + logIfError(err) + defer jsonFile.Close() + + byteValue, err := ioutil.ReadAll(jsonFile) + logIfError(err) + + var fixtures KeeperTestCases + err = json.Unmarshal([]byte(byteValue), &fixtures) + logIfError(err) + + keeperSuite := new(KeeperSuite) + keeperSuite.Fixtures = fixtures + + suite.Run(t, keeperSuite) +} + +func (s *KeeperSuite) SDKNil(e sdk.Error) { + var msg string + if e != nil { + msg = e.Error() + } + s.Nil(e, msg) +} + +func (s *KeeperSuite) EqualError(e sdk.Error, code int) { + var msg string + if e.Code() != sdk.CodeType(code) { + msg = fmt.Sprintf("%sExpected: %d\n", e.Error(), code) + } + s.Equal(sdk.CodeType(code), e.Code(), msg) +} + +func (s *KeeperSuite) TestGetPrefixStore() { + prefStore := s.Keeper.getPrefixStore(s.Context, "toast-") + store := s.Context.KVStore(s.Keeper.storeKey) + + expected := []byte{0xff} + + prefStore.Set([]byte("1"), expected) + actual := store.Get([]byte("toast-1")) + + s.Equal(expected, actual) +} + +func (s *KeeperSuite) TestSetGenesisState() { + genesis := s.Fixtures.HeaderTestCases.ValidateDiffChange[0].Anchor + epochStart := s.Fixtures.HeaderTestCases.ValidateDiffChange[0].PrevEpochStart + err := s.Keeper.SetGenesisState(s.Context, genesis, epochStart) + s.SDKNil(err) + + gen, err := s.Keeper.GetRelayGenesis(s.Context) + s.SDKNil(err) + s.Equal(genesis.Hash, gen) + + lca, err := s.Keeper.GetLastReorgLCA(s.Context) + s.SDKNil(err) + s.Equal(genesis.Hash, lca) + + best, err := s.Keeper.GetBestKnownDigest(s.Context) + s.SDKNil(err) + s.Equal(genesis.Hash, best) + + diff := s.Keeper.getCurrentEpochDifficulty(s.Context) + s.Equal(btcspv.ExtractDifficulty(genesis.Raw), diff) + + err = s.Keeper.SetGenesisState(s.Context, genesis, epochStart) + s.Equal(types.AlreadyInit, err.Code()) +} diff --git a/golang/x/relay/keeper/links.go b/golang/x/relay/keeper/links.go new file mode 100644 index 00000000..e788c92b --- /dev/null +++ b/golang/x/relay/keeper/links.go @@ -0,0 +1,63 @@ +package keeper + +import ( + btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" + "github.com/summa-tx/relays/golang/x/relay/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) getLinkStore(ctx sdk.Context) sdk.KVStore { + return k.getPrefixStore(ctx, types.LinkStorePrefix) +} + +func (k Keeper) hasLink(ctx sdk.Context, digestLE types.Hash256Digest) bool { + store := k.getLinkStore(ctx) + return store.Has(digestLE[:]) +} + +func (k Keeper) setLink(ctx sdk.Context, header types.BitcoinHeader) { + store := k.getLinkStore(ctx) + store.Set(header.Hash[:], header.PrevHash[:]) +} + +func (k Keeper) getLink(ctx sdk.Context, digestLE types.Hash256Digest) types.Hash256Digest { + store := k.getLinkStore(ctx) + buf := store.Get(digestLE[:]) + // Can only fail if data store is corrupt + parentHash, _ := btcspv.NewHash256Digest(buf) + return parentHash +} + +// FindAncestor finds the nth ancestor of some digest +func (k Keeper) FindAncestor(ctx sdk.Context, digestLE types.Hash256Digest, offset uint32) (types.Hash256Digest, sdk.Error) { + current := digestLE + if !k.hasLink(ctx, current) { + return types.Hash256Digest{}, types.ErrUnknownBlock(types.DefaultCodespace, "digest", digestLE) + } + + for i := uint32(0); i < offset; i++ { + current = k.getLink(ctx, current) + if !k.hasLink(ctx, current) { + return types.Hash256Digest{}, types.ErrBadOffset(types.DefaultCodespace, current) + } + } + + return current, nil +} + +// IsAncestor checks if there is a link between an ancestor and header +func (k Keeper) IsAncestor(ctx sdk.Context, digestLE, ancestor types.Hash256Digest, limit uint32) bool { + current := digestLE + + for i := uint32(0); i < limit; i++ { + if !k.hasLink(ctx, current) { + return false + } + current = k.getLink(ctx, current) + if current == ancestor { + return true + } + } + return false +} diff --git a/golang/x/relay/keeper/links_test.go b/golang/x/relay/keeper/links_test.go new file mode 100644 index 00000000..275f0327 --- /dev/null +++ b/golang/x/relay/keeper/links_test.go @@ -0,0 +1,55 @@ +package keeper + +import "github.com/cosmos/cosmos-sdk/types" + +func (s *KeeperSuite) TestGetLink() { + headers := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers + parent := headers[0] + child := headers[1] + + // stores and retrieves link + s.Keeper.setLink(s.Context, child) + hasHeader := s.Keeper.hasLink(s.Context, child.Hash) + s.Equal(true, hasHeader) + getHeader := s.Keeper.getLink(s.Context, child.Hash) + s.Equal(parent.Hash, getHeader) +} + +func (s *KeeperSuite) TestFindAncestor() { + headers := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers + anchor := s.Fixtures.HeaderTestCases.ValidateChain[0].Anchor + tc := s.Fixtures.LinkTestCases.FindAncestor.TestCases + + // errors if link is not found + _, err := s.Keeper.FindAncestor(s.Context, tc[0].Digest, tc[0].Offset) + s.Equal(types.CodeType(tc[0].Error), err.Code()) + + s.Keeper.ingestHeader(s.Context, anchor) + err = s.Keeper.IngestHeaderChain(s.Context, headers) + s.SDKNil(err) + + for i := 1; i < len(tc); i++ { + ancestor, err := s.Keeper.FindAncestor(s.Context, tc[i].Digest, tc[i].Offset) + if tc[i].Error == 0 { + s.SDKNil(err) + s.Equal(tc[i].Output, ancestor) + } else { + s.Equal(types.CodeType(tc[i].Error), err.Code()) + } + } +} + +func (s *KeeperSuite) TestIsAncestor() { + headers := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers + anchor := s.Fixtures.HeaderTestCases.ValidateChain[0].Anchor + tc := s.Fixtures.LinkTestCases.IsAncestor.TestCases + + s.Keeper.ingestHeader(s.Context, anchor) + err := s.Keeper.IngestHeaderChain(s.Context, headers) + s.SDKNil(err) + + for i := range tc { + isAncestor := s.Keeper.IsAncestor(s.Context, tc[i].Digest, tc[i].Ancestor, tc[i].Limit) + s.Equal(tc[i].Output, isAncestor) + } +} diff --git a/golang/x/relay/keeper/querier.go b/golang/x/relay/keeper/querier.go new file mode 100644 index 00000000..cd0036d1 --- /dev/null +++ b/golang/x/relay/keeper/querier.go @@ -0,0 +1,326 @@ +package keeper + +import ( + "fmt" + "strconv" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/summa-tx/relays/golang/x/relay/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +func decodeUint32FromPath(path []string, idx int, defaultLimit uint32) (uint32, sdk.Error) { + if idx+1 > len(path) { + return defaultLimit, nil + } + // parse int from path[idx], return error if necessary + num, convErr := strconv.ParseUint(path[idx], 10, 32) + if convErr != nil { + return defaultLimit, types.ErrExternal(types.DefaultCodespace, convErr) + } + return uint32(num), nil +} + +// NewQuerier makes a query routing function +func NewQuerier(keeper Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + switch path[0] { + case types.QueryIsAncestor: + return queryIsAncestor(ctx, req, keeper) + case types.QueryGetRelayGenesis: + return queryGetRelayGenesis(ctx, req, keeper) + case types.QueryGetLastReorgLCA: + return queryGetLastReorgLCA(ctx, req, keeper) + case types.QueryGetBestDigest: + return queryGetBestDigest(ctx, req, keeper) + case types.QueryFindAncestor: + return queryFindAncestor(ctx, req, keeper) + case types.QueryHeaviestFromAncestor: + return queryHeaviestFromAncestor(ctx, req, keeper) + case types.QueryIsMostRecentCommonAncestor: + return queryIsMostRecentCommonAncestor(ctx, req, keeper) + case types.QueryGetRequest: + return queryGetRequest(ctx, req, keeper) + case types.QueryCheckRequests: + return queryCheckRequests(ctx, req, keeper) + case types.QueryCheckProof: + return queryCheckProof(ctx, req, keeper) + default: + return nil, sdk.ErrUnknownRequest("unknown relay query endpoint") + } + } +} + +func queryIsAncestor(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params types.QueryParamsIsAncestor + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if unmarshallErr != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", unmarshallErr)) + } + + limit := params.Limit + if limit == 0 { + limit = types.DefaultLookupLimit + } + + // This calls the keeper with the parsed arguments, and gets an answer + result := keeper.IsAncestor(ctx, params.DigestLE, params.ProspectiveAncestor, limit) + + // Now we format the answer as a response + response := types.QueryResIsAncestor{ + Params: params, + Res: result, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryGetRelayGenesis(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + // This calls the keeper and gets an answer + result, err := keeper.GetRelayGenesis(ctx) + if err != nil { + return []byte{}, err + } + + // Now we format the answer as a response + response := types.QueryResGetRelayGenesis{ + Res: result, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryGetLastReorgLCA(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + // This calls the keeper and gets an answer + result, err := keeper.GetLastReorgLCA(ctx) + if err != nil { + return []byte{}, err + } + + // Now we format the answer as a response + response := types.QueryResGetLastReorgLCA{ + Res: result, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryGetBestDigest(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + // This calls the keeper and gets an answer + result, err := keeper.GetBestKnownDigest(ctx) + if err != nil { + return []byte{}, err + } + + // Now we format the answer as a response + response := types.QueryResGetBestDigest{ + Res: result, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryFindAncestor(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params types.QueryParamsFindAncestor + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if unmarshallErr != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", unmarshallErr)) + } + + // This calls the keeper with the parsed arguments, and gets an answer + result, err := keeper.FindAncestor(ctx, params.DigestLE, params.Offset) + if err != nil { + return []byte{}, err + } + + // Now we format the answer as a response + response := types.QueryResFindAncestor{ + Params: params, + Res: result, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryHeaviestFromAncestor(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params types.QueryParamsHeaviestFromAncestor + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if unmarshallErr != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", unmarshallErr)) + } + + limit := params.Limit + if limit == 0 { + limit = types.DefaultLookupLimit + } + + // This calls the keeper with the parsed arguments, and gets an answer + result, err := keeper.HeaviestFromAncestor(ctx, params.Ancestor, params.CurrentBest, params.NewBest, limit) + if err != nil { + return []byte{}, err + } + + // Now we format the answer as a response + response := types.QueryResHeaviestFromAncestor{ + Params: params, + Res: result, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryIsMostRecentCommonAncestor(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params types.QueryParamsIsMostRecentCommonAncestor + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if unmarshallErr != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", unmarshallErr)) + } + + limit := params.Limit + if limit == 0 { + limit = types.DefaultLookupLimit + } + + // This calls the keeper with the parsed arguments, and gets an answer + result := keeper.IsMostRecentCommonAncestor(ctx, params.Ancestor, params.Left, params.Right, limit) + + // Now we format the answer as a response + response := types.QueryResIsMostRecentCommonAncestor{ + Params: params, + Res: result, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryGetRequest(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params types.QueryParamsGetRequest + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if unmarshallErr != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", unmarshallErr)) + } + + // This calls the keeper with the parsed arguments, and gets an answer + result, resErr := keeper.getRequest(ctx, params.ID) + if resErr != nil { + return []byte{}, resErr + } + + // Now we format the answer as a response + response := types.QueryResGetRequest{ + Params: params, + Res: result, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryCheckRequests(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params types.QueryParamsCheckRequests + var errMsg string + valid := true + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if unmarshallErr != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", unmarshallErr)) + } + + // This calls the keeper with the parsed arguments, and gets an answer + _, resErr := keeper.checkRequestsFilled(ctx, params.Filled) + if resErr != nil { + valid = false + errMsg = resErr.Error() + } + + // Now we format the answer as a response + response := types.QueryResCheckRequests{ + Params: params, + Valid: valid, + ErrorMessage: errMsg, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} + +func queryCheckProof(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params types.QueryParamsCheckProof + var errMsg string + valid := true + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if unmarshallErr != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", unmarshallErr)) + } + + // This calls the keeper with the parsed arguments, and gets an answer + resErr := keeper.validateProof(ctx, params.Proof) + if resErr != nil { + valid = false + errMsg = resErr.Error() + } + + // Now we format the answer as a response + response := types.QueryResCheckProof{ + Params: params, + Valid: valid, + ErrorMessage: errMsg, + } + + // And we serialize that response as JSON + res, marshalErr := codec.MarshalJSONIndent(keeper.cdc, response) + if marshalErr != nil { + return []byte{}, types.ErrMarshalJSON(types.DefaultCodespace) + } + return res, nil +} diff --git a/golang/x/relay/keeper/querier_test.go b/golang/x/relay/keeper/querier_test.go new file mode 100644 index 00000000..95c1f9da --- /dev/null +++ b/golang/x/relay/keeper/querier_test.go @@ -0,0 +1,464 @@ +package keeper + +import ( + "encoding/json" + + "github.com/summa-tx/relays/golang/x/relay/types" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +func (s *KeeperSuite) TestDecodeUint32FromPath() { + DecodeUintPass := []struct { + Path []string + Idx int + DefaultLimit uint32 + Output uint32 + }{ + { + []string{"", "", "12"}, + 2, + 15, + 12, + }, { + []string{"", ""}, + 2, + 15, + 15, + }, + } + + DecodeUintFail := []struct { + Path []string + Idx int + DefaultLimit uint32 + Err sdk.CodeType + }{ + { + []string{"", "", "aj"}, + 2, + 15, + types.ExternalError, + }, + } + + for i := range DecodeUintPass { + path := DecodeUintPass[i].Path + index := DecodeUintPass[i].Idx + limit := DecodeUintPass[i].DefaultLimit + num, err := decodeUint32FromPath(path, index, limit) + s.SDKNil(err) + s.Equal(num, DecodeUintPass[i].Output) + } + for i := range DecodeUintFail { + path := DecodeUintFail[i].Path + index := DecodeUintFail[i].Idx + limit := DecodeUintFail[i].DefaultLimit + _, err := decodeUint32FromPath(path, index, limit) + s.Equal(DecodeUintFail[i].Err, err.Code()) + } +} + +func (s *KeeperSuite) TestNewQuerier() { + querier := NewQuerier(s.Keeper) + + // Set up neccessary params with a bad path + path := []string{"badpath"} + + req := abci.RequestQuery{ + Path: "custom/relay/badpath", + Data: []byte{}, + } + + // Test that NewQuerier errors when given a bad path + _, err := querier(s.Context, path, req) + s.Equal(sdk.CodeType(6), err.Code()) +} + +func (s *KeeperSuite) TestQueryIsAncestor() { + headers := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers + anchor := s.Fixtures.HeaderTestCases.ValidateChain[0].Anchor + querier := NewQuerier(s.Keeper) + + s.Keeper.ingestHeader(s.Context, anchor) + err := s.Keeper.IngestHeaderChain(s.Context, headers) + s.SDKNil(err) + + params := types.QueryParamsIsAncestor{ + DigestLE: headers[4].Hash, + ProspectiveAncestor: headers[1].Hash, + Limit: 15, + } + marshalledParams, marshalErr := json.Marshal(params) + s.Nil(marshalErr) + + path := []string{"isancestor"} + + req := abci.RequestQuery{ + Path: "custom/relay/isancestor", + Data: marshalledParams, + } + + res, err := querier(s.Context, path, req) + s.SDKNil(err) + + var result types.QueryResIsAncestor + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(true, result.Res) + + // If Limit is 0, it will use default limit + params = types.QueryParamsIsAncestor{ + DigestLE: headers[4].Hash, + ProspectiveAncestor: headers[1].Hash, + Limit: 0, + } + marshalledParams, marshalErr = json.Marshal(params) + s.Nil(marshalErr) + req = abci.RequestQuery{ + Path: "custom/relay/isancestor", + Data: marshalledParams, + } + + res, err = querier(s.Context, path, req) + s.SDKNil(err) + + unmarshallErr = types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(true, result.Res) + + // Test unmarshall error + req = abci.RequestQuery{ + Path: "custom/relay/isancestor", + Data: []byte{1, 1, 1, 1}, + } + + _, err = querier(s.Context, path, req) + s.Equal(sdk.CodeType(1), err.Code()) +} + +func (s *KeeperSuite) TestQueryGetRelayGenesis() { + genesis := s.Fixtures.HeaderTestCases.ValidateDiffChange[0].Anchor + epochStart := s.Fixtures.HeaderTestCases.ValidateDiffChange[0].PrevEpochStart + // Make a new querier + querier := NewQuerier(s.Keeper) + + path := []string{"getrelaygenesis"} + + req := abci.RequestQuery{ + Path: "custom/relay/getrelaygenesis", + Data: []byte{}, + } + + // Test that GetRelayGenesis errors if RelayGenesis is not found + _, err := querier(s.Context, path, req) + s.Equal(sdk.CodeType(types.BadHash256Digest), err.Code()) + + // Set Genesis state + err = s.Keeper.SetGenesisState(s.Context, genesis, epochStart) + s.SDKNil(err) + + // Use querier handler to get RelayGenesis + res, err := querier(s.Context, path, req) + s.SDKNil(err) + + // Unmarshall the result and test + var result types.QueryResGetRelayGenesis + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(genesis.Hash, result.Res) +} + +func (s *KeeperSuite) TestQueryGetLastReorgLCA() { + genesis := s.Fixtures.HeaderTestCases.ValidateDiffChange[0].Anchor + epochStart := s.Fixtures.HeaderTestCases.ValidateDiffChange[0].PrevEpochStart + querier := NewQuerier(s.Keeper) + + path := []string{"getlastreorglca"} + + req := abci.RequestQuery{ + Path: "custom/relay/getlastreorglca", + Data: []byte{}, + } + + // Test that it errors if it doesn't find LastReorgLCA + _, err := querier(s.Context, path, req) + s.Equal(sdk.CodeType(types.BadHash256Digest), err.Code()) + + setStateErr := s.Keeper.SetGenesisState(s.Context, genesis, epochStart) + s.SDKNil(setStateErr) + + res, getLCAErr := querier(s.Context, path, req) + s.SDKNil(getLCAErr) + + var result types.QueryResGetLastReorgLCA + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(genesis.Hash, result.Res) +} + +func (s *KeeperSuite) TestQueryFindAncestor() { + headers := s.Fixtures.HeaderTestCases.ValidateChain[0].Headers + anchor := s.Fixtures.HeaderTestCases.ValidateChain[0].Anchor + querier := NewQuerier(s.Keeper) + + params := types.QueryParamsFindAncestor{ + DigestLE: headers[4].Hash, + Offset: 2, + } + marshalledParams, marshalErr := json.Marshal(params) + s.Nil(marshalErr) + + path := []string{"findancestor"} + req := abci.RequestQuery{ + Path: "custom/relay/findancestor", + Data: marshalledParams, + } + + // Test that it errors if ancestor is not found + _, findAncestorErr := querier(s.Context, path, req) + s.Equal(sdk.CodeType(types.UnknownBlock), findAncestorErr.Code()) + + // initialize data + s.Keeper.ingestHeader(s.Context, anchor) + ingestErr := s.Keeper.IngestHeaderChain(s.Context, headers) + s.SDKNil(ingestErr) + + // test that it retrieves the correct ancestor + res, err := querier(s.Context, path, req) + s.SDKNil(err) + + var result types.QueryResFindAncestor + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(res, &result) + // s.SDKNil(unmarshallErr) + s.Nil(unmarshallErr) + s.Equal(headers[2].Hash, result.Res) + + // Test unmarshall error + req = abci.RequestQuery{ + Path: "custom/relay/findancestor", + Data: []byte{1, 1, 1, 1}, + } + + _, err = querier(s.Context, path, req) + s.Equal(sdk.CodeType(1), err.Code()) +} + +func (s *KeeperSuite) TestQueryHeaviestFromAncestor() { + tv := s.Fixtures.ChainTestCases.HeaviestFromAncestor + headers := tv.Headers[0:8] + headersWithMain := tv.Headers[0:9] + querier := NewQuerier(s.Keeper) + + var headersWithOrphan []types.BitcoinHeader + headersWithOrphan = append(headersWithOrphan, headers...) + headersWithOrphan = append(headersWithOrphan, tv.Orphan) + + s.Keeper.ingestHeader(s.Context, tv.Genesis) + err := s.Keeper.IngestHeaderChain(s.Context, headersWithMain) + s.SDKNil(err) + err = s.Keeper.IngestHeaderChain(s.Context, headersWithOrphan) + s.SDKNil(err) + + params := types.QueryParamsHeaviestFromAncestor{ + Ancestor: headers[3].Hash, + CurrentBest: headers[5].Hash, + NewBest: headers[4].Hash, + Limit: 20, + } + marshalledParams, marshalErr := json.Marshal(params) + s.Nil(marshalErr) + + path := []string{"heaviestfromancestor"} + + req := abci.RequestQuery{ + Path: "custom/relay/heaviestfromancestor", + Data: marshalledParams, + } + + res, err := querier(s.Context, path, req) + s.SDKNil(err) + + var result types.QueryResHeaviestFromAncestor + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(headers[5].Hash, result.Res) + + // Test that it errors if HeaviestFromAncestorErrors + params = types.QueryParamsHeaviestFromAncestor{ + Ancestor: tv.Headers[10].Hash, + CurrentBest: headers[3].Hash, + NewBest: headers[4].Hash, + Limit: 20, + } + marshalledParams, marshalErr = json.Marshal(params) + s.Nil(marshalErr) + + req = abci.RequestQuery{ + Path: "custom/relay/heaviestfromancestor", + Data: marshalledParams, + } + + _, err = querier(s.Context, path, req) + s.Equal(sdk.CodeType(types.UnknownBlock), err.Code()) + + // Test that default limit is used if limit is set to zero + params = types.QueryParamsHeaviestFromAncestor{ + Ancestor: headers[3].Hash, + CurrentBest: headers[5].Hash, + NewBest: headers[4].Hash, + Limit: 0, + } + marshalledParams, marshalErr = json.Marshal(params) + s.Nil(marshalErr) + + req = abci.RequestQuery{ + Path: "custom/relay/heaviestfromancestor", + Data: marshalledParams, + } + + res, err = querier(s.Context, path, req) + s.SDKNil(err) + + unmarshallErr = types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(headers[5].Hash, result.Res) + + // Test unmarshall error + req = abci.RequestQuery{ + Path: "custom/relay/heaviestfromancestor", + Data: []byte{1, 1, 1, 1}, + } + + _, err = querier(s.Context, path, req) + s.Equal(sdk.CodeType(1), err.Code()) +} + +func (s *KeeperSuite) TestQueryIsMostRecentCommonAncestor() { + tv := s.Fixtures.ChainTestCases.IsMostRecentCA + pre := tv.PreRetargetChain + post := tv.PostRetargetChain + querier := NewQuerier(s.Keeper) + + var postWithOrphan []types.BitcoinHeader + postWithOrphan = append(postWithOrphan, post[:len(post)-2]...) + postWithOrphan = append(postWithOrphan, tv.Orphan) + + err := s.Keeper.SetGenesisState(s.Context, tv.Genesis, tv.OldPeriodStart) + s.SDKNil(err) + + err = s.Keeper.IngestHeaderChain(s.Context, pre) + s.SDKNil(err) + err = s.Keeper.IngestDifficultyChange(s.Context, tv.OldPeriodStart.Hash, post) + s.SDKNil(err) + err = s.Keeper.IngestDifficultyChange(s.Context, tv.OldPeriodStart.Hash, postWithOrphan) + s.SDKNil(err) + + params := types.QueryParamsIsMostRecentCommonAncestor{ + Ancestor: post[2].Hash, + Left: post[3].Hash, + Right: post[2].Hash, + Limit: 5, + } + marshalledParams, marshalErr := json.Marshal(params) + s.Nil(marshalErr) + + path := []string{"ismostrecentcommonancestor"} + + req := abci.RequestQuery{ + Path: "custom/relay/ismostrecentcommonancestor", + Data: marshalledParams, + } + + res, err := querier(s.Context, path, req) + s.SDKNil(err) + + var result types.QueryResIsMostRecentCommonAncestor + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(true, result.Res) + + // Test that it looks up the default limit if limit is set to zero + params = types.QueryParamsIsMostRecentCommonAncestor{ + Ancestor: post[2].Hash, + Left: post[3].Hash, + Right: post[2].Hash, + Limit: 0, + } + marshalledParams, marshalErr = json.Marshal(params) + s.Nil(marshalErr) + + req = abci.RequestQuery{ + Path: "custom/relay/ismostrecentcommonancestor", + Data: marshalledParams, + } + + res, err = querier(s.Context, path, req) + s.SDKNil(err) + + unmarshallErr = types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(true, result.Res) + + // Test unmarshall error + req = abci.RequestQuery{ + Path: "custom/relay/ismostrecentcommonancestor", + Data: []byte{1, 1, 1, 1}, + } + + _, err = querier(s.Context, path, req) + s.Equal(sdk.CodeType(1), err.Code()) +} + +func (s *KeeperSuite) TestQueryGetRequest() { + querier := NewQuerier(s.Keeper) + + path := []string{"getrequest"} + + // bad req + req := abci.RequestQuery{ + Path: "custom/relay/getrequest", + Data: []byte{0}, + } + + // Errors if it cannot unmarshal req data + _, err := querier(s.Context, path, req) + s.Equal(sdk.CodeType(1), err.Code()) + + // marshal params and set req + params := types.QueryParamsGetRequest{ + ID: types.RequestID{}, + } + marshalledParams, marshalErr := json.Marshal(params) + s.Nil(marshalErr) + + req = abci.RequestQuery{ + Path: "custom/relay/getrequest", + Data: marshalledParams, + } + + // Errors if request is not found + _, err = querier(s.Context, path, req) + s.Equal(sdk.CodeType(types.UnknownRequest), err.Code()) + + // Set Request + err = s.Keeper.setRequest(s.Context, []byte{0}, []byte{0}, 0, 0, types.Local, nil) + s.SDKNil(err) + + // Use querier handler to get request + res, err := querier(s.Context, path, req) + s.SDKNil(err) + + // Unmarshall the result and test + var result types.QueryResGetRequest + + unmarshallErr := types.ModuleCdc.UnmarshalJSON(res, &result) + s.Nil(unmarshallErr) + s.Equal(s.Fixtures.RequestTestCases.EmptyRequest, result.Res) +} diff --git a/golang/x/relay/keeper/request_test.go b/golang/x/relay/keeper/request_test.go new file mode 100644 index 00000000..b6b49533 --- /dev/null +++ b/golang/x/relay/keeper/request_test.go @@ -0,0 +1,190 @@ +package keeper + +import ( + "bytes" + + "github.com/summa-tx/relays/golang/x/relay/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/summa-tx/bitcoin-spv/golang/btcspv" +) + +func (s *KeeperSuite) TestEmitProofRequest() { + s.Keeper.emitProofRequest(s.Context, []byte{0}, []byte{0}, 0, types.RequestID{}, types.Local) + + events := s.Context.EventManager().Events() + e := events[0] + s.Equal("proof_request", e.Type) +} + +// tests getNextID and incrementID +func (s *KeeperSuite) TestIncrementID() { + id, err := s.Keeper.getNextID(s.Context) + s.SDKNil(err) + s.Equal(types.RequestID{}, id) + + err = s.Keeper.incrementID(s.Context) + s.SDKNil(err) + + id, err = s.Keeper.getNextID(s.Context) + s.SDKNil(err) + s.Equal(types.RequestID{0, 0, 0, 0, 0, 0, 0, 1}, id) + + // errors if it cannot get next ID + store := s.Keeper.getRequestStore(s.Context) + idTag := []byte(types.RequestIDTag) + store.Set(idTag, bytes.Repeat([]byte{9}, 9)) + + err = s.Keeper.incrementID(s.Context) + s.Equal(sdk.CodeType(107), err.Code()) +} + +func (s *KeeperSuite) TestHasRequest() { + hasRequest := s.Keeper.hasRequest(s.Context, types.RequestID{}) + s.Equal(false, hasRequest) + requestErr := s.Keeper.setRequest(s.Context, []byte{0}, []byte{0}, 0, 4, types.Local, nil) + s.Nil(requestErr) + hasRequest = s.Keeper.hasRequest(s.Context, types.RequestID{}) + s.Equal(true, hasRequest) +} + +func (s *KeeperSuite) TestSetRequest() { + store := s.Keeper.getRequestStore(s.Context) + idTag := []byte(types.RequestIDTag) + store.Set(idTag, bytes.Repeat([]byte{9}, 9)) + + err := s.Keeper.setRequest(s.Context, []byte{0}, []byte{0}, 0, 0, types.Local, nil) + s.Equal(sdk.CodeType(107), err.Code()) +} + +func (s *KeeperSuite) TestSetRequestState() { + // errors if request is not found + activeErr := s.Keeper.setRequestState(s.Context, types.RequestID{}, false) + s.Equal(sdk.CodeType(601), activeErr.Code()) + + // set request + requestErr := s.Keeper.setRequest(s.Context, []byte{1}, []byte{1}, 0, 0, types.Local, nil) + s.Nil(requestErr) + // change active state to false + activeErr = s.Keeper.setRequestState(s.Context, types.RequestID{}, false) + s.Nil(activeErr) + + deactivatedRequest, deactivatedRequestErr := s.Keeper.getRequest(s.Context, types.RequestID{}) + s.Nil(deactivatedRequestErr) + s.Equal(false, deactivatedRequest.ActiveState) +} + +func (s *KeeperSuite) TestGetRequest() { + requestRes := s.Fixtures.RequestTestCases.EmptyRequest + request, err := s.Keeper.getRequest(s.Context, types.RequestID{}) + s.Equal(sdk.CodeType(601), err.Code()) + s.Equal(types.ProofRequest{}, request) + + requestErr := s.Keeper.setRequest(s.Context, []byte{0}, []byte{0}, 0, 0, types.Local, nil) + s.Nil(requestErr) + + request, err = s.Keeper.getRequest(s.Context, types.RequestID{}) + s.Nil(err) + s.Equal(requestRes, request) +} + +func (s *KeeperSuite) TestCheckRequests() { + tc := s.Fixtures.RequestTestCases.CheckRequests + v := tc[0] + + // Errors if request is not found + err := s.Keeper.checkRequests( + s.Context, + v.InputIdx, + v.OutputIdx, + v.Vin, + v.Vout, + v.RequestID) + s.Equal(sdk.CodeType(601), err.Code()) + + // set request + requestErr := s.Keeper.setRequest(s.Context, []byte{1}, []byte{1}, 0, 0, types.Local, nil) + s.Nil(requestErr) + // change active state to false + activeErr := s.Keeper.setRequestState(s.Context, types.RequestID{}, false) + s.Nil(activeErr) + // errors if request is not active + err = s.Keeper.checkRequests( + s.Context, + v.InputIdx, + v.OutputIdx, + v.Vin, + v.Vout, + v.RequestID) + s.Equal(sdk.CodeType(606), err.Code()) + + // change active state to false + activeErr = s.Keeper.setRequestState(s.Context, types.RequestID{}, true) + s.Nil(activeErr) + // errors if request pays is not equal to output + err = s.Keeper.checkRequests( + s.Context, + v.InputIdx, + v.OutputIdx, + v.Vin, + v.Vout, + v.RequestID) + s.Equal(sdk.CodeType(607), err.Code()) + + // Errors if output value is less than pays value + out, outErr := btcspv.ExtractOutputAtIndex(v.Vout, uint(v.OutputIdx)) + s.Nil(outErr) + // out[8:] extracts the output script which we use to set the request + requestErr = s.Keeper.setRequest(s.Context, []byte{0}, out[8:], 1000, 0, types.Local, nil) + s.SDKNil(requestErr) + err = s.Keeper.checkRequests( + s.Context, + v.InputIdx, + v.OutputIdx, + v.Vin, + v.Vout, + types.RequestID{0, 0, 0, 0, 0, 0, 0, 1}) + s.Equal(sdk.CodeType(608), err.Code()) + + // Errors if input value does not equal spends value + requestErr = s.Keeper.setRequest(s.Context, []byte{1}, []byte{}, 0, 255, types.Local, nil) + s.SDKNil(requestErr) + err = s.Keeper.checkRequests( + s.Context, + v.InputIdx, + v.OutputIdx, + v.Vin, + v.Vout, + types.RequestID{0, 0, 0, 0, 0, 0, 0, 2}) + s.Equal(sdk.CodeType(609), err.Code()) + + // Success + in, extractErr := btcspv.ExtractInputAtIndex(v.Vin, uint(v.InputIdx)) + s.Nil(extractErr) + outpoint := btcspv.ExtractOutpoint(in) + // out[8:] extracts the output script which we use to set the request + requestErr = s.Keeper.setRequest(s.Context, outpoint, out[8:], 10, 255, types.Local, nil) + s.SDKNil(requestErr) + err = s.Keeper.checkRequests( + s.Context, + v.InputIdx, + v.OutputIdx, + v.Vin, + v.Vout, + types.RequestID{0, 0, 0, 0, 0, 0, 0, 3}) + s.SDKNil(err) + + for i := 1; i < len(tc); i++ { + err := s.Keeper.checkRequests( + s.Context, + tc[i].InputIdx, + tc[i].OutputIdx, + tc[i].Vin, + tc[i].Vout, + tc[i].RequestID) + if tc[i].Error == 0 { + s.SDKNil(err) + } else { + s.Equal(sdk.CodeType(tc[i].Error), err.Code()) + } + } +} diff --git a/golang/x/relay/keeper/requests.go b/golang/x/relay/keeper/requests.go new file mode 100644 index 00000000..3f4b5c64 --- /dev/null +++ b/golang/x/relay/keeper/requests.go @@ -0,0 +1,192 @@ +package keeper + +import ( + "bytes" + "encoding/binary" + "encoding/json" + + btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" + "github.com/summa-tx/relays/golang/x/relay/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) emitProofRequest(ctx sdk.Context, pays, spends []byte, paysValue uint64, id types.RequestID, origin types.Origin) { + ctx.EventManager().EmitEvent(types.NewProofRequestEvent(pays, spends, paysValue, id, origin)) +} + +func (k Keeper) getRequestStore(ctx sdk.Context) sdk.KVStore { + return k.getPrefixStore(ctx, types.RequestStorePrefix) +} + +func (k Keeper) hasRequest(ctx sdk.Context, id types.RequestID) bool { + store := k.getRequestStore(ctx) + return store.Has(id[:]) +} + +func (k Keeper) setRequest(ctx sdk.Context, spends []byte, pays []byte, paysValue uint64, numConfs uint8, origin types.Origin, action types.HexBytes) sdk.Error { + store := k.getRequestStore(ctx) + + var spendsDigest types.Hash256Digest + if len(spends) == 0 { + spendsDigest = types.Hash256Digest{} + } else { + spendsDigest = btcspv.Hash256(spends) + } + + var paysDigest types.Hash256Digest + if len(pays) == 0 { + paysDigest = types.Hash256Digest{} + } else { + paysDigest = btcspv.Hash256(pays) + } + + request := types.ProofRequest{ + Spends: spendsDigest, + Pays: paysDigest, + PaysValue: paysValue, + ActiveState: true, + NumConfs: numConfs, + Origin: origin, + Action: action, + } + + // When a new request comes in, get the id and use it to store request + id, err := k.getNextID(ctx) + if err != nil { + return err + } + + buf, marshalErr := json.Marshal(request) + if marshalErr != nil { + return types.ErrMarshalJSON(types.DefaultCodespace) + } + store.Set(id[:], buf) + + // Increment the ID + incrementErr := k.incrementID(ctx) + if incrementErr != nil { + return incrementErr + } + + // Emit Proof Request event + k.emitProofRequest(ctx, pays, spends, request.PaysValue, id, origin) + return nil +} + +func (k Keeper) setRequestState(ctx sdk.Context, requestID types.RequestID, active bool) sdk.Error { + store := k.getRequestStore(ctx) + request, err := k.getRequest(ctx, requestID) + if err != nil { + return err + } + + request.ActiveState = active + + buf, marshalErr := json.Marshal(request) + if marshalErr != nil { + return types.ErrMarshalJSON(types.DefaultCodespace) + } + store.Set(requestID[:], buf) + return nil +} + +func (k Keeper) getRequest(ctx sdk.Context, id types.RequestID) (types.ProofRequest, sdk.Error) { + store := k.getRequestStore(ctx) + + hasRequest := k.hasRequest(ctx, id) + if !hasRequest { + return types.ProofRequest{}, types.ErrUnknownRequest(types.DefaultCodespace) + } + + buf := store.Get(id[:]) + + var request types.ProofRequest + jsonErr := json.Unmarshal(buf, &request) + if jsonErr != nil { + return types.ProofRequest{}, types.ErrExternal(types.DefaultCodespace, jsonErr) + } + return request, nil +} + +// incrementID increments the id used to store a request, +// ID must be in bytes +func (k Keeper) incrementID(ctx sdk.Context) sdk.Error { + store := k.getRequestStore(ctx) + // get id + id, err := k.getNextID(ctx) + if err != nil { + return err + } + // convert id to uint64 and add 1 + newID := binary.BigEndian.Uint64(id[:]) + 1 + // convert back to bytes and store + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, newID) + store.Set([]byte(types.RequestIDTag), b) + // if no errors, return nil + return nil +} + +// getNextID retrieves the ID. The ID is incremented after storing a request, +// so this returns the next ID to be used. +func (k Keeper) getNextID(ctx sdk.Context) (types.RequestID, sdk.Error) { + store := k.getRequestStore(ctx) + idTag := []byte(types.RequestIDTag) + if !store.Has(idTag) { + store.Set(idTag, bytes.Repeat([]byte{0}, 8)) + } + id := store.Get(idTag) + newID, err := types.NewRequestID(id) + if err != nil { + return types.RequestID{}, err + } + return newID, nil +} + +// checkRequests validates a request +func (k Keeper) checkRequests(ctx sdk.Context, inputIndex, outputIndex uint32, vin []byte, vout []byte, requestID types.RequestID) sdk.Error { + if !btcspv.ValidateVin(vin) { + return types.ErrInvalidVin(types.DefaultCodespace) + } + if !btcspv.ValidateVout(vout) { + return types.ErrInvalidVout(types.DefaultCodespace) + } + + req, reqErr := k.getRequest(ctx, requestID) + if reqErr != nil { + return reqErr + } + if !req.ActiveState { + return types.ErrClosedRequest(types.DefaultCodespace) + } + + hasPays := req.Pays != btcspv.Hash256Digest{} + if hasPays { + // We can ignore this error because we know that ValidateVout passed + out, _ := btcspv.ExtractOutputAtIndex(vout, uint(outputIndex)) + // hash the output script (out[8:]) + outDigest := btcspv.Hash256(out[8:]) + if outDigest != req.Pays { + return types.ErrRequestPays(types.DefaultCodespace, requestID) + } + paysValue := req.PaysValue + if paysValue != 0 && uint64(btcspv.ExtractValue(out)) < paysValue { + return types.ErrRequestValue(types.DefaultCodespace, requestID) + } + } + + hasSpends := req.Spends != btcspv.Hash256Digest{} + if hasSpends { + in, err := btcspv.ExtractInputAtIndex(vin, uint(inputIndex)) + if err != nil { + return types.FromBTCSPVError(types.DefaultCodespace, err) + } + outpoint := btcspv.ExtractOutpoint(in) + inDigest := btcspv.Hash256(outpoint) + if hasSpends && inDigest != req.Spends { + return types.ErrRequestSpends(types.DefaultCodespace, requestID) + } + } + return nil +} diff --git a/golang/x/relay/keeper/validator.go b/golang/x/relay/keeper/validator.go new file mode 100644 index 00000000..75ff5b34 --- /dev/null +++ b/golang/x/relay/keeper/validator.go @@ -0,0 +1,94 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/summa-tx/relays/golang/x/relay/types" +) + +func (k Keeper) emitProofProvided( + ctx sdk.Context, + filled types.FilledRequests, +) { + filledIDs := []types.RequestID{} + for _, f := range filled.Filled { + filledIDs = append(filledIDs, f.ID) + } + ctx.EventManager().EmitEvent(types.NewProofProvidedEvent(filled.Proof.TxID, filledIDs)) +} + +// getConfs returns the number of confirmations of any given header +func (k Keeper) getConfs(ctx sdk.Context, header types.BitcoinHeader) (uint32, sdk.Error) { + bestKnown, err := k.GetBestKnownDigest(ctx) + if err != nil { + return 0, err + } + bestKnownHeader, err := k.GetHeader(ctx, bestKnown) + if err != nil { + return 0, err + } + return bestKnownHeader.Height - header.Height, nil +} + +// validateProof validates an SPV Proof and checks that it is stored correctly +func (k Keeper) validateProof(ctx sdk.Context, proof types.SPVProof) sdk.Error { + // If it is not valid, it will return an error + _, err := proof.Validate() + if err != nil { + return types.FromBTCSPVError(types.DefaultCodespace, err) + } + + lca, lcaErr := k.GetLastReorgLCA(ctx) + if lcaErr != nil { + return lcaErr + } + isAncestor := k.IsAncestor(ctx, proof.ConfirmingHeader.Hash, lca, 240) + if !isAncestor { + return types.ErrNotAncestor(types.DefaultCodespace, proof.ConfirmingHeader.Hash) + } + + return nil +} + +func (k Keeper) checkRequestsFilled(ctx sdk.Context, filledRequests types.FilledRequests) ([]types.ProofRequest, sdk.Error) { + // Validate Proof once + err := k.validateProof(ctx, filledRequests.Proof) + if err != nil { + return nil, err + } + + confs, confsErr := k.getConfs(ctx, filledRequests.Proof.ConfirmingHeader) + if confsErr != nil { + return nil, confsErr + } + + var filled []types.ProofRequest + + for i := range filledRequests.Filled { + // get request + request, getErr := k.getRequest(ctx, filledRequests.Filled[i].ID) + if getErr != nil { + return nil, getErr + } + // check confirmations + if confs < uint32(request.NumConfs) { + return nil, types.ErrNotEnoughConfs(types.DefaultCodespace, filledRequests.Filled[i].ID) + } + + // check request + err := k.checkRequests( + ctx, + filledRequests.Filled[i].InputIndex, + filledRequests.Filled[i].OutputIndex, + filledRequests.Proof.Vin, + filledRequests.Proof.Vout, + filledRequests.Filled[i].ID) + if err != nil { + return nil, err + } + + filled = append(filled, request) + } + + k.emitProofProvided(ctx, filledRequests) + return filled, nil +} diff --git a/golang/x/relay/keeper/validator_test.go b/golang/x/relay/keeper/validator_test.go new file mode 100644 index 00000000..4ab0a435 --- /dev/null +++ b/golang/x/relay/keeper/validator_test.go @@ -0,0 +1,108 @@ +package keeper + +import ( + "github.com/summa-tx/relays/golang/x/relay/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (s *KeeperSuite) TestGetConfs() { + header := s.Fixtures.ValidatorTestCases.ValidateProof[0].Proof.ConfirmingHeader + bestKnown := s.Fixtures.ValidatorTestCases.ValidateProof[0].BestKnown + + // errors if Best Known Digest is not found + confs, err := s.Keeper.getConfs(s.Context, header) + s.Equal(sdk.CodeType(types.BadHash256Digest), err.Code()) + s.Equal(uint32(0), confs) + + // errors if Best Known Digest header is not found + s.Keeper.setBestKnownDigest(s.Context, bestKnown.Hash) + + confs, err = s.Keeper.getConfs(s.Context, header) + s.Equal(sdk.CodeType(types.UnknownBlock), err.Code()) + s.Equal(uint32(0), confs) + + // success + s.Keeper.ingestHeader(s.Context, bestKnown) + + confs, err = s.Keeper.getConfs(s.Context, header) + s.SDKNil(err) + s.Equal(uint32(4), confs) +} + +func (s *KeeperSuite) TestValidateProof() { + proofCases := s.Fixtures.ValidatorTestCases.ValidateProof + proof := proofCases[0].Proof + + // errors if LCA is not found + err := s.Keeper.validateProof(s.Context, proof) + s.Equal(sdk.CodeType(types.BadHash256Digest), err.Code()) + + // errors if link is not found + s.Keeper.setLastReorgLCA(s.Context, proofCases[0].LCA) + + err = s.Keeper.validateProof(s.Context, proof) + s.Equal(sdk.CodeType(types.NotAncestor), err.Code()) + + for i := range proofCases { + // Store lots of stuff + s.Keeper.setLastReorgLCA(s.Context, proofCases[i].LCA) + s.Keeper.ingestHeader(s.Context, proofCases[i].Proof.ConfirmingHeader) + s.Keeper.setLink(s.Context, proofCases[i].Proof.ConfirmingHeader) + + if proofCases[i].Error != 0 { + err := s.Keeper.validateProof(s.Context, proofCases[i].Proof) + s.Equal(sdk.CodeType(proofCases[i].Error), err.Code()) + } else { + err := s.Keeper.validateProof(s.Context, proofCases[i].Proof) + s.Nil(err) + } + } +} + +func (s *KeeperSuite) TestCheckRequestsFilled() { + tc := s.Fixtures.ValidatorTestCases.CheckRequestsFilled + validProof := s.Fixtures.ValidatorTestCases.ValidateProof[0] + + s.Keeper.setLastReorgLCA(s.Context, validProof.LCA) + s.Keeper.ingestHeader(s.Context, validProof.Proof.ConfirmingHeader) + s.Keeper.setLink(s.Context, validProof.Proof.ConfirmingHeader) + s.Keeper.ingestHeader(s.Context, validProof.BestKnown) + requestErr := s.Keeper.setRequest(s.Context, []byte{}, []byte{}, 0, 4, types.Local, nil) + s.Nil(requestErr) + + // errors if getConfs fails + _, err := s.Keeper.checkRequestsFilled(s.Context, tc[0].FilledRequests) + s.Equal(sdk.CodeType(types.BadHash256Digest), err.Code()) + + s.Keeper.setBestKnownDigest(s.Context, validProof.BestKnown.Hash) + + // errors if checkRequest errors + // deactivate request + activeErr := s.Keeper.setRequestState(s.Context, types.RequestID{}, false) + s.SDKNil(activeErr) + + _, err = s.Keeper.checkRequestsFilled(s.Context, tc[0].FilledRequests) + s.Equal(sdk.CodeType(types.ClosedRequest), err.Code()) + + // reactivate request + activeErr = s.Keeper.setRequestState(s.Context, types.RequestID{}, true) + s.SDKNil(activeErr) + + for i := range tc { + _, err := s.Keeper.checkRequestsFilled(s.Context, tc[i].FilledRequests) + if tc[i].Error != 0 { + s.Equal(sdk.CodeType(tc[i].Error), err.Code()) + } else { + s.SDKNil(err) + } + } + + // errors if number of confirmations is less than the number of confirmations on the request + requestErr = s.Keeper.setRequest(s.Context, []byte{0}, []byte{0}, 0, 5, types.Local, nil) + s.Nil(requestErr) + + copiedRequest := tc[0].FilledRequests + copiedRequest.Filled[0].ID = types.RequestID{0, 0, 0, 0, 0, 0, 0, 1} + _, err = s.Keeper.checkRequestsFilled(s.Context, copiedRequest) + s.Equal(sdk.CodeType(types.NotEnoughConfs), err.Code()) +} diff --git a/golang/x/relay/module.go b/golang/x/relay/module.go new file mode 100644 index 00000000..5bbc74b9 --- /dev/null +++ b/golang/x/relay/module.go @@ -0,0 +1,132 @@ +package relay + +import ( + "encoding/json" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/summa-tx/relays/golang/x/relay/client/cli" + "github.com/summa-tx/relays/golang/x/relay/client/rest" + "github.com/summa-tx/relays/golang/x/relay/keeper" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// type check to ensure the interface is properly implemented +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic is app module Basics object +type AppModuleBasic struct{} + +// Name is +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterCodec is +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + RegisterCodec(cdc) +} + +// DefaultGenesis is +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) +} + +// ValidateGenesis validates check of the Genesis +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data GenesisState + err := ModuleCdc.UnmarshalJSON(bz, &data) + if err != nil { + return err + } + // Once json successfully marshalled, passes along to genesis.go + return ValidateGenesis(data) +} + +// RegisterRESTRoutes registers rest routes +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + rest.RegisterRoutes(ctx, rtr, StoreKey) +} + +// GetQueryCmd get the root query command of this module +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetQueryCmd(StoreKey, cdc) +} + +// GetTxCmd get the root tx command of this module +func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetTxCmd(StoreKey, cdc) +} + +// AppModule is the AppModule +type AppModule struct { + AppModuleBasic + keeper Keeper +} + +// NewAppModule creates a new AppModule Object +func NewAppModule(k Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: k, + } +} + +// Name is +func (AppModule) Name() string { + return ModuleName +} + +// RegisterInvariants is +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {} + +// Route is +func (am AppModule) Route() string { + return RouterKey +} + +// NewHandler makes a new handler +func (am AppModule) NewHandler() sdk.Handler { + return keeper.NewHandler(am.keeper) +} + +// QuerierRoute is +func (am AppModule) QuerierRoute() string { + return ModuleName +} + +// NewQuerierHandler is +func (am AppModule) NewQuerierHandler() sdk.Querier { + return NewQuerier(am.keeper) +} + +// BeginBlock is +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock is +func (am AppModule) EndBlock(sdk.Context, abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// InitGenesis is +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState GenesisState + ModuleCdc.MustUnmarshalJSON(data, &genesisState) + return InitGenesis(ctx, am.keeper, genesisState) +} + +// ExportGenesis is +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return ModuleCdc.MustMarshalJSON(gs) +} diff --git a/golang/x/relay/types/codec.go b/golang/x/relay/types/codec.go new file mode 100644 index 00000000..bd3f33dd --- /dev/null +++ b/golang/x/relay/types/codec.go @@ -0,0 +1,21 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// ModuleCdc is the codec for the module +var ModuleCdc = codec.New() + +func init() { + RegisterCodec(ModuleCdc) +} + +// RegisterCodec registers concrete types on the Amino codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgIngestHeaderChain{}, "relay/IngestHeaderChain", nil) + cdc.RegisterConcrete(MsgIngestDifficultyChange{}, "relay/IngestDifficultyChange", nil) + cdc.RegisterConcrete(MsgMarkNewHeaviest{}, "relay/MarkNewHeaviest", nil) + cdc.RegisterConcrete(MsgNewRequest{}, "relay/NewRequest", nil) + cdc.RegisterConcrete(MsgProvideProof{}, "relay/ProvideProof", nil) +} diff --git a/golang/x/relay/types/errors.go b/golang/x/relay/types/errors.go new file mode 100644 index 00000000..2db5d57f --- /dev/null +++ b/golang/x/relay/types/errors.go @@ -0,0 +1,342 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // DefaultCodespace is the default code space + DefaultCodespace sdk.CodespaceType = ModuleName + + // 100-block -- shared errors + + // BadHeaderLength means Header array length not divisible by 80 + BadHeaderLength sdk.CodeType = 101 + // BadHeaderLengthMessage is the corresponding message + BadHeaderLengthMessage = "Header array length must be divisble by 80 but header labeled %s with header %x has length %d" + + // BadHeight occurs when a proposed descendant is below a proposed ancestor + BadHeight sdk.CodeType = 102 + // BadHeightMessage is the corresponding message + BadHeightMessage = "Block labeled %s with digest %x is below the ancestor height" + + // HeightMismatch occurs when blocks do not have have consecutive height increments + HeightMismatch sdk.CodeType = 103 + // HeightMismatchMessage is the corresponding message + HeightMismatchMessage = "Height mismatch between blocks %x and %x" + + // UnknownBlock is the error code for unknown blocks + UnknownBlock sdk.CodeType = 104 + // UnknownBlockMessage is the corresponding message + UnknownBlockMessage = "Unknown block labeled %s with digest %x" + + // BadHash256Digest occurs when a wrong-length hash256 digest is found + BadHash256Digest sdk.CodeType = 105 + // BadHash256DigestMessage is the corresponding message + BadHash256DigestMessage = "Digest %s had wrong length" + + // BadHex occurs when a hex argument couldn't be deserialized + BadHex sdk.CodeType = 106 + // BadHexMessage is the corresponding message + BadHexMessage = "Bad hex string in query or msg: %s" + + // BadHexLen occurs when a hex argument is the wrong length + BadHexLen sdk.CodeType = 107 + // BadHexLenMessage is the corresponding message + BadHexLenMessage = "Expected %d bytes in a RequestID, got %d" + + // BitcoinSPV is the code for errors bubbled up from Bitcoin SPV + BitcoinSPV sdk.CodeType = 108 + // BitcoinSPVMessage is the corresponding message + + // AlreadyInit is the code for a second attempt to init the relay + AlreadyInit sdk.CodeType = 109 + // AlreadyInitMessage is the corresponding message + AlreadyInitMessage = "Relay has already set genesis state" + + // BadOffset occurs when chain has traversed to block with no link + BadOffset sdk.CodeType = 110 + // BadOffsetMessage is the corresponding message + BadOffsetMessage = "Reached bottom of relay chain: block with digest %x has no link" + + // 200-block -- AddHeaders + + // UnexpectedRetarget indicates a retarget was seen during AddHeaders loop + UnexpectedRetarget sdk.CodeType = 201 + // UnexpectedRetargetMessage is the corresponding message + UnexpectedRetargetMessage = "Target changed unexpectedly at block %x" + + // 300-block AddHeadersWithRetarget + + // WrongEnd means the end block is at the wrong height + WrongEnd sdk.CodeType = 301 + // WrongEndMessage is the corresponding message + WrongEndMessage = "Must provide the last header of the closing difficulty period" + + // WrongStart means the start block is at the wrong height + WrongStart sdk.CodeType = 302 + // WrongStartMessage is the corresponding message + WrongStartMessage = "Must provide exactly 1 difficulty period" + + // PeriodMismatch means the start and end block do not have the same difficulty + PeriodMismatch sdk.CodeType = 303 + // PeriodMismatchMessage is the corresponding message + PeriodMismatchMessage = "Period header difficulties do not match" + + // BadRetarget means the provided blocks did not create the expected retarget + BadRetarget sdk.CodeType = 304 + // BadRetargetMessage is the corresponding message + BadRetargetMessage = "Invalid retarget provided" + + // 400-block -- MarkNewBestHeight + + // NotBestKnown means a block should have been the best known, but wasn't + NotBestKnown sdk.CodeType = 403 + // NotBestKnownMessage is the corresponding message + NotBestKnownMessage = "Provided digest %x is not current best known, expecting block with hash %x" + + // NotHeaviestAncestor means a later common ancestor was found + NotHeaviestAncestor sdk.CodeType = 404 + // NotHeaviestAncestorMessage is the corresponding message + NotHeaviestAncestorMessage = "Ancestor %x is not heaviest common ancestor" + + // NotHeavier means the proposed new best is not heavier than the current best + NotHeavier sdk.CodeType = 405 + // NotHeavierMessage is the corresponding message + NotHeavierMessage = "New best received %x does not have more work than previous best %x" + + // 500-block Queries + + // MarshalJSON means there was an error marshalling a query result to json + MarshalJSON sdk.CodeType = 503 + // MarshalJSONMessage is the corresponding message + MarshalJSONMessage = "Could not marshal result to JSON" + + // 600-block Proof Requests + + // UnknownRequest means the request was not found + UnknownRequest sdk.CodeType = 601 + // UnknownRequestMessage is the corresponding message + UnknownRequestMessage = "Request not found" + + // SpendsLength means the spend value is not 36 bytes + SpendsLength sdk.CodeType = 602 + // SpendsLengthMessage is the corresponding message + SpendsLengthMessage = "Spends value is not 36 bytes" + + // PaysLength means the pays value is greater than 50 bytes + PaysLength sdk.CodeType = 603 + // PaysLengthMessage is the corresponding message + PaysLengthMessage = "Pays value is greater than 50 bytes" + + // InvalidVin means the vin is not valid + InvalidVin sdk.CodeType = 604 + // InvalidVinMessage is the corresponding message + InvalidVinMessage = "Vin is not valid" + + // InvalidVout means the vout is not valid + InvalidVout sdk.CodeType = 605 + // InvalidVoutMessage is the corresponding message + InvalidVoutMessage = "Vout is not valid" + + // ClosedRequest means the request is not active + ClosedRequest sdk.CodeType = 606 + // ClosedRequestMessage is the corresponding message + ClosedRequestMessage = "Request is not active" + + // RequestPays means the output does not match the pays request + RequestPays sdk.CodeType = 607 + // RequestPaysMessage is the corresponding message + RequestPaysMessage = "Output does not match pays for requestID %d" + + // RequestValue means the pays value and value of the output does not match + RequestValue sdk.CodeType = 608 + // RequestValueMessage is the corresponding message + RequestValueMessage = "Output value does not match pays value for requestID %d" + + // RequestSpends means the request spends does not match the input + RequestSpends sdk.CodeType = 609 + // RequestSpendsMessage is the corresponding message + RequestSpendsMessage = "Input does not match spends for requestID %d" + + // NotAncestor means the LCA is not an ancestor of the SPV Proof header + NotAncestor sdk.CodeType = 610 + // NotAncestorMessage is the corresponding message + NotAncestorMessage = "LCA %x not ancestor of proof header" + + // NotEnoughConfs means the proof does not have enough confirmations + NotEnoughConfs sdk.CodeType = 611 + // NotEnoughConfsMessage is the corresponding message + NotEnoughConfsMessage = "Not enough confirmations for requestID %d" + + // ActionLength means the pays value is greater than 50 bytes + ActionLength sdk.CodeType = 612 + // ActionLengthMessage is the corresponding message + ActionLengthMessage = "Action value is greater than 500 bytes" + + // 700-block External + + // ExternalError is an error from a dependency + ExternalError sdk.CodeType = 701 +) + +// ErrBadHeaderLength throws an error +func ErrBadHeaderLength(codespace sdk.CodespaceType, label string, digest RawHeader, length int) sdk.Error { + return sdk.NewError(codespace, BadHeaderLength, fmt.Sprint(BadHeaderLengthMessage, label, digest, length)) +} + +// ErrBadHeight throws an error +func ErrBadHeight(codespace sdk.CodespaceType, label string, digest Hash256Digest) sdk.Error { + return sdk.NewError(codespace, BadHeight, fmt.Sprintf(BadHeightMessage, label, digest)) +} + +func ErrHeightMismatch(codespace sdk.CodespaceType, prevDigest, digest Hash256Digest) sdk.Error { + return sdk.NewError(codespace, HeightMismatch, fmt.Sprintf(HeightMismatchMessage, prevDigest, digest)) +} + +// ErrUnknownBlock throws an error +func ErrUnknownBlock(codespace sdk.CodespaceType, label string, digest Hash256Digest) sdk.Error { + return sdk.NewError(codespace, UnknownBlock, fmt.Sprint(UnknownBlockMessage, label, digest)) +} + +// ErrUnexpectedRetarget throws an error +func ErrUnexpectedRetarget(codespace sdk.CodespaceType, rawHeader RawHeader) sdk.Error { + return sdk.NewError(codespace, UnexpectedRetarget, fmt.Sprint(UnexpectedRetargetMessage, rawHeader)) +} + +// ErrWrongEnd throws an error +func ErrWrongEnd(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, WrongEnd, WrongEndMessage) +} + +// ErrWrongStart throws an error +func ErrWrongStart(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, WrongStart, WrongStartMessage) +} + +// ErrPeriodMismatch throws an error +func ErrPeriodMismatch(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, PeriodMismatch, PeriodMismatchMessage) +} + +// ErrBadRetarget throws an error +func ErrBadRetarget(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, BadRetarget, BadRetargetMessage) +} + +// ErrNotBestKnown throws an error +func ErrNotBestKnown(codespace sdk.CodespaceType, invalidBest, expectedBest Hash256Digest) sdk.Error { + return sdk.NewError(codespace, NotBestKnown, fmt.Sprintf(NotBestKnownMessage, invalidBest, expectedBest)) +} + +// ErrNotHeaviestAncestor throws an error +func ErrNotHeaviestAncestor(codespace sdk.CodespaceType, ancestor Hash256Digest) sdk.Error { + return sdk.NewError(codespace, NotHeaviestAncestor, fmt.Sprintf(NotHeaviestAncestorMessage, ancestor)) +} + +// ErrNotHeavier throws an error +func ErrNotHeavier(codespace sdk.CodespaceType, newBest, prevBest Hash256Digest) sdk.Error { + return sdk.NewError(codespace, NotHeavier, fmt.Sprintf(NotHeavierMessage, newBest, prevBest)) +} + +// ErrBadHash256Digest throws an error +func ErrBadHash256Digest(codespace sdk.CodespaceType, invalidDigest string) sdk.Error { + return sdk.NewError(codespace, BadHash256Digest, fmt.Sprintf(BadHash256DigestMessage, invalidDigest)) +} + +// ErrBadHex throws an error +func ErrBadHex(codespace sdk.CodespaceType, invalidHex string) sdk.Error { + return sdk.NewError(codespace, BadHex, fmt.Sprintf(BadHexMessage, invalidHex)) +} + +// ErrBadHexLen throws an error +func ErrBadHexLen(codespace sdk.CodespaceType, expected, actual int) sdk.Error { + return sdk.NewError(codespace, BadHexLen, fmt.Sprintf(BadHexLenMessage, expected, actual)) +} + +// ErrAlreadyInit throws an error +func ErrAlreadyInit(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, AlreadyInit, AlreadyInitMessage) +} + +// ErrBadOffset throws an error +func ErrBadOffset(codespace sdk.CodespaceType, digest Hash256Digest) sdk.Error { + return sdk.NewError(codespace, BadHexLen, fmt.Sprintf(BadOffsetMessage, digest)) +} + +// ErrMarshalJSON throws an error +func ErrMarshalJSON(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, MarshalJSON, MarshalJSONMessage) +} + +// FromBTCSPVError converts a btcutils error into an sdk error +func FromBTCSPVError(codespace sdk.CodespaceType, err error) sdk.Error { + return sdk.NewError(codespace, BitcoinSPV, err.Error()) +} + +// ErrUnknownRequest throws an error +func ErrUnknownRequest(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, UnknownRequest, UnknownRequestMessage) +} + +// ErrSpendsLength throws an error +func ErrSpendsLength(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, SpendsLength, SpendsLengthMessage) +} + +// ErrPaysLength throws an error +func ErrPaysLength(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, PaysLength, PaysLengthMessage) +} + +// ErrActionLength throws an error +func ErrActionLength(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, ActionLength, ActionLengthMessage) +} + +// ErrInvalidVin throws an error +func ErrInvalidVin(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, InvalidVin, InvalidVinMessage) +} + +// ErrInvalidVout throws an error +func ErrInvalidVout(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, InvalidVout, InvalidVoutMessage) +} + +// ErrClosedRequest throws an error +func ErrClosedRequest(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, ClosedRequest, ClosedRequestMessage) +} + +// ErrRequestPays throws an error +func ErrRequestPays(codespace sdk.CodespaceType, requestID RequestID) sdk.Error { + return sdk.NewError(codespace, RequestPays, fmt.Sprintf(RequestPaysMessage, requestID)) +} + +// ErrRequestValue throws an error +func ErrRequestValue(codespace sdk.CodespaceType, requestID RequestID) sdk.Error { + return sdk.NewError(codespace, RequestValue, fmt.Sprintf(RequestValueMessage, requestID)) +} + +// ErrRequestSpends throws an error +func ErrRequestSpends(codespace sdk.CodespaceType, requestID RequestID) sdk.Error { + return sdk.NewError(codespace, RequestSpends, fmt.Sprintf(RequestSpendsMessage, requestID)) +} + +// ErrNotAncestor throws an error +func ErrNotAncestor(codespace sdk.CodespaceType, lca Hash256Digest) sdk.Error { + return sdk.NewError(codespace, NotAncestor, fmt.Sprintf(NotAncestorMessage, lca)) +} + +// ErrNotEnoughConfs throws an error +func ErrNotEnoughConfs(codespace sdk.CodespaceType, requestID RequestID) sdk.Error { + return sdk.NewError(codespace, NotEnoughConfs, fmt.Sprintf(NotEnoughConfsMessage, requestID)) +} + +// ErrExternal converts any external error into an sdk error +func ErrExternal(codespace sdk.CodespaceType, err error) sdk.Error { + return sdk.NewError(codespace, ExternalError, err.Error()) +} diff --git a/golang/x/relay/types/events.go b/golang/x/relay/types/events.go new file mode 100644 index 00000000..3d58a150 --- /dev/null +++ b/golang/x/relay/types/events.go @@ -0,0 +1,74 @@ +package types + +import ( + "encoding/hex" + "encoding/json" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Relay module event types +const ( + EventTypeExtension = "extension" + EventTypeReorg = "reorg" + EventTypeProofRequest = "proof_request" + EventTypeProofProvided = "proof_provided" + + AttributeKeyFirstBlock = "first_block" + AttributeKeyLastBlock = "last_block" + + AttributeKeyPreviousBest = "previous_best" + AttributeKeyNewBest = "new_best" + AttributeKeyLatestCommon = "latest_common_ancestor" + + AttributeKeyRequestID = "request_id" + AttributeKeyPays = "pays" + AttributeKeySpends = "spends" + AttributeKeyPaysValue = "value" + AttributeKeyOrigin = "origin" + + AttributeKeyTXID = "txid" + AttributeKeyFilled = "filled" +) + +// NewReorgEvent instantiates a reorg event +func NewReorgEvent(prev, new, lca Hash256Digest) sdk.Event { + return sdk.NewEvent( + EventTypeReorg, + sdk.NewAttribute(AttributeKeyPreviousBest, "0x"+hex.EncodeToString(prev[:])), + sdk.NewAttribute(AttributeKeyNewBest, "0x"+hex.EncodeToString(new[:])), + sdk.NewAttribute(AttributeKeyLatestCommon, "0x"+hex.EncodeToString(lca[:])), + ) +} + +// NewExtensionEvent instantiates an extension event +func NewExtensionEvent(first, last BitcoinHeader) sdk.Event { + return sdk.NewEvent( + EventTypeExtension, + sdk.NewAttribute(AttributeKeyFirstBlock, "0x"+hex.EncodeToString(first.Hash[:])), + sdk.NewAttribute(AttributeKeyLastBlock, "0x"+hex.EncodeToString(last.Hash[:])), + ) +} + +// NewProofRequestEvent instantiates a proof request event +func NewProofRequestEvent(pays, spends []byte, paysValue uint64, id RequestID, origin Origin) sdk.Event { + return sdk.NewEvent( + EventTypeProofRequest, + sdk.NewAttribute(AttributeKeyRequestID, fmt.Sprintf("%d", id)), + sdk.NewAttribute(AttributeKeyPays, "0x"+hex.EncodeToString(pays[:])), + sdk.NewAttribute(AttributeKeySpends, "0x"+hex.EncodeToString(spends[:])), + sdk.NewAttribute(AttributeKeyPaysValue, fmt.Sprintf("%d", paysValue)), + sdk.NewAttribute(AttributeKeyOrigin, fmt.Sprintf("%d", origin)), + ) +} + +// NewProofProvidedEvent instantiates a proof provided event +func NewProofProvidedEvent(txid Hash256Digest, filled []RequestID) sdk.Event { + filledJSON, _ := json.Marshal(filled) + return sdk.NewEvent( + EventTypeProofProvided, + sdk.NewAttribute(AttributeKeyTXID, "0x"+hex.EncodeToString(txid[:])), + sdk.NewAttribute(AttributeKeyFilled, string(filledJSON)), + ) +} diff --git a/golang/x/relay/types/keys.go b/golang/x/relay/types/keys.go new file mode 100644 index 00000000..c9925b85 --- /dev/null +++ b/golang/x/relay/types/keys.go @@ -0,0 +1,40 @@ +package types + +const ( + // ModuleName is the name of the module + ModuleName = "relay" + + // StoreKey to be used when creating the KVStore + StoreKey = ModuleName + + // LinkStorePrefix to be used when accessing links + LinkStorePrefix = ModuleName + "-links-" + + // HeaderStorePrefix to be used when accessing headers + HeaderStorePrefix = ModuleName + "-headers-" + + // RequestStorePrefix to be used when making requests + RequestStorePrefix = ModuleName + "-requests-" + + // ChainStorePrefix to be used when accessing chain metadata + ChainStorePrefix = ModuleName + "-chain-" + + // RelayGenesisStorage is the storage key for the relay genesis digest + RelayGenesisStorage = "RelayGenesis" + + // BestKnownDigestStorage is the storage key for the best known digest + BestKnownDigestStorage = "BestKnownDigest" + + // LastReorgLCAStorage is the storage key for the last reorg LCA + LastReorgLCAStorage = "LastReorgLCA" + + // CurrentEpochDiffStorage is the storage key for the current epoch difficulty + CurrentEpochDiffStorage = "currentEpochDifficulty" + + // PrevEpochDiffStorage is the storage key for the prev epoch difficulty + PrevEpochDiffStorage = "prevEpochDifficulty" + + // RequestIDTag is the storage key for the next Request ID to be used + // when storing a request + RequestIDTag = "id" +) diff --git a/golang/x/relay/types/msgs.go b/golang/x/relay/types/msgs.go new file mode 100644 index 00000000..3f5df761 --- /dev/null +++ b/golang/x/relay/types/msgs.go @@ -0,0 +1,250 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// RouterKey is a name for the router +const RouterKey = ModuleName // this was defined in your key.go file + +/***** IngestHeaderChain *****/ + +// MsgIngestHeaderChain defines a IngestHeaderChain message +type MsgIngestHeaderChain struct { + Signer sdk.AccAddress `json:"signer"` + Headers []BitcoinHeader `json:"headers"` +} + +// NewMsgIngestHeaderChain instantiates a MsgIngestHeaderChain +func NewMsgIngestHeaderChain(address sdk.AccAddress, headers []BitcoinHeader) MsgIngestHeaderChain { + return MsgIngestHeaderChain{ + address, + headers, + } +} + +// GetSigners gets signers +func (msg MsgIngestHeaderChain) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +// Type returns an identifier +func (msg MsgIngestHeaderChain) Type() string { return "ingest_header_chain" } + +// ValidateBasic runs stateless validation +func (msg MsgIngestHeaderChain) ValidateBasic() sdk.Error { + for i := range msg.Headers { + valid, err := msg.Headers[i].Validate() + if !valid || err != nil { + return FromBTCSPVError(DefaultCodespace, err) + } + } + return nil +} + +// GetSignBytes returns the sighash for the message +func (msg MsgIngestHeaderChain) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// Route returns the route key +func (msg MsgIngestHeaderChain) Route() string { return RouterKey } + +/***** IngestDifficultyChange *****/ + +// MsgIngestDifficultyChange defines a IngestDifficultyChange message +type MsgIngestDifficultyChange struct { + Signer sdk.AccAddress `json:"signer"` + Start Hash256Digest `json:"prevEpochStartLE"` + Headers []BitcoinHeader `json:"headers"` +} + +// NewMsgIngestDifficultyChange instantiates a MsgIngestDifficultyChange +func NewMsgIngestDifficultyChange(address sdk.AccAddress, start Hash256Digest, headers []BitcoinHeader) MsgIngestDifficultyChange { + return MsgIngestDifficultyChange{ + address, + start, + headers, + } +} + +// GetSigners gets signers +func (msg MsgIngestDifficultyChange) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +// Type returns an identifier +func (msg MsgIngestDifficultyChange) Type() string { return "ingest_difficulty_change" } + +// ValidateBasic runs stateless validation +func (msg MsgIngestDifficultyChange) ValidateBasic() sdk.Error { + for i := range msg.Headers { + valid, err := msg.Headers[i].Validate() + if !valid || err != nil { + return FromBTCSPVError(DefaultCodespace, err) + } + } + return nil +} + +// GetSignBytes returns the sighash for the message +func (msg MsgIngestDifficultyChange) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// Route returns the route key +func (msg MsgIngestDifficultyChange) Route() string { return RouterKey } + +/***** MarkNewHeaviest *****/ + +// MsgMarkNewHeaviest defines a MarkNewHeaviest message +type MsgMarkNewHeaviest struct { + Signer sdk.AccAddress `json:"signer"` + Ancestor Hash256Digest `json:"ancestor"` + CurrentBest RawHeader `json:"currentBest"` + NewBest RawHeader `json:"newBest"` + Limit uint32 `json:"limit"` +} + +// NewMsgMarkNewHeaviest instantiates a MsgMarkNewHeaviest +func NewMsgMarkNewHeaviest(address sdk.AccAddress, ancestor Hash256Digest, currentBest RawHeader, newBest RawHeader, limit uint32) MsgMarkNewHeaviest { + if limit == 0 { + limit = DefaultLookupLimit + } + + return MsgMarkNewHeaviest{ + address, + ancestor, + currentBest, + newBest, + limit, + } +} + +// GetSigners gets signers +func (msg MsgMarkNewHeaviest) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +// Type returns an identifier +func (msg MsgMarkNewHeaviest) Type() string { return "mark_new_heaviest" } + +// ValidateBasic runs stateless validation +func (msg MsgMarkNewHeaviest) ValidateBasic() sdk.Error { + if len(msg.CurrentBest) != 80 { + return ErrBadHeaderLength(DefaultCodespace, "currentBest", msg.CurrentBest, len(msg.CurrentBest)) + } + + if len(msg.NewBest) != 80 { + return ErrBadHeaderLength(DefaultCodespace, "newBest", msg.NewBest, len(msg.NewBest)) + } + + return nil +} + +// GetSignBytes returns the sighash for the message +func (msg MsgMarkNewHeaviest) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// Route returns the route key +func (msg MsgMarkNewHeaviest) Route() string { return RouterKey } + +/***** NewRequest *****/ + +// MsgNewRequest defines a NewRequest message +type MsgNewRequest struct { + Signer sdk.AccAddress `json:"signer"` + Spends HexBytes `json:"spends"` + Pays HexBytes `json:"pays"` + PaysValue uint64 `json:"paysValue"` + NumConfs uint8 `json:"numConfs"` + Origin Origin `json:"origin"` + Action HexBytes `json:"action"` +} + +// NewMsgNewRequest instantiates a MsgNewRequest +func NewMsgNewRequest(address sdk.AccAddress, spends, pays []byte, paysValue uint64, numConfs uint8, origin Origin, action HexBytes) MsgNewRequest { + return MsgNewRequest{ + address, + spends, + pays, + paysValue, + numConfs, + origin, + action, + } +} + +// GetSigners gets signers +func (msg MsgNewRequest) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +// Type returns an identifier +func (msg MsgNewRequest) Type() string { return "new_request" } + +// ValidateBasic runs stateless validation +func (msg MsgNewRequest) ValidateBasic() sdk.Error { + // TODO: validate output types + if len(msg.Spends) != 36 && len(msg.Spends) != 0 { + return ErrSpendsLength(DefaultCodespace) + } + if len(msg.Pays) > 50 { + return ErrPaysLength(DefaultCodespace) + } + if len(msg.Action) > 500 { + return ErrActionLength(DefaultCodespace) + } + return nil +} + +// GetSignBytes returns the sighash for the message +func (msg MsgNewRequest) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// Route returns the route key +func (msg MsgNewRequest) Route() string { return RouterKey } + +/***** ProvideProof *****/ + +// MsgProvideProof defines a NewRequest message +type MsgProvideProof struct { + Signer sdk.AccAddress `json:"signer"` + Filled FilledRequests `json:"filled"` +} + +// NewMsgProvideProof instantiates a MsgProvideProof +func NewMsgProvideProof(address sdk.AccAddress, filledRequests FilledRequests) MsgProvideProof { + return MsgProvideProof{ + address, + filledRequests, + } +} + +// GetSigners gets signers +func (msg MsgProvideProof) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +// ValidateBasic runs stateless validation +func (msg MsgProvideProof) ValidateBasic() sdk.Error { + valid, err := msg.Filled.Proof.Validate() + if !valid || err != nil { + return FromBTCSPVError(DefaultCodespace, err) + } + + return nil +} + +// Type returns an identifier +func (msg MsgProvideProof) Type() string { return "provide_proof" } + +// GetSignBytes returns the sighash for the message +func (msg MsgProvideProof) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// Route returns the route key +func (msg MsgProvideProof) Route() string { return RouterKey } diff --git a/golang/x/relay/types/querier.go b/golang/x/relay/types/querier.go new file mode 100644 index 00000000..4b1d6a94 --- /dev/null +++ b/golang/x/relay/types/querier.go @@ -0,0 +1,224 @@ +package types + +import ( + "encoding/hex" + "encoding/json" + "fmt" +) + +const ( + // DefaultLookupLimit is the default limit for lookup requests + DefaultLookupLimit = 18 + + // QueryIsAncestor is a query string tag for IsAncestor + QueryIsAncestor = "isancestor" + + // QueryGetRelayGenesis is a query string tag for GetRelayGenesis + QueryGetRelayGenesis = "getrelaygenesis" + + // QueryGetLastReorgLCA is a query string tag for GetLastReorgLCA + QueryGetLastReorgLCA = "getlastreorglca" + + // QueryGetBestDigest is a query string tag for GetBestDigest + QueryGetBestDigest = "getbestdigest" + + // QueryFindAncestor is a query string tag for FindAncestor + QueryFindAncestor = "findancestor" + + // QueryHeaviestFromAncestor is a query string tag for HeaviestFromAncestor + QueryHeaviestFromAncestor = "heaviestfromancestor" + + // QueryIsMostRecentCommonAncestor is a query string tag for IsMostRecentCommonAncestor + QueryIsMostRecentCommonAncestor = "ismostrecentcommonancestor" + + // QueryGetRequest is a query string tag for getRequest + QueryGetRequest = "getrequest" + + // QueryCheckRequests is a query string tag for checkRequests + QueryCheckRequests = "checkrequests" + + // QueryCheckProof is a query string tag for checkProof + QueryCheckProof = "checkproof" +) + +// QueryParamsIsAncestor represents the parameters for an IsAncestor query +type QueryParamsIsAncestor struct { + DigestLE Hash256Digest `json:"digest"` + ProspectiveAncestor Hash256Digest `json:"prospectiveAncestor"` + Limit uint32 `json:"limit"` +} + +// QueryResIsAncestor is the response to a IsAncestor query +type QueryResIsAncestor struct { + Params QueryParamsIsAncestor `json:"params"` + Res bool `json:"result"` +} + +// String formats a QueryResIsAncestor struct +func (r QueryResIsAncestor) String() string { + dig := "0x" + hex.EncodeToString(r.Params.DigestLE[:]) + digAnc := "0x" + hex.EncodeToString(r.Params.ProspectiveAncestor[:]) + return fmt.Sprintf( + "Digest: %s, Ancestor: %s, Limit: %d, Result: %t", + dig, digAnc, r.Params.Limit, r.Res) +} + +// QueryResGetRelayGenesis is the response struct for queryGetRelayGenesis +type QueryResGetRelayGenesis struct { + Res Hash256Digest `json:"result"` +} + +// String formats a QueryResGetRelayGenesis struct +func (r QueryResGetRelayGenesis) String() string { + digest := "0x" + hex.EncodeToString(r.Res[:]) + return fmt.Sprintf("%s\n", digest) +} + +// QueryResGetLastReorgLCA is the response struct for queryGetLastReorgLCA +type QueryResGetLastReorgLCA struct { + Res Hash256Digest `json:"result"` +} + +// String formats a QueryResGetLastReorgLCA struct +func (r QueryResGetLastReorgLCA) String() string { + digest := "0x" + hex.EncodeToString(r.Res[:]) + return fmt.Sprintf("%s\n", digest) +} + +// QueryResGetBestDigest is the response struct for queryGetBestDigest +type QueryResGetBestDigest struct { + Res Hash256Digest `json:"result"` +} + +// String formats a QueryResGetBestDigest struct +func (r QueryResGetBestDigest) String() string { + digest := "0x" + hex.EncodeToString(r.Res[:]) + return fmt.Sprintf("%s\n", digest) +} + +// QueryParamsFindAncestor represents the parameters for a FindAncestor query +type QueryParamsFindAncestor struct { + DigestLE Hash256Digest `json:"digestLE"` + Offset uint32 `json:"offset"` +} + +// QueryResFindAncestor is the response struct for queryFindAncestor +type QueryResFindAncestor struct { + Params QueryParamsFindAncestor `json:"params"` + Res Hash256Digest `json:"result"` +} + +// String formats a QueryResFindAncestor struct +func (r QueryResFindAncestor) String() string { + dig := "0x" + hex.EncodeToString(r.Params.DigestLE[:]) + offset := r.Params.Offset + res := "0x" + hex.EncodeToString(r.Res[:]) + return fmt.Sprintf( + "Digest LE: %s, Offset: %d, Result: %s", + dig, offset, res) +} + +// QueryParamsHeaviestFromAncestor is the params struct for queryHeaviestFromAncestor +type QueryParamsHeaviestFromAncestor struct { + Ancestor Hash256Digest `json:"ancestor"` + CurrentBest Hash256Digest `json:"currentBest"` + NewBest Hash256Digest `json:"newBest"` + Limit uint32 `json:"limit"` +} + +// QueryResHeaviestFromAncestor is the response struct for queryHeaviestFromAncestor +type QueryResHeaviestFromAncestor struct { + Params QueryParamsHeaviestFromAncestor `json:"params"` + Res Hash256Digest `json:"result"` +} + +// String formats a QueryResHeaviestFromAncestor struct +func (r QueryResHeaviestFromAncestor) String() string { + anc := "0x" + hex.EncodeToString(r.Params.Ancestor[:]) + curBest := "0x" + hex.EncodeToString(r.Params.CurrentBest[:]) + newBest := "0x" + hex.EncodeToString(r.Params.NewBest[:]) + res := "0x" + hex.EncodeToString(r.Res[:]) + return fmt.Sprintf( + "Ancestor: %s, Current Best: %s, New Best: %s, Limit: %d, Result: %s", + anc, curBest, newBest, r.Params.Limit, res) +} + +// QueryParamsIsMostRecentCommonAncestor is the params struct for queryIsMostRecentCommonAncestor +type QueryParamsIsMostRecentCommonAncestor struct { + Ancestor Hash256Digest `json:"ancestor"` + Left Hash256Digest `json:"left"` + Right Hash256Digest `json:"right"` + Limit uint32 `json:"limit"` +} + +// QueryResIsMostRecentCommonAncestor is the response struct for queryIsMostRecentCommonAncestor +type QueryResIsMostRecentCommonAncestor struct { + Params QueryParamsIsMostRecentCommonAncestor `json:"params"` + Res bool `json:"result"` +} + +// String formats a QueryResIsMostRecentCommonAncestor struct +func (r QueryResIsMostRecentCommonAncestor) String() string { + anc := "0x" + hex.EncodeToString(r.Params.Ancestor[:]) + left := "0x" + hex.EncodeToString(r.Params.Left[:]) + right := "0x" + hex.EncodeToString(r.Params.Right[:]) + return fmt.Sprintf( + "Ancestor: %s, Left: %s, Right: %s, Limit: %d, Result: %t", + anc, left, right, r.Params.Limit, r.Res) +} + +// QueryParamsGetRequest is the response struct for queryGetRequest +type QueryParamsGetRequest struct { + ID RequestID `json:"id"` +} + +// QueryResGetRequest is the response struct for queryGetRequest +type QueryResGetRequest struct { + Params QueryParamsGetRequest `json:"params"` + Res ProofRequest `json:"result"` +} + +// String formats a QueryResIsMostRecentCommonAncestor struct +func (r QueryResGetRequest) String() string { + spends := "0x" + hex.EncodeToString(r.Res.Spends[:]) + pays := "0x" + hex.EncodeToString(r.Res.Pays[:]) + return fmt.Sprintf( + "ID: %d, Spends: %s, Pays: %s, Value: %d, Active: %t, Confirmations: %d", + r.Params.ID, spends, pays, r.Res.PaysValue, r.Res.ActiveState, r.Res.NumConfs) +} + +// QueryParamsCheckRequests is the response struct for queryCheckRequests +type QueryParamsCheckRequests struct { + Filled FilledRequests `json:"filledRequests"` +} + +// QueryResCheckRequests is the response struct for queryCheckRequests +type QueryResCheckRequests struct { + Params QueryParamsCheckRequests `json:"params"` + Valid bool `json:"valid"` + ErrorMessage string `json:"errorMessage"` +} + +// String formats a QueryResCheckRequests struct +func (r QueryResCheckRequests) String() string { + json, _ := json.Marshal(r) + return string(json) +} + +// QueryParamsCheckProof is the response struct for queryCheckProof +type QueryParamsCheckProof struct { + Proof SPVProof `json:"proof"` +} + +// QueryResCheckProof is the response struct for queryCheckProof +type QueryResCheckProof struct { + Params QueryParamsCheckProof `json:"params"` + Valid bool `json:"valid"` + ErrorMessage string `json:"errorMessage"` +} + +// String formats a QueryResCheckProof struct +func (r QueryResCheckProof) String() string { + json, _ := json.Marshal(r) + return string(json) +} diff --git a/golang/x/relay/types/requests.go b/golang/x/relay/types/requests.go new file mode 100644 index 00000000..5d625e9d --- /dev/null +++ b/golang/x/relay/types/requests.go @@ -0,0 +1,88 @@ +package types + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/summa-tx/bitcoin-spv/golang/btcspv" +) + +// RequestID is an 8 byte id used to store requests +type RequestID [8]byte + +// ProofRequest is info about a proof request +type ProofRequest struct { + Spends Hash256Digest `json:"spends"` + Pays Hash256Digest `json:"pays"` + PaysValue uint64 `json:"paysValue"` + ActiveState bool `json:"activeState"` + NumConfs uint8 `json:"numConfs"` + Origin Origin `json:"origin"` + Action HexBytes `json:"action"` +} + +// NewRequestID instantiates a RequestID from a byte slice +func NewRequestID(b []byte) (RequestID, sdk.Error) { + if len(b) != 8 { + return RequestID{}, ErrBadHexLen(DefaultCodespace, 8, len(b)) + } + var h RequestID + copied := copy(h[:], b) + if copied != 8 { + return RequestID{}, ErrBadHexLen(DefaultCodespace, 8, copied) + } + return h, nil +} + +// RequestIDFromString converts a hex string or integer string into a RequestID +func RequestIDFromString(s string) (RequestID, error) { + var idBytes []byte + var err error + + if s[:2] == "0x" { + idBytes, err = hex.DecodeString(s[2:]) + if err != nil { + return RequestID{}, ErrBadHex(DefaultCodespace, s) + } + } else { + id, parseErr := strconv.ParseUint(s, 10, 64) + if parseErr != nil { + return RequestID{}, parseErr + } + + // convert to bytes + binary.BigEndian.PutUint64(idBytes, id) + } + + requestID, newIDErr := NewRequestID(idBytes) + if newIDErr != nil { + return RequestID{}, newIDErr + } + return requestID, err +} + +// UnmarshalJSON unmarshalls 8 byte requestID +func (r *RequestID) UnmarshalJSON(b []byte) error { + // Have to trim quotation marks off byte array + buf, err := hex.DecodeString(btcspv.Strip0xPrefix(string(b[1 : len(b)-1]))) + if err != nil { + return err + } + + if len(buf) != 8 { + return fmt.Errorf("Expected 8 bytes, got %d bytes", len(buf)) + } + + copy(r[:], buf) + + return nil +} + +// MarshalJSON marashalls 8 byte RequestID as 0x-prepended hex +func (r RequestID) MarshalJSON() ([]byte, error) { + encoded := "\"0x" + hex.EncodeToString(r[:]) + "\"" + return []byte(encoded), nil +} diff --git a/golang/x/relay/types/types.go b/golang/x/relay/types/types.go new file mode 100644 index 00000000..5904a598 --- /dev/null +++ b/golang/x/relay/types/types.go @@ -0,0 +1,72 @@ +package types + +import ( + "encoding/hex" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/summa-tx/bitcoin-spv/golang/btcspv" +) + +// ProofHandler is an interface to which the keeper dispatches valid proofs +type ProofHandler interface { + HandleValidProof(ctx sdk.Context, filled FilledRequests, requests []ProofRequest) +} + +// Hash256Digest 32-byte double-sha2 digest +type Hash256Digest = btcspv.Hash256Digest + +// Hash160Digest is a 20-byte ripemd160+sha2 hash +type Hash160Digest = btcspv.Hash160Digest + +// RawHeader is an 80-byte raw header +type RawHeader = btcspv.RawHeader + +// HexBytes is a type alias to make JSON hex ser/deser easier +type HexBytes = btcspv.HexBytes + +// BitcoinHeader is a parsed Bitcoin header +type BitcoinHeader = btcspv.BitcoinHeader + +// SPVProof is the base struct for an SPV proof +type SPVProof = btcspv.SPVProof + +// Origin an enum of types denoting requests either from the local chain +// or a remote chain +type Origin int + +// Origin possible types +const ( + Local Origin = 0 + Remote Origin = 1 +) + +// Hash256DigestFromHex converts a hex into a Hash256Digest +func Hash256DigestFromHex(hexStr string) (Hash256Digest, sdk.Error) { + data := hexStr + if data[:2] == "0x" { + data = data[2:] + } + + bytes, decodeErr := hex.DecodeString(data) + if decodeErr != nil { + return Hash256Digest{}, ErrBadHex(DefaultCodespace, hexStr) + } + digest, newDigestErr := btcspv.NewHash256Digest(bytes) + if newDigestErr != nil { + return Hash256Digest{}, FromBTCSPVError(DefaultCodespace, newDigestErr) + } + return digest, nil +} + +// NullHandler does nothing +type NullHandler struct{} + +// HandleValidProof handles a valid proof (by doing nothing) +func (n NullHandler) HandleValidProof(ctx sdk.Context, filled FilledRequests, requests []ProofRequest) { +} + +// NewNullHandler instantiates a new null handler +func NewNullHandler() NullHandler { + return NullHandler{} +} diff --git a/golang/x/relay/types/types_test.go b/golang/x/relay/types/types_test.go new file mode 100644 index 00000000..65c9cd3d --- /dev/null +++ b/golang/x/relay/types/types_test.go @@ -0,0 +1,47 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" +) + +func TestHash256DigestFromHex(t *testing.T) { + Hash256FromHexPass := []struct { + Input string + Output Hash256Digest + }{ + { + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + Hash256Digest{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + Hash256Digest{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + } + + Hash256FromHexFail := []struct { + Input string + Err sdk.CodeType + }{ + { + "jjjjjj", + BadHex, + }, { + "ffffff", + BitcoinSPV, + }, + } + + for i := range Hash256FromHexPass { + digest, err := Hash256DigestFromHex(Hash256FromHexPass[i].Input) + assert.Nil(t, err) + assert.Equal(t, digest, Hash256FromHexPass[i].Output) + } + for i := range Hash256FromHexFail { + _, err := Hash256DigestFromHex(Hash256FromHexFail[i].Input) + assert.Equal(t, Hash256FromHexFail[i].Err, err.Code()) + } +} diff --git a/golang/x/relay/types/validator.go b/golang/x/relay/types/validator.go new file mode 100644 index 00000000..d4cf2e03 --- /dev/null +++ b/golang/x/relay/types/validator.go @@ -0,0 +1,22 @@ +package types + +// FilledRequestInfo contains information about what input and/or output satisfied the request +type FilledRequestInfo struct { + InputIndex uint32 `json:"inputIndex"` + OutputIndex uint32 `json:"outputIndex"` + ID RequestID `json:"id"` +} + +// FilledRequests contains a proof that satisfies one or more requests +type FilledRequests struct { + Proof SPVProof `json:"proof"` + Filled []FilledRequestInfo `json:"requests"` +} + +// NewFilledRequests instantiates a FilledRequests +func NewFilledRequests(proof SPVProof, filled []FilledRequestInfo) FilledRequests { + return FilledRequests{ + proof, + filled, + } +} diff --git a/maintainer/Dockerfile b/maintainer/Dockerfile new file mode 100644 index 00000000..f00ce831 --- /dev/null +++ b/maintainer/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.7 AS build + +WORKDIR /tmp + +RUN pip install pipenv + +COPY Pipfile . +COPY Pipfile.lock . + +RUN pipenv install --system + +COPY maintainer maintainer +COPY setup.py setup.py + +RUN python setup.py install + +ENTRYPOINT ["python", "maintainer/header_forwarder/h.py", "/mnt/relay-maintainer/.env"] diff --git a/Pipfile b/maintainer/Pipfile similarity index 89% rename from Pipfile rename to maintainer/Pipfile index 631f2864..20b8a689 100644 --- a/Pipfile +++ b/maintainer/Pipfile @@ -16,14 +16,14 @@ pytest-cov = "*" atomicwrites = "*" [packages] -riemann-ether = "==6.0.5" +riemann-ether = "==6.0.6" aiohttp = "*" riemann-keys = "*" mypy-extensions = "*" python-dotenv = "*" python-socketio = {extras = ["asyncio_client"],version = "*"} riemann-tx = "==2.1.0" -bitcoin-spv-py = "==2.0.0" +bitcoin-spv-py = "==3.0.1" python-engineio = "==3.10.0" [requires] diff --git a/Pipfile.lock b/maintainer/Pipfile.lock similarity index 97% rename from Pipfile.lock rename to maintainer/Pipfile.lock index 0561b872..9041f2bc 100644 --- a/Pipfile.lock +++ b/maintainer/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "10b48b44e058d0a64ad4d277d1fe5bbbfab26336458c286416739ef10737a140" + "sha256": "e7da5761168c4f0dc6b2b3063e4b9c5515bf3aed2e23f940d057d1ddbf4209c9" }, "pipfile-spec": 6, "requires": { @@ -50,11 +50,11 @@ }, "bitcoin-spv-py": { "hashes": [ - "sha256:1e2240b33e5ea26ce8bc3919495914d44f548e16a2a99aba59ff8ab62ce086d1", - "sha256:5e53e1f1f14c95e42f870e826b727030053cc1ec8059e5419f3ab58be8c84e88" + "sha256:a0c3e7047505f958ae820f094982f7f5fb52323fb5c220820435b0465899205f", + "sha256:eff76d4c6931c12c2eb082dc2c32eeef1f339c50476a24086ca9d67e15c010b2" ], "index": "pypi", - "version": "==2.0.0" + "version": "==3.0.1" }, "cffi": { "hashes": [ @@ -239,11 +239,11 @@ }, "riemann-ether": { "hashes": [ - "sha256:4b40264b3e53c88a66939e18f082845d4ce931860008d69e43939b30ebd7b07d", - "sha256:bccf83a246bc000f41eccfcb4cfd4750196dafc8395275017440e00572ea0f2f" + "sha256:105c4ed41c6dc5f345cd9a7d18a8591e19467893a732ad187d6568d8d1f721eb", + "sha256:4f777cc2be070521e9e47106aa3eba0b6f8cbe19cd1ccc5e7cf0ee04d2398af9" ], "index": "pypi", - "version": "==6.0.5" + "version": "==6.0.6" }, "riemann-keys": { "hashes": [ @@ -565,10 +565,10 @@ }, "pyparsing": { "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.6" + "version": "==2.4.7" }, "pytest": { "hashes": [ diff --git a/maintainer/README.md b/maintainer/README.md new file mode 100644 index 00000000..eaf0f824 --- /dev/null +++ b/maintainer/README.md @@ -0,0 +1,39 @@ +# Relay Maintainer + +This simple python app maintains the header relay by querying a bcoin node and +pushing headers to an associated geth or infura node. + +Generally it follows the crash-only programming paradigm. Rather than +recovering from errors, we expose them, crash, and emphasize safe resume via a +reboot. + +It is ALPHA-quality software at best. It is not long-term stable. It has poor +handling of bitcoin reorgs, for example. + +## Setup + +install `pipenv` and `pyenv` + +```sh +$ pipenv install --python=$(pyenv which python3.7) +``` + +## Testing +Current testscript runs linting and typechecking only. + +```sh +$ pipenv run test +``` + +## Running the header forwarder + +Make a config `.env` file in `maintainer/config`. + +```sh +$ cp maintainer/config/.sample.env maintainer/config/.my_env_file.env + +# update the env to point to your BCOIN node, and either geth or infura +$ vim maintainer/config/.env + +$ pipenv run python maintainer/header_forwarder/h.py .my_env_file.env +``` diff --git a/maintainer/docs/manual-setup.adoc b/maintainer/docs/manual-setup.adoc new file mode 100644 index 00000000..49fb146d --- /dev/null +++ b/maintainer/docs/manual-setup.adoc @@ -0,0 +1,186 @@ += Running Relay Maintainer locally + +This instruction describes how to set up Relay Maintainer using local Ethereum +node and Bitcoin Testnet node. + +== Prepare local Ethereum and Bitcoin testnet nodes + +The easiest way to set up a local Ethereum node is running scripts located in +the ```keep-network/local-setup``` repo: + +``` +$ cd local-setup +$ ./initialize-geth.sh +$ ./run-geth.sh +``` + +The Ethereum node will run on ```127.0.0.1:8545``` and have ```1101``` as chain id. +You should also have a Bitcoin Testnet node running (locally or remotely). + +== Deploy smart contracts to the Ethereum node + +``` +$ cd relays/solidity +$ npm install -g truffle +$ truffle migrate --reset --network local_test +``` + +Part of the output is shown below, the contract address field value will be +needed later. + +``` +2_deploy_contracts.js +===================== + +network is local_test +First request ID is 36033525777956864 +Press Ctrl+C to cancel + + + Replacing 'TestnetRelay' + ------------------------ + > transaction hash: 0x5aa4bd13d874bcb282fbef0f2166aeb687d065f7706879d8d4de14750017a9c5 + > Blocks: 2 Seconds: 4 + > contract address: 0xD5d52E85621994Ed5f4edf454ba283b6D99780a9 + > block number: 11 + > block timestamp: 1615295593 + > account: 0x3d373D872B7BA29d92Ed47CAA8605b4DD6Ec84eF + > balance: 1000000000000000000000000000000000000 + > gas used: 3099547 (0x2f4b9b) + > gas price: 20 gwei + > value sent: 0 ETH + > total cost: 0.06199094 ETH + + + > Saving migration to chain. + > Saving artifacts + ------------------------------------- + > Total cost: 0.06199094 ETH + +``` + +== Prepare config file + +You can name the file ```.my_env_file.env``` and put it in ```relay/maintainer/maintainer/config directory```. + +The value ```d6ce6a6ca295bbef9ddee79e583fabdcdd98290bbea2dc03ff8317ccf70d3f87``` +is a private key extracted from UTC keystore file. + +You need to fill details of the Bitcoin Testnet node (password, port, ip) - marked +with ... below and copy contract address from previous step to ```SUMMA_RELAY_CONTRACT```: + +``` +# default: 127.0.0.1 +SUMMA_RELAY_ETHER_HOST=127.0.0.1 + +# default: 8545 +SUMMA_RELAY_ETHER_PORT=8545 + +# no default +# 32-byte hex-encoded privkey +SUMMA_RELAY_OPERATOR_KEY="d6ce6a6ca295bbef9ddee79e583fabdcdd98290bbea2dc03ff8317ccf70d3f87" + +# default: ropsten +SUMMA_RELAY_ETH_NETWORK=local_test + +# default: inherited from ETH_NETWORK. +# to override, leave network blank +SUMMA_RELAY_ETH_CHAIN_ID=1101 + +# default: 127.0.0.1 +SUMMA_RELAY_BCOIN_HOST=... + +# default: "" (empty string) +SUMMA_RELAY_BCOIN_API_KEY=... + +# default: 8332 +SUMMA_RELAY_BCOIN_PORT=... + +# no default +# target relay smart contract address +SUMMA_RELAY_CONTRACT=0xD5d52E85621994Ed5f4edf454ba283b6D99780a9 + +# default: 100 +DEFAULT_GAS_PRICE_GWEI="7" + +# default: 600 +MAX_GAS_PRICE_GWEI="90" +``` + +== Install python 3.7 and run Relay Maintainer + +Relay Maintainer requiers python 3.7 to run. +If your system’s python version is different, you can install python 3.7 via pyenv: + +* Install pyenv (link:https://realpython.com/intro-to-pyenv/#installing-pyenv[useful info]) +* Install python 3.7.10 using pyenv (if you get an error, you may need to install +some libraries like zlib) +Select 3.7.10 as your global version. You can verify this step by checking python +version. Install pipenv. +Note: You can later return to your default python version with ```pyenv global system``` + +``` +$ pyenv install 3.7.10 +$ pyenv versions +$ pyenv global 3.7.10 +$ python --version +$ pip install pipenv +``` + +* Navigate to directory ```relays/maintainer``` (where Pipfile is located) and +install dependencies, install ```setup.py```. +(Note: if you modify Relay Maintainer’s code, you need to repeat the last step). +Run Relay Maintainer and pass full path to the directory where your .env file is +located. + +``` +$ pipenv install --system +$ python setup.py install +$ python maintainer/header_forwarder/h.py /full/path/to/maintainer/config/.my_env_file.env +``` + +Relay Maintainer should be running now. + +== Running with Ropsten Ethereum testnet + +You can set up Relay Maintainer to use Ropsten instead of a local node. +The smart contracts should already be deployed, just provide details needed to +connect to the bitcoin node - marked with ... below: + +``` +# default: 127.0.0.1 +SUMMA_RELAY_ETHER_HOST=https://ropsten.infura.io/v3/414a548bc7434bbfb7a135b694b15aa4 + +# default: 8545 +SUMMA_RELAY_ETHER_PORT=80 + +# no default +# 32-byte hex-encoded privkey +SUMMA_RELAY_OPERATOR_KEY="3158378be31bd3dd9ffec1be06b35c49a8de2d9b4dbc0c82f888e63b37f91533" + +# default: ropsten +SUMMA_RELAY_ETH_NETWORK=ropsten + +# default: 127.0.0.1 +SUMMA_RELAY_BCOIN_HOST=... + +# default: "" (empty string) +SUMMA_RELAY_BCOIN_API_KEY=... + +# default: 8332 +SUMMA_RELAY_BCOIN_PORT=... + +# no default +# infura project ID +SUMMA_RELAY_INFURA_KEY="414a548bc7434bbfb7a135b694b15aa4" + +# no default +# target relay smart contract address +SUMMA_RELAY_CONTRACT=0xcF3a6246879aab9eb7beC0d936743208EA51d0Ed + +# default: 100 +DEFAULT_GAS_PRICE_GWEI="7" + +# default: 600 +MAX_GAS_PRICE_GWEI="90" +``` diff --git a/maintainer/ethereum/shared.py b/maintainer/ethereum/shared.py deleted file mode 100644 index 2abaeba8..00000000 --- a/maintainer/ethereum/shared.py +++ /dev/null @@ -1,180 +0,0 @@ -import asyncio -import logging - -from ether import calldata, ethrpc - -from relay import config - -from ether.ether_types import Receipt -from ether.transactions import UnsignedEthTx -from typing import Any, cast, Dict, Iterator, List, Optional - -logger = logging.getLogger('root.summa_relay.shared_eth') - - -GWEI = 1000000000 -DEFAULT_GAS = 500_000 -DEFAULT_GAS_PRICE = 2 * GWEI - -CONNECTION: ethrpc.BaseRPC -NONCE: Iterator[int] # yields ints, takes no sends - - -def _nonce(i: int) -> Iterator[int]: - '''Infinite generator for nonces''' - index = i - while 1: - yield index - index += 1 - - -async def init() -> None: - '''Set up a connection to the interwebs''' - global CONNECTION - - c = config.get() - network = c['NETWORK'] - project_id = c['PROJECT_ID'] - uri = c['ETHER_URL'] - force_https = project_id != '' - - logger.info(f'contract is {c["CONTRACT"]}') - - CONNECTION = ethrpc.get_client( - network=network, - infura_key=project_id, - uri=uri, - logger=logger.getChild('ethrpc'), - force_https=force_https) - - await CONNECTION.open() - - if c['PRIVKEY'] is None and c['GETH_UNLOCK'] is None: - logger.warn( - 'No ethereum privkey found in env config. Txns will error') - else: - global NONCE - address = cast(str, c['ETH_ADDRESS']) - n = await CONNECTION.get_nonce(address) - NONCE = _nonce(n) - logger.info(f'nonce is {n}') - - -async def close_connection() -> None: - try: - global CONNECTION - await CONNECTION.close() - except NameError: - pass - - -async def sign_and_broadcast( - tx: UnsignedEthTx, - ignore_result: bool = False) -> None: - '''Sign an ethereum transaction and broadcast it to the network''' - c = config.get() - privkey = c['PRIVKEY'] - address = c['ETH_ADDRESS'] - unlock_code = c['GETH_UNLOCK'] - - if privkey is None and unlock_code is None: - raise RuntimeError('Attempted to sign tx without access to key') - - if privkey is None: - logger.debug('signing with ether node') - await CONNECTION._RPC( - 'personal_unlockAccount', - [address, unlock_code]) - tx_id = await CONNECTION.send_transaction(cast(str, address), tx) - else: - logger.debug('signing with local key') - signed = tx.sign(cast(bytes, privkey)) - serialized = signed.serialize_hex() - tx_id = await CONNECTION.broadcast(serialized) - - logger.info(f'dispatched transaction {tx_id}') - if not ignore_result: - asyncio.ensure_future(_track_tx_result(tx_id)) - - -def make_call_tx( - contract: str, - abi: List[Dict[str, Any]], - method: str, - args: List[Any], - nonce: int, - value: int = 0, - gas: int = DEFAULT_GAS, - gas_price: int = DEFAULT_GAS_PRICE) -> UnsignedEthTx: - ''' - Sends tokens to a recipient - Args: - contract (str): address of contract being called - abi (dict): contract ABI - method (str): the name of the method to call - args (list): the arguments to the method call - nonce (int): the account nonce for the txn - value (int): ether in wei - gas_price (int): the price of gas in wei or gwei - Returns: - (UnsignedEthTx): the unsigned tx object - ''' - logger.debug(f'making tx call {method} on {contract} ' - f'with value {value} and {len(args)} args') - - gas_price = _adjust_gas_price(gas_price) - chainId = config.get()['CHAIN_ID'] - - data = calldata.call( - method, - args, - abi) - - txn = UnsignedEthTx( - to=contract, - value=value, - gas=gas, - gasPrice=gas_price, - nonce=nonce, - data=data, - chainId=chainId) - - return txn - - -def _adjust_gas_price(gas_price: int) -> int: - ''' - We accept gas price in GWEI or in WEI. - This adjusts, and ensures we error if it's high. - Args: - gas_price (int): the user-provided gas price - Returns: - (int): the adjusted price - ''' - if gas_price < GWEI: - gas_price = gas_price * GWEI - if gas_price > 1000 * GWEI: - logger.error('rejecting high gas price') - raise ValueError( - 'very high gas price detected: {} gwei'.format(gas_price / GWEI)) - return gas_price - - -async def _track_tx_result(tx_id: str) -> None: - '''Keep track of the result of a transaction by polling every 25 seconds''' - receipt_or_none: Optional[Receipt] = None - - for _ in range(20): - await asyncio.sleep(30) - receipt_or_none = await CONNECTION.get_tx_receipt(tx_id) - if receipt_or_none is not None: - break - - if receipt_or_none is None: - raise RuntimeError(f'No receipt after 10 minutes: {tx_id}') - - receipt = cast(Receipt, receipt_or_none) - logger.info(f'Receipt for {tx_id} status is {receipt["status"]}') - - if receipt['status'] != '0x1': - raise RuntimeError(f'Failed tx: {receipt["transactionHash"]}') diff --git a/maintainer/__init__.py b/maintainer/maintainer/__init__.py similarity index 100% rename from maintainer/__init__.py rename to maintainer/maintainer/__init__.py diff --git a/maintainer/base.py b/maintainer/maintainer/base.py similarity index 99% rename from maintainer/base.py rename to maintainer/maintainer/base.py index c60822ed..043463bf 100644 --- a/maintainer/base.py +++ b/maintainer/maintainer/base.py @@ -6,7 +6,7 @@ from functools import partial from dotenv import load_dotenv -from relay import config +from maintainer import config from typing import Awaitable, Callable from asyncio.events import AbstractEventLoop diff --git a/maintainer/bitcoin/__init__.py b/maintainer/maintainer/bitcoin/__init__.py similarity index 100% rename from maintainer/bitcoin/__init__.py rename to maintainer/maintainer/bitcoin/__init__.py diff --git a/maintainer/bitcoin/bcoin_rpc.py b/maintainer/maintainer/bitcoin/bcoin_rpc.py similarity index 88% rename from maintainer/bitcoin/bcoin_rpc.py rename to maintainer/maintainer/bitcoin/bcoin_rpc.py index d681e4a7..6a94877c 100644 --- a/maintainer/bitcoin/bcoin_rpc.py +++ b/maintainer/maintainer/bitcoin/bcoin_rpc.py @@ -1,9 +1,9 @@ import aiohttp import logging -from relay import config +from maintainer import config -from relay.relay_types import BCoinTx +from maintainer.relay_types import BCoinTx from btcspv.types import RelayHeader from typing import Any, cast, Dict, List, Optional, Tuple, Union S = aiohttp.ClientSession @@ -88,10 +88,21 @@ async def _PUT( return status, result -async def get_header_by_hash( +async def get_header_by_hash_le( hash: Union[str, bytes], session: S = SESSION) -> Optional[RelayHeader]: + hash_hex: str + try: + hash_hex = cast(bytes, hash)[::-1].hex() + except AttributeError: + hash_hex = bytes.fromhex(cast(str, hash))[::-1].hex() + return await get_header_by_hash_be(hash_hex) + +async def get_header_by_hash_be( + hash: Union[str, bytes], + session: S = SESSION) -> Optional[RelayHeader]: + '''Gets a header by it's LE hash''' hash_hex: str try: @@ -124,13 +135,10 @@ async def get_header_by_hash( return RelayHeader( raw=bytes.fromhex(raw)[:80], - hash=digest, - hash_le=digest[::-1], + hash=digest[::-1], height=block_info['height'], - merkle_root=merkle_root, - merkle_root_le=merkle_root[::-1], - prevhash=prevhash, - prevhash_le=prevhash[::-1]) + merkle_root=merkle_root[::-1], + prevhash=prevhash[::-1]) async def _get_header_by_height( @@ -156,7 +164,7 @@ async def get_header_by_height( return None block_info = cast(dict, block_info_or_none) - return await get_header_by_hash(block_info['hash']) + return await get_header_by_hash_be(block_info['hash']) async def get_chain_tips(session: S = SESSION) -> List[str]: diff --git a/maintainer/bitcoin/bsock.py b/maintainer/maintainer/bitcoin/bsock.py similarity index 89% rename from maintainer/bitcoin/bsock.py rename to maintainer/maintainer/bitcoin/bsock.py index c9ab96d8..003bcb98 100644 --- a/maintainer/bitcoin/bsock.py +++ b/maintainer/maintainer/bitcoin/bsock.py @@ -2,7 +2,7 @@ import logging import socketio -from relay import config +from maintainer import config from typing import Dict, List, Tuple, Union @@ -29,7 +29,8 @@ async def close_connection() -> None: @sio.event async def connect() -> None: - await sio.call('auth', config.get()['API_KEY']) + if len(config.get()['API_KEY']) > 0: + await sio.call('auth', config.get()['API_KEY']) logger.info(f'connected and authed') diff --git a/maintainer/config/.gitignore b/maintainer/maintainer/config/.gitignore similarity index 100% rename from maintainer/config/.gitignore rename to maintainer/maintainer/config/.gitignore diff --git a/maintainer/config/.sample.env b/maintainer/maintainer/config/.sample.env similarity index 100% rename from maintainer/config/.sample.env rename to maintainer/maintainer/config/.sample.env diff --git a/maintainer/config/__init__.py b/maintainer/maintainer/config/__init__.py similarity index 98% rename from maintainer/config/__init__.py rename to maintainer/maintainer/config/__init__.py index 7bc939ae..96bd6344 100644 --- a/maintainer/config/__init__.py +++ b/maintainer/maintainer/config/__init__.py @@ -3,7 +3,7 @@ from ether import crypto from typing import cast, Tuple, Optional -from relay.relay_types import RelayConfig +from maintainer.relay_types import RelayConfig CONFIG: RelayConfig diff --git a/maintainer/ethereum/__init__.py b/maintainer/maintainer/ethereum/__init__.py similarity index 100% rename from maintainer/ethereum/__init__.py rename to maintainer/maintainer/ethereum/__init__.py diff --git a/maintainer/ethereum/contract.py b/maintainer/maintainer/ethereum/contract.py similarity index 82% rename from maintainer/ethereum/contract.py rename to maintainer/maintainer/ethereum/contract.py index 0ea4ef4a..54b28371 100644 --- a/maintainer/ethereum/contract.py +++ b/maintainer/maintainer/ethereum/contract.py @@ -1,12 +1,12 @@ import logging from ether import abi, calldata, events -from relay import config -from relay.ethereum import shared -from relay.relay_abi import ABI as relay_ABI +from maintainer import config +from maintainer.ethereum import shared +from maintainer.relay_abi import ABI as relay_ABI + +from typing import cast -EXPIRED = events._make_topic0( - abi.find('SubscriptionExpired', relay_ABI)[0]) CLOSED = events._make_topic0( abi.find('RequestClosed', relay_ABI)[0]) FILLED = events._make_topic0( @@ -31,6 +31,10 @@ async def find_height(digest_le: bytes) -> int: 'latest' # block height parameter ] ) + # if more than 1 ABI slot long, return 0 + if len(res) > 36: + logger.debug(f'findHeight for {digest_le.hex()} is unknown') + return 0 logger.debug(f'findHeight for {digest_le.hex()} is {res}') return int(res, 16) @@ -46,7 +50,10 @@ async def is_ancestor( ancestor: bytes, descendant: bytes, limit: int = 240) -> bool: - '''Determine if ancestor precedes descendant''' + ''' + Determine if ancestor precedes descendant + ancestor and descendant MUST be LE + ''' data = calldata.call( "isAncestor", [ancestor, descendant, limit], @@ -71,6 +78,8 @@ async def get_best_block() -> str: Get the contract's marked best known digest. Counterintuitively, the contract may know of a better digest that hasn't been marked yet + + returns LE digest ''' f = abi.find('getBestKnownDigest', relay_ABI)[0] selector = calldata.make_selector(f) @@ -85,5 +94,4 @@ async def get_best_block() -> str: 'latest' # block height parameter ] ) - digest = bytes.fromhex(res[2:])[::-1].hex() # block-explorer format - return digest + return cast(str, res[2:]) # block-explorer format diff --git a/maintainer/maintainer/ethereum/shared.py b/maintainer/maintainer/ethereum/shared.py new file mode 100644 index 00000000..ee1205fc --- /dev/null +++ b/maintainer/maintainer/ethereum/shared.py @@ -0,0 +1,276 @@ +import asyncio +import logging + +from ether import calldata, ethrpc + +from maintainer import config + +from ether.ether_types import Receipt +from ether.transactions import UnsignedEthTx +from typing import Any, cast, Dict, Iterator, List, Optional + +logger = logging.getLogger('root.summa_relay.shared_eth') + + +GWEI = 1000000000 +DEFAULT_GAS = 500_000 +DEFAULT_GAS_PRICE = 100 * GWEI +MAX_GAS_PRICE = 600 * GWEI + +CONNECTION: ethrpc.BaseRPC +NONCE: Iterator[int] # yields ints, takes no sends +LATEST_PENDING_NONCE = 0 + +def _nonce(i: int) -> Iterator[int]: + '''Infinite generator for nonces''' + index = i + while 1: + yield index + index += 1 + + +async def init() -> None: + '''Set up a connection to the interwebs''' + global CONNECTION + + c = config.get() + network = c['NETWORK'] + project_id = c['PROJECT_ID'] + uri = c['ETHER_URL'] + force_https = project_id != '' + + logger.info(f'contract is {c["CONTRACT"]}') + + CONNECTION = ethrpc.get_client( + network=network, + infura_key=project_id, + uri=uri, + logger=logger.getChild('ethrpc'), + force_https=force_https) + + await CONNECTION.open() + + if c['PRIVKEY'] is None and c['GETH_UNLOCK'] is None: + logger.warn( + 'No ethereum privkey found in env config. Txns will error') + else: + global NONCE + global LATEST_PENDING_NONCE + address = cast(str, c['ETH_ADDRESS']) + # Get the already-mined count. + mined_tx_count = int(await CONNECTION._RPC( + method='eth_getTransactionCount', + params=[address, 'latest']), 16) - 1 + logger.info(f'mined tx count is {mined_tx_count}') + + LATEST_PENDING_NONCE = await CONNECTION.get_nonce(address) - 1 + logger.info(f'latest pending nonce is {LATEST_PENDING_NONCE}') + + # Replace all pending txes by starting the nonce at the mined count. + # Note that we could crash if the next tx we send finds the first + # unconfirmed nonce having already been mined---this is fine, the + # process can be restarted and will read the latest pending and mined + # state at that time. + # + # If all pending nonces are already complete, make sure to start 1 + # ahead. + next_nonce = mined_tx_count + 1 + NONCE = _nonce(next_nonce) + logger.info(f'next nonce is {next_nonce}') + +async def close_connection() -> None: + try: + global CONNECTION + await CONNECTION.close() + except NameError: + pass + + +async def sign_and_broadcast( + tx: UnsignedEthTx, + ignore_result: bool = False, + ticks: int = 0) -> None: + '''Sign an ethereum transaction and broadcast it to the network''' + c = config.get() + privkey = c['PRIVKEY'] + address = c['ETH_ADDRESS'] + unlock_code = c['GETH_UNLOCK'] + + if privkey is None and unlock_code is None: + raise RuntimeError('Attempted to sign tx without access to key') + + logger.info(f'dispatching transaction at nonce {tx.nonce} with gas price {tx.gasPrice}') + try: + if privkey is None: + logger.debug('signing with ether node') + await CONNECTION._RPC( + 'personal_unlockAccount', + [address, unlock_code]) + tx_id = await CONNECTION.send_transaction(cast(str, address), tx) + else: + logger.debug('signing with local key') + signed = tx.sign(cast(bytes, privkey)) + serialized = signed.serialize_hex() + tx_id = await CONNECTION.broadcast(serialized) + except RuntimeError as err: + if type(err.args[0]) is dict and 'known transaction: ' in dict(err.args[0])['message']: + tx_id = dict(err.args[0])[19:] + elif 'transaction underpriced' in err.args[0] or 'already known' in err.args[0]: + logger.warn( + f'Got an error {err} submitting nonce {tx.nonce} at gas price ' + + f'{tx.gasPrice}; boosting gas.' + ) + # We're trying to submit a transaction that's already been + # submitted; start a retry loop so we can climb to a higher + # gas level if needed. + asyncio.ensure_future(_track_tx_result(tx, "")) + return + elif 'nonce too low' in err.args[0] and ticks > 0: + logger.warn( + f'Got an error {err} submitting nonce {tx.nonce} at gas price ' + + f'{tx.gasPrice}; assuming a lower-priced version cleared and ' + + f'continuing normally.' + ) + else: + raise err # re-raise + + logger.info(f'dispatched transaction {tx_id} at nonce {tx.nonce} with gas price {tx.gasPrice}') + if not ignore_result: + asyncio.ensure_future(_track_tx_result(tx, tx_id, ticks)) + + +def make_call_tx( + contract: str, + abi: List[Dict[str, Any]], + method: str, + args: List[Any], + nonce: int, + value: int = 0, + gas: int = DEFAULT_GAS, + gas_price: int = -1) -> UnsignedEthTx: + ''' + Sends tokens to a recipient + Args: + contract (str): address of contract being called + abi (dict): contract ABI + method (str): the name of the method to call + args (list): the arguments to the method call + nonce (int): the account nonce for the txn + value (int): ether in wei + gas_price (int): the price of gas in wei or gwei + Returns: + (UnsignedEthTx): the unsigned tx object + ''' + logger.debug(f'making tx call {method} on {contract} ' + f'with value {value} and {len(args)} args') + + global LATEST_PENDING_NONCE + + # Adjust gas price for current pending txes. + if gas_price == -1: + gas_price = _compute_tx_gas_price(nonce, 0) + + if nonce > LATEST_PENDING_NONCE: + LATEST_PENDING_NONCE = nonce + + gas_price = _adjust_gas_price(gas_price) + chainId = config.get()['CHAIN_ID'] + + data = calldata.call( + method, + args, + abi) + + txn = UnsignedEthTx( + to=contract, + value=value, + gas=gas, + gasPrice=gas_price, + nonce=nonce, + data=data, + chainId=chainId) + + return txn + + +def _adjust_gas_price(gas_price: int) -> int: + ''' + We accept gas price in GWEI or in WEI. + This adjusts, and ensures we error if it's high. + Args: + gas_price (int): the user-provided gas price + Returns: + (int): the adjusted price + ''' + if gas_price < GWEI: + gas_price = gas_price * GWEI + if gas_price > 1000 * GWEI: + logger.error('rejecting high gas price') + raise ValueError( + 'very high gas price detected: {} gwei'.format(gas_price / GWEI)) + return gas_price + +def _compute_tx_gas_price(tx_nonce, tx_ticks): + '''Compute the proper gas price, adjusting for other pending txes and how + long this tx has been pending, taking the max gas price into account.''' + gas_price_factor = max((LATEST_PENDING_NONCE - tx_nonce + 1) * tx_ticks, 0) + adjusted_gas_price = round((1 + gas_price_factor * 0.5) * DEFAULT_GAS_PRICE) + + return max(min(adjusted_gas_price, MAX_GAS_PRICE), DEFAULT_GAS_PRICE) + +async def _track_tx_result(tx: UnsignedEthTx, tx_id: str, ticks: int = 0) -> None: + '''Keep track of the result of a transaction by polling every 25 seconds''' + receipt_or_none: Optional[Receipt] = None + + latest_gas_price = tx.gasPrice + + for _ in range(20): + receipt_or_none = None + + # For a blank tx_id, skip the sleep and go straight to cranking gas. + # Blank tx_ids are really just us trying to catch up to the latest used + # gas price. + if tx_id != "": + await asyncio.sleep(30) + receipt_or_none = await CONNECTION.get_tx_receipt(tx_id) + + if receipt_or_none is not None: + break + else: + ticks += 1 + new_gas_price = _compute_tx_gas_price(tx.nonce, ticks) + + # If the new gas price is higher, resubmit this transaction with a + # boosted gas price to improve chances of confirmation. + if new_gas_price > latest_gas_price: + logger.info(f'resubmitting {tx_id} with gas price {new_gas_price}') + newTx = UnsignedEthTx( + nonce = tx.nonce, + gasPrice = new_gas_price, + gas = tx.gas, + to = tx.to, + value = tx.value, + data = tx.data, + chainId = tx.chainId) + + # Broadcast and set up tracking for the new tx, and stop + asyncio.ensure_future(sign_and_broadcast(newTx, False, ticks)) + return + # If gas is still below the max gas price, try again with the next + # tick. + elif new_gas_price < MAX_GAS_PRICE: + logger.info(f'attempting to boost gas for {tx_id} at gas price {latest_gas_price} past gas price {new_gas_price}, tick {ticks}') + asyncio.ensure_future(_track_tx_result(tx, tx_id, ticks)) + return + else: + logger.info(f'no gas price bump possible, continuing to wait for receipt: {tx_id}, nonce: {tx.nonce}, gas price: {tx.gasPrice}, bumped price: {new_gas_price}') + + # This is reachable only when we've hit max gas. + if receipt_or_none is None: + raise RuntimeError(f'No receipt after 10 minutes: {tx_id}, nonce: {tx.nonce}, gas price: {tx.gasPrice}') + + receipt = cast(Receipt, receipt_or_none) + logger.info(f'Receipt for {tx_id} status is {receipt["status"]}') + + if receipt['status'] != '0x1': + raise RuntimeError(f'Failed tx: {receipt["transactionHash"]}') diff --git a/maintainer/header_forwarder/__init__.py b/maintainer/maintainer/header_forwarder/__init__.py similarity index 100% rename from maintainer/header_forwarder/__init__.py rename to maintainer/maintainer/header_forwarder/__init__.py diff --git a/maintainer/header_forwarder/h.py b/maintainer/maintainer/header_forwarder/h.py similarity index 75% rename from maintainer/header_forwarder/h.py rename to maintainer/maintainer/header_forwarder/h.py index e08a39da..c9ce69fa 100644 --- a/maintainer/header_forwarder/h.py +++ b/maintainer/maintainer/header_forwarder/h.py @@ -2,10 +2,10 @@ import asyncio import logging -from relay import base, utils -from relay.bitcoin import bcoin_rpc, bsock -from relay.ethereum import contract, shared -from relay.header_forwarder import pull, push +from maintainer import base, utils +from maintainer.bitcoin import bcoin_rpc, bsock +from maintainer.ethereum import contract, shared +from maintainer.header_forwarder import pull, push from typing import cast from btcspv.types import RelayHeader @@ -29,9 +29,13 @@ async def run() -> None: f'Received {len(latest_digest) // 2} bytes instead. ' 'Hint: is this account authorized?') - latest = cast( - RelayHeader, - await bcoin_rpc.get_header_by_hash(latest_digest)) + latest_or_none = await bcoin_rpc.get_header_by_hash_le(latest_digest) + if latest_or_none is None: + raise ValueError( + 'Relay\'s latest digest is not known to the Bitcoin node. ' + f'Got {latest_digest}. ' + 'Hint: is your node on the same Bitcoin network as the relay?') + latest = cast(RelayHeader, latest_or_none) better_or_same = cast( RelayHeader, await bcoin_rpc.get_header_by_height(latest['height'])) @@ -41,7 +45,7 @@ async def run() -> None: while latest != better_or_same: latest = cast( RelayHeader, - await bcoin_rpc.get_header_by_hash(latest['prevhash'])) + await bcoin_rpc.get_header_by_hash_le(latest['prevhash'])) better_or_same = cast( RelayHeader, await bcoin_rpc.get_header_by_height(latest['height'])) diff --git a/maintainer/header_forwarder/pull.py b/maintainer/maintainer/header_forwarder/pull.py similarity index 95% rename from maintainer/header_forwarder/pull.py rename to maintainer/maintainer/header_forwarder/pull.py index e1d24f61..4ef76368 100644 --- a/maintainer/header_forwarder/pull.py +++ b/maintainer/maintainer/header_forwarder/pull.py @@ -1,7 +1,7 @@ import asyncio import logging -from relay.bitcoin import bcoin_rpc +from maintainer.bitcoin import bcoin_rpc from typing import cast from btcspv.types import RelayHeader diff --git a/maintainer/header_forwarder/push.py b/maintainer/maintainer/header_forwarder/push.py similarity index 74% rename from maintainer/header_forwarder/push.py rename to maintainer/maintainer/header_forwarder/push.py index f18f31e6..8fe0742a 100644 --- a/maintainer/header_forwarder/push.py +++ b/maintainer/maintainer/header_forwarder/push.py @@ -1,10 +1,10 @@ import asyncio import logging -from relay import config, utils -from relay.bitcoin import bcoin_rpc -from relay.ethereum import contract, shared -from relay.relay_abi import ABI as relay_ABI +from maintainer import config, utils +from maintainer.bitcoin import bcoin_rpc +from maintainer.ethereum import contract, shared +from maintainer.relay_abi import ABI as relay_ABI from typing import cast, List from btcspv.types import RelayHeader @@ -74,8 +74,8 @@ async def _add_headers(headers: List[RelayHeader]) -> None: f'first is {utils.format_header(headers[0])}\n' f'last is {utils.format_header(headers[-1])}\n') nonce = next(shared.NONCE) - anchor_or_none = await bcoin_rpc.get_header_by_hash( - headers[0]['prevhash'].hex()) + anchor_or_none = await bcoin_rpc.get_header_by_hash_le( + headers[0]['prevhash']) anchor = cast(RelayHeader, anchor_or_none) headers_hex = ''.join(h['raw'].hex() for h in headers) @@ -86,7 +86,7 @@ async def _add_headers(headers: List[RelayHeader]) -> None: method='addHeaders', args=[anchor["raw"], headers_hex], nonce=nonce) - asyncio.create_task(shared.sign_and_broadcast(tx)) + await shared.sign_and_broadcast(tx) async def _add_diff_change(headers: List[RelayHeader]) -> None: @@ -104,8 +104,8 @@ async def _add_diff_change(headers: List[RelayHeader]) -> None: # we know these casts won't fail old_start = cast(RelayHeader, old_start_or_none) old_end = cast(RelayHeader, old_end_or_none) - logger.debug(f'old start is {old_start["hash_le"].hex()}') - logger.debug(f'old end is {old_end["hash_le"].hex()}') + logger.debug(f'old start is {old_start["hash"].hex()}') + logger.debug(f'old end is {old_end["hash"].hex()}') headers_hex = ''.join(h['raw'].hex() for h in headers) @@ -119,12 +119,39 @@ async def _add_diff_change(headers: List[RelayHeader]) -> None: headers_hex], nonce=nonce) - asyncio.create_task(shared.sign_and_broadcast(tx)) + await shared.sign_and_broadcast(tx) + + +async def find_lca( + new_best: RelayHeader, + current_best: RelayHeader +) -> RelayHeader: + # find the latest block in current's history that is an ancestor of new + is_ancestor = False + + for i in range(1, 6): + logger.info(f'Attempt {i} to find LCA in previous 20 blocks') + + ancestor = current_best + for _ in range(20): + is_ancestor = await contract.is_ancestor( + ancestor['hash'], + new_best['hash'] + ) + if is_ancestor: + return ancestor + ancestor = cast( + RelayHeader, + await bcoin_rpc.get_header_by_hash_le(ancestor['prevhash'])) + await asyncio.sleep(15) + + raise RuntimeError('Unable to find LCA after 5 attempts') # TODO: refactor this to not be shit async def _update_best_digest( - new_best: RelayHeader) -> None: + new_best: RelayHeader +) -> None: '''Send an ethereum transaction that marks a new best known chain tip''' nonce = next(shared.NONCE) will_succeed = False @@ -133,30 +160,27 @@ async def _update_best_digest( current_best_digest = await contract.get_best_block() current_best = cast( RelayHeader, - await bcoin_rpc.get_header_by_hash(current_best_digest)) - - delta = new_best['height'] - current_best['height'] + 1 - # find the latest block in current's history that is an ancestor of new - is_ancestor = False - ancestor = current_best - while True: - is_ancestor = await contract.is_ancestor( - ancestor['hash_le'], - new_best['hash_le']) - if is_ancestor: - break - ancestor = cast( - RelayHeader, - await bcoin_rpc.get_header_by_hash(ancestor['prevhash'])) + await bcoin_rpc.get_header_by_hash_le(current_best_digest)) - ancestor_le = ancestor['hash_le'] + try: + ancestor = await find_lca(new_best, current_best) + except RuntimeError as err: + if err.args[0] == 'Unable to find LCA after 5 attempts': + logger.warn(err) + # Retry after a while, we're probably behind. + await asyncio.sleep(60) + continue + else: + raise err # re-raise + + delta = new_best['height'] - ancestor['height'] + 1 tx = shared.make_call_tx( contract=config.get()['CONTRACT'], abi=relay_ABI, method='markNewHeaviest', args=[ - ancestor_le, + ancestor['hash'], current_best["raw"], new_best["raw"], delta], @@ -177,4 +201,4 @@ async def _update_best_digest( f'previous best was {utils.format_header(current_best)}\n' f'new best is {utils.format_header(new_best)}\n') - asyncio.create_task(shared.sign_and_broadcast(tx)) + await shared.sign_and_broadcast(tx) diff --git a/maintainer/maintainer/relay_abi.py b/maintainer/maintainer/relay_abi.py new file mode 100644 index 00000000..cfeb6475 --- /dev/null +++ b/maintainer/maintainer/relay_abi.py @@ -0,0 +1,8 @@ +# flake8: noqa + +true = True +false = False +null = None + + +ABI = [{"constant":true,"inputs":[],"name":"getCurrentEpochDifficulty","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBestKnownDigest","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"nextID","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"latestValidatedTx","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getPrevEpochDifficulty","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ancestor","type":"bytes32"},{"name":"_left","type":"bytes32"},{"name":"_right","type":"bytes32"},{"name":"_limit","type":"uint256"}],"name":"isMostRecentAncestor","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_digest","type":"bytes32"},{"name":"_offset","type":"uint256"}],"name":"findAncestor","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_digest","type":"bytes32"}],"name":"findHeight","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_anchor","type":"bytes"},{"name":"_headers","type":"bytes"}],"name":"addHeaders","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"HEIGHT_INTERVAL","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_ancestor","type":"bytes32"},{"name":"_currentBest","type":"bytes"},{"name":"_newBest","type":"bytes"},{"name":"_limit","type":"uint256"}],"name":"markNewHeaviest","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_oldPeriodStartHeader","type":"bytes"},{"name":"_oldPeriodEndHeader","type":"bytes"},{"name":"_headers","type":"bytes"}],"name":"addHeadersWithRetarget","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"BASE_COST","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"remoteGasAllowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ancestor","type":"bytes32"},{"name":"_left","type":"bytes"},{"name":"_right","type":"bytes"}],"name":"heaviestFromAncestor","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ancestor","type":"bytes32"},{"name":"_descendant","type":"bytes32"},{"name":"_limit","type":"uint256"}],"name":"isAncestor","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLastReorgCommonAncestor","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getRelayGenesis","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_genesisHeader","type":"bytes"},{"name":"_height","type":"uint256"},{"name":"_periodStart","type":"bytes32"},{"name":"_firstID","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_first","type":"bytes32"},{"indexed":true,"name":"_last","type":"bytes32"}],"name":"Extension","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"bytes32"},{"indexed":true,"name":"_to","type":"bytes32"},{"indexed":true,"name":"_gcd","type":"bytes32"}],"name":"Reorg","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_requester","type":"address"},{"indexed":true,"name":"_requestID","type":"uint256"},{"indexed":false,"name":"_paysValue","type":"uint64"},{"indexed":false,"name":"_spends","type":"bytes"},{"indexed":false,"name":"_pays","type":"bytes"}],"name":"NewProofRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_requestID","type":"uint256"}],"name":"RequestClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_txid","type":"bytes32"},{"indexed":true,"name":"_requestID","type":"uint256"}],"name":"RequestFilled","type":"event"},{"constant":false,"inputs":[{"name":"_requestID","type":"uint256"}],"name":"cancelRequest","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getLatestValidatedTx","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_requestID","type":"uint256"}],"name":"getRequest","outputs":[{"name":"spends","type":"bytes32"},{"name":"pays","type":"bytes32"},{"name":"paysValue","type":"uint64"},{"name":"state","type":"uint8"},{"name":"consumer","type":"address"},{"name":"owner","type":"address"},{"name":"numConfs","type":"uint8"},{"name":"notBefore","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spends","type":"bytes"},{"name":"_pays","type":"bytes"},{"name":"_paysValue","type":"uint64"},{"name":"_consumer","type":"address"},{"name":"_numConfs","type":"uint8"},{"name":"_notBefore","type":"uint256"}],"name":"request","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_header","type":"bytes"},{"name":"_proof","type":"bytes"},{"name":"_version","type":"bytes4"},{"name":"_locktime","type":"bytes4"},{"name":"_index","type":"uint256"},{"name":"_reqIndices","type":"uint16"},{"name":"_vin","type":"bytes"},{"name":"_vout","type":"bytes"},{"name":"_requestID","type":"uint256"}],"name":"provideProof","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}] diff --git a/maintainer/relay_types.py b/maintainer/maintainer/relay_types.py similarity index 98% rename from maintainer/relay_types.py rename to maintainer/maintainer/relay_types.py index fc51e2fc..55a0ba1f 100644 --- a/maintainer/relay_types.py +++ b/maintainer/maintainer/relay_types.py @@ -72,6 +72,5 @@ class RelayConfig(TypedDict): GETH_UNLOCK: Optional[str] BCOIN_URL: str BCOIN_WS_URL: str - PLUGIN_WS_URL: str PROJECT_ID: str CONTRACT: str diff --git a/maintainer/utils.py b/maintainer/maintainer/utils.py similarity index 94% rename from maintainer/utils.py rename to maintainer/maintainer/utils.py index 56d5d070..4e13f2a1 100644 --- a/maintainer/utils.py +++ b/maintainer/maintainer/utils.py @@ -1,6 +1,6 @@ from riemann import tx from btcspv.types import RelayHeader -from relay.relay_types import RelayRequest +from maintainer.relay_types import RelayRequest def format_header(h: RelayHeader) -> str: diff --git a/maintainer/relay_abi.py b/maintainer/relay_abi.py deleted file mode 100644 index d4bf9536..00000000 --- a/maintainer/relay_abi.py +++ /dev/null @@ -1,8 +0,0 @@ -# flake8: noqa - -true = True -false = False -null = None - - -ABI = [{"constant":true,"inputs":[],"name":"getSubRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_subscriber","type":"address"}],"name":"getSubscription","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentEpochDifficulty","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"seizeControl","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_beneficiary","type":"address"}],"name":"withdrawFees","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBestKnownDigest","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"nextID","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"latestValidatedTx","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getPrevEpochDifficulty","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ancestor","type":"bytes32"},{"name":"_left","type":"bytes32"},{"name":"_right","type":"bytes32"},{"name":"_limit","type":"uint256"}],"name":"isMostRecentAncestor","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_digest","type":"bytes32"},{"name":"_offset","type":"uint256"}],"name":"findAncestor","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_requestID","type":"uint256"}],"name":"cancelRequest","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PERMANENT_SUB","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beneficiary","type":"address"}],"name":"subscribe","outputs":[{"name":"","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"cedeControl","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"fourWeekSub","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_digest","type":"bytes32"}],"name":"findHeight","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_anchor","type":"bytes"},{"name":"_headers","type":"bytes"}],"name":"addHeaders","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_caller","type":"address"}],"name":"isOperator","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"HEIGHT_INTERVAL","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_ancestor","type":"bytes32"},{"name":"_currentBest","type":"bytes"},{"name":"_newBest","type":"bytes"},{"name":"_limit","type":"uint256"}],"name":"markNewHeaviest","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_oldPeriodStartHeader","type":"bytes"},{"name":"_oldPeriodEndHeader","type":"bytes"},{"name":"_headers","type":"bytes"}],"name":"addHeadersWithRetarget","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ONE_DAY","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_bestKnown","type":"bytes"},{"name":"_headers","type":"bytes"}],"name":"seizeControl","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spends","type":"bytes"},{"name":"_pays","type":"bytes"},{"name":"_paysValue","type":"uint64"},{"name":"_consumer","type":"address"},{"name":"_numConfs","type":"uint8"},{"name":"_notBefore","type":"uint256"}],"name":"request","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"BASE_COST","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"remoteGasAllowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLatestValidatedTx","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beneficiary","type":"address"}],"name":"changePaymentAdmin","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_header","type":"bytes"},{"name":"_proof","type":"bytes"},{"name":"_version","type":"bytes4"},{"name":"_locktime","type":"bytes4"},{"name":"_index","type":"uint256"},{"name":"_reqIndices","type":"uint16"},{"name":"_vin","type":"bytes"},{"name":"_vout","type":"bytes"},{"name":"_requestID","type":"uint256"}],"name":"provideProof","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_ancestor","type":"bytes32"},{"name":"_left","type":"bytes"},{"name":"_right","type":"bytes"}],"name":"heaviestFromAncestor","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newRate","type":"uint256"}],"name":"setSubRate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getPaymentAdmin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beneficiary","type":"address"},{"name":"_subEnd","type":"uint256"}],"name":"manageSubscription","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_caller","type":"address"}],"name":"isSubscribed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ancestor","type":"bytes32"},{"name":"_descendant","type":"bytes32"},{"name":"_limit","type":"uint256"}],"name":"isAncestor","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lastUpdate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLastReorgCommonAncestor","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_requestID","type":"uint256"}],"name":"getRequest","outputs":[{"name":"spends","type":"bytes32"},{"name":"pays","type":"bytes32"},{"name":"paysValue","type":"uint64"},{"name":"state","type":"uint8"},{"name":"consumer","type":"address"},{"name":"owner","type":"address"},{"name":"numConfs","type":"uint8"},{"name":"notBefore","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getRelayGenesis","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOperator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FOUR_WEEKS","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_operator","type":"address"},{"name":"_paymentAdmin","type":"address"},{"name":"_genesisHeader","type":"bytes"},{"name":"_height","type":"uint256"},{"name":"_periodStart","type":"bytes32"},{"name":"_firstID","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"}],"name":"SubscriptionExpired","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_first","type":"bytes32"},{"indexed":true,"name":"_last","type":"bytes32"}],"name":"Extension","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"bytes32"},{"indexed":true,"name":"_to","type":"bytes32"},{"indexed":true,"name":"_gcd","type":"bytes32"}],"name":"Reorg","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_requester","type":"address"},{"indexed":true,"name":"_requestID","type":"uint256"},{"indexed":false,"name":"_paysValue","type":"uint64"},{"indexed":false,"name":"_spends","type":"bytes"},{"indexed":false,"name":"_pays","type":"bytes"}],"name":"NewProofRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_requestID","type":"uint256"}],"name":"RequestClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_txid","type":"bytes32"},{"indexed":true,"name":"_requestID","type":"uint256"}],"name":"RequestFilled","type":"event"},{"constant":false,"inputs":[{"name":"_allowance","type":"uint256"}],"name":"setRemoteGasAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}] diff --git a/maintainer/setup.py b/maintainer/setup.py new file mode 100644 index 00000000..535129ac --- /dev/null +++ b/maintainer/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup, find_packages + +setup( + name='summa-relay', + version='0.1.0', + description=('Summa minimal relay'), + author=["James Prestwich"], + license="LGPLv3.0", + install_requires=[ + 'aiohttp', + 'riemann-ether', + 'riemann-keys', + 'mypy-extensions'], + packages=find_packages(), + package_data={'relay': ['py.typed']}, + package_dir={'relay': 'relay'}, + python_requires='>=3.6' +) diff --git a/solidity/README.md b/solidity/README.md index ef1c216d..ffe645f1 100644 --- a/solidity/README.md +++ b/solidity/README.md @@ -1,8 +1,14 @@ +## cosmos-sdk Bitcoin Relay + +This is a full-featured Bitcoin relay module for EVM chains. It indexes +Bitcoin headers, provides information about the latest-known state of the +Bitcoin chain, and validates SPV Proofs against its view of the chain. It is a +critical component for many EVM applications to interact with Bitcoin. + ### How do I develop for it? ``` $ npm i -g truffle $ npm i -$ truffle compile -$ npm run lint +$ npm run test ``` diff --git a/solidity/contracts/Migrations.sol b/solidity/contracts/Migrations.sol new file mode 100644 index 00000000..89d6ee33 --- /dev/null +++ b/solidity/contracts/Migrations.sol @@ -0,0 +1,23 @@ +pragma solidity >=0.4.21 <0.6.0; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + constructor() public { + owner = msg.sender; + } + + modifier restricted() { + if (msg.sender == owner) _; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/solidity/contracts/OnDemandSPV.sol b/solidity/contracts/OnDemandSPV.sol index ba10b694..98e13480 100644 --- a/solidity/contracts/OnDemandSPV.sol +++ b/solidity/contracts/OnDemandSPV.sol @@ -54,8 +54,6 @@ contract OnDemandSPV is ISPVRequestManager, Relay { nextID = _firstID; } - function () external payable {} - /// @notice Cancel a bitcoin event request. /// @dev Prevents the relay from forwarding tx infromation /// @param _requestID The ID of the request to be cancelled @@ -139,36 +137,36 @@ contract OnDemandSPV is ISPVRequestManager, Relay { ) internal returns (uint256) { uint256 _requestID = nextID; nextID = nextID + 1; + bytes memory pays = _pays; - uint256 _spendsLen = _spends.length; - require(_spendsLen == 36 || _spendsLen == 0, "Not a valid UTXO"); + require(_spends.length == 36 || _spends.length == 0, "Not a valid UTXO"); /* NB: This will fail if the output is not p2pkh, p2sh, p2wpkh, or p2wsh*/ - uint256 _paysLen = _pays.length; + uint256 _paysLen = pays.length; // if it's not length-prefixed, length-prefix it - if (_paysLen > 0 && uint8(_pays[0]) != _paysLen - 1) { - _pays = abi.encodePacked(uint8(_paysLen), _pays); + if (_paysLen > 0 && uint8(pays[0]) != _paysLen - 1) { + pays = abi.encodePacked(uint8(_paysLen), pays); _paysLen += 1; // update the length because we made it longer } - bytes memory _p = abi.encodePacked(bytes8(0), _pays); + bytes memory _p = abi.encodePacked(bytes8(0), pays); require( _paysLen == 0 || // no request OR _p.extractHash().length > 0 || // standard output OR _p.extractOpReturnData().length > 0, // OP_RETURN output "Not a standard output type"); - require(_spendsLen > 0 || _paysLen > 0, "No request specified"); + require(_spends.length > 0 || _paysLen > 0, "No request specified"); ProofRequest storage _req = requests[_requestID]; _req.owner = msg.sender; - if (_spendsLen > 0) { + if (_spends.length > 0) { _req.spends = keccak256(_spends); } if (_paysLen > 0) { - _req.pays = keccak256(_pays); + _req.pays = keccak256(pays); } if (_paysValue > 0) { _req.paysValue = _paysValue; @@ -182,7 +180,7 @@ contract OnDemandSPV is ISPVRequestManager, Relay { _req.consumer = _consumer; _req.state = RequestStates.ACTIVE; - emit NewProofRequest(msg.sender, _requestID, _paysValue, _spends, _pays); + emit NewProofRequest(msg.sender, _requestID, _paysValue, _spends, pays); return _requestID; } diff --git a/solidity/contracts/Relay.sol b/solidity/contracts/Relay.sol index 16bf74bf..124816d5 100644 --- a/solidity/contracts/Relay.sol +++ b/solidity/contracts/Relay.sol @@ -224,8 +224,8 @@ contract Relay is IRelay { // update the stored prevEpochDiff // Don't update if this is a deep past epoch uint256 _oldDiff = _oldPeriodStartHeader.extractDifficulty(); - if (prevEpochDiff != _oldDiff && _findHeight(_oldPeriodEndHeader) > _findHeight(bestKnownDigest).sub(2016)) { - prevEpochDiff = _oldDiff; + if (prevEpochDiff != _oldDiff && _endHeight > _findHeight(bestKnownDigest).sub(2016)) { + prevEpochDiff = _oldDiff; } // Pass all but the first through to be added @@ -338,7 +338,7 @@ contract Relay is IRelay { uint256 _newDiff = _newBest.extractDifficulty(); if (_newDiff != currentEpochDiff) { - currentEpochDiff = _newDiff; + currentEpochDiff = _newDiff; } emit Reorg( @@ -481,17 +481,17 @@ contract Relay is IRelay { /// @dev This is updated when a new heavist header has a new diff /// @return The difficulty of the bestKnownDigest function getCurrentEpochDifficulty() external view returns (uint256) { - return currentEpochDiff; + return currentEpochDiff; } /// @notice Getter for prevEpochDiff /// @dev This is updated when a difficulty change is accepted /// @return The difficulty of the previous epoch function getPrevEpochDifficulty() external view returns (uint256) { - return prevEpochDiff; + return prevEpochDiff; } /// @notice Getter for relayGenesis - /// @dev This is an initialization paramter + /// @dev This is an initialization parameter /// @return The hash of the first block of the relay function getRelayGenesis() public view returns (bytes32) { return relayGenesis; diff --git a/solidity/contracts/test/DummyOnDemandSPV.sol b/solidity/contracts/test/DummyOnDemandSPV.sol new file mode 100644 index 00000000..64f604c9 --- /dev/null +++ b/solidity/contracts/test/DummyOnDemandSPV.sol @@ -0,0 +1,132 @@ +pragma solidity ^0.5.10; + +/** @title OnDemandSPV */ +/** @author Summa (https://summa.one) */ + +import {ISPVConsumer} from "../Interfaces.sol"; +import {OnDemandSPV} from "../OnDemandSPV.sol"; + +contract DummyConsumer is ISPVConsumer { + event Consumed(bytes32 indexed _txid, uint256 indexed _requestID, uint256 _gasLeft); + + bool broken = false; + + function setBroken(bool _b) external { + broken = _b; + } + + function spv( + bytes32 _txid, + bytes calldata, + bytes calldata, + uint256 _requestID, + uint8, + uint8 + ) external { + emit Consumed(_txid, _requestID, gasleft()); + if (broken) { + revert("BORKED"); + } + } + + function cancel( + uint256 _requestID, + address payable _odspv + ) external returns (bool) { + return OnDemandSPV(_odspv).cancelRequest(_requestID); + } +} + +contract DummyOnDemandSPV is OnDemandSPV { + + constructor( + bytes memory _genesisHeader, + uint256 _height, + bytes32 _periodStart, + uint256 _firstID + ) OnDemandSPV( + _genesisHeader, + _height, + _periodStart, + _firstID + ) public {return ;} + + bool callResult = false; + + function requestTest( + uint256 _requestID, + bytes calldata _spends, + bytes calldata _pays, + uint64 _paysValue, + address _consumer, + uint8 _numConfs, + uint256 _notBefore + ) external returns (uint256) { + nextID = _requestID; + return _request(_spends, _pays, _paysValue, _consumer, _numConfs, _notBefore); + } + + function setCallResult(bool _r) external { + callResult = _r; + } + + function _isAncestor(bytes32, bytes32, uint256) internal view returns (bool) { + return callResult; + } + + function getValidatedTx(bytes32 _txid) public view returns (bool) { + return validatedTxns[_txid]; + } + + function setValidatedTx(bytes32 _txid) public { + validatedTxns[_txid] = true; + } + + function unsetValidatedTx(bytes32 _txid) public { + validatedTxns[_txid] = false; + } + + function callCallback( + bytes32 _txid, + uint16 _reqIndices, + bytes calldata _vin, + bytes calldata _vout, + uint256 _requestID + ) external returns (bool) { + return _callCallback(_txid, _reqIndices, _vin, _vout, _requestID); + } + + function checkInclusion( + bytes calldata _header, + bytes calldata _proof, + uint256 _index, + bytes32 _txid, + uint256 _requestID + ) external view returns (bool) { + return _checkInclusion(_header, _proof, _index, _txid, _requestID); + } + + function _getConfs(bytes32 _header) internal view returns (uint8){ + if (_header == bytes32(0)) { + return OnDemandSPV._getConfs(lastReorgCommonAncestor); + } + return 8; + } + + function getConfsTest() external view returns (uint8) { + return _getConfs(bytes32(0)); + } + + function checkRequests( + uint16 _requestIndices, + bytes calldata _vin, + bytes calldata _vout, + uint256 _requestID + ) external view returns (bool) { + return _checkRequests(_requestIndices, _vin, _vout, _requestID); + } + + function whatTimeIsItRightNowDotCom() external view returns (uint256) { + return block.timestamp; + } +} diff --git a/solidity/migrations/2_deploy_contracts.js b/solidity/migrations/2_deploy_contracts.js index 338dad0b..d0abeaba 100644 --- a/solidity/migrations/2_deploy_contracts.js +++ b/solidity/migrations/2_deploy_contracts.js @@ -10,7 +10,7 @@ function sleep(milliseconds) { } module.exports = async (deployer, network) => { - if (['test', 'development', 'coverage'].includes(network)) { + if (['test', 'development', 'soliditycoverage'].includes(network)) { // never run deployments on development. We deploy in tests return; } @@ -35,5 +35,5 @@ module.exports = async (deployer, network) => { await sleep(7500); /* eslint-enable */ - deployer.deploy(contract, genesis, height, epochStart, firstID); + await deployer.deploy(contract, genesis, height, epochStart, firstID); }; diff --git a/solidity/migrations/networkInfo.js b/solidity/migrations/networkInfo.js index 332cc593..94ad2e17 100644 --- a/solidity/migrations/networkInfo.js +++ b/solidity/migrations/networkInfo.js @@ -6,15 +6,15 @@ const ID_SPACE_SIZE = new BN('2', 10).pow(new BN('32', 10)); const truffleConf = require('../truffle-config'); const bitcoinMain = { - genesis: '0x00000020d208b5e50a8d3bd3a87f7a238e3f196621d0f9ffb5f302000000000000000000ee3af51ad3643a8a109935b45d9ca32b1003cda41df39dd75a17e13ba13aff4211aa585d39301c174a8ead73', - height: 590588, - epochStart: '0x704de08dc5329269011b878835be108a8202a93a0a2a1c000000000000000000', + genesis: '0x00006020dd02d03c03dbc1f41312a6940e89919ce67fbf99a20307000000000000000000260d70e7ae07c80db07fbf29d09ec1a86d4f788e58098189a6f9021236572a7dd99eb15e397a11178294a823', + height: 629070, + epochStart: '0x459ec50d4ea62a89da04eb1ef3e352ec740bca50e8a808000000000000000000', }; const bitcoinTest = { - genesis: '0x0000ff3ffc663e3a0b12b4cc2c05a425bdaf51922ce090acd8fa3a8a180300000000000084080b23fc40476d284da49fedaea9f7cee3aba33a8bad1347fa54740a29f02752b4c45dfcff031a279c2b3a', - height: 1607272, - epochStart: '0x84a9ec3b82556297ea36d1377901ecaef0bb5a5cf683f9f05103000000000000' + genesis: '0x0000c0205d1103efc13e6647977e3d65f253c3e762451e9ca9b920517d000000000000008442a07bcde3292a888277ea6337ba5bbdfa808ae01535846f19d843144c8f60478bb15e7b41011a88be5d36', + height: 1723030, + epochStart: '0xe2657f702faa9470815005305c45b4be2271c22ade1348e6fe00000000000000' }; module.exports = { @@ -26,7 +26,7 @@ module.exports = { ropsten_test: { network_id: truffleConf.networks.ropsten_test.network_id, bitcoin: bitcoinTest, - firstID: ID_SPACE_SIZE.muln(truffleConf.networks.ropsten_test.network_id) + firstID: ID_SPACE_SIZE.muln(truffleConf.networks.ropsten_test.network_id + 0x800000) }, kovan: { network_id: truffleConf.networks.kovan.network_id, @@ -36,6 +36,21 @@ module.exports = { kovan_test: { network_id: truffleConf.networks.kovan_test.network_id, bitcoin: bitcoinTest, - firstID: ID_SPACE_SIZE.muln(truffleConf.networks.kovan_test.network_id) - } + firstID: ID_SPACE_SIZE.muln(truffleConf.networks.kovan_test.network_id + 0x800000) + }, + alfajores: { + network_id: truffleConf.networks.alfajores_test.network_id, + bitcoin: bitcoinMain, + firstID: ID_SPACE_SIZE.muln(truffleConf.networks.alfajores.network_id) + }, + alfajores_test: { + network_id: truffleConf.networks.alfajores_test.network_id, + bitcoin: bitcoinTest, + firstID: ID_SPACE_SIZE.muln(truffleConf.networks.alfajores_test.network_id + 0x800000) + }, + local_test: { + network_id: truffleConf.networks.local_test.network_id, + bitcoin: bitcoinTest, + firstID: ID_SPACE_SIZE.muln(truffleConf.networks.local_test.network_id + 0x800000) + }, }; diff --git a/solidity/package-lock.json b/solidity/package-lock.json index 3c215b27..25a95213 100644 --- a/solidity/package-lock.json +++ b/solidity/package-lock.json @@ -1,6 +1,6 @@ { "name": "@summa-tx/relay-sol", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -30,6 +30,511 @@ "js-tokens": "^4.0.0" } }, + "@celo/contractkit": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@celo/contractkit/-/contractkit-0.3.3.tgz", + "integrity": "sha512-KRIQgqEMDrLAueY7MctsRF+X9iKl1BLSIEo+E6mIaNl62lmSPVXvsAuQ0xuxUYFVJJgn3gPZP7MMlPnPH6aVJQ==", + "requires": { + "@celo/utils": "0.1.8", + "@ledgerhq/hw-app-eth": "^5.11.0", + "@ledgerhq/hw-transport": "^5.11.0", + "@types/debug": "^4.1.5", + "bignumber.js": "^9.0.0", + "cross-fetch": "3.0.4", + "debug": "^4.1.1", + "eth-lib": "^0.2.8", + "ethereumjs-util": "^5.2.0", + "fp-ts": "2.1.1", + "io-ts": "2.0.1", + "web3": "1.2.4", + "web3-core": "1.2.4", + "web3-core-helpers": "1.2.4", + "web3-eth-abi": "1.2.4", + "web3-eth-contract": "1.2.4", + "web3-utils": "1.2.4" + }, + "dependencies": { + "@types/node": { + "version": "12.12.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.35.tgz", + "integrity": "sha512-ASYsaKecA7TUsDrqIGPNk3JeEox0z/0XR/WsJJ8BIX/9+SkMSImQXKWfU/yBrSyc7ZSE/NPqLu36Nur0miCFfQ==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + }, + "ethers": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.0-beta.3.tgz", + "integrity": "sha512-YYPogooSknTwvHg3+Mv71gM/3Wcrx+ZpCzarBj3mqs9njjRkrOo2/eufzhHloOCo3JSoNI4TQJJ6yU5ABm3Uog==", + "requires": { + "@types/node": "^10.3.2", + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.3.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.3", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "@types/node": { + "version": "10.17.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.19.tgz", + "integrity": "sha512-46/xThm3zvvc9t9/7M3AaLEqtOpqlYYYcCZbpYVAQHG20+oMZBkae/VMrn4BTi6AJ8cpack0mEXhGiKmDNbLrQ==" + }, + "elliptic": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", + "integrity": "sha1-VILZZG1UvLif19mU/J4ulWiHbj8=", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "inherits": "^2.0.1" + } + } + } + }, + "keccak": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz", + "integrity": "sha512-eZVaCpblK5formjPjeTBik7TAg+pqnDrMHIffSvi9Lh7PQgM1+hSzakUeZFCk9DVVG0dacZJuaz2ntwlzZUIBw==", + "requires": { + "bindings": "^1.2.1", + "inherits": "^2.0.3", + "nan": "^2.2.1", + "safe-buffer": "^5.1.0" + } + }, + "web3": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.4.tgz", + "integrity": "sha512-xPXGe+w0x0t88Wj+s/dmAdASr3O9wmA9mpZRtixGZxmBexAF0MjfqYM+MS4tVl5s11hMTN3AZb8cDD4VLfC57A==", + "requires": { + "@types/node": "^12.6.1", + "web3-bzz": "1.2.4", + "web3-core": "1.2.4", + "web3-eth": "1.2.4", + "web3-eth-personal": "1.2.4", + "web3-net": "1.2.4", + "web3-shh": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-bzz": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.4.tgz", + "integrity": "sha512-MqhAo/+0iQSMBtt3/QI1rU83uvF08sYq8r25+OUZ+4VtihnYsmkkca+rdU0QbRyrXY2/yGIpI46PFdh0khD53A==", + "requires": { + "@types/node": "^10.12.18", + "got": "9.6.0", + "swarm-js": "0.1.39", + "underscore": "1.9.1" + }, + "dependencies": { + "@types/node": { + "version": "10.17.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.19.tgz", + "integrity": "sha512-46/xThm3zvvc9t9/7M3AaLEqtOpqlYYYcCZbpYVAQHG20+oMZBkae/VMrn4BTi6AJ8cpack0mEXhGiKmDNbLrQ==" + } + } + }, + "web3-core": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.4.tgz", + "integrity": "sha512-CHc27sMuET2cs1IKrkz7xzmTdMfZpYswe7f0HcuyneTwS1yTlTnHyqjAaTy0ZygAb/x4iaVox+Gvr4oSAqSI+A==", + "requires": { + "@types/bignumber.js": "^5.0.0", + "@types/bn.js": "^4.11.4", + "@types/node": "^12.6.1", + "web3-core-helpers": "1.2.4", + "web3-core-method": "1.2.4", + "web3-core-requestmanager": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-core-helpers": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.4.tgz", + "integrity": "sha512-U7wbsK8IbZvF3B7S+QMSNP0tni/6VipnJkB0tZVEpHEIV2WWeBHYmZDnULWcsS/x/jn9yKhJlXIxWGsEAMkjiw==", + "requires": { + "underscore": "1.9.1", + "web3-eth-iban": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-core-method": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.4.tgz", + "integrity": "sha512-8p9kpL7di2qOVPWgcM08kb+yKom0rxRCMv6m/K+H+yLSxev9TgMbCgMSbPWAHlyiF3SJHw7APFKahK5Z+8XT5A==", + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.4", + "web3-core-promievent": "1.2.4", + "web3-core-subscriptions": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-core-promievent": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.4.tgz", + "integrity": "sha512-gEUlm27DewUsfUgC3T8AxkKi8Ecx+e+ZCaunB7X4Qk3i9F4C+5PSMGguolrShZ7Zb6717k79Y86f3A00O0VAZw==", + "requires": { + "any-promise": "1.3.0", + "eventemitter3": "3.1.2" + } + }, + "web3-core-requestmanager": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.4.tgz", + "integrity": "sha512-eZJDjyNTDtmSmzd3S488nR/SMJtNnn/GuwxnMh3AzYCqG3ZMfOylqTad2eYJPvc2PM5/Gj1wAMQcRpwOjjLuPg==", + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.4", + "web3-providers-http": "1.2.4", + "web3-providers-ipc": "1.2.4", + "web3-providers-ws": "1.2.4" + } + }, + "web3-core-subscriptions": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.4.tgz", + "integrity": "sha512-3D607J2M8ymY9V+/WZq4MLlBulwCkwEjjC2U+cXqgVO1rCyVqbxZNCmHyNYHjDDCxSEbks9Ju5xqJxDSxnyXEw==", + "requires": { + "eventemitter3": "3.1.2", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.4" + } + }, + "web3-eth": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.4.tgz", + "integrity": "sha512-+j+kbfmZsbc3+KJpvHM16j1xRFHe2jBAniMo1BHKc3lho6A8Sn9Buyut6odubguX2AxoRArCdIDCkT9hjUERpA==", + "requires": { + "underscore": "1.9.1", + "web3-core": "1.2.4", + "web3-core-helpers": "1.2.4", + "web3-core-method": "1.2.4", + "web3-core-subscriptions": "1.2.4", + "web3-eth-abi": "1.2.4", + "web3-eth-accounts": "1.2.4", + "web3-eth-contract": "1.2.4", + "web3-eth-ens": "1.2.4", + "web3-eth-iban": "1.2.4", + "web3-eth-personal": "1.2.4", + "web3-net": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-eth-abi": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.2.4.tgz", + "integrity": "sha512-8eLIY4xZKoU3DSVu1pORluAw9Ru0/v4CGdw5so31nn+7fR8zgHMgwbFe0aOqWQ5VU42PzMMXeIJwt4AEi2buFg==", + "requires": { + "ethers": "4.0.0-beta.3", + "underscore": "1.9.1", + "web3-utils": "1.2.4" + } + }, + "web3-eth-accounts": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.4.tgz", + "integrity": "sha512-04LzT/UtWmRFmi4hHRewP5Zz43fWhuHiK5XimP86sUQodk/ByOkXQ3RoXyGXFMNoRxdcAeRNxSfA2DpIBc9xUw==", + "requires": { + "@web3-js/scrypt-shim": "^0.1.0", + "any-promise": "1.3.0", + "crypto-browserify": "3.12.0", + "eth-lib": "0.2.7", + "ethereumjs-common": "^1.3.2", + "ethereumjs-tx": "^2.1.1", + "underscore": "1.9.1", + "uuid": "3.3.2", + "web3-core": "1.2.4", + "web3-core-helpers": "1.2.4", + "web3-core-method": "1.2.4", + "web3-utils": "1.2.4" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", + "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "web3-eth-contract": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.4.tgz", + "integrity": "sha512-b/9zC0qjVetEYnzRA1oZ8gF1OSSUkwSYi5LGr4GeckLkzXP7osEnp9lkO/AQcE4GpG+l+STnKPnASXJGZPgBRQ==", + "requires": { + "@types/bn.js": "^4.11.4", + "underscore": "1.9.1", + "web3-core": "1.2.4", + "web3-core-helpers": "1.2.4", + "web3-core-method": "1.2.4", + "web3-core-promievent": "1.2.4", + "web3-core-subscriptions": "1.2.4", + "web3-eth-abi": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-eth-ens": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.4.tgz", + "integrity": "sha512-g8+JxnZlhdsCzCS38Zm6R/ngXhXzvc3h7bXlxgKU4coTzLLoMpgOAEz71GxyIJinWTFbLXk/WjNY0dazi9NwVw==", + "requires": { + "eth-ens-namehash": "2.0.8", + "underscore": "1.9.1", + "web3-core": "1.2.4", + "web3-core-helpers": "1.2.4", + "web3-core-promievent": "1.2.4", + "web3-eth-abi": "1.2.4", + "web3-eth-contract": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-eth-iban": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.4.tgz", + "integrity": "sha512-D9HIyctru/FLRpXakRwmwdjb5bWU2O6UE/3AXvRm6DCOf2e+7Ve11qQrPtaubHfpdW3KWjDKvlxV9iaFv/oTMQ==", + "requires": { + "bn.js": "4.11.8", + "web3-utils": "1.2.4" + } + }, + "web3-eth-personal": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.4.tgz", + "integrity": "sha512-5Russ7ZECwHaZXcN3DLuLS7390Vzgrzepl4D87SD6Sn1DHsCZtvfdPIYwoTmKNp69LG3mORl7U23Ga5YxqkICw==", + "requires": { + "@types/node": "^12.6.1", + "web3-core": "1.2.4", + "web3-core-helpers": "1.2.4", + "web3-core-method": "1.2.4", + "web3-net": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-net": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.4.tgz", + "integrity": "sha512-wKOsqhyXWPSYTGbp7ofVvni17yfRptpqoUdp3SC8RAhDmGkX6irsiT9pON79m6b3HUHfLoBilFQyt/fTUZOf7A==", + "requires": { + "web3-core": "1.2.4", + "web3-core-method": "1.2.4", + "web3-utils": "1.2.4" + } + }, + "web3-providers-http": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.4.tgz", + "integrity": "sha512-dzVCkRrR/cqlIrcrWNiPt9gyt0AZTE0J+MfAu9rR6CyIgtnm1wFUVVGaxYRxuTGQRO4Dlo49gtoGwaGcyxqiTw==", + "requires": { + "web3-core-helpers": "1.2.4", + "xhr2-cookies": "1.1.0" + } + }, + "web3-providers-ipc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.4.tgz", + "integrity": "sha512-8J3Dguffin51gckTaNrO3oMBo7g+j0UNk6hXmdmQMMNEtrYqw4ctT6t06YOf9GgtOMjSAc1YEh3LPrvgIsR7og==", + "requires": { + "oboe": "2.1.4", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.4" + } + }, + "web3-providers-ws": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.4.tgz", + "integrity": "sha512-F/vQpDzeK+++oeeNROl1IVTufFCwCR2hpWe5yRXN0ApLwHqXrMI7UwQNdJ9iyibcWjJf/ECbauEEQ8CHgE+MYQ==", + "requires": { + "@web3-js/websocket": "^1.0.29", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.4" + } + }, + "web3-shh": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.4.tgz", + "integrity": "sha512-z+9SCw0dE+69Z/Hv8809XDbLj7lTfEv9Sgu8eKEIdGntZf4v7ewj5rzN5bZZSz8aCvfK7Y6ovz1PBAu4QzS4IQ==", + "requires": { + "web3-core": "1.2.4", + "web3-core-method": "1.2.4", + "web3-core-subscriptions": "1.2.4", + "web3-net": "1.2.4" + } + }, + "web3-utils": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.4.tgz", + "integrity": "sha512-+S86Ip+jqfIPQWvw2N/xBQq5JNqCO0dyvukGdJm8fEWHZbckT4WxSpHbx+9KLEWY4H4x9pUwnoRkK87pYyHfgQ==", + "requires": { + "bn.js": "4.11.8", + "eth-lib": "0.2.7", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", + "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + } + } + } + } + }, + "@celo/utils": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@celo/utils/-/utils-0.1.8.tgz", + "integrity": "sha512-Zgr/N7wHTdMsc+C519JkZCqe6rfrNW8LSnscTISi5NfMTPbvDs/TvWrcuKi36JLVU5SYAPx55JGrSVOhQ1mmeg==", + "requires": { + "@umpirsky/country-list": "git://github.com/umpirsky/country-list.git#05fda51", + "bigi": "^1.1.0", + "bignumber.js": "^7.2.0", + "bls12377js": "git+https://github.com/celo-org/bls12377js.git#400bcaeec9e7620b040bfad833268f5289699cac", + "bn.js": "4.11.8", + "buffer-reverse": "^1.0.1", + "country-data": "^0.0.31", + "crypto-js": "^3.1.9-1", + "elliptic": "^6.4.1", + "ethereumjs-util": "^5.2.0", + "futoin-hkdf": "^1.0.3", + "google-libphonenumber": "^3.2.4", + "keccak256": "^1.0.0", + "lodash": "^4.17.14", + "numeral": "^2.0.6", + "web3-utils": "1.2.4" + }, + "dependencies": { + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + }, + "keccak": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz", + "integrity": "sha512-eZVaCpblK5formjPjeTBik7TAg+pqnDrMHIffSvi9Lh7PQgM1+hSzakUeZFCk9DVVG0dacZJuaz2ntwlzZUIBw==", + "requires": { + "bindings": "^1.2.1", + "inherits": "^2.0.3", + "nan": "^2.2.1", + "safe-buffer": "^5.1.0" + } + }, + "web3-utils": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.4.tgz", + "integrity": "sha512-+S86Ip+jqfIPQWvw2N/xBQq5JNqCO0dyvukGdJm8fEWHZbckT4WxSpHbx+9KLEWY4H4x9pUwnoRkK87pYyHfgQ==", + "requires": { + "bn.js": "4.11.8", + "eth-lib": "0.2.7", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + } + } + } + }, "@ethersproject/abi": { "version": "5.0.0-beta.142", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.0-beta.142.tgz", @@ -150,21 +655,71 @@ "integrity": "sha512-P2oZkSMMazvMB0OaOM9GJnmLzHHSeCKqOp9bPAAY/rb65ICdtNjQMRYhOwinBFabrdV2z5TKWpwA9KIBkI0rTg==", "dev": true, "requires": { - "@ethersproject/bytes": ">=5.0.0-beta.129", - "@ethersproject/logger": ">=5.0.0-beta.129" + "@ethersproject/bytes": ">=5.0.0-beta.129", + "@ethersproject/logger": ">=5.0.0-beta.129" + } + }, + "@ethersproject/strings": { + "version": "5.0.0-beta.136", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz", + "integrity": "sha512-Hb9RvTrgGcOavHvtQZz+AuijB79BO3g1cfF2MeMfCU9ID4j3mbZv/olzDMS2pK9r4aERJpAS94AmlWzCgoY2LQ==", + "dev": true, + "requires": { + "@ethersproject/bytes": ">=5.0.0-beta.129", + "@ethersproject/constants": ">=5.0.0-beta.128", + "@ethersproject/logger": ">=5.0.0-beta.129" + } + }, + "@ledgerhq/devices": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-5.13.0.tgz", + "integrity": "sha512-jx3qX4dOkJpOL/TlnuzAwVcOm/IDCFvhXvfIAxu7F9dhafHDqYP0+8uHKBeJtkLyA4wd63SbHXVo16xmsAHp4Q==", + "requires": { + "@ledgerhq/errors": "^5.13.0", + "@ledgerhq/logs": "^5.13.0", + "rxjs": "^6.5.5" + }, + "dependencies": { + "rxjs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@ledgerhq/errors": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-5.13.0.tgz", + "integrity": "sha512-I+13snTaDZQbhnbxe3Hwud3bkmDqDSe/s8z0dzkhbchFdXvmtp77IbQrbJZ2m4L5W2bOBHAhv6Dz2SZv5Ll/VA==" + }, + "@ledgerhq/hw-app-eth": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-5.13.0.tgz", + "integrity": "sha512-1Ru/ke/eK2mNDAF1Sk0QHogba5ta/nqX0E0FJyhW4D6mOxo3q8baJzK4sIb8UAEV5K2hF2dttG9awnUfNmiT7Q==", + "requires": { + "@ledgerhq/errors": "^5.13.0", + "@ledgerhq/hw-transport": "^5.13.0", + "bignumber.js": "^9.0.0" } }, - "@ethersproject/strings": { - "version": "5.0.0-beta.136", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz", - "integrity": "sha512-Hb9RvTrgGcOavHvtQZz+AuijB79BO3g1cfF2MeMfCU9ID4j3mbZv/olzDMS2pK9r4aERJpAS94AmlWzCgoY2LQ==", - "dev": true, + "@ledgerhq/hw-transport": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-5.13.0.tgz", + "integrity": "sha512-IUwmW3YTULWZyuw5JNgGCTmPZs80XJRq5vLi6nH53Ouvgz1yVy+ktNALOcaOxyR2WRA1flwRTRiss/OtJrCDjQ==", "requires": { - "@ethersproject/bytes": ">=5.0.0-beta.129", - "@ethersproject/constants": ">=5.0.0-beta.128", - "@ethersproject/logger": ">=5.0.0-beta.129" + "@ledgerhq/devices": "^5.13.0", + "@ledgerhq/errors": "^5.13.0", + "events": "^3.1.0" } }, + "@ledgerhq/logs": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-5.13.0.tgz", + "integrity": "sha512-yMvzQiMjWDMRma3HPxQQibhvEqMaEdXXkNBk1+eaW+N47Y3neYSSyCJlyihzzMBeeoin4ChlP/5uKQaABwBeTg==" + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -194,9 +749,57 @@ "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, + "@solidity-parser/parser": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.5.2.tgz", + "integrity": "sha512-uRyvnvVYmgNmTBpWDbBsH/0kPESQhQpEc4KsvMRLVzFJ1o1s0uIv0Y6Y9IB5vI1Dwz2CbS4X/y4Wyw/75cTFnQ==", "dev": true }, + "@stablelib/binary": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-0.7.2.tgz", + "integrity": "sha1-GzOSFwyKh0HIuPhD6ilN5xrrLPc=", + "requires": { + "@stablelib/int": "^0.5.0" + } + }, + "@stablelib/blake2s": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@stablelib/blake2s/-/blake2s-0.10.4.tgz", + "integrity": "sha512-IasdklC7YfXXLmVbnsxqmd66+Ki+Ysbp0BtcrNxAtrGx/HRGjkUZbSTbEa7HxFhBWIstJRcE5ExgY+RCqAiULQ==", + "requires": { + "@stablelib/binary": "^0.7.2", + "@stablelib/hash": "^0.5.0", + "@stablelib/wipe": "^0.5.0" + } + }, + "@stablelib/blake2xs": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@stablelib/blake2xs/-/blake2xs-0.10.4.tgz", + "integrity": "sha512-1N0S4cruso/StV9TmoujPGj3RU0Cy42wlZneBWLWby7m2ssnY57l/CsYQSm03TshOoYss4hqc5kwSy5pmWAdUA==", + "requires": { + "@stablelib/blake2s": "^0.10.4", + "@stablelib/hash": "^0.5.0", + "@stablelib/wipe": "^0.5.0" + } + }, + "@stablelib/hash": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@stablelib/hash/-/hash-0.5.0.tgz", + "integrity": "sha1-if6QQKPUODsZIcfYpglIvDCEYGg=" + }, + "@stablelib/int": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@stablelib/int/-/int-0.5.0.tgz", + "integrity": "sha1-zKkiWVHVXS3khlZ1V4R4hjNmDCs=" + }, + "@stablelib/wipe": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-0.5.0.tgz", + "integrity": "sha1-poLV+USOlQ4JnlN+b3L8lgJ10VE=" + }, "@summa-tx/bitcoin-spv-sol": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@summa-tx/bitcoin-spv-sol/-/bitcoin-spv-sol-2.2.0.tgz", @@ -206,7 +809,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, "requires": { "defer-to-connect": "^1.0.1" } @@ -230,9 +832,9 @@ }, "dependencies": { "@types/node": { - "version": "12.12.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz", - "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==", + "version": "12.12.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.35.tgz", + "integrity": "sha512-ASYsaKecA7TUsDrqIGPNk3JeEox0z/0XR/WsJJ8BIX/9+SkMSImQXKWfU/yBrSyc7ZSE/NPqLu36Nur0miCFfQ==", "dev": true }, "bn.js": { @@ -270,9 +872,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.18.tgz", - "integrity": "sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg==", + "version": "10.17.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.19.tgz", + "integrity": "sha512-46/xThm3zvvc9t9/7M3AaLEqtOpqlYYYcCZbpYVAQHG20+oMZBkae/VMrn4BTi6AJ8cpack0mEXhGiKmDNbLrQ==", "dev": true } } @@ -382,9 +984,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.18.tgz", - "integrity": "sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg==", + "version": "10.17.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.19.tgz", + "integrity": "sha512-46/xThm3zvvc9t9/7M3AaLEqtOpqlYYYcCZbpYVAQHG20+oMZBkae/VMrn4BTi6AJ8cpack0mEXhGiKmDNbLrQ==", "dev": true }, "ethers": { @@ -576,11 +1178,18 @@ "web3": "1.2.1" } }, + "@types/bignumber.js": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-0DH7aPGCClywOFaxxjE6UwpN2kQYe9LwuDQMv+zYA97j5GkOMo8e66LYT+a8JYU7jfmUFRZLa9KycxHDsKXJCA==", + "requires": { + "bignumber.js": "*" + } + }, "@types/bn.js": { "version": "4.11.5", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.5.tgz", "integrity": "sha512-AEAZcIZga0JgVMHNtl1CprA/hXX7/wPt79AgR4XqaDt7jyj3QWYw6LPoOiznPtugDmlubUnAahMs2PFxGcQrng==", - "dev": true, "requires": { "@types/node": "*" } @@ -600,6 +1209,11 @@ "@types/node": "*" } }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -635,8 +1249,7 @@ "@types/node": { "version": "10.14.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.18.tgz", - "integrity": "sha512-ryO3Q3++yZC/+b8j8BdKd/dn9JlzlHBPdm80656xwYUdmPkpTGTjkAdt6BByiNupGPE8w0FhBgvYy/fX9hRNGQ==", - "dev": true + "integrity": "sha512-ryO3Q3++yZC/+b8j8BdKd/dn9JlzlHBPdm80656xwYUdmPkpTGTjkAdt6BByiNupGPE8w0FhBgvYy/fX9hRNGQ==" }, "@types/qs": { "version": "6.9.1", @@ -644,11 +1257,14 @@ "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==", "dev": true }, + "@umpirsky/country-list": { + "version": "git://github.com/umpirsky/country-list.git#05fda51cd97b3294e8175ffed06104c44b3c71d7", + "from": "git://github.com/umpirsky/country-list.git#05fda51" + }, "@web3-js/scrypt-shim": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@web3-js/scrypt-shim/-/scrypt-shim-0.1.0.tgz", "integrity": "sha512-ZtZeWCc/s0nMcdx/+rZwY1EcuRdemOK9ag21ty9UsHkFxsNb/AaoucUz0iPuyGe0Ku+PFuRmWZG7Z7462p9xPw==", - "dev": true, "requires": { "scryptsy": "^2.1.0", "semver": "^6.3.0" @@ -658,7 +1274,6 @@ "version": "1.0.30", "resolved": "https://registry.npmjs.org/@web3-js/websocket/-/websocket-1.0.30.tgz", "integrity": "sha512-fDwrD47MiDrzcJdSeTLF75aCcxVVt8B1N74rA+vh2XCAvFy4tEWJjtnUtj2QG7/zlQ6g9cQ88bZFBxwd9/FmtA==", - "dev": true, "requires": { "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -671,7 +1286,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -679,14 +1293,12 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" } } }, @@ -700,7 +1312,6 @@ "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, "requires": { "mime-types": "~2.1.24", "negotiator": "0.6.2" @@ -727,14 +1338,12 @@ "aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=", - "dev": true + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -749,6 +1358,11 @@ "dev": true, "optional": true }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==" + }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -769,14 +1383,12 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -784,8 +1396,7 @@ "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, "anymatch": { "version": "1.3.2", @@ -797,11 +1408,15 @@ "normalize-path": "^2.0.0" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -830,8 +1445,7 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-includes": { "version": "3.1.1", @@ -1038,7 +1652,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -1047,7 +1660,6 @@ "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -1057,16 +1669,19 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, "assign-symbols": { "version": "1.0.0", @@ -1095,14 +1710,12 @@ "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", @@ -1113,14 +1726,12 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axios": { "version": "0.19.2", @@ -1134,8 +1745,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -1207,18 +1817,31 @@ "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, "requires": { "tweetnacl": "^0.14.3" } }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" + }, + "bigi": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", + "integrity": "sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU=" + }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -1229,7 +1852,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, "requires": { "file-uri-to-path": "1.0.0" } @@ -1238,7 +1860,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -1247,17 +1868,221 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "dev": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" } }, + "bls12377js": { + "version": "git+https://github.com/celo-org/bls12377js.git#400bcaeec9e7620b040bfad833268f5289699cac", + "from": "git+https://github.com/celo-org/bls12377js.git#400bcaeec9e7620b040bfad833268f5289699cac", + "requires": { + "@stablelib/blake2xs": "0.10.4", + "@types/node": "^12.11.7", + "big-integer": "^1.6.44", + "chai": "^4.2.0", + "mocha": "^6.2.2", + "ts-node": "^8.4.1", + "typescript": "^3.6.4" + }, + "dependencies": { + "@types/node": { + "version": "12.12.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.35.tgz", + "integrity": "sha512-ASYsaKecA7TUsDrqIGPNk3JeEox0z/0XR/WsJJ8BIX/9+SkMSImQXKWfU/yBrSyc7ZSE/NPqLu36Nur0miCFfQ==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.4", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "bluebird": { "version": "3.5.5", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", - "dev": true + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" }, "bn.js": { "version": "5.1.1", @@ -1268,7 +2093,6 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, "requires": { "bytes": "3.1.0", "content-type": "~1.0.4", @@ -1286,7 +2110,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -1294,14 +2117,12 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" } } }, @@ -1309,7 +2130,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1329,20 +2149,17 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -1356,7 +2173,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, "requires": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", @@ -1367,7 +2183,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", @@ -1379,7 +2194,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" @@ -1388,8 +2202,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, @@ -1397,7 +2210,6 @@ "version": "0.0.4", "resolved": "https://registry.npmjs.org/browserify-sha3/-/browserify-sha3-0.0.4.tgz", "integrity": "sha1-CGxHuMgjFsnUcCLCYYWVRXbdjiY=", - "dev": true, "requires": { "js-sha3": "^0.6.1", "safe-buffer": "^5.1.1" @@ -1406,8 +2218,7 @@ "js-sha3": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.6.1.tgz", - "integrity": "sha1-W4n3enR3Z5h39YxKB1JAk0sflcA=", - "dev": true + "integrity": "sha1-W4n3enR3Z5h39YxKB1JAk0sflcA=" } } }, @@ -1415,7 +2226,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, "requires": { "bn.js": "^4.1.1", "browserify-rsa": "^4.0.0", @@ -1429,8 +2239,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, @@ -1438,7 +2247,6 @@ "version": "5.4.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", - "dev": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" @@ -1448,7 +2256,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", "buffer-fill": "^1.0.0" @@ -1457,44 +2264,42 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-reverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz", + "integrity": "sha1-SSg8jvpvkBvAH6MwTQYCeXGuL2A=" }, "buffer-to-arraybuffer": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", - "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=", - "dev": true + "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=" }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "cache-base": { "version": "1.0.1", @@ -1525,7 +2330,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -1540,7 +2344,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -1548,14 +2351,12 @@ "http-cache-semantics": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", - "integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==", - "dev": true + "integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==" }, "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" } } }, @@ -1568,20 +2369,30 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1600,6 +2411,11 @@ "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", "dev": true }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" + }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -1646,14 +2462,12 @@ "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -1793,7 +2607,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -1824,7 +2637,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -1832,8 +2644,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.3.3", @@ -1845,7 +2656,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -1865,8 +2675,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -1896,7 +2705,6 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, "requires": { "safe-buffer": "5.1.2" }, @@ -1904,34 +2712,29 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "cookie": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" }, "copy-descriptor": { "version": "0.1.1", @@ -1942,24 +2745,30 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, "requires": { "object-assign": "^4", "vary": "^1" } }, + "country-data": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/country-data/-/country-data-0.0.31.tgz", + "integrity": "sha1-gJZrjh0Uf6bWpYnTKTP4eTd0lW0=", + "requires": { + "currency-symbol-map": "~2", + "underscore": ">1.4.4" + } + }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", - "dev": true, "requires": { "bn.js": "^4.1.0", "elliptic": "^6.0.0" @@ -1968,8 +2777,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, @@ -1977,7 +2785,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -1990,7 +2797,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -2000,6 +2806,15 @@ "sha.js": "^2.4.8" } }, + "cross-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz", + "integrity": "sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==", + "requires": { + "node-fetch": "2.6.0", + "whatwg-fetch": "3.0.0" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -2031,7 +2846,6 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, "requires": { "browserify-cipher": "^1.0.0", "browserify-sign": "^4.0.0", @@ -2046,11 +2860,20 @@ "randomfill": "^1.0.3" } }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, + "currency-symbol-map": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/currency-symbol-map/-/currency-symbol-map-2.2.0.tgz", + "integrity": "sha1-KzwYcv8aws5ZXYJz5Y4f/wJyrqI=" + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, "requires": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -2060,7 +2883,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -2075,7 +2897,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, "requires": { "ms": "^2.1.1" } @@ -2083,20 +2904,17 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", - "dev": true, "requires": { "decompress-tar": "^4.0.0", "decompress-tarbz2": "^4.0.0", @@ -2112,7 +2930,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -2121,7 +2938,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", - "dev": true, "requires": { "file-type": "^5.2.0", "is-stream": "^1.1.0", @@ -2132,7 +2948,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", - "dev": true, "requires": { "decompress-tar": "^4.1.0", "file-type": "^6.1.0", @@ -2144,8 +2959,7 @@ "file-type": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", - "dev": true + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==" } } }, @@ -2153,7 +2967,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", - "dev": true, "requires": { "decompress-tar": "^4.1.1", "file-type": "^5.2.0", @@ -2164,7 +2977,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", - "dev": true, "requires": { "file-type": "^3.8.0", "get-stream": "^2.2.0", @@ -2175,14 +2987,12 @@ "file-type": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" }, "get-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", - "dev": true, "requires": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" @@ -2190,6 +3000,14 @@ } } }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -2199,14 +3017,12 @@ "defer-to-connect": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.0.2.tgz", - "integrity": "sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw==", - "dev": true + "integrity": "sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw==" }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -2273,20 +3089,17 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true, "requires": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" @@ -2295,8 +3108,7 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detect-port": { "version": "1.3.0", @@ -2328,14 +3140,12 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, "requires": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", @@ -2345,8 +3155,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, @@ -2379,8 +3188,7 @@ "dom-walk": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", - "dev": true + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" }, "dotenv": { "version": "8.2.0", @@ -2391,7 +3199,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=", - "dev": true, "requires": { "browserify-aes": "^1.0.6", "create-hash": "^1.1.2", @@ -2401,14 +3208,12 @@ "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -2417,14 +3222,12 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "elliptic": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", "integrity": "sha1-VILZZG1UvLif19mU/J4ulWiHbj8=", - "dev": true, "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -2435,28 +3238,24 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -2480,7 +3279,6 @@ "version": "1.14.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz", "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==", - "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", @@ -2498,7 +3296,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -2509,7 +3306,6 @@ "version": "0.10.51", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", - "dev": true, "requires": { "es6-iterator": "~2.0.3", "es6-symbol": "~3.1.1", @@ -2520,7 +3316,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -2531,7 +3326,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz", "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==", - "dev": true, "requires": { "d": "^1.0.1", "es5-ext": "^0.10.51" @@ -2552,14 +3346,12 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.8.1", @@ -2818,8 +3610,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.2.0", @@ -2862,14 +3653,12 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "eth-ens-namehash": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", "integrity": "sha1-IprEbsqG1S4MmR58sq74P/D2i88=", - "dev": true, "requires": { "idna-uts46-hx": "^2.3.1", "js-sha3": "^0.5.7" @@ -2948,7 +3737,6 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", - "dev": true, "requires": { "bn.js": "^4.11.6", "elliptic": "^6.4.0", @@ -2958,14 +3746,12 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "elliptic": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", - "dev": true, "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -2982,7 +3768,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.6.tgz", "integrity": "sha512-dE9CGNzgOOsdh7msZirvv8qjHtnHpvBlKe2647kM8v+yeF71IRso55jpojemvHV+jMjr48irPWxMRaHuOWzAFA==", - "dev": true, "requires": { "js-sha3": "^0.8.0" }, @@ -2990,22 +3775,19 @@ "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" } } }, "ethereumjs-common": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/ethereumjs-common/-/ethereumjs-common-1.5.0.tgz", - "integrity": "sha512-SZOjgK1356hIY7MRj3/ma5qtfr/4B5BL+G4rP/XSMYr2z1H5el4RX5GReYCKmQmYI/nSBmRnwrZ17IfHuG0viQ==", - "dev": true + "integrity": "sha512-SZOjgK1356hIY7MRj3/ma5qtfr/4B5BL+G4rP/XSMYr2z1H5el4RX5GReYCKmQmYI/nSBmRnwrZ17IfHuG0viQ==" }, "ethereumjs-tx": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz", "integrity": "sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==", - "dev": true, "requires": { "ethereumjs-common": "^1.5.0", "ethereumjs-util": "^6.0.0" @@ -3015,7 +3797,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.0.tgz", "integrity": "sha512-vb0XN9J2QGdZGIEKG2vXM+kUdEivUfU6Wmi5y0cg+LRhDYKnXIZ/Lz7XjFbHRR9VIKq2lVGLzGBkA++y2nOdOQ==", - "dev": true, "requires": { "@types/bn.js": "^4.11.3", "bn.js": "^4.11.0", @@ -3029,8 +3810,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, @@ -3084,7 +3864,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", "integrity": "sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk=", - "dev": true, "requires": { "bn.js": "4.11.6", "number-to-bn": "1.7.0" @@ -3093,8 +3872,7 @@ "bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", - "dev": true + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" } } }, @@ -3102,7 +3880,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", - "dev": true, "requires": { "is-hex-prefixed": "1.0.0", "strip-hex-prefix": "1.0.0" @@ -3121,14 +3898,17 @@ "eventemitter3": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "dev": true + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, + "events": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==" }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -3206,7 +3986,6 @@ "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dev": true, "requires": { "accepts": "~1.3.7", "array-flatten": "1.1.1", @@ -3244,7 +4023,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -3252,28 +4030,24 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -3327,14 +4101,12 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-glob": { "version": "3.2.2", @@ -3398,8 +4170,7 @@ "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", @@ -3420,7 +4191,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, "requires": { "pend": "~1.2.0" } @@ -3446,14 +4216,12 @@ "file-type": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=" }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "filename-regex": { "version": "2.0.1", @@ -3478,7 +4246,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -3493,7 +4260,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -3501,8 +4267,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -3515,6 +4280,21 @@ "locate-path": "^2.0.0" } }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "requires": { + "is-buffer": "~2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + } + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -3562,7 +4342,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "requires": { "is-callable": "^1.1.3" } @@ -3585,14 +4364,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -3602,8 +4379,12 @@ "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fp-ts": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.1.1.tgz", + "integrity": "sha512-YcWhMdDCFCja0MmaDroTgNu+NWWrrnUEn92nvDgrtVy9Z71YFnhNVIghoHPt8gs82ijoMzFGeWKvArbyICiJgw==" }, "fragment-cache": { "version": "0.2.1", @@ -3617,20 +4398,17 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -3641,7 +4419,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, "requires": { "minipass": "^2.6.0" } @@ -3655,8 +4432,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.12", @@ -4212,8 +4988,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -4221,6 +4996,11 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "futoin-hkdf": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.3.2.tgz", + "integrity": "sha512-3EVi3ETTyJg5PSXlxLCaUVVn0pSbDf62L3Gwxne7Uq+d8adOSNWQAad4gg7WToHkcgnCJb3Wlb1P8r4Evj4GPw==" + }, "ganache-cli": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/ganache-cli/-/ganache-cli-6.9.0.tgz", @@ -4900,6 +5680,11 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" + }, "get-port": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", @@ -4910,7 +5695,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -4925,7 +5709,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -5003,7 +5786,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", - "dev": true, "requires": { "min-document": "^2.19.0", "process": "~0.5.1" @@ -5078,11 +5860,15 @@ } } }, + "google-libphonenumber": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.8.tgz", + "integrity": "sha512-iWs1KcxOozmKQbCeGjvU0M7urrkNjBYOSBtb819RjkUNJHJLfn7DADKkKwdJTOMPLcLOE11/4h/FyFwJsTiwLg==" + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, "requires": { "@sindresorhus/is": "^0.14.0", "@szmarczak/http-timer": "^1.1.2", @@ -5100,169 +5886,48 @@ "graceful-fs": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" }, "graceful-readlink": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" }, "handlebars": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.5.tgz", - "integrity": "sha512-PiM2ZRLZ0X+CIRSX66u7tkQi3rzrlSHAuioMBI1XP8DsfDaXEA+sD7Iyyoz4QACFuhX5z+IimN+n3BFWvvgWrQ==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", "dev": true, "requires": { + "minimist": "^1.2.5", "neo-async": "^2.6.0", "source-map": "^0.6.1", "uglify-js": "^3.1.4", - "yargs": "^14.2.3" + "wordwrap": "^1.0.0" }, "dependencies": { - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", - "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^15.0.1" - } - }, - "yargs-parser": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", - "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -5272,7 +5937,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -5280,26 +5944,22 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbol-support-x": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" }, "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-to-string-tag-x": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "dev": true, "requires": { "has-symbol-support-x": "^1.4.1" } @@ -5368,7 +6028,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -5378,7 +6037,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.0" @@ -5394,7 +6052,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -5423,7 +6080,6 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -5435,16 +6091,14 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "http-https": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", - "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=", - "dev": true + "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" }, "http-response-object": { "version": "3.0.2", @@ -5459,7 +6113,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -5470,7 +6123,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -5479,7 +6131,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", - "dev": true, "requires": { "punycode": "2.1.0" }, @@ -5487,16 +6138,14 @@ "punycode": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", - "dev": true + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=" } } }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "ignore": { "version": "4.0.6", @@ -5524,7 +6173,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -5533,8 +6181,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -5665,11 +6312,15 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "io-ts": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.0.1.tgz", + "integrity": "sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ==" + }, "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", - "dev": true + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" }, "is-accessor-descriptor": { "version": "0.1.6", @@ -5704,8 +6355,7 @@ "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-data-descriptor": { "version": "0.1.4", @@ -5719,8 +6369,7 @@ "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-descriptor": { "version": "0.1.6", @@ -5771,14 +6420,12 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-function": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", - "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=", - "dev": true + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" }, "is-glob": { "version": "4.0.1", @@ -5792,14 +6439,12 @@ "is-hex-prefixed": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=", - "dev": true + "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=" }, "is-natural-number": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", - "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", - "dev": true + "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=" }, "is-number": { "version": "2.1.0", @@ -5813,14 +6458,12 @@ "is-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" }, "is-plain-object": { "version": "2.0.4", @@ -5861,7 +6504,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, "requires": { "has": "^1.0.1" } @@ -5869,14 +6511,12 @@ "is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-string": { "version": "1.0.5", @@ -5888,7 +6528,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, "requires": { "has-symbols": "^1.0.0" } @@ -5896,8 +6535,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-windows": { "version": "1.0.2", @@ -5908,14 +6546,12 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "2.1.0", @@ -5929,14 +6565,12 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "isurl": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "dev": true, "requires": { "has-to-string-tag-x": "^1.2.0", "is-object": "^1.0.1" @@ -5945,8 +6579,7 @@ "js-sha3": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=", - "dev": true + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" }, "js-string-escape": { "version": "1.0.1", @@ -5964,7 +6597,6 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -5973,26 +6605,22 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -6003,14 +6631,12 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, "requires": { "graceful-fs": "^4.1.6" } @@ -6025,7 +6651,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -6037,7 +6662,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/keccak/-/keccak-2.1.0.tgz", "integrity": "sha512-m1wbJRTo+gWbctZWay9i26v5fFnYkOn7D5PCxJ3fZUGUEb49dE1Pm4BREUYCt/aoO6di7jeoGmhvqN9Nzylm3Q==", - "dev": true, "requires": { "bindings": "^1.5.0", "inherits": "^2.0.4", @@ -6048,8 +6672,34 @@ "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + } + } + }, + "keccak256": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.0.tgz", + "integrity": "sha512-8qv2vJdQk+Aa2tFXo8zYodm+6DgXqUOqvNJhj1p1V2pxQJT1oNKxNF+zWfhtKXNLZdLvyxjB/dvd9GwcvTHSQQ==", + "requires": { + "bn.js": "^4.11.8", + "keccak": "^1.4.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "keccak": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz", + "integrity": "sha512-eZVaCpblK5formjPjeTBik7TAg+pqnDrMHIffSvi9Lh7PQgM1+hSzakUeZFCk9DVVG0dacZJuaz2ntwlzZUIBw==", + "requires": { + "bindings": "^1.2.1", + "inherits": "^2.0.3", + "nan": "^2.2.1", + "safe-buffer": "^5.1.0" + } } } }, @@ -6057,7 +6707,6 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/keccakjs/-/keccakjs-0.2.3.tgz", "integrity": "sha512-BjLkNDcfaZ6l8HBG9tH0tpmDv3sS2mA7FNQxFHpCdzP3Gb2MVruXBSuoM66SnVxKJpAr5dKGdkHD+bDokt8fTg==", - "dev": true, "requires": { "browserify-sha3": "^0.0.4", "sha3": "^1.2.2" @@ -6067,7 +6716,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, "requires": { "json-buffer": "3.0.0" } @@ -6125,8 +6773,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.toarray": { "version": "4.4.0", @@ -6134,11 +6781,18 @@ "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", "dev": true }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "requires": { + "chalk": "^2.0.1" + } + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lru-queue": { "version": "0.1.0", @@ -6153,7 +6807,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, "requires": { "pify": "^3.0.0" }, @@ -6161,11 +6814,15 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -6197,7 +6854,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -6207,8 +6863,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { "version": "1.1.0", @@ -6238,8 +6893,7 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, "merge2": { "version": "1.3.0", @@ -6250,8 +6904,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { "version": "2.3.11", @@ -6295,7 +6948,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" @@ -6304,28 +6956,24 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "dev": true + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "dev": true, "requires": { "mime-db": "1.40.0" } @@ -6339,14 +6987,12 @@ "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "dev": true, "requires": { "dom-walk": "^0.1.0" } @@ -6354,20 +7000,17 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6375,14 +7018,12 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6392,7 +7033,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, "requires": { "minipass": "^2.9.0" } @@ -6421,14 +7061,12 @@ "mkdirp": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", - "dev": true + "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==" }, "mkdirp-promise": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", "integrity": "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=", - "dev": true, "requires": { "mkdirp": "*" } @@ -6510,14 +7148,12 @@ "mock-fs": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.10.1.tgz", - "integrity": "sha512-w22rOL5ZYu6HbUehB5deurghGM0hS/xBVyHMGKOuQctkk93J9z9VEOhDsiWrXOprVNQpP9uzGKdl8v9mFspKuw==", - "dev": true + "integrity": "sha512-w22rOL5ZYu6HbUehB5deurghGM0hS/xBVyHMGKOuQctkk93J9z9VEOhDsiWrXOprVNQpP9uzGKdl8v9mFspKuw==" }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mute-stream": { "version": "0.0.8", @@ -6528,14 +7164,12 @@ "nan": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", - "dev": true + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" }, "nano-json-stream-parser": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", - "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=", - "dev": true + "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=" }, "nanomatch": { "version": "1.2.13", @@ -6585,8 +7219,7 @@ "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "neo-async": { "version": "2.6.1", @@ -6597,8 +7230,7 @@ "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, "nice-try": { "version": "1.0.5", @@ -6615,6 +7247,27 @@ "lodash.toarray": "^4.4.0" } }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -6656,8 +7309,7 @@ "normalize-url": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.3.0.tgz", - "integrity": "sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ==", - "dev": true + "integrity": "sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ==" }, "npm-run-path": { "version": "2.0.2", @@ -6678,7 +7330,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=", - "dev": true, "requires": { "bn.js": "4.11.6", "strip-hex-prefix": "1.0.0" @@ -6687,22 +7338,24 @@ "bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", - "dev": true + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" } } }, + "numeral": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", + "integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY=" + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -6729,14 +7382,12 @@ "object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", - "dev": true + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -6759,7 +7410,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -6779,6 +7429,88 @@ "has": "^1.0.3" } }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + } + } + }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", @@ -6903,7 +7635,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.4.tgz", "integrity": "sha1-IMiM2wwVNxuwQRklfU/dNLCqSfY=", - "dev": true, "requires": { "http-https": "^1.0.0" } @@ -6912,7 +7643,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, "requires": { "ee-first": "1.1.1" } @@ -6921,7 +7651,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -6977,14 +7706,12 @@ "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { "version": "1.3.0", @@ -7008,7 +7735,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "dev": true, "requires": { "p-finally": "^1.0.0" } @@ -7032,7 +7758,6 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", - "dev": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", @@ -7081,7 +7806,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.2.tgz", "integrity": "sha512-/LypJhzFmyBIDYP9aDVgeyEb5sQfbfY5mnDq4hVhlQ69js87wXfmEI5V3xI6vvXasqebp0oCytYFLxsBVfCzSg==", - "dev": true, "requires": { "for-each": "^0.3.3", "string.prototype.trim": "^1.1.2" @@ -7099,8 +7823,7 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "pascalcase": { "version": "0.1.1", @@ -7111,14 +7834,12 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "2.0.1", @@ -7135,8 +7856,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "path-type": { "version": "2.0.0", @@ -7147,11 +7867,15 @@ "pify": "^2.0.0" } }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" + }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", - "dev": true, "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -7169,14 +7893,12 @@ "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picomatch": { "version": "2.2.2", @@ -7187,20 +7909,17 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "^2.0.0" } @@ -7229,8 +7948,7 @@ "prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" }, "preserve": { "version": "0.2.0", @@ -7241,14 +7959,12 @@ "process": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", - "dev": true + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -7269,7 +7985,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", - "dev": true, "requires": { "forwarded": "~0.1.2", "ipaddr.js": "1.9.0" @@ -7284,14 +7999,12 @@ "psl": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", - "dev": true + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", @@ -7304,8 +8017,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, @@ -7313,7 +8025,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -7322,20 +8033,17 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dev": true, "requires": { "decode-uri-component": "^0.2.0", "object-assign": "^4.1.0", @@ -7377,7 +8085,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -7386,7 +8093,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, "requires": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" @@ -7401,14 +8107,12 @@ "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, "requires": { "bytes": "3.1.0", "http-errors": "1.7.2", @@ -7441,7 +8145,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7455,8 +8158,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -7853,7 +8555,6 @@ "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -7880,8 +8581,7 @@ "uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" } } }, @@ -7908,8 +8608,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "1.0.1", @@ -7942,7 +8641,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, "requires": { "lowercase-keys": "^1.0.0" } @@ -7982,7 +8680,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -7992,7 +8689,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.4.tgz", "integrity": "sha512-fdq2yYCWpAQBhwkZv+Z8o/Z4sPmYm1CUq6P7n6lVTOdb949CnqA0sndXal5C1NleSVSZm6q5F3iEbauyVln/iw==", - "dev": true, "requires": { "bn.js": "^4.11.1" }, @@ -8000,8 +8696,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" } } }, @@ -8032,8 +8727,7 @@ "safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" }, "safe-regex": { "version": "1.1.0", @@ -8047,8 +8741,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sc-istanbul": { "version": "0.4.5", @@ -8098,9 +8791,9 @@ "dev": true }, "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -8126,8 +8819,7 @@ "scrypt-js": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.3.tgz", - "integrity": "sha1-uwBAvgMEPamgEqLOqfyfhSz8h9Q=", - "dev": true + "integrity": "sha1-uwBAvgMEPamgEqLOqfyfhSz8h9Q=" }, "scrypt-shim": { "version": "github:web3-js/scrypt-shim#be5e616323a8b5e568788bf94d03c1b8410eac54", @@ -8141,14 +8833,12 @@ "scryptsy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", - "dev": true + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" }, "secp256k1": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.0.tgz", "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==", - "dev": true, "requires": { "bindings": "^1.5.0", "bip66": "^1.1.5", @@ -8163,14 +8853,12 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "elliptic": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", - "dev": true, "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -8184,8 +8872,7 @@ "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" } } }, @@ -8193,7 +8880,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", - "dev": true, "requires": { "commander": "~2.8.1" }, @@ -8202,7 +8888,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", - "dev": true, "requires": { "graceful-readlink": ">= 1.0.0" } @@ -8212,14 +8897,12 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dev": true, "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -8240,7 +8923,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" }, @@ -8248,16 +8930,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, @@ -8265,7 +8945,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -8277,7 +8956,6 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", - "dev": true, "requires": { "body-parser": "^1.16.0", "cors": "^2.8.1", @@ -8289,8 +8967,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.1", @@ -8318,20 +8995,17 @@ "setimmediate": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", - "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=", - "dev": true + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -8351,7 +9025,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/sha3/-/sha3-1.2.3.tgz", "integrity": "sha512-sOWDZi8cDBRkLfWOw18wvJyNblXDHzwMGnRWut8zNNeIeLnmMRO17bjpLc7OzMuj1ASUgx2IyohzUCAl+Kx5vA==", - "dev": true, "requires": { "nan": "2.13.2" } @@ -8391,14 +9064,12 @@ "simple-concat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", - "dev": true + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" }, "simple-get": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", - "dev": true, "requires": { "decompress-response": "^3.3.0", "once": "^1.3.1", @@ -8592,11 +9263,12 @@ } }, "solidity-coverage": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.7.2.tgz", - "integrity": "sha512-LizgpcrkMRmZdvOmwYN7l/h9ak0Yq/5BuXjEaH0Phds1Gu5hZfHGyvplgWYDFmzLwGB7hZ6d0/IdkdYm4HK0mg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.7.4.tgz", + "integrity": "sha512-eaYjKLJK7n+uAN9Xrm2kDujXOkxxBbHfNrjK+ZabU1DR4QWSVQiwDDF2JLciN943kkZdd2JOzJGp32NxCbdCWA==", "dev": true, "requires": { + "@solidity-parser/parser": "^0.5.2", "@truffle/provider": "^0.1.17", "chalk": "^2.4.2", "death": "^1.1.0", @@ -8613,14 +9285,13 @@ "recursive-readdir": "^2.2.2", "sc-istanbul": "^0.4.5", "shelljs": "^0.8.3", - "solidity-parser-diligence": "^0.4.16", "web3": "1.2.6" }, "dependencies": { "@types/node": { - "version": "12.12.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz", - "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==", + "version": "12.12.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.35.tgz", + "integrity": "sha512-ASYsaKecA7TUsDrqIGPNk3JeEox0z/0XR/WsJJ8BIX/9+SkMSImQXKWfU/yBrSyc7ZSE/NPqLu36Nur0miCFfQ==", "dev": true }, "bn.js": { @@ -8648,9 +9319,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.18.tgz", - "integrity": "sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg==", + "version": "10.17.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.19.tgz", + "integrity": "sha512-46/xThm3zvvc9t9/7M3AaLEqtOpqlYYYcCZbpYVAQHG20+oMZBkae/VMrn4BTi6AJ8cpack0mEXhGiKmDNbLrQ==", "dev": true } } @@ -8701,9 +9372,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.18.tgz", - "integrity": "sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg==", + "version": "10.17.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.19.tgz", + "integrity": "sha512-46/xThm3zvvc9t9/7M3AaLEqtOpqlYYYcCZbpYVAQHG20+oMZBkae/VMrn4BTi6AJ8cpack0mEXhGiKmDNbLrQ==", "dev": true } } @@ -9204,6 +9875,22 @@ "urix": "^0.1.0" } }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", @@ -9254,14 +9941,12 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -9298,8 +9983,7 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stealthy-require": { "version": "1.1.1", @@ -9310,14 +9994,12 @@ "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -9327,7 +10009,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -9338,7 +10019,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz", "integrity": "sha512-9EIjYD/WdlvLpn987+ctkLf0FfvBefOCuiEr2henD8X+7jfwPnyvTdmW8OJhj5p+M0/96mBdynLWkxUr+rHlpg==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.13.0", @@ -9349,7 +10029,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -9359,7 +10038,6 @@ "version": "1.17.5", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -9378,7 +10056,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -9388,20 +10065,17 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "is-callable": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" }, "is-regex": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -9409,14 +10083,12 @@ "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" }, "string.prototype.trimleft": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5", @@ -9427,7 +10099,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5", @@ -9440,7 +10111,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", - "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -9450,7 +10120,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", - "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -9460,7 +10129,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -9470,7 +10138,6 @@ "version": "1.17.5", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -9489,7 +10156,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -9499,20 +10165,17 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "is-callable": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" }, "is-regex": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -9520,14 +10183,12 @@ "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" }, "string.prototype.trimleft": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5", @@ -9538,7 +10199,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5", @@ -9551,7 +10211,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" }, @@ -9559,8 +10218,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -9568,7 +10226,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, "requires": { "ansi-regex": "^4.1.0" }, @@ -9576,8 +10233,7 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" } } }, @@ -9591,7 +10247,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", - "dev": true, "requires": { "is-natural-number": "^4.0.1" } @@ -9606,7 +10261,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", - "dev": true, "requires": { "is-hex-prefixed": "1.0.0" } @@ -9621,7 +10275,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -9630,7 +10283,6 @@ "version": "0.1.39", "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.39.tgz", "integrity": "sha512-QLMqL2rzF6n5s50BptyD6Oi0R1aWlJC5Y17SRIVXRj6OR1DRIPM7nepvrxxkjA1zNzFz6mUOMjfeqeDaWB7OOg==", - "dev": true, "requires": { "bluebird": "^3.5.0", "buffer": "^5.0.5", @@ -9649,14 +10301,12 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "elliptic": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", - "dev": true, "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -9671,7 +10321,6 @@ "version": "0.1.27", "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.27.tgz", "integrity": "sha512-B8czsfkJYzn2UIEMwjc7Mbj+Cy72V+/OXH/tb44LV8jhrjizQJJ325xMOMyk3+ETa6r6oi0jsUY14+om8mQMWA==", - "dev": true, "requires": { "bn.js": "^4.11.6", "elliptic": "^6.4.0", @@ -9685,14 +10334,12 @@ "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "got": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "dev": true, "requires": { "decompress-response": "^3.2.0", "duplexer3": "^0.1.4", @@ -9713,26 +10360,22 @@ "p-cancelable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", - "dev": true + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, "requires": { "prepend-http": "^1.0.1" } @@ -9788,7 +10431,6 @@ "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", @@ -9803,7 +10445,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "dev": true, "requires": { "minimist": "^1.2.5" } @@ -9814,7 +10455,6 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "dev": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -9861,14 +10501,12 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, "timers-ext": { "version": "0.1.7", @@ -9892,8 +10530,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" }, "to-object-path": { "version": "0.3.0", @@ -9907,8 +10544,7 @@ "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" }, "to-regex": { "version": "3.0.2", @@ -9946,14 +10582,12 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -9962,8 +10596,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" } } }, @@ -9992,17 +10625,34 @@ "sol-merger": "2.0.1" } }, + "ts-node": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.2.tgz", + "integrity": "sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==", + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + } + } + }, "tslib": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", - "dev": true + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -10010,14 +10660,12 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/type/-/type-1.0.3.tgz", - "integrity": "sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==", - "dev": true + "integrity": "sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==" }, "type-check": { "version": "0.3.2", @@ -10028,11 +10676,15 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -10048,20 +10700,23 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" + }, "uglify-js": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", - "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.0.tgz", + "integrity": "sha512-j5wNQBWaql8gr06dOUrfaohHlscboQZ9B8sNsoK5o4sBjm7Ht9dxSbrMXyktQpA16Acaij8AcoozteaPYZON0g==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" + "commander": "~2.20.3" }, "dependencies": { "commander": { @@ -10070,27 +10725,18 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "optional": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true } } }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" }, "unbzip2-stream": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.0.tgz", "integrity": "sha512-kVx7CDAsdBSWVf404Mw7oI9i09w5/mTT/Ruk+RWa64PLYKvsAucLLFHvQtnvjeADM4ZizxrvG5SHnF4Te4T2Cg==", - "dev": true, "requires": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -10099,8 +10745,7 @@ "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", - "dev": true + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, "union-value": { "version": "1.0.1", @@ -10117,14 +10762,12 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unset-value": { "version": "1.0.0", @@ -10176,7 +10819,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -10191,7 +10833,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, "requires": { "prepend-http": "^2.0.0" } @@ -10199,14 +10840,12 @@ "url-set-query": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", - "integrity": "sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk=", - "dev": true + "integrity": "sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk=" }, "url-to-options": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" }, "use": { "version": "3.1.1", @@ -10217,26 +10856,22 @@ "utf8": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", - "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=", - "dev": true + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" }, "v8-compile-cache": { "version": "2.1.0", @@ -10257,14 +10892,12 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -10669,11 +11302,15 @@ } } }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -10681,8 +11318,15 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } }, "word-wrap": { "version": "1.2.3", @@ -10746,8 +11390,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", @@ -10773,7 +11416,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, "requires": { "async-limiter": "~1.0.0", "safe-buffer": "~5.1.0", @@ -10783,8 +11425,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -10792,7 +11433,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", - "dev": true, "requires": { "global": "~4.3.0", "is-function": "^1.0.1", @@ -10804,7 +11444,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", - "dev": true, "requires": { "buffer-to-arraybuffer": "^0.0.5", "object-assign": "^4.1.1", @@ -10819,7 +11458,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.2.tgz", "integrity": "sha1-NDxE0e53JrhkgGloLQ+EDIO0Jh0=", - "dev": true, "requires": { "xhr-request": "^1.0.1" } @@ -10828,7 +11466,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", "integrity": "sha1-fXdEnQmZGX8VXLc7I99yUF7YnUg=", - "dev": true, "requires": { "cookiejar": "^2.1.1" } @@ -10836,32 +11473,27 @@ "xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", - "dev": true + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", - "dev": true + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yargs": { "version": "10.1.2", @@ -10908,15 +11540,135 @@ } } }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "dependencies": { + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } } diff --git a/solidity/package.json b/solidity/package.json index 0595f55e..ab57522a 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,11 +1,11 @@ { "name": "@summa-tx/relay-sol", - "version": "2.0.0", + "version": "2.0.1", "description": "Bitcoin Relay", "main": "index.js", "scripts": { "compile": "truffle compile", - "test:coverage": "npx solidity-coverage", + "test:coverage": "truffle run coverage", "lint": "solium -d contracts/ && eslint ./test", "lint:fix": "solium --fix -d contracts/ && eslint --fix ./test", "test": "truffle test" @@ -13,6 +13,7 @@ "author": "James Prestwich", "license": "UNLICENSED", "dependencies": { + "@celo/contractkit": "^0.3.3", "@summa-tx/bitcoin-spv-sol": "^2.2.0", "bn.js": "^5.1.1", "dotenv": "^8.2.0" @@ -22,7 +23,7 @@ "eslint-config-airbnb-base": "^13.2.0", "eslint-plugin-import": "^2.20.2", "eth-gas-reporter": "^0.2.16", - "solidity-coverage": "^0.7.2", + "solidity-coverage": "^0.7.4", "solium": "^1.2.4", "truffle-hdwallet-provider": "^1.0.17", "truffle-plugin-verify": "^0.3.10" diff --git a/solidity/test/OnDemandSPV.test.js b/solidity/test/OnDemandSPV.test.js new file mode 100644 index 00000000..9d6b0589 --- /dev/null +++ b/solidity/test/OnDemandSPV.test.js @@ -0,0 +1,582 @@ +/* global artifacts contract before after describe it assert web3 */ +const BN = require('bn.js'); +const constants = require('./OnDemandSPVHelpers.json'); +const REGULAR_CHAIN = require('./headers.json'); + +const DummyConsumer = artifacts.require('DummyConsumer'); +const DummyOnDemandSPV = artifacts.require('DummyOnDemandSPV'); + +contract('OnDemandSPV', async (accounts) => { + let instance; + let consumer; + + const [deployer, requestOwner, outsideCaller] = accounts; + const { genesis } = REGULAR_CHAIN; + const BYTES32_0 = '0x0000000000000000000000000000000000000000000000000000000000000000'; + + before(async () => { + instance = await DummyOnDemandSPV.new( + genesis.hex, + genesis.height, + genesis.digest_le, + 0, + { from: deployer } + ); + consumer = await DummyConsumer.new({ from: deployer }); + }); + + describe('#cancelRequest', async () => { + const [sub1Id, sub2Id] = [1, 2]; + + before(async () => { + await instance.requestTest( + sub1Id, + `0x${'00'.repeat(36)}`, + '0x', + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + await instance.requestTest( + sub2Id, + `0x${'00'.repeat(36)}`, + '0x', + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + }); + + it('errors if not active', async () => { + try { + await instance.cancelRequest(3); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Request not active'); + } + }); + + it('cannot be cancelled by an outside caller', async () => { + try { + await instance.cancelRequest(sub1Id, { from: outsideCaller }); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Can only be cancelled by owner or consumer'); + } + }); + + it('can be canceled by the owner', async () => { + await instance.cancelRequest(sub1Id, { from: requestOwner }); + const request = await instance.getRequest(sub1Id); + assert(request[3].eq(new BN('2', 10))); + }); + + it('can be canceled by the consumer', async () => { + await consumer.cancel(sub2Id, instance.address); + const request = await instance.getRequest(sub2Id); + assert(request[3].eq(new BN('2', 10))); + }); + }); + + describe('#getRequest', async () => { + const sub3Id = 3; + + before(async () => { + await instance.requestTest( + sub3Id, + `0x${'11'.repeat(36)}`, + '0x', + 100, + consumer.address, + 0, + 0, + { from: requestOwner } + ); + }); + + it('retrieves request information', async () => { + const res = await instance.getRequest(sub3Id); + + // this is the keccak256 of `0x${'11'.repeat(36)}` + assert.strictEqual(res[0], '0x600e7bfdb8c3cc85df9cd058022100f260c17e7c58603758d2c1ac92c63469a6'); + assert.strictEqual(res[1], BYTES32_0); + assert(res[2].eq(new BN('100', 10))); + assert(res[3].eq(new BN('1', 10))); + assert.strictEqual(res[4], consumer.address); + assert.strictEqual(res[5], requestOwner); + }); + }); + + describe('#request', async () => { + const sub4Id = 4; + + it('errors if the outpoint is present and not 36 bytes', async () => { + try { + await instance.requestTest(sub4Id, '0xff', '0x', 0, consumer.address, 1, 0, { from: requestOwner }); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Not a valid UTXO'); + } + }); + + it('errors if the output is present, but nonstandard', async () => { + try { + await instance.requestTest(sub4Id, '0x', '0x00000000000000000000000000', 0, consumer.address, 1, 0, { from: requestOwner }); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Not a standard output type'); + } + }); + + it('errors if spends and pays are both 0', async () => { + try { + await instance.requestTest(sub4Id, '0x', '0x', 0, consumer.address, 1, 0, { from: requestOwner }); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'No request specified'); + } + }); + + it('stores a request and emits an event', async () => { + const blockNumber = await web3.eth.getBlock('latest').number; + + await instance.requestTest(sub4Id, `0x${'11'.repeat(36)}`, '0x', 100, consumer.address, 1, 0, { from: requestOwner }); + const res = await instance.getRequest(sub4Id); + + // this is the keccak256 of `0x${'11'.repeat(36)}` + assert.strictEqual(res[0], '0x600e7bfdb8c3cc85df9cd058022100f260c17e7c58603758d2c1ac92c63469a6'); + assert.strictEqual(res[1], BYTES32_0); + assert(res[2].eq(new BN('100', 10))); + assert(res[3].eq(new BN('1', 10))); + assert.strictEqual(res[4], consumer.address); + assert.strictEqual(res[5], requestOwner); + + + const eventList = await instance.getPastEvents( + 'NewProofRequest', + { fromBlock: blockNumber, toBlock: 'latest' } + ); + /* eslint-disable-next-line no-underscore-dangle */ + assert.strictEqual(eventList[0].returnValues._requester, requestOwner); + /* eslint-disable-next-line no-underscore-dangle */ + assert.strictEqual(parseInt(eventList[0].returnValues._requestID, 10), 4); + }); + + it('stores bytes32(0) for unspecified spends', async () => { + await instance.requestTest( + 88, + '0x', + constants.OP_RETURN_PAYS_1, + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + const res = await instance.getRequest(88); + assert.strictEqual(res[0], BYTES32_0); + }); + }); + + describe('#provideProof', async () => { + const sub5Id = 5; + + before(async () => { + await instance.requestTest( + sub5Id, + constants.OP_RETURN_SPENDS_0, + constants.OP_RETURN_PAYS_1, + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + await instance.setCallResult(true); + }); + + after(async () => { + await instance.setCallResult(false); + }); + + it('runs succesfully, and sets validatedTxns and latestValidatedTx', async () => { + assert.isOk(await instance.provideProof( + constants.OP_RETURN_HEADER, + constants.OP_RETURN_PROOF, + constants.OP_RETURN_VERSION, + constants.OP_RETURN_LOCKTIME, + constants.OP_RETURN_INDEX, + '0x0001', // requestIndices + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub5Id + )); + + let res = await instance.getValidatedTx(constants.OP_RETURN_TX_ID_LE); + assert.isTrue(res); + + res = await instance.getLatestValidatedTx.call(); + assert.equal(res, constants.OP_RETURN_TX_ID_LE); + }); + + it('shortcuts inclusion validatins for already-seen txns', async () => { + if (!await instance.getValidatedTx.call(constants.OP_RETURN_TX_ID_LE)) { + await instance.setValidatedTx(constants.OP_RETURN_TX_ID_LE); + } + assert.isOk(await instance.provideProof( + '0x', + '0x', + constants.OP_RETURN_VERSION, + constants.OP_RETURN_LOCKTIME, + 0, + '0x0001', // requestIndices + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub5Id + )); + }); + }); + + describe('#callCallback', async () => { + const sub6Id = 6; + + async function submitValidProof() { + return instance.provideProof( + constants.OP_RETURN_HEADER, + constants.OP_RETURN_PROOF, + constants.OP_RETURN_VERSION, + constants.OP_RETURN_LOCKTIME, + constants.OP_RETURN_INDEX, + '0x0001', // requestIndices + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub6Id, + { gas: 4000000 } + ); + } + + before(async () => { + await instance.requestTest( + sub6Id, + constants.OP_RETURN_SPENDS_0, + constants.OP_RETURN_PAYS_1, + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + await instance.setCallResult(true); + }); + + it('calls the consumer with 500,000 gas', async () => { + const blockNumber = await web3.eth.getBlock('latest').number; + + await submitValidProof(); + + const eventList = await consumer.getPastEvents( + 'Consumed', + { fromBlock: blockNumber, toBlock: 'latest' } + ); + /* eslint-disable-next-line no-underscore-dangle */ + assert(new BN(eventList[0].returnValues._gasLeft, 10).ltn(500000)); + }); + + it('functions even if the remote contract reverts', async () => { + const blockNumber = await web3.eth.getBlock('latest').number; + + await consumer.setBroken(true); + assert.isOk(await submitValidProof()); // doesn't revert + await consumer.setBroken(false); + + // should be no events, because consumer reverted + const eventList = await consumer.getPastEvents( + 'Consumed', + { fromBlock: blockNumber, toBlock: 'latest' } + ); + assert.strictEqual(eventList.length, 0); + }); + }); + + describe('#checkInclusion', async () => { + it('errors on a bad inclusion proof', async () => { + try { + await instance.checkInclusion( + constants.OP_RETURN_HEADER, + '0x', + constants.OP_RETURN_INDEX, + constants.OP_RETURN_TX_ID_LE, + 1, + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Bad inclusion proof'); + } + }); + + it('errors if isAncestor fails', async () => { + await instance.setCallResult(false); + try { + await instance.checkInclusion( + constants.OP_RETURN_HEADER, + constants.OP_RETURN_PROOF, + constants.OP_RETURN_INDEX, + constants.OP_RETURN_TX_ID_LE, + 1 + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'GCD does not confirm header'); + } + await instance.setCallResult(true); + }); + + it('errors if insufficinet confirmations', async () => { + const sub = 838; + await instance.requestTest( + sub, + `0x${'00'.repeat(36)}`, + '0x', + 0, + consumer.address, + 240, // very large conf requirement + 0, + { from: requestOwner } + ); + try { + await instance.checkInclusion( + constants.OP_RETURN_HEADER, + constants.OP_RETURN_PROOF, + constants.OP_RETURN_INDEX, + constants.OP_RETURN_TX_ID_LE, + sub + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Insufficient confirmations'); + } + await instance.setCallResult(true); + }); + + it('succeeds', async () => { + await instance.setCallResult(true); + const res = await instance.checkInclusion.call( + constants.OP_RETURN_HEADER, + constants.OP_RETURN_PROOF, + constants.OP_RETURN_INDEX, + constants.OP_RETURN_TX_ID_LE, + 1 + ); + assert.isTrue(res); + }); + }); + + describe('#checkRequests', async () => { + const sub7Id = 7; + const sub8Id = 8; + const sub9Id = 9; + + before(async () => { + await instance.requestTest( // both + sub7Id, + constants.OP_RETURN_SPENDS_0, + constants.OP_RETURN_PAYS_1, + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + await instance.requestTest( // only spends + sub8Id, + constants.OP_RETURN_SPENDS_0, + '0x', + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + await instance.requestTest( // only pays + sub9Id, + '0x', + constants.OP_RETURN_PAYS_1, + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + await instance.setCallResult(true); + }); + + it('errors if the vin is malformatted', async () => { + try { + await instance.checkRequests( + '0x0001', + `0x${'01'.repeat(66)}`, + constants.OP_RETURN_VOUT, + sub7Id + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Vin is malformatted'); + } + }); + + it('errors if the vout is malformatted', async () => { + try { + await instance.checkRequests( + '0x0001', + constants.OP_RETURN_VIN, + `0x${'01'.repeat(66)}`, + sub7Id + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Vout is malformatted'); + } + }); + + it('errors if the request is not active', async () => { + try { + await instance.checkRequests( + '0x0001', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + 11 // fragile: not yet active. breaks if we add more cases above this; + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Request is not active'); + } + }); + + it('errors if the specified output does not match the pays request', async () => { + try { + await instance.checkRequests( + '0x0000', // first output instead of second + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub7Id + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Does not match pays request'); + } + + try { + await instance.checkRequests( + '0xFF00', // first output instead of second + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub9Id + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Does not match pays request'); + } + }); + + it('errors if the specified output does not match the paysValue request', async () => { + await instance.requestTest( + 333, + constants.OP_RETURN_SPENDS_0, + constants.OP_RETURN_PAYS_1, + 1000, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + try { + await instance.checkRequests( + '0x0001', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + 333 + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Does not match value request'); + } + }); + + it('errors if the specified input does not match the spends request', async () => { + await instance.requestTest( + 444, + `0x${'33'.repeat(36)}`, + constants.OP_RETURN_PAYS_1, + 0, + consumer.address, + 1, + 0, + { from: requestOwner } + ); + try { + await instance.checkRequests( + '0x0001', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + 444 + ); + assert(false, 'expected an error'); + } catch (e) { + assert.include(e.message, 'Does not match spends request'); + } + }); + + it('suceeds', async () => { + assert.ok(await instance.checkRequests( + '0x0001', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub7Id + )); + assert.ok(await instance.checkRequests( + '0x0001', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub8Id + )); + assert.ok(await instance.checkRequests( + '0x0001', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub9Id + )); + }); + + it('allows 0xFF for unchecked', async () => { + assert.ok(await instance.checkRequests( + '0x0001', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub7Id + )); + assert.ok(await instance.checkRequests( + '0x00FF', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub8Id + )); + assert.ok(await instance.checkRequests( + '0xFF01', + constants.OP_RETURN_VIN, + constants.OP_RETURN_VOUT, + sub9Id + )); + }); + }); + + describe('#_getConfs', async () => { + it('should return the number of confirmations', async () => { + const res = await instance.getConfsTest.call(); + assert(res.eq(new BN('0', 10))); // <- fragile: best and LCA are the same here + }); + }); +}); diff --git a/solidity/test/OnDemandSPVHelpers.json b/solidity/test/OnDemandSPVHelpers.json new file mode 100644 index 00000000..99a7d030 --- /dev/null +++ b/solidity/test/OnDemandSPVHelpers.json @@ -0,0 +1,13 @@ +{ + "OP_RETURN_TX": "0x010000000001011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff024897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211024730440220276e0ec78028582054d86614c65bc4bf85ff5710b9d3a248ca28dd311eb2fa6802202ec950dd2a8c9435ff2d400cc45d7a4854ae085f49e05cc3f503834546d410de012103732783eef3af7e04d3af444430a629b16a9261e4025f52bf4d6d026299c37c7400000000", + "OP_RETURN_PROOF": "0xe35a0d6de94b656694589964a252957e4673a9fb1d2f8b4a92e3f0a7bb654fddb94e5a1e6d7f7f499fd1be5dd30a73bf5584bf137da5fdd77cc21aeb95b9e35788894be019284bd4fbed6dd6118ac2cb6d26bc4be4e423f55a3a48f2874d8d02a65d9c87d07de21d4dfe7b0a9f4a23cc9a58373e9e6931fefdb5afade5df54c91104048df1ee999240617984e18b6f931e2373673d0195b8c6987d7ff7650d5ce53bcec46e13ab4f2da1146a7fc621ee672f62bc22742486392d75e55e67b09960c3386a0b49e75f1723d6ab28ac9a2028a0c72866e2111d79d4817b88e17c821937847768d92837bae3832bb8e5a4ab4434b97e00a6c10182f211f592409068d6f5652400d9a3d1cc150a7fb692e874cc42d76bdafc842f2fe0f835a7c24d2d60c109b187d64571efbaa8047be85821f8e67e0e85f2f5894bc63d00c2ed9d64", + "OP_RETURN_INDEX": 281, + "OP_RETURN_VERSION": "0x01000000", + "OP_RETURN_VIN": "0x011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff", + "OP_RETURN_VOUT": "0x024897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211", + "OP_RETURN_LOCKTIME": "0x00000000", + "OP_RETURN_HEADER": "0x0000002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000000296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c216349c5ba11928170d38782b", + "OP_RETURN_SPENDS_0": "0x1746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba3000000000", + "OP_RETURN_PAYS_1": "0x166a14edb1b5c2f39af0fec151732585b1049b07895211", + "OP_RETURN_TX_ID_LE": "0x48e5a1a0e616d8fd92b4ef228c424e0c816799a256c6a90892195ccfc53300d6" +} diff --git a/solidity/test/Relay.test.js b/solidity/test/Relay.test.js index 5ed2a173..6e1d3bd9 100644 --- a/solidity/test/Relay.test.js +++ b/solidity/test/Relay.test.js @@ -474,13 +474,10 @@ contract('Relay', async () => { const POST_CHAIN = REORG_AND_RETARGET_CHAIN.postRetargetChain; const orphan = REORG_AND_RETARGET_CHAIN.orphan_437478; - const preHex = PRE_CHAIN.map(header => header.hex); - const pre = utils.concatenateHexStrings(preHex); - const postHex = POST_CHAIN.map(header => header.hex); - const post = utils.concatenateHexStrings(postHex.slice(0, -2)); - const postWithOrphan = utils.concatenateHexStrings([post, orphan.hex]); - const lastTwo = POST_CHAIN.slice(-2); - const postWithoutOrphan = utils.concatenateHexStrings([post, lastTwo[0].hex, lastTwo[1].hex]); + const pre = utils.concatenateHeadersHexes(PRE_CHAIN); + const post = utils.concatenateHeadersHexes(POST_CHAIN); + const shortPost = utils.concatenateHeadersHexes(POST_CHAIN.slice(0, POST_CHAIN.length - 2)); + const postWithOrphan = utils.concatenateHexStrings([shortPost, orphan.hex]); before(async () => { instance = await Relay.new( @@ -494,12 +491,12 @@ contract('Relay', async () => { ); await instance.addHeadersWithRetarget( REORG_AND_RETARGET_CHAIN.oldPeriodStart.hex, - preHex.slice(-1)[0], - postWithoutOrphan + PRE_CHAIN[PRE_CHAIN.length - 1].hex, + post ); await instance.addHeadersWithRetarget( REORG_AND_RETARGET_CHAIN.oldPeriodStart.hex, - preHex.slice(-1)[0], + PRE_CHAIN[PRE_CHAIN.length - 1].hex, postWithOrphan ); }); @@ -508,13 +505,13 @@ contract('Relay', async () => { let res = await instance.heaviestFromAncestor.call( REORG_AND_RETARGET_CHAIN.genesis.digest_le, orphan.hex, - preHex[3] + PRE_CHAIN[3].hex ); assert.equal(res, orphan.digest_le); res = await instance.heaviestFromAncestor.call( REORG_AND_RETARGET_CHAIN.genesis.digest_le, - preHex[3], + PRE_CHAIN[3].hex, orphan.hex ); assert.equal(res, orphan.digest_le); @@ -524,13 +521,13 @@ contract('Relay', async () => { let res = await instance.heaviestFromAncestor.call( REORG_AND_RETARGET_CHAIN.genesis.digest_le, orphan.hex, - postHex[3] + POST_CHAIN[3].hex ); assert.equal(res, orphan.digest_le); res = await instance.heaviestFromAncestor.call( REORG_AND_RETARGET_CHAIN.genesis.digest_le, - postHex[3], + POST_CHAIN[3].hex, orphan.hex ); assert.equal(res, orphan.digest_le); @@ -542,13 +539,10 @@ contract('Relay', async () => { const POST_CHAIN = REORG_AND_RETARGET_CHAIN.postRetargetChain; const orphan = REORG_AND_RETARGET_CHAIN.orphan_437478; - const preHex = PRE_CHAIN.map(header => header.hex); - const pre = utils.concatenateHexStrings(preHex); - const postHex = POST_CHAIN.map(header => header.hex); - const post = utils.concatenateHexStrings(postHex.slice(0, -2)); - const postWithOrphan = utils.concatenateHexStrings([post, orphan.hex]); - const lastTwo = POST_CHAIN.slice(-2); - const postWithoutOrphan = utils.concatenateHexStrings([post, lastTwo[0].hex, lastTwo[1].hex]); + const pre = utils.concatenateHeadersHexes(PRE_CHAIN); + const post = utils.concatenateHeadersHexes(POST_CHAIN); + const shortPost = utils.concatenateHeadersHexes(POST_CHAIN.slice(0, POST_CHAIN.length - 2)); + const postWithOrphan = utils.concatenateHexStrings([shortPost, orphan.hex]); before(async () => { instance = await Relay.new( @@ -562,12 +556,12 @@ contract('Relay', async () => { ); await instance.addHeadersWithRetarget( REORG_AND_RETARGET_CHAIN.oldPeriodStart.hex, - preHex.slice(-1)[0], - postWithoutOrphan + PRE_CHAIN[PRE_CHAIN.length - 1].hex, + post ); await instance.addHeadersWithRetarget( REORG_AND_RETARGET_CHAIN.oldPeriodStart.hex, - preHex.slice(-1)[0], + PRE_CHAIN[PRE_CHAIN.length - 1].hex, postWithOrphan ); }); @@ -626,13 +620,10 @@ contract('Relay', async () => { const POST_CHAIN = REORG_AND_RETARGET_CHAIN.postRetargetChain; const orphan = REORG_AND_RETARGET_CHAIN.orphan_437478; - const preHex = PRE_CHAIN.map(header => header.hex); - const pre = utils.concatenateHexStrings(preHex); - const postHex = POST_CHAIN.map(header => header.hex); - const post = utils.concatenateHexStrings(postHex.slice(0, -2)); - const postWithOrphan = utils.concatenateHexStrings([post, orphan.hex]); - const lastTwo = POST_CHAIN.slice(-2); - const postWithoutOrphan = utils.concatenateHexStrings([post, lastTwo[0].hex, lastTwo[1].hex]); + const pre = utils.concatenateHeadersHexes(PRE_CHAIN); + const post = utils.concatenateHeadersHexes(POST_CHAIN); + const shortPost = utils.concatenateHeadersHexes(POST_CHAIN.slice(0, POST_CHAIN.length - 2)); + const postWithOrphan = utils.concatenateHexStrings([shortPost, orphan.hex]); before(async () => { instance = await Relay.new( @@ -646,12 +637,12 @@ contract('Relay', async () => { ); await instance.addHeadersWithRetarget( REORG_AND_RETARGET_CHAIN.oldPeriodStart.hex, - preHex.slice(-1)[0], - postWithoutOrphan + PRE_CHAIN[PRE_CHAIN.length - 1].hex, + post ); await instance.addHeadersWithRetarget( REORG_AND_RETARGET_CHAIN.oldPeriodStart.hex, - preHex.slice(-1)[0], + PRE_CHAIN[PRE_CHAIN.length - 1].hex, postWithOrphan ); }); diff --git a/solidity/test/utils.js b/solidity/test/utils.js index dd0e10e1..71c26af0 100644 --- a/solidity/test/utils.js +++ b/solidity/test/utils.js @@ -12,4 +12,8 @@ module.exports = { } return current; }, + concatenateHeadersHexes: function concatenateHeadersHexes(arr) { + const hexes = arr.map(_arr => _arr.hex); + return this.concatenateHexStrings(hexes); + } }; diff --git a/solidity/truffle-config.js b/solidity/truffle-config.js index 6b8ab0fd..7ab94a6d 100644 --- a/solidity/truffle-config.js +++ b/solidity/truffle-config.js @@ -1,9 +1,13 @@ /* eslint-disable */ require('dotenv').config(); + +const Kit = require('@celo/contractkit') + const HDWalletProvider = require('truffle-hdwallet-provider'); const infuraKey = process.env.SUMMA_RELAY_INFURA_KEY; const mnemonic = process.env.MNEMONIC; + const ropsten = { provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/${infuraKey}`), network_id: 3, @@ -20,11 +24,36 @@ const kovan = { timeoutBlocks: 200 } +const alfajores = { + provider: () => { + const provider = new HDWalletProvider(mnemonic, 'http://127.0.0.1:9999'); // sinkhole any requests + // slip44 + const celoBIP44 = "m/44'/52752'/0'/0/0"; + const hdkey = provider.hdwallet.derivePath(celoBIP44); + // Get the privkey and hand it to the kit + const privkey = hdkey._hdkey.privateKey.toString('hex'); + const kit = Kit.newKit('https://alfajores-forno.celo-testnet.org'); + kit.addAccount(privkey); + return kit.web3.currentProvider; + }, + network_id: 44786, + gas: 5500000, + confirmations: 2, + timeoutBlocks: 200 +} + +const local = { + host: "127.0.0.1", + port: 8545, + network_id: "*" // Match any network id +} + module.exports = { api_keys: { etherscan: process.env.ETHERSCAN_KEY }, plugins: [ + 'solidity-coverage', 'truffle-plugin-verify' ], networks: { @@ -41,6 +70,11 @@ module.exports = { kovan: kovan, kovan_test: kovan, + + alfajores: alfajores, + alfajores_test: alfajores, + + local_test: local, }, // mocha: { @@ -58,3 +92,4 @@ module.exports = { } } }; + diff --git a/testVectors.json b/testVectors.json new file mode 100644 index 00000000..2cb02575 --- /dev/null +++ b/testVectors.json @@ -0,0 +1,1084 @@ +{ + "link": { + "isAncestor": { + "testCases": [{ + "digest": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "ancestor": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "limit": 15, + "output": true + }, { + "digest": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "ancestor": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "limit": 15, + "output": false + }, { + "digest": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "ancestor": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "limit": 0, + "output": false + }] + }, + "findAncestor": { + "testCases": [{ + "digest": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "offset": 2, + "error": 104, + "output": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, { + "digest": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "offset": 2, + "error": 0, + "output": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000" + }, { + "digest": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "offset": 3, + "error": 107, + "output": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }] + } + }, + "header": { + "validateDifficultyChange": [ + { + "comment": "valid case", + "headers": [ + { + "raw": "02000000b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b97642004a153a2ab5118824d1fac", + "hash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "height": 306432, + "prevhash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "merkle_root": "cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b9764" + }, + { + "raw": "02000000e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000868920a00e8d660b31983d8ce460d0f0381772e622d7a3ff635a516c08af75f7e50aa153a2ab51182fa596fc", + "hash": "8add7a6d29a5698517d7a3514e1a95acee2f2a27c615d4000000000000000000", + "height": 306433, + "prevhash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "merkle_root": "868920a00e8d660b31983d8ce460d0f0381772e622d7a3ff635a516c08af75f7" + } + ], + "prevEpochStart": { + "raw": "02000000dca825543cefd662b3199e02afa13c6aad4b01890180010400000000000000000187743d481db520170f22701f34a1449384446a55575e2c46e183de28b4854701e690539a855d18b189b5e4", + "hash": "a388949a3c2ba997243005ca41f8076b9a970fa7f230ad0e0000000000000000", + "height": 304416, + "prevhash": "dca825543cefd662b3199e02afa13c6aad4b0189018001040000000000000000", + "merkle_root": "0187743d481db520170f22701f34a1449384446a55575e2c46e183de28b48547" + }, + "anchor": { + "raw": "0200000075e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f3657853c04a1539a855d182522ac98", + "hash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "height": 306431, + "prevhash": "75e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000", + "merkle_root": "b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f365785" + }, + "output": 0 + }, + { + "comment": "invalid anchor", + "headers": [ + { + "raw": "02000000b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b97642004a153a2ab5118824d1fac", + "hash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "height": 306432, + "prevhash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "merkle_root": "cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b9764" + } + ], + "prevEpochStart": { + "raw": "02000000dca825543cefd662b3199e02afa13c6aad4b01890180010400000000000000000187743d481db520170f22701f34a1449384446a55575e2c46e183de28b4854701e690539a855d18b189b5e4", + "hash": "a388949a3c2ba997243005ca41f8076b9a970fa7f230ad0e0000000000000000", + "height": 304416, + "prevhash": "dca825543cefd662b3199e02afa13c6aad4b0189018001040000000000000000", + "merkle_root": "0187743d481db520170f22701f34a1449384446a55575e2c46e183de28b48547" + }, + "anchor": { + "raw": "0200000075e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f3657853c04a1539a855d182522ac98", + "hash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "height": 306432, + "prevhash": "75e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000", + "merkle_root": "b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f365785" + }, + "output": 301 + }, + { + "comment": "invalid prevEphochStart", + "headers": [ + { + "raw": "02000000b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b97642004a153a2ab5118824d1fac", + "hash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "height": 306432, + "prevhash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "merkle_root": "cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b9764" + } + ], + "prevEpochStart": { + "raw": "02000000dca825543cefd662b3199e02afa13c6aad4b01890180010400000000000000000187743d481db520170f22701f34a1449384446a55575e2c46e183de28b4854701e690539a855d18b189b5e4", + "hash": "a388949a3c2ba997243005ca41f8076b9a970fa7f230ad0e0000000000000000", + "height": 304417, + "prevhash": "dca825543cefd662b3199e02afa13c6aad4b0189018001040000000000000000", + "merkle_root": "0187743d481db520170f22701f34a1449384446a55575e2c46e183de28b48547" + }, + "anchor": { + "raw": "0200000075e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f3657853c04a1539a855d182522ac98", + "hash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "height": 306431, + "prevhash": "75e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000", + "merkle_root": "b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f365785" + }, + "output": 302 + }, + { + "comment": "period header difficulties do not match", + "headers": [ + { + "raw": "02000000b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b97642004a153a2ab5118824d1fac", + "hash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "height": 306432, + "prevhash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "merkle_root": "cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b9764" + } + ], + "prevEpochStart": { + "raw": "02000000dca825543cefd662b3199e02afa13c6aad4b01890180010400000000000000000187743d481db520170f22701f34a1449384446a55575e2c46e183de28b4854701e690538a855d18b189b5e4", + "hash": "a388949a3c2ba997243005ca41f8076b9a970fa7f230ad0e0000000000000000", + "height": 304416, + "prevhash": "dca825543cefd662b3199e02afa13c6aad4b0189018001040000000000000000", + "merkle_root": "0187743d481db520170f22701f34a1449384446a55575e2c46e183de28b48547" + }, + "anchor": { + "raw": "0200000075e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f3657853c04a1539a855d182522ac98", + "hash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "height": 306431, + "prevhash": "75e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000", + "merkle_root": "b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f365785" + }, + "output": 303 + }, + { + "comment": "invalid retarget", + "headers": [ + { + "raw": "02000000b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b97642004a153a2ab5218824d1fac", + "hash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "height": 306432, + "prevhash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "merkle_root": "cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b9764" + }, + { + "raw": "02000000e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000868920a00e8d660b31983d8ce460d0f0381772e622d7a3ff635a516c08af75f7e50aa153a2ab51182fa596fc", + "hash": "8add7a6d29a5698517d7a3514e1a95acee2f2a27c615d4000000000000000000", + "height": 306433, + "prevhash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "merkle_root": "868920a00e8d660b31983d8ce460d0f0381772e622d7a3ff635a516c08af75f7" + } + ], + "prevEpochStart": { + "raw": "02000000dca825543cefd662b3199e02afa13c6aad4b01890180010400000000000000000187743d481db520170f22701f34a1449384446a55575e2c46e183de28b4854701e690539a855d18b189b5e4", + "hash": "a388949a3c2ba997243005ca41f8076b9a970fa7f230ad0e0000000000000000", + "height": 304416, + "prevhash": "dca825543cefd662b3199e02afa13c6aad4b0189018001040000000000000000", + "merkle_root": "0187743d481db520170f22701f34a1449384446a55575e2c46e183de28b48547" + }, + "anchor": { + "raw": "0200000075e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f3657853c04a1539a855d182522ac98", + "hash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "height": 306431, + "prevhash": "75e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000", + "merkle_root": "b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f365785" + }, + "output": 304 + } + ], + "validateHeaderChain": [ + { + "comment": "valid case", + "headers": [ + { + "height": 562623, + "hash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "prevhash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "merkle_root": "0xb16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c", + "raw": "0x00000020b8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000b16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c44c0615c886f2e1744ea7cc4" + }, + { + "height": 562624, + "hash": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "prevhash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "merkle_root": "0xaf9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf257534", + "raw": "0x00000020f549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000af9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf25753430c2615c886f2e170a654758" + }, + { + "height": 562625, + "hash": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "prevhash": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "merkle_root": "0xe78265c12495ef469b3e85aa570667fdcb5bf534304fcbe4621c706d0e7ca814", + "raw": "0x00e0ff3f8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000e78265c12495ef469b3e85aa570667fdcb5bf534304fcbe4621c706d0e7ca8149dc3615c886f2e171c3fa2b8" + }, + { + "height": 562626, + "hash": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "prevhash": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "merkle_root": "0x6f510b84a156d42cb64f30b97a7fc6dd030c9b77e19bbcd850c9f3c69bc533da", + "raw": "0x00000020fee20039fe494f7408c090e03970ec9b132366066f380d0000000000000000006f510b84a156d42cb64f30b97a7fc6dd030c9b77e19bbcd850c9f3c69bc533dacec3615c886f2e170a1a921d" + }, + { + "height": 562627, + "hash": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "prevhash": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "merkle_root": "0x6aa31402bb974ebcd45eb2b0be7df8cc48ea9721493978e8715ce93b55d5ea20", + "raw": "0x00000020d2406bb15e4f917104d2b2d9320454b947fb08a723301d0000000000000000006aa31402bb974ebcd45eb2b0be7df8cc48ea9721493978e8715ce93b55d5ea20d1c5615c886f2e1767973d73" + }, + { + "height": 562628, + "hash": "0xb86918706260609b3b6aaf684aed961eac6b015e637024000000000000000000", + "prevhash": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "merkle_root": "0x2488333a3bab6cc72d04ee1523ae83c9559938bef8521cb624c9641bb58cabe9", + "raw": "0x00e0002096f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b0000000000000000002488333a3bab6cc72d04ee1523ae83c9559938bef8521cb624c9641bb58cabe953ce615c886f2e172e0885ce" + }, + { + "height": 562629, + "hash": "0x20ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f05000000000000000000", + "prevhash": "0xb86918706260609b3b6aaf684aed961eac6b015e637024000000000000000000", + "merkle_root": "0xe72e0a6fd324d36edee39f9336c56f129f1ef7c2ec26d0dfb01e4520fb7a8d7f", + "raw": "0x00000020b86918706260609b3b6aaf684aed961eac6b015e637024000000000000000000e72e0a6fd324d36edee39f9336c56f129f1ef7c2ec26d0dfb01e4520fb7a8d7fcdce615c886f2e17415cdb46" + }, + { + "height": 562630, + "hash": "0x51214b0c42383a1ea7bf28f20062f81d7b72497cb1030a000000000000000000", + "prevhash": "0x20ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f05000000000000000000", + "merkle_root": "0x9d1479517fda612a10a279b2339952bbdba8fe47c8fec644921d146ec79482ec", + "raw": "0x0000002020ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f050000000000000000009d1479517fda612a10a279b2339952bbdba8fe47c8fec644921d146ec79482ecebd3615c886f2e179234c38e" + }, + { + "height": 562631, + "hash": "0xbf01515ce1f4f971b9805205373093c200b2bf92d56408000000000000000000", + "prevhash": "0x51214b0c42383a1ea7bf28f20062f81d7b72497cb1030a000000000000000000", + "merkle_root": "0x00af444756eb5313dae6cb8dc7b4e00ae7d79cfa67a85b4b486a9583896ab331", + "raw": "0x0000002051214b0c42383a1ea7bf28f20062f81d7b72497cb1030a00000000000000000000af444756eb5313dae6cb8dc7b4e00ae7d79cfa67a85b4b486a9583896ab3314bd8615c886f2e17f152bc1f" + }, + { + "height": 562632, + "hash": "0xa4c292016c1585e6f81986f7e216c79d28b15b1d513a10000000000000000000", + "prevhash": "0xbf01515ce1f4f971b9805205373093c200b2bf92d56408000000000000000000", + "merkle_root": "0xb2c2fcb555d6e2d677bb9919cc2d9660c81879225d15f53f679e3fdbfad129d0", + "raw": "0x00e00020bf01515ce1f4f971b9805205373093c200b2bf92d56408000000000000000000b2c2fcb555d6e2d677bb9919cc2d9660c81879225d15f53f679e3fdbfad129d032db615c886f2e17155f22df" + }, + { + "height": 562633, + "hash": "0x577d11b45f90733748343b5add65dbe88216fa3027cc20000000000000000000", + "prevhash": "0xa4c292016c1585e6f81986f7e216c79d28b15b1d513a10000000000000000000", + "merkle_root": "0x4e33f75c5f63371d4a05e7ab93afb7c1caa22d2f9d4fce61e194ab8ffe741f35", + "raw": "0x00000020a4c292016c1585e6f81986f7e216c79d28b15b1d513a100000000000000000004e33f75c5f63371d4a05e7ab93afb7c1caa22d2f9d4fce61e194ab8ffe741f35c0de615c886f2e17032ddf4b" + }, + { + "height": 562634, + "hash": "0xf5f2d6840112ff9281bd88f445f135dfb41a64cebeb725000000000000000000", + "prevhash": "0x577d11b45f90733748343b5add65dbe88216fa3027cc20000000000000000000", + "merkle_root": "0xef200d52b09ae1902d62476f09405e21fa81cd7972ccfeaf37b47b00ef4e2180", + "raw": "0x00000020577d11b45f90733748343b5add65dbe88216fa3027cc20000000000000000000ef200d52b09ae1902d62476f09405e21fa81cd7972ccfeaf37b47b00ef4e2180cdde615c886f2e171f2b5f80" + }, + { + "height": 562635, + "hash": "0xf98794c5b71e25f07eb2a31ab31b2e2487e0859abec000000000000000000000", + "prevhash": "0xf5f2d6840112ff9281bd88f445f135dfb41a64cebeb725000000000000000000", + "merkle_root": "0xa91738fc7b8628e70906164624f80ce54bafe26fe0ef8678f569b0b64e83589c", + "raw": "0x00000020f5f2d6840112ff9281bd88f445f135dfb41a64cebeb725000000000000000000a91738fc7b8628e70906164624f80ce54bafe26fe0ef8678f569b0b64e83589cb3e0615c886f2e17c624e58c" + }, + { + "height": 562636, + "hash": "0x0ed18ffcb751e45471dddab23d34538869d3b2cdd48428000000000000000000", + "prevhash": "0xf98794c5b71e25f07eb2a31ab31b2e2487e0859abec000000000000000000000", + "merkle_root": "0xc29b14f0fe90ac2173197665d460df45c37ccf0c873276f59d095cbed4bcc7c2", + "raw": "0x00e00020f98794c5b71e25f07eb2a31ab31b2e2487e0859abec000000000000000000000c29b14f0fe90ac2173197665d460df45c37ccf0c873276f59d095cbed4bcc7c2fae4615c886f2e178a505d9d" + }, + { + "height": 562637, + "hash": "0x30ae17c8d62a00b0425c341f6babbc8424e6edc236032a000000000000000000", + "prevhash": "0x0ed18ffcb751e45471dddab23d34538869d3b2cdd48428000000000000000000", + "merkle_root": "0x3505702919866f91f1196e078799287f80be0b2c3af830ed6011ce89fc7f0d65", + "raw": "0x000000200ed18ffcb751e45471dddab23d34538869d3b2cdd484280000000000000000003505702919866f91f1196e078799287f80be0b2c3af830ed6011ce89fc7f0d657ce5615c886f2e17d759c936" + }, + { + "height": 562638, + "hash": "0x51f32093b60f4484c041d383fb6bf35c44d428bde54d2d000000000000000000", + "prevhash": "0x30ae17c8d62a00b0425c341f6babbc8424e6edc236032a000000000000000000", + "merkle_root": "0x097a32800849429e29be38addab29936925589e1e547d1ce968d62e566a45882", + "raw": "0x0000802030ae17c8d62a00b0425c341f6babbc8424e6edc236032a000000000000000000097a32800849429e29be38addab29936925589e1e547d1ce968d62e566a45882f1e5615c886f2e1796e65983" + }, + { + "height": 562639, + "hash": "0x48417c9cd5b52348f8d603dcc624bc5db68ef4a7dbb80f000000000000000000", + "prevhash": "0x51f32093b60f4484c041d383fb6bf35c44d428bde54d2d000000000000000000", + "merkle_root": "0x1888692df72fdbc27082d23e323b6acacaa5424d191dbaa7f13dfed36cdfbee9", + "raw": "0x0000802051f32093b60f4484c041d383fb6bf35c44d428bde54d2d0000000000000000001888692df72fdbc27082d23e323b6acacaa5424d191dbaa7f13dfed36cdfbee905e6615c886f2e177b59d28f" + } + ], + "anchor": { + "height": 562622, + "hash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "prevhash": "0x4615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000", + "merkle_root": "0xb034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a", + "raw": "0x000000204615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000b034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a34c0615c886f2e17046e7325" + }, + "internal": false, + "isMainnet": true, + "output": 0 + }, + { + "comment": "internal, valid", + "headers": [ + { + "height": 562623, + "hash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "prevhash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "merkle_root": "0xb16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c", + "raw": "0x00000020b8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000b16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c44c0615c886f2e1744ea7cc4" + }, + { + "height": 562624, + "hash": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "prevhash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "merkle_root": "0xaf9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf257534", + "raw": "0x00000020f549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000af9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf25753430c2615c886f2e170a654758" + } + ], + "anchor": { + "height": 562622, + "hash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "prevhash": "0x4615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000", + "merkle_root": "0xb034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a", + "raw": "0x000000204615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000b034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a34c0615c886f2e17046e7325" + }, + "internal": true, + "isMainnet": true, + "output": 0 + }, + { + "comment": "allowed retarget, because not mainnet", + "headers": [ + { + "raw": "02000000b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b97642004a153a2ab5118824d1fac", + "hash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "height": 306432, + "prevhash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "merkle_root": "cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b9764" + }, + { + "raw": "02000000e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000868920a00e8d660b31983d8ce460d0f0381772e622d7a3ff635a516c08af75f7e50aa153a2ab51182fa596fc", + "hash": "8add7a6d29a5698517d7a3514e1a95acee2f2a27c615d4000000000000000000", + "height": 306433, + "prevhash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "merkle_root": "868920a00e8d660b31983d8ce460d0f0381772e622d7a3ff635a516c08af75f7" + } + ], + "anchor": { + "raw": "0200000075e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f3657853c04a1539a855d182522ac98", + "hash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "height": 306431, + "prevhash": "75e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000", + "merkle_root": "b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f365785" + }, + "internal": false, + "isMainnet": false, + "output": 0 + }, + { + "comment": "unexpected retarget on non-internal", + "headers": [ + { + "raw": "02000000b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b97642004a153a2ab5118824d1fac", + "hash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "height": 306432, + "prevhash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "merkle_root": "cd19b368ea76f54a604d5c5222d190112f364df1a5af37fbc24cb3fbd32b9764" + }, + { + "raw": "02000000e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000868920a00e8d660b31983d8ce460d0f0381772e622d7a3ff635a516c08af75f7e50aa153a2ab51182fa596fc", + "hash": "8add7a6d29a5698517d7a3514e1a95acee2f2a27c615d4000000000000000000", + "height": 306433, + "prevhash": "e9c9a1aa62955afb469bba669d4fe157169fd377dcd0030d0000000000000000", + "merkle_root": "868920a00e8d660b31983d8ce460d0f0381772e622d7a3ff635a516c08af75f7" + } + ], + "anchor": { + "raw": "0200000075e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f3657853c04a1539a855d182522ac98", + "hash": "b2b3d204fbd1fda5f1bfa8e83d6f67be7307c05a64d4441b0000000000000000", + "height": 306431, + "prevhash": "75e95a670774b501ff619fdb000f504c0ad29d3f083a27510000000000000000", + "merkle_root": "b013371f3c2ee20683a8e492547bb0b87da4b3d8a0ac8ad79bd16ad35f365785" + }, + "internal": false, + "isMainnet": true, + "output": 201 + }, + { + "comment": "heights not consecutive", + "headers": [ + { + "height": 562623, + "hash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "prevhash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "merkle_root": "0xb16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c", + "raw": "0x00000020b8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000b16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c44c0615c886f2e1744ea7cc4" + }, + { + "height": 562625, + "hash": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "prevhash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "merkle_root": "0xaf9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf257534", + "raw": "0x00000020f549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000af9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf25753430c2615c886f2e170a654758" + } + ], + "anchor": { + "height": 562622, + "hash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "prevhash": "0x4615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000", + "merkle_root": "0xb034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a", + "raw": "0x000000204615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000b034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a34c0615c886f2e17046e7325" + }, + "internal": false, + "isMainnet": true, + "output": 103 + }, + { + "comment": "header doesn't pass validation, header[0].merkle_root_le is wrong", + "headers": [ + { + "height": 562623, + "hash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "prevhash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "merkle_root": "0xF16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c", + "raw": "0x00000020b8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000b16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c44c0615c886f2e1744ea7cc4" + }, + { + "height": 562624, + "hash": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "prevhash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "merkle_root": "0xaf9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf257534", + "raw": "0x00000020f549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000af9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf25753430c2615c886f2e170a654758" + } + ], + "anchor": { + "height": 562622, + "hash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "prevhash": "0x4615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000", + "merkle_root": "0xb034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a", + "raw": "0x000000204615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000b034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a34c0615c886f2e17046e7325" + }, + "internal": false, + "isMainnet": true, + "output": 108 + }, + { + "comment": "header chain doesn't pass validation, anchor is low work", + "headers": [ + { + "height": 562623, + "hash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "prevhash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "merkle_root": "0xb16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c", + "raw": "0x00000020b8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000b16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c44c0615c886f2e1744ea7cc4" + }, + { + "height": 562624, + "hash": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "prevhash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "merkle_root": "0xaf9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf257534", + "raw": "0x00000020f549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000af9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf25753430c2615c886f2e170a654758" + } + ], + "anchor": { + "height": 562622, + "hash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "prevhash": "0x4615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000", + "merkle_root": "0xb034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a", + "raw": "0xF00000204615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000b034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a34c0615c886f2e17046e7325" + }, + "internal": false, + "isMainnet": true, + "output": 108 + } + ], + "compareTargets": [ + { + "full": "0xff", + "truncated": "0xff", + "output": true + }, + { + "full": "0xffff", + "truncated": "0xff00", + "output": true + }, + { + "full": "0xff00", + "truncated": "0xffff", + "output": false + }, + { + "full": "0x1892413990612304162374102234", + "truncated": "0x1892410000000000000000000000", + "output": true + }, + { + "full": "0x0011223344ffeeddccbbaa", + "truncated": "0x000000000000000000", + "output": true + } + ] + }, + "chain": { + "isMostRecentCommonAncestor": { + "orphan": { + "raw": "0x00000020e156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000c52c39a4e31158ff7c34417c9750038b27c9a94dc08210e53ba624667c9310973d161e5874510418a9f7e0d0", + "hash": "0xf24d697743f1fce5857ee6f7d709ed60830ae4239da71d010000000000000000", + "height": 437478, + "prevhash": "0xe156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000", + "merkle_root": "0xc52c39a4e31158ff7c34417c9750038b27c9a94dc08210e53ba624667c931097" + }, + "oldPeriodStart": { + "raw": "0x00000020a32e8e27455216a02d4704b3a3c4731cf34117d468d97f020000000000000000948fa46542c2d31a37733dca03b2cc82ffadc067fd603479f64d773fcfc13801d2a90b58d25504183d4ee3b4", + "hash": "0x9b00e94f19ef7f100c3f5c4484b059afdb4897595ced82000000000000000000", + "height": 435456, + "prevhash": "0xa32e8e27455216a02d4704b3a3c4731cf34117d468d97f020000000000000000", + "merkle_root": "0x948fa46542c2d31a37733dca03b2cc82ffadc067fd603479f64d773fcfc13801" + }, + "genesis": { + "raw": "0x00000020d0eb8cc9a5668ddabb83e6f657d940fd56902ceb039f200000000000000000005e407cf51ee81930145648d1e72c1caeafa6f6bfc4a7ec3988bcb7cf69a1594af3fe1d58d2550418e90ec632", + "hash": "0x16d4ac8089850ad78c94132a7fd2b7d8115704b73ea673000000000000000000", + "height": 437466, + "prevhash": "0xd0eb8cc9a5668ddabb83e6f657d940fd56902ceb039f20000000000000000000", + "merkle_root": "0x5e407cf51ee81930145648d1e72c1caeafa6f6bfc4a7ec3988bcb7cf69a1594a" + }, + "preRetargetChain": [ + { + "raw": "0x0000002016d4ac8089850ad78c94132a7fd2b7d8115704b73ea673000000000000000000993956e6dbdabd742e9078408e4a5a75e3760d18a24cc6b18e65b6bb3b15ed0f5b001e58d2550418183b1b41", + "hash": "0x58bc010196dd410ab4437e8b1601376e273fa45abb5b3c030000000000000000", + "height": 437467, + "prevhash": "0x16d4ac8089850ad78c94132a7fd2b7d8115704b73ea673000000000000000000", + "merkle_root": "0x993956e6dbdabd742e9078408e4a5a75e3760d18a24cc6b18e65b6bb3b15ed0f" + }, + { + "raw": "0x0000002058bc010196dd410ab4437e8b1601376e273fa45abb5b3c03000000000000000035092ae7558bab7b92b811bd07bd3a5a3c16a84e37a67e47817e813728f6f5e612011e58d255041805ebb671", + "hash": "0x011971d9bb7764f55507a99ad3e0a63b5aa949613b469c030000000000000000", + "height": 437468, + "prevhash": "0x58bc010196dd410ab4437e8b1601376e273fa45abb5b3c030000000000000000", + "merkle_root": "0x35092ae7558bab7b92b811bd07bd3a5a3c16a84e37a67e47817e813728f6f5e6" + }, + { + "raw": "0x00000020011971d9bb7764f55507a99ad3e0a63b5aa949613b469c030000000000000000ea0ac12b8c46d54d1eac6637b916ae7775aad858dc1c8a43cff50c40bb51a3bc2c081e58d255041859d608ec", + "hash": "0x7314dff30a198ad935fb69c077e80905abe41a43ceaa0d020000000000000000", + "height": 437469, + "prevhash": "0x011971d9bb7764f55507a99ad3e0a63b5aa949613b469c030000000000000000", + "merkle_root": "0xea0ac12b8c46d54d1eac6637b916ae7775aad858dc1c8a43cff50c40bb51a3bc" + }, + { + "raw": "0x000000207314dff30a198ad935fb69c077e80905abe41a43ceaa0d020000000000000000d3439b160498d4464fcb6eee7ac75e801933067932f30234e3b924e2671e31a4e1091e58d25504188ccd158e", + "hash": "0xf2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff5030000000000000000", + "height": 437470, + "prevhash": "0x7314dff30a198ad935fb69c077e80905abe41a43ceaa0d020000000000000000", + "merkle_root": "0xd3439b160498d4464fcb6eee7ac75e801933067932f30234e3b924e2671e31a4" + }, + { + "raw": "0x00000020f2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff503000000000000000051aa623be08b19c2a6984e0c229bbffce20673675267eab5bf09a963b0547c673c0c1e58d25504183e20ab5b", + "hash": "0xa590cf5dc89b4d2ec22144097231d109c0a2b88431a264030000000000000000", + "height": 437471, + "prevhash": "0xf2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff5030000000000000000", + "merkle_root": "0x51aa623be08b19c2a6984e0c229bbffce20673675267eab5bf09a963b0547c67" + } + ], + "postRetargetChain": [ + { + "raw": "0x00000020a590cf5dc89b4d2ec22144097231d109c0a2b88431a264030000000000000000a37e2d39aaf31d8bd7c00a07fa9784fee88ab361082f03601119dac4a0d87c8d020d1e58745104189b665b65", + "hash": "0xb76325d903198d9be86addba452d83c6764644f1235cc7030000000000000000", + "height": 437472, + "prevhash": "0xa590cf5dc89b4d2ec22144097231d109c0a2b88431a264030000000000000000", + "merkle_root": "0xa37e2d39aaf31d8bd7c00a07fa9784fee88ab361082f03601119dac4a0d87c8d" + }, + { + "raw": "0x00000020b76325d903198d9be86addba452d83c6764644f1235cc70300000000000000001d9e6cc2b0871a8f88db857db870dfa7000f80f3c89c7a0eaaa925974fc392459e111e5874510418f4dcbaed", + "hash": "0x612bf328b7d0e9395eab852ee7cd9f34818bfbf6d98460000000000000000000", + "height": 437473, + "prevhash": "0xb76325d903198d9be86addba452d83c6764644f1235cc7030000000000000000", + "merkle_root": "0x1d9e6cc2b0871a8f88db857db870dfa7000f80f3c89c7a0eaaa925974fc39245" + }, + { + "raw": "0x00000020612bf328b7d0e9395eab852ee7cd9f34818bfbf6d984600000000000000000001698022f9aeaccae63250f24ad66093504b088686d2e307bf7653c700b7e23d1e2111e5874510418914866a7", + "hash": "0x4db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a1030000000000000000", + "height": 437474, + "prevhash": "0x612bf328b7d0e9395eab852ee7cd9f34818bfbf6d98460000000000000000000", + "merkle_root": "0x1698022f9aeaccae63250f24ad66093504b088686d2e307bf7653c700b7e23d1" + }, + { + "raw": "0x000000204db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a10300000000000000005c93b3ce08ce22ba64fb26b8e75d695ac8c2bea0badd34200e698ccc73ca421b97121e58745104183ded0054", + "hash": "0xa8cd6f757013d17d47bafec89cf0c3a89d0dc501f624a0000000000000000000", + "height": 437475, + "prevhash": "0x4db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a1030000000000000000", + "merkle_root": "0x5c93b3ce08ce22ba64fb26b8e75d695ac8c2bea0badd34200e698ccc73ca421b" + }, + { + "raw": "0x00000020a8cd6f757013d17d47bafec89cf0c3a89d0dc501f624a00000000000000000004d7fd5d8c9984f2be03a80a75a4b6fe02dfda894181fcab0b6beb60698c7d6f0db121e587451041816e7fbbd", + "hash": "0xdb3a0776bb7fb1cbe2a5c9263556210069114008784946030000000000000000", + "height": 437476, + "prevhash": "0xa8cd6f757013d17d47bafec89cf0c3a89d0dc501f624a0000000000000000000", + "merkle_root": "0x4d7fd5d8c9984f2be03a80a75a4b6fe02dfda894181fcab0b6beb60698c7d6f0" + }, + { + "raw": "0x00000020db3a0776bb7fb1cbe2a5c9263556210069114008784946030000000000000000c8cbccf35d668296e8da72aed974983a8679e5077445b79d1e3e6c4d1bbff2c730161e58745104188674ac6f", + "hash": "0xe156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000", + "height": 437477, + "prevhash": "0xdb3a0776bb7fb1cbe2a5c9263556210069114008784946030000000000000000", + "merkle_root": "0xc8cbccf35d668296e8da72aed974983a8679e5077445b79d1e3e6c4d1bbff2c7" + }, + { + "raw": "0x00000020e156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000f7e22ae2e5442bac43e18fc032dafd058fbdf886e33a3330e7597ce6eb4c073347161e58745104186cff9846", + "hash": "0xd339c3b98a46b386a793076518f94b153cf09712231f3f010000000000000000", + "height": 437478, + "prevhash": "0xe156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000", + "merkle_root": "0xf7e22ae2e5442bac43e18fc032dafd058fbdf886e33a3330e7597ce6eb4c0733" + }, + { + "raw": "0x00000020d339c3b98a46b386a793076518f94b153cf09712231f3f01000000000000000018f078c9c73734d892fef90854381524f30017370d12dfa8319313c5729dbf85b8191e58745104187d43c005", + "hash": "0xa0774df24dccbaa522a9b51fac75066335656454f543a2030000000000000000", + "height": 437479, + "prevhash": "0xd339c3b98a46b386a793076518f94b153cf09712231f3f010000000000000000", + "merkle_root": "0x18f078c9c73734d892fef90854381524f30017370d12dfa8319313c5729dbf85" + } + ], + "testCases": [{ + "ancestor": "0x4db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a1030000000000000000", + "left": "0xa8cd6f757013d17d47bafec89cf0c3a89d0dc501f624a0000000000000000000", + "right": "0x4db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a1030000000000000000", + "limit": 5, + "output": true + }, { + "ancestor": "0xe156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000", + "left": "0xd339c3b98a46b386a793076518f94b153cf09712231f3f010000000000000000", + "right": "0xf24d697743f1fce5857ee6f7d709ed60830ae4239da71d010000000000000000", + "limit": 5, + "output": true + }, { + "ancestor": "0xf2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff5030000000000000000", + "left": "0xf2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff5030000000000000000", + "right": "0xf2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff5030000000000000000", + "limit": 5, + "output": true + }, { + "ancestor": "0xb76325d903198d9be86addba452d83c6764644f1235cc7030000000000000000", + "left": "0xf2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff5030000000000000000", + "right": "0x4db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a1030000000000000000", + "limit": 5, + "output": false + }, { + "ancestor": "0xb76325d903198d9be86addba452d83c6764644f1235cc7030000000000000000", + "left": "0xa8cd6f757013d17d47bafec89cf0c3a89d0dc501f624a0000000000000000000", + "right": "0x4db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a1030000000000000000", + "limit": 1, + "output": false + }] + }, + "heaviestFromAncestor": { + "orphan": { + "raw": "0x0000802020ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f0500000000000000000058c8af6bf8e8e00c3d6ff512b2133533ebdcde164de306e6b65e53157fc22b53e7d3615c886f2e17ed205930", + "hash": "0x5204b3afd5c0dc010de8eeb28925b97d4b38a16b1d020f000000000000000000", + "height": 562630, + "prevhash": "0x20ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f05000000000000000000", + "merkle_root": "0x58c8af6bf8e8e00c3d6ff512b2133533ebdcde164de306e6b65e53157fc22b53" + }, + "badHeader": { + "raw": "0x00000020fe70e48339d6b17fbbf1340d245338f57336e97767cc240000000000000000005af53b865c27c6e9b5e5db4c3ea8e024f8329178a79ddb39f7727ea2fe6e6825d1349c5ba1192817e2d95159", + "hash": "0xbaaea6746f4c16ccb7cd961655b636d39b5fe1519b8f15000000000000000000", + "height": 562630, + "prevhash": "0x20ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f05000000000000000000", + "merkle_root": "0x58c8af6bf8e8e00c3d6ff512b2133533ebdcde164de306e6b65e53157fc22b53" + }, + "genesis": { + "raw": "0x00000020db62962b5989325f30f357762ae456b2ec340432278e14000000000000000000d1dd4e30908c361dfeabfb1e560281c1a270bde3c8719dbda7c848005317594440bf615c886f2e17bd6b082d", + "hash": "0x4615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000", + "height": 562621, + "prevhash": "0xdb62962b5989325f30f357762ae456b2ec340432278e14000000000000000000", + "merkle_root": "0xd1dd4e30908c361dfeabfb1e560281c1a270bde3c8719dbda7c8480053175944" + }, + "headers": [ + { + "raw": "0x000000204615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000b034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a34c0615c886f2e17046e7325", + "hash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "height": 562622, + "prevhash": "0x4615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000", + "merkle_root": "0xb034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a" + }, + { + "raw": "0x00000020b8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000b16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c44c0615c886f2e1744ea7cc4", + "hash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "height": 562623, + "prevhash": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "merkle_root": "0xb16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c" + }, + { + "raw": "0x00000020f549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000af9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf25753430c2615c886f2e170a654758", + "hash": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "height": 562624, + "prevhash": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "merkle_root": "0xaf9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf257534" + }, + { + "raw": "0x00e0ff3f8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000e78265c12495ef469b3e85aa570667fdcb5bf534304fcbe4621c706d0e7ca8149dc3615c886f2e171c3fa2b8", + "hash": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "height": 562625, + "prevhash": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "merkle_root": "0xe78265c12495ef469b3e85aa570667fdcb5bf534304fcbe4621c706d0e7ca814" + }, + { + "raw": "0x00000020fee20039fe494f7408c090e03970ec9b132366066f380d0000000000000000006f510b84a156d42cb64f30b97a7fc6dd030c9b77e19bbcd850c9f3c69bc533dacec3615c886f2e170a1a921d", + "hash": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "height": 562626, + "prevhash": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "merkle_root": "0x6f510b84a156d42cb64f30b97a7fc6dd030c9b77e19bbcd850c9f3c69bc533da" + }, + { + "raw": "0x00000020d2406bb15e4f917104d2b2d9320454b947fb08a723301d0000000000000000006aa31402bb974ebcd45eb2b0be7df8cc48ea9721493978e8715ce93b55d5ea20d1c5615c886f2e1767973d73", + "hash": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "height": 562627, + "prevhash": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "merkle_root": "0x6aa31402bb974ebcd45eb2b0be7df8cc48ea9721493978e8715ce93b55d5ea20" + }, + { + "raw": "0x00e0002096f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b0000000000000000002488333a3bab6cc72d04ee1523ae83c9559938bef8521cb624c9641bb58cabe953ce615c886f2e172e0885ce", + "hash": "0xb86918706260609b3b6aaf684aed961eac6b015e637024000000000000000000", + "height": 562628, + "prevhash": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "merkle_root": "0x2488333a3bab6cc72d04ee1523ae83c9559938bef8521cb624c9641bb58cabe9" + }, + { + "raw": "0x00000020b86918706260609b3b6aaf684aed961eac6b015e637024000000000000000000e72e0a6fd324d36edee39f9336c56f129f1ef7c2ec26d0dfb01e4520fb7a8d7fcdce615c886f2e17415cdb46", + "hash": "0x20ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f05000000000000000000", + "height": 562629, + "prevhash": "0xb86918706260609b3b6aaf684aed961eac6b015e637024000000000000000000", + "merkle_root": "0xe72e0a6fd324d36edee39f9336c56f129f1ef7c2ec26d0dfb01e4520fb7a8d7f" + }, + { + "raw": "0x0000002020ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f050000000000000000009d1479517fda612a10a279b2339952bbdba8fe47c8fec644921d146ec79482ecebd3615c886f2e179234c38e", + "hash": "0x51214b0c42383a1ea7bf28f20062f81d7b72497cb1030a000000000000000000", + "height": 562630, + "prevhash": "0x20ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f05000000000000000000", + "merkle_root": "0x9d1479517fda612a10a279b2339952bbdba8fe47c8fec644921d146ec79482ec" + }, + { + "raw": "0x0000002051214b0c42383a1ea7bf28f20062f81d7b72497cb1030a00000000000000000000af444756eb5313dae6cb8dc7b4e00ae7d79cfa67a85b4b486a9583896ab3314bd8615c886f2e17f152bc1f", + "hash": "0xbf01515ce1f4f971b9805205373093c200b2bf92d56408000000000000000000", + "height": 562631, + "prevhash": "0x51214b0c42383a1ea7bf28f20062f81d7b72497cb1030a000000000000000000", + "merkle_root": "0x00af444756eb5313dae6cb8dc7b4e00ae7d79cfa67a85b4b486a9583896ab331" + }, + { + "raw": "0x00e00020bf01515ce1f4f971b9805205373093c200b2bf92d56408000000000000000000b2c2fcb555d6e2d677bb9919cc2d9660c81879225d15f53f679e3fdbfad129d032db615c886f2e17155f22df", + "hash": "0xa4c292016c1585e6f81986f7e216c79d28b15b1d513a10000000000000000000", + "height": 562632, + "prevhash": "0xbf01515ce1f4f971b9805205373093c200b2bf92d56408000000000000000000", + "merkle_root": "0xb2c2fcb555d6e2d677bb9919cc2d9660c81879225d15f53f679e3fdbfad129d0" + }, + { + "raw": "0x00000020a4c292016c1585e6f81986f7e216c79d28b15b1d513a100000000000000000004e33f75c5f63371d4a05e7ab93afb7c1caa22d2f9d4fce61e194ab8ffe741f35c0de615c886f2e17032ddf4b", + "hash": "0x577d11b45f90733748343b5add65dbe88216fa3027cc20000000000000000000", + "height": 562633, + "prevhash": "0xa4c292016c1585e6f81986f7e216c79d28b15b1d513a10000000000000000000", + "merkle_root": "0x4e33f75c5f63371d4a05e7ab93afb7c1caa22d2f9d4fce61e194ab8ffe741f35" + }, + { + "raw": "0x00000020577d11b45f90733748343b5add65dbe88216fa3027cc20000000000000000000ef200d52b09ae1902d62476f09405e21fa81cd7972ccfeaf37b47b00ef4e2180cdde615c886f2e171f2b5f80", + "hash": "0xf5f2d6840112ff9281bd88f445f135dfb41a64cebeb725000000000000000000", + "height": 562634, + "prevhash": "0x577d11b45f90733748343b5add65dbe88216fa3027cc20000000000000000000", + "merkle_root": "0xef200d52b09ae1902d62476f09405e21fa81cd7972ccfeaf37b47b00ef4e2180" + }, + { + "raw": "0x00000020f5f2d6840112ff9281bd88f445f135dfb41a64cebeb725000000000000000000a91738fc7b8628e70906164624f80ce54bafe26fe0ef8678f569b0b64e83589cb3e0615c886f2e17c624e58c", + "hash": "0xf98794c5b71e25f07eb2a31ab31b2e2487e0859abec000000000000000000000", + "height": 562635, + "prevhash": "0xf5f2d6840112ff9281bd88f445f135dfb41a64cebeb725000000000000000000", + "merkle_root": "0xa91738fc7b8628e70906164624f80ce54bafe26fe0ef8678f569b0b64e83589c" + }, + { + "raw": "0x00e00020f98794c5b71e25f07eb2a31ab31b2e2487e0859abec000000000000000000000c29b14f0fe90ac2173197665d460df45c37ccf0c873276f59d095cbed4bcc7c2fae4615c886f2e178a505d9d", + "hash": "0x0ed18ffcb751e45471dddab23d34538869d3b2cdd48428000000000000000000", + "height": 562636, + "prevhash": "0xf98794c5b71e25f07eb2a31ab31b2e2487e0859abec000000000000000000000", + "merkle_root": "0xc29b14f0fe90ac2173197665d460df45c37ccf0c873276f59d095cbed4bcc7c2" + }, + { + "raw": "0x000000200ed18ffcb751e45471dddab23d34538869d3b2cdd484280000000000000000003505702919866f91f1196e078799287f80be0b2c3af830ed6011ce89fc7f0d657ce5615c886f2e17d759c936", + "hash": "0x30ae17c8d62a00b0425c341f6babbc8424e6edc236032a000000000000000000", + "height": 562637, + "prevhash": "0x0ed18ffcb751e45471dddab23d34538869d3b2cdd48428000000000000000000", + "merkle_root": "0x3505702919866f91f1196e078799287f80be0b2c3af830ed6011ce89fc7f0d65" + }, + { + "raw": "0x0000802030ae17c8d62a00b0425c341f6babbc8424e6edc236032a000000000000000000097a32800849429e29be38addab29936925589e1e547d1ce968d62e566a45882f1e5615c886f2e1796e65983", + "hash": "0x51f32093b60f4484c041d383fb6bf35c44d428bde54d2d000000000000000000", + "height": 562638, + "prevhash": "0x30ae17c8d62a00b0425c341f6babbc8424e6edc236032a000000000000000000", + "merkle_root": "0x097a32800849429e29be38addab29936925589e1e547d1ce968d62e566a45882" + }, + { + "raw": "0x0000802051f32093b60f4484c041d383fb6bf35c44d428bde54d2d0000000000000000001888692df72fdbc27082d23e323b6acacaa5424d191dbaa7f13dfed36cdfbee905e6615c886f2e177b59d28f", + "hash": "0x48417c9cd5b52348f8d603dcc624bc5db68ef4a7dbb80f000000000000000000", + "height": 562639, + "prevhash": "0x51f32093b60f4484c041d383fb6bf35c44d428bde54d2d000000000000000000", + "merkle_root": "0x1888692df72fdbc27082d23e323b6acacaa5424d191dbaa7f13dfed36cdfbee9" + } + ], + "testCases": [{ + "ancestor": "0xa4c292016c1585e6f81986f7e216c79d28b15b1d513a10000000000000000000", + "currentBest": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "newBest": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "limit": 20, + "error": 104, + "output": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, { + "ancestor": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "currentBest": "0xa4c292016c1585e6f81986f7e216c79d28b15b1d513a10000000000000000000", + "newBest": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "limit": 20, + "error": 104, + "output": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, { + "ancestor": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "currentBest": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "newBest": "0xa4c292016c1585e6f81986f7e216c79d28b15b1d513a10000000000000000000", + "limit": 20, + "error": 104, + "output": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, { + "ancestor": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "currentBest": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "newBest": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "limit": 20, + "error": 102, + "output": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, { + "ancestor": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "currentBest": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "newBest": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "limit": 20, + "error": 102, + "output": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, { + "ancestor": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "currentBest": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "newBest": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "limit": 20, + "error": 0, + "output": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000" + }, { + "ancestor": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "currentBest": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "newBest": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "limit": 20, + "error": 0, + "output": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000" + }, { + "ancestor": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "currentBest": "0x51214b0c42383a1ea7bf28f20062f81d7b72497cb1030a000000000000000000", + "newBest": "0x5204b3afd5c0dc010de8eeb28925b97d4b38a16b1d020f000000000000000000", + "limit": 20, + "error": 0, + "output": "0x51214b0c42383a1ea7bf28f20062f81d7b72497cb1030a000000000000000000" + }, { + "ancestor": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "currentBest": "0x5204b3afd5c0dc010de8eeb28925b97d4b38a16b1d020f000000000000000000", + "newBest": "0x51214b0c42383a1ea7bf28f20062f81d7b72497cb1030a000000000000000000", + "limit": 20, + "error": 0, + "output": "0x5204b3afd5c0dc010de8eeb28925b97d4b38a16b1d020f000000000000000000" + }] + }, + "markNewHeaviest": [{ + "bestKnownDigest": "0x8b00e94f19ef7f100c3f5c4484b059afdb4897595ced82000000000000000000", + "ancestor": "0x9b00e94f19ef7f100c3f5c4484b059afdb4897595ced82000000000000000000", + "currentBest": "0x00000020a32e8e27455216a02d4704b3a3c4731cf34117d468d97f020000000000000000948fa46542c2d31a37733dca03b2cc82ffadc067fd603479f64d773fcfc13801d2a90b58d25504183d4ee3b4", + "newBest": "0x00000020a32e8e27455216a02d4704b3a3c4731cf34117d468d97f020000000000000000948fa46542c2d31a37733dca03b2cc82ffadc067fd603479f64d773fcfc13801d2a90b58d25504183d4ee3b4", + "limit": 10, + "error": 403, + "output": "" + }, { + "bestKnownDigest": "0x16d4ac8089850ad78c94132a7fd2b7d8115704b73ea673000000000000000000", + "ancestor": "0x16d4ac8089850ad78c94132a7fd2b7d8115704b73ea673000000000000000000", + "currentBest": "0x00000020d0eb8cc9a5668ddabb83e6f657d940fd56902ceb039f200000000000000000005e407cf51ee81930145648d1e72c1caeafa6f6bfc4a7ec3988bcb7cf69a1594af3fe1d58d2550418e90ec632", + "newBest": "9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", + "limit": 10, + "error": 104, + "output": "" + }, { + "bestKnownDigest": "0x58bc010196dd410ab4437e8b1601376e273fa45abb5b3c030000000000000000", + "ancestor": "0x58bc010196dd410ab4437e8b1601376e273fa45abb5b3c030000000000000000", + "currentBest": "0x0000002016d4ac8089850ad78c94132a7fd2b7d8115704b73ea673000000000000000000993956e6dbdabd742e9078408e4a5a75e3760d18a24cc6b18e65b6bb3b15ed0f5b001e58d2550418183b1b41", + "newBest": "0x00000020e156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000c52c39a4e31158ff7c34417c9750038b27c9a94dc08210e53ba624667c9310973d161e5874510418a9f7e0d0", + "limit": 20, + "error": 0, + "output": "extension" + }, { + "bestKnownDigest": "0xf24d697743f1fce5857ee6f7d709ed60830ae4239da71d010000000000000000", + "ancestor": "0xe156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000", + "currentBest": "0x00000020e156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000c52c39a4e31158ff7c34417c9750038b27c9a94dc08210e53ba624667c9310973d161e5874510418a9f7e0d0", + "newBest": "0x00000020e156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000f7e22ae2e5442bac43e18fc032dafd058fbdf886e33a3330e7597ce6eb4c073347161e58745104186cff9846", + "limit": 10, + "error": 405, + "output": "" + }] + }, + "validator": { + "validateProof": [{ + "proof": { + "version": "0x01000000", + "vin": "0x0101748906a5c7064550a594c4683ffc6d1ee25292b638c4328bb66403cfceb58a000000006a4730440220364301a77ee7ae34fa71768941a2aad5bd1fa8d3e30d4ce6424d8752e83f2c1b02203c9f8aafced701f59ffb7c151ff2523f3ed1586d29b674efb489e803e9bf93050121029b3008c0fa147fd9db5146e42b27eb0a77389497713d3aad083313d1b1b05ec0ffffffff", + "vout": "0x0316312f00000000001976a91400cc8d95d6835252e0d95eb03b11691a21a7bac588ac220200000000000017a914e5034b9de4881d62480a2df81032ef0299dcdc32870000000000000000166a146f6d6e69000000000000001f0000000315e17900", + "locktime": "0x00000000", + "tx_id": "0x5176f6b03b8bc29f4deafbb7384b673debde6ae712deab93f3b0c91fdcd6d674", + "index": 26, + "intermediate_nodes": "0x8d7a6d53ce27f79802631f1aae5f172c43d128b210ab4962d488c81c96136cfb75c95def872e878839bd93b42c04eb44da44c401a2d580ca343c3262e9c0a2819ed4bbfb9ea620280b31433f43b2512a893873b8c8c679f61e1a926c0ec80bcfc6225a15d72fbd1116f78b14663d8518236b02e765bf0a746a6a08840c122a02afa4df3ab6b9197a20f00495a404ee8e07da2b7554e94609e9ee1d5da0fb7857ea0332072568d0d53a9aedf851892580504a7fcabfbdde076242eb7f4e5f218a14d2a3f357d950b4f6a1dcf93f7c19c44d0fc122d00afa297b9503c1a6ad24cf36cb5f2835bcf490371db2e96047813a24176c3d3416f84b7ddfb7d8c915eb0c5ce7de089b5d9e700ecd12e09163f173b70bb4c9af33051b466b1f55abd66f3121216ad0ad9dfa898535e1d5e51dd07bd0a73d584daace7902f20ece4ba4f4f241c80cb31eda88a244a3c68d0f157c1049b4153d7addd6548aca0885acafbf98a1f8345c89914c24729ad095c7a0b9acd20232ccd90dbd359468fcc4eee7b67d","confirming_header": { + "hash": "0x4d0cfbf5aa3b2359e5cb7dcf3b286264bd22de883b6316000000000000000000", + "height": 592920, + "raw": "0x0000c020c238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000b61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2ddd6376d5d3e211a17d8706a84", + "merkle_root": "0xb61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2dd", + "prevhash": "0xc238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000" + } + }, + "bestKnown": { + "hash_le": "0x5d0cfbf5aa3b2359e5cb7dcf3b286264bd22de883b6316000000000000000000", + "height": 592924 + }, + "lca": "0xc238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000", + "error": 0 + }, + { + "proof": { + "version": "0x01000000", + "vin": "0x0102748906a5c7064550a594c4683ffc6d1ee25292b638c4328bb66403cfceb58a000000006a4730440220364301a77ee7ae34fa71768941a2aad5bd1fa8d3e30d4ce6424d8752e83f2c1b02203c9f8aafced701f59ffb7c151ff2523f3ed1586d29b674efb489e803e9bf93050121029b3008c0fa147fd9db5146e42b27eb0a77389497713d3aad083313d1b1b05ec0ffffffff", + "vout": "0x0316312f00000000001976a91400cc8d95d6835252e0d95eb03b11691a21a7bac588ac220200000000000017a914e5034b9de4881d62480a2df81032ef0299dcdc32870000000000000000166a146f6d6e69000000000000001f0000000315e17900", + "locktime": "0x00000000", + "tx_id": "0x5176f6b03b8bc29f4deafbb7384b673debde6ae712deab93f3b0c91fdcd6d674", + "index": 26, + "intermediate_nodes": "0x8d7a6d53ce27f79802631f1aae5f172c43d128b210ab4962d488c81c96136cfb75c95def872e878839bd93b42c04eb44da44c401a2d580ca343c3262e9c0a2819ed4bbfb9ea620280b31433f43b2512a893873b8c8c679f61e1a926c0ec80bcfc6225a15d72fbd1116f78b14663d8518236b02e765bf0a746a6a08840c122a02afa4df3ab6b9197a20f00495a404ee8e07da2b7554e94609e9ee1d5da0fb7857ea0332072568d0d53a9aedf851892580504a7fcabfbdde076242eb7f4e5f218a14d2a3f357d950b4f6a1dcf93f7c19c44d0fc122d00afa297b9503c1a6ad24cf36cb5f2835bcf490371db2e96047813a24176c3d3416f84b7ddfb7d8c915eb0c5ce7de089b5d9e700ecd12e09163f173b70bb4c9af33051b466b1f55abd66f3121216ad0ad9dfa898535e1d5e51dd07bd0a73d584daace7902f20ece4ba4f4f241c80cb31eda88a244a3c68d0f157c1049b4153d7addd6548aca0885acafbf98a1f8345c89914c24729ad095c7a0b9acd20232ccd90dbd359468fcc4eee7b67d","confirming_header": { + "hash": "0x4d0cfbf5aa3b2359e5cb7dcf3b286264bd22de883b6316000000000000000000", + "height": 592920, + "raw": "0x0000c020c238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000b61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2ddd6376d5d3e211a17d8706a84", + "merkle_root": "0xb61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2dd", + "prevhash": "0xc238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000" + } + }, + "lca": "0xc238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000", + "error": 108 + }], + "checkRequestsFilled": [{ + "filledRequest": { + "proof": { + "version": "0x01000000", + "vin": "0x0101748906a5c7064550a594c4683ffc6d1ee25292b638c4328bb66403cfceb58a000000006a4730440220364301a77ee7ae34fa71768941a2aad5bd1fa8d3e30d4ce6424d8752e83f2c1b02203c9f8aafced701f59ffb7c151ff2523f3ed1586d29b674efb489e803e9bf93050121029b3008c0fa147fd9db5146e42b27eb0a77389497713d3aad083313d1b1b05ec0ffffffff", + "vout": "0x0316312f00000000001976a91400cc8d95d6835252e0d95eb03b11691a21a7bac588ac220200000000000017a914e5034b9de4881d62480a2df81032ef0299dcdc32870000000000000000166a146f6d6e69000000000000001f0000000315e17900", + "locktime": "0x00000000", + "tx_id": "0x5176f6b03b8bc29f4deafbb7384b673debde6ae712deab93f3b0c91fdcd6d674", + "index": 26, + "intermediate_nodes": "0x8d7a6d53ce27f79802631f1aae5f172c43d128b210ab4962d488c81c96136cfb75c95def872e878839bd93b42c04eb44da44c401a2d580ca343c3262e9c0a2819ed4bbfb9ea620280b31433f43b2512a893873b8c8c679f61e1a926c0ec80bcfc6225a15d72fbd1116f78b14663d8518236b02e765bf0a746a6a08840c122a02afa4df3ab6b9197a20f00495a404ee8e07da2b7554e94609e9ee1d5da0fb7857ea0332072568d0d53a9aedf851892580504a7fcabfbdde076242eb7f4e5f218a14d2a3f357d950b4f6a1dcf93f7c19c44d0fc122d00afa297b9503c1a6ad24cf36cb5f2835bcf490371db2e96047813a24176c3d3416f84b7ddfb7d8c915eb0c5ce7de089b5d9e700ecd12e09163f173b70bb4c9af33051b466b1f55abd66f3121216ad0ad9dfa898535e1d5e51dd07bd0a73d584daace7902f20ece4ba4f4f241c80cb31eda88a244a3c68d0f157c1049b4153d7addd6548aca0885acafbf98a1f8345c89914c24729ad095c7a0b9acd20232ccd90dbd359468fcc4eee7b67d", + "confirming_header": { + "hash": "0x4d0cfbf5aa3b2359e5cb7dcf3b286264bd22de883b6316000000000000000000", + "height": 592920, + "raw": "0x0000c020c238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000b61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2ddd6376d5d3e211a17d8706a84", + "merkle_root": "0xb61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2dd", + "prevhash": "0xc238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000" + } + }, + "requests": [{ + "inputIndex": 0, + "outputIndex": 1, + "id": "0x0000000000000000" + }] + }, + "error": 0 + }, { + "filledRequest": { + "proof": { + "version": "0x01000000", + "vin": "0x011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffff", + "vout": "0x0316312f00000000001976a91400cc8d95d6835252e0d95eb03b11691a21a7bac588ac220200000000000017a914e5034b9de4881d62480a2df81032ef0299dcdc32870000000000000000166a146f6d6e69000000000000001f0000000315e17900", + "locktime": "0x00000000", + "tx_id": "0x5176f6b03b8bc29f4deafbb7384b673debde6ae712deab93f3b0c91fdcd6d674", + "index": 26, + "intermediate_nodes": "0x8d7a6d53ce27f79802631f1aae5f172c43d128b210ab4962d488c81c96136cfb75c95def872e878839bd93b42c04eb44da44c401a2d580ca343c3262e9c0a2819ed4bbfb9ea620280b31433f43b2512a893873b8c8c679f61e1a926c0ec80bcfc6225a15d72fbd1116f78b14663d8518236b02e765bf0a746a6a08840c122a02afa4df3ab6b9197a20f00495a404ee8e07da2b7554e94609e9ee1d5da0fb7857ea0332072568d0d53a9aedf851892580504a7fcabfbdde076242eb7f4e5f218a14d2a3f357d950b4f6a1dcf93f7c19c44d0fc122d00afa297b9503c1a6ad24cf36cb5f2835bcf490371db2e96047813a24176c3d3416f84b7ddfb7d8c915eb0c5ce7de089b5d9e700ecd12e09163f173b70bb4c9af33051b466b1f55abd66f3121216ad0ad9dfa898535e1d5e51dd07bd0a73d584daace7902f20ece4ba4f4f241c80cb31eda88a244a3c68d0f157c1049b4153d7addd6548aca0885acafbf98a1f8345c89914c24729ad095c7a0b9acd20232ccd90dbd359468fcc4eee7b67d", + "confirming_header": { + "hash": "0x4d0cfbf5aa3b2359e5cb7dcf3b286264bd22de883b6316000000000000000000", + "height": 592920, + "raw": "0x0000c020c238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000b61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2ddd6376d5d3e211a17d8706a84", + "merkle_root": "0xb61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2dd", + "prevhash": "0xc238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000" + } + }, + "requests": [{ + "inputIndex": 0, + "outputIndex": 1, + "id": "0x0000000000000000" + }] + }, + "error": 108 + }, { + "filledRequest": { + "proof": { + "version": "0x01000000", + "vin": "0x0101748906a5c7064550a594c4683ffc6d1ee25292b638c4328bb66403cfceb58a000000006a4730440220364301a77ee7ae34fa71768941a2aad5bd1fa8d3e30d4ce6424d8752e83f2c1b02203c9f8aafced701f59ffb7c151ff2523f3ed1586d29b674efb489e803e9bf93050121029b3008c0fa147fd9db5146e42b27eb0a77389497713d3aad083313d1b1b05ec0ffffffff", + "vout": "0x0316312f00000000001976a91400cc8d95d6835252e0d95eb03b11691a21a7bac588ac220200000000000017a914e5034b9de4881d62480a2df81032ef0299dcdc32870000000000000000166a146f6d6e69000000000000001f0000000315e17900", + "locktime": "0x00000000", + "tx_id": "0x5176f6b03b8bc29f4deafbb7384b673debde6ae712deab93f3b0c91fdcd6d674", + "index": 26, + "intermediate_nodes": "0x8d7a6d53ce27f79802631f1aae5f172c43d128b210ab4962d488c81c96136cfb75c95def872e878839bd93b42c04eb44da44c401a2d580ca343c3262e9c0a2819ed4bbfb9ea620280b31433f43b2512a893873b8c8c679f61e1a926c0ec80bcfc6225a15d72fbd1116f78b14663d8518236b02e765bf0a746a6a08840c122a02afa4df3ab6b9197a20f00495a404ee8e07da2b7554e94609e9ee1d5da0fb7857ea0332072568d0d53a9aedf851892580504a7fcabfbdde076242eb7f4e5f218a14d2a3f357d950b4f6a1dcf93f7c19c44d0fc122d00afa297b9503c1a6ad24cf36cb5f2835bcf490371db2e96047813a24176c3d3416f84b7ddfb7d8c915eb0c5ce7de089b5d9e700ecd12e09163f173b70bb4c9af33051b466b1f55abd66f3121216ad0ad9dfa898535e1d5e51dd07bd0a73d584daace7902f20ece4ba4f4f241c80cb31eda88a244a3c68d0f157c1049b4153d7addd6548aca0885acafbf98a1f8345c89914c24729ad095c7a0b9acd20232ccd90dbd359468fcc4eee7b67d", + "confirming_header": { + "hash": "0x4d0cfbf5aa3b2359e5cb7dcf3b286264bd22de883b6316000000000000000000", + "height": 592920, + "raw": "0x0000c020c238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000b61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2ddd6376d5d3e211a17d8706a84", + "merkle_root": "0xb61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2dd", + "prevhash": "0xc238b601308b7297346ab2ed59942d7d7ecea8d23a1001000000000000000000" + } + }, + "requests": [{ + "inputIndex": 0, + "outputIndex": 1, + "id": "0x0000000000000000" + }, { + "inputIndex": 0, + "outputIndex": 1, + "id": "0x0700000000000000" + }] + }, + "error": 601 + }] + }, + "requests": { + "emptyRequest": { + "spends": "0x1406e05881e299367766d313e26c05564ec91bf721d31726bd6e46e60689539a", + "pays": "0x1406e05881e299367766d313e26c05564ec91bf721d31726bd6e46e60689539a", + "paysValue": 0, + "activeState": true, + "numConfs": 0 + }, + "checkRequests": [{ + "inputIndex": 0, + "outputIndex": 1, + "vin": "0x0101748906a5c7064550a594c4683ffc6d1ee25292b638c4328bb66403cfceb58a000000006a4730440220364301a77ee7ae34fa71768941a2aad5bd1fa8d3e30d4ce6424d8752e83f2c1b02203c9f8aafced701f59ffb7c151ff2523f3ed1586d29b674efb489e803e9bf93050121029b3008c0fa147fd9db5146e42b27eb0a77389497713d3aad083313d1b1b05ec0ffffffff", + "vout": "0x0316312f00000000001976a91400cc8d95d6835252e0d95eb03b11691a21a7bac588ac220200000000000017a914e5034b9de4881d62480a2df81032ef0299dcdc32870000000000000000166a146f6d6e69000000000000001f0000000315e17900", + "requestID": "0x0000000000000000", + "error": 605 + }, { + "inputIndex": 1, + "outputIndex": 2, + "vin": "0x011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffff", + "vout": "0x02", + "requestID": "0x0100000000000000", + "error": 604 + }, { + "inputIndex": 1, + "outputIndex": 2, + "vin": "0x0101748906a5c7064550a594c4683ffc6d1ee25292b638c4328bb66403cfceb58a000000006a4730440220364301a77ee7ae34fa71768941a2aad5bd1fa8d3e30d4ce6424d8752e83f2c1b02203c9f8aafced701f59ffb7c151ff2523f3ed1586d29b674efb489e803e9bf93050121029b3008c0fa147fd9db5146e42b27eb0a77389497713d3aad083313d1b1b05ec0ffffffff", + "vout": "0x004897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211", + "requestID": "0x0100000000000000", + "error": 605 + }] + } +}