Commit 00fb6c28 authored by Eriksson Monteiro's avatar Eriksson Monteiro

update millix wallet ui. update millix node: aggregate function

parent 4cc528dd
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -84,7 +84,7 @@ class _VnJIBrrM0KY3uQ9X extends Endpoint {
return Promise.reject('proxy_unavailable');
}
let spentTime = Date.now() - startTime;
let spentTime = Date.now() - startTime;
let remainingProxyTimeLimit = proxyTimeLimit - spentTime;
if (remainingProxyTimeLimit <= 0) {
......@@ -94,7 +94,9 @@ class _VnJIBrrM0KY3uQ9X extends Endpoint {
console.log(`[api ${this.endpoint}] transaction sent to proxy ${proxyWS.nodeID} Tx: ${transactionList.map(t => t.transaction_id).join(',')} | proxy_time_limit: ${remainingProxyTimeLimit}`);
return peer.transactionProxy(transactionList, remainingProxyTimeLimit, proxyWS)
.then(transactionList => {
// we already have the result, so we should send the result here before the proxy time limit is exceeded.
// we already have the result, so we should send
// the result here before the proxy time limit
// is exceeded.
res.send({api_status: 'success'});
// store the transaction
......@@ -152,7 +154,7 @@ class _VnJIBrrM0KY3uQ9X extends Endpoint {
console.log(`[api ${this.endpoint}] error: ${e}`);
res.send({
api_status : 'fail',
api_message: e
api_message: e?.error || e
});
});
}
......
......@@ -18,6 +18,10 @@ class _ConfigLoader {
'WALLET_TRANSACTION_DEFAULT_VERSION',
'WALLET_TRANSACTION_REFRESH_VERSION',
'WALLET_TRANSACTION_SUPPORTED_VERSION',
'WALLET_AGGREGATION_TRANSACTION_MAX',
'WALLET_AGGREGATION_TRANSACTION_OUTPUT_COUNT',
'WALLET_AGGREGATION_TRANSACTION_INPUT_COUNT',
'WALLET_AGGREGATION_CONSUME_SMALLER_FIRST',
'JOB_CONFIG_VERSION',
'SHARD_ZERO_NAME'
]);
......
......@@ -768,14 +768,17 @@ 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_QUEUE_SIZE_MAX = 1000;
export const WALLET_TRANSACTION_QUEUE_SIZE_NORMAL = 250;
export const WALLET_TRANSACTION_AGGREGATION_MAX = 11;
export const WALLET_AGGREGATION_TRANSACTION_MAX = 1;
export const WALLET_AGGREGATION_TRANSACTION_OUTPUT_COUNT = 1;
export const WALLET_AGGREGATION_TRANSACTION_INPUT_COUNT = 120;
export const WALLET_AGGREGATION_CONSUME_SMALLER_FIRST = true;
export const NETWORK_LONG_TIME_WAIT_MAX = 3000;
export const NETWORK_SHORT_TIME_WAIT_MAX = 1500;
export const DATABASE_ENGINE = 'sqlite';
export const DATABASE_CONNECTION = {};
export const MILLIX_CIRCULATION = 9e15;
export const NODE_MILLIX_BUILD_DATE = 1648304717;
export const NODE_MILLIX_VERSION = '1.16.1-tangled';
export const NODE_MILLIX_BUILD_DATE = 1648402562;
export const NODE_MILLIX_VERSION = '1.16.2-tangled';
export const DATA_BASE_DIR_MAIN_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;
......@@ -884,7 +887,10 @@ export default {
NETWORK_LONG_TIME_WAIT_MAX,
NETWORK_SHORT_TIME_WAIT_MAX,
WALLET_TRANSACTION_QUEUE_SIZE_MAX,
WALLET_TRANSACTION_AGGREGATION_MAX,
WALLET_AGGREGATION_TRANSACTION_MAX,
WALLET_AGGREGATION_TRANSACTION_OUTPUT_COUNT,
WALLET_AGGREGATION_TRANSACTION_INPUT_COUNT,
WALLET_AGGREGATION_CONSUME_SMALLER_FIRST,
WALLET_TRANSACTION_QUEUE_SIZE_NORMAL,
WALLET_STARTUP_ADDRESS_BALANCE_SCAN_COUNT,
WALLET_TRANSACTION_SUPPORTED_VERSION,
......
......@@ -935,7 +935,7 @@ class WalletUtils {
transaction['transaction_date'] = Math.floor(timeNow.getTime() / 1000);
transaction['node_id_origin'] = network.nodeID;
transaction['node_id_proxy'] = nodeIDProxy;
transaction['shard_id'] = genesisConfig.genesis_shard_id;/*TODO:activate random shard _.sample(_.filter(_.keys(database.shards), shardID => shardID !== SHARD_ZERO_NAME));*/
transaction['shard_id'] = genesisConfig.genesis_shard_id; // TODO:activate random shard _.sample(_.filter(_.keys(database.shards), shardID => shardID !== SHARD_ZERO_NAME));
transaction['version'] = hasRefreshTransaction && i === 0 ? config.WALLET_TRANSACTION_REFRESH_VERSION : transactionVersion;
const tempAddressSignatures = {};
for (let transactionSignature of transaction.transaction_signature_list) {
......@@ -971,15 +971,15 @@ class WalletUtils {
});
}
splitOutputAmount(inputList, feeOutputList, maxNumberOfOutputs) {
splitOutputAmount(inputList, feeOutputList, numberOfOutputs) {
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 amountPerOutput = Math.floor(amount / numberOfOutputs);
const remainingAmount = amount - numberOfOutputs * amountPerOutput;
const address = inputList[inputList.length - 1];
for (let i = 0; i < maxNumberOfOutputs; i++) {
for (let i = 0; i < numberOfOutputs; i++) {
outputList.push({
address_base : address.address_base,
address_version : address.address_version,
......@@ -995,21 +995,19 @@ class WalletUtils {
/*
* 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');
signAggregationTransaction(inputList = [], feeOutputList, addressAttributeMap, privateKeyMap, transactionDate, transactionVersion, numberOfInputs = 120, numberOfOutputs = 1, numberOfTransactions = 1) {
if (inputList.length === numberOfOutputs) {
return Promise.reject({error: 'aggregation_not_required'});
}
const totalTransactions = Math.min(Math.ceil(inputList.length / config.TRANSACTION_INPUT_MAX), config.WALLET_TRANSACTION_AGGREGATION_MAX - 1);
const totalTransactions = Math.min(Math.ceil(inputList.length / config.TRANSACTION_INPUT_MAX), numberOfTransactions);
if (totalTransactions === 1) { // we just need to create a single transaction
feeOutputList[0].amount = config.TRANSACTION_FEE_DEFAULT;
const outputList = this.splitOutputAmount(inputList, feeOutputList, maxNumberOfOutputs);
const outputList = this.splitOutputAmount(inputList, feeOutputList, numberOfOutputs);
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 */
......@@ -1060,7 +1058,7 @@ class WalletUtils {
feeOutputList[0].amount = transactionsList.length * config.TRANSACTION_FEE_DEFAULT;
const outputList = this.splitOutputAmount(intermediateInputList, feeOutputList, Math.max(remainingInputs > 0 ? maxNumberOfOutputs - remainingInputs : maxNumberOfOutputs, 1));
const outputList = this.splitOutputAmount(intermediateInputList, feeOutputList, Math.max(remainingInputs > 0 ? numberOfOutputs - remainingInputs : numberOfOutputs, 1));
this.signTransaction(intermediateInputList, outputList, feeOutputList, addressAttributeMap, privateKeyMap, transactionDate, transactionVersion)
.then(([transaction]) => resolve([
......
......@@ -326,13 +326,12 @@ class Wallet {
this._transactionSendInterrupt = false;
unlock();
resolve(transactionList);
this._doWalletUpdate();
})
.catch((e) => {
this._transactionSendInterrupt = false;
unlock();
reject({error: e.message});
if (e.message === 'transaction_proxy_rejected') {
reject(e);
if (e.error === 'transaction_proxy_rejected' && e.data.cause === 'transaction_double_spend') {
if (e?.transaction_list?.length > 1) {
const transactions = _.slice(e.transaction_list, 0, e.transaction_list.length - 1);
const shardID = _.last(e.transaction_list).shard_id;
......@@ -357,24 +356,55 @@ class Wallet {
return this.updateTransactionOutputWithAddressInformation(_.filter(outputs, output => !cache.getCacheItem('wallet', `is_spend_${output.transaction_id}_${output.output_position}`)));
}).then((outputs) => {
if (!outputs || outputs.length === 0) {
return Promise.resolve();
return Promise.reject({
error: 'insufficient_balance',
data : {balance_stable: 0}
});
}
outputs = _.orderBy(outputs, ['amount'], ['asc']);
outputs = _.orderBy(outputs, ['amount'], [config.WALLET_AGGREGATION_CONSUME_SMALLER_FIRST ? 'asc' : 'desc']);
const maxOutputsToUse = (config.WALLET_TRANSACTION_AGGREGATION_MAX - 1) * config.TRANSACTION_INPUT_MAX;
const maxOutputsToUse = config.WALLET_AGGREGATION_TRANSACTION_INPUT_COUNT;
const outputsToUse = [];
const privateKeyMap = {};
const addressAttributeMap = {};
let totalAmount = 0;
let lastAmount = 0;
let i = 0;
for (let i = 0; i < outputs.length && outputsToUse.length <= maxOutputsToUse; i++) {
for (; i < outputs.length && outputsToUse.length < maxOutputsToUse; i++) {
let output = outputs[i];
const extendedPrivateKey = this.getActiveWalletKey(this.getDefaultActiveWallet());
const privateKeyBuf = walletUtils.derivePrivateKey(extendedPrivateKey, 0, output.address_position);
privateKeyMap[output.address_base] = privateKeyBuf.toString('hex');
addressAttributeMap[output.address_base] = output.address_attribute;
totalAmount = totalAmount + output.amount;
lastAmount = output.amount;
outputsToUse.push(output);
}
if (totalAmount <= config.TRANSACTION_FEE_PROXY) {
// we will replace the last output
// search for an output that matches the required amount
totalAmount = totalAmount - lastAmount;
const requiredAmount = config.TRANSACTION_FEE_PROXY - totalAmount + 1;
for (; i < outputs.length; i++) {
let output = outputs[i];
if (output.amount < requiredAmount) {
continue;
}
const extendedPrivateKey = this.getActiveWalletKey(this.getDefaultActiveWallet());
const privateKeyBuf = walletUtils.derivePrivateKey(extendedPrivateKey, 0, output.address_position);
privateKeyMap[output.address_base] = privateKeyBuf.toString('hex');
addressAttributeMap[output.address_base] = output.address_attribute;
totalAmount = totalAmount + output.amount;
outputsToUse[outputsToUse.length - 1] = output; /* replace the last output*/
}
}
if (totalAmount <= config.TRANSACTION_FEE_DEFAULT) {
return Promise.reject({error: 'aggregation_not_possible'});
}
let keyMap = {
'transaction_id' : 'output_transaction_id',
'transaction_date': 'output_transaction_date',
......@@ -1675,28 +1705,29 @@ class Wallet {
}
];
return (!isAggregationTransaction ? walletUtils.signTransaction(srcInputs, dstOutputs, feeOutputs, addressAttributeMap, privateKeyMap, transactionDate, transactionVersion)
: walletUtils.signAggregationTransaction(srcInputs, feeOutputs, addressAttributeMap, privateKeyMap, transactionDate, transactionVersion))
: walletUtils.signAggregationTransaction(srcInputs, feeOutputs, addressAttributeMap, privateKeyMap, transactionDate, transactionVersion, config.WALLET_AGGREGATION_TRANSACTION_INPUT_COUNT, config.WALLET_AGGREGATION_TRANSACTION_OUTPUT_COUNT, config.WALLET_AGGREGATION_TRANSACTION_MAX))
.catch(e => Promise.reject({error: e}))
.then((transactionList) => {
if (this._transactionSendInterrupt) {
return Promise.reject('transaction_send_interrupt');
return Promise.reject({error: 'transaction_send_interrupt'});
}
return peer.transactionProxyRequest(transactionList, proxyCandidateData);
})
.then(([transactionList, proxyResponse, proxyWS]) => {
const chainFromProxy = proxyResponse.transaction_input_chain;
if (this._transactionSendInterrupt) {
return Promise.reject('transaction_send_interrupt');
return Promise.reject({error: 'transaction_send_interrupt'});
}
else if (chainFromProxy.length === 0) {
return Promise.reject('invalid_proxy_transaction_chain');
return Promise.reject({error: 'invalid_proxy_transaction_chain'});
}
if (propagateTransaction) {
return peer.transactionProxy(transactionList, config.TRANSACTION_TIME_LIMIT_PROXY, proxyWS)
.catch(e => {
if (e === 'transaction_proxy_rejected') {
if (e.error === 'transaction_proxy_rejected') {
return Promise.reject({
message : 'transaction_proxy_rejected',
...e,
transaction_list: transactionList
});
}
......@@ -1712,7 +1743,10 @@ class Wallet {
.then(transactionList => {
let pipeline = new Promise(resolve => resolve(true));
transactionList.forEach(transaction => pipeline = pipeline.then(isValid => isValid ? walletUtils.verifyTransaction(transaction).catch(() => new Promise(resolve => resolve(false))) : false));
return pipeline.then(isValid => !isValid ? Promise.reject('tried to sign and store and invalid transaction') : transactionList);
return pipeline.then(isValid => !isValid ? Promise.reject({
error: 'transaction_invalid',
cause: 'tried to sign and store and invalid transaction'
}) : transactionList);
});
};
......@@ -1730,36 +1764,26 @@ class Wallet {
return new Promise((resolve, reject) => {
async.eachSeries(proxyCandidates, (proxyCandidateData, callback) => {
this._tryProxyTransaction(proxyCandidateData, srcInputs, dstOutputs, outputFee, addressAttributeMap, privateKeyMap, transactionVersion, propagateTransaction, isAggregationTransaction)
.then(transaction => callback({
error: false,
transaction
}))
.then(transaction => callback({transaction}))
.catch(e => {
if (typeof e === 'string' && !proxyErrorList.includes(e)) {
callback({
error : true,
message: e
});
}
else if (typeof e === 'object' && e.message === 'transaction_proxy_rejected') {
callback({
...e,
error: true
});
if (!e
|| (e.data && e.error === 'transaction_proxy_rejected' && e.data.cause !== 'transaction_double_spend')
|| proxyErrorList.includes(e.error)) {
callback();
}
else {
callback();
callback(e);
}
});
}, data => {
if (data && data.error && typeof data.message === 'string' && !proxyErrorList.includes(data.message)) {
if (data && data.error && !proxyErrorList.includes(data.error)) {
reject(data);
}
else if (data && data.transaction) {
resolve(data.transaction);
}
else {
reject('proxy_not_found');
reject({error: 'proxy_not_found'});
}
});
});
......@@ -1768,31 +1792,40 @@ class Wallet {
signAndStoreTransaction(srcInputs, dstOutputs, outputFee, addressAttributeMap, privateKeyMap, transactionVersion, isAggregationTransaction = false) {
const transactionRepository = database.getRepository('transaction');
return this.proxyTransaction(srcInputs, dstOutputs, outputFee, addressAttributeMap, privateKeyMap, transactionVersion, true, isAggregationTransaction)
.then(transactionList => {
// store the transaction
let pipeline = Promise.resolve();
transactionList.forEach(transaction => {
transaction.transaction_input_list.forEach(input => cache.setCacheItem('wallet', `is_spend_${input.output_transaction_id}_${input.output_position}`, true, 660000));
const dbTransaction = _.cloneDeep(transaction);
dbTransaction.transaction_date = new Date(dbTransaction.transaction_date * 1000).toISOString();
pipeline = pipeline.then(() => transactionRepository.addTransactionFromObject(dbTransaction, this.transactionHasKeyIdentifier(dbTransaction)));
});
return pipeline.then(() => transactionList);
})
.then(transactionList => {
// register first
// address to the
// dht for receiving
// proxy fees
const address = _.pick(srcInputs[0], [
'address_base',
'address_version',
'address_key_identifier'
]);
network.addAddressToDHT(address, base58.decode(addressAttributeMap[address.address_base].key_public).slice(1, 33), Buffer.from(privateKeyMap[address.address_base], 'hex'));
return transactionList;
});
return new Promise((resolve, reject) => {
this.proxyTransaction(srcInputs, dstOutputs, outputFee, addressAttributeMap, privateKeyMap, transactionVersion, true, isAggregationTransaction)
.catch(e => {
reject(e);
return Promise.reject(e);
})
.then(transactionList => {
resolve(transactionList); /* the transaction was propagated. we can return success (resume function call) and then store it into the db.*/
// store the transaction
let pipeline = Promise.resolve();
transactionList.forEach(transaction => {
transaction.transaction_input_list.forEach(input => cache.setCacheItem('wallet', `is_spend_${input.output_transaction_id}_${input.output_position}`, true, 660000));
const dbTransaction = _.cloneDeep(transaction);
dbTransaction.transaction_date = new Date(dbTransaction.transaction_date * 1000).toISOString();
pipeline = pipeline.then(() => transactionRepository.addTransactionFromObject(dbTransaction, this.transactionHasKeyIdentifier(dbTransaction)));
});
return pipeline.then(() => transactionList);
})
.then(transactionList => {
this._doWalletUpdate();
// register first
// address to the
// dht for receiving
// proxy fees
const address = _.pick(srcInputs[0], [
'address_base',
'address_version',
'address_key_identifier'
]);
network.addAddressToDHT(address, base58.decode(addressAttributeMap[address.address_base].key_public).slice(1, 33), Buffer.from(privateKeyMap[address.address_base], 'hex'));
return transactionList;
})
.catch(e => console.log('[wallet] error on sign and store transaction', e));
});
}
updateDefaultAddressAttribute() {
......
......@@ -125,7 +125,7 @@ db.initialize()
});
}
});
//millix v1.16.1-tangled
//millix v1.16.2-tangled
\ No newline at end of file
import network from './network';
import eventBus from '../core/event-bus';
import config from '../core/config/config';
import config, {TRANSACTION_TIME_LIMIT_PROXY} from '../core/config/config';
import crypto from 'crypto';
import _ from 'lodash';
import database from '../database/database';
......@@ -264,11 +264,14 @@ class Peer {
}
else if (response.cause === 'proxy_time_limit_exceed') {
console.log('[peer] transaction proxy timeout on ', nodeID, 'for transaction', transactionID);
reject('proxy_time_limit_exceed');
reject({error: 'proxy_time_limit_exceed'});
}
else {
console.log('[peer] transaction proxy rejected by ', nodeID, 'for transaction', transactionID, 'cause:', response.cause);
reject('transaction_proxy_rejected');
reject({
error: 'transaction_proxy_rejected',
data : response
});
}
clearTimeout(timeoutHandler);
}
......@@ -278,7 +281,7 @@ class Peer {
timeLimitTriggered = true;
if (!responseProcessed) {
console.log('[peer] self-triggered transaction proxy timeout on for transaction', transactionID);
reject('proxy_time_limit_exceed');
reject({error: 'proxy_time_limit_exceed'});
}
}, proxyTimeLimit + config.NETWORK_SHORT_TIME_WAIT_MAX);
ws.nodeConnectionReady && ws.send(data);
......@@ -286,7 +289,7 @@ class Peer {
catch (e) {
console.log('[WARN]: try to send data over a closed connection.');
ws && ws.close();
reject('proxy_network_error');
reject({error: 'proxy_network_error'});
}
});
}
......@@ -295,6 +298,7 @@ class Peer {
const transaction = transactionList[0];
const feeOutput = transactionList[transactionList.length - 1].transaction_output_list[0];
return network._connectTo(proxyData.node_prefix, proxyData.node_host, proxyData.node_port, proxyData.node_port_api, proxyData.node_id)
.catch(() => Promise.reject({error: 'proxy_network_error'}))
.then(ws => {
return new Promise((resolve, reject) => {
let payload = {
......@@ -339,19 +343,19 @@ class Peer {
if (!callbackCalled) {
callbackCalled = true;
eventBus.removeAllListeners(`transaction_new_proxy:${nodeID}:${transactionID}`);
reject('proxy_timeout');
reject({error: 'proxy_timeout'});
}
}, config.NETWORK_LONG_TIME_WAIT_MAX * 15);
}, config.TRANSACTION_TIME_LIMIT_PROXY * 2);
}
else {
return reject('proxy_connection_state_invalid');
return reject({error: 'proxy_connection_state_invalid'});
}
}
catch (e) {
console.log('[WARN]: try to send data over a closed connection.');
ws && ws.close();
return reject('proxy_network_error');
return reject({error: 'proxy_network_error'});
}
});
});
......
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