Move updateAmounts to new showEvent implementation.
[novacoin.git] / src / qt / multisigdialog.cpp
1 #include <QClipboard>
2 #include <QDialog>
3 #include <QMessageBox>
4 #include <QScrollBar>
5 #include <vector>
6
7 #include "addresstablemodel.h"
8 #include "base58.h"
9 #include "key.h"
10 #include "main.h"
11 #include "multisigaddressentry.h"
12 #include "multisiginputentry.h"
13 #include "multisigdialog.h"
14 #include "ui_multisigdialog.h"
15 #include "script.h"
16 #include "sendcoinsentry.h"
17 #include "util.h"
18 #include "wallet.h"
19 #include "walletmodel.h"
20
21 #ifdef USE_LEVELDB
22 #include "txdb-leveldb.h"
23 #else
24 #include "txdb-bdb.h"
25 #endif
26
27 MultisigDialog::MultisigDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MultisigDialog), model(0)
28 {
29     ui->setupUi(this);
30
31 #ifdef Q_WS_MAC // Icons on push buttons are very uncommon on Mac
32     ui->addPubKeyButton->setIcon(QIcon());
33     ui->clearButton->setIcon(QIcon());
34     ui->addInputButton->setIcon(QIcon());
35     ui->addOutputButton->setIcon(QIcon());
36     ui->signTransactionButton->setIcon(QIcon());
37     ui->sendTransactionButton->setIcon(QIcon());
38 #endif
39
40     addPubKey();
41     addPubKey();
42
43     connect(ui->addPubKeyButton, SIGNAL(clicked()), this, SLOT(addPubKey()));
44     connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
45
46     addInput();
47     addOutput();
48
49     connect(ui->addInputButton, SIGNAL(clicked()), this, SLOT(addInput()));
50     connect(ui->addOutputButton, SIGNAL(clicked()), this, SLOT(addOutput()));
51
52     ui->signTransactionButton->setEnabled(false);
53     ui->sendTransactionButton->setEnabled(false);
54 }
55
56 void MultisigDialog::showEvent(QShowEvent *event)
57 {
58     QWidget::showEvent(event);
59
60     if (!model)
61         return;
62
63     updateAmounts();
64 }
65
66 void MultisigDialog::hideEvent(QHideEvent *event)
67 {
68     QWidget::hideEvent(event);
69
70     if (!model)
71         return;
72
73     clear();
74 }
75
76 MultisigDialog::~MultisigDialog()
77 {
78     delete ui;
79 }
80
81 void MultisigDialog::setModel(WalletModel *model)
82 {
83     this->model = model;
84
85     for(int i = 0; i < ui->pubkeyEntries->count(); i++)
86     {
87         MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
88         if(entry)
89             entry->setModel(model);
90     }
91
92
93     for(int i = 0; i < ui->inputs->count(); i++)
94     {
95         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
96         if(entry)
97             entry->setModel(model);
98     }
99
100
101     for(int i = 0; i < ui->outputs->count(); i++)
102     {
103         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
104         if(entry)
105             entry->setModel(model);
106     }
107 }
108
109 void MultisigDialog::updateRemoveEnabled()
110 {
111     bool enabled = (ui->pubkeyEntries->count() > 2);
112
113     for(int i = 0; i < ui->pubkeyEntries->count(); i++)
114     {
115         MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
116         if(entry)
117             entry->setRemoveEnabled(enabled);
118     }
119
120     QString maxSigsStr;
121     maxSigsStr.setNum(ui->pubkeyEntries->count());
122     ui->maxSignaturesLabel->setText(QString("/ ") + maxSigsStr);
123
124
125     enabled = (ui->inputs->count() > 1);
126     for(int i = 0; i < ui->inputs->count(); i++)
127     {
128         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
129         if(entry)
130             entry->setRemoveEnabled(enabled);
131     }
132
133
134     enabled = (ui->outputs->count() > 1);
135     for(int i = 0; i < ui->outputs->count(); i++)
136     {
137         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
138         if(entry)
139             entry->setRemoveEnabled(enabled);
140     }
141 }
142
143 void MultisigDialog::on_createAddressButton_clicked()
144 {
145     ui->multisigAddress->clear();
146     ui->redeemScript->clear();
147
148     if(!model)
149         return;
150
151     std::vector<CKey> pubkeys;
152     pubkeys.resize(ui->pubkeyEntries->count());
153     unsigned int required = ui->requiredSignatures->text().toUInt();
154
155     for(int i = 0; i < ui->pubkeyEntries->count(); i++)
156     {
157         MultisigAddressEntry *entry = qobject_cast<MultisigAddressEntry *>(ui->pubkeyEntries->itemAt(i)->widget());
158         if(!entry->validate())
159             return;
160         QString str = entry->getPubkey();
161         CPubKey vchPubKey(ParseHex(str.toStdString().c_str()));
162         if(!vchPubKey.IsValid())
163             return;
164         pubkeys[i].SetPubKey(vchPubKey);
165     }
166
167     if((required == 0) || (required > pubkeys.size()))
168         return;
169
170     CScript script;
171     script.SetMultisig(required, pubkeys);
172     CScriptID scriptID = script.GetID();
173     CBitcoinAddress address(scriptID);
174
175     ui->multisigAddress->setText(address.ToString().c_str());
176     ui->redeemScript->setText(HexStr(script.begin(), script.end()).c_str());
177 }
178
179 void MultisigDialog::on_copyMultisigAddressButton_clicked()
180 {
181     QApplication::clipboard()->setText(ui->multisigAddress->text());
182 }
183
184 void MultisigDialog::on_copyRedeemScriptButton_clicked()
185 {
186     QApplication::clipboard()->setText(ui->redeemScript->text());
187 }
188
189 void MultisigDialog::on_saveRedeemScriptButton_clicked()
190 {
191     if(!model)
192         return;
193
194     CWallet *wallet = model->getWallet();
195     std::string redeemScript = ui->redeemScript->text().toStdString();
196     std::vector<unsigned char> scriptData(ParseHex(redeemScript));
197     CScript script(scriptData.begin(), scriptData.end());
198     CScriptID scriptID = script.GetID();
199
200     LOCK(wallet->cs_wallet);
201     if(!wallet->HaveCScript(scriptID))
202         wallet->AddCScript(script);
203 }
204
205 void MultisigDialog::on_saveMultisigAddressButton_clicked()
206 {
207     if(!model)
208         return;
209
210     CWallet *wallet = model->getWallet();
211     std::string redeemScript = ui->redeemScript->text().toStdString();
212     std::string address = ui->multisigAddress->text().toStdString();
213     std::string label("multisig");
214
215     if(!model->validateAddress(QString(address.c_str())))
216         return;
217
218     std::vector<unsigned char> scriptData(ParseHex(redeemScript));
219     CScript script(scriptData.begin(), scriptData.end());
220     CScriptID scriptID = script.GetID();
221
222     LOCK(wallet->cs_wallet);
223     if(!wallet->HaveCScript(scriptID))
224         wallet->AddCScript(script);
225     if(!wallet->mapAddressBook.count(CBitcoinAddress(address).Get()))
226         wallet->SetAddressBookName(CBitcoinAddress(address).Get(), label);
227 }
228
229 void MultisigDialog::clear()
230 {
231     while(ui->pubkeyEntries->count())
232         delete ui->pubkeyEntries->takeAt(0)->widget();
233
234     addPubKey();
235     addPubKey();
236     updateRemoveEnabled();
237 }
238
239 MultisigAddressEntry * MultisigDialog::addPubKey()
240 {
241     MultisigAddressEntry *entry = new MultisigAddressEntry(this);
242
243     entry->setModel(model);
244     ui->pubkeyEntries->addWidget(entry);
245     connect(entry, SIGNAL(removeEntry(MultisigAddressEntry *)), this, SLOT(removeEntry(MultisigAddressEntry *)));
246     updateRemoveEnabled();
247     entry->clear();
248     ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
249     QScrollBar *bar = ui->scrollArea->verticalScrollBar();
250     if(bar)
251         bar->setSliderPosition(bar->maximum());
252
253     return entry;
254 }
255
256 void MultisigDialog::removeEntry(MultisigAddressEntry *entry)
257 {
258     delete entry;
259     updateRemoveEnabled();
260 }
261
262 void MultisigDialog::on_createTransactionButton_clicked()
263 {
264     CTransaction transaction;
265
266     // Get inputs
267     for(int i = 0; i < ui->inputs->count(); i++)
268     {
269         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
270         if(entry)
271         {
272             if(entry->validate())
273             {
274                 CTxIn input = entry->getInput();
275                 transaction.vin.push_back(input);
276             }
277             else
278                 return;
279         }
280     }
281
282     // Get outputs
283     for(int i = 0; i < ui->outputs->count(); i++)
284     {
285         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
286
287         if(entry)
288         {
289             if(entry->validate())
290             {
291                 SendCoinsRecipient recipient = entry->getValue();
292                 CBitcoinAddress address(recipient.address.toStdString());
293                 CScript scriptPubKey;
294                 scriptPubKey.SetDestination(address.Get());
295                 int64 amount = recipient.amount;
296                 CTxOut output(amount, scriptPubKey);
297                 transaction.vout.push_back(output);
298             }
299             else
300                 return;
301         }
302     }
303
304     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
305     ss << transaction;
306     ui->transaction->setText(HexStr(ss.begin(), ss.end()).c_str());
307 }
308
309 void MultisigDialog::on_transaction_textChanged()
310 {
311     while(ui->inputs->count())
312         delete ui->inputs->takeAt(0)->widget();
313     while(ui->outputs->count())
314         delete ui->outputs->takeAt(0)->widget();
315
316     if(ui->transaction->text().size() > 0)
317         ui->signTransactionButton->setEnabled(true);
318     else
319         ui->signTransactionButton->setEnabled(false);
320
321     // Decode the raw transaction
322     std::vector<unsigned char> txData(ParseHex(ui->transaction->text().toStdString()));
323     CDataStream ss(txData, SER_NETWORK, PROTOCOL_VERSION);
324     CTransaction tx;
325     try
326     {
327         ss >> tx;
328     }
329     catch(std::exception &e)
330     {
331         return;
332     }
333
334     // Fill input list
335     int index = -1;
336     BOOST_FOREACH(const CTxIn& txin, tx.vin)
337     {
338         uint256 prevoutHash = txin.prevout.hash;
339         addInput();
340         index++;
341         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(index)->widget());
342         if(entry)
343         {
344             entry->setTransactionId(QString(prevoutHash.GetHex().c_str()));
345             entry->setTransactionOutputIndex(txin.prevout.n);
346         }
347     }
348
349     // Fill output list
350     index = -1;
351     BOOST_FOREACH(const CTxOut& txout, tx.vout)
352     {
353         CScript scriptPubKey = txout.scriptPubKey;
354         CTxDestination addr;
355         ExtractDestination(scriptPubKey, addr);
356         CBitcoinAddress address(addr);
357         SendCoinsRecipient recipient;
358         recipient.address = QString(address.ToString().c_str());
359         recipient.amount = txout.nValue;
360         addOutput();
361         index++;
362         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(index)->widget());
363         if(entry)
364         {
365             entry->setValue(recipient);
366         }
367     }
368
369     updateRemoveEnabled();
370 }
371
372 void MultisigDialog::on_copyTransactionButton_clicked()
373 {
374     QApplication::clipboard()->setText(ui->transaction->text());
375 }
376
377 void MultisigDialog::on_pasteTransactionButton_clicked()
378 {
379     ui->transaction->setText(QApplication::clipboard()->text());
380 }
381
382 void MultisigDialog::on_signTransactionButton_clicked()
383 {
384     ui->signedTransaction->clear();
385
386     if(!model)
387         return;
388
389     CWallet *wallet = model->getWallet();
390
391     // Decode the raw transaction
392     std::vector<unsigned char> txData(ParseHex(ui->transaction->text().toStdString()));
393     CDataStream ss(txData, SER_NETWORK, PROTOCOL_VERSION);
394     CTransaction tx;
395     try
396     {
397         ss >> tx;
398     }
399     catch(std::exception &e)
400     {
401         return;
402     }
403     CTransaction mergedTx(tx);
404
405     // Fetch previous transactions (inputs)
406     std::map<COutPoint, CScript> mapPrevOut;
407     for(int i = 0; i < mergedTx.vin.size(); i++)
408     {
409         CTransaction tempTx;
410         MapPrevTx mapPrevTx;
411         CTxDB txdb("r");
412         std::map<uint256, CTxIndex> unused;
413         bool fInvalid;
414
415         tempTx.vin.push_back(mergedTx.vin[i]);
416         tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid);
417
418         BOOST_FOREACH(const CTxIn& txin, tempTx.vin)
419         {
420             const uint256& prevHash = txin.prevout.hash;
421             if(mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size() > txin.prevout.n)
422                 mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey;
423         }
424     }
425
426     // Add the redeem scripts to the wallet keystore
427     for(int i = 0; i < ui->inputs->count(); i++)
428     {
429         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
430         if(entry)
431         {
432             QString redeemScriptStr = entry->getRedeemScript();
433             if(redeemScriptStr.size() > 0)
434             {
435                 std::vector<unsigned char> scriptData(ParseHex(redeemScriptStr.toStdString()));
436                 CScript redeemScript(scriptData.begin(), scriptData.end());
437                 wallet->AddCScript(redeemScript);
438             }
439         }
440     }
441
442     WalletModel::UnlockContext ctx(model->requestUnlock());
443     if(!ctx.isValid())
444         return;
445
446     // Sign what we can
447     bool fComplete = true;
448     for(int i = 0; i < mergedTx.vin.size(); i++)
449     {
450         CTxIn& txin = mergedTx.vin[i];
451         if(mapPrevOut.count(txin.prevout) == 0)
452         {
453             fComplete = false;
454             continue;
455         }
456         const CScript& prevPubKey = mapPrevOut[txin.prevout];
457
458         txin.scriptSig.clear();
459         SignSignature(*wallet, prevPubKey, mergedTx, i, SIGHASH_ALL);
460         txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, tx.vin[i].scriptSig);
461         if(!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, true, 0))
462         {
463             fComplete = false;
464         }
465     }
466
467     CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
468     ssTx << mergedTx;
469     ui->signedTransaction->setText(HexStr(ssTx.begin(), ssTx.end()).c_str());
470
471     if(fComplete)
472     {
473         ui->statusLabel->setText(tr("Transaction signature is complete"));
474         ui->sendTransactionButton->setEnabled(true);
475     }
476     else
477     {
478         ui->statusLabel->setText(tr("Transaction is NOT completely signed"));
479         ui->sendTransactionButton->setEnabled(false);
480     }
481 }
482
483 void MultisigDialog::on_copySignedTransactionButton_clicked()
484 {
485     QApplication::clipboard()->setText(ui->signedTransaction->text());
486 }
487
488 void MultisigDialog::on_sendTransactionButton_clicked()
489 {
490     int64 transactionSize = ui->signedTransaction->text().size() / 2;
491     if(transactionSize == 0)
492         return;
493
494     // Check the fee
495     int64 fee = (int64 ) (ui->fee->text().toDouble() * COIN);
496     int64 minFee = MIN_TX_FEE * (1 + (int64) transactionSize / 1000);
497     if(fee < minFee)
498     {
499         QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Confirm send transaction"), tr("The fee of the transaction (%1 NVC) is smaller than the expected fee (%2 NVC). Do you want to send the transaction anyway?").arg((double) fee / COIN).arg((double) minFee / COIN), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
500         if(ret != QMessageBox::Yes)
501             return;
502     }
503     else if(fee > minFee)
504     {
505         QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Confirm send transaction"), tr("The fee of the transaction (%1 NVC) is bigger than the expected fee (%2 NVC). Do you want to send the transaction anyway?").arg((double) fee / COIN).arg((double) minFee / COIN), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
506         if(ret != QMessageBox::Yes)
507             return;
508     }
509
510     // Decode the raw transaction
511     std::vector<unsigned char> txData(ParseHex(ui->signedTransaction->text().toStdString()));
512     CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
513     CTransaction tx;
514     try
515     {
516         ssData >> tx;
517     }
518     catch(std::exception &e)
519     {
520         return;
521     }
522     uint256 txHash = tx.GetHash();
523
524     // Check if the transaction is already in the blockchain
525     CTransaction existingTx;
526     uint256 blockHash = 0;
527     if(GetTransaction(txHash, existingTx, blockHash))
528     {
529         if(blockHash != 0)
530             return;
531     }
532
533     // Send the transaction to the local node
534     CTxDB txdb("r");
535     if(!tx.AcceptToMemoryPool(txdb, false))
536     return;
537     SyncWithWallets(tx, NULL, true);
538     //(CInv(MSG_TX, txHash), tx);
539     RelayTransaction(tx, txHash);
540 }
541
542 MultisigInputEntry * MultisigDialog::addInput()
543 {
544     MultisigInputEntry *entry = new MultisigInputEntry(this);
545
546     entry->setModel(model);
547     ui->inputs->addWidget(entry);
548     connect(entry, SIGNAL(removeEntry(MultisigInputEntry *)), this, SLOT(removeEntry(MultisigInputEntry *)));
549     connect(entry, SIGNAL(updateAmount()), this, SLOT(updateAmounts()));
550     updateRemoveEnabled();
551     entry->clear();
552     ui->scrollAreaWidgetContents_2->resize(ui->scrollAreaWidgetContents_2->sizeHint());
553     QScrollBar *bar = ui->scrollArea_2->verticalScrollBar();
554     if(bar)
555         bar->setSliderPosition(bar->maximum());
556
557     return entry;
558 }
559
560 void MultisigDialog::removeEntry(MultisigInputEntry *entry)
561 {
562     delete entry;
563     updateRemoveEnabled();
564 }
565
566 SendCoinsEntry * MultisigDialog::addOutput()
567 {
568     SendCoinsEntry *entry = new SendCoinsEntry(this);
569
570     entry->setModel(model);
571     ui->outputs->addWidget(entry);
572     connect(entry, SIGNAL(removeEntry(SendCoinsEntry *)), this, SLOT(removeEntry(SendCoinsEntry *)));
573     connect(entry, SIGNAL(payAmountChanged()), this, SLOT(updateAmounts()));
574     updateRemoveEnabled();
575     entry->clear();
576     ui->scrollAreaWidgetContents_3->resize(ui->scrollAreaWidgetContents_3->sizeHint());
577     QScrollBar *bar = ui->scrollArea_3->verticalScrollBar();
578     if(bar)
579         bar->setSliderPosition(bar->maximum());
580
581     return entry;
582 }
583
584 void MultisigDialog::removeEntry(SendCoinsEntry *entry)
585 {
586     delete entry;
587     updateRemoveEnabled();
588 }
589
590 void MultisigDialog::updateAmounts()
591 {
592     // Update inputs amount
593     int64 inputsAmount = 0;
594     for(int i = 0; i < ui->inputs->count(); i++)
595     {
596         MultisigInputEntry *entry = qobject_cast<MultisigInputEntry *>(ui->inputs->itemAt(i)->widget());
597         if(entry)
598             inputsAmount += entry->getAmount();
599     }
600     QString inputsAmountStr;
601     inputsAmountStr.sprintf("%.6f", (double) inputsAmount / COIN);
602     ui->inputsAmount->setText(inputsAmountStr);
603
604     // Update outputs amount
605     int64 outputsAmount = 0;
606     for(int i = 0; i < ui->outputs->count(); i++)
607     {
608         SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(ui->outputs->itemAt(i)->widget());
609         if(entry)
610             outputsAmount += entry->getValue().amount;
611     }
612     QString outputsAmountStr;
613     outputsAmountStr.sprintf("%.6f", (double) outputsAmount / COIN);
614     ui->outputsAmount->setText(outputsAmountStr);
615
616     // Update Fee amount
617     int64 fee = inputsAmount - outputsAmount;
618     QString feeStr;
619     feeStr.sprintf("%.6f", (double) fee / COIN);
620     ui->fee->setText(feeStr);
621 }