Browse Source

Clean up APIs

pull/1/head
Sarah Jamie Lewis 6 months ago
parent
commit
80615a955c
13 changed files with 289 additions and 256 deletions
  1. +8
    -1
      README.md
  2. +108
    -0
      api.go
  3. +8
    -0
      api_test.go
  4. +0
    -51
      client/client.go
  5. +0
    -118
      cmd/main.go
  6. +16
    -17
      examples/accounting/main.go
  7. +4
    -6
      go.mod
  8. +10
    -11
      go.sum
  9. +16
    -0
      hex.go
  10. +24
    -0
      testing/quality.sh
  11. +28
    -0
      testing/zcash_client_integration_test.go
  12. +0
    -13
      transaction.go
  13. +67
    -39
      zcash_client.go

+ 8
- 1
README.md View File

@@ -1,2 +1,9 @@
# zcash2cwtch
# zcashrpc

A small go library for interfacing with a zcash full node. Currently supports:

* `gettransaction`
* `z_validateaddress`
* `z_sendmany`
* `z_listreceivedbyaddress`


+ 108
- 0
api.go View File

@@ -0,0 +1,108 @@
package zcashrpc

import (
"encoding/json"
)

// Method restricts method definitions to constants
type Method string

// Constant block of Method assignments
const (
GetTransaction Method = "gettransaction"

ZListReceivedByAddress = "z_listreceivedbyaddress"
ZSendMany = "z_sendmany"
ZValidateAddress = "z_validateaddress"
)

// zcashRequest encapsulates the common parameters in an api request to a zcash full node
type zcashRequest struct {
JSONRPCVersion string `json:"jsonrpc"`
ID string `json:"id"`
Method Method `json:"method"`
Params []interface{} `json:"params"`
}

func setupZcashRequest() zcashRequest {
return zcashRequest{ID: "zcash_api_go", JSONRPCVersion: "1.0"}
}

// NewGetTransaction constructs a properly formatted gettransaction request
func NewGetTransaction(is string) []byte {
request := setupZcashRequest()
request.Method = GetTransaction
request.Params = append(request.Params, is)
data, _ := json.Marshal(request)
return data
}

// NewZValidateAddress constructs a properly formatted z_validateaddress request
func NewZValidateAddress(address string) []byte {
request := setupZcashRequest()
request.Method = ZValidateAddress
request.Params = append(request.Params, address)
data, _ := json.Marshal(request)
return data
}

// NewZListReceivedByAddress constructs a properly formatted z_listreceivedbyaddress request
func NewZListReceivedByAddress(fromAddress string) []byte {
request := setupZcashRequest()
request.Method = ZListReceivedByAddress
request.Params = append(request.Params, fromAddress)
data, _ := json.Marshal(request)
return data
}

// NewZSendMany constructs a properly formatted z_sendmany request
func NewZSendMany(fromAddress string, amounts []ZcashAmount) []byte {
request := setupZcashRequest()
request.Method = ZSendMany
request.Params = append(request.Params, fromAddress)
request.Params = append(request.Params, amounts)
data, _ := json.Marshal(request)
return data
}

// ZcashAmount an object representing an amount to send in z_sendmany
type ZcashAmount struct {
Address string `json:"address"`
Amount float64 `json:"amount"`
Memo Hex `json:"memo"`
}

// NewZcashAmount generates a new ZcashAmount object with a properly hex encoded memo field
func NewZcashAmount(toAddress string, memo string, amount float64) ZcashAmount {
return ZcashAmount{Address: toAddress, Memo: MakeHex(memo), Amount: amount}
}

// Transaction encapsulates the result of GetTransaction
// TODO: currently this only supports time so we can obtain an ordered list of transactions
type Transaction struct {
Time int `json:"time"`
}

// ZcashTransaction defines a transaction received to a zaddress
type ZcashTransaction struct {
TransactionID string `json:"txid"`
Amount float64 `json:"amount"`
Memo Hex `json:"memo"`
Change bool `json:"change"`
OutIndex int `json:"outindex"`
}

// ZcashResult represents the result of a zcash operation. Result can be any one of a number of defined types.
type ZcashResult struct {
Result interface{} `json:"result"`
}

// ZValidateAddressResponse encapsulates the result returned from a z_validateaddress request
type ZValidateAddressResponse struct {
IsValid bool `json:"isvalid"`
Address string `json:"address"`
Type string `json:"type"`
IsMine bool `json:"ismine"`
Diversifier Hex `json:"diversifier"`
DiversifiedTransmissionKey Hex `json:"diversifiedtransmissionkey"`
}

+ 8
- 0
api_test.go View File

@@ -0,0 +1,8 @@
package zcashrpc

import "testing"

func TestNewZSendMany(t *testing.T) {
t.Logf("%s", NewZSendMany("fromaddress", []ZcashAmount{NewZcashAmount("toAddress", "Needs to be Hex Encoded", 3.45)}))

}

+ 0
- 51
client/client.go View File

@@ -1,51 +0,0 @@
package main

import (
"cwtch.im/tapir"
"cwtch.im/tapir/applications"
"cwtch.im/tapir/networks/tor"
"cwtch.im/tapir/primitives"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"strings"
"time"
)

type PublicMemoClient struct {
applications.AuthApp
}

func (ma PublicMemoClient) NewInstance() tapir.Application {
return new(PublicMemoClient)
}

// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability
// or the connection is closed.
func (ma *PublicMemoClient) Init(connection tapir.Connection) {
ma.AuthApp.Init(connection)

if connection.HasCapability(applications.AuthCapability) {
memo := connection.Expect()
for string(memo) != "{}" {
log.Infof("Received Public Memo: %s", strings.ReplaceAll(string(memo), "\n", "\\n"))
memo = connection.Expect()
}
}
}

func main() {
log.SetLevel(log.LevelDebug)
var acn connectivity.ACN
acn, _ = connectivity.StartTor("./", "")
acn.WaitTillBootstrapped()

id, sk := primitives.InitializeEphemeralIdentity()
var client tapir.Service
client = new(tor.BaseOnionService)
client.Init(acn, sk, &id)
client.Connect("a6lh4dju3jqmh6tghnfjdqqohnp3lvww3wgbtvkez4weswartvv75qqd", &PublicMemoClient{})

// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
// we will wait a little while then exit.
time.Sleep(time.Second * 10)
}

+ 0
- 118
cmd/main.go View File

@@ -1,118 +0,0 @@
package main

import (
"cwtch.im/tapir"
"cwtch.im/tapir/applications"
"cwtch.im/tapir/networks/tor"
"cwtch.im/tapir/primitives"
"cwtch.im/zcash2cwtch"
"encoding/base64"
"encoding/hex"
"encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"golang.org/x/crypto/ed25519"
"io/ioutil"
"sync"
"time"
)

type ZcashConfig struct {
Username string `json: "username"`
Password string `json: "password"`
Onion string `json: "onion"`
ZAddress string `json: "zaddress"`
PublicKey string `json: "publickey"`
PrivateKey string `json: "privatekey"`
}

type TapirServer struct {
acn connectivity.ACN
seenMap sync.Map
transactions []zcash2cwtch.ZcashTransaction
service tapir.Service
}

type MemoApp struct {
applications.AuthApp
service *TapirServer
}

func (ma MemoApp) NewInstance() tapir.Application {
memoApp := new(MemoApp)
memoApp.service = ma.service
return memoApp
}

// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability
// or the connection is closed.
func (ma MemoApp) Init(connection tapir.Connection) {
ma.AuthApp.Init(connection)

if connection.HasCapability(applications.AuthCapability) {
for _, transaction := range ma.service.transactions {
memo, _ := hex.DecodeString(transaction.Memo)
connection.Send([]byte("{\"public\": \"" + string(memo) + "\"}"))
}
connection.Send([]byte("{}"))
}
}

func StartTapirServer() *TapirServer {
ts := new(TapirServer)
acn, _ := connectivity.StartTor("", "")
acn.WaitTillBootstrapped()
ts.acn = acn

configFile, _ := ioutil.ReadFile("config.json")
config := ZcashConfig{}
_ = json.Unmarshal([]byte(configFile), &config)

var service tapir.Service
service = new(tor.BaseOnionService)
// Initialize an onion service with one identity, but the auth app with another, this should
// trigger a failure in authentication protocol
sk, _ := base64.StdEncoding.DecodeString(config.PrivateKey)
pk, _ := base64.StdEncoding.DecodeString(config.PublicKey)
publicKey := ed25519.PublicKey(pk)
privateKey := ed25519.PrivateKey(sk)
id := primitives.InitializeIdentity("", &privateKey, &publicKey)
service.Init(acn, ed25519.PrivateKey(sk), &id)
ts.service = service
return ts
}

func (ts *TapirServer) Listen() {
ts.service.Listen(MemoApp{service: ts})
}

func (ts *TapirServer) Refresh() {
for {
configFile, _ := ioutil.ReadFile("config.json")
config := ZcashConfig{}
_ = json.Unmarshal([]byte(configFile), &config)

zc := zcash2cwtch.NewClient(config.Onion, config.Username, config.Password, ts.acn)
transactions, err := zc.ListReceivedTransactionsByAddress(config.ZAddress)
if err != nil {
log.Errorf("Error fetching zcash transactions: %v", err)
}
for _, transaction := range transactions {
_, exists := ts.seenMap.Load(transaction.TransactionID)
if !exists {
log.Infof("Got a new transaction txid:%s, amount %f", transaction.TransactionID, transaction.Amount)
ts.transactions = append(ts.transactions, transaction)
ts.seenMap.Store(transaction.TransactionID, true)
}
}
time.Sleep(time.Minute)
}
}

func main() {
log.SetLevel(log.LevelDebug)
ts := StartTapirServer()

go ts.Refresh()
ts.Listen()
}

accounting/main.go → examples/accounting/main.go View File

@@ -2,7 +2,6 @@ package main

import (
"cwtch.im/zcash2cwtch"
"encoding/hex"
"encoding/json"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
@@ -13,19 +12,19 @@ import (
"time"
)

type ZcashConfig struct {
Username string `json: "username"`
Password string `json: "password"`
Onion string `json: "onion"`
ZAddress string `json: "zaddress"`
type zcashConfig struct {
Username string `json:"username"`
Password string `json:"password"`
Onion string `json:"onion"`
ZAddress string `json:"zaddress"`
}

type Transaction struct {
type transaction struct {
time time.Time
transaction zcash2cwtch.ZcashTransaction
transaction zcashrpc.ZcashTransaction
}

type timeSlice []Transaction
type timeSlice []transaction

func (p timeSlice) Len() int {
return len(p)
@@ -42,12 +41,12 @@ func (p timeSlice) Swap(i, j int) {
func main() {
log.SetLevel(log.LevelDebug)
configFile, _ := ioutil.ReadFile("accounting.json")
config := ZcashConfig{}
config := zcashConfig{}
_ = json.Unmarshal([]byte(configFile), &config)
var acn connectivity.ACN
acn, _ = connectivity.StartTor("./", "")
acn.WaitTillBootstrapped()
zc := zcash2cwtch.NewClient(config.Onion, config.Username, config.Password, acn)
zc := zcashrpc.NewOnionClient(config.Onion, config.Username, config.Password, acn)
transactions, err := zc.ListReceivedTransactionsByAddress(config.ZAddress)
if err != nil {
log.Errorf("Error fetching zcash transactions: %v", err)
@@ -55,19 +54,19 @@ func main() {

sortedTransactions := make(timeSlice, 0)

for _, transaction := range transactions {
t, err := zc.GetTransaction(transaction.TransactionID)
log.Infof("Transaction: %v, Err %v", transaction, err)
for _, tr := range transactions {
t, err := zc.GetTransaction(tr.TransactionID)
log.Infof("Transaction: %v, Err %v", t, err)
ttime := time.Unix(int64(t.Time), 0)
sortedTransactions = append(sortedTransactions, Transaction{ttime, transaction})
sortedTransactions = append(sortedTransactions, transaction{ttime, tr})
}
sort.Sort(sortedTransactions)

// Output a CSV of all transactions
for _, transaction := range sortedTransactions {
memoBytes, _ := hex.DecodeString(transaction.transaction.Memo)
memoBytes, _ := transaction.transaction.Memo.Decode()
memo := strings.ReplaceAll(string(memoBytes), "\"", "\"\"")
memo = strings.ReplaceAll(string(memoBytes), string([]byte{0x00}), "")
memo = strings.ReplaceAll(memo, string([]byte{0x00}), "")
fmt.Printf("%s,%s,%f,%v,\"%s\"\n", transaction.time, transaction.transaction.TransactionID, transaction.transaction.Amount, transaction.transaction.Change, memo)
}


+ 4
- 6
go.mod View File

@@ -1,17 +1,15 @@
module cwtch.im/zcash2cwtch
module git.openprivacy.ca/openprivacy/zcashrpc

go 1.12

require (
cwtch.im/tapir v0.1.10
git.openprivacy.ca/openprivacy/libricochet-go v1.0.6
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.3.2 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.4.0 // indirect
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

+ 10
- 11
go.sum View File

@@ -1,6 +1,3 @@
cwtch.im/tapir v0.1.10 h1:V+TkmwXNd6gySZqlVw468wMYEkmDwMSyvhkkpOfUw7w=
cwtch.im/tapir v0.1.10/go.mod h1:EuRYdVrwijeaGBQ4OijDDRHf7R2MDSypqHkSl5DxI34=
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4/go.mod h1:yMSG1gBaP4f1U+RMZXN85d29D39OK5s8aTpyVRoH5FY=
git.openprivacy.ca/openprivacy/libricochet-go v1.0.6 h1:5o4K2qn3otEE1InC5v5CzU0yL7Wl7DhVp4s8H3K6mXY=
git.openprivacy.ca/openprivacy/libricochet-go v1.0.6/go.mod h1:yMSG1gBaP4f1U+RMZXN85d29D39OK5s8aTpyVRoH5FY=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
@@ -9,41 +6,43 @@ github.com/cretz/bine v0.1.0 h1:1/fvhLE+fk0bPzjdO5Ci+0ComYxEMuB1JhM4X5skT3g=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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/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.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
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=
golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20190215142949-d0b11bdaac8a/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-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

+ 16
- 0
hex.go View File

@@ -0,0 +1,16 @@
package zcashrpc

import "encoding/hex"

// Hex encapsulates behaviour related to zcash hex encoding of certain fields like memo and keys
type Hex string

// MakeHex converts a string into a zcash-compatible hex string
func MakeHex(input string) Hex {
return Hex(hex.EncodeToString([]byte(input)))
}

// Decode returns the raw byte representation of a hex-string
func (h Hex) Decode() ([]byte, error) {
return hex.DecodeString(string(h))
}

+ 24
- 0
testing/quality.sh View File

@@ -0,0 +1,24 @@
#!/bin/sh

echo "Checking code quality (you want to see no output here)"
echo ""

echo "Vetting:"
go list ./... | xargs go vet

echo ""
echo "Linting:"

go list ./... | xargs golint


echo "Time to format"
gofmt -l -s -w .

# ineffassign (https://github.com/gordonklaus/ineffassign)
echo "Checking for ineffectual assignment of errors (unchecked errors...)"
ineffassign .

# misspell (https://github.com/client9/misspell/cmd/misspell)
echo "Checking for misspelled words..."
misspell . | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"

+ 28
- 0
testing/zcash_client_integration_test.go View File

@@ -0,0 +1,28 @@
package zcash2cwtch

import (
"cwtch.im/zcash2cwtch"
"encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"io/ioutil"
"testing"
)

type ZcashConfig struct {
Username string `json:"username"`
Password string `json:"password"`
}

func TestNewZValidateAddress(t *testing.T) {
log.SetLevel(log.LevelDebug)
configFile, _ := ioutil.ReadFile("config.json")
config := ZcashConfig{}
_ = json.Unmarshal(configFile, &config)

zc := zcashrpc.NewLocalClient(config.Username, config.Password)
result, err := zc.ValidateAddress("zs1pjv7eneq9jshw0eyywpruv2cetl74sh84ymdnyv4c4vg8vl5k2qmlv0n7ye77g49lhqkg75v52f")
t.Logf("Result: %v %v", result, err)
if err != nil || result.IsValid == false {
t.Errorf("Failed to validate real zaddress")
}
}

+ 0
- 13
transaction.go View File

@@ -1,13 +0,0 @@
package zcash2cwtch

type Transaction struct {
Time int `json:"time"`
}

type ZcashTransaction struct {
TransactionID string `json:"txid"`
Amount float64 `json:"amount"`
Memo string `json:"memo"`
Change bool `json:"change"`
OutIndex int `json:"outindex"`
}

+ 67
- 39
zcash_client.go View File

@@ -1,4 +1,4 @@
package zcash2cwtch
package zcashrpc

import (
"bytes"
@@ -11,25 +11,40 @@ import (
"net/http"
)

type ZcashResult struct {
Result interface{} `json:"result"`
}

// ZcashClient defines an interface for any zcash client to present.
type ZcashClient interface {
ListReceivedTransactionsByAddress(string) ([]ZcashTransaction, error)
GetTransaction(string) (Transaction, error)
SendOne(string, string, string, float64) ([]byte, error)
ValidateAddress(address string) (ZValidateAddressResponse, error)
}

type zcashClient struct {
client http.Client
address string
auth string
}

type zcashOnionClient struct {
client http.Client
onion string
auth string
// NewLocalClient creates a new Zcash rpc client over a local address
func NewLocalClient(username, password string) ZcashClient {
zc := new(zcashClient)
zc.address = "127.0.0.1:8232"
zc.client = http.Client{
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
conn, err := net.Dial("tcp", zc.address)
return conn, err
},
},
}
zc.auth = base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
return zc
}

// NewClient creates a new Zcash rpc client over an onion address
func NewClient(onion string, username, password string, acn connectivity.ACN) ZcashClient {
zc := new(zcashOnionClient)
zc.onion = onion
// NewOnionClient creates a new Zcash rpc client over an onion address
func NewOnionClient(onion string, username, password string, acn connectivity.ACN) ZcashClient {
zc := new(zcashClient)
zc.address = onion + ".onion:9878"
zc.client = http.Client{
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
@@ -42,33 +57,49 @@ func NewClient(onion string, username, password string, acn connectivity.ACN) Zc
return zc
}

func (zc *zcashOnionClient) GetTransaction(id string) (Transaction, error) {
var jsonStr = []byte(`{"jsonrpc": "1.0", "id":"zcash2cwtch", "method": "gettransaction", "params": ["` + id + `"]}`)
req, err := http.NewRequest("POST", "http://"+zc.onion+".onion:9878", bytes.NewBuffer(jsonStr))
if err != nil {
return Transaction{}, err
// GetTransaction returns a specific transaction given an id
func (zc *zcashClient) GetTransaction(id string) (Transaction, error) {
body, err := zc.sendRequest(NewGetTransaction(id))
transaction := Transaction{}
if err == nil {
result := &ZcashResult{Result: &transaction}
err = json.Unmarshal(body, &result)
}
req.Header.Add("Authorization", "Basic "+zc.auth)
log.Debugf("Sending request to zcash server %v", req)
resp, err := zc.client.Do(req)
if err != nil {
return Transaction{}, err
return transaction, err
}

// ListReceivedTransactionsByAddress returns all the transactions received by a given zcash address
func (zc *zcashClient) ListReceivedTransactionsByAddress(address string) ([]ZcashTransaction, error) {
body, err := zc.sendRequest(NewZListReceivedByAddress(address))
var transactions []ZcashTransaction
if err == nil {
result := &ZcashResult{Result: &transactions}
err = json.Unmarshal(body, &result)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Transaction{}, err
return transactions, err
}

// SendOne uses z_sendmany to send a single payment to the given address from the given fromAddress
func (zc *zcashClient) SendOne(fromAddress string, toaddress string, memo string, amount float64) ([]byte, error) {
jsonStr := NewZSendMany(fromAddress, []ZcashAmount{NewZcashAmount(toaddress, memo, amount)})
log.Debugf("Zcash SendMany %s", jsonStr)
return zc.sendRequest(jsonStr)
}

// ValidateAddress returns the result of a z_validateaddress call
func (zc *zcashClient) ValidateAddress(address string) (ZValidateAddressResponse, error) {
body, err := zc.sendRequest(NewZValidateAddress(address))
var validation ZValidateAddressResponse
if err == nil {
result := &ZcashResult{Result: &validation}
log.Debugf("Result: %s", body)
err = json.Unmarshal(body, &result)
}
log.Debugf("Got response from zcash server %s", body)
var transaction Transaction
result := &ZcashResult{Result: &transaction}
err = json.Unmarshal(body, &result)
return transaction, err
return validation, err
}

func (zc *zcashOnionClient) ListReceivedTransactionsByAddress(address string) ([]ZcashTransaction, error) {
var jsonStr = []byte(`{"jsonrpc": "1.0", "id":"zcash2cwtch", "method": "z_listreceivedbyaddress", "params": ["` + address + `"]}`)
req, err := http.NewRequest("POST", "http://"+zc.onion+".onion:9878", bytes.NewBuffer(jsonStr))
func (zc *zcashClient) sendRequest(jsonStr []byte) ([]byte, error) {
req, err := http.NewRequest("POST", "http://"+zc.address, bytes.NewBuffer(jsonStr))
if err != nil {
return nil, err
}
@@ -84,8 +115,5 @@ func (zc *zcashOnionClient) ListReceivedTransactionsByAddress(address string) ([
return nil, err
}
log.Debugf("Got response from zcash server %s", body)
transactions := []ZcashTransaction{}
result := &ZcashResult{Result: &transactions}
err = json.Unmarshal(body, &result)
return transactions, err
return body, nil
}

Loading…
Cancel
Save