示例#1
0
        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());
        }
示例#2
0
        private JObject GetBlockStat()
        {
            var json = new JObject
            {
                ["currentHeight"] = _blockManager.GetHeight()
            };

            return(json);
        }
示例#3
0
        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);
            }
        }
示例#4
0
 public void Test_EventFormat()
 {
     _blockManager.TryBuildGenesisBlock();
     TestEventFormat();
     Assert.IsFalse(HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight()));
     while (!HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight()))
     {
         GenerateBlocks(1);
     }
     TestEventFormat();
 }
示例#5
0
 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();
 }
示例#6
0
        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();
        }
示例#7
0
        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);
            }
        }
示例#8
0
        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);
        }
示例#9
0
        /*
         * 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());
        }
示例#10
0
        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);
        }
示例#11
0
 public string GetBlockNumber()
 {
     return(Web3DataFormatUtils.Web3Number(_blockManager.GetHeight()));
 }
示例#12
0
        // 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
            });
        }
示例#13
0
        // 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());
            }