Coverage Summary for Class: LogsNotificationEmitter (co.rsk.rpc.modules.eth.subscribe)

Class Method, % Line, %
LogsNotificationEmitter 0% (0/9) 0% (0/52)
LogsNotificationEmitter$1 0% (0/2) 0% (0/2)
LogsNotificationEmitter$Subscription 0% (0/2) 0% (0/4)
Total 0% (0/13) 0% (0/58)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2019 RSK Labs Ltd. 4  * 5  * This program is free software: you can redistribute it and/or modify 6  * it under the terms of the GNU Lesser General Public License as published by 7  * the Free Software Foundation, either version 3 of the License, or 8  * (at your option) any later version. 9  * 10  * This program is distributed in the hope that it will be useful, 11  * but WITHOUT ANY WARRANTY; without even the implied warranty of 12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13  * GNU Lesser General Public License for more details. 14  * 15  * You should have received a copy of the GNU Lesser General Public License 16  * along with this program. If not, see <http://www.gnu.org/licenses/>. 17  */ 18 package co.rsk.rpc.modules.eth.subscribe; 19  20 import co.rsk.core.bc.BlockFork; 21 import co.rsk.core.bc.BlockchainBranchComparator; 22 import co.rsk.rpc.JsonRpcSerializer; 23 import io.netty.channel.Channel; 24 import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 25 import org.ethereum.core.Block; 26 import org.ethereum.core.Transaction; 27 import org.ethereum.core.TransactionReceipt; 28 import org.ethereum.db.ReceiptStore; 29 import org.ethereum.db.TransactionInfo; 30 import org.ethereum.facade.Ethereum; 31 import org.ethereum.listener.EthereumListenerAdapter; 32 import org.ethereum.rpc.AddressesTopicsFilter; 33 import org.ethereum.vm.LogInfo; 34 import org.slf4j.Logger; 35 import org.slf4j.LoggerFactory; 36  37 import java.io.IOException; 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Optional; 42 import java.util.concurrent.ConcurrentHashMap; 43  44 public class LogsNotificationEmitter { 45  private static final Logger logger = LoggerFactory.getLogger(LogsNotificationEmitter.class); 46  47  private final JsonRpcSerializer jsonRpcSerializer; 48  private final ReceiptStore receiptStore; 49  private final BlockchainBranchComparator branchComparator; 50  51  private final Map<SubscriptionId, Subscription> subscriptions = new ConcurrentHashMap<>(); 52  private Block lastEmitted; 53  54  public LogsNotificationEmitter( 55  Ethereum ethereum, 56  JsonRpcSerializer jsonRpcSerializer, 57  ReceiptStore receiptStore, 58  BlockchainBranchComparator branchComparator) { 59  this.jsonRpcSerializer = jsonRpcSerializer; 60  this.receiptStore = receiptStore; 61  this.branchComparator = branchComparator; 62  ethereum.addListener(new EthereumListenerAdapter() { 63  @Override 64  public void onBestBlock(Block block, List<TransactionReceipt> receipts) { 65  emitLogs(block); 66  } 67  }); 68  } 69  70  public void subscribe(SubscriptionId subscriptionId, Channel channel, EthSubscribeLogsParams params) { 71  subscriptions.put(subscriptionId, new Subscription(channel, params)); 72  } 73  74  public boolean unsubscribe(SubscriptionId subscriptionId) { 75  return subscriptions.remove(subscriptionId) != null; 76  } 77  78  public void unsubscribe(Channel channel) { 79  subscriptions.values().removeIf(s -> channel.equals(s.channel)); 80  } 81  82  private void emitLogs(Block block) { 83  if (subscriptions.isEmpty()) { 84  return; 85  } 86  87  if (lastEmitted == null) { 88  emitLogs(getLogsNotifications(block, false)); 89  } else { 90  BlockFork blockFork = branchComparator.calculateFork(lastEmitted, block); 91  for (Block oldBlock : blockFork.getOldBlocks()) { 92  emitLogs(getLogsNotifications(oldBlock, true)); 93  } 94  95  for (Block newBlock : blockFork.getNewBlocks()) { 96  emitLogs(getLogsNotifications(newBlock, false)); 97  } 98  } 99  100  lastEmitted = block; 101  } 102  103  private void emitLogs(List<LogsNotification> notifications) { 104  for (Map.Entry<SubscriptionId, Subscription> entry : subscriptions.entrySet()) { 105  SubscriptionId id = entry.getKey(); 106  Channel channel = entry.getValue().channel; 107  AddressesTopicsFilter filter = entry.getValue().filter; 108  109  for (LogsNotification notification : notifications) { 110  if (filter.matchesExactly(notification.getLogInfo())) { 111  EthSubscriptionNotification request = new EthSubscriptionNotification( 112  new EthSubscriptionParams(id, notification) 113  ); 114  115  try { 116  String msg = jsonRpcSerializer.serializeMessage(request); 117  channel.write(new TextWebSocketFrame(msg)); 118  } catch (IOException e) { 119  logger.error("Couldn't serialize block header result for notification", e); 120  } 121  } 122  } 123  124  channel.flush(); 125  } 126  } 127  128  private List<LogsNotification> getLogsNotifications(Block block, boolean removed) { 129  List<LogsNotification> notifications = new ArrayList<>(); 130  for (int transactionIndex = 0; transactionIndex < block.getTransactionsList().size(); transactionIndex++) { 131  Transaction transaction = block.getTransactionsList().get(transactionIndex); 132  Optional<TransactionInfo> transactionInfoOpt = receiptStore.get(transaction.getHash(), block.getHash()); 133  if (!transactionInfoOpt.isPresent()) { 134  logger.error("Missing receipt for transaction {} in block {}", transaction.getHash(), block.getHash()); 135  continue; 136  } 137  138  TransactionInfo transactionInfo = transactionInfoOpt.get(); 139  TransactionReceipt receipt = transactionInfo.getReceipt(); 140  List<LogInfo> logInfoList = receipt.getLogInfoList(); 141  for (int logIndex = 0; logIndex < logInfoList.size(); logIndex++) { 142  LogInfo logInfo = logInfoList.get(logIndex); 143  LogsNotification notification = new LogsNotification( 144  logInfo, block, transactionIndex, transaction, logIndex, removed); 145  notifications.add(notification); 146  } 147  } 148  149  return notifications; 150  } 151  152  private static class Subscription { 153  private final Channel channel; 154  private final AddressesTopicsFilter filter; 155  156  private Subscription(Channel channel, EthSubscribeLogsParams params) { 157  this.channel = channel; 158  this.filter = new AddressesTopicsFilter(params.getAddresses(), params.getTopics()); 159  } 160  } 161 }