Ejemplo n.º 1
0
        ListSinceBlockTransactionCategoryModel GetListSinceBlockTransactionCategoryModel(
            GetTransactionModel transaction)
        {
            if (transaction.Isgenerated ?? false)
            {
                return(transaction.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity
                    ? ListSinceBlockTransactionCategoryModel.Generate
                    : ListSinceBlockTransactionCategoryModel.Immature);
            }

            return(transaction.Amount > 0
                ? ListSinceBlockTransactionCategoryModel.Receive
                : ListSinceBlockTransactionCategoryModel.Send);
        }
        public async Task GetTransactionOnGeneratedTransactionAsync()
        {
            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StraxRegTest150Miner).Start();

                ChainedHeader header     = sendingNode.FullNode.ChainIndexer.GetHeader(3);
                Block         fetchBlock = sendingNode.FullNode.NodeService <IBlockStore>().GetBlock(header.HashBlock);
                string        blockHash  = header.HashBlock.ToString();

                // Transaction included in block at height 3.
                string txId = fetchBlock.Transactions[0].GetHash().ToString();

                // Check transaction exists in block #3.
                BlockModel block = await $"http://localhost:{sendingNode.ApiPort}/api"
                                   .AppendPathSegment("blockstore/block")
                                   .SetQueryParams(new { hash = blockHash, outputJson = true })
                                   .GetJsonAsync <BlockModel>();

                block.Transactions.Should().ContainSingle(t => (string)t == txId);

                // Act.
                RPCClient   rpc      = sendingNode.CreateRPCClient();
                RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId);

                // Assert.
                GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>();
                result.Amount.Should().Be(this.network.Consensus.ProofOfWorkReward.ToDecimal(MoneyUnit.BTC));
                result.Fee.Should().BeNull();
                result.Confirmations.Should().Be(148);
                result.Isgenerated.Should().BeTrue();
                result.TransactionId.Should().Be(uint256.Parse(txId));
                result.BlockHash.ToString().Should().Be(blockHash);
                result.BlockIndex.Should().Be(0);
                result.BlockTime.Should().Be(header.Header.Time);
                result.TimeReceived.Should().Be(fetchBlock.Header.Time);
                result.TransactionTime.Should().Be(fetchBlock.Header.Time);
                result.Details.Should().ContainSingle();

                GetTransactionDetailsModel details = result.Details.Single();
                details.Address.Should().Be(fetchBlock.Transactions[0].Outputs[0].ScriptPubKey.GetDestinationAddress(this.network).ToString());
                details.Amount.Should().Be(this.network.Consensus.ProofOfWorkReward.ToDecimal(MoneyUnit.BTC));
                details.Fee.Should().BeNull();
                details.Category.Should().Be(GetTransactionDetailsCategoryModel.Generate);
                details.OutputIndex.Should().Be(0);
            }
        }
Ejemplo n.º 3
0
        public GetTransactionModel GetTransaction(string txid)
        {
            uint256 trxid;

            if (!uint256.TryParse(txid, out trxid))
            {
                throw new ArgumentException(nameof(txid));
            }

            var accountReference = this.GetAccount();
            var account          = this.walletManager.GetAccounts(accountReference.WalletName)
                                   .Where(i => i.Name.Equals(accountReference.AccountName))
                                   .Single();

            var transaction = account.GetTransactionsById(trxid)?.Single();

            if (transaction == null)
            {
                return(null);
            }

            var model = new GetTransactionModel
            {
                Amount          = transaction.Amount,
                BlockHash       = transaction.BlockHash,
                TransactionId   = transaction.Id,
                TransactionTime = transaction.CreationTime.ToUnixTimeSeconds(),
                Details         = new List <GetTransactionDetailsModel>(),
                Hex             = transaction.Hex == null ? string.Empty : transaction.Hex
            };

            if (transaction.SpendingDetails?.Payments != null)
            {
                foreach (var paymentDetail in transaction.SpendingDetails.Payments)
                {
                    model.Details.Add(new GetTransactionDetailsModel
                    {
                        Address  = paymentDetail.DestinationAddress,
                        Category = "send",
                        Amount   = paymentDetail.Amount
                    });
                }
            }

            return(model);
        }
Ejemplo n.º 4
0
        public async Task GetTransactionOnGeneratedTransactionAsync()
        {
            // Transaction included in block at height 3.
            string txId = "5369de2c6b7b62902d303995e90406d82de48f8c5ed0e847618dcf4e5cde84a1";

            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start();

                // Check transaction exists in block #3.
                BlockModel block = await $"http://localhost:{sendingNode.ApiPort}/api"
                                   .AppendPathSegment("blockstore/block")
                                   .SetQueryParams(new { hash = "57546d732a7cdf38135377fd3eddebbb928ec760c1e6a4b1b36e775b4ac2a3c7", outputJson = true })
                                   .GetJsonAsync <BlockModel>();

                block.Transactions.Should().ContainSingle(t => (string)t == txId);

                // Act.
                RPCClient   rpc      = sendingNode.CreateRPCClient();
                RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId);

                // Assert.
                GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>();
                result.Amount.Should().Be((decimal)4.00000000);
                result.Fee.Should().BeNull();
                result.Confirmations.Should().Be(148);
                result.Isgenerated.Should().BeTrue();
                result.TransactionId.Should().Be(uint256.Parse(txId));
                result.BlockHash.ToString().Should().Be("57546d732a7cdf38135377fd3eddebbb928ec760c1e6a4b1b36e775b4ac2a3c7");
                result.BlockIndex.Should().Be(0);
                result.BlockTime.Should().Be(1579271980);
                result.TimeReceived.Should().Be(1579271980);
                result.TransactionTime.Should().Be(1579271980);
                result.Details.Should().ContainSingle();

                GetTransactionDetailsModel details = result.Details.Single();
                details.Address.Should().Be("TXYdgqNVbHTfW5FsdxqSX6vejBByNEk2Yb");
                details.Amount.Should().Be((decimal)4.00000000);
                details.Fee.Should().BeNull();
                details.Category.Should().Be(GetTransactionDetailsCategoryModel.Generate);
                details.OutputIndex.Should().Be(0);
            }
        }
Ejemplo n.º 5
0
        public async Task GetTransactionOnGeneratedTransactionAsync()
        {
            // Transaction included in block at height 3.
            string txId = "21f3b1fd361f50992db217ea16728acc6b3dbf49c8531e60f8a28d7f3bdf4674";

            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start();

                // Check transaction exists in block #3.
                BlockModel block = await $"http://localhost:{sendingNode.ApiPort}/api"
                                   .AppendPathSegment("blockstore/block")
                                   .SetQueryParams(new { hash = "b1209de1c0347be83bb02a3bf9b70e33b06c82b91e68bc6392e6fb813cd5e4bd", outputJson = true })
                                   .GetJsonAsync <BlockModel>();

                block.Transactions.Should().ContainSingle(t => (string)t == txId);

                // Act.
                RPCClient   rpc      = sendingNode.CreateRPCClient();
                RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId);

                // Assert.
                GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>();
                result.Amount.Should().Be((decimal)4.00000000);
                result.Fee.Should().BeNull();
                result.Confirmations.Should().Be(148);
                result.Isgenerated.Should().BeTrue();
                result.TransactionId.Should().Be(uint256.Parse(txId));
                result.BlockHash.ToString().Should().Be("b1209de1c0347be83bb02a3bf9b70e33b06c82b91e68bc6392e6fb813cd5e4bd");
                result.BlockIndex.Should().Be(0);
                result.BlockTime.Should().Be(1547206662);
                result.TimeReceived.Should().Be(1547206661);
                result.TransactionTime.Should().Be(1547206661);
                result.Details.Should().ContainSingle();

                GetTransactionDetailsModel details = result.Details.Single();
                details.Address.Should().Be("TXYdgqNVbHTfW5FsdxqSX6vejBByNEk2Yb");
                details.Amount.Should().Be((decimal)4.00000000);
                details.Fee.Should().BeNull();
                details.Category.Should().Be(GetTransactionDetailsCategoryModel.Generate);
                details.OutputIndex.Should().Be(0);
            }
        }
Ejemplo n.º 6
0
        public async Task GetTransactionOnImmatureTransactionAsync()
        {
            // Transaction included in block at height 145.
            string txId = "d65542742643891e17e7527101cee774e7b561f3f6499302c653aa93ace0d303";

            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start();

                // Check transaction exists in block #145.
                BlockTransactionDetailsModel blockTransactionDetailsModel = await $"http://localhost:{sendingNode.ApiPort}/api"
                                                                            .AppendPathSegment("blockstore/block")
                                                                            .SetQueryParams(new { hash = "a5f03782979d83120d446b2eee764c2e976697c9bf6baf5f5e3145b58ad9e72a", outputJson = true, showtransactiondetails = true })
                                                                            .GetJsonAsync <BlockTransactionDetailsModel>();

                blockTransactionDetailsModel.Transactions.Should().ContainSingle(t => t.TxId == txId);

                // Act.
                RPCClient   rpc      = sendingNode.CreateRPCClient();
                RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId);

                // Assert.
                GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>();
                result.Amount.Should().Be((decimal)4.00000000);
                result.Fee.Should().BeNull();
                result.Confirmations.Should().Be(6);
                result.Isgenerated.Should().BeTrue();
                result.TransactionId.Should().Be(uint256.Parse(txId));
                result.BlockHash.ToString().Should().Be("a5f03782979d83120d446b2eee764c2e976697c9bf6baf5f5e3145b58ad9e72a");
                result.BlockIndex.Should().Be(0);
                result.BlockTime.Should().Be(blockTransactionDetailsModel.Time);
                result.TimeReceived.Should().BeLessOrEqualTo(blockTransactionDetailsModel.Time);
                result.Details.Should().ContainSingle();

                GetTransactionDetailsModel details = result.Details.Single();
                details.Address.Should().Be("TXYdgqNVbHTfW5FsdxqSX6vejBByNEk2Yb");
                details.Amount.Should().Be((decimal)4.00000000);
                details.Fee.Should().BeNull();
                details.Category.Should().Be(GetTransactionDetailsCategoryModel.Immature);
                details.OutputIndex.Should().Be(0);
            }
        }
Ejemplo n.º 7
0
        public async Task GetTransactionOnImmatureTransactionAsync()
        {
            // Transaction included in block at height 145.
            string txId = "3a96e0ffd83526b24fc035920fec89f689a778f64cd692b5eda2861cccb1ddba";

            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start();

                // Check transaction exists in block #145.
                BlockTransactionDetailsModel block = await $"http://localhost:{sendingNode.ApiPort}/api"
                                                     .AppendPathSegment("blockstore/block")
                                                     .SetQueryParams(new { hash = "b8ab1d66febfde4c26d0b4755f7def50a63e06267cefc6d6836651fa8910babc", outputJson = true, showtransactiondetails = true })
                                                     .GetJsonAsync <BlockTransactionDetailsModel>();

                block.Transactions.Should().ContainSingle(t => t.TxId == txId);

                // Act.
                RPCClient   rpc      = sendingNode.CreateRPCClient();
                RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId);

                // Assert.
                GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>();
                result.Amount.Should().Be((decimal)4.00000000);
                result.Fee.Should().BeNull();
                result.Confirmations.Should().Be(6);
                result.Isgenerated.Should().BeTrue();
                result.TransactionId.Should().Be(uint256.Parse(txId));
                result.BlockHash.ToString().Should().Be("b8ab1d66febfde4c26d0b4755f7def50a63e06267cefc6d6836651fa8910babc");
                result.BlockIndex.Should().Be(0);
                result.BlockTime.Should().Be(block.Time.ToUnixTimeSeconds());
                result.TimeReceived.Should().BeLessOrEqualTo(block.Time.ToUnixTimeSeconds());
                result.Details.Should().ContainSingle();

                GetTransactionDetailsModel details = result.Details.Single();
                details.Address.Should().Be("TXYdgqNVbHTfW5FsdxqSX6vejBByNEk2Yb");
                details.Amount.Should().Be((decimal)4.00000000);
                details.Fee.Should().BeNull();
                details.Category.Should().Be(GetTransactionDetailsCategoryModel.Immature);
                details.OutputIndex.Should().Be(0);
            }
        }
Ejemplo n.º 8
0
        public GetTransactionModel GetTransaction(string txid)
        {
            uint256 trxid;

            if (!uint256.TryParse(txid, out trxid))
            {
                throw new ArgumentException(nameof(txid));
            }

            var accountReference = this.GetAccount();
            var account          = this.walletManager.GetAccounts(accountReference.WalletName)
                                   .Where(i => i.Name.Equals(accountReference.AccountName))
                                   .Single();

            // 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.GetHistoryById(trxid, accountReference.WalletName, accountReference.AccountName).ToList();

            if (!accountsHistory.Any())
            {
                return(null); // This transaction is not relevant for our wallet.
            }

            var accountHistory        = accountsHistory.First();
            var accountHistoryHistory = accountHistory.History.FirstOrDefault();
            var transaction           = accountHistoryHistory.Transaction;
            var isChangeAddress       = accountHistoryHistory.Address.IsChangeAddress();

            var confirmations = GetConformationCount(accountHistoryHistory.Transaction);

            var details = new List <GetTransactionDetailsModel>();

            // Calculate the actual effect on the wallet that this transaction had.
            Money amount = 0;

            if (isChangeAddress)
            {
                // The actual amount of sent amount is accessible on the Payments that exists on the TransactionData that is the source of this
                // input. So we need to query the history to get that other transaction, so we can parse its Payments, and not those that exists
                // on the this current transaction.
                var paymentTransactions = account.GetTransactionsByPaymentTransactionId(trxid);

                foreach (var paymentTransaction in paymentTransactions)
                {
                    // For change address history, the Amount property on the transaction will display the amount that was sent into the change address.
                    // What we need for this RPC call, is the amount that was "taken out" of the wallet, and that is available in the spending details.
                    foreach (var payment in paymentTransaction.SpendingDetails.Payments)
                    {
                        // Increase the total amount of change in the wallet if there are multiple payments.
                        amount += -payment.Amount;

                        details.Add(new GetTransactionDetailsModel
                        {
                            Account  = string.Empty, // Can be empty string for default account.
                            Address  = payment.DestinationAddress,
                            Category = "send",
                            Amount   = -payment.Amount
                        });
                    }
                }
            }
            else
            {
                if (transaction.IsCoinStake.GetValueOrDefault(false))
                {
                    // Since this is a coin stake output, we'll hard-code to the ProofOfStakeReward. If this is implemented with a variable
                    // output, then this will likely result in invalid responses.
                    amount = this.FullNode.Network.Consensus.ProofOfStakeReward;

                    details.Add(new GetTransactionDetailsModel
                    {
                        Account  = string.Empty, // Can be empty string for default account.
                        Address  = accountHistoryHistory.Address.Address,
                        Category = "stake",
                        Amount   = amount
                    });
                }
                else
                {
                    amount = transaction.Amount;

                    // For "receive" payments, we can read the actual Amount on the TransactionData instance. This will display the correct change on the wallet.
                    details.Add(new GetTransactionDetailsModel
                    {
                        Account  = string.Empty, // Can be empty string for default account.
                        Address  = accountHistoryHistory.Address.Address,
                        Category = "receive",
                        Amount   = amount
                    });
                }
            }

            var model = new GetTransactionModel
            {
                Amount          = amount,
                BlockHash       = transaction.BlockHash,
                TransactionId   = transaction.Id,
                TransactionTime = transaction.CreationTime.ToUnixTimeSeconds(),
                Confirmations   = confirmations,
                Details         = details,
                Hex             = transaction.Hex == null ? string.Empty : transaction.Hex,
            };

            return(model);
        }
        public async Task <GetTransactionModel> GetTransactionAsync(string txid)
        {
            if (!uint256.TryParse(txid, out uint256 trxid))
            {
                throw new ArgumentException(nameof(txid));
            }

            WalletAccountReference accountReference = this.GetAccount();
            HdAccount account = this.walletManager.GetAccounts(accountReference.WalletName).Single(a => a.Name == accountReference.AccountName);

            // Get the transaction from the wallet by looking into received and send transactions.
            List <HdAddress>       addresses            = account.GetCombinedAddresses().ToList();
            List <TransactionData> receivedTransactions = addresses.Where(r => !r.IsChangeAddress() && r.Transactions != null).SelectMany(a => a.Transactions.Where(t => t.Id == trxid)).ToList();
            List <TransactionData> sendTransactions     = addresses.Where(r => r.Transactions != null).SelectMany(a => a.Transactions.Where(t => t.SpendingDetails != null && t.SpendingDetails.TransactionId == trxid)).ToList();

            if (!receivedTransactions.Any() && !sendTransactions.Any())
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id.");
            }

            // Get the block hash from the transaction in the wallet.
            TransactionData transactionFromWallet = null;
            uint256         blockHash             = null;
            int?            blockHeight;

            if (receivedTransactions.Any())
            {
                blockHeight           = receivedTransactions.First().BlockHeight;
                blockHash             = receivedTransactions.First().BlockHash;
                transactionFromWallet = receivedTransactions.First();
            }
            else
            {
                blockHeight = sendTransactions.First().SpendingDetails.BlockHeight;
                blockHash   = blockHeight != null?this.Chain.GetBlock(blockHeight.Value).HashBlock : null;
            }

            // Get the block containing the transaction (if it has  been confirmed).
            ChainedHeaderBlock chainedHeaderBlock = null;

            if (blockHash != null)
            {
                await this.ConsensusManager.GetOrDownloadBlocksAsync(new List <uint256> {
                    blockHash
                }, b => { chainedHeaderBlock = b; });
            }

            Block       block = null;
            Transaction transactionFromStore = null;

            if (chainedHeaderBlock != null)
            {
                block = chainedHeaderBlock.Block;
                transactionFromStore = block.Transactions.Single(t => t.GetHash() == trxid);
            }

            DateTimeOffset transactionTime;
            bool           isGenerated;
            string         hex;

            if (transactionFromStore != null)
            {
                transactionTime = Utils.UnixTimeToDateTime(transactionFromStore.Time);
                isGenerated     = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake;
                hex             = transactionFromStore.ToHex();
            }
            else if (transactionFromWallet != null)
            {
                transactionTime = transactionFromWallet.CreationTime;
                isGenerated     = transactionFromWallet.IsCoinBase == true || transactionFromWallet.IsCoinStake == true;
                hex             = transactionFromWallet.Hex;
            }
            else
            {
                transactionTime = sendTransactions.First().SpendingDetails.CreationTime;
                isGenerated     = false;
                hex             = null; // TODO get from mempool
            }

            Money amountSent  = sendTransactions.Select(s => s.SpendingDetails).SelectMany(sds => sds.Payments).GroupBy(p => p.DestinationAddress).Select(g => g.First()).Sum(p => p.Amount);
            Money totalAmount = receivedTransactions.Sum(t => t.Amount) - amountSent;

            var model = new GetTransactionModel
            {
                Amount          = totalAmount.ToDecimal(MoneyUnit.BTC),
                Fee             = null,// TODO this still needs to be worked on.
                Confirmations   = blockHeight != null ? this.ConsensusManager.Tip.Height - blockHeight.Value + 1 : 0,
                Isgenerated     = isGenerated ? true : (bool?)null,
                BlockHash       = blockHash,
                BlockIndex      = block?.Transactions.FindIndex(t => t.GetHash() == trxid),
                BlockTime       = block?.Header.BlockTime.ToUnixTimeSeconds(),
                TransactionId   = uint256.Parse(txid),
                TransactionTime = transactionTime.ToUnixTimeSeconds(),
                TimeReceived    = transactionTime.ToUnixTimeSeconds(),
                Details         = new List <GetTransactionDetailsModel>(),
                Hex             = hex
            };

            // Send transactions details.
            foreach (PaymentDetails paymentDetail in sendTransactions.Select(s => s.SpendingDetails).SelectMany(sd => sd.Payments))
            {
                // Only a single item should appear per destination address.
                if (model.Details.SingleOrDefault(d => d.Address == paymentDetail.DestinationAddress) == null)
                {
                    model.Details.Add(new GetTransactionDetailsModel
                    {
                        Address     = paymentDetail.DestinationAddress,
                        Category    = GetTransactionDetailsCategoryModel.Send,
                        Amount      = -paymentDetail.Amount.ToDecimal(MoneyUnit.BTC),
                        Fee         = null, // TODO this still needs to be worked on.
                        OutputIndex = paymentDetail.OutputIndex
                    });
                }
            }

            // Receive transactions details.
            foreach (TransactionData trxInWallet in receivedTransactions)
            {
                GetTransactionDetailsCategoryModel category;
                if (isGenerated)
                {
                    category = model.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? GetTransactionDetailsCategoryModel.Generate : GetTransactionDetailsCategoryModel.Immature;
                }
                else
                {
                    category = GetTransactionDetailsCategoryModel.Receive;
                }

                model.Details.Add(new GetTransactionDetailsModel
                {
                    Address     = addresses.First(a => a.Transactions.Contains(trxInWallet)).Address,
                    Category    = category,
                    Amount      = trxInWallet.Amount.ToDecimal(MoneyUnit.BTC),
                    OutputIndex = trxInWallet.Index
                });
            }

            return(model);
        }
Ejemplo n.º 10
0
        public GetTransactionModel GetTransaction(string txid)
        {
            if (!uint256.TryParse(txid, out uint256 trxid))
            {
                throw new ArgumentException(nameof(txid));
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();

            Types.Wallet wallet  = this.walletManager.GetWalletByName(accountReference.WalletName);
            HdAccount    account = this.walletManager.GetAccounts(accountReference.WalletName).Single(a => a.Name == accountReference.AccountName);

            // Get the transaction from the wallet by looking into received and send transactions.
            List <HdAddress>             addresses            = account.GetCombinedAddresses().ToList();
            List <TransactionOutputData> receivedTransactions = addresses.Where(r => !r.IsChangeAddress()).SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.Id == trxid)).ToList();
            List <TransactionOutputData> sendTransactions     = addresses.SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.SpendingDetails != null && t.SpendingDetails.TransactionId == trxid)).ToList();

            if (!receivedTransactions.Any() && !sendTransactions.Any())
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id.");
            }

            // Get the block hash from the transaction in the wallet.
            TransactionOutputData transactionFromWallet = null;
            uint256 blockHash = null;
            int?    blockHeight, blockIndex;

            if (receivedTransactions.Any())
            {
                blockHeight           = receivedTransactions.First().BlockHeight;
                blockIndex            = receivedTransactions.First().BlockIndex;
                blockHash             = receivedTransactions.First().BlockHash;
                transactionFromWallet = receivedTransactions.First();
            }
            else
            {
                blockHeight = sendTransactions.First().SpendingDetails.BlockHeight;
                blockIndex  = sendTransactions.First().SpendingDetails.BlockIndex;
                blockHash   = blockHeight != null?this.ChainIndexer.GetHeader(blockHeight.Value).HashBlock : null;
            }

            // Get the block containing the transaction (if it has  been confirmed).
            ChainedHeaderBlock chainedHeaderBlock = null;

            if (blockHash != null)
            {
                this.ConsensusManager.GetOrDownloadBlocks(new List <uint256> {
                    blockHash
                }, b => { chainedHeaderBlock = b; });
            }

            Block       block = null;
            Transaction transactionFromStore      = null;

            if (chainedHeaderBlock != null)
            {
                block = chainedHeaderBlock.Block;
                transactionFromStore = block.Transactions.Single(t => t.GetHash() == trxid);
            }

            DateTimeOffset transactionTime;
            bool           isGenerated;
            string         hex;

            if (transactionFromStore != null)
            {
                // TODO: Use block header time only. The transaction times will need to be uniformly set to a fixed value when an anti-malleability softfork activates
                if (transactionFromStore is IPosTransactionWithTime posTrx)
                {
                    transactionTime = Utils.UnixTimeToDateTime(posTrx.Time);
                }
                else
                {
                    transactionTime = Utils.UnixTimeToDateTime(chainedHeaderBlock.ChainedHeader.Header.Time);
                }

                isGenerated = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake;
                hex         = transactionFromStore.ToHex();
            }
            else if (transactionFromWallet != null)
            {
                transactionTime = transactionFromWallet.CreationTime;
                isGenerated     = transactionFromWallet.IsCoinBase == true || transactionFromWallet.IsCoinStake == true;
                hex             = transactionFromWallet.Hex;
            }
            else
            {
                transactionTime = sendTransactions.First().SpendingDetails.CreationTime;
                isGenerated     = false;
                hex             = null; // TODO get from mempool
            }

            var model = new GetTransactionModel
            {
                Confirmations   = blockHeight != null ? this.ConsensusManager.Tip.Height - blockHeight.Value + 1 : 0,
                Isgenerated     = isGenerated ? true : (bool?)null,
                BlockHash       = blockHash,
                BlockIndex      = blockIndex ?? block?.Transactions.FindIndex(t => t.GetHash() == trxid),
                BlockTime       = block?.Header.BlockTime.ToUnixTimeSeconds(),
                TransactionId   = uint256.Parse(txid),
                TransactionTime = transactionTime.ToUnixTimeSeconds(),
                TimeReceived    = transactionTime.ToUnixTimeSeconds(),
                Details         = new List <GetTransactionDetailsModel>(),
                Hex             = hex
            };

            Money feeSent = Money.Zero;

            if (sendTransactions.Any())
            {
                feeSent = wallet.GetSentTransactionFee(trxid);
            }

            // Send transactions details.
            foreach (PaymentDetails paymentDetail in sendTransactions.Select(s => s.SpendingDetails).SelectMany(sd => sd.Payments))
            {
                // Only a single item should appear per destination address.
                if (model.Details.SingleOrDefault(d => d.Address == paymentDetail.DestinationAddress) == null)
                {
                    model.Details.Add(new GetTransactionDetailsModel
                    {
                        Address     = paymentDetail.DestinationAddress,
                        Category    = GetTransactionDetailsCategoryModel.Send,
                        Amount      = -paymentDetail.Amount.ToDecimal(MoneyUnit.BTC),
                        Fee         = -feeSent.ToDecimal(MoneyUnit.BTC),
                        OutputIndex = paymentDetail.OutputIndex
                    });
                }
            }

            // Get the ColdStaking script template if available.
            Dictionary <string, ScriptTemplate> templates = this.walletManager.GetValidStakingTemplates();
            ScriptTemplate coldStakingTemplate            = templates.ContainsKey("ColdStaking") ? templates["ColdStaking"] : null;

            // Receive transactions details.
            foreach (TransactionOutputData trxInWallet in receivedTransactions)
            {
                // Skip the details if the script pub key is cold staking.
                // TODO: Verify if we actually need this any longer, after changing the internals to recognice account type!
                if (coldStakingTemplate != null && coldStakingTemplate.CheckScriptPubKey(trxInWallet.ScriptPubKey))
                {
                    continue;
                }

                GetTransactionDetailsCategoryModel category;

                if (isGenerated)
                {
                    category = model.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? GetTransactionDetailsCategoryModel.Generate : GetTransactionDetailsCategoryModel.Immature;
                }
                else
                {
                    category = GetTransactionDetailsCategoryModel.Receive;
                }

                model.Details.Add(new GetTransactionDetailsModel
                {
                    Address     = trxInWallet.Address,
                    Category    = category,
                    Amount      = trxInWallet.Amount.ToDecimal(MoneyUnit.BTC),
                    OutputIndex = trxInWallet.Index
                });
            }

            model.Amount = model.Details.Sum(d => d.Amount);
            model.Fee    = model.Details.FirstOrDefault(d => d.Category == GetTransactionDetailsCategoryModel.Send)?.Fee;

            return(model);
        }
Ejemplo n.º 11
0
        public Task <ListSinceBlockModel> ListSinceBlockAsync(string blockHash, int targetConfirmations = 1)
        {
            ChainedHeader headerBlock = null;

            if (!string.IsNullOrEmpty(blockHash) && uint256.TryParse(blockHash, out uint256 hashBlock))
            {
                headerBlock = this.ChainIndexer.GetHeader(hashBlock);
            }

            if (!string.IsNullOrEmpty(blockHash) && headerBlock == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
            }

            if (targetConfirmations < 1)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, "Invalid parameter");
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();

            Types.Wallet wallet = this.walletManager.GetWallet(accountReference.WalletName);

            IEnumerable <TransactionOutputData> transactions = wallet.GetAllTransactions();

            var model = new ListSinceBlockModel();

            foreach (TransactionOutputData transactionData in transactions)
            {
                GetTransactionModel transaction = this.GetTransaction(transactionData.Id.ToString());

                int blockHeight = transactionData.BlockHeight ?? 0;

                if (headerBlock != null && blockHeight < headerBlock.Height)
                {
                    continue;
                }

                if (transaction.Confirmations < targetConfirmations)
                {
                    continue;
                }

                ListSinceBlockTransactionCategoryModel category = GetListSinceBlockTransactionCategoryModel(transaction);

                model.Transactions.Add(new ListSinceBlockTransactionModel
                {
                    Confirmations   = transaction.Confirmations,
                    BlockHash       = transaction.BlockHash,
                    BlockIndex      = transaction.BlockIndex,
                    BlockTime       = transaction.BlockTime,
                    TransactionId   = transaction.TransactionId,
                    TransactionTime = transaction.TransactionTime,
                    TimeReceived    = transaction.TimeReceived,
                    Account         = accountReference.AccountName,
                    Address         = transactionData.ScriptPubKey?.GetDestinationAddress(this.Network)?.ToString(),
                    Amount          = transaction.Amount,
                    Category        = category,
                    Fee             = transaction.Fee
                });
            }

            model.LastBlock = this.ChainIndexer.Tip.HashBlock;

            return(Task.FromResult(model));
        }
Ejemplo n.º 12
0
        public async Task GetTransactionOnTransactionSentFromMultipleOutputsAsync()
        {
            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                CoreNode sendingNode   = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start();
                CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start();

                TestHelper.ConnectAndSync(sendingNode, receivingNode);

                // Get an address to send to.
                IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api"
                                                       .AppendPathSegment("wallet/unusedAddresses")
                                                       .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 })
                                                       .GetJsonAsync <IEnumerable <string> >();

                // Build and send the transaction with an Op_Return.
                WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api"
                                                                    .AppendPathSegment("wallet/build-transaction")
                                                                    .PostJsonAsync(new BuildTransactionRequest
                {
                    WalletName       = "mywallet",
                    AccountName      = "account 0",
                    FeeType          = "low",
                    Password         = "******",
                    ShuffleOutputs   = false,
                    AllowUnconfirmed = true,
                    Recipients       = unusedaddresses.Select(address => new RecipientModel
                    {
                        DestinationAddress = address,
                        Amount             = "98000002"
                    }).ToList(),
                })
                                                                    .ReceiveJson <WalletBuildTransactionModel>();

                await $"http://localhost:{sendingNode.ApiPort}/api"
                .AppendPathSegment("wallet/send-transaction")
                .PostJsonAsync(new SendTransactionRequest
                {
                    Hex = buildTransactionModel.Hex
                })
                .ReceiveJson <WalletSendTransactionModel>();

                uint256 txId = buildTransactionModel.TransactionId;

                // Mine so that we make sure the node is up to date.
                TestHelper.MineBlocks(sendingNode, 1);

                // Get the block that was mined.
                string lastBlockHash = await $"http://localhost:{sendingNode.ApiPort}/api"
                                       .AppendPathSegment("consensus/getbestblockhash")
                                       .GetJsonAsync <string>();

                BlockModel blockModelAtTip = await $"http://localhost:{sendingNode.ApiPort}/api"
                                             .AppendPathSegment("blockstore/block")
                                             .SetQueryParams(new { hash = lastBlockHash, outputJson = true })
                                             .GetJsonAsync <BlockModel>();

                Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex);

                RPCClient   rpcSendingNode  = sendingNode.CreateRPCClient();
                RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString());

                // Assert.
                GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>();
                resultSendingWallet.Amount.Should().Be((decimal) - 98000002.00000000);
                resultSendingWallet.Fee.Should().Be((decimal) - 0.0001);
                resultSendingWallet.Confirmations.Should().Be(1);
                resultSendingWallet.Isgenerated.Should().BeNull();
                resultSendingWallet.TransactionId.Should().Be(txId);
                resultSendingWallet.BlockHash.Should().Be(uint256.Parse(blockModelAtTip.Hash));
                resultSendingWallet.BlockIndex.Should().Be(1);
                resultSendingWallet.BlockTime.Should().Be(blockModelAtTip.Time);
                resultSendingWallet.TimeReceived.Should().BeLessOrEqualTo(blockModelAtTip.Time);
                resultSendingWallet.TransactionTime.Should().Be(((PosTransaction)trx).Time);
                resultSendingWallet.Details.Count.Should().Be(1);

                GetTransactionDetailsModel detailsSendingWallet = resultSendingWallet.Details.Single();
                detailsSendingWallet.Address.Should().Be(unusedaddresses.Single());
                detailsSendingWallet.Amount.Should().Be((decimal) - 98000002.00000000);
                detailsSendingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Send);
                detailsSendingWallet.Fee.Should().Be((decimal) - 0.0001);
                detailsSendingWallet.OutputIndex.Should().Be(1);
            }
        }
Ejemplo n.º 13
0
        public async Task GetTransactionOnUnconfirmedTransactionAsync()
        {
            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                // Create a sending and a receiving node.
                CoreNode sendingNode   = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start();
                CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start();

                TestHelper.ConnectAndSync(sendingNode, receivingNode);

                // Get an address to send to.
                IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api"
                                                       .AppendPathSegment("wallet/unusedAddresses")
                                                       .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 })
                                                       .GetJsonAsync <IEnumerable <string> >();

                // Build and send the transaction with an Op_Return.
                WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api"
                                                                    .AppendPathSegment("wallet/build-transaction")
                                                                    .PostJsonAsync(new BuildTransactionRequest
                {
                    WalletName       = "mywallet",
                    AccountName      = "account 0",
                    FeeType          = "low",
                    Password         = "******",
                    ShuffleOutputs   = false,
                    AllowUnconfirmed = true,
                    Recipients       = unusedaddresses.Select(address => new RecipientModel
                    {
                        DestinationAddress = address,
                        Amount             = "1"
                    }).ToList(),
                })
                                                                    .ReceiveJson <WalletBuildTransactionModel>();

                await $"http://localhost:{sendingNode.ApiPort}/api"
                .AppendPathSegment("wallet/send-transaction")
                .PostJsonAsync(new SendTransactionRequest
                {
                    Hex = buildTransactionModel.Hex
                })
                .ReceiveJson <WalletSendTransactionModel>();

                uint256 txId = buildTransactionModel.TransactionId;

                TestBase.WaitLoop(() =>
                {
                    WalletHistoryModel history = $"http://localhost:{receivingNode.ApiPort}/api"
                                                 .AppendPathSegment("wallet/history")
                                                 .SetQueryParams(new { walletName = "mywallet", AccountName = "account 0" })
                                                 .GetAsync()
                                                 .ReceiveJson <WalletHistoryModel>().GetAwaiter().GetResult();

                    return(history.AccountsHistoryModel.First().TransactionsHistory.Any(h => h.Id == txId));
                });

                TestBase.WaitLoop(() =>
                {
                    WalletHistoryModel history = $"http://localhost:{sendingNode.ApiPort}/api"
                                                 .AppendPathSegment("wallet/history")
                                                 .SetQueryParams(new { walletName = "mywallet", AccountName = "account 0" })
                                                 .GetAsync()
                                                 .ReceiveJson <WalletHistoryModel>().GetAwaiter().GetResult();

                    return(history.AccountsHistoryModel.First().TransactionsHistory.Any(h => h.Id == txId));
                });

                Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex);

                RPCClient   rpcReceivingNode  = receivingNode.CreateRPCClient();
                RPCResponse txReceivingWallet = rpcReceivingNode.SendCommand(RPCOperations.gettransaction, txId.ToString());

                RPCClient   rpcSendingNode  = sendingNode.CreateRPCClient();
                RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString());

                // Assert.
                GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>();
                resultSendingWallet.Amount.Should().Be((decimal) - 1.00000000);
                resultSendingWallet.Fee.Should().Be((decimal) - 0.0001);
                resultSendingWallet.Confirmations.Should().Be(0);
                resultSendingWallet.TransactionId.Should().Be(txId);
                resultSendingWallet.BlockHash.Should().BeNull();
                resultSendingWallet.BlockIndex.Should().BeNull();
                resultSendingWallet.BlockTime.Should().BeNull();
                resultSendingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds());
                resultSendingWallet.Details.Count.Should().Be(1);

                GetTransactionDetailsModel detailsSendingWallet = resultSendingWallet.Details.Single();
                detailsSendingWallet.Address.Should().Be(unusedaddresses.Single());
                detailsSendingWallet.Amount.Should().Be((decimal) - 1.00000000);
                detailsSendingWallet.Fee.Should().Be((decimal) - 0.0001);
                detailsSendingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Send);
                detailsSendingWallet.OutputIndex.Should().Be(1); // The output at index 0 is the change.

                GetTransactionModel resultReceivingWallet = txReceivingWallet.Result.ToObject <GetTransactionModel>();
                resultReceivingWallet.Amount.Should().Be((decimal)1.00000000);
                resultReceivingWallet.Fee.Should().BeNull();
                resultReceivingWallet.Confirmations.Should().Be(0);
                resultReceivingWallet.TransactionId.Should().Be(txId);
                resultReceivingWallet.BlockHash.Should().BeNull();
                resultReceivingWallet.BlockIndex.Should().BeNull();
                resultReceivingWallet.BlockTime.Should().BeNull();
                resultReceivingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds());
                resultReceivingWallet.TransactionTime.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds());
                resultReceivingWallet.Details.Should().ContainSingle();

                GetTransactionDetailsModel detailsReceivingWallet = resultReceivingWallet.Details.Single();
                detailsReceivingWallet.Address.Should().Be(unusedaddresses.Single());
                detailsReceivingWallet.Amount.Should().Be((decimal)1.00000000);
                detailsReceivingWallet.Fee.Should().BeNull();
                detailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive);
                detailsReceivingWallet.OutputIndex.Should().Be(1);
            }
        }
        public async Task GetTransactionOnTransactionReceivedToMultipleAddressesAsync()
        {
            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                // Create a sending and a receiving node.
                CoreNode sendingNode   = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start();
                CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start();

                TestHelper.ConnectAndSync(sendingNode, receivingNode);

                // Get an address to send to.
                IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api"
                                                       .AppendPathSegment("wallet/unusedAddresses")
                                                       .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 2 })
                                                       .GetJsonAsync <IEnumerable <string> >();

                // Build and send the transaction with an Op_Return.
                WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api"
                                                                    .AppendPathSegment("wallet/build-transaction")
                                                                    .PostJsonAsync(new BuildTransactionRequest
                {
                    WalletName       = "mywallet",
                    AccountName      = "account 0",
                    FeeType          = "low",
                    Password         = "******",
                    ShuffleOutputs   = false,
                    AllowUnconfirmed = true,
                    Recipients       = unusedaddresses.Select(address => new RecipientModel
                    {
                        DestinationAddress = address,
                        Amount             = "1"
                    }).ToList()
                })
                                                                    .ReceiveJson <WalletBuildTransactionModel>();

                await $"http://localhost:{sendingNode.ApiPort}/api"
                .AppendPathSegment("wallet/send-transaction")
                .PostJsonAsync(new SendTransactionRequest
                {
                    Hex = buildTransactionModel.Hex
                })
                .ReceiveJson <WalletSendTransactionModel>();

                uint256 txId = buildTransactionModel.TransactionId;

                // Mine and sync so that we make sure the receiving node is up to date.
                TestHelper.MineBlocks(sendingNode, 1);
                TestHelper.WaitForNodeToSync(sendingNode, receivingNode);

                // Get the block that was mined.
                string lastBlockHash = await $"http://localhost:{sendingNode.ApiPort}/api"
                                       .AppendPathSegment("consensus/getbestblockhash")
                                       .GetJsonAsync <string>();

                BlockModel tip = await $"http://localhost:{sendingNode.ApiPort}/api"
                                 .AppendPathSegment("blockstore/block")
                                 .SetQueryParams(new { hash = lastBlockHash, outputJson = true })
                                 .GetJsonAsync <BlockModel>();

                Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex);

                RPCClient   rpcReceivingNode  = receivingNode.CreateRPCClient();
                RPCResponse txReceivingWallet = rpcReceivingNode.SendCommand(RPCOperations.gettransaction, txId.ToString());

                RPCClient   rpcSendingNode  = sendingNode.CreateRPCClient();
                RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString());

                // Assert.
                GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>();
                resultSendingWallet.Amount.Should().Be((decimal) - 2.00000000);
                //resultSendingWallet.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available.
                resultSendingWallet.Confirmations.Should().Be(1);
                resultSendingWallet.Isgenerated.Should().BeNull();
                resultSendingWallet.TransactionId.Should().Be(txId);
                resultSendingWallet.BlockHash.Should().Be(uint256.Parse(tip.Hash));
                resultSendingWallet.BlockIndex.Should().Be(1);
                resultSendingWallet.BlockTime.Should().Be(tip.Time.ToUnixTimeSeconds());
                resultSendingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds());
                resultSendingWallet.TransactionTime.Should().Be(trx.Time);
                resultSendingWallet.Details.Count.Should().Be(2);

                GetTransactionDetailsModel detailsSendingWalletFirstRecipient = resultSendingWallet.Details.Single(d => d.Address == unusedaddresses.First());
                detailsSendingWalletFirstRecipient.Address.Should().Be(unusedaddresses.First());
                detailsSendingWalletFirstRecipient.Amount.Should().Be((decimal) - 1.00000000);
                //detailsSendingWalletFirstRecipient.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available.
                detailsSendingWalletFirstRecipient.Category.Should().Be(GetTransactionDetailsCategoryModel.Send);
                detailsSendingWalletFirstRecipient.OutputIndex.Should().Be(1); // Output at index 0 contains the change.

                GetTransactionDetailsModel detailsSendingWalletSecondRecipient = resultSendingWallet.Details.Single(d => d.Address == unusedaddresses.Last());
                detailsSendingWalletSecondRecipient.Address.Should().Be(unusedaddresses.Last());
                detailsSendingWalletSecondRecipient.Amount.Should().Be((decimal) - 1.00000000);
                //detailsSendingWalletSecondRecipient.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available.
                detailsSendingWalletSecondRecipient.Category.Should().Be(GetTransactionDetailsCategoryModel.Send);
                detailsSendingWalletSecondRecipient.OutputIndex.Should().Be(2);

                // Checking receiver.
                GetTransactionModel resultReceivingWallet = txReceivingWallet.Result.ToObject <GetTransactionModel>();
                resultReceivingWallet.Amount.Should().Be((decimal)2.00000000);
                resultReceivingWallet.Fee.Should().BeNull();
                resultReceivingWallet.Confirmations.Should().Be(1);
                resultReceivingWallet.Isgenerated.Should().BeNull();
                resultReceivingWallet.TransactionId.Should().Be(txId);
                resultReceivingWallet.BlockHash.Should().Be(uint256.Parse(tip.Hash));
                resultReceivingWallet.BlockIndex.Should().Be(1);
                resultReceivingWallet.BlockTime.Should().Be(tip.Time.ToUnixTimeSeconds());
                resultReceivingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds());
                resultReceivingWallet.TransactionTime.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds());
                resultReceivingWallet.Details.Count.Should().Be(2);

                GetTransactionDetailsModel firstDetailsReceivingWallet = resultReceivingWallet.Details.Single(d => d.Address == unusedaddresses.First());
                firstDetailsReceivingWallet.Address.Should().Be(unusedaddresses.First());
                firstDetailsReceivingWallet.Amount.Should().Be((decimal)1.00000000);
                firstDetailsReceivingWallet.Fee.Should().BeNull();
                firstDetailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive);
                firstDetailsReceivingWallet.OutputIndex.Should().Be(1); // Output at index 0 contains the change.

                GetTransactionDetailsModel secondDetailsReceivingWallet = resultReceivingWallet.Details.Single(d => d.Address == unusedaddresses.Last());
                secondDetailsReceivingWallet.Address.Should().Be(unusedaddresses.Last());
                secondDetailsReceivingWallet.Amount.Should().Be((decimal)1.00000000);
                secondDetailsReceivingWallet.Fee.Should().BeNull();
                secondDetailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive);
                secondDetailsReceivingWallet.OutputIndex.Should().Be(2);
            }
        }
Ejemplo n.º 15
0
        public GetTransactionModel GetTransaction(string txid)
        {
            if (!uint256.TryParse(txid, out uint256 trxid))
            {
                throw new ArgumentException(nameof(txid));
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();

            Wallet    hdWallet  = this.walletManager.WalletRepository.GetWallet(accountReference.WalletName);
            HdAccount hdAccount = this.walletManager.WalletRepository.GetAccounts(hdWallet, accountReference.AccountName).First();

            IWalletAddressReadOnlyLookup addressLookup = this.walletManager.WalletRepository.GetWalletAddressLookup(accountReference.WalletName);

            bool IsChangeAddress(Script scriptPubKey)
            {
                return(addressLookup.Contains(scriptPubKey, out AddressIdentifier addressIdentifier) && addressIdentifier.AddressType == 1);
            }

            // Get the transaction from the wallet by looking into received and send transactions.
            List <TransactionData> receivedTransactions = this.walletManager.WalletRepository.GetTransactionOutputs(hdAccount, null, trxid, true)
                                                          .Where(td => !IsChangeAddress(td.ScriptPubKey)).ToList();
            List <TransactionData> sentTransactions = this.walletManager.WalletRepository.GetTransactionInputs(hdAccount, null, trxid, true).ToList();

            TransactionData firstReceivedTransaction = receivedTransactions.FirstOrDefault();
            TransactionData firstSendTransaction     = sentTransactions.FirstOrDefault();

            if (firstReceivedTransaction == null && firstSendTransaction == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id.");
            }

            uint256         blockHash = null;
            int?            blockHeight, blockIndex;
            DateTimeOffset  transactionTime;
            SpendingDetails spendingDetails = firstSendTransaction?.SpendingDetails;

            if (firstReceivedTransaction != null)
            {
                blockHeight     = firstReceivedTransaction.BlockHeight;
                blockIndex      = firstReceivedTransaction.BlockIndex;
                blockHash       = firstReceivedTransaction.BlockHash;
                transactionTime = firstReceivedTransaction.CreationTime;
            }
            else
            {
                blockHeight     = spendingDetails.BlockHeight;
                blockIndex      = spendingDetails.BlockIndex;
                blockHash       = spendingDetails.BlockHash;
                transactionTime = spendingDetails.CreationTime;
            }

            // Get the block containing the transaction (if it has been confirmed).
            ChainedHeaderBlock chainedHeaderBlock = null;

            if (blockHash != null)
            {
                this.ConsensusManager.GetOrDownloadBlocks(new List <uint256> {
                    blockHash
                }, b => { chainedHeaderBlock = b; });
            }

            Block       block = null;
            Transaction transactionFromStore = null;

            if (chainedHeaderBlock != null)
            {
                block = chainedHeaderBlock.Block;
                if (block != null)
                {
                    if (blockIndex == null)
                    {
                        blockIndex = block.Transactions.FindIndex(t => t.GetHash() == trxid);
                    }

                    transactionFromStore = block.Transactions[(int)blockIndex];
                }
            }

            bool   isGenerated;
            string hex;

            if (transactionFromStore != null)
            {
                transactionTime = Utils.UnixTimeToDateTime(chainedHeaderBlock.ChainedHeader.Header.Time);
                isGenerated     = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake;
                hex             = transactionFromStore.ToHex();
            }
            else
            {
                isGenerated = false;
                hex         = null; // TODO get from mempool
            }

            var model = new GetTransactionModel
            {
                Confirmations   = blockHeight != null ? this.ConsensusManager.Tip.Height - blockHeight.Value + 1 : 0,
                Isgenerated     = isGenerated ? true : (bool?)null,
                BlockHash       = blockHash,
                BlockIndex      = blockIndex,
                BlockTime       = block?.Header.BlockTime.ToUnixTimeSeconds(),
                TransactionId   = uint256.Parse(txid),
                TransactionTime = transactionTime.ToUnixTimeSeconds(),
                TimeReceived    = transactionTime.ToUnixTimeSeconds(),
                Details         = new List <GetTransactionDetailsModel>(),
                Hex             = hex
            };

            // Send transactions details.
            if (spendingDetails != null)
            {
                Money feeSent = Money.Zero;
                if (firstSendTransaction != null)
                {
                    // Get the change.
                    long change = spendingDetails.Change.Sum(o => o.Amount);

                    Money inputsAmount  = new Money(sentTransactions.Sum(i => i.Amount));
                    Money outputsAmount = new Money(spendingDetails.Payments.Sum(p => p.Amount) + change);

                    feeSent = inputsAmount - outputsAmount;
                }

                var details = spendingDetails.Payments
                              .GroupBy(detail => detail.DestinationAddress)
                              .Select(p => new GetTransactionDetailsModel()
                {
                    Address     = p.Key,
                    Category    = GetTransactionDetailsCategoryModel.Send,
                    OutputIndex = p.First().OutputIndex,
                    Amount      = 0 - p.Sum(detail => detail.Amount.ToDecimal(MoneyUnit.BTC)),
                    Fee         = -feeSent.ToDecimal(MoneyUnit.BTC)
                });

                model.Details.AddRange(details);
            }

            // Get the ColdStaking script template if available.
            Dictionary <string, ScriptTemplate> templates = this.walletManager.GetValidStakingTemplates();
            ScriptTemplate coldStakingTemplate            = templates.ContainsKey("ColdStaking") ? templates["ColdStaking"] : null;

            // Receive transactions details.
            IScriptAddressReader scriptAddressReader = this.FullNode.NodeService <IScriptAddressReader>();

            foreach (TransactionData trxInWallet in receivedTransactions)
            {
                // Skip the details if the script pub key is cold staking.
                // TODO: Verify if we actually need this any longer, after changing the internals to recognize account type
                if (coldStakingTemplate != null && coldStakingTemplate.CheckScriptPubKey(trxInWallet.ScriptPubKey))
                {
                    continue;
                }

                GetTransactionDetailsCategoryModel category;

                if (isGenerated)
                {
                    category = model.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? GetTransactionDetailsCategoryModel.Generate : GetTransactionDetailsCategoryModel.Immature;
                }
                else
                {
                    category = GetTransactionDetailsCategoryModel.Receive;
                }

                string address = scriptAddressReader.GetAddressFromScriptPubKey(this.FullNode.Network, trxInWallet.ScriptPubKey);

                model.Details.Add(new GetTransactionDetailsModel
                {
                    Address     = address,
                    Category    = category,
                    Amount      = trxInWallet.Amount.ToDecimal(MoneyUnit.BTC),
                    OutputIndex = trxInWallet.Index
                });
            }

            model.Amount = model.Details.Sum(d => d.Amount);
            model.Fee    = model.Details.FirstOrDefault(d => d.Category == GetTransactionDetailsCategoryModel.Send)?.Fee;

            return(model);
        }