github.com/bchainhub/blockbook@v0.3.2/bchain/coins/eth/ethrpc.go (about) 1 package eth 2 3 import ( 4 "blockbook/bchain" 5 "context" 6 "encoding/json" 7 "fmt" 8 "math/big" 9 "strconv" 10 "sync" 11 "time" 12 13 ethereum "github.com/ethereum/go-ethereum" 14 ethcommon "github.com/ethereum/go-ethereum/common" 15 ethtypes "github.com/ethereum/go-ethereum/core/types" 16 "github.com/ethereum/go-ethereum/ethclient" 17 "github.com/ethereum/go-ethereum/rpc" 18 "github.com/golang/glog" 19 "github.com/juju/errors" 20 ) 21 22 // EthereumNet type specifies the type of ethereum network 23 type EthereumNet uint32 24 25 const ( 26 // MainNet is production network 27 MainNet EthereumNet = 1 28 // TestNet is Ropsten test network 29 TestNet EthereumNet = 3 30 ) 31 32 // Configuration represents json config file 33 type Configuration struct { 34 CoinName string `json:"coin_name"` 35 CoinShortcut string `json:"coin_shortcut"` 36 RPCURL string `json:"rpc_url"` 37 RPCTimeout int `json:"rpc_timeout"` 38 BlockAddressesToKeep int `json:"block_addresses_to_keep"` 39 MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"` 40 QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"` 41 } 42 43 // EthereumRPC is an interface to JSON-RPC eth service. 44 type EthereumRPC struct { 45 *bchain.BaseChain 46 client *ethclient.Client 47 rpc *rpc.Client 48 timeout time.Duration 49 Parser *EthereumParser 50 Mempool *bchain.MempoolEthereumType 51 mempoolInitialized bool 52 bestHeaderLock sync.Mutex 53 bestHeader *ethtypes.Header 54 bestHeaderTime time.Time 55 chanNewBlock chan *ethtypes.Header 56 newBlockSubscription *rpc.ClientSubscription 57 chanNewTx chan ethcommon.Hash 58 newTxSubscription *rpc.ClientSubscription 59 ChainConfig *Configuration 60 } 61 62 // NewEthereumRPC returns new EthRPC instance. 63 func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { 64 var err error 65 var c Configuration 66 err = json.Unmarshal(config, &c) 67 if err != nil { 68 return nil, errors.Annotatef(err, "Invalid configuration file") 69 } 70 // keep at least 100 mappings block->addresses to allow rollback 71 if c.BlockAddressesToKeep < 100 { 72 c.BlockAddressesToKeep = 100 73 } 74 75 rc, ec, err := openRPC(c.RPCURL) 76 if err != nil { 77 return nil, err 78 } 79 80 s := &EthereumRPC{ 81 BaseChain: &bchain.BaseChain{}, 82 client: ec, 83 rpc: rc, 84 ChainConfig: &c, 85 } 86 87 // always create parser 88 s.Parser = NewEthereumParser(c.BlockAddressesToKeep) 89 s.timeout = time.Duration(c.RPCTimeout) * time.Second 90 91 // new blocks notifications handling 92 // the subscription is done in Initialize 93 s.chanNewBlock = make(chan *ethtypes.Header) 94 go func() { 95 for { 96 h, ok := <-s.chanNewBlock 97 if !ok { 98 break 99 } 100 glog.V(2).Info("rpc: new block header ", h.Number) 101 // update best header to the new header 102 s.bestHeaderLock.Lock() 103 s.bestHeader = h 104 s.bestHeaderTime = time.Now() 105 s.bestHeaderLock.Unlock() 106 // notify blockbook 107 pushHandler(bchain.NotificationNewBlock) 108 } 109 }() 110 111 // new mempool transaction notifications handling 112 // the subscription is done in Initialize 113 s.chanNewTx = make(chan ethcommon.Hash) 114 go func() { 115 for { 116 t, ok := <-s.chanNewTx 117 if !ok { 118 break 119 } 120 hex := t.Hex() 121 if glog.V(2) { 122 glog.Info("rpc: new tx ", hex) 123 } 124 s.Mempool.AddTransactionToMempool(hex) 125 pushHandler(bchain.NotificationNewTx) 126 } 127 }() 128 129 return s, nil 130 } 131 132 func openRPC(url string) (*rpc.Client, *ethclient.Client, error) { 133 rc, err := rpc.Dial(url) 134 if err != nil { 135 return nil, nil, err 136 } 137 ec := ethclient.NewClient(rc) 138 return rc, ec, nil 139 } 140 141 // Initialize initializes ethereum rpc interface 142 func (b *EthereumRPC) Initialize() error { 143 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 144 defer cancel() 145 146 id, err := b.client.NetworkID(ctx) 147 if err != nil { 148 return err 149 } 150 151 // parameters for getInfo request 152 switch EthereumNet(id.Uint64()) { 153 case MainNet: 154 b.Testnet = false 155 b.Network = "livenet" 156 break 157 case TestNet: 158 b.Testnet = true 159 b.Network = "testnet" 160 break 161 default: 162 return errors.Errorf("Unknown network id %v", id) 163 } 164 glog.Info("rpc: block chain ", b.Network) 165 166 return nil 167 } 168 169 // CreateMempool creates mempool if not already created, however does not initialize it 170 func (b *EthereumRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) { 171 if b.Mempool == nil { 172 b.Mempool = bchain.NewMempoolEthereumType(chain, b.ChainConfig.MempoolTxTimeoutHours, b.ChainConfig.QueryBackendOnMempoolResync) 173 glog.Info("mempool created, MempoolTxTimeoutHours=", b.ChainConfig.MempoolTxTimeoutHours, ", QueryBackendOnMempoolResync=", b.ChainConfig.QueryBackendOnMempoolResync) 174 } 175 return b.Mempool, nil 176 } 177 178 // InitializeMempool creates subscriptions to newHeads and newPendingTransactions 179 func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc) error { 180 if b.Mempool == nil { 181 return errors.New("Mempool not created") 182 } 183 184 // get initial mempool transactions 185 txs, err := b.GetMempoolTransactions() 186 if err != nil { 187 return err 188 } 189 for _, txid := range txs { 190 b.Mempool.AddTransactionToMempool(txid) 191 } 192 193 b.Mempool.OnNewTxAddr = onNewTxAddr 194 195 if err = b.subscribeEvents(); err != nil { 196 return err 197 } 198 199 b.mempoolInitialized = true 200 201 return nil 202 } 203 204 func (b *EthereumRPC) subscribeEvents() error { 205 // subscriptions 206 if err := b.subscribe(func() (*rpc.ClientSubscription, error) { 207 // invalidate the previous subscription - it is either the first one or there was an error 208 b.newBlockSubscription = nil 209 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 210 defer cancel() 211 sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads") 212 if err != nil { 213 return nil, errors.Annotatef(err, "EthSubscribe newHeads") 214 } 215 b.newBlockSubscription = sub 216 glog.Info("Subscribed to newHeads") 217 return sub, nil 218 }); err != nil { 219 return err 220 } 221 222 if err := b.subscribe(func() (*rpc.ClientSubscription, error) { 223 // invalidate the previous subscription - it is either the first one or there was an error 224 b.newTxSubscription = nil 225 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 226 defer cancel() 227 sub, err := b.rpc.EthSubscribe(ctx, b.chanNewTx, "newPendingTransactions") 228 if err != nil { 229 return nil, errors.Annotatef(err, "EthSubscribe newPendingTransactions") 230 } 231 b.newTxSubscription = sub 232 glog.Info("Subscribed to newPendingTransactions") 233 return sub, nil 234 }); err != nil { 235 return err 236 } 237 238 return nil 239 } 240 241 // subscribe subscribes notification and tries to resubscribe in case of error 242 func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error { 243 s, err := f() 244 if err != nil { 245 return err 246 } 247 go func() { 248 Loop: 249 for { 250 // wait for error in subscription 251 e := <-s.Err() 252 // nil error means sub.Unsubscribe called, exit goroutine 253 if e == nil { 254 return 255 } 256 glog.Error("Subscription error ", e) 257 timer := time.NewTimer(time.Second * 2) 258 // try in 2 second interval to resubscribe 259 for { 260 select { 261 case e = <-s.Err(): 262 if e == nil { 263 return 264 } 265 case <-timer.C: 266 ns, err := f() 267 if err == nil { 268 // subscription successful, restart wait for next error 269 s = ns 270 continue Loop 271 } 272 glog.Error("Resubscribe error ", err) 273 timer.Reset(time.Second * 2) 274 } 275 } 276 } 277 }() 278 return nil 279 } 280 281 func (b *EthereumRPC) closeRPC() { 282 if b.newBlockSubscription != nil { 283 b.newBlockSubscription.Unsubscribe() 284 } 285 if b.newTxSubscription != nil { 286 b.newTxSubscription.Unsubscribe() 287 } 288 if b.rpc != nil { 289 b.rpc.Close() 290 } 291 } 292 293 func (b *EthereumRPC) reconnectRPC() error { 294 glog.Info("Reconnecting RPC") 295 b.closeRPC() 296 rc, ec, err := openRPC(b.ChainConfig.RPCURL) 297 if err != nil { 298 return err 299 } 300 b.rpc = rc 301 b.client = ec 302 return b.subscribeEvents() 303 } 304 305 // Shutdown cleans up rpc interface to ethereum 306 func (b *EthereumRPC) Shutdown(ctx context.Context) error { 307 b.closeRPC() 308 close(b.chanNewBlock) 309 glog.Info("rpc: shutdown") 310 return nil 311 } 312 313 // GetCoinName returns coin name 314 func (b *EthereumRPC) GetCoinName() string { 315 return b.ChainConfig.CoinName 316 } 317 318 // GetSubversion returns empty string, ethereum does not have subversion 319 func (b *EthereumRPC) GetSubversion() string { 320 return "" 321 } 322 323 // GetChainInfo returns information about the connected backend 324 func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) { 325 h, err := b.getBestHeader() 326 if err != nil { 327 return nil, err 328 } 329 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 330 defer cancel() 331 id, err := b.client.NetworkID(ctx) 332 if err != nil { 333 return nil, err 334 } 335 var ver, protocol string 336 if err := b.rpc.CallContext(ctx, &ver, "web3_clientVersion"); err != nil { 337 return nil, err 338 } 339 if err := b.rpc.CallContext(ctx, &protocol, "eth_protocolVersion"); err != nil { 340 return nil, err 341 } 342 rv := &bchain.ChainInfo{ 343 Blocks: int(h.Number.Int64()), 344 Bestblockhash: h.Hash().Hex(), 345 Difficulty: h.Difficulty.String(), 346 Version: ver, 347 ProtocolVersion: protocol, 348 } 349 idi := int(id.Uint64()) 350 if idi == 1 { 351 rv.Chain = "mainnet" 352 } else { 353 rv.Chain = "testnet " + strconv.Itoa(idi) 354 } 355 return rv, nil 356 } 357 358 func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) { 359 b.bestHeaderLock.Lock() 360 defer b.bestHeaderLock.Unlock() 361 // if the best header was not updated for 15 minutes, there could be a subscription problem, reconnect RPC 362 // do it only in case of normal operation, not initial synchronization 363 if b.bestHeaderTime.Add(15*time.Minute).Before(time.Now()) && !b.bestHeaderTime.IsZero() && b.mempoolInitialized { 364 err := b.reconnectRPC() 365 if err != nil { 366 return nil, err 367 } 368 b.bestHeader = nil 369 } 370 if b.bestHeader == nil { 371 var err error 372 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 373 defer cancel() 374 b.bestHeader, err = b.client.HeaderByNumber(ctx, nil) 375 if err != nil { 376 b.bestHeader = nil 377 return nil, err 378 } 379 b.bestHeaderTime = time.Now() 380 } 381 return b.bestHeader, nil 382 } 383 384 // GetBestBlockHash returns hash of the tip of the best-block-chain 385 func (b *EthereumRPC) GetBestBlockHash() (string, error) { 386 h, err := b.getBestHeader() 387 if err != nil { 388 return "", err 389 } 390 return h.Hash().Hex(), nil 391 } 392 393 // GetBestBlockHeight returns height of the tip of the best-block-chain 394 func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) { 395 h, err := b.getBestHeader() 396 if err != nil { 397 return 0, err 398 } 399 return uint32(h.Number.Uint64()), nil 400 } 401 402 // GetBlockHash returns hash of block in best-block-chain at given height 403 func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) { 404 var n big.Int 405 n.SetUint64(uint64(height)) 406 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 407 defer cancel() 408 h, err := b.client.HeaderByNumber(ctx, &n) 409 if err != nil { 410 if err == ethereum.NotFound { 411 return "", bchain.ErrBlockNotFound 412 } 413 return "", errors.Annotatef(err, "height %v", height) 414 } 415 return h.Hash().Hex(), nil 416 } 417 418 func (b *EthereumRPC) ethHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, error) { 419 height, err := ethNumber(h.Number) 420 if err != nil { 421 return nil, err 422 } 423 c, err := b.computeConfirmations(uint64(height)) 424 if err != nil { 425 return nil, err 426 } 427 time, err := ethNumber(h.Time) 428 if err != nil { 429 return nil, err 430 } 431 size, err := ethNumber(h.Size) 432 if err != nil { 433 return nil, err 434 } 435 return &bchain.BlockHeader{ 436 Hash: h.Hash, 437 Prev: h.ParentHash, 438 Height: uint32(height), 439 Confirmations: int(c), 440 Time: time, 441 Size: int(size), 442 }, nil 443 } 444 445 // GetBlockHeader returns header of block with given hash 446 func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { 447 raw, err := b.getBlockRaw(hash, 0, false) 448 if err != nil { 449 return nil, err 450 } 451 var h rpcHeader 452 if err := json.Unmarshal(raw, &h); err != nil { 453 return nil, errors.Annotatef(err, "hash %v", hash) 454 } 455 return b.ethHeaderToBlockHeader(&h) 456 } 457 458 func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) { 459 bh, err := b.getBestHeader() 460 if err != nil { 461 return 0, err 462 } 463 bn := bh.Number.Uint64() 464 // transaction in the best block has 1 confirmation 465 return uint32(bn - n + 1), nil 466 } 467 468 func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (json.RawMessage, error) { 469 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 470 defer cancel() 471 var raw json.RawMessage 472 var err error 473 if hash != "" { 474 if hash == "pending" { 475 err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", hash, fullTxs) 476 } else { 477 err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), fullTxs) 478 } 479 } else { 480 err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), fullTxs) 481 } 482 if err != nil { 483 return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) 484 } else if len(raw) == 0 { 485 return nil, bchain.ErrBlockNotFound 486 } 487 return raw, nil 488 } 489 490 func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*rpcLog, error) { 491 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 492 defer cancel() 493 var logs []rpcLogWithTxHash 494 err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ 495 "fromBlock": blockNumber, 496 "toBlock": blockNumber, 497 "topics": []string{erc20TransferEventSignature}, 498 }) 499 if err != nil { 500 return nil, errors.Annotatef(err, "blockNumber %v", blockNumber) 501 } 502 r := make(map[string][]*rpcLog) 503 for i := range logs { 504 l := &logs[i] 505 r[l.Hash] = append(r[l.Hash], &l.rpcLog) 506 } 507 return r, nil 508 } 509 510 // GetBlock returns block with given hash or height, hash has precedence if both passed 511 func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { 512 raw, err := b.getBlockRaw(hash, height, true) 513 if err != nil { 514 return nil, err 515 } 516 var head rpcHeader 517 if err := json.Unmarshal(raw, &head); err != nil { 518 return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) 519 } 520 var body rpcBlockTransactions 521 if err := json.Unmarshal(raw, &body); err != nil { 522 return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) 523 } 524 bbh, err := b.ethHeaderToBlockHeader(&head) 525 if err != nil { 526 return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) 527 } 528 // get ERC20 events 529 logs, err := b.getERC20EventsForBlock(head.Number) 530 if err != nil { 531 return nil, err 532 } 533 btxs := make([]bchain.Tx, len(body.Transactions)) 534 for i := range body.Transactions { 535 tx := &body.Transactions[i] 536 btx, err := b.Parser.ethTxToTx(tx, &rpcReceipt{Logs: logs[tx.Hash]}, bbh.Time, uint32(bbh.Confirmations), true) 537 if err != nil { 538 return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash) 539 } 540 btxs[i] = *btx 541 if b.mempoolInitialized { 542 b.Mempool.RemoveTransactionFromMempool(tx.Hash) 543 } 544 } 545 bbk := bchain.Block{ 546 BlockHeader: *bbh, 547 Txs: btxs, 548 } 549 return &bbk, nil 550 } 551 552 // GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids 553 func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { 554 raw, err := b.getBlockRaw(hash, 0, false) 555 if err != nil { 556 return nil, err 557 } 558 var head rpcHeader 559 var txs rpcBlockTxids 560 if err := json.Unmarshal(raw, &head); err != nil { 561 return nil, errors.Annotatef(err, "hash %v", hash) 562 } 563 if err = json.Unmarshal(raw, &txs); err != nil { 564 return nil, err 565 } 566 bch, err := b.ethHeaderToBlockHeader(&head) 567 if err != nil { 568 return nil, err 569 } 570 return &bchain.BlockInfo{ 571 BlockHeader: *bch, 572 Difficulty: json.Number(head.Difficulty), 573 Nonce: json.Number(head.Nonce), 574 Txids: txs.Transactions, 575 }, nil 576 } 577 578 // GetTransactionForMempool returns a transaction by the transaction ID. 579 // It could be optimized for mempool, i.e. without block time and confirmations 580 func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { 581 return b.GetTransaction(txid) 582 } 583 584 // GetTransaction returns a transaction by the transaction ID. 585 func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { 586 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 587 defer cancel() 588 var tx *rpcTransaction 589 hash := ethcommon.HexToHash(txid) 590 err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", hash) 591 if err != nil { 592 return nil, err 593 } else if tx == nil { 594 if b.mempoolInitialized { 595 b.Mempool.RemoveTransactionFromMempool(txid) 596 } 597 return nil, bchain.ErrTxNotFound 598 } 599 var btx *bchain.Tx 600 if tx.BlockNumber == "" { 601 // mempool tx 602 btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true) 603 if err != nil { 604 return nil, errors.Annotatef(err, "txid %v", txid) 605 } 606 } else { 607 // non mempool tx - read the block header to get the block time 608 raw, err := b.getBlockRaw(tx.BlockHash, 0, false) 609 if err != nil { 610 return nil, err 611 } 612 var ht struct { 613 Time string `json:"timestamp"` 614 } 615 if err := json.Unmarshal(raw, &ht); err != nil { 616 return nil, errors.Annotatef(err, "hash %v", hash) 617 } 618 var time int64 619 if time, err = ethNumber(ht.Time); err != nil { 620 return nil, errors.Annotatef(err, "txid %v", txid) 621 } 622 var receipt rpcReceipt 623 err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash) 624 if err != nil { 625 return nil, errors.Annotatef(err, "txid %v", txid) 626 } 627 n, err := ethNumber(tx.BlockNumber) 628 if err != nil { 629 return nil, errors.Annotatef(err, "txid %v", txid) 630 } 631 confirmations, err := b.computeConfirmations(uint64(n)) 632 if err != nil { 633 return nil, errors.Annotatef(err, "txid %v", txid) 634 } 635 btx, err = b.Parser.ethTxToTx(tx, &receipt, time, confirmations, true) 636 if err != nil { 637 return nil, errors.Annotatef(err, "txid %v", txid) 638 } 639 // remove tx from mempool if it is there 640 if b.mempoolInitialized { 641 b.Mempool.RemoveTransactionFromMempool(txid) 642 } 643 } 644 return btx, nil 645 } 646 647 // GetTransactionSpecific returns json as returned by backend, with all coin specific data 648 func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) { 649 csd, ok := tx.CoinSpecificData.(completeTransaction) 650 if !ok { 651 ntx, err := b.GetTransaction(tx.Txid) 652 if err != nil { 653 return nil, err 654 } 655 csd, ok = ntx.CoinSpecificData.(completeTransaction) 656 if !ok { 657 return nil, errors.New("Cannot get CoinSpecificData") 658 } 659 } 660 m, err := json.Marshal(&csd) 661 return json.RawMessage(m), err 662 } 663 664 // GetMempoolTransactions returns transactions in mempool 665 func (b *EthereumRPC) GetMempoolTransactions() ([]string, error) { 666 raw, err := b.getBlockRaw("pending", 0, false) 667 if err != nil { 668 return nil, err 669 } 670 var body rpcBlockTxids 671 if len(raw) > 0 { 672 if err := json.Unmarshal(raw, &body); err != nil { 673 return nil, err 674 } 675 } 676 return body.Transactions, nil 677 } 678 679 // EstimateFee returns fee estimation 680 func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) { 681 return b.EstimateSmartFee(blocks, true) 682 } 683 684 // EstimateSmartFee returns fee estimation 685 func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { 686 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 687 defer cancel() 688 var r big.Int 689 gp, err := b.client.SuggestGasPrice(ctx) 690 if err == nil && b != nil { 691 r = *gp 692 } 693 return r, err 694 } 695 696 func getStringFromMap(p string, params map[string]interface{}) (string, bool) { 697 v, ok := params[p] 698 if ok { 699 s, ok := v.(string) 700 return s, ok 701 } 702 return "", false 703 } 704 705 // EthereumTypeEstimateGas returns estimation of gas consumption for given transaction parameters 706 func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) { 707 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 708 defer cancel() 709 msg := ethereum.CallMsg{} 710 s, ok := getStringFromMap("from", params) 711 if ok && len(s) > 0 { 712 msg.From = ethcommon.HexToAddress(s) 713 } 714 s, ok = getStringFromMap("to", params) 715 if ok && len(s) > 0 { 716 a := ethcommon.HexToAddress(s) 717 msg.To = &a 718 } 719 s, ok = getStringFromMap("data", params) 720 if ok && len(s) > 0 { 721 msg.Data = ethcommon.FromHex(s) 722 } 723 return b.client.EstimateGas(ctx, msg) 724 } 725 726 // SendRawTransaction sends raw transaction 727 func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) { 728 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 729 defer cancel() 730 var raw json.RawMessage 731 err := b.rpc.CallContext(ctx, &raw, "eth_sendRawTransaction", hex) 732 if err != nil { 733 return "", err 734 } else if len(raw) == 0 { 735 return "", errors.New("SendRawTransaction: failed") 736 } 737 var result string 738 if err := json.Unmarshal(raw, &result); err != nil { 739 return "", errors.Annotatef(err, "raw result %v", raw) 740 } 741 if result == "" { 742 return "", errors.New("SendRawTransaction: failed, empty result") 743 } 744 return result, nil 745 } 746 747 // EthereumTypeGetBalance returns current balance of an address 748 func (b *EthereumRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) { 749 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 750 defer cancel() 751 return b.client.BalanceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil) 752 } 753 754 // EthereumTypeGetNonce returns current balance of an address 755 func (b *EthereumRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) { 756 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 757 defer cancel() 758 return b.client.NonceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil) 759 } 760 761 // GetChainParser returns ethereum BlockChainParser 762 func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser { 763 return b.Parser 764 }