MetacoSA / NBitcoin

Comprehensive Bitcoin library for the .NET framework.
MIT License
1.87k stars 846 forks source link

Transaction Broadcast is successfull, but it never appears in the block explorer #303

Closed EstebanGameDevelopment closed 5 years ago

EstebanGameDevelopment commented 6 years ago

I've problems to make a simple transaction work. It's always telling me that everything was fine, that the transaction was done successfully but I never see it in the block explorer https://testnet.blockexplorer.com/

What am I doing wrong? Why is telling me that everything is fine when it's not? These are some of the code the code tells me that are good transactions:

cdf1cb69fecd62aa2c4790a0cb8bbb60b7468a7aea9a530a5f9d767e12226f3c a416526fcfac4f114e6bb67dd8b4c61485da3d042b3e1fd1d80067a0db5f5f01 62f54f11382e862fba1b1cb943c98748a87f4f909e4066bb95632e148b9134a3

Please, could you help me?

        // Create a client
    NBitcoin.Networ m_network = NBitcoin.Network.TestNet;
        QBitNinjaClient clientQBitNinja = new QBitNinjaClient("http://api.qbit.ninja/", m_network);

    // CUSTOMER KEY
        var customerPrivateKey = new BitcoinSecret(GetPrivateKey());

        // SELLER KEY
        BitcoinAddress sellerPublicAddress = BitcoinAddress.Create("SELLER PUBLIC ADDRESS", m_network);

        // CREATE SOME FUNDING, I DON'T UNDERSTAND WHY THIS???
        Transaction customerFunding = new Transaction()
        {
            Outputs =
            {
                new TxOut("0.0009", customerPrivateKey.PrivateKey.PubKey)
            }
        };
        Coin[] customerCoins = customerFunding
                                .Outputs
                                .Select((o, i) => new Coin(new OutPoint(customerFunding.GetHash(), i), o))
                                .ToArray();

        // SEND ONLY TO HOUSE AND MINERS
        TransactionBuilder transactionBuilder = new TransactionBuilder();
        Transaction transaction = transactionBuilder
            .AddCoins(customerCoins)
            .AddKeys(customerPrivateKey.PrivateKey)
            .Send(sellerPublicAddress, "0.0002")
            .SendFees("0.0001")
            .SetChange(customerPrivateKey.GetAddress())
            .BuildTransaction(true);

        // BROADCAST TRANSACTION
        BroadcastResponse broadcastResponse = clientQBitNinja.Broadcast(transaction).Result;
NicolasDorier commented 6 years ago

Hey it is possible the problem come from QBitNinja for this. Can you try to push with https://www.smartbit.com.au/txs/pushtx ? and tell me if it works ?

EstebanGameDevelopment commented 6 years ago

I've tried:

QBitNinjaClient clientQBitNinja = new QBitNinjaClient("https://www.smartbit.com.au/txs/pushtx", NBitcoin.Network.TestNet);

and I get this error:

TlsException: Invalid certificate received from server. Error code: 0xffffffff800b010a
Mono.Security.Protocol.Tls.RecordProtocol.EndReceiveRecord (System.IAsyncResult asyncResult) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Mono.Security.Protocol.Tls.SslClientStream.SafeEndReceiveRecord (System.IAsyncResult ar, System.Boolean ignoreEmpty) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Mono.Security.Protocol.Tls.SslClientStream.NegotiateAsyncWorker (System.IAsyncResult result) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Rethrow as IOException: The authentication or decryption has failed.
Mono.Security.Protocol.Tls.SslClientStream.EndNegotiateHandshake (System.IAsyncResult result) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (System.IAsyncResult asyncResult) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Rethrow as IOException: The authentication or decryption has failed.
Mono.Security.Protocol.Tls.SslStreamBase.EndRead (System.IAsyncResult asyncResult) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Mono.Net.Security.Private.LegacySslStream.EndAuthenticateAsClient (System.IAsyncResult asyncResult) (at <344dc4d3f1ad41809df78607b6121a41>:0)
Mono.Net.Security.Private.LegacySslStream.AuthenticateAsClient (System.String targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, System.Boolean checkCertificateRevocation) (at <344dc4d3f1ad41809df78607b6121a41>:0)
Mono.Net.Security.MonoTlsStream.CreateStream (System.Byte[] buffer) (at <344dc4d3f1ad41809df78607b6121a41>:0)
System.Net.WebConnection.CreateStream (System.Net.HttpWebRequest request) (at <344dc4d3f1ad41809df78607b6121a41>:0)
Rethrow as WebException: Error: TrustFailure (The authentication or decryption has failed.)
System.Net.HttpWebRequest.EndGetRequestStream (System.IAsyncResult asyncResult) (at <344dc4d3f1ad41809df78607b6121a41>:0)
System.Threading.Tasks.TaskFactory`1[TResult].FromAsyncCoreLogic (System.IAsyncResult iar, System.Func`2[T,TResult] endFunction, System.Action`1[T] endAction, System.Threading.Tasks.Task`1[TResult] promise, System.Boolean requiresSynchronization) (at <9c9f068c46c64ffd91fda7af157b4d15>:0)
NicolasDorier commented 6 years ago

no no, I mean print the transaction with tx.ToHex() then copy paste this on https://www.smartbit.com.au/txs/pushtx

EstebanGameDevelopment commented 6 years ago

After copy&paste the transaction tx.ToHex 01000008000260a67fc4000000001976a9149d8c04627227ea55c6750b53a6027a0e3207622488ac201efc01000000001976a9144e61345dca15625474ef9399449010893178a8c688ac00000000 to the URL you gave me the result is: "PUSH TRANSACTION ERROR: TX DECODE FAILED"

This is the code I'm running and I'm getting the success response from broadcast:

// QBITNINJA CLIENT
QBitNinjaClient.SetCompression(false);
QBitNinjaClient clientQBitNinja = new QBitNinjaClient("http://api.qbit.ninja/", NBitcoin.Network.Main);

// KEYS CUSTOMER AND SELLER
var customerPrivateKey = new BitcoinSecret("PRIVATE KEY ON NETWORK MAIN");
BitcoinAddress sellerAddress = BitcoinAddress.Create("PUBLIC SELLER ADDRESS", NBitcoin.Network.Main);

Transaction customerTransaction = new Transaction();

// SET OUTPUTS
float moneyToPlayForVideo = 0.0001f;
float originMoney = (moneyToPlayForVideo / 100);
float destinationMoney = moneyToPlayForVideo - originMoney;
customerTransaction.Outputs.Add(new TxOut(Money.Coins((decimal)destinationMoney), sellerAddress.ScriptPubKey));
customerTransaction.Outputs.Add(new TxOut(Money.Coins((decimal)originMoney), customerPrivateKey.GetAddress().ScriptPubKey));

// SIGN TRANSACTION
Coin[] coins = customerTransaction.Outputs.AsCoins().ToArray();
customerTransaction.Sign(customerPrivateKey, coins);

// BROADCAST TRANSACTION
BroadcastResponse broadcastResponse = clientQBitNinja.Broadcast(customerTransaction).Result;
if (!broadcastResponse.Success)
{
    // IT IS RETURNING ALWAYS TRUE BUT THE TRANSACTION IS NOWHERE
}

I've extracted the part of the code in the NBitcoin Unity testing project in case you wanted to run by yourself, Unity is free, no need to pay for it.

Code of the NBitcoin Unity Project

This are instructions how to run the transaction I'm talking you about:

Basic Instructions to run the aforemetioned transaction

You can download Unity for free here, it's a tool thousands of programmers use, it's straighforward easy to use, just press the play button and you are good.

NicolasDorier commented 6 years ago

This can't work, you did not put any input in your transaction. Please read https://www.gitbook.com/book/programmingblockchain/programmingblockchain/details so you better understand how bitcoin works. You need inputs to spend money.

EstebanGameDevelopment commented 6 years ago

Sorry, I really don't understand the example of the tutorial. Why should I access to an existing transaction that I do not have or does not exist in order to fill the input?

Ok, this is what I'm trying to do. I'm trying just to fill the inputs with the coins of the customer wallet. That makes more sense to me than to access to a previous transaction I do not know:

        // QBITNINJA CLIENT
        QBitNinjaClient clientQBitNinja = new QBitNinjaClient("http://api.qbit.ninja/", NBitcoin.Network.Main);

        // KEYS CUSTOMER AND SELLER
        var customerPrivateKey = new BitcoinSecret("PRIVATE KEY OF THE CUSTOMER");
        BitcoinAddress sellerAddress = BitcoinAddress.Create("PUBLIC KEY OF THE SELLER", NBitcoin.Network.Main);

        Transaction customerTransaction = new Transaction();

        // SET INPUTS
        List<Coin> coinsCustomer = GetUnspentCoins(customerPrivateKey.GetAddress());
        if (coinsCustomer == null) return;
        for (int i = 0; i < coinsCustomer.Count; i++)
        {
            customerTransaction.Inputs.Add(new TxIn(coinsCustomer[i].Outpoint));
        }

        // SET OUTPUTS
        float moneyToPlayForVideo = 0.0001f;
        float originMoney = (moneyToPlayForVideo / 100);
        float destinationMoney = moneyToPlayForVideo - originMoney;
        customerTransaction.Outputs.Add(new TxOut(Money.Coins((decimal)destinationMoney), sellerAddress.ScriptPubKey));
        customerTransaction.Outputs.Add(new TxOut(Money.Coins((decimal)originMoney), customerPrivateKey.GetAddress().ScriptPubKey));

        // SIGN TRANSACTION
        Coin[] coins = customerTransaction.Outputs.AsCoins().ToArray();
        customerTransaction.Sign(customerPrivateKey, coins);

        // BROADCAST TRANSACTION
        BroadcastResponse broadcastResponse = clientQBitNinja.Broadcast(customerTransaction).Result;

This time I get an error, but I really don't know what is going on:

ErrorCode: INVALID Error message: dust

What is dust ?

I'm sorry if I feel kind of lost, I have a thousand of things to work out, I'm running out of time and sometimes I get a little bit lost and need help. Could you help me, please?

NicolasDorier commented 6 years ago

dust mean that the value of an output is so small that your transaction will be rejected by the network.

Please do not build a transaction by hand and use the TransactionBuilder instead (Explained in the book in later chapter), it prevents you from shooting yourself in the foot, and gives you better error message.

EstebanGameDevelopment commented 6 years ago

I have used the transaction but without any luck, I get success in the transaction but the operation is never done. In the transaction explorer it keeps telling me that the inputs are missing, but I've not found any example anywhere about how to add the input like it is done here (SetUnspent(coins)). Please, can you help me out to find out how you do it to introduce the input through the TransactionBuilder?

NicolasDorier commented 6 years ago

https://programmingblockchain.gitbooks.io/programmingblockchain/content/other_types_of_ownership/using_the_transactionbuilder.html

NicolasDorier commented 6 years ago

read the whole chapter. If the inputs are missing it means you either did not fill any input or that the input are already spent.

EstebanGameDevelopment commented 6 years ago

Yes, I've been working several days based on that chapter without any progress. Anyway, thanks for your help, I'll try to keep looking for a solution by myself.

NicolasDorier commented 6 years ago

look that the inputs you are using are not spent already.

EstebanGameDevelopment commented 6 years ago

Thanks Nicolas for your support.

I wasn't able to use the transaction builder, I did it manually and finally I spent some coins of my wallet to pay multiple addresses.

It has been a nice challenge to work all these days with the Blockchain, now that I'm able to pay I can keep doing good progress in the application.

NicolasDorier commented 6 years ago

You should really use the transaction builder. Please take a look at https://programmingblockchain.gitbooks.io/programmingblockchain/content/other_types_of_ownership/using_the_transactionbuilder.html . This is only a matter of specifying the Coins, the keys, where to send and signing.

EstebanGameDevelopment commented 6 years ago

Nicolas, I really appreciate the work you have done so far and I don't really want to be impolite, but I'm not able to understand the documentation and the code examples don't work at all. Critical parts of information are missing. I had to figure it out by myself looking in other sources to understand how they do it. It has not been easy because everywhere I looked they tried to make it as difficult as possible. In fact, it's pretty easy once you really understand the whole thing. Now I'm able to fill properly the inputs and the outputs without the TransactionBuilder. I have got so far several duplicate transactions that block some of my addresses for a while, but I'm willing to take the risk. I really didn't want to be rude, but redirect me to a documentation that is incomplete doesn't work for me. If I have annoy you I apologize and I'm not going to bother you ever again.

NicolasDorier commented 6 years ago

I think the problem is that you are searching for an easy to use code to reach your goal, while this book is not a recipe, but more a guide for you to understand bitcoin design. Please, take the time to read it from one chapter to the next so you understand better the spirit about the design of the TransactionBuilder.

If you search for code sample, you can take a look at unit tests in NBitcoin.

Building transactions by hand is really not recommended.

EstebanGameDevelopment commented 6 years ago

Yes, maybe it's my problem, but I code to create solutions not for the pleasure of coding by itself. I work towards a goal that is finish my app in the scheduled time. So I'm sorry, but I really don't have an infine amount of time and money to wonder around without any goal. If anyone is interested to get a transaction work in a matter of minutes and not days you can take this code and make instantly work:

public class BitCoinController : MonoBehaviour
{
    public const decimal MINER_FEE = 0.000001m;

    // -------------------------------------------
    /* 
     * Constructor
     */
    public BitCoinController()
    {
        // Since MonoPosixHelper is not there for Unity programmers we have to disable the compression
        QBitNinjaClient.SetCompression(false);
    }

    // -------------------------------------------
    /* 
     * Runs the most basic transaction possible: send money from your wallet to a collection of address
     */
    public void RunTransaction(string _privateKeyOrigin, decimal _amountToTransfer, params string[] _publicKeyDestinations)
    {
        // QBITNINJA CLIENT
        QBitNinjaClient clientQBitNinja = new QBitNinjaClient("http://api.qbit.ninja/", NBitcoin.Network.Main);

        // KEYS CUSTOMER
        var customerPrivateKey = new BitcoinSecret(_privateKeyOrigin);

        // ADDRESSES OF DESTINATIONS
        List<BitcoinAddress> destinationsAddresses = new List<BitcoinAddress>();
        for (int i = 0; i < _publicKeyDestinations.Length; i++)
        {
            destinationsAddresses.Add(BitcoinAddress.Create(_publicKeyDestinations[i], NBitcoin.Network.Main));
        }

        Transaction customerTransaction = new Transaction();

        // CHECK ENOUGH FUNDS IN YOUR WALLET
        List<Coin> coinsCustomer = GetUnspentCoins(customerPrivateKey.GetAddress());
        decimal totalAmountToTransfer = _amountToTransfer + MINER_FEE;
        if (!CheckBalanceEnough(customerPrivateKey, coinsCustomer, totalAmountToTransfer))
        {
            AddLog("++ERROR++ OPERATION CANCELED BECAUSE THERE ARE NO FUNDS TO COVER THE TRANSACTION");
            return;
        }

        // COINS TO SPEND
        List<Coin> coinsToSpendInTransaction = GetTransactionInputCoins(customerPrivateKey.GetAddress(), coinsCustomer, totalAmountToTransfer);

        // ADD INPUTS
        AddInputs(customerTransaction, coinsToSpendInTransaction);

        // SET OUTPUTS
        AddOuputs(customerPrivateKey, totalAmountToTransfer, MINER_FEE, customerTransaction, coinsToSpendInTransaction, destinationsAddresses.ToArray());

        // MESSAGE
        AddMessageOutput(customerTransaction, "Android Transaction!");

        // SIGN TRANSACTION
        SignTransaction(customerPrivateKey, customerTransaction);

        // BROADCAST TRANSACTION
        BroadcastTransaction(clientQBitNinja, customerTransaction);
    }

    // -------------------------------------------
    /* 
     * Gets the list of unspent coins of your wallet
     */
    private List<Coin> GetUnspentCoins(BitcoinAddress _address)
    {
        var client = new QBitNinjaClient("http://api.qbit.ninja/", NBitcoin.Network.Main);
        var balanceModel = client.GetBalance(_address, true).Result;
        if (balanceModel.Operations.Count == 0)
        {
            return null;
        }
        List<Coin> unspentCoins = new List<Coin>();
        foreach (var operation in balanceModel.Operations)
        {
            unspentCoins.AddRange(operation.ReceivedCoins.Select(coin => coin as Coin));
        }
        var balance = unspentCoins.Sum(x => x.Amount.ToDecimal(MoneyUnit.BTC));
        return unspentCoins;
    }

    // -------------------------------------------
    /* 
     * Gets the list of needed coins you can use to perform the transaction
     */
    private List<Coin> GetTransactionInputCoins(BitcoinAddress _customerAddress, List<Coin> _coinsCustomer, decimal _totalAmount)
    {
        List<Coin> coinsToSpend = new List<Coin>();
        decimal acumulated = 0;
        Debug.Log("TOTAL COINS TO ANALIZE[" + _coinsCustomer.Count + "]");
        for (int i = _coinsCustomer.Count - 1; i >= 0; i--)
        {
            var coin = _coinsCustomer[i];
            if (acumulated < _totalAmount)
            {
                if (coin.TxOut.ScriptPubKey == _customerAddress.ScriptPubKey)
                {
                    decimal realAmountCoin = coin.Amount.ToUnit(MoneyUnit.BTC);
                    acumulated += realAmountCoin;
                    coinsToSpend.Add(coin);
                }
            }
        }
        if (coinsToSpend.Count == 0)
        {
            Debug.Log("TxOut doesn't contain our ScriptPubKey");
            return null;
        }
        else
        {
            Debug.Log("We want to spend " + coinsToSpend.Count + " outpoints");
            for (int k = 0; k < coinsToSpend.Count; k++)
            {
                Debug.Log("    [COIN:" + k + "][AMOUNT:" + coinsToSpend[k].Amount.ToUnit(MoneyUnit.BTC) + "][OUTPOINT:" + coinsToSpend[k].Outpoint.N + "]");
            }

            return coinsToSpend;
        }
    }

    // -------------------------------------------
    /* 
     * Check if you have cash enough in your wallet to assume the transaction
     */
    private bool CheckBalanceEnough(BitcoinSecret _customerPrivateKey, List<Coin> _coinsCustomer, decimal _finalAmountToTransferTo)
    {        
        if (_coinsCustomer == null)
        {
            AddLog("++ERROR++ CONSUMER HAS NO COINS");
            return false;
        }
        decimal balance = _coinsCustomer.Sum(x => x.Amount.ToDecimal(MoneyUnit.BTC));
        AddLog("++++CURRENT BALANCE[" + balance + "] AND MONEY TO TRANSFER[" + _finalAmountToTransferTo + "]++++");
        if (_finalAmountToTransferTo > balance / 2)
        {
            AddLog("++ERROR++ THE FINAL AMOUNT[" + _finalAmountToTransferTo + "] IS BIGGER THAN THE 20% OF BALANCE[" + (balance / 2) + "]");
            return false;
        }
        return true;
    }

    // -------------------------------------------
    /* 
     * Add the inputs of the transaction from the coins of your wallet you are going to spend
     */
    private void AddInputs(Transaction _customerTransaction, List<Coin> _coinsToSpendInTransaction)
    {
        if ((_coinsToSpendInTransaction == null) || (_coinsToSpendInTransaction.Count == 0))
        {
            AddLog("++ERROR++ THERE IS NO LAST TRANSACTION");
            return;
        }
        for (int k = 0; k < _coinsToSpendInTransaction.Count; k++)
        {
            _customerTransaction.Inputs.Add(new TxIn()
            {
                PrevOut = _coinsToSpendInTransaction[k].Outpoint
            });
        }
    }

    // -------------------------------------------
    /* 
     * Add the outputs of the money to be sent, plus the miner fee
     */
    private void AddOuputs(BitcoinSecret _customerPrivateKey, decimal _amountToSpend, decimal _feeToSpend, Transaction _customerTransaction, List<Coin> _coinsToSpendInTransaction, params BitcoinAddress[] _publicDestinationAddresses)
    {
        // FRAGMENT THE TOTAL AMOUNT BETWEEN ALL THE ADDRESSES
        List<Money> moneyToPay = new List<Money>();
        decimal pieceOfMoney = _amountToSpend / _publicDestinationAddresses.Length;
        AddLog("ADD OUTPUTS:: pieceOfMoney="+ pieceOfMoney);
        for (int i = 0; i < _publicDestinationAddresses.Length; i++)
        {
            moneyToPay.Add(new Money(pieceOfMoney, MoneyUnit.BTC));
        }

        if ((_coinsToSpendInTransaction == null) || (_coinsToSpendInTransaction.Count == 0))
        {
            AddLog("++ERROR++ THERE IS NO COIN TO SELECT");
            return;
        }

        // CREATE ALL THE MONEY FROM THE COINS OF THE WALLET
        Money txInAmount = _coinsToSpendInTransaction[0].Amount;
        for (int k = 1; k < _coinsToSpendInTransaction.Count; k++)
        {
            txInAmount += _coinsToSpendInTransaction[k].Amount;
        }

        // CALCULATE THE MONEY TO RETURN TO ORIGIN (THE TRANSACTION SHOULD CONSUME ALL THE COINS, THAT'S WHY IT'S LIKE THIS)
        Money changeBackAmount = txInAmount;
        for (int i = 0; i < moneyToPay.Count; i++)
        {
            changeBackAmount -= moneyToPay[i];
        }

        // SET MINERS FEE
        changeBackAmount -= new Money(_feeToSpend, MoneyUnit.BTC);

        // ADD TXOUT OF DESTINATIONS
        for (int i = 0; i < moneyToPay.Count; i++)
        {
            TxOut destinationTxOut = new TxOut()
            {
                Value = moneyToPay[i],
                ScriptPubKey = _publicDestinationAddresses[i].ScriptPubKey
            };
            _customerTransaction.Outputs.Add(destinationTxOut);
        }

        // ADD TXOUT OF CHANGE BACK
        TxOut originTxOut = new TxOut()
        {
            Value = changeBackAmount,
            ScriptPubKey = _customerPrivateKey.ScriptPubKey
        };
        _customerTransaction.Outputs.Add(originTxOut);
    }

    // -------------------------------------------
    /* 
     * Add an informative message to the transaction
     */
    private void AddMessageOutput(Transaction _customerTransaction, string _message)
    {
        var bytes = Encoding.UTF8.GetBytes(_message);
        _customerTransaction.Outputs.Add(new TxOut()
        {
            Value = Money.Zero,
            ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes)
        });
    }

    // -------------------------------------------
    /* 
     * Sign the transaction with your private key
     */
    private void SignTransaction(BitcoinSecret _customerPrivateKey, Transaction _customerTransaction)
    {
        for (int k = 0; k < _customerTransaction.Inputs.Count; k++)
        {
            _customerTransaction.Inputs[k].ScriptSig = _customerPrivateKey.ScriptPubKey;
        }
        _customerTransaction.Sign(_customerPrivateKey, false);
    }

    // -------------------------------------------
    /* 
     * Broadcast the transaction to make it real
     */
    private void BroadcastTransaction(QBitNinjaClient _clientQBitNinja, Transaction _customerTransaction)
    {
        // BROADCAST TRANSACTION
        BroadcastResponse broadcastResponse = _clientQBitNinja.Broadcast(_customerTransaction).Result;
        if (broadcastResponse.Success)
        {
            // IT IS RETURNING ALWAYS TRUE BUT THE TRANSACTION IS NOWHERE
            AddLog("Success! You can check out the hash of the transaciton in any block explorer:");
            AddLog("HASH");
            AddLog(_customerTransaction.GetHash().ToString());
            AddLog("HEXADECIMAL");
            AddLog(_customerTransaction.ToHex());
        }
        else
        {
            AddLog("ErrorCode: " + broadcastResponse.Error.ErrorCode);
            AddLog("Error message: " + broadcastResponse.Error.Reason);
        }
    }

    private void AddLog(string _message)
    {
        Debug.Log(_message);
    }
}
NicolasDorier commented 6 years ago

You can remove AddOuputs, AddInputs, CheckBalanceEnough, GetTransactionInputCoins and replace with literally 20 lines of code bug free. (your code contains several bugs which will prevent some of the transactions to work on mainnet, so if you value your time, you must use TB)

try
{
    TransactionBuilder builder = new TransactionBuilder();
    builder.AddCoins(GetUnspentCoins(customerPrivateKey.GetAddress()));
    builder.AddKeys(customerPrivateKey);
    Money amounts = totalAmountToTransfer.Split(destinationsAddresses.Length).ToArray();
    for(int i = 0; i < destinationsAddresses.Length; i++)
    {
        builder.Send(destinationsAddresses[i], amounts[i]);
    }
        builder.SetChange(customerPrivateKey.ScriptPubKey);
    builder.SendFees(feeToSpend);
    var tx = builder.BuildTransaction(true);
} 
catch(NotEnoughFundsException ex)
{
    // not enough money, ex.Missing coins
}
EstebanGameDevelopment commented 6 years ago

Thanks for the code.

Could you point me out where are the bugs you talked about that I done in order to understand a little bit better how it works?

NicolasDorier commented 6 years ago

Even for me the number of rules are too big to enumerate them. TransactionBuilder checks a big part of them. On top of my head: several overflow issues that someone could exploit to make you send any number of bitcoin they want. Someone spamming your with below-dust output making your transaction unlikely to be mined. Lack of outpoint locking wihch mean that sometimes two transactions will try to spend the same outpoint.

EstebanGameDevelopment commented 6 years ago

I've been trying several times your transaction builder without success. It tells me everything is ok, that the transaction has been done correctly but when I check the code in https://blockexplorer.com/ it doesn't appear and when I put the hex in https://www.smartbit.com.au/txs/pushtx it tells everything is ok but the transaction information appears totally empty. The worse part is that after I used your code then I get my accounts with a TXN-MEMPOOL-CONFLICT and I can't use them for several days.

Please, could you give me some leads where to find what I need to change in my code to make the transaction safe without having to use the transaction builder?

NicolasDorier commented 6 years ago

TXN-MEMPOOL-CONFLICT is not a problem with my code, it means you are double spending the same input... seee "outpoint locking" I talked about. Also see above I exactly copy pasted the code you need for TransactionBuilder.

0x4Graham commented 6 years ago

Hi,

Seem's like I'm experiencing something similar.

I'm doing a simple transaction, and it says that the transaction is a success, but I cannot find anything on the block explorer for testnet.

Here is my code for the transaction:

` Transaction createTransaction = new Transaction() { Outputs = { new TxOut("0.5", secret.GetAddress()), new TxOut("0.55", secret.PrivateKey.PubKey) } }; Coin[] sendCoins = createTransaction .Outputs .Select((o, i) => new Coin(new OutPoint(createTransaction.GetHash(), i), o)) .ToArray();

                    var txBuilder = new TransactionBuilder();
                    var tx = txBuilder
                        .AddCoins(sendCoins)
                        .AddKeys(secret)
                        .Send(sendToAddress.ScriptPubKey, "0.2")
                        .SendFees("0.015")
                        .SetChange(secret.GetAddress())
                        .BuildTransaction(true);`

Which gives me a 'decoded' hex of: { "Version": "1", "LockTime": "0", "Vin": [ { "TxId": "c19af2e21ac61c14c0e2a8b7958c7b6e11c4159b2d9317c71c55c283e5f5e477", "Vout": "0", "ScriptSig": { "Asm": "304402206d2a7ed1532fee9dee559c3a10260e2304b16d2a935498fa1a8176bddb354e4202200bfbd235afa428854dd11e1e64e41f364bba6a8915992a57a99114223ad004b4[ALL] 037195ff7e15702362ec07e2df35f1af4cedb847c8c9b7c1c24dacb78fd092eacc", "Hex": "47304402206d2a7ed1532fee9dee559c3a10260e2304b16d2a935498fa1a8176bddb354e4202200bfbd235afa428854dd11e1e64e41f364bba6a8915992a57a99114223ad004b40121037195ff7e15702362ec07e2df35f1af4cedb847c8c9b7c1c24dacb78fd092eacc" }, "CoinBase": null, "TxInWitness": null, "Sequence": "4294967295" } ], "Vout": [ { "Value": 0.285, "N": 0, "ScriptPubKey": { "Asm": "OP_DUP OP_HASH160 88a0c85530b10d7c3ba79235e7cc761e808f7660 OP_EQUALVERIFY OP_CHECKSIG", "Hex": "76a91488a0c85530b10d7c3ba79235e7cc761e808f766088ac", "ReqSigs": 1, "Type": "pubkeyhash", "Addresses": [ "1DTRWTUAcgMwZ3c8DBy8T8p5mWbX8u4EAf" ] } }, { "Value": 0.2, "N": 1, "ScriptPubKey": { "Asm": "OP_HASH160 a9974100aeee974a20cda9a2f545704a0ab54fdc OP_EQUAL", "Hex": "a914a9974100aeee974a20cda9a2f545704a0ab54fdc87", "ReqSigs": 1, "Type": "scripthash", "Addresses": [ "3H9jKGajhPjtscscWeRGM94eKzVcaygeV6" ] } } ], "TxId": "33594b5da1df92315098d3035f4429f1978ba8303211b1f8594e9b1a8864969b" }

When trying to push to testnet, smart bit states it's missing inputs. What am I missing?

zeptin commented 6 years ago

The transaction being referred to for the input, c19af2e21ac61c14c0e2a8b7958c7b6e11c4159b2d9317c71c55c283e5f5e477, does not appear on the testnet block explorer, hence the 'missing inputs' message is accurate.

0x4Graham commented 6 years ago

What am I doing wrong in my code then that it's not fetching the input transaction?

NicolasDorier commented 6 years ago

for info you can replace

Coin[] sendCoins = createTransaction
.Outputs
.Select((o, i) => new Coin(new OutPoint(createTransaction.GetHash(), i), o))
.ToArray();

by

Coin[] sendCoins = createTransaction.Outputs.AsCoins().ToArray();

Your code is not fetching input transactions, you are just making a non existent createTransaction. This transaction need to come from the blockchain, and you need to have the keys for it.