Updates made to send notifications for new fork headers when the chai… (#403)

Signed-off-by: David Mechler <david.mechler@consensys.net>
pull/442/head
David Mechler 5 years ago committed by GitHub
parent 84056febd9
commit 4d198eb909
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 51
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionService.java
  2. 236
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java
  3. 26
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockAddedEvent.java
  4. 3
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchain.java

@ -22,8 +22,12 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
import org.hyperledger.besu.ethereum.chain.BlockAddedObserver;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.Hash;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
@ -43,28 +47,43 @@ public class NewBlockHeadersSubscriptionService implements BlockAddedObserver {
@Override
public void onBlockAdded(final BlockAddedEvent event, final Blockchain blockchain) {
if (event.isNewCanonicalHead()) {
subscriptionManager.notifySubscribersOnWorkerThread(
SubscriptionType.NEW_BLOCK_HEADERS,
NewBlockHeadersSubscription.class,
subscribers -> {
final Hash newBlockHash = event.getBlock().getHash();
final List<Block> blocks = new ArrayList<>();
Block blockPtr = event.getBlock();
// memoize
final Supplier<BlockResult> blockWithTx =
Suppliers.memoize(() -> blockWithCompleteTransaction(newBlockHash));
final Supplier<BlockResult> blockWithoutTx =
Suppliers.memoize(() -> blockWithTransactionHash(newBlockHash));
while (!blockPtr.getHash().equals(event.getCommonAncestorHash())) {
blocks.add(blockPtr);
for (final NewBlockHeadersSubscription subscription : subscribers) {
final BlockResult newBlock =
subscription.getIncludeTransactions() ? blockWithTx.get() : blockWithoutTx.get();
blockPtr =
blockchain
.getBlockByHash(blockPtr.getHeader().getParentHash())
.orElseThrow(() -> new IllegalStateException("The block was on a orphaned chain."));
}
subscriptionManager.sendMessage(subscription.getSubscriptionId(), newBlock);
}
});
Collections.reverse(blocks);
blocks.forEach(b -> notifySubscribers(b.getHash()));
}
}
private void notifySubscribers(final Hash newBlockHash) {
subscriptionManager.notifySubscribersOnWorkerThread(
SubscriptionType.NEW_BLOCK_HEADERS,
NewBlockHeadersSubscription.class,
subscribers -> {
// memoize
final Supplier<BlockResult> blockWithTx =
Suppliers.memoize(() -> blockWithCompleteTransaction(newBlockHash));
final Supplier<BlockResult> blockWithoutTx =
Suppliers.memoize(() -> blockWithTransactionHash(newBlockHash));
for (final NewBlockHeadersSubscription subscription : subscribers) {
final BlockResult newBlock =
subscription.getIncludeTransactions() ? blockWithTx.get() : blockWithoutTx.get();
subscriptionManager.sendMessage(subscription.getSubscriptionId(), newBlock);
}
});
}
private BlockResult blockWithCompleteTransaction(final Hash hash) {
return blockchainQueries.blockByHash(hash).map(blockResult::transactionComplete).orElse(null);
}

@ -15,138 +15,191 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.blockheaders;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.crypto.SECP256K1.KeyPair;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.JsonRpcResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager;
import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
import org.hyperledger.besu.ethereum.chain.BlockchainStorage;
import org.hyperledger.besu.ethereum.chain.DefaultBlockchain;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
import java.util.ArrayList;
import java.util.Collections;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class NewBlockHeadersSubscriptionServiceTest {
private NewBlockHeadersSubscriptionService newBlockHeadersSubscriptionService;
@Captor ArgumentCaptor<Long> subscriptionIdCaptor;
@Captor ArgumentCaptor<JsonRpcResult> responseCaptor;
@Mock private SubscriptionManager subscriptionManager;
@Mock private BlockchainQueries blockchainQueries;
private final BlockHeaderTestFixture blockHeaderTestFixture = new BlockHeaderTestFixture();
private final TransactionTestFixture txTestFixture = new TransactionTestFixture();
private final BlockHeader blockHeader = blockHeaderTestFixture.buildHeader();
private final BlockResultFactory blockResultFactory = new BlockResultFactory();
private final BlockDataGenerator gen = new BlockDataGenerator();
private final BlockchainStorage blockchainStorage =
new KeyValueStoragePrefixedKeyBlockchainStorage(
new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions());
private final Block genesisBlock = gen.genesisBlock();
private final MutableBlockchain blockchain =
DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, new NoOpMetricsSystem());
@Spy
private final SubscriptionManager subscriptionManagerSpy =
new SubscriptionManager(new NoOpMetricsSystem());
@Spy
private final BlockchainQueries blockchainQueriesSpy =
Mockito.spy(new BlockchainQueries(blockchain, createInMemoryWorldStateArchive()));
@Before
public void before() {
newBlockHeadersSubscriptionService =
new NewBlockHeadersSubscriptionService(subscriptionManager, blockchainQueries);
final NewBlockHeadersSubscriptionService newBlockHeadersSubscriptionService =
new NewBlockHeadersSubscriptionService(subscriptionManagerSpy, blockchainQueriesSpy);
blockchain.observeBlockAdded(newBlockHeadersSubscriptionService);
}
@Test
public void shouldSendMessageWhenBlockAddedOnCanonicalChain() {
final NewBlockHeadersSubscription subscription = createSubscription(false);
mockSubscriptionManagerNotifyMethod(subscription);
final BlockResult expectedNewBlock = expectedBlockWithTransactions(Collections.emptyList());
simulateAddingBlockOnCanonicalChain();
final Block testBlock = appendBlockWithParent(blockchain, blockchain.getChainHeadBlock());
final BlockResult expectedNewBlock =
blockResultFactory.transactionHash(
blockchainQueriesSpy.blockByHashWithTxHashes(testBlock.getHash()).orElse(null));
verify(subscriptionManager)
verify(subscriptionManagerSpy)
.sendMessage(subscriptionIdCaptor.capture(), responseCaptor.capture());
assertThat(subscriptionIdCaptor.getValue()).isEqualTo(subscription.getSubscriptionId());
assertThat(responseCaptor.getValue())
.isEqualToComparingFieldByFieldRecursively(expectedNewBlock);
assertThat(responseCaptor.getValue()).usingRecursiveComparison().isEqualTo(expectedNewBlock);
}
@Test
public void shouldNotSendMessageWhenBlockAddedIsNotOnCanonicalChain() {
simulateAddingBlockOnNonCanonicalChain();
final NewBlockHeadersSubscription subscription = createSubscription(false);
mockSubscriptionManagerNotifyMethod(subscription);
final Block canonicalBlock = appendBlockWithParent(blockchain, genesisBlock);
final BlockOptions options =
new BlockOptions()
.setBlockNumber(genesisBlock.getHeader().getNumber() + 1)
.setParentHash(genesisBlock.getHash())
.setDifficulty(genesisBlock.getHeader().getDifficulty().divide(100L));
appendBlockWithParent(blockchain, options);
final BlockResult expectedNewBlock =
blockResultFactory.transactionHash(
blockchainQueriesSpy.blockByHashWithTxHashes(canonicalBlock.getHash()).orElse(null));
verify(subscriptionManagerSpy, times(1)).notifySubscribersOnWorkerThread(any(), any(), any());
verify(subscriptionManagerSpy, times(1))
.sendMessage(subscriptionIdCaptor.capture(), responseCaptor.capture());
assertThat(subscriptionIdCaptor.getValue()).isEqualTo(subscription.getSubscriptionId());
List<JsonRpcResult> capturedNewBlocks = responseCaptor.getAllValues();
assertThat(capturedNewBlocks.size()).isEqualTo(1);
assertThat(capturedNewBlocks.get(0)).usingRecursiveComparison().isEqualTo(expectedNewBlock);
}
@Test
public void shouldSendMessagesWhenReorgBlockAdded() {
final NewBlockHeadersSubscription subscription = createSubscription(false);
mockSubscriptionManagerNotifyMethod(subscription);
verifyZeroInteractions(subscriptionManager);
final Block canonicalBlock = appendBlockWithParent(blockchain, genesisBlock);
final BlockOptions options =
new BlockOptions()
.setBlockNumber(genesisBlock.getHeader().getNumber() + 1)
.setParentHash(genesisBlock.getHash())
.setDifficulty(genesisBlock.getHeader().getDifficulty().divide(100L));
final Block forkBlock = appendBlockWithParent(blockchain, options);
options.setDifficulty(forkBlock.getHeader().getDifficulty().divide(100L));
appendBlockWithParent(blockchain, options);
options.setDifficulty(blockchain.getChainHeadBlock().getHeader().getDifficulty().multiply(2L));
final Block forkBlock2 = appendBlockWithParent(blockchain, options);
final BlockResult expectedNewBlock =
blockResultFactory.transactionHash(
blockchainQueriesSpy.blockByHashWithTxHashes(canonicalBlock.getHash()).orElse(null));
final BlockResult expectedNewBlock1 =
blockResultFactory.transactionHash(
blockchainQueriesSpy.blockByHashWithTxHashes(forkBlock2.getHash()).orElse(null));
verify(subscriptionManagerSpy, times(2)).notifySubscribersOnWorkerThread(any(), any(), any());
verify(subscriptionManagerSpy, times(2))
.sendMessage(subscriptionIdCaptor.capture(), responseCaptor.capture());
assertThat(subscriptionIdCaptor.getValue()).isEqualTo(subscription.getSubscriptionId());
List<JsonRpcResult> capturedNewBlocks = responseCaptor.getAllValues();
assertThat(capturedNewBlocks.size()).isEqualTo(2);
assertThat(capturedNewBlocks.get(0)).usingRecursiveComparison().isEqualTo(expectedNewBlock);
assertThat(capturedNewBlocks.get(1)).usingRecursiveComparison().isEqualTo(expectedNewBlock1);
}
@Test
public void shouldReturnTxHashesWhenIncludeTransactionsFalse() {
final NewBlockHeadersSubscription subscription = createSubscription(false);
mockSubscriptionManagerNotifyMethod(subscription);
final List<Hash> txHashList = transactionsWithHashOnly();
final BlockResult expectedNewBlock = expectedBlockWithTransactions(txHashList);
simulateAddingBlockOnCanonicalChain();
final Block testBlock = appendBlockWithParent(blockchain, blockchain.getChainHeadBlock());
final BlockResult expectedNewBlock =
blockResultFactory.transactionHash(
blockchainQueriesSpy.blockByHashWithTxHashes(testBlock.getHash()).orElse(null));
verify(subscriptionManager)
verify(subscriptionManagerSpy)
.sendMessage(subscriptionIdCaptor.capture(), responseCaptor.capture());
assertThat(subscriptionIdCaptor.getValue()).isEqualTo(subscription.getSubscriptionId());
final Object actualBlock = responseCaptor.getValue();
assertThat(actualBlock).isInstanceOf(BlockResult.class);
assertThat(((BlockResult) actualBlock).getTransactions()).hasSize(txHashList.size());
assertThat(actualBlock).isEqualToComparingFieldByFieldRecursively(expectedNewBlock);
assertThat(((BlockResult) actualBlock).getTransactions())
.hasSize(expectedNewBlock.getTransactions().size());
assertThat(actualBlock).usingRecursiveComparison().isEqualTo(expectedNewBlock);
verify(blockchainQueries, times(1)).blockByHashWithTxHashes(any());
verify(blockchainQueries, times(0)).blockByHash(any());
verify(blockchainQueriesSpy, times(2)).blockByHashWithTxHashes(any());
verify(blockchainQueriesSpy, times(0)).blockByHash(any());
}
@Test
public void shouldReturnCompleteTxWhenParameterTrue() {
final NewBlockHeadersSubscription subscription = createSubscription(true);
mockSubscriptionManagerNotifyMethod(subscription);
final List<TransactionWithMetadata> txHashList = transactionsWithMetadata();
final BlockWithMetadata<TransactionWithMetadata, Hash> testBlockWithMetadata =
new BlockWithMetadata<>(
blockHeader, txHashList, Collections.emptyList(), blockHeader.getDifficulty(), 0);
final BlockResult expectedNewBlock =
blockResultFactory.transactionComplete(testBlockWithMetadata);
when(blockchainQueries.blockByHash(testBlockWithMetadata.getHeader().getHash()))
.thenReturn(Optional.of(testBlockWithMetadata));
simulateAddingBlockOnCanonicalChain();
final Block testBlock = appendBlockWithParent(blockchain, blockchain.getChainHeadBlock());
final BlockResult expectedNewBlock =
blockResultFactory.transactionComplete(
blockchainQueriesSpy.blockByHash(testBlock.getHeader().getHash()).orElse(null));
verify(subscriptionManager)
verify(subscriptionManagerSpy)
.sendMessage(subscriptionIdCaptor.capture(), responseCaptor.capture());
assertThat(subscriptionIdCaptor.getValue()).isEqualTo(subscription.getSubscriptionId());
final Object actualBlock = responseCaptor.getValue();
assertThat(actualBlock).isInstanceOf(BlockResult.class);
assertThat(((BlockResult) actualBlock).getTransactions()).hasSize(txHashList.size());
assertThat(actualBlock).isEqualToComparingFieldByFieldRecursively(expectedNewBlock);
assertThat(((BlockResult) actualBlock).getTransactions())
.hasSize(testBlock.getBody().getTransactions().size());
assertThat(actualBlock).usingRecursiveComparison().isEqualTo(expectedNewBlock);
verify(subscriptionManager, times(1)).sendMessage(any(), any());
verify(blockchainQueries, times(0)).blockByHashWithTxHashes(any());
verify(blockchainQueries, times(1)).blockByHash(any());
verify(subscriptionManagerSpy, times(1)).sendMessage(any(), any());
verify(blockchainQueriesSpy, times(0)).blockByHashWithTxHashes(any());
verify(blockchainQueriesSpy, times(2)).blockByHash(any());
}
@Test
@ -157,9 +210,9 @@ public class NewBlockHeadersSubscriptionServiceTest {
final NewBlockHeadersSubscription subscription4 = createSubscription(false);
mockSubscriptionManagerNotifyMethod(subscription1, subscription2, subscription3, subscription4);
simulateAddingBlockOnCanonicalChain();
appendBlockWithParent(blockchain, blockchain.getChainHeadBlock());
verify(subscriptionManager, times(4))
verify(subscriptionManagerSpy, times(4))
.sendMessage(subscriptionIdCaptor.capture(), responseCaptor.capture());
assertThat(subscriptionIdCaptor.getAllValues())
.containsExactly(
@ -168,18 +221,8 @@ public class NewBlockHeadersSubscriptionServiceTest {
subscription3.getSubscriptionId(),
subscription4.getSubscriptionId());
verify(blockchainQueries, times(1)).blockByHashWithTxHashes(any());
verify(blockchainQueries, times(1)).blockByHash(any());
}
private BlockResult expectedBlockWithTransactions(final List<Hash> objects) {
final BlockWithMetadata<Hash, Hash> testBlockWithMetadata =
new BlockWithMetadata<>(blockHeader, objects, Collections.emptyList(), Difficulty.ONE, 1);
final BlockResult expectedNewBlock = blockResultFactory.transactionHash(testBlockWithMetadata);
when(blockchainQueries.blockByHashWithTxHashes(testBlockWithMetadata.getHeader().getHash()))
.thenReturn(Optional.of(testBlockWithMetadata));
return expectedNewBlock;
verify(blockchainQueriesSpy, times(1)).blockByHashWithTxHashes(any());
verify(blockchainQueriesSpy, times(1)).blockByHash(any());
}
private void mockSubscriptionManagerNotifyMethod(
@ -190,46 +233,29 @@ public class NewBlockHeadersSubscriptionServiceTest {
consumer.accept(List.of(subscriptions));
return null;
})
.when(subscriptionManager)
.when(subscriptionManagerSpy)
.notifySubscribersOnWorkerThread(any(), any(), any());
}
private void simulateAddingBlockOnCanonicalChain() {
final BlockBody blockBody = new BlockBody(Collections.emptyList(), Collections.emptyList());
final Block testBlock = new Block(blockHeader, blockBody);
newBlockHeadersSubscriptionService.onBlockAdded(
BlockAddedEvent.createForHeadAdvancement(testBlock, Collections.emptyList()),
blockchainQueries.getBlockchain());
verify(blockchainQueries, times(1)).getBlockchain();
private NewBlockHeadersSubscription createSubscription(final boolean includeTransactions) {
return new NewBlockHeadersSubscription(1L, "conn", includeTransactions);
}
private void simulateAddingBlockOnNonCanonicalChain() {
final BlockBody blockBody = new BlockBody(Collections.emptyList(), Collections.emptyList());
final Block testBlock = new Block(blockHeader, blockBody);
newBlockHeadersSubscriptionService.onBlockAdded(
BlockAddedEvent.createForFork(testBlock), blockchainQueries.getBlockchain());
verify(blockchainQueries, times(1)).getBlockchain();
}
private Block appendBlockWithParent(final MutableBlockchain blockchain, final Block parent) {
final BlockOptions options =
new BlockOptions()
.setBlockNumber(parent.getHeader().getNumber() + 1)
.setParentHash(parent.getHash());
private List<TransactionWithMetadata> transactionsWithMetadata() {
final TransactionWithMetadata t1 =
new TransactionWithMetadata(
txTestFixture.createTransaction(KeyPair.generate()), 0L, Hash.ZERO, 0);
final TransactionWithMetadata t2 =
new TransactionWithMetadata(
txTestFixture.createTransaction(KeyPair.generate()), 1L, Hash.ZERO, 1);
return Lists.newArrayList(t1, t2);
return appendBlockWithParent(blockchain, options);
}
private List<Hash> transactionsWithHashOnly() {
final List<Hash> hashes = new ArrayList<>();
for (final TransactionWithMetadata transactionWithMetadata : transactionsWithMetadata()) {
hashes.add(transactionWithMetadata.getTransaction().getHash());
}
return hashes;
}
private Block appendBlockWithParent(
final MutableBlockchain blockchain, final BlockOptions options) {
final Block newBlock = gen.block(options);
final List<TransactionReceipt> receipts = gen.receipts(newBlock);
blockchain.appendBlock(newBlock, receipts);
private NewBlockHeadersSubscription createSubscription(final boolean includeTransactions) {
return new NewBlockHeadersSubscription(1L, "conn", includeTransactions);
return newBlock;
}
}

@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.chain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import org.hyperledger.besu.ethereum.core.Transaction;
@ -28,6 +29,7 @@ public class BlockAddedEvent {
private final List<Transaction> removedTransactions;
private final EventType eventType;
private final List<LogWithMetadata> logsWithMetadata;
private final Hash commonAncestorHash;
public enum EventType {
HEAD_ADVANCED,
@ -40,12 +42,14 @@ public class BlockAddedEvent {
final Block block,
final List<Transaction> addedTransactions,
final List<Transaction> removedTransactions,
final List<LogWithMetadata> logsWithMetadata) {
final List<LogWithMetadata> logsWithMetadata,
final Hash commonAncestorHash) {
this.eventType = eventType;
this.block = block;
this.addedTransactions = addedTransactions;
this.removedTransactions = removedTransactions;
this.logsWithMetadata = logsWithMetadata;
this.commonAncestorHash = commonAncestorHash;
}
public static BlockAddedEvent createForHeadAdvancement(
@ -55,16 +59,23 @@ public class BlockAddedEvent {
block,
block.getBody().getTransactions(),
Collections.emptyList(),
logsWithMetadata);
logsWithMetadata,
block.getHeader().getParentHash());
}
public static BlockAddedEvent createForChainReorg(
final Block block,
final List<Transaction> addedTransactions,
final List<Transaction> removedTransactions,
final List<LogWithMetadata> logsWithMetadata) {
final List<LogWithMetadata> logsWithMetadata,
final Hash commonAncestorHash) {
return new BlockAddedEvent(
EventType.CHAIN_REORG, block, addedTransactions, removedTransactions, logsWithMetadata);
EventType.CHAIN_REORG,
block,
addedTransactions,
removedTransactions,
logsWithMetadata,
commonAncestorHash);
}
public static BlockAddedEvent createForFork(final Block block) {
@ -73,7 +84,8 @@ public class BlockAddedEvent {
block,
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList());
Collections.emptyList(),
block.getHeader().getParentHash());
}
public Block getBlock() {
@ -99,4 +111,8 @@ public class BlockAddedEvent {
public List<LogWithMetadata> getLogsWithMetadata() {
return logsWithMetadata;
}
public Hash getCommonAncestorHash() {
return commonAncestorHash;
}
}

@ -402,7 +402,8 @@ public class DefaultBlockchain implements MutableBlockchain {
newTransactions.values().stream().flatMap(Collection::stream).collect(toList()),
removedTransactions,
Stream.concat(removedLogsWithMetadata.stream(), addedLogsWithMetadata.stream())
.collect(Collectors.toUnmodifiableList()));
.collect(Collectors.toUnmodifiableList()),
currentNewChainWithReceipts.getBlock().getHash());
}
@Override

Loading…
Cancel
Save