dagcoin-core
Version:
138 lines (130 loc) • 5.78 kB
JavaScript
/*jslint node: true */
;
var constants = require('byteballcore/constants.js');
var conf = require('byteballcore/conf.js');
var db = require('byteballcore/db.js');
var mutex = require('byteballcore/mutex.js');
function readLeastFundedAddresses(asset, wallet, handleFundedAddresses) {
db.query("SELECT address, SUM(amount) AS total \n\
FROM my_addresses CROSS JOIN outputs USING(address) \n\
CROSS JOIN units USING(unit) \n\
WHERE wallet=? AND is_stable=1 AND sequence='good' AND is_spent=0 AND " + (asset ? "asset=" + db.escape(asset) : "asset IS NULL") + " \n\
AND NOT EXISTS ( \n\
SELECT * FROM units CROSS JOIN unit_authors USING(unit) \n\
WHERE is_stable=0 AND unit_authors.address=outputs.address AND definition_chash IS NOT NULL \n\
) \n\
GROUP BY address ORDER BY SUM(amount) LIMIT 15", [wallet], function (rows) {
handleFundedAddresses(rows.map(function (row) {
return row.address;
}));
});
}
function determineCountOfOutputs(asset, wallet, handleCount) {
db.query("SELECT COUNT(*) AS count FROM my_addresses CROSS JOIN outputs USING(address) JOIN units USING(unit) \n\
WHERE wallet=? AND is_spent=0 AND " + (asset ? "asset=" + db.escape(asset) : "asset IS NULL") + " AND is_stable=1 AND sequence='good'", [wallet], function (rows) {
handleCount(rows[0].count);
});
}
function readDestinationAddress(wallet, handleAddress) {
db.query("SELECT address FROM my_addresses WHERE wallet=? ORDER BY is_change DESC, address_index ASC LIMIT 1", [wallet], function (rows) {
if (rows.length === 0) throw Error('no dest address');
handleAddress(rows[0].address);
});
}
function consolidate(wallet, signer) {
var asset = null;
mutex.lock(['consolidate'], function (unlock) {
determineCountOfOutputs(asset, wallet, function (count) {
console.log(count + ' unspent outputs');
if (count <= conf.MAX_UNSPENT_OUTPUTS) return unlock();
var count_to_spend = Math.min(count - conf.MAX_UNSPENT_OUTPUTS + 1, constants.MAX_INPUTS_PER_PAYMENT_MESSAGE - 1);
readLeastFundedAddresses(asset, wallet, function (arrAddresses) {
db.query("SELECT address, unit, message_index, output_index, amount \n\
FROM outputs \n\
CROSS JOIN units USING(unit) \n\
WHERE address IN(?) AND is_stable=1 AND sequence='good' AND is_spent=0 AND " + (asset ? "asset=" + db.escape(asset) : "asset IS NULL") + " \n\
AND NOT EXISTS ( \n\
SELECT * FROM units CROSS JOIN unit_authors USING(unit) \n\
WHERE is_stable=0 AND unit_authors.address=outputs.address AND definition_chash IS NOT NULL \n\
) \n\
ORDER BY amount LIMIT ?", [arrAddresses, count_to_spend], function (rows) {
// if all inputs are so small that they don't pay even for fees, add one more large input
function addLargeInputIfNecessary(onDone) {
var target_amount = 1000 + 100 * rows.length;
if (input_amount > target_amount) return onDone();
target_amount += 100;
db.query("SELECT address, unit, message_index, output_index, amount \n\
FROM my_addresses \n\
CROSS JOIN outputs USING(address) \n\
CROSS JOIN units USING(unit) \n\
WHERE wallet=? AND is_stable=1 AND sequence='good' \n\
AND is_spent=0 AND " + (asset ? "asset=" + db.escape(asset) : "asset IS NULL") + " \n\
AND NOT EXISTS ( \n\
SELECT * FROM units CROSS JOIN unit_authors USING(unit) \n\
WHERE is_stable=0 AND unit_authors.address=outputs.address AND definition_chash IS NOT NULL \n\
) \n\
AND amount>? AND unit NOT IN(?) \n\
LIMIT 1", [wallet, target_amount - input_amount, Object.keys(assocUsedUnits)], function (large_rows) {
if (large_rows.length === 0) return onDone("no large input found");
var row = large_rows[0];
assocUsedAddresses[row.address] = true;
input_amount += row.amount;
arrInputs.push({
unit: row.unit,
message_index: row.message_index,
output_index: row.output_index
});
onDone();
});
}
var assocUsedAddresses = {};
var assocUsedUnits = {};
var input_amount = 0;
var arrInputs = rows.map(function (row) {
assocUsedAddresses[row.address] = true;
assocUsedUnits[row.unit] = true;
input_amount += row.amount;
return {
unit: row.unit,
message_index: row.message_index,
output_index: row.output_index
};
});
addLargeInputIfNecessary(function (err) {
if (err) {
console.log("consolidation failed: " + err);
return unlock();
}
var arrUsedAddresses = Object.keys(assocUsedAddresses);
readDestinationAddress(wallet, function (dest_address) {
var composer = require('byteballcore/composer.js');
composer.composeJoint({
paying_addresses: arrUsedAddresses,
outputs: [{ address: dest_address, amount: 0 }],
inputs: arrInputs,
input_amount: input_amount,
earned_headers_commission_recipients: [{ address: dest_address, earned_headers_commission_share: 100 }],
callbacks: composer.getSavingCallbacks({
ifOk: function ifOk(objJoint) {
var network = require('byteballcore/network.js');
network.broadcastJoint(objJoint);
unlock();
consolidate(wallet, signer); // do more if something's left
},
ifError: function ifError(err) {
throw Error('failed to compose consolidation transaction: ' + err);
},
ifNotEnoughFunds: function ifNotEnoughFunds(err) {
throw Error('not enough funds to compose consolidation transaction: ' + err);
}
}),
signer: signer
});
});
});
});
});
});
});
}
exports.consolidate = consolidate;