Commit b773b449 authored by Eriksson Monteiro's avatar Eriksson Monteiro

update millix node and wallet ui: add aggregate button

parent f9f58fec
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -3,6 +3,9 @@ import walletUtils from '../../core/wallet/wallet-utils'; ...@@ -3,6 +3,9 @@ import walletUtils from '../../core/wallet/wallet-utils';
import ntp from '../../core/ntp'; import ntp from '../../core/ntp';
import config from '../../core/config/config'; import config from '../../core/config/config';
import wallet from '../../core/wallet/wallet'; import wallet from '../../core/wallet/wallet';
import async from 'async';
import database from '../../database/database';
import _ from 'lodash';
/** /**
...@@ -65,7 +68,7 @@ class _RVBqKlGdk9aEhi5J extends Endpoint { ...@@ -65,7 +68,7 @@ class _RVBqKlGdk9aEhi5J extends Endpoint {
(() => { (() => {
if (!Array.isArray(transactionInputs) || !Array.isArray(transactionOutputs) || typeof(outputFee) !== "object") { if (!Array.isArray(transactionInputs) || !Array.isArray(transactionOutputs) || typeof (outputFee) !== 'object') {
return Promise.reject('invalid request body'); return Promise.reject('invalid request body');
} }
...@@ -83,9 +86,38 @@ class _RVBqKlGdk9aEhi5J extends Endpoint { ...@@ -83,9 +86,38 @@ class _RVBqKlGdk9aEhi5J extends Endpoint {
} }
return Promise.resolve(); return Promise.resolve();
})().then(() => { })().then(() => {
return new Promise((resolve, reject) => {
const amount = _.sum(_.map(transactionOutputs, o => o.amount)) + outputFee.amount;
let allocatedFunds = 0;
async.eachSeries(transactionInputs, (input, callback) => {
database.firstShards((shardID) => {
const transactionRepository = database.getRepository('transaction', shardID);
return transactionRepository.getTransactionOutput({
transaction_id : input.output_transaction_id,
output_position : input.output_position,
address_key_identifier: input.address_key_identifier
});
}).then(output => {
input.amount = output.amount;
allocatedFunds += output.amount;
callback();
}).catch((e) => {
callback(`transaction_output_not_found: ${JSON.stringify(input)}, ${e}`);
});
}, (err) => {
if (err) {
return reject(err);
}
else if (amount !== allocatedFunds) {
return reject(`invalid_amount: allocated (${allocatedFunds}), spend (${amount})`);
}
resolve();
});
});
}).then(() => {
return wallet.proxyTransaction(transactionInputs, transactionOutputs, outputFee, addressAttributeMap, privateKeyMap, transactionVersion, false) return wallet.proxyTransaction(transactionInputs, transactionOutputs, outputFee, addressAttributeMap, privateKeyMap, transactionVersion, false)
.then(signedTransactionList => { .then(signedTransactionList => {
console.log(`[api ${this.endpoint}] Successfully signed transaction transaction. Tx: ${signedTransactionList.map(t=>t.transaction_id).join(",")}`); console.log(`[api ${this.endpoint}] Successfully signed transaction transaction. Tx: ${signedTransactionList.map(t => t.transaction_id).join(',')}`);
res.send(signedTransactionList); res.send(signedTransactionList);
}); });
}).catch(e => { }).catch(e => {
......
import Endpoint from '../endpoint';
import mutex from '../../core/mutex';
import wallet from '../../core/wallet/wallet';
/**
* api send_aggregation_transaction_from_wallet
*/
class _kC5N9Tz06b2rA4Pg extends Endpoint {
constructor() {
super('kC5N9Tz06b2rA4Pg');
}
/**
* submits a new aggregation transaction from the active wallet which
* optimizes the funds and allows spending more funds in fewer
* transactions. this API builds the tx payload and submits it
* @param app
* @param req
* @param res
* @returns {*}
*/
handler(app, req, res) {
mutex.lock(['submit_transaction'], (unlock) => {
wallet.aggregateOutputs()
.then(transaction => {
unlock();
res.send({
api_status: 'success',
transaction
});
})
.catch(e => {
console.log(`[api ${this.endpoint}] error: ${e}`);
unlock();
res.send({
api_status : 'fail',
api_message: e
});
});
});
}
}
export default new _kC5N9Tz06b2rA4Pg();
...@@ -414,6 +414,15 @@ ...@@ -414,6 +414,15 @@
"permission": "{\"require_identity\": true, \"private\": true}", "permission": "{\"require_identity\": true, \"private\": true}",
"enable": true "enable": true
}, },
{
"id": "kC5N9Tz06b2rA4Pg",
"name": "send_aggregation_transaction_from_wallet",
"description": "submits a new aggregation transaction from the active wallet which optimizes the funds and allows spending more funds in fewer transactions. this API builds the tx payload and submits it",
"method": "GET",
"version_released": "1.16.0",
"permission": "{\"require_identity\": true, \"private\": true}",
"enable": true
},
{ {
"id": "w9UTTA7NXnEDUXhe", "id": "w9UTTA7NXnEDUXhe",
"name": "list_transaction_history", "name": "list_transaction_history",
......
...@@ -768,13 +768,14 @@ export const WALLET_TRANSACTION_SUPPORTED_VERSION_TEST_NETWORK = [ ...@@ -768,13 +768,14 @@ export const WALLET_TRANSACTION_SUPPORTED_VERSION_TEST_NETWORK = [
export const WALLET_TRANSACTION_SUPPORTED_VERSION = MODE_TEST_NETWORK ? WALLET_TRANSACTION_SUPPORTED_VERSION_TEST_NETWORK : WALLET_TRANSACTION_SUPPORTED_VERSION_MAIN_NETWORK; export const WALLET_TRANSACTION_SUPPORTED_VERSION = MODE_TEST_NETWORK ? WALLET_TRANSACTION_SUPPORTED_VERSION_TEST_NETWORK : WALLET_TRANSACTION_SUPPORTED_VERSION_MAIN_NETWORK;
export const WALLET_TRANSACTION_QUEUE_SIZE_MAX = 1000; export const WALLET_TRANSACTION_QUEUE_SIZE_MAX = 1000;
export const WALLET_TRANSACTION_QUEUE_SIZE_NORMAL = 250; export const WALLET_TRANSACTION_QUEUE_SIZE_NORMAL = 250;
export const WALLET_TRANSACTION_AGGREGATION_MAX = 11;
export const NETWORK_LONG_TIME_WAIT_MAX = 3000; export const NETWORK_LONG_TIME_WAIT_MAX = 3000;
export const NETWORK_SHORT_TIME_WAIT_MAX = 1500; export const NETWORK_SHORT_TIME_WAIT_MAX = 1500;
export const DATABASE_ENGINE = 'sqlite'; export const DATABASE_ENGINE = 'sqlite';
export const DATABASE_CONNECTION = {}; export const DATABASE_CONNECTION = {};
export const MILLIX_CIRCULATION = 9e15; export const MILLIX_CIRCULATION = 9e15;
export const NODE_MILLIX_BUILD_DATE = 1647879243; export const NODE_MILLIX_BUILD_DATE = 1648161547;
export const NODE_MILLIX_VERSION = '1.15.3-tangled'; export const NODE_MILLIX_VERSION = '1.16.0-tangled';
export const DATA_BASE_DIR_MAIN_NETWORK = './millix-tangled'; export const DATA_BASE_DIR_MAIN_NETWORK = './millix-tangled';
export const DATA_BASE_DIR_TEST_NETWORK = './millix-tangled'; export const DATA_BASE_DIR_TEST_NETWORK = './millix-tangled';
let DATA_BASE_DIR = MODE_TEST_NETWORK ? DATA_BASE_DIR_TEST_NETWORK : DATA_BASE_DIR_MAIN_NETWORK; let DATA_BASE_DIR = MODE_TEST_NETWORK ? DATA_BASE_DIR_TEST_NETWORK : DATA_BASE_DIR_MAIN_NETWORK;
...@@ -883,6 +884,7 @@ export default { ...@@ -883,6 +884,7 @@ export default {
NETWORK_LONG_TIME_WAIT_MAX, NETWORK_LONG_TIME_WAIT_MAX,
NETWORK_SHORT_TIME_WAIT_MAX, NETWORK_SHORT_TIME_WAIT_MAX,
WALLET_TRANSACTION_QUEUE_SIZE_MAX, WALLET_TRANSACTION_QUEUE_SIZE_MAX,
WALLET_TRANSACTION_AGGREGATION_MAX,
WALLET_TRANSACTION_QUEUE_SIZE_NORMAL, WALLET_TRANSACTION_QUEUE_SIZE_NORMAL,
WALLET_STARTUP_ADDRESS_BALANCE_SCAN_COUNT, WALLET_STARTUP_ADDRESS_BALANCE_SCAN_COUNT,
WALLET_TRANSACTION_SUPPORTED_VERSION, WALLET_TRANSACTION_SUPPORTED_VERSION,
......
...@@ -646,7 +646,12 @@ class WalletUtils { ...@@ -646,7 +646,12 @@ class WalletUtils {
|| !this.isValidAddress(input.address_key_identifier) || !this.isValidAddress(input.address_key_identifier)
|| !addressRepository.supportedVersionSet.has(input.address_version) || !addressRepository.supportedVersionSet.has(input.address_version)
|| outputUsedInTransaction.has(outputID) || outputUsedInTransaction.has(outputID)
|| transactionDate < input.output_transaction_date) { || transactionDate < input.output_transaction_date
|| !_.has(input, 'input_position')
|| !_.has(input, 'output_position')
|| !_.has(input, 'output_transaction_date')
|| !_.has(input, 'output_transaction_id')
|| !_.has(input, 'output_shard_id')) {
return false; return false;
} }
outputUsedInTransaction.add(outputID); outputUsedInTransaction.add(outputID);
...@@ -784,31 +789,12 @@ class WalletUtils { ...@@ -784,31 +789,12 @@ class WalletUtils {
maximumOldestDate.setMinutes(maximumOldestDate.getMinutes() - config.TRANSACTION_OUTPUT_EXPIRE_OLDER_THAN); maximumOldestDate.setMinutes(maximumOldestDate.getMinutes() - config.TRANSACTION_OUTPUT_EXPIRE_OLDER_THAN);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let allocatedFunds = 0; const allocatedFunds = _.sum(_.map(inputList, o => o.amount));
const amount = _.sum(_.map(outputList, o => o.amount)) + _.sum(_.map(feeOutputList, o => o.amount)); const amount = _.sum(_.map(outputList, o => o.amount)) + _.sum(_.map(feeOutputList, o => o.amount));
async.eachSeries(inputList, (input, callback) => { if (amount !== allocatedFunds) {
database.firstShards((shardID) => { return reject(`invalid_amount: allocated (${allocatedFunds}), spend (${amount})`);
const transactionRepository = database.getRepository('transaction', shardID); }
return transactionRepository.getTransactionOutput({ resolve();
transaction_id : input.output_transaction_id,
output_position : input.output_position,
address_key_identifier: input.address_key_identifier
});
}).then(output => {
allocatedFunds += output.amount;
callback();
}).catch(() => {
callback(`transaction_output_not_found: ${JSON.stringify(input)}`);
});
}, (err) => {
if (err) {
return reject(err);
}
else if (amount !== allocatedFunds) {
return reject(`invalid_amount: allocated (${allocatedFunds}), spend (${amount})`);
}
resolve();
});
}).then(() => new Promise((resolve, reject) => { }).then(() => new Promise((resolve, reject) => {
const addressBaseList = _.uniq(_.map(inputList, i => i.address_base)); const addressBaseList = _.uniq(_.map(inputList, i => i.address_base));
const signatureList = _.map(addressBaseList, addressBase => ({ const signatureList = _.map(addressBaseList, addressBase => ({
...@@ -984,6 +970,107 @@ class WalletUtils { ...@@ -984,6 +970,107 @@ class WalletUtils {
return transactionList; return transactionList;
}); });
} }
splitOutputAmount(inputList, feeOutputList, maxNumberOfOutputs) {
const outputList = [];
const amount = _.sum(_.map(inputList, o => o.amount)) - _.sum(_.map(feeOutputList, o => o.amount));
const amountPerOutput = Math.floor(amount / maxNumberOfOutputs);
const remainingAmount = amount - maxNumberOfOutputs * amountPerOutput;
const address = inputList[inputList.length - 1];
for (let i = 0; i < maxNumberOfOutputs; i++) {
outputList.push({
address_base : address.address_base,
address_version : address.address_version,
address_key_identifier: address.address_key_identifier,
amount : amountPerOutput
});
}
// add the remaining amount to the last output
outputList[outputList.length - 1].amount += remainingAmount;
return outputList;
}
/*
* generates an aggregation transaction from the active wallet which optimizes the funds and allows spending more funds in fewer transactions
*/
signAggregationTransaction(inputList = [], feeOutputList, addressAttributeMap, privateKeyMap, transactionDate, transactionVersion, consumeSmallerFirst = true) {
const maxNumberOfOutputs = 120;
if (inputList.length <= maxNumberOfOutputs) {
return Promise.reject('aggregation_not_required');
}
const totalTransactions = Math.min(Math.floor(inputList.length / config.TRANSACTION_INPUT_MAX), config.WALLET_TRANSACTION_AGGREGATION_MAX - 1);
if (totalTransactions === 0) { // we just need to create a single transaction
feeOutputList[0].amount = config.TRANSACTION_FEE_DEFAULT;
const outputList = this.splitOutputAmount(inputList, feeOutputList, maxNumberOfOutputs);
return this.signTransaction(inputList, outputList, feeOutputList, addressAttributeMap, privateKeyMap, transactionDate, transactionVersion);
}
const remainingInputs = inputList.length - totalTransactions * config.TRANSACTION_INPUT_MAX;
inputList = _.sortBy(inputList, o => consumeSmallerFirst ? o.amount : -o.amount);
return new Promise((resolve, reject) => {
const feeOutputListIntermediate = _.cloneDeep(feeOutputList);
feeOutputListIntermediate.forEach(fee => fee.amount = 0); /* dont pay fee for intermediate transactions */
async.times(totalTransactions, (idx, callback) => {
const inputListSlice = _.slice(inputList, idx * config.TRANSACTION_INPUT_MAX, (idx + 1) * config.TRANSACTION_INPUT_MAX);
const amount = _.sum(_.map(inputListSlice, o => o.amount));
const address = inputListSlice[inputListSlice.length - 1];
const outputList = [
{
address_base : address.address_base,
address_version : address.address_version,
address_key_identifier: address.address_key_identifier,
amount
}
];
this.signTransaction(inputListSlice, outputList, feeOutputListIntermediate, addressAttributeMap, privateKeyMap, transactionDate, config.WALLET_TRANSACTION_REFRESH_VERSION)
.then(transactions => callback(null, transactions))
.catch(error => callback(error));
}, (error, transactionsList) => {
if (error) {
return reject(error);
}
// get only the refresh transaction or the single transaction
// if there is no refresh
transactionsList = _.map(transactionsList, intermediateTransactionList => _.first(intermediateTransactionList));
// get all outputs from intermediate transactions
const intermediateInputList = _.map(transactionsList, intermediateTransaction => ({
'output_transaction_id' : intermediateTransaction.transaction_id,
'output_position' : 0,
'output_transaction_date': intermediateTransaction.transaction_date,
'output_shard_id' : intermediateTransaction.shard_id,
..._.pick(intermediateTransaction.transaction_output_list[0], [
'address_base',
'address_version',
'address_key_identifier',
'amount'
])
}));
// generate the transaction that aggregates all other
// intermediate transactions.
if (_.isEmpty(feeOutputList)) {
return reject('transaction_invalid_fee_output');
}
feeOutputList[0].amount = transactionsList.length * config.TRANSACTION_FEE_DEFAULT;
const outputList = this.splitOutputAmount(intermediateInputList, feeOutputList, Math.max(maxNumberOfOutputs - remainingInputs, 1));
this.signTransaction(intermediateInputList, outputList, feeOutputList, addressAttributeMap, privateKeyMap, transactionDate, transactionVersion)
.then(([transaction]) => resolve([
...transactionsList,
transaction
]))
.catch(reject);
});
});
}
} }
......
This diff is collapsed.
...@@ -125,7 +125,7 @@ db.initialize() ...@@ -125,7 +125,7 @@ db.initialize()
}); });
} }
}); });
//millix v1.15.3-tangled //millix v1.16.0-tangled
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment