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