mirror of https://github.com/hyperledger/besu
TxPool code refactor to improve readability (#4566)
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>pull/4595/head
parent
42260fd56b
commit
166aa69531
@ -0,0 +1,117 @@ |
||||
/* |
||||
* Copyright Besu contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.transactions; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
import java.util.concurrent.atomic.AtomicLong; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* Tracks the additional metadata associated with transactions to enable prioritization for mining |
||||
* and deciding which transactions to drop when the transaction pool reaches its size limit. |
||||
*/ |
||||
public class PendingTransaction { |
||||
|
||||
private static final AtomicLong TRANSACTIONS_ADDED = new AtomicLong(); |
||||
private final Transaction transaction; |
||||
private final boolean receivedFromLocalSource; |
||||
private final Instant addedToPoolAt; |
||||
private final long sequence; // Allows prioritization based on order transactions are added
|
||||
|
||||
public PendingTransaction( |
||||
final Transaction transaction, |
||||
final boolean receivedFromLocalSource, |
||||
final Instant addedToPoolAt) { |
||||
this.transaction = transaction; |
||||
this.receivedFromLocalSource = receivedFromLocalSource; |
||||
this.addedToPoolAt = addedToPoolAt; |
||||
this.sequence = TRANSACTIONS_ADDED.getAndIncrement(); |
||||
} |
||||
|
||||
public Transaction getTransaction() { |
||||
return transaction; |
||||
} |
||||
|
||||
public Wei getGasPrice() { |
||||
return transaction.getGasPrice().orElse(Wei.ZERO); |
||||
} |
||||
|
||||
public long getSequence() { |
||||
return sequence; |
||||
} |
||||
|
||||
public long getNonce() { |
||||
return transaction.getNonce(); |
||||
} |
||||
|
||||
public Address getSender() { |
||||
return transaction.getSender(); |
||||
} |
||||
|
||||
public boolean isReceivedFromLocalSource() { |
||||
return receivedFromLocalSource; |
||||
} |
||||
|
||||
public Hash getHash() { |
||||
return transaction.getHash(); |
||||
} |
||||
|
||||
public Instant getAddedToPoolAt() { |
||||
return addedToPoolAt; |
||||
} |
||||
|
||||
public static List<Transaction> toTransactionList( |
||||
final Collection<PendingTransaction> transactionsInfo) { |
||||
return transactionsInfo.stream() |
||||
.map(PendingTransaction::getTransaction) |
||||
.collect(Collectors.toUnmodifiableList()); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(final Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
|
||||
PendingTransaction that = (PendingTransaction) o; |
||||
|
||||
return sequence == that.sequence; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return 31 * (int) (sequence ^ (sequence >>> 32)); |
||||
} |
||||
|
||||
public String toTraceLog() { |
||||
return "{sequence: " |
||||
+ sequence |
||||
+ ", addedAt: " |
||||
+ addedToPoolAt |
||||
+ ", " |
||||
+ transaction.toTraceLog() |
||||
+ "}"; |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
/* |
||||
* Copyright Besu contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.transactions; |
||||
|
||||
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
public enum TransactionAddedStatus { |
||||
ALREADY_KNOWN(TransactionInvalidReason.TRANSACTION_ALREADY_KNOWN), |
||||
REJECTED_UNDERPRICED_REPLACEMENT(TransactionInvalidReason.TRANSACTION_REPLACEMENT_UNDERPRICED), |
||||
NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER(TransactionInvalidReason.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER), |
||||
LOWER_NONCE_INVALID_TRANSACTION_KNOWN( |
||||
TransactionInvalidReason.LOWER_NONCE_INVALID_TRANSACTION_EXISTS), |
||||
ADDED(); |
||||
|
||||
private final Optional<TransactionInvalidReason> invalidReason; |
||||
|
||||
TransactionAddedStatus() { |
||||
this.invalidReason = Optional.empty(); |
||||
} |
||||
|
||||
TransactionAddedStatus(final TransactionInvalidReason invalidReason) { |
||||
this.invalidReason = Optional.of(invalidReason); |
||||
} |
||||
|
||||
public Optional<TransactionInvalidReason> getInvalidReason() { |
||||
return invalidReason; |
||||
} |
||||
} |
@ -0,0 +1,206 @@ |
||||
/* |
||||
* Copyright Besu contributors. |
||||
* |
||||
* 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. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.eth.transactions.sorter; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.NavigableSet; |
||||
import java.util.TreeSet; |
||||
import java.util.function.Consumer; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.StreamSupport; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
class LowestInvalidNonceCache { |
||||
private static final Logger LOG = LoggerFactory.getLogger(LowestInvalidNonceCache.class); |
||||
|
||||
private final int maxSize; |
||||
private final Map<Address, InvalidNonceStatus> lowestInvalidKnownNonceBySender; |
||||
private final NavigableSet<InvalidNonceStatus> evictionOrder = new TreeSet<>(); |
||||
|
||||
public LowestInvalidNonceCache(final int maxSize) { |
||||
this.maxSize = maxSize; |
||||
this.lowestInvalidKnownNonceBySender = new HashMap<>(maxSize); |
||||
} |
||||
|
||||
synchronized long registerInvalidTransaction(final Transaction transaction) { |
||||
final Address sender = transaction.getSender(); |
||||
final long invalidNonce = transaction.getNonce(); |
||||
final InvalidNonceStatus currStatus = lowestInvalidKnownNonceBySender.get(sender); |
||||
if (currStatus == null) { |
||||
final InvalidNonceStatus newStatus = new InvalidNonceStatus(sender, invalidNonce); |
||||
addInvalidNonceStatus(newStatus); |
||||
LOG.trace("Added invalid nonce status {}, cache status {}", newStatus, this); |
||||
return invalidNonce; |
||||
} |
||||
|
||||
updateInvalidNonceStatus( |
||||
currStatus, |
||||
status -> { |
||||
if (invalidNonce < currStatus.nonce) { |
||||
currStatus.updateNonce(invalidNonce); |
||||
} else { |
||||
currStatus.newHit(); |
||||
} |
||||
}); |
||||
LOG.trace("Updated invalid nonce status {}, cache status {}", currStatus, this); |
||||
|
||||
return currStatus.nonce; |
||||
} |
||||
|
||||
synchronized void registerValidTransaction(final Transaction transaction) { |
||||
final InvalidNonceStatus currStatus = |
||||
lowestInvalidKnownNonceBySender.get(transaction.getSender()); |
||||
if (currStatus != null) { |
||||
evictionOrder.remove(currStatus); |
||||
lowestInvalidKnownNonceBySender.remove(transaction.getSender()); |
||||
LOG.trace( |
||||
"Valid transaction, removed invalid nonce status {}, cache status {}", currStatus, this); |
||||
} |
||||
} |
||||
|
||||
synchronized boolean hasInvalidLowerNonce(final Transaction transaction) { |
||||
final InvalidNonceStatus currStatus = |
||||
lowestInvalidKnownNonceBySender.get(transaction.getSender()); |
||||
if (currStatus != null && transaction.getNonce() > currStatus.nonce) { |
||||
updateInvalidNonceStatus(currStatus, status -> status.newHit()); |
||||
LOG.trace("New hit for invalid nonce status {}, cache status {}", currStatus, this); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private void updateInvalidNonceStatus( |
||||
final InvalidNonceStatus status, final Consumer<InvalidNonceStatus> updateAction) { |
||||
evictionOrder.remove(status); |
||||
updateAction.accept(status); |
||||
evictionOrder.add(status); |
||||
} |
||||
|
||||
private void addInvalidNonceStatus(final InvalidNonceStatus newStatus) { |
||||
if (lowestInvalidKnownNonceBySender.size() >= maxSize) { |
||||
final InvalidNonceStatus statusToEvict = evictionOrder.pollFirst(); |
||||
lowestInvalidKnownNonceBySender.remove(statusToEvict.address); |
||||
LOG.trace("Evicted invalid nonce status {}, cache status {}", statusToEvict, this); |
||||
} |
||||
lowestInvalidKnownNonceBySender.put(newStatus.address, newStatus); |
||||
evictionOrder.add(newStatus); |
||||
} |
||||
|
||||
synchronized String toTraceLog() { |
||||
return "by eviction order " |
||||
+ StreamSupport.stream(evictionOrder.spliterator(), false) |
||||
.map(InvalidNonceStatus::toString) |
||||
.collect(Collectors.joining("; ")); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "LowestInvalidNonceCache{" |
||||
+ "maxSize: " |
||||
+ maxSize |
||||
+ ", currentSize: " |
||||
+ lowestInvalidKnownNonceBySender.size() |
||||
+ ", evictionOrder: [size: " |
||||
+ evictionOrder.size() |
||||
+ ", first evictable: " |
||||
+ evictionOrder.first() |
||||
+ "]" |
||||
+ '}'; |
||||
} |
||||
|
||||
private static class InvalidNonceStatus implements Comparable<InvalidNonceStatus> { |
||||
|
||||
final Address address; |
||||
long nonce; |
||||
long hits; |
||||
long lastUpdate; |
||||
|
||||
InvalidNonceStatus(final Address address, final long nonce) { |
||||
this.address = address; |
||||
this.nonce = nonce; |
||||
this.hits = 1L; |
||||
this.lastUpdate = System.currentTimeMillis(); |
||||
} |
||||
|
||||
void updateNonce(final long nonce) { |
||||
this.nonce = nonce; |
||||
newHit(); |
||||
} |
||||
|
||||
void newHit() { |
||||
this.hits++; |
||||
this.lastUpdate = System.currentTimeMillis(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(final Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
|
||||
InvalidNonceStatus that = (InvalidNonceStatus) o; |
||||
|
||||
return address.equals(that.address); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return address.hashCode(); |
||||
} |
||||
|
||||
/** |
||||
* An InvalidNonceStatus is smaller than another when it has fewer hits and was last access |
||||
* earlier, the address is the last tiebreaker |
||||
* |
||||
* @param o the object to be compared. |
||||
* @return 0 if they are equal, negative if this is smaller, positive if this is greater |
||||
*/ |
||||
@Override |
||||
public int compareTo(final InvalidNonceStatus o) { |
||||
final int cmpHits = Long.compare(this.hits, o.hits); |
||||
if (cmpHits != 0) { |
||||
return cmpHits; |
||||
} |
||||
final int cmpLastUpdate = Long.compare(this.lastUpdate, o.lastUpdate); |
||||
if (cmpLastUpdate != 0) { |
||||
return cmpLastUpdate; |
||||
} |
||||
return this.address.compareTo(o.address); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "{" |
||||
+ "address=" |
||||
+ address |
||||
+ ", nonce=" |
||||
+ nonce |
||||
+ ", hits=" |
||||
+ hits |
||||
+ ", lastUpdate=" |
||||
+ lastUpdate |
||||
+ '}'; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue