mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-10 15:01:21 +00:00
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
|
// Apply fix for block 212937 but fix will be rolled back before we exit method
|
||||||
Block212937.processFix(this);
|
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())) {
|
else if (InvalidNameRegistrationBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||||
// Apply fix for affected name registration blocks, but fix will be rolled back before we exit method
|
// Apply fix for affected name registration blocks, but fix will be rolled back before we exit method
|
||||||
InvalidNameRegistrationBlocks.processFix(this);
|
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()) {
|
for (Transaction transaction : this.getTransactions()) {
|
||||||
TransactionData transactionData = transaction.getTransactionData();
|
TransactionData transactionData = transaction.getTransactionData();
|
||||||
@ -1464,12 +1472,21 @@ public class Block {
|
|||||||
// Distribute block rewards, including transaction fees, before transactions processed
|
// Distribute block rewards, including transaction fees, before transactions processed
|
||||||
processBlockRewards();
|
processBlockRewards();
|
||||||
|
|
||||||
if (this.blockData.getHeight() == 212937)
|
if (this.blockData.getHeight() == 212937) {
|
||||||
// Apply fix for block 212937
|
// Apply fix for block 212937
|
||||||
Block212937.processFix(this);
|
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);
|
SelfSponsorshipAlgoV1Block.processAccountPenalties(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're about to (test-)process a batch of transactions,
|
// 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.
|
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
|
||||||
this.cachedExpandedAccounts = null;
|
this.cachedExpandedAccounts = null;
|
||||||
|
|
||||||
if (this.blockData.getHeight() == 212937)
|
if (this.blockData.getHeight() == 212937) {
|
||||||
// Revert fix for block 212937
|
// Revert fix for block 212937
|
||||||
Block212937.orphanFix(this);
|
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);
|
SelfSponsorshipAlgoV1Block.orphanAccountPenalties(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Block rewards, including transaction fees, removed after transactions undone
|
// Block rewards, including transaction fees, removed after transactions undone
|
||||||
orphanBlockRewards();
|
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…
x
Reference in New Issue
Block a user