diff --git a/modAionImpl/src/module-info.java b/modAionImpl/src/module-info.java index 73df9dda88..97821676e0 100644 --- a/modAionImpl/src/module-info.java +++ b/modAionImpl/src/module-info.java @@ -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; } diff --git a/modApiServer/src/org/aion/api/server/rpc3/AionChainHolder.java b/modApiServer/src/org/aion/api/server/rpc3/AionChainHolder.java index 8f2e0a28ea..493516894c 100644 --- a/modApiServer/src/org/aion/api/server/rpc3/AionChainHolder.java +++ b/modApiServer/src/org/aion/api/server/rpc3/AionChainHolder.java @@ -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; @@ -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 currentTemplate; private final AccountManagerInterface accountManager; + private final FutureBlockRule futureBlockRule; public AionChainHolder(IAionChain chain, AccountManagerInterface accountManager) { @@ -44,6 +47,7 @@ public AionChainHolder(IAionChain chain, this.chain = chain; currentTemplate = new AtomicReference<>(null); this.accountManager = accountManager; + this.futureBlockRule = new FutureBlockRule(); } @Override @@ -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); @@ -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); diff --git a/modApiServer/test/org/aion/api/server/rpc3/MiningRPCImplTest.java b/modApiServer/test/org/aion/api/server/rpc3/MiningRPCImplTest.java index d5a69defd4..94634de985 100644 --- a/modApiServer/test/org/aion/api/server/rpc3/MiningRPCImplTest.java +++ b/modApiServer/test/org/aion/api/server/rpc3/MiningRPCImplTest.java @@ -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; @@ -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; @@ -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 execute(Request request, Function decoder) { final Object object = RPCServerMethods.execute(request, rpcMethods); return decoder.apply(object); diff --git a/modApiServer/test/org/aion/api/server/rpc3/StakingRPCImplTest.java b/modApiServer/test/org/aion/api/server/rpc3/StakingRPCImplTest.java index 6ac3d3b96a..7bf0bc5c71 100644 --- a/modApiServer/test/org/aion/api/server/rpc3/StakingRPCImplTest.java +++ b/modApiServer/test/org/aion/api/server/rpc3/StakingRPCImplTest.java @@ -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; @@ -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; @@ -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 execute(Request request, Function extractor) { return extractor.apply(RPCServerMethods .execute(request, rpcMethods)); diff --git a/tooling/README.md b/tooling/README.md index 3c92655e71..c3ebfd43f9 100644 --- a/tooling/README.md +++ b/tooling/README.md @@ -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. - + diff --git a/tooling/blockSigner/block_signer.jar b/tooling/blockSigner/block_signer.jar new file mode 100644 index 0000000000..498753daa0 Binary files /dev/null and b/tooling/blockSigner/block_signer.jar differ diff --git a/tooling/externalStaker/launchStaker.sh b/tooling/blockSigner/launchBlockSigner.sh similarity index 70% rename from tooling/externalStaker/launchStaker.sh rename to tooling/blockSigner/launchBlockSigner.sh index 41e8c4b7d5..c7fe4f45b9 100755 --- a/tooling/externalStaker/launchStaker.sh +++ b/tooling/blockSigner/launchBlockSigner.sh @@ -1,5 +1,4 @@ #!/bin/bash -set -x echo echo "Launching external staker..." @@ -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..." diff --git a/tooling/externalStaker/external_staker.jar b/tooling/externalStaker/external_staker.jar deleted file mode 100755 index bebf055851..0000000000 Binary files a/tooling/externalStaker/external_staker.jar and /dev/null differ diff --git a/tooling/externalStaker/lib/commons-codec-1.11.jar b/tooling/externalStaker/lib/commons-codec-1.11.jar deleted file mode 100755 index 22451206dd..0000000000 Binary files a/tooling/externalStaker/lib/commons-codec-1.11.jar and /dev/null differ diff --git a/tooling/externalStaker/lib/ed25519.jar b/tooling/externalStaker/lib/ed25519.jar deleted file mode 100755 index 58520ddd0f..0000000000 Binary files a/tooling/externalStaker/lib/ed25519.jar and /dev/null differ diff --git a/tooling/externalStaker/lib/gson-2.8.5.jar b/tooling/externalStaker/lib/gson-2.8.5.jar deleted file mode 100755 index 0d5baf3fa7..0000000000 Binary files a/tooling/externalStaker/lib/gson-2.8.5.jar and /dev/null differ diff --git a/tooling/externalStaker/lib/modUtil.jar b/tooling/externalStaker/lib/modUtil.jar deleted file mode 100755 index 8665e45296..0000000000 Binary files a/tooling/externalStaker/lib/modUtil.jar and /dev/null differ diff --git a/tooling/externalStaker/lib/node-test-harness.jar b/tooling/externalStaker/lib/node-test-harness.jar deleted file mode 100755 index 17d8cd6f71..0000000000 Binary files a/tooling/externalStaker/lib/node-test-harness.jar and /dev/null differ diff --git a/tooling/externalStaker/lib/offline-signer.jar b/tooling/externalStaker/lib/offline-signer.jar deleted file mode 100755 index c6fae6d605..0000000000 Binary files a/tooling/externalStaker/lib/offline-signer.jar and /dev/null differ diff --git a/tooling/externalStaker/lib/org-aion-avm-api.jar b/tooling/externalStaker/lib/org-aion-avm-api.jar deleted file mode 100755 index c1f08006b1..0000000000 Binary files a/tooling/externalStaker/lib/org-aion-avm-api.jar and /dev/null differ diff --git a/tooling/externalStaker/lib/org-aion-avm-userlib.jar b/tooling/externalStaker/lib/org-aion-avm-userlib.jar deleted file mode 100755 index 3b0a0084b6..0000000000 Binary files a/tooling/externalStaker/lib/org-aion-avm-userlib.jar and /dev/null differ