Skip to content

Commit

Permalink
chore: rework Period object constructor (#167)
Browse files Browse the repository at this point in the history
* chore: rework Period object constructor

1. Set `usePrev` by default. BREAKING change
2. Accept optional third arguments of `PeriodOptions` type:
allows to set validator data and set `usePrev` to false for legacy proofs.

* docs: add period hashing schema
  • Loading branch information
troggy authored Dec 23, 2019
1 parent dcde9aa commit ded6ec8
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 53 deletions.
19 changes: 14 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,15 +254,24 @@ declare module "leap-core" {
public static from(height: number, timestamp: number, txList: LeapTransaction[]): Block;
}

type PeriodOptions = {
validatorData?: {
slotId: number;
ownerAddr: string | Buffer | number;
casBitmap?: string | Buffer | number;
};
excludePrevHashFromProof?: Boolean;
};

class Period {
constructor(prevHash: string, blocks: Array<Block>);
constructor(prevHash: string, blocks: Array<Block>, opts?: PeriodOptions);
addBlock(block: Block): Period;
getMerkleTree(): MerkleTree;
merkleRoot(): string;
proof(tx: Tx<any>): Proof;
static periodBlockRange(blockNumber: number): [number, number];
static periodForBlockRange(plasma: ExtendedWeb3, startBlock: number, endBlock: number): Promise<Period>;
static periodForTx(plasma: ExtendedWeb3, tx: LeapTransaction): Promise<Period>;
static periodForBlockRange(plasma: ExtendedWeb3, startBlock: number, endBlock: number, periodOpts?: PeriodOptions): Promise<Period>;
static periodForTx(plasma: ExtendedWeb3, tx: LeapTransaction, periodOpts?: PeriodOptions): Promise<Period>;
}

export type Proof = string[];
Expand Down Expand Up @@ -331,7 +340,7 @@ declare module "leap-core" {
};

type PeriodData = {
validatorAddress: string;
ownerAddr: string;
slotId: number;
casBitmap?: string;
periodStart?: number;
Expand Down Expand Up @@ -360,7 +369,7 @@ declare module "leap-core" {
export function periodBlockRange(blockNumber: number): [number, number];
export function getTxWithYoungestBlock(txs: LeapTransaction[]): InputTx;
export function getYoungestInputTx(plasma: ExtendedWeb3, tx: Tx<any>): Promise<InputTx>;
export function getProof(plasma: ExtendedWeb3, tx: LeapTransaction, fallbackPeriodData?: PeriodData): Promise<Proof>;
export function getProof(plasma: ExtendedWeb3, tx: LeapTransaction, periodOpts?: PeriodOptions): Promise<Proof>;
// Depending on plasma instance, resolves to either Web3's Transaction or Ethers' TransactionReceipt
export function sendSignedTransaction(plasma: ExtendedWeb3, tx: string): Promise<any>;
export function simulateSpendCond(plasma: ExtendedWeb3, tx: Tx<Type.SPEND_COND>): Promise<SpendCondSimResult>;
Expand Down
23 changes: 12 additions & 11 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,25 +213,26 @@ export function getYoungestInputTx(plasma, tx) {
*
* @param {ExtendedWeb3|LeapProvider} plasma instance of Leap Web3
* @param {LeapTransaction} tx
* @param {PeriodOpts} periodOpts — options for cosntructed Period as defined in Period constructor
* @returns {Promise<Proof>} promise that resolves to period inclusion proof
*/
export function getProof(plasma, tx, fallbackPeriodData) {
return Promise.all([
Period.periodForTx(plasma, tx),
plasma.getPeriodByBlockHeight(tx.blockNumber)
]).then(([period, periodData]) => {
const [periodDataObj] = periodData || [fallbackPeriodData];
if (!periodData || !periodData.length) {
export function getProof(plasma, tx, periodOpts = {}) {
return plasma.getPeriodByBlockHeight(tx.blockNumber)
.then(periodData => {
if (periodData && periodData.length) {
Object.assign(periodOpts, {
validatorData: periodData[0],
});
} else {
const msg = `No period data for the given tx. Height: ${tx.blockNumber}`;
if (!fallbackPeriodData) {
if (!periodOpts.validatorData) {
throw new Error(msg);
} else {
console.warn(msg, 'Using fallback values'); // eslint-disable-line no-console
}
}
const { slotId, validatorAddress, casBitmap } = periodDataObj;

period.setValidatorData(slotId, validatorAddress, casBitmap);
return Period.periodForTx(plasma, tx, periodOpts);
}).then((period) => {
return period.proof(Tx.fromRaw(tx.raw));
});
}
Expand Down
19 changes: 13 additions & 6 deletions lib/helpers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ describe('helpers', () => {
const transactions = n === 4 ? [{ raw: deposit1.hex() }] : [];
return { number: n, timestamp: 123, transactions };
},
getPeriodByBlockHeight: () => null,
getPeriodByBlockHeight: () => Promise.resolve(null),
};

expect(
Expand All @@ -199,15 +199,18 @@ describe('helpers', () => {
const transactions = n === 4 ? [{ raw: deposit1.hex() }] : [];
return { number: n, timestamp: 123, transactions };
},
getPeriodByBlockHeight: () => null,
getPeriodByBlockHeight: () => Promise.resolve(null),
};

const fallbackData = { slotId: 0, validatorAddress: ADDR_1 };
const periodOpts = {
validatorData: { slotId: 0, ownerAddr: ADDR_1 },
excludePrevHashFromProof: true,
};

const proof = getProof(
plasma,
{ blockNumber: 4, raw: deposit1.hex() },
fallbackData
periodOpts
);
return expect(proof).to.eventually.eql([
'0x29aa1b0213471dbf84175e8f688e5a63c2e5724ad6bc581a10b9521f4b8a6083',
Expand Down Expand Up @@ -240,11 +243,15 @@ describe('helpers', () => {
},
getPeriodByBlockHeight: n => {
expect(n).to.be.equal(4);
return [{ slotId: 0, validatorAddress: ADDR_1, casBitmap }];
return Promise.resolve([{ slotId: 0, ownerAddr: ADDR_1, casBitmap }]);
},
};

const proof = getProof(plasma, { blockNumber: 4, raw: deposit1.hex() });
const proof = getProof(
plasma,
{ blockNumber: 4, raw: deposit1.hex() },
{ excludePrevHashFromProof: true }
);
return expect(proof).to.eventually.eql([
'0x6eefe22ae29bc837d66e743334a70ecc19635c3c9ef31d4c2987b337b9d015c6',
'0x4404003c00000000000000080000000000000000000000000000000000000000',
Expand Down
61 changes: 49 additions & 12 deletions lib/period.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,40 @@ import Util from './util';
import { BLOCKS_PER_PERIOD } from './constants';

export default class Period {
constructor(prevHash, blocks) {

/**
*
* Create a Period object linked to the previous Period via `prevHash` and
* containing given list of `blocks`.
*
* Optional parameters can be given via `opts` argument. Format as follows:
*
* type PeriodOptions = {
* validatorData?: {
* slotId: number;
* ownerAddr: string | Buffer | number;
* casBitmap?: string | Buffer | number;
* };
* excludePrevHashFromProof?: Boolean;
* };
*
* @param {string} prevHash - previous period root hash
* @param {Array<Block>} blocks - array of blocks to include in the period
* @param {PeriodOptions} opts - options defined as above. Default {}
*/
constructor(prevHash, blocks, opts = {}) {
this.prevHash = prevHash;
this.blockList = [];
this.blockHashList = [];
this.usePrev = !opts.excludePrevHashFromProof;
if (blocks) {
blocks.forEach(block => this.addBlock(block));
}

const { slotId, ownerAddr, casBitmap } = opts.validatorData || {};
if ((slotId || slotId === 0) && ownerAddr) {
this.setValidatorData(slotId, ownerAddr, casBitmap);
}
}

addBlock(block) {
Expand Down Expand Up @@ -64,11 +91,19 @@ export default class Period {
}
}

usePrevPeriod() {
this.usePrev = true;
}

// helpful: https://docs.google.com/drawings/d/13oFjua-v_E_yaFYUbralluI-EysgtTaZb_sSgGbxG_A
//
/**
* helpful: https://docs.google.com/drawings/d/13oFjua-v_E_yaFYUbralluI-EysgtTaZb_sSgGbxG_A
*
* period root
* / \
* consensus root CAS root
* / \ / \
* blocks root meta root CAS bitmap validator root
* / \ / \
* fees hash prevPeriodHash * slotId 0x0
* * ownerAddr
*/
periodData() {
if (typeof this.slotId === 'undefined' || !this.ownerAddr) {
throw Error('period is missing validator data to create period root.');
Expand Down Expand Up @@ -124,7 +159,7 @@ export default class Period {
throw Error('not set to use prev period in proofs');
}

const { casRoot, metaRoot } = this.periodData();
const { casRoot } = this.periodData();

const proof = [];

Expand All @@ -147,7 +182,7 @@ export default class Period {
toBuffer(proof[1]).copy(result, 32);
const metaRoot = keccak256(result);
result = Buffer.alloc(64, 0);
//blocks root
// blocks root
toBuffer(proof[2]).copy(result);
metaRoot.copy(result, 32);
const consensusRoot = keccak256(result);
Expand Down Expand Up @@ -212,9 +247,10 @@ export default class Period {
* @param {ExtendedWeb3} plasma instance of Leap Web3 or Leap Ethers
* @param {Number} startBlock first block to include in the period
* @param {Number} endBlock last block to include in the period
* @param {PeriodOpts} periodOpts options for Period object as defined in Period constructor
* @returns {Period} period
*/
static periodForBlockRange(plasma, startBlock, endBlock) {
static periodForBlockRange(plasma, startBlock, endBlock, periodOpts = {}) {
return Promise.all(
Util.range(startBlock, endBlock).map(n => (plasma.eth || plasma).getBlock(n, true)),
).then((blocks) => {
Expand All @@ -223,7 +259,7 @@ export default class Period {
.map(({ number, timestamp, transactions }) =>
Block.from(number, timestamp, transactions),
);
return new Period(null, blockList);
return new Period(null, blockList, periodOpts);
});
}

Expand All @@ -232,12 +268,13 @@ export default class Period {
*
* @param {ExtendedWeb3} plasma instance of Leap Web3
* @param {Transaction} tx transaction to create {Period} for
* @param {PeriodOpts} periodOpts options for Period object as defined in Period constructor
* @returns {Period} period
*/
static periodForTx(plasma, tx) {
static periodForTx(plasma, tx, periodOpts = {}) {
const { blockNumber } = tx;
const [startBlock, endBlock] = Period.periodBlockRange(blockNumber);
return Period.periodForBlockRange(plasma, startBlock, endBlock);
return Period.periodForBlockRange(plasma, startBlock, endBlock, periodOpts);
}

}
54 changes: 35 additions & 19 deletions lib/period.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ const PRIV = '0x94890218f2b0d04296f30aeafd13655eba4c5bbf1770273276fee52cbe3f2cb4
const ADDR = '0x82e8c6cf42c8d1ff9594b17a3f50e94a12cc860f';
const slotId = 4;

const validatorData = {
slotId,
ownerAddr: ADDR
};

/**
* Excluding prevHash from period proof as it was in the original version,
* so that we can verify proofs for older periods
*/
const legacyOpts = {
validatorData,
excludePrevHashFromProof: true
}

describe('periods', () => {
it('should allow to get proof from period.', (done) => {
const height = 123;
Expand All @@ -24,8 +38,7 @@ describe('periods', () => {
const block2 = new Block(height + 1);
block2.addTx(Tx.deposit(2, value * 2, ADDR, color));

const period = new Period(null, [block1, block2]);
period.setValidatorData(slotId, ADDR);
const period = new Period(null, [block1, block2], legacyOpts);
const proof = period.proof(deposit1);
expect(proof).to.eql([
period.periodRoot(),
Expand All @@ -52,8 +65,7 @@ describe('periods', () => {
const block2 = new Block(2);
block2.addTx(deposit2);

const period = new Period(null, [block1]);
period.setValidatorData(slotId, ADDR);
const period = new Period(null, [block1], legacyOpts);
expect(
() => period.proof(deposit2)
).to.throw('tx not in this period');
Expand All @@ -73,8 +85,14 @@ describe('periods', () => {
const block2 = new Block(height + 1);
block2.addTx(Tx.deposit(2, value * 2, ADDR, color));

const period = new Period(null, [block1, block2]);
period.setValidatorData(slotId, ADDR, PRIV);
const period = new Period(null, [block1, block2], {
validatorData: {
slotId,
ownerAddr: ADDR,
casBitmap: '0x4000000000000000000000000000000000000000000000000000000000000000'
},
excludePrevHashFromProof: true
});
const proof = period.proof(deposit1);
expect(proof).to.eql([
period.periodRoot(),
Expand All @@ -84,7 +102,7 @@ describe('periods', () => {
'0x430ce01c495ecaa94a3b4b3154906343e755b7f9e51bf3403b09dd932a0b18ee',
'0x77bc0389ba07196637b929d5347b1453f3294175e9015e13b5e3c5fb19f3c0f4',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x492a381524e41b7bf6f1eef65ff5057dbfacea9e8bfea2c2c895c59b9369e245',
'0xde2318630368656c2cce43bb993c6a3c077bc0b1cc75341375439f5a1206d347',
]);
done();
});
Expand All @@ -111,8 +129,7 @@ describe('periods', () => {
block3.addTx(Tx.deposit(7, value, ADDR, color));
block3.addTx(Tx.deposit(8, value, ADDR, color));

const period = new Period(null, [block1, block2, block3]);
period.setValidatorData(slotId, ADDR);
const period = new Period(null, [block1, block2, block3], legacyOpts);
const proof = period.proof(deposit2);
expect(proof).to.eql([
'0x8e08277cfeb7f80da02df9e165d59bd7fccc10221ee24c3d660d7a4739524fc7',
Expand Down Expand Up @@ -140,8 +157,7 @@ describe('periods', () => {
blocks.push(block);
}

const period = new Period(null, blocks);
period.setValidatorData(slotId, ADDR);
const period = new Period(null, blocks, legacyOpts);
const proof = period.proof(Tx.deposit(12, value, ADDR, color));
expect(proof).to.eql([
'0x275e30e070a5637312ec95d135e8b393824e38c420ab142e8b72bb1528a26088',
Expand Down Expand Up @@ -175,8 +191,7 @@ describe('periods', () => {
}
}

const period = new Period(null, blocks);
period.setValidatorData(slotId, ADDR);
const period = new Period(null, blocks, legacyOpts);
const proof = period.proof(Tx.deposit(13, value, ADDR, color));
expect(proof).to.eql([
'0x2f66d9caf3a49598e7259c850c5bf3bbf7cdc61be65846f03296b1c0f54386a2',
Expand Down Expand Up @@ -218,8 +233,7 @@ describe('periods', () => {
block = new Block(31).addTx(transfer);
blocks.push(block);

const period = new Period(null, blocks);
period.setValidatorData(slotId, ADDR);
const period = new Period(null, blocks, legacyOpts);
const proof = period.proof(transfer);

expect(proof).to.eql([
Expand Down Expand Up @@ -262,11 +276,13 @@ describe('periods', () => {
block = new Block(31).addTx(transfer);
blocks.push(block);

const period = new Period("0x8b04de057fe524a3118eb7c8e14a2e55323c67fd7b6080583d1047b700b2d674", blocks);
period.setValidatorData(slotId, ADDR);
const periodAfter = new Period(period.periodRoot(), blocks);
const period = new Period(
"0x8b04de057fe524a3118eb7c8e14a2e55323c67fd7b6080583d1047b700b2d674",
blocks,
{ validatorData }
);
const periodAfter = new Period(period.periodRoot(), blocks, { validatorData });
periodAfter.setValidatorData(slotId, ADDR);
periodAfter.usePrevPeriod();

const proof = periodAfter.prevPeriodProof();
expect(Period.verifyPrevPeriodProof(proof)).to.eql(periodAfter.periodRoot());
Expand Down

0 comments on commit ded6ec8

Please sign in to comment.