public IActionResult Call([FromBody] BuildCallContractTransactionRequest request) { if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } BuildCallContractTransactionResponse response = this.smartContractTransactionService.BuildCallTx(request); if (!response.Success) { return(BadRequest(Json(response))); } Transaction transaction = this.network.CreateTransaction(response.Hex); this.walletManager.ProcessTransaction(transaction, null, null, false); this.broadcasterManager.BroadcastTransactionAsync(transaction).GetAwaiter().GetResult(); return(Json(response)); }
public IActionResult ListAccounts([FromQuery] ListAccountsModel request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { IEnumerable <HdAccount> result = this.walletManager.GetAccounts(request.WalletName); return(this.Json(result.Select(a => a.Name))); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult GetUnusedAddress([FromQuery] GetUnusedAddressModel request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { HdAddress result = this.walletManager.GetUnusedAddress(new WalletAccountReference(request.WalletName, request.AccountName)); return(this.Json(result.Address)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
private IActionResult VoteAddKickFedMember(HexPubKeyModel request, bool addMember) { Guard.NotNull(request, nameof(request)); if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } if (!this.fedManager.IsFederationMember) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Only federation members can vote", string.Empty)); } try { var key = new PubKey(request.PubKeyHex); if (IsMultisigMember(this.network, key)) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Multisig members can't be voted on", string.Empty)); } IFederationMember federationMember = new FederationMember(key); byte[] fedMemberBytes = (this.network.Consensus.ConsensusFactory as PoAConsensusFactory).SerializeFederationMember(federationMember); this.votingManager.ScheduleVote(new VotingData() { Key = addMember ? VoteKey.AddFederationMember : VoteKey.KickFederationMember, Data = fedMemberBytes }); return(this.Ok()); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "There was a problem executing a command.", e.ToString())); } }
public IActionResult GetReceipt([FromQuery] string txHash) { this.logger.LogTrace("(){0}:{1}", nameof(txHash), txHash); if (!this.ModelState.IsValid) { this.logger.LogTrace("(-)[MODELSTATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } uint256 txHashNum = new uint256(txHash); SmartContractReceipt receipt = this.receiptStorage.GetReceipt(txHashNum); if (receipt == null) { this.logger.LogTrace("(-)[RECEIPT_NOT_FOUND]"); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Receipt not found.", "Could not find a stored transaction for this hash.")); } return(Json(ReceiptModel.FromSmartContractReceipt(receipt, this.network))); }
private IActionResult VoteAddKickFedMember(CollateralFederationMemberModel request, bool addMember) { Guard.NotNull(request, nameof(request)); // TODO remove this line when multisig recreation is implemented. return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Disabled in current version.", string.Empty)); if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } if (!this.fedManager.IsFederationMember) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Only federation members can vote", string.Empty)); } try { var key = new PubKey(request.PubKeyHex); IFederationMember federationMember = new CollateralFederationMember(key, new Money(request.CollateralAmountSatoshis), request.CollateralMainchainAddress); byte[] fedMemberBytes = (this.network.Consensus.ConsensusFactory as PoAConsensusFactory).SerializeFederationMember(federationMember); this.votingManager.ScheduleVote(new VotingData() { Key = addMember ? VoteKey.AddFederationMember : VoteKey.KickFederationMember, Data = fedMemberBytes }); return(this.Ok()); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "There was a problem executing a command.", e.ToString())); } }
public IActionResult EstimateOfflineColdStakingSetupFee([FromBody] SetupOfflineColdStakingRequest request) { Guard.NotNull(request, nameof(request)); // Checks the request is valid. if (!this.ModelState.IsValid) { this.logger.LogTrace("(-)[MODEL_STATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { Money amount = Money.Parse(request.Amount); Money estimatedFee = this.ColdStakingManager.EstimateSetupTransactionFee( this.walletTransactionHandler, request.ColdWalletAddress, request.HotWalletAddress, request.WalletName, request.WalletAccount, null, amount, request.SubtractFeeFromAmount, true, request.SegwitChangeAddress, request.SplitCount); this.logger.LogTrace("(-):'{0}'", estimatedFee); return(this.Json(estimatedFee)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); this.logger.LogTrace("(-)[ERROR]"); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult CreateColdStakingAccount([FromBody] CreateColdStakingAccountRequest request) { Guard.NotNull(request, nameof(request)); // Checks that the request is valid. if (!this.ModelState.IsValid) { this.logger.LogTrace("(-)[MODEL_STATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { ExtPubKey extPubKey = null; try { extPubKey = ExtPubKey.Parse(request.ExtPubKey); } catch { } var model = new CreateColdStakingAccountResponse { AccountName = this.ColdStakingManager.GetOrCreateColdStakingAccount(request.WalletName, request.IsColdWalletAccount, request.WalletPassword, extPubKey).Name }; this.logger.LogTrace("(-):'{0}'", model); return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); this.logger.LogTrace("(-)[ERROR]"); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public async Task <IActionResult> BuildAndSendCallSmartContractTransactionAsync([FromBody] BuildCallContractTransactionRequest request) { if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } // Ignore this check if the node is running dev mode. if (this.nodeSettings.DevMode == null && !this.connectionManager.ConnectedPeers.Any()) { this.logger.LogTrace("(-)[NO_CONNECTED_PEERS]"); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.Forbidden, "Can't send transaction as the node requires at least one connection.", string.Empty)); } BuildCallContractTransactionResponse response = this.smartContractTransactionService.BuildCallTx(request); if (!response.Success) { return(this.Json(response)); } Transaction transaction = this.network.CreateTransaction(response.Hex); await this.broadcasterManager.BroadcastTransactionAsync(transaction); // Check if transaction was actually added to a mempool. TransactionBroadcastEntry transactionBroadCastEntry = this.broadcasterManager.GetTransaction(transaction.GetHash()); if (transactionBroadCastEntry?.TransactionBroadcastState == Features.Wallet.Broadcasting.TransactionBroadcastState.CantBroadcast) { this.logger.LogError("Exception occurred: {0}", transactionBroadCastEntry.ErrorMessage); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, transactionBroadCastEntry.ErrorMessage, "Transaction Exception")); } response.Message = $"Your CALL method {request.MethodName} transaction was successfully built and sent. Check the receipt using the transaction ID once it has been included in a new block."; return(this.Json(response)); }
public IActionResult GetXServerStats() { if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { var serverStats = new GetXServerStatsResult() { Connected = this.xServerManager.ConnectedSeeds.Count, Nodes = this.xServerManager.ConnectedSeeds }; return(this.Json(serverStats)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult GetBalance([FromQuery] WalletBalanceRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { var model = new WalletBalanceModel(); IEnumerable <AccountBalance> balances = this.walletManager.GetBalances(request.WalletName, request.AccountName); foreach (AccountBalance balance in balances) { HdAccount account = balance.Account; model.AccountsBalances.Add(new AccountBalanceModel { CoinType = this.coinType, Name = account.Name, HdPath = account.HdPath, AmountConfirmed = balance.AmountConfirmed, AmountUnconfirmed = balance.AmountUnconfirmed }); } return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult GetColdStakingAddress([FromQuery] GetColdStakingAddressRequest request) { Guard.NotNull(request, nameof(request)); // Checks that the request is valid. if (!this.ModelState.IsValid) { this.logger.LogTrace("(-)[MODEL_STATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { var address = this.ColdStakingManager.GetFirstUnusedColdStakingAddress(request.WalletName, request.IsColdWalletAddress); var model = new GetColdStakingAddressResponse { Address = request.Segwit ? address?.Bech32Address : address?.Address }; if (model.Address == null) { throw new WalletException("The cold staking account does not exist."); } this.logger.LogTrace("(-):'{0}'", model); return(Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); this.logger.LogTrace("(-)[ERROR]"); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult ColdStakingWithdrawal([FromBody] ColdStakingWithdrawalRequest request) { Guard.NotNull(request, nameof(request)); this.logger.LogTrace("({0}:'{1}')", nameof(request), request); // Checks the request is valid. if (!this.ModelState.IsValid) { this.logger.LogTrace("(-)[MODEL_STATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { Money amount = Money.Parse(request.Amount); Money feeAmount = Money.Parse(request.Fees); Transaction transaction = this.ColdStakingManager.GetColdStakingWithdrawalTransaction(this.walletTransactionHandler, request.ReceivingAddress, request.WalletName, request.WalletPassword, amount, feeAmount); var model = new ColdStakingWithdrawalResponse { TransactionHex = transaction.ToHex() }; this.logger.LogTrace("(-):'{0}'", model); return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); this.logger.LogTrace("(-)[ERROR]"); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public async Task <IActionResult> GetTransactionAsync(string id) { if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentNullException("id", "id must be specified"); } try { Transaction trx = this.blockRepository.GetTransactionById(new uint256(id)); var model = new TransactionVerboseModel(trx, this.network); return(Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult GetStorage([FromQuery] GetStorageRequest request) { if (!this.ModelState.IsValid) { this.logger.LogTrace("(-)[MODELSTATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } var height = request.BlockHeight.HasValue ? request.BlockHeight.Value : (ulong)this.chainIndexer.Height; ChainedHeader chainedHeader = this.chainIndexer.GetHeader((int)height); var scHeader = chainedHeader?.Header as ISmartContractBlockHeader; IStateRepositoryRoot stateAtHeight = this.stateRoot.GetSnapshotTo(scHeader.HashStateRoot.ToBytes()); uint160 addressNumeric = request.ContractAddress.ToUint160(this.network); byte[] storageValue = stateAtHeight.GetStorageValue(addressNumeric, Encoding.UTF8.GetBytes(request.StorageKey)); if (storageValue == null) { return(this.NotFound(new { Message = string.Format("No data at storage with key {0}", request.StorageKey) })); } // Interpret the storage bytes as an object of the given type object interpretedStorageValue = this.InterpretStorageValue(request.DataType, storageValue); // Use MethodParamStringSerializer to serialize the interpreted object to a string string serialized = MethodParameterStringSerializer.Serialize(interpretedStorageValue, this.network); return(this.Json(serialized)); }
public IActionResult ReplaceFederationMemberIp([FromBody] ReplaceFederationMemberIpModel model) { if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { IPEndPoint endPointToReplace = model.EndPoint.ToIPEndPoint(this.fullNode.Network.DefaultPort); IPEndPoint endPointToUse = model.EndPointToUse.ToIPEndPoint(this.fullNode.Network.DefaultPort); if (!this.federatedPegSettings.FederationNodeIpEndPoints.Contains(endPointToReplace)) { return(this.Json($"{endPointToReplace} does not exist in the federation.")); } this.federatedPegSettings.FederationNodeIpEndPoints.Remove(endPointToReplace); this.federatedPegSettings.FederationNodeIpAddresses.Remove(endPointToReplace.Address); if (this.federatedPegSettings.FederationNodeIpEndPoints.Contains(endPointToUse)) { return(this.Json($"{endPointToUse} already exists in the federation.")); } this.federatedPegSettings.FederationNodeIpEndPoints.Add(endPointToUse); this.federatedPegSettings.FederationNodeIpAddresses.Add(endPointToUse.Address); return(this.Json($"{endPointToReplace} has been replaced with {endPointToUse}.")); } catch (Exception e) { this.logger.Error("Exception thrown calling /api/FederationGateway/{0}: {1}.", FederationGatewayRouteEndPoint.FederationMemberIpReplace, e.Message); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
protected async Task <IActionResult> Execute <TRequest>(TRequest request, CancellationToken token, Func <TRequest, CancellationToken, Task <IActionResult> > action, bool checkModelState = true) { if (checkModelState && !this.ModelState.IsValid) { Guard.NotNull(request, nameof(request)); this.Logger.LogTrace($"{nameof(request)}(-)[MODEL_STATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { return(await action(request, token)); } catch (FeatureException e) { this.Logger.LogError("Exception occurred: {0}", e.ToString()); return(e.MapToErrorResponse()); } catch (Exception e) { this.Logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult Recover([FromBody] WalletRecoveryRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { Wallet wallet = this.walletManager.RecoverWallet(request.Password, request.Name, request.Mnemonic, request.CreationDate, passphrase: request.Passphrase); this.SyncFromBestHeightForRecoveredWallets(request.CreationDate); return(this.Ok()); } catch (WalletException e) { // indicates that this wallet already exists this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.Conflict, e.Message, e.ToString())); } catch (FileNotFoundException e) { // indicates that this wallet does not exist this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.NotFound, "Wallet not found.", e.ToString())); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult SetupColdStaking([FromBody] SetupColdStakingRequest request) { Guard.NotNull(request, nameof(request)); // Checks the request is valid. if (!this.ModelState.IsValid) { this.logger.LogTrace("(-)[MODEL_STATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { var amount = Money.Parse(request.Amount); var feeAmount = Money.Parse(request.Fees); var transaction = this.ColdStakingManager.GetColdStakingSetupTransaction( this.walletTransactionHandler, request.ColdWalletAddress, request.HotWalletAddress, request.WalletName, request.WalletAccount, request.WalletPassword, amount, feeAmount, request.SegwitChangeAddress, request.PayToScript); var model = new SetupColdStakingResponse { TransactionHex = transaction.ToHex() }; this.logger.LogTrace("(-):'{0}'", model); return(Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); this.logger.LogTrace("(-)[ERROR]"); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult SignWantedSystemMessageAsync([FromBody] SignWantedSystemMessageRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { // parse the transaction Transaction tx = this.network.CreateTransaction(request.TransactionHex); // check if we have the message transaction in our message store WalletManager wm = this.walletManager as WalletManager; if (!wm.WantedSystemMessages.Any(t => t.Key == tx.GetHash())) { throw new Exception($"The transcation with hash '{tx.GetHash()}' is not in the message store. You must upload it first by using the upload-wanted-system-message API."); } //WantedSystemMessageModel wantedMessage = wm.WantedSystemMessages.First(t => t.Key == tx.GetHash()).Value; // get the signing keys List <BitcoinExtKey> privateKeys = new List <BitcoinExtKey>(); if (!String.IsNullOrWhiteSpace(request.SigningKey)) { // parse request.SigningKey into a Bitcoin Private Key try { privateKeys.Add(new BitcoinExtKey(request.SigningKey)); } catch { throw new Exception($"The signing key you provided is not in a valid format."); } } else { throw new Exception($"You must provide a signing key in order to sign the message."); // TODO: add our own private keys } // sign the transaction with the key var reviewerAddress = wm.ReviewerAddresses[request.ReviewerAddress]; if (reviewerAddress == null) { throw new Exception($"The reviewer '{request.ReviewerAddress}' was not found in the address book."); } TransactionBuilder tb = new TransactionBuilder(this.network); tb.AddCoins((this.walletTransactionHandler as WalletTransactionHandler).GetCoinsForReviewersAddress(reviewerAddress)); tb.AddCoins(tx); Transaction signedTx = tb.AddKeys(privateKeys.ToArray()).SignTransaction(tx); if (signedTx.GetHash() == tx.GetHash()) { throw new Exception($"The signing key you provided is not associated with the message or it is already signed with that key."); } // return the (partially) signed transaction SignWantedSystemMessageModel model = new SignWantedSystemMessageModel { TransactionHex = signedTx.ToHex(), WasSigned = request.TransactionHex != signedTx.ToHex() }; return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult SendWantedSystemMessageAsync([FromBody] SendWantedSystemMessageRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } if (!this.connectionManager.ConnectedPeers.Any()) { throw new WalletException("Can't send transaction: sending transaction requires at least one connection!"); } try { Transaction transaction = this.network.CreateTransaction(request.Hex); WalletSendTransactionModel model = new WalletSendTransactionModel { TransactionId = transaction.GetHash(), Outputs = new List <TransactionOutputModel>() }; foreach (var output in transaction.Outputs) { if (WantedSystemMessageTemplate.Instance.CheckScriptPubKey(output.ScriptPubKey)) { model.Outputs.Add(new TransactionOutputModel { Address = $"N/A (Wanted System Message)", Amount = output.Value }); } else { model.Outputs.Add(new TransactionOutputModel { Address = output.ScriptPubKey.GetDestinationAddress(this.network).ToString(), Amount = output.Value }); } } if (transaction.Inputs.All(tri => String.IsNullOrEmpty(tri.ScriptSig.ToString()))) { throw new Exception("This transcation is not signed. In order to publish a transaction on the network, it must be fully signed first."); } if (transaction.Inputs.Any(tri => String.IsNullOrEmpty(tri.ScriptSig.ToString()))) { throw new Exception("This transcation is only partially signed. In order to publish a transaction on the network, it must be fully signed first."); } this.walletManager.ProcessTransaction(transaction, null, null, false); this.broadcasterManager.BroadcastTransactionAsync(transaction).GetAwaiter().GetResult(); return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult CreateReviewerAddressAsync([FromBody] CreateReviewerAddressRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { // calculate the Manager password hash byte[] binaryPassword = System.Text.Encoding.ASCII.GetBytes(request.RsaPassword); Org.BouncyCastle.Crypto.Digests.Sha512Digest sha = new Org.BouncyCastle.Crypto.Digests.Sha512Digest(); sha.BlockUpdate(binaryPassword, 0, binaryPassword.Length); byte[] shaOutput = new byte[512 / 8]; sha.DoFinal(shaOutput, 0); NBitcoin.DataEncoders.HexEncoder he = new NBitcoin.DataEncoders.HexEncoder(); string rsaPasswordHashHex = he.EncodeData(shaOutput); // create the multisig address PubKey[] groupMemberKeys = request.SignaturePubKeys.Select(pubKeyHex => new PubKey(pubKeyHex)).ToArray(); var scriptPubKey = PayToMultiSigTemplate .Instance .GenerateScriptPubKey(request.RequeiredSignatureCount, groupMemberKeys); PublicReviewerAddressModel model = new PublicReviewerAddressModel { PublicApiUrl = request.PublicApiUrl }; // check if the API is reachable and the address can be added to the watch list Uri apiRequestUri = new Uri(new Uri(model.PublicApiUrl), $"/api/WatchOnlyWallet/watch?address={scriptPubKey.Hash.GetAddress(this.network).ToString()}"); try { HttpWebRequest apiRequest = (HttpWebRequest)WebRequest.Create(apiRequestUri); ASCIIEncoding encoding = new ASCIIEncoding(); string postData = ""; byte[] data = encoding.GetBytes(postData); apiRequest.Method = "POST"; apiRequest.ContentType = "application/x-www-form-urlencoded"; apiRequest.ContentLength = data.Length; using (Stream stream = apiRequest.GetRequestStream()) { stream.Write(data, 0, data.Length); } HttpWebResponse apiResponse = (HttpWebResponse)apiRequest.GetResponse(); string responseString = new StreamReader(apiResponse.GetResponseStream()).ReadToEnd(); if (apiResponse.StatusCode != HttpStatusCode.OK) { throw new Exception($"The API request '{apiRequestUri.ToString()}' returned the status code '{apiResponse.StatusCode}'."); } } catch (Exception e) { throw new Exception($"The API request '{apiRequestUri.ToString()}' returned an error '{e.Message}'."); } // generate the RSA keypair for the address AsymmetricCipherKeyPair rsaKeyPair = GetRSAKeyPairFromSeed(request.RsaPassword + scriptPubKey.Hash.GetAddress(this.network).ToString()); RsaKeyParameters rsaPublicKey = rsaKeyPair.Public as RsaKeyParameters; RsaPublicKey pbk = new RsaPublicKey() { Exponent = rsaPublicKey.Exponent.ToByteArrayUnsigned(), Modulus = rsaPublicKey.Modulus.ToByteArrayUnsigned() }; RsaPrivateCrtKeyParameters rsaPrivateKey = rsaKeyPair.Private as RsaPrivateCrtKeyParameters; RsaPrivateKey prk = new RsaPrivateKey() { DP = rsaPrivateKey.DP.ToByteArrayUnsigned(), DQ = rsaPrivateKey.DQ.ToByteArrayUnsigned(), Exponent = rsaPrivateKey.Exponent.ToByteArrayUnsigned(), Modulus = rsaPrivateKey.Modulus.ToByteArrayUnsigned(), P = rsaPrivateKey.P.ToByteArrayUnsigned(), PublicExponent = rsaPrivateKey.PublicExponent.ToByteArrayUnsigned(), Q = rsaPrivateKey.Q.ToByteArrayUnsigned(), QInv = rsaPrivateKey.QInv.ToByteArrayUnsigned() }; // return all the information we have model = new PublicReviewerAddressModel { Network = this.network.ToString(), Address = scriptPubKey.Hash.GetAddress(this.network).ToString(), PublicName = request.PublicName, GroupName = request.GroupName, ValidFrom = request.ValidFrom.ToString("o"), ValidUntil = request.ValidUntil.ToString("o"), ScriptPubKeyHex = scriptPubKey.ToHex(), RsaPublicKeyHex = pbk.ToHex(), RsaPrivateKeyHex = prk.ToHex(), RsaPasswordHashHex = rsaPasswordHashHex, PublicApiUrl = request.PublicApiUrl }; ((WalletManager)this.walletManager).AddReviewerAddressToReviewerStore(model); return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult GetBlock([FromQuery] SearchByHashRequest query) { if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { uint256 blockId = uint256.Parse(query.Hash); ChainedHeader chainedHeader = this.chainIndexer.GetHeader(blockId); if (chainedHeader == null) { return(this.Ok("Block not found")); } Block block = chainedHeader.Block ?? this.blockStore.GetBlock(blockId); // In rare occasions a block that is found in the // indexer may not have been pushed to the store yet. if (block == null) { return(this.Ok("Block not found")); } if (!query.OutputJson) { return(this.Json(block)); } BlockModel blockModel = query.ShowTransactionDetails ? new BlockTransactionDetailsModel(block, chainedHeader, this.chainIndexer.Tip, this.network) : new BlockModel(block, chainedHeader, this.chainIndexer.Tip, this.network); if (this.network.Consensus.IsProofOfStake) { var posBlock = block as PosBlock; blockModel.PosBlockSignature = posBlock.BlockSignature.ToHex(this.network.Consensus.ConsensusFactory); blockModel.PosBlockTrust = new Target(chainedHeader.GetBlockTarget()).ToUInt256().ToString(); blockModel.PosChainTrust = chainedHeader.ChainWork.ToString(); // this should be similar to ChainWork if (this.stakeChain != null) { BlockStake blockStake = this.stakeChain.Get(blockId); blockModel.PosModifierv2 = blockStake?.StakeModifierV2.ToString(); blockModel.PosFlags = blockStake?.Flags == BlockFlag.BLOCK_PROOF_OF_STAKE ? "proof-of-stake" : "proof-of-work"; blockModel.PosHashProof = blockStake?.HashProof.ToString(); } } return(this.Json(blockModel)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult UploadWantedSystemMessageAsync([FromBody] UploadWantedSystemMessageRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { Transaction transaction = this.network.CreateTransaction(request.TransactionHex); var wantedMessageOuts = transaction.Outputs.Where(txOut => WantedSystemMessageTemplate.Instance.CheckScriptPubKey(txOut.ScriptPubKey)); if (wantedMessageOuts.Count() == 0) { throw new Exception("The transaction you provided doesn't contain any Wanted System Messages."); } var wm = this.walletManager as WalletManager; WantedSystemMessageModel wsmm = null; // let's make sure that the transaction is put into the message store wsmm = wm.AddWantedSystemMessageToMessageStore(transaction); // add the partially signed transaction to the message store // TODO: do not limit this check to the first input if (transaction.Inputs[0].ScriptSig.Length > 0) { wsmm = wm.AddPartiallySignedTxToMessageStore(transaction); } // get the reviewer's address details var reviewerAddress = wm.ReviewerAddresses[request.ReviewerAddress]; if (reviewerAddress == null) { throw new Exception($"The reviewer '{request.ReviewerAddress}' was not found in the address book."); } var multiSigParams = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(reviewerAddress.ScriptPubKey); // try to combine the partially signed signatures TransactionBuilder tb = new TransactionBuilder(this.network); tb.AddCoins((this.walletTransactionHandler as WalletTransactionHandler).GetCoinsForReviewersAddress(reviewerAddress)); var fullySignedTx = tb.CombineSignatures(wsmm.PartiallySignedTransactions.Select(stx => this.network.CreateTransaction(stx.TransactionHex)).ToArray()); if (fullySignedTx != null) { var checkResults = fullySignedTx.Check(); // TODO: do not limit this check to the first input string[] signatures = fullySignedTx.Inputs[0].ScriptSig.ToString().Split(' '); int signatureCount = signatures.Count(s => s != "0"); if ((checkResults != TransactionCheckResult.Success) || (signatureCount < multiSigParams.SignatureCount)) { fullySignedTx = null; } } // return the fully signed transaction if available UploadWantedSystemMessageModel model = new UploadWantedSystemMessageModel { WantedSystemMessage = wsmm, FullySignedTransactionHex = fullySignedTx == null ? "" : fullySignedTx.ToHex() }; return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult SetupOfflineColdStaking([FromBody] SetupOfflineColdStakingRequest request) { Guard.NotNull(request, nameof(request)); // Checks the request is valid. if (!this.ModelState.IsValid) { this.logger.LogTrace("(-)[MODEL_STATE_INVALID]"); return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { Money amount = Money.Parse(request.Amount); Money feeAmount = Money.Parse(request.Fees); (Transaction transaction, TransactionBuildContext context) = this.ColdStakingManager.GetColdStakingSetupTransaction( this.walletTransactionHandler, request.ColdWalletAddress, request.HotWalletAddress, request.WalletName, request.WalletAccount, null, amount, feeAmount, request.SubtractFeeFromAmount, true, request.SplitCount, request.SegwitChangeAddress); // TODO: We use the same code in the regular wallet for offline signing request construction, perhaps it should be moved to a common method // Need to be able to look up the keypath for the UTXOs that were used. IEnumerable <UnspentOutputReference> spendableTransactions = this.ColdStakingManager.GetSpendableTransactionsInAccount( new WalletAccountReference(request.WalletName, request.WalletAccount)).ToList(); var utxos = new List <UtxoDescriptor>(); var addresses = new List <AddressDescriptor>(); foreach (ICoin coin in context.TransactionBuilder.FindSpentCoins(transaction)) { utxos.Add(new UtxoDescriptor() { Amount = coin.TxOut.Value.ToUnit(MoneyUnit.BTC).ToString(), TransactionId = coin.Outpoint.Hash.ToString(), Index = coin.Outpoint.N.ToString(), ScriptPubKey = coin.TxOut.ScriptPubKey.ToHex() }); UnspentOutputReference outputReference = spendableTransactions.FirstOrDefault(u => u.Transaction.Id == coin.Outpoint.Hash && u.Transaction.Index == coin.Outpoint.N); if (outputReference != null) { bool segwit = outputReference.Transaction.ScriptPubKey.IsScriptType(ScriptType.P2WPKH); addresses.Add(new AddressDescriptor() { Address = segwit ? outputReference.Address.Bech32Address : outputReference.Address.Address, AddressType = segwit ? "p2wpkh" : "p2pkh", KeyPath = outputReference.Address.HdPath }); } } // Return transaction hex, UTXO list, address list. The offline signer will infer from the transaction structure that a cold staking setup is being made. var model = new BuildOfflineSignResponse() { WalletName = request.WalletName, WalletAccount = request.WalletAccount, Fee = context.TransactionFee.ToUnit(MoneyUnit.BTC).ToString(), UnsignedTransaction = transaction.ToHex(), Utxos = utxos, Addresses = addresses }; this.logger.LogTrace("(-):'{0}'", model); return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); this.logger.LogTrace("(-)[ERROR]"); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult ListSpendableTransactionOuts([FromBody] ListSpendableTransactionOutsRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { string requestWalletName = null; string requestAccountName = null; if (String.IsNullOrWhiteSpace(request.WalletName) || String.IsNullOrWhiteSpace(request.AccountName)) { // let's find the proper wallet and account first foreach (var walletName in this.walletManager.GetWalletsNames()) { HdAddress address = null; foreach (var hdAccount in this.walletManager.GetWallet(walletName).GetAccountsByCoinType(this.coinType)) { address = hdAccount.ExternalAddresses.FirstOrDefault(hdAddress => hdAddress.Address.ToString() == request.Address); if (address == null) { address = hdAccount.InternalAddresses.FirstOrDefault(hdAddress => hdAddress.Address.ToString() == request.Address); } if (address != null) { requestAccountName = hdAccount.Name; break; } } if (requestAccountName != null) { requestWalletName = walletName; break; } } if ((requestWalletName == null) || (requestAccountName == null)) { throw new Exception("The address you requested is not in this wallet."); } } else { requestWalletName = request.WalletName; requestAccountName = request.AccountName; } WalletAccountReference account = new WalletAccountReference(requestWalletName, requestAccountName); List <DetailedSpendableTransactionModel> transactionList = new List <DetailedSpendableTransactionModel>(); foreach (var spendableOutput in this.walletManager.GetSpendableTransactionsInAccount(account, request.MinConfirmations).OrderByDescending(a => a.Transaction.Amount)) { if (spendableOutput.Transaction.Amount == 0) { continue; } if (!String.IsNullOrWhiteSpace(request.Address) && (spendableOutput.Address.Address.ToString() != request.Address)) { continue; } transactionList.Add(new DetailedSpendableTransactionModel() { Address = spendableOutput.Address.Address.ToString(), TransactionHash = spendableOutput.Transaction.Id, Index = spendableOutput.Transaction.Index, Amount = spendableOutput.Transaction.Amount.Satoshi, ScriptPubKey = spendableOutput.Transaction.ScriptPubKey.ToHex() }); } ListSpendableTransactionsModel model = new ListSpendableTransactionsModel { Network = this.network.ToString(), WalletName = account.WalletName, AccountName = account.AccountName, SpendableTransactions = transactionList }; return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult GetHistory([FromQuery] WalletHistoryRequest request) { Guard.NotNull(request, nameof(request)); if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { var model = new WalletHistoryModel(); // Get a list of all the transactions found in an account (or in a wallet if no account is specified), with the addresses associated with them. IEnumerable <AccountHistory> accountsHistory = this.walletManager.GetHistory(request.WalletName, request.AccountName); foreach (AccountHistory accountHistory in accountsHistory) { var transactionItems = new List <TransactionItemModel>(); List <FlatHistory> items = accountHistory.History.OrderByDescending(o => o.Transaction.CreationTime).Take(200).ToList(); // Represents a sublist containing only the transactions that have already been spent. List <FlatHistory> spendingDetails = items.Where(t => t.Transaction.SpendingDetails != null).ToList(); // Represents a sublist of transactions associated with receive addresses + a sublist of already spent transactions associated with change addresses. // In effect, we filter out 'change' transactions that are not spent, as we don't want to show these in the history. List <FlatHistory> history = items.Where(t => !t.Address.IsChangeAddress() || (t.Address.IsChangeAddress() && !t.Transaction.IsSpendable())).ToList(); // Represents a sublist of 'change' transactions. List <FlatHistory> allchange = items.Where(t => t.Address.IsChangeAddress()).ToList(); foreach (FlatHistory item in history) { TransactionData transaction = item.Transaction; HdAddress address = item.Address; // We don't show in history transactions that are outputs of staking transactions. if (transaction.IsCoinStake != null && transaction.IsCoinStake.Value && transaction.SpendingDetails == null) { continue; } // First we look for staking transaction as they require special attention. // A staking transaction spends one of our inputs into 2 outputs, paid to the same address. if (transaction.SpendingDetails?.IsCoinStake != null && transaction.SpendingDetails.IsCoinStake.Value) { // We look for the 2 outputs related to our spending input. List <FlatHistory> relatedOutputs = items.Where(h => h.Transaction.Id == transaction.SpendingDetails.TransactionId && h.Transaction.IsCoinStake != null && h.Transaction.IsCoinStake.Value).ToList(); if (relatedOutputs.Any()) { // Add staking transaction details. // The staked amount is calculated as the difference between the sum of the outputs and the input and should normally be equal to 1. var stakingItem = new TransactionItemModel { Type = TransactionItemType.Staked, ToAddress = address.Address, Amount = relatedOutputs.Sum(o => o.Transaction.Amount) - transaction.Amount, Id = transaction.SpendingDetails.TransactionId, Timestamp = transaction.SpendingDetails.CreationTime, ConfirmedInBlock = transaction.SpendingDetails.BlockHeight }; transactionItems.Add(stakingItem); } // No need for further processing if the transaction itself is the output of a staking transaction. if (transaction.IsCoinStake != null) { continue; } } // Create a record for a 'receive' transaction. if (!address.IsChangeAddress()) { // Add incoming fund transaction details. var receivedItem = new TransactionItemModel { Type = TransactionItemType.Received, ToAddress = address.Address, Amount = transaction.Amount, Id = transaction.Id, Timestamp = transaction.CreationTime, ConfirmedInBlock = transaction.BlockHeight }; transactionItems.Add(receivedItem); } // If this is a normal transaction (not staking) that has been spent, add outgoing fund transaction details. if (transaction.SpendingDetails != null && transaction.SpendingDetails.IsCoinStake == null) { // Create a record for a 'send' transaction. uint256 spendingTransactionId = transaction.SpendingDetails.TransactionId; var sentItem = new TransactionItemModel { Type = TransactionItemType.Send, Id = spendingTransactionId, Timestamp = transaction.SpendingDetails.CreationTime, ConfirmedInBlock = transaction.SpendingDetails.BlockHeight, Amount = Money.Zero }; // If this 'send' transaction has made some external payments, i.e the funds were not sent to another address in the wallet. if (transaction.SpendingDetails.Payments != null) { sentItem.Payments = new List <PaymentDetailModel>(); foreach (PaymentDetails payment in transaction.SpendingDetails.Payments) { sentItem.Payments.Add(new PaymentDetailModel { DestinationAddress = payment.DestinationAddress, Amount = payment.Amount }); sentItem.Amount += payment.Amount; } } // Get the change address for this spending transaction. FlatHistory changeAddress = allchange.FirstOrDefault(a => a.Transaction.Id == spendingTransactionId); // Find all the spending details containing the spending transaction id and aggregate the sums. // This is our best shot at finding the total value of inputs for this transaction. var inputsAmount = new Money(spendingDetails.Where(t => t.Transaction.SpendingDetails.TransactionId == spendingTransactionId).Sum(t => t.Transaction.Amount)); // The fee is calculated as follows: funds in utxo - amount spent - amount sent as change. sentItem.Fee = inputsAmount - sentItem.Amount - (changeAddress == null ? 0 : changeAddress.Transaction.Amount); // Mined/staked coins add more coins to the total out. // That makes the fee negative. If that's the case ignore the fee. if (sentItem.Fee < 0) { sentItem.Fee = 0; } if (!transactionItems.Contains(sentItem, new SentTransactionItemModelComparer())) { transactionItems.Add(sentItem); } } } model.AccountsHistoryModel.Add(new AccountHistoryModel { TransactionsHistory = transactionItems.OrderByDescending(t => t.Timestamp).ToList(), Name = accountHistory.Account.Name, CoinType = this.coinType, HdPath = accountHistory.Account.HdPath }); } return(this.Json(model)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
//[ProducesResponseType(typeof(void), 401)] public IActionResult GetBlockAsync(string id, [FromQuery] BlockQueryRequest query) { if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } ChainedHeader chainHeader = null; if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentNullException("id", "id must be block hash or block height"); } // If the id is more than 50 characters, it is likely hash and not height. if (id.Length < 50) { chainHeader = this.chain.GetHeader(int.Parse(id)); } else { chainHeader = this.chain.GetHeader(new uint256(id)); } if (chainHeader == null) { return(new NotFoundObjectResult("Block not found")); } try { Block block = this.blockStoreCache.GetBlock(chainHeader.Header.GetHash()); if (block == null) { return(new NotFoundObjectResult("Block not found")); } PosBlockModel blockModel = new PosBlockModel(block, this.chain); if (this.stakeChain != null) { BlockStake blockStake = this.stakeChain.Get(chainHeader.HashBlock); if (blockStake != null) { blockModel.StakeTime = blockStake.StakeTime; blockModel.StakeModifierV2 = blockStake.StakeModifierV2; blockModel.HashProof = blockStake.HashProof; } } return(Json(blockModel)); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }