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 }