Exemplo n.º 1
0
        private static TrustlessKeygen TestSerializationRoundTrip(TrustlessKeygen keyGen, EcdsaKeyPair keyPair)
        {
            var bytes    = keyGen.ToBytes();
            var restored = TrustlessKeygen.FromBytes(bytes, keyPair);

            Assert.AreEqual(restored, keyGen);
            return(restored);
        }
Exemplo n.º 2
0
        public void Test_OneNodeCycle()
        {
            var stateManager       = _container?.Resolve <IStateManager>();
            var contractRegisterer = _container?.Resolve <IContractRegisterer>();
            var tx       = new TransactionReceipt();
            var sender   = new BigInteger(0).ToUInt160();
            var context  = new InvocationContext(sender, stateManager !.LastApprovedSnapshot, tx);
            var contract = new GovernanceContract(context);
            var keyPair  = new EcdsaKeyPair("0xD95D6DB65F3E2223703C5D8E205D98E3E6B470F067B0F94F6C6BF73D4301CE48"
                                            .HexToBytes().ToPrivateKey());

            byte[]           pubKey  = CryptoUtils.EncodeCompressed(keyPair.PublicKey);
            ECDSAPublicKey[] allKeys = { keyPair.PublicKey };
            var          keygen      = new TrustlessKeygen(keyPair, allKeys, 0, 0);
            var          cycle       = 0.ToUInt256();
            ValueMessage value;

            // call ChangeValidators method
            {
                byte[][] validators = { pubKey };
                var      input      = ContractEncoder.Encode(GovernanceInterface.MethodChangeValidators, cycle, validators);
                var      call       = contractRegisterer !.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.ChangeValidators(cycle, validators, frame));
            }
            // check correct validator
            {
                var input = ContractEncoder.Encode(GovernanceInterface.MethodIsNextValidator, pubKey);
                var call  = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.IsNextValidator(pubKey, frame));
                Assert.AreEqual(frame.ReturnValue, 1.ToUInt256().ToBytes());
            }
            // check incorrect validator
            {
                byte[] incorrectPubKey = pubKey.Reverse().ToArray();
                var    input           = ContractEncoder.Encode(GovernanceInterface.MethodIsNextValidator, incorrectPubKey);
                var    call            = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.IsNextValidator(incorrectPubKey, frame));
                Assert.AreEqual(frame.ReturnValue, 0.ToUInt256().ToBytes());
            }
            // call commit
            {
                var      commitMessage = keygen.StartKeygen();
                byte[]   commitment    = commitMessage.Commitment.ToBytes();
                byte[][] encryptedRows = commitMessage.EncryptedRows;
                var      input         = ContractEncoder.Encode(GovernanceInterface.MethodKeygenCommit, cycle, commitment, encryptedRows);
                var      call          = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenCommit(cycle, commitment, encryptedRows, frame));
                // several calls is ok
                Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenCommit(cycle, commitment, encryptedRows, frame));
                // set keygen state
                value = keygen.HandleCommit(0, commitMessage);
            }
            // send value
            {
                var proposer = new BigInteger(0).ToUInt256();
                var input    = ContractEncoder.Encode(GovernanceInterface.MethodKeygenSendValue, cycle, proposer, value.EncryptedValues);
                var call     = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenSendValue(cycle, proposer, value.EncryptedValues, frame));
                // set keygen state
                Assert.IsTrue(keygen.HandleSendValue(0, value));
                Assert.IsTrue(keygen.Finished());
            }
            // confirm
            {
                ThresholdKeyring?keyring = keygen.TryGetKeys();
                Assert.IsNotNull(keyring);
                var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenConfirm, cycle,
                                                   keyring !.Value.TpkePublicKey.ToBytes(),
                                                   keyring !.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray());
                var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenConfirm(cycle, keyring !.Value.TpkePublicKey.ToBytes(),
                                                                           keyring !.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray(), frame));
                // set keygen state
                Assert.IsTrue(keygen.HandleConfirm(keyring !.Value.TpkePublicKey,
                                                   keyring !.Value.ThresholdSignaturePublicKeySet));
            }
            // check no validators in storage
            Assert.Throws <ConsensusStateNotPresentException>(() => context.Snapshot.Validators.GetValidatorsPublicKeys());
            // finish cycle
            {
                var input = ContractEncoder.Encode(GovernanceInterface.MethodFinishCycle, cycle);
                var call  = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                // should fail due to the invalid block
                Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.FinishCycle(cycle, frame));
                // set next cycle block number in frame:
                frame.InvocationContext.Receipt.Block = StakingContract.CycleDuration;
                Assert.AreEqual(ExecutionStatus.Ok, contract.FinishCycle(cycle, frame));
            }
            // check new validators in storage
            var newValidators = context.Snapshot.Validators.GetValidatorsPublicKeys().ToArray();

            Assert.AreEqual(newValidators.Count(), 1);
            Assert.AreEqual(newValidators[0], keyPair.PublicKey);
        }
Exemplo n.º 3
0
        public void Test_InvalidValidatorKey()
        {
            var stateManager       = _container?.Resolve <IStateManager>();
            var contractRegisterer = _container?.Resolve <IContractRegisterer>();
            var tx       = new TransactionReceipt();
            var sender   = new BigInteger(0).ToUInt160();
            var context  = new InvocationContext(sender, stateManager !.LastApprovedSnapshot, tx);
            var contract = new GovernanceContract(context);
            var keyPair  = new EcdsaKeyPair(Crypto.GeneratePrivateKey().ToPrivateKey());

            ECDSAPublicKey[] allKeys = { keyPair.PublicKey };
            var          keygen      = new TrustlessKeygen(keyPair, allKeys, 0, 0);
            var          cycle       = 0.ToUInt256();
            ValueMessage value;

            // call ChangeValidators method with invalid key
            {
                byte[][] validators = { new byte[] { 0 } };
                var      input      = ContractEncoder.Encode(GovernanceInterface.MethodChangeValidators, cycle, validators);
                var      call       = contractRegisterer !.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.ChangeValidators(cycle, validators, frame));
            }

            // call commit
            {
                var      commitMessage = keygen.StartKeygen();
                byte[]   commitment    = commitMessage.Commitment.ToBytes();
                byte[][] encryptedRows = commitMessage.EncryptedRows;
                var      input         = ContractEncoder.Encode(GovernanceInterface.MethodKeygenCommit, cycle, commitment, encryptedRows);
                var      call          = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.KeyGenCommit(cycle, commitment, encryptedRows, frame));
                // set keygen state
                value = keygen.HandleCommit(0, commitMessage);
            }

            // send value
            {
                var proposer = new BigInteger(0).ToUInt256();
                var input    = ContractEncoder.Encode(GovernanceInterface.MethodKeygenSendValue, cycle, proposer, value.EncryptedValues);
                var call     = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.ExecutionHalted, contract.KeyGenSendValue(cycle, proposer, value.EncryptedValues, frame));
                // set keygen state
                Assert.IsTrue(keygen.HandleSendValue(0, value));
                Assert.IsTrue(keygen.Finished());
            }

            // confirm
            {
                ThresholdKeyring?keyring = keygen.TryGetKeys();
                Assert.IsNotNull(keyring);
                var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenConfirm, cycle,
                                                   keyring !.Value.TpkePublicKey.ToBytes(),
                                                   keyring !.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray());
                var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenConfirm(cycle, keyring !.Value.TpkePublicKey.ToBytes(),
                                                                           keyring !.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray(), frame));
                // set keygen state
                Assert.IsTrue(keygen.HandleConfirm(keyring !.Value.TpkePublicKey,
                                                   keyring !.Value.ThresholdSignaturePublicKeySet));
            }

            // check no validators in storage
            Assert.Throws <ConsensusStateNotPresentException>(() => context.Snapshot.Validators.GetValidatorsPublicKeys());

            // finish cycle
            {
                var input = ContractEncoder.Encode(GovernanceInterface.MethodFinishCycle, cycle);
                var call  = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input);
                Assert.IsNotNull(call);
                var frame = new SystemContractExecutionFrame(call !, context, input, 100_000_000);
                // set next cycle block number in frame:
                frame.InvocationContext.Receipt.Block = StakingContract.CycleDuration;
                Assert.AreEqual(ExecutionStatus.Ok, contract.FinishCycle(cycle, frame));
            }

            // check no validators in storage again
            Assert.IsEmpty(context.Snapshot.Validators.GetValidatorsPublicKeys());
        }
Exemplo n.º 4
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());
            }