mirror of https://github.com/hyperledger/besu
[PAN-2327] Notify of dropped messages (#1156)
* Added ability to subscribe to dropped transactions from the transaction pending pool. Implemented subscription webservice to support this. * Added metrics to the pending transactions, tracking the number of local and remote transactions in the pool. * Converted listener management in pending transactions to use the Subscribers util object. Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
324222752a
commit
cdefb330be
@ -0,0 +1,19 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2018 ConsenSys AG. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
*/ |
||||||
|
package tech.pegasys.pantheon.ethereum.core; |
||||||
|
|
||||||
|
@FunctionalInterface |
||||||
|
public interface PendingTransactionDroppedListener { |
||||||
|
|
||||||
|
void onTransactionDropped(Transaction transaction); |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2018 ConsenSys AG. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
*/ |
||||||
|
package tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.pending; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.PendingTransactionDroppedListener; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Transaction; |
||||||
|
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.Subscription; |
||||||
|
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.SubscriptionManager; |
||||||
|
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.request.SubscriptionType; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class PendingTransactionDroppedSubscriptionService |
||||||
|
implements PendingTransactionDroppedListener { |
||||||
|
|
||||||
|
private final SubscriptionManager subscriptionManager; |
||||||
|
|
||||||
|
public PendingTransactionDroppedSubscriptionService( |
||||||
|
final SubscriptionManager subscriptionManager) { |
||||||
|
this.subscriptionManager = subscriptionManager; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onTransactionDropped(final Transaction transaction) { |
||||||
|
notifySubscribers(transaction.hash()); |
||||||
|
} |
||||||
|
|
||||||
|
private void notifySubscribers(final Hash pendingTransaction) { |
||||||
|
final List<Subscription> subscriptions = pendingDroppedTransactionSubscriptions(); |
||||||
|
|
||||||
|
final PendingTransactionResult msg = new PendingTransactionResult(pendingTransaction); |
||||||
|
for (final Subscription subscription : subscriptions) { |
||||||
|
subscriptionManager.sendMessage(subscription.getId(), msg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private List<Subscription> pendingDroppedTransactionSubscriptions() { |
||||||
|
return subscriptionManager.subscriptionsOfType( |
||||||
|
SubscriptionType.DROPPED_PENDING_TRANSACTIONS, Subscription.class); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,109 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2018 ConsenSys AG. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||||
|
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||||
|
* specific language governing permissions and limitations under the License. |
||||||
|
*/ |
||||||
|
package tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.pending; |
||||||
|
|
||||||
|
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.verify; |
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions; |
||||||
|
import static org.mockito.Mockito.verifyZeroInteractions; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.ethereum.chain.Blockchain; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Block; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Transaction; |
||||||
|
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.Subscription; |
||||||
|
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.SubscriptionManager; |
||||||
|
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.request.SubscriptionType; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class PendingTransactionDroppedSubscriptionServiceTest { |
||||||
|
|
||||||
|
private static final Hash TX_ONE = |
||||||
|
Hash.fromHexString("0x15876958423545c3c7b0fcf9be8ffb543305ee1b43db87ed380dcf0cd16589f7"); |
||||||
|
|
||||||
|
@Mock private SubscriptionManager subscriptionManager; |
||||||
|
@Mock private Blockchain blockchain; |
||||||
|
@Mock private Block block; |
||||||
|
|
||||||
|
private PendingTransactionDroppedSubscriptionService service; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
service = new PendingTransactionDroppedSubscriptionService(subscriptionManager); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void onTransactionAddedMustSendMessage() { |
||||||
|
final long[] subscriptionIds = new long[] {5, 56, 989}; |
||||||
|
setUpSubscriptions(subscriptionIds); |
||||||
|
final Transaction pending = transaction(TX_ONE); |
||||||
|
|
||||||
|
service.onTransactionDropped(pending); |
||||||
|
|
||||||
|
verifyZeroInteractions(block); |
||||||
|
verifyZeroInteractions(blockchain); |
||||||
|
verifySubscriptionMangerInteractions(messages(TX_ONE, subscriptionIds)); |
||||||
|
} |
||||||
|
|
||||||
|
private void verifySubscriptionMangerInteractions(final Map<Long, Hash> expected) { |
||||||
|
verify(subscriptionManager) |
||||||
|
.subscriptionsOfType(SubscriptionType.DROPPED_PENDING_TRANSACTIONS, Subscription.class); |
||||||
|
|
||||||
|
for (final Map.Entry<Long, Hash> message : expected.entrySet()) { |
||||||
|
verify(subscriptionManager) |
||||||
|
.sendMessage( |
||||||
|
eq(message.getKey()), refEq(new PendingTransactionResult(message.getValue()))); |
||||||
|
} |
||||||
|
|
||||||
|
verifyNoMoreInteractions(subscriptionManager); |
||||||
|
} |
||||||
|
|
||||||
|
private Map<Long, Hash> messages(final Hash result, final long... subscriptionIds) { |
||||||
|
final Map<Long, Hash> messages = new HashMap<>(); |
||||||
|
|
||||||
|
for (final long subscriptionId : subscriptionIds) { |
||||||
|
messages.put(subscriptionId, result); |
||||||
|
} |
||||||
|
|
||||||
|
return messages; |
||||||
|
} |
||||||
|
|
||||||
|
private Transaction transaction(final Hash hash) { |
||||||
|
final Transaction tx = mock(Transaction.class); |
||||||
|
when(tx.hash()).thenReturn(hash); |
||||||
|
return tx; |
||||||
|
} |
||||||
|
|
||||||
|
private void setUpSubscriptions(final long... subscriptionsIds) { |
||||||
|
when(subscriptionManager.subscriptionsOfType(any(), any())) |
||||||
|
.thenReturn( |
||||||
|
Arrays.stream(subscriptionsIds) |
||||||
|
.mapToObj(id -> new Subscription(id, SubscriptionType.DROPPED_PENDING_TRANSACTIONS)) |
||||||
|
.collect(Collectors.toList())); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue