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