Quickstart BCHD gRPC API for Golang Developers

2019-01-29T12:00:12.000Z Honest Cash

gRPC

Quickstart BCHD gRPC API for Golang Developers

The recently announced protobuf gRPC API on BCHD provides developers with easy access to an indexing blockchain server. Simplifying the infrastructure back-end by integrating the indexing server into the BCHD bitcoin node. Read the announcement for more details on all the advantages of using gRPC over a JSON REST API.

Alright, lets jump right into it.

1. Connection Prereqs

Read the client usage example for detailed information on how to set up a connection.

All you need is a URI (and the corresponding certificate if self-signed). In this example we'll be using a public node provided by @zquestz.

bchd.greyh.at:8335

If you are using a self-signed certificate on a locally hosted bchd node, you need to copy the cert to your local .bchd folder or get the certificate by using a browser and surf to https://your.bchd.node:8335. Click the lock, view certificate and export it.

export certificate

2. Setting up the connection

We'll create a new TLSClient and use that to set up the gRPC connection.

const  grpcServer = "bchd.greyh.at:8335"
func initGRPC() (*grpc.ClientConn, error) {
    conn, err := grpc.Dial(grpcServer, grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")))
    if err != nil {
    return nil, err
    }

    return conn, nil
}

3. Making a request

func  main() {
    conn, err := initGRPC()
    if err !=  nil {
        fmt.Println(err)
    }
    defer conn.Close()
    c := pb.NewBchrpcClient(conn)
    blockchainInfoResp, err := c.GetBlockchainInfo(context.Background(), &pb.GetBlockchainInfoRequest{})
    if err !=  nil {
        fmt.Println(err)
    }
    bestHeight := blockchainInfoResp.GetBestHeight()
    bestBlockHash := blockchainInfoResp.GetBestBlockHash()
    fmt.Printf("%d - %x\n", bestHeight, bestBlockHash)
    fmt.Printf("%v\n", bestBlockHash)
    fmt.Printf("%#v\n", bestBlockHash)
}

Output:

> go run honest1.go
581802 - 05b8671c385c73d2214009b9fdfbf4ea3465eaa6b39901030000000000000000
[5 184 103 28 56 92 115 210 33 64 9 185 253 251 244 234 52 101 234 166 179 153 1 3 0 0 0 0 0 0 0 0]
[]byte{0x5, 0xb8, 0x67, 0x1c, 0x38, 0x5c, 0x73, 0xd2, 0x21, 0x40, 0x9, 0xb9, 0xfd, 0xfb, 0xf4, 0xea, 0x34, 0x65, 0xea, 0xa6, 0xb3, 0x99, 0x1, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

As you can see here, the block hash looks a bit different from what we are expecting, it ends in zeroes instead of starting with zeroes. There is a pretty good reason for this according to Chris Pacia and Mark B. Lundeberg:

Chris Pacia:

The entire (bchd) codebase treats all byte arrays as little endian (I believe the c++ code does as well?). Only when you go to string does it convert to big endian for display purposes. So given that, this is just continuing the rule that if it's a byte array it should be little endian.

In our Go libraries there's an object called chainhash that holds the hash representation. The constructors look like this:

NewHash(newHash []byte) (*Hash, error)  // expects little endian byte array.
NewHashFromStr(hash string) (*Hash, error) // expects big endian hex string.

So at least in Go we use the bytes directly from the protobuf object into a chainhash without doing any reversal.

I realize that might not be the case in other languages. I'm open to hearing other opinions. But if we changed it, it would be the only place in the codebase were we have to reverse the bytes before using them… both when writing to the wire and also when reading from the wire.

Mark B. Lundeberg:

Another way to say it: if you take double-sha256 of a block header it ends in a bunch of zero bytes, not starts. The bytestrings usually just get reversed when converting to hex.

We have to take this into account when we want to print the string encoded representation of the hash. First reverse the byteslice.

Let's see how this works.

4. Printing hashes

Define the reverse function.

func reverse(slice []byte) []byte {
    for i := len(slice)/2 - 1; i >= 0; i-- {
        opp := len(slice) - 1 - i
        slice[i], slice[opp] = slice[opp], slice[i]
    }

    return slice
}

That should clear things up.

package main

import (
    "fmt"

    pb "github.com/gcash/bchd/bchrpc/pb"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

const grpcServer = "bchd.greyh.at:8335"

func main() {
    conn, err := initGRPC()
    if err != nil {
        fmt.Println(err)
    }
    defer conn.Close()
    c := pb.NewBchrpcClient(conn)

    blockchainInfoResp, err := c.GetBlockchainInfo(context.Background(), &pb.GetBlockchainInfoRequest{})
    if err != nil {
        fmt.Println(err)
    }

    bestHeight := blockchainInfoResp.GetBestHeight()
    bestBlockHash := blockchainInfoResp.GetBestBlockHash()

    fmt.Printf("%d - %x\n", bestHeight, bestBlockHash)

    fmt.Println("GRPC RESPONSE:")
    fmt.Printf("%v\n", bestBlockHash)
    fmt.Printf("%#v\n", bestBlockHash)
    fmt.Printf("%x\n", bestBlockHash)

    bestBlockHash = reverse(bestBlockHash)
    fmt.Println("REVERSED:")
    fmt.Printf("%v\n", bestBlockHash)
    fmt.Printf("%#v\n", bestBlockHash)
    fmt.Printf("%x\n", bestBlockHash)

}

func initGRPC() (*grpc.ClientConn, error) {
    conn, err := grpc.Dial(grpcServer, grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")))
    if err != nil {
        return nil, err
    }

    return conn, nil
}

func reverse(slice []byte) []byte {
    for i := len(slice)/2 - 1; i >= 0; i-- {
        opp := len(slice) - 1 - i
        slice[i], slice[opp] = slice[opp], slice[i]
    }

    return slice
}

Output:

> go run honest2.go
581806 - 9789862a9eea7216892e6c5b0ef8224344440d4c3a9d35010000000000000000
GRPC RESPONSE:
[151 137 134 42 158 234 114 22 137 46 108 91 14 248 34 67 68 68 13 76 58 157 53 1 0 0 0 0 0 0 0 0]
[]byte{0x97, 0x89, 0x86, 0x2a, 0x9e, 0xea, 0x72, 0x16, 0x89, 0x2e, 0x6c, 0x5b, 0xe, 0xf8, 0x22, 0x43, 0x44, 0x44, 0xd, 0x4c, 0x3a, 0x9d, 0x35, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
9789862a9eea7216892e6c5b0ef8224344440d4c3a9d35010000000000000000
REVERSED:
[0 0 0 0 0 0 0 0 1 53 157 58 76 13 68 68 67 34 248 14 91 108 46 137 22 114 234 158 42 134 137 151]
[]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x35, 0x9d, 0x3a, 0x4c, 0xd, 0x44, 0x44, 0x43, 0x22, 0xf8, 0xe, 0x5b, 0x6c, 0x2e, 0x89, 0x16, 0x72, 0xea, 0x9e, 0x2a, 0x86, 0x89, 0x97}
000000000000000001359d3a4c0d44444322f80e5b6c2e891672ea9e2a868997

Next up, parsing blocks. (Probably sometime next week)

Responses