public async Task CanMineVotingRequestTransactionAsync() { var network = new TestPoACollateralNetwork(true, Guid.NewGuid().ToString()); using (PoANodeBuilder builder = PoANodeBuilder.CreatePoANodeBuilder(this)) { string walletName = "mywallet"; string walletPassword = "******"; string walletAccount = "account 0"; Money transferAmount = Money.Coins(0.01m); Money feeAmount = Money.Coins(0.0001m); var counterchainNetwork = new StraxTest(); CoreNode nodeA = builder.CreatePoANodeWithCounterchain(network, counterchainNetwork, network.FederationKey1).WithWallet(walletPassword, walletName).Start(); CoreNode nodeB = builder.CreatePoANodeWithCounterchain(network, counterchainNetwork, network.FederationKey2).WithWallet(walletPassword, walletName).Start(); TestHelper.Connect(nodeA, nodeB); long toMineCount = network.Consensus.PremineHeight + network.Consensus.CoinbaseMaturity + 1 - nodeA.GetTip().Height; // Get coins on nodeA via the premine. await nodeA.MineBlocksAsync((int)toMineCount).ConfigureAwait(false); CoreNodePoAExtensions.WaitTillSynced(nodeA, nodeB); // Create voting-request transaction. var minerKey = new Key(); var collateralKey = new Key(); var request = new JoinFederationRequest(minerKey.PubKey, new Money(CollateralFederationMember.MinerCollateralAmount, MoneyUnit.BTC), collateralKey.PubKey.Hash); // In practice this signature will come from calling the counter-chain "signmessage" API. request.AddSignature(collateralKey.SignMessage(request.SignatureMessage)); var encoder = new JoinFederationRequestEncoder(); JoinFederationRequestResult result = JoinFederationRequestBuilder.BuildTransaction(nodeA.FullNode.WalletTransactionHandler(), this.poaNetwork, request, encoder, walletName, walletAccount, walletPassword); await nodeA.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(result.Transaction.ToHex())); TestBase.WaitLoop(() => nodeA.CreateRPCClient().GetRawMempool().Length == 1 && nodeB.CreateRPCClient().GetRawMempool().Length == 1); await nodeB.MineBlocksAsync((int)toMineCount).ConfigureAwait(false); TestBase.WaitLoop(() => nodeA.CreateRPCClient().GetRawMempool().Length == 0 && nodeB.CreateRPCClient().GetRawMempool().Length == 0); IWalletManager walletManager = nodeB.FullNode.NodeService <IWalletManager>(); TestBase.WaitLoop(() => { long balance = walletManager.GetBalances(walletName, walletAccount).Sum(x => x.AmountConfirmed); return(balance == feeAmount); }); Assert.Single(nodeA.FullNode.NodeService <VotingManager>().GetPendingPolls()); Assert.Single(nodeB.FullNode.NodeService <VotingManager>().GetPendingPolls()); } }
public async Task <PubKey> JoinFederationAsync(JoinFederationRequestModel request, CancellationToken cancellationToken) { // Get the address pub key hash. BitcoinAddress address = BitcoinAddress.Create(request.CollateralAddress, this.counterChainSettings.CounterChainNetwork); KeyId addressKey = PayToPubkeyHashTemplate.Instance.ExtractScriptPubKeyParameters(address.ScriptPubKey); // Get mining key. var keyTool = new KeyTool(this.nodeSettings.DataFolder); Key minerKey = keyTool.LoadPrivateKey(); if (minerKey == null) { throw new Exception($"The private key file ({KeyTool.KeyFileDefaultName}) has not been configured or is not present."); } var expectedCollateralAmount = CollateralFederationMember.GetCollateralAmountForPubKey(this.network, minerKey.PubKey); var collateralAmount = new Money(expectedCollateralAmount, MoneyUnit.BTC); var joinRequest = new JoinFederationRequest(minerKey.PubKey, collateralAmount, addressKey); // Populate the RemovalEventId. var collateralFederationMember = new CollateralFederationMember(minerKey.PubKey, false, joinRequest.CollateralAmount, request.CollateralAddress); byte[] federationMemberBytes = (this.network.Consensus.ConsensusFactory as CollateralPoAConsensusFactory).SerializeFederationMember(collateralFederationMember); Poll poll = this.votingManager.GetApprovedPolls().FirstOrDefault(x => x.IsExecuted && x.VotingData.Key == VoteKey.KickFederationMember && x.VotingData.Data.SequenceEqual(federationMemberBytes)); joinRequest.RemovalEventId = (poll == null) ? Guid.Empty : new Guid(poll.PollExecutedBlockData.Hash.ToBytes().TakeLast(16).ToArray()); // Get the signature by calling the counter-chain "signmessage" API. var signMessageRequest = new SignMessageRequest() { Message = joinRequest.SignatureMessage, WalletName = request.CollateralWalletName, Password = request.CollateralWalletPassword, ExternalAddress = request.CollateralAddress }; var walletClient = new WalletClient(this.loggerFactory, this.httpClientFactory, $"http://{this.counterChainSettings.CounterChainApiHost}", this.counterChainSettings.CounterChainApiPort); string signature = await walletClient.SignMessageAsync(signMessageRequest, cancellationToken); if (signature == null) { throw new Exception("The call to sign the join federation request failed. It could have timed-out or the counter chain node is offline."); } joinRequest.AddSignature(signature); IWalletTransactionHandler walletTransactionHandler = this.fullNode.NodeService <IWalletTransactionHandler>(); var encoder = new JoinFederationRequestEncoder(this.loggerFactory); JoinFederationRequestResult result = JoinFederationRequestBuilder.BuildTransaction(walletTransactionHandler, this.network, joinRequest, encoder, request.WalletName, request.WalletAccount, request.WalletPassword); if (result.Transaction == null) { throw new Exception(result.Errors); } IWalletService walletService = this.fullNode.NodeService <IWalletService>(); await walletService.SendTransaction(new SendTransactionRequest(result.Transaction.ToHex()), cancellationToken); return(minerKey.PubKey); }
public async Task <PubKey> JoinFederationAsync(JoinFederationRequestModel request, CancellationToken cancellationToken) { // Wait until the node is synced before joining. if (this.initialBlockDownloadState.IsInitialBlockDownload()) { throw new Exception($"Please wait until the node is synced with the network before attempting to join the federation."); } // First ensure that this collateral address isnt already present in the federation. if (this.federationManager.GetFederationMembers().IsCollateralAddressRegistered(request.CollateralAddress)) { throw new Exception($"The provided collateral address '{request.CollateralAddress}' is already present in the federation."); } // Get the address pub key hash. BitcoinAddress address = BitcoinAddress.Create(request.CollateralAddress, this.counterChainSettings.CounterChainNetwork); KeyId addressKey = PayToPubkeyHashTemplate.Instance.ExtractScriptPubKeyParameters(address.ScriptPubKey); // Get mining key. var keyTool = new KeyTool(this.nodeSettings.DataFolder); Key minerKey = keyTool.LoadPrivateKey(); if (minerKey == null) { throw new Exception($"The private key file ({KeyTool.KeyFileDefaultName}) has not been configured or is not present."); } var expectedCollateralAmount = CollateralFederationMember.GetCollateralAmountForPubKey(this.network, minerKey.PubKey); var joinRequest = new JoinFederationRequest(minerKey.PubKey, new Money(expectedCollateralAmount, MoneyUnit.BTC), addressKey); // Populate the RemovalEventId. SetLastRemovalEventId(joinRequest, GetFederationMemberBytes(joinRequest, this.network, this.counterChainSettings.CounterChainNetwork), this.votingManager); // Get the signature by calling the counter-chain "signmessage" API. var signMessageRequest = new SignMessageRequest() { Message = joinRequest.SignatureMessage, WalletName = request.CollateralWalletName, Password = request.CollateralWalletPassword, ExternalAddress = request.CollateralAddress }; var walletClient = new WalletClient(this.httpClientFactory, $"http://{this.counterChainSettings.CounterChainApiHost}", this.counterChainSettings.CounterChainApiPort); try { string signature = await walletClient.SignMessageAsync(signMessageRequest, cancellationToken); joinRequest.AddSignature(signature); } catch (Exception err) { throw new Exception($"The call to sign the join federation request failed: '{err.Message}'."); } IWalletTransactionHandler walletTransactionHandler = this.fullNode.NodeService <IWalletTransactionHandler>(); var encoder = new JoinFederationRequestEncoder(); JoinFederationRequestResult result = JoinFederationRequestBuilder.BuildTransaction(walletTransactionHandler, this.network, joinRequest, encoder, request.WalletName, request.WalletAccount, request.WalletPassword); if (result.Transaction == null) { throw new Exception(result.Errors); } IWalletService walletService = this.fullNode.NodeService <IWalletService>(); await walletService.SendTransaction(new SendTransactionRequest(result.Transaction.ToHex()), cancellationToken); return(minerKey.PubKey); }