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

Rewrite getRandomOuts #13

Merged
merged 11 commits into from
Feb 15, 2022
28 changes: 24 additions & 4 deletions src/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -903,14 +903,19 @@ body{
font-weight: 400;
margin-bottom:5px;
}
.field input, .field .subfield, .field select{
.field input, .field select{
border: 1px solid #D9DCE7;
border-radius: 5px;
font-size: 14px;
padding: 17px 16px 15px;
width: 100%;
color: rgba(71, 78, 93, 0.7);
background: #FFFFFF;
position: relative;
}

.field .subfield{
position: relative;
}

.field input:read-only, .field input.readonly, .field .subfield.readonly{
Expand All @@ -925,21 +930,36 @@ body{
padding: 0;
}
.field .subfield input{
width: calc(100% - 41px);
/*width: calc(100% - 41px);
border:none;
padding: 5px;
display: inline-block;*/
display: inline-block;
border: 1px solid #D9DCE7;
border-radius: 5px;
font-size: 14px;
padding: 17px 16px 15px;
width: 100%;
color: rgba(71, 78, 93, 0.7);
background: #FFFFFF;
}
.field .subfield input.twoActions {
width: calc(100% - 82px);
}

.field .subfield .action{
width: 20px;
/*width: 20px;
border-left:1px solid #D9DCE7;
padding: 18px 8px;
display: inline-block;
height: 100%;
height: 100%;*/
border-left: 1px solid #D9DCE7;
padding: 18px 8px;
display: block;
height: auto;
position: absolute;
top: 0;
right: 0;
}

.field.checkbox input{
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ global.config = {
coinFee: new JSBigInt('10000000000'),
feePerKB: new JSBigInt('10000000000'), //for testnet its not used, as fee is dynamic.
dustThreshold: new JSBigInt('100000000'),//used for choosing outputs/change - we decompose all the way down if the receiver wants now regardless of threshold
defaultMixin: 0, // default value mixin
defaultMixin: 3, // default value mixin

idleTimeout: 30,
idleWarningDuration: 20,
Expand Down
22 changes: 12 additions & 10 deletions src/model/Cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2161,7 +2161,7 @@ export namespace CnTransactions{
tx_pub_key:string,
}[],
mix_outs:{
outputs:{
outs:{
public_key:string,
global_index:number
}[],
Expand All @@ -2183,7 +2183,7 @@ export namespace CnTransactions{
throw 'Wrong number of mix outs provided (' + outputs.length + ' outputs, ' + mix_outs.length + ' mix outs)';
}
for (i = 0; i < mix_outs.length; i++) {
if ((mix_outs[i].outputs || []).length < fake_outputs_count) {
if ((mix_outs[i].outs || []).length < fake_outputs_count) {
throw 'Not enough outputs to mix with';
}
}
Expand Down Expand Up @@ -2230,19 +2230,21 @@ export namespace CnTransactions{
}
};
src.amount = new JSBigInt(outputs[i].amount).toString();
if (mix_outs.length !== 0) {
if (mix_outs.length !== 0) { // if mixin
// Sort fake outputs by global index
console.log('mix outs before sort',mix_outs[i].outputs);
mix_outs[i].outputs.sort(function(a, b) {
console.log('mix outs before sort',mix_outs[i].outs);
mix_outs[i].outs.sort(function(a, b) {
return new JSBigInt(a.global_index).compare(b.global_index);
});
j = 0;

console.log('mix outs sorted',mix_outs[i].outputs);
console.log('mix outs sorted',mix_outs[i].outs);

while ((src.outputs.length < fake_outputs_count) && (j < mix_outs[i].outputs.length)) {
let out = mix_outs[i].outputs[j];
console.log('chekcing mixin',out, outputs[i]);
while ((src.outputs.length < fake_outputs_count) && (j < mix_outs[i].outs.length)) {
let out = mix_outs[i].outs[j];
console.log('chekcing mixin');
console.log("out: ", out);
console.log("output ", i, ": ", outputs[i]);
if (out.global_index === outputs[i].global_index) {
console.log('got mixin the same as output, skipping');
j++;
Expand All @@ -2267,7 +2269,7 @@ export namespace CnTransactions{
src.outputs.push(oe);
j++;
}
}
} // end of if mixin
let real_oe = {
index:new JSBigInt(outputs[i].global_index || 0).toString(),
key:outputs[i].public_key,
Expand Down
49 changes: 14 additions & 35 deletions src/model/TransactionsExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {Transaction, TransactionIn, TransactionOut} from "./Transaction";
import {Wallet} from "./Wallet";
import {MathUtil} from "./MathUtil";
import {Cn, CnNativeBride, CnRandom, CnTransactions, CnUtils} from "./Cn";
import {RawDaemon_Transaction} from "./blockchain/BlockchainExplorer";
import {RawDaemon_Transaction, RawDaemon_Out} from "./blockchain/BlockchainExplorer";
import hextobin = CnUtils.hextobin;

export const TX_EXTRA_PADDING_MAX_COUNT = 255;
Expand Down Expand Up @@ -470,7 +470,7 @@ export class TransactionsExplorer {
userPaymentId: string = '',
wallet: Wallet,
blockchainHeight: number,
obtainMixOutsCallback: (quantity: number) => Promise<any[]>,
obtainMixOutsCallback: (amounts: number[], numberOuts: number) => Promise<RawDaemon_Out[]>,
confirmCallback: (amount: number, feesAmount: number) => Promise<void>,
mixin: number = config.defaultMixin):
Promise<{ raw: { hash: string, prvkey: string, raw: string }, signed: any }> {
Expand Down Expand Up @@ -559,17 +559,8 @@ export class TransactionsExplorer {
//console.log("Using output: " + out.amount + " - " + JSON.stringify(out));
}

const calculateFeeWithBytes = function (fee_per_kb: number, bytes: number, fee_multiplier: number) {
let kB = (bytes + 1023) / 1024;
return kB * fee_per_kb * fee_multiplier;
};

console.log("Selected outs:", usingOuts);

if (neededFee < 10000000) {
neededFee = 10000000;
}

console.log('using amount of ' + usingOuts_amount + ' for sending ' + totalAmountWithoutFee + ' with fees of ' + (neededFee / Math.pow(10, config.coinUnitPlaces)) + ' KRB');
confirmCallback(totalAmountWithoutFee, neededFee).then(function () {
if (usingOuts_amount.compare(totalAmount) < 0) {
Expand All @@ -584,49 +575,37 @@ export class TransactionsExplorer {
let changeAmount = usingOuts_amount.subtract(totalAmount);
//add entire change for rct
console.log("1) Sending change of " + Cn.formatMoneySymbol(changeAmount)
+ " to " /*+ AccountService.getAddress()*/);
+ " to " + wallet.getPublicAddress());
dsts.push({
address: wallet.getPublicAddress(),
amount: changeAmount
});
} else if (usingOuts_amount.compare(totalAmount) === 0) {
} /*
// not applicable for Karbo
else if (usingOuts_amount.compare(totalAmount) === 0) {
//create random destination to keep 2 outputs always in case of 0 change
let fakeAddress = Cn.create_address(CnRandom.random_scalar()).public_addr;
console.log("Sending 0 KRB to a fake address to keep tx uniform (no change exists): " + fakeAddress);
dsts.push({
address: fakeAddress,
amount: 0
});
}
}*/
console.log('destinations', dsts);

let amounts: string[] = [];
let amounts: number[] = [];
for (let l = 0; l < usingOuts.length; l++) {
amounts.push(usingOuts[l].amount.toString());
amounts.push(usingOuts[l].amount);
}

obtainMixOutsCallback(amounts.length * (mixin + 1)).then(function (lotsMixOuts: any[]) {
console.log('------------------------------mix_outs', lotsMixOuts);
let nbOutsNeeded: number = mixin + 1;

obtainMixOutsCallback(amounts, nbOutsNeeded).then(function (lotsMixOuts: any[]) {
console.log('------------------------------mix_outs');
console.log('amounts', amounts);
console.log('lots_mix_outs', lotsMixOuts);

let mix_outs = [];
let iMixOutsIndexes = 0;
for (let amount of amounts) {
let localMixOuts = [];
for (let i = 0; i < mixin + 1; ++i) {
localMixOuts.push(lotsMixOuts[iMixOutsIndexes]);
++iMixOutsIndexes;
}
localMixOuts.sort().reverse();
mix_outs.push({
outputs: localMixOuts.slice(),
amount: 0
});
}
console.log('mix_outs', mix_outs);

TransactionsExplorer.createRawTx(dsts, wallet, false, usingOuts, pid_encrypt, mix_outs, mixin, neededFee, paymentId).then(function (data: { raw: { hash: string, prvkey: string, raw: string }, signed: any }) {
TransactionsExplorer.createRawTx(dsts, wallet, false, usingOuts, pid_encrypt, lotsMixOuts, mixin, neededFee, paymentId).then(function (data: { raw: { hash: string, prvkey: string, raw: string }, signed: any }) {
resolve(data);
}).catch(function (e) {
reject(e);
Expand Down
7 changes: 6 additions & 1 deletion src/model/blockchain/BlockchainExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export type RemoteNodeInformation = {
status: string
};

export type RawDaemon_Out = {
global_index: number,
public_key: string
}

export interface BlockchainExplorer {
resolveOpenAlias(str: string): Promise<{ address: string, name: string | null }>;

Expand All @@ -66,7 +71,7 @@ export interface BlockchainExplorer {

sendRawTx(rawTx: string): Promise<any>;

getRandomOuts(numberOuts: number): Promise<any[]>;
getRandomOuts(amounts: number[], nbOutsNeeded: number): Promise<RawDaemon_Out[]>;

getNetworkInfo(): Promise<NetworkInfo>;

Expand Down
110 changes: 14 additions & 96 deletions src/model/blockchain/BlockchainExplorerRPCDaemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import {BlockchainExplorer, NetworkInfo, RawDaemon_Transaction, RemoteNodeInformation} from "./BlockchainExplorer";
import {BlockchainExplorer, NetworkInfo, RawDaemon_Transaction, RawDaemon_Out, RemoteNodeInformation} from "./BlockchainExplorer";
import {Wallet} from "../Wallet";
import {MathUtil} from "../MathUtil";
import {CnTransactions, CnUtils} from "../Cn";
Expand Down Expand Up @@ -247,103 +247,21 @@ export class BlockchainExplorerRpcDaemon implements BlockchainExplorer {
});
}

existingOuts: any[] = [];

getRandomOuts(nbOutsNeeded: number, initialCall = true): Promise<any[]> {
let self = this;
if (initialCall) {
self.existingOuts = [];
}

return this.getHeight().then(function (height: number) {
let txs: RawDaemon_Transaction[] = [];
let promiseGetCompressedBlocks: Promise<void> = Promise.resolve();

let randomBlocksIndexesToGet: number[] = [];
let numOuts = height;

let compressedBlocksToGet: { [key: string]: boolean } = {};

console.log('Requires ' + nbOutsNeeded + ' outs');

//select blocks for the final mixin. selection is made with a triangular selection
for (let i = 0; i < nbOutsNeeded; ++i) {
let selectedIndex: number = -1;
do {
selectedIndex = MathUtil.randomTriangularSimplified(numOuts);
if (selectedIndex >= height - config.txCoinbaseMinConfirms)
selectedIndex = -1;
} while (selectedIndex === -1 || randomBlocksIndexesToGet.indexOf(selectedIndex) !== -1);
randomBlocksIndexesToGet.push(selectedIndex);

compressedBlocksToGet[Math.floor(selectedIndex / 100) * 100] = true;
}

console.log('Random blocks required: ', randomBlocksIndexesToGet);
console.log('Blocks to get for outputs selections:', compressedBlocksToGet);

//load compressed blocks (100 blocks) containing the blocks referred by their index
for (let compressedBlock in compressedBlocksToGet) {
promiseGetCompressedBlocks = promiseGetCompressedBlocks.then(() => {
return self.getTransactionsForBlocks(parseInt(compressedBlock), Math.min(parseInt(compressedBlock) + 99, height - config.txCoinbaseMinConfirms), false).then(function (rawTransactions: RawDaemon_Transaction[]) {
txs.push.apply(txs, rawTransactions);
});
});
getRandomOuts(amounts: number[], nbOutsNeeded: number): Promise<RawDaemon_Out[]> {
return this.makeRequest('POST', 'getrandom_outs', {
amounts: amounts,
outs_count: nbOutsNeeded
}).then((response: {
status: 'OK' | 'string',
outs: { global_index: number, public_key: string }[]
}) => {
if (response.status !== 'OK') throw 'invalid_getrandom_outs_answer';
if (response.outs.length > 0) {
console.log("Got random outs: ");
console.log(response.outs);
}

return promiseGetCompressedBlocks.then(function () {
console.log('txs selected for outputs: ', txs);
let txCandidates: any = {};
for (let iOut = 0; iOut < txs.length; ++iOut) {
let tx = txs[iOut];

if (
(typeof tx.height !== 'undefined' && randomBlocksIndexesToGet.indexOf(tx.height) === -1) ||
typeof tx.height === 'undefined'
) {
continue;
}

for (let output_idx_in_tx = 0; output_idx_in_tx < tx.vout.length; ++output_idx_in_tx) {
let rct = null;
let globalIndex = output_idx_in_tx;
if (typeof tx.global_index_start !== 'undefined' && typeof tx.output_indexes !== 'undefined') {
globalIndex = tx.output_indexes[output_idx_in_tx];
}
if (tx.vout[output_idx_in_tx].amount !== 0) {//check if miner tx
rct = CnTransactions.zeroCommit(CnUtils.d2s(tx.vout[output_idx_in_tx].amount));
} else {
let rtcOutPk = tx.rct_signatures.outPk[output_idx_in_tx];
let rtcMask = tx.rct_signatures.ecdhInfo[output_idx_in_tx].mask;
let rtcAmount = tx.rct_signatures.ecdhInfo[output_idx_in_tx].amount;
rct = rtcOutPk + rtcMask + rtcAmount;
}

let newOut = {
rct: rct,
public_key: tx.vout[output_idx_in_tx].target.data.key,
global_index: globalIndex,
// global_index: count,
};
if (typeof txCandidates[tx.height] === 'undefined') txCandidates[tx.height] = [];
txCandidates[tx.height].push(newOut);

}
}

console.log(txCandidates);

let selectedOuts = [];
for (let txsOutsHeight in txCandidates) {
let outIndexSelect = MathUtil.getRandomInt(0, txCandidates[txsOutsHeight].length - 1);
console.log('select ' + outIndexSelect + ' for ' + txsOutsHeight + ' with length of ' + txCandidates[txsOutsHeight].length);
selectedOuts.push(txCandidates[txsOutsHeight][outIndexSelect]);
}

console.log(selectedOuts);

return selectedOuts;
});
return response.outs;
});
}

Expand Down
9 changes: 9 additions & 0 deletions src/pages/send.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ <h2 class="text" >{{ $t("sendPage.qrCodeScanning.explication") }}</h2>
</div>
</div>

<div class="field" >
<label>{{ $t("sendPage.sendBlock.mixin.label") }}</label>
<input class="amountInput" type="number" min="0" max="10" v-model="mixIn" placeholder="3"
@change="() => { if(this.mixIn > 10 || this.mixIn < 3 || Number.isNaN(parseInt(this.mixIn))) { this.mixIn = 0 }}" :readonly="lockedForm"/>
<div v-if="!mixinIsValid && mixIn != ''" class="message error">
{{ $t("sendPage.sendBlock.mixin.invalid") }}
</div>
</div>

<div class="actions tc">
<button type="button" class="btn primary" @click="send()" :disabled="!destinationAddressValid || !amountToSendValid" >{{ $t("sendPage.sendBlock.sendButton") }}</button>
</div>
Expand Down
Loading