]>
Commit | Line | Data |
---|---|---|
cbe39a38 | 1 | // Copyright (c) 2009-2014 The Bitcoin developers |
78253fcb | 2 | // Distributed under the MIT software license, see the accompanying |
cbe39a38 JG |
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | ||
5 | #include "base58.h" | |
71697f97 | 6 | #include "clientversion.h" |
afd4b94b | 7 | #include "primitives/block.h" // for MAX_BLOCK_SIZE |
d2270111 | 8 | #include "primitives/transaction.h" |
611116d4 | 9 | #include "core_io.h" |
afd4b94b | 10 | #include "coins.h" |
cbe39a38 | 11 | #include "keystore.h" |
c4408a6c | 12 | #include "script/script.h" |
e088d65a | 13 | #include "script/sign.h" |
cbe39a38 JG |
14 | #include "ui_interface.h" // for _(...) |
15 | #include "univalue/univalue.h" | |
611116d4 | 16 | #include "util.h" |
afd4b94b | 17 | #include "utilstrencodings.h" |
611116d4 | 18 | #include "utilmoneystr.h" |
cbe39a38 JG |
19 | |
20 | #include <stdio.h> | |
611116d4 | 21 | |
fb14452c | 22 | #include <boost/algorithm/string.hpp> |
611116d4 | 23 | #include <boost/assign/list_of.hpp> |
cbe39a38 | 24 | |
cbe39a38 | 25 | using namespace boost::assign; |
611116d4 | 26 | using namespace std; |
cbe39a38 JG |
27 | |
28 | static bool fCreateBlank; | |
29 | static map<string,UniValue> registers; | |
30 | CClientUIInterface uiInterface; | |
31 | ||
32 | static bool AppInitRawTx(int argc, char* argv[]) | |
33 | { | |
34 | // | |
35 | // Parameters | |
36 | // | |
37 | ParseParameters(argc, argv); | |
38 | ||
39 | // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) | |
40 | if (!SelectParamsFromCommandLine()) { | |
41 | fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); | |
42 | return false; | |
43 | } | |
44 | ||
45 | fCreateBlank = GetBoolArg("-create", false); | |
46 | ||
47 | if (argc<2 || mapArgs.count("-?") || mapArgs.count("-help")) | |
48 | { | |
49 | // First part of help message is specific to this utility | |
50 | std::string strUsage = _("Bitcoin Core bitcoin-tx utility version") + " " + FormatFullVersion() + "\n\n" + | |
51 | _("Usage:") + "\n" + | |
52 | " bitcoin-tx [options] <hex-tx> [commands] " + _("Update hex-encoded bitcoin transaction") + "\n" + | |
53 | " bitcoin-tx [options] -create [commands] " + _("Create hex-encoded bitcoin transaction") + "\n" + | |
54 | "\n"; | |
55 | ||
56 | fprintf(stdout, "%s", strUsage.c_str()); | |
57 | ||
58 | strUsage = _("Options:") + "\n"; | |
59 | strUsage += " -? " + _("This help message") + "\n"; | |
60 | strUsage += " -create " + _("Create new, empty TX.") + "\n"; | |
61 | strUsage += " -json " + _("Select JSON output") + "\n"; | |
84877904 | 62 | strUsage += " -txid " + _("Output only the hex-encoded transaction id of the resultant transaction.") + "\n"; |
cbe39a38 JG |
63 | strUsage += " -regtest " + _("Enter regression test mode, which uses a special chain in which blocks can be solved instantly.") + "\n"; |
64 | strUsage += " -testnet " + _("Use the test network") + "\n"; | |
65 | strUsage += "\n"; | |
66 | ||
67 | fprintf(stdout, "%s", strUsage.c_str()); | |
68 | ||
69 | ||
70 | strUsage = _("Commands:") + "\n"; | |
71 | strUsage += " delin=N " + _("Delete input N from TX") + "\n"; | |
72 | strUsage += " delout=N " + _("Delete output N from TX") + "\n"; | |
73 | strUsage += " in=TXID:VOUT " + _("Add input to TX") + "\n"; | |
74 | strUsage += " locktime=N " + _("Set TX lock time to N") + "\n"; | |
75 | strUsage += " nversion=N " + _("Set TX version to N") + "\n"; | |
76 | strUsage += " outaddr=VALUE:ADDRESS " + _("Add address-based output to TX") + "\n"; | |
77 | strUsage += " outscript=VALUE:SCRIPT " + _("Add raw script output to TX") + "\n"; | |
78 | strUsage += " sign=SIGHASH-FLAGS " + _("Add zero or more signatures to transaction") + "\n"; | |
79 | strUsage += " This command requires JSON registers:\n"; | |
80 | strUsage += " prevtxs=JSON object\n"; | |
81 | strUsage += " privatekeys=JSON object\n"; | |
82 | strUsage += " See signrawtransaction docs for format of sighash flags, JSON objects.\n"; | |
83 | strUsage += "\n"; | |
84 | fprintf(stdout, "%s", strUsage.c_str()); | |
85 | ||
86 | strUsage = _("Register Commands:") + "\n"; | |
87 | strUsage += " load=NAME:FILENAME " + _("Load JSON file FILENAME into register NAME") + "\n"; | |
88 | strUsage += " set=NAME:JSON-STRING " + _("Set register NAME to given JSON-STRING") + "\n"; | |
89 | strUsage += "\n"; | |
90 | fprintf(stdout, "%s", strUsage.c_str()); | |
91 | ||
92 | return false; | |
93 | } | |
94 | return true; | |
95 | } | |
96 | ||
97 | static void RegisterSetJson(const string& key, const string& rawJson) | |
98 | { | |
99 | UniValue val; | |
100 | if (!val.read(rawJson)) { | |
101 | string strErr = "Cannot parse JSON for key " + key; | |
102 | throw runtime_error(strErr); | |
103 | } | |
104 | ||
105 | registers[key] = val; | |
106 | } | |
107 | ||
108 | static void RegisterSet(const string& strInput) | |
109 | { | |
110 | // separate NAME:VALUE in string | |
111 | size_t pos = strInput.find(':'); | |
112 | if ((pos == string::npos) || | |
113 | (pos == 0) || | |
114 | (pos == (strInput.size() - 1))) | |
115 | throw runtime_error("Register input requires NAME:VALUE"); | |
116 | ||
117 | string key = strInput.substr(0, pos); | |
118 | string valStr = strInput.substr(pos + 1, string::npos); | |
119 | ||
120 | RegisterSetJson(key, valStr); | |
121 | } | |
122 | ||
123 | static void RegisterLoad(const string& strInput) | |
124 | { | |
125 | // separate NAME:FILENAME in string | |
126 | size_t pos = strInput.find(':'); | |
127 | if ((pos == string::npos) || | |
128 | (pos == 0) || | |
129 | (pos == (strInput.size() - 1))) | |
130 | throw runtime_error("Register load requires NAME:FILENAME"); | |
131 | ||
132 | string key = strInput.substr(0, pos); | |
133 | string filename = strInput.substr(pos + 1, string::npos); | |
134 | ||
135 | FILE *f = fopen(filename.c_str(), "r"); | |
136 | if (!f) { | |
137 | string strErr = "Cannot open file " + filename; | |
138 | throw runtime_error(strErr); | |
139 | } | |
140 | ||
141 | // load file chunks into one big buffer | |
142 | string valStr; | |
143 | while ((!feof(f)) && (!ferror(f))) { | |
144 | char buf[4096]; | |
145 | int bread = fread(buf, 1, sizeof(buf), f); | |
146 | if (bread <= 0) | |
147 | break; | |
148 | ||
149 | valStr.insert(valStr.size(), buf, bread); | |
150 | } | |
151 | ||
152 | if (ferror(f)) { | |
153 | string strErr = "Error reading file " + filename; | |
154 | throw runtime_error(strErr); | |
155 | } | |
156 | ||
157 | fclose(f); | |
158 | ||
159 | // evaluate as JSON buffer register | |
160 | RegisterSetJson(key, valStr); | |
161 | } | |
162 | ||
163 | static void MutateTxVersion(CMutableTransaction& tx, const string& cmdVal) | |
164 | { | |
165 | int64_t newVersion = atoi64(cmdVal); | |
166 | if (newVersion < 1 || newVersion > CTransaction::CURRENT_VERSION) | |
167 | throw runtime_error("Invalid TX version requested"); | |
168 | ||
169 | tx.nVersion = (int) newVersion; | |
170 | } | |
171 | ||
172 | static void MutateTxLocktime(CMutableTransaction& tx, const string& cmdVal) | |
173 | { | |
174 | int64_t newLocktime = atoi64(cmdVal); | |
175 | if (newLocktime < 0LL || newLocktime > 0xffffffffLL) | |
176 | throw runtime_error("Invalid TX locktime requested"); | |
177 | ||
178 | tx.nLockTime = (unsigned int) newLocktime; | |
179 | } | |
180 | ||
181 | static void MutateTxAddInput(CMutableTransaction& tx, const string& strInput) | |
182 | { | |
183 | // separate TXID:VOUT in string | |
184 | size_t pos = strInput.find(':'); | |
185 | if ((pos == string::npos) || | |
186 | (pos == 0) || | |
187 | (pos == (strInput.size() - 1))) | |
188 | throw runtime_error("TX input missing separator"); | |
189 | ||
190 | // extract and validate TXID | |
191 | string strTxid = strInput.substr(0, pos); | |
192 | if ((strTxid.size() != 64) || !IsHex(strTxid)) | |
193 | throw runtime_error("invalid TX input txid"); | |
194 | uint256 txid(strTxid); | |
195 | ||
196 | static const unsigned int minTxOutSz = 9; | |
197 | static const unsigned int maxVout = MAX_BLOCK_SIZE / minTxOutSz; | |
198 | ||
199 | // extract and validate vout | |
200 | string strVout = strInput.substr(pos + 1, string::npos); | |
201 | int vout = atoi(strVout); | |
202 | if ((vout < 0) || (vout > (int)maxVout)) | |
203 | throw runtime_error("invalid TX input vout"); | |
204 | ||
205 | // append to transaction input list | |
206 | CTxIn txin(txid, vout); | |
207 | tx.vin.push_back(txin); | |
208 | } | |
209 | ||
210 | static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput) | |
211 | { | |
212 | // separate VALUE:ADDRESS in string | |
213 | size_t pos = strInput.find(':'); | |
214 | if ((pos == string::npos) || | |
215 | (pos == 0) || | |
216 | (pos == (strInput.size() - 1))) | |
217 | throw runtime_error("TX output missing separator"); | |
218 | ||
219 | // extract and validate VALUE | |
220 | string strValue = strInput.substr(0, pos); | |
a372168e | 221 | CAmount value; |
cbe39a38 JG |
222 | if (!ParseMoney(strValue, value)) |
223 | throw runtime_error("invalid TX output value"); | |
224 | ||
225 | // extract and validate ADDRESS | |
226 | string strAddr = strInput.substr(pos + 1, string::npos); | |
227 | CBitcoinAddress addr(strAddr); | |
228 | if (!addr.IsValid()) | |
229 | throw runtime_error("invalid TX output address"); | |
230 | ||
0be990ba PW |
231 | // build standard output script via GetScriptForDestination() |
232 | CScript scriptPubKey = GetScriptForDestination(addr.Get()); | |
cbe39a38 JG |
233 | |
234 | // construct TxOut, append to transaction output list | |
235 | CTxOut txout(value, scriptPubKey); | |
236 | tx.vout.push_back(txout); | |
237 | } | |
238 | ||
239 | static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput) | |
240 | { | |
241 | // separate VALUE:SCRIPT in string | |
242 | size_t pos = strInput.find(':'); | |
243 | if ((pos == string::npos) || | |
15ef1b90 | 244 | (pos == 0)) |
cbe39a38 JG |
245 | throw runtime_error("TX output missing separator"); |
246 | ||
247 | // extract and validate VALUE | |
248 | string strValue = strInput.substr(0, pos); | |
a372168e | 249 | CAmount value; |
cbe39a38 JG |
250 | if (!ParseMoney(strValue, value)) |
251 | throw runtime_error("invalid TX output value"); | |
252 | ||
253 | // extract and validate script | |
254 | string strScript = strInput.substr(pos + 1, string::npos); | |
255 | CScript scriptPubKey = ParseScript(strScript); // throws on err | |
256 | ||
257 | // construct TxOut, append to transaction output list | |
258 | CTxOut txout(value, scriptPubKey); | |
259 | tx.vout.push_back(txout); | |
260 | } | |
261 | ||
262 | static void MutateTxDelInput(CMutableTransaction& tx, const string& strInIdx) | |
263 | { | |
264 | // parse requested deletion index | |
265 | int inIdx = atoi(strInIdx); | |
266 | if (inIdx < 0 || inIdx >= (int)tx.vin.size()) { | |
267 | string strErr = "Invalid TX input index '" + strInIdx + "'"; | |
268 | throw runtime_error(strErr.c_str()); | |
269 | } | |
270 | ||
271 | // delete input from transaction | |
272 | tx.vin.erase(tx.vin.begin() + inIdx); | |
273 | } | |
274 | ||
275 | static void MutateTxDelOutput(CMutableTransaction& tx, const string& strOutIdx) | |
276 | { | |
277 | // parse requested deletion index | |
278 | int outIdx = atoi(strOutIdx); | |
279 | if (outIdx < 0 || outIdx >= (int)tx.vout.size()) { | |
280 | string strErr = "Invalid TX output index '" + strOutIdx + "'"; | |
281 | throw runtime_error(strErr.c_str()); | |
282 | } | |
283 | ||
284 | // delete output from transaction | |
285 | tx.vout.erase(tx.vout.begin() + outIdx); | |
286 | } | |
287 | ||
288 | static const unsigned int N_SIGHASH_OPTS = 6; | |
289 | static const struct { | |
290 | const char *flagStr; | |
291 | int flags; | |
292 | } sighashOptions[N_SIGHASH_OPTS] = { | |
616c2430 CF |
293 | {"ALL", SIGHASH_ALL}, |
294 | {"NONE", SIGHASH_NONE}, | |
295 | {"SINGLE", SIGHASH_SINGLE}, | |
296 | {"ALL|ANYONECANPAY", SIGHASH_ALL|SIGHASH_ANYONECANPAY}, | |
297 | {"NONE|ANYONECANPAY", SIGHASH_NONE|SIGHASH_ANYONECANPAY}, | |
298 | {"SINGLE|ANYONECANPAY", SIGHASH_SINGLE|SIGHASH_ANYONECANPAY}, | |
cbe39a38 JG |
299 | }; |
300 | ||
301 | static bool findSighashFlags(int& flags, const string& flagStr) | |
302 | { | |
303 | flags = 0; | |
304 | ||
305 | for (unsigned int i = 0; i < N_SIGHASH_OPTS; i++) { | |
306 | if (flagStr == sighashOptions[i].flagStr) { | |
307 | flags = sighashOptions[i].flags; | |
308 | return true; | |
309 | } | |
310 | } | |
311 | ||
312 | return false; | |
313 | } | |
314 | ||
315 | uint256 ParseHashUO(map<string,UniValue>& o, string strKey) | |
316 | { | |
317 | if (!o.count(strKey)) | |
318 | return 0; | |
319 | return ParseHashUV(o[strKey], strKey); | |
320 | } | |
321 | ||
322 | vector<unsigned char> ParseHexUO(map<string,UniValue>& o, string strKey) | |
323 | { | |
324 | if (!o.count(strKey)) { | |
325 | vector<unsigned char> emptyVec; | |
326 | return emptyVec; | |
327 | } | |
328 | return ParseHexUV(o[strKey], strKey); | |
329 | } | |
330 | ||
331 | static void MutateTxSign(CMutableTransaction& tx, const string& flagStr) | |
332 | { | |
333 | int nHashType = SIGHASH_ALL; | |
334 | ||
335 | if (flagStr.size() > 0) | |
336 | if (!findSighashFlags(nHashType, flagStr)) | |
337 | throw runtime_error("unknown sighash flag/sign option"); | |
338 | ||
339 | vector<CTransaction> txVariants; | |
340 | txVariants.push_back(tx); | |
341 | ||
342 | // mergedTx will end up with all the signatures; it | |
343 | // starts as a clone of the raw tx: | |
344 | CMutableTransaction mergedTx(txVariants[0]); | |
345 | bool fComplete = true; | |
346 | CCoinsView viewDummy; | |
7c70438d | 347 | CCoinsViewCache view(&viewDummy); |
cbe39a38 JG |
348 | |
349 | if (!registers.count("privatekeys")) | |
350 | throw runtime_error("privatekeys register variable must be set."); | |
351 | bool fGivenKeys = false; | |
352 | CBasicKeyStore tempKeystore; | |
353 | UniValue keysObj = registers["privatekeys"]; | |
354 | fGivenKeys = true; | |
355 | ||
356 | for (unsigned int kidx = 0; kidx < keysObj.count(); kidx++) { | |
357 | if (!keysObj[kidx].isStr()) | |
358 | throw runtime_error("privatekey not a string"); | |
359 | CBitcoinSecret vchSecret; | |
360 | bool fGood = vchSecret.SetString(keysObj[kidx].getValStr()); | |
361 | if (!fGood) | |
362 | throw runtime_error("privatekey not valid"); | |
363 | ||
364 | CKey key = vchSecret.GetKey(); | |
365 | tempKeystore.AddKey(key); | |
366 | } | |
367 | ||
368 | // Add previous txouts given in the RPC call: | |
369 | if (!registers.count("prevtxs")) | |
370 | throw runtime_error("prevtxs register variable must be set."); | |
371 | UniValue prevtxsObj = registers["privatekeys"]; | |
372 | { | |
373 | for (unsigned int previdx = 0; previdx < prevtxsObj.count(); previdx++) { | |
374 | UniValue prevOut = prevtxsObj[previdx]; | |
375 | if (!prevOut.isObject()) | |
376 | throw runtime_error("expected prevtxs internal object"); | |
377 | ||
378 | map<string,UniValue::VType> types = map_list_of("txid", UniValue::VSTR)("vout",UniValue::VNUM)("scriptPubKey",UniValue::VSTR); | |
379 | if (!prevOut.checkObject(types)) | |
380 | throw runtime_error("prevtxs internal object typecheck fail"); | |
381 | ||
382 | uint256 txid = ParseHashUV(prevOut, "txid"); | |
383 | ||
384 | int nOut = atoi(prevOut["vout"].getValStr()); | |
385 | if (nOut < 0) | |
386 | throw runtime_error("vout must be positive"); | |
387 | ||
388 | vector<unsigned char> pkData(ParseHexUV(prevOut, "scriptPubKey")); | |
389 | CScript scriptPubKey(pkData.begin(), pkData.end()); | |
390 | ||
f28aec01 PW |
391 | { |
392 | CCoinsModifier coins = view.ModifyCoins(txid); | |
393 | if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) { | |
cbe39a38 | 394 | string err("Previous output scriptPubKey mismatch:\n"); |
f28aec01 | 395 | err = err + coins->vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+ |
cbe39a38 JG |
396 | scriptPubKey.ToString(); |
397 | throw runtime_error(err); | |
398 | } | |
f28aec01 PW |
399 | if ((unsigned int)nOut >= coins->vout.size()) |
400 | coins->vout.resize(nOut+1); | |
401 | coins->vout[nOut].scriptPubKey = scriptPubKey; | |
402 | coins->vout[nOut].nValue = 0; // we don't know the actual output value | |
cbe39a38 | 403 | } |
cbe39a38 JG |
404 | |
405 | // if redeemScript given and private keys given, | |
406 | // add redeemScript to the tempKeystore so it can be signed: | |
407 | if (fGivenKeys && scriptPubKey.IsPayToScriptHash() && | |
408 | prevOut.exists("redeemScript")) { | |
409 | UniValue v = prevOut["redeemScript"]; | |
410 | vector<unsigned char> rsData(ParseHexUV(v, "redeemScript")); | |
411 | CScript redeemScript(rsData.begin(), rsData.end()); | |
412 | tempKeystore.AddCScript(redeemScript); | |
413 | } | |
414 | } | |
415 | } | |
416 | ||
417 | const CKeyStore& keystore = tempKeystore; | |
418 | ||
419 | bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); | |
420 | ||
421 | // Sign what we can: | |
422 | for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { | |
423 | CTxIn& txin = mergedTx.vin[i]; | |
629d75fa PW |
424 | const CCoins* coins = view.AccessCoins(txin.prevout.hash); |
425 | if (!coins || !coins->IsAvailable(txin.prevout.n)) { | |
cbe39a38 JG |
426 | fComplete = false; |
427 | continue; | |
428 | } | |
629d75fa | 429 | const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey; |
cbe39a38 JG |
430 | |
431 | txin.scriptSig.clear(); | |
432 | // Only sign SIGHASH_SINGLE if there's a corresponding output: | |
433 | if (!fHashSingle || (i < mergedTx.vout.size())) | |
434 | SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); | |
435 | ||
436 | // ... and merge in other signatures: | |
437 | BOOST_FOREACH(const CTransaction& txv, txVariants) { | |
438 | txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); | |
439 | } | |
5c1e798a | 440 | if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, SignatureChecker(mergedTx, i))) |
cbe39a38 JG |
441 | fComplete = false; |
442 | } | |
443 | ||
444 | if (fComplete) { | |
445 | // do nothing... for now | |
446 | // perhaps store this for later optional JSON output | |
447 | } | |
448 | ||
449 | tx = mergedTx; | |
450 | } | |
451 | ||
452 | static void MutateTx(CMutableTransaction& tx, const string& command, | |
453 | const string& commandVal) | |
454 | { | |
455 | if (command == "nversion") | |
456 | MutateTxVersion(tx, commandVal); | |
457 | else if (command == "locktime") | |
458 | MutateTxLocktime(tx, commandVal); | |
459 | ||
460 | else if (command == "delin") | |
461 | MutateTxDelInput(tx, commandVal); | |
462 | else if (command == "in") | |
463 | MutateTxAddInput(tx, commandVal); | |
464 | ||
465 | else if (command == "delout") | |
466 | MutateTxDelOutput(tx, commandVal); | |
467 | else if (command == "outaddr") | |
468 | MutateTxAddOutAddr(tx, commandVal); | |
469 | else if (command == "outscript") | |
470 | MutateTxAddOutScript(tx, commandVal); | |
471 | ||
472 | else if (command == "sign") | |
473 | MutateTxSign(tx, commandVal); | |
474 | ||
475 | else if (command == "load") | |
476 | RegisterLoad(commandVal); | |
477 | ||
478 | else if (command == "set") | |
479 | RegisterSet(commandVal); | |
480 | ||
481 | else | |
482 | throw runtime_error("unknown command"); | |
483 | } | |
484 | ||
485 | static void OutputTxJSON(const CTransaction& tx) | |
486 | { | |
487 | UniValue entry(UniValue::VOBJ); | |
488 | TxToUniv(tx, 0, entry); | |
489 | ||
490 | string jsonOutput = entry.write(4); | |
491 | fprintf(stdout, "%s\n", jsonOutput.c_str()); | |
492 | } | |
493 | ||
84877904 | 494 | static void OutputTxHash(const CTransaction& tx) |
495 | { | |
496 | string strHexHash = tx.GetHash().GetHex(); // the hex-encoded transaction hash (aka the transaction id) | |
497 | ||
498 | fprintf(stdout, "%s\n", strHexHash.c_str()); | |
499 | } | |
500 | ||
cbe39a38 JG |
501 | static void OutputTxHex(const CTransaction& tx) |
502 | { | |
503 | string strHex = EncodeHexTx(tx); | |
504 | ||
505 | fprintf(stdout, "%s\n", strHex.c_str()); | |
506 | } | |
507 | ||
508 | static void OutputTx(const CTransaction& tx) | |
509 | { | |
510 | if (GetBoolArg("-json", false)) | |
511 | OutputTxJSON(tx); | |
84877904 | 512 | else if (GetBoolArg("-txid", false)) |
513 | OutputTxHash(tx); | |
cbe39a38 JG |
514 | else |
515 | OutputTxHex(tx); | |
516 | } | |
517 | ||
fb14452c JG |
518 | static string readStdin() |
519 | { | |
520 | char buf[4096]; | |
521 | string ret; | |
522 | ||
523 | while (!feof(stdin)) { | |
524 | size_t bread = fread(buf, 1, sizeof(buf), stdin); | |
525 | ret.append(buf, bread); | |
526 | if (bread < sizeof(buf)) | |
527 | break; | |
528 | } | |
529 | ||
530 | if (ferror(stdin)) | |
531 | throw runtime_error("error reading stdin"); | |
532 | ||
533 | boost::algorithm::trim_right(ret); | |
534 | ||
535 | return ret; | |
536 | } | |
537 | ||
cbe39a38 JG |
538 | static int CommandLineRawTx(int argc, char* argv[]) |
539 | { | |
540 | string strPrint; | |
541 | int nRet = 0; | |
542 | try { | |
fb14452c JG |
543 | // Skip switches; Permit common stdin convention "-" |
544 | while (argc > 1 && IsSwitchChar(argv[1][0]) && | |
545 | (argv[1][1] != 0)) { | |
cbe39a38 JG |
546 | argc--; |
547 | argv++; | |
548 | } | |
549 | ||
550 | CTransaction txDecodeTmp; | |
551 | int startArg; | |
552 | ||
553 | if (!fCreateBlank) { | |
554 | // require at least one param | |
555 | if (argc < 2) | |
556 | throw runtime_error("too few parameters"); | |
557 | ||
558 | // param: hex-encoded bitcoin transaction | |
559 | string strHexTx(argv[1]); | |
fb14452c JG |
560 | if (strHexTx == "-") // "-" implies standard input |
561 | strHexTx = readStdin(); | |
cbe39a38 JG |
562 | |
563 | if (!DecodeHexTx(txDecodeTmp, strHexTx)) | |
564 | throw runtime_error("invalid transaction encoding"); | |
565 | ||
566 | startArg = 2; | |
567 | } else | |
568 | startArg = 1; | |
569 | ||
570 | CMutableTransaction tx(txDecodeTmp); | |
571 | ||
572 | for (int i = startArg; i < argc; i++) { | |
573 | string arg = argv[i]; | |
574 | string key, value; | |
575 | size_t eqpos = arg.find('='); | |
576 | if (eqpos == string::npos) | |
577 | key = arg; | |
578 | else { | |
579 | key = arg.substr(0, eqpos); | |
580 | value = arg.substr(eqpos + 1); | |
581 | } | |
582 | ||
583 | MutateTx(tx, key, value); | |
584 | } | |
585 | ||
586 | OutputTx(tx); | |
587 | } | |
588 | ||
589 | catch (boost::thread_interrupted) { | |
590 | throw; | |
591 | } | |
592 | catch (std::exception& e) { | |
593 | strPrint = string("error: ") + e.what(); | |
594 | nRet = EXIT_FAILURE; | |
595 | } | |
596 | catch (...) { | |
597 | PrintExceptionContinue(NULL, "CommandLineRawTx()"); | |
598 | throw; | |
599 | } | |
600 | ||
601 | if (strPrint != "") { | |
602 | fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); | |
603 | } | |
604 | return nRet; | |
605 | } | |
606 | ||
607 | int main(int argc, char* argv[]) | |
608 | { | |
609 | SetupEnvironment(); | |
610 | ||
611 | try { | |
612 | if(!AppInitRawTx(argc, argv)) | |
613 | return EXIT_FAILURE; | |
614 | } | |
615 | catch (std::exception& e) { | |
616 | PrintExceptionContinue(&e, "AppInitRawTx()"); | |
617 | return EXIT_FAILURE; | |
618 | } catch (...) { | |
619 | PrintExceptionContinue(NULL, "AppInitRawTx()"); | |
620 | return EXIT_FAILURE; | |
621 | } | |
622 | ||
623 | int ret = EXIT_FAILURE; | |
624 | try { | |
625 | ret = CommandLineRawTx(argc, argv); | |
626 | } | |
627 | catch (std::exception& e) { | |
628 | PrintExceptionContinue(&e, "CommandLineRawTx()"); | |
629 | } catch (...) { | |
630 | PrintExceptionContinue(NULL, "CommandLineRawTx()"); | |
631 | } | |
632 | return ret; | |
633 | } |