Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi send #1445

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions qa/rpc-tests/llmq-is-spark.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,34 @@ def run_test(self):
break;
val = Decimal((val - 10000) / 1e+8).quantize(Decimal('1e-7'))

address = self.nodes[0].getnewaddress()[0]
for i in range(0, 3):
multiTxids = self.nodes[0].mintspark({address: {"amount": 1, "subtractFee": False}, sparkaddress: {"amount": 2, "memo": "Test", "subtractFee": False}})

for multiTxid in multiTxids:
sendTx = self.nodes[0].getrawtransaction(multiTxid, 1)
valToSend = 0
for vi in sendTx['vin']:
valToSend += vi['valueSat']
if valToSend > 30000:
break;
valToSend = Decimal((valToSend - 30000) / 1e+8).quantize(Decimal('1e-7'))

assert(self.wait_for_instantlock(mintTxid, self.nodes[0]))
assert(self.wait_for_instantlock(multiTxid, self.nodes[0]))

mintDspend = self.nodes[0].createrawtransaction(mintTx['vin'], {self.nodes[0].getnewaddress(): str(val)})
assert_raises_jsonrpc(-26, 'tx-txlock-conflict', self.nodes[0].sendrawtransaction, mintDspend)

self.nodes[0].generate(3)
assert (self.nodes[0].getrawtransaction(mintTxid, True)['confirmations'] > 0)

sendDspend = self.nodes[0].createrawtransaction(sendTx['vin'], {self.nodes[0].getnewaddress(): str(valToSend)})
assert_raises_jsonrpc(-26, 'tx-txlock-conflict', self.nodes[0].sendrawtransaction, sendDspend)

self.nodes[0].generate(3)
assert (self.nodes[0].getrawtransaction(multiTxid, True)['confirmations'] > 0)

spendTxid = self.nodes[0].spendspark({self.sporkAddress: {"amount": 0.1, "subtractFee": False}})
assert(self.wait_for_instantlock(spendTxid, self.nodes[0]))

Expand Down
21 changes: 14 additions & 7 deletions qa/rpc-tests/spark_mint.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,47 +17,54 @@ def run_test(self):
self.nodes[0].generate(1001)

# generate coins
amounts = [1, 1.1, 2, 10]
amounts = [1, 1.1, 2, 10, 3, 4]

# 10 confirmations
address = self.nodes[0].getnewsparkaddress()[0]
nAddress = self.nodes[0].getnewsparkaddress()[0]
self.nodes[0].mintspark({address: {"amount": amounts[0], "memo":"Test memo"}})
self.nodes[0].mintspark({address: {"amount": amounts[1], "memo": "Test memo"}})
self.nodes[0].generate(5)

# 5 confirmations
self.nodes[0].mintspark({address: {"amount": amounts[2], "memo": "Test memo"}})
self.nodes[0].mintspark({address: {"amount": amounts[3], "memo": "Test memo"}})

nAddress = self.nodes[0].getnewsparkaddress()[0]
self.nodes[0].mintspark({address: {"amount": amounts[4], "subtractFee": False}, nAddress: {"amount": amounts[5], "memo": "Test", "subtractFee": False}})

self.nodes[0].generate(5)

# get all mints and utxos
mints = self.verify_listsparkmints(amounts)
self.verify_listunspentsparkmints(amounts)
assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints)))
assert_equal([False, False, False, False, False, False], list(map(lambda m : m["isUsed"], mints)))

# state modification test
# mark two coins as used
self.nodes[0].setsparkmintstatus(mints[2]["lTagHash"], True)
self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], True)

mints = self.verify_listsparkmints(amounts)
self.verify_listunspentsparkmints([1, 1.1])
assert_equal([False, False, True, True], list(map(lambda m : m["isUsed"], mints)))
self.verify_listunspentsparkmints([1, 1.1, 4, 10])
assert_equal([False, False, True, True, False, False], list(map(lambda m : m["isUsed"], mints)))

# set a coin as unused
self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], False)
mints = self.verify_listsparkmints(amounts)
self.verify_listunspentsparkmints([1, 1.1, 10])
assert_equal([False, False, True, False], list(map(lambda m : m["isUsed"], mints)))
self.verify_listunspentsparkmints([1, 1.1, 3, 4, 10])
assert_equal([False, False, True, False, False, False], list(map(lambda m : m["isUsed"], mints)))

self.nodes[0].setsparkmintstatus(mints[0]["lTagHash"], False)
self.nodes[0].setsparkmintstatus(mints[1]["lTagHash"], False)
self.nodes[0].setsparkmintstatus(mints[2]["lTagHash"], False)
self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], False)
self.nodes[0].setsparkmintstatus(mints[4]["lTagHash"], False)
self.nodes[0].setsparkmintstatus(mints[5]["lTagHash"], False)

mints = self.verify_listsparkmints(amounts)
self.verify_listunspentsparkmints(amounts)
assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints)))
assert_equal([False, False, False, False, False, False], list(map(lambda m : m["isUsed"], mints)))

def verify_listsparkmints(self, expected_amounts):
mints = self.nodes[0].listsparkmints()
Expand Down
9 changes: 7 additions & 2 deletions qa/rpc-tests/spark_setmintstatus_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ def run_test(self):
self.nodes[0].generate(801)
self.sync_all()

sparkAddress = self.nodes[0].getnewsparkaddress()[0]
sparkAddress1 = self.nodes[0].getnewsparkaddress()[0]
txid = list()
txid.append(self.nodes[0].mintspark({sparkAddress: {"amount": 1, "memo":"Test memo"}}))
txid.append(self.nodes[0].mintspark({sparkAddress1: {"amount": 1, "memo":"Test memo"}}))

spark_mint = self.nodes[0].listsparkmints()

Expand Down Expand Up @@ -56,6 +56,11 @@ def run_test(self):
assert not mint_info['isUsed'], \
'This mint with txid: {} should not be Used.'.format(txid)

sparkAddress2 = self.nodes[0].getnewsparkaddress()[0]
self.nodes[0].mintspark({sparkAddress1: {"amount": 1, "subtractFee": False}, sparkAddress2: {"amount": 2, "memo": "Test", "subtractFee": False}})
spark_mint = self.nodes[0].listsparkmints()

assert len(spark_mint) == 3, 'Should be number of mints.'

assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, [(mint_info['lTagHash'], "sometext")])
assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, [mint_info['lTagHash']])
Expand Down
14 changes: 12 additions & 2 deletions qa/rpc-tests/spark_spend_gettransaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,18 @@ def run_test(self):
self.nodes[0].generate(1)
self.sync_all()

balance = self.nodes[0].getsparkbalance()
assert balance['availableBalance'] / 1e8 == 10
sparkBalance = self.nodes[0].getsparkbalance()
assert sparkBalance['availableBalance'] / 1e8 == 10

address = self.nodes[0].getnewaddress()
for _ in range(10):
self.nodes[0].mintspark({address: {"amount": 1, "subtractFee": False}, sparkAddress: {"amount": 2, "memo": "Test", "subtractFee": False}})

self.nodes[0].generate(1)
self.sync_all()

sparkBalance = self.nodes[0].getsparkbalance()
assert sparkBalance['availableBalance'] / 1e8 == 30

# case 1: Spend many with watchonly address
spendto_wo_id = self.nodes[0].spendspark({watchonly_address: {"amount": 1, "subtractFee": False}})
Expand Down
8 changes: 4 additions & 4 deletions src/qt/sendcoinsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ void SendCoinsDialog::on_sendButton_clicked()
prepareStatus = model->prepareJoinSplitTransaction(currentTransaction, &ctrl);
} else if ((fAnonymousMode == true) && spark::IsSparkAllowed()) {
prepareStatus = model->prepareSpendSparkTransaction(currentTransaction, &ctrl);
} else if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount)) {
} else if ((fAnonymousMode == false) && (sparkAddressCount > 0)) {
if (spark::IsSparkAllowed())
prepareStatus = model->prepareMintSparkTransaction(transactions, recipients, wtxAndFees, reservekeys, &ctrl);
else {
Expand Down Expand Up @@ -462,7 +462,7 @@ void SendCoinsDialog::on_sendButton_clicked()
QString questionString = tr("Are you sure you want to send?");
questionString.append("<br /><br />%1");
double txSize;
if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount) && spark::IsSparkAllowed())
if ((fAnonymousMode == false) && (sparkAddressCount > 0) && spark::IsSparkAllowed())
{
for (auto &transaction : transactions) {
txFee += transaction.getTransactionFee();
Expand All @@ -488,7 +488,7 @@ void SendCoinsDialog::on_sendButton_clicked()

// add total amount in all subdivision units
questionString.append("<hr />");
if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount) && spark::IsSparkAllowed())
if ((fAnonymousMode == false) && (sparkAddressCount > 0) && spark::IsSparkAllowed())
{
totalAmount = mintSparkAmount + txFee;
} else if ((fAnonymousMode == true) && (recipients.size() == 1) && spark::IsSparkAllowed()) {
Expand Down Expand Up @@ -530,7 +530,7 @@ void SendCoinsDialog::on_sendButton_clicked()
sendStatus = model->sendPrivateCoins(currentTransaction);
} else if ((fAnonymousMode == true) && spark::IsSparkAllowed()) {
sendStatus = model->spendSparkCoins(currentTransaction);
} else if ((fAnonymousMode == false) && (sparkAddressCount == recipients.size()) && spark::IsSparkAllowed()) {
} else if ((fAnonymousMode == false) && (sparkAddressCount > 0) && spark::IsSparkAllowed()) {
sendStatus = model->mintSparkCoins(transactions, wtxAndFees, reservekeys);
} else if ((fAnonymousMode == false) && (sparkAddressCount == 0)) {
sendStatus = model->sendCoins(currentTransaction);
Expand Down
4 changes: 3 additions & 1 deletion src/qt/sparkmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "guiconstants.h"
#include "guiutil.h"
#include "sparkmodel.h"
#include "../wallet/wallet.h"

#include <QDateTime>
#include <QTimer>
Expand Down Expand Up @@ -83,7 +84,8 @@ CAmount SparkModel::mintSparkAll()

std::vector<std::pair<CWalletTx, CAmount>> wtxAndFee;
std::vector<spark::MintedCoinData> outputs;
std::string strError = wallet->MintAndStoreSpark(outputs, wtxAndFee, true, true);
std::vector<CRecipient> vecSend;
std::string strError = wallet->MintAndStoreSpark(vecSend, outputs, wtxAndFee, true, true);
if (strError != "") {
throw std::runtime_error("Fail to mint all public balance, " + strError);
}
Expand Down
30 changes: 19 additions & 11 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareMintSparkTransaction(std::vecto
return OK;
}

std::vector<CRecipient> vecSend;
QSet<QString> setAddress; // Used to detect duplicates
int nAddresses = 0;
std::vector<spark::MintedCoinData> outputs;
Expand All @@ -1365,22 +1366,28 @@ WalletModel::SendCoinsReturn WalletModel::prepareMintSparkTransaction(std::vecto
fSubtractFeeFromAmount = true;

{ // User-entered Firo address / amount:
if (!validateSparkAddress(rcp.address)) {
return InvalidAddress;
}
if (rcp.amount <= 0) {
return InvalidAmount;
}

setAddress.insert(rcp.address);
++nAddresses;

spark::Address address(params);
address.decode(rcp.address.toStdString());
spark::MintedCoinData data;
data.address = address;
data.memo = "";
data.v = rcp.amount;
outputs.push_back(data);
if (validateAddress(rcp.address)) {
CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);
} else if (validateSparkAddress(rcp.address)) {
spark::Address address(params);
address.decode(rcp.address.toStdString());
spark::MintedCoinData data;
data.address = address;
data.memo = "";
data.v = rcp.amount;
outputs.push_back(data);
} else {
return InvalidAddress;
}
total += rcp.amount;
}
}
Expand All @@ -1401,7 +1408,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareMintSparkTransaction(std::vecto
int nChangePosRet = -1;

std::string strFailReason;
bool fCreated = wallet->CreateSparkMintTransactions(outputs, wtxAndFees, nFeeRequired, reservekeys, nChangePosRet, fSubtractFeeFromAmount, strFailReason, coinControl, false);
bool fCreated = wallet->CreateSparkMintTransactions(vecSend,outputs, wtxAndFees, nFeeRequired, reservekeys, nChangePosRet, fSubtractFeeFromAmount, strFailReason, coinControl, false);

transactions.clear();
transactions.reserve(wtxAndFees.size());
for (auto &wtxAndFee : wtxAndFees) {
Expand Down
15 changes: 9 additions & 6 deletions src/qt/walletmodeltransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,19 @@ void WalletModelTransaction::setTransactionFee(const CAmount& newFee)
void WalletModelTransaction::reassignAmounts(int nChangePosRet)
{
int i = 0;
for (QList<SendCoinsRecipient>::iterator it = recipients.begin(); it != recipients.end(); ++it)
QList<SendCoinsRecipient>::iterator rec = recipients.begin();
for (auto it = walletTransaction->tx->vout.begin(); it != walletTransaction->tx->vout.end(); ++it)
{
SendCoinsRecipient& rcp = (*it);
SendCoinsRecipient& rcp = (*rec);
{
if (i == nChangePosRet)
i++;
if (walletTransaction->tx->vout[i].scriptPubKey.IsSparkSMint()) {
continue;

if (it->scriptPubKey.IsSparkSMint()) {
bool ok = true;
spark::Coin coin(spark::Params::get_default());
try {
spark::ParseSparkMintCoin(walletTransaction->tx->vout[i].scriptPubKey, coin);
spark::ParseSparkMintCoin(it->scriptPubKey, coin);
} catch (std::invalid_argument&) {
ok = false;
}
Expand All @@ -73,9 +75,10 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet)
}
}
} else {
rcp.amount = walletTransaction->tx->vout[i].nValue;
rcp.amount = it->nValue;
}
i++;
++rec;
}
}
}
Expand Down
Loading
Loading