Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return correct gRPC error codes #503

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ func startServer(opts *common.Options) error {
"error": err,
}).Fatal("getting initial information from zebrad or zcashd")
}
// Zebrad always supports lightwalletd (no special configuration needed);
// zcashd needs the lightwalletd=1 configuration option, so check that here.
if getLightdInfo.LightwalletdDisabled && !strings.Contains(getLightdInfo.ZcashdSubversion, "Zebra") {
common.Log.Fatal("zcashd is not configured as a lightwalletd server")
}
common.Log.Info("Got sapling height ", getLightdInfo.SaplingActivationHeight,
" block height ", getLightdInfo.BlockHeight,
" chain ", getLightdInfo.ChainName,
Expand Down
66 changes: 37 additions & 29 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/sirupsen/logrus"
"github.com/zcash/lightwalletd/parser"
"github.com/zcash/lightwalletd/walletrpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

// 'make build' will overwrite this string with the output of git-describe (tag)
Expand Down Expand Up @@ -93,9 +95,13 @@ type (
}

// zcashd rpc "getinfo"
// If we're connected to an old version of zcashd that doesn't return
// LightwalletdDisabled, it will be false (the default bool value),
// and that will let us start up.
ZcashdRpcReplyGetinfo struct {
Build string
Subversion string
Build string
Subversion string
LightwalletdDisabled bool
}

// zcashd rpc "getaddresstxids"
Expand Down Expand Up @@ -294,6 +300,7 @@ func GetLightdInfo() (*walletrpc.LightdInfo, error) {
EstimatedHeight: uint64(getblockchaininfoReply.EstimatedHeight),
ZcashdBuild: getinfoReply.Build,
ZcashdSubversion: getinfoReply.Subversion,
LightwalletdDisabled: getinfoReply.LightwalletdDisabled,
}, nil
}

Expand All @@ -311,15 +318,13 @@ func getBlockFromRPC(height int) (*walletrpc.CompactBlock, error) {
if err != nil {
Log.Fatal("getBlockFromRPC bad height argument", height, err)
}
params := make([]json.RawMessage, 2)
params[0] = heightJSON
// Fetch the block using the verbose option ("1") because it provides
// both the list of txids, which we're not yet able to compute for
// Orchard (V5) transactions, and the block hash (block ID), which
// we need to fetch the raw data format of the same block. Don't fetch
// by height in case a reorg occurs between the two getblock calls;
// using block hash ensures that we're fetching the same block.
params[1] = json.RawMessage("1")
params := []json.RawMessage{heightJSON, json.RawMessage("1")}
result, rpcErr := RawRequest("getblock", params)
if rpcErr != nil {
// Check to see if we are requesting a height the zcashd doesn't have yet
upbqdn marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -331,14 +336,14 @@ func getBlockFromRPC(height int) (*walletrpc.CompactBlock, error) {
var block1 ZcashRpcReplyGetblock1
err = json.Unmarshal(result, &block1)
if err != nil {
return nil, err
Log.Fatal("getBlockFromRPC: Can't unmarshal block:", err)
}
blockHash, err := json.Marshal(block1.Hash)
if err != nil {
Log.Fatal("getBlockFromRPC bad block hash", block1.Hash)
}
params[0] = blockHash
params[1] = json.RawMessage("0") // non-verbose (raw hex)
// non-verbose (raw hex) version of block
params = []json.RawMessage{blockHash, json.RawMessage("0")}
result, rpcErr = RawRequest("getblock", params)

// For some reason, the error responses are not JSON
Expand Down Expand Up @@ -476,6 +481,7 @@ func BlockIngestor(c *BlockCache, rep int) {
// GetBlock returns the compact block at the requested height, first by querying
// the cache, then, if not found, will request the block from zcashd. It returns
// nil if no block exists at this height.
// This returns gRPC-compatible errors.
func GetBlock(cache *BlockCache, height int) (*walletrpc.CompactBlock, error) {
// First, check the cache to see if we have the block
var block *walletrpc.CompactBlock
Expand All @@ -489,11 +495,13 @@ func GetBlock(cache *BlockCache, height int) (*walletrpc.CompactBlock, error) {
// Not in the cache
block, err := getBlockFromRPC(height)
if err != nil {
return nil, err
return nil, status.Errorf(codes.InvalidArgument,
"GetBlock: getblock failed, error: %s", err.Error())
}
if block == nil {
// Block height is too large
return nil, errors.New("block requested is newer than latest block")
return nil, status.Errorf(codes.OutOfRange,
"GetBlock: block requested is newer than latest block: %d", height)
}
return block, nil
}
Expand Down Expand Up @@ -537,27 +545,27 @@ func GetBlockRange(cache *BlockCache, blockOut chan<- *walletrpc.CompactBlock, e
// the meanings of the `Height` field of the `RawTransaction` type are as
// follows:
//
// * height 0: the transaction is in the mempool
// * height 0xffffffffffffffff: the transaction has been mined on a fork that
// is not currently the main chain
// * any other height: the transaction has been mined in the main chain at the
// given height
// - height 0: the transaction is in the mempool
// - height 0xffffffffffffffff: the transaction has been mined on a fork that
// is not currently the main chain
// - any other height: the transaction has been mined in the main chain at the
// given height
func ParseRawTransaction(message json.RawMessage) (*walletrpc.RawTransaction, error) {
// Many other fields are returned, but we need only these two.
var txinfo ZcashdRpcReplyGetrawtransaction
err := json.Unmarshal(message, &txinfo)
if err != nil {
return nil, err
}
txBytes, err := hex.DecodeString(txinfo.Hex)
if err != nil {
return nil, err
}
// Many other fields are returned, but we need only these two.
var txinfo ZcashdRpcReplyGetrawtransaction
err := json.Unmarshal(message, &txinfo)
if err != nil {
return nil, err
}
txBytes, err := hex.DecodeString(txinfo.Hex)
if err != nil {
return nil, err
}

return &walletrpc.RawTransaction{
Data: txBytes,
Height: uint64(txinfo.Height),
}, nil
return &walletrpc.RawTransaction{
Data: txBytes,
Height: uint64(txinfo.Height),
}, nil
}

func displayHash(hash []byte) string {
Expand Down
102 changes: 51 additions & 51 deletions common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ func TestGetBlockRange(t *testing.T) {
select {
case err := <-errChan:
// this will also catch context.DeadlineExceeded from the timeout
if err.Error() != "block requested is newer than latest block" {
if !strings.Contains(err.Error(), "block requested is newer than latest block") {
t.Fatal("unexpected error:", err)
}
case <-blockChan:
Expand Down Expand Up @@ -605,7 +605,7 @@ func mempoolStub(method string, params []json.RawMessage) (json.RawMessage, erro
if txid != "mempooltxid-1" {
testT.Fatal("unexpected txid")
}
r, _ := json.Marshal(map[string]string{"hex":"aabb"})
r, _ := json.Marshal(map[string]string{"hex": "aabb"})
return r, nil
case 5:
// Simulate that still no new block has arrived ...
Expand Down Expand Up @@ -637,7 +637,7 @@ func mempoolStub(method string, params []json.RawMessage) (json.RawMessage, erro
if txid != "mempooltxid-2" {
testT.Fatal("unexpected txid")
}
r, _ := json.Marshal(map[string]string{"hex":"ccdd"})
r, _ := json.Marshal(map[string]string{"hex": "ccdd"})
return r, nil
case 8:
// A new block arrives, this will cause these two tx to be returned
Expand Down Expand Up @@ -713,59 +713,59 @@ func TestMempoolStream(t *testing.T) {
}

func TestZcashdRpcReplyUnmarshalling(t *testing.T) {
var txinfo0 ZcashdRpcReplyGetrawtransaction
err0 := json.Unmarshal([]byte("{\"hex\": \"deadbeef\", \"height\": 123456}"), &txinfo0)
if err0 != nil {
t.Fatal("Failed to unmarshal tx with known height.")
}
if txinfo0.Height != 123456 {
t.Errorf("Unmarshalled incorrect height: got: %d, want: 123456.", txinfo0.Height)
}
var txinfo0 ZcashdRpcReplyGetrawtransaction
err0 := json.Unmarshal([]byte("{\"hex\": \"deadbeef\", \"height\": 123456}"), &txinfo0)
if err0 != nil {
t.Fatal("Failed to unmarshal tx with known height.")
}
if txinfo0.Height != 123456 {
t.Errorf("Unmarshalled incorrect height: got: %d, want: 123456.", txinfo0.Height)
}

var txinfo1 ZcashdRpcReplyGetrawtransaction
err1 := json.Unmarshal([]byte("{\"hex\": \"deadbeef\", \"height\": -1}"), &txinfo1)
if err1 != nil {
t.Fatal("failed to unmarshal tx not in main chain")
}
if txinfo1.Height != -1 {
t.Errorf("Unmarshalled incorrect height: got: %d, want: -1.", txinfo1.Height)
}
var txinfo1 ZcashdRpcReplyGetrawtransaction
err1 := json.Unmarshal([]byte("{\"hex\": \"deadbeef\", \"height\": -1}"), &txinfo1)
if err1 != nil {
t.Fatal("failed to unmarshal tx not in main chain")
}
if txinfo1.Height != -1 {
t.Errorf("Unmarshalled incorrect height: got: %d, want: -1.", txinfo1.Height)
}

var txinfo2 ZcashdRpcReplyGetrawtransaction
err2 := json.Unmarshal([]byte("{\"hex\": \"deadbeef\"}"), &txinfo2)
if err2 != nil {
t.Fatal("failed to unmarshal reply lacking height data")
}
if txinfo2.Height != 0 {
t.Errorf("Unmarshalled incorrect height: got: %d, want: 0.", txinfo2.Height)
}
var txinfo2 ZcashdRpcReplyGetrawtransaction
err2 := json.Unmarshal([]byte("{\"hex\": \"deadbeef\"}"), &txinfo2)
if err2 != nil {
t.Fatal("failed to unmarshal reply lacking height data")
}
if txinfo2.Height != 0 {
t.Errorf("Unmarshalled incorrect height: got: %d, want: 0.", txinfo2.Height)
}
}

func TestParseRawTransaction(t *testing.T) {
rt0, err0 := ParseRawTransaction([]byte("{\"hex\": \"deadbeef\", \"height\": 123456}"))
if err0 != nil {
t.Fatal("Failed to parse raw transaction response with known height.")
}
if rt0.Height != 123456 {
t.Errorf("Unmarshalled incorrect height: got: %d, expected: 123456.", rt0.Height)
}
rt0, err0 := ParseRawTransaction([]byte("{\"hex\": \"deadbeef\", \"height\": 123456}"))
if err0 != nil {
t.Fatal("Failed to parse raw transaction response with known height.")
}
if rt0.Height != 123456 {
t.Errorf("Unmarshalled incorrect height: got: %d, expected: 123456.", rt0.Height)
}

rt1, err1 := ParseRawTransaction([]byte("{\"hex\": \"deadbeef\", \"height\": -1}"))
if err1 != nil {
t.Fatal("Failed to parse raw transaction response for a known tx not in the main chain.")
}
// We expect the int64 value `-1` to have been reinterpreted as a uint64 value in order
// to be representable as a uint64 in `RawTransaction`. The conversion from the twos-complement
// signed representation should map `-1` to `math.MaxUint64`.
if rt1.Height != math.MaxUint64 {
t.Errorf("Unmarshalled incorrect height: got: %d, want: 0x%X.", rt1.Height, uint64(math.MaxUint64))
}
rt1, err1 := ParseRawTransaction([]byte("{\"hex\": \"deadbeef\", \"height\": -1}"))
if err1 != nil {
t.Fatal("Failed to parse raw transaction response for a known tx not in the main chain.")
}
// We expect the int64 value `-1` to have been reinterpreted as a uint64 value in order
// to be representable as a uint64 in `RawTransaction`. The conversion from the twos-complement
// signed representation should map `-1` to `math.MaxUint64`.
if rt1.Height != math.MaxUint64 {
t.Errorf("Unmarshalled incorrect height: got: %d, want: 0x%X.", rt1.Height, uint64(math.MaxUint64))
}

rt2, err2 := ParseRawTransaction([]byte("{\"hex\": \"deadbeef\"}"))
if err2 != nil {
t.Fatal("Failed to parse raw transaction response for a tx in the mempool.")
}
if rt2.Height != 0 {
t.Errorf("Unmarshalled incorrect height: got: %d, expected: 0.", rt2.Height)
}
rt2, err2 := ParseRawTransaction([]byte("{\"hex\": \"deadbeef\"}"))
if err2 != nil {
t.Fatal("Failed to parse raw transaction response for a tx in the mempool.")
}
if rt2.Height != 0 {
t.Errorf("Unmarshalled incorrect height: got: %d, expected: 0.", rt2.Height)
}
}
7 changes: 7 additions & 0 deletions docs/rtd/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,13 @@ <h3 id="cash.z.wallet.sdk.rpc.LightdInfo">LightdInfo</h3>
<td><p>example: &#34;/MagicBean:4.1.1/&#34; </p></td>
</tr>

<tr>
<td>lightwalletdDisabled</td>
<td><a href="#bool">bool</a></td>
<td></td>
<td><p>whether zcashd is configured as a lwd server </p></td>
</tr>

</tbody>
</table>

Expand Down
8 changes: 4 additions & 4 deletions frontend/frontend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func TestGetTransaction(t *testing.T) {
if err == nil {
testT.Fatal("GetTransaction unexpectedly succeeded")
}
if err.Error() != "please call GetTransaction with txid" {
if !strings.Contains(err.Error(), "GetTransaction: specify a txid") {
testT.Fatal("GetTransaction unexpected error message")
}
if rawtx != nil {
Expand All @@ -127,7 +127,7 @@ func TestGetTransaction(t *testing.T) {
if err == nil {
testT.Fatal("GetTransaction unexpectedly succeeded")
}
if err.Error() != "can't GetTransaction with a blockhash+num, please call GetTransaction with txid" {
if !strings.Contains(err.Error(), "GetTransaction: specify a txid, not a blockhash+num") {
testT.Fatal("GetTransaction unexpected error message")
}
if rawtx != nil {
Expand Down Expand Up @@ -302,7 +302,7 @@ func TestGetTaddressTxids(t *testing.T) {
if err == nil {
t.Fatal("GetTaddressTxids should have failed on bad address, case", i)
}
if err.Error() != "invalid address" {
if !strings.Contains(err.Error(), "invalid characters") {
t.Fatal("GetTaddressTxids incorrect error on bad address, case", i)
}
}
Expand Down Expand Up @@ -408,7 +408,7 @@ func TestGetBlock(t *testing.T) {
if err == nil {
t.Fatal("GetBlock should have failed")
}
if err.Error() != "gRPC GetBlock by Hash is not yet implemented" {
if !strings.Contains(err.Error(), "GetBlock: Block hash specifier is not yet implemented") {
t.Fatal("GetBlock hash unimplemented error message failed")
}

Expand Down
Loading
Loading