Repository: coinbase/rosetta-cli
Pull request #212 (2021-01-12 16:08:29)

Replace at-tip checks with utils functions from the latest sdk
Fixes # .

### Motivation
<!--
Does this solve a bug? Enable a new use-case? Improve an existing behavior? Concrete examples are helpful here.
-->

Rosetta CLI checks to see if it is at tip at a few different places. These checks are not consistent.

For instance, before constructing transactions, the CLI checks if it is a tip based on (1) if the stored current block is within tip delay or (2) block returned by network/status is at tip (it is within tip delay or has sync status of true) and it is same as the stored current block.

Before broadcasting transactions, the CLI checks if it is at tip based only on if the stored current block is within tip delay.

These inconsistent checks were leading to the CLI getting stuck during transaction broadcast for chains that use sync status rather than tip delays.

### Solution
<!--
What is the solution here from a high level. What are the key technical decisions and why were they made?
-->
Use utility functions from the sdk's utils package to do at-tip checks consistently.

### Open questions
<!--
(optional) Any open questions or feedback on design desired?
-->


patrick-ogrady: You'll also want to modify this: https://github.com/coinbase/rosetta-cli/blob/23e6daf53528a2695b05e6a7f584d4f5e31c1023/cmd/root.go#L272
coveralls: ## Pull Request Test Coverage Report for [Build 6710](https://coveralls.io/builds/36270550)

* **0** of **0**   changed or added relevant lines in **0** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **58.737%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/36270550/badge)](https://coveralls.io/builds/36270550) |
| :-- | --: |
| Change from base [Build 6692](https://coveralls.io/builds/36169475): |  0.0% |
| Covered Lines: | 521 |
| Relevant Lines: | 887 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/cmd/root.go b/cmd/root.go
index 018358a..2b6bf31 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -269,6 +269,6 @@ var versionCmd = &cobra.Command{
 	Use:   "version",
 	Short: "Print rosetta-cli version",
 	Run: func(cmd *cobra.Command, args []string) {
-		fmt.Println("v0.6.6")
+		fmt.Println("v0.6.7")
 	},
 }
diff --git a/go.mod b/go.mod
index 2df8bef..ee7d452 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/coinbase/rosetta-cli
 go 1.13
 
 require (
-	github.com/coinbase/rosetta-sdk-go v0.6.7
+	github.com/coinbase/rosetta-sdk-go v0.6.8
 	github.com/fatih/color v1.10.0
 	github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c
 	github.com/spf13/cobra v1.1.1
diff --git a/go.sum b/go.sum
index 7a1efc7..e23cc8f 100644
--- a/go.sum
+++ b/go.sum
@@ -80,8 +80,8 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U=
-github.com/coinbase/rosetta-sdk-go v0.6.7 h1:v2fzv1cOVWB4A2QjIEcZpuh/CFwfLmX+Jd6qP3AZHN0=
-github.com/coinbase/rosetta-sdk-go v0.6.7/go.mod h1:DbkDDurZUlfMqFrWfSU3gLsLCaGDyw3Cqz/f1JWjo5U=
+github.com/coinbase/rosetta-sdk-go v0.6.8 h1:DCWvfpzN9HkKux0eBZwN+9b7vstP4ij77zpyHtYy8dk=
+github.com/coinbase/rosetta-sdk-go v0.6.8/go.mod h1:jczGuxueJURzr7J8/OeZM3FRbezjw8lWIMxXOBLONOM=
 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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@@ -366,23 +366,21 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
-github.com/tidwall/gjson v1.6.1 h1:LRbvNuNuvAiISWg6gxLEFuCe72UKy5hDqhxW/8183ws=
-github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0=
-github.com/tidwall/gjson v1.6.4 h1:JKsCsJqRVFz8eYCsQ5E/ANRbK6CanAtA9IUvGsXklyo=
-github.com/tidwall/gjson v1.6.4/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0=
-github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
-github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
+github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE=
+github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
+github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
+github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
 github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/tidwall/sjson v1.1.2 h1:NC5okI+tQ8OG/oyzchvwXXxRxCV/FVdhODbPKkQ25jQ=
-github.com/tidwall/sjson v1.1.2/go.mod h1:SEzaDwxiPzKzNfUEO4HbYF/m4UCSJDsGgNqsS1LvdoY=
+github.com/tidwall/sjson v1.1.4 h1:bTSsPLdAYF5QNLSwYsKfBKKTnlGbIuhqL3CpRsjzGhg=
+github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/Lx1fg=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
 github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/vmihailenco/msgpack/v5 v5.1.0 h1:+od5YbEXxW95SPlW6beocmt8nOtlh83zqat5Ip9Hwdc=
-github.com/vmihailenco/msgpack/v5 v5.1.0/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI=
+github.com/vmihailenco/msgpack/v5 v5.1.4 h1:6K44/cU6dMNGkVTGGuu7ef2NdSRFMhAFGGLfE3cqtHM=
+github.com/vmihailenco/msgpack/v5 v5.1.4/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI=
 github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
 github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
 github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
diff --git a/pkg/processor/broadcast_storage_helper.go b/pkg/processor/broadcast_storage_helper.go
index 91eecb2..5e88185 100644
--- a/pkg/processor/broadcast_storage_helper.go
+++ b/pkg/processor/broadcast_storage_helper.go
@@ -17,6 +17,7 @@ package processor
 import (
 	"context"
 	"fmt"
+	"github.com/coinbase/rosetta-sdk-go/utils"
 
 	"github.com/coinbase/rosetta-sdk-go/fetcher"
 	"github.com/coinbase/rosetta-sdk-go/storage/database"
@@ -29,16 +30,19 @@ var _ modules.BroadcastStorageHelper = (*BroadcastStorageHelper)(nil)
 // BroadcastStorageHelper implements the storage.Helper
 // interface.
 type BroadcastStorageHelper struct {
+	network      *types.NetworkIdentifier
 	blockStorage *modules.BlockStorage
 	fetcher      *fetcher.Fetcher
 }
 
 // NewBroadcastStorageHelper returns a new BroadcastStorageHelper.
 func NewBroadcastStorageHelper(
+	network *types.NetworkIdentifier,
 	blockStorage *modules.BlockStorage,
 	fetcher *fetcher.Fetcher,
 ) *BroadcastStorageHelper {
 	return &BroadcastStorageHelper{
+		network:      network,
 		blockStorage: blockStorage,
 		fetcher:      fetcher,
 	}
@@ -49,9 +53,9 @@ func (h *BroadcastStorageHelper) AtTip(
 	ctx context.Context,
 	tipDelay int64,
 ) (bool, error) {
-	atTip, _, err := h.blockStorage.AtTip(ctx, tipDelay)
+	atTip, _, err := utils.CheckStorageTip(ctx, h.network, tipDelay, h.fetcher, h.blockStorage)
 	if err != nil {
-		return false, fmt.Errorf("%w: unable to determine if at tip", err)
+		return false, err
 	}
 
 	return atTip, nil


Test patch:
diff --git a/pkg/tester/construction.go b/pkg/tester/construction.go
index 509a9f1..9f997d7 100644
--- a/pkg/tester/construction.go
+++ b/pkg/tester/construction.go
@@ -159,6 +159,7 @@ func InitializeConstruction(
 
 	parser := parser.New(onlineFetcher.Asserter, nil, networkOptions.Allow.BalanceExemptions)
 	broadcastHelper := processor.NewBroadcastStorageHelper(
+		network,
 		blockStorage,
 		onlineFetcher,
 	)
@@ -327,20 +328,18 @@ func (t *ConstructionTester) StartPeriodicLogger(
 }
 
 func (t *ConstructionTester) checkTip(ctx context.Context) (int64, error) {
-	status, fetchErr := t.onlineFetcher.NetworkStatusRetry(ctx, t.network, nil)
-	if fetchErr != nil {
-		return -1, fmt.Errorf("%w: unable to fetch network status", fetchErr.Err)
-	}
-
-	// If a block has yet to be synced, start syncing from tip.
-	if utils.AtTip(t.config.TipDelay, status.CurrentBlockTimestamp) {
-		return status.CurrentBlockIdentifier.Index, nil
+	atTip, blockIdentifier, err := utils.CheckNetworkTip(
+		ctx,
+		t.network,
+		t.config.TipDelay,
+		t.onlineFetcher,
+	)
+	if err != nil {
+		return -1, err
 	}
 
-	// If the Rosetta implementation says it is at tip (regardless of the current
-	// block timestamp), we should start.
-	if status.SyncStatus != nil && status.SyncStatus.Synced != nil && *status.SyncStatus.Synced {
-		return status.CurrentBlockIdentifier.Index, nil
+	if atTip {
+		return blockIdentifier.Index, nil
 	}
 
 	return -1, nil
diff --git a/pkg/tester/data.go b/pkg/tester/data.go
index 13e3ff4..336664c 100644
--- a/pkg/tester/data.go
+++ b/pkg/tester/data.go
@@ -488,39 +488,24 @@ func (t *DataTester) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 // syncedStatus returns a boolean indicating if we are synced to tip and
 // the last synced block.
 func (t *DataTester) syncedStatus(ctx context.Context) (bool, int64, error) {
-	headBlock, err := t.blockStorage.GetBlock(ctx, nil)
-	if errors.Is(err, storageErrs.ErrHeadBlockNotFound) {
-		return false, -1, nil
-	}
+	atTip, blockIdentifier, err := utils.CheckStorageTip(
+		ctx,
+		t.network,
+		t.config.TipDelay,
+		t.fetcher,
+		t.blockStorage,
+	)
 	if err != nil {
 		return false, -1, err
 	}
 
-	blockIdentifier := headBlock.BlockIdentifier
-
-	// If the last synced block is within TipDelay
-	// seconds from the current time, we are synced
-	// to tip.
-	if utils.AtTip(t.config.TipDelay, headBlock.Timestamp) {
-		return true, blockIdentifier.Index, nil
-	}
-
-	status, fetchErr := t.fetcher.NetworkStatusRetry(ctx, t.network, nil)
-	if fetchErr != nil {
-		return false, -1, fmt.Errorf("%w: unable to fetch network status", fetchErr.Err)
-	}
+	var blockIndex int64 = -1
 
-	// If the Rosetta implementation says it is at tip (regardless of the current
-	// block timestamp) and our last synced block has the same index,
-	// we are synced to tip.
-	if status.SyncStatus != nil &&
-		status.SyncStatus.Synced != nil &&
-		*status.SyncStatus.Synced &&
-		blockIdentifier.Index == status.CurrentBlockIdentifier.Index {
-		return true, blockIdentifier.Index, nil
+	if blockIdentifier != nil {
+		blockIndex = blockIdentifier.Index
 	}
 
-	return false, blockIdentifier.Index, nil
+	return atTip, blockIndex, nil
 }
 
 // EndAtTipLoop runs a loop that evaluates end condition EndAtTip