Skip to content

Instantly share code, notes, and snippets.

@johnnieskywalker
Last active March 9, 2024 12:27
Show Gist options
  • Save johnnieskywalker/c56c89de45d378b44d2b77e21c764c0b to your computer and use it in GitHub Desktop.
Save johnnieskywalker/c56c89de45d378b44d2b77e21c764c0b to your computer and use it in GitHub Desktop.
txo
const { assert } = require('chai');
const TXO = require('../TXO');
describe('TXO', function () {
const address = "1DBS97W3jWw6FnAqdduK1NW6kFo3Aid1N6";
const amount = 10;
const txo = new TXO(address, amount);
describe('constructor', () => {
it('should set the owner', () => {
assert.equal(txo.owner, address);
});
it('should set the amount', () => {
assert.equal(txo.amount, amount);
});
it('should set spent to false', () => {
assert.equal(txo.spent, false);
});
});
describe('spend', () => {
it('should set spent to true', () => {
txo.spend();
assert.equal(txo.spent, true);
});
});
});
const { assert } = require('chai');
const Transaction = require('../Transaction');
const TXO = require('../TXO');
describe('Transaction', function () {
const fromAddress = "1DBS97W3jWw6FnAqdduK1NW6kFo3Aid1N6";
const toAddress = "12ruWjb4naCME5QhjrQSJuS5disgME22fe";
describe('with no remainder', () => {
const txo1 = new TXO(fromAddress, 5);
const txo2 = new TXO(fromAddress, 5);
const outputTXO1 = new TXO(toAddress, 7);
const outputTXO2 = new TXO(fromAddress, 3);
const tx = new Transaction([txo1, txo2], [outputTXO1, outputTXO2]);
tx.execute();
it('should have zero fee', () => {
assert.equal(tx.fee, 0);
});
});
describe('with some remainder', () => {
const txo1 = new TXO(fromAddress, 15);
const outputTXO1 = new TXO(toAddress, 7);
const outputTXO2 = new TXO(fromAddress, 6);
const tx = new Transaction([txo1], [outputTXO1, outputTXO2]);
tx.execute();
it('should have the remainder as the fee', () => {
assert.equal(tx.fee, 2);
});
});
});
const { assert } = require('chai');
const Transaction = require('../Transaction');
const TXO = require('../TXO');
describe('Transaction', function () {
const fromAddress = "1DBS97W3jWw6FnAqdduK1NW6kFo3Aid1N6";
const toAddress = "12ruWjb4naCME5QhjrQSJuS5disgME22fe";
describe('with sufficient UTXOs', () => {
const txo1 = new TXO(fromAddress, 5);
const txo2 = new TXO(fromAddress, 5);
const outputTXO1 = new TXO(toAddress, 10);
const tx = new Transaction([txo1, txo2], [outputTXO1]);
it('should execute without error', () => {
try {
tx.execute();
}
catch (ex) {
assert.fail(ex.message);
console.log(ex);
}
});
});
describe('with insufficient UTXOs', () => {
const txo1 = new TXO(fromAddress, 3);
const txo2 = new TXO(fromAddress, 3);
const txo3 = new TXO(fromAddress, 3);
const outputTXO1 = new TXO(toAddress, 10);
const tx = new Transaction([txo1, txo2, txo3], [outputTXO1]);
it('should throw an error on execute', () => {
let ex;
try {
tx.execute();
}
catch (_ex) {
ex = _ex;
}
assert(ex, "Did not throw an exception for a lack of UTXO funds!");
});
});
});
const { assert } = require('chai');
const Transaction = require('../Transaction');
const TXO = require('../TXO');
describe('Transaction', function () {
const fromAddress = "1DBS97W3jWw6FnAqdduK1NW6kFo3Aid1N6";
const toAddress = "12ruWjb4naCME5QhjrQSJuS5disgME22fe";
describe('with unspent input TXOs', () => {
const inputTXO1 = new TXO(fromAddress, 5);
const inputTXO2 = new TXO(fromAddress, 5);
const outputTXO1 = new TXO(toAddress, 10);
const tx = new Transaction([inputTXO1, inputTXO2], [outputTXO1]);
it('should execute without error', () => {
try {
tx.execute();
}
catch(ex) {
assert.fail(ex.message);
console.log(ex);
}
});
});
describe('with a spent input TXO', () => {
const txo1 = new TXO(fromAddress, 5);
const txo2 = new TXO(fromAddress, 5);
const txo3 = new TXO(fromAddress, 5);
const outputTXO1 = new TXO(toAddress, 15);
txo2.spend();
const tx = new Transaction([txo1, txo2, txo3], [outputTXO1]);
it('should throw an error on execute', () => {
let ex;
try {
tx.execute();
}
catch (_ex) {
ex = _ex;
}
assert(ex, "Did not throw an exception with a spent input TXO!");
});
});
});
const { assert } = require('chai');
const Transaction = require('../Transaction');
const TXO = require('../TXO');
describe('Transaction', function () {
const fromAddress = "1DBS97W3jWw6FnAqdduK1NW6kFo3Aid1N6";
const toAddress = "12ruWjb4naCME5QhjrQSJuS5disgME22fe";
const txo1 = new TXO(fromAddress, 5);
const txo2 = new TXO(fromAddress, 5);
const txo3 = new TXO(fromAddress, 3);
const txo4 = new TXO(fromAddress, 4);
const outputTXO1 = new TXO(toAddress, 7);
const outputTXO2 = new TXO(fromAddress, 3);
it('should mark both inputs as spent', () => {
const tx = new Transaction([txo1, txo2], [outputTXO1, outputTXO2]);
tx.execute();
assert(txo1.spent);
assert(txo2.spent);
});
it('should leave both inputs unspent if funds unavailable', () => {
const tx = new Transaction([txo3, txo4], [outputTXO1, outputTXO2]);
let ex;
try {
tx.execute();
}
catch (_ex) {
ex = _ex;
}
assert(ex, "Expected an exception to be thrown!");
assert(!txo3.spent, "The transaction should be marked as unspent");
assert(!txo4.spent, "The transaction should be marked as unspent");
});
it('should leave valid inputs unspent if a double spend occurs', () => {
const tx = new Transaction([txo1, txo4], [outputTXO1, outputTXO2]);
let ex;
try {
tx.execute();
}
catch (_ex) {
ex = _ex;
}
assert(ex, "Expected an exception to be thrown!");
assert(!txo4.spent, "The transaction should be marked as unspent");
});
});
class Transaction {
constructor(inputUTXOs, outputUTXOs) {
this.inputUTXOs = inputUTXOs;
this.outputUTXOs = outputUTXOs;
}
execute() {
const sumInputUTXOs = this.inputUTXOs.reduce((acc, curr) => acc + curr.amount, 0);
const sumOutputUTXOs = this.outputUTXOs.reduce((acc, curr) => acc + curr.amount, 0);
try {
const hasSpentInput = this.inputUTXOs.some(input => input.spent);
if (hasSpentInput) {
throw new Error('One or more input UTXOs have already been spent.');
}
const sufficientInput = sumInputUTXOs >= sumOutputUTXOs;
if (!sufficientInput) {
throw new Error('Insufficient input UTXOs');
}
} catch (error) {
if (error.message === 'Insufficient input UTXOs') {
this.inputUTXOs.forEach(input => input.spent = false);
}
throw error;
}
this.inputUTXOs.forEach(input => input.spent = true);
this.fee = sumInputUTXOs - sumOutputUTXOs;
}
}
module.exports = Transaction;
class TXO {
constructor(owner, amount) {
this.owner = owner;
this.amount = amount;
this.spent = false;
}
spend() {
this.spent = true;
}
}
module.exports = TXO;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment