forked from Qortal/qortal
Chain history cleanups to correct legacy balance bugs.
This commit is contained in:
parent
f808e80045
commit
cd792fff55
@ -1213,10 +1213,18 @@ public class Block {
|
||||
// Apply fix for block 212937 but fix will be rolled back before we exit method
|
||||
Block212937.processFix(this);
|
||||
}
|
||||
else if (this.blockData.getHeight() == 1333492) {
|
||||
// Apply fix for block 1333492 but fix will be rolled back before we exit method
|
||||
Block1333492.processFix(this);
|
||||
}
|
||||
else if (InvalidNameRegistrationBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||
// Apply fix for affected name registration blocks, but fix will be rolled back before we exit method
|
||||
InvalidNameRegistrationBlocks.processFix(this);
|
||||
}
|
||||
else if (InvalidBalanceBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||
// Apply fix for affected balance blocks, but fix will be rolled back before we exit method
|
||||
InvalidBalanceBlocks.processFix(this);
|
||||
}
|
||||
|
||||
for (Transaction transaction : this.getTransactions()) {
|
||||
TransactionData transactionData = transaction.getTransactionData();
|
||||
@ -1464,12 +1472,21 @@ public class Block {
|
||||
// Distribute block rewards, including transaction fees, before transactions processed
|
||||
processBlockRewards();
|
||||
|
||||
if (this.blockData.getHeight() == 212937)
|
||||
if (this.blockData.getHeight() == 212937) {
|
||||
// Apply fix for block 212937
|
||||
Block212937.processFix(this);
|
||||
|
||||
else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height())
|
||||
}
|
||||
else if (this.blockData.getHeight() == 1333492) {
|
||||
// Apply fix for block 1333492
|
||||
Block1333492.processFix(this);
|
||||
}
|
||||
else if (InvalidBalanceBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||
// Apply fix for affected balance blocks
|
||||
InvalidBalanceBlocks.processFix(this);
|
||||
}
|
||||
else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) {
|
||||
SelfSponsorshipAlgoV1Block.processAccountPenalties(this);
|
||||
}
|
||||
}
|
||||
|
||||
// We're about to (test-)process a batch of transactions,
|
||||
@ -1726,12 +1743,21 @@ public class Block {
|
||||
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
|
||||
this.cachedExpandedAccounts = null;
|
||||
|
||||
if (this.blockData.getHeight() == 212937)
|
||||
if (this.blockData.getHeight() == 212937) {
|
||||
// Revert fix for block 212937
|
||||
Block212937.orphanFix(this);
|
||||
|
||||
else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height())
|
||||
}
|
||||
else if (this.blockData.getHeight() == 1333492) {
|
||||
// Revert fix for block 1333492
|
||||
Block1333492.orphanFix(this);
|
||||
}
|
||||
else if (InvalidBalanceBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||
// Revert fix for affected balance blocks
|
||||
InvalidBalanceBlocks.orphanFix(this);
|
||||
}
|
||||
else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) {
|
||||
SelfSponsorshipAlgoV1Block.orphanAccountPenalties(this);
|
||||
}
|
||||
|
||||
// Block rewards, including transaction fees, removed after transactions undone
|
||||
orphanBlockRewards();
|
||||
|
101
src/main/java/org/qortal/block/Block1333492.java
Normal file
101
src/main/java/org/qortal/block/Block1333492.java
Normal file
@ -0,0 +1,101 @@
|
||||
package org.qortal.block;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||
import org.qortal.data.account.AccountBalanceData;
|
||||
import org.qortal.repository.DataException;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.UnmarshalException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Block 1333492
|
||||
* <p>
|
||||
* As described in InvalidBalanceBlocks.java, legacy bugs caused a small drift in account balances.
|
||||
* This block adjusts any remaining differences between a clean reindex/resync and a recent bootstrap.
|
||||
* <p>
|
||||
* The block height 1333492 isn't significant - it's simply the height of a recent bootstrap at the
|
||||
* time of development, so that the account balances could be accessed and compared against the same
|
||||
* block in a reindexed db.
|
||||
* <p>
|
||||
* As with InvalidBalanceBlocks, the discrepancies are insignificant, except for a single
|
||||
* account which has a 3.03 QORT discrepancy. This was due to the account being the first recipient
|
||||
* of a name sale and encountering an early bug in this area.
|
||||
* <p>
|
||||
* The total offset for this block is 3.02816514 QORT.
|
||||
*/
|
||||
public final class Block1333492 {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Block1333492.class);
|
||||
private static final String ACCOUNT_DELTAS_SOURCE = "block-1333492-deltas.json";
|
||||
|
||||
private static final List<AccountBalanceData> accountDeltas = readAccountDeltas();
|
||||
|
||||
private Block1333492() {
|
||||
/* Do not instantiate */
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<AccountBalanceData> readAccountDeltas() {
|
||||
Unmarshaller unmarshaller;
|
||||
|
||||
try {
|
||||
// Create JAXB context aware of classes we need to unmarshal
|
||||
JAXBContext jc = JAXBContextFactory.createContext(new Class[] {
|
||||
AccountBalanceData.class
|
||||
}, null);
|
||||
|
||||
// Create unmarshaller
|
||||
unmarshaller = jc.createUnmarshaller();
|
||||
|
||||
// Set the unmarshaller media type to JSON
|
||||
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
|
||||
|
||||
// Tell unmarshaller that there's no JSON root element in the JSON input
|
||||
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
|
||||
} catch (JAXBException e) {
|
||||
String message = "Failed to setup unmarshaller to read block 1333492 deltas";
|
||||
LOGGER.error(message, e);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
|
||||
ClassLoader classLoader = BlockChain.class.getClassLoader();
|
||||
InputStream in = classLoader.getResourceAsStream(ACCOUNT_DELTAS_SOURCE);
|
||||
StreamSource jsonSource = new StreamSource(in);
|
||||
|
||||
try {
|
||||
// Attempt to unmarshal JSON stream to BlockChain config
|
||||
return (List<AccountBalanceData>) unmarshaller.unmarshal(jsonSource, AccountBalanceData.class).getValue();
|
||||
} catch (UnmarshalException e) {
|
||||
String message = "Failed to parse block 1333492 deltas";
|
||||
LOGGER.error(message, e);
|
||||
throw new RuntimeException(message, e);
|
||||
} catch (JAXBException e) {
|
||||
String message = "Unexpected JAXB issue while processing block 1333492 deltas";
|
||||
LOGGER.error(message, e);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void processFix(Block block) throws DataException {
|
||||
block.repository.getAccountRepository().modifyAssetBalances(accountDeltas);
|
||||
}
|
||||
|
||||
public static void orphanFix(Block block) throws DataException {
|
||||
// Create inverse deltas
|
||||
List<AccountBalanceData> inverseDeltas = accountDeltas.stream()
|
||||
.map(delta -> new AccountBalanceData(delta.getAddress(), delta.getAssetId(), 0 - delta.getBalance()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
block.repository.getAccountRepository().modifyAssetBalances(inverseDeltas);
|
||||
}
|
||||
|
||||
}
|
134
src/main/java/org/qortal/block/InvalidBalanceBlocks.java
Normal file
134
src/main/java/org/qortal/block/InvalidBalanceBlocks.java
Normal file
@ -0,0 +1,134 @@
|
||||
package org.qortal.block;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||
import org.qortal.data.account.AccountBalanceData;
|
||||
import org.qortal.repository.DataException;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.UnmarshalException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* Due to various bugs - which have been fixed - a small amount of balance drift occurred
|
||||
* in the chainstate of running nodes and bootstraps, when compared with a clean sync from genesis.
|
||||
* This resulted in a significant number of invalid transactions in the chain history due to
|
||||
* subtle balance discrepancies. The sum of all discrepancies that resulted in an invalid
|
||||
* transaction is 0.00198322 QORT, so despite the large quantity of transactions, they
|
||||
* represent an insignificant amount when summed.
|
||||
* <p>
|
||||
* This class is responsible for retroactively fixing all the past transactions which
|
||||
* are invalid due to the balance discrepancies.
|
||||
*/
|
||||
|
||||
|
||||
public final class InvalidBalanceBlocks {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(InvalidBalanceBlocks.class);
|
||||
|
||||
private static final String ACCOUNT_DELTAS_SOURCE = "invalid-transaction-balance-deltas.json";
|
||||
|
||||
private static final List<AccountBalanceData> accountDeltas = readAccountDeltas();
|
||||
private static final List<Integer> affectedHeights = getAffectedHeights();
|
||||
|
||||
private InvalidBalanceBlocks() {
|
||||
/* Do not instantiate */
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<AccountBalanceData> readAccountDeltas() {
|
||||
Unmarshaller unmarshaller;
|
||||
|
||||
try {
|
||||
// Create JAXB context aware of classes we need to unmarshal
|
||||
JAXBContext jc = JAXBContextFactory.createContext(new Class[] {
|
||||
AccountBalanceData.class
|
||||
}, null);
|
||||
|
||||
// Create unmarshaller
|
||||
unmarshaller = jc.createUnmarshaller();
|
||||
|
||||
// Set the unmarshaller media type to JSON
|
||||
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
|
||||
|
||||
// Tell unmarshaller that there's no JSON root element in the JSON input
|
||||
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
|
||||
} catch (JAXBException e) {
|
||||
String message = "Failed to setup unmarshaller to read block 212937 deltas";
|
||||
LOGGER.error(message, e);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
|
||||
ClassLoader classLoader = BlockChain.class.getClassLoader();
|
||||
InputStream in = classLoader.getResourceAsStream(ACCOUNT_DELTAS_SOURCE);
|
||||
StreamSource jsonSource = new StreamSource(in);
|
||||
|
||||
try {
|
||||
// Attempt to unmarshal JSON stream to BlockChain config
|
||||
return (List<AccountBalanceData>) unmarshaller.unmarshal(jsonSource, AccountBalanceData.class).getValue();
|
||||
} catch (UnmarshalException e) {
|
||||
String message = "Failed to parse balance deltas";
|
||||
LOGGER.error(message, e);
|
||||
throw new RuntimeException(message, e);
|
||||
} catch (JAXBException e) {
|
||||
String message = "Unexpected JAXB issue while processing balance deltas";
|
||||
LOGGER.error(message, e);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Integer> getAffectedHeights() {
|
||||
List<Integer> heights = new ArrayList<>();
|
||||
for (AccountBalanceData accountBalanceData : accountDeltas) {
|
||||
if (!heights.contains(accountBalanceData.getHeight())) {
|
||||
heights.add(accountBalanceData.getHeight());
|
||||
}
|
||||
}
|
||||
return heights;
|
||||
}
|
||||
|
||||
private static List<AccountBalanceData> getAccountDeltasAtHeight(int height) {
|
||||
return accountDeltas.stream().filter(a -> a.getHeight() == height).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static boolean isAffectedBlock(int height) {
|
||||
return affectedHeights.contains(Integer.valueOf(height));
|
||||
}
|
||||
|
||||
public static void processFix(Block block) throws DataException {
|
||||
Integer blockHeight = block.getBlockData().getHeight();
|
||||
List<AccountBalanceData> deltas = getAccountDeltasAtHeight(blockHeight);
|
||||
if (deltas == null) {
|
||||
throw new DataException(String.format("Unable to lookup invalid balance data for block height %d", blockHeight));
|
||||
}
|
||||
|
||||
block.repository.getAccountRepository().modifyAssetBalances(deltas);
|
||||
|
||||
LOGGER.info("Applied balance patch for block {}", blockHeight);
|
||||
}
|
||||
|
||||
public static void orphanFix(Block block) throws DataException {
|
||||
Integer blockHeight = block.getBlockData().getHeight();
|
||||
List<AccountBalanceData> deltas = getAccountDeltasAtHeight(blockHeight);
|
||||
if (deltas == null) {
|
||||
throw new DataException(String.format("Unable to lookup invalid balance data for block height %d", blockHeight));
|
||||
}
|
||||
|
||||
// Create inverse delta(s)
|
||||
for (AccountBalanceData accountBalanceData : deltas) {
|
||||
AccountBalanceData inverseBalanceData = new AccountBalanceData(accountBalanceData.getAddress(), accountBalanceData.getAssetId(), -accountBalanceData.getBalance());
|
||||
block.repository.getAccountRepository().modifyAssetBalances(List.of(inverseBalanceData));
|
||||
}
|
||||
|
||||
LOGGER.info("Reverted balance patch for block {}", blockHeight);
|
||||
}
|
||||
|
||||
}
|
6106
src/main/resources/block-1333492-deltas.json
Normal file
6106
src/main/resources/block-1333492-deltas.json
Normal file
File diff suppressed because it is too large
Load Diff
1796
src/main/resources/invalid-transaction-balance-deltas.json
Normal file
1796
src/main/resources/invalid-transaction-balance-deltas.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user