1
1
package zec
2
2
3
3
import (
4
+ "bytes"
4
5
"encoding/json"
6
+ "os/exec"
7
+ "reflect"
5
8
6
9
"github.com/golang/glog"
7
10
"github.com/juju/errors"
@@ -42,7 +45,7 @@ func NewZCashRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
42
45
z := & ZCashRPC {
43
46
BitcoinRPC : b .(* btc.BitcoinRPC ),
44
47
}
45
- z .RPCMarshaler = btc. JSONMarshalerV1 {}
48
+ z .RPCMarshaler = JSONMarshalerV1Zebra {}
46
49
z .ChainConfig .SupportsEstimateSmartFee = false
47
50
return z , nil
48
51
}
@@ -84,13 +87,16 @@ func (z *ZCashRPC) GetChainInfo() (*bchain.ChainInfo, error) {
84
87
return nil , chainInfo .Error
85
88
}
86
89
90
+ // networkinfo not supported by zebra
87
91
networkInfo := btc.ResGetNetworkInfo {}
88
- err = z .Call (& btc.CmdGetNetworkInfo {Method : "getnetworkinfo" }, & networkInfo )
89
- if err != nil {
90
- return nil , err
91
- }
92
- if networkInfo .Error != nil {
93
- return nil , networkInfo .Error
92
+
93
+ zebrad := "zebra"
94
+ cmd := exec .Command ("/opt/coins/nodes/zcash/bin/zebrad" , "--version" )
95
+ var out bytes.Buffer
96
+ cmd .Stdout = & out
97
+ err = cmd .Run ()
98
+ if err == nil {
99
+ zebrad = out .String ()
94
100
}
95
101
96
102
return & bchain.ChainInfo {
@@ -100,7 +106,7 @@ func (z *ZCashRPC) GetChainInfo() (*bchain.ChainInfo, error) {
100
106
Difficulty : string (chainInfo .Result .Difficulty ),
101
107
Headers : chainInfo .Result .Headers ,
102
108
SizeOnDisk : chainInfo .Result .SizeOnDisk ,
103
- Version : string ( networkInfo . Result . Version ) ,
109
+ Version : zebrad ,
104
110
Subversion : string (networkInfo .Result .Subversion ),
105
111
ProtocolVersion : string (networkInfo .Result .ProtocolVersion ),
106
112
Timeoffset : networkInfo .Result .Timeoffset ,
@@ -111,6 +117,22 @@ func (z *ZCashRPC) GetChainInfo() (*bchain.ChainInfo, error) {
111
117
112
118
// GetBlock returns block with given hash.
113
119
func (z * ZCashRPC ) GetBlock (hash string , height uint32 ) (* bchain.Block , error ) {
120
+ type rpcBlock struct {
121
+ bchain.BlockHeader
122
+ Txs []bchain.Tx `json:"tx"`
123
+ }
124
+ type rpcBlockTxids struct {
125
+ Txids []string `json:"tx"`
126
+ }
127
+ type resGetBlockV1 struct {
128
+ Error * bchain.RPCError `json:"error"`
129
+ Result rpcBlockTxids `json:"result"`
130
+ }
131
+ type resGetBlockV2 struct {
132
+ Error * bchain.RPCError `json:"error"`
133
+ Result rpcBlock `json:"result"`
134
+ }
135
+
114
136
var err error
115
137
if hash == "" && height > 0 {
116
138
hash , err = z .GetBlockHash (height )
@@ -119,40 +141,86 @@ func (z *ZCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
119
141
}
120
142
}
121
143
122
- glog .V (1 ).Info ("rpc: getblock (verbosity=1) " , hash )
123
-
124
- res := btc.ResGetBlockThin {}
144
+ var rawResponse json.RawMessage
145
+ resV2 := resGetBlockV2 {}
125
146
req := btc.CmdGetBlock {Method : "getblock" }
126
147
req .Params .BlockHash = hash
127
- req .Params .Verbosity = 1
128
- err = z .Call (& req , & res )
129
-
148
+ req .Params .Verbosity = 2
149
+ err = z .Call (& req , & rawResponse )
130
150
if err != nil {
131
151
return nil , errors .Annotatef (err , "hash %v" , hash )
132
152
}
133
- if res .Error != nil {
134
- return nil , errors .Annotatef (res .Error , "hash %v" , hash )
153
+ // hack for ZCash, where the field "valueZat" is used instead of "valueSat"
154
+ rawResponse = bytes .ReplaceAll (rawResponse , []byte (`"valueZat"` ), []byte (`"valueSat"` ))
155
+ err = json .Unmarshal (rawResponse , & resV2 )
156
+ if err != nil {
157
+ return nil , errors .Annotatef (err , "hash %v" , hash )
135
158
}
136
159
137
- txs := make ([]bchain.Tx , 0 , len (res .Result .Txids ))
138
- for _ , txid := range res .Result .Txids {
139
- tx , err := z .GetTransaction (txid )
140
- if err != nil {
141
- if err == bchain .ErrTxNotFound {
142
- glog .Errorf ("rpc: getblock: skipping transanction in block %s due error: %s" , hash , err )
143
- continue
144
- }
145
- return nil , err
146
- }
147
- txs = append (txs , * tx )
160
+ if resV2 .Error != nil {
161
+ return nil , errors .Annotatef (resV2 .Error , "hash %v" , hash )
148
162
}
149
163
block := & bchain.Block {
150
- BlockHeader : res .Result .BlockHeader ,
151
- Txs : txs ,
164
+ BlockHeader : resV2 .Result .BlockHeader ,
165
+ Txs : resV2 .Result .Txs ,
166
+ }
167
+
168
+ // transactions fetched in block with verbosity 2 do not contain txids, so we need to get it separately
169
+ resV1 := resGetBlockV1 {}
170
+ req .Params .Verbosity = 1
171
+ err = z .Call (& req , & resV1 )
172
+ if err != nil {
173
+ return nil , errors .Annotatef (err , "hash %v" , hash )
174
+ }
175
+ if resV1 .Error != nil {
176
+ return nil , errors .Annotatef (resV1 .Error , "hash %v" , hash )
177
+ }
178
+ for i := range resV1 .Result .Txids {
179
+ block .Txs [i ].Txid = resV1 .Result .Txids [i ]
152
180
}
153
181
return block , nil
154
182
}
155
183
184
+ // GetTransaction returns a transaction by the transaction ID
185
+ func (z * ZCashRPC ) GetTransaction (txid string ) (* bchain.Tx , error ) {
186
+ r , err := z .getRawTransaction (txid )
187
+ if err != nil {
188
+ return nil , err
189
+ }
190
+ // hack for ZCash, where the field "valueZat" is used instead of "valueSat"
191
+ r = bytes .ReplaceAll (r , []byte (`"valueZat"` ), []byte (`"valueSat"` ))
192
+ tx , err := z .Parser .ParseTxFromJson (r )
193
+ if err != nil {
194
+ return nil , errors .Annotatef (err , "txid %v" , txid )
195
+ }
196
+ tx .Blocktime = tx .Time
197
+ tx .Txid = txid
198
+ tx .CoinSpecificData = r
199
+ return tx , nil
200
+ }
201
+
202
+ // getRawTransaction returns json as returned by backend, with all coin specific data
203
+ func (z * ZCashRPC ) getRawTransaction (txid string ) (json.RawMessage , error ) {
204
+ glog .V (1 ).Info ("rpc: getrawtransaction " , txid )
205
+
206
+ res := btc.ResGetRawTransaction {}
207
+ req := btc.CmdGetRawTransaction {Method : "getrawtransaction" }
208
+ req .Params .Txid = txid
209
+ req .Params .Verbose = true
210
+ err := z .Call (& req , & res )
211
+
212
+ if err != nil {
213
+ return nil , errors .Annotatef (err , "txid %v" , txid )
214
+ }
215
+ if res .Error != nil {
216
+ if btc .IsMissingTx (res .Error ) {
217
+ return nil , bchain .ErrTxNotFound
218
+ }
219
+ return nil , errors .Annotatef (res .Error , "txid %v" , txid )
220
+ }
221
+ return res .Result , nil
222
+ }
223
+
156
224
// GetTransactionForMempool returns a transaction by the transaction ID.
157
225
// It could be optimized for mempool, i.e. without block time and confirmations
158
226
func (z * ZCashRPC ) GetTransactionForMempool (txid string ) (* bchain.Tx , error ) {
@@ -168,3 +236,72 @@ func (z *ZCashRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
168
236
func (z * ZCashRPC ) GetBlockRaw (hash string ) (string , error ) {
169
237
return "" , errors .New ("GetBlockRaw: not supported" )
170
238
}
239
+
240
+ // JSONMarshalerV1 is used for marshalling requests to legacy Bitcoin Type RPC interfaces
241
+ type JSONMarshalerV1Zebra struct {}
242
+
243
+ // Marshal converts struct passed by parameter to JSON
244
+ func (JSONMarshalerV1Zebra ) Marshal (v interface {}) ([]byte , error ) {
245
+ u := cmdUntypedParams {}
246
+
247
+ switch v := v .(type ) {
248
+ case * btc.CmdGetBlock :
249
+ u .Method = v .Method
250
+ u .Params = append (u .Params , v .Params .BlockHash )
251
+ u .Params = append (u .Params , v .Params .Verbosity )
252
+ case * btc.CmdGetRawTransaction :
253
+ var n int
254
+ if v .Params .Verbose {
255
+ n = 1
256
+ }
257
+ u .Method = v .Method
258
+ u .Params = append (u .Params , v .Params .Txid )
259
+ u .Params = append (u .Params , n )
260
+ default :
261
+ {
262
+ v := reflect .ValueOf (v ).Elem ()
263
+
264
+ f := v .FieldByName ("Method" )
265
+ if ! f .IsValid () || f .Kind () != reflect .String {
266
+ return nil , btc .ErrInvalidValue
267
+ }
268
+ u .Method = f .String ()
269
+
270
+ f = v .FieldByName ("Params" )
271
+ if f .IsValid () {
272
+ var arr []interface {}
273
+ switch f .Kind () {
274
+ case reflect .Slice :
275
+ arr = make ([]interface {}, f .Len ())
276
+ for i := 0 ; i < f .Len (); i ++ {
277
+ arr [i ] = f .Index (i ).Interface ()
278
+ }
279
+ case reflect .Struct :
280
+ arr = make ([]interface {}, f .NumField ())
281
+ for i := 0 ; i < f .NumField (); i ++ {
282
+ arr [i ] = f .Field (i ).Interface ()
283
+ }
284
+ default :
285
+ return nil , btc .ErrInvalidValue
286
+ }
287
+ u .Params = arr
288
+ }
289
+ }
290
+ }
291
+ u .Id = "-"
292
+ if u .Params == nil {
293
+ u .Params = make ([]interface {}, 0 )
294
+ }
295
+ d , err := json .Marshal (u )
296
+ if err != nil {
297
+ return nil , err
298
+ }
299
+
300
+ return d , nil
301
+ }
302
+
303
+ type cmdUntypedParams struct {
304
+ Method string `json:"method"`
305
+ Id string `json:"id"`
306
+ Params []interface {} `json:"params"`
307
+ }
0 commit comments