test/specs/generic/consensus/base/account/Accounts.spec.js
describe('Accounts', () => {
it('cannot commit a wrong block', (done) => {
(async function () {
const testBlockchain = await TestBlockchain.createVolatileTest(0, 4);
const block = await testBlockchain.createBlock();
const accounts = await Accounts.createVolatile();
let error_thrown = false;
try {
await accounts.commitBlock(block);
} catch (e) {
error_thrown = true;
}
expect(error_thrown).toBe(true);
})().then(done, done.fail);
});
it('can apply and revert a block', (done) => {
(async function () {
const testBlockchain = await TestBlockchain.createVolatileTest(0, 4);
const accounts = testBlockchain.accounts;
const accountsHash1 = await accounts.hash();
const block = await testBlockchain.createBlock();
await accounts.commitBlock(block, testBlockchain.transactionCache);
testBlockchain.transactionCache.pushBlock(block);
let accountsHash2 = await accounts.hash();
expect(accountsHash1.equals(accountsHash2)).toEqual(false);
await accounts.revertBlock(block, testBlockchain.transactionCache);
testBlockchain.transactionCache.revertBlock(block);
accountsHash2 = await accounts.hash();
expect(accountsHash1.equals(accountsHash2)).toEqual(true);
})().then(done, done.fail);
});
it('cannot revert invalid blocks', (done) => {
(async function () {
const testBlockchain = await TestBlockchain.createVolatileTest(0, 4);
const accounts = testBlockchain.accounts;
const accountsHash1 = await accounts.hash();
let block = await testBlockchain.createBlock();
await accounts.commitBlock(block, testBlockchain.transactionCache);
testBlockchain.transactionCache.pushBlock(block);
let accountsHash2 = await accounts.hash();
expect(accountsHash1.equals(accountsHash2)).toEqual(false);
block = await testBlockchain.createBlock();
let threw = false;
try {
await accounts.revertBlock(block, testBlockchain.transactionCache);
testBlockchain.transactionCache.revertBlock(block);
} catch (e) {
threw = true;
}
expect(threw).toEqual(true);
})().then(done, done.fail);
});
it('can apply and revert a block with multiple transaction per sender', (done) => {
(async function () {
const testBlockchain = await TestBlockchain.createVolatileTest(0, 5);
const accounts = testBlockchain.accounts;
const user0 = testBlockchain.users[0];
const user1 = testBlockchain.users[1];
const user2 = testBlockchain.users[2];
const user3 = testBlockchain.users[3];
const user4 = testBlockchain.users[4];
const accountsHash1 = await accounts.hash();
const tx1 = TestBlockchain.createTransaction(user0.publicKey, user1.address, 1, 0, 1, user0.privateKey);
const tx2 = TestBlockchain.createTransaction(user0.publicKey, user2.address, 1, 0, 1, user0.privateKey);
const tx3 = TestBlockchain.createTransaction(user0.publicKey, user3.address, 1, 0, 1, user0.privateKey);
const tx4 = TestBlockchain.createTransaction(user0.publicKey, user4.address, 1, 0, 1, user0.privateKey);
const block = await testBlockchain.createBlock({transactions: [tx4, tx2, tx1, tx3], minerAddr: user1.address});
const status = await testBlockchain.pushBlock(block);
expect(status).toBe(FullChain.OK_EXTENDED);
let accountsHash2 = await accounts.hash();
expect(accountsHash1.equals(accountsHash2)).toEqual(false);
await accounts.revertBlock(block, testBlockchain.transactionCache);
accountsHash2 = await accounts.hash();
expect(accountsHash1.equals(accountsHash2)).toEqual(true);
})().then(done, done.fail);
});
it('put and get an account', (done) => {
const balance = 42;
const accountState1 = new BasicAccount(balance);
const accountAddress = Address.unserialize(BufferUtils.fromBase64(Dummy.address2));
(async function () {
const account = await Accounts.createVolatile();
await account._tree.put(accountAddress, accountState1);
const state1 = await account.get(accountAddress);
expect(state1.balance).toBe(accountState1.balance);
// Verify that get() returns Account.INITIAL when called with an unknown address
const state2 = await account.get(Address.unserialize(BufferUtils.fromBase64(Dummy.address3)));
expect(Account.INITIAL.equals(state2)).toBe(true);
})().then(done, done.fail);
});
it('correctly rewards miners', (done) => {
(async function () {
const testBlockchain = await TestBlockchain.createVolatileTest(0, 4);
const user1 = testBlockchain.users[0];
const user2 = testBlockchain.users[1];
const user3 = testBlockchain.users[2];
const user4 = testBlockchain.users[3];
const accounts = testBlockchain.accounts;
// initial setup: user1 mined genesis block with no transactions, user2 has a balance of 0
let balance = (await accounts.get(user2.address)).balance;
expect(balance).toBe(0);
const amount1 = 20;
const fee1 = 10;
const amount2 = 15;
const fee2 = 5;
const transactions = [
TestBlockchain.createTransaction(user1.publicKey, user3.address, amount1, fee1, 1, user1.privateKey),
TestBlockchain.createTransaction(user1.publicKey, user4.address, amount2, fee2, 1, user1.privateKey)
];
const block = await testBlockchain.createBlock({
transactions: transactions,
minerAddr: user2.address
});
await accounts.commitBlock(block, testBlockchain.transactionCache);
// now: expect user2 to have received the transaction fees and block reward
balance = (await testBlockchain.accounts.get(user2.address, Account.Type.BASIC)).balance;
expect(balance).toBe(Policy.blockRewardAt(block.height) + fee1 + fee2);
})().then(done, done.fail);
});
it('can safely roll-back an invalid block', (done) => {
(async function () {
const testBlockchain = await TestBlockchain.createVolatileTest(0, 4);
const user1 = testBlockchain.users[0]; // sender tx 1
const user2 = testBlockchain.users[1]; // sender tx 2
const user3 = testBlockchain.users[2]; // receiver tx 1 + 2
const user4 = testBlockchain.users[3]; // receiver fees tx 1 + 2
const amount1 = 250;
const amount2 = 7;
const fee = 3;
// user1 -- 250(+3) --> user3 (valid)
// user2 ---- 7(+3) --> user3 (invalid, user2 has balance 0)
const transactions = [
TestBlockchain.createTransaction(user1.publicKey, user3.address, amount1, fee, 0, user1.privateKey),
TestBlockchain.createTransaction(user2.publicKey, user3.address, amount2, fee, 0, user1.privateKey)
];
const block = await testBlockchain.createBlock({transactions: transactions});
const accounts = testBlockchain.accounts;
// we expect rejection of block
try {
await accounts.commitBlock(block, testBlockchain.transactionCache);
} catch (e) {
const balance1 = (await accounts.get(user1.address, Account.Type.BASIC)).balance;
const balance3 = (await accounts.get(user3.address)).balance;
const balance4 = (await accounts.get(user4.address)).balance;
expect(balance1).toBe(Policy.blockRewardAt(block.height - 1));
expect(balance3).toBe(0);
expect(balance4).toBe(0);
done();
return;
}
throw 'Invalid block not rejected';
})().then(done, done.fail);
});
it('can handle a large amount of block transactions', (done) => {
(async function test() {
const testBlockchain = await TestBlockchain.createVolatileTest(0, 4);
const accounts = testBlockchain.accounts;
const numTransactions = 720;
const sender = testBlockchain.users[0];
const transactions = [];
for (let i = 0; i < numTransactions; i++) {
const recipient = Address.fromHash(Hash.blake2b(BufferUtils.fromAscii(`tx${i}`)));
transactions.push(TestBlockchain.createTransaction(sender.publicKey, recipient, 1, 1, 1, sender.privateKey));
}
transactions.sort((a, b) => a.compareBlockOrder(b));
const time = new Time();
const block = await testBlockchain.createBlock({
transactions: transactions
});
expect(await block.verify(time)).toBeTruthy();
expect(await accounts.commitBlock(block, testBlockchain.transactionCache)).toBeTruthy();
})().then(done, done.fail);
});
// note that a lot of possible errors are already tested in the blockchain, transaction and block specs.
it('rejects invalid transactions', (done) => {
(async function () {
const testBlockchain = await TestBlockchain.createVolatileTest(0, 4);
const user1 = testBlockchain.users[0];
const user2 = testBlockchain.users[1];
const user3 = testBlockchain.users[2];
const accounts = testBlockchain.accounts;
// sender balance not enough (amount + fee > block reward)
const transaction = TestBlockchain.createTransaction(user1.publicKey, user2.address, Policy.blockRewardAt(1) + 5, 1, 0, user1.privateKey);
const block = await testBlockchain.createBlock({
transactions: [transaction],
minerAddr: user3.address
});
let error = false;
try {
await accounts.commitBlock(block, testBlockchain.transactionCache);
} catch(e) {
expect(e.message.toLowerCase()).toContain('balance error!');
error = true;
}
expect(error).toBe(true);
// sender balance will be enough AFTER block is mined -> make sender also miner (should still fail)
block.body._minerAddr = user1.address;
error = false;
try {
await accounts.commitBlock(block, testBlockchain.transactionCache);
} catch(e) {
expect(e.message.toLowerCase()).toContain('balance error!');
error = true;
}
expect(error).toBe(true);
})().then(done, done.fail);
});
it('can initialize genesis accounts', (done) => {
(async () => {
const map = new Map();
map.set(Address.fromBase64(Dummy.address1), new BasicAccount(5));
map.set(Address.fromBase64(Dummy.address2), new VestingContract(10, Address.fromBase64(Dummy.address2), 0, 10, 2));
map.set(Address.fromBase64(Dummy.address3), new VestingContract(20, Address.fromBase64(Dummy.address1), 0, 20, 5));
let size = 2;
for (const entry of map.entries()) {
size += entry[0].serializedSize + entry[1].serializedSize;
}
const buf = new SerialBuffer(size);
buf.writeUint16(map.size);
for (const entry of map.entries()) {
entry[0].serialize(buf);
entry[1].serialize(buf);
}
const genesis = new Block(
new BlockHeader(Hash.NULL, Hash.NULL, GenesisConfig.GENESIS_BLOCK.bodyHash, Hash.fromBase64('IDpF4aKOYPHMBBOHHoTvF8SdybqwwtQ7qd3AFgWq1sU=') /* TODO */, BlockUtils.difficultyToCompact(1), 1, 0, 0),
GenesisConfig.GENESIS_BLOCK.interlink,
GenesisConfig.GENESIS_BLOCK.body
);
const accounts = await Accounts.createVolatile();
await accounts.initialize(genesis, BufferUtils.toBase64(buf));
expect((await accounts.get(genesis.minerAddr)).equals(new BasicAccount(Policy.blockRewardAt(1)))).toBe(true);
for (const entry of map.entries()) {
expect((await accounts.get(entry[0])).equals(entry[1])).toBe(true);
}
})().then(done, done.fail);
});
});