github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/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 "github.com/golang/glog" 14 "github.com/juju/errors" 15 16 ethereum "github.com/ethereum/go-ethereum" 17 ethcommon "github.com/ethereum/go-ethereum/common" 18 ethtypes "github.com/ethereum/go-ethereum/core/types" 19 "github.com/ethereum/go-ethereum/ethclient" 20 "github.com/ethereum/go-ethereum/rpc" 21 ) 22 23 // EthereumNet type specifies the type of ethereum network 24 type EthereumNet uint32 25 26 const ( 27 // MainNet is production network 28 MainNet EthereumNet = 1 29 // TestNet is Ropsten test network 30 TestNet EthereumNet = 3 31 ) 32 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 } 39 40 // EthereumRPC is an interface to JSON-RPC eth service. 41 type EthereumRPC struct { 42 client *ethclient.Client 43 rpc *rpc.Client 44 timeout time.Duration 45 Parser *EthereumParser 46 Testnet bool 47 Network string 48 Mempool *bchain.NonUTXOMempool 49 bestHeaderMu sync.Mutex 50 bestHeader *ethtypes.Header 51 bestHeaderTime time.Time 52 chanNewBlock chan *ethtypes.Header 53 newBlockSubscription *rpc.ClientSubscription 54 chanNewTx chan ethcommon.Hash 55 newTxSubscription *rpc.ClientSubscription 56 ChainConfig *Configuration 57 isETC bool 58 } 59 60 // NewEthereumRPC returns new EthRPC instance. 61 func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { 62 var err error 63 var c Configuration 64 err = json.Unmarshal(config, &c) 65 if err != nil { 66 return nil, errors.Annotatef(err, "Invalid configuration file") 67 } 68 rc, err := rpc.Dial(c.RPCURL) 69 if err != nil { 70 return nil, err 71 } 72 ec := ethclient.NewClient(rc) 73 74 s := &EthereumRPC{ 75 client: ec, 76 rpc: rc, 77 ChainConfig: &c, 78 } 79 80 // always create parser 81 s.Parser = NewEthereumParser() 82 s.timeout = time.Duration(c.RPCTimeout) * time.Second 83 84 // detect ethereum classic 85 s.isETC = s.ChainConfig.CoinName == "Ethereum Classic" 86 87 // new blocks notifications handling 88 // the subscription is done in Initialize 89 s.chanNewBlock = make(chan *ethtypes.Header) 90 go func() { 91 for { 92 h, ok := <-s.chanNewBlock 93 if !ok { 94 break 95 } 96 glog.V(2).Info("rpc: new block header ", h.Number) 97 // update best header to the new header 98 s.bestHeaderMu.Lock() 99 s.bestHeader = h 100 s.bestHeaderTime = time.Now() 101 s.bestHeaderMu.Unlock() 102 // notify blockbook 103 pushHandler(bchain.NotificationNewBlock) 104 } 105 }() 106 107 // new mempool transaction notifications handling 108 // the subscription is done in Initialize 109 s.chanNewTx = make(chan ethcommon.Hash) 110 go func() { 111 for { 112 t, ok := <-s.chanNewTx 113 if !ok { 114 break 115 } 116 if glog.V(2) { 117 glog.Info("rpc: new tx ", t.Hex()) 118 } 119 pushHandler(bchain.NotificationNewTx) 120 } 121 }() 122 123 return s, nil 124 } 125 126 // Initialize initializes ethereum rpc interface 127 func (b *EthereumRPC) Initialize() error { 128 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 129 defer cancel() 130 131 id, err := b.client.NetworkID(ctx) 132 if err != nil { 133 return err 134 } 135 136 // parameters for getInfo request 137 switch EthereumNet(id.Uint64()) { 138 case MainNet: 139 b.Testnet = false 140 b.Network = "livenet" 141 break 142 case TestNet: 143 b.Testnet = true 144 b.Network = "testnet" 145 break 146 default: 147 return errors.Errorf("Unknown network id %v", id) 148 } 149 glog.Info("rpc: block chain ", b.Network) 150 151 if b.isETC { 152 glog.Info(b.ChainConfig.CoinName, " does not support subscription to newHeads") 153 } else { 154 // subscriptions 155 if err = b.subscribe(func() (*rpc.ClientSubscription, error) { 156 // invalidate the previous subscription - it is either the first one or there was an error 157 b.newBlockSubscription = nil 158 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 159 defer cancel() 160 sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads") 161 if err != nil { 162 return nil, errors.Annotatef(err, "EthSubscribe newHeads") 163 } 164 b.newBlockSubscription = sub 165 glog.Info("Subscribed to newHeads") 166 return sub, nil 167 }); err != nil { 168 return err 169 } 170 } 171 if err = b.subscribe(func() (*rpc.ClientSubscription, error) { 172 // invalidate the previous subscription - it is either the first one or there was an error 173 b.newTxSubscription = nil 174 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 175 defer cancel() 176 sub, err := b.rpc.EthSubscribe(ctx, b.chanNewTx, "newPendingTransactions") 177 if err != nil { 178 return nil, errors.Annotatef(err, "EthSubscribe newPendingTransactions") 179 } 180 b.newTxSubscription = sub 181 glog.Info("Subscribed to newPendingTransactions") 182 return sub, nil 183 }); err != nil { 184 return err 185 } 186 187 // create mempool 188 b.Mempool = bchain.NewNonUTXOMempool(b) 189 190 return nil 191 } 192 193 // subscribe subscribes notification and tries to resubscribe in case of error 194 func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error { 195 s, err := f() 196 if err != nil { 197 return err 198 } 199 go func() { 200 Loop: 201 for { 202 // wait for error in subscription 203 e := <-s.Err() 204 // nil error means sub.Unsubscribe called, exit goroutine 205 if e == nil { 206 return 207 } 208 glog.Error("Subscription error ", e) 209 timer := time.NewTimer(time.Second) 210 // try in 1 second interval to resubscribe 211 for { 212 select { 213 case e = <-s.Err(): 214 if e == nil { 215 return 216 } 217 case <-timer.C: 218 ns, err := f() 219 if err == nil { 220 // subscription successful, restart wait for next error 221 s = ns 222 continue Loop 223 } 224 timer.Reset(time.Second) 225 } 226 } 227 } 228 }() 229 return nil 230 } 231 232 // Shutdown cleans up rpc interface to ethereum 233 func (b *EthereumRPC) Shutdown(ctx context.Context) error { 234 if b.newBlockSubscription != nil { 235 b.newBlockSubscription.Unsubscribe() 236 } 237 if b.newTxSubscription != nil { 238 b.newTxSubscription.Unsubscribe() 239 } 240 if b.rpc != nil { 241 b.rpc.Close() 242 } 243 close(b.chanNewBlock) 244 glog.Info("rpc: shutdown") 245 return nil 246 } 247 248 func (b *EthereumRPC) IsTestnet() bool { 249 return b.Testnet 250 } 251 252 func (b *EthereumRPC) GetNetworkName() string { 253 return b.Network 254 } 255 256 func (b *EthereumRPC) GetCoinName() string { 257 return b.ChainConfig.CoinName 258 } 259 260 func (b *EthereumRPC) GetSubversion() string { 261 return "" 262 } 263 264 // GetChainInfo returns information about the connected backend 265 func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) { 266 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 267 defer cancel() 268 id, err := b.client.NetworkID(ctx) 269 if err != nil { 270 return nil, err 271 } 272 rv := &bchain.ChainInfo{} 273 idi := int(id.Uint64()) 274 if idi == 1 { 275 rv.Chain = "mainnet" 276 } else { 277 rv.Chain = "testnet " + strconv.Itoa(idi) 278 } 279 // TODO - return more information about the chain 280 return rv, nil 281 } 282 283 func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) { 284 b.bestHeaderMu.Lock() 285 defer b.bestHeaderMu.Unlock() 286 // ETC does not have newBlocks subscription, bestHeader must be updated very often (each 1 second) 287 if b.isETC { 288 if b.bestHeaderTime.Add(1 * time.Second).Before(time.Now()) { 289 b.bestHeader = nil 290 } 291 } 292 if b.bestHeader == nil { 293 var err error 294 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 295 defer cancel() 296 b.bestHeader, err = b.client.HeaderByNumber(ctx, nil) 297 if err != nil { 298 return nil, err 299 } 300 b.bestHeaderTime = time.Now() 301 } 302 return b.bestHeader, nil 303 } 304 305 func (b *EthereumRPC) GetBestBlockHash() (string, error) { 306 h, err := b.getBestHeader() 307 if err != nil { 308 return "", err 309 } 310 return ethHashToHash(h.Hash()), nil 311 } 312 313 func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) { 314 h, err := b.getBestHeader() 315 if err != nil { 316 return 0, err 317 } 318 // TODO - can it grow over 2^32 ? 319 return uint32(h.Number.Uint64()), nil 320 } 321 322 func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) { 323 var n big.Int 324 n.SetUint64(uint64(height)) 325 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 326 defer cancel() 327 h, err := b.client.HeaderByNumber(ctx, &n) 328 if err != nil { 329 if err == ethereum.NotFound { 330 return "", bchain.ErrBlockNotFound 331 } 332 return "", errors.Annotatef(err, "height %v", height) 333 } 334 return ethHashToHash(h.Hash()), nil 335 } 336 337 func (b *EthereumRPC) ethHeaderToBlockHeader(h *ethtypes.Header) (*bchain.BlockHeader, error) { 338 hn := h.Number.Uint64() 339 c, err := b.computeConfirmations(hn) 340 if err != nil { 341 return nil, err 342 } 343 return &bchain.BlockHeader{ 344 Hash: ethHashToHash(h.Hash()), 345 Height: uint32(hn), 346 Confirmations: int(c), 347 Time: int64(h.Time.Uint64()), 348 // Next 349 // Prev 350 }, nil 351 } 352 353 func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { 354 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 355 defer cancel() 356 h, err := b.client.HeaderByHash(ctx, ethcommon.HexToHash(hash)) 357 if err != nil { 358 if err == ethereum.NotFound { 359 return nil, bchain.ErrBlockNotFound 360 } 361 return nil, errors.Annotatef(err, "hash %v", hash) 362 } 363 return b.ethHeaderToBlockHeader(h) 364 } 365 366 func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) { 367 bh, err := b.getBestHeader() 368 if err != nil { 369 return 0, err 370 } 371 bn := bh.Number.Uint64() 372 // transaction in the best block has 1 confirmation 373 return uint32(bn - n + 1), nil 374 } 375 376 // GetBlock returns block with given hash or height, hash has precedence if both passed 377 func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { 378 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 379 defer cancel() 380 var raw json.RawMessage 381 var err error 382 if hash != "" { 383 err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), true) 384 } else { 385 386 err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), true) 387 } 388 if err != nil { 389 return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) 390 } else if len(raw) == 0 { 391 return nil, bchain.ErrBlockNotFound 392 } 393 // Decode header and transactions. 394 var head *ethtypes.Header 395 var body rpcBlock 396 if err := json.Unmarshal(raw, &head); err != nil { 397 return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) 398 } 399 if head == nil { 400 return nil, bchain.ErrBlockNotFound 401 } 402 if err := json.Unmarshal(raw, &body); err != nil { 403 return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) 404 } 405 // Quick-verify transaction and uncle lists. This mostly helps with debugging the server. 406 if head.UncleHash == ethtypes.EmptyUncleHash && len(body.UncleHashes) > 0 { 407 return nil, errors.Annotatef(fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles"), "hash %v, height %v", hash, height) 408 } 409 if head.UncleHash != ethtypes.EmptyUncleHash && len(body.UncleHashes) == 0 { 410 return nil, errors.Annotatef(fmt.Errorf("server returned empty uncle list but block header indicates uncles"), "hash %v, height %v", hash, height) 411 } 412 if head.TxHash == ethtypes.EmptyRootHash && len(body.Transactions) > 0 { 413 return nil, errors.Annotatef(fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions"), "hash %v, height %v", hash, height) 414 } 415 if head.TxHash != ethtypes.EmptyRootHash && len(body.Transactions) == 0 { 416 return nil, errors.Annotatef(fmt.Errorf("server returned empty transaction list but block header indicates transactions"), "hash %v, height %v", hash, height) 417 } 418 bbh, err := b.ethHeaderToBlockHeader(head) 419 if err != nil { 420 return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) 421 } 422 // TODO - this is probably not the correct size 423 bbh.Size = len(raw) 424 btxs := make([]bchain.Tx, len(body.Transactions)) 425 for i, tx := range body.Transactions { 426 btx, err := b.Parser.ethTxToTx(&tx, int64(head.Time.Uint64()), uint32(bbh.Confirmations)) 427 if err != nil { 428 return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash.String()) 429 } 430 btxs[i] = *btx 431 } 432 bbk := bchain.Block{ 433 BlockHeader: *bbh, 434 Txs: btxs, 435 } 436 return &bbk, nil 437 } 438 439 // GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids 440 func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { 441 // TODO - implement 442 return nil, errors.New("Not implemented yet") 443 } 444 445 // GetTransactionForMempool returns a transaction by the transaction ID. 446 // It could be optimized for mempool, i.e. without block time and confirmations 447 func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { 448 return b.GetTransaction(txid) 449 } 450 451 // GetTransaction returns a transaction by the transaction ID. 452 func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { 453 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 454 defer cancel() 455 var tx *rpcTransaction 456 err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid)) 457 if err != nil { 458 return nil, err 459 } else if tx == nil { 460 return nil, ethereum.NotFound 461 } else if tx.R == "" { 462 if !b.isETC { 463 return nil, errors.Annotatef(fmt.Errorf("server returned transaction without signature"), "txid %v", txid) 464 } else { 465 glog.Warning("server returned transaction without signature, txid ", txid) 466 } 467 } 468 var btx *bchain.Tx 469 if tx.BlockNumber == "" { 470 // mempool tx 471 btx, err = b.Parser.ethTxToTx(tx, 0, 0) 472 if err != nil { 473 return nil, errors.Annotatef(err, "txid %v", txid) 474 } 475 } else { 476 // non mempool tx - we must read the block header to get the block time 477 n, err := ethNumber(tx.BlockNumber) 478 if err != nil { 479 return nil, errors.Annotatef(err, "txid %v", txid) 480 } 481 h, err := b.client.HeaderByHash(ctx, *tx.BlockHash) 482 if err != nil { 483 return nil, errors.Annotatef(err, "txid %v", txid) 484 } 485 confirmations, err := b.computeConfirmations(uint64(n)) 486 if err != nil { 487 return nil, errors.Annotatef(err, "txid %v", txid) 488 } 489 btx, err = b.Parser.ethTxToTx(tx, h.Time.Int64(), confirmations) 490 if err != nil { 491 return nil, errors.Annotatef(err, "txid %v", txid) 492 } 493 } 494 return btx, nil 495 } 496 497 // GetTransactionSpecific returns json as returned by backend, with all coin specific data 498 func (b *EthereumRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) { 499 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 500 defer cancel() 501 var tx json.RawMessage 502 err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid)) 503 if err != nil { 504 return nil, err 505 } else if tx == nil { 506 return nil, ethereum.NotFound 507 } 508 return tx, nil 509 } 510 511 type rpcMempoolBlock struct { 512 Transactions []string `json:"transactions"` 513 } 514 515 func (b *EthereumRPC) GetMempool() ([]string, error) { 516 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 517 defer cancel() 518 var raw json.RawMessage 519 var err error 520 err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", "pending", false) 521 if err != nil { 522 return nil, err 523 } else if len(raw) == 0 { 524 return nil, bchain.ErrBlockNotFound 525 } 526 var body rpcMempoolBlock 527 if err := json.Unmarshal(raw, &body); err != nil { 528 return nil, err 529 } 530 return body.Transactions, nil 531 } 532 533 // EstimateFee returns fee estimation 534 func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) { 535 return b.EstimateSmartFee(blocks, true) 536 } 537 538 // EstimateSmartFee returns fee estimation 539 func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { 540 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 541 defer cancel() 542 // TODO - what parameters of msg to use to get better estimate, maybe more data from the wallet are needed 543 a := ethcommon.HexToAddress("0x1234567890123456789012345678901234567890") 544 msg := ethereum.CallMsg{ 545 To: &a, 546 } 547 g, err := b.client.EstimateGas(ctx, msg) 548 var r big.Int 549 if err != nil { 550 return r, err 551 } 552 r.SetUint64(g) 553 return r, nil 554 } 555 556 // SendRawTransaction sends raw transaction. 557 func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) { 558 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 559 defer cancel() 560 var raw json.RawMessage 561 err := b.rpc.CallContext(ctx, &raw, "eth_sendRawTransaction", hex) 562 if err != nil { 563 return "", err 564 } else if len(raw) == 0 { 565 return "", errors.New("SendRawTransaction: failed") 566 } 567 var result string 568 if err := json.Unmarshal(raw, &result); err != nil { 569 return "", errors.Annotatef(err, "raw result %v", raw) 570 } 571 if result == "" { 572 return "", errors.New("SendRawTransaction: failed, empty result") 573 } 574 return result, nil 575 } 576 577 func (b *EthereumRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) { 578 return b.Mempool.Resync(onNewTxAddr) 579 } 580 581 // GetMempoolTransactions returns slice of mempool transactions for given address 582 func (b *EthereumRPC) GetMempoolTransactions(address string) ([]string, error) { 583 return b.Mempool.GetTransactions(address) 584 } 585 586 // GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor 587 func (b *EthereumRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, error) { 588 return b.Mempool.GetAddrDescTransactions(addrDesc) 589 } 590 591 func (b *EthereumRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) { 592 return nil, errors.New("GetMempoolEntry: not implemented") 593 } 594 595 func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser { 596 return b.Parser 597 }