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

Try handle fragmented utxos #104

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions helios-internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,7 @@ declare module "helios" {
VALIDITY_RANGE_END_OFFSET?: number | undefined;
IGNORE_UNEVALUATED_CONSTANTS?: boolean | undefined;
CHECK_CASTS?: boolean | undefined;
MAX_ASSETS_PER_CHANGE_OUTPUT?: number | undefined;
}): void;
const DEBUG: boolean;
const STRICT_BABBAGE: boolean;
Expand All @@ -873,6 +874,7 @@ declare module "helios" {
const VALIDITY_RANGE_END_OFFSET: number;
const IGNORE_UNEVALUATED_CONSTANTS: boolean;
const CHECK_CASTS: boolean;
const MAX_ASSETS_PER_CHANGE_OUTPUT: undefined;
}
/**
* Read non-byte aligned numbers
Expand Down
8 changes: 8 additions & 0 deletions helios.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export namespace config {
* VALIDITY_RANGE_END_OFFSET?: number
* IGNORE_UNEVALUATED_CONSTANTS?: boolean
* CHECK_CASTS?: boolean
* MAX_ASSETS_PER_CHANGE_OUTPUT?: number
* }} props
*/
function set(props: {
Expand All @@ -135,6 +136,7 @@ export namespace config {
VALIDITY_RANGE_END_OFFSET?: number | undefined;
IGNORE_UNEVALUATED_CONSTANTS?: boolean | undefined;
CHECK_CASTS?: boolean | undefined;
MAX_ASSETS_PER_CHANGE_OUTPUT?: number | undefined;
}): void;
/**
* Global debug flag. Currently unused.
Expand Down Expand Up @@ -207,6 +209,12 @@ export namespace config {
* @type {boolean}
*/
const CHECK_CASTS: boolean;
/**
* Maximum number of assets per change output. Used to break up very large asset outputs into multiple outputs.
*
* Default: `undefined` (no limit).
*/
const MAX_ASSETS_PER_CHANGE_OUTPUT: undefined;
}
/**
* Function that generates a random number between 0 and 1
Expand Down
50 changes: 45 additions & 5 deletions helios.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export const config = {
* VALIDITY_RANGE_END_OFFSET?: number
* IGNORE_UNEVALUATED_CONSTANTS?: boolean
* CHECK_CASTS?: boolean
* MAX_ASSETS_PER_CHANGE_OUTPUT?: number
* }} props
*/
set: (props) => {
Expand Down Expand Up @@ -424,6 +425,13 @@ export const config = {
* @type {boolean}
*/
CHECK_CASTS: false,

/**
* Maximum number of assets per change output. Used to break up very large asset outputs into multiple outputs.
*
* Default: `undefined` (no limit).
*/
MAX_ASSETS_PER_CHANGE_OUTPUT: undefined,
}


Expand Down Expand Up @@ -44802,9 +44810,34 @@ export class Tx extends CborData {
} else {
const diff = inputAssets.sub(outputAssets);

const changeOutput = new TxOutput(changeAddress, new Value(0n, diff));
if (config.MAX_ASSETS_PER_CHANGE_OUTPUT) {
const maxAssetsPerOutput = config.MAX_ASSETS_PER_CHANGE_OUTPUT;

let changeAssets = new Assets();
let tokensAdded = 0;

diff.mintingPolicies.forEach((mph) => {
const tokens = diff.getTokens(mph);
tokens.forEach(([token, quantity], i) => {
changeAssets.addComponent(mph, token, quantity);
tokensAdded += 1;
if (tokensAdded == maxAssetsPerOutput) {
this.#body.addOutput(new TxOutput(changeAddress, new Value(0n, changeAssets)));
changeAssets = new Assets();
tokensAdded = 0;
}
});
});

this.#body.addOutput(changeOutput);
// If we are here and have No assets, they we're done
if (!changeAssets.isZero()) {
this.#body.addOutput(new TxOutput(changeAddress, new Value(0n, changeAssets)));
}
} else {
const changeOutput = new TxOutput(changeAddress, new Value(0n, diff));

this.#body.addOutput(changeOutput);
}
}
}

Expand Down Expand Up @@ -44956,7 +44989,7 @@ export class Tx extends CborData {
nonChangeOutputValue = feeValue.add(nonChangeOutputValue);

// this is quite restrictive, but we really don't want to touch UTxOs containing assets just for balancing purposes
const spareAssetUTxOs = spareUtxos.some(utxo => !utxo.value.assets.isZero());
const spareAssetUTxOs = spareUtxos.filter(utxo => !utxo.value.assets.isZero());
spareUtxos = spareUtxos.filter(utxo => utxo.value.assets.isZero());

// use some spareUtxos if the inputValue doesn't cover the outputs and fees
Expand All @@ -44966,8 +44999,15 @@ export class Tx extends CborData {
let spare = spareUtxos.pop();

if (spare === undefined) {
if (spareAssetUTxOs) {
throw new Error(`UTxOs too fragmented`);
if (spareAssetUTxOs.length > 0) {
spare = spareAssetUTxOs.pop();

if (!spare){
throw new Error(`UTxOs too fragmented - or no Spare UTxOs available to fix this mess`);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to change the Error - Sorry; this was me playing around!

}

this.#body.addInput(spare);
this.balanceAssets(changeAddress);
} else {
throw new Error(`need ${totalOutputValue.lovelace} lovelace, but only have ${inputValue.lovelace}`);
}
Expand Down
8 changes: 8 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const config = {
* VALIDITY_RANGE_END_OFFSET?: number
* IGNORE_UNEVALUATED_CONSTANTS?: boolean
* CHECK_CASTS?: boolean
* MAX_ASSETS_PER_CHANGE_OUTPUT?: number
* }} props
*/
set: (props) => {
Expand Down Expand Up @@ -122,4 +123,11 @@ export const config = {
* @type {boolean}
*/
CHECK_CASTS: false,

/**
* Maximum number of assets per change output. Used to break up very large asset outputs into multiple outputs.
*
* Default: `undefined` (no limit).
*/
MAX_ASSETS_PER_CHANGE_OUTPUT: undefined,
}
42 changes: 37 additions & 5 deletions src/tx-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -867,9 +867,34 @@ export class Tx extends CborData {
} else {
const diff = inputAssets.sub(outputAssets);

const changeOutput = new TxOutput(changeAddress, new Value(0n, diff));
if (config.MAX_ASSETS_PER_CHANGE_OUTPUT) {
const maxAssetsPerOutput = config.MAX_ASSETS_PER_CHANGE_OUTPUT;

let changeAssets = new Assets();
let tokensAdded = 0;

diff.mintingPolicies.forEach((mph) => {
const tokens = diff.getTokens(mph);
tokens.forEach(([token, quantity], i) => {
changeAssets.addComponent(mph, token, quantity);
tokensAdded += 1;
if (tokensAdded == maxAssetsPerOutput) {
this.#body.addOutput(new TxOutput(changeAddress, new Value(0n, changeAssets)));
changeAssets = new Assets();
tokensAdded = 0;
}
});
});

this.#body.addOutput(changeOutput);
// If we are here and have No assets, they we're done
if (!changeAssets.isZero()) {
this.#body.addOutput(new TxOutput(changeAddress, new Value(0n, changeAssets)));
}
} else {
const changeOutput = new TxOutput(changeAddress, new Value(0n, diff));

this.#body.addOutput(changeOutput);
}
}
}

Expand Down Expand Up @@ -1021,7 +1046,7 @@ export class Tx extends CborData {
nonChangeOutputValue = feeValue.add(nonChangeOutputValue);

// this is quite restrictive, but we really don't want to touch UTxOs containing assets just for balancing purposes
const spareAssetUTxOs = spareUtxos.some(utxo => !utxo.value.assets.isZero());
const spareAssetUTxOs = spareUtxos.filter(utxo => !utxo.value.assets.isZero());
spareUtxos = spareUtxos.filter(utxo => utxo.value.assets.isZero());

// use some spareUtxos if the inputValue doesn't cover the outputs and fees
Expand All @@ -1031,8 +1056,15 @@ export class Tx extends CborData {
let spare = spareUtxos.pop();

if (spare === undefined) {
if (spareAssetUTxOs) {
throw new Error(`UTxOs too fragmented`);
if (spareAssetUTxOs.length > 0) {
spare = spareAssetUTxOs.pop();

if (!spare){
throw new Error(`UTxOs too fragmented - or no Spare UTxOs available to fix this mess`);
}

this.#body.addInput(spare);
this.balanceAssets(changeAddress);
} else {
throw new Error(`need ${totalOutputValue.lovelace} lovelace, but only have ${inputValue.lovelace}`);
}
Expand Down
Loading