Coverage Summary for Class: PendingState (co.rsk.core.bc)

Class Method, % Line, %
PendingState 46.7% (7/15) 54.4% (31/57)
PendingState$PostExecutionAction
PendingState$TransactionExecutorFactory
Total 46.7% (7/15) 54.4% (31/57)


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 co.rsk.core.bc; 20  21 import co.rsk.core.Coin; 22 import co.rsk.core.RskAddress; 23 import co.rsk.db.RepositorySnapshot; 24 import org.ethereum.core.Repository; 25 import org.ethereum.core.Transaction; 26 import org.ethereum.core.TransactionExecutor; 27 import org.ethereum.core.TransactionSet; 28 import org.ethereum.util.ByteUtil; 29 import org.ethereum.vm.DataWord; 30 import org.slf4j.Logger; 31 import org.slf4j.LoggerFactory; 32  33 import java.math.BigInteger; 34 import java.util.*; 35 import java.util.stream.Collectors; 36  37 import static java.util.Collections.reverseOrder; 38 import static org.ethereum.util.BIUtil.toBI; 39  40 public class PendingState implements AccountInformationProvider { 41  42  private static final Logger LOGGER = LoggerFactory.getLogger(PendingState.class); 43  44  private final Repository pendingRepository; 45  private final TransactionExecutorFactory transactionExecutorFactory; 46  private final TransactionSet pendingTransactions; 47  private boolean executed = false; 48  49  50  public PendingState(RepositorySnapshot repository, TransactionSet pendingTransactions, TransactionExecutorFactory transactionExecutorFactory) { 51  this.pendingRepository = repository.startTracking(); 52  this.pendingTransactions = pendingTransactions; 53  this.transactionExecutorFactory = transactionExecutorFactory; 54  } 55  56  @Override 57  public Coin getBalance(RskAddress addr) { 58  return postExecutionReturn(executedRepository -> executedRepository.getBalance(addr)); 59  } 60  61  @Override 62  public DataWord getStorageValue(RskAddress addr, DataWord key) { 63  return postExecutionReturn(executedRepository -> executedRepository.getStorageValue(addr, key)); 64  } 65  66  @Override 67  public byte[] getStorageBytes(RskAddress addr, DataWord key) { 68  return postExecutionReturn(executedRepository -> executedRepository.getStorageBytes(addr, key)); 69  } 70  71  @Override 72  public Iterator<DataWord> getStorageKeys(RskAddress addr) { 73  return postExecutionReturn(executedRepository -> executedRepository.getStorageKeys(addr)); 74  } 75  76  @Override 77  public int getStorageKeysCount(RskAddress addr) { 78  return postExecutionReturn(executedRepository -> executedRepository.getStorageKeysCount(addr)); 79  } 80  81  @Override 82  public byte[] getCode(RskAddress addr) { 83  return postExecutionReturn(executedRepository -> executedRepository.getCode(addr)); 84  } 85  86  @Override 87  public boolean isContract(RskAddress addr) { 88  return postExecutionReturn(executedRepository -> executedRepository.isContract(addr)); 89  } 90  91  @Override 92  public BigInteger getNonce(RskAddress addr) { 93  BigInteger nextNonce = pendingRepository.getNonce(addr); 94  Optional<BigInteger> maxNonce = this.pendingTransactions.getTransactionsWithSender(addr).stream() 95  .map(Transaction::getNonceAsInteger) 96  .max(BigInteger::compareTo) 97  .map(nonce -> nonce.add(BigInteger.ONE)) 98  .filter(nonce -> nonce.compareTo(nextNonce) >= 0); 99  100  return maxNonce.orElse(nextNonce); 101  } 102  103  // sortByPriceTakingIntoAccountSenderAndNonce sorts the transactions by price, but 104  // first clustering by sender and then each cluster is order by nonce. 105  // 106  // This method first sorts list of getPendingTransactions into individual 107  // sender and sorts those lists by nonce. After the nonce ordering is 108  // satisfied, the results are merged back together by price, always comparing only 109  // the head transaction from each sender, in this way we keep the sender and nonce ordering 110  // for each individual list. 111  // To order the price we use a heap to keep it fast. 112  113  // Note that this sort doesn't return the best solution, it is an approximation algorithm to find approximate 114  // solution. (No trivial solution) 115  public static List<Transaction> sortByPriceTakingIntoAccountSenderAndNonce(List<Transaction> transactions) { 116  117  //Priority heap, and list of transactions are ordered by descending gas price. 118  Comparator<Transaction> gasPriceComparator = reverseOrder(Comparator.comparing(Transaction::getGasPrice)); 119  120  //First create a map to separate txs by each sender. 121  Map<RskAddress, List<Transaction>> senderTxs = transactions.stream().collect(Collectors.groupingBy(Transaction::getSender)); 122  123  //For each sender, order all txs by nonce and then by hash, 124  //finally we order by price in cases where nonce are equal, and then by hash to disambiguate 125  for (List<Transaction> transactionList : senderTxs.values()) { 126  transactionList.sort( 127  Comparator.<Transaction>comparingLong(tx -> ByteUtil.byteArrayToLong(tx.getNonce())) 128  .thenComparing(gasPriceComparator) 129  .thenComparing(Transaction::getHash) 130  ); 131  } 132  133  PriorityQueue<Transaction> candidateTxs = new PriorityQueue<>(gasPriceComparator); 134  135  //Add the first transaction from each sender to the heap. 136  //Notice that we never push two transaction from the same sender 137  //to avoid losing nonce ordering 138  senderTxs.values().forEach(x -> { 139  Transaction tx = x.remove(0); 140  candidateTxs.add(tx); 141  }); 142  143  long txsCount = transactions.size(); 144  List<Transaction> sortedTxs = new ArrayList<>(); 145  //In each iteration we get the tx with max price (head) from the heap. 146  while (txsCount > 0) { 147  Transaction nextTxToAdd = candidateTxs.remove(); 148  sortedTxs.add(nextTxToAdd); 149  List<Transaction> txs = senderTxs.get(nextTxToAdd.getSender()); 150  if (!txs.isEmpty()) { 151  Transaction tx = txs.remove(0); 152  candidateTxs.add(tx); 153  } 154  155  txsCount--; 156  } 157  158  return sortedTxs; 159  } 160  161  private <T> T postExecutionReturn(PostExecutionAction<T> action) { 162  if (!executed) { 163  executeTransactions(pendingRepository, pendingTransactions.getTransactions()); 164  executed = true; 165  } 166  return action.execute(pendingRepository); 167  } 168  169  private void executeTransactions(Repository currentRepository, List<Transaction> pendingTransactions) { 170  171  PendingState.sortByPriceTakingIntoAccountSenderAndNonce(pendingTransactions) 172  .forEach(pendingTransaction -> executeTransaction(currentRepository, pendingTransaction)); 173  } 174  175  private void executeTransaction(Repository currentRepository, Transaction tx) { 176  LOGGER.trace("Apply pending state tx: {} {}", toBI(tx.getNonce()), tx.getHash()); 177  178  TransactionExecutor executor = transactionExecutorFactory.newInstance(currentRepository, tx); 179  180  executor.executeTransaction(); 181  } 182  183  private interface PostExecutionAction<T> { 184  T execute(Repository executedRepository); 185  } 186  187  public interface TransactionExecutorFactory { 188  TransactionExecutor newInstance(Repository repository, Transaction tx); 189  } 190 }