public void Test_StakeWithdraw() { _blockManager.TryBuildGenesisBlock(); GenerateBlocks(1, 1); _validatorStatusManager.Start(false); Assert.IsTrue(_validatorStatusManager.IsStarted()); Assert.IsFalse(_validatorStatusManager.IsWithdrawTriggered()); int blockNum = (int)_blockManager.GetHeight(); Assert.IsFalse(HardforkHeights.IsHardfork_9Active((ulong)blockNum)); while (!HardforkHeights.IsHardfork_9Active((ulong)blockNum)) { blockNum++; GenerateBlocks(blockNum, blockNum); } var systemContractReader = _container?.Resolve <ISystemContractReader>() ?? throw new Exception("Container is not loaded"); var stake = new Money(systemContractReader.GetStake()); Console.WriteLine($"Current stake is {stake}"); Assert.That(stake > Money.Zero, "Stake is zero"); _validatorStatusManager.WithdrawStakeAndStop(); Assert.IsTrue(_validatorStatusManager.IsStarted()); Assert.IsTrue(_validatorStatusManager.IsWithdrawTriggered()); // Test node is the only validator, so it is a next validator always // and it can't withdraw its stake. TODO: test to check withdraw is working //GenerateBlocks(50); //Assert.IsFalse(_validatorStatusManager.IsStarted()); }
private JObject GetBlockStat() { var json = new JObject { ["currentHeight"] = _blockManager.GetHeight() }; return(json); }
public void Test_Block_Execution() { _blockManager.TryBuildGenesisBlock(); var block = BuildNextBlock(); var result = ExecuteBlock(block); Assert.AreEqual(OperatingError.Ok, result); // using new chain id Assert.IsFalse(HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight())); while (!HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight())) { block = BuildNextBlock(); result = ExecuteBlock(block); Assert.AreEqual(OperatingError.Ok, result); } int total = 100; for (var it = 0; it < total; it++) { block = BuildNextBlock(); result = ExecuteBlock(block); Assert.AreEqual(OperatingError.Ok, result); } }
public void Test_EventFormat() { _blockManager.TryBuildGenesisBlock(); TestEventFormat(); Assert.IsFalse(HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight())); while (!HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight())) { GenerateBlocks(1); } TestEventFormat(); }
public void Test_Tx_Pool_Adding() { TestTxPoolAdding(); _blockManager.TryBuildGenesisBlock(); Assert.IsFalse(HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight())); while (!HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight())) { var block = BuildNextBlock(); var result = ExecuteBlock(block); Assert.AreEqual(OperatingError.Ok, result); } TestTxPoolAdding(); }
public void Restore() { var txHashes = _poolRepository.GetTransactionPool(); Logger.LogTrace($"restoring transactions from pool to in-memory storage"); // transactionsToRemove stores all the transactions that was not added to // the in-memory pool from persistent storage List <UInt256> transactionsToRemove = new List <UInt256>(); foreach (var txHash in txHashes) { Logger.LogTrace($"Tx from pool: {txHash.ToHex()}"); var tx = _poolRepository.GetTransactionByHash(txHash); if (tx is null) { continue; } if (Add(tx, false) != OperatingError.Ok) { transactionsToRemove.Add(tx.Hash); } } // if a transaction was not added to the pool, that means it's not a valid // transactions, so we should also erase it from the persistent storage _poolRepository.RemoveTransactions(transactionsToRemove); _lastSanitized = _blockManager.GetHeight(); CheckConsistency(); }
public uint HandleTransactionsFromPeer(IEnumerable <TransactionReceipt> transactions, ECDSAPublicKey publicKey) { lock (_txLock) { var txs = transactions.ToArray(); Logger.LogTrace($"Received {txs.Length} transactions from peer {publicKey.ToHex()}"); var persisted = 0u; foreach (var tx in txs) { if (tx.Signature.IsZero()) { Logger.LogTrace($"Received zero-signature transaction: {tx.Hash.ToHex()}"); if (_transactionPool.Add(tx, false) == OperatingError.Ok) { persisted++; } continue; } var error = _transactionManager.Verify(tx, HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight() + 1)); if (error != OperatingError.Ok) { Logger.LogTrace($"Unable to verify transaction: {tx.Hash.ToHex()} ({error})"); continue; } error = _transactionPool.Add(tx, false); if (error == OperatingError.Ok) { persisted++; } else { Logger.LogTrace($"Transaction {tx.Hash.ToHex()} not persisted: {error}"); } } lock (_peerHasTransactions) Monitor.PulseAll(_peerHasTransactions); Logger.LogTrace($"Persisted {persisted} transactions from peer {publicKey.ToHex()}"); return(persisted); } }
public void Test_ContractDeployAndInvoke() { int blockNum = 1; TestContractDeployAndInvoke(blockNum); Assert.IsFalse(HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight())); blockNum = (int)_blockManager.GetHeight() + 1; while (!HardforkHeights.IsHardfork_9Active((ulong)blockNum - 1)) { GenerateBlock(blockNum); blockNum++; } var topUpTx = TopUpBalanceTx(_wallet.EcdsaKeyPair.PublicKey.GetAddress(), Money.Parse("10.0").ToUInt256(), 0, true); var error = _transactionPool.Add(topUpTx); Assert.AreEqual(OperatingError.Ok, error, $"failed to add top up tx to pool: {error}"); GenerateBlock(blockNum); blockNum++; TestContractDeployAndInvoke(blockNum); }
/* * DeployContract * contract, string, contract bytecode as a hexstring * constructor params according to contract constructor */ public string DeployContract(string[] arguments) { var from = _keyPair.PublicKey.GetAddress(); var nonce = _stateManager.LastApprovedSnapshot.Transactions.GetTotalTransactionCount(from); /* calculate contract hash */ var hash = from.ToBytes().Concat(nonce.ToBytes()).Ripemd(); Console.WriteLine("Contract Hash: " + hash.ToHex()); var byteCode = arguments[1].HexToBytes(); if (!VirtualMachine.VerifyContract(byteCode, true)) { return("Unable to validate smart-contract code"); } // TODO: use deploy abi if required var tx = _transactionBuilder.DeployTransaction(from, byteCode); var signedTx = _transactionSigner.Sign(tx, _keyPair, HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight() + 1)); _transactionPool.Add(signedTx); return(signedTx.Hash.ToHex()); }
public void Test_MessageSender() { TestMessageSender(1); int blockNum = (int)_blockManager.GetHeight(); Assert.IsFalse(HardforkHeights.IsHardfork_9Active((ulong)blockNum)); while (!HardforkHeights.IsHardfork_9Active((ulong)blockNum)) { GenerateBlock(blockNum + 1); blockNum++; } TestMessageSender(blockNum + 1); }
public string GetBlockNumber() { return(Web3DataFormatUtils.Web3Number(_blockManager.GetHeight())); }
// Taking the selected transaction receipts from consensus, CreateHeader removes bad // transactions, adds necessary system transactions, emulate the transactions, calculate // stateHash and finally returns a blockHeader public BlockHeader?CreateHeader( ulong index, IReadOnlyCollection <TransactionReceipt> receipts, ulong nonce, out TransactionReceipt[] receiptsTaken ) { Logger.LogTrace("CreateHeader"); if (_blockManager.GetHeight() >= index) { Logger.LogWarning("Block already produced"); receiptsTaken = new TransactionReceipt[] {}; return(null); } // we don't need to verify receipts here // verfification will be done during emulation // But we need to verify the hash as we map the receipts with its hash // we skip the transactions with hash mismatch receipts = receipts.Where(receipt => receipt.Transaction.FullHash(receipt.Signature, HardforkHeights.IsHardfork_9Active(index)).Equals(receipt.Hash)).ToList(); receipts = receipts.OrderBy(receipt => receipt, new ReceiptComparer()) .ToList(); var cycle = index / StakingContract.CycleDuration; var indexInCycle = index % StakingContract.CycleDuration; // try to add necessary system transactions at the end if (cycle > 0 && indexInCycle == StakingContract.AttendanceDetectionDuration) { var txToAdd = DistributeCycleRewardsAndPenaltiesTxReceipt(index); if (receipts.Select(x => x.Hash).Contains(txToAdd.Hash)) { Logger.LogDebug("DistributeCycleRewardsAndPenaltiesTxReceipt is already in txPool"); } else { receipts = receipts.Concat(new[] { txToAdd }).ToList(); } } else if (indexInCycle == StakingContract.VrfSubmissionPhaseDuration) { var txToAdd = FinishVrfLotteryTxReceipt(index); if (receipts.Select(x => x.Hash).Contains(txToAdd.Hash)) { Logger.LogDebug("FinishVrfLotteryTxReceipt is already in txPool"); } else { receipts = receipts.Concat(new[] { txToAdd }).ToList(); } } else if (cycle > 0 && indexInCycle == 0) { var txToAdd = FinishCycleTxReceipt(index); if (receipts.Select(x => x.Hash).Contains(txToAdd.Hash)) { Logger.LogDebug("FinishCycleTxReceipt is already in txPool"); } else { receipts = receipts.Concat(new[] { txToAdd }).ToList(); } } if (_blockManager.LatestBlock().Header.Index + 1 != index) { throw new InvalidOperationException( $"Latest block is {_blockManager.LatestBlock().Header.Index} " + $"with hash {_blockManager.LatestBlock().Hash.ToHex()}, " + $"but we are trying to create block {index}"); } var blockWithTransactions = new BlockBuilder(_blockManager.LatestBlock().Header) .WithTransactions(receipts) .Build(nonce); var(operatingError, removedTxs, stateHash, returnedTxs) = _blockManager.Emulate(blockWithTransactions.Block, blockWithTransactions.Transactions); var badReceipts = new HashSet <TransactionReceipt>(removedTxs.Concat(returnedTxs)); receiptsTaken = receipts.Where(receipt => !badReceipts.Contains(receipt)).ToArray(); blockWithTransactions = new BlockBuilder(_blockManager.LatestBlock().Header) .WithTransactions(receiptsTaken) .Build(nonce); if (operatingError != OperatingError.Ok) { throw new InvalidOperationException($"Cannot assemble block: error {operatingError}"); } return(new BlockHeader { Index = blockWithTransactions.Block.Header.Index, MerkleRoot = blockWithTransactions.Block.Header.MerkleRoot, Nonce = nonce, PrevBlockHash = blockWithTransactions.Block.Header.PrevBlockHash, StateHash = stateHash }); }
// For every cycle, a new set of keys are required for the validators. This key generation process // is done on-chain. That means, every communication between participating nodes happen via transactions // in the block. For example, if node A wants to send a msg to node B, then node A encrypts the // msg with node B's public key and broadcast this as a transaction to the governance contract. // After this transaction is added to the chain, node B can decrypt the msg and read it. // During block execution, after every system transaction is executed, the following method // is invoked. It evaluates the transaction and if it's keygen related, it produces // appropriate response in form of a transaction and adds it to the pool for the addition // in the block. private void BlockManagerOnSystemContractInvoked(object _, InvocationContext context) { if (context.Receipt is null) { return; } var highestBlock = _blockSynchronizer.GetHighestBlock(); var willParticipate = !highestBlock.HasValue || GovernanceContract.IsKeygenBlock(context.Receipt.Block) && GovernanceContract.SameCycle(highestBlock.Value, context.Receipt.Block); if (!willParticipate) { Logger.LogInformation( highestBlock != null ? $"Will not participate in keygen: highest block is {highestBlock.Value}, call block is {context.Receipt.Block}" : $"Will not participate in keygen: highest block is null, call block is {context.Receipt.Block}" ); } var tx = context.Receipt.Transaction; if ( !tx.To.Equals(ContractRegisterer.GovernanceContract) && !tx.To.Equals(ContractRegisterer.StakingContract) ) { return; } if (context.Receipt.Block < _blockManager.GetHeight() && !GovernanceContract.SameCycle(context.Receipt.Block, _blockManager.GetHeight())) { Logger.LogWarning( $"System contract invoked from outdated tx: {context.Receipt.Hash}, tx block {context.Receipt.Block}, our height is {_blockManager.GetHeight()}"); return; } if (tx.Invocation.Length < 4) { return; } var signature = ContractEncoder.MethodSignatureAsInt(tx.Invocation); var decoder = new ContractDecoder(tx.Invocation.ToArray()); var contractAddress = tx.To; if (contractAddress.Equals(ContractRegisterer.GovernanceContract) && signature == ContractEncoder.MethodSignatureAsInt(GovernanceInterface.MethodFinishCycle)) { Logger.LogDebug("Aborting ongoing keygen because cycle was finished"); _keyGenRepository.SaveKeyGenState(Array.Empty <byte>()); } else if (signature == ContractEncoder.MethodSignatureAsInt(StakingInterface.MethodFinishVrfLottery)) { Logger.LogDebug($"Detected call of StakingInterface.{StakingInterface.MethodFinishVrfLottery}"); var cycle = GovernanceContract.GetCycleByBlockNumber(context.Receipt.Block); var data = new GovernanceContract(context).GetNextValidators(); var publicKeys = (data ?? throw new ArgumentException("Cannot parse method args")) .Select(x => x.ToPublicKey()) .ToArray(); Logger.LogDebug( $"Keygen is started in cycle={cycle}, block={context.Receipt.Block} for validator set: {string.Join(",", publicKeys.Select(x => x.ToHex()))}" ); if (!publicKeys.Contains(_privateWallet.EcdsaKeyPair.PublicKey)) { Logger.LogWarning("Skipping validator change event since we are not new validator"); return; } var keygen = GetCurrentKeyGen(); if (keygen != null && keygen.Cycle == cycle) { throw new ArgumentException("Cannot start keygen, since one is already running"); } if (keygen != null) { Logger.LogWarning($"Aborted keygen for cycle {keygen.Cycle} to start keygen for cycle {cycle}"); } _keyGenRepository.SaveKeyGenState(Array.Empty <byte>()); var faulty = (publicKeys.Length - 1) / 3; keygen = new TrustlessKeygen(_privateWallet.EcdsaKeyPair, publicKeys, faulty, cycle); var commitTx = MakeCommitTransaction(keygen.StartKeygen(), cycle); Logger.LogTrace($"Produced commit tx with hash: {commitTx.Hash.ToHex()}"); if (willParticipate) { Logger.LogInformation($"Try to send KeyGen Commit transaction"); if (_transactionPool.Add(commitTx) is var error && error != OperatingError.Ok) { Logger.LogError($"Error creating commit transaction ({commitTx.Hash.ToHex()}): {error}"); } else { Logger.LogInformation($"KeyGen Commit transaction sent"); } } Logger.LogDebug($"Saving keygen {keygen.ToBytes().ToHex()}"); _keyGenRepository.SaveKeyGenState(keygen.ToBytes()); }