public void Test_Tx_Pool_Replacement() { _blockManager.TryBuildGenesisBlock(); TestTxPoolAdding(); var privateKey = Crypto.GeneratePrivateKey().ToPrivateKey(); var keyPair = new EcdsaKeyPair(privateKey); AddRandomTxesToPool(keyPair, out var randomTxes); var rnd = new Random(); randomTxes = randomTxes.OrderBy(item => rnd.Next()).ToList(); bool useNewChainId = HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight() + 1); var signer = new TransactionSigner(); foreach (var tx in randomTxes) { Console.WriteLine($"nonce: {tx.Transaction.Nonce}"); var newTx = new Transaction(tx.Transaction); // lower price newTx.GasPrice = tx.Transaction.GasPrice - 1; var randomTx = TestUtils.GetRandomTransaction(useNewChainId).Transaction; newTx.To = randomTx.To; newTx.Value = randomTx.Value; newTx.GasLimit = randomTx.GasLimit; var newTxReceipt = signer.Sign(newTx, keyPair, useNewChainId); var result = _transactionPool.Add(newTxReceipt); Console.WriteLine($"old gas price: {tx.Transaction.GasPrice}, new gas price: {newTxReceipt.Transaction.GasPrice}"); Assert.AreEqual(OperatingError.Underpriced, result); // equal price newTx.GasPrice = tx.Transaction.GasPrice; newTxReceipt = signer.Sign(newTx, keyPair, useNewChainId); result = _transactionPool.Add(newTxReceipt); Console.WriteLine($"old gas price: {tx.Transaction.GasPrice}, new gas price: {newTxReceipt.Transaction.GasPrice}"); Assert.AreEqual(OperatingError.Underpriced, result); // higher price newTx.GasPrice = tx.Transaction.GasPrice + 1; newTxReceipt = signer.Sign(newTx, keyPair, useNewChainId); result = _transactionPool.Add(newTxReceipt); Console.WriteLine($"old gas price: {tx.Transaction.GasPrice}, new gas price: {newTxReceipt.Transaction.GasPrice}"); Assert.AreEqual(OperatingError.DuplicatedTransaction, result); // higher price and all fields same newTx = new Transaction(tx.Transaction); newTx.GasPrice = tx.Transaction.GasPrice + 1; newTxReceipt = signer.Sign(newTx, keyPair, useNewChainId); result = _transactionPool.Add(newTxReceipt); Console.WriteLine($"old gas price: {tx.Transaction.GasPrice}, new gas price: {newTxReceipt.Transaction.GasPrice}"); Assert.AreEqual(OperatingError.Ok, result); } }
/// <inheritdoc /> public override async Task Handle(TransactionMessage message, IPeer sender) { var transaction = message.Payload; if (transaction is MinerTransaction) { return; } if (transaction.Hash == null) { _transactionSigner.Sign(transaction); } if (await _transactionRepository.ContainsTransaction(transaction.Hash)) { _logger.LogInformation($"The transaction \"{transaction.Hash?.ToString(true)}\" exists already on the blockchain."); return; } // Transaction is not added right away but queued to be verified and added. // It is the reason why we do not broadcast immediately. // TODO #374: It is a bit more complicated _transactionPool.Add(transaction); _logger.LogInformation($"Transaction with Hash {transaction.Hash?.ToString(true)} added to the TransactionPool."); }
public void ExecuteTxesInSeveralBlocks(List <TransactionReceipt> txes) { txes = txes.OrderBy(x => x, new ReceiptComparer()).ToList(); foreach (var tx in txes) { _transactionPool.Add(tx); } int total = 10; for (int it = 0; it < total; it++) { var remBlocks = total - it; var txesToTake = txes.Count / remBlocks; var takenTxes = _transactionPool.Peek(txesToTake, txesToTake, _blockManager.GetHeight() + 1); var block = BuildNextBlock(takenTxes.ToArray()); var result = ExecuteBlock(block, takenTxes.ToArray()); Assert.AreEqual(result, OperatingError.Ok); var executedBlock = _stateManager.LastApprovedSnapshot.Blocks.GetBlockByHeight(block.Header.Index); Assert.AreEqual(executedBlock !.TransactionHashes.Count, takenTxes.Count); // check if the txes are executed properly foreach (var tx in takenTxes) { var executedTx = _stateManager.LastApprovedSnapshot.Transactions.GetTransactionByHash(tx.Hash); Assert.AreNotEqual(null, executedTx, $"Transaction {tx.Hash.ToHex()} not found"); Assert.AreEqual(TransactionStatus.Executed, executedTx !.Status, "Transaction {tx.Hash.ToHex()} was not executed properly"); } } }
// Changed GetBalance to public public void TestGetBalancePending() { var tx = TestUtils.GetRandomTransaction(false); _stateManager.LastApprovedSnapshot.Balances.AddBalance(tx.Transaction.From, Money.Parse("1000")); var result = _transactionPool.Add(tx); Assert.AreEqual(OperatingError.Ok, result); var beforeBalance = _stateManager.LastApprovedSnapshot.Balances.GetBalance(tx.Transaction.From); var sentValue = new Money(tx.Transaction.Value); var afterBalance = beforeBalance - sentValue - new Money(tx.Transaction.GasLimit * tx.Transaction.GasPrice); var knownAfterBalance = Money.Parse("1000") - sentValue - new Money(tx.Transaction.GasLimit * tx.Transaction.GasPrice); Assert.AreEqual(knownAfterBalance, afterBalance); var balance = _apiService.GetBalance(tx.Transaction.From.ToHex(), "pending"); Assert.IsTrue(Web3DataFormatUtils.Web3Number(afterBalance.ToWei().ToUInt256()) == balance); }
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()); }
private void AddTxToPool(Transaction tx) { var useNewChainId = HardforkHeights.IsHardfork_9Active(_stateManager.LastApprovedSnapshot.Blocks.GetTotalBlockHeight() + 1); var receipt = _transactionSigner.Sign(tx, _privateWallet.EcdsaKeyPair, useNewChainId); _sendingTxHash = tx.FullHash(receipt.Signature, useNewChainId); var result = _transactionPool.Add(receipt); Logger.LogDebug(result == OperatingError.Ok ? $"Transaction successfully submitted: {receipt.Hash.ToHex()}" : $"Cannot add tx to pool: {result}"); }
//changed GetTransactionReceipt from private to public public void Test_GetTransactionReceipt() { _blockManager.TryBuildGenesisBlock(); var tx = TestUtils.GetRandomTransaction(HardforkHeights.IsHardfork_9Active(1)); _stateManager.LastApprovedSnapshot.Balances.AddBalance(tx.Transaction.From, Money.Parse("1000")); var result = _transactionPool.Add(tx); Assert.AreEqual(OperatingError.Ok, result); GenerateBlocks(1, 1); var txHashSent = tx.Hash.ToHex(); var txReceipt = _apiService.GetTransactionReceipt(txHashSent); var txHashReceived = txReceipt !["transactionHash"] !.ToString();
private void _Bench_Tx_Pool( ITransactionBuilder transactionBuilder, ITransactionSigner transactionSigner, EcdsaKeyPair keyPair) { const int txGenerate = 1000; const int txPerBlock = 1000; Logger.LogInformation($"Setting initial balance for the 'From' address"); _stateManager.LastApprovedSnapshot.Balances.AddBalance(keyPair.PublicKey.GetAddress(), Money.Parse("200000")); var txReceipts = new List <TransactionReceipt>(); for (int i = 0; i < txGenerate; i++) { var randomValue = new Random().Next(1, 100); var amount = Money.Parse($"{randomValue}.0").ToUInt256(); byte[] random = new byte[32]; RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(random); var tx = new Transaction { To = random.Slice(0, 20).ToUInt160(), From = keyPair.PublicKey.GetAddress(), GasPrice = (ulong)Money.Parse("0.0000001").ToWei(), GasLimit = 100000000, Nonce = (ulong)i, Value = amount }; txReceipts.Add(transactionSigner.Sign(tx, keyPair, true)); } ITransactionPool transactionPool = _container.Resolve <ITransactionPool>(); var watch = System.Diagnostics.Stopwatch.StartNew(); foreach (var txr in txReceipts) { transactionPool.Add(txr, false); } watch.Stop(); Console.WriteLine($"Time to Add {transactionPool.Transactions.Count} Tx to pool: {watch.ElapsedMilliseconds} ms"); watch.Restart(); var txs = transactionPool.Peek(txGenerate, txGenerate); watch.Stop(); Console.WriteLine($"Time to Peek {txs.Count} Tx from pool: {watch.ElapsedMilliseconds} ms"); }
public void Run() { var cancellationToken = _cancellationTokenSource.Token; Task.Factory.StartNew(async() => { while (!cancellationToken.IsCancellationRequested) { if (!_unverifiedTransactionPool.Any()) { await _asyncDelayer.Delay(DefaultTransactionPollingInterval, cancellationToken); continue; } var unverifiedTransactionHashes = _unverifiedTransactionPool.Keys; var transactionPool = _verifiedTransactionPool.Concat( _unverifiedTransactionPool.Values.Where(t => unverifiedTransactionHashes.Contains(t.Hash))) .ToArray(); foreach (var transactionHash in unverifiedTransactionHashes) { if (!_unverifiedTransactionPool.TryGetValue(transactionHash, out var transaction)) { continue; } var valid = this._transactionVerifier.Verify(transaction); if (transactionPool .Where(t => t.Hash != transactionHash) .Where(p => p != transaction) .SelectMany(p => p.Inputs) .Intersect(transaction.Inputs) .Any()) { valid = false; } if (valid) { _verifiedTransactionPool.Add(transaction); } _unverifiedTransactionPool.TryRemove(transactionHash, out _); OnTransactionProcessed?.Invoke(this, transaction); } } }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); }
private JObject SendRawTransaction(string rawTransaction, string signature) { bool useNewChainId = HardforkHeights.IsHardfork_9Active(_stateManager.LastApprovedSnapshot.Blocks.GetTotalBlockHeight() + 1); var transaction = Transaction.Parser.ParseFrom(rawTransaction.HexToBytes()); var s = signature.HexToBytes().ToSignature(useNewChainId); var json = new JObject { ["hash"] = transaction.FullHash(s, useNewChainId).ToHex() }; var result = _transactionPool.Add( transaction, signature.HexToBytes().ToSignature(useNewChainId)); if (result != OperatingError.Ok) { json["failed"] = true; } else { json["failed"] = false; } json["result"] = result.ToString(); return(json); }
public void Test_StakeSize() { _blockManager.TryBuildGenesisBlock(); var systemContractReader = _container?.Resolve <ISystemContractReader>() ?? throw new Exception("Container is not loaded"); var tx = TopUpBalanceTx(systemContractReader.NodeAddress(), Money.Parse("3000.0").ToUInt256(), 0, HardforkHeights.IsHardfork_9Active(1)); Assert.AreEqual(OperatingError.Ok, _transactionPool.Add(tx)); GenerateBlocks(1, 1); var balance = _stateManager.CurrentSnapshot.Balances.GetBalance(systemContractReader.NodeAddress()); var placeToStake = Money.Parse("2000.0"); Assert.That(balance > placeToStake, "Not enough balance to make stake"); _validatorStatusManager.StartWithStake(placeToStake.ToUInt256()); Assert.That(_validatorStatusManager.IsStarted(), "Manager is not started"); Assert.That(!_validatorStatusManager.IsWithdrawTriggered(), "Withdraw was triggered from the beggining"); int blockNum = (int)_blockManager.GetHeight(); Assert.IsFalse(HardforkHeights.IsHardfork_9Active((ulong)blockNum)); while (!HardforkHeights.IsHardfork_9Active((ulong)blockNum)) { blockNum++; GenerateBlocks(blockNum, blockNum); } var stake = new Money(systemContractReader.GetStake(systemContractReader.NodeAddress())); // TODO: fix it // Assert.That(stake == placeToStake, $"Stake is not as intended: {stake} != {placeToStake}"); _validatorStatusManager.WithdrawStakeAndStop(); Assert.That(_validatorStatusManager.IsStarted(), "Manager is stopped right after withdraw request"); Assert.That(_validatorStatusManager.IsWithdrawTriggered(), "Withdraw is not triggered"); // Test node is the only vaidator, so it is a next validator always // and it can't withdraw its stake. // GenerateBlocks(50); // Assert.That(!_validatorStatusManager.IsStarted(), "Manager is not stopped"); //_validatorStatusManager.Stop(); }
// 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()); }