fuel-core
Version:
Essential javascript architecture for the Fuel side chain
1,745 lines (1,459 loc) • 56.1 kB
JavaScript
const { utils } = require('ethers');
const {
abi,
big,
BN,
emptyAddress,
emptyBytes32,
RLP,
} = require('../utils/utils');
const _utils = require('../utils/utils');
const {
FuelInputTypes,
FuelOutputTypes,
FuelProofTypes,
FuelDBKeys,
} = require('../interfaces/interfaces');
const interfaces = require('../interfaces/interfaces');
const {
ByPassError,
} = require('../errors/errors');
const errors = require('../errors/errors');
const {
TypeDB,
TypeProvider,
TypeFunction,
TypeRPC,
TypeArray, TypeInstance,
TypeString, TypeDefined,
TypeNumber, TypeBoolean, TypeAddress,
TypeHex, TypeBigNumber, TypeObject,
} = require('../types/types');
const types = require('../types/types');
function assertNew(self, type) {
if (!(self instanceof type)) throw new Error(type.name + ' not instantiated with `new`');
}
function BlockHeader({ producer, previousBlockHash, blockHeight, ethereumBlockNumber, transactionRoots }) {
// Enforce Object
assertNew(this, BlockHeader);
// Enfore Types
TypeAddress(producer);
TypeHex(previousBlockHash, 32);
TypeBigNumber(blockHeight);
TypeBigNumber(ethereumBlockNumber);
TypeArray(transactionRoots);
for (var rootIndex = 0; rootIndex < transactionRoots.length; rootIndex++) {
TypeHex(transactionRoots[rootIndex], 32);
}
// Props
this.producer = producer;
this.previousBlockHash = previousBlockHash;
this.height = blockHeight;
this.blockHeight = this.height;
this.ethereumBlockNumber = ethereumBlockNumber;
this.transactionRoots = transactionRoots;
// Construct Block Header
this.types = [
'address', // - blockProducer [32 bytes] -- padded address
'bytes32', // - previousBlockHash [32 bytes]
'uint256', //- blockHeight [32 bytes]
'uint256', // - ethereumBlockNumber [32 bytes]
'bytes32[]', // - transactionRoots [64 + dynamic bytes]
];
this.values = [producer, previousBlockHash, blockHeight, ethereumBlockNumber, transactionRoots];
this.encoded = abi.encode(this.types, this.values);
this.hash = utils.keccak256(this.encoded);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionRootHeader({ producer, merkleTreeRoot, commitmentHash, index, transactionHash }) {
// Enforce Object
assertNew(this, TransactionRootHeader);
TypeAddress(producer);
TypeHex(merkleTreeRoot, 32);
TypeHex(commitmentHash, 32);
if (typeof index !== 'undefined') { TypeBigNumber(index); }
if (typeof transactionHash !== 'undefined') { TypeHex(transactionHash, 32); }
this.producer = producer;
this.merkleTreeRoot = merkleTreeRoot;
this.commitmentHash = commitmentHash;
this.transactionHash = transactionHash;
this.index = index || 0;
this.types = [
'address', // - transactionRootProducer [32 bytes] -- padded address
'bytes32', // - transactionRootMerkleTreeRoot [32 bytes] -- padded bytes32
'bytes32', // - transactionRootCommitmentHash [32 bytes]
'uint16', // - transactionRootIndex [32 bytes] -- padded uint16
];
const values = this.values = [producer, merkleTreeRoot, commitmentHash, this.index];
this.encoded = abi.encode(this.types, this.values);
this.rlp = () => RLP.encode(values);
this.hash = utils.keccak256(abi.encode(['address', 'bytes32', 'bytes32'], [producer, merkleTreeRoot, commitmentHash]));
this.length = utils.hexDataLength(this.encoded);
}
function TransactionData({ inputIndex, outputIndex, witnessIndex, transactionIndex, transactionLeaf }) {
// Enforce Object
assertNew(this, TransactionData);
// Type Enforcement
TypeNumber(inputIndex);
TypeNumber(outputIndex);
TypeNumber(witnessIndex);
TypeNumber(transactionIndex);
TypeObject(transactionLeaf);
// Construction
this.types = [
'uint8', // - input index [32 bytes] -- padded uint8
'uint8', // - output index [32 bytes] -- padded uint8
'uint8', // - witness index [32 bytes] -- padded uint8
'uint16', // - transactionIndex [32 bytes] -- padded uint16
'bytes', // - transactionLeafData [dynamic bytes]
];
this.inputIndex = inputIndex;
this.outputIndex = outputIndex;
this.witnessIndex = witnessIndex;
this.transactionIndex = transactionIndex;
this.transactionLeaf = transactionLeaf;
this.values = [inputIndex, outputIndex, witnessIndex, transactionIndex, transactionLeaf.encoded];
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionLength(value) {
// Enforce Object
assertNew(this, TransactionLength);
this.types = [
'uint16', // - bytes length
];
this.values = [value];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionLengthSpecifier(value) {
// Enforce Object
assertNew(this, TransactionLengthSpecifier);
this.types = [
'uint8', // - bytes length
];
this.values = [value];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionMetadata({ blockHeight, transactionRootIndex, transactionIndex, outputIndex }) {
// Enforce Object
assertNew(this, TransactionMetadata);
// Enforce Types
TypeBigNumber(blockHeight);
if (typeof transactionRootIndex === 'object') {
TypeBigNumber(transactionRootIndex);
} else {
TypeNumber(transactionRootIndex);
}
if (typeof transactionIndex === 'object') {
TypeBigNumber(transactionIndex);
} else {
TypeNumber(transactionIndex);
}
if (typeof outputIndex === 'object') {
TypeBigNumber(outputIndex);
} else {
TypeNumber(outputIndex);
}
// Props
this.blockHeight = blockHeight;
this.transactionRootIndex = transactionRootIndex;
this.transactionIndex = transactionIndex;
this.outputIndex = outputIndex;
// Construct
this.types = [
'uint32', // - blockHeight [4 bytes] -- uint32
'uint8', // - transactionRootIndex [1 byte] -- uint8
'uint16', // - transactionIndex [2 bytes] -- uint16
'uint8', // - output index [1 byte] -- uint8
];
this.values = [ blockHeight, transactionRootIndex, transactionIndex, outputIndex ];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Ethers Signature
function encodeSignature(ethersSignature) {
TypeObject(ethersSignature);
// Joined Encoded
const joined = utils.joinSignature(ethersSignature); // RSV
// Return output // VRS
return '0x' + joined.slice(-2) + joined.substr(2, 128); // VRS
}
// Decode Signature
function decodeSignature(vrsSignature) {
// Enforce Signature
TypeHex(vrsSignature, 65);
// Split Signature
return {
v: '0x' + vrsSignature.substr(2, 2),
r: '0x' + vrsSignature.substr(4, 64),
s: '0x' + vrsSignature.substr(4 + 64, 64),
};
}
// Unsigned TX with Signing Key
function constructWitness(unsignedTransaction, signingKey) {
// Enforce Types
TypeInstance(unsignedTransaction, TransactionUnsigned);
TypeObject(signingKey);
// Construct VRS Object
return decodeSignature(encodeSignature(signingKey.signDigest(unsignedTransaction.hash)));
}
// encode packed
function ecrecoverPacked(digest, vrsSignature) {
// TypeHex(digest, 32);
// TypeHex(vrsSignature, 65);
// split
const flat = '0x'
+ vrsSignature.substr(4, 64) // r
+ vrsSignature.substr(4 + 64, 64) // s
+ vrsSignature.substr(2, 2); // v
return utils.recoverAddress(digest, utils.splitSignature(flat));
}
// Can insert encoded
function TransactionWitness({ signature, encoded, v, r, s }) {
// Enforce Object
assertNew(this, TransactionWitness);
// Check Types
if (typeof encoded !== 'undefined') { TypeHex(encoded, 65); }
if (typeof signature !== 'undefined') { TypeObject(signature); }
if (typeof v !== 'undefined') { TypeHex(v, 1); }
if (typeof r !== 'undefined') { TypeHex(r, 32); }
if (typeof s !== 'undefined') { TypeHex(s, 32); }
// Split signature
const sig = (encoded || signature)
? decodeSignature(encoded ? encoded : encodeSignature(signature))
: { v, r, s };
// Signature Properties.
this.v = sig.v;
this.r = sig.r;
this.s = sig.s;
// Construct Types
this.types = [
'uint8', // - v [4 bytes] -- uint32 V
'bytes32', // - r [1 byte] -- uint8 R
'bytes32', // - s [2 bytes] -- uint16 S
];
this.values = [sig.v, sig.r, sig.s];
this.vrsSignature = () => String(sig.v + sig.r.slice(2) + sig.s.slice(2));
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
/*
new TransactionMetadata({
blockHeight: 1,
transactionRootindex: 0,
transactionIndex: 0,
outputIndex: 0,
})
*/
function constructDepositHashID({ account, token, ethereumBlockNumber }) {
// Enforce Types
TypeAddress(account);
TypeAddress(account);
TypeBigNumber(ethereumBlockNumber);
// Construct Hash
const types = [
'address',
'address',
'uint256',
];
const values = [
account,
token,
ethereumBlockNumber,
];
// Return Construction
return utils.keccak256(abi.encode(types, values));
}
// Construct withdrawal hash id
function constructWithdrawalHashID({ transactionRootIndex, transactionLeafHash, outputIndex }) {
// Enforce Types
TypeNumber(transactionRootIndex);
TypeHex(transactionLeafHash, 32);
TypeNumber(outputIndex);
// Construct Hash
const types = [
'uint8',
'bytes32',
'uint8',
];
const values = [
transactionRootIndex,
transactionLeafHash,
outputIndex,
];
// Return Construction
return utils.keccak256(abi.encode(types, values));
}
function serializeRLP(arr) {
return arr.map(v => (typeof v === 'number') ? big(v).toHexString() : v);
}
// Transaction Input UTXO
function TransactionInputUTXO({ utxoID, witnessReference }) {
// Enforce Object
assertNew(this, TransactionInputUTXO);
// Enforce Types
TypeHex(utxoID, 32);
TypeNumber(witnessReference);
// Props
this.utxoID = utxoID;
this.witnessReference = witnessReference;
this.type = FuelInputTypes.UTXO;
// Witness reference
this.types = [
'uint8', // - type [1 bytes] -- uint32
'bytes32', // - depositHashID [32 bytes] -- bytes32
'uint8', // - witness reference [1 byte] -- uint8
];
this.values = [ FuelInputTypes.UTXO, utxoID, witnessReference ];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Transaction Input UTXO
function TransactionInputHTLC({ utxoID, witnessReference, preImage }) {
// Enforce Object
assertNew(this, TransactionInputHTLC);
// Enforce Types
TypeHex(utxoID, 32);
TypeHex(preImage, 32);
TypeNumber(witnessReference);
// Props
this.utxoID = utxoID;
this.witnessReference = witnessReference;
this.preImage = preImage;
this.type = FuelInputTypes.HTLC;
// Witness reference
this.types = [
'uint8', // - blockHeight [4 bytes] -- uint32
'bytes32', // - depositHashID [32 bytes] -- bytes32
'uint8', // - witness reference [1 byte] -- uint8
'bytes32', // - witness reference [1 byte] -- uint8
];
this.values = [FuelInputTypes.HTLC, utxoID, witnessReference, preImage ];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Transaction Input UTXO
function TransactionInputChange({ utxoID, witnessReference }) {
// Enforce Object
assertNew(this, TransactionInputChange);
// Enforce Types
TypeHex(utxoID, 32);
TypeNumber(witnessReference);
// Props
this.utxoID = utxoID;
this.witnessReference = witnessReference;
this.type = FuelInputTypes.Change;
// Witness reference
this.types = [
'uint8', // - blockHeight [4 bytes] -- uint32
'bytes32', // - depositHashID [32 bytes] -- bytes32
'uint8', // - witness reference [1 byte] -- uint8
];
this.values = [FuelInputTypes.Change, utxoID, witnessReference ];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionInputDeposit({ depositHashID, witnessReference }) {
// Enforce Object
assertNew(this, TransactionInputDeposit);
// Enforce Types
TypeHex(depositHashID, 32);
TypeNumber(witnessReference);
// Props
this.depositHashID = depositHashID;
this.witnessReference = witnessReference;
this.type = FuelInputTypes.Deposit;
// Witness reference
this.types = [
'uint8', // - blockHeight [4 bytes] -- uint32
'bytes32', // - depositHashID [32 bytes] -- bytes32
'uint8', // - witness reference [1 byte] -- uint8
];
this.values = [FuelInputTypes.Deposit, depositHashID, witnessReference ];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionOutputUTXO({ amount, tokenID, owner }) {
// Enforce Object
assertNew(this, TransactionOutputUTXO);
// Enforce Types
TypeBigNumber(amount);
TypeBigNumber(tokenID);
TypeAddress(owner);
// Construct
this.type = FuelOutputTypes.UTXO;
this.amount = utils.hexStripZeros(big(amount).toHexString());
this.amountBig = big(amount);
// is odd
if (this.amount.length % 2) {
this.amount = '0x0' + this.amount.slice(2);
}
this.amountLength = big(utils.hexDataLength(this.amount) || 1);
this.owner = owner;
this.tokenID = tokenID;
this.tokenID = tokenID;
this.types = [
'uint8',
'uint8',
'uint' + this.amountLength.mul(8).toString(),
'address',
'uint32',
];
this.values = [this.type, this.amountLength, this.amount, this.owner, this.tokenID];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionOutputWithdrawal({ amount, tokenID, owner }) {
// Enforce Object
assertNew(this, TransactionOutputWithdrawal);
// Enforce Types
TypeBigNumber(amount);
TypeBigNumber(tokenID);
TypeAddress(owner);
// Construct
this.type = FuelOutputTypes.Withdrawal;
this.amount = utils.hexStripZeros(big(amount).toHexString());
// is odd
if (this.amount.length % 2) {
this.amount = '0x0' + this.amount.slice(2);
}
this.amountLength = big(utils.hexDataLength(this.amount) || 1);
this.tokenID = tokenID;
this.owner = owner;
this.types = [
'uint8',
'uint8',
'uint' + this.amountLength.mul(8).toString(),
'address',
'uint32',
];
this.values = [this.type, this.amountLength, this.amount, this.owner, this.tokenID];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionOutputChange({ amount, tokenID, ownerAsWitnessIndex }) {
// Enforce Object
assertNew(this, TransactionOutputChange);
// Enforce Types
TypeBigNumber(amount);
TypeBigNumber(tokenID);
TypeNumber(ownerAsWitnessIndex);
// Construct
this.type = FuelOutputTypes.Change;
this.amount = utils.hexStripZeros(big(amount).toHexString());
// is odd
if (this.amount.length % 2) {
this.amount = '0x0' + this.amount.slice(2);
}
this.amountLength = big(utils.hexDataLength(this.amount) || 1);
this.ownerAsWitnessIndex = ownerAsWitnessIndex;
this.tokenID = tokenID;
this.types = [
'uint8',
'uint8',
'uint' + this.amountLength.mul(8).toString(),
'uint8',
'uint32',
];
this.values = [this.type, this.amountLength, this.amount, this.ownerAsWitnessIndex, this.tokenID];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionOutputHTLC({ amount, tokenID, owner, digest, expiry, returnWitnessIndex }) {
// Enforce Object
assertNew(this, TransactionOutputHTLC);
// Enforce Types
TypeBigNumber(amount);
TypeBigNumber(tokenID);
TypeAddress(owner);
TypeHex(digest, 32);
TypeBigNumber(expiry);
TypeNumber(returnWitnessIndex);
// Construct
this.type = FuelOutputTypes.HTLC;
this.amount = utils.hexStripZeros(big(amount).toHexString());
// is odd
if (this.amount.length % 2) {
this.amount = '0x0' + this.amount.slice(2);
}
this.amountLength = big(utils.hexDataLength(this.amount) || 1);
this.owner = owner;
this.tokenID = tokenID;
this.digest = digest;
this.expiry = expiry;
this.returnWitnessIndex = returnWitnessIndex;
this.types = [
'uint8',
'uint8',
'uint' + this.amountLength.mul(8).toString(),
'address',
'uint32',
'bytes32',
'uint32',
'uint8',
];
this.values = [this.type, this.amountLength, this.amount, this.owner,
this.tokenID, digest, expiry, big(returnWitnessIndex)];
this.encoded = utils.solidityPack(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionUnsigned({ inputs, outputs }) { // Array, Array
// Enforce Object
assertNew(this, TransactionUnsigned);
// Enforce Types
TypeArray(inputs);
TypeArray(outputs);
// Construct
this.inputsLength = new TransactionLengthSpecifier(inputs.length);
this.outputLength = new TransactionLengthSpecifier(outputs.length);
this.inputs = inputs;
this.outputs = outputs;
this.encoded = encodeProofs([ this.inputsLength, this.outputLength, this.inputs, this.outputs ]);
this.hash = utils.keccak256(this.encoded);
this.rlp = witnesses => RLP.encode([
this.inputs.map(v => serializeRLP(v.values)),
this.outputs.map(v => serializeRLP(v.values)),
witnesses.map(v => serializeRLP(v.values)),
]);
// Transaction Total bytes length
this.length = utils.hexDataLength(this.encoded);
}
function EmptyTransactionLeaf(length) {
// Enforce Object
assertNew(this, EmptyTransactionLeaf);
TypeNumber(length);
const hexLength = big(length || 0).toHexString();
this.encoded = utils.hexZeroPad(hexLength, 2) + utils.hexZeroPad('0x', big(hexLength).sub(2).toNumber()).slice(2);
this.hash = length === 0 ? emptyBytes32 : utils.keccak256(this.encoded);
this.length = length;
}
function RealEmptyTransactionLeaf(length) {
// Enforce Object
assertNew(this, RealEmptyTransactionLeaf);
TypeNumber(length);
const hexLength = big(length || 2).toHexString(); // 2 for length
this.encoded = utils.hexZeroPad(hexLength, 2) + utils.hexZeroPad('0x', big(hexLength).sub(2).toNumber()).slice(2);
this.hash = length === 0 ? emptyBytes32 : utils.keccak256(this.encoded);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionLeaf({ metadata, witnesses, unsignedTransaction }) { // Array, Array, Object
// Enforce Object
assertNew(this, TransactionLeaf);
// Type Enforcement
TypeArray(metadata);
TypeArray(witnesses);
TypeObject(unsignedTransaction);
// Construct Leaf
this.metadata = metadata;
this.witnesses = witnesses;
this.unsignedTransaction = unsignedTransaction;
// Lengths
this.metadataLength = new TransactionLengthSpecifier(this.metadata.length);
this.witnessesLength = new TransactionLengthSpecifier(this.witnesses.length);
// Encoded Leaf data
this.encodedLeaf = encodeProofs([
this.metadataLength,
this.metadata,
this.witnessesLength,
this.witnesses,
this.unsignedTransaction,
]);
// Length is Encoded Leaf length + 2 for it's own length
this.transactionLength = new TransactionLength(utils.hexDataLength(this.encodedLeaf) + 2);
// include everythign
this.encoded = this.transactionLength.encoded + this.encodedLeaf.slice(2);
// Leaf Hash / Hash ID (includes length)!
this.hash = utils.keccak256(this.encoded);
this.hashID = this.unsignedTransaction.hash;
// Transaction Total bytes length
this.length = utils.hexDataLength(this.encoded);
}
// UTXO proofs
function UTXOProof({ transactionHashId, outputIndex, type, amount, owner,
_ownerAddress, tokenID, digest, expiry, returnWitness,
returnWitnessIndex, block, root, transactionIndex }) {
// Enforce Object
assertNew(this, UTXOProof);
// Enforce Types
TypeHex(transactionHashId, 32);
TypeNumber(outputIndex);
TypeNumber(type);
TypeBigNumber(amount);
if (type === FuelOutputTypes.HTLC) {
TypeDefined(digest);
TypeDefined(expiry);
}
if (typeof owner !== 'number') { TypeAddress(owner); }
TypeBigNumber(tokenID);
if (digest) { TypeHex(digest, 32); }
if (expiry) { TypeBigNumber(expiry); }
if (returnWitness || returnWitnessIndex) { TypeNumber(returnWitness || returnWitnessIndex || 0); }
if (typeof block !== 'undefined') { TypeInstance(block, BlockHeader); }
if (typeof root !== 'undefined') { TypeInstance(root, TransactionRootHeader); }
if (typeof transactionIndex !== 'undefined') { TypeNumber(transactionIndex); }
if (typeof _ownerAddress !== 'undefined') { TypeAddress(_ownerAddress); }
this.transactionHashId = transactionHashId;
this.outputIndex = outputIndex;
this.type = type;
this.amount = amount;
this.owner = owner;
this.tokenID = reduceTokenID(tokenID);
this.digest = digest || emptyBytes32;
this.expiry = expiry || big(0);
this.returnWitness = returnWitness || returnWitnessIndex || 0;
this._ownerAddress = _ownerAddress || (typeof this.owner !== 'number' ? this.owner : emptyAddress);
// Construct TYpes
this.types = [
'bytes32', // - transactionHashId [32 bytes]
'uint8', // - outputIndex [32 bytes]
'uint8', // - type [32 bytes] -- padded uint8
'uint256', // - amount [32 bytes]
(typeof owner === 'number') ? 'uint8' : 'address', // - owner [32 bytes]
'uint32', // - tokenID [32 bytes] -- padded uint32
'bytes32', // - digest [32 bytes]
'uint32', // - expiry [32 bytes] -- padded uint32
'uint8', // - return witness index [32 bytes] -- padded uint8
];
// Construct
this.values = [transactionHashId, outputIndex, type, amount, owner,
this.tokenID, this.digest, this.expiry, this.returnWitness];
this.rlp = () => RLP.encode([
[
this.transactionHashId,
big(this.outputIndex).toHexString(),
big(this.type).toHexString(),
this.amount,
big(this.owner).toHexString(),
this.tokenID,
this.digest,
this.expiry,
big(this.returnWitness).toHexString(),
this._ownerAddress,
],
block ? block.height : '0x0',
block ? block.hash : '0x0',
root ? big(root.index).toHexString() : '0x0',
transactionIndex ? big(transactionIndex).toHexString() : '0x0'
]);
this.encoded = abi.encode(this.types, this.values);
this.hash = utils.keccak256(this.encoded);
this.length = utils.hexDataLength(this.encoded);
}
// Decode UTXO
function decodeUTXORLP(entryRLP) {
if (!entryRLP) return null;
TypeString(entryRLP);
// Deconstruct Entry
const rlp = RLP.decode(entryRLP);
// utxo entry
const utxoEntry = rlp[0];
const blockHeight = big(rlp[1]);
const blockHash = rlp[2];
const transactionRootIndex = big(rlp[3]);
const transactionIndex = big(rlp[4]);
const type = big(utxoEntry[2]).toNumber();
// RLP Decoded (we dont fully construct these until we need to)
return {
proof: {
transactionHashId: utxoEntry[0],
outputIndex: big(utxoEntry[1]).toNumber(),
type,
amount: big(utxoEntry[3]),
owner: type === FuelInputTypes.Change ? big(utxoEntry[4]).toNumber() : utxoEntry[4],
tokenID: big(utxoEntry[5]),
digest: utxoEntry[6],
expiry: big(utxoEntry[7]),
returnWitness: big(utxoEntry[8]).toNumber(),
_ownerAddress: utxoEntry[9],
_kind: 'utxo',
},
blockHeight,
blockHash,
transactionRootIndex,
transactionIndex,
};
}
// Decode Deposit
function decodeDepositRLP(entryRLP) {
// If not found, return null
if (!entryRLP) { return null; }
TypeString(entryRLP);
// RLP decode
const entry = RLP.decode(entryRLP);
// Return structure
return {
proof: {
account: entry[0],
token: entry[1],
ethereumBlockNumber: big(entry[2]),
tokenID: big(entry[3]),
amount: big(entry[4]),
_kind: 'deposit',
_ownerAddress: entry[0],
},
};
}
// get utxo from database
async function getUTXO(db, utxoID) {
try {
// Enforce Types
TypeDB(db);
TypeHex(utxoID, 32);
// get entry
const entry = await db.get(FuelDBKeys.UTXO + utxoID.toLowerCase().slice(2));
// Decode UTXO
return decodeUTXORLP(entry);
} catch (error) {
throw new ByPassError(error);
}
}
async function getDeposit(db, depositHashID) {
try {
// Type Enforcement
TypeDB(db);
TypeHex(depositHashID, 32);
// Decode RLP
const entry = await db.get(FuelDBKeys.deposit + depositHashID.toLowerCase().slice(2));
// Decode Deposit Entry
return decodeDepositRLP(entry);
} catch (error) {
throw new ByPassError(error);
}
}
// Encode Multiple Proofs
function encodeProofs(proofs) {
return '0x' + proofs.map(proof => {
// Flatten
if (Array.isArray(proof)) {
return encodeProofs(proof).slice(2);
}
// Normal encoding
return proof.encoded.slice(2);
}).join('');
}
// Transaction Fraud Proofs
function TransactionProofLengths(values) {
// Enforce Object
assertNew(this, TransactionProofLengths);
this.types = [
'uint16', // - blockHeaderlength [32 bytes] -- padded uint16
'uint16', // - transactionRootHeaderLength [32 bytes] -- padded uint16
'uint16', // - transactionDataLength [32 bytes] -- padded uint16
'uint16', // - merkleProofLength [32 bytes] -- padded uint16
'uint16', // - utxoProofsLength [32 bytes] -- padded uint16
];
this.values = values;
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Transaction Proofs
function TransactionProofs(proofs) {
// Enforce Object
assertNew(this, TransactionProofs);
// Enforce Types
TypeArray(proofs);
// Construct
this.encoded = encodeProofs(proofs);
this.length = utils.hexDataLength(this.encoded);
}
function EmptyUTXOProofs() {
// Enforce Object
assertNew(this, EmptyUTXOProofs);
this.encoded = '0x';
this.length = utils.hexDataLength(this.encoded);
}
// Fraud proofs
function TransactionProof({ block, root, merkle, transaction, proofs, message }) {
// Enforce Object
assertNew(this, TransactionProof);
// Enforce Types
TypeInstance(block, BlockHeader);
TypeInstance(root, TransactionRootHeader);
TypeObject(merkle);
TypeInstance(transaction, TransactionData);
if (proofs !== 0) { TypeInstance(proofs, TransactionProofs); }
// Properties
this.block = block;
this.root = root;
this.merkle = merkle;
this.transaction = transaction;
this.message = message || null;
// Include Proofs
if (proofs) {
this.proofs = proofs;
} else {
this.proofs = new EmptyUTXOProofs();
}
// Construct lengths
this.lengths = new TransactionProofLengths([
this.block.length,
this.root.length,
this.merkle.length,
this.transaction.length,
this.proofs.length,
]);
// values
this.values = [this.lengths, this.block, this.root, this.merkle, this.transaction, this.proofs];
// include everythign
this.encoded = encodeProofs(this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Construct Merkle Tree Root
function constructMerkleTreeRoot(transactionLeafs) {
// Leafs
TypeArray(transactionLeafs);
// Hashes
let hashes = transactionLeafs.map(leaf => leaf.hash);
let swap = [];
// Merklization Routine (inefficient)
for (var i = 0; hashes.length > 0; i++) { // depth
const hash = hashes[i];
// if its uneven, pad with zero hash
if (hashes.length % 2 > 0) {
hashes.push(emptyBytes32);
}
for (var z = 0; z < hashes.length; z += 2) { // do hashes (2 hashes together)
// push master hash
swap.push(utils.keccak256(hashes[z] + hashes[z + 1].slice(2)));
}
// switch places to next height
hashes = swap;
swap = []; // clear swap
// shim 1 to zero (stop), i.e. top height end..
if (hashes.length < 2) {
// Hashes length
break;
}
}
// Swap zero is the master hash
return hashes[0];
}
function OverflowingTransactionMerkleProof() {
// Enforce Object
assertNew(this, OverflowingTransactionMerkleProof);
this.opppositeLeafHash = emptyBytes32;
this.proof = (new Array(256)).fill(0).map(() => emptyBytes32);
this.types = [
'bytes32',
'bytes32[]',
];
this.values = [this.opppositeLeafHash, this.proof];
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function TransactionMerkleProof({ transactionLeafs, transactionIndex }) {
// Enforce Object
assertNew(this, TransactionMerkleProof);
// Enforce
TypeArray(transactionLeafs);
TypeNumber(transactionIndex);
// Construct
let hashes = transactionLeafs.map(leaf => leaf.hash);
if (hashes.length % 2 > 0) {
hashes.push(emptyBytes32);
}
let oppositeLeafHash = hashes[transactionIndex]; // select actual leaf hash
let masterHash = oppositeLeafHash;
let swap = [];
let proof = [];
let leftish = false;
// Merklization Routine (inefficient)
for (var i = 0; hashes.length > 0; i++) { // depth
// if its uneven, pad with zero hash
if (hashes.length % 2 > 0) {
hashes.push(emptyBytes32);
}
for (var z = 0; z < hashes.length; z += 2) { // do hashes (2 hashes together)
// depth hash
let depthHash = utils.keccak256(hashes[z]
+ hashes[z + 1].slice(2));
// push proof up
if (hashes[z] === masterHash) {
proof.push(hashes[z + 1]);
masterHash = depthHash;
if (z < hashes.length) {
leftish = true;
}
}
// push proof up rightish
if (hashes[z + 1] === masterHash) {
proof.push(hashes[z]);
masterHash = depthHash;
}
// push master hash
swap.push(depthHash);
}
// switch places to next height
hashes = swap;
swap = []; // clear swap
// shim 1 to zero (stop), i.e. top height end..
if (hashes.length < 2) {
// Hashes length
break;
}
}
this.leftish = leftish;
this.oppositeLeafHash = oppositeLeafHash;
this.transactionHash = oppositeLeafHash;
this.proof = proof;
this.types = [
'bytes32',
'bytes32[]',
];
this.values = [this.oppositeLeafHash, this.proof];
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Empty Proof
function EmptyProof() {
// Enforce Object
assertNew(this, EmptyProof);
this.values = '0x';
this.encoded = '0x';
this.length = 0;
}
// Hex fill proof
function FillProof(data) {
// Enforce Object
assertNew(this, FillProof);
TypeHex(data);
this.values = data;
this.encoded = data;
this.hash = utils.keccak256(this.encoded);
this.length = utils.hexDataLength(this.encoded);
}
// Block Transaction Encoding
function BlockTransactions(transactionLeafs) {
// Enforce Object
assertNew(this, BlockTransactions);
TypeArray(transactionLeafs);
// Construct Block Transactions
this.values = transactionLeafs;
this.leafs = transactionLeafs;
this.encoded = encodeProofs(transactionLeafs);
this.hash = utils.keccak256(this.encoded);
if (this.encoded === '0x0') throw new Error('invalid hex 0x0');
// Length
this.length = utils.hexDataLength(this.encoded);
// Merkle Proof
this.merkleProof = transactionIndex => new TransactionMerkleProof({
transactionLeafs: this.leafs,
transactionIndex,
});
this.get = transactionIndex => {
if (transactionIndex >= this.leafs.length) {
return new EmptyTransactionLeaf(0);
} else {
return this.leafs[transactionIndex]
}
};
this.rightMostIndex = () => this.leafs.length % 2 > 0 ? this.leafs.length : this.leafs.length - 1;
this.rightMost = () => this.leafs[rightMostIndex()];
// check for empty transactions case
if (this.length !== 0) {
this.merkleTreeRoot = () => constructMerkleTreeRoot(transactionLeafs);
} else {
this.merkleTreeRoot = () => emptyBytes32;
}
}
// Malformed Block Fraud Proofs Lengths Proof
function BlockFraudProofLengths(values) {
assertNew(this, BlockFraudProofLengths);
this.types = [
'uint16', // - blockHeaderlength [32 bytes] -- padded uint16
'uint16', // - transactionRootHeaderLength [32 bytes] -- padded uint16
'uint16', // - transactionsLength [32 bytes] -- padded uint16
];
this.values = values;
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function BytesEncode(proof) {
assertNew(this, BytesEncode);
this.encoded = abi.encode(['bytes'], [proof.encoded]);
this.length = utils.hexDataLength(this.encoded);
}
// Block Fraud Proof Construction
function BlockFraudProof({ block, root, transactions }) {
assertNew(this, BlockFraudProof);
// Type Enforcement
TypeInstance(block, BlockHeader);
TypeInstance(root, TransactionRootHeader);
TypeObject(transactions);
// TypeInstance(transactions, BlockTransactions);
const transactionsProof = new BytesEncode(transactions)
// Construct Fraud Proof
this.block = block;
this.root = root;
this.transactions = transactions;
this.lengths = new BlockFraudProofLengths([
this.block.length,
this.root.length,
transactionsProof.length,
]);
// include everythign
this.encoded = encodeProofs([this.lengths, this.block, this.root, transactionsProof]);
this.length = utils.hexDataLength(this.encoded);
}
// Proof Type
function ProofType(proofType) {
assertNew(this, ProofType);
// Enforce Types
TypeString(proofType);
if (typeof FuelProofTypes[proofType] === 'undefined') { throw new Error('Invalid Type'); }
// Construct Proof Types
this.types = [
'uint8', // proof type
];
this.values = [FuelProofTypes[proofType]];
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Malformed Block Proof
function MalformedBlockProof(invalidBlockProof) {
assertNew(this, MalformedBlockProof);
// Enforce Types
TypeInstance(invalidBlockProof, BlockFraudProof);
// Construct Proof
this.type = new ProofType('malformedBlock');
this.proof = invalidBlockProof;
this.encoded = encodeProofs([this.type, this.proof]);
this.length = utils.hexDataLength(this.encoded);
}
// Malformed Transaction Proof
function MalformedTransactionProof(transactionProof) {
assertNew(this, MalformedTransactionProof);
// Enforce Types
TypeInstance(transactionProof, TransactionProof);
// Construct Proof
this.type = new ProofType('malformedTransaction');
this.proof = transactionProof;
this.encoded = encodeProofs([this.type, this.proof]);
this.length = utils.hexDataLength(this.encoded);
}
function InvalidTransactionProof(transactionProof) {
assertNew(this, InvalidTransactionProof);
// Enforce Types
TypeInstance(transactionProof, TransactionProof);
// Construct Proof
this.type = new ProofType('invalidTransaction');
this.proof = transactionProof;
this.encoded = encodeProofs([this.type, this.proof]);
this.length = utils.hexDataLength(this.encoded);
}
function InvalidTransactionInputProof(transactionProofA, transactionProofB) {
assertNew(this, InvalidTransactionInputProof);
// Enforce Types
TypeInstance(transactionProofA, TransactionProof);
TypeInstance(transactionProofB, TransactionProof);
// Construct Proof
this.type = new ProofType('invalidTransactionInput');
this.proofA = transactionProofA;
this.proofB = transactionProofB;
this.encoded = encodeProofs([this.type, this.proofA, this.proofB]);
this.length = utils.hexDataLength(this.encoded);
}
function InvalidTransactionDoubleSpendProof(transactionProofA, transactionProofB) {
assertNew(this, InvalidTransactionDoubleSpendProof);
// Enforce Types
TypeInstance(transactionProofA, TransactionProof);
TypeInstance(transactionProofB, TransactionProof);
// Construct Proof
this.type = new ProofType('invalidTransactionDoubleSpend');
this.proofA = transactionProofA;
this.proofB = transactionProofB;
this.encoded = encodeProofs([this.type, this.proofA, this.proofB]);
this.length = utils.hexDataLength(this.encoded);
}
// Malformed Transaction Proof
function WithdrawalProof(token) {
assertNew(this, WithdrawalProof);
// Enforce Types
TypeAddress(token);
// Construct Proof Types
this.types = [
'address', // proof type
];
this.values = [token];
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Malformed Transaction Proof
function SummingProof(token) {
assertNew(this, SummingProof);
// Enforce Types
TypeAddress(token);
// Construct Proof Types
this.types = [
'address', // proof type
];
this.values = [token];
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
function DepositProof({ account, token, ethereumBlockNumber }) {
assertNew(this, DepositProof);
// Enforce Types
TypeAddress(account);
TypeAddress(token);
TypeBigNumber(ethereumBlockNumber);
// Properties
this.account = account;
this.token = token;
this.ethereumBlockNumber = ethereumBlockNumber;
// Construct Proof Types
this.types = [
'address', // proof type
'address', // proof type
'uint256', // proof type
];
this.values = [account, token, ethereumBlockNumber];
this.encoded = abi.encode(this.types, this.values);
this.length = utils.hexDataLength(this.encoded);
}
// Malformed Transaction Proof
function UserWithdrawalProof(transactionProof) {
assertNew(this, UserWithdrawalProof);
// Enforce Types
TypeInstance(transactionProof, TransactionProof);
// Construct Proof
this.type = new ProofType('userWithdrawal');
this.proof = transactionProof;
this.encoded = encodeProofs([this.type, this.proof]);
this.length = utils.hexDataLength(this.encoded);
}
// Malformed Transaction Proof
function BondWithdrawalProof(blockHeader) {
assertNew(this, BondWithdrawalProof);
// Enforce Types
TypeInstance(blockHeader, BlockHeader);
// Construct Proof
this.type = new ProofType('bondWithdrawal');
this.proof = blockHeader;
this.encoded = encodeProofs([this.type, this.proof]);
this.length = utils.hexDataLength(this.encoded);
}
// Genesis Block
function GenesisBlock() {
// Construct Genesis Block Header
return new BlockHeader({
producer: emptyAddress, // - blockProducer [32 bytes] -- padded address
previousBlockHash: emptyBytes32, // - previousBlockHash [32 bytes]
blockHeight: big(0), //- blockHeight [32 bytes]
ethereumBlockNumber: big(0), // - ethereumBlockNumber [32 bytes]
transactionRoots: [], // - transactionRoots [64 + dynamic bytes]
});
}
const reduceTokenID = tokenID => big(tokenID.toNumber());
// get Requests..
const getRequests = (db, limit = 7) => new Promise((resolve, reject) => {
let items = [];
const readSteam = db.createReadStream();
// Go through stream..
(readSteam.then ? readSteam : Promise.resolve(readSteam))
.then((stream) => {
stream
.on('error', err => reject(err))
.on('data', row => {
if (items.length >= limit) {
resolve(items);
readSteam.destroy();
return;
}
// If data is an address, get it some tokens!
if (utils.hexDataLength(row.value, 20)) {
items.push({ ip: row.key.slice(2), key: row.key, address: row.value }); // slice IP away
}
})
.on('end', () => {
resolve(items);
});
})
.catch(reject);
});
// get outputs
const getSpendableInputs = (db, limit = 7) => new Promise((resolve, reject) => {
let items = [];
const readSteam = db.createReadStream();
(readSteam.then ? readSteam : Promise.resolve(readSteam))
.then((stream) => {
stream
.on('error', err => reject(err))
.on('data', row => {
if (items.length >= limit) {
resolve(items);
return;
}
items.push(row);
})
.on('end', () => {
resolve(items);
});
})
.catch(reject);
});
// Empty Logger Method
function EmptyLogger() {
assertNew(this, EmptyLogger);
this.log = () => {};
this.time = () => {};
this.timeEnd = () => {};
this.error = () => {};
}
// Last Ethereum block processed.
async function lastEthereumBlockProcessed(opts = {}) {
try {
TypeObject(opts);
TypeObject(opts.db);
// get last block processes
const ethereumBlockProcessed = await opts.db.get(interfaces.FuelDBKeys.ethereumBlockProcessed);
// If no log, return null..
if (!ethereumBlockProcessed) { return null; }
// If there is a from block saved
return _utils.big(ethereumBlockProcessed);
} catch (error) {
throw new ByPassError(error);
}
}
// Get Genesis Log of a Fuel Contract
async function genesisLog(opts = {}) {
try {
TypeObject(opts);
TypeObject(opts.contract);
TypeFunction(opts.rpc);
// Genesis Log
const genesisLog = (await opts.rpc('eth_getLogs', {
address: opts.contract.address,
fromBlock: '0x0',
toBlock: 'latest',
topics: interfaces.FuelEventsInterface.events.BlockCommitted.encodeTopics([
null, 0, 0,
]),
})).pop();
if (!genesisLog) {
throw new ByPassError('No genesis log found for this Fuel contract.');
}
// Format return log block number...
const returnLog = Object.assign({}, genesisLog, {
blockNumber: _utils.big(genesisLog.blockNumber)
});
// block height
return returnLog;
} catch (error) {
throw new ByPassError(error);
}
}
// get Latest published fraud log
async function latestFraudLog(opts = {}) {
try {
TypeObject(opts);
TypeObject(opts.contract);
TypeFunction(opts.rpc);
// Last Fraud Block
const lastFraudBlockProcessed = await opts.db.get(interfaces.FuelDBKeys.lastEthereumFraudBlock);
// Covert last procssed fraud block to _utils.big num
if (lastFraudBlockProcessed) {
// set last fraud block
lastEthereumFraudBlock = _utils.big(_utils.RLP.decode(lastFraudBlockProcessed));
}
// Get lastest fraud block it knows about
if (!lastFraudBlockProcessed) {
// Get fraud logs on boot
const latestFraudLog = (await opts.rpc('eth_getLogs', {
address: opts.contract.address,
fromBlock: '0x0',
toBlock: 'latest',
topics: [ interfaces.FuelEventsInterface
.events.FraudCommitted.topic ],
}) || []).pop();
// Return latest fraud log
return latestFraudLog ? Object.assign({}, latestFraudLog, {
blockNumber: _utils.big(latestFraudLog.blockNumber),
}) : null;
}
// Null if nothing..
return null;
} catch (error) {
throw new ByPassError(error);
}
}
// Utility Method
function resolveIfPromise(v) {
return v.then ? v : Promise.resolve(v);
}
const toArr = require('../utils/toArray');
const getMempoolTransactions = async (mempool,
mempoolAge,
maximumStreamSize = 2000) => {
try {
// Types
types.TypeDB(mempool);
types.TypeNumber(mempoolAge);
types.TypeNumber(maximumStreamSize);
// Start Time
const maximumAge = _utils.unixtime() - mempoolAge;
// Transactions
let includedTransactions = {}; // tx hash => bool
let toBeIncluded = [];
let reads = []; // utxo's to grab
let promises = [];
let oldestTransactionAge = 0; // oldest tx hash
// Filter for transaciton data
const transform = data => {
// If it's the commitment entry, ignore it
if (data.key === interfaces.FuelDBKeys.commitment) { return null; }
// Included in previous blocks, ignore
if (data.dependancy === true && data.value === null) { return null; }
// Decode entry, get age,
const value = _utils.RLP.decode(data.value);
const created = parseInt(value[5], 16);
const requiredTransactionHashes = value[7]; // hash => bool
const transactionHash = value[0];
const precisionTime = parseInt(value[8], 16);
const utxoReads = value[6];
// Included txs
if (includedTransactions[transactionHash] === true) { return null };
// console.log('Created tx hash', transactionHash, created < maximumAge);
// Tx age is newer than maximum age, than don't include it
if (created > maximumAge) { return null; }
// Add the utxo to the reads
for (var r = 0; r < utxoReads.length; r++) {
reads.push(utxoReads[r]);
}
// Reset oldest age
if (created < oldestTransactionAge || oldestTransactionAge === 0) {
oldestTransactionAge = created;
}
// Include this as to not be included again
includedTransactions[value[0]] = true;
// Dependancies
for (var i = 0; i < requiredTransactionHashes.length; i++) {
if (!includedTransactions[requiredTransactionHashes[i].toLowerCase()]) { // maybe use positive / continue statement here.
toBeIncluded.push(interfaces.FuelDBKeys.mempoolTransaction
+ requiredTransactionHashes[i].toLowerCase().slice(2));
}
}
// console.log('Get mempool tx hash', transactionHash);
// Return true
return { key: data.key, value, created, precisionTime };
};
// console.log('Get mempool transactions');
// Transactions
const mempoolStream = await resolveIfPromise(mempool.createReadStream());
let transactions = await toArr(mempoolStream, maximumStreamSize, transform); // transactions array
// Grab all dependancies / if any
while (toBeIncluded.length) {
for (var i = 0; i < toBeIncluded.length; i++) {
const entry = transform({
key: toBeIncluded[i],
value: await mempool.get(toBeIncluded[i]),
dependancy: true,
});
const entryDB = await mempool.get(toBeIncluded[i]);
/* console.log('To be included / was null',
'0x' + toBeIncluded[i].slice(4),
includedTransactions['0x' + toBeIncluded[i].slice(4)] === true,
entryDB === null, entry === null); */
// Entry
if (entry !== null) {
transactions.push(entry);
}
toBeIncluded = toBeIncluded
.filter(key => key !== toBeIncluded[i]);
}
}
/* console.log('Txs included / txs arr',
Object.keys(includedTransactions).length,
transactions.length,
maximumStreamSize); */
// Reurn
return {
mempoolTransactions: transactions
.sort((a, b) => a.precisionTime - b.precisionTime), // organize by timestamp
oldestTransactionAge,
reads,
};
} catch (error) {
throw new ByPassError(error);
}
};
// Organize mempool transactions into root blocks
function mempoolToRoots(proposedTip, submissionProducer,
mempoolTransactions, checkUTXOs) {
types.TypeBigNumber(proposedTip);
types.TypeAddress(submissionProducer);
types.TypeArray(mempoolTransactions);
types.TypeObject(checkUTXOs);
// Define alterable variables
let placement = {}; // transactionHashId => { transactionRootIndex, transactionIndex },
let roots = { '0': [], }; // root index => root array []
let numRoots = 1;
let transactionRootIndex = 0;
let currentRootSize = 0;
let transactionIndex = 0;
let resultRoots = [];
let hashes = [[]];
// organize by unixtime here..
// mempoolTransactions.map(v => console.log(v.precisionTime));
// Placement of Transactions Across Roots
for (var mempoolIndex = 0;
mempoolIndex < mempoolTransactions.length;
mempoolIndex++) {
// Mempool Transaction
const mempoolTransactionHashKey = mempoolTransactions[mempoolIndex].key;
const mempoolTransaction = mempoolTransactions[mempoolIndex].value;
// Transaction Hash
const transactionHash = mempoolTransaction[0];
const unsignedTransaction = new FillProof(mempoolTransaction[1]);
const witnesses = mempoolTransaction[2];
const metadata = mempoolTransaction[3];
// Estimated Size
const estimatedSize = mempoolTransaction[1].length
+ (witnesses.length * interfaces.FuelConstants.WitnessSize)
+ (metadata.length * interfaces.FuelConstants.MetadataSize);
// Root size + estimated size, than create a new root
if ((currentRootSize + estimatedSize) >= 15000 /*interfaces.FuelConstants.MAX_TRANSACTIONS_SIZE)*/) {
transactionRootIndex += 1;
currentRootSize = 0;
transactionIndex = 0;
numRoots += 1;
roots[transactionRootIndex] = [];
hashes[transactionRootIndex] = [];
}
// console.log('Plecement tx hash', transactionHash, transactionRootIndex, transactionIndex);
// placement map this tx hash
placement[transactionHash] = {
transactionRootIndex,
transactionIndex,
};
// Hash key
hashes[transactionRootIndex].push(mempoolTransactionHashKey);
// Process Into Leaf
roots[transactionRootIndex][transactionIndex] = transactionPlacement => ({
unsignedTransaction,
metadata: metadata.map(data => {
if (data.length === 3) {
const transactionHashId = data[0];
const outputIndex = data[1];
const utxoId = data[2];
const utxoDB = checkUTXOs[interfaces.FuelDBKeys.UTXO
+ utxoId.slice(2)] || null;
let utxo_blockNum = proposedTip;
let utxo_rootIndex = null;
let utxo_txIndex = null;
// If this is no longer a mempool tx, we than use the db version
if (utxoDB !== null) {
const decodeUTXO = _utils.RLP.decode(utxoDB);
utxo_blockNum = _utils.big(decodeUTXO[1]); // 2 is block hash..
utxo_rootIndex = _utils.big(decodeUTXO[3]);
utxo_txIndex = _utils.big(decodeUTXO[4]);
} else {
// console.log('Requested hash ID', transactionHashId);
utxo_rootIndex = _utils.big(transactionPlacement[transactionHashId]
.transactionRootIndex);
utxo_txIndex = _utils.big(transactionPlacement[transactionHashId]
.transactionIndex);
}
return new TransactionMetadata({ // tx referenc