Skip to content

Commit

Permalink
Merge pull request #847 from aionnetwork/sync-blocks-pr
Browse files Browse the repository at this point in the history
Blocks transfer functionality for fast sync
  • Loading branch information
AionJayT committed Mar 21, 2019
2 parents 2b933db + fe30ac3 commit fe7a3ea
Show file tree
Hide file tree
Showing 30 changed files with 3,221 additions and 21 deletions.
90 changes: 90 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/AionBlockchainImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicLong;
Expand All @@ -35,6 +36,7 @@
import org.aion.interfaces.db.RepositoryCache;
import org.aion.log.AionLoggerFactory;
import org.aion.log.LogEnum;
import org.aion.mcf.core.FastImportResult;
import org.aion.mcf.core.ImportResult;
import org.aion.mcf.db.IBlockStorePow;
import org.aion.mcf.db.TransactionStore;
Expand Down Expand Up @@ -321,6 +323,11 @@ public AionBlock getBlockByNumber(long blockNr) {
return getBlockStore().getChainBlockByNumber(blockNr);
}

@Override
public List<AionBlock> getBlocksByRange(long first, long last) {
return getBlockStore().getBlocksByRange(first, last);
}

@Override
/* NOTE: only returns receipts from the main chain
*/
Expand Down Expand Up @@ -557,6 +564,89 @@ public boolean isPruneRestricted(long blockNumber) {
return blockNumber < bestBlockNumber.get() - repository.getPruneBlockCount() + 1;
}

/**
* Import block without validity checks and creating the state. Cannot be used for storing the
* pivot which will not have a parent present in the database.
*
* @param block the block to be imported
* @return a result describing the status of the attempted import
*/
public synchronized FastImportResult tryFastImport(final AionBlock block) {
if (block == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Fast sync import attempted with null block or header.");
}
return FastImportResult.INVALID_BLOCK;
}
if (block.getTimestamp()
> (System.currentTimeMillis() / THOUSAND_MS
+ this.chainConfiguration.getConstants().getClockDriftBufferTime())) {
if (LOG.isDebugEnabled()) {
LOG.debug(
"Block {} invalid due to timestamp {}.",
block.getShortHash(),
block.getTimestamp());
}
return FastImportResult.INVALID_BLOCK;
}

// check that the block is not already known
AionBlock known = getBlockStore().getBlockByHash(block.getHash());
if (known != null && known.getNumber() == block.getNumber()) {
return FastImportResult.KNOWN;
}

// a child must be present to import the parent
AionBlock child = getBlockStore().getChainBlockByNumber(block.getNumber() + 1);
if (child == null || !Arrays.equals(child.getParentHash(), block.getHash())) {
return FastImportResult.NO_CHILD;
} else {
// the total difficulty will be updated after the chain is complete
getBlockStore().saveBlock(block, ZERO, true);

if (LOG.isDebugEnabled()) {
LOG.debug(
"Fast sync block saved: number: {}, hash: {}, child: {}",
block.getNumber(),
block.getShortHash(),
child.getShortHash());
}
return FastImportResult.IMPORTED;
}
}

/**
* Walks though the ancestor blocks starting with the given hash to determine if there is an
* ancestor missing from storage. Returns the ancestor's hash if one is found missing or {@code
* null} when the history is complete, i.e. no missing ancestors exist.
*
* @param block the first block to be checked if present in the repository
* @return the ancestor's hash and height if one is found missing or {@code null} when the
* history is complete
* @throws NullPointerException when given a null block as input
*/
public Pair<ByteArrayWrapper, Long> findMissingAncestor(AionBlock block) {
Objects.requireNonNull(block);

// initialize with given parameter
byte[] currentHash = block.getHash();
long currentNumber = block.getNumber();

AionBlock known = getBlockStore().getBlockByHash(currentHash);

while (known != null && known.getNumber() > 0) {
currentHash = known.getParentHash();
currentNumber--;
known = getBlockStore().getBlockByHash(currentHash);
}

if (known == null) {
return Pair.of(ByteArrayWrapper.wrap(currentHash), currentNumber);
} else {
return null;
}
}

public synchronized ImportResult tryToConnect(final AionBlock block) {
return tryToConnectInternal(block, System.currentTimeMillis() / THOUSAND_MS);
}
Expand Down
8 changes: 8 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/SystemExitCodes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.aion.zero.impl;

/** Error coded to be used when calling {@link System#exit(int)} from the kernel. */
public class SystemExitCodes {

public static final int NORMAL = 0;
public static final int OUT_OF_DISK_SPACE = 1;
}
16 changes: 16 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/core/IAionBlockchain.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ BlockContext createNewBlockContext(

AionBlock getBlockByNumber(long num);

/**
* Returns a range of main chain blocks.
*
* @param first the height of the first block in the requested range; this block must exist in
* the blockchain and be above the genesis to return a non-null output
* @param last the height of the last block in the requested range; when requesting blocks in
* ascending order the last element will be substituted with the best block if its height is
* above the best known block
* @return a list containing consecutive main chain blocks with heights ranging according to the
* given parameters; or {@code null} in case of errors or illegal request
* @apiNote The blocks must be added to the list in the order that they are requested. If {@code
* first > last} the blocks are returned in descending order of their height, otherwise when
* {@code first < last} the blocks are returned in ascending order of their height.
*/
List<AionBlock> getBlocksByRange(long first, long last);

/**
* Recovery functionality for rebuilding the world state.
*
Expand Down
98 changes: 98 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/db/AionBlockStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
Expand Down Expand Up @@ -241,6 +242,103 @@ public AionBlock getChainBlockByNumber(long number) {
}
}

/**
* Returns a range of main chain blocks.
*
* @param first the height of the first block in the requested range; this block must exist in
* the blockchain and be above the genesis to return a non-null output
* @param last the height of the last block in the requested range; when requesting blocks in
* ascending order the last element will be substituted with the best block if its height is
* above the best known block
* @return a list containing consecutive main chain blocks with heights ranging according to the
* given parameters; or {@code null} in case of errors or illegal request
* @apiNote The blocks must be added to the list in the order that they are requested. If {@code
* first > last} the blocks are returned in descending order of their height, otherwise when
* {@code first < last} the blocks are returned in ascending order of their height.
*/
public List<AionBlock> getBlocksByRange(long first, long last) {
if (first <= 0L) {
return null;
}

lock.readLock().lock();

try {
AionBlock block = getChainBlockByNumber(first);
if (block == null) {
// invalid request
return null;
}

if (first == last) {
return List.of(block);
} else if (first > last) { // first is highest -> can query directly by parent hash
List<AionBlock> blocks = new ArrayList<>();
blocks.add(block);

for (long i = first - 1; i >= (last > 0 ? last : 1); i--) {
block = getBlockByHash(block.getParentHash());
if (block == null) {
// the block should have been stored but null was returned above
LOG.error(
"Encountered a kernel database corruption: cannot find block at level {} in data store.",
i);
return null; // stops at any invalid data
} else {
blocks.add(block);
}
}
return blocks;
} else { // last is highest
LinkedList<AionBlock> blocks = new LinkedList<>();
AionBlock lastBlock = getChainBlockByNumber(last);

if (lastBlock == null) { // assuming height was above best block
// attempt to get best block
lastBlock = getBestBlock();
if (lastBlock == null) {
LOG.error(
"Encountered a kernel database corruption: cannot find best block in data store.");
// invalid data store
return null;
} else if (last < lastBlock.getNumber()) {
// the block should have been stored but null was returned above
LOG.error(
"Encountered a kernel database corruption: cannot find block at level {} in data store.",
last);
// invalid data store
return null;
}
}
// the block was not null
// or it was higher than the best block and replaced with the best block

// building existing range
blocks.addFirst(lastBlock);
long newLast = lastBlock.getNumber();
for (long i = newLast - 1; i > first; i--) {
lastBlock = getBlockByHash(lastBlock.getParentHash());
if (lastBlock == null) {
LOG.error(
"Encountered a kernel database corruption: cannot find block at level {} in data store.",
i);
return null; // stops at any invalid data
} else {
// always adding at the beginning of the list
// to return the expected order of blocks
blocks.addFirst(lastBlock);
}
}

// adding the initial block
blocks.addFirst(block);
return blocks;
}
} finally {
lock.readLock().unlock();
}
}

@SuppressWarnings("Duplicates")
public Map.Entry<AionBlock, BigInteger> getChainBlockByNumberWithTotalDifficulty(long number) {
lock.readLock().lock();
Expand Down
4 changes: 4 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/sync/Act.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ public final class Act {
public static final byte REQUEST_TRIE_DATA = 8;

public static final byte RESPONSE_TRIE_DATA = 9;

public static final byte REQUEST_BLOCKS = 12;

public static final byte RESPONSE_BLOCKS = 13;
}
Loading

0 comments on commit fe7a3ea

Please sign in to comment.