Coverage Summary for Class: LogFilter (org.ethereum.rpc)
Class |
Method, %
|
Line, %
|
LogFilter |
0%
(0/10)
|
0%
(0/101)
|
LogFilter$LogFilterEvent |
0%
(0/2)
|
0%
(0/3)
|
Total |
0%
(0/12)
|
0%
(0/104)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2018 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
19 package org.ethereum.rpc;
20
21 import co.rsk.core.RskAddress;
22 import co.rsk.logfilter.BlocksBloom;
23 import co.rsk.logfilter.BlocksBloomStore;
24 import org.ethereum.core.*;
25 import org.ethereum.db.TransactionInfo;
26 import org.ethereum.vm.LogInfo;
27
28 import java.util.Collection;
29
30 import static org.ethereum.rpc.TypeConverter.stringHexToByteArray;
31
32 /**
33 * Created by ajlopez on 17/01/2018.
34 */
35 public class LogFilter extends Filter {
36 class LogFilterEvent extends FilterEvent {
37 private final LogFilterElement el;
38
39 LogFilterEvent(LogFilterElement el) {
40 this.el = el;
41 }
42
43 @Override
44 public LogFilterElement getJsonEventObject() {
45 return el;
46 }
47 }
48
49 private AddressesTopicsFilter addressesTopicsFilter;
50 private boolean fromLatestBlock;
51 private boolean toLatestBlock;
52 private final Blockchain blockchain;
53
54 public LogFilter(AddressesTopicsFilter addressesTopicsFilter, Blockchain blockchain, boolean fromLatestBlock, boolean toLatestBlock) {
55 this.addressesTopicsFilter = addressesTopicsFilter;
56 this.blockchain = blockchain;
57 this.fromLatestBlock = fromLatestBlock;
58 this.toLatestBlock = toLatestBlock;
59 }
60
61 void onLogMatch(LogInfo logInfo, Block b, int txIndex, Transaction tx, int logIdx) {
62 add(new LogFilterEvent(new LogFilterElement(logInfo, b, txIndex, tx, logIdx)));
63 }
64
65 void onTransaction(Transaction tx, Block b, int txIndex) {
66 TransactionInfo txInfo = blockchain.getTransactionInfo(tx.getHash().getBytes());
67 TransactionReceipt receipt = txInfo.getReceipt();
68
69 LogFilterElement[] logs = new LogFilterElement[receipt.getLogInfoList().size()];
70
71 for (int i = 0; i < logs.length; i++) {
72 LogInfo logInfo = receipt.getLogInfoList().get(i);
73
74 if (addressesTopicsFilter.matchesExactly(logInfo)) {
75 onLogMatch(logInfo, b, txIndex, receipt.getTransaction(), i);
76 }
77 }
78 }
79
80 void onBlock(Block b) {
81 if (addressesTopicsFilter.matchBloom(new Bloom(b.getLogBloom()))) {
82 int txIdx = 0;
83
84 for (Transaction tx : b.getTransactionsList()) {
85 onTransaction(tx, b, txIdx);
86 txIdx++;
87 }
88 }
89 }
90
91 @Override
92 public void newBlockReceived(Block b) {
93 if (this.fromLatestBlock) {
94 this.clearEvents();
95 onBlock(b);
96 }
97 else if (this.toLatestBlock) {
98 onBlock(b);
99 }
100 }
101
102 @Override
103 public void newPendingTx(Transaction tx) {
104 //empty method
105 }
106
107 public static LogFilter fromFilterRequest(Web3.FilterRequest fr, Blockchain blockchain, BlocksBloomStore blocksBloomStore) throws Exception {
108 RskAddress[] addresses;
109
110 // Now, there is an array of array of topics
111 // first level are topic filters by position
112 // second level contains OR topic filters for that position
113 // null value matches anything
114 Topic[][] topics;
115
116 if (fr.address instanceof String) {
117 addresses = new RskAddress[] { new RskAddress(stringHexToByteArray((String) fr.address)) };
118 } else if (fr.address instanceof Collection<?>) {
119 Collection<?> iterable = (Collection<?>)fr.address;
120
121 addresses = iterable.stream()
122 .filter(String.class::isInstance)
123 .map(String.class::cast)
124 .map(TypeConverter::stringHexToByteArray)
125 .map(RskAddress::new)
126 .toArray(RskAddress[]::new);
127 }
128 else {
129 addresses = new RskAddress[0];
130 }
131
132 if (fr.topics != null) {
133 topics = new Topic[fr.topics.length][];
134
135 for (int nt = 0; nt < fr.topics.length; nt++) {
136 Object topic = fr.topics[nt];
137
138 if (topic == null) {
139 topics[nt] = new Topic[0];
140 } else if (topic instanceof String) {
141 topics[nt] = new Topic[] { new Topic((String) topic) };
142 } else if (topic instanceof Collection<?>) {
143 // TODO list of topics as topic with OR logic
144
145 Collection<?> iterable = (Collection<?>)topic;
146
147 topics[nt] = iterable.stream()
148 .filter(String.class::isInstance)
149 .map(String.class::cast)
150 .map(TypeConverter::stringHexToByteArray)
151 .map(Topic::new)
152 .toArray(Topic[]::new);
153 }
154 }
155 }
156 else {
157 topics = null;
158 }
159
160 AddressesTopicsFilter addressesTopicsFilter = new AddressesTopicsFilter(addresses, topics);
161
162 // TODO review pending transaction processing
163 // when fromBlock and/or toBlock are "pending"
164
165 // Default from block value
166 if (fr.fromBlock == null) {
167 fr.fromBlock = "latest";
168 }
169
170 // Default to block value
171 if (fr.toBlock == null) {
172 fr.toBlock = "latest";
173 }
174
175 boolean fromLatestBlock = "latest".equalsIgnoreCase(fr.fromBlock);
176 boolean toLatestBlock = "latest".equalsIgnoreCase(fr.toBlock);
177
178 LogFilter filter = new LogFilter(addressesTopicsFilter, blockchain, fromLatestBlock, toLatestBlock);
179
180 retrieveHistoricalData(fr, blockchain, filter, blocksBloomStore);
181
182 return filter;
183 }
184
185 private static void retrieveHistoricalData(Web3.FilterRequest fr, Blockchain blockchain, LogFilter filter, BlocksBloomStore blocksBloomStore) throws Exception {
186 Block blockFrom = isBlockWord(fr.fromBlock) ? null : Web3Impl.getBlockByNumberOrStr(fr.fromBlock, blockchain);
187 Block blockTo = isBlockWord(fr.toBlock) ? null : Web3Impl.getBlockByNumberOrStr(fr.toBlock, blockchain);
188
189 if (blockFrom == null && "earliest".equalsIgnoreCase(fr.fromBlock)) {
190 blockFrom = blockchain.getBlockByNumber(0);
191 }
192
193 if (blockFrom != null) {
194 // need to add historical data
195 blockTo = blockTo == null ? blockchain.getBestBlock() : blockTo;
196
197 processBlocks(blockFrom.getNumber(), blockTo.getNumber(), filter, blockchain, blocksBloomStore);
198 }
199 else if ("latest".equalsIgnoreCase(fr.fromBlock)) {
200 filter.onBlock(blockchain.getBestBlock());
201 }
202 }
203
204 private static void processBlocks(long fromBlockNumber, long toBlockNumber, LogFilter filter, Blockchain blockchain, BlocksBloomStore blocksBloomStore) {
205 BlocksBloom auxiliaryBlocksBloom = null;
206 long bestBlockNumber = blockchain.getBestBlock().getNumber();
207
208 for (long blockNum = fromBlockNumber; blockNum <= toBlockNumber; blockNum++) {
209 boolean isConfirmedBlock = blockNum <= bestBlockNumber - blocksBloomStore.getNoConfirmations();
210
211 if (isConfirmedBlock) {
212 if (blocksBloomStore.firstNumberInRange(blockNum) == blockNum) {
213 if (blocksBloomStore.hasBlockNumber(blockNum)) {
214 BlocksBloom blocksBloom = blocksBloomStore.getBlocksBloomByNumber(blockNum);
215
216 if (!filter.addressesTopicsFilter.matchBloom(blocksBloom.getBloom())) {
217 blockNum = blocksBloomStore.lastNumberInRange(blockNum);
218 continue;
219 }
220 }
221
222 auxiliaryBlocksBloom = new BlocksBloom();
223 }
224
225 Block block = blockchain.getBlockByNumber(blockNum);
226
227 if (auxiliaryBlocksBloom != null) {
228 auxiliaryBlocksBloom.addBlockBloom(blockNum, new Bloom(block.getLogBloom()));
229 }
230
231 if (auxiliaryBlocksBloom != null && blocksBloomStore.lastNumberInRange(blockNum) == blockNum) {
232 blocksBloomStore.addBlocksBloom(auxiliaryBlocksBloom);
233 }
234
235 filter.onBlock(block);
236 }
237 else {
238 filter.onBlock(blockchain.getBlockByNumber(blockNum));
239 }
240 }
241 }
242
243 private static boolean isBlockWord(String id) {
244 return "latest".equalsIgnoreCase(id) || "pending".equalsIgnoreCase(id) || "earliest".equalsIgnoreCase(id);
245 }
246 }