test/specs/generic/consensus/base/account/tree/AccountsProof.spec.js
describe('AccountsProof', () => {
let sizesArray, accountsArray, prefixesArray, testNodesArray;
/*
* We're going to construct three proofs based on this tree:
*
* R1
* |
* B1
* / | \
* T1 B2 T2
* / \
* T3 T4
*
* The first proof proves the 4 terminal nodes (T1, T2, T3 and T4)
* The second proof proves the 2 leftmost terminal nodes (T1 and T3)
* The third proof just proves T4
*/
beforeEach(() => {
const account1 = new BasicAccount(25);
const account2 = new BasicAccount(1);
const account3 = new BasicAccount(1322);
const account4 = new BasicAccount(93);
const t1 = AccountsTreeNode.terminalNode('0011111111111111111111111111111111111111', account1);
const t1Hash = t1.hash();
const t2 = AccountsTreeNode.terminalNode('0033333333333333333333333333333333333333', account2);
const t2Hash = t2.hash();
const t3 = AccountsTreeNode.terminalNode('0020000000000000000000000000000000000000', account3);
const t3Hash = t3.hash();
const t4 = AccountsTreeNode.terminalNode('0022222222222222222222222222222222222222', account4);
const t4Hash = t4.hash();
const b2 = AccountsTreeNode.branchNode('002', ['0000000000000000000000000000000000000', undefined, '2222222222222222222222222222222222222'], [t3Hash, undefined, t4Hash]);
const b2Hash = b2.hash();
const b1 = AccountsTreeNode.branchNode('00', [undefined, '11111111111111111111111111111111111111', '2', '33333333333333333333333333333333333333'], [undefined, t1Hash, b2Hash, t2Hash]);
const b1Hash = b1.hash();
const r1 = AccountsTreeNode.branchNode('', ['00'], [b1Hash]);
const nodes1 = [t1, t3, t4, b2, t2, b1, r1];
const nodes2 = [t1, t3, b2, b1, r1];
const nodes3 = [t4, b2, b1, r1];
sizesArray = [7, 5, 4];
accountsArray = [account2, account3, account4];
prefixesArray = [t2.prefix.split(''), t3.prefix.split(''), t4.prefix.split('')];
testNodesArray = [nodes1, nodes2, nodes3];
});
it('must have a well defined nodes array', () => {
/* eslint-disable no-unused-vars */
expect(() => {
const tesT3 = new AccountsProof(undefined);
}).toThrowError('Malformed nodes');
expect(() => {
const tesT3 = new AccountsProof(null);
}).toThrowError('Malformed nodes');
expect(() => {
const tesT3 = new AccountsProof(1);
}).toThrowError('Malformed nodes');
expect(() => {
const tesT3 = new AccountsProof(new Uint8Array(101));
}).toThrowError('Malformed nodes');
/* eslint-enable no-unused-vars */
});
it('is serializable and unserializable', () => {
for (const nodes of testNodesArray) {
const accountsProof1 = new AccountsProof(nodes);
const accountsProof2 = AccountsProof.unserialize(accountsProof1.serialize());
const nodesArray = accountsProof2.nodes;
const length2 = accountsProof2.length;
expect(length2).toBe(sizesArray.shift());
expect(accountsProof1.length === length2).toBe(true);
for (let j = 0; j < length2; j++) {
expect(nodesArray[j].equals(nodes[j])).toBe(true);
}
}
});
it('must not return an account before verify() has been run', (done) => {
(async () => {
for (const nodes of testNodesArray) {
const accountsProof1 = new AccountsProof(nodes);
const address = TestUtils.raw2address(prefixesArray.shift());
expect(function () { accountsProof1.getAccount(address); }).toThrowError(Error, 'AccountsProof must be verified before retrieving accounts. Call verify() first.');
}
})().then(done, done.fail);
});
it('must be able to correctly return an account after verify() has been run', () => {
for (const nodes of testNodesArray) {
const accountsProof1 = new AccountsProof(nodes);
const address = TestUtils.raw2address(prefixesArray.shift());
const verified = accountsProof1.verify();
expect(verified).toBe(true);
const account = accountsProof1.getAccount(address);
expect(account.equals(accountsArray.shift())).toBe(true);
}
});
it('must not verify successfully neither return an account if it contains a tainted AccountTreeNode', () => {
for (const nodes of testNodesArray) {
const node = nodes[0]; // get the first node
node._account = accountsArray[0]; // change its account to a different one
nodes[0] = node; // set it back
const accountsProof1 = new AccountsProof(nodes);
const address = TestUtils.raw2address(prefixesArray.shift());
// Since hashes of AccountTreeNodes are cached locally, we need to simulate
// sending the node through the network by serializing and unserializing it
// for this to work
const accountsProof2 = AccountsProof.unserialize(accountsProof1.serialize());
const verified = accountsProof2.verify();
expect(verified).toBe(false);
expect(function () { accountsProof2.getAccount(address); }).toThrowError(Error, 'Requested address not part of AccountsProof');
}
});
it('must not verify successfully if it contains any node before the Root Node', (done) => {
(async () => {
for (const nodes of testNodesArray) {
const fakeAccount = new BasicAccount(42);
const fakeTreeNode = AccountsTreeNode.terminalNode('0020000000000000345000000000000000000000', fakeAccount);
nodes.push(fakeTreeNode);
const accountsProof1 = new AccountsProof(nodes);
// Since hashes of AccountTreeNodes are cached locally, we need to simulate
// sending the node through the network by serializing and unserializing it
// for this to work
const accountsProof2 = AccountsProof.unserialize(accountsProof1.serialize());
const verified = await accountsProof2.verify();
expect(verified).toBe(false);
}
})().then(done, done.fail);
});
it('must not verify successfully if it contains a node that is not part of the Tree at the end', () => {
for (const nodes of testNodesArray) {
const fakeAccount = new BasicAccount(42);
const fakeTreeNode = AccountsTreeNode.terminalNode('0020000000000000345000000000000000000000', fakeAccount);
nodes.unshift(fakeTreeNode);
const accountsProof1 = new AccountsProof(nodes);
// Since hashes of AccountTreeNodes are cached locally, we need to simulate
// sending the node through the network by serializing and unserializing it
// for this to work
const accountsProof2 = AccountsProof.unserialize(accountsProof1.serialize());
const verified = accountsProof2.verify();
expect(verified).toBe(false);
}
});
it('must not verify successfully neither return the account if it contains a node that is not part of the Tree', () => {
for (const nodes of testNodesArray) {
const fakeAccount = new BasicAccount(42);
const fakeTreeNode = AccountsTreeNode.terminalNode('0020000000000000345000000000000000000000', fakeAccount);
nodes.splice(2, 0, fakeTreeNode);
const accountsProof1 = new AccountsProof(nodes);
const address = TestUtils.raw2address('0020000000000000345000000000000000000000'.split(''));
// Since hashes of AccountTreeNodes are cached locally, we need to simulate
// sending the node through the network by serializing and unserializing it
// for this to work
const accountsProof2 = AccountsProof.unserialize(accountsProof1.serialize());
const verified = accountsProof2.verify();
expect(verified).toBe(false);
expect(function () { accountsProof2.getAccount(address); }).toThrowError(Error, 'Requested address not part of AccountsProof');
}
});
it('must return the correct root hash', () => {
for (const nodes of testNodesArray) {
const accountsProof1 = new AccountsProof(nodes);
const rootHash = new Hash(BufferUtils.fromBase64('2YcmtlVU+aO61ZGIh/tJ8WOR04f29e3B9bfFVwH8c4M='));
const hash = accountsProof1.root();
expect(hash.equals(rootHash)).toBe(true);
}
});
it('must return the correct length', () => {
for (const nodes of testNodesArray) {
const accountsProof1 = new AccountsProof(nodes);
expect(accountsProof1.length).toBe(sizesArray.shift());
}
});
it('must return the correct nodes array', () => {
for (const nodes of testNodesArray) {
const accountsProof1 = new AccountsProof(nodes);
const hashesArray = accountsProof1.nodes;
for (let i = 0; i < nodes.length; i++) {
expect(hashesArray[i].equals(nodes[i])).toBe(true);
}
}
});
});