Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AKI-648 validate block timestamp in the block submit api #1109

Merged
merged 2 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modAionImpl/src/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@
exports org.aion.zero.impl.pendingState;
exports org.aion.zero.impl.vm.avm;
exports org.aion.zero.impl.vm.avm.schedule;
exports org.aion.zero.impl.valid;
}
16 changes: 14 additions & 2 deletions modApiServer/src/org/aion/api/server/rpc3/AionChainHolder.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.aion.api.server.rpc3;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
Expand All @@ -24,12 +25,14 @@
import org.aion.zero.impl.types.AionTxInfo;
import org.aion.zero.impl.types.BlockContext;
import org.aion.zero.impl.types.StakingBlock;
import org.aion.zero.impl.valid.FutureBlockRule;

public class AionChainHolder implements ChainHolder {

private final IAionChain chain;//An implementation of AionChain
private final AtomicReference<BlockContext> currentTemplate;
private final AccountManagerInterface accountManager;
private final FutureBlockRule futureBlockRule;

public AionChainHolder(IAionChain chain,
AccountManagerInterface accountManager) {
Expand All @@ -44,6 +47,7 @@ public AionChainHolder(IAionChain chain,
this.chain = chain;
currentTemplate = new AtomicReference<>(null);
this.accountManager = accountManager;
this.futureBlockRule = new FutureBlockRule();
}

@Override
Expand Down Expand Up @@ -90,7 +94,11 @@ public boolean submitSignature(byte[] signature, byte[] sealHash) {
else {
StakingBlock stakingBlock = chain.getBlockchain().getCachingStakingBlockTemplate(sealHash);
stakingBlock.seal(signature, stakingBlock.getHeader().getSigningPublicKey());
final boolean sealed = addNewBlock(stakingBlock);

//AKI-648 reject the block add into the kernel if the timestamp of the block is in the future.
boolean isValidTimestamp = futureBlockRule.validate(stakingBlock.getHeader(), new ArrayList<>());

final boolean sealed = isValidTimestamp && addNewBlock(stakingBlock);

if (sealed) {
logSealedBlock(stakingBlock);
Expand Down Expand Up @@ -138,7 +146,11 @@ public boolean submitBlock(byte[] nonce, byte[] solution, byte[] headerHash) {
return false; // cannot seal a block that does not exist
} else {
bestPowBlock.seal(nonce, solution);
final boolean sealedSuccessfully = addNewBlock(bestPowBlock);

//AKI-648 reject the block add into the kernel if the timestamp of the block is in the future.
boolean isValidTimestamp = futureBlockRule.validate(bestPowBlock.getHeader(), new ArrayList<>());

final boolean sealedSuccessfully = isValidTimestamp && addNewBlock(bestPowBlock);

if (sealedSuccessfully) {
logSealedBlock(bestPowBlock);
Expand Down
106 changes: 105 additions & 1 deletion modApiServer/test/org/aion/api/server/rpc3/MiningRPCImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.function.Function;
import org.aion.api.server.account.AccountManager;
import org.aion.api.server.account.AccountManagerInterface;
import org.aion.crypto.HashUtil;
import org.aion.rpc.errors.RPCExceptions;
Expand All @@ -36,7 +38,11 @@
import org.aion.rpc.types.RPCTypesConverter.SubmissionResultConverter;
import org.aion.rpc.types.RPCTypesConverter.SubmitBlockParamsConverter;
import org.aion.rpc.types.RPCTypesConverter.VoidParamsConverter;
import org.aion.util.types.AddressUtils;
import org.aion.zero.impl.blockchain.AionBlockchainImpl;
import org.aion.zero.impl.blockchain.AionImpl;
import org.aion.zero.impl.types.A0BlockHeader;
import org.aion.zero.impl.types.AionBlock;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
Expand Down Expand Up @@ -177,6 +183,104 @@ public void getDifficulty(){
BigIntConverter.encode(execute(request, RPCTypesConverter.BigIntConverter::decode));
}

@Test
public void testTimestampInSubmitSolution(){

AionBlock blockWithRightTimestamp = new AionBlock(new byte[A0BlockHeader.HASH_BYTE_SIZE]
, AddressUtils.ZERO_ADDRESS
, new byte[A0BlockHeader.BLOOM_BYTE_SIZE]
, new byte[A0BlockHeader.MAX_DIFFICULTY_LENGTH]
, 0
, (System.currentTimeMillis() / 1000)
, new byte[0]
, new byte[1]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new ArrayList<>()
, equihashSolution.toBytes()
, 0
, 0);

AionImpl aionImpl = mock(AionImpl.class);
AionBlockchainImpl chainImpl = mock(AionBlockchainImpl.class);

ChainHolder chainHolder = spy(new AionChainHolder(aionImpl, accountManager));
doReturn(true).when(chainHolder).isUnityForkEnabled();
doReturn(chainImpl).when(aionImpl).getBlockchain();
doReturn(blockWithRightTimestamp).when(chainImpl).getCachingMiningBlockTemplate(blockThatCanBeSealed.toBytes());
doReturn(true).when(chainHolder).addNewBlock(blockWithRightTimestamp);

doCallRealMethod().when(chainHolder).submitBlock(nonce.toBytes(), equihashSolution.toBytes(), blockThatCanBeSealed.toBytes());
rpcMethods= new RPCMethods(chainHolder);

String method = "submitBlock";
Request request1 =
buildRequest(method,
SubmitBlockParamsConverter.encode(new SubmitBlockParams(nonce, equihashSolution, blockThatCanBeSealed)));

SubmissionResult submissionResult = execute(request1, SubmissionResultConverter::decode);
assertNotNull(submissionResult);
assertTrue(submissionResult.result);


// Now we test current timestamp + 1 (for testing the clock drift)
AionBlock blockWithRightTimestamp1 = new AionBlock(new byte[A0BlockHeader.HASH_BYTE_SIZE]
, AddressUtils.ZERO_ADDRESS
, new byte[A0BlockHeader.BLOOM_BYTE_SIZE]
, new byte[A0BlockHeader.MAX_DIFFICULTY_LENGTH]
, 0
, (System.currentTimeMillis() / 1000) + 1
, new byte[0]
, new byte[1]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new ArrayList<>()
, equihashSolution.toBytes()
, 0
, 0);

doReturn(blockWithRightTimestamp1).when(chainImpl).getCachingMiningBlockTemplate(blockThatCanBeSealed.toBytes());
doReturn(true).when(chainHolder).addNewBlock(blockWithRightTimestamp1);

request1 =
buildRequest(method,
SubmitBlockParamsConverter.encode(new SubmitBlockParams(nonce, equihashSolution, blockThatCanBeSealed)));

submissionResult = execute(request1, SubmissionResultConverter::decode);
assertNotNull(submissionResult);
assertTrue(submissionResult.result);

// Now we test the future block timestamp (timestamp + 2)
AionBlock blockWithFutureTimestamp = new AionBlock(new byte[A0BlockHeader.HASH_BYTE_SIZE]
, AddressUtils.ZERO_ADDRESS
, new byte[A0BlockHeader.BLOOM_BYTE_SIZE]
, new byte[A0BlockHeader.MAX_DIFFICULTY_LENGTH]
, 0
, (System.currentTimeMillis() / 1000) + 2
, new byte[0]
, new byte[1]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new byte[A0BlockHeader.HASH_BYTE_SIZE]
, new ArrayList<>()
, equihashSolution.toBytes()
, 0
, 0);

doReturn(blockWithFutureTimestamp).when(chainImpl).getCachingMiningBlockTemplate(blockThatCanBeSealed.toBytes());
doReturn(true).when(chainHolder).addNewBlock(blockWithFutureTimestamp);

request1 =
buildRequest(method,
SubmitBlockParamsConverter.encode(new SubmitBlockParams(nonce, equihashSolution, blockThatCanBeSealed)));

submissionResult = execute(request1, SubmissionResultConverter::decode);
assertNotNull(submissionResult);
assertFalse(submissionResult.result);
}

private <T> T execute(Request request, Function<Object, T> decoder) {
final Object object = RPCServerMethods.execute(request, rpcMethods);
return decoder.apply(object);
Expand Down
110 changes: 109 additions & 1 deletion modApiServer/test/org/aion/api/server/rpc3/StakingRPCImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Function;
import org.aion.api.server.account.AccountManager;
import org.aion.api.server.account.AccountManagerInterface;
import org.aion.rpc.errors.RPCExceptions.BlockTemplateNotFoundRPCException;
import org.aion.rpc.errors.RPCExceptions.UnsupportedUnityFeatureRPCException;
Expand All @@ -31,7 +31,11 @@
import org.aion.rpc.types.RPCTypesConverter.VoidParamsConverter;
import org.aion.types.AionAddress;
import org.aion.util.conversions.Hex;
import org.aion.util.types.AddressUtils;
import org.aion.zero.impl.blockchain.AionBlockchainImpl;
import org.aion.zero.impl.blockchain.AionImpl;
import org.aion.zero.impl.types.StakingBlock;
import org.aion.zero.impl.types.StakingBlockHeader;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
Expand Down Expand Up @@ -142,6 +146,110 @@ public void testCallUnityFeatureBeforeFork(){
}
}

@Test
public void testTimeStampInSubmitSignature(){

StakingBlock blockWithRightTimestamp = new StakingBlock(new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, AddressUtils.ZERO_ADDRESS
, new byte[StakingBlockHeader.BLOOM_BYTE_SIZE]
, new byte[StakingBlockHeader.MAX_DIFFICULTY_LENGTH]
, 0
, (System.currentTimeMillis() / 1000)
, new byte[0]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new ArrayList<>()
, 0
, 0
, validSignature.toBytes()
, validSeed.toBytes()
, validSigningPublicKey.toBytes());

AionImpl aionImpl = mock(AionImpl.class);
AionBlockchainImpl chainImpl = mock(AionBlockchainImpl.class);

chainHolder = spy(new AionChainHolder(aionImpl, accountManager));
doReturn(true).when(chainHolder).isUnityForkEnabled();
doReturn(chainImpl).when(aionImpl).getBlockchain();
doReturn(blockWithRightTimestamp).when(chainImpl).getCachingStakingBlockTemplate(validSealHash.toBytes());
doReturn(true).when(chainHolder).addNewBlock(blockWithRightTimestamp);

doCallRealMethod().when(chainHolder).submitSignature(validSignature.toBytes(), validSealHash.toBytes());
rpcMethods= new RPCMethods(chainHolder);

String method = "submitsignature";

assertTrue(
execute(
new Request(
1,
method,
SubmitSignatureParamsConverter.encode(new SubmitSignatureParams(validSignature, validSealHash)),
VersionType.Version2),
BoolConverter::decode));

// Now we test current timestamp + 1 (for testing the clock drift)
StakingBlock blockWithRightTimestamp1 = new StakingBlock(new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, AddressUtils.ZERO_ADDRESS
, new byte[StakingBlockHeader.BLOOM_BYTE_SIZE]
, new byte[StakingBlockHeader.MAX_DIFFICULTY_LENGTH]
, 0
, (System.currentTimeMillis() / 1000 + 1)
, new byte[0]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new ArrayList<>()
, 0
, 0
, validSignature.toBytes()
, validSeed.toBytes()
, validSigningPublicKey.toBytes());

doReturn(blockWithRightTimestamp1).when(chainImpl).getCachingStakingBlockTemplate(validSealHash.toBytes());
doReturn(true).when(chainHolder).addNewBlock(blockWithRightTimestamp1);

assertTrue(
execute(
new Request(
1,
method,
SubmitSignatureParamsConverter.encode(new SubmitSignatureParams(validSignature, validSealHash)),
VersionType.Version2),
BoolConverter::decode));

// Now we test the future block timestamp (timestamp + 2)
StakingBlock futureBlock = new StakingBlock(new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, AddressUtils.ZERO_ADDRESS
, new byte[StakingBlockHeader.BLOOM_BYTE_SIZE]
, new byte[StakingBlockHeader.MAX_DIFFICULTY_LENGTH]
, 0
, (System.currentTimeMillis() / 1000 + 2)
, new byte[0]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new byte[StakingBlockHeader.HASH_BYTE_SIZE]
, new ArrayList<>()
, 0
, 0
, validSignature.toBytes()
, validSeed.toBytes()
, validSigningPublicKey.toBytes());

doReturn(futureBlock).when(chainImpl).getCachingStakingBlockTemplate(validSealHash.toBytes());
doReturn(true).when(chainHolder).addNewBlock(futureBlock);

assertFalse(
execute(
new Request(
1,
method,
SubmitSignatureParamsConverter.encode(new SubmitSignatureParams(validSignature, validSealHash)),
VersionType.Version2),
BoolConverter::decode));
}

private <T> T execute(Request request, Function<Object, T> extractor) {
return extractor.apply(RPCServerMethods
.execute(request, rpcMethods));
Expand Down
10 changes: 5 additions & 5 deletions tooling/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ The script will:
* and then shut down the kernel.
Once the script has finished running, the custom network is unity ready to be used for testing purposes.

# Launching the staker
# Launching the block signer

After the kernel setup is ready (after running the bootstrap script), the user can boot up the node and run the staker script.
After the kernel setup is ready (after running the bootstrap script), the user can boot up the node and run the block signer script.

The staker can be used directly from the terminal by launching `./launchStaker.sh` in the `externalStaker` folder.
The staker can be used directly from the terminal by launching `./launchBlockSigner.sh` in the `blockSigner` folder.

The script will run the staker using the custom network default settings.
The script will run the blockSigner app using the custom network default settings.

<!--For more details regarding the external staker, please read the `README.md` in the `externalStaker` folder.-->
<!--For more details regarding the blockSigner, please read the README.md in https://github.com/aionnetwork/block_signer-->

Binary file added tooling/blockSigner/block_signer.jar
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/bin/bash
set -x

echo
echo "Launching external staker..."
Expand All @@ -9,8 +8,10 @@ SIGNING_ADDRESS_PRIVATE_KEY="0xcc76648ce8798bc18130bc9d637995e5c42a922ebeab78795
COINBASE_ADDRESS="0xa02df9004be3c4a20aeb50c459212412b1d0a58da3e1ac70ba74dde6b4accf4b"
NODE_IP="127.0.0.1"
NODE_PORT="8545"
NETWORK="amity"
VERBOSE="false"

java -cp "external_staker.jar:lib/*" org.aion.staker.ExternalStaker $SIGNING_ADDRESS_PRIVATE_KEY $COINBASE_ADDRESS $NODE_IP $NODE_PORT
java -jar block_signer.jar $SIGNING_ADDRESS_PRIVATE_KEY $COINBASE_ADDRESS $NETWORK $NODE_IP $NODE_PORT $VERBOSE

echo
echo "Shutting down the external staker..."
Expand Down
Binary file removed tooling/externalStaker/external_staker.jar
Binary file not shown.
Binary file removed tooling/externalStaker/lib/commons-codec-1.11.jar
Binary file not shown.
Binary file removed tooling/externalStaker/lib/ed25519.jar
Binary file not shown.
Binary file removed tooling/externalStaker/lib/gson-2.8.5.jar
Binary file not shown.
Binary file removed tooling/externalStaker/lib/modUtil.jar
Binary file not shown.
Binary file removed tooling/externalStaker/lib/node-test-harness.jar
Binary file not shown.
Binary file removed tooling/externalStaker/lib/offline-signer.jar
Binary file not shown.
Binary file removed tooling/externalStaker/lib/org-aion-avm-api.jar
Binary file not shown.
Binary file removed tooling/externalStaker/lib/org-aion-avm-userlib.jar
Binary file not shown.