- Recover bid heights from the TXRecords. Historical not-owned bids will
have them default (-1) for the migration, as TXRecords for them are not
recorded.
- Link owned bid <-> reveals. Historical not-owned bids will have them
default (null outpoint) for the migration, as TXRecords for them are
not recorded.
2344 lines
64 KiB
JavaScript
2344 lines
64 KiB
JavaScript
'use strict';
|
|
|
|
const Network = require('../lib/protocol/network');
|
|
const MTX = require('../lib/primitives/mtx');
|
|
const {isSignatureEncoding, isKeyEncoding} = require('../lib/script/common');
|
|
const {Resource} = require('../lib/dns/resource');
|
|
const Address = require('../lib/primitives/address');
|
|
const Output = require('../lib/primitives/output');
|
|
const HD = require('../lib/hd/hd');
|
|
const Mnemonic = require('../lib/hd/mnemonic');
|
|
const rules = require('../lib/covenants/rules');
|
|
const {types} = rules;
|
|
const secp256k1 = require('bcrypto/lib/secp256k1');
|
|
const network = Network.get('regtest');
|
|
const assert = require('bsert');
|
|
const {BufferSet} = require('buffer-map');
|
|
const common = require('./util/common');
|
|
const Outpoint = require('../lib/primitives/outpoint');
|
|
const consensus = require('../lib/protocol/consensus');
|
|
const NodeContext = require('./util/node-context');
|
|
|
|
const {
|
|
treeInterval,
|
|
biddingPeriod,
|
|
revealPeriod,
|
|
transferLockup
|
|
} = network.names;
|
|
|
|
describe('Wallet HTTP', function() {
|
|
this.timeout(20000);
|
|
|
|
/** @type {NodeContext} */
|
|
let nodeCtx;
|
|
let wclient, nclient;
|
|
|
|
// primary wallet client.
|
|
let wallet, cbAddress;
|
|
|
|
const beforeAll = async () => {
|
|
nodeCtx = new NodeContext({
|
|
apiKey: 'foo',
|
|
network: 'regtest',
|
|
walletAuth: true,
|
|
wallet: true
|
|
});
|
|
|
|
await nodeCtx.open();
|
|
|
|
wclient = nodeCtx.wclient;
|
|
nclient = nodeCtx.nclient;
|
|
|
|
wallet = nodeCtx.wclient.wallet('primary');
|
|
cbAddress = (await wallet.createAddress('default')).address;
|
|
};
|
|
|
|
const afterAll = async () => {
|
|
await nodeCtx.close();
|
|
};
|
|
|
|
describe('Create wallet', function() {
|
|
before(beforeAll);
|
|
after(afterAll);
|
|
|
|
it('should create wallet', async () => {
|
|
const info = await wclient.createWallet('test');
|
|
assert.strictEqual(info.id, 'test');
|
|
const wallet = wclient.wallet('test', info.token);
|
|
await wallet.open();
|
|
});
|
|
|
|
it('should create wallet with spanish mnemonic', async () => {
|
|
await wclient.createWallet(
|
|
'cartera1',
|
|
{language: 'spanish'}
|
|
);
|
|
const master = await wclient.getMaster('cartera1');
|
|
const phrase = master.mnemonic.phrase;
|
|
for (const word of phrase.split(' ')) {
|
|
const language = Mnemonic.getLanguage(word);
|
|
assert.strictEqual(language, 'spanish');
|
|
// Comprobar la cordura:
|
|
assert.notStrictEqual(language, 'english');
|
|
}
|
|
|
|
// Verificar
|
|
await wclient.createWallet(
|
|
'cartera2',
|
|
{mnemonic: phrase}
|
|
);
|
|
assert.deepStrictEqual(
|
|
await wclient.getAccount('cartera1', 'default'),
|
|
await wclient.getAccount('cartera2', 'default')
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Lookahead', function() {
|
|
before(beforeAll);
|
|
after(afterAll);
|
|
|
|
it('should create wallet with default account 1000 lookahead', async () => {
|
|
const wname = 'lookahead';
|
|
await wclient.createWallet(wname, {
|
|
lookahead: 1000
|
|
});
|
|
|
|
const defAccount = await wclient.getAccount(wname, 'default');
|
|
assert.strictEqual(defAccount.lookahead, 1000);
|
|
|
|
const newAccount = await wclient.createAccount(wname, 'newaccount', {
|
|
lookahead: 1001
|
|
});
|
|
assert.strictEqual(newAccount.lookahead, 1001);
|
|
const getNewAccount = await wclient.getAccount(wname, 'newaccount', {
|
|
lookahead: 1001
|
|
});
|
|
|
|
assert.strictEqual(getNewAccount.lookahead, 1001);
|
|
});
|
|
|
|
it('should modify account lookahead to 1000', async () => {
|
|
const wname = 'lookahead2';
|
|
await wclient.createWallet(wname);
|
|
|
|
const defAccount = await wclient.getAccount(wname, 'default');
|
|
assert.strictEqual(defAccount.lookahead, 200);
|
|
|
|
const modified = await wclient.modifyAccount(wname, 'default', {
|
|
lookahead: 1000
|
|
});
|
|
assert.strictEqual(modified.lookahead, 1000);
|
|
});
|
|
});
|
|
|
|
describe('Wallet info', function() {
|
|
let wallet;
|
|
|
|
before(async () => {
|
|
await beforeAll();
|
|
|
|
await wclient.createWallet('test');
|
|
wallet = wclient.wallet('test');
|
|
});
|
|
after(afterAll);
|
|
|
|
it('should get wallet info', async () => {
|
|
const info = await wallet.getInfo();
|
|
assert.strictEqual(info.id, 'test');
|
|
const acct = await wallet.getAccount('default');
|
|
const str = acct.receiveAddress;
|
|
assert(typeof str === 'string');
|
|
});
|
|
});
|
|
|
|
describe('Key/Address', function() {
|
|
before(beforeAll);
|
|
after(afterAll);
|
|
|
|
it('should get key by address from watch-only', async () => {
|
|
const phrase = 'abandon abandon abandon abandon abandon abandon '
|
|
+ 'abandon abandon abandon abandon abandon about';
|
|
const master = HD.HDPrivateKey.fromPhrase(phrase);
|
|
const xprv = master.deriveAccount(44, 5355, 5);
|
|
const xpub = xprv.toPublic();
|
|
const pubkey = xpub.derive(0).derive(0);
|
|
const addr = Address.fromPubkey(pubkey.publicKey);
|
|
const wallet = wclient.wallet('watchonly');
|
|
await wclient.createWallet('watchonly', {
|
|
watchOnly: true,
|
|
accountKey: xpub.xpubkey('regtest')
|
|
});
|
|
const key = await wallet.getKey(addr.toString('regtest'));
|
|
assert.equal(xpub.childIndex ^ HD.common.HARDENED, key.account);
|
|
assert.equal(0, key.branch);
|
|
assert.equal(0, key.index);
|
|
});
|
|
});
|
|
|
|
describe('Mine/Fund', function() {
|
|
before(beforeAll);
|
|
after(afterAll);
|
|
|
|
it('should mine to the primary/default wallet', async () => {
|
|
const height = 20;
|
|
|
|
await nodeCtx.mineBlocks(height, cbAddress);
|
|
|
|
const info = await nclient.getInfo();
|
|
assert.equal(info.chain.height, height);
|
|
|
|
const accountInfo = await wallet.getAccount('default');
|
|
// each coinbase output was indexed
|
|
assert.equal(accountInfo.balance.coin, height);
|
|
|
|
const coins = await wallet.getCoins();
|
|
// the wallet has no previous history besides
|
|
// what it has mined
|
|
assert.ok(coins.every(coin => coin.coinbase === true));
|
|
});
|
|
});
|
|
|
|
describe('Events', function() {
|
|
before(beforeAll);
|
|
after(afterAll);
|
|
|
|
it('balance address and tx events', async () => {
|
|
await wclient.createWallet('test');
|
|
const testWallet = wclient.wallet('test');
|
|
await testWallet.open();
|
|
const {address} = await testWallet.createAddress('default');
|
|
|
|
const mtx = new MTX();
|
|
mtx.addOutpoint(new Outpoint(consensus.ZERO_HASH, 0));
|
|
mtx.addOutput(address, 50460);
|
|
mtx.addOutput(address, 50460);
|
|
mtx.addOutput(address, 50460);
|
|
mtx.addOutput(address, 50460);
|
|
|
|
const tx = mtx.toTX();
|
|
|
|
let balance = null;
|
|
testWallet.once('balance', (b) => {
|
|
balance = b;
|
|
});
|
|
|
|
let receive = null;
|
|
testWallet.once('address', (r) => {
|
|
receive = r[0];
|
|
});
|
|
|
|
let details = null;
|
|
testWallet.once('tx', (d) => {
|
|
details = d;
|
|
});
|
|
|
|
await nodeCtx.wdb.addTX(tx);
|
|
await new Promise(r => setTimeout(r, 300));
|
|
|
|
assert(receive);
|
|
assert.strictEqual(receive.name, 'default');
|
|
assert.strictEqual(receive.branch, 0);
|
|
assert(balance);
|
|
assert.strictEqual(balance.confirmed, 0);
|
|
assert.strictEqual(balance.unconfirmed, 201840);
|
|
assert(details);
|
|
assert.strictEqual(details.hash, tx.txid());
|
|
});
|
|
});
|
|
|
|
describe('Create/Send transaction', function() {
|
|
let wallet2;
|
|
|
|
before(async () => {
|
|
await beforeAll();
|
|
await nodeCtx.mineBlocks(20, cbAddress);
|
|
await wclient.createWallet('secondary');
|
|
wallet2 = wclient.wallet('secondary');
|
|
});
|
|
|
|
after(afterAll);
|
|
|
|
it('should create a transaction', async () => {
|
|
const tx = await wallet.createTX({
|
|
outputs: [{ address: cbAddress, value: 1e4 }]
|
|
});
|
|
|
|
assert.ok(tx);
|
|
assert.equal(tx.outputs.length, 1 + 1); // send + change
|
|
assert.equal(tx.locktime, 0);
|
|
});
|
|
|
|
it('should create self-send transaction with HD paths', async () => {
|
|
const tx = await wallet.createTX({
|
|
paths: true,
|
|
outputs: [{ address: cbAddress, value: 1e4 }]
|
|
});
|
|
|
|
assert.ok(tx);
|
|
assert.ok(tx.inputs);
|
|
|
|
for (let i = 0; i < tx.inputs.length; i++) {
|
|
const path = tx.inputs[i].path;
|
|
|
|
assert.ok(typeof path.name === 'string');
|
|
assert.ok(typeof path.account === 'number');
|
|
assert.ok(typeof path.change === 'boolean');
|
|
assert.ok(typeof path.derivation === 'string');
|
|
}
|
|
|
|
// cbAddress is a self-send
|
|
// so all output paths including change should be known
|
|
for (let i = 0; i < tx.outputs.length; i++) {
|
|
const path = tx.outputs[i].path;
|
|
|
|
assert.ok(typeof path.name === 'string');
|
|
assert.ok(typeof path.account === 'number');
|
|
assert.ok(typeof path.change === 'boolean');
|
|
assert.ok(typeof path.derivation === 'string');
|
|
}
|
|
});
|
|
|
|
it('should create a transaction with HD paths', async () => {
|
|
const tx = await wallet.createTX({
|
|
paths: true,
|
|
outputs: [{
|
|
address: 'rs1qlf5se77y0xlg5940slyf00djvveskcsvj9sdrd',
|
|
value: 1e4
|
|
}]
|
|
});
|
|
|
|
assert.ok(tx);
|
|
assert.ok(tx.inputs);
|
|
|
|
for (let i = 0; i < tx.inputs.length; i++) {
|
|
const path = tx.inputs[i].path;
|
|
|
|
assert.ok(typeof path.name === 'string');
|
|
assert.ok(typeof path.account === 'number');
|
|
assert.ok(typeof path.change === 'boolean');
|
|
assert.ok(typeof path.derivation === 'string');
|
|
}
|
|
{
|
|
const path = tx.outputs[1].path; // change
|
|
assert.ok(typeof path.name === 'string');
|
|
assert.ok(typeof path.account === 'number');
|
|
assert.ok(typeof path.change === 'boolean');
|
|
assert.ok(typeof path.derivation === 'string');
|
|
}
|
|
{
|
|
const path = tx.outputs[0].path; // receiver
|
|
assert(!path);
|
|
}
|
|
});
|
|
|
|
it('should create a transaction with a locktime', async () => {
|
|
const locktime = 8e6;
|
|
|
|
const tx = await wallet.createTX({
|
|
locktime: locktime,
|
|
outputs: [{ address: cbAddress, value: 1e4 }]
|
|
});
|
|
|
|
assert.equal(tx.locktime, locktime);
|
|
});
|
|
|
|
it('should create a transaction that is not bip 69 sorted', async () => {
|
|
// create a list of outputs that descend in value
|
|
// bip 69 sorts in ascending order based on the value
|
|
const outputs = [];
|
|
for (let i = 0; i < 5; i++) {
|
|
const addr = await wallet.createAddress('default');
|
|
outputs.push({ address: addr.address, value: (5 - i) * 1e5 });
|
|
}
|
|
|
|
const tx = await wallet.createTX({
|
|
outputs: outputs,
|
|
sort: false
|
|
});
|
|
|
|
// assert outputs in the same order that they were sent from the client
|
|
for (const [i, output] of outputs.entries()) {
|
|
assert.equal(tx.outputs[i].value, output.value);
|
|
assert.equal(tx.outputs[i].address.toString(network), output.address);
|
|
}
|
|
|
|
const mtx = MTX.fromJSON(tx);
|
|
mtx.sortMembers();
|
|
|
|
// the order changes after sorting
|
|
assert.ok(tx.outputs[0].value !== mtx.outputs[0].value);
|
|
});
|
|
|
|
it('should create a transaction that is bip 69 sorted', async () => {
|
|
const outputs = [];
|
|
for (let i = 0; i < 5; i++) {
|
|
const addr = await wallet.createAddress('default');
|
|
outputs.push({ address: addr.address, value: (5 - i) * 1e5 });
|
|
}
|
|
|
|
const tx = await wallet.createTX({
|
|
outputs: outputs
|
|
});
|
|
|
|
const mtx = MTX.fromJSON(tx);
|
|
mtx.sortMembers();
|
|
|
|
// assert the ordering of the outputs is the
|
|
// same after sorting the response client side
|
|
for (const [i, output] of tx.outputs.entries()) {
|
|
assert.equal(output.value, mtx.outputs[i].value);
|
|
assert.equal(output.address, mtx.outputs[i].address.toString(network));
|
|
}
|
|
});
|
|
|
|
it('should mine to the secondary/default wallet', async () => {
|
|
const height = 5;
|
|
|
|
const {address} = await wallet2.createAddress('default');
|
|
await nodeCtx.mineBlocks(height, address);
|
|
|
|
const accountInfo = await wallet2.getAccount('default');
|
|
assert.equal(accountInfo.balance.coin, height);
|
|
});
|
|
});
|
|
|
|
describe('Get balance', function() {
|
|
before(async () => {
|
|
await beforeAll();
|
|
await nodeCtx.mineBlocks(20, cbAddress);
|
|
});
|
|
|
|
after(afterAll);
|
|
|
|
it('should get balance', async () => {
|
|
const balance = await wallet.getBalance();
|
|
assert.equal(balance.tx, 20);
|
|
assert.equal(balance.coin, 20);
|
|
});
|
|
});
|
|
|
|
describe('Get TX', function() {
|
|
let hash;
|
|
|
|
before(async () => {
|
|
await beforeAll();
|
|
|
|
await nodeCtx.mineBlocks(10, cbAddress);
|
|
const {address} = await wallet.createAddress('default');
|
|
const tx = await wallet.send({outputs: [{address, value: 1e4}]});
|
|
|
|
hash = tx.hash;
|
|
});
|
|
|
|
after(afterAll);
|
|
|
|
it('should fail to get TX that does not exist', async () => {
|
|
const hash = consensus.ZERO_HASH;
|
|
const tx = await wallet.getTX(hash.toString('hex'));
|
|
assert.strictEqual(tx, null);
|
|
});
|
|
|
|
it('should get TX', async () => {
|
|
const tx = await wallet.getTX(hash.toString('hex'));
|
|
assert(tx);
|
|
assert.strictEqual(tx.hash, hash);
|
|
});
|
|
});
|
|
|
|
describe('Create account (Integration)', function() {
|
|
before(beforeAll);
|
|
after(afterAll);
|
|
|
|
it('should create an account', async () => {
|
|
const info = await wallet.createAccount('foo');
|
|
assert(info);
|
|
assert(info.initialized);
|
|
assert.strictEqual(info.name, 'foo');
|
|
assert.strictEqual(info.accountIndex, 1);
|
|
assert.strictEqual(info.m, 1);
|
|
assert.strictEqual(info.n, 1);
|
|
});
|
|
|
|
it('should create account', async () => {
|
|
const info = await wallet.createAccount('foo1');
|
|
assert(info);
|
|
assert(info.initialized);
|
|
assert.strictEqual(info.name, 'foo1');
|
|
assert.strictEqual(info.accountIndex, 2);
|
|
assert.strictEqual(info.m, 1);
|
|
assert.strictEqual(info.n, 1);
|
|
});
|
|
|
|
it('should create account', async () => {
|
|
const info = await wallet.createAccount('foo2', {
|
|
type: 'multisig',
|
|
m: 1,
|
|
n: 2
|
|
});
|
|
assert(info);
|
|
assert(!info.initialized);
|
|
assert.strictEqual(info.name, 'foo2');
|
|
assert.strictEqual(info.accountIndex, 3);
|
|
assert.strictEqual(info.m, 1);
|
|
assert.strictEqual(info.n, 2);
|
|
});
|
|
});
|
|
|
|
describe('Wallet auction (Integration)', function() {
|
|
const accountTwo = 'foobar';
|
|
|
|
let name, wallet2;
|
|
|
|
const ownedNames = [];
|
|
const allNames = [];
|
|
|
|
before(async () => {
|
|
await beforeAll();
|
|
|
|
await nodeCtx.mineBlocks(20, cbAddress);
|
|
await wallet.createAccount(accountTwo);
|
|
|
|
await wclient.createWallet('secondary');
|
|
wallet2 = wclient.wallet('secondary');
|
|
const saddr = (await wallet2.createAddress('default')).address;
|
|
await nodeCtx.mineBlocks(5, saddr);
|
|
});
|
|
|
|
after(afterAll);
|
|
|
|
beforeEach(async () => {
|
|
name = await nclient.execute('grindname', [5]);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await nodeCtx.mempool.reset();
|
|
});
|
|
|
|
it('should have no name state indexed initially', async () => {
|
|
const names = await wallet.getNames();
|
|
assert.strictEqual(names.length, 0);
|
|
});
|
|
|
|
it('should allow covenants with create tx', async () => {
|
|
const {address} = await wallet.createChange('default');
|
|
|
|
const output = openOutput(name, address);
|
|
|
|
const tx = await wallet.createTX({outputs: [output]});
|
|
assert.equal(tx.outputs[0].covenant.type, types.OPEN);
|
|
});
|
|
|
|
it('should allow covenants with send tx', async () => {
|
|
const {address} = await wallet.createChange('default');
|
|
|
|
const output = openOutput(name, address);
|
|
|
|
const tx = await wallet.send({outputs: [output]});;
|
|
assert.equal(tx.outputs[0].covenant.type, types.OPEN);
|
|
});
|
|
|
|
it('should create an open and broadcast the tx', async () => {
|
|
let emitted = 0;
|
|
const handler = () => emitted++;
|
|
nodeCtx.mempool.on('tx', handler);
|
|
|
|
const mempoolTXEvent = common.forEvent(nodeCtx.mempool, 'tx');
|
|
const json = await wallet.createOpen({
|
|
name: name
|
|
});
|
|
await mempoolTXEvent;
|
|
|
|
const mempool = await nodeCtx.nclient.getMempool();
|
|
|
|
assert.ok(mempool.includes(json.hash));
|
|
|
|
const opens = json.outputs.filter(output => output.covenant.type === types.OPEN);
|
|
assert.equal(opens.length, 1);
|
|
|
|
assert.equal(emitted, 1);
|
|
|
|
// reset for next test
|
|
nodeCtx.mempool.removeListener('tx', handler);
|
|
});
|
|
|
|
it('should create an open and not broadcast the transaction', async () => {
|
|
let entered = false;
|
|
const handler = () => entered = true;
|
|
nodeCtx.mempool.on('tx', handler);
|
|
|
|
const json = await wallet.createOpen({
|
|
name: name,
|
|
broadcast: false
|
|
});
|
|
|
|
await sleep(200);
|
|
|
|
// tx is not in the mempool
|
|
assert.equal(entered, false);
|
|
const mempool = await nclient.getMempool();
|
|
assert.ok(!mempool.includes(json.hash));
|
|
|
|
const mtx = MTX.fromJSON(json);
|
|
assert.ok(mtx.hasWitness());
|
|
|
|
// the signature and pubkey are templated correctly
|
|
const sig = mtx.inputs[0].witness.get(0);
|
|
assert.ok(isSignatureEncoding(sig));
|
|
const pubkey = mtx.inputs[0].witness.get(1);
|
|
assert.ok(isKeyEncoding(pubkey));
|
|
assert.ok(secp256k1.publicKeyVerify(pubkey));
|
|
|
|
// transaction is valid
|
|
assert.ok(mtx.verify());
|
|
|
|
const opens = mtx.outputs.filter(output => output.covenant.type === types.OPEN);
|
|
assert.equal(opens.length, 1);
|
|
|
|
// reset for next test
|
|
nodeCtx.mempool.removeListener('tx', handler);
|
|
});
|
|
|
|
it('should create an open and not sign the transaction', async () => {
|
|
let entered = false;
|
|
const handler = () => entered = true;
|
|
nodeCtx.mempool.on('tx', handler);
|
|
|
|
const json = await wallet.createOpen({
|
|
name: name,
|
|
broadcast: false,
|
|
sign: false
|
|
});
|
|
|
|
await sleep(200);
|
|
|
|
// tx is not in the mempool
|
|
assert.equal(entered, false);
|
|
const mempool = await nclient.getMempool();
|
|
assert.ok(!mempool.includes(json.hash));
|
|
|
|
// the signature is templated as an
|
|
// empty buffer
|
|
const mtx = MTX.fromJSON(json);
|
|
const sig = mtx.inputs[0].witness.get(0);
|
|
assert.bufferEqual(Buffer.from(''), sig);
|
|
assert.ok(!isSignatureEncoding(sig));
|
|
|
|
// the pubkey is properly templated
|
|
const pubkey = mtx.inputs[0].witness.get(1);
|
|
assert.ok(isKeyEncoding(pubkey));
|
|
assert.ok(secp256k1.publicKeyVerify(pubkey));
|
|
|
|
// transaction not valid
|
|
assert.equal(mtx.verify(), false);
|
|
|
|
// reset for next test
|
|
nodeCtx.mempool.removeListener('tx', handler);
|
|
});
|
|
|
|
it('should throw error with incompatible broadcast and sign options', async () => {
|
|
const fn = async () => await (wallet.createOpen({
|
|
name: name,
|
|
broadcast: true,
|
|
sign: false
|
|
}));
|
|
|
|
await assert.rejects(fn, {message: 'Must sign when broadcasting.'});
|
|
});
|
|
|
|
it('should fail to create open for account with no monies', async () => {
|
|
const info = await wallet.getAccount(accountTwo);
|
|
assert.equal(info.balance.tx, 0);
|
|
assert.equal(info.balance.coin, 0);
|
|
|
|
const fn = async () => (await wallet.createOpen({
|
|
name: name,
|
|
account: accountTwo
|
|
}));
|
|
|
|
await assert.rejects(fn, {message: /Not enough funds./});
|
|
});
|
|
|
|
it('should mine to the account with no monies', async () => {
|
|
const height = 5;
|
|
|
|
const {receiveAddress} = await wallet.getAccount(accountTwo);
|
|
|
|
await nodeCtx.mineBlocks(height, receiveAddress);
|
|
|
|
const info = await wallet.getAccount(accountTwo);
|
|
assert.equal(info.balance.tx, height);
|
|
assert.equal(info.balance.coin, height);
|
|
});
|
|
|
|
it('should create open for specific account', async () => {
|
|
const json = await wallet.createOpen({
|
|
name: name,
|
|
account: accountTwo
|
|
});
|
|
|
|
const info = await wallet.getAccount(accountTwo);
|
|
|
|
// assert that each of the inputs belongs to the account
|
|
for (const {address} of json.inputs) {
|
|
const keyInfo = await wallet.getKey(address);
|
|
assert.equal(keyInfo.name, info.name);
|
|
}
|
|
});
|
|
|
|
it('should open an auction', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
// save chain height for later comparison
|
|
const info = await nclient.getInfo();
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
const json = await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
const bids = json.outputs.filter(output => output.covenant.type === types.BID);
|
|
assert.equal(bids.length, 1);
|
|
|
|
const [bid] = bids;
|
|
assert.equal(bid.covenant.items.length, 4);
|
|
|
|
const [nameHash, start, rawName, blind] = bid.covenant.items;
|
|
assert.equal(nameHash, rules.hashName(name).toString('hex'));
|
|
|
|
// initially opened in the first block mined, so chain.height + 1
|
|
const hex = Buffer.from(start, 'hex').reverse().toString('hex');
|
|
assert.equal(parseInt(hex, 16), info.chain.height + 1);
|
|
|
|
assert.equal(rawName, Buffer.from(name, 'ascii').toString('hex'));
|
|
|
|
// blind is type string, so 32 * 2
|
|
assert.equal(blind.length, 32 * 2);
|
|
});
|
|
|
|
it('should be able to get nonce', async () => {
|
|
const bid = 100;
|
|
|
|
const response = await wallet.getNonce(name, {
|
|
address: cbAddress,
|
|
bid: bid
|
|
});
|
|
|
|
const address = Address.fromString(cbAddress, network.type);
|
|
const nameHash = rules.hashName(name);
|
|
|
|
const primary = nodeCtx.wdb.primary;
|
|
const nonces = await primary.generateNonces(nameHash, address, bid);
|
|
const blinds = nonces.map(nonce => rules.blind(bid, nonce));
|
|
|
|
assert.deepStrictEqual(response, {
|
|
address: address.toString(network.type),
|
|
blinds: blinds.map(blind => blind.toString('hex')),
|
|
nonces: nonces.map(nonce => nonce.toString('hex')),
|
|
bid: bid,
|
|
name: name,
|
|
nameHash: nameHash.toString('hex')
|
|
});
|
|
});
|
|
|
|
it('should be able to get nonce for bid=0', async () => {
|
|
const bid = 0;
|
|
|
|
const response = await wallet.getNonce(name, {
|
|
address: cbAddress,
|
|
bid: bid
|
|
});
|
|
|
|
const address = Address.fromString(cbAddress, network.type);
|
|
const nameHash = rules.hashName(name);
|
|
|
|
const primary = nodeCtx.wdb.primary;
|
|
const nonces = await primary.generateNonces(nameHash, address, bid);
|
|
const blinds = nonces.map(nonce => rules.blind(bid, nonce));
|
|
|
|
assert.deepStrictEqual(response, {
|
|
address: address.toString(network.type),
|
|
blinds: blinds.map(blind => blind.toString('hex')),
|
|
nonces: nonces.map(nonce => nonce.toString('hex')),
|
|
bid: bid,
|
|
name: name,
|
|
nameHash: nameHash.toString('hex')
|
|
});
|
|
});
|
|
|
|
it('should get name info', async () => {
|
|
const names = await wallet.getNames();
|
|
|
|
assert.strictEqual(allNames.length, names.length);
|
|
|
|
assert(names.length > 0);
|
|
const [ns] = names;
|
|
|
|
const nameInfo = await wallet.getName(ns.name);
|
|
|
|
assert.deepEqual(ns, nameInfo);
|
|
});
|
|
|
|
it('should fail to open a bid without a bid value', async () => {
|
|
const fn = async () => (await wallet.createBid({
|
|
name: name
|
|
}));
|
|
|
|
await assert.rejects(fn, {message: 'Bid is required.'});
|
|
});
|
|
|
|
it('should fail to open a bid without a lockup value', async () => {
|
|
const fn = async () => (await wallet.createBid({
|
|
name: name,
|
|
bid: 1000
|
|
}));
|
|
|
|
await assert.rejects(fn, {message: 'Lockup is required.'});
|
|
});
|
|
|
|
it('should send bid with 0 value and non-dust lockup', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 0,
|
|
lockup: 1000
|
|
});
|
|
});
|
|
|
|
it('should fail to send bid with 0 value and 0 lockup', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
const fn = async () => await wallet.createBid({
|
|
name: name,
|
|
bid: 0,
|
|
lockup: 0
|
|
});
|
|
|
|
await assert.rejects(fn, {message: 'Output is dust.'});
|
|
});
|
|
|
|
it('should get all bids (single player)', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
const tx1 = await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
const tx2 = await wallet.createBid({
|
|
name: name,
|
|
bid: 2000,
|
|
lockup: 3000
|
|
});
|
|
|
|
const tx3 = await wallet.createBid({
|
|
name: name,
|
|
bid: 4000,
|
|
lockup: 5000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(1, cbAddress);
|
|
|
|
// this method gets all bids for all names
|
|
const bids = await wallet.getBids();
|
|
|
|
// this depends on this it block creating
|
|
// the first bids of this test suite
|
|
assert.equal(bids.length, 3);
|
|
assert.ok(bids.every(bid => bid.name === name));
|
|
assert.ok(bids.every(bid => bid.height === nodeCtx.height));
|
|
|
|
// tx1
|
|
assert.ok(bids.find(bid =>
|
|
(bid.value === 1000
|
|
&& bid.lockup === 2000
|
|
&& bid.prevout.hash === tx1.hash)
|
|
));
|
|
|
|
// tx2
|
|
assert.ok(bids.find(bid =>
|
|
(bid.value === 2000
|
|
&& bid.lockup === 3000
|
|
&& bid.prevout.hash === tx2.hash)
|
|
));
|
|
|
|
// tx3
|
|
assert.ok(bids.find(bid =>
|
|
(bid.value === 4000
|
|
&& bid.lockup === 5000
|
|
&& bid.prevout.hash === tx3.hash)
|
|
));
|
|
});
|
|
|
|
it('should get all bids (two players)', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
const tx1 = await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
const tx2 = await wallet2.createBid({
|
|
name: name,
|
|
bid: 2000,
|
|
lockup: 3000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(1, cbAddress);
|
|
|
|
{
|
|
await sleep(100);
|
|
// fetch all bids for the name
|
|
const bids = await wallet.getBidsByName(name);
|
|
assert.equal(bids.length, 2);
|
|
|
|
assert.ok(bids.every(bid => bid.height === nodeCtx.height));
|
|
|
|
// there is no value property on bids
|
|
// from other wallets
|
|
assert.ok(bids.find(bid =>
|
|
(bid.lockup === 2000
|
|
&& bid.prevout.hash === tx1.hash)
|
|
));
|
|
|
|
assert.ok(bids.find(bid =>
|
|
(bid.lockup === 3000
|
|
&& bid.prevout.hash === tx2.hash)
|
|
));
|
|
}
|
|
|
|
{
|
|
// fetch only own bids for the name
|
|
const bids = await wallet.getBidsByName(name, {own: true});
|
|
assert.equal(bids.length, 1);
|
|
const [bid] = bids;
|
|
assert.equal(bid.prevout.hash, tx1.hash);
|
|
assert.strictEqual(bid.height, nodeCtx.height);
|
|
}
|
|
});
|
|
|
|
it('should create a reveal', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
const {info} = await nclient.execute('getnameinfo', [name]);
|
|
assert.equal(info.name, name);
|
|
assert.equal(info.state, 'REVEAL');
|
|
|
|
const json = await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
const reveals = json.outputs.filter(output => output.covenant.type === types.REVEAL);
|
|
assert.equal(reveals.length, 1);
|
|
});
|
|
|
|
it('should create all reveals', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
}
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
const {info} = await nclient.execute('getnameinfo', [name]);
|
|
assert.equal(info.name, name);
|
|
assert.equal(info.state, 'REVEAL');
|
|
|
|
const json = await wallet.createReveal();
|
|
|
|
const reveals = json.outputs.filter(output => output.covenant.type === types.REVEAL);
|
|
assert.equal(reveals.length, 3);
|
|
|
|
ownedNames.push(name);
|
|
|
|
await nodeCtx.mineBlocks(1, cbAddress);
|
|
|
|
const allReveals = await wallet.getReveals();
|
|
assert.strictEqual(allReveals.length, 3);
|
|
assert.ok(allReveals.every(reveal => reveal.name === name));
|
|
assert.ok(allReveals.every(reveal => reveal.height === nodeCtx.height));
|
|
|
|
const revealsByName = await wallet.getRevealsByName(name);
|
|
assert.strictEqual(revealsByName.length, 3);
|
|
assert.ok(revealsByName.every(reveal => reveal.name === name));
|
|
assert.ok(revealsByName.every(reveal => reveal.height === nodeCtx.height));
|
|
});
|
|
|
|
it('should get all reveals (single player)', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
const name2 = await nclient.execute('grindname', [5]);
|
|
|
|
await wallet.createOpen({
|
|
name: name2
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
allNames.push(name2);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await wallet.createBid({
|
|
name: name2,
|
|
bid: 2000,
|
|
lockup: 3000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await wallet.createReveal({
|
|
name: name2
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// Confirmed REVEAL with highest bid makes wallet the owner
|
|
ownedNames.push(name);
|
|
ownedNames.push(name2);
|
|
|
|
{
|
|
const reveals = await wallet.getReveals();
|
|
assert.equal(reveals.length, 5);
|
|
}
|
|
|
|
{
|
|
// a single reveal per name
|
|
const reveals = await wallet.getRevealsByName(name);
|
|
const [reveal] = reveals;
|
|
assert.strictEqual(reveal.height + revealPeriod, nodeCtx.height);
|
|
assert.equal(reveals.length, 1);
|
|
}
|
|
});
|
|
|
|
// this test creates namestate to use duing the
|
|
// next test, hold on to the name being used.
|
|
const state = {
|
|
name: '',
|
|
bids: [],
|
|
reveals: []
|
|
};
|
|
|
|
it('should get own reveals (two players)', async () => {
|
|
state.name = name;
|
|
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
const b1 = await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
const b2 = await wallet2.createBid({
|
|
name: name,
|
|
bid: 2000,
|
|
lockup: 3000
|
|
});
|
|
|
|
state.bids.push(b1);
|
|
state.bids.push(b2);
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
const r1 = await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
const r2 = await wallet2.createReveal({
|
|
name: name
|
|
});
|
|
|
|
state.reveals.push(r1);
|
|
state.reveals.push(r2);
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// wallet did not win this auction so name is not pushed to ownedNames[]
|
|
|
|
{
|
|
const reveals = await wallet.getRevealsByName(name, {own: true});
|
|
assert.strictEqual(reveals.length, 1);
|
|
const [reveal] = reveals;
|
|
assert.strictEqual(reveal.bidPrevout.hash, state.bids[0].hash);
|
|
assert.strictEqual(reveal.bidPrevout.index, 0);
|
|
assert.strictEqual(reveal.own, true);
|
|
assert.strictEqual(reveal.prevout.hash, r1.hash);
|
|
}
|
|
|
|
{
|
|
const reveals = await wallet.getRevealsByName(name);
|
|
assert.strictEqual(reveals.length, 2);
|
|
|
|
const r1 = reveals.find(reveal =>
|
|
reveal.prevout.hash === state.reveals[0].hash);
|
|
const r2 = reveals.find(reveal =>
|
|
reveal.prevout.hash === state.reveals[1].hash);
|
|
|
|
assert.ok(r1);
|
|
assert.ok(r2);
|
|
|
|
assert.strictEqual(r1.bidPrevout.hash, state.bids[0].hash);
|
|
assert.strictEqual(r1.bidPrevout.index, 0);
|
|
|
|
assert.strictEqual(r2.bidPrevout.hash, state.bids[1].hash);
|
|
assert.strictEqual(r2.bidPrevout.index, 0);
|
|
}
|
|
|
|
const dump = await nodeCtx.wdb.dump();
|
|
const dumpSlice = {};
|
|
|
|
Object.keys(dump).filter((key) => {
|
|
const wid1 = '7400000001';
|
|
// txdblayout.t
|
|
if (key.startsWith(wid1 + '74'))
|
|
dumpSlice[key] = dump[key];
|
|
|
|
// txdblayout.i
|
|
if (key.startsWith(wid1 + '69'))
|
|
dumpSlice[key] = dump[key];
|
|
|
|
// txdblayout.B
|
|
if (key.startsWith(wid1 + '42'))
|
|
dumpSlice[key] = dump[key];
|
|
});
|
|
|
|
console.log(dumpSlice);
|
|
});
|
|
|
|
it('should get auction info', async () => {
|
|
const ns = await wallet.getName(state.name);
|
|
|
|
const auction = await wallet.getAuctionByName(ns.name);
|
|
|
|
// auction info returns a list of bids
|
|
// and a list of reveals for the name
|
|
assert.ok(Array.isArray(auction.bids));
|
|
assert.ok(Array.isArray(auction.reveals));
|
|
|
|
// 2 bids and 2 reveals in the previous test
|
|
assert.equal(auction.bids.length, 2);
|
|
assert.equal(auction.reveals.length, 2);
|
|
|
|
// ordering can be nondeterministic
|
|
function matchTxId(namestates, target) {
|
|
assert.ok(namestates.find(ns => ns.prevout.hash === target));
|
|
}
|
|
|
|
matchTxId(auction.bids, state.bids[0].hash);
|
|
matchTxId(auction.bids, state.bids[1].hash);
|
|
matchTxId(auction.reveals, state.reveals[0].hash);
|
|
matchTxId(auction.reveals, state.reveals[1].hash);
|
|
});
|
|
|
|
it('should create a bid and a reveal (reveal in advance)', async () => {
|
|
const balanceBeforeTest = await wallet.getBalance();
|
|
const lockConfirmedBeforeTest = balanceBeforeTest.lockedConfirmed;
|
|
const lockUnconfirmedBeforeTest = balanceBeforeTest.lockedUnconfirmed;
|
|
|
|
await wallet.createOpen({ name: name });
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 2, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
const balanceBeforeBid = await wallet.getBalance();
|
|
assert.equal(balanceBeforeBid.lockedConfirmed - lockConfirmedBeforeTest, 0);
|
|
assert.equal(
|
|
balanceBeforeBid.lockedUnconfirmed - lockUnconfirmedBeforeTest,
|
|
0
|
|
);
|
|
|
|
const bidValue = 1000000;
|
|
const lockupValue = 5000000;
|
|
|
|
const auctionTXs = await wallet.client.post(
|
|
`/wallet/${wallet.id}/auction`,
|
|
{
|
|
name: name,
|
|
bid: 1000000,
|
|
lockup: 5000000,
|
|
broadcastBid: true
|
|
}
|
|
);
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
let walletAuction = await wallet.getAuctionByName(name);
|
|
const bidFromWallet = walletAuction.bids.find(
|
|
b => b.prevout.hash === auctionTXs.bid.hash
|
|
);
|
|
assert(bidFromWallet);
|
|
|
|
const { info } = await nclient.execute('getnameinfo', [name]);
|
|
assert.equal(info.name, name);
|
|
assert.equal(info.state, 'REVEAL');
|
|
|
|
const b5 = await wallet.getBalance();
|
|
assert.equal(b5.lockedConfirmed - lockConfirmedBeforeTest, lockupValue);
|
|
assert.equal(b5.lockedUnconfirmed - lockUnconfirmedBeforeTest, lockupValue);
|
|
|
|
await nclient.broadcast(auctionTXs.reveal.hex);
|
|
await nodeCtx.mineBlocks(1, cbAddress);
|
|
|
|
// Confirmed REVEAL with highest bid makes wallet the owner
|
|
ownedNames.push(name);
|
|
|
|
walletAuction = await wallet.getAuctionByName(name);
|
|
const revealFromWallet = walletAuction.reveals.find(
|
|
b => b.prevout.hash === auctionTXs.reveal.hash
|
|
);
|
|
assert(revealFromWallet);
|
|
|
|
const b6 = await wallet.getBalance();
|
|
assert.equal(b6.lockedConfirmed - lockConfirmedBeforeTest, bidValue);
|
|
assert.equal(b6.lockedUnconfirmed - lockUnconfirmedBeforeTest, bidValue);
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
const ns = await nclient.execute('getnameinfo', [name]);
|
|
const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index);
|
|
assert.ok(coin);
|
|
});
|
|
|
|
it('should create a redeem', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
// wallet2 wins the auction, wallet can submit redeem
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await wallet2.createBid({
|
|
name: name,
|
|
bid: 2000,
|
|
lockup: 3000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await wallet2.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// wallet did not win this auction so name is not pushed to ownedNames[]
|
|
|
|
// wallet2 is the winner, therefore cannot redeem
|
|
const fn = async () => (await wallet2.createRedeem({
|
|
name: name
|
|
}));
|
|
|
|
await assert.rejects(
|
|
fn,
|
|
{message: `No reveals to redeem for name: ${name}.`}
|
|
);
|
|
|
|
const json = await wallet.createRedeem({
|
|
name: name
|
|
});
|
|
|
|
const redeem = json.outputs.filter(({covenant}) => covenant.type === types.REDEEM);
|
|
assert.ok(redeem.length > 0);
|
|
});
|
|
|
|
it('should create an update', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// Confirmed REVEAL with highest bid makes wallet the owner
|
|
ownedNames.push(name);
|
|
|
|
{
|
|
const json = await wallet.createUpdate({
|
|
name: name,
|
|
data: {
|
|
records: [
|
|
{
|
|
type: 'TXT',
|
|
txt: ['foobar']
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
// register directly after reveal
|
|
const registers = json.outputs.filter(({covenant}) => covenant.type === types.REGISTER);
|
|
assert.equal(registers.length, 1);
|
|
}
|
|
|
|
// mine a block
|
|
await nodeCtx.mineBlocks(1, cbAddress);
|
|
|
|
{
|
|
const json = await wallet.createUpdate({
|
|
name: name,
|
|
data: {
|
|
records: [
|
|
{
|
|
type: 'TXT',
|
|
txt: ['barfoo']
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
// update after register or update
|
|
const updates = json.outputs.filter(({covenant}) => covenant.type === types.UPDATE);
|
|
assert.equal(updates.length, 1);
|
|
}
|
|
});
|
|
|
|
it('should get name resource', async () => {
|
|
const names = await wallet.getNames();
|
|
// filter out names that have data
|
|
// this test depends on the previous test
|
|
const [ns] = names.filter(n => n.data.length > 0);
|
|
assert(ns);
|
|
|
|
const state = Resource.decode(Buffer.from(ns.data, 'hex'));
|
|
|
|
const resource = await wallet.getResource(ns.name);
|
|
assert(resource);
|
|
const res = Resource.fromJSON(resource);
|
|
|
|
assert.deepEqual(state, res);
|
|
});
|
|
|
|
it('should fail to get name resource for non existent name', async () => {
|
|
const name = await nclient.execute('grindname', [10]);
|
|
|
|
const resource = await wallet.getResource(name);
|
|
assert.equal(resource, null);
|
|
});
|
|
|
|
it('should create a renewal', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// Confirmed REVEAL with highest bid makes wallet the owner
|
|
ownedNames.push(name);
|
|
|
|
await wallet.createUpdate({
|
|
name: name,
|
|
data: {
|
|
records: [
|
|
{
|
|
type: 'TXT',
|
|
txt: ['foobar']
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
// mine up to the earliest point in which a renewal
|
|
// can be submitted, a treeInterval into the future
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
const json = await wallet.createRenewal({
|
|
name
|
|
});
|
|
|
|
const updates = json.outputs.filter(({covenant}) => covenant.type === types.RENEW);
|
|
assert.equal(updates.length, 1);
|
|
});
|
|
|
|
it('should create a transfer', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// Confirmed REVEAL with highest bid makes wallet the owner
|
|
ownedNames.push(name);
|
|
|
|
await wallet.createUpdate({
|
|
name: name,
|
|
data: {
|
|
records: [
|
|
{
|
|
type: 'TXT',
|
|
txt: ['foobar']
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
const {receiveAddress} = await wallet.getAccount(accountTwo);
|
|
|
|
const json = await wallet.createTransfer({
|
|
name,
|
|
address: receiveAddress
|
|
});
|
|
|
|
const xfer = json.outputs.filter(({covenant}) => covenant.type === types.TRANSFER);
|
|
assert.equal(xfer.length, 1);
|
|
});
|
|
|
|
it('should create a finalize', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// Confirmed REVEAL with highest bid makes wallet the owner
|
|
ownedNames.push(name);
|
|
|
|
await wallet.createUpdate({
|
|
name: name,
|
|
data: {
|
|
records: [
|
|
{
|
|
type: 'TXT',
|
|
txt: ['foobar']
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
const {receiveAddress} = await wallet2.getAccount('default');
|
|
|
|
await wallet.createTransfer({
|
|
name,
|
|
address: receiveAddress
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(transferLockup + 1, cbAddress);
|
|
|
|
const json = await wallet.createFinalize({
|
|
name
|
|
});
|
|
|
|
const final = json.outputs.filter(({covenant}) => covenant.type === types.FINALIZE);
|
|
assert.equal(final.length, 1);
|
|
|
|
await nodeCtx.mineBlocks(1, cbAddress);
|
|
|
|
// Confirmed FINALIZE means this wallet is not the owner anymore!
|
|
ownedNames.splice(ownedNames.indexOf(name), 1);
|
|
|
|
const ns = await nclient.execute('getnameinfo', [name]);
|
|
const coin = await nclient.getCoin(ns.info.owner.hash, ns.info.owner.index);
|
|
|
|
assert.equal(coin.address, receiveAddress);
|
|
});
|
|
|
|
it('should create a cancel', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// Confirmed REVEAL with highest bid makes wallet the owner
|
|
ownedNames.push(name);
|
|
|
|
await wallet.createUpdate({
|
|
name: name,
|
|
data: {
|
|
records: [
|
|
{
|
|
type: 'TXT',
|
|
txt: ['foobar']
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
const {receiveAddress} = await wallet.getAccount(accountTwo);
|
|
|
|
await wallet.createTransfer({
|
|
name,
|
|
address: receiveAddress
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(transferLockup + 1, cbAddress);
|
|
|
|
const json = await wallet.createCancel({name});
|
|
|
|
const cancel = json.outputs.filter(({covenant}) => covenant.type === types.UPDATE);
|
|
assert.equal(cancel.length, 1);
|
|
|
|
await nodeCtx.mineBlocks(1, cbAddress);
|
|
|
|
const ns = await nclient.execute('getnameinfo', [name]);
|
|
assert.equal(ns.info.name, name);
|
|
|
|
const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index);
|
|
assert.ok(coin);
|
|
|
|
const keyInfo = await wallet.getKey(coin.address);
|
|
assert.ok(keyInfo);
|
|
});
|
|
|
|
it('should create a revoke', async () => {
|
|
await wallet.createOpen({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Confirmed OPEN adds name to wallet's namemap
|
|
allNames.push(name);
|
|
|
|
await wallet.createBid({
|
|
name: name,
|
|
bid: 1000,
|
|
lockup: 2000
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
await wallet.createReveal({
|
|
name: name
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// Confirmed REVEAL with highest bid makes wallet the owner
|
|
ownedNames.push(name);
|
|
|
|
await wallet.createUpdate({
|
|
name: name,
|
|
data: {
|
|
records: [
|
|
{
|
|
type: 'TXT',
|
|
txt: ['foobar']
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
const json = await wallet.createRevoke({name});
|
|
|
|
const final = json.outputs.filter(({covenant}) => covenant.type === types.REVOKE);
|
|
assert.equal(final.length, 1);
|
|
|
|
await nodeCtx.mineBlocks(1, cbAddress);
|
|
|
|
// Confirmed REVOKE means no one owns this name anymore
|
|
ownedNames.splice(ownedNames.indexOf(name), 1);
|
|
|
|
const ns = await nclient.execute('getnameinfo', [name]);
|
|
assert.equal(ns.info.name, name);
|
|
assert.equal(ns.info.state, 'REVOKED');
|
|
});
|
|
|
|
it('should require passphrase for auction TXs', async () => {
|
|
const passphrase = 'BitDNS!5353';
|
|
await wclient.createWallet('lockedWallet', {passphrase});
|
|
const lockedWallet = await wclient.wallet('lockedWallet');
|
|
|
|
// Fast-forward through the default 60-second unlock timeout
|
|
async function lock() {
|
|
const wallet = await nodeCtx.wdb.get('lockedWallet');
|
|
return wallet.lock();
|
|
}
|
|
await lock();
|
|
|
|
// Wallet is created and encrypted
|
|
const info = await lockedWallet.getInfo();
|
|
assert(info);
|
|
assert(info.master.encrypted);
|
|
|
|
// Fund
|
|
const addr = await lockedWallet.createAddress('default');
|
|
await nodeCtx.mineBlocks(10, addr.address);
|
|
await common.forValue(nodeCtx.wdb, 'height', nodeCtx.chain.height);
|
|
const bal = await lockedWallet.getBalance();
|
|
assert(bal.confirmed > 0);
|
|
|
|
// Open
|
|
await assert.rejects(
|
|
lockedWallet.createOpen({name}),
|
|
{message: 'No passphrase.'}
|
|
);
|
|
|
|
await lockedWallet.createOpen({name, passphrase});
|
|
await lock();
|
|
|
|
await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);
|
|
|
|
// Bid
|
|
await assert.rejects(
|
|
lockedWallet.createBid({name, lockup: 1, bid: 1}),
|
|
{message: 'No passphrase.'}
|
|
);
|
|
|
|
// Send multiple bids, wallet remains unlocked for 60 seconds (all 3 bids)
|
|
await lockedWallet.createBid(
|
|
{name, lockup: 1000000, bid: 1000000, passphrase}
|
|
);
|
|
await lockedWallet.createBid({name, lockup: 2000000, bid: 2000000});
|
|
await lockedWallet.createBid({name, lockup: 3000000, bid: 3000000});
|
|
await lock();
|
|
|
|
await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);
|
|
|
|
// Reveal
|
|
await assert.rejects(
|
|
lockedWallet.createReveal({name}),
|
|
{message: 'No passphrase.'}
|
|
);
|
|
const revealAll = await lockedWallet.createReveal({name, passphrase});
|
|
await lock();
|
|
|
|
// All 3 bids are revealed
|
|
const reveals = revealAll.outputs.filter(
|
|
output => output.covenant.type === types.REVEAL
|
|
);
|
|
assert.equal(reveals.length, 3);
|
|
|
|
await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);
|
|
|
|
// Redeem all by not passing specific name
|
|
await assert.rejects(
|
|
lockedWallet.createRedeem(),
|
|
{message: 'No passphrase.'}
|
|
);
|
|
const redeemAll = await lockedWallet.createRedeem({passphrase});
|
|
await lock();
|
|
|
|
// Only 2 reveals are redeemed (because the third one is the winner)
|
|
const redeems = redeemAll.outputs.filter(
|
|
output => output.covenant.type === types.REDEEM
|
|
);
|
|
assert.equal(redeems.length, 2);
|
|
|
|
// Register
|
|
await assert.rejects(
|
|
lockedWallet.createUpdate({name, data: {records: []}}),
|
|
{message: 'No passphrase.'}
|
|
);
|
|
const register = await lockedWallet.createUpdate(
|
|
{name, data: {records: []}, passphrase}
|
|
);
|
|
await lock();
|
|
|
|
// Only 1 register, only 1 winner!
|
|
const registers = register.outputs.filter(
|
|
output => output.covenant.type === types.REGISTER
|
|
);
|
|
assert.equal(registers.length, 1);
|
|
});
|
|
|
|
it('should get all wallet names', async () => {
|
|
const names = await wallet.getNames();
|
|
|
|
assert.equal(allNames.length, names.length);
|
|
|
|
for (const {name} of names) {
|
|
assert(allNames.includes(name));
|
|
}
|
|
});
|
|
|
|
it('should only get wallet-owned names', async () => {
|
|
const names = await wallet.getNames({ own: true });
|
|
|
|
assert.equal(names.length, ownedNames.length);
|
|
|
|
for (const {name} of names) {
|
|
assert(ownedNames.includes(name));
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('HTTP tx races (Integration)', function() {
|
|
const WNAME1 = 'racetest-1';
|
|
const WNAME2 = 'racetest-2';
|
|
const FUND_VALUE = 1e6;
|
|
const HARD_FEE = 1e4;
|
|
const NAMES = [];
|
|
const PASSPHRASE1 = 'racetest-passphrase-1';
|
|
const PASSPHRASE2 = 'racetest-passphrase-2';
|
|
|
|
let rcwallet1, rcwallet2, wclient;
|
|
let w1addr;
|
|
|
|
const checkDoubleSpends = (txs) => {
|
|
const spentCoins = new BufferSet();
|
|
|
|
for (const tx of txs) {
|
|
for (const input of tx.inputs) {
|
|
const key = input.prevout.toKey();
|
|
|
|
if (spentCoins.has(key))
|
|
throw new Error(`Input ${input.prevout.format()} is already spent.`);
|
|
|
|
spentCoins.add(key);
|
|
}
|
|
}
|
|
};
|
|
|
|
const wMineBlocks = async (n = 1) => {
|
|
const forConnect = common.forEvent(nodeCtx.wdb, 'block connect', n);
|
|
await nodeCtx.mineBlocks(n, w1addr);
|
|
await forConnect;
|
|
};
|
|
|
|
const fundNcoins = async (recvWallet, n, value = FUND_VALUE) => {
|
|
assert(typeof n === 'number');
|
|
for (let i = 0; i < n; i++) {
|
|
const addr = (await recvWallet.createAddress('default')).address;
|
|
|
|
await wallet.send({
|
|
hardFee: HARD_FEE,
|
|
outputs: [{
|
|
address: addr,
|
|
value: value
|
|
}]
|
|
});
|
|
}
|
|
|
|
await wMineBlocks(1);
|
|
};
|
|
|
|
before(async () => {
|
|
await beforeAll();
|
|
|
|
wclient = nodeCtx.wclient;
|
|
rcwallet1 = wclient.wallet(WNAME1);
|
|
rcwallet2 = wclient.wallet(WNAME2);
|
|
|
|
w1addr = (await wallet.createAddress('default')).address;
|
|
const winfo1 = await wclient.createWallet(WNAME1, {
|
|
passphrase: PASSPHRASE1
|
|
});
|
|
|
|
const winfo2 = await wclient.createWallet(WNAME2, {
|
|
passphrase: PASSPHRASE2
|
|
});
|
|
|
|
assert(winfo1);
|
|
assert(winfo2);
|
|
|
|
// Fund primary wallet.
|
|
await wMineBlocks(5);
|
|
});
|
|
|
|
after(afterAll);
|
|
|
|
beforeEach(async () => {
|
|
await rcwallet1.lock();
|
|
await rcwallet2.lock();
|
|
});
|
|
|
|
it('should fund 3 new transactions', async () => {
|
|
const promises = [];
|
|
|
|
await fundNcoins(rcwallet1, 3);
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet1.send({
|
|
passphrase: PASSPHRASE1,
|
|
subtractFee: true,
|
|
hardFee: HARD_FEE,
|
|
outputs: [{
|
|
address: w1addr,
|
|
value: FUND_VALUE
|
|
}]
|
|
}));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(details => MTX.fromHex(details.tx));
|
|
checkDoubleSpends(txs);
|
|
|
|
await forMemTX;
|
|
await wMineBlocks(1);
|
|
|
|
const balance = await rcwallet1.getBalance();
|
|
|
|
assert.strictEqual(balance.confirmed, 0);
|
|
assert.strictEqual(balance.unconfirmed, 0);
|
|
assert.strictEqual(balance.coin, 0);
|
|
});
|
|
|
|
it('should open 3 name auctions', async () => {
|
|
await fundNcoins(rcwallet1, 3);
|
|
|
|
for (let i = 0; i < 3; i++)
|
|
NAMES.push(rules.grindName(10, nodeCtx.chain.tip.height, network));
|
|
|
|
const promises = [];
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 4);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet1.createOpen({
|
|
name: NAMES[i],
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(result => MTX.fromHex(result.hex));
|
|
checkDoubleSpends(txs);
|
|
|
|
// spend all money for now.
|
|
// Passphrase not necessary as the wallet is unlocked.
|
|
await rcwallet1.send({
|
|
subtractFee: true,
|
|
outputs: [{
|
|
value: (FUND_VALUE - HARD_FEE) * 3,
|
|
address: w1addr
|
|
}]
|
|
});
|
|
|
|
await forMemTX;
|
|
await wMineBlocks(1);
|
|
|
|
const balance = await rcwallet1.getBalance();
|
|
// 3 opens (0 value)
|
|
assert.strictEqual(balance.coin, 3);
|
|
assert.strictEqual(balance.confirmed, 0);
|
|
});
|
|
|
|
it('should bid 3 times', async () => {
|
|
const promises = [];
|
|
|
|
// 2 blocks.
|
|
await fundNcoins(rcwallet1, 3);
|
|
await fundNcoins(rcwallet2, 6);
|
|
|
|
// this is 2 blocks ahead, but does not matter for this test.
|
|
await wMineBlocks(network.names.treeInterval + 1);
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3 + 3 * 2);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
// make sure we use ALL coins, no NONE left.
|
|
// winner.
|
|
promises.push(rcwallet1.createBid({
|
|
name: NAMES[i],
|
|
bid: HARD_FEE,
|
|
lockup: HARD_FEE,
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: FUND_VALUE - HARD_FEE
|
|
}));
|
|
|
|
// We want redeemer to not have enough funds
|
|
// to redeem the money back and has to use
|
|
// extra funds for it.
|
|
//
|
|
// ALSO We want to have enough redeems to
|
|
// do redeemAll and redeem.
|
|
for (let j = 0; j < 2; j++) {
|
|
promises.push(rcwallet2.createBid({
|
|
name: NAMES[i],
|
|
bid: HARD_FEE - 1,
|
|
lockup: HARD_FEE - 1,
|
|
passphrase: PASSPHRASE2,
|
|
// lose all funds in fees.
|
|
hardFee: FUND_VALUE - HARD_FEE
|
|
}));
|
|
}
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(result => MTX.fromHex(result.hex));
|
|
checkDoubleSpends(txs);
|
|
|
|
await forMemTX;
|
|
|
|
await wMineBlocks(1);
|
|
const balance1 = await rcwallet1.getBalance();
|
|
const balance2 = await rcwallet2.getBalance();
|
|
|
|
// 3 opens and 3 bids (nothing extra)
|
|
assert.strictEqual(balance1.coin, 6);
|
|
assert.strictEqual(balance1.confirmed, HARD_FEE * 3);
|
|
|
|
// 6 bids (nothing extra)
|
|
assert.strictEqual(balance2.coin, 6);
|
|
assert.strictEqual(balance2.confirmed, (HARD_FEE - 1) * 6);
|
|
});
|
|
|
|
it('should reveal 3 times and reveal all', async () => {
|
|
// Now we don't have fees to reveal. Fund these fees.
|
|
await fundNcoins(rcwallet1, 3, HARD_FEE);
|
|
await fundNcoins(rcwallet2, 1, HARD_FEE);
|
|
|
|
const promises = [];
|
|
|
|
await wMineBlocks(network.names.biddingPeriod);
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 4);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet1.createReveal({
|
|
name: NAMES[i],
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
}
|
|
|
|
// do reveal all
|
|
promises.push(rcwallet2.createReveal({
|
|
passphrase: PASSPHRASE2,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(r => MTX.fromHex(r.hex));
|
|
checkDoubleSpends(txs);
|
|
await forMemTX;
|
|
|
|
await wMineBlocks(1);
|
|
|
|
const balance1 = await rcwallet1.getBalance();
|
|
|
|
// 3 opens and 3 reveals
|
|
assert.strictEqual(balance1.coin, 6);
|
|
assert.strictEqual(balance1.confirmed, HARD_FEE * 3);
|
|
|
|
const balance2 = await rcwallet2.getBalance();
|
|
|
|
// 6 reveals
|
|
assert.strictEqual(balance2.coin, 6);
|
|
assert.strictEqual(balance2.confirmed, (HARD_FEE - 1) * 6);
|
|
await wMineBlocks(network.names.revealPeriod);
|
|
});
|
|
|
|
it('should register 3 times', async () => {
|
|
const promises = [];
|
|
|
|
// We don't have funds to fund anything.
|
|
// Add 3 coins to pay for the fees and cause
|
|
// double spend.
|
|
await fundNcoins(rcwallet1, 3, HARD_FEE);
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet1.createUpdate({
|
|
name: NAMES[i],
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: HARD_FEE,
|
|
data: {
|
|
records: [
|
|
{
|
|
type: 'TXT',
|
|
txt: ['foobar']
|
|
}
|
|
]
|
|
}
|
|
}));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(r => MTX.fromHex(r.hex));
|
|
checkDoubleSpends(txs);
|
|
await forMemTX;
|
|
|
|
await wMineBlocks(1);
|
|
});
|
|
|
|
it('should redeem 3 times and redeem all', async () => {
|
|
const promises = [];
|
|
|
|
await fundNcoins(rcwallet2, 3, HARD_FEE);
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
for (let i = 0; i < 2; i++) {
|
|
promises.push(rcwallet2.createRedeem({
|
|
name: NAMES[i],
|
|
passphrase: PASSPHRASE2,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
}
|
|
|
|
promises.push(rcwallet2.createRedeem({
|
|
hardFee: HARD_FEE
|
|
}));
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(r => MTX.fromHex(r.hex));
|
|
checkDoubleSpends(txs);
|
|
await forMemTX;
|
|
});
|
|
|
|
it('should renew 3 names', async () => {
|
|
const promises = [];
|
|
|
|
await wMineBlocks(network.names.treeInterval);
|
|
await fundNcoins(rcwallet1, 3, HARD_FEE);
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet1.createRenewal({
|
|
name: NAMES[i],
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(r => MTX.fromHex(r.hex));
|
|
checkDoubleSpends(txs);
|
|
await forMemTX;
|
|
|
|
await wMineBlocks(1);
|
|
});
|
|
|
|
it('should transfer 3 names', async () => {
|
|
const promises = [];
|
|
|
|
await fundNcoins(rcwallet1, 3, HARD_FEE);
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
const addrs = [
|
|
(await rcwallet2.createAddress('default')).address,
|
|
(await rcwallet2.createAddress('default')).address,
|
|
(await rcwallet2.createAddress('default')).address
|
|
];
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet1.createTransfer({
|
|
name: NAMES[i],
|
|
address: addrs[i],
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(r => MTX.fromHex(r.hex));
|
|
checkDoubleSpends(txs);
|
|
await forMemTX;
|
|
await wMineBlocks(1);
|
|
});
|
|
|
|
it('should cancel 3 names', async () => {
|
|
const promises = [];
|
|
|
|
await fundNcoins(rcwallet1, 3, HARD_FEE);
|
|
|
|
const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet1.createCancel({
|
|
name: NAMES[i],
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(r => MTX.fromHex(r.hex));
|
|
checkDoubleSpends(txs);
|
|
await forMemTX;
|
|
await wMineBlocks(1);
|
|
});
|
|
|
|
it('should finalize 3 names', async () => {
|
|
await fundNcoins(rcwallet1, 6, HARD_FEE);
|
|
|
|
let forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
const addrs = [
|
|
(await rcwallet2.createAddress('default')).address,
|
|
(await rcwallet2.createAddress('default')).address,
|
|
(await rcwallet2.createAddress('default')).address
|
|
];
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
await rcwallet1.createTransfer({
|
|
name: NAMES[i],
|
|
address: addrs[i],
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: HARD_FEE
|
|
});
|
|
}
|
|
|
|
await forMemTX;
|
|
await wMineBlocks(network.names.transferLockup);
|
|
|
|
// Now we finalize all.
|
|
const promises = [];
|
|
|
|
forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet1.createFinalize({
|
|
name: NAMES[i],
|
|
passphrase: PASSPHRASE1,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(r => MTX.fromHex(r.hex));
|
|
checkDoubleSpends(txs);
|
|
await forMemTX;
|
|
|
|
await wMineBlocks(1);
|
|
});
|
|
|
|
it('should revoke 3 names', async () => {
|
|
// send them back
|
|
await fundNcoins(rcwallet2, 6, HARD_FEE);
|
|
|
|
let forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
|
|
const addrs = [
|
|
(await rcwallet1.createAddress('default')).address,
|
|
(await rcwallet1.createAddress('default')).address,
|
|
(await rcwallet1.createAddress('default')).address
|
|
];
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
await rcwallet2.createTransfer({
|
|
name: NAMES[i],
|
|
address: addrs[i],
|
|
passphrase: PASSPHRASE2,
|
|
hardFee: HARD_FEE
|
|
});
|
|
}
|
|
|
|
await forMemTX;
|
|
await wMineBlocks(network.names.transferLockup);
|
|
|
|
forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
|
|
const promises = [];
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
promises.push(rcwallet2.createRevoke({
|
|
name: NAMES[i],
|
|
passphrase: PASSPHRASE2,
|
|
hardFee: HARD_FEE
|
|
}));
|
|
}
|
|
|
|
const results = await Promise.all(promises);
|
|
const txs = results.map(r => MTX.fromHex(r.hex));
|
|
checkDoubleSpends(txs);
|
|
await forMemTX;
|
|
});
|
|
});
|
|
});
|
|
|
|
async function sleep(time) {
|
|
return new Promise(resolve => setTimeout(resolve, time));
|
|
}
|
|
|
|
// create an OPEN output
|
|
function openOutput(name, address) {
|
|
const nameHash = rules.hashName(name);
|
|
const rawName = Buffer.from(name, 'ascii');
|
|
|
|
const output = new Output();
|
|
output.address = Address.fromString(address);
|
|
output.value = 0;
|
|
output.covenant.setOpen(nameHash, rawName);
|
|
|
|
return output;
|
|
}
|