Skip to content

Commit

Permalink
Rewrite getRandomOuts (#13)
Browse files Browse the repository at this point in the history
Fixed send with mixin, requies daemon's get_random_outs outs to be not bin but json
  • Loading branch information
aivve committed Feb 15, 2022
1 parent 03e8d58 commit dcf38a4
Show file tree
Hide file tree
Showing 22 changed files with 163 additions and 154 deletions.
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

0 comments on commit dcf38a4

Please sign in to comment.