UNPKG

@trivechain/triveasset-protocol

Version:

TriveAsset Protocol provides the definition, encode and decode functions that recognize a TriveAsset Encoding

279 lines (262 loc) 8.95 kB
const _ = require('lodash') const bitcoinjs = require('bitcoinjs-lib') const debug = require('debug')('findBestMatchByNeededAssets') const findBestMatchByNeededAssets = function ( utxos, assetList, key, txb, inputvalues, metadata ) { debug('findBestMatchByNeededAssets: start for ' + key) const selectedUtxos = [] let foundAmount = 0 // 1. try to find a utxo with such amount of the asset which is greater or equal to the target amount const bestGreaterOrEqualAmountUtxo = findBestGreaterOrEqualAmountUtxo( utxos, assetList, key ) if (bestGreaterOrEqualAmountUtxo) { debug('bestGreaterOrEqualAmountUtxo = ', bestGreaterOrEqualAmountUtxo) selectedUtxos[0] = bestGreaterOrEqualAmountUtxo } else { // 2. try to get the minimal number of utxos where the sum of their amount of the asset greater than or equal to the remaining target amount debug('try to get utxos smaller than amount') const utxosSortedByAssetAmount = _.sortBy(utxos, function (utxo) { return -getUtxoAssetAmount(utxo, key) }) const found = utxosSortedByAssetAmount.some(function (utxo) { selectedUtxos.push(utxo) foundAmount += getUtxoAssetAmount(utxo, key) return foundAmount >= assetList[key].amount }) if (!found) selectedUtxos.length = 0 } debug( 'selectedUtxos = ', _.map(selectedUtxos, function (utxo) { return { utxo: utxo.txid + ':' + utxo.index, amount: getUtxoAssetAmount(utxo, key), } }) ) if (!selectedUtxos.length) { debug('not enough amount') return false } debug('adding inputs by assets and amounts') let lastAssetId selectedUtxos.some(function (utxo) { utxo.assets.forEach(function (asset) { try { debug('maybe adding input for ' + asset.assetId) if (assetList[asset.assetId] && !assetList[asset.assetId].done) { debug('probably adding input for ' + asset.assetId) debug( 'transfer request: ' + assetList[asset.assetId].amount + ' available in utxo: ' + asset.amount ) debug('adding input') let inputIndex = txb.tx.ins.length if ( !txb.tx.ins.some(function (txutxo, i) { if ( txutxo.index === utxo.index && Buffer.from(txutxo.hash, 'hex') === utxo.txid ) { debug('more assets in same utxo') inputIndex = i return true } return false }) ) { txb.addInput(utxo.txid, utxo.index) debug( 'setting input value ' + utxo.value + ' actual: ' + Math.round(utxo.value) ) inputvalues.amount += Math.round(utxo.value) debug('setting input in asset list') if (metadata.flags && metadata.flags.injectPreviousOutput) { const chunks = bitcoinjs.script.decompile( Buffer.from(utxo.scriptPubKey.hex, 'hex') ) txb.tx.ins[ txb.tx.ins.length - 1 ].script = bitcoinjs.script.compile(chunks) } } const aggregationPolicy = asset.aggregationPolicy || 'aggregatable' // TODO - remove after all assets have this field const inputIndexInAsset = assetList[asset.assetId].inputs.length debug('inputIndex = ' + inputIndex) debug('inputIndexInAsset = ' + inputIndexInAsset) if (assetList[asset.assetId].amount <= asset.amount) { const totalamount = asset.amount if ( aggregationPolicy === 'aggregatable' && lastAssetId === asset.assetId && assetList[asset.assetId].inputs.length ) { debug( '#1 assetList[' + asset.assetId + '].inputs[' + (inputIndexInAsset - 1) + '].amount += ' + assetList[asset.assetId].amount ) assetList[asset.assetId].inputs[inputIndexInAsset - 1].amount += assetList[asset.assetId].amount } else { debug( '#2 assetList[' + asset.assetId + '].inputs.push({ index: ' + inputIndex + ', amount: ' + assetList[asset.assetId].amount + '})' ) assetList[asset.assetId].inputs.push({ index: inputIndex, amount: assetList[asset.assetId].amount, }) } debug('setting change') assetList[asset.assetId].change = totalamount - assetList[asset.assetId].amount debug('setting done') assetList[asset.assetId].done = true } else { if ( aggregationPolicy === 'aggregatable' && lastAssetId === asset.assetId && assetList[asset.assetId].inputs.length ) { debug( '#3 assetList[' + asset.assetId + '].inputs[' + (inputIndexInAsset - 1) + '].amount += ' + asset.amount ) assetList[asset.assetId].inputs[inputIndexInAsset - 1].amount += asset.amount } else { debug( '#4 assetList[' + asset.assetId + '].inputs.push({ index: ' + inputIndex + ', amount: ' + asset.amount + '})' ) assetList[asset.assetId].inputs.push({ index: inputIndex, amount: asset.amount, }) } assetList[asset.assetId].amount -= asset.amount } } else { debug('not adding input for ' + asset.assetId) } } catch (e) { debug('findBestMatchByNeededAssets: error = ', e) } lastAssetId = asset.assetId }) debug('returning ' + assetList[key].done) return assetList[key].done }) debug('findBestMatchByNeededAssets: done') return true } const findBestGreaterOrEqualAmountUtxo = function (utxos, assetList, key) { debug('findBestGreaterOrEqualAmountUtxo for ', key) debug('assetList[' + key + '].amount = ', assetList[key].amount) let foundLargerOrEqualAmountUtxo = false utxos.forEach(function (utxo) { utxo.score = 0 let assetAmount = getUtxoAssetAmount(utxo, key) if (assetAmount < assetList[key].amount) { // debug('for utxo ' + utxo.txid + ':' + utxo.index + ', assetAmount = ' + assetAmount + ', no score.') return } foundLargerOrEqualAmountUtxo = true if (assetAmount === assetList[key].amount) { // debug('for utxo ' + utxo.txid + ':' + utxo.index + ', assetAmount = ' + assetAmount + ', score += 10000') utxo.score += 10000 } else { // assetAmount > assetList[key].amount // debug('for utxo ' + utxo.txid + ':' + utxo.index + ', assetAmount = ' + assetAmount + ', score += 1000') utxo.score += 1000 } for (const assetId in assetList) { if (assetId === key) continue assetAmount = getUtxoAssetAmount(utxo, assetId) debug('checking assetId = ' + assetId) if (assetAmount === assetList[assetId].amount) { debug( 'for utxo ' + utxo.txid + ':' + utxo.index + ', assetAmount = ' + assetAmount + ', score += 100' ) utxo.score += 100 } else if (assetAmount > assetList[assetId].amount) { debug( 'for utxo ' + utxo.txid + ':' + utxo.index + ', assetAmount = ' + assetAmount + ', score += 10' ) utxo.score += 10 } else { // assetAmount < assetList[assetId].amount debug( 'for utxo ' + utxo.txid + ':' + utxo.index + ', assetAmount = ' + assetAmount + ', score += ' + assetAmount / assetList[assetId].amount ) utxo.score += assetAmount / assetList[assetId].amount } } }) debug('findBestGreaterOrEqualAmountUtxo: done iterating over utxos') return ( foundLargerOrEqualAmountUtxo && _.maxBy(utxos, function (utxo) { return utxo.score }) ) } const getUtxoAssetAmount = function (utxo, assetId) { return _(utxo.assets) .filter(function (asset) { return asset.assetId === assetId }) .sumBy('amount') } module.exports = findBestMatchByNeededAssets