Skip to content

Commit

Permalink
Rollup state machine failure recovery (#481)
Browse files Browse the repository at this point in the history
* Adding recoverability to RollupStateMachine

Other:
* updating SparseMerkleTreeImpl to the async static factory approach and updating the many many references
* Making Aggregator constructor private in favor of async static factory method
* Adding some logging in merkle tree and state machine init functions
  • Loading branch information
willmeister committed Oct 2, 2019
1 parent dd05372 commit e8e77cb
Show file tree
Hide file tree
Showing 11 changed files with 497 additions and 117 deletions.
2 changes: 1 addition & 1 deletion packages/contracts/test/helpers/RollupBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class RollupBlock {
public async generateTree(): Promise<void> {
// Create a tree!
const treeHeight = Math.ceil(Math.log2(this.transitions.length)) + 1 // The height should actually not be plus 1
this.tree = new SparseMerkleTreeImpl(
this.tree = await SparseMerkleTreeImpl.create(
new BaseDB(new MemDown('') as any, 256),
undefined,
treeHeight
Expand Down
11 changes: 4 additions & 7 deletions packages/contracts/test/merklization/RollupMerkleUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
BigNumber,
BaseDB,
SparseMerkleTreeImpl,
newInMemoryDB,
} from '@pigi/core'
import MemDown from 'memdown'

Expand All @@ -32,19 +33,15 @@ async function createSMTfromDataBlocks(
): Promise<SparseMerkleTreeImpl> {
const treeHeight = Math.ceil(Math.log2(dataBlocks.length)) + 1 // The height should actually not be plus 1
log('Creating tree of height:', treeHeight - 1)
const tree = getNewSMT(treeHeight)
const tree = await getNewSMT(treeHeight)
for (let i = 0; i < dataBlocks.length; i++) {
await tree.update(new BigNumber(i, 10), dataBlocks[i])
}
return tree
}

function getNewSMT(treeHeight: number): SparseMerkleTreeImpl {
return new SparseMerkleTreeImpl(
new BaseDB(new MemDown('') as any, 256),
undefined,
treeHeight
)
async function getNewSMT(treeHeight: number): Promise<SparseMerkleTreeImpl> {
return SparseMerkleTreeImpl.create(newInMemoryDB(), undefined, treeHeight)
}

function makeRandomBlockOfSize(blockSize: number): string[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ describe('RollupChain', () => {
const encodedPreStates = preStateObjects.map((obj) => abiEncodeState(obj))
// Create the state tree
const treeHeight = 32 // Default tree height
const stateTree = new SparseMerkleTreeImpl(
const stateTree = await SparseMerkleTreeImpl.create(
new BaseDB(new MemDown('') as any, 256),
undefined,
treeHeight + 1
Expand Down
55 changes: 48 additions & 7 deletions packages/core/src/app/block-production/merkle-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,34 @@ export class SparseMerkleTreeImpl implements SparseMerkleTree {
private readonly hashFunction: (Buffer) => Buffer
private readonly hashBuffer: Buffer = Buffer.alloc(64)

constructor(
private readonly db: DB,
public static async create(
db: DB,
rootHash?: Buffer,
private readonly height: number = 160,
height: number = 160,
hashFunction = keccak256
): Promise<SparseMerkleTreeImpl> {
assert(!rootHash || rootHash.length === 32, 'Root hash must be 32 bytes')

const tree = new SparseMerkleTreeImpl(db, height, hashFunction)

await tree.init(rootHash)
return tree
}

private constructor(
private db: DB,
private height: number = 160,
hashFunction: HashFunction = keccak256
) {
assert(!rootHash || rootHash.length === 32, 'Root hash must be 32 bytes')
assert(height > 0, 'SMT height needs to be > 0')

// TODO: Hack for now -- change everything to string if/when it makes sense
this.hashFunction = (buff: Buffer) =>
Buffer.from(hashFunction(buff.toString('hex')), 'hex')
}

this.populateZeroHashesAndRoot(rootHash)
private async init(rootHash?: Buffer): Promise<void> {
await this.populateZeroHashesAndRoot(rootHash)
}

public getHeight(): number {
Expand Down Expand Up @@ -533,7 +547,7 @@ export class SparseMerkleTreeImpl implements SparseMerkleTree {
*
* @param rootHash The optional root hash to assign the tree
*/
private populateZeroHashesAndRoot(rootHash?: Buffer): void {
private async populateZeroHashesAndRoot(rootHash?: Buffer): Promise<void> {
const hashes: Buffer[] = [
this.hashFunction(SparseMerkleTreeImpl.emptyBuffer),
]
Expand All @@ -545,7 +559,34 @@ export class SparseMerkleTreeImpl implements SparseMerkleTree {
}

this.zeroHashes = hashes.reverse()
this.root = this.createNode(rootHash || this.zeroHashes[0], undefined, ZERO)

if (!!rootHash) {
log.info(
`Attempting to initialize SMT with root hash ${rootHash.toString(
'hex'
)}`
)
this.root = await this.getNode(rootHash, ZERO)
}

if (!this.root) {
this.root = this.createNode(
rootHash || this.zeroHashes[0],
undefined,
ZERO
)
log.info(
`Initialized Sparse Merkle Tree with root ${(
rootHash || this.zeroHashes[0]
).toString('hex')}`
)
} else {
log.info(
`Initialized Sparse Merkle Tree with root ${this.root.hash.toString(
'hex'
)}`
)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class MerkleInclusionProofDecider implements Decider {
input: MerkleInclusionProofDeciderInput,
_noCache?: boolean
): Promise<Decision> {
const tree = new SparseMerkleTreeImpl(
const tree = await SparseMerkleTreeImpl.create(
newInMemoryDB(256),
input.merkleProof.rootHash,
input.merkleProof.siblings.length + 1
Expand Down
50 changes: 25 additions & 25 deletions packages/core/test/app/block-production/merkle-tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const createAndVerifyEmptyTreeDepthWithDepth = async (
key: BigNumber,
depth: number
): Promise<SparseMerkleTree> => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
depth,
Expand Down Expand Up @@ -117,22 +117,22 @@ describe('SparseMerkleTreeImpl', () => {

describe('Constructor', () => {
it('should construct without error', async () => {
new SparseMerkleTreeImpl(db)
await SparseMerkleTreeImpl.create(db)
})

it('accepts a non-empty root hash', async () => {
new SparseMerkleTreeImpl(db, Buffer.alloc(32).fill('root', 0))
await SparseMerkleTreeImpl.create(db, Buffer.alloc(32).fill('root', 0))
})

it('throws if root is not 32 bytes', async () => {
TestUtils.assertThrows(() => {
new SparseMerkleTreeImpl(db, Buffer.alloc(31).fill('root', 0))
await TestUtils.assertThrowsAsync(async () => {
await SparseMerkleTreeImpl.create(db, Buffer.alloc(31).fill('root', 0))
}, assert.AssertionError)
})

it('throws if height is < 0', async () => {
TestUtils.assertThrows(() => {
new SparseMerkleTreeImpl(db, undefined, -1)
await TestUtils.assertThrowsAsync(async () => {
await SparseMerkleTreeImpl.create(db, undefined, -1)
}, assert.AssertionError)
})
})
Expand Down Expand Up @@ -161,7 +161,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('fails on invalid proof for empty root', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
2,
Expand Down Expand Up @@ -189,7 +189,7 @@ describe('SparseMerkleTreeImpl', () => {
.fill(bufferHashFunction(SparseMerkleTreeImpl.emptyBuffer), 32)
)

const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
root,
2,
Expand Down Expand Up @@ -217,7 +217,7 @@ describe('SparseMerkleTreeImpl', () => {
.fill(bufferHashFunction(value), 32)
)

const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
root,
2,
Expand Down Expand Up @@ -245,7 +245,7 @@ describe('SparseMerkleTreeImpl', () => {
.fill(bufferHashFunction(SparseMerkleTreeImpl.emptyBuffer), 32)
)

const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
root,
2,
Expand All @@ -268,7 +268,7 @@ describe('SparseMerkleTreeImpl', () => {

describe('update', () => {
it('updates empty tree', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand All @@ -289,7 +289,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('updates empty tree at key 1', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand All @@ -311,7 +311,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('updates empty tree at key 2', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand All @@ -333,7 +333,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('updates empty tree at key 3', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -363,7 +363,7 @@ describe('SparseMerkleTreeImpl', () => {
zh zh zh zh A zh zh zh A D zh zh
*/

const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -414,7 +414,7 @@ describe('SparseMerkleTreeImpl', () => {
zh zh zh zh A zh zh zh A zh D zh
*/

const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -460,7 +460,7 @@ describe('SparseMerkleTreeImpl', () => {

describe('batchUpdate', () => {
it('updates 2 values', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -516,7 +516,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('updates 3 values', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -591,7 +591,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('updates 4 values', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -687,7 +687,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('fails if one proof fails', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -802,7 +802,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('gets merkle proof for non-empty tree', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand All @@ -829,7 +829,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('gets merkle proof for non-empty siblings 0 & 1', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -883,7 +883,7 @@ describe('SparseMerkleTreeImpl', () => {
})

it('gets merkle proof for non-empty siblings 0 & 2', async () => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
3,
Expand Down Expand Up @@ -949,7 +949,7 @@ describe('SparseMerkleTreeImpl', () => {
numUpdates: number,
keyRange: number
): Promise<void> => {
const tree: SparseMerkleTree = new SparseMerkleTreeImpl(
const tree: SparseMerkleTree = await SparseMerkleTreeImpl.create(
db,
undefined,
treeHeight,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('MerkleTreeInclusionProofDecider', () => {
const leafValue: Buffer = Buffer.from('Leaf value')

it('should return true when inclusion proof is valid', async () => {
const merkleTree: SparseMerkleTreeImpl = new SparseMerkleTreeImpl(
const merkleTree: SparseMerkleTreeImpl = await SparseMerkleTreeImpl.create(
newInMemoryDB(256)
)
assert(
Expand Down Expand Up @@ -47,7 +47,7 @@ describe('MerkleTreeInclusionProofDecider', () => {
})

it('should return true when inclusion proof is valid for 32-height tree', async () => {
const merkleTree: SparseMerkleTreeImpl = new SparseMerkleTreeImpl(
const merkleTree: SparseMerkleTreeImpl = await SparseMerkleTreeImpl.create(
newInMemoryDB(256),
undefined,
32
Expand Down Expand Up @@ -80,7 +80,7 @@ describe('MerkleTreeInclusionProofDecider', () => {

it('should return true when inclusion proof is valid for different key', async () => {
const key: BigNumber = new BigNumber(10)
const merkleTree: SparseMerkleTreeImpl = new SparseMerkleTreeImpl(
const merkleTree: SparseMerkleTreeImpl = await SparseMerkleTreeImpl.create(
newInMemoryDB(256)
)
assert(
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('MerkleTreeInclusionProofDecider', () => {

it('should return false when inclusion proof is invalid', async () => {
const key: BigNumber = new BigNumber(10)
const merkleTree: SparseMerkleTreeImpl = new SparseMerkleTreeImpl(
const merkleTree: SparseMerkleTreeImpl = await SparseMerkleTreeImpl.create(
newInMemoryDB(256)
)
assert(
Expand Down

0 comments on commit e8e77cb

Please sign in to comment.