1489 lines
36 KiB
JavaScript
1489 lines
36 KiB
JavaScript
/* eslint-env mocha */
|
|
/* eslint prefer-arrow-callback: "off" */
|
|
|
|
'use strict';
|
|
|
|
const assert = require('./util/assert');
|
|
const consensus = require('../lib/protocol/consensus');
|
|
const util = require('../lib/utils/util');
|
|
const encoding = require('../lib/utils/encoding');
|
|
const digest = require('../lib/crypto/digest');
|
|
const random = require('../lib/crypto/random');
|
|
const WalletDB = require('../lib/wallet/walletdb');
|
|
const WorkerPool = require('../lib/workers/workerpool');
|
|
const Address = require('../lib/primitives/address');
|
|
const MTX = require('../lib/primitives/mtx');
|
|
const Coin = require('../lib/primitives/coin');
|
|
const KeyRing = require('../lib/primitives/keyring');
|
|
const Input = require('../lib/primitives/input');
|
|
const Outpoint = require('../lib/primitives/outpoint');
|
|
const Script = require('../lib/script/script');
|
|
const HD = require('../lib/hd');
|
|
|
|
const KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt'
|
|
+ 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ';
|
|
|
|
const KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp'
|
|
+ 'faKekc2m4AChLYH1XDzANhrSdxHYWUeTWjYJwFwWFyHkTMnMeAcW4JyRCZa';
|
|
|
|
const workers = new WorkerPool({
|
|
enabled: false
|
|
});
|
|
|
|
const wdb = new WalletDB({
|
|
db: 'memory',
|
|
verify: true,
|
|
workers
|
|
});
|
|
|
|
let currentWallet = null;
|
|
let importedWallet = null;
|
|
let importedKey = null;
|
|
let doubleSpendWallet = null;
|
|
let doubleSpendCoin = null;
|
|
|
|
let globalTime = util.now();
|
|
let globalHeight = 1;
|
|
|
|
function nextBlock() {
|
|
const height = globalHeight++;
|
|
const time = globalTime++;
|
|
|
|
const prevHead = encoding.U32(height - 1);
|
|
const prevHash = digest.hash256(prevHead);
|
|
|
|
const head = encoding.U32(height);
|
|
const hash = digest.hash256(head);
|
|
|
|
return {
|
|
hash: hash.toString('hex'),
|
|
height: height,
|
|
prevBlock: prevHash.toString('hex'),
|
|
time: time,
|
|
merkleRoot: encoding.NULL_HASH,
|
|
nonce: 0,
|
|
bits: 0
|
|
};
|
|
}
|
|
|
|
function dummyInput() {
|
|
const hash = random.randomBytes(32).toString('hex');
|
|
return Input.fromOutpoint(new Outpoint(hash, 0));
|
|
}
|
|
|
|
async function testP2PKH(witness, nesting) {
|
|
const flags = Script.flags.STANDARD_VERIFY_FLAGS;
|
|
|
|
const wallet = await wdb.create({
|
|
witness
|
|
});
|
|
|
|
const addr = Address.fromString(wallet.getAddress('string'));
|
|
|
|
const type = witness ? Address.types.WITNESS : Address.types.PUBKEYHASH;
|
|
assert.strictEqual(addr.type, type);
|
|
|
|
const src = new MTX();
|
|
src.addInput(dummyInput());
|
|
src.addOutput(nesting ? wallet.getNested() : wallet.getAddress(), 5460 * 2);
|
|
src.addOutput(new Address(), 2 * 5460);
|
|
|
|
const mtx = new MTX();
|
|
mtx.addTX(src, 0);
|
|
mtx.addOutput(wallet.getAddress(), 5460);
|
|
|
|
await wallet.sign(mtx);
|
|
|
|
const [tx, view] = mtx.commit();
|
|
|
|
assert(tx.verify(view, flags));
|
|
}
|
|
|
|
async function testP2SH(witness, nesting) {
|
|
const flags = Script.flags.STANDARD_VERIFY_FLAGS;
|
|
const receive = nesting ? 'nested' : 'receive';
|
|
const receiveDepth = nesting ? 'nestedDepth' : 'receiveDepth';
|
|
const vector = witness ? 'witness' : 'script';
|
|
|
|
// Create 3 2-of-3 wallets with our pubkeys as "shared keys"
|
|
const options = {
|
|
witness,
|
|
type: 'multisig',
|
|
m: 2,
|
|
n: 3
|
|
};
|
|
|
|
const alice = await wdb.create(options);
|
|
const bob = await wdb.create(options);
|
|
const carol = await wdb.create(options);
|
|
const recipient = await wdb.create();
|
|
|
|
await alice.addSharedKey(bob.account.accountKey);
|
|
await alice.addSharedKey(carol.account.accountKey);
|
|
|
|
await bob.addSharedKey(alice.account.accountKey);
|
|
await bob.addSharedKey(carol.account.accountKey);
|
|
|
|
await carol.addSharedKey(alice.account.accountKey);
|
|
await carol.addSharedKey(bob.account.accountKey);
|
|
|
|
// Our p2sh address
|
|
const addr1 = alice.account[receive].getAddress();
|
|
|
|
if (witness) {
|
|
const type = nesting ? Address.types.SCRIPTHASH : Address.types.WITNESS;
|
|
assert.strictEqual(addr1.type, type);
|
|
} else {
|
|
assert.strictEqual(addr1.type, Address.types.SCRIPTHASH);
|
|
}
|
|
|
|
assert(alice.account[receive].getAddress().equals(addr1));
|
|
assert(bob.account[receive].getAddress().equals(addr1));
|
|
assert(carol.account[receive].getAddress().equals(addr1));
|
|
|
|
const nestedAddr1 = alice.getNested();
|
|
|
|
if (witness) {
|
|
assert(nestedAddr1);
|
|
assert(alice.getNested().equals(nestedAddr1));
|
|
assert(bob.getNested().equals(nestedAddr1));
|
|
assert(carol.getNested().equals(nestedAddr1));
|
|
}
|
|
|
|
{
|
|
// Add a shared unspent transaction to our wallets
|
|
const fund = new MTX();
|
|
fund.addInput(dummyInput());
|
|
fund.addOutput(nesting ? nestedAddr1 : addr1, 5460 * 10);
|
|
|
|
// Simulate a confirmation
|
|
const block = nextBlock();
|
|
|
|
assert.strictEqual(alice.account[receiveDepth], 1);
|
|
|
|
await wdb.addBlock(block, [fund.toTX()]);
|
|
|
|
assert.strictEqual(alice.account[receiveDepth], 2);
|
|
assert.strictEqual(alice.account.changeDepth, 1);
|
|
}
|
|
|
|
const addr2 = alice.account[receive].getAddress();
|
|
assert(!addr2.equals(addr1));
|
|
|
|
assert(alice.account[receive].getAddress().equals(addr2));
|
|
assert(bob.account[receive].getAddress().equals(addr2));
|
|
assert(carol.account[receive].getAddress().equals(addr2));
|
|
|
|
// Create a tx requiring 2 signatures
|
|
const send = new MTX();
|
|
|
|
send.addOutput(recipient.getAddress(), 5460);
|
|
|
|
assert(!send.verify(flags));
|
|
|
|
await alice.fund(send, {
|
|
rate: 10000,
|
|
round: true
|
|
});
|
|
|
|
await alice.sign(send);
|
|
|
|
assert(!send.verify(flags));
|
|
|
|
await bob.sign(send);
|
|
|
|
const [tx, view] = send.commit();
|
|
assert(tx.verify(view, flags));
|
|
|
|
assert.strictEqual(alice.account.changeDepth, 1);
|
|
|
|
const change = alice.account.change.getAddress();
|
|
|
|
assert(alice.account.change.getAddress().equals(change));
|
|
assert(bob.account.change.getAddress().equals(change));
|
|
assert(carol.account.change.getAddress().equals(change));
|
|
|
|
// Simulate a confirmation
|
|
{
|
|
const block = nextBlock();
|
|
|
|
await wdb.addBlock(block, [tx]);
|
|
|
|
assert.strictEqual(alice.account[receiveDepth], 2);
|
|
assert.strictEqual(alice.account.changeDepth, 2);
|
|
|
|
assert(alice.account[receive].getAddress().equals(addr2));
|
|
assert(!alice.account.change.getAddress().equals(change));
|
|
}
|
|
|
|
const change2 = alice.account.change.getAddress();
|
|
|
|
assert(alice.account.change.getAddress().equals(change2));
|
|
assert(bob.account.change.getAddress().equals(change2));
|
|
assert(carol.account.change.getAddress().equals(change2));
|
|
|
|
const input = tx.inputs[0];
|
|
input[vector].setData(2, encoding.ZERO_SIG);
|
|
input[vector].compile();
|
|
|
|
assert(!tx.verify(view, flags));
|
|
assert.strictEqual(tx.getFee(view), 10000);
|
|
}
|
|
|
|
describe('Wallet', function() {
|
|
this.timeout(5000);
|
|
|
|
it('should open walletdb', async () => {
|
|
consensus.COINBASE_MATURITY = 0;
|
|
await wdb.open();
|
|
});
|
|
|
|
it('should generate new key and address', async () => {
|
|
const wallet = await wdb.create();
|
|
|
|
const addr1 = wallet.getAddress();
|
|
assert(addr1);
|
|
|
|
const str = addr1.toString();
|
|
const addr2 = Address.fromString(str);
|
|
|
|
assert(addr2.equals(addr1));
|
|
});
|
|
|
|
it('should validate existing address', () => {
|
|
assert(Address.fromString('1KQ1wMNwXHUYj1nV2xzsRcKUH8gVFpTFUc'));
|
|
});
|
|
|
|
it('should fail to validate invalid address', () => {
|
|
assert.throws(() => {
|
|
Address.fromString('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc');
|
|
});
|
|
});
|
|
|
|
it('should create and get wallet', async () => {
|
|
const wallet1 = await wdb.create();
|
|
|
|
await wallet1.destroy();
|
|
|
|
const wallet2 = await wdb.get(wallet1.id);
|
|
|
|
assert(wallet1 !== wallet2);
|
|
assert(wallet1.master !== wallet2.master);
|
|
assert(wallet1.master.key.equals(wallet2.master.key));
|
|
assert(wallet1.account.accountKey.equals(wallet2.account.accountKey));
|
|
});
|
|
|
|
it('should sign/verify p2pkh tx', async () => {
|
|
await testP2PKH(false, false);
|
|
});
|
|
|
|
it('should sign/verify p2wpkh tx', async () => {
|
|
await testP2PKH(true, false);
|
|
});
|
|
|
|
it('should sign/verify p2wpkh tx w/ nested bullshit', async () => {
|
|
await testP2PKH(true, true);
|
|
});
|
|
|
|
it('should multisign/verify TX', async () => {
|
|
const wallet = await wdb.create({
|
|
type: 'multisig',
|
|
m: 1,
|
|
n: 2
|
|
});
|
|
|
|
const xpriv = HD.PrivateKey.generate();
|
|
const key = xpriv.deriveAccount(44, 0).toPublic();
|
|
|
|
await wallet.addSharedKey(key);
|
|
|
|
const script = Script.fromMultisig(1, 2, [
|
|
wallet.account.receive.getPublicKey(),
|
|
key.derivePath('m/0/0').publicKey
|
|
]);
|
|
|
|
// Input transaction (bare 1-of-2 multisig)
|
|
const src = new MTX();
|
|
src.addInput(dummyInput());
|
|
src.addOutput(script, 5460 * 2);
|
|
src.addOutput(new Address(), 5460 * 2);
|
|
|
|
const tx = new MTX();
|
|
tx.addTX(src, 0);
|
|
tx.addOutput(wallet.getAddress(), 5460);
|
|
|
|
const maxSize = await tx.estimateSize();
|
|
|
|
await wallet.sign(tx);
|
|
|
|
assert(tx.toRaw().length <= maxSize);
|
|
assert(tx.verify());
|
|
});
|
|
|
|
it('should handle missed and invalid txs', async () => {
|
|
const alice = await wdb.create();
|
|
const bob = await wdb.create();
|
|
|
|
// Coinbase
|
|
// balance: 51000
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(alice.getAddress(), 50000);
|
|
t1.addOutput(alice.getAddress(), 1000);
|
|
|
|
const t2 = new MTX();
|
|
t2.addTX(t1, 0); // 50000
|
|
t2.addOutput(alice.getAddress(), 24000);
|
|
t2.addOutput(alice.getAddress(), 24000);
|
|
|
|
// Save for later.
|
|
doubleSpendWallet = alice;
|
|
doubleSpendCoin = Coin.fromTX(t1, 0, -1);
|
|
|
|
// balance: 49000
|
|
await alice.sign(t2);
|
|
|
|
const t3 = new MTX();
|
|
t3.addTX(t1, 1); // 1000
|
|
t3.addTX(t2, 0); // 24000
|
|
t3.addOutput(alice.getAddress(), 23000);
|
|
|
|
// balance: 47000
|
|
await alice.sign(t3);
|
|
|
|
const t4 = new MTX();
|
|
t4.addTX(t2, 1); // 24000
|
|
t4.addTX(t3, 0); // 23000
|
|
t4.addOutput(alice.getAddress(), 11000);
|
|
t4.addOutput(alice.getAddress(), 11000);
|
|
|
|
// balance: 22000
|
|
await alice.sign(t4);
|
|
|
|
const f1 = new MTX();
|
|
f1.addTX(t4, 1); // 11000
|
|
f1.addOutput(bob.getAddress(), 10000);
|
|
|
|
// balance: 11000
|
|
await alice.sign(f1);
|
|
|
|
const fake = new MTX();
|
|
fake.addTX(t1, 1); // 1000 (already redeemed)
|
|
fake.addOutput(alice.getAddress(), 500);
|
|
|
|
// Script inputs but do not sign
|
|
await alice.template(fake);
|
|
// Fake signature
|
|
const input = fake.inputs[0];
|
|
input.script.setData(0, encoding.ZERO_SIG);
|
|
input.script.compile();
|
|
// balance: 11000
|
|
|
|
// Fake TX should temporarily change output.
|
|
{
|
|
await wdb.addTX(fake.toTX());
|
|
await wdb.addTX(t4.toTX());
|
|
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 22500);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 72500);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t2.toTX());
|
|
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 46500);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t3.toTX());
|
|
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 22000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(f1.toTX());
|
|
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 11000);
|
|
|
|
const txs = await alice.getHistory();
|
|
assert(txs.some((wtx) => {
|
|
return wtx.hash === f1.hash('hex');
|
|
}));
|
|
}
|
|
|
|
{
|
|
const balance = await bob.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 10000);
|
|
|
|
const txs = await bob.getHistory();
|
|
assert(txs.some((wtx) => {
|
|
return wtx.tx.hash('hex') === f1.hash('hex');
|
|
}));
|
|
}
|
|
});
|
|
|
|
it('should cleanup spenders after double-spend', async () => {
|
|
const wallet = doubleSpendWallet;
|
|
|
|
{
|
|
const txs = await wallet.getHistory();
|
|
assert.strictEqual(txs.length, 5);
|
|
|
|
const total = txs.reduce((t, wtx) => {
|
|
return t + wtx.tx.getOutputValue();
|
|
}, 0);
|
|
assert.strictEqual(total, 154000);
|
|
}
|
|
|
|
{
|
|
const balance = await wallet.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 11000);
|
|
}
|
|
|
|
{
|
|
const tx = new MTX();
|
|
tx.addCoin(doubleSpendCoin);
|
|
tx.addOutput(wallet.getAddress(), 5000);
|
|
|
|
await wallet.sign(tx);
|
|
|
|
await wdb.addTX(tx.toTX());
|
|
|
|
const balance = await wallet.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 6000);
|
|
}
|
|
|
|
{
|
|
const txs = await wallet.getHistory();
|
|
assert.strictEqual(txs.length, 2);
|
|
|
|
const total = txs.reduce((t, wtx) => {
|
|
return t + wtx.tx.getOutputValue();
|
|
}, 0);
|
|
assert.strictEqual(total, 56000);
|
|
}
|
|
});
|
|
|
|
it('should handle missed txs without resolution', async () => {
|
|
const wdb = new WalletDB({
|
|
name: 'wallet-test',
|
|
db: 'memory',
|
|
verify: false
|
|
});
|
|
|
|
await wdb.open();
|
|
|
|
const alice = await wdb.create();
|
|
const bob = await wdb.create();
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(alice.getAddress(), 50000);
|
|
t1.addOutput(alice.getAddress(), 1000);
|
|
|
|
// balance: 51000
|
|
|
|
const t2 = new MTX();
|
|
t2.addTX(t1, 0); // 50000
|
|
t2.addOutput(alice.getAddress(), 24000);
|
|
t2.addOutput(alice.getAddress(), 24000);
|
|
|
|
// balance: 49000
|
|
await alice.sign(t2);
|
|
|
|
const t3 = new MTX();
|
|
t3.addTX(t1, 1); // 1000
|
|
t3.addTX(t2, 0); // 24000
|
|
t3.addOutput(alice.getAddress(), 23000);
|
|
|
|
// balance: 47000
|
|
await alice.sign(t3);
|
|
|
|
const t4 = new MTX();
|
|
t4.addTX(t2, 1); // 24000
|
|
t4.addTX(t3, 0); // 23000
|
|
t4.addOutput(alice.getAddress(), 11000);
|
|
t4.addOutput(alice.getAddress(), 11000);
|
|
|
|
// balance: 22000
|
|
await alice.sign(t4);
|
|
|
|
const f1 = new MTX();
|
|
f1.addTX(t4, 1); // 11000
|
|
f1.addOutput(bob.getAddress(), 10000);
|
|
|
|
// balance: 11000
|
|
await alice.sign(f1);
|
|
|
|
{
|
|
await wdb.addTX(t4.toTX());
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 22000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t1.toTX());
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 73000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t2.toTX());
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 47000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t3.toTX());
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 22000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(f1.toTX());
|
|
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 11000);
|
|
|
|
const txs = await alice.getHistory();
|
|
assert(txs.some((wtx) => {
|
|
return wtx.tx.hash('hex') === f1.hash('hex');
|
|
}));
|
|
}
|
|
|
|
{
|
|
const balance = await bob.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 10000);
|
|
|
|
const txs = await bob.getHistory();
|
|
assert(txs.some((wtx) => {
|
|
return wtx.tx.hash('hex') === f1.hash('hex');
|
|
}));
|
|
}
|
|
|
|
await wdb.addTX(t2.toTX());
|
|
await wdb.addTX(t3.toTX());
|
|
await wdb.addTX(t4.toTX());
|
|
await wdb.addTX(f1.toTX());
|
|
|
|
{
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 11000);
|
|
}
|
|
|
|
{
|
|
const balance = await bob.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 10000);
|
|
}
|
|
});
|
|
|
|
it('should fill tx with inputs', async () => {
|
|
const alice = await wdb.create();
|
|
const bob = await wdb.create();
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
// Create new transaction
|
|
const m2 = new MTX();
|
|
m2.addOutput(bob.getAddress(), 5460);
|
|
|
|
await alice.fund(m2, {
|
|
rate: 10000,
|
|
round: true
|
|
});
|
|
|
|
await alice.sign(m2);
|
|
|
|
const [t2, v2] = m2.commit();
|
|
|
|
assert(t2.verify(v2));
|
|
|
|
assert.strictEqual(t2.getInputValue(v2), 16380);
|
|
assert.strictEqual(t2.getOutputValue(), 6380);
|
|
assert.strictEqual(t2.getFee(v2), 10000);
|
|
|
|
// Create new transaction
|
|
const t3 = new MTX();
|
|
t3.addOutput(bob.getAddress(), 15000);
|
|
|
|
let err;
|
|
try {
|
|
await alice.fund(t3, {
|
|
rate: 10000,
|
|
round: true
|
|
});
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert.strictEqual(err.requiredFunds, 25000);
|
|
});
|
|
|
|
it('should fill tx with inputs with accurate fee', async () => {
|
|
const alice = await wdb.create({
|
|
master: KEY1
|
|
});
|
|
|
|
const bob = await wdb.create({
|
|
master: KEY2
|
|
});
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addOutpoint(new Outpoint(encoding.NULL_HASH, 0));
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
// Create new transaction
|
|
const m2 = new MTX();
|
|
m2.addOutput(bob.getAddress(), 5460);
|
|
|
|
await alice.fund(m2, {
|
|
rate: 10000
|
|
});
|
|
|
|
await alice.sign(m2);
|
|
|
|
const [t2, v2] = m2.commit();
|
|
|
|
assert(t2.verify(v2));
|
|
|
|
assert.strictEqual(t2.getInputValue(v2), 16380);
|
|
|
|
// Should now have a change output:
|
|
assert.strictEqual(t2.getOutputValue(), 11130);
|
|
|
|
assert.strictEqual(t2.getFee(v2), 5250);
|
|
|
|
assert.strictEqual(t2.getWeight(), 2084);
|
|
assert.strictEqual(t2.getBaseSize(), 521);
|
|
assert.strictEqual(t2.getSize(), 521);
|
|
assert.strictEqual(t2.getVirtualSize(), 521);
|
|
|
|
let balance;
|
|
bob.once('balance', (b) => {
|
|
balance = b;
|
|
});
|
|
|
|
await wdb.addTX(t2);
|
|
|
|
// Create new transaction
|
|
const t3 = new MTX();
|
|
t3.addOutput(bob.getAddress(), 15000);
|
|
|
|
let err;
|
|
try {
|
|
await alice.fund(t3, {
|
|
rate: 10000
|
|
});
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert(balance);
|
|
assert.strictEqual(balance.unconfirmed, 5460);
|
|
});
|
|
|
|
it('should sign multiple inputs using different keys', async () => {
|
|
const alice = await wdb.create();
|
|
const bob = await wdb.create();
|
|
const carol = await wdb.create();
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
|
|
// Coinbase
|
|
const t2 = new MTX();
|
|
t2.addInput(dummyInput());
|
|
t2.addOutput(bob.getAddress(), 5460);
|
|
t2.addOutput(bob.getAddress(), 5460);
|
|
t2.addOutput(bob.getAddress(), 5460);
|
|
t2.addOutput(bob.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
await wdb.addTX(t2.toTX());
|
|
|
|
// Create our tx with an output
|
|
const tx = new MTX();
|
|
tx.addOutput(carol.getAddress(), 5460);
|
|
|
|
const coins1 = await alice.getCoins();
|
|
const coins2 = await bob.getCoins();
|
|
|
|
// Add our unspent inputs to sign
|
|
tx.addCoin(coins1[0]);
|
|
tx.addCoin(coins1[1]);
|
|
tx.addCoin(coins2[0]);
|
|
|
|
// Sign transaction
|
|
assert.strictEqual(await alice.sign(tx), 2);
|
|
assert.strictEqual(await bob.sign(tx), 1);
|
|
|
|
// Verify
|
|
assert.strictEqual(tx.verify(), true);
|
|
|
|
tx.inputs.length = 0;
|
|
tx.addCoin(coins1[1]);
|
|
tx.addCoin(coins1[2]);
|
|
tx.addCoin(coins2[1]);
|
|
|
|
assert.strictEqual(await alice.sign(tx), 2);
|
|
assert.strictEqual(await bob.sign(tx), 1);
|
|
|
|
// Verify
|
|
assert.strictEqual(tx.verify(), true);
|
|
});
|
|
|
|
it('should verify 2-of-3 p2sh tx', async () => {
|
|
await testP2SH(false, false);
|
|
});
|
|
|
|
it('should verify 2-of-3 p2wsh tx', async () => {
|
|
await testP2SH(true, false);
|
|
});
|
|
|
|
it('should verify 2-of-3 p2wsh tx w/ nested bullshit', async () => {
|
|
await testP2SH(true, true);
|
|
});
|
|
|
|
it('should fill tx with account 1', async () => {
|
|
const alice = await wdb.create();
|
|
const bob = await wdb.create();
|
|
|
|
{
|
|
const account = await alice.createAccount({
|
|
name: 'foo'
|
|
});
|
|
assert.strictEqual(account.name, 'foo');
|
|
assert.strictEqual(account.accountIndex, 1);
|
|
}
|
|
|
|
const account = await alice.getAccount('foo');
|
|
assert.strictEqual(account.name, 'foo');
|
|
assert.strictEqual(account.accountIndex, 1);
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(account.receive.getAddress(), 5460);
|
|
t1.addOutput(account.receive.getAddress(), 5460);
|
|
t1.addOutput(account.receive.getAddress(), 5460);
|
|
t1.addOutput(account.receive.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
// Create new transaction
|
|
const t2 = new MTX();
|
|
t2.addOutput(bob.getAddress(), 5460);
|
|
|
|
await alice.fund(t2, {
|
|
rate: 10000,
|
|
round: true
|
|
});
|
|
|
|
await alice.sign(t2);
|
|
|
|
assert(t2.verify());
|
|
|
|
assert.strictEqual(t2.getInputValue(), 16380);
|
|
assert.strictEqual(t2.getOutputValue(), 6380);
|
|
assert.strictEqual(t2.getFee(), 10000);
|
|
|
|
// Create new transaction
|
|
const t3 = new MTX();
|
|
t3.addOutput(bob.getAddress(), 15000);
|
|
|
|
let err;
|
|
try {
|
|
await alice.fund(t3, {
|
|
rate: 10000,
|
|
round: true
|
|
});
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert.strictEqual(err.requiredFunds, 25000);
|
|
|
|
const accounts = await alice.getAccounts();
|
|
assert.deepStrictEqual(accounts, ['default', 'foo']);
|
|
});
|
|
|
|
it('should fail to fill tx with account 1', async () => {
|
|
const wallet = await wdb.create();
|
|
|
|
{
|
|
const account = await wallet.createAccount({
|
|
name: 'foo'
|
|
});
|
|
assert.strictEqual(account.name, 'foo');
|
|
assert.strictEqual(account.accountIndex, 1);
|
|
}
|
|
|
|
const account = await wallet.getAccount('foo');
|
|
assert.strictEqual(account.name, 'foo');
|
|
assert.strictEqual(account.accountIndex, 1);
|
|
assert.strictEqual(wallet.account.accountIndex, 0);
|
|
|
|
assert(!account.receive.getAddress().equals(
|
|
wallet.account.receive.getAddress()));
|
|
|
|
assert(wallet.getAddress().equals(wallet.account.receive.getAddress()));
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(wallet.getAddress(), 5460);
|
|
t1.addOutput(wallet.getAddress(), 5460);
|
|
t1.addOutput(wallet.getAddress(), 5460);
|
|
t1.addOutput(account.receive.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
// Should fill from `foo` and fail
|
|
const t2 = new MTX();
|
|
|
|
t2.addOutput(wallet.getAddress(), 5460);
|
|
|
|
let err;
|
|
try {
|
|
await wallet.fund(t2, {
|
|
rate: 10000,
|
|
round: true,
|
|
account: 'foo'
|
|
});
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
|
|
// Should fill from whole wallet and succeed
|
|
const t3 = new MTX();
|
|
t3.addOutput(wallet.getAddress(), 5460);
|
|
|
|
await wallet.fund(t3, {
|
|
rate: 10000,
|
|
round: true
|
|
});
|
|
|
|
// Coinbase
|
|
const t4 = new MTX();
|
|
t4.addInput(dummyInput());
|
|
t4.addOutput(account.receive.getAddress(), 5460);
|
|
t4.addOutput(account.receive.getAddress(), 5460);
|
|
t4.addOutput(account.receive.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t4.toTX());
|
|
|
|
// Should fill from `foo` and succeed
|
|
const t5 = new MTX();
|
|
t5.addOutput(wallet.getAddress(), 5460);
|
|
|
|
await wallet.fund(t5, {
|
|
rate: 10000,
|
|
round: true,
|
|
account: 'foo'
|
|
});
|
|
|
|
currentWallet = wallet;
|
|
});
|
|
|
|
it('should create two accounts (multiple encryption)', async () => {
|
|
{
|
|
const wallet = await wdb.create({
|
|
id: 'foobar',
|
|
passphrase: 'foo'
|
|
});
|
|
await wallet.destroy();
|
|
}
|
|
|
|
const wallet = await wdb.get('foobar');
|
|
assert(wallet);
|
|
|
|
const options = {
|
|
name: 'foo1'
|
|
};
|
|
|
|
const account = await wallet.createAccount(options, 'foo');
|
|
|
|
assert(account);
|
|
|
|
await wallet.lock();
|
|
});
|
|
|
|
it('should fill tx with inputs when encrypted', async () => {
|
|
const wallet = await wdb.create({
|
|
passphrase: 'foo'
|
|
});
|
|
|
|
wallet.master.stop();
|
|
wallet.master.key = null;
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(wallet.getAddress(), 5460);
|
|
t1.addOutput(wallet.getAddress(), 5460);
|
|
t1.addOutput(wallet.getAddress(), 5460);
|
|
t1.addOutput(wallet.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
// Create new transaction
|
|
const t2 = new MTX();
|
|
t2.addOutput(wallet.getAddress(), 5460);
|
|
|
|
await wallet.fund(t2, {
|
|
rate: 10000,
|
|
round: true
|
|
});
|
|
|
|
// Should fail
|
|
let err;
|
|
try {
|
|
await wallet.sign(t2, 'bar');
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert(!t2.verify());
|
|
|
|
// Should succeed
|
|
await wallet.sign(t2, 'foo');
|
|
assert(t2.verify());
|
|
});
|
|
|
|
it('should fill tx with inputs with subtract fee (1)', async () => {
|
|
const alice = await wdb.create();
|
|
const bob = await wdb.create();
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
// Create new transaction
|
|
const t2 = new MTX();
|
|
t2.addOutput(bob.getAddress(), 21840);
|
|
|
|
await alice.fund(t2, {
|
|
rate: 10000,
|
|
round: true,
|
|
subtractFee: true
|
|
});
|
|
|
|
await alice.sign(t2);
|
|
|
|
assert(t2.verify());
|
|
|
|
assert.strictEqual(t2.getInputValue(), 5460 * 4);
|
|
assert.strictEqual(t2.getOutputValue(), 21840 - 10000);
|
|
assert.strictEqual(t2.getFee(), 10000);
|
|
});
|
|
|
|
it('should fill tx with inputs with subtract fee (2)', async () => {
|
|
const alice = await wdb.create();
|
|
const bob = await wdb.create();
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
const options = {
|
|
subtractFee: true,
|
|
rate: 10000,
|
|
round: true,
|
|
outputs: [{ address: bob.getAddress(), value: 21840 }]
|
|
};
|
|
|
|
// Create new transaction
|
|
const t2 = await alice.createTX(options);
|
|
await alice.sign(t2);
|
|
|
|
assert(t2.verify());
|
|
|
|
assert.strictEqual(t2.getInputValue(), 5460 * 4);
|
|
assert.strictEqual(t2.getOutputValue(), 21840 - 10000);
|
|
assert.strictEqual(t2.getFee(), 10000);
|
|
});
|
|
|
|
it('should fill tx with smart coin selection', async () => {
|
|
const alice = await wdb.create();
|
|
const bob = await wdb.create();
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
t1.addOutput(alice.getAddress(), 5460);
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
// Coinbase
|
|
const t2 = new MTX();
|
|
t2.addInput(dummyInput());
|
|
t2.addOutput(alice.getAddress(), 5460);
|
|
t2.addOutput(alice.getAddress(), 5460);
|
|
t2.addOutput(alice.getAddress(), 5460);
|
|
t2.addOutput(alice.getAddress(), 5460);
|
|
|
|
const block = nextBlock();
|
|
|
|
await wdb.addBlock(block, [t2.toTX()]);
|
|
|
|
{
|
|
const coins = await alice.getSmartCoins();
|
|
assert.strictEqual(coins.length, 4);
|
|
|
|
for (let i = 0; i < coins.length; i++) {
|
|
const coin = coins[i];
|
|
assert.strictEqual(coin.height, block.height);
|
|
}
|
|
}
|
|
|
|
// Create a change output for ourselves.
|
|
await alice.send({
|
|
subtractFee: true,
|
|
rate: 1000,
|
|
depth: 1,
|
|
outputs: [{ address: bob.getAddress(), value: 1461 }]
|
|
});
|
|
|
|
const coins = await alice.getSmartCoins();
|
|
assert.strictEqual(coins.length, 4);
|
|
|
|
let total = 0;
|
|
|
|
{
|
|
let found = false;
|
|
|
|
for (let i = 0; i < coins.length; i++) {
|
|
const coin = coins[i];
|
|
if (coin.height === -1) {
|
|
assert(!found);
|
|
assert(coin.value < 5460);
|
|
found = true;
|
|
} else {
|
|
assert.strictEqual(coin.height, block.height);
|
|
}
|
|
total += coin.value;
|
|
}
|
|
|
|
assert(found);
|
|
}
|
|
|
|
// Use smart selection
|
|
const options = {
|
|
subtractFee: true,
|
|
smart: true,
|
|
rate: 10000,
|
|
outputs: [{
|
|
address: bob.getAddress(),
|
|
value: total
|
|
}]
|
|
};
|
|
|
|
const t3 = await alice.createTX(options);
|
|
assert.strictEqual(t3.inputs.length, 4);
|
|
|
|
{
|
|
let found = false;
|
|
|
|
for (let i = 0; i < t3.inputs.length; i++) {
|
|
const coin = t3.view.getCoinFor(t3.inputs[i]);
|
|
if (coin.height === -1) {
|
|
assert(!found);
|
|
assert(coin.value < 5460);
|
|
found = true;
|
|
} else {
|
|
assert.strictEqual(coin.height, block.height);
|
|
}
|
|
}
|
|
|
|
assert(found);
|
|
}
|
|
|
|
await alice.sign(t3);
|
|
|
|
assert(t3.verify());
|
|
});
|
|
|
|
it('should get range of txs', async () => {
|
|
const wallet = currentWallet;
|
|
const txs = await wallet.getRange({
|
|
start: util.now() - 1000
|
|
});
|
|
assert.strictEqual(txs.length, 2);
|
|
});
|
|
|
|
it('should get range of txs from account', async () => {
|
|
const wallet = currentWallet;
|
|
const txs = await wallet.getRange('foo', {
|
|
start: util.now() - 1000
|
|
});
|
|
assert.strictEqual(txs.length, 2);
|
|
});
|
|
|
|
it('should not get range of txs from non-existent account', async () => {
|
|
const wallet = currentWallet;
|
|
|
|
let txs, err;
|
|
try {
|
|
txs = await wallet.getRange('bad', {
|
|
start: 0xdeadbeef - 1000
|
|
});
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(!txs);
|
|
assert(err);
|
|
assert.strictEqual(err.message, 'Account not found.');
|
|
});
|
|
|
|
it('should get account balance', async () => {
|
|
const wallet = currentWallet;
|
|
const balance = await wallet.getBalance('foo');
|
|
assert.strictEqual(balance.unconfirmed, 21840);
|
|
});
|
|
|
|
it('should import privkey', async () => {
|
|
const key = KeyRing.generate();
|
|
|
|
const wallet = await wdb.create({
|
|
passphrase: 'test'
|
|
});
|
|
|
|
await wallet.importKey('default', key, 'test');
|
|
|
|
const wkey = await wallet.getKey(key.getHash('hex'));
|
|
|
|
assert.strictEqual(wkey.getHash('hex'), key.getHash('hex'));
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addOutput(key.getAddress(), 5460);
|
|
t1.addOutput(key.getAddress(), 5460);
|
|
t1.addOutput(key.getAddress(), 5460);
|
|
t1.addOutput(key.getAddress(), 5460);
|
|
|
|
t1.addInput(dummyInput());
|
|
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
const wtx = await wallet.getTX(t1.hash('hex'));
|
|
assert(wtx);
|
|
assert.strictEqual(t1.hash('hex'), wtx.hash);
|
|
|
|
const options = {
|
|
rate: 10000,
|
|
round: true,
|
|
outputs: [{
|
|
address: wallet.getAddress(),
|
|
value: 7000
|
|
}]
|
|
};
|
|
|
|
// Create new transaction
|
|
const t2 = await wallet.createTX(options);
|
|
await wallet.sign(t2);
|
|
assert(t2.verify());
|
|
assert.strictEqual(t2.inputs[0].prevout.hash, wtx.hash);
|
|
|
|
importedWallet = wallet;
|
|
importedKey = key;
|
|
});
|
|
|
|
it('should import pubkey', async () => {
|
|
const key = KeyRing.generate();
|
|
const pub = new KeyRing(key.publicKey);
|
|
|
|
const wallet = await wdb.create({
|
|
watchOnly: true
|
|
});
|
|
|
|
await wallet.importKey('default', pub);
|
|
|
|
const path = await wallet.getPath(pub.getHash('hex'));
|
|
assert.strictEqual(path.hash, pub.getHash('hex'));
|
|
|
|
const wkey = await wallet.getKey(pub.getHash('hex'));
|
|
assert(wkey);
|
|
});
|
|
|
|
it('should import address', async () => {
|
|
const key = KeyRing.generate();
|
|
|
|
const wallet = await wdb.create({
|
|
watchOnly: true
|
|
});
|
|
|
|
await wallet.importAddress('default', key.getAddress());
|
|
|
|
const path = await wallet.getPath(key.getHash('hex'));
|
|
assert(path);
|
|
assert.strictEqual(path.hash, key.getHash('hex'));
|
|
|
|
const wkey = await wallet.getKey(key.getHash('hex'));
|
|
assert(!wkey);
|
|
});
|
|
|
|
it('should get details', async () => {
|
|
const wallet = currentWallet;
|
|
|
|
const txs = await wallet.getRange('foo', {
|
|
start: util.now() - 1000
|
|
});
|
|
|
|
const details = await wallet.toDetails(txs);
|
|
|
|
assert(details.some((tx) => {
|
|
return tx.toJSON().outputs[0].path.name === 'foo';
|
|
}));
|
|
});
|
|
|
|
it('should rename wallet', async () => {
|
|
const wallet = currentWallet;
|
|
|
|
await wallet.rename('test');
|
|
|
|
const txs = await wallet.getRange('foo', {
|
|
start: util.now() - 1000
|
|
});
|
|
|
|
const details = await wallet.toDetails(txs);
|
|
|
|
assert.strictEqual(details[0].toJSON().id, 'test');
|
|
});
|
|
|
|
it('should change passphrase with encrypted imports', async () => {
|
|
const wallet = importedWallet;
|
|
const addr = importedKey.getAddress();
|
|
|
|
assert(wallet.master.encrypted);
|
|
|
|
let data;
|
|
{
|
|
const path = await wallet.getPath(addr);
|
|
assert(path);
|
|
assert(path.data && path.encrypted);
|
|
data = path.data;
|
|
}
|
|
|
|
await wallet.decrypt('test');
|
|
|
|
{
|
|
const path = await wallet.getPath(addr);
|
|
assert(path);
|
|
assert(path.data && !path.encrypted);
|
|
assert(await wallet.getKey(addr));
|
|
}
|
|
|
|
await wallet.encrypt('foo');
|
|
|
|
{
|
|
const path = await wallet.getPath(addr);
|
|
assert(path);
|
|
assert(path.data && path.encrypted);
|
|
assert(!data.equals(path.data));
|
|
assert(!await wallet.getKey(addr));
|
|
}
|
|
|
|
await wallet.unlock('foo');
|
|
|
|
const key = await wallet.getKey(addr);
|
|
assert(key);
|
|
assert.strictEqual(key.getHash('hex'), addr.getHash('hex'));
|
|
});
|
|
|
|
it('should recover from a missed tx', async () => {
|
|
const wdb = new WalletDB({
|
|
name: 'wallet-test',
|
|
db: 'memory',
|
|
verify: false
|
|
});
|
|
|
|
await wdb.open();
|
|
|
|
const alice = await wdb.create({
|
|
master: KEY1
|
|
});
|
|
|
|
const bob = await wdb.create({
|
|
master: KEY1
|
|
});
|
|
|
|
const addr = alice.getAddress();
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(addr, 50000);
|
|
|
|
await alice.add(t1.toTX());
|
|
await bob.add(t1.toTX());
|
|
|
|
// Bob misses this tx!
|
|
const t2 = new MTX();
|
|
t2.addTX(t1, 0);
|
|
t2.addOutput(addr, 24000);
|
|
t2.addOutput(addr, 24000);
|
|
|
|
await alice.sign(t2);
|
|
|
|
await alice.add(t2.toTX());
|
|
|
|
assert.notStrictEqual(
|
|
(await alice.getBalance()).unconfirmed,
|
|
(await bob.getBalance()).unconfirmed);
|
|
|
|
// Bob sees this one.
|
|
const t3 = new MTX();
|
|
t3.addTX(t2, 0);
|
|
t3.addTX(t2, 1);
|
|
t3.addOutput(addr, 30000);
|
|
|
|
await alice.sign(t3);
|
|
|
|
assert.strictEqual((await bob.getBalance()).unconfirmed, 50000);
|
|
|
|
await alice.add(t3.toTX());
|
|
await bob.add(t3.toTX());
|
|
|
|
assert.strictEqual((await alice.getBalance()).unconfirmed, 30000);
|
|
|
|
// Bob sees t2 on the chain.
|
|
await bob.add(t2.toTX());
|
|
|
|
// Bob sees t3 on the chain.
|
|
await bob.add(t3.toTX());
|
|
|
|
assert.strictEqual((await bob.getBalance()).unconfirmed, 30000);
|
|
});
|
|
|
|
it('should recover from a missed tx and double spend', async () => {
|
|
const wdb = new WalletDB({
|
|
name: 'wallet-test',
|
|
db: 'memory',
|
|
verify: false
|
|
});
|
|
|
|
await wdb.open();
|
|
|
|
const alice = await wdb.create({
|
|
master: KEY1
|
|
});
|
|
|
|
const bob = await wdb.create({
|
|
master: KEY1
|
|
});
|
|
|
|
const addr = alice.getAddress();
|
|
|
|
// Coinbase
|
|
const t1 = new MTX();
|
|
t1.addInput(dummyInput());
|
|
t1.addOutput(addr, 50000);
|
|
|
|
await alice.add(t1.toTX());
|
|
await bob.add(t1.toTX());
|
|
|
|
// Bob misses this tx!
|
|
const t2a = new MTX();
|
|
t2a.addTX(t1, 0);
|
|
t2a.addOutput(addr, 24000);
|
|
t2a.addOutput(addr, 24000);
|
|
|
|
await alice.sign(t2a);
|
|
|
|
await alice.add(t2a.toTX());
|
|
|
|
assert.notStrictEqual(
|
|
(await alice.getBalance()).unconfirmed,
|
|
(await bob.getBalance()).unconfirmed);
|
|
|
|
// Bob doublespends.
|
|
const t2b = new MTX();
|
|
t2b.addTX(t1, 0);
|
|
t2b.addOutput(addr, 10000);
|
|
t2b.addOutput(addr, 10000);
|
|
|
|
await bob.sign(t2b);
|
|
|
|
await bob.add(t2b.toTX());
|
|
|
|
// Bob sees this one.
|
|
const t3 = new MTX();
|
|
t3.addTX(t2a, 0);
|
|
t3.addTX(t2a, 1);
|
|
t3.addOutput(addr, 30000);
|
|
|
|
await alice.sign(t3);
|
|
|
|
assert.strictEqual((await bob.getBalance()).unconfirmed, 20000);
|
|
|
|
await alice.add(t3.toTX());
|
|
await bob.add(t3.toTX());
|
|
|
|
assert.strictEqual((await alice.getBalance()).unconfirmed, 30000);
|
|
|
|
// Bob sees t2a on the chain.
|
|
await bob.add(t2a.toTX());
|
|
|
|
// Bob sees t3 on the chain.
|
|
await bob.add(t3.toTX());
|
|
|
|
assert.strictEqual((await bob.getBalance()).unconfirmed, 30000);
|
|
});
|
|
|
|
it('should cleanup', () => {
|
|
consensus.COINBASE_MATURITY = 100;
|
|
});
|
|
});
|