Пример #1
0
        public static (Func <Context, Append>[] appends, Identifier keyid) inputs_and_change(
            OutputData[] coins,
            WalletConfig config,
            Keychain keychain,
            ulong amount
            )


        {
            var parts = new List <Func <Context, Append> >();

            // calculate the total across all inputs, and how much is left
            var total = coins.Select(s => s.Value).Aggregate((a, b) => a + b);

            if (total < amount)
            {
                throw new WalletErrorException(WalletError.NotEnoughFunds);
            }

            // sender is responsible for setting the fee on the partial tx
            // recipient should double check the fee calculation and not blindly trust the
            // sender
            var fee = Types.tx_fee((uint)coins.Length, 2, null);

            parts.Add(c => c.with_fee(fee));

            // if we are spending 10,000 coins to send 1,000 then our change will be 9,000
            // the fee will come out of the amount itself
            // if the fee is 80 then the recipient will only receive 920
            // but our change will still be 9,000
            var change = total - amount;

            // build inputs using the appropriate derived key_ids
            foreach (var coin in coins)
            {
                var keyId = keychain.Derive_key_id(coin.NChild);
                parts.Add(c => c.Input(coin.Value, keyId));
            }

            // track the output representing our change
            var changeKey = WalletData.With_wallet(config.DataFileDir,
                                                   walletData =>
            {
                var rootKeyId        = keychain.Root_key_id();
                var changeDerivation = walletData.Next_child(rootKeyId.Clone());
                var changekey        = keychain.Derive_key_id(changeDerivation);

                walletData.Add_output(new OutputData(
                                          rootKeyId.HexValue,
                                          changekey.HexValue,
                                          changeDerivation,
                                          change,
                                          OutputStatus.Unconfirmed,
                                          0,
                                          0,
                                          false));

                return(changekey);
            });


            parts.Add(c => c.Output(change, changeKey.Clone()));


            return(parts.ToArray(), changeKey);
        }
Пример #2
0
        /// Issue a new transaction to the provided sender by spending some of our
        /// walvar
        /// UTXOs. The destination can be "stdout" (for command line) or a URL to the
        /// recipients walvar receiver (to be implemented).
        public static void issue_send_tx(
            WalletConfig config,
            Keychain keychain,
            ulong amount,
            ulong minimumConfirmations,
            string dest,
            uint maxOutputs,
            bool selectionStrategy
            )
        {
            Checker.refresh_outputs(config, keychain);

            var chainTip      = Checker.get_tip_from_node(config);
            var currentHeight = chainTip.Height;

            // proof of concept - set lock_height on the tx
            var lockHeight = chainTip.Height;


            var(tx, blindSum, coins, changeKey) = build_send_tx(
                config,
                keychain,
                amount,
                currentHeight,
                minimumConfirmations,
                lockHeight,
                maxOutputs,
                selectionStrategy);

            var partialTx = PartialTx.build_partial_tx(amount, blindSum, tx);

            // Closure to acquire walvar lock and lock the coins being spent
            // so we avoid accidental double spend attempt.
            void UpdateWallet()
            {
                WalletData.With_wallet(config.DataFileDir, walletData =>
                {
                    foreach (var coin in coins)
                    {
                        walletData.Lock_output(coin);
                    }
                    return(walletData);
                });
            }

            // Closure to acquire walvar lock and devare the change output in case of tx failure.
            void RollbackWallet()
            {
                WalletData.With_wallet(config.DataFileDir, walletData =>
                {
                    Log.Information("cleaning up unused change output from walvar");
                    walletData.Delete_output(changeKey);
                    return(walletData);
                });
            }

            if (dest == "stdout")
            {
                var jsonTx = JsonConvert.SerializeObject(partialTx, Formatting.Indented);

                UpdateWallet();

                Console.WriteLine(jsonTx);
            }
            else if (dest.StartsWith("http"))
            {
                var url = $"{dest}/v1/receive/transaction";

                Log.Debug("Posting partial transaction to {url}", url);

                try
                {
                    Client.send_partial_tx(url, partialTx);
                    UpdateWallet();
                }
                catch
                {
                    Log.Error("Communication with receiver failed. Aborting transaction");
                    RollbackWallet();
                }
            }
            else
            {
                throw new Exception($"dest not in expected format: {dest}");
            }
        }
Пример #3
0
        /// Builds a single api query to retrieve the latest output data from the node.
        /// So we can refresh the local wallet outputs.
        public static bool refresh_outputs(WalletConfig config, Keychain keychain)

        {
            Log.Debug("Refreshing wallet outputs");


            var walletOutputs = new Dictionary <string, Identifier>();
            var commits       = new List <Commitment>();

            // build a local map of wallet outputs by commits
            // and a list of outputs we want to query the node for
            WalletData.Read_wallet(config.DataFileDir,
                                   walletData =>
            {
                foreach (var op in walletData
                         .Outputs
                         .Values
                         .Where(w => w.RootKeyId == keychain.Root_key_id().HexValue&& w.Status != OutputStatus.Spent))
                {
                    var commit = keychain.commit_with_key_index(op.Value, op.NChild);
                    commits.Add(commit);
                    walletOutputs.Add(commit.Hex, Identifier.From_hex(op.KeyId));
                }

                return(walletData);
            });

            // build the necessary query params -
            // ?id=xxx&id=yyy&id=zzz
            var queryParams = commits.Select(s => $"id={s.Hex}");

            var queryString = string.Join("&", queryParams);

            var url = $"{config.CheckNodeApiHttpAddr}/v1/chain/utxos/byids?{queryString}";


            // build a map of api outputs by commit so we can look them up efficiently
            var apiOutputs = new Dictionary <string, ApiOutput>();

            //HttpClient here
            // todo:asyncification



            HttpResponseMessage response;

            try
            {
                response = ApiClient.GetAsync(url).Result;
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Failed to refresh from {Url}", url);
                return(false);
            }


            // if we got anything other than 200 back from server, don't attempt to refresh the wallet
            //  data after
            if (!response.IsSuccessStatusCode)
            {
                Log.Warning("Failed to refresh from {Url} : {StatusCode} - {ReasonPhrase}", url, response.StatusCode, response.ReasonPhrase);
                return(false);
            }

            var content = response.Content.ReadAsStringAsync().Result;

            var outputs = JsonConvert.DeserializeObject <ApiOutput[]>(content);

            foreach (var op in outputs)
            {
                apiOutputs.Add(HexUtil.to_hex(op.Commit), op);
            }


// now for each commit, find the output in the wallet and
// the corresponding api output (if it exists)
// and refresh it in-place in the wallet.
// Note: minimizing the time we spend holding the wallet lock.
            WalletData.With_wallet(config.DataFileDir, walletData =>
            {
                foreach (var commit in commits)
                {
                    var id = walletOutputs[commit.Hex];

                    if (walletData.Outputs.TryGetValue(id.HexValue, out var op))
                    {
                        if (apiOutputs.TryGetValue(commit.Hex, out var apiOutput))
                        {
                            refresh_output(op, apiOutput);
                        }
                        else
                        {
                            mark_spent_output(op);
                        }
                    }
                }
                return(walletData);
            });

            return(true);
        }