Skip to content

Commit 703e16a

Browse files
authored
Merge pull request #928 from rsksmart/migration_fix
Handle unitrie migration border cases
2 parents 208e7b3 + 289c884 commit 703e16a

File tree

2 files changed

+44
-55
lines changed

2 files changed

+44
-55
lines changed

rskj-core/src/main/java/co/rsk/db/migration/MissingOrchidStorageKeysProvider.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.slf4j.Logger;
2929
import org.slf4j.LoggerFactory;
3030

31-
import javax.annotation.Nullable;
3231
import java.io.File;
3332
import java.io.FileOutputStream;
3433
import java.io.IOException;
@@ -42,7 +41,7 @@
4241

4342
public class MissingOrchidStorageKeysProvider {
4443

45-
private static final String MAPDB_FILENAME = "migration-extras";
44+
public static final String MAPDB_FILENAME = "migration-extras";
4645

4746
private static final Logger logger = LoggerFactory.getLogger(MissingOrchidStorageKeysProvider.class);
4847

@@ -57,7 +56,6 @@ public MissingOrchidStorageKeysProvider(String databaseDir, URL missingStorageKe
5756
this.databaseLocalFile = databasePath.resolve(MAPDB_FILENAME).toFile();
5857
}
5958

60-
@Nullable
6159
public DataWord getKeccak256PreImage(Keccak256 storageKeyHash) {
6260
if (patchDatabase == null) {
6361
if (!databaseLocalFile.exists()) {
@@ -82,13 +80,24 @@ public DataWord getKeccak256PreImage(Keccak256 storageKeyHash) {
8280
}
8381

8482
byte[] storageKey = patchDatabase.get(storageKeyHash.getBytes());
85-
if (!Arrays.equals(storageKeyHash.getBytes(), Keccak256Helper.keccak256(storageKey))) {
83+
if (storageKey == null) {
84+
throw new IllegalStateException(String.format(
85+
"We have detected a recoverable inconsistency in your database during the migration process.\n" +
86+
"The patching information required to do this is not available at the moment, " +
87+
"but it's automatically updated every hour.\n" +
88+
"Please try again in a few minutes. " +
89+
"If you have any question please reach through our support channel at http://gitter.im/rsksmart/rskj\n" +
90+
"Missing storage key: %s",
91+
storageKeyHash.toHexString()
92+
));
93+
}
94+
if (!Arrays.equals(storageKeyHash.getBytes(), Keccak256Helper.keccak256(DataWord.valueOf(storageKey).getData()))) {
8695
throw new IllegalStateException(
8796
String.format("You have downloaded an inconsistent database. %s doesn't match expected keccak256 hash (%s)",
8897
Hex.toHexString(storageKey),
8998
Hex.toHexString(storageKeyHash.getBytes())
9099
));
91100
}
92-
return storageKey != null? DataWord.valueOf(storageKey) : null;
101+
return DataWord.valueOf(storageKey);
93102
}
94103
}

rskj-core/src/main/java/co/rsk/db/migration/OrchidToUnitrieMigrator.java

+30-50
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.bouncycastle.util.encoders.Hex;
2828
import org.ethereum.core.AccountState;
2929
import org.ethereum.core.Block;
30+
import org.ethereum.core.ImportResult;
3031
import org.ethereum.core.Repository;
3132
import org.ethereum.crypto.Keccak256Helper;
3233
import org.ethereum.datasource.HashMapDB;
@@ -117,7 +118,8 @@ public static void migrateStateToUnitrieIfNeeded(RskContext ctx) throws IOExcept
117118

118119
// this block number has to be validated before the release to ensure the migration works fine for every user
119120
long minimumBlockNumberToMigrate = ctx.getRskSystemProperties().getDatabaseMigrationMinimumHeight();
120-
Block blockToMigrate = ctx.getBlockStore().getBestBlock();
121+
Block bestBlock = ctx.getBlockStore().getBestBlock();
122+
Block blockToMigrate = ctx.getBlockStore().getBlockByHash(bestBlock.getParentHash().getBytes());
121123
if (blockToMigrate == null || blockToMigrate.getNumber() < minimumBlockNumberToMigrate) {
122124
logger.error(
123125
"The database can't be migrated because the node wasn't up to date before upgrading. " +
@@ -127,7 +129,7 @@ public static void migrateStateToUnitrieIfNeeded(RskContext ctx) throws IOExcept
127129
logger.error("Reset database or continue syncing with previous version");
128130
// just opening the db against the unitrie directory creates certain file structure
129131
// we clean that here in case of an error
130-
Files.deleteIfExists(unitrieDatabase);
132+
FileUtil.recursiveDelete(unitrieDatabase.toString());
131133
System.exit(1);
132134
}
133135

@@ -143,11 +145,31 @@ public static void migrateStateToUnitrieIfNeeded(RskContext ctx) throws IOExcept
143145
)
144146
);
145147

146-
unitrieMigrationTool.migrate();
148+
try {
149+
unitrieMigrationTool.migrate();
150+
} catch (RuntimeException e) {
151+
logger.error("Couldn't migrate the database", e);
152+
153+
FileUtil.recursiveDelete(unitrieDatabase.toString());
154+
Files.deleteIfExists(Paths.get(databaseDir, MissingOrchidStorageKeysProvider.MAPDB_FILENAME));
155+
System.exit(1);
156+
}
157+
158+
ctx.getBlockStore().removeBlock(bestBlock);
159+
if (ctx.getBlockchain().tryToConnect(bestBlock) != ImportResult.IMPORTED_BEST) {
160+
logger.error(
161+
"The database can't be migrated because the block {} couldn't be connected after the migration.",
162+
bestBlock.getNumber()
163+
);
164+
// just opening the db against the unitrie directory creates certain file structure
165+
// we clean that here in case of an error
166+
FileUtil.recursiveDelete(unitrieDatabase.toString());
167+
System.exit(1);
168+
}
147169
}
148170

149171
public void migrate() {
150-
logger.info("Migration started");
172+
logger.info("Migration started. It can take up to 15 minutes");
151173
logger.info("Block {}", blockToMigrate.getNumber());
152174
Trie migratedTrie = migrateState(blockToMigrate);
153175
unitrieRepository.flush();
@@ -165,19 +187,7 @@ private Trie migrateState(Block blockToMigrate) {
165187
throw new IllegalStateException(String.format("Stored account state is not consistent with the expected root (%s) for block %d", Hex.toHexString(orchidStateRoot), blockToMigrate.getNumber()));
166188
}
167189

168-
try {
169-
buildPartialUnitrie(orchidAccountsTrie, unitrieRepository);
170-
} catch (MissingContractStorageKeysException e) {
171-
StringBuilder missingStorageKeysMessage = new StringBuilder(
172-
"We have detected an inconsistency in your database and are unable to migrate it automatically.\n" +
173-
"Please visit https://github.com/rsksmart/rskj/issues/452 for information on how to continue.\n" +
174-
"Here is the data you'll need:\n"
175-
);
176-
for (Keccak256 entry : e.getMissingStorageKeys()) {
177-
missingStorageKeysMessage.append(entry.toHexString()).append("\n");
178-
}
179-
logger.error(missingStorageKeysMessage.toString());
180-
}
190+
buildPartialUnitrie(orchidAccountsTrie, unitrieRepository);
181191

182192
byte[] lastStateRoot = unitrieRepository.getRoot();
183193
byte[] orchidMigratedStateRoot = trieConverter.getOrchidAccountTrieRoot(unitrieRepository.getMutableTrie().getTrie());
@@ -195,12 +205,11 @@ private Trie migrateState(Block blockToMigrate) {
195205
return unitrieRepository.getMutableTrie().getTrie();
196206
}
197207

198-
private void buildPartialUnitrie(Trie orchidAccountsTrie, Repository repository) throws MissingContractStorageKeysException {
208+
private void buildPartialUnitrie(Trie orchidAccountsTrie, Repository repository) {
199209
int accountsToLog = 500;
200210
int accountsCounter = 0;
201211
logger.trace("(x = {} accounts): ", accountsToLog);
202212
Iterator<Trie.IterationElement> orchidAccountsTrieIterator = orchidAccountsTrie.getPreOrderIterator();
203-
Collection<Keccak256> missingStorageKeys = new HashSet<>();
204213
while (orchidAccountsTrieIterator.hasNext()) {
205214
Trie.IterationElement orchidAccountsTrieElement = orchidAccountsTrieIterator.next();
206215
TrieKeySlice currentElementExpandedPath = orchidAccountsTrieElement.getNodeKey();
@@ -216,23 +225,16 @@ private void buildPartialUnitrie(Trie orchidAccountsTrie, Repository repository)
216225
byte[] codeHash = oldAccountState.getCodeHash();
217226
byte[] accountStateRoot = oldAccountState.getStateRoot();
218227
if (contractData != null) {
219-
try {
220-
migrateContract(accountAddress, repository, contractData, codeHash, accountStateRoot);
221-
} catch (MissingContractStorageKeysException e) {
222-
missingStorageKeys.addAll(e.getMissingStorageKeys());
223-
}
228+
migrateContract(accountAddress, repository, contractData, codeHash, accountStateRoot);
224229
}
225230
if (accountsCounter % accountsToLog == 0) {
226231
logger.trace("x");
227232
}
228233
}
229234
}
230-
if (!missingStorageKeys.isEmpty()) {
231-
throw new MissingContractStorageKeysException(missingStorageKeys);
232-
}
233235
}
234236

235-
private void migrateContract(RskAddress accountAddress, Repository currentRepository, byte[] contractDataRaw, byte[] accountCodeHash, byte[] stateRoot) throws MissingContractStorageKeysException {
237+
private void migrateContract(RskAddress accountAddress, Repository currentRepository, byte[] contractDataRaw, byte[] accountCodeHash, byte[] stateRoot) {
236238
ContractData contractData = new ContractData(contractDataRaw);
237239

238240
boolean initialized = false;
@@ -260,17 +262,12 @@ private void migrateContract(RskAddress accountAddress, Repository currentReposi
260262
Keccak256 storageKeyHash = new Keccak256(Keccak256Helper.keccak256(rawKey));
261263
keccak256Cache.put(storageKeyHash, storageKey);
262264
}
263-
Collection<Keccak256> missingStorageKeys = new HashSet<>();
264265
Iterator<Trie.IterationElement> inOrderIterator = contractStorageTrie.getInOrderIterator();
265266
while (inOrderIterator.hasNext()) {
266267
Trie.IterationElement iterationElement = inOrderIterator.next();
267268
if (iterationElement.getNode().getValue() != null) {
268269
Keccak256 storageKeyHash = new Keccak256(iterationElement.getNodeKey().encode());
269270
DataWord storageKey = keccak256Cache.computeIfAbsent(storageKeyHash, missingOrchidStorageKeysProvider::getKeccak256PreImage);
270-
if (storageKey == null) {
271-
missingStorageKeys.add(storageKeyHash);
272-
continue;
273-
}
274271

275272
byte[] value = iterationElement.getNode().getValue();
276273
migratedKeysCounter++;
@@ -284,10 +281,6 @@ private void migrateContract(RskAddress accountAddress, Repository currentReposi
284281
currentRepository.addStorageBytes(contractAddress, storageKey, value);
285282
}
286283
}
287-
if (!missingStorageKeys.isEmpty()) {
288-
logger.error("{} keys lost", missingStorageKeys.size());
289-
throw new MissingContractStorageKeysException(missingStorageKeys);
290-
}
291284
}
292285

293286
byte[] code = contractData.getCode();
@@ -484,17 +477,4 @@ public byte[] retrieveValue(byte[] hash) {
484477
public void flush() {
485478
}
486479
}
487-
488-
private static class MissingContractStorageKeysException extends Exception {
489-
490-
private final Collection<Keccak256> missingStorageKeys;
491-
492-
private MissingContractStorageKeysException(Collection<Keccak256> missingStorageKeys) {
493-
this.missingStorageKeys = Collections.unmodifiableCollection(missingStorageKeys);
494-
}
495-
496-
private Collection<Keccak256> getMissingStorageKeys() {
497-
return missingStorageKeys;
498-
}
499-
}
500480
}

0 commit comments

Comments
 (0)