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 }