Skip to content

Commit

Permalink
Merge pull request #81 from coinbase/patrick/construction-tester
Browse files Browse the repository at this point in the history
Construction API Tester
  • Loading branch information
patrick-ogrady authored Jul 29, 2020
2 parents dbe09a0 + ec5dccd commit e77ad26
Show file tree
Hide file tree
Showing 41 changed files with 5,689 additions and 420 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
validator-data
rosetta-cli
bootstrap_balances.csv
13 changes: 11 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: deps lint format check-format test test-cover add-license \
check-license shorten-lines salus validate watch-blocks \
watch-transactions watch-balances watch-reconciliations \
view-block-benchmarks view-account-benchmarks
view-block-benchmarks view-account-benchmarks mocks

# To run the the following packages as commands,
# it is necessary to use `go run <pkg>`. Running `go get` does
Expand All @@ -11,7 +11,12 @@ ADDLICENSE_CMD=go run github.com/google/addlicense
ADDLICENCE_SCRIPT=${ADDLICENSE_CMD} -c "Coinbase, Inc." -l "apache" -v
GOLINES_CMD=go run github.com/segmentio/golines
GOVERALLS_CMD=go run github.com/mattn/goveralls
COVERAGE_TEST_DIRECTORIES=./configuration/... ./internal/constructor/... \
./internal/logger/... ./internal/scenario/... \
./internal/statefulsyncer/... ./internal/storage/... \
./internal/tester/... ./internal/utils/...
TEST_SCRIPT=go test -v ./internal/... ./configuration/...
COVERAGE_TEST_SCRIPT=go test -v ${COVERAGE_TEST_DIRECTORIES}

deps:
go get ./...
Expand All @@ -30,7 +35,7 @@ test:
${TEST_SCRIPT}

test-cover:
if [ "${COVERALLS_TOKEN}" ]; then ${TEST_SCRIPT} -coverprofile=c.out -covermode=count; ${GOVERALLS_CMD} -coverprofile=c.out -repotoken ${COVERALLS_TOKEN}; fi
if [ "${COVERALLS_TOKEN}" ]; then ${COVERAGE_TEST_SCRIPT} -coverprofile=c.out -covermode=count; ${GOVERALLS_CMD} -coverprofile=c.out -repotoken ${COVERALLS_TOKEN}; fi

add-license:
${ADDLICENCE_SCRIPT} .
Expand All @@ -51,3 +56,7 @@ build:

install:
go install ./...

mocks:
rm -rf mocks;
mockery --dir internal/constructor --all --case underscore --outpkg constructor --output mocks/constructor;
106 changes: 94 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Available Commands:
version Print rosetta-cli version
view:account View an account balance
view:block View a block
view:network View network status
view:networks View all network statuses
Flags:
--configuration-file string Configuration file that provides connection and test settings.
Expand All @@ -64,7 +64,36 @@ Flags:
Use "rosetta-cli [command] --help" for more information about a command.
```

### version
### Configuration
All `rosetta-cli` parameters are populated from a configuration file (`--configuration-file`)
provided at runtime. If a configuration file is not provided, the default
configuration is used. This default configuration can be viewed
[here](examples/configuration/default.json).

In the `examples/configuration` directory, you can find examples configuration
files for running tests against a Bitcoin Rosetta implementation
([config](examples/configuration/bitcoin.json)) and an Ethereum Rosetta
implementation ([config](examples/configuration/ethereum.json)).

#### Disable Complex Checks
If you are just getting started with your implementation, you may want
to disable balance tracking (did any address balance go below zero?) and
reconciliation (does the balance I calculated match the balance returned
by the `/account/balance` endpoint?). Take a look at the
[simple configuration](examples/configuration/simple.json) for an example of
how to do this.

#### Future Work
In the near future, we will add support for providing complex exit conditions
(i.e. did we reach tip? did we reconcile every account?) for both
`check:construction` and `check:data` so that the `rosetta-cli`
can be integrated into a CI flow. Currently, the only way to exit with a
successful status in the `rosetta-cli` is to provide an `--end` flag
when running `check:data` (returns 0 if no errors up to a block index
are observed).

### Commands
#### version
```
Print rosetta-cli version
Expand All @@ -83,7 +112,7 @@ Global Flags:
default values.
```

### check:data
#### check:data
```
Check all server responses are properly constructed, that
there are no duplicate blocks and transactions, that blocks can be processed
Expand Down Expand Up @@ -137,14 +166,29 @@ Global Flags:
default values.
```

#### Status Codes
##### Status Codes
If there are no issues found while running `check`, it will exit with a `0` status code.
If there are any issues, it will exit with a `1` status code. It can be useful
to run this command as an integration test for any changes to your implementation.

### configuration:create
#### check:construction
```
Check the correctness of a Rosetta Construction API Implementation
The check:construction command runs an automated test of a
Construction API implementation by creating and broadcasting transactions
on a blockchain. In short, this tool generates new addresses, requests
funds, constructs transactions, signs transactions, broadcasts transactions,
and confirms transactions land on-chain. At each phase, a series of tests
are run to ensure that intermediate representations are correct (i.e. does
an unsigned transaction return a superset of operations provided during
construction?).
Check out the https://github.com/coinbase/rosetta-cli/tree/master/examples
directory for examples of how to configure this test for Bitcoin and
Ethereum.
Right now, this tool only supports transfer testing (for both account-based
and UTXO-based blockchains). However, we plan to add support for testing
arbitrary scenarios (i.e. staking, governance).
Usage:
rosetta-cli check:construction [flags]
Expand All @@ -161,7 +205,45 @@ Global Flags:
default values.
```

### view:network
#### configuration:create
```
Create a default configuration file at the provided path
Usage:
rosetta-cli configuration:create [flags]
Flags:
-h, --help help for configuration:create
Global Flags:
--configuration-file string Configuration file that provides connection and test settings.
If you would like to generate a starter configuration file (populated
with the defaults), run rosetta-cli configuration:create.
Any fields not populated in the configuration file will be populated with
default values.
```

#### configuration:validate
```
Validate the correctness of a configuration file at the provided path
Usage:
rosetta-cli configuration:validate [flags]
Flags:
-h, --help help for configuration:validate
Global Flags:
--configuration-file string Configuration file that provides connection and test settings.
If you would like to generate a starter configuration file (populated
with the defaults), run rosetta-cli configuration:create.
Any fields not populated in the configuration file will be populated with
default values.
```

#### view:networks
```
While debugging a Data API implementation, it can be very
useful to view network(s) status. This command fetches the network
Expand All @@ -171,10 +253,10 @@ If this command errors, it is likely because the /network/* endpoints are
not formatted correctly.
Usage:
rosetta-cli view:network [flags]
rosetta-cli view:networks [flags]
Flags:
-h, --help help for view:network
-h, --help help for view:networks
Global Flags:
--configuration-file string Configuration file that provides connection and test settings.
Expand All @@ -185,7 +267,7 @@ Global Flags:
default values.
```

### view:account
#### view:account
```
While debugging, it is often useful to inspect the state
of an account at a certain block. This command allows you to look up
Expand All @@ -211,7 +293,7 @@ Global Flags:
default values.
```

### view:block
#### view:block
```
While debugging a Data API implementation, it can be very
useful to inspect block contents. This command allows you to fetch any
Expand All @@ -238,7 +320,7 @@ Global Flags:
default values.
```

### utils:asserter-configuration
#### utils:asserter-configuration
```
In production deployments, it is useful to initialize the response
Asserter (https://github.com/coinbase/rosetta-sdk-go/tree/master/asserter) using
Expand Down
97 changes: 94 additions & 3 deletions cmd/check_construction.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,111 @@
package cmd

import (
"context"
"log"
"os"
"time"

"github.com/coinbase/rosetta-cli/internal/tester"
"github.com/coinbase/rosetta-cli/internal/utils"

"github.com/coinbase/rosetta-sdk-go/fetcher"
"github.com/fatih/color"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)

var (
checkConstructionCmd = &cobra.Command{
Use: "check:construction",
Short: "Check the correctness of a Rosetta Construction API Implementation",
Run: runCheckConstructionCmd,
Long: `The check:construction command runs an automated test of a
Construction API implementation by creating and broadcasting transactions
on a blockchain. In short, this tool generates new addresses, requests
funds, constructs transactions, signs transactions, broadcasts transactions,
and confirms transactions land on-chain. At each phase, a series of tests
are run to ensure that intermediate representations are correct (i.e. does
an unsigned transaction return a superset of operations provided during
construction?).
Check out the https://github.com/coinbase/rosetta-cli/tree/master/examples
directory for examples of how to configure this test for Bitcoin and
Ethereum.
Right now, this tool only supports transfer testing (for both account-based
and UTXO-based blockchains). However, we plan to add support for testing
arbitrary scenarios (i.e. staking, governance).`,
Run: runCheckConstructionCmd,
}
)

func runCheckConstructionCmd(cmd *cobra.Command, args []string) {
// ensureDataDirectoryExists()
log.Fatal("not implemented!")
ensureDataDirectoryExists()
ctx, cancel := context.WithCancel(context.Background())

fetcher := fetcher.New(
Config.OnlineURL,
fetcher.WithBlockConcurrency(Config.BlockConcurrency),
fetcher.WithTransactionConcurrency(Config.TransactionConcurrency),
fetcher.WithRetryElapsedTime(ExtendedRetryElapsedTime),
fetcher.WithTimeout(time.Duration(Config.HTTPTimeout)*time.Second),
)

_, _, err := fetcher.InitializeAsserter(ctx)
if err != nil {
log.Fatalf("%s: unable to initialize asserter", err.Error())
}

_, err = utils.CheckNetworkSupported(ctx, Config.Network, fetcher)
if err != nil {
log.Fatalf("%s: unable to confirm network is supported", err.Error())
}

constructionTester, err := tester.InitializeConstruction(
ctx,
Config,
Config.Network,
fetcher,
cancel,
)
if err != nil {
log.Fatalf("%s: unable to initialize construction tester", err.Error())
}

defer constructionTester.CloseDatabase(ctx)

if err := constructionTester.PerformBroadcasts(ctx); err != nil {
log.Fatalf("%s: unable to perform broadcasts", err.Error())
}

g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
return constructionTester.StartPeriodicLogger(ctx)
})

g.Go(func() error {
return constructionTester.StartSyncer(ctx, cancel)
})

g.Go(func() error {
return constructionTester.StartConstructor(ctx)
})

sigListeners := []context.CancelFunc{cancel}
go handleSignals(sigListeners)

err = g.Wait()
if SignalReceived {
color.Red("Check halted")
os.Exit(1)
return
}

if err != nil {
color.Red("Check failed: %s", err.Error())
os.Exit(1)
}

// Will only hit this once exit conditions are added
color.Green("Check succeeded")
}
5 changes: 2 additions & 3 deletions cmd/check_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ func runCheckDataCmd(cmd *cobra.Command, args []string) {

fetcher := fetcher.New(
Config.OnlineURL,
fetcher.WithBlockConcurrency(Config.Data.BlockConcurrency),
fetcher.WithTransactionConcurrency(Config.Data.TransactionConcurrency),
fetcher.WithBlockConcurrency(Config.BlockConcurrency),
fetcher.WithTransactionConcurrency(Config.TransactionConcurrency),
fetcher.WithRetryElapsedTime(ExtendedRetryElapsedTime),
fetcher.WithTimeout(time.Duration(Config.HTTPTimeout)*time.Second),
)
Expand Down Expand Up @@ -126,7 +126,6 @@ func runCheckDataCmd(cmd *cobra.Command, args []string) {
defer dataTester.CloseDatabase(ctx)

g, ctx := errgroup.WithContext(ctx)

g.Go(func() error {
return dataTester.StartPeriodicLogger(ctx)
})
Expand Down
4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ default values.`,
// View Commands
rootCmd.AddCommand(viewBlockCmd)
rootCmd.AddCommand(viewAccountCmd)
rootCmd.AddCommand(viewNetworkCmd)
rootCmd.AddCommand(viewNetworksCmd)

// Utils
rootCmd.AddCommand(utilsAsserterConfigurationCmd)
Expand Down Expand Up @@ -142,6 +142,6 @@ var versionCmd = &cobra.Command{
Use: "version",
Short: "Print rosetta-cli version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("v0.3.2")
fmt.Println("v0.4.0")
},
}
3 changes: 2 additions & 1 deletion cmd/view_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func runViewAccountCmd(cmd *cobra.Command, args []string) {
lookupBlock = &types.PartialBlockIdentifier{Index: &index}
}

block, amounts, metadata, err := newFetcher.AccountBalanceRetry(
block, amounts, coins, metadata, err := newFetcher.AccountBalanceRetry(
ctx,
Config.Network,
account,
Expand All @@ -98,6 +98,7 @@ func runViewAccountCmd(cmd *cobra.Command, args []string) {
}

log.Printf("Amounts: %s\n", types.PrettyPrintStruct(amounts))
log.Printf("Coins: %s\n", types.PrettyPrintStruct(coins))
log.Printf("Metadata: %s\n", types.PrettyPrintStruct(metadata))
log.Printf("Balance Fetched At: %s\n", types.PrettyPrintStruct(block))
}
Loading

0 comments on commit e77ad26

Please sign in to comment.