chrisrico

Multi-output spends

Feb 6th, 2012
197
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Diff 10.94 KB | None | 0 0
  1. Index: tests/com/google/bitcoin/core/WalletTest.java
  2. IDEA additional info:
  3. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  4. <+>UTF-8
  5. ===================================================================
  6. --- tests/com/google/bitcoin/core/WalletTest.java   (revision b43b68626432650e94ffa680bbaf980589f8e3ec)
  7. +++ tests/com/google/bitcoin/core/WalletTest.java   (revision )
  8. @@ -86,6 +86,43 @@
  9.      }
  10.  
  11.      @Test
  12. +    public void customTransactionSpending() throws Exception {
  13. +        // We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change.
  14. +        BigInteger v1 = Utils.toNanoCoins(3, 0);
  15. +        Transaction t1 = createFakeTx(params, v1, myAddress);
  16. +
  17. +        wallet.receiveFromBlock(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
  18. +        assertEquals(v1, wallet.getBalance());
  19. +        assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
  20. +        assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
  21. +
  22. +        ECKey k2 = new ECKey();
  23. +        Address a2 = k2.toAddress(params);
  24. +        BigInteger v2 = toNanoCoins(0, 50);
  25. +        BigInteger v3 = toNanoCoins(0, 75);
  26. +        BigInteger v4 = toNanoCoins(1, 25);
  27. +
  28. +        Transaction t2 = new Transaction(params);
  29. +        t2.addOutput(v2, a2);
  30. +        t2.addOutput(v3, a2);
  31. +        t2.addOutput(v4, a2);
  32. +        boolean complete = wallet.completeTx(t2);
  33. +
  34. +        // Do some basic sanity checks.
  35. +        assertTrue(complete);
  36. +        assertEquals(1, t2.getInputs().size());
  37. +        assertEquals(myAddress, t2.getInputs().get(0).getScriptSig().getFromAddress());
  38. +        assertEquals(t2.getConfidence().getConfidenceType(), TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
  39. +
  40. +        // We have NOT proven that the signature is correct!
  41. +
  42. +        wallet.commitTx(t2);
  43. +        assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
  44. +        assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.SPENT));
  45. +        assertEquals(2, wallet.getPoolSize(WalletTransaction.Pool.ALL));
  46. +    }
  47. +
  48. +    @Test
  49.      public void sideChain() throws Exception {
  50.          // The wallet receives a coin on the main chain, then on a side chain. Only main chain counts towards balance.
  51.          BigInteger v1 = Utils.toNanoCoins(1, 0);
  52. Index: src/com/google/bitcoin/core/Transaction.java
  53. IDEA additional info:
  54. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  55. <+>UTF-8
  56. ===================================================================
  57. --- src/com/google/bitcoin/core/Transaction.java    (revision b43b68626432650e94ffa680bbaf980589f8e3ec)
  58. +++ src/com/google/bitcoin/core/Transaction.java    (revision )
  59. @@ -559,6 +559,13 @@
  60.      }
  61.  
  62.      /**
  63. +     * Creates an output based on the given address and value, adds it to this transaction.
  64. +     */
  65. +    public void addOutput(BigInteger value, Address address) {
  66. +        addOutput(new TransactionOutput(params, this, value, address));
  67. +    }
  68. +
  69. +    /**
  70.       * Once a transaction has some inputs and outputs added, the signatures in the inputs can be calculated. The
  71.       * signature is over the transaction itself, to prove the redeemer actually created that transaction,
  72.       * so we have to do this step last.<p>
  73. Index: src/com/google/bitcoin/core/Wallet.java
  74. IDEA additional info:
  75. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  76. <+>UTF-8
  77. ===================================================================
  78. --- src/com/google/bitcoin/core/Wallet.java (revision b43b68626432650e94ffa680bbaf980589f8e3ec)
  79. +++ src/com/google/bitcoin/core/Wallet.java (revision )
  80. @@ -679,7 +679,7 @@
  81.       *         and we did not create it, and it spends some of our outputs.</li>
  82.       * </ol>
  83.       */
  84. -    synchronized void commitTx(Transaction tx) throws VerificationException {
  85. +    public synchronized void commitTx(Transaction tx) throws VerificationException {
  86.          assert !pending.containsKey(tx.getHash()) : "commitTx called on the same transaction twice";
  87.          log.info("commitTx of {}", tx.getHashAsString());
  88.          tx.updatedAt = Utils.now();
  89. @@ -884,18 +884,14 @@
  90.  
  91.      /**
  92.       * Statelessly creates a transaction that sends the given number of nanocoins to address. The change is sent to
  93. -     * the first address in the wallet, so you must have added at least one key.<p>
  94. +     * {@link Wallet#getChangeAddress()}, so you must have added at least one key.<p>
  95.       * <p/>
  96.       * This method is stateless in the sense that calling it twice with the same inputs will result in two
  97.       * Transaction objects which are equal. The wallet is not updated to track its pending status or to mark the
  98.       * coins as spent until commitTx is called on the result.
  99.       */
  100.      synchronized Transaction createSend(Address address, BigInteger nanocoins) {
  101. -        // For now let's just pick the first key in our keychain. In future we might want to do something else to
  102. -        // give the user better privacy here, eg in incognito mode.
  103. -        assert keychain.size() > 0 : "Can't send value without an address to use for receiving change";
  104. -        ECKey first = keychain.get(0);
  105. -        return createSend(address, nanocoins, first.toAddress(params));
  106. +        return createSend(address, nanocoins, getChangeAddress());
  107.      }
  108.  
  109.      /**
  110. @@ -920,8 +916,8 @@
  111.      }
  112.  
  113.      /**
  114. -     * Sends coins to the given address, via the given {@link PeerGroup}. Change is returned to the first key in the
  115. -     * wallet. The transaction will be announced to any connected nodes asynchronously. If you would like to know when
  116. +     * Sends coins to the given address, via the given {@link PeerGroup}. Change is returned to {@link Wallet#getChangeAddress()}.
  117. +     * The transaction will be announced to any connected nodes asynchronously. If you would like to know when
  118.       * the transaction was successfully sent to at least one node, use
  119.       * {@link Wallet#sendCoinsOffline(Address, java.math.BigInteger)} and then {@link PeerGroup#broadcastTransaction(Transaction)}
  120.       * on the result to obtain a {@link java.util.concurrent.Future<Transaction>}.
  121. @@ -941,8 +937,8 @@
  122.      }
  123.  
  124.      /**
  125. -     * Sends coins to the given address, via the given {@link PeerGroup}. Change is returned to the first key in the
  126. -     * wallet. The method will block until the transaction has been announced to at least one node.
  127. +     * Sends coins to the given address, via the given {@link PeerGroup}. Change is returned to {@link Wallet#getChangeAddress()}.
  128. +     * The method will block until the transaction has been announced to at least one node.
  129.       *
  130.       * @param peerGroup a PeerGroup to use for broadcast or null.
  131.       * @param to        Which address to send coins to.
  132. @@ -961,7 +957,7 @@
  133.      }
  134.  
  135.      /**
  136. -     * Sends coins to the given address, via the given {@link Peer}. Change is returned to the first key in the wallet.
  137. +     * Sends coins to the given address, via the given {@link Peer}. Change is returned to {@link Wallet#getChangeAddress()}.
  138.       * If an exception is thrown by {@link Peer#sendMessage(Message)} the transaction is still committed, so the
  139.       * pending transaction must be broadcast <b>by you</b> at some other time.
  140.       *
  141. @@ -1004,6 +1000,33 @@
  142.      synchronized Transaction createSend(Address address, BigInteger nanocoins, Address changeAddress) {
  143.          log.info("Creating send tx to " + address.toString() + " for " +
  144.                  bitcoinValueToFriendlyString(nanocoins));
  145. +
  146. +        Transaction sendTx = new Transaction(params);
  147. +        sendTx.addOutput(nanocoins, address);
  148. +
  149. +        if (completeTx(sendTx, changeAddress)) {
  150. +            return sendTx;
  151. +        } else {
  152. +            return null;
  153. +        }
  154. +    }
  155. +
  156. +    /**
  157. +     * Takes a transaction with arbitrary outputs, gathers the necessary inputs for spending, and signs it
  158. +     * @param sendTx           The transaction to complete
  159. +     * @param changeAddress    Which address to send the change to, in case we can't make exactly the right value from
  160. +     *                         our coins. This should be an address we own (is in the keychain).
  161. +     * @return False if we cannot afford this send, true otherwise
  162. +     */
  163. +    public synchronized boolean completeTx(Transaction sendTx, Address changeAddress) {
  164. +        // Calculate the transaction total
  165. +        BigInteger nanocoins = BigInteger.ZERO;
  166. +        for(TransactionOutput output : sendTx.getOutputs()) {
  167. +            nanocoins = nanocoins.add(output.getValue());
  168. +        }
  169. +
  170. +        log.info("Completing send tx with {} outputs totalling {}", sendTx.getOutputs().size(), bitcoinValueToFriendlyString(nanocoins));
  171. +
  172.          // To send money to somebody else, we need to do gather up transactions with unspent outputs until we have
  173.          // sufficient value. Many coin selection algorithms are possible, we use a simple but suboptimal one.
  174.          // TODO: Sort coins so we use the smallest first, to combat wallet fragmentation and reduce fees.
  175. @@ -1023,12 +1046,10 @@
  176.              log.info("Insufficient value in wallet for send, missing " +
  177.                      bitcoinValueToFriendlyString(nanocoins.subtract(valueGathered)));
  178.              // TODO: Should throw an exception here.
  179. -            return null;
  180. +            return false;
  181.          }
  182.          assert gathered.size() > 0;
  183. -        Transaction sendTx = new Transaction(params);
  184.          sendTx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
  185. -        sendTx.addOutput(new TransactionOutput(params, sendTx, nanocoins, address));
  186.          BigInteger change = valueGathered.subtract(nanocoins);
  187.          if (change.compareTo(BigInteger.ZERO) > 0) {
  188.              // The value of the inputs is greater than what we want to send. Just like in real life then,
  189. @@ -1049,8 +1070,26 @@
  190.              // happen, if it does it means the wallet has got into an inconsistent state.
  191.              throw new RuntimeException(e);
  192.          }
  193. -        log.info("  created {}", sendTx.getHashAsString());
  194. -        return sendTx;
  195. +        log.info("  completed {}", sendTx.getHashAsString());
  196. +        return true;
  197. +    }
  198. +
  199. +    /**
  200. +     * Takes a transaction with arbitrary outputs, gathers the necessary inputs for spending, and signs it.
  201. +     * Change goes to {@link Wallet#getChangeAddress()}
  202. +     * @param sendTx           The transaction to complete
  203. +     * @return False if we cannot afford this send, true otherwise
  204. +     */
  205. +    public synchronized boolean completeTx(Transaction sendTx) {
  206. +        return completeTx(sendTx, getChangeAddress());
  207. +    }
  208. +
  209. +    synchronized Address getChangeAddress() {
  210. +        // For now let's just pick the first key in our keychain. In future we might want to do something else to
  211. +        // give the user better privacy here, eg in incognito mode.
  212. +        assert keychain.size() > 0 : "Can't send value without an address to use for receiving change";
  213. +        ECKey first = keychain.get(0);
  214. +        return first.toAddress(params);
  215.      }
  216.  
  217.      /**
Advertisement
Add Comment
Please, Sign In to add comment