Skip to content

Commit

Permalink
Add Beacon Hash validation to Blockchain and Pending State
Browse files Browse the repository at this point in the history
  • Loading branch information
aion-kelvin committed Sep 23, 2019
1 parent 3575926 commit 6b9b8c2
Show file tree
Hide file tree
Showing 6 changed files with 705 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
Expand Down Expand Up @@ -52,6 +53,7 @@
import org.aion.zero.impl.trie.TrieNodeResult;
import org.aion.zero.impl.types.BlockContext;
import org.aion.zero.impl.types.BlockIdentifier;
import org.aion.zero.impl.valid.BeaconHashValidator;
import org.aion.zero.impl.valid.BlockHeaderValidator;
import org.aion.zero.impl.valid.GrandParentBlockHeaderValidator;
import org.aion.zero.impl.valid.ParentBlockHeaderValidator;
Expand Down Expand Up @@ -117,6 +119,7 @@ public class AionBlockchainImpl implements IAionBlockchain {
private final GrandParentBlockHeaderValidator grandParentBlockHeaderValidator;
private final ParentBlockHeaderValidator parentHeaderValidator;
private final BlockHeaderValidator blockHeaderValidator;
public final BeaconHashValidator beaconHashValidator;

/**
* Chain configuration class, because chain configuration may change dependant on the block
Expand Down Expand Up @@ -210,6 +213,31 @@ protected AionBlockchainImpl(
}
this.energyLimitStrategy = config.getEnergyLimitStrategy();

Optional<Long> maybeFork050 = load050ForkNumberFromConfig(CfgAion.inst());
if(! maybeFork050.isPresent()) {
this.beaconHashValidator = new BeaconHashValidator(this,
BeaconHashValidator.FORK_050_DISABLED);
} else {
this.beaconHashValidator = new BeaconHashValidator(this,
maybeFork050.get());
}

}

/**
* Determine fork 0.5.0 fork number from Aion Config.
*
* @param cfgAion configuration
* @return 0.5.0 fork number, if configured; {@link Optional#empty()} otherwise.
* @throws NumberFormatException if "fork0.5.0" present in the config, but not parseable
*/
public static Optional<Long> load050ForkNumberFromConfig(CfgAion cfgAion) {
String fork050Cfg = cfgAion.getFork().getProperties().getProperty("fork0.5.0");
if(fork050Cfg == null) {
return Optional.empty();
} else {
return Optional.of(Long.valueOf(fork050Cfg));
}
}

/**
Expand Down Expand Up @@ -1322,7 +1350,8 @@ private boolean isValid(Block block) {
.anyMatch(
tx ->
!TXValidator.isValid(tx)
|| !TransactionTypeValidator.isValid(tx))) {
|| !TransactionTypeValidator.isValid(tx)
|| !beaconHashValidator.validateTxForBlock(tx, block))) {
LOG.error("Some transactions in the block are invalid");
if (TX_LOG.isDebugEnabled()) {
for (AionTransaction tx : txs) {
Expand Down Expand Up @@ -2180,4 +2209,13 @@ private class State {
void resetPubBestBlock(Block blk) {
pubBestBlock = blk;
}

public boolean isMainChain(byte[] hash, long level) {
return getBlockStore().isMainChain(hash, level);
}

public boolean isMainChain(byte[] hash) {
return getBlockStore().isMainChain(hash);
}

}
21 changes: 21 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/blockchain/IAionBlockchain.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,25 @@ void dropImported(
// BlockIdentifierImpl identifier, int skip, int limit, boolean reverse);

List<byte[]> getListOfBodiesByHashes(List<byte[]> hashes);

/**
* Checks whether a hash is indexed as main chain or side chain.
*
* @param hash the hash for which we check its chain status
* @param level the height at which the block should be indexed
* @return {@code true} if the block is indexed as a main chain block, {@code false} if the
* block is not indexed or is a side chain block
*/
boolean isMainChain(byte[] hash, long level);

/**
* Checks if a hash is indexed as main chain or side chain
*
* @param hash the hash for which we check its chain status
*
* @return {@code true} if the block is indexed as a main chain block, {@code false} if the
* block is not indexed or is a side chain block
*/
public boolean isMainChain(byte[] hash);

}
14 changes: 14 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/db/AionBlockStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,20 @@ public boolean isMainChain(byte[] hash, long level) {
}
}

/**
* Checks if a hash is indexed as main chain or side chain; like
* {@link #isMainChain(byte[], long)}, but less efficient
*
* @param hash the hash for which we check its chain status
*
* @return {@code true} if the block is indexed as a main chain block, {@code false} if the
* block is not indexed or is a side chain block
*/
public boolean isMainChain(byte[] hash) {
Block block = getBlockByHash(hash);
return block == null ? false : isMainChain(hash, block.getNumber());
}

/**
* First checks if the size key is missing or smaller than it should be. If it is incorrect, the
* method attempts to correct it by setting it to the given level.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
Expand Down Expand Up @@ -61,6 +62,7 @@
import org.aion.zero.impl.db.AionRepositoryImpl;
import org.aion.zero.impl.types.AionBlock;
import org.aion.zero.impl.types.AionTxInfo;
import org.aion.zero.impl.valid.BeaconHashValidator;
import org.aion.zero.impl.valid.TXValidator;
import org.aion.zero.impl.valid.TransactionTypeValidator;
import org.aion.base.AionTxExecSummary;
Expand Down Expand Up @@ -145,6 +147,8 @@ public TransactionSortedSet() {

private boolean closeToNetworkBest = true;

private BeaconHashValidator beaconHashValidator;

private long fork040Block = -1;
private boolean fork040Enable = false;

Expand Down Expand Up @@ -343,6 +347,8 @@ private AionPendingStateImpl(CfgAion _cfgAion, AionRepositoryImpl _repository) {
public void init(final AionBlockchainImpl blockchain, boolean test) {

this.blockchain = blockchain;
this.beaconHashValidator = blockchain.beaconHashValidator;

this.best = new AtomicReference<>();
this.test = test;

Expand Down Expand Up @@ -452,7 +458,9 @@ public synchronized TxResponse addPendingTransaction(AionTransaction tx) {
}

public boolean isValid(AionTransaction tx) {
return TXValidator.isValid(tx) && TransactionTypeValidator.isValid(tx);
return TXValidator.isValid(tx)
&& TransactionTypeValidator.isValid(tx)
&& beaconHashValidator.validateTxForPendingState(tx);
}

/**
Expand Down Expand Up @@ -644,7 +652,7 @@ private List<TxResponse> seedProcess(List<AionTransaction> transactions) {
txResponses.add(TxResponse.SUCCESS);
} else {
LOGGER_TX.error(
"tx sig does not match with the tx raw data, tx[{}]", tx.toString());
"tx is not valid: tx[{}]", tx.toString());
txResponses.add(TxResponse.INVALID_TX);
}
}
Expand Down
171 changes: 171 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/valid/BeaconHashValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package org.aion.zero.impl.valid;

import com.google.common.base.Preconditions;
import org.aion.base.AionTransaction;
import org.aion.log.LogEnum;
import org.aion.mcf.blockchain.Block;
import org.aion.util.bytes.ByteUtil;
import org.aion.zero.impl.blockchain.IAionBlockchain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.pqc.crypto.DigestingStateAwareMessageSigner;

/** Validates beacon hash of transaction */
public class BeaconHashValidator {
private final IAionBlockchain blockchain;
private final long fork050Number;
private static final Logger TX_LOG = LoggerFactory.getLogger(LogEnum.TX.name());

/**
* The value to use for fork050Number when calling constructor {@link
* #BeaconHashValidator(IAionBlockchain, long)} if beacon hash validation is
* disabled.
*/
public static final long FORK_050_DISABLED = Long.MAX_VALUE;

/**
* Constructor.
*
* @param blockchain blockchain
* @param fork050Number the block number at which hard fork 0.5.0 takes effect. Use
* {@link #FORK_050_DISABLED} if validation should be disabled;
* i.e. behave as if fork050 never happens and always return true
* when {@link #validateTxForBlock(AionTransaction, Block)} or
* {@link #validateTxForPendingState(AionTransaction)} is called
*/
public BeaconHashValidator(IAionBlockchain blockchain, long fork050Number) {
Preconditions.checkNotNull(blockchain, "Blockstore can't be null");
Preconditions.checkArgument(fork050Number >= 0, "Invalid fork0.5.0 block number: must be >= 0");

this.blockchain = blockchain;
this.fork050Number = fork050Number;
}

/**
* Evaluate if a transaction's (possibly null) beacon hash is valid for a particular block.
*
* If the block has not reached the fork 0.5.0 block number, or fork 0.5.0 is disabled,
* the transaction must not provide a beacon hash (i.e. must be null), otherwise it is
* not valid.
*
* If the block has reached the fork 0.5.0 block number, the transaction is valid if
* either the beacon hash is absent (i.e. is null) or the beacon hash is the hash of a
* block on the chain that {@link Block#getParentHash()} is on.
*
* @param tx transaction to validate
* @param block block that the transaction is in
* @return whether beacon hash of transaction is valid
*/
public boolean validateTxForBlock(AionTransaction tx, Block block) {
long t0 = System.nanoTime();
try {
Preconditions.checkNotNull(tx, "AionTransaction must not be null");
Preconditions.checkNotNull(tx, "Block must not be null");

byte[] beaconHash = tx.getBeaconHash();
if (beaconHash == null) {
return true;
}

if (!isFork050Active(block.getNumber()) && beaconHash != null) {
return false;
}

if (blockchain.isMainChain(block.getParentHash())) {
boolean isMainChain = blockchain.isMainChain(beaconHash);
TX_LOG.debug(String.format(
"BeaconHashValidator#validateTxForBlock: isMainChain(0x%s) = %b.",
ByteUtil.toHexString(beaconHash), isMainChain));
return isMainChain;
} else {
boolean onCorrectSidechain = checkSideChain(beaconHash,
blockchain.getBlockByHash(block.getParentHash()));
TX_LOG.debug(String.format(
"BeaconHashValidator#validateTxForBlock: checkSideChain(0x%s, 0x%s) = %b.",
ByteUtil.toHexString(beaconHash),
ByteUtil.toHexString(block.getParentHash()),
onCorrectSidechain));
return onCorrectSidechain;
}
} finally {
TX_LOG.debug("BeaconHashValidator#validateTxForBlock: tx {} took {} usec",
ByteUtil.toHexString(block.getHash()),
(System.nanoTime() - t0)/1000);
}
}

/**
* Evaluate if a transaction's (possibly null) beacon hash is valid for pending state.
*
* If the main chain's (best block + 1) has not reached the fork 0.5.0 block number, or
* fork 0.5.0 is disabled, the transaction must not provide a beacon hash (i.e.
* must be null), otherwise it is not valid.
*
* If the main chain's (best block + 1) has reached the fork 0.5.0 block number, the
* transaction is valid if either the beacon hash is absent (i.e. is null) or the beacon
* hash is on the main chain.
*
* @param tx transaction to validate
* @return whether beacon hash of transaction is valid
*/
public boolean validateTxForPendingState(AionTransaction tx) {
long t0 = System.nanoTime();
try {
Preconditions.checkNotNull(tx, "AionTransaction must not be null");

byte[] beaconHash = tx.getBeaconHash();
if (beaconHash == null) {
return true;
}

// the next block number might be larger than (current best + 1), but
// we will tolerate false negatives
long minNextBlockNumber = blockchain.getBestBlock().getNumber() + 1;
if (!isFork050Active(minNextBlockNumber) && beaconHash != null) {
return false;
}

boolean isMainChain = blockchain.isMainChain(beaconHash);
TX_LOG.debug(String.format("BeaconHashValidator#validate: isMainChain(0x%s) = %b.",
ByteUtil.toHexString(beaconHash), isMainChain));
return isMainChain;
} finally {
TX_LOG.debug("BeaconHashValidator#validateTxForPendingState: took {} usec",
(System.nanoTime() - t0)/1000);
}
}

private boolean isFork050Active(long blockNumber) {
return blockNumber >= fork050Number && fork050Number != FORK_050_DISABLED;
}

private boolean checkSideChain(byte[] beaconHash, Block sideChainHead) {
if(blockchain.getBlockByHash(beaconHash) == null) {
return false;
}

Block cur = sideChainHead;
long beaconNumber = blockchain.getBlockByHash(beaconHash).getNumber();

while(null != cur) {
if(beaconHash.equals(cur.getHash())) {
return true;
} else if(beaconNumber >= cur.getNumber()) {
TX_LOG.debug("BeaconHashValidator#checkSideChain: reached level of beacon hash block {}",
ByteUtil.toHexString(cur.getHash()));
return false;
} else if(blockchain.isMainChain(cur.getHash(), cur.getNumber())) {
TX_LOG.debug("BeaconHashValidator#checkSideChain: found fork point to mainchain at {}",
ByteUtil.toHexString(cur.getHash()));
return blockchain.isMainChain(beaconHash);
}

cur = blockchain.getBlockByHash(cur.getParentHash());
}

// should not actually reach this since genesis block is a main chain block
// log an error - probably a bug
TX_LOG.warn("BeaconHashValidator#checkSideChain: reached orphaned block without finding mainchain");
return false;
}
}
Loading

0 comments on commit 6b9b8c2

Please sign in to comment.