def test_raise_key_error_on_missing_root_hash(self): """Tests that we correctly raise key error on a missing hash """ database = DictDatabase() delta_store = StateDeltaStore(database) with self.assertRaises(KeyError): delta_store.get_state_deltas('unknown_state_root_hash')
def test_state_store_get_and_set(self): """Tests that we correctly get and set state changes to a StateDeltaStore. This tests sets a list of state change values and then gets them back, ensuring that the data is the same. """ database = DictDatabase() delta_store = StateDeltaStore(database) changes = [StateChange(address='a100000' + str(i), value=str(i).encode(), type=StateChange.SET) for i in range(0, 10)] delta_store.save_state_deltas('my_state_root_hash', changes) stored_changes = delta_store.get_state_deltas('my_state_root_hash') # This is a list-like repeated field, but to make it comparable we'll # have to generate a new list stored_changes = [c for c in stored_changes] self.assertEqual(changes, stored_changes)
class TestContextManager(unittest.TestCase): def setUp(self): self.database_of_record = dict_database.DictDatabase() self.state_delta_store = StateDeltaStore(dict_database.DictDatabase()) self.context_manager = context_manager.ContextManager( self.database_of_record, self.state_delta_store) self.first_state_hash = self.context_manager.get_first_root() # used for replicating state hash through direct merkle tree updates self.database_results = dict_database.DictDatabase() def tearDown(self): self.context_manager.stop() def _create_address(self, value=None): """ Args: value: (str) Returns: (str) sha512 of value or random """ if value is None: value = time.time().hex() return hashlib.sha512(value.encode()).hexdigest()[:70] def _setup_context(self): # 1) Create transaction data first_transaction = {'inputs': [self._create_address(a) for a in ['aaaa', 'bbbb', 'cccc']], 'outputs': [self._create_address(a) for a in ['llaa', 'aall', 'nnnn']]} second_transaction = { 'inputs': [self._create_address(a) for a in ['aaaa', 'dddd']], 'outputs': [self._create_address(a) for a in ['zzzz', 'yyyy', 'tttt', 'qqqq']] } third_transaction = { 'inputs': [self._create_address(a) for a in ['eeee', 'dddd', 'ffff']], 'outputs': [self._create_address(a) for a in ['oooo', 'oozz', 'zzoo', 'ppoo', 'aeio']] } # 2) Create contexts based on that data context_id_1 = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=first_transaction['inputs'], outputs=first_transaction['outputs']) context_id_2 = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=second_transaction['inputs'], outputs=second_transaction['outputs']) context_id_3 = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=third_transaction['inputs'], outputs=third_transaction['outputs']) # 3) Set addresses with values self.context_manager.set(context_id_1, [{self._create_address(a): v} for a, v in [('llaa', b'1'), ('aall', b'2'), ('nnnn', b'3')]]) self.context_manager.set(context_id_2, [{self._create_address(a): v} for a, v in [('zzzz', b'9'), ('yyyy', b'11'), ('tttt', b'12'), ('qqqq', b'13')]]) self.context_manager.set(context_id_3, [{self._create_address(a): v} for a, v in [('oooo', b'25'), ('oozz', b'26'), ('zzoo', b'27'), ('ppoo', b'28'), ('aeio', b'29')]]) # 4) context_id = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[context_id_1, context_id_2, context_id_3], inputs=[self._create_address(a) for a in ['llaa', 'yyyy', 'tttt', 'zzoo']], outputs=[self._create_address(a) for a in ['llaa', 'yyyy', 'tttt', 'zzoo', 'aeio']]) return context_id def _create_txn_inputs_outputs(self, start=None): """Create unique addresses that make up the inputs, outputs, reads, and writes that are involved in a context. Venn Diagram of relationship of disjoint sets that make up the inputs, outputs, reads, and writes. Knowledge of which disjoint set an address is a part of may give knowledge about a test failure in the context manager. Inputs Outputs +----------+--------------------------+-----------+ | | | | | i___ |Reads io__ Writes _o__ | | | | | | +-----------+-----------+---------------+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |i_r_ | ior_| iorw | io_w | _o_w | | | | | | | | | | | | | | | | | | | | | | | | | | | +-----------+-----------+---------------+ | | | | | | | | | +----------+--------------------------+-----------+ Args: start (int): An integer to start the sequence of integers being hashed to addresses. Returns (namedtuple): An object that holds inputs, outputs, reads, and writes. """ if start is None: start = 0 iorw = [self._create_address(str(i)) for i in range(start, start + 10)] i_r_ = [self._create_address(str(i)) for i in range(start + 10, start + 20)] ior_ = [self._create_address(str(i)) for i in range(start + 20, start + 30)] io__ = [self._create_address(str(i)) for i in range(start + 30, start + 40)] io_w = [self._create_address(str(i)) for i in range(start + 40, start + 50)] _o_w = [self._create_address(str(i)) for i in range(start + 50, start + 60)] _o__ = [self._create_address(str(i)) for i in range(start + 60, start + 70)] i___ = [self._create_address(str(i)) for i in range(start + 70, start + 80)] addresses = TestAddresses( inputs=iorw + ior_ + io__ + io_w + i___, outputs=ior_ + io__ + io_w + _o__ + _o_w, reads=i_r_ + ior_, writes=io_w + _o_w ) return addresses def test_address_enforcement(self): """Tests that the ContextManager enforces address characteristics. Notes: 1. Call get and set on the ContextManager with an address that is under a namespace, but is an invalid address, and test that the methods raise an AuthorizationException. """ # 1) invalid_address1 = 'a' * 69 + 'n' invalid_address2 = 'b' * 69 + 'y' context_id1 = self.context_manager.create_context( state_hash=self.context_manager.get_first_root(), base_contexts=[], inputs=['aaaaaaaa', 'bbbbbbbb'], outputs=['aaaaaaaa', 'bbbbbbbb']) with self.assertRaises(context_manager.AuthorizationException): self.context_manager.get( context_id=context_id1, address_list=[invalid_address1, invalid_address2]) with self.assertRaises(context_manager.AuthorizationException): self.context_manager.set( context_id=context_id1, address_value_list=[{invalid_address1: b'1'}, {invalid_address2: b'2'}]) def test_get_set_wrong_namespace(self): """Tests that getting and setting from outside the namespace will raise a AuthorizationException. Notes: 1. Assert that sets on a context with addresses that aren't under an output namespace raise an AuthorizationException. 2. Assert that gets on a context with addresses that aren't under an input namespace raise an AuthorizationException. """ wrong_namespace1 = self._create_address('a')[-10:] wrong_namespace2 = '00000000' ctx_1 = self.context_manager.create_context( state_hash=self.context_manager.get_first_root(), base_contexts=[], inputs=[wrong_namespace1, wrong_namespace2], outputs=[wrong_namespace1, wrong_namespace2]) # 1 with self.assertRaises(context_manager.AuthorizationException): self.context_manager.set( context_id=ctx_1, address_value_list=[{self._create_address('a'): b'1'}]) with self.assertRaises(context_manager.AuthorizationException): self.context_manager.set( context_id=ctx_1, address_value_list=[{self._create_address('c'): b'5'}]) # 2 with self.assertRaises(context_manager.AuthorizationException): self.context_manager.get( context_id=ctx_1, address_list=[self._create_address('a')]) with self.assertRaises(context_manager.AuthorizationException): self.context_manager.get( context_id=ctx_1, address_list=[self._create_address('c')]) def test_exception_on_invalid_input(self): """Tests that invalid inputs raise an exception. Tested with invalid characters, odd number of characters, and too long namespace; Notes: 1) Assert that inputs with a namespace with an odd number of characters raise a CreateContextException. 2) Assert that inputs with a 71 character namespace raise a CreateContextException. 3) Assert that inputs with a namespace with several invalid characters raise a CreateContextException. """ invalid_input_output1 = '0db7e8zc' # invalid character invalid_input_output2 = '7ef84ed' * 10 + '5' # too long, 71 chars invalid_input_output3 = 'yy76ftoph7465873ddde389f' # invalid chars valid_input_output1 = 'd8f533bbb74443222daad4' valid_input_output2 = '77465847465784757848ddddddf' state_hash = self.context_manager.get_first_root() # 1 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[invalid_input_output1, valid_input_output1], outputs=[valid_input_output2]) # 2 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[valid_input_output1, invalid_input_output2], outputs=[valid_input_output2]) # 3 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[invalid_input_output3, valid_input_output2], outputs=[valid_input_output2, valid_input_output1]) def test_exception_on_invalid_output(self): """Tests that invalid outputs raise an exception. Tested with invalid characters, odd number of characters, and too long namespace; Notes: 1) Assert that outputs with a namespace with an odd number of characters raise a CreateContextException. 2) Assert that outputs with a 71 character namespace raise a CreateContextException. 3) Assert that outputs with a namespace with several invalid characters raise a CreateContextException. """ invalid_input_output1 = '0db7e87' # Odd number of characters invalid_input_output2 = '7ef84ed' * 10 + '5' # too long, 71 chars invalid_input_output3 = 'yy76ftoph7465873ddde389f' # invalid chars valid_input_output1 = 'd8f533bbb74443222daad4' valid_input_output2 = '77465847465784757848ddddddff' state_hash = self.context_manager.get_first_root() # 1 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[valid_input_output2, valid_input_output1], outputs=[invalid_input_output1]) # 2 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[valid_input_output1, valid_input_output2], outputs=[invalid_input_output2]) # 3 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[valid_input_output1, valid_input_output2], outputs=[valid_input_output2, invalid_input_output3]) def test_namespace_gets(self): """Tests that gets for an address under a namespace will return the correct value. Notes: 1) Create ctx_1 and set 'b' to b'8'. 2) squash the previous context creating state_hash_1. 3) Create 2 contexts off of this state hash and assert that gets on these contexts retrieve the correct value for an address that is not fully specified in the inputs. 4) Set values to addresses in these contexts. 5) Create 1 context off of these prior 2 contexts and assert that gets from this context retrieve the correct values for addresses that are not fully specified in the inputs. 2 of the values are found in the chain of contexts, and 1 is not found and so must be retrieved from the merkle tree. """ # 1 ctx_1 = self.context_manager.create_context( state_hash=self.context_manager.get_first_root(), base_contexts=[], inputs=[self._create_address('a')], outputs=[self._create_address('b')]) self.context_manager.set( context_id=ctx_1, address_value_list=[{self._create_address('b'): b'8'}]) # 2 squash = self.context_manager.get_squash_handler() state_hash_1 = squash( state_root=self.context_manager.get_first_root(), context_ids=[ctx_1], persist=True, clean_up=True) # 3 ctx_1a = self.context_manager.create_context( state_hash=state_hash_1, base_contexts=[], inputs=[self._create_address('a')[:10]], outputs=[self._create_address('c')]) self.assertEquals( self.context_manager.get( context_id=ctx_1a, address_list=[self._create_address('a')]), [(self._create_address('a'), None)]) ctx_1b = self.context_manager.create_context( state_hash=state_hash_1, base_contexts=[], inputs=[self._create_address('b')[:6]], outputs=[self._create_address('z')]) self.assertEquals( self.context_manager.get( context_id=ctx_1b, address_list=[self._create_address('b')]), [(self._create_address('b'), b'8')]) # 4 self.context_manager.set( context_id=ctx_1b, address_value_list=[{self._create_address('z'): b'2'}]) self.context_manager.set( context_id=ctx_1a, address_value_list=[{self._create_address('c'): b'1'}] ) ctx_2 = self.context_manager.create_context( state_hash=state_hash_1, base_contexts=[ctx_1a, ctx_1b], inputs=[self._create_address('z')[:10], self._create_address('c')[:10], self._create_address('b')[:10]], outputs=[self._create_address('w')]) self.assertEquals( self.context_manager.get( context_id=ctx_2, address_list=[self._create_address('z'), self._create_address('c'), self._create_address('b')]), [(self._create_address('z'), b'2'), (self._create_address('c'), b'1'), (self._create_address('b'), b'8')]) def test_create_context_with_prior_state(self): """Tests context creation with prior state from base contexts. Notes: Set up the context: Create 3 prior contexts each with 3-5 addresses to set to. Make set calls to those addresses. Create 1 new context based on those three prior contexts. this test method: Test: Make a get call on addresses that are from prior state, making assertions about the correct values. """ context_id = self._setup_context() self.assertEqual(self.context_manager.get( context_id, [self._create_address(a) for a in ['llaa', 'yyyy', 'tttt', 'zzoo']]), [(self._create_address(a), v) for a, v in [('llaa', b'1'), ('yyyy', b'11'), ('tttt', b'12'), ('zzoo', b'27')]]) def test_squash(self): """Tests that squashing a context based on state from other contexts will result in the same merkle hash as updating the merkle tree with the same data. Notes: Set up the context Test: 1) Make set calls on several of the addresses. 2) Squash the context to get a new state hash. 3) Apply all of the aggregate sets from all of the contexts, to another database with a merkle tree. 4) Assert that the state hashes are the same. 5) Assert that the state deltas have been stored """ # 1) context_id = self._setup_context() self.context_manager.set( context_id, [{self._create_address(a): v} for a, v in [('yyyy', b'2'), ('tttt', b'4')]]) # 2) squash = self.context_manager.get_squash_handler() resulting_state_hash = squash(self.first_state_hash, [context_id], persist=True, clean_up=True) # 3) final_state_to_update = {self._create_address(a): v for a, v in [('llaa', b'1'), ('aall', b'2'), ('nnnn', b'3'), ('zzzz', b'9'), ('yyyy', b'2'), ('tttt', b'4'), ('qqqq', b'13'), ('oooo', b'25'), ('oozz', b'26'), ('zzoo', b'27'), ('ppoo', b'28'), ('aeio', b'29')]} test_merkle_tree = MerkleDatabase(self.database_results) test_resulting_state_hash = test_merkle_tree.update( final_state_to_update, virtual=False) # 4) self.assertEqual(resulting_state_hash, test_resulting_state_hash) state_changes = self.state_delta_store.get_state_deltas( resulting_state_hash) # 5) for addr, value in final_state_to_update.items(): expected_state_change = StateChange( address=addr, value=value, type=StateChange.SET) self.assertTrue(expected_state_change in state_changes) def test_squash_no_updates(self): """Tests that squashing a context that has no state updates will return the starting state root hash. Notes: Set up the context Test: 1) Squash the context. 2) Assert that the state hash is the same as the starting hash. 3) Assert that the state deltas have not been overwritten """ self.state_delta_store.save_state_deltas( self.first_state_hash, [StateChange(address='aaa', value=b'xyz', type=StateChange.SET)]) context_id = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=[], outputs=[]) # 1) squash = self.context_manager.get_squash_handler() resulting_state_hash = squash(self.first_state_hash, [context_id], persist=True, clean_up=True) # 2 self.assertIsNotNone(resulting_state_hash) self.assertEquals(resulting_state_hash, self.first_state_hash) # 3 changes = self.state_delta_store.get_state_deltas(resulting_state_hash) self.assertEqual( [StateChange(address='aaa', value=b'xyz', type=StateChange.SET)], [c for c in changes]) def test_reads_from_context_w_several_writes(self): """Tests that those context values that have been written to the Merkle tree, or that have been set to a base_context, will have the correct value at the address for a given context. ->context_id_a1 | | | | | | sh0-->context_id1-->sh1-->context_a----- -->context_id_b | | | | | | | | -->context_id_a2 Notes: Test: 1. From a Merkle Tree with only the root node in it, create a context and set several values, and then squash that context upon the first state hash. 2. Create a context with no base context, based on the merkle root computed from the first squash. Assert that gets from this context will provide values that were set in the first context. 3. Write to all of the available outputs 4. Create a new context based on context_a, from #2, 5. Assert that gets from this context equal the values set to Context A. 6. Create a new context based on context_a and set values to this context. 7. Create a new context based on the 2 contexts made in 4 and 6 8. From this context assert that gets equal the correct values set in the prior contexts. """ squash = self.context_manager.get_squash_handler() test_addresses = self._create_txn_inputs_outputs() # 1) context_id1 = self.context_manager.create_context( state_hash=self.first_state_hash, inputs=test_addresses.inputs, outputs=test_addresses.outputs, base_contexts=[]) values1 = [bytes(i) for i in range(len(test_addresses.writes))] self.context_manager.set( context_id1, [{a: v} for a, v in zip(test_addresses.writes, values1)]) sh1 = squash( state_root=self.first_state_hash, context_ids=[context_id1], persist=True, clean_up=True) # 2) context_a = self.context_manager.create_context( state_hash=sh1, inputs=test_addresses.writes, # read from every address written to outputs=test_addresses.outputs, base_contexts=[] ) address_values = self.context_manager.get( context_a, list(test_addresses.writes) ) self.assertEquals( address_values, [(a, v) for a, v in zip(test_addresses.writes, values1)] ) # 3) values2 = [bytes(v.encode()) for v in test_addresses.outputs] self.context_manager.set( context_id=context_a, address_value_list=[{a: v} for a, v in zip(test_addresses.outputs, values2)]) # 4) context_id_a1 = self.context_manager.create_context( state_hash=sh1, inputs=test_addresses.outputs, outputs=test_addresses.outputs, base_contexts=[context_a] ) # 5) c_ida1_address_values = self.context_manager.get( context_id=context_id_a1, address_list=list(test_addresses.outputs) ) self.assertEquals( c_ida1_address_values, [(a, v) for a, v in zip(test_addresses.outputs, values2)] ) # 6) test_addresses2 = self._create_txn_inputs_outputs(80) context_id_a2 = self.context_manager.create_context( state_hash=sh1, inputs=test_addresses2.inputs, outputs=test_addresses2.outputs, base_contexts=[context_a] ) values3 = [bytes(v.encode()) for v in test_addresses2.writes] self.context_manager.set( context_id=context_id_a2, address_value_list=[{a: v} for a, v in zip(test_addresses2.writes, values3)], ) # 7) context_id_b = self.context_manager.create_context( state_hash=sh1, inputs=test_addresses2.writes + test_addresses.outputs, outputs=[], base_contexts=[context_id_a1, context_id_a2] ) # 8) self.assertEquals( self.context_manager.get( context_id_b, list(test_addresses2.writes + test_addresses.outputs) ), [(a, v) for a, v in zip( test_addresses2.writes + test_addresses.outputs, values3 + values2)] ) def test_state_root_after_parallel_ctx(self): """Tests that the correct state root is calculated after basing one context off of multiple contexts. i=abcd o=aaaa +>context_1+ | aaaa=1 | | | i=llll | i=bacd | i=bbbb,aaaa o=llll | o=bbbb | o=cccc,llll sh0--->ctx_0-->sh1>|-->context_2-+---->context_n---->sh2 llll=5 | bbbb=2 | cccc=4 | | llll=8 | i=abcd | | o=cccc | +>context_3+ cccc=3 Notes: Test: 1. Create a context, set a value in it and squash it into a new state hash. 2. Create 3 contexts based off of the state root from #1. 3. Set values at addresses to all three contexts. 4. Base another context off of the contexts from #2. 5. Set a value to an address in this context that has already been set to in the non-base context. 6. Squash the contexts producing a state hash and assert that it equals a state hash obtained by manually updating the merkle tree. """ sh0 = self.first_state_hash # 1) squash = self.context_manager.get_squash_handler() ctx_1 = self.context_manager.create_context( state_hash=sh0, base_contexts=[], inputs=[self._create_address('llll')], outputs=[self._create_address('llll')] ) self.context_manager.set( context_id=ctx_1, address_value_list=[{self._create_address('llll'): b'5'}] ) sh1 = squash( state_root=sh0, context_ids=[ctx_1], persist=True, clean_up=True) # 2) context_1 = self.context_manager.create_context( state_hash=sh1, base_contexts=[], inputs=[self._create_address('abcd')], outputs=[self._create_address('aaaa')] ) context_2 = self.context_manager.create_context( state_hash=sh1, base_contexts=[], inputs=[self._create_address('bacd')], outputs=[self._create_address('bbbb')] ) context_3 = self.context_manager.create_context( state_hash=sh1, base_contexts=[], inputs=[self._create_address('abcd')], outputs=[self._create_address('cccc'), self._create_address('dddd')] ) # 3) self.context_manager.set( context_id=context_1, address_value_list=[{self._create_address('aaaa'): b'1'}] ) self.context_manager.set( context_id=context_2, address_value_list=[{self._create_address('bbbb'): b'2'}] ) self.context_manager.set( context_id=context_3, address_value_list=[{self._create_address('cccc'): b'3'}] ) # 4) context_n = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_1, context_2, context_3], inputs=[self._create_address('bbbb'), self._create_address('aaaa')], outputs=[self._create_address('cccc'), self._create_address('llll')] ) # 5) self.context_manager.set( context_id=context_n, address_value_list=[{self._create_address('cccc'): b'4', self._create_address('llll'): b'8'}] ) # 6) cm_state_root = squash( state_root=sh1, context_ids=[context_n], persist=False, clean_up=True) tree = MerkleDatabase(self.database_results) calc_state_root = tree.update({self._create_address('aaaa'): b'1', self._create_address('bbbb'): b'2', self._create_address('cccc'): b'4', self._create_address('llll'): b'8'}) self.assertEquals(calc_state_root, cm_state_root) def test_complex_basecontext_squash(self): """Tests complex context basing and squashing. i=qq,dd dd=0 o=dd,pp pp=1 i=cc,aa +->context_3_2a_1+| o=dd,ll | | i=aa,ab +->context_2a| i=aa aa=0 | o=cc,ab | dd=10 | o=aa,ll ll=1 | sh0->context_1-->sh1| ll=11 +->context_3_2a_2+|->sh1 cc=0 | i=cc,aa +->context_3_2b_1+| ab=1 | o=nn,mm | i=nn,ba mm=0 | +->context_2b| o=mm,ba ba=1 | nn=0 | | mm=1 +->context_3_2b_2+| i=nn,oo ab=0 o=ab,oo oo=1 Notes: Test: 1. Create a context off of the first state hash, set addresses in it, and squash that context, getting a new merkle root. 2. Create 2 contexts with the context in # 1 as the base, and for each of these contexts set addresses to values where the outputs for each are disjoint. 3. For each of these 2 contexts create 2 more contexts each having one of the contexts in # 2 as the base context, and set addresses to values. 4. Squash the 4 contexts from #3 and assert the state hash is equal to a manually computed state hash. """ squash = self.context_manager.get_squash_handler() # 1) inputs_1 = [self._create_address('aa'), self._create_address('ab')] outputs_1 = [self._create_address('cc'), self._create_address('ab')] context_1 = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=inputs_1, outputs=outputs_1) self.context_manager.set( context_id=context_1, address_value_list=[{a: v} for a, v in zip( outputs_1, [bytes(i) for i in range(len(outputs_1))])]) sh1 = squash( state_root=self.first_state_hash, context_ids=[context_1], persist=True, clean_up=True) # 2) inputs_2a = [self._create_address('cc'), self._create_address('aa')] outputs_2a = [self._create_address('dd'), self._create_address('ll')] context_2a = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=inputs_2a, outputs=outputs_2a) inputs_2b = [self._create_address('cc'), self._create_address('aa')] outputs_2b = [self._create_address('nn'), self._create_address('mm')] context_2b = self.context_manager.create_context( state_hash=sh1, base_contexts=[], inputs=inputs_2b, outputs=outputs_2b) self.context_manager.set( context_id=context_2a, address_value_list=[{a: bytes(v)} for a, v in zip(outputs_2a, range(10, 10 + len(outputs_2a)))] ) self.context_manager.set( context_id=context_2b, address_value_list=[{a: bytes(v)} for a, v in zip(outputs_2b, range(len(outputs_2b)))] ) # 3) inputs_3_2a_1 = [self._create_address('qq'), self._create_address('dd')] outputs_3_2a_1 = [self._create_address('dd'), self._create_address('pp')] context_3_2a_1 = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_2a], inputs=inputs_3_2a_1, outputs=outputs_3_2a_1 ) inputs_3_2a_2 = [self._create_address('aa')] outputs_3_2a_2 = [self._create_address('aa'), self._create_address('ll')] context_3_2a_2 = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_2a], inputs=inputs_3_2a_2, outputs=outputs_3_2a_2) inputs_3_2b_1 = [self._create_address('nn'), self._create_address('ab')] outputs_3_2b_1 = [self._create_address('mm'), self._create_address('ba')] context_3_2b_1 = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_2b], inputs=inputs_3_2b_1, outputs=outputs_3_2b_1) inputs_3_2b_2 = [self._create_address('nn'), self._create_address('oo')] outputs_3_2b_2 = [self._create_address('ab'), self._create_address('oo')] context_3_2b_2 = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_2b], inputs=inputs_3_2b_2, outputs=outputs_3_2b_2) self.context_manager.set( context_id=context_3_2a_1, address_value_list=[{a: bytes(v)} for a, v in zip(outputs_3_2a_1, range(len(outputs_3_2a_1)))]) self.context_manager.set( context_id=context_3_2a_2, address_value_list=[{a: bytes(v)} for a, v in zip(outputs_3_2a_2, range(len(outputs_3_2a_2)))]) self.context_manager.set( context_id=context_3_2b_1, address_value_list=[{a: bytes(v)} for a, v in zip(outputs_3_2b_1, range(len(outputs_3_2b_1)))]) self.context_manager.set( context_id=context_3_2b_2, address_value_list=[{a: bytes(v)} for a, v in zip(outputs_3_2b_2, range(len(outputs_3_2b_2)))]) # 4) sh2 = squash( state_root=sh1, context_ids=[context_3_2a_1, context_3_2a_2, context_3_2b_1, context_3_2b_2], persist=False, clean_up=True) tree = MerkleDatabase(self.database_results) state_hash_from_1 = tree.update( set_items={a: v for a, v in zip(outputs_1, [bytes(i) for i in range(len(outputs_1))])}, virtual=False) self.assertEquals(state_hash_from_1, sh1, "The manually calculated state hash from the first " "context and the one calculated by squashing that " "state hash should be the same") tree.set_merkle_root(state_hash_from_1) test_sh2 = tree.update(set_items={self._create_address('aa'): bytes(0), self._create_address('ab'): bytes(0), self._create_address('ba'): bytes(1), self._create_address('dd'): bytes(0), self._create_address('ll'): bytes(1), self._create_address('mm'): bytes(0), self._create_address('oo'): bytes(1), self._create_address('pp'): bytes(1), self._create_address('nn'): bytes(0), self._create_address('cc'): bytes(0)}) self.assertEquals(sh2, test_sh2, "Manually calculated and context " "manager calculated merkle hashes " "are the same") @unittest.skip("Necessary to catch scheduler bugs--Depth-first search") def test_check_for_bad_combination(self): """Tests that the context manager will raise an exception if asked to combine contexts, either via base contexts in create_context or via squash that shouldn't be combined because they share addresses that can't be determined by the scheduler to not have been parallel. This is a check on scheduler bugs. Examples where the context manager should raise an exception on duplicate addresses: 1. Success i=a o=b +>ctx_1+ dup|->b=3 | | | sh0| ----->state hash or context | i=q | | o=b | +>ctx_2+ dup-->b=2 2. i=b o=d +>ctx_1a_1+ | d=4 | i=a | | o=b | | +>ctx_1a| | | b=2 | i=b | | | o=c | sh0| +>ctx_1a_2| | dup-> c=7 |------>state hash or context | i=a +>ctx_1b_1| | o=c | | +>ctx_1b| | dup-->c=5 | i=t | | o=p | +>ctx_1b_2+ p=8 3. i=b o=d +>ctx_1a_1+ | d=4 | i=d,c i=a | | o=n o=b | <>ctx_3a+ +>ctx_1a| | n=5 | | b=2 | i=b | | | | o=c | | sh0| +>ctx_1a_2+ <----->state hash or context | dup--> c=7 | | i=a +>ctx_1b_1+ | | o=c | | i=c | +>ctx_1b| | o=q | c=5 | i=c <>ctx_3b+ | o=c | q=5 +>ctx_1b_2+ dup--> c=1 """ # 1. squash = self.context_manager.get_squash_handler() sh0 = self.first_state_hash inputs_1 = [self._create_address('a')] outputs_1 = [self._create_address('b')] ctx_1 = self.context_manager.create_context( state_hash=sh0, base_contexts=[], inputs=inputs_1, outputs=outputs_1 ) self.context_manager.set( context_id=ctx_1, address_value_list=[{self._create_address('b'): b'3'}] ) inputs_2 = [self._create_address('q')] outputs_2 = [self._create_address('b')] ctx_2 = self.context_manager.create_context( state_hash=sh0, base_contexts=[], inputs=inputs_2, outputs=outputs_2 ) self.context_manager.set( context_id=ctx_2, address_value_list=[{self._create_address('b'): b'2'}] ) try: sh1 = squash( state_root=sh0, context_ids=[ctx_1, ctx_2], persist=True, clean_up=True) self.fail("squash of two contexts with a duplicate address") except Exception: pass
class TestContextManager(unittest.TestCase): def setUp(self): self.database_of_record = dict_database.DictDatabase() self.state_delta_store = StateDeltaStore(dict_database.DictDatabase()) self.context_manager = context_manager.ContextManager( self.database_of_record, self.state_delta_store) self.first_state_hash = self.context_manager.get_first_root() # used for replicating state hash through direct merkle tree updates self.database_results = dict_database.DictDatabase() def tearDown(self): self.context_manager.stop() def _create_address(self, value=None): """ Args: value: (str) Returns: (str) sha512 of value or random """ if value is None: value = time.time().hex() return hashlib.sha512(value.encode()).hexdigest()[:70] def _setup_context(self): # 1) Create transaction data first_transaction = { 'inputs': [self._create_address(a) for a in ['aaaa', 'bbbb', 'cccc']], 'outputs': [self._create_address(a) for a in ['llaa', 'aall', 'nnnn']] } second_transaction = { 'inputs': [self._create_address(a) for a in ['aaaa', 'dddd']], 'outputs': [ self._create_address(a) for a in ['zzzz', 'yyyy', 'tttt', 'qqqq'] ] } third_transaction = { 'inputs': [self._create_address(a) for a in ['eeee', 'dddd', 'ffff']], 'outputs': [ self._create_address(a) for a in ['oooo', 'oozz', 'zzoo', 'ppoo', 'aeio'] ] } # 2) Create contexts based on that data context_id_1 = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=first_transaction['inputs'], outputs=first_transaction['outputs']) context_id_2 = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=second_transaction['inputs'], outputs=second_transaction['outputs']) context_id_3 = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=third_transaction['inputs'], outputs=third_transaction['outputs']) # 3) Set addresses with values self.context_manager.set(context_id_1, [{ self._create_address(a): v } for a, v in [('llaa', b'1'), ('aall', b'2'), ('nnnn', b'3')]]) self.context_manager.set(context_id_2, [{ self._create_address(a): v } for a, v in [('zzzz', b'9'), ('yyyy', b'11'), ('tttt', b'12'), ('qqqq', b'13')]]) self.context_manager.set(context_id_3, [{ self._create_address(a): v } for a, v in [('oooo', b'25'), ('oozz', b'26'), ( 'zzoo', b'27'), ('ppoo', b'28'), ('aeio', b'29')]]) # 4) context_id = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[context_id_1, context_id_2, context_id_3], inputs=[ self._create_address(a) for a in ['llaa', 'yyyy', 'tttt', 'zzoo'] ], outputs=[ self._create_address(a) for a in ['llaa', 'yyyy', 'tttt', 'zzoo', 'aeio'] ]) return context_id def _create_txn_inputs_outputs(self, start=None): """Create unique addresses that make up the inputs, outputs, reads, and writes that are involved in a context. Venn Diagram of relationship of disjoint sets that make up the inputs, outputs, reads, and writes. Knowledge of which disjoint set an address is a part of may give knowledge about a test failure in the context manager. Inputs Outputs +----------+--------------------------+-----------+ | | | | | i___ |Reads io__ Writes _o__ | | | | | | +-----------+-----------+---------------+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |i_r_ | ior_| iorw | io_w | _o_w | | | | | | | | | | | | | | | | | | | | | | | | | | | +-----------+-----------+---------------+ | | | | | | | | | +----------+--------------------------+-----------+ Args: start (int): An integer to start the sequence of integers being hashed to addresses. Returns (namedtuple): An object that holds inputs, outputs, reads, and writes. """ if start is None: start = 0 iorw = [self._create_address(str(i)) for i in range(start, start + 10)] i_r_ = [ self._create_address(str(i)) for i in range(start + 10, start + 20) ] ior_ = [ self._create_address(str(i)) for i in range(start + 20, start + 30) ] io__ = [ self._create_address(str(i)) for i in range(start + 30, start + 40) ] io_w = [ self._create_address(str(i)) for i in range(start + 40, start + 50) ] _o_w = [ self._create_address(str(i)) for i in range(start + 50, start + 60) ] _o__ = [ self._create_address(str(i)) for i in range(start + 60, start + 70) ] i___ = [ self._create_address(str(i)) for i in range(start + 70, start + 80) ] addresses = TestAddresses(inputs=iorw + ior_ + io__ + io_w + i___, outputs=ior_ + io__ + io_w + _o__ + _o_w, reads=i_r_ + ior_, writes=io_w + _o_w) return addresses def test_address_enforcement(self): """Tests that the ContextManager enforces address characteristics. Notes: 1. Call get and set on the ContextManager with an address that is under a namespace, but is an invalid address, and test that the methods raise an AuthorizationException. """ # 1) invalid_address1 = 'a' * 69 + 'n' invalid_address2 = 'b' * 69 + 'y' context_id1 = self.context_manager.create_context( state_hash=self.context_manager.get_first_root(), base_contexts=[], inputs=['aaaaaaaa', 'bbbbbbbb'], outputs=['aaaaaaaa', 'bbbbbbbb']) with self.assertRaises(context_manager.AuthorizationException): self.context_manager.get( context_id=context_id1, address_list=[invalid_address1, invalid_address2]) with self.assertRaises(context_manager.AuthorizationException): self.context_manager.set(context_id=context_id1, address_value_list=[{ invalid_address1: b'1' }, { invalid_address2: b'2' }]) def test_get_set_wrong_namespace(self): """Tests that getting and setting from outside the namespace will raise a AuthorizationException. Notes: 1. Assert that sets on a context with addresses that aren't under an output namespace raise an AuthorizationException. 2. Assert that gets on a context with addresses that aren't under an input namespace raise an AuthorizationException. """ wrong_namespace1 = self._create_address('a')[-10:] wrong_namespace2 = '00000000' ctx_1 = self.context_manager.create_context( state_hash=self.context_manager.get_first_root(), base_contexts=[], inputs=[wrong_namespace1, wrong_namespace2], outputs=[wrong_namespace1, wrong_namespace2]) # 1 with self.assertRaises(context_manager.AuthorizationException): self.context_manager.set(context_id=ctx_1, address_value_list=[{ self._create_address('a'): b'1' }]) with self.assertRaises(context_manager.AuthorizationException): self.context_manager.set(context_id=ctx_1, address_value_list=[{ self._create_address('c'): b'5' }]) # 2 with self.assertRaises(context_manager.AuthorizationException): self.context_manager.get(context_id=ctx_1, address_list=[self._create_address('a')]) with self.assertRaises(context_manager.AuthorizationException): self.context_manager.get(context_id=ctx_1, address_list=[self._create_address('c')]) def test_exception_on_invalid_input(self): """Tests that invalid inputs raise an exception. Tested with invalid characters, odd number of characters, and too long namespace; Notes: 1) Assert that inputs with a namespace with an odd number of characters raise a CreateContextException. 2) Assert that inputs with a 71 character namespace raise a CreateContextException. 3) Assert that inputs with a namespace with several invalid characters raise a CreateContextException. """ invalid_input_output1 = '0db7e8zc' # invalid character invalid_input_output2 = '7ef84ed' * 10 + '5' # too long, 71 chars invalid_input_output3 = 'yy76ftoph7465873ddde389f' # invalid chars valid_input_output1 = 'd8f533bbb74443222daad4' valid_input_output2 = '77465847465784757848ddddddf' state_hash = self.context_manager.get_first_root() # 1 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[invalid_input_output1, valid_input_output1], outputs=[valid_input_output2]) # 2 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[valid_input_output1, invalid_input_output2], outputs=[valid_input_output2]) # 3 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[invalid_input_output3, valid_input_output2], outputs=[valid_input_output2, valid_input_output1]) def test_exception_on_invalid_output(self): """Tests that invalid outputs raise an exception. Tested with invalid characters, odd number of characters, and too long namespace; Notes: 1) Assert that outputs with a namespace with an odd number of characters raise a CreateContextException. 2) Assert that outputs with a 71 character namespace raise a CreateContextException. 3) Assert that outputs with a namespace with several invalid characters raise a CreateContextException. """ invalid_input_output1 = '0db7e87' # Odd number of characters invalid_input_output2 = '7ef84ed' * 10 + '5' # too long, 71 chars invalid_input_output3 = 'yy76ftoph7465873ddde389f' # invalid chars valid_input_output1 = 'd8f533bbb74443222daad4' valid_input_output2 = '77465847465784757848ddddddff' state_hash = self.context_manager.get_first_root() # 1 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[valid_input_output2, valid_input_output1], outputs=[invalid_input_output1]) # 2 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[valid_input_output1, valid_input_output2], outputs=[invalid_input_output2]) # 3 with self.assertRaises(context_manager.CreateContextException): self.context_manager.create_context( state_hash=state_hash, base_contexts=[], inputs=[valid_input_output1, valid_input_output2], outputs=[valid_input_output2, invalid_input_output3]) def test_namespace_gets(self): """Tests that gets for an address under a namespace will return the correct value. Notes: 1) Create ctx_1 and set 'b' to b'8'. 2) squash the previous context creating state_hash_1. 3) Create 2 contexts off of this state hash and assert that gets on these contexts retrieve the correct value for an address that is not fully specified in the inputs. 4) Set values to addresses in these contexts. 5) Create 1 context off of these prior 2 contexts and assert that gets from this context retrieve the correct values for addresses that are not fully specified in the inputs. 2 of the values are found in the chain of contexts, and 1 is not found and so must be retrieved from the merkle tree. """ # 1 ctx_1 = self.context_manager.create_context( state_hash=self.context_manager.get_first_root(), base_contexts=[], inputs=[self._create_address('a')], outputs=[self._create_address('b')]) self.context_manager.set(context_id=ctx_1, address_value_list=[{ self._create_address('b'): b'8' }]) # 2 squash = self.context_manager.get_squash_handler() state_hash_1 = squash(state_root=self.context_manager.get_first_root(), context_ids=[ctx_1], persist=True, clean_up=True) # 3 ctx_1a = self.context_manager.create_context( state_hash=state_hash_1, base_contexts=[], inputs=[self._create_address('a')[:10]], outputs=[self._create_address('c')]) self.assertEquals( self.context_manager.get(context_id=ctx_1a, address_list=[self._create_address('a')]), [(self._create_address('a'), None)]) ctx_1b = self.context_manager.create_context( state_hash=state_hash_1, base_contexts=[], inputs=[self._create_address('b')[:6]], outputs=[self._create_address('z')]) self.assertEquals( self.context_manager.get(context_id=ctx_1b, address_list=[self._create_address('b')]), [(self._create_address('b'), b'8')]) # 4 self.context_manager.set(context_id=ctx_1b, address_value_list=[{ self._create_address('z'): b'2' }]) self.context_manager.set(context_id=ctx_1a, address_value_list=[{ self._create_address('c'): b'1' }]) ctx_2 = self.context_manager.create_context( state_hash=state_hash_1, base_contexts=[ctx_1a, ctx_1b], inputs=[ self._create_address('z')[:10], self._create_address('c')[:10], self._create_address('b')[:10] ], outputs=[self._create_address('w')]) self.assertEquals( self.context_manager.get(context_id=ctx_2, address_list=[ self._create_address('z'), self._create_address('c'), self._create_address('b') ]), [(self._create_address('z'), b'2'), (self._create_address('c'), b'1'), (self._create_address('b'), b'8')]) def test_create_context_with_prior_state(self): """Tests context creation with prior state from base contexts. Notes: Set up the context: Create 3 prior contexts each with 3-5 addresses to set to. Make set calls to those addresses. Create 1 new context based on those three prior contexts. this test method: Test: Make a get call on addresses that are from prior state, making assertions about the correct values. """ context_id = self._setup_context() self.assertEqual( self.context_manager.get(context_id, [ self._create_address(a) for a in ['llaa', 'yyyy', 'tttt', 'zzoo'] ]), [(self._create_address(a), v) for a, v in [('llaa', b'1'), ('yyyy', b'11'), ('tttt', b'12'), ('zzoo', b'27')]]) def test_squash(self): """Tests that squashing a context based on state from other contexts will result in the same merkle hash as updating the merkle tree with the same data. Notes: Set up the context Test: 1) Make set calls on several of the addresses. 2) Squash the context to get a new state hash. 3) Apply all of the aggregate sets from all of the contexts, to another database with a merkle tree. 4) Assert that the state hashes are the same. 5) Assert that the state deltas have been stored """ # 1) context_id = self._setup_context() self.context_manager.set(context_id, [{ self._create_address(a): v } for a, v in [('yyyy', b'2'), ('tttt', b'4')]]) # 2) squash = self.context_manager.get_squash_handler() resulting_state_hash = squash(self.first_state_hash, [context_id], persist=True, clean_up=True) # 3) final_state_to_update = { self._create_address(a): v for a, v in [('llaa', b'1'), ('aall', b'2'), ( 'nnnn', b'3'), ('zzzz', b'9'), ('yyyy', b'2'), ( 'tttt', b'4'), ('qqqq', b'13'), ('oooo', b'25'), ( 'oozz', b'26'), ('zzoo', b'27'), ('ppoo', b'28'), ('aeio', b'29')] } test_merkle_tree = MerkleDatabase(self.database_results) test_resulting_state_hash = test_merkle_tree.update( final_state_to_update, virtual=False) # 4) self.assertEqual(resulting_state_hash, test_resulting_state_hash) state_changes = self.state_delta_store.get_state_deltas( resulting_state_hash) # 5) for addr, value in final_state_to_update.items(): expected_state_change = StateChange(address=addr, value=value, type=StateChange.SET) self.assertTrue(expected_state_change in state_changes) def test_squash_no_updates(self): """Tests that squashing a context that has no state updates will return the starting state root hash. Notes: Set up the context Test: 1) Squash the context. 2) Assert that the state hash is the same as the starting hash. 3) Assert that the state deltas have not been overwritten """ self.state_delta_store.save_state_deltas( self.first_state_hash, [StateChange(address='aaa', value=b'xyz', type=StateChange.SET)]) context_id = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=[], outputs=[]) # 1) squash = self.context_manager.get_squash_handler() resulting_state_hash = squash(self.first_state_hash, [context_id], persist=True, clean_up=True) # 2 self.assertIsNotNone(resulting_state_hash) self.assertEquals(resulting_state_hash, self.first_state_hash) # 3 changes = self.state_delta_store.get_state_deltas(resulting_state_hash) self.assertEqual( [StateChange(address='aaa', value=b'xyz', type=StateChange.SET)], [c for c in changes]) def test_reads_from_context_w_several_writes(self): """Tests that those context values that have been written to the Merkle tree, or that have been set to a base_context, will have the correct value at the address for a given context. ->context_id_a1 | | | | | | sh0-->context_id1-->sh1-->context_a----- -->context_id_b | | | | | | | | -->context_id_a2 Notes: Test: 1. From a Merkle Tree with only the root node in it, create a context and set several values, and then squash that context upon the first state hash. 2. Create a context with no base context, based on the merkle root computed from the first squash. Assert that gets from this context will provide values that were set in the first context. 3. Write to all of the available outputs 4. Create a new context based on context_a, from #2, 5. Assert that gets from this context equal the values set to Context A. 6. Create a new context based on context_a and set values to this context. 7. Create a new context based on the 2 contexts made in 4 and 6 8. From this context assert that gets equal the correct values set in the prior contexts. """ squash = self.context_manager.get_squash_handler() test_addresses = self._create_txn_inputs_outputs() # 1) context_id1 = self.context_manager.create_context( state_hash=self.first_state_hash, inputs=test_addresses.inputs, outputs=test_addresses.outputs, base_contexts=[]) values1 = [bytes(i) for i in range(len(test_addresses.writes))] self.context_manager.set(context_id1, [{ a: v } for a, v in zip(test_addresses.writes, values1)]) sh1 = squash(state_root=self.first_state_hash, context_ids=[context_id1], persist=True, clean_up=True) # 2) context_a = self.context_manager.create_context( state_hash=sh1, inputs=test_addresses.writes, # read from every address written to outputs=test_addresses.outputs, base_contexts=[]) address_values = self.context_manager.get(context_a, list(test_addresses.writes)) self.assertEquals(address_values, [(a, v) for a, v in zip(test_addresses.writes, values1)]) # 3) values2 = [bytes(v.encode()) for v in test_addresses.outputs] self.context_manager.set( context_id=context_a, address_value_list=[{ a: v } for a, v in zip(test_addresses.outputs, values2)]) # 4) context_id_a1 = self.context_manager.create_context( state_hash=sh1, inputs=test_addresses.outputs, outputs=test_addresses.outputs, base_contexts=[context_a]) # 5) c_ida1_address_values = self.context_manager.get( context_id=context_id_a1, address_list=list(test_addresses.outputs)) self.assertEquals(c_ida1_address_values, [(a, v) for a, v in zip(test_addresses.outputs, values2)]) # 6) test_addresses2 = self._create_txn_inputs_outputs(80) context_id_a2 = self.context_manager.create_context( state_hash=sh1, inputs=test_addresses2.inputs, outputs=test_addresses2.outputs, base_contexts=[context_a]) values3 = [bytes(v.encode()) for v in test_addresses2.writes] self.context_manager.set( context_id=context_id_a2, address_value_list=[{ a: v } for a, v in zip(test_addresses2.writes, values3)], ) # 7) context_id_b = self.context_manager.create_context( state_hash=sh1, inputs=test_addresses2.writes + test_addresses.outputs, outputs=[], base_contexts=[context_id_a1, context_id_a2]) # 8) self.assertEquals( self.context_manager.get( context_id_b, list(test_addresses2.writes + test_addresses.outputs)), [(a, v) for a, v in zip(test_addresses2.writes + test_addresses.outputs, values3 + values2)]) def test_state_root_after_parallel_ctx(self): """Tests that the correct state root is calculated after basing one context off of multiple contexts. i=abcd o=aaaa +>context_1+ | aaaa=1 | | | i=llll | i=bacd | i=bbbb,aaaa o=llll | o=bbbb | o=cccc,llll sh0--->ctx_0-->sh1>|-->context_2-+---->context_n---->sh2 llll=5 | bbbb=2 | cccc=4 | | llll=8 | i=abcd | | o=cccc | +>context_3+ cccc=3 Notes: Test: 1. Create a context, set a value in it and squash it into a new state hash. 2. Create 3 contexts based off of the state root from #1. 3. Set values at addresses to all three contexts. 4. Base another context off of the contexts from #2. 5. Set a value to an address in this context that has already been set to in the non-base context. 6. Squash the contexts producing a state hash and assert that it equals a state hash obtained by manually updating the merkle tree. """ sh0 = self.first_state_hash # 1) squash = self.context_manager.get_squash_handler() ctx_1 = self.context_manager.create_context( state_hash=sh0, base_contexts=[], inputs=[self._create_address('llll')], outputs=[self._create_address('llll')]) self.context_manager.set(context_id=ctx_1, address_value_list=[{ self._create_address('llll'): b'5' }]) sh1 = squash(state_root=sh0, context_ids=[ctx_1], persist=True, clean_up=True) # 2) context_1 = self.context_manager.create_context( state_hash=sh1, base_contexts=[], inputs=[self._create_address('abcd')], outputs=[self._create_address('aaaa')]) context_2 = self.context_manager.create_context( state_hash=sh1, base_contexts=[], inputs=[self._create_address('bacd')], outputs=[self._create_address('bbbb')]) context_3 = self.context_manager.create_context( state_hash=sh1, base_contexts=[], inputs=[self._create_address('abcd')], outputs=[ self._create_address('cccc'), self._create_address('dddd') ]) # 3) self.context_manager.set(context_id=context_1, address_value_list=[{ self._create_address('aaaa'): b'1' }]) self.context_manager.set(context_id=context_2, address_value_list=[{ self._create_address('bbbb'): b'2' }]) self.context_manager.set(context_id=context_3, address_value_list=[{ self._create_address('cccc'): b'3' }]) # 4) context_n = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_1, context_2, context_3], inputs=[ self._create_address('bbbb'), self._create_address('aaaa') ], outputs=[ self._create_address('cccc'), self._create_address('llll') ]) # 5) self.context_manager.set(context_id=context_n, address_value_list=[{ self._create_address('cccc'): b'4', self._create_address('llll'): b'8' }]) # 6) cm_state_root = squash(state_root=sh1, context_ids=[context_n], persist=False, clean_up=True) tree = MerkleDatabase(self.database_results) calc_state_root = tree.update({ self._create_address('aaaa'): b'1', self._create_address('bbbb'): b'2', self._create_address('cccc'): b'4', self._create_address('llll'): b'8' }) self.assertEquals(calc_state_root, cm_state_root) def test_complex_basecontext_squash(self): """Tests complex context basing and squashing. i=qq,dd dd=0 o=dd,pp pp=1 i=cc,aa +->context_3_2a_1+| o=dd,ll | | i=aa,ab +->context_2a| i=aa aa=0 | o=cc,ab | dd=10 | o=aa,ll ll=1 | sh0->context_1-->sh1| ll=11 +->context_3_2a_2+|->sh1 cc=0 | i=cc,aa +->context_3_2b_1+| ab=1 | o=nn,mm | i=nn,ba mm=0 | +->context_2b| o=mm,ba ba=1 | nn=0 | | mm=1 +->context_3_2b_2+| i=nn,oo ab=0 o=ab,oo oo=1 Notes: Test: 1. Create a context off of the first state hash, set addresses in it, and squash that context, getting a new merkle root. 2. Create 2 contexts with the context in # 1 as the base, and for each of these contexts set addresses to values where the outputs for each are disjoint. 3. For each of these 2 contexts create 2 more contexts each having one of the contexts in # 2 as the base context, and set addresses to values. 4. Squash the 4 contexts from #3 and assert the state hash is equal to a manually computed state hash. """ squash = self.context_manager.get_squash_handler() # 1) inputs_1 = [self._create_address('aa'), self._create_address('ab')] outputs_1 = [self._create_address('cc'), self._create_address('ab')] context_1 = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=inputs_1, outputs=outputs_1) self.context_manager.set( context_id=context_1, address_value_list=[{ a: v } for a, v in zip(outputs_1, [bytes(i) for i in range(len(outputs_1))])]) sh1 = squash(state_root=self.first_state_hash, context_ids=[context_1], persist=True, clean_up=True) # 2) inputs_2a = [self._create_address('cc'), self._create_address('aa')] outputs_2a = [self._create_address('dd'), self._create_address('ll')] context_2a = self.context_manager.create_context( state_hash=self.first_state_hash, base_contexts=[], inputs=inputs_2a, outputs=outputs_2a) inputs_2b = [self._create_address('cc'), self._create_address('aa')] outputs_2b = [self._create_address('nn'), self._create_address('mm')] context_2b = self.context_manager.create_context(state_hash=sh1, base_contexts=[], inputs=inputs_2b, outputs=outputs_2b) self.context_manager.set( context_id=context_2a, address_value_list=[{ a: bytes(v) } for a, v in zip(outputs_2a, range(10, 10 + len(outputs_2a)))]) self.context_manager.set( context_id=context_2b, address_value_list=[{ a: bytes(v) } for a, v in zip(outputs_2b, range(len(outputs_2b)))]) # 3) inputs_3_2a_1 = [ self._create_address('qq'), self._create_address('dd') ] outputs_3_2a_1 = [ self._create_address('dd'), self._create_address('pp') ] context_3_2a_1 = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_2a], inputs=inputs_3_2a_1, outputs=outputs_3_2a_1) inputs_3_2a_2 = [self._create_address('aa')] outputs_3_2a_2 = [ self._create_address('aa'), self._create_address('ll') ] context_3_2a_2 = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_2a], inputs=inputs_3_2a_2, outputs=outputs_3_2a_2) inputs_3_2b_1 = [ self._create_address('nn'), self._create_address('ab') ] outputs_3_2b_1 = [ self._create_address('mm'), self._create_address('ba') ] context_3_2b_1 = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_2b], inputs=inputs_3_2b_1, outputs=outputs_3_2b_1) inputs_3_2b_2 = [ self._create_address('nn'), self._create_address('oo') ] outputs_3_2b_2 = [ self._create_address('ab'), self._create_address('oo') ] context_3_2b_2 = self.context_manager.create_context( state_hash=sh1, base_contexts=[context_2b], inputs=inputs_3_2b_2, outputs=outputs_3_2b_2) self.context_manager.set( context_id=context_3_2a_1, address_value_list=[{ a: bytes(v) } for a, v in zip(outputs_3_2a_1, range(len(outputs_3_2a_1)))]) self.context_manager.set( context_id=context_3_2a_2, address_value_list=[{ a: bytes(v) } for a, v in zip(outputs_3_2a_2, range(len(outputs_3_2a_2)))]) self.context_manager.set( context_id=context_3_2b_1, address_value_list=[{ a: bytes(v) } for a, v in zip(outputs_3_2b_1, range(len(outputs_3_2b_1)))]) self.context_manager.set( context_id=context_3_2b_2, address_value_list=[{ a: bytes(v) } for a, v in zip(outputs_3_2b_2, range(len(outputs_3_2b_2)))]) # 4) sh2 = squash(state_root=sh1, context_ids=[ context_3_2a_1, context_3_2a_2, context_3_2b_1, context_3_2b_2 ], persist=False, clean_up=True) tree = MerkleDatabase(self.database_results) state_hash_from_1 = tree.update(set_items={ a: v for a, v in zip(outputs_1, [bytes(i) for i in range(len(outputs_1))]) }, virtual=False) self.assertEquals( state_hash_from_1, sh1, "The manually calculated state hash from the first " "context and the one calculated by squashing that " "state hash should be the same") tree.set_merkle_root(state_hash_from_1) test_sh2 = tree.update( set_items={ self._create_address('aa'): bytes(0), self._create_address('ab'): bytes(0), self._create_address('ba'): bytes(1), self._create_address('dd'): bytes(0), self._create_address('ll'): bytes(1), self._create_address('mm'): bytes(0), self._create_address('oo'): bytes(1), self._create_address('pp'): bytes(1), self._create_address('nn'): bytes(0), self._create_address('cc'): bytes(0) }) self.assertEquals( sh2, test_sh2, "Manually calculated and context " "manager calculated merkle hashes " "are the same") @unittest.skip("Necessary to catch scheduler bugs--Depth-first search") def test_check_for_bad_combination(self): """Tests that the context manager will raise an exception if asked to combine contexts, either via base contexts in create_context or via squash that shouldn't be combined because they share addresses that can't be determined by the scheduler to not have been parallel. This is a check on scheduler bugs. Examples where the context manager should raise an exception on duplicate addresses: 1. Success i=a o=b +>ctx_1+ dup|->b=3 | | | sh0| ----->state hash or context | i=q | | o=b | +>ctx_2+ dup-->b=2 2. i=b o=d +>ctx_1a_1+ | d=4 | i=a | | o=b | | +>ctx_1a| | | b=2 | i=b | | | o=c | sh0| +>ctx_1a_2| | dup-> c=7 |------>state hash or context | i=a +>ctx_1b_1| | o=c | | +>ctx_1b| | dup-->c=5 | i=t | | o=p | +>ctx_1b_2+ p=8 3. i=b o=d +>ctx_1a_1+ | d=4 | i=d,c i=a | | o=n o=b | <>ctx_3a+ +>ctx_1a| | n=5 | | b=2 | i=b | | | | o=c | | sh0| +>ctx_1a_2+ <----->state hash or context | dup--> c=7 | | i=a +>ctx_1b_1+ | | o=c | | i=c | +>ctx_1b| | o=q | c=5 | i=c <>ctx_3b+ | o=c | q=5 +>ctx_1b_2+ dup--> c=1 """ # 1. squash = self.context_manager.get_squash_handler() sh0 = self.first_state_hash inputs_1 = [self._create_address('a')] outputs_1 = [self._create_address('b')] ctx_1 = self.context_manager.create_context(state_hash=sh0, base_contexts=[], inputs=inputs_1, outputs=outputs_1) self.context_manager.set(context_id=ctx_1, address_value_list=[{ self._create_address('b'): b'3' }]) inputs_2 = [self._create_address('q')] outputs_2 = [self._create_address('b')] ctx_2 = self.context_manager.create_context(state_hash=sh0, base_contexts=[], inputs=inputs_2, outputs=outputs_2) self.context_manager.set(context_id=ctx_2, address_value_list=[{ self._create_address('b'): b'2' }]) try: sh1 = squash(state_root=sh0, context_ids=[ctx_1, ctx_2], persist=True, clean_up=True) self.fail("squash of two contexts with a duplicate address") except Exception: pass