import '../setup' ; import { BigNumber , ONE } from '@pigi/core-utils' ; import * as assert from 'assert' ; import { DefaultStateDB , DefaultStateManager , PluginManager , PredicatePlugin , StateDB , StateManager , StateObject , StateUpdate , Transaction , TransactionResult , VerifiedStateUpdate , } from '../../src/' ; import { stateUpdatesEqual } from '../../src/app/utils' ; /*******************
 * Mocks & Helpers *
 *******************/ class DummyPluginManager implements PluginManager { public getPlugin ( address ) { return undefined ; } public loadPlugin ( address , path ) { return undefined ; } } class DummyPredicatePlugin implements PredicatePlugin { public executeStateTransition ( previousStateUpdate , transaction , witness = 'none' ) { return undefined ; } } function getPluginThatReturns ( stateObjects ) { const predicatePlugin = new DummyPredicatePlugin ( ) ; predicatePlugin . executeStateTransition = async ( previousStateUpdate , transaction , witness = 'none' ) : Promise < StateObject > => { if ( stateObjects . length > 1 ) { return stateObjects . shift ( ) ; } return stateObjects [ 0 ] ; } ; return predicatePlugin ; } function getPluginManagerThatReturns ( pluginMap ) { const pluginManager = new DummyPluginManager ( ) ; pluginManager . getPlugin = async ( address ) : Promise < PredicatePlugin | undefined > => { return pluginMap . get ( address ) ; } ; return pluginManager ; } function getStateDBThatReturns ( verifiedStateUpdates ) { const stateDB = new DefaultStateDB ( ) ; stateDB . getVerifiedStateUpdates = async ( start , end ) : Promise < VerifiedStateUpdate [ ] > => { return verifiedStateUpdates ; } ; return stateDB ; } function getStateUpdate ( start , end , plasmaBlockNumber , stateObject , depositAddress = '0x1234' ) { return { range : { start , end , } , stateObject , depositAddress , plasmaBlockNumber : new BigNumber ( plasmaBlockNumber ) , } ; } function getVerifiedStateUpdate ( start , end , block , depositAddress , predicateAddress , data = { dummyData : false } ) { return { range : { start , end , } , verifiedBlockNumber : block , stateUpdate : getStateUpdate ( start , end , block , { predicateAddress , data } , depositAddress ) , } ; } function getTransaction ( depositAddress , start , end , body = { dummyData : false } ) { return { depositAddress , body , range : { start , end , } , } ; } /*********
 * TESTS *
 *********/ describe ( 'DefaultStateManager' , ( ) => { describe ( 'Construction' , ( ) => { it ( 'should initialize' , async ( ) => { new DefaultStateManager ( new DefaultStateDB ( ) , new DummyPluginManager ( ) ) ; } ) ; } ) ; describe ( 'executeTransaction' , ( ) => { const start = new BigNumber ( 10 ) ; const end = new BigNumber ( 20 ) ; const defaultWitness = 'none' ; const previousBlockNumber = new BigNumber ( 10 ) ; const nextBlockNumber = new BigNumber ( 11 ) ; const depositAddress = '0x1234' ; const predicateAddress = '0x12345678' ; const defaultEndData = { testResult : 'test' } ; const transaction = getTransaction ( depositAddress , start , end ) ; const endStateObject = { predicateAddress , data : defaultEndData , } ; const endStateUpdate = getStateUpdate ( start , end , nextBlockNumber , endStateObject , depositAddress ) ; it ( 'should process simple transaction for contiguous range' , async ( ) => { const verifiedStateUpdates = [ getVerifiedStateUpdate ( start , end , previousBlockNumber , depositAddress , predicateAddress ) , ] ; const plugin = getPluginThatReturns ( [ endStateObject ] ) ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; const result = await stateManager . executeTransaction ( transaction , nextBlockNumber , defaultWitness ) ; assert ( stateUpdatesEqual ( result . stateUpdate , endStateUpdate ) ) ; result . validRanges . length . should . equal ( 1 ) ; assert ( result . validRanges [ 0 ] . start . eq ( start ) , `Valid Range start is [${ result . validRanges [ 0 ] . start . toString ( ) }], when [${ start . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 0 ] . end . eq ( end ) , `Valid Range end is [${ result . validRanges [ 0 ] . end . toString ( ) }], when [${ end . toString ( ) }] was expected.` ) ; } ) ; it ( 'should process complex transaction for contiguous range' , async ( ) => { const midPoint = end . sub ( start ) . divRound ( new BigNumber ( 2 ) ) . add ( start ) ; const verifiedStateUpdates = [ getVerifiedStateUpdate ( start , midPoint , previousBlockNumber , depositAddress , predicateAddress ) , getVerifiedStateUpdate ( midPoint , end , previousBlockNumber , depositAddress , predicateAddress ) , ] ; const plugin = getPluginThatReturns ( [ endStateObject ] ) ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; const result = await stateManager . executeTransaction ( transaction , nextBlockNumber , defaultWitness ) ; assert ( stateUpdatesEqual ( result . stateUpdate , endStateUpdate ) ) ; result . validRanges . length . should . equal ( 2 ) ; assert ( result . validRanges [ 0 ] . start . eq ( start ) , `Valid Range [0] start is [${ result . validRanges [ 0 ] . start . toString ( ) }], when [${ start . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 0 ] . end . eq ( midPoint ) , `Valid Range [0] end is [${ result . validRanges [ 0 ] . end . toString ( ) }], when [${ midPoint . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 1 ] . start . eq ( midPoint ) , `Valid Range [1] start is [${ result . validRanges [ 1 ] . start . toString ( ) }], when [${ midPoint . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 1 ] . end . eq ( end ) , `Valid Range [1] end is [${ result . validRanges [ 1 ] . end . toString ( ) }], when [${ end . toString ( ) }] was expected.` ) ; } ) ; it ( 'should process complex transaction for non-subset range' , async ( ) => { const midPoint = end . sub ( start ) . divRound ( new BigNumber ( 2 ) ) . add ( start ) ; const verifiedStateUpdates = [ getVerifiedStateUpdate ( start . sub ( new BigNumber ( 5 ) ) , midPoint , previousBlockNumber , depositAddress , predicateAddress ) , getVerifiedStateUpdate ( midPoint , end . add ( new BigNumber ( 4 ) ) , previousBlockNumber , depositAddress , predicateAddress ) , ] ; const plugin = getPluginThatReturns ( [ endStateObject ] ) ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; const result = await stateManager . executeTransaction ( transaction , nextBlockNumber , defaultWitness ) ; assert ( stateUpdatesEqual ( result . stateUpdate , endStateUpdate ) ) ; result . validRanges . length . should . equal ( 2 ) ; assert ( result . validRanges [ 0 ] . start . eq ( start ) , `Valid Range [0] start is [${ result . validRanges [ 0 ] . start . toString ( ) }], when [${ start . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 0 ] . end . eq ( midPoint ) , `Valid Range [0] end is [${ result . validRanges [ 0 ] . end . toString ( ) }], when [${ midPoint . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 1 ] . start . eq ( midPoint ) , `Valid Range [1] start is [${ result . validRanges [ 1 ] . start . toString ( ) }], when [${ midPoint . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 1 ] . end . eq ( end ) , `Valid Range [1] end is [${ result . validRanges [ 1 ] . end . toString ( ) }], when [${ end . toString ( ) }] was expected.` ) ; } ) ; it ( 'should process complex transaction for non-contiguous range' , async ( ) => { const endRange1 = start . add ( ONE ) ; const startRange2 = end . sub ( ONE ) ; const verifiedStateUpdates = [ getVerifiedStateUpdate ( start , endRange1 , previousBlockNumber , depositAddress , predicateAddress ) , getVerifiedStateUpdate ( startRange2 , end , previousBlockNumber , depositAddress , predicateAddress ) , ] ; const plugin = getPluginThatReturns ( [ endStateObject ] ) ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; const result = await stateManager . executeTransaction ( transaction , nextBlockNumber , defaultWitness ) ; assert ( stateUpdatesEqual ( result . stateUpdate , endStateUpdate ) ) ; result . validRanges . length . should . equal ( 2 ) ; assert ( result . validRanges [ 0 ] . start . eq ( start ) , `Valid Range [0] start is [${ result . validRanges [ 0 ] . start . toString ( ) }], when [${ start . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 0 ] . end . eq ( endRange1 ) , `Valid Range [0] end is [${ result . validRanges [ 0 ] . end . toString ( ) }], when [${ endRange1 . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 1 ] . start . eq ( startRange2 ) , `Valid Range [1] start is [${ result . validRanges [ 1 ] . start . toString ( ) }], when [${ startRange2 . toString ( ) }] was expected.` ) ; assert ( result . validRanges [ 1 ] . end . eq ( end ) , `Valid Range [1] end is [${ result . validRanges [ 1 ] . end . toString ( ) }], when [${ end . toString ( ) }] was expected.` ) ; } ) ; it ( 'should return empty range if no VerifiedStateUpdates' , async ( ) => { const verifiedStateUpdates = [ ] ; const plugin = undefined ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; const result = await stateManager . executeTransaction ( transaction , nextBlockNumber , defaultWitness ) ; assert ( result . stateUpdate === undefined ) ; result . validRanges . length . should . equal ( 0 ) ; } ) ; it ( 'should return empty range if VerifiedStateUpdates do not overlap' , async ( ) => { const verifiedStateUpdates = [ getVerifiedStateUpdate ( end , end . add ( ONE ) , previousBlockNumber , depositAddress , predicateAddress ) , getVerifiedStateUpdate ( start . sub ( ONE ) , start , previousBlockNumber , depositAddress , predicateAddress ) , ] ; const plugin = getPluginThatReturns ( [ endStateObject ] ) ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; try { await stateManager . executeTransaction ( transaction , nextBlockNumber , defaultWitness ) ; assert ( false , 'this call should have thrown an error.' ) ; } catch ( e ) { assert ( true , 'this call threw an error as expected.' ) ; } } ) ; it ( 'should throw if VerifiedStateUpdates have different predicates' , async ( ) => { const secondPredicateAddress = '0x87654321' ; const midPoint = end . sub ( start ) . divRound ( new BigNumber ( 2 ) ) . add ( start ) ; const verifiedStateUpdates = [ getVerifiedStateUpdate ( start , midPoint , previousBlockNumber , depositAddress , predicateAddress ) , getVerifiedStateUpdate ( midPoint , end , previousBlockNumber , depositAddress , predicateAddress ) , ] ; const firstStateObject = { predicateAddress , data : { testResult : 'test' } , } ; const plugin = getPluginThatReturns ( [ firstStateObject ] ) ; const secondStateObject = { predicateAddress : secondPredicateAddress , data : { testResult : 'test' } , } ; const secondPlugin = getPluginThatReturns ( [ secondStateObject , ] ) ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] , [ secondPredicateAddress , secondPlugin ] , ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; try { await stateManager . executeTransaction ( transaction , nextBlockNumber , defaultWitness ) ; assert ( false , 'this call should have thrown an error.' ) ; } catch ( e ) { assert ( true , 'this call threw an error as expected.' ) ; } } ) ; it ( 'should fail if same predicate but StateObjects do not match' , async ( ) => { const midPoint = end . sub ( start ) . divRound ( new BigNumber ( 2 ) ) . add ( start ) ; const verifiedStateUpdates = [ getVerifiedStateUpdate ( start , midPoint , previousBlockNumber , depositAddress , predicateAddress ) , getVerifiedStateUpdate ( midPoint , end , previousBlockNumber , depositAddress , predicateAddress ) , ] ; const firstStateObject = { predicateAddress , data : { testResult : 'test' } , } ; const secondStateObject = { predicateAddress , data : { testResult : 'test 2' } , } ; const plugin = getPluginThatReturns ( [ firstStateObject , secondStateObject , ] ) ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; try { await stateManager . executeTransaction ( transaction , nextBlockNumber , defaultWitness ) ; assert ( false , 'this call should have thrown an error.' ) ; } catch ( e ) { assert ( true , 'this call threw an error as expected.' ) ; } } ) ; it ( 'should throw if block number is incorrect' , async ( ) => { const verifiedStateUpdates = [ getVerifiedStateUpdate ( start , end , previousBlockNumber , depositAddress , predicateAddress ) , ] ; const plugin = getPluginThatReturns ( [ endStateObject ] ) ; const stateDB = getStateDBThatReturns ( verifiedStateUpdates ) ; const pluginManager = getPluginManagerThatReturns ( new Map < string , PredicatePlugin > ( [ [ predicateAddress , plugin ] ] ) ) ; const stateManager = new DefaultStateManager ( stateDB , pluginManager ) ; try { await stateManager . executeTransaction ( transaction , nextBlockNumber . add ( ONE ) , defaultWitness ) ; assert ( false , 'this call should have thrown an error.' ) ; } catch ( e ) { assert ( true , 'this call threw an error as expected.' ) ; } } ) ; } ) ; } ) ;