org.coinspark.protocol.CoinSparkTransferList Maven / Gradle / Ivy
/*
* CoinSpark 2.1 - Java library
*
* Copyright (c) Coin Sciences Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.coinspark.protocol;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* CoinSparkTransferList class for managing list of asset transfer metadata
*/
public class CoinSparkTransferList extends CoinSparkBase{
/**
* CoinSparkTransferList class for managing list of asset transfer metadata
*/
public CoinSparkTransferList()
{
maxTransfers = MAX_TRANSFERS_DEFAULT;
transfersList = new CoinSparkTransfer[maxTransfers];
}
/**
* CoinSparkTransferList class for managing list of asset transfer metadata
* @param MaxTransfers maximal number of transfers
*/
public CoinSparkTransferList(int MaxTransfers)
{
maxTransfers = MaxTransfers;
transfersList = new CoinSparkTransfer[maxTransfers];
}
/**
* Returns real number of transfer in the list
* @return number of transfers in the list
*/
public int count() {
return countTransfers;
}
/**
* Return individual transfer
* @param i -transfer id (0 based)
* @return individual transfer
*/
public CoinSparkTransfer getTransfer(int i)
{
if (i < transfersList.length)
return transfersList[i];
return null;
}
@Override
public String toString()
{
int[] ordering = new int[1024];
if (countTransfers > ordering.length)
return null;
transfersGroupOrdering(this.transfersList, ordering, countTransfers);
StringBuilder sb = new StringBuilder();
sb.append("COINSPARK TRANSFERS\n");
for (int index=0; index < countTransfers; index++)
{
if (transfersList[index] == null)
break;
if (index>0)
sb.append("\n");
sb.append(CoinSparkTransfer.toStringInner(transfersList[index], false));
}
sb.append("END COINSPARK TRANSFERS\n\n");
return sb.toString();
}
/**
* If strict is true then the ordering in the two arrays must be identical. If strict is false
* then it is enough if each list is equivalent, i.e. the same transfers in the same order for each asset reference.
*
* @param transfers2 transfer list to compare to
* @param strict strinct flag
* @return Returns true if the two arrays of transfers in this and transfers2 are the same.
*/
public boolean match(CoinSparkTransferList transfers2, boolean strict)
{
int[] ordering1 = new int[1024];
int[] ordering2 = new int[1024];
if(countTransfers != transfers2.countTransfers)
{
return false;
}
if (strict)
{
for (int transferIndex=0; transferIndexordering1.length)
return false;
transfersGroupOrdering(this.transfersList, ordering1, countTransfers);
transfersGroupOrdering(transfers2.transfersList, ordering2, countTransfers);
for(int i = 0; i < countTransfers; i++)
{
if (!transfersList[i].match(transfers2.transfersList[i]))
{
return false;
}
}
}
return true;
}
@Override
public CoinSparkTransferList clone() throws CloneNotSupportedException
{
CoinSparkTransferList clone = new CoinSparkTransferList(maxTransfers);
clone.maxTransfers = maxTransfers;
clone.countTransfers = this.countTransfers;
clone.transfersList = new CoinSparkTransfer[maxTransfers];
for (int i = 0; i < maxTransfers; i++) {
clone.transfersList[i] = this.transfersList[i].clone();
}
return clone;
}
/**
* Adds transfer to transfer list
*
* @param i id in list
* @param transfer to set
*/
public void setTransfer(int i, CoinSparkTransfer transfer)
{
if (maxTransfers > i)
{
transfersList[i] = transfer;
if(i+1>countTransfers)
{
countTransfers=i+1;
}
}
}
/**
* Encodes the transfer list into metadata.
*
* @param countInputs number of inputs in transaction
* @param countOutputs number of outputs in transaction
* @param metadataMaxLen maximal size of metadata
* @return String | null Encoded transfer list as hexadecimal, null if we failed.
*/
public String encodeToHex(int countInputs, int countOutputs,int metadataMaxLen)
{
CoinSparkBuffer buffer=new CoinSparkBuffer();
if(!encode(buffer,countInputs,countOutputs,metadataMaxLen))
{
return null;
}
return buffer.toHex();
}
/**
* Encodes the transfer list into metadata.
*
* @param countInputs number of inputs in transaction
* @param countOutputs number of outputs in transaction
* @param metadataMaxLen maximal size of metadata
* @return byte [] | null Encoded transfer list as raw data, null if we failed.
*/
public byte [] encode(int countInputs, int countOutputs,int metadataMaxLen)
{
CoinSparkBuffer buffer=new CoinSparkBuffer();
if(!encode(buffer,countInputs,countOutputs,metadataMaxLen))
{
return null;
}
return buffer.toBytes();
}
/**
* Decodes the metadata into transfer list.
*
* @param metadata Metadata to decode as hexadecimal
* @param countInputs number of inputs in transaction
* @param countOutputs number of outputs in transaction
* @return true on success, false on failure
*/
public boolean decode(String metadata, int countInputs, int countOutputs)
{
CoinSparkBuffer buffer=new CoinSparkBuffer(metadata, true);
return decode(buffer,countInputs,countOutputs);
}
/**
* Decodes the metadata into transfer list.
*
* @param metadata Metadata to decode as raw data
* @param countInputs number of inputs in transaction
* @param countOutputs number of outputs in transaction
* @return true on success, false on failure
*/
public boolean decode(byte [] metadata, int countInputs, int countOutputs)
{
CoinSparkBuffer buffer=new CoinSparkBuffer(metadata);
return decode(buffer,countInputs,countOutputs);
}
/**
* Decodes the metadata into transfer list.
*
* @param metadata Metadata to decode as hexadecimal
* @param countInputs number of inputs in transaction
* @param countOutputs number of outputs in transaction
* @return number of transfers
*/
public int decodeCount(String metadata, int countInputs, int countOutputs)
{
CoinSparkBuffer buffer=new CoinSparkBuffer(metadata, true);
return decodeCount(buffer);
}
/**
* Decodes the metadata into transfer list.
*
* @param metadata Metadata to decode as raw data
* @param countInputs number of inputs in transaction
* @param countOutputs number of outputs in transaction
* @return number of transfers
*/
public int decodeCount(byte [] metadata, int countInputs, int countOutputs)
{
CoinSparkBuffer buffer=new CoinSparkBuffer(metadata);
return decodeCount(buffer);
}
/**
* Use CoinSparkScriptIsRegular() to pass an array of bools in outputsRegular for whether each output script is regular.
* Pass the number of transaction inputs and outputs in countInputs and countOutputs respectively.
* @param countInputs number of inputs in transaction
* @param outputsSatoshis Pass the number of bitcoin satoshis in each output in outputsSatoshis (array size countOutputs).
* @param outputsRegular pass array of booleans for whether each output script is regular
* @return Returns the minimum transaction fee (in bitcoin satoshis) required to make the set of transfers (array size countTransfers) valid.
*/
public long calcMinFee(int countInputs, long[] outputsSatoshis, boolean[] outputsRegular) {
if(outputsSatoshis.length != outputsRegular.length)
{
return COINSPARK_SATOSHI_QTY_MAX;
}
int countOutputs=Math.min(outputsSatoshis.length, outputsRegular.length);
int outputIndex, lastOutputIndex;
int transfersToCover = 0;
for (int transferIndex = 0; transferIndex < countTransfers; transferIndex++) {
if (
(this.transfersList[transferIndex].assetRef.getBlockNum() !=
CoinSparkTransfer.COINSPARK_TRANSFER_BLOCK_NUM_DEFAULT_ROUTE) && // don't count default routes
(this.transfersList[transferIndex].inputs.count > 0) &&
(this.transfersList[transferIndex].inputs.first < countInputs) // only count if at least one valid input index
)
{
outputIndex = Math.max(this.transfersList[transferIndex].outputs.first, 0);
lastOutputIndex = Math.min(this.transfersList[transferIndex].outputs.first +
this.transfersList[transferIndex].outputs.count, countOutputs)-1;
for (; outputIndex<=lastOutputIndex; outputIndex++) {
if (outputsRegular[outputIndex])
transfersToCover++;
}
}
}
long temp = getMinFeeBasis(outputsSatoshis, outputsRegular);
temp *= transfersToCover;
return temp;
}
/**
* For the asset specified by assetRef and genesis, and list of transfers (size countTransfers), applies those transfers
* to move units of that asset from inputBalances (size countInputs) to outputBalances.
* Only transfers whose assetRef matches the function's assetRef parameter will be applied (apart from default routes).
* Use CoinSparkScriptIsRegular() to pass an array of bools in outputsRegular for whether each output script is regular.
* ** Call this if the transaction DOES HAVE a sufficient fee to make the list of transfers valid **
*
* @param reference Asset reference
* @param genesis Genesis object corresponding to asset reference
* @param inputBalances Input balances
* @param outputsRegular pass array of booleans for whether each output script is regular
* @return Output balances
*/
public long[] apply(CoinSparkAssetRef reference, CoinSparkGenesis genesis,
long[] inputBalances,boolean[] outputsRegular)
{
return applyInner(reference, genesis, inputBalances, outputsRegular);
}
/**
* For the asset specified by assetRef and genesis, move units of that asset from inputBalances (size countInputs) to
* outputBalances, applying the default behavior only (all assets goes to last regular output).
* ** Call this if the transaction DOES NOT HAVE a sufficient fee to make the list of transfers valid **
* @param assetRef Asset reference
* @param genesis Genesis object corresponding to asset reference
* @param inputBalances Input balances
* @param outputsRegular pass array of booleans for whether each output script is regular
* @return Output balances
*/
public long[] applyNone(CoinSparkAssetRef assetRef, CoinSparkGenesis genesis,
long[] inputBalances,boolean[] outputsRegular)
{
return new CoinSparkTransferList().applyInner(assetRef, genesis,inputBalances,outputsRegular);
}
/**
* For the list of transfers (size countTransfers) on a transaction with countInputs inputs, calculate
* the array of bools in outputsDefault where each entry indicates whether that
* output might receive some assets due to default routes.
*
* @param countInputs number of inputs in transaction
* @param outputsRegular pass array of booleans for whether each output script is regular
* @return array of booleans indicating where each entry indicates whether that output might receive some assets due to default routes.
*/
public boolean [] defaultOutputs(int countInputs, boolean[] outputsRegular)
{
int countOutputs=outputsRegular.length;
boolean [] outputsDefault=new boolean [countOutputs];
int outputIndex;
for (outputIndex = 0; outputIndex < countOutputs; outputIndex++) {
outputsDefault[outputIndex] = false;
}
int[] inputDefaultOutput = getDefaultRouteMap(countInputs, outputsRegular);
for (int inputIndex=0; inputIndex ordering.length)
return false; // too many for statically sized array
buffer.writeString(COINSPARK_METADATA_IDENTIFIER);
buffer.writeByte(COINSPARK_TRANSFERS_PREFIX);
// Encode each transfer, grouping by asset reference, but preserving original order otherwise
transfersGroupOrdering(this.transfersList, ordering, countTransfers);
CoinSparkTransfer previousTransfer = null;
for (int transferIndex = 0; transferIndexmetadataMaxLen)
{
return false;
}
return true;
}
private boolean decode(CoinSparkBuffer buffer, int countInputs, int countOutputs)
{
CoinSparkTransfer transfer, previousTransfer = null;
long transferBytesUsed = 0;
if(!buffer.locateRange(COINSPARK_TRANSFERS_PREFIX))
return false;
// Iterate over list
try {
countTransfers=0;
transfer = new CoinSparkTransfer();
while (buffer.availableForRead() > 0)
{
if(transfer.decode(buffer, countTransfers != 0 ? previousTransfer : null, countInputs, countOutputs))
{
if (countTransfers < maxTransfers)
transfersList[countTransfers]=transfer.clone(); // copy across if still space
countTransfers++;
previousTransfer=transfer.clone();
}
else
return false;
}
} catch (CloneNotSupportedException ex) {
Logger.getLogger(CoinSparkTransferList.class.getName()).log(Level.SEVERE, null, ex);
}
return true;
}
private int decodeCount(CoinSparkBuffer buffer)
{
if(decode(buffer, CoinSparkIORange.COINSPARK_IO_INDEX_MAX, CoinSparkIORange.COINSPARK_IO_INDEX_MAX))
{
return countTransfers;
}
return 0;
}
private long[] applyInner(CoinSparkAssetRef reference, CoinSparkGenesis genesis,
long[] inputBalances, boolean[] outputsRegular)
{
int countInputs=inputBalances.length;
int countOutputs=outputsRegular.length;
long[] outputBalances=new long[countOutputs];
int transferIndex;
int inputIndex, outputIndex, lastInputIndex, lastOutputIndex;
long transferRemaining;
long transferQuantity;
// Copy all input quantities and zero output quantities
long[] inputsRemaining = new long[countInputs];
for (inputIndex=0; inputIndex0) // skip all this if nothing is to be transferred (branch not really necessary)
{
inputsRemaining[inputIndex] = inputsRemaining[inputIndex] - transferQuantity;
transferRemaining = transferRemaining - transferQuantity;
transferQuantity = Math.min(transferQuantity,
CoinSparkAssetQty.COINSPARK_ASSET_QTY_MAX - outputBalances[outputIndex]); // prevent overflow
outputBalances[outputIndex] = (outputBalances[outputIndex] + transferQuantity);
}
if (transferRemaining>0)
inputIndex++; // move to next input since this one is drained
else
break; // stop if we have nothing left to transfer
}
}
}
}
}
// Apply payment charges to all quantities not routed by default
for (outputIndex=0; outputIndex0) )
outputBalances[outputIndex] = genesis.calcNet(outputBalances[outputIndex]);
}
// Send remaining quantities to default outputs
int[] inputDefaultOutput = getDefaultRouteMap(countInputs, outputsRegular);
for (inputIndex=0; inputIndex=0; transferIndex--) {
if (transfersList[transferIndex].assetRef.getBlockNum() ==
CoinSparkTransfer.COINSPARK_TRANSFER_BLOCK_NUM_DEFAULT_ROUTE)
{
outputIndex = transfersList[transferIndex].outputs.first; // outputs.count is not relevant
if ( (outputIndex>=0) && (outputIndex0) && transfers[ordering[orderIndex-1]].assetRef.match(transfers[transferIndex].assetRef))
transferScore=2; // then next best is one which has same asset reference as previous
else
transferScore=1; // otherwise any will do
if (transferScore>bestTransferScore) { // if it's clearly the best, take it
bestTransferScore=transferScore;
bestTransferIndex=transferIndex;
} else if (transferScore==bestTransferScore) // otherwise give priority to "lower" asset references
if (transfers[transferIndex].assetRef.compare(transfers[bestTransferIndex].assetRef)<0)
bestTransferIndex=transferIndex;
}
}
ordering[orderIndex]=bestTransferIndex;
transferUsed[bestTransferIndex] = true;
}
return ordering;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy