github.com/bchainhub/blockbook@v0.3.2/bchain/coins/nuls/nulsrpc.go (about) 1 package nuls 2 3 import ( 4 "blockbook/bchain" 5 "blockbook/bchain/coins/btc" 6 "bytes" 7 "encoding/base64" 8 "encoding/hex" 9 "encoding/json" 10 "io" 11 "io/ioutil" 12 "math/big" 13 "net" 14 "net/http" 15 "runtime/debug" 16 "strconv" 17 "time" 18 19 "github.com/juju/errors" 20 21 "github.com/golang/glog" 22 ) 23 24 // NulsRPC is an interface to JSON-RPC bitcoind service 25 type NulsRPC struct { 26 *btc.BitcoinRPC 27 client http.Client 28 rpcURL string 29 user string 30 password string 31 } 32 33 // NewNulsRPC returns new NulsRPC instance 34 func NewNulsRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { 35 b, err := btc.NewBitcoinRPC(config, pushHandler) 36 if err != nil { 37 return nil, err 38 } 39 40 var c btc.Configuration 41 err = json.Unmarshal(config, &c) 42 if err != nil { 43 return nil, errors.Annotatef(err, "Invalid configuration file") 44 } 45 46 transport := &http.Transport{ 47 Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial, 48 MaxIdleConns: 100, 49 MaxIdleConnsPerHost: 100, // necessary to not to deplete ports 50 } 51 52 s := &NulsRPC{ 53 BitcoinRPC: b.(*btc.BitcoinRPC), 54 client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport}, 55 rpcURL: c.RPCURL, 56 user: c.RPCUser, 57 password: c.RPCPass, 58 } 59 s.BitcoinRPC.RPCMarshaler = btc.JSONMarshalerV1{} 60 s.BitcoinRPC.ChainConfig.SupportsEstimateSmartFee = false 61 62 return s, nil 63 } 64 65 // Initialize initializes GincoinRPC instance. 66 func (n *NulsRPC) Initialize() error { 67 chainName := "" 68 params := GetChainParams(chainName) 69 70 // always create parser 71 n.BitcoinRPC.Parser = NewNulsParser(params, n.BitcoinRPC.ChainConfig) 72 73 // parameters for getInfo request 74 if params.Net == MainnetMagic { 75 n.BitcoinRPC.Testnet = false 76 n.BitcoinRPC.Network = "livenet" 77 } else { 78 n.BitcoinRPC.Testnet = true 79 n.BitcoinRPC.Network = "testnet" 80 } 81 82 glog.Info("rpc: block chain ", params.Name) 83 84 return nil 85 } 86 87 type CmdGetNetworkInfo struct { 88 Success bool `json:"success"` 89 Data struct { 90 LocalBestHeight int64 `json:"localBestHeight"` 91 NetBestHeight int `json:"netBestHeight"` 92 TimeOffset string `json:"timeOffset"` 93 InCount int8 `json:"inCount"` 94 OutCount int8 `json:"outCount"` 95 } `json:"data"` 96 } 97 98 type CmdGetVersionInfo struct { 99 Success bool `json:"success"` 100 Data struct { 101 MyVersion string `json:"myVersion"` 102 NewestVersion string `json:"newestVersion"` 103 NetworkVersion int `json:"networkVersion"` 104 Information string `json:"information"` 105 } `json:"data"` 106 } 107 108 type CmdGetBestBlockHash struct { 109 Success bool `json:"success"` 110 Data struct { 111 Value string `json:"value"` 112 } `json:"data"` 113 } 114 115 type CmdGetBestBlockHeight struct { 116 Success bool `json:"success"` 117 Data struct { 118 Value uint32 `json:"value"` 119 } `json:"data"` 120 } 121 122 type CmdTxBroadcast struct { 123 Success bool `json:"success"` 124 Data struct { 125 Value string `json:"value"` 126 } `json:"data"` 127 } 128 129 type CmdGetBlockHeader struct { 130 Success bool `json:"success"` 131 Data struct { 132 Hash string `json:"hash"` 133 PreHash string `json:"preHash"` 134 MerkleHash string `json:"merkleHash"` 135 StateRoot string `json:"stateRoot"` 136 Time int64 `json:"time"` 137 Height int64 `json:"height"` 138 TxCount int `json:"txCount"` 139 PackingAddress string `json:"packingAddress"` 140 ConfirmCount int `json:"confirmCount"` 141 ScriptSig string `json:"scriptSig"` 142 Size int `json:"size"` 143 Reward float64 `json:"reward"` 144 Fee float64 `json:"fee"` 145 } `json:"data"` 146 } 147 148 type CmdGetBlock struct { 149 Success bool `json:"success"` 150 Data struct { 151 Hash string `json:"hash"` 152 PreHash string `json:"preHash"` 153 MerkleHash string `json:"merkleHash"` 154 StateRoot string `json:"stateRoot"` 155 Time int64 `json:"time"` 156 Height int64 `json:"height"` 157 TxCount int `json:"txCount"` 158 PackingAddress string `json:"packingAddress"` 159 ConfirmCount int `json:"confirmCount"` 160 ScriptSig string `json:"scriptSig"` 161 Size int `json:"size"` 162 Reward float64 `json:"reward"` 163 Fee float64 `json:"fee"` 164 165 TxList []Tx `json:"txList"` 166 } `json:"data"` 167 } 168 169 type CmdGetTx struct { 170 Success bool `json:"success"` 171 Tx Tx `json:"data"` 172 } 173 174 type Tx struct { 175 Hash string `json:"hash"` 176 Type int `json:"type"` 177 Time int64 `json:"time"` 178 BlockHeight int64 `json:"blockHeight"` 179 Fee float64 `json:"fee"` 180 Value float64 `json:"value"` 181 Remark string `json:"remark"` 182 ScriptSig string `json:"scriptSig"` 183 Status int `json:"status"` 184 ConfirmCount int `json:"confirmCount"` 185 Size int `json:"size"` 186 Inputs []struct { 187 FromHash string `json:"fromHash"` 188 FromIndex uint32 `json:"fromIndex"` 189 Address string `json:"address"` 190 Value float64 `json:"value"` 191 } `json:"inputs"` 192 Outputs []struct { 193 Address string `json:"address"` 194 Value int64 `json:"value"` 195 LockTime int64 `json:"lockTime"` 196 } `json:"outputs"` 197 } 198 199 type CmdGetTxBytes struct { 200 Success bool `json:"success"` 201 Data struct { 202 Value string `json:"value"` 203 } `json:"data"` 204 } 205 206 func (n *NulsRPC) GetChainInfo() (*bchain.ChainInfo, error) { 207 networkInfo := CmdGetNetworkInfo{} 208 error := n.Call("/api/network/info", &networkInfo) 209 if error != nil { 210 return nil, error 211 } 212 213 versionInfo := CmdGetVersionInfo{} 214 error = n.Call("/api/client/version", &versionInfo) 215 if error != nil { 216 return nil, error 217 } 218 219 chainInfo := &bchain.ChainInfo{ 220 Chain: "nuls", 221 Blocks: networkInfo.Data.NetBestHeight, 222 Headers: networkInfo.Data.NetBestHeight, 223 Bestblockhash: "", 224 Difficulty: networkInfo.Data.TimeOffset, 225 SizeOnDisk: networkInfo.Data.LocalBestHeight, 226 Version: versionInfo.Data.MyVersion, 227 Subversion: versionInfo.Data.NewestVersion, 228 ProtocolVersion: strconv.Itoa(versionInfo.Data.NetworkVersion), 229 Timeoffset: 0, 230 Warnings: versionInfo.Data.Information, 231 } 232 return chainInfo, nil 233 } 234 235 func (n *NulsRPC) GetBestBlockHash() (string, error) { 236 bestBlockHash := CmdGetBestBlockHash{} 237 error := n.Call("/api/block/newest/hash", &bestBlockHash) 238 if error != nil { 239 return "", error 240 } 241 return bestBlockHash.Data.Value, nil 242 } 243 244 func (n *NulsRPC) GetBestBlockHeight() (uint32, error) { 245 bestBlockHeight := CmdGetBestBlockHeight{} 246 error := n.Call("/api/block/newest/height", &bestBlockHeight) 247 if error != nil { 248 return 0, error 249 } 250 return bestBlockHeight.Data.Value, nil 251 } 252 253 func (n *NulsRPC) GetBlockHash(height uint32) (string, error) { 254 blockHeader := CmdGetBlockHeader{} 255 error := n.Call("/api/block/header/height/"+strconv.Itoa(int(height)), &blockHeader) 256 if error != nil { 257 return "", error 258 } 259 260 if !blockHeader.Success { 261 return "", bchain.ErrBlockNotFound 262 } 263 264 return blockHeader.Data.Hash, nil 265 } 266 267 func (n *NulsRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { 268 uri := "/api/block/header/hash/" + hash 269 return n.getBlobkHeader(uri) 270 } 271 272 func (n *NulsRPC) GetBlockHeaderByHeight(height uint32) (*bchain.BlockHeader, error) { 273 uri := "/api/block/header/height/" + strconv.Itoa(int(height)) 274 return n.getBlobkHeader(uri) 275 } 276 277 func (n *NulsRPC) getBlobkHeader(uri string) (*bchain.BlockHeader, error) { 278 blockHeader := CmdGetBlockHeader{} 279 error := n.Call(uri, &blockHeader) 280 if error != nil { 281 return nil, error 282 } 283 284 if !blockHeader.Success { 285 return nil, bchain.ErrBlockNotFound 286 } 287 288 nexHash, _ := n.GetBlockHash(uint32(blockHeader.Data.Height + 1)) 289 290 header := &bchain.BlockHeader{ 291 Hash: blockHeader.Data.Hash, 292 Prev: blockHeader.Data.PreHash, 293 Next: nexHash, 294 Height: uint32(blockHeader.Data.Height), 295 Confirmations: blockHeader.Data.ConfirmCount, 296 Size: blockHeader.Data.Size, 297 Time: blockHeader.Data.Time / 1000, 298 } 299 return header, nil 300 } 301 302 func (n *NulsRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { 303 304 url := "/api/block/hash/" + hash 305 306 if hash == "" { 307 url = "/api/block/height/" + strconv.Itoa(int(height)) 308 } 309 310 getBlock := CmdGetBlock{} 311 error := n.Call(url, &getBlock) 312 if error != nil { 313 return nil, error 314 } 315 316 if !getBlock.Success { 317 return nil, bchain.ErrBlockNotFound 318 } 319 320 nexHash, _ := n.GetBlockHash(uint32(getBlock.Data.Height + 1)) 321 322 header := bchain.BlockHeader{ 323 Hash: getBlock.Data.Hash, 324 Prev: getBlock.Data.PreHash, 325 Next: nexHash, 326 Height: uint32(getBlock.Data.Height), 327 Confirmations: getBlock.Data.ConfirmCount, 328 Size: getBlock.Data.Size, 329 Time: getBlock.Data.Time / 1000, 330 } 331 332 var txs []bchain.Tx 333 334 for _, rawTx := range getBlock.Data.TxList { 335 tx, err := converTx(rawTx) 336 if err != nil { 337 return nil, err 338 } 339 tx.Blocktime = header.Time 340 txs = append(txs, *tx) 341 } 342 343 block := &bchain.Block{ 344 BlockHeader: header, 345 Txs: txs, 346 } 347 return block, nil 348 } 349 350 func (n *NulsRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { 351 if hash == "" { 352 return nil, bchain.ErrBlockNotFound 353 } 354 355 getBlock := CmdGetBlock{} 356 error := n.Call("/api/block/hash/"+hash, &getBlock) 357 if error != nil { 358 return nil, error 359 } 360 361 if !getBlock.Success { 362 return nil, bchain.ErrBlockNotFound 363 } 364 365 nexHash, _ := n.GetBlockHash(uint32(getBlock.Data.Height + 1)) 366 367 header := bchain.BlockHeader{ 368 Hash: getBlock.Data.Hash, 369 Prev: getBlock.Data.PreHash, 370 Next: nexHash, 371 Height: uint32(getBlock.Data.Height), 372 Confirmations: getBlock.Data.ConfirmCount, 373 Size: getBlock.Data.Size, 374 Time: getBlock.Data.Time / 1000, 375 } 376 377 var txIds []string 378 379 for _, rawTx := range getBlock.Data.TxList { 380 txIds = append(txIds, rawTx.Hash) 381 } 382 383 blockInfo := &bchain.BlockInfo{ 384 BlockHeader: header, 385 MerkleRoot: getBlock.Data.MerkleHash, 386 //Version: getBlock.Data.StateRoot, 387 Txids: txIds, 388 } 389 return blockInfo, nil 390 } 391 392 func (n *NulsRPC) GetMempoolTransactions() ([]string, error) { 393 return nil, nil 394 } 395 396 func (n *NulsRPC) GetTransaction(txid string) (*bchain.Tx, error) { 397 if txid == "" { 398 return nil, bchain.ErrTxidMissing 399 } 400 401 getTx := CmdGetTx{} 402 error := n.Call("/api/tx/hash/"+txid, &getTx) 403 if error != nil { 404 return nil, error 405 } 406 407 if !getTx.Success { 408 return nil, bchain.ErrTxNotFound 409 } 410 411 tx, err := converTx(getTx.Tx) 412 if err != nil { 413 return nil, err 414 } 415 416 blockHeaderHeight := getTx.Tx.BlockHeight 417 // shouldn't it check the error here? 418 blockHeader, _ := n.GetBlockHeaderByHeight(uint32(blockHeaderHeight)) 419 if blockHeader != nil { 420 tx.Blocktime = blockHeader.Time 421 } 422 423 hexBytys, e := n.GetTransactionSpecific(tx) 424 if e == nil { 425 var hex string 426 json.Unmarshal(hexBytys, &hex) 427 tx.Hex = hex 428 tx.CoinSpecificData = hex 429 } 430 return tx, nil 431 } 432 433 func (n *NulsRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { 434 return nil, nil 435 } 436 437 func (n *NulsRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) { 438 if tx == nil { 439 return nil, bchain.ErrTxNotFound 440 } 441 442 if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok { 443 return csd, nil 444 } 445 446 getTxBytes := CmdGetTxBytes{} 447 error := n.Call("/api/tx/bytes?hash="+tx.Txid, &getTxBytes) 448 if error != nil { 449 return nil, error 450 } 451 452 if !getTxBytes.Success { 453 return nil, bchain.ErrTxNotFound 454 } 455 456 txBytes, byErr := base64.StdEncoding.DecodeString(getTxBytes.Data.Value) 457 if byErr != nil { 458 return nil, byErr 459 } 460 hexBytes := make([]byte, len(txBytes)*2) 461 hex.Encode(hexBytes, txBytes) 462 463 m, err := json.Marshal(string(hexBytes)) 464 return json.RawMessage(m), err 465 } 466 467 func (n *NulsRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { 468 return n.EstimateFee(blocks) 469 } 470 471 func (n *NulsRPC) EstimateFee(blocks int) (big.Int, error) { 472 return *big.NewInt(100000), nil 473 } 474 475 func (n *NulsRPC) SendRawTransaction(tx string) (string, error) { 476 broadcast := CmdTxBroadcast{} 477 req := struct { 478 TxHex string `json:"txHex"` 479 }{ 480 TxHex: tx, 481 } 482 483 error := n.Post("/api/accountledger/transaction/broadcast", req, &broadcast) 484 if error != nil { 485 return "", error 486 } 487 488 if !broadcast.Success { 489 return "", bchain.ErrTxidMissing 490 } 491 492 return broadcast.Data.Value, nil 493 } 494 495 // Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request 496 func (b *NulsRPC) Call(uri string, res interface{}) error { 497 url := b.rpcURL + uri 498 httpReq, err := http.NewRequest("GET", url, nil) 499 if err != nil { 500 return err 501 } 502 httpReq.SetBasicAuth(b.user, b.password) 503 httpRes, err := b.client.Do(httpReq) 504 // in some cases the httpRes can contain data even if it returns error 505 // see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/ 506 if httpRes != nil { 507 defer httpRes.Body.Close() 508 } 509 if err != nil { 510 return err 511 } 512 // if server returns HTTP error code it might not return json with response 513 // handle both cases 514 if httpRes.StatusCode != 200 { 515 err = safeDecodeResponse(httpRes.Body, &res) 516 if err != nil { 517 return errors.Errorf("%v %v", httpRes.Status, err) 518 } 519 return nil 520 } 521 return safeDecodeResponse(httpRes.Body, &res) 522 } 523 524 func (b *NulsRPC) Post(uri string, req interface{}, res interface{}) error { 525 url := b.rpcURL + uri 526 httpData, err := b.RPCMarshaler.Marshal(req) 527 if err != nil { 528 return err 529 } 530 httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(httpData)) 531 if err != nil { 532 return err 533 } 534 httpReq.SetBasicAuth(b.user, b.password) 535 httpReq.Header.Set("Content-Type", "application/json") 536 httpRes, err := b.client.Do(httpReq) 537 // in some cases the httpRes can contain data even if it returns error 538 // see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/ 539 if httpRes != nil { 540 defer httpRes.Body.Close() 541 } 542 if err != nil { 543 return err 544 } 545 // if server returns HTTP error code it might not return json with response 546 // handle both cases 547 if httpRes.StatusCode != 200 { 548 err = safeDecodeResponse(httpRes.Body, &res) 549 if err != nil { 550 return errors.Errorf("%v %v", httpRes.Status, err) 551 } 552 return nil 553 } 554 return safeDecodeResponse(httpRes.Body, &res) 555 } 556 557 func safeDecodeResponse(body io.ReadCloser, res *interface{}) (err error) { 558 var data []byte 559 defer func() { 560 if r := recover(); r != nil { 561 glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data)) 562 debug.PrintStack() 563 if len(data) > 0 && len(data) < 2048 { 564 err = errors.Errorf("Error: %v", string(data)) 565 } else { 566 err = errors.New("Internal error") 567 } 568 } 569 }() 570 data, err = ioutil.ReadAll(body) 571 if err != nil { 572 return err 573 } 574 575 //fmt.Println(string(data)) 576 error := json.Unmarshal(data, res) 577 return error 578 } 579 580 func converTx(rawTx Tx) (*bchain.Tx, error) { 581 var lockTime int64 = 0 582 var vins = make([]bchain.Vin, 0) 583 var vouts []bchain.Vout 584 585 for _, input := range rawTx.Inputs { 586 vin := bchain.Vin{ 587 Coinbase: "", 588 Txid: input.FromHash, 589 Vout: input.FromIndex, 590 ScriptSig: bchain.ScriptSig{}, 591 Sequence: 0, 592 Addresses: []string{input.Address}, 593 } 594 vins = append(vins, vin) 595 } 596 597 for index, output := range rawTx.Outputs { 598 vout := bchain.Vout{ 599 ValueSat: *big.NewInt(output.Value), 600 //JsonValue: "", 601 //LockTime: output.LockTime, 602 N: uint32(index), 603 ScriptPubKey: bchain.ScriptPubKey{ 604 Hex: output.Address, 605 Addresses: []string{ 606 output.Address, 607 }, 608 }, 609 } 610 vouts = append(vouts, vout) 611 612 if lockTime < output.LockTime { 613 lockTime = output.LockTime 614 } 615 } 616 617 tx := &bchain.Tx{ 618 Hex: "", 619 Txid: rawTx.Hash, 620 Version: 0, 621 LockTime: uint32(lockTime), 622 Vin: vins, 623 Vout: vouts, 624 Confirmations: uint32(rawTx.ConfirmCount), 625 Time: rawTx.Time / 1000, 626 } 627 628 return tx, nil 629 }