|
|
|
@ -14,249 +14,365 @@ |
|
|
|
|
*/ |
|
|
|
|
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.logs; |
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
|
import static org.mockito.ArgumentMatchers.any; |
|
|
|
|
import static org.mockito.ArgumentMatchers.eq; |
|
|
|
|
import static org.mockito.ArgumentMatchers.refEq; |
|
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
|
import static org.mockito.Mockito.times; |
|
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
|
import static org.mockito.Mockito.when; |
|
|
|
|
|
|
|
|
|
import org.hyperledger.besu.crypto.SECP256K1.KeyPair; |
|
|
|
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; |
|
|
|
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.LogResult; |
|
|
|
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; |
|
|
|
|
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager; |
|
|
|
|
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; |
|
|
|
|
import org.hyperledger.besu.ethereum.api.query.LogWithMetadata; |
|
|
|
|
import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata; |
|
|
|
|
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; |
|
|
|
|
import org.hyperledger.besu.ethereum.chain.Blockchain; |
|
|
|
|
import org.hyperledger.besu.ethereum.api.query.TopicsParameter; |
|
|
|
|
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.Address; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.Block; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.BlockDataGenerator; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.BlockHeader; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.Hash; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.BlockWithReceipts; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.InMemoryStorageProvider; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.Log; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.LogTopic; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.Transaction; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.TransactionReceipt; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.TransactionTestFixture; |
|
|
|
|
import org.hyperledger.besu.util.bytes.BytesValue; |
|
|
|
|
|
|
|
|
|
import java.util.ArrayList; |
|
|
|
|
import java.util.Arrays; |
|
|
|
|
import java.util.Collections; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.Optional; |
|
|
|
|
import java.util.concurrent.atomic.AtomicLong; |
|
|
|
|
import java.util.function.Supplier; |
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
import java.util.stream.Stream; |
|
|
|
|
|
|
|
|
|
import com.google.common.collect.Lists; |
|
|
|
|
import org.junit.Before; |
|
|
|
|
import org.junit.Test; |
|
|
|
|
import org.junit.runner.RunWith; |
|
|
|
|
import org.mockito.ArgumentMatchers; |
|
|
|
|
import org.mockito.ArgumentCaptor; |
|
|
|
|
import org.mockito.Mock; |
|
|
|
|
import org.mockito.junit.MockitoJUnitRunner; |
|
|
|
|
|
|
|
|
|
@RunWith(MockitoJUnitRunner.class) |
|
|
|
|
public class LogsSubscriptionServiceTest { |
|
|
|
|
|
|
|
|
|
private final KeyPair keyPair = KeyPair.generate(); |
|
|
|
|
private final BlockHeaderTestFixture blockHeaderTestFixture = new BlockHeaderTestFixture(); |
|
|
|
|
private final TransactionTestFixture txTestFixture = new TransactionTestFixture(); |
|
|
|
|
private final BlockDataGenerator gen = new BlockDataGenerator(1); |
|
|
|
|
private final MutableBlockchain blockchain = |
|
|
|
|
InMemoryStorageProvider.createInMemoryBlockchain(gen.genesisBlock()); |
|
|
|
|
|
|
|
|
|
private LogsSubscriptionService logsSubscriptionService; |
|
|
|
|
private final AtomicLong nextSubscriptionId = new AtomicLong(); |
|
|
|
|
|
|
|
|
|
@Mock private SubscriptionManager subscriptionManager; |
|
|
|
|
@Mock private BlockchainQueries blockchainQueries; |
|
|
|
|
@Mock private Blockchain blockchain; |
|
|
|
|
|
|
|
|
|
@Before |
|
|
|
|
public void before() { |
|
|
|
|
logsSubscriptionService = new LogsSubscriptionService(subscriptionManager, blockchainQueries); |
|
|
|
|
logsSubscriptionService = new LogsSubscriptionService(subscriptionManager); |
|
|
|
|
blockchain.observeBlockAdded(logsSubscriptionService); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void shouldSendLogMessageWhenBlockAddedEventHasAddedTransactionsMatchingSubscription() { |
|
|
|
|
final Address address = Address.fromHexString("0x0"); |
|
|
|
|
final LogsSubscription subscription = createSubscription(address); |
|
|
|
|
final Transaction transaction = createTransaction(); |
|
|
|
|
final Log log = createLog(address); |
|
|
|
|
final LogResult expectedLogResult = createLogResult(transaction, log, false); |
|
|
|
|
public void singleMatchingLogEvent() { |
|
|
|
|
final BlockWithReceipts blockWithReceipts = generateBlock(2, 2, 2); |
|
|
|
|
final Block block = blockWithReceipts.getBlock(); |
|
|
|
|
final List<TransactionReceipt> receipts = blockWithReceipts.getReceipts(); |
|
|
|
|
|
|
|
|
|
logsSubscriptionService.onBlockAdded(createBlockAddedEvent(transaction, null), blockchain); |
|
|
|
|
final int txIndex = 1; |
|
|
|
|
final int logIndex = 1; |
|
|
|
|
final Log targetLog = receipts.get(txIndex).getLogs().get(logIndex); |
|
|
|
|
|
|
|
|
|
verify(subscriptionManager) |
|
|
|
|
.sendMessage( |
|
|
|
|
ArgumentMatchers.eq(subscription.getSubscriptionId()), refEq(expectedLogResult)); |
|
|
|
|
} |
|
|
|
|
final LogsSubscription subscription = createSubscription(targetLog.getLogger()); |
|
|
|
|
registerSubscriptions(subscription); |
|
|
|
|
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts()); |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void shouldSendLogMessageWhenBlockAddedEventHasRemovedTransactionsMatchingSubscription() { |
|
|
|
|
final Address address = Address.fromHexString("0x0"); |
|
|
|
|
final LogsSubscription subscription = createSubscription(address); |
|
|
|
|
final Transaction transaction = createTransaction(); |
|
|
|
|
final Log log = createLog(address); |
|
|
|
|
final LogResult expectedLogResult = createLogResult(transaction, log, true); |
|
|
|
|
final ArgumentCaptor<LogResult> captor = ArgumentCaptor.forClass(LogResult.class); |
|
|
|
|
verify(subscriptionManager).sendMessage(eq(subscription.getSubscriptionId()), captor.capture()); |
|
|
|
|
|
|
|
|
|
logsSubscriptionService.onBlockAdded(createBlockAddedEvent(null, transaction), blockchain); |
|
|
|
|
final List<LogResult> logResults = captor.getAllValues(); |
|
|
|
|
|
|
|
|
|
verify(subscriptionManager) |
|
|
|
|
.sendMessage( |
|
|
|
|
ArgumentMatchers.eq(subscription.getSubscriptionId()), refEq(expectedLogResult)); |
|
|
|
|
assertThat(logResults).hasSize(1); |
|
|
|
|
final LogResult result = logResults.get(0); |
|
|
|
|
assertLogResultMatches(result, block, receipts, txIndex, logIndex, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void shouldSendMessageForAllLogsMatchingSubscription() { |
|
|
|
|
final Address address = Address.fromHexString("0x0"); |
|
|
|
|
final Log log = createLog(address); |
|
|
|
|
final LogsSubscription subscription = createSubscription(address); |
|
|
|
|
final List<Transaction> addedTransactions = createTransactionsWithLog(log); |
|
|
|
|
final List<Transaction> removedTransactions = createTransactionsWithLog(log); |
|
|
|
|
public void singleMatchingLogEmittedThenRemovedInReorg() { |
|
|
|
|
// Create block that emits an event
|
|
|
|
|
final BlockWithReceipts blockWithReceipts = generateBlock(2, 2, 2); |
|
|
|
|
final Block block = blockWithReceipts.getBlock(); |
|
|
|
|
final List<TransactionReceipt> receipts = blockWithReceipts.getReceipts(); |
|
|
|
|
|
|
|
|
|
final int txIndex = 1; |
|
|
|
|
final int logIndex = 1; |
|
|
|
|
final Log targetLog = receipts.get(txIndex).getLogs().get(logIndex); |
|
|
|
|
|
|
|
|
|
final LogsSubscription subscription = createSubscription(targetLog.getLogger()); |
|
|
|
|
registerSubscriptions(subscription); |
|
|
|
|
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts()); |
|
|
|
|
|
|
|
|
|
// Cause a reorg that removes the block which emitted an event
|
|
|
|
|
BlockHeader parentHeader = blockchain.getGenesisBlock().getHeader(); |
|
|
|
|
while (!blockchain.getChainHeadHash().equals(parentHeader.getHash())) { |
|
|
|
|
final BlockWithReceipts newBlock = generateBlock(parentHeader, 2, 0, 0); |
|
|
|
|
parentHeader = newBlock.getBlock().getHeader(); |
|
|
|
|
blockchain.appendBlock(newBlock.getBlock(), newBlock.getReceipts()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logsSubscriptionService.onBlockAdded( |
|
|
|
|
createBlockAddedEvent(addedTransactions, removedTransactions), blockchain); |
|
|
|
|
final ArgumentCaptor<LogResult> captor = ArgumentCaptor.forClass(LogResult.class); |
|
|
|
|
verify(subscriptionManager, times(2)) |
|
|
|
|
.sendMessage(eq(subscription.getSubscriptionId()), captor.capture()); |
|
|
|
|
|
|
|
|
|
final int totalOfLogs = addedTransactions.size() + removedTransactions.size(); |
|
|
|
|
final List<LogResult> logResults = captor.getAllValues(); |
|
|
|
|
|
|
|
|
|
verify(subscriptionManager, times(totalOfLogs)) |
|
|
|
|
.sendMessage(ArgumentMatchers.eq(subscription.getSubscriptionId()), any()); |
|
|
|
|
assertThat(logResults).hasSize(2); |
|
|
|
|
final LogResult firstLog = logResults.get(0); |
|
|
|
|
assertLogResultMatches(firstLog, block, receipts, txIndex, logIndex, false); |
|
|
|
|
final LogResult secondLog = logResults.get(1); |
|
|
|
|
assertLogResultMatches(secondLog, block, receipts, txIndex, logIndex, true); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void shouldSendLogMessageToAllMatchingSubscriptions() { |
|
|
|
|
final Address address = Address.fromHexString("0x0"); |
|
|
|
|
final List<LogsSubscription> subscriptions = createSubscriptions(address); |
|
|
|
|
final Transaction transaction = createTransaction(); |
|
|
|
|
final Log log = createLog(address); |
|
|
|
|
final LogResult expectedLogResult = createLogResult(transaction, log, false); |
|
|
|
|
|
|
|
|
|
logsSubscriptionService.onBlockAdded(createBlockAddedEvent(transaction, null), blockchain); |
|
|
|
|
public void singleMatchingLogEmittedThenMovedInReorg() { |
|
|
|
|
// Create block that emits an event
|
|
|
|
|
final BlockWithReceipts blockWithReceipts = generateBlock(2, 2, 2); |
|
|
|
|
final Block block = blockWithReceipts.getBlock(); |
|
|
|
|
final List<TransactionReceipt> receipts = blockWithReceipts.getReceipts(); |
|
|
|
|
|
|
|
|
|
final int txIndex = 1; |
|
|
|
|
final int logIndex = 1; |
|
|
|
|
final Log targetLog = receipts.get(txIndex).getLogs().get(logIndex); |
|
|
|
|
|
|
|
|
|
final LogsSubscription subscription = createSubscription(targetLog.getLogger()); |
|
|
|
|
registerSubscriptions(subscription); |
|
|
|
|
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts()); |
|
|
|
|
|
|
|
|
|
// Cause a reorg that removes the block which emitted an event
|
|
|
|
|
BlockHeader parentHeader = blockchain.getGenesisBlock().getHeader(); |
|
|
|
|
while (!blockchain.getChainHeadHash().equals(parentHeader.getHash())) { |
|
|
|
|
final BlockWithReceipts newBlock = generateBlock(parentHeader, 2, 0, 0); |
|
|
|
|
parentHeader = newBlock.getBlock().getHeader(); |
|
|
|
|
blockchain.appendBlock(newBlock.getBlock(), newBlock.getReceipts()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
verify(subscriptionManager, times(subscriptions.size())) |
|
|
|
|
.sendMessage(any(), refEq(expectedLogResult)); |
|
|
|
|
// Now add another block that re-emits the target log
|
|
|
|
|
final BlockWithReceipts newBlockWithLog = |
|
|
|
|
generateBlock(1, () -> Collections.singletonList(targetLog)); |
|
|
|
|
blockchain.appendBlock(newBlockWithLog.getBlock(), newBlockWithLog.getReceipts()); |
|
|
|
|
// Sanity check
|
|
|
|
|
assertThat(blockchain.getChainHeadHash()).isEqualTo(newBlockWithLog.getBlock().getHash()); |
|
|
|
|
|
|
|
|
|
final ArgumentCaptor<LogResult> captor = ArgumentCaptor.forClass(LogResult.class); |
|
|
|
|
verify(subscriptionManager, times(3)) |
|
|
|
|
.sendMessage(eq(subscription.getSubscriptionId()), captor.capture()); |
|
|
|
|
|
|
|
|
|
final List<LogResult> logResults = captor.getAllValues(); |
|
|
|
|
|
|
|
|
|
assertThat(logResults).hasSize(3); |
|
|
|
|
final LogResult originalLog = logResults.get(0); |
|
|
|
|
assertLogResultMatches(originalLog, block, receipts, txIndex, logIndex, false); |
|
|
|
|
final LogResult removedLog = logResults.get(1); |
|
|
|
|
assertLogResultMatches(removedLog, block, receipts, txIndex, logIndex, true); |
|
|
|
|
final LogResult updatedLog = logResults.get(2); |
|
|
|
|
assertLogResultMatches( |
|
|
|
|
updatedLog, newBlockWithLog.getBlock(), newBlockWithLog.getReceipts(), 0, 0, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void shouldNotSendLogMessageWhenBlockAddedEventHasNoTransactions() { |
|
|
|
|
final Address address = Address.fromHexString("0x0"); |
|
|
|
|
createSubscription(address); |
|
|
|
|
public void multipleMatchingLogsEmitted() { |
|
|
|
|
final Log targetLog = gen.log(); |
|
|
|
|
final Log otherLog = gen.log(); |
|
|
|
|
final List<Log> logs = Arrays.asList(targetLog, otherLog); |
|
|
|
|
|
|
|
|
|
final LogsSubscription subscription = createSubscription(targetLog.getLogger()); |
|
|
|
|
registerSubscriptions(subscription); |
|
|
|
|
|
|
|
|
|
// Generate blocks with multiple logs matching subscription
|
|
|
|
|
final int txCount = 2; |
|
|
|
|
final List<BlockWithReceipts> targetBlocks = new ArrayList<>(); |
|
|
|
|
for (int i = 0; i < 2; i++) { |
|
|
|
|
final BlockWithReceipts blockWithReceipts = generateBlock(txCount, () -> logs); |
|
|
|
|
targetBlocks.add(blockWithReceipts); |
|
|
|
|
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts()); |
|
|
|
|
|
|
|
|
|
// Add another block with unrelated logs
|
|
|
|
|
final BlockWithReceipts otherBlock = generateBlock(txCount, 2, 2); |
|
|
|
|
blockchain.appendBlock(otherBlock.getBlock(), otherBlock.getReceipts()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logsSubscriptionService.onBlockAdded( |
|
|
|
|
createBlockAddedEvent(Collections.emptyList(), Collections.emptyList()), blockchain); |
|
|
|
|
final ArgumentCaptor<LogResult> captor = ArgumentCaptor.forClass(LogResult.class); |
|
|
|
|
verify(subscriptionManager, times(targetBlocks.size() * txCount)) |
|
|
|
|
.sendMessage(eq(subscription.getSubscriptionId()), captor.capture()); |
|
|
|
|
final List<LogResult> logResults = captor.getAllValues(); |
|
|
|
|
|
|
|
|
|
// Verify all logs are emitted
|
|
|
|
|
assertThat(logResults).hasSize(targetBlocks.size() * txCount); |
|
|
|
|
for (int i = 0; i < targetBlocks.size(); i++) { |
|
|
|
|
final BlockWithReceipts targetBlock = targetBlocks.get(i); |
|
|
|
|
for (int j = 0; j < txCount; j++) { |
|
|
|
|
final int resultIndex = i * txCount + j; |
|
|
|
|
assertLogResultMatches( |
|
|
|
|
logResults.get(resultIndex), |
|
|
|
|
targetBlock.getBlock(), |
|
|
|
|
targetBlock.getReceipts(), |
|
|
|
|
j, |
|
|
|
|
0, |
|
|
|
|
false); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
verify(subscriptionManager).subscriptionsOfType(any(), any()); |
|
|
|
|
verify(subscriptionManager, times(0)).sendMessage(any(), any()); |
|
|
|
|
@Test |
|
|
|
|
public void multipleSubscriptionsForSingleMatchingLog() { |
|
|
|
|
final BlockWithReceipts blockWithReceipts = generateBlock(2, 2, 2); |
|
|
|
|
final Block block = blockWithReceipts.getBlock(); |
|
|
|
|
final List<TransactionReceipt> receipts = blockWithReceipts.getReceipts(); |
|
|
|
|
|
|
|
|
|
final int txIndex = 1; |
|
|
|
|
final int logIndex = 1; |
|
|
|
|
final Log targetLog = receipts.get(txIndex).getLogs().get(logIndex); |
|
|
|
|
|
|
|
|
|
final List<LogsSubscription> subscriptions = |
|
|
|
|
Stream.generate(() -> createSubscription(targetLog.getLogger())) |
|
|
|
|
.limit(3) |
|
|
|
|
.collect(Collectors.toList()); |
|
|
|
|
registerSubscriptions(subscriptions); |
|
|
|
|
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts()); |
|
|
|
|
|
|
|
|
|
for (LogsSubscription subscription : subscriptions) { |
|
|
|
|
final ArgumentCaptor<LogResult> captor = ArgumentCaptor.forClass(LogResult.class); |
|
|
|
|
verify(subscriptionManager) |
|
|
|
|
.sendMessage(eq(subscription.getSubscriptionId()), captor.capture()); |
|
|
|
|
|
|
|
|
|
final List<LogResult> logResults = captor.getAllValues(); |
|
|
|
|
|
|
|
|
|
assertThat(logResults).hasSize(1); |
|
|
|
|
final LogResult result = logResults.get(0); |
|
|
|
|
assertLogResultMatches(result, block, receipts, txIndex, logIndex, false); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void shouldNotSendLogMessageWhenLogsDoNotMatchAnySubscription() { |
|
|
|
|
createSubscription(Address.fromHexString("0x0")); |
|
|
|
|
final Transaction transaction = createTransaction(); |
|
|
|
|
final Log log = createLog(Address.fromHexString("0x1")); |
|
|
|
|
createLogResult(transaction, log, false); |
|
|
|
|
public void noLogsEmitted() { |
|
|
|
|
final Address address = Address.fromHexString("0x0"); |
|
|
|
|
final LogsSubscription subscription = createSubscription(address); |
|
|
|
|
registerSubscriptions(subscription); |
|
|
|
|
|
|
|
|
|
logsSubscriptionService.onBlockAdded(createBlockAddedEvent(transaction, null), blockchain); |
|
|
|
|
final BlockWithReceipts blockWithReceipts = generateBlock(2, 0, 0); |
|
|
|
|
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts()); |
|
|
|
|
|
|
|
|
|
verify(subscriptionManager).subscriptionsOfType(any(), any()); |
|
|
|
|
verify(subscriptionManager, times(0)).sendMessage(any(), any()); |
|
|
|
|
final ArgumentCaptor<LogResult> captor = ArgumentCaptor.forClass(LogResult.class); |
|
|
|
|
verify(subscriptionManager, times(0)) |
|
|
|
|
.sendMessage(eq(subscription.getSubscriptionId()), captor.capture()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Transaction createTransaction() { |
|
|
|
|
return txTestFixture.createTransaction(keyPair); |
|
|
|
|
@Test |
|
|
|
|
public void noMatchingLogsEmitted() { |
|
|
|
|
final Address address = Address.fromHexString("0x0"); |
|
|
|
|
final LogsSubscription subscription = createSubscription(address); |
|
|
|
|
registerSubscriptions(subscription); |
|
|
|
|
|
|
|
|
|
final BlockWithReceipts blockWithReceipts = generateBlock(2, 2, 2); |
|
|
|
|
blockchain.appendBlock(blockWithReceipts.getBlock(), blockWithReceipts.getReceipts()); |
|
|
|
|
|
|
|
|
|
final ArgumentCaptor<LogResult> captor = ArgumentCaptor.forClass(LogResult.class); |
|
|
|
|
verify(subscriptionManager, times(0)) |
|
|
|
|
.sendMessage(eq(subscription.getSubscriptionId()), captor.capture()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Log createLog(final Address address) { |
|
|
|
|
return new Log(address, BytesValue.EMPTY, Collections.emptyList()); |
|
|
|
|
private void assertLogResultMatches( |
|
|
|
|
final LogResult result, |
|
|
|
|
final Block block, |
|
|
|
|
final List<TransactionReceipt> receipts, |
|
|
|
|
final int txIndex, |
|
|
|
|
final int logIndex, |
|
|
|
|
final boolean isRemoved) { |
|
|
|
|
final Transaction expectedTransaction = block.getBody().getTransactions().get(txIndex); |
|
|
|
|
final Log expectedLog = receipts.get(txIndex).getLogs().get(logIndex); |
|
|
|
|
|
|
|
|
|
assertThat(result.getLogIndex()).isEqualTo(Quantity.create(logIndex)); |
|
|
|
|
assertThat(result.getTransactionIndex()).isEqualTo(Quantity.create(txIndex)); |
|
|
|
|
assertThat(result.getBlockNumber()).isEqualTo(Quantity.create(block.getHeader().getNumber())); |
|
|
|
|
assertThat(result.getBlockHash()).isEqualTo(block.getHash().toString()); |
|
|
|
|
assertThat(result.getTransactionHash()).isEqualTo(expectedTransaction.getHash().toString()); |
|
|
|
|
assertThat(result.getAddress()).isEqualTo(expectedLog.getLogger().toString()); |
|
|
|
|
assertThat(result.getData()).isEqualTo(expectedLog.getData().toString()); |
|
|
|
|
assertThat(result.getTopics()) |
|
|
|
|
.isEqualTo( |
|
|
|
|
expectedLog.getTopics().stream().map(LogTopic::toString).collect(Collectors.toList())); |
|
|
|
|
assertThat(result.isRemoved()).isEqualTo(isRemoved); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private LogsSubscription createSubscription(final Address address) { |
|
|
|
|
final FilterParameter filterParameter = |
|
|
|
|
new FilterParameter(null, null, Lists.newArrayList(address.toString()), null, null); |
|
|
|
|
final LogsSubscription logsSubscription = new LogsSubscription(1L, "conn", filterParameter); |
|
|
|
|
when(subscriptionManager.subscriptionsOfType(any(), any())) |
|
|
|
|
.thenReturn(Lists.newArrayList(logsSubscription)); |
|
|
|
|
return logsSubscription; |
|
|
|
|
private BlockWithReceipts generateBlock( |
|
|
|
|
final int txCount, final int logsPerTx, final int topicsPerLog) { |
|
|
|
|
final BlockHeader parent = blockchain.getChainHeadHeader(); |
|
|
|
|
return generateBlock(parent, txCount, () -> gen.logs(logsPerTx, topicsPerLog)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private List<LogsSubscription> createSubscriptions(final Address address) { |
|
|
|
|
final List<LogsSubscription> subscriptions = new ArrayList<>(); |
|
|
|
|
for (long i = 0; i < 3; i++) { |
|
|
|
|
final FilterParameter filterParameter = |
|
|
|
|
new FilterParameter(null, null, Lists.newArrayList(address.toString()), null, null); |
|
|
|
|
subscriptions.add(new LogsSubscription(i, "conn", filterParameter)); |
|
|
|
|
} |
|
|
|
|
when(subscriptionManager.subscriptionsOfType(any(), any())) |
|
|
|
|
.thenReturn(Lists.newArrayList(subscriptions)); |
|
|
|
|
return subscriptions; |
|
|
|
|
private BlockWithReceipts generateBlock( |
|
|
|
|
final BlockHeader parentHeader, |
|
|
|
|
final int txCount, |
|
|
|
|
final int logsPerTx, |
|
|
|
|
final int topicsPerLog) { |
|
|
|
|
return generateBlock(parentHeader, txCount, () -> gen.logs(logsPerTx, topicsPerLog)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private LogResult createLogResult( |
|
|
|
|
final Transaction transaction, final Log log, final boolean removed) { |
|
|
|
|
final TransactionReceiptWithMetadata txReceiptWithMetadata = |
|
|
|
|
createTransactionWithLog(transaction, log); |
|
|
|
|
final LogWithMetadata logWithMetadata = createLogWithMetadata(txReceiptWithMetadata, removed); |
|
|
|
|
return new LogResult(logWithMetadata); |
|
|
|
|
private BlockWithReceipts generateBlock( |
|
|
|
|
final int txCount, final Supplier<List<Log>> logsSupplier) { |
|
|
|
|
final BlockHeader parent = blockchain.getChainHeadHeader(); |
|
|
|
|
return generateBlock(parent, txCount, logsSupplier); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private TransactionReceiptWithMetadata createTransactionWithLog( |
|
|
|
|
final Transaction transaction, final Log log) { |
|
|
|
|
final BlockHeader blockHeader = blockHeaderTestFixture.buildHeader(); |
|
|
|
|
final TransactionReceipt transactionReceipt = |
|
|
|
|
new TransactionReceipt(Hash.ZERO, 1L, Lists.newArrayList(log), Optional.empty()); |
|
|
|
|
final TransactionReceiptWithMetadata transactionReceiptWithMetadata = |
|
|
|
|
TransactionReceiptWithMetadata.create( |
|
|
|
|
transactionReceipt, |
|
|
|
|
transaction, |
|
|
|
|
transaction.getHash(), |
|
|
|
|
0, |
|
|
|
|
1L, |
|
|
|
|
blockHeader.getHash(), |
|
|
|
|
blockHeader.getNumber()); |
|
|
|
|
private BlockWithReceipts generateBlock( |
|
|
|
|
final BlockHeader parentHeader, final int txCount, final Supplier<List<Log>> logsSupplier) { |
|
|
|
|
final List<TransactionReceipt> receipts = new ArrayList<>(); |
|
|
|
|
final List<Log> logs = new ArrayList<>(); |
|
|
|
|
final BlockOptions blockOptions = BlockOptions.create(); |
|
|
|
|
for (int i = 0; i < txCount; i++) { |
|
|
|
|
final Transaction tx = gen.transaction(); |
|
|
|
|
final TransactionReceipt receipt = gen.receipt(logsSupplier.get()); |
|
|
|
|
|
|
|
|
|
receipts.add(receipt); |
|
|
|
|
receipt.getLogs().forEach(logs::add); |
|
|
|
|
blockOptions.addTransaction(tx); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
when(blockchainQueries.transactionReceiptByTransactionHash(eq(transaction.getHash()))) |
|
|
|
|
.thenReturn(Optional.of(transactionReceiptWithMetadata)); |
|
|
|
|
blockOptions.setParentHash(parentHeader.getHash()); |
|
|
|
|
blockOptions.setBlockNumber(parentHeader.getNumber() + 1L); |
|
|
|
|
final Block block = gen.block(blockOptions); |
|
|
|
|
|
|
|
|
|
return transactionReceiptWithMetadata; |
|
|
|
|
return new BlockWithReceipts(block, receipts); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private BlockAddedEvent createBlockAddedEvent( |
|
|
|
|
final Transaction addedTransaction, final Transaction removedTransaction) { |
|
|
|
|
final Block block = mock(Block.class); |
|
|
|
|
return BlockAddedEvent.createForChainReorg( |
|
|
|
|
block, |
|
|
|
|
addedTransaction != null ? Lists.newArrayList(addedTransaction) : Collections.emptyList(), |
|
|
|
|
removedTransaction != null |
|
|
|
|
? Lists.newArrayList(removedTransaction) |
|
|
|
|
: Collections.emptyList()); |
|
|
|
|
private LogsSubscription createSubscription(final Address address) { |
|
|
|
|
return createSubscription(Arrays.asList(address), Collections.emptyList()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private BlockAddedEvent createBlockAddedEvent( |
|
|
|
|
final List<Transaction> addedTransactions, final List<Transaction> removedTransactions) { |
|
|
|
|
final Block block = mock(Block.class); |
|
|
|
|
return BlockAddedEvent.createForChainReorg( |
|
|
|
|
block, |
|
|
|
|
addedTransactions != null ? Lists.newArrayList(addedTransactions) : Collections.emptyList(), |
|
|
|
|
removedTransactions != null |
|
|
|
|
? Lists.newArrayList(removedTransactions) |
|
|
|
|
: Collections.emptyList()); |
|
|
|
|
private LogsSubscription createSubscription( |
|
|
|
|
final List<Address> addresses, final List<List<LogTopic>> logTopics) { |
|
|
|
|
// TODO: FilterParameter constructor should work with proper types instead of Strings
|
|
|
|
|
final List<String> addressStrings = |
|
|
|
|
addresses.stream().map(Address::toString).collect(Collectors.toList()); |
|
|
|
|
final List<List<String>> topicStrings = |
|
|
|
|
logTopics.stream() |
|
|
|
|
.map(topics -> topics.stream().map(LogTopic::toString).collect(Collectors.toList())) |
|
|
|
|
.collect(Collectors.toList()); |
|
|
|
|
|
|
|
|
|
final FilterParameter filterParameter = |
|
|
|
|
new FilterParameter(null, null, addressStrings, new TopicsParameter(topicStrings), null); |
|
|
|
|
final LogsSubscription logsSubscription = |
|
|
|
|
new LogsSubscription(nextSubscriptionId.incrementAndGet(), "conn", filterParameter); |
|
|
|
|
return logsSubscription; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private List<Transaction> createTransactionsWithLog(final Log log) { |
|
|
|
|
final ArrayList<Transaction> transactions = |
|
|
|
|
Lists.newArrayList(createTransaction(), createTransaction(), createTransaction()); |
|
|
|
|
transactions.forEach(tx -> createTransactionWithLog(tx, log)); |
|
|
|
|
return transactions; |
|
|
|
|
private void registerSubscriptions(final LogsSubscription... subscriptions) { |
|
|
|
|
registerSubscriptions(Arrays.asList(subscriptions)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private LogWithMetadata createLogWithMetadata( |
|
|
|
|
final TransactionReceiptWithMetadata transactionReceiptWithMetadata, final boolean removed) { |
|
|
|
|
return new LogWithMetadata( |
|
|
|
|
0, |
|
|
|
|
transactionReceiptWithMetadata.getBlockNumber(), |
|
|
|
|
transactionReceiptWithMetadata.getBlockHash(), |
|
|
|
|
transactionReceiptWithMetadata.getTransactionHash(), |
|
|
|
|
transactionReceiptWithMetadata.getTransactionIndex(), |
|
|
|
|
transactionReceiptWithMetadata.getReceipt().getLogs().get(0).getLogger(), |
|
|
|
|
transactionReceiptWithMetadata.getReceipt().getLogs().get(0).getData(), |
|
|
|
|
transactionReceiptWithMetadata.getReceipt().getLogs().get(0).getTopics(), |
|
|
|
|
removed); |
|
|
|
|
private void registerSubscriptions(final List<LogsSubscription> subscriptions) { |
|
|
|
|
when(subscriptionManager.subscriptionsOfType(any(), any())) |
|
|
|
|
.thenReturn(Lists.newArrayList(subscriptions)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|