|
12 | 12 | import org.twostack.bitcoin4j.exception.SignatureDecodeException;
|
13 | 13 | import org.twostack.bitcoin4j.exception.TransactionException;
|
14 | 14 | import org.twostack.bitcoin4j.params.NetworkAddressType;
|
15 |
| -import org.twostack.bitcoin4j.script.Script; |
16 |
| -import org.twostack.bitcoin4j.script.ScriptError; |
17 | 15 |
|
18 | 16 | import java.io.IOException;
|
19 | 17 | import java.io.InputStreamReader;
|
20 | 18 | import java.math.BigInteger;
|
21 | 19 | import java.nio.charset.StandardCharsets;
|
22 |
| -import java.util.Set; |
23 |
| - |
24 |
| -import static org.twostack.bitcoin4j.utils.TestUtil.parseVerifyFlags; |
| 20 | +import java.util.HashMap; |
25 | 21 |
|
26 | 22 | public class TransactionBuilderTest {
|
27 | 23 |
|
@@ -67,4 +63,145 @@ public void processAndSignMultiInput() throws IOException, InvalidKeyException,
|
67 | 63 |
|
68 | 64 | }
|
69 | 65 |
|
| 66 | + @Test |
| 67 | + public void builderCanSpendFromOutput() throws InvalidKeyException, IOException { |
| 68 | + |
| 69 | + //This WIF is for a private key that actually has testnet coins on TESTNET |
| 70 | + //The transactions in multi_input.json are UTXOs that exist(ed) on TESTNET |
| 71 | + // at time of writing this test, and can be viewed on TESTNET using a block explorer |
| 72 | + String wif = "cRTUuWgPdp7tJPrn1Xeq196eZa4ZCpg8n3cgDJsJmgDHBZ8x9fpv"; |
| 73 | + PrivateKey privateKey = PrivateKey.fromWIF(wif); |
| 74 | + |
| 75 | + JsonNode json = new ObjectMapper().readTree( |
| 76 | + new InputStreamReader(getClass().getResourceAsStream("multi_input.json"), |
| 77 | + StandardCharsets.UTF_8) |
| 78 | + ); |
| 79 | + |
| 80 | + //build one large transaction that spends all the inputs |
| 81 | + TransactionBuilder builder = new TransactionBuilder(); |
| 82 | + for (JsonNode utxoInfo : json) { |
| 83 | + |
| 84 | + Integer fundingOutputIndex = utxoInfo.get("tx_pos").asInt(); |
| 85 | + String rawTxHex = utxoInfo.get("raw_tx").asText(); |
| 86 | + BigInteger amount = BigInteger.valueOf(utxoInfo.get("value").asInt()); |
| 87 | + |
| 88 | + Transaction fundingTx = Transaction.fromHex(rawTxHex); |
| 89 | + UnlockingScriptBuilder unlocker = new P2PKHUnlockBuilder(privateKey.getPublicKey()); |
| 90 | + TransactionSigner signer = new TransactionSigner(SigHashType.ALL.value | SigHashType.FORKID.value, privateKey); |
| 91 | + |
| 92 | + builder.spendFromOutput(signer, fundingTx.getTransactionId(), fundingOutputIndex, amount, TransactionInput.MAX_SEQ_NUMBER, unlocker); |
| 93 | + |
| 94 | + } |
| 95 | + |
| 96 | + Address recipientAddress = Address.fromKey(NetworkAddressType.TEST_PKH, privateKey.getPublicKey()); |
| 97 | + |
| 98 | + |
| 99 | + Assertions.assertThatCode(() -> { |
| 100 | + Transaction broadcastTx = builder.withFeePerKb(512) |
| 101 | + .spendTo(new P2PKHLockBuilder(recipientAddress), BigInteger.valueOf(100000)) |
| 102 | + .sendChangeTo(recipientAddress) |
| 103 | + .build(true); |
| 104 | + }).doesNotThrowAnyException(); |
| 105 | + |
| 106 | + } |
| 107 | + |
| 108 | + |
| 109 | + @Test |
| 110 | + public void builderCanSpendFromOutpoint() throws InvalidKeyException, IOException { |
| 111 | + |
| 112 | + //This WIF is for a private key that actually has testnet coins on TESTNET |
| 113 | + //The transactions in multi_input.json are UTXOs that exist(ed) on TESTNET |
| 114 | + // at time of writing this test, and can be viewed on TESTNET using a block explorer |
| 115 | + String wif = "cRTUuWgPdp7tJPrn1Xeq196eZa4ZCpg8n3cgDJsJmgDHBZ8x9fpv"; |
| 116 | + PrivateKey privateKey = PrivateKey.fromWIF(wif); |
| 117 | + |
| 118 | + JsonNode json = new ObjectMapper().readTree( |
| 119 | + new InputStreamReader(getClass().getResourceAsStream("multi_input.json"), |
| 120 | + StandardCharsets.UTF_8) |
| 121 | + ); |
| 122 | + |
| 123 | + //build one large transaction that spends all the inputs |
| 124 | + TransactionBuilder builder = new TransactionBuilder(); |
| 125 | + for (JsonNode utxoInfo : json) { |
| 126 | + |
| 127 | + Integer fundingOutputIndex = utxoInfo.get("tx_pos").asInt(); |
| 128 | + String rawTxHex = utxoInfo.get("raw_tx").asText(); |
| 129 | + BigInteger amount = BigInteger.valueOf(utxoInfo.get("value").asInt()); |
| 130 | + |
| 131 | + Transaction fundingTx = Transaction.fromHex(rawTxHex); |
| 132 | + |
| 133 | + TransactionOutpoint outpoint = new TransactionOutpoint(); |
| 134 | + outpoint.setTransactionId(fundingTx.getTransactionId()); |
| 135 | + outpoint.setSatoshis(amount); |
| 136 | + outpoint.setOutputIndex(fundingOutputIndex); |
| 137 | + outpoint.setLockingScript(fundingTx.getOutputs().get(fundingOutputIndex).getScript()); |
| 138 | + |
| 139 | + UnlockingScriptBuilder unlocker = new P2PKHUnlockBuilder(privateKey.getPublicKey()); |
| 140 | + |
| 141 | + TransactionSigner signer = new TransactionSigner(SigHashType.ALL.value | SigHashType.FORKID.value, privateKey); |
| 142 | + |
| 143 | + builder.spendFromOutpoint(signer, outpoint, TransactionInput.MAX_SEQ_NUMBER, unlocker); |
| 144 | + |
| 145 | + } |
| 146 | + |
| 147 | + Address recipientAddress = Address.fromKey(NetworkAddressType.TEST_PKH, privateKey.getPublicKey()); |
| 148 | + |
| 149 | + |
| 150 | + Assertions.assertThatCode(() -> { |
| 151 | + Transaction broadcastTx = builder.withFeePerKb(512) |
| 152 | + .spendTo(new P2PKHLockBuilder(recipientAddress), BigInteger.valueOf(100000)) |
| 153 | + .sendChangeTo(recipientAddress) |
| 154 | + .build(true); |
| 155 | + }).doesNotThrowAnyException(); |
| 156 | + } |
| 157 | + |
| 158 | + @Test |
| 159 | + public void builderCanSpendFromUtxoMap() throws InvalidKeyException, IOException { |
| 160 | + |
| 161 | + |
| 162 | + //This WIF is for a private key that actually has testnet coins on TESTNET |
| 163 | + //The transactions in multi_input.json are UTXOs that exist(ed) on TESTNET |
| 164 | + // at time of writing this test, and can be viewed on TESTNET using a block explorer |
| 165 | + String wif = "cRTUuWgPdp7tJPrn1Xeq196eZa4ZCpg8n3cgDJsJmgDHBZ8x9fpv"; |
| 166 | + PrivateKey privateKey = PrivateKey.fromWIF(wif); |
| 167 | + |
| 168 | + JsonNode json = new ObjectMapper().readTree( |
| 169 | + new InputStreamReader(getClass().getResourceAsStream("multi_input.json"), |
| 170 | + StandardCharsets.UTF_8) |
| 171 | + ); |
| 172 | + |
| 173 | + //build one large transaction that spends all the inputs |
| 174 | + TransactionBuilder builder = new TransactionBuilder(); |
| 175 | + for (JsonNode utxoInfo : json) { |
| 176 | + |
| 177 | + Integer fundingOutputIndex = utxoInfo.get("tx_pos").asInt(); |
| 178 | + String rawTxHex = utxoInfo.get("raw_tx").asText(); |
| 179 | + BigInteger amount = BigInteger.valueOf(utxoInfo.get("value").asInt()); |
| 180 | + |
| 181 | + Transaction fundingTx = Transaction.fromHex(rawTxHex); |
| 182 | + |
| 183 | + HashMap utxoMap = new HashMap(); |
| 184 | + utxoMap.put("transactionId", fundingTx.getTransactionId()); |
| 185 | + utxoMap.put("satoshis", amount); |
| 186 | + utxoMap.put("sequenceNumber", TransactionInput.MAX_SEQ_NUMBER); |
| 187 | + utxoMap.put("outputIndex", fundingOutputIndex); |
| 188 | + utxoMap.put("scriptPubKey", Utils.HEX.encode(fundingTx.getOutputs().get(fundingOutputIndex).serialize())); |
| 189 | + |
| 190 | + UnlockingScriptBuilder unlocker = new P2PKHUnlockBuilder(privateKey.getPublicKey()); |
| 191 | + |
| 192 | + TransactionSigner signer = new TransactionSigner(SigHashType.ALL.value | SigHashType.FORKID.value, privateKey); |
| 193 | + builder.spendFromUtxoMap(signer, utxoMap, unlocker); |
| 194 | + |
| 195 | + } |
| 196 | + |
| 197 | + Address recipientAddress = Address.fromKey(NetworkAddressType.TEST_PKH, privateKey.getPublicKey()); |
| 198 | + |
| 199 | + |
| 200 | + Assertions.assertThatCode(() -> { |
| 201 | + Transaction broadcastTx = builder.withFeePerKb(512) |
| 202 | + .spendTo(new P2PKHLockBuilder(recipientAddress), BigInteger.valueOf(100000)) |
| 203 | + .sendChangeTo(recipientAddress) |
| 204 | + .build(true); |
| 205 | + }).doesNotThrowAnyException(); |
| 206 | + } |
70 | 207 | }
|
0 commit comments