Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create txdb credit for known key paths when confirming #262

Merged
merged 2 commits into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions lib/wallet/txdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,7 @@ class TXDB {
const details = new Details(wtx, block);
const state = new BalanceDelta();
const view = new CoinView();
let own = false;

wtx.setBlock(block);

Expand Down Expand Up @@ -1126,6 +1127,7 @@ class TXDB {

const path = await this.getPath(coin);
assert(path);
own = true;

details.setInput(i, path, coin);

Expand Down Expand Up @@ -1157,8 +1159,22 @@ class TXDB {

details.setOutput(i, path);

const credit = await this.getCredit(hash, i);
assert(credit);
let credit = await this.getCredit(hash, i);

if (!credit) {
// This credit didn't belong to us the first time we
// saw the transaction (before confirmation or rescan).
// Create new credit for database.
credit = Credit.fromTX(tx, i, height);

// If this tx spent any of our own coins, we "own" this output,
// meaning if it becomes unconfirmed, we can still confidently spend it.
credit.own = own;

// Add coin to "unconfirmed" balance (which includes confirmed coins)
state.coin(path, 1);
state.unconfirmed(path, credit.coin.value);
}

// Credits spent in the mempool add an
// undo coin for ease. If this credit is
Expand Down
163 changes: 161 additions & 2 deletions test/wallet-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ 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 PrivateKey = require('../lib/hd/private.js');
const policy = require('../lib/protocol/policy');
const HDPrivateKey = require('../lib/hd/private');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol - this .js was leftover from an earlier port #97 I guess I opened the PR on hsd before it was finished on bcoin!


const KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt'
+ 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ';
Expand Down Expand Up @@ -1220,7 +1220,7 @@ describe('Wallet', function() {
);
}

const privateKey = PrivateKey.generate();
const privateKey = HDPrivateKey.generate();
const xpub = privateKey.xpubkey('main');
watchWallet = await wdb.create({
watchOnly: true,
Expand Down Expand Up @@ -1572,6 +1572,165 @@ describe('Wallet', function() {
});
});

it('should create credit if not found during confirmation', async () => {
// Create wallet and get one address
const wallet = await wdb.create();
const addr1 = await wallet.receiveAddress();

// Outside the wallet, generate a second private key and address.
const key2 = HDPrivateKey.generate();
const ring2 = KeyRing.fromPrivate(key2.privateKey);
const addr2 = ring2.getAddress();

// Build TX to both addresses, known and unknown
const mtx = new MTX();
mtx.addOutpoint(new Outpoint(Buffer.alloc(32), 0));
mtx.addOutput(addr1, 1020304);
mtx.addOutput(addr2, 4030201);
const tx = mtx.toTX();
const hash = tx.hash();

// Add unconfirmed TX to txdb (no block provided)
await wallet.txdb.add(tx, null);

// Check
const bal1 = await wallet.getBalance();
assert.strictEqual(bal1.tx, 1);
assert.strictEqual(bal1.coin, 1);
assert.strictEqual(bal1.confirmed, 0);
assert.strictEqual(bal1.unconfirmed, 1020304);

// Import private key into wallet
assert(!await wallet.hasAddress(addr2));
await wallet.importKey('default', ring2);
assert(await wallet.hasAddress(addr2));

// Confirm TX with newly-added output address
// Create dummy block
const block = {
height: 100,
hash: Buffer.alloc(32),
time: Date.now()
};

// Get TX from txdb
const wtx = await wallet.txdb.getTX(hash);

// Confirm TX with dummy block in txdb
const details = await wallet.txdb.confirm(wtx, block);
assert.bufferEqual(details.tx.hash(), hash);

// Check balance
const bal2 = await wallet.getBalance();
assert.strictEqual(bal2.confirmed, bal2.unconfirmed);
assert.strictEqual(bal2.confirmed, 5050505);
assert.strictEqual(bal2.coin, 2);
assert.strictEqual(bal2.tx, 1);

// Check for unconfirmed transactions
const pending = await wallet.getPending();
assert.strictEqual(pending.length, 0);

// Check history for TX
const history = await wallet.getHistory();
const wtxs = await wallet.toDetails(history);
assert.strictEqual(wtxs.length, 1);
assert.bufferEqual(wtxs[0].hash, hash);

// Both old and new credits are not "owned"
// (created by the wallet spending its own coins)
for (let i = 0; i < tx.outputs.length; i++) {
const credit = await wallet.txdb.getCredit(tx.hash(), i);
assert(!credit.own);
}
});

it('should create owned credit if not found during confirm', async () => {
// Create wallet and get one address
const wallet = await wdb.create();
const addr1 = await wallet.receiveAddress();

// Outside the wallet, generate a second private key and address.
const key2 = HDPrivateKey.generate();
const ring2 = KeyRing.fromPrivate(key2.privateKey);
const addr2 = ring2.getAddress();

// Create a confirmed, unspent, wallet-owned credit in txdb
const mtx1 = new MTX();
mtx1.addOutpoint(new Outpoint(Buffer.alloc(32), 0));
mtx1.addOutput(addr1, 1 * 1e8);
const tx1 = mtx1.toTX();
await wallet.txdb.add(tx1, null);

// Create dummy block
const block1 = {
height: 99,
hash: Buffer.alloc(32),
time: Date.now()
};
// Get TX from txdb
const wtx1 = await wallet.txdb.getTX(tx1.hash());

// Confirm TX with dummy block in txdb
await wallet.txdb.confirm(wtx1, block1);

// Build TX to both addresses, known and unknown
const mtx2 = new MTX();
mtx2.addTX(tx1, 0, 99);
mtx2.addOutput(addr1, 1020304);
mtx2.addOutput(addr2, 4030201);
const tx2 = mtx2.toTX();
const hash = tx2.hash();

// Add unconfirmed TX to txdb (no block provided)
await wallet.txdb.add(tx2, null);

// Check
const bal1 = await wallet.getBalance();
assert.strictEqual(bal1.tx, 2);
assert.strictEqual(bal1.coin, 1);
assert.strictEqual(bal1.confirmed, 1 * 1e8);
assert.strictEqual(bal1.unconfirmed, 1020304);

// Import private key into wallet
assert(!await wallet.hasAddress(addr2));
await wallet.importKey('default', ring2);
assert(await wallet.hasAddress(addr2));

// Confirm TX with newly-added output address
// Create dummy block
const block2 = {
height: 100,
hash: Buffer.alloc(32),
time: Date.now()
};

// Get TX from txdb
const wtx2 = await wallet.txdb.getTX(hash);

// Confirm TX with dummy block in txdb
const details = await wallet.txdb.confirm(wtx2, block2);
assert.bufferEqual(details.tx.hash(), hash);

// Check balance
const bal2 = await wallet.getBalance();
assert.strictEqual(bal2.confirmed, bal2.unconfirmed);
assert.strictEqual(bal2.confirmed, 5050505);
assert.strictEqual(bal2.coin, 2);
assert.strictEqual(bal2.tx, 2);

// Check for unconfirmed transactions
const pending = await wallet.getPending();
assert.strictEqual(pending.length, 0);

// Both old and new credits are "owned"
// (created by the wallet spending its own coins)
for (let i = 0; i < tx2.outputs.length; i++) {
const credit = await wallet.txdb.getCredit(tx2.hash(), i);
assert(credit.own);
}
});

it('should cleanup', async () => {
network.coinbaseMaturity = 2;
await wdb.close();
Expand Down