github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/validator/tests/test_scheduler/tests.py (about)

     1  # Copyright 2016, 2017 Intel Corporation
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  # ------------------------------------------------------------------------------
    15  
    16  # pylint: disable=too-many-lines,invalid-name
    17  
    18  import unittest
    19  import hashlib
    20  import os
    21  import shutil
    22  import tempfile
    23  import threading
    24  import time
    25  
    26  from sawtooth_signing import create_context
    27  from sawtooth_signing import CryptoFactory
    28  
    29  import sawtooth_validator.protobuf.transaction_pb2 as transaction_pb2
    30  
    31  from sawtooth_validator.execution.context_manager import ContextManager
    32  from sawtooth_validator.execution.scheduler_exceptions import SchedulerError
    33  from sawtooth_validator.execution.scheduler_serial import SerialScheduler
    34  from sawtooth_validator.execution.scheduler_parallel import ParallelScheduler
    35  from sawtooth_validator.database.native_lmdb import NativeLmdbDatabase
    36  from sawtooth_validator.state.merkle import MerkleDatabase
    37  
    38  from test_scheduler.yaml_scheduler_tester import create_batch
    39  from test_scheduler.yaml_scheduler_tester import create_transaction
    40  
    41  
    42  def _get_address_from_txn(txn_info):
    43      txn_header = transaction_pb2.TransactionHeader()
    44      txn_header.ParseFromString(txn_info.txn.header)
    45      inputs_or_outputs = list(txn_header.inputs)
    46      address_b = inputs_or_outputs[0]
    47      return address_b
    48  
    49  
    50  def create_address(payload):
    51      return hashlib.sha512(payload.encode()).hexdigest()[0:70]
    52  
    53  
    54  class TestSchedulers(unittest.TestCase):
    55      def __init__(self, test_name):
    56          super().__init__(test_name)
    57          self._temp_dir = None
    58  
    59      def setUp(self):
    60          self._temp_dir = tempfile.mkdtemp()
    61  
    62          database = NativeLmdbDatabase(
    63              os.path.join(self._temp_dir, 'test_schedulers.lmdb'),
    64              indexes=MerkleDatabase.create_index_configuration(),
    65              _size=10 * 1024 * 1024)
    66  
    67          self._context_manager = ContextManager(database)
    68  
    69          self._context = create_context('secp256k1')
    70          self._crypto_factory = CryptoFactory(self._context)
    71  
    72      def tearDown(self):
    73          self._context_manager.stop()
    74          shutil.rmtree(self._temp_dir)
    75  
    76      def _setup_serial_scheduler(self):
    77          context_manager = self._context_manager
    78  
    79          squash_handler = context_manager.get_squash_handler()
    80          first_state_root = context_manager.get_first_root()
    81          scheduler = SerialScheduler(squash_handler,
    82                                      first_state_root,
    83                                      always_persist=False)
    84          return context_manager, scheduler
    85  
    86      def _setup_parallel_scheduler(self):
    87          context_manager = self._context_manager
    88  
    89          squash_handler = context_manager.get_squash_handler()
    90          first_state_root = context_manager.get_first_root()
    91          scheduler = ParallelScheduler(squash_handler,
    92                                        first_state_root,
    93                                        always_persist=False)
    94          return context_manager, scheduler
    95  
    96      def test_parallel_dependencies(self):
    97          """Tests that transactions dependent on other transactions will fail
    98          their batch, if the dependency fails
    99          """
   100  
   101          context_manager, scheduler = self._setup_parallel_scheduler()
   102          self._dependencies(scheduler, context_manager)
   103  
   104      def test_serial_dependencies(self):
   105          """Tests that transactions dependent on other transactions will fail
   106          their batch, if the dependency fails
   107          """
   108  
   109          context_manager, scheduler = self._setup_serial_scheduler()
   110          self._dependencies(scheduler, context_manager)
   111  
   112      def _dependencies(self, scheduler, context_manager):
   113          """Tests that transactions dependent on other transactions will fail
   114          their batch, if the dependency fails.
   115  
   116              1        2    3   4   5
   117          dependency--> B       D   F
   118          [A, B, C] [D, E] [F] [G] [H]
   119                 x <------- invalid
   120          Notes:
   121              1. Add 5 batches with 8 txns with dependencies as in the diagram.
   122              2. Run through the scheduler setting all the transaction results,
   123                 including the single invalid txn, C.
   124              3. Assert the batch validity, that there are 2 valid batches,
   125                 3 and 5.
   126  
   127          """
   128          private_key = self._context.new_random_private_key()
   129          signer = self._crypto_factory.new_signer(private_key)
   130  
   131          transaction_validity = {}
   132  
   133          # 1.
   134  
   135          txn_a, _ = create_transaction(
   136              payload='A'.encode(),
   137              signer=signer)
   138  
   139          transaction_validity[txn_a.header_signature] = True
   140  
   141          txn_b, _ = create_transaction(
   142              payload='B'.encode(),
   143              signer=signer)
   144  
   145          transaction_validity[txn_b.header_signature] = True
   146  
   147          txn_c, _ = create_transaction(
   148              payload='C'.encode(),
   149              signer=signer)
   150  
   151          transaction_validity[txn_c.header_signature] = False
   152  
   153          batch_1 = create_batch(
   154              transactions=[txn_a, txn_b, txn_c],
   155              signer=signer)
   156  
   157          txn_d, _ = create_transaction(
   158              payload='D'.encode(),
   159              signer=signer)
   160  
   161          transaction_validity[txn_d.header_signature] = True
   162  
   163          txn_e, _ = create_transaction(
   164              payload='E'.encode(),
   165              signer=signer,
   166              dependencies=[txn_b.header_signature])
   167  
   168          transaction_validity[txn_e.header_signature] = True
   169  
   170          batch_2 = create_batch(
   171              transactions=[txn_d, txn_e],
   172              signer=signer)
   173  
   174          txn_f, _ = create_transaction(
   175              payload='F'.encode(),
   176              signer=signer)
   177  
   178          transaction_validity[txn_f.header_signature] = True
   179  
   180          batch_3 = create_batch(
   181              transactions=[txn_f],
   182              signer=signer)
   183  
   184          txn_g, _ = create_transaction(
   185              payload='G'.encode(),
   186              signer=signer,
   187              dependencies=[txn_d.header_signature])
   188  
   189          transaction_validity[txn_g.header_signature] = True
   190  
   191          batch_4 = create_batch(
   192              transactions=[txn_g],
   193              signer=signer)
   194  
   195          txn_h, _ = create_transaction(
   196              payload='H'.encode(),
   197              signer=signer,
   198              dependencies=[txn_f.header_signature])
   199  
   200          transaction_validity[txn_h.header_signature] = True
   201  
   202          batch_5 = create_batch(
   203              transactions=[txn_h],
   204              signer=signer)
   205  
   206          for batch in [batch_1, batch_2, batch_3, batch_4, batch_5]:
   207              scheduler.add_batch(batch)
   208  
   209          # 2.
   210          scheduler.finalize()
   211          scheduler_iter = iter(scheduler)
   212          while not scheduler.complete(block=False):
   213              txn_info = next(scheduler_iter)
   214              context_id = context_manager.create_context(
   215                  state_hash=txn_info.state_hash,
   216                  base_contexts=txn_info.base_context_ids,
   217                  inputs=[_get_address_from_txn(txn_info)],
   218                  outputs=[_get_address_from_txn(txn_info)])
   219              txn_id = txn_info.txn.header_signature
   220              validity = transaction_validity[txn_id]
   221  
   222              scheduler.set_transaction_execution_result(
   223                  txn_signature=txn_id,
   224                  is_valid=validity,
   225                  context_id=context_id)
   226  
   227          # 3.
   228          for i, batch_info in enumerate(
   229                  [(batch_1, False),
   230                   (batch_2, False),
   231                   (batch_3, True),
   232                   (batch_4, False),
   233                   (batch_5, True)]):
   234              batch, validity = batch_info[0], batch_info[1]
   235              batch_id = batch.header_signature
   236              result = scheduler.get_batch_execution_result(batch_id)
   237              self.assertEqual(
   238                  result.is_valid,
   239                  validity,
   240                  "Batch {} was {} when it should have been {}".format(
   241                      i + 1,
   242                      'valid' if result.is_valid else 'invalid',
   243                      'valid' if validity else 'invalid'))
   244              txn_results = scheduler.get_transaction_execution_results(batch_id)
   245              # Check that a result was returned for every transaction
   246              result_txn_ids = [result.signature for result in txn_results]
   247              batch_txn_ids = [txn.header_signature
   248                               for txn in batch.transactions]
   249              self.assertEqual(result_txn_ids, batch_txn_ids)
   250              if validity:
   251                  self.assertTrue(all(txn.is_valid for txn in txn_results))
   252  
   253      def test_serial_fail_fast(self):
   254          """Tests that transactions that are already determined to be in an
   255          invalid batch due to a prior transaction being invalid, won't run.
   256          """
   257  
   258          _, scheduler = self._setup_serial_scheduler()
   259          self._fail_fast(scheduler)
   260  
   261      def _fail_fast(self, scheduler):
   262          """Tests that transactions that are already determined to be in an
   263          invalid batch due to a prior transaction being invalid, won't run.
   264            x
   265          [ A B C ] [ D E F]
   266                      B
   267          Notes:
   268               1. Create an invalid transaction, txn A, and put it in a batch
   269                  with txn B. Create a transaction, txn C, that depends on txn B
   270                  and put it in a batch with txn D.
   271  
   272               2. Add the batches to the scheduler and call finalize.
   273  
   274               3. Assert that only txn A is run.
   275          """
   276  
   277          private_key = self._context.new_random_private_key()
   278          signer = self._crypto_factory.new_signer(private_key)
   279  
   280          transaction_validity = {}
   281  
   282          txn_a, _ = create_transaction(
   283              payload='A'.encode(),
   284              signer=signer)
   285  
   286          transaction_validity[txn_a.header_signature] = False
   287  
   288          txn_b, _ = create_transaction(
   289              payload='B'.encode(),
   290              signer=signer)
   291  
   292          txn_c, _ = create_transaction(
   293              payload='C'.encode(),
   294              signer=signer)
   295  
   296          batch_1 = create_batch(transactions=[txn_a, txn_b, txn_c],
   297                                 signer=signer)
   298  
   299          txn_d, _ = create_transaction(
   300              payload='D'.encode(),
   301              signer=signer,
   302              dependencies=[txn_b.header_signature])
   303  
   304          txn_e, _ = create_transaction(
   305              payload='E'.encode(),
   306              signer=signer)
   307  
   308          txn_f, _ = create_transaction(
   309              payload='F'.encode(),
   310              signer=signer)
   311  
   312          batch_2 = create_batch(
   313              transactions=[txn_d, txn_e, txn_f],
   314              signer=signer)
   315  
   316          scheduler.add_batch(batch_1)
   317          scheduler.add_batch(batch_2)
   318          scheduler.finalize()
   319  
   320          scheduler_iter = iter(scheduler)
   321          while not scheduler.complete(block=False):
   322              try:
   323                  txn_from_scheduler = next(scheduler_iter)
   324              except StopIteration:
   325                  break
   326              txn_id = txn_from_scheduler.txn.header_signature
   327  
   328              self.assertEqual(txn_id,
   329                               txn_a.header_signature,
   330                               "Only Transaction A is run, not txn {}"
   331                               "".format(txn_from_scheduler.txn.payload))
   332  
   333              validity = transaction_validity[txn_id]
   334              scheduler.set_transaction_execution_result(
   335                  txn_signature=txn_id,
   336                  is_valid=validity,
   337                  context_id=None)
   338  
   339          scheduler_iter2 = iter(scheduler)
   340          txn_info_from_2 = next(scheduler_iter2)
   341          txn_id_from_2 = txn_info_from_2.txn.header_signature
   342          self.assertEqual(txn_id_from_2, txn_a.header_signature,
   343                           "Only Transaction A is run, not "
   344                           "txn {}".format(txn_info_from_2.txn.payload))
   345          with self.assertRaises(StopIteration):
   346              next(scheduler_iter2)
   347  
   348      def test_serial_wildcarded_unschedule(self):
   349          context_manager, scheduler = self._setup_serial_scheduler()
   350  
   351          self._test_wildcarded_unschedule(
   352              scheduler=scheduler,
   353              context_manager=context_manager)
   354  
   355      def test_parallel_wildcarded_unschedule(self):
   356          context_manager, scheduler = self._setup_parallel_scheduler()
   357  
   358          self._test_wildcarded_unschedule(
   359              scheduler=scheduler,
   360              context_manager=context_manager)
   361  
   362      def _test_wildcarded_unschedule(self, scheduler, context_manager):
   363          """Tests interaction of the context manager and scheduler when
   364          addresses are wildcarded. The error condition is that this
   365          will raise a KeyError.
   366  
   367          Notes:
   368              1) Add two batches, each with one transaction, with wildcarded
   369                 inputs and outputs.
   370              2) Create a context.
   371              3) Set the transaction result as valid.
   372              4) Get the next transaction and create a context.
   373              5) Unschedule the schedule.
   374              6) Make a Get on the context manager.
   375  
   376          """
   377  
   378          private_key = self._context.new_random_private_key()
   379          signer = self._crypto_factory.new_signer(private_key)
   380  
   381          txn1, _ = create_transaction(
   382              payload='A'.encode(),
   383              signer=signer,
   384              inputs=[create_address('A')[0:40]],
   385              outputs=[create_address('A')[0:40]])
   386  
   387          batch_1 = create_batch(
   388              [txn1],
   389              signer=signer)
   390  
   391          txn2, _ = create_transaction(
   392              payload='A'.encode(),
   393              signer=signer,
   394              inputs=[create_address('A')[0:40]])
   395  
   396          batch_2 = create_batch(
   397              [txn2],
   398              signer=signer)
   399  
   400          # 1
   401          scheduler.add_batch(batch=batch_1)
   402          scheduler.add_batch(batch=batch_2)
   403  
   404          # 2
   405          txn_info = scheduler.next_transaction()
   406  
   407          header = transaction_pb2.TransactionHeader()
   408          header.ParseFromString(txn_info.txn.header)
   409          inputs = list(header.inputs)
   410          outputs = list(header.outputs)
   411  
   412          ctx_id1 = context_manager.create_context(
   413              state_hash=txn_info.state_hash,
   414              base_contexts=txn_info.base_context_ids,
   415              inputs=inputs,
   416              outputs=outputs)
   417  
   418          # 3
   419          scheduler.set_transaction_execution_result(
   420              txn_signature=txn_info.txn.header_signature,
   421              is_valid=True,
   422              context_id=ctx_id1)
   423  
   424          # 4
   425          txn_info_2 = scheduler.next_transaction()
   426  
   427          header = transaction_pb2.TransactionHeader()
   428          header.ParseFromString(txn_info_2.txn.header)
   429          inputs = list(header.inputs)
   430          outputs = list(header.outputs)
   431  
   432          ctx_id2 = context_manager.create_context(
   433              state_hash=txn_info_2.state_hash,
   434              base_contexts=txn_info_2.base_context_ids,
   435              inputs=inputs,
   436              outputs=outputs)
   437  
   438          # 5
   439          scheduler.unschedule_incomplete_batches()
   440          scheduler.finalize()
   441          scheduler.complete(block=True)
   442  
   443          # 6
   444          context_manager.get(ctx_id2, address_list=[create_address('A')])
   445  
   446      def test_serial_unschedule(self):
   447          context_manager, scheduler = self._setup_serial_scheduler()
   448  
   449          self._test_unschedule(
   450              scheduler=scheduler,
   451              context_manager=context_manager)
   452  
   453      def test_parallel_unschedule(self):
   454          context_manager, scheduler = self._setup_parallel_scheduler()
   455  
   456          self._test_unschedule(
   457              scheduler=scheduler,
   458              context_manager=context_manager)
   459  
   460      def _test_unschedule(self, scheduler, context_manager):
   461          """Tests that each of the top level methods on scheduler can be
   462          called after unscheduling.
   463  
   464          Notes:
   465  
   466          - The test creates two batches, the first containing one txn,
   467            the second containing two.
   468          - The first batch's txn is completed, and therefore should be
   469            included in the final result.
   470          - The second batch's first txn is marked as "in-flight", and therefore
   471            a response is expected.
   472          - With a cancel before receiving the result, setting the txn result
   473            should not cause any errors (it should be ignored).
   474          """
   475  
   476          private_key = self._context.new_random_private_key()
   477          signer = self._crypto_factory.new_signer(private_key)
   478  
   479          txn1a, _ = create_transaction(
   480              payload='A'.encode(),
   481              signer=signer,
   482              inputs=[create_address('A')[0:40]],
   483              outputs=[create_address('A')[0:40]])
   484  
   485          batch_1 = create_batch(
   486              [txn1a],
   487              signer=signer)
   488  
   489          txn2a, _ = create_transaction(
   490              payload='A'.encode(),
   491              signer=signer,
   492              inputs=[create_address('A')[0:40]])
   493  
   494          txn2B, _ = create_transaction(
   495              payload='B'.encode(),
   496              signer=signer,
   497              inputs=[create_address('B')[0:40]])
   498  
   499          batch_2 = create_batch(
   500              [txn2a, txn2B],
   501              signer=signer)
   502  
   503          # 1
   504          scheduler.add_batch(batch=batch_1)
   505          scheduler.add_batch(batch=batch_2)
   506  
   507          sched_iter = iter(scheduler)
   508  
   509          # 2
   510          txn_info1a = next(sched_iter)
   511          header = transaction_pb2.TransactionHeader()
   512          header.ParseFromString(txn_info1a.txn.header)
   513          inputs = list(header.inputs)
   514          outputs = list(header.outputs)
   515  
   516          ctx_id1a = context_manager.create_context(
   517              state_hash=txn_info1a.state_hash,
   518              inputs=inputs,
   519              outputs=outputs,
   520              base_contexts=txn_info1a.base_context_ids)
   521  
   522          # 3
   523          scheduler.set_transaction_execution_result(
   524              txn_info1a.txn.header_signature,
   525              False,
   526              ctx_id1a)
   527  
   528          # 4
   529          txn_info2a = next(sched_iter)
   530          header = transaction_pb2.TransactionHeader()
   531          header.ParseFromString(txn_info2a.txn.header)
   532          inputs = list(header.inputs)
   533          outputs = list(header.outputs)
   534  
   535          ctx_id2a = context_manager.create_context(
   536              state_hash=txn_info2a.state_hash,
   537              inputs=inputs,
   538              outputs=outputs,
   539              base_contexts=txn_info2a.base_context_ids)
   540  
   541          # 5
   542          scheduler.unschedule_incomplete_batches()
   543          scheduler.set_transaction_execution_result(
   544              txn_info2a.txn.header_signature,
   545              True,
   546              ctx_id2a)
   547  
   548      def test_serial_completion_on_finalize(self):
   549          """Tests that iteration will stop when finalized is called on an
   550          otherwise complete serial scheduler.
   551          """
   552  
   553          _, scheduler = self._setup_serial_scheduler()
   554          self._completion_on_finalize(scheduler)
   555  
   556      def test_parallel_completion_on_finalize(self):
   557          """Tests that iteration will stop when finalized is called on an
   558          otherwise complete parallel scheduler.
   559          """
   560  
   561          _, scheduler = self._setup_parallel_scheduler()
   562          self._completion_on_finalize(scheduler)
   563  
   564      def _completion_on_finalize(self, scheduler):
   565          """Tests that iteration will stop when finalized is called on an
   566          otherwise complete scheduler.
   567  
   568          Notes:
   569              Adds one batch and transaction, then verifies the iterable
   570              returns that transaction. Sets the execution result and
   571              then calls finalize. Since the the scheduler is complete
   572              (all transactions have had results set, and it's been
   573              finalized), we should get a StopIteration. This check is
   574              useful in making sure the finalize() can occur after all
   575              set_transaction_execution_result()s have been performed,
   576              because in a normal situation, finalize will probably
   577              occur prior to those calls.
   578  
   579          This test should work for both a serial and parallel scheduler.
   580  
   581          """
   582  
   583          private_key = self._context.new_random_private_key()
   584          signer = self._crypto_factory.new_signer(private_key)
   585  
   586          txn, _ = create_transaction(
   587              payload='a'.encode(),
   588              signer=signer)
   589  
   590          batch = create_batch(
   591              transactions=[txn],
   592              signer=signer)
   593  
   594          iterable = iter(scheduler)
   595  
   596          scheduler.add_batch(batch)
   597  
   598          scheduled_txn_info = next(iterable)
   599          self.assertIsNotNone(scheduled_txn_info)
   600          self.assertEqual(txn.payload, scheduled_txn_info.txn.payload)
   601          scheduler.set_transaction_execution_result(
   602              txn.header_signature, False, None)
   603  
   604          scheduler.finalize()
   605  
   606          with self.assertRaises(StopIteration):
   607              next(iterable)
   608  
   609      def test_serial_completion_on_finalize_only_when_done(self):
   610          """Tests that complete will only be true when the serial scheduler
   611          has had finalize called and all txns have execution result set.
   612          """
   613  
   614          _, scheduler = self._setup_serial_scheduler()
   615          self._completion_on_finalize_only_when_done(scheduler)
   616  
   617      def test_parallel_completion_on_finalize_only_when_done(self):
   618          """Tests that complete will only be true when the parallel scheduler
   619          has had finalize called and all txns have execution result set.
   620          """
   621  
   622          _, scheduler = self._setup_parallel_scheduler()
   623          self._completion_on_finalize_only_when_done(scheduler)
   624  
   625      def _completion_on_finalize_only_when_done(self, scheduler):
   626          """Tests that complete will only be true when the scheduler
   627          has had finalize called and all txns have execution result set.
   628  
   629          Notes:
   630              Adds one batch and transaction, then verifies the iterable returns
   631              that transaction.  Finalizes then sets the execution result. The
   632              schedule should not be marked as complete until after the
   633              execution result is set.
   634  
   635              This check is useful in making sure the finalize() can
   636              occur after all set_transaction_execution_result()s have
   637              been performed, because in a normal situation, finalize
   638              will probably occur prior to those calls.
   639  
   640          This test should work for both a serial and parallel scheduler.
   641  
   642          """
   643          private_key = self._context.new_random_private_key()
   644          signer = self._crypto_factory.new_signer(private_key)
   645  
   646          txn, _ = create_transaction(
   647              payload='a'.encode(),
   648              signer=signer)
   649  
   650          batch = create_batch(
   651              transactions=[txn],
   652              signer=signer)
   653  
   654          iterable = iter(scheduler)
   655  
   656          scheduler.add_batch(batch)
   657  
   658          scheduled_txn_info = next(iterable)
   659          self.assertIsNotNone(scheduled_txn_info)
   660          self.assertEqual(txn.payload, scheduled_txn_info.txn.payload)
   661          scheduler.finalize()
   662          self.assertFalse(scheduler.complete(block=False))
   663          scheduler.set_transaction_execution_result(
   664              txn.header_signature, False, None)
   665          self.assertTrue(scheduler.complete(block=False))
   666  
   667          with self.assertRaises(StopIteration):
   668              next(iterable)
   669  
   670      def test_serial_add_batch_after_empty_iteration(self):
   671          """Tests that iterations of the serial scheduler will continue
   672          as result of add_batch().
   673          """
   674  
   675          _, scheduler = self._setup_serial_scheduler()
   676          self._add_batch_after_empty_iteration(scheduler)
   677  
   678      def test_parallel_add_batch_after_empty_iteration(self):
   679          """Tests that iterations of the parallel scheduler will continue
   680          as result of add_batch().
   681          """
   682  
   683          _, scheduler = self._setup_parallel_scheduler()
   684          self._add_batch_after_empty_iteration(scheduler)
   685  
   686      def _add_batch_after_empty_iteration(self, scheduler):
   687          """Tests that iterations will continue as result of add_batch().
   688          This test calls next() on a scheduler iterator in a separate thread
   689          called the IteratorThread.  The test waits until the IteratorThread
   690          is waiting in next(); internal to the scheduler, it will be waiting on
   691          a condition variable as there are no transactions to return and the
   692          scheduler is not finalized.  Then, the test continues by running
   693          add_batch(), which should cause the next() running in the
   694          IterableThread to return a transaction.
   695          This demonstrates the scheduler's ability to wait on an empty iterator
   696          but continue as transactions become available via add_batch.
   697  
   698          This test should work for both a serial and parallel scheduler.
   699          """
   700          private_key = self._context.new_random_private_key()
   701          signer = self._crypto_factory.new_signer(private_key)
   702  
   703          # Create a basic transaction and batch.
   704          txn, _ = create_transaction(
   705              payload='a'.encode(),
   706              signer=signer)
   707          batch = create_batch(
   708              transactions=[txn],
   709              signer=signer)
   710  
   711          # This class is used to run the scheduler's iterator.
   712          class IteratorThread(threading.Thread):
   713              def __init__(self, iterable):
   714                  threading.Thread.__init__(self)
   715                  self._iterable = iterable
   716                  self.ready = False
   717                  self.condition = threading.Condition()
   718                  self.txn_info = None
   719  
   720              def run(self):
   721                  # Even with this lock here, there is a race condition between
   722                  # exit of the lock and entry into the iterable.  That is solved
   723                  # by sleep later in the test.
   724                  with self.condition:
   725                      self.ready = True
   726                      self.condition.notify()
   727                  txn_info = next(self._iterable)
   728                  with self.condition:
   729                      self.txn_info = txn_info
   730                      self.condition.notify()
   731  
   732          # This is the iterable we are testing, which we will use in the
   733          # IteratorThread.  We also use it in this thread below to test
   734          # for StopIteration.
   735          iterable = iter(scheduler)
   736  
   737          # Create and startup thread.
   738          thread = IteratorThread(iterable=iterable)
   739          thread.start()
   740  
   741          # Pause here to make sure the thread is absolutely as far along as
   742          # possible; in other words, right before we call next() in it's run()
   743          # method.  When this returns, there should be very little time until
   744          # the iterator is blocked on a condition variable.
   745          with thread.condition:
   746              while not thread.ready:
   747                  thread.condition.wait()
   748  
   749          # May the daemons stay away during this dark time, and may we be
   750          # forgiven upon our return.
   751          time.sleep(1)
   752  
   753          # At this point, the IteratorThread should be waiting next(), so we go
   754          # ahead and give it a batch.
   755          scheduler.add_batch(batch)
   756  
   757          # If all goes well, thread.txn_info will get set to the result of the
   758          # next() call.  If not, it will timeout and thread.txn_info will be
   759          # empty.
   760          with thread.condition:
   761              if thread.txn_info is None:
   762                  thread.condition.wait(5)
   763  
   764          # If thread.txn_info is empty, the test failed as iteration did not
   765          # continue after add_batch().
   766          self.assertIsNotNone(thread.txn_info, "iterable failed to return txn")
   767          self.assertEqual(txn.payload, thread.txn_info.txn.payload)
   768  
   769          # Continue with normal shutdown/cleanup.
   770          scheduler.finalize()
   771          scheduler.set_transaction_execution_result(
   772              txn.header_signature, False, None)
   773          with self.assertRaises(StopIteration):
   774              next(iterable)
   775  
   776      def test_serial_valid_batch_invalid_batch(self):
   777          """Tests the squash function. That the correct state hash is found
   778          at the end of valid and invalid batches, similar to block publishing.
   779          """
   780  
   781          context_manager, scheduler = self._setup_serial_scheduler()
   782          self._add_valid_batch_invalid_batch(scheduler, context_manager)
   783  
   784      def test_parallel_add_valid_batch_invalid_batch(self):
   785          """Tests the squash function. That the correct state hash is found
   786          at the end of valid and invalid batches, similar to block publishing.
   787          """
   788  
   789          context_manager, scheduler = self._setup_parallel_scheduler()
   790          self._add_valid_batch_invalid_batch(scheduler, context_manager)
   791  
   792      def _add_valid_batch_invalid_batch(self, scheduler, context_manager):
   793          """Tests the squash function. That the correct state hash is found
   794          at the end of valid and invalid batches, similar to block publishing.
   795  
   796           Basically:
   797              1. Adds two batches, one where all the txns are valid,
   798                 and one where one of the txns is invalid.
   799              2. Run through the scheduler executor interaction
   800                 as txns are processed.
   801              3. Verify that the state root obtained through the squash function
   802                 is the same as directly updating the merkle tree.
   803              4. Verify that correct batch statuses are set
   804  
   805          This test should work for both a serial and parallel scheduler.
   806          """
   807          private_key = self._context.new_random_private_key()
   808          signer = self._crypto_factory.new_signer(private_key)
   809  
   810          # 1)
   811          batch_signatures = []
   812          for names in [['a', 'b'], ['invalid', 'c'], ['d', 'e']]:
   813              batch_txns = []
   814              for name in names:
   815                  txn, _ = create_transaction(
   816                      payload=name.encode(),
   817                      signer=signer)
   818  
   819                  batch_txns.append(txn)
   820  
   821              batch = create_batch(
   822                  transactions=batch_txns,
   823                  signer=signer)
   824  
   825              batch_signatures.append(batch.header_signature)
   826              scheduler.add_batch(batch)
   827          scheduler.finalize()
   828          # 2)
   829          sched1 = iter(scheduler)
   830          invalid_payload = hashlib.sha512('invalid'.encode()).hexdigest()
   831          while not scheduler.complete(block=False):
   832              try:
   833                  txn_info = next(sched1)
   834              except StopIteration:
   835                  break
   836              txn_header = transaction_pb2.TransactionHeader()
   837              txn_header.ParseFromString(txn_info.txn.header)
   838              inputs_or_outputs = list(txn_header.inputs)
   839              c_id = context_manager.create_context(
   840                  state_hash=txn_info.state_hash,
   841                  inputs=inputs_or_outputs,
   842                  outputs=inputs_or_outputs,
   843                  base_contexts=txn_info.base_context_ids)
   844              if txn_header.payload_sha512 == invalid_payload:
   845                  scheduler.set_transaction_execution_result(
   846                      txn_info.txn.header_signature, False, None)
   847              else:
   848                  context_manager.set(c_id, [{inputs_or_outputs[0]: b"1"}])
   849                  scheduler.set_transaction_execution_result(
   850                      txn_info.txn.header_signature, True, c_id)
   851  
   852          sched2 = iter(scheduler)
   853          # 3)
   854          txn_info_a = next(sched2)
   855  
   856          address_a = _get_address_from_txn(txn_info_a)
   857  
   858          txn_info_b = next(sched2)
   859          address_b = _get_address_from_txn(txn_info_b)
   860  
   861          next(sched2)
   862  
   863          txn_info_d = next(sched2)
   864          address_d = _get_address_from_txn(txn_info_d)
   865  
   866          txn_info_e = next(sched2)
   867          address_e = _get_address_from_txn(txn_info_e)
   868  
   869          database = NativeLmdbDatabase(
   870              os.path.join(self._temp_dir,
   871                           '_add_valid_batch_invalid_batch.lmdb'),
   872              indexes=MerkleDatabase.create_index_configuration(),
   873              _size=10 * 1024 * 1024)
   874          merkle_database = MerkleDatabase(database)
   875          state_root_end = merkle_database.update(
   876              {address_a: b"1", address_b: b"1",
   877               address_d: b"1", address_e: b"1"},
   878              virtual=False)
   879  
   880          # 4)
   881          batch1_result = scheduler.get_batch_execution_result(
   882              batch_signatures[0])
   883          self.assertTrue(batch1_result.is_valid)
   884  
   885          batch2_result = scheduler.get_batch_execution_result(
   886              batch_signatures[1])
   887          self.assertFalse(batch2_result.is_valid)
   888  
   889          batch3_result = scheduler.get_batch_execution_result(
   890              batch_signatures[2])
   891          self.assertTrue(batch3_result.is_valid)
   892          self.assertEqual(batch3_result.state_hash, state_root_end)
   893  
   894      def test_serial_sequential_add_batch_after_all_results_set(self):
   895          """Tests that adding a new batch only after setting all of the
   896          txn results will produce only expected state roots.
   897          """
   898  
   899          context_manager, scheduler = self._setup_serial_scheduler()
   900          self._sequential_add_batch_after_all_results_set(
   901              scheduler=scheduler,
   902              context_manager=context_manager)
   903  
   904      def test_parallel_sequential_add_batch_after_all_results_set(self):
   905          """Tests that adding a new batch only after setting all of the
   906          txn results will produce only expected state roots.
   907          """
   908  
   909          context_manager, scheduler = self._setup_parallel_scheduler()
   910          self._sequential_add_batch_after_all_results_set(
   911              scheduler=scheduler,
   912              context_manager=context_manager)
   913  
   914      def _sequential_add_batch_after_all_results_set(self,
   915                                                      scheduler,
   916                                                      context_manager):
   917          """Tests that adding a new batch only after setting all of the
   918          txn results will produce only expected state roots. Here no state
   919          roots were specified, so similar to block publishing use of scheduler.
   920          Basically:
   921              1) Create 3 batches, the last being marked as having an invalid
   922                 transaction. Add one batch and then while the scheduler keeps
   923                 on returning transactions, set the txn result, and then
   924                 call next_transaction.
   925              2) Call finalize, and then assert that the scheduler is complete
   926              3) Assert that the first batch is valid and has no state hash,
   927                 the second batch is valid and since it is the last valid batch
   928                 in the scheduler has a state hash, and that the third batch
   929                 is invalid and consequently has no state hash.
   930          """
   931  
   932          private_key = self._context.new_random_private_key()
   933          signer = self._crypto_factory.new_signer(private_key)
   934  
   935          # 1)
   936          batch_signatures = []
   937          batches = []
   938          for names in [['a', 'b'], ['d', 'e'], ['invalid', 'c']]:
   939              batch_txns = []
   940              for name in names:
   941                  txn, _ = create_transaction(
   942                      payload=name.encode(),
   943                      signer=signer)
   944  
   945                  batch_txns.append(txn)
   946  
   947              batch = create_batch(
   948                  transactions=batch_txns,
   949                  signer=signer)
   950              batches.append(batch)
   951              batch_signatures.append(batch.header_signature)
   952          invalid_payload_sha = hashlib.sha512(
   953              'invalid'.encode()).hexdigest()
   954          for batch in batches:
   955              scheduler.add_batch(batch=batch)
   956              txn_info = scheduler.next_transaction()
   957              while txn_info is not None:
   958                  txn_header = transaction_pb2.TransactionHeader()
   959                  txn_header.ParseFromString(txn_info.txn.header)
   960                  inputs_outputs = list(txn_header.inputs)
   961                  c_id = context_manager.create_context(
   962                      state_hash=context_manager.get_first_root(),
   963                      base_contexts=txn_info.base_context_ids,
   964                      inputs=list(txn_header.inputs),
   965                      outputs=list(txn_header.outputs))
   966                  context_manager.set(
   967                      context_id=c_id,
   968                      address_value_list=[{inputs_outputs[0]: b'5'}])
   969                  if txn_header.payload_sha512 == invalid_payload_sha:
   970                      scheduler.set_transaction_execution_result(
   971                          txn_info.txn.header_signature,
   972                          is_valid=False,
   973                          context_id=None)
   974                  else:
   975                      scheduler.set_transaction_execution_result(
   976                          txn_info.txn.header_signature,
   977                          is_valid=True,
   978                          context_id=c_id)
   979                  txn_info = scheduler.next_transaction()
   980  
   981          # 2)
   982          scheduler.finalize()
   983          self.assertTrue(scheduler.complete(block=False),
   984                          "The scheduler has had all txn results set so after "
   985                          " calling finalize the scheduler is complete")
   986          # 3)
   987          first_batch_id = batch_signatures.pop(0)
   988          result1 = scheduler.get_batch_execution_result(first_batch_id)
   989          self.assertEqual(
   990              result1.is_valid,
   991              True,
   992              "The first batch is valid")
   993          self.assertIsNone(result1.state_hash,
   994                            "The first batch doesn't produce"
   995                            " a state hash")
   996          second_batch_id = batch_signatures.pop(0)
   997          result2 = scheduler.get_batch_execution_result(second_batch_id)
   998          self.assertEqual(
   999              result2.is_valid,
  1000              True,
  1001              "The second batch is valid")
  1002          self.assertIsNotNone(result2.state_hash, "The second batch is the "
  1003                                                   "last valid batch in the "
  1004                                                   "scheduler")
  1005  
  1006          third_batch_id = batch_signatures.pop(0)
  1007          result3 = scheduler.get_batch_execution_result(third_batch_id)
  1008          self.assertEqual(result3.is_valid, False)
  1009          self.assertIsNone(result3.state_hash,
  1010                            "The last batch is invalid so "
  1011                            "doesn't have a state hash")
  1012  
  1013  
  1014  class TestSerialScheduler(unittest.TestCase):
  1015      def __init__(self, test_name):
  1016          super().__init__(test_name)
  1017          self._temp_dir = None
  1018  
  1019      def setUp(self):
  1020          self._temp_dir = tempfile.mkdtemp()
  1021  
  1022          database = NativeLmdbDatabase(
  1023              os.path.join(self._temp_dir, 'test_serial_schedulers.lmdb'),
  1024              indexes=MerkleDatabase.create_index_configuration(),
  1025              _size=10 * 1024 * 1024)
  1026  
  1027          self.context_manager = ContextManager(database)
  1028          squash_handler = self.context_manager.get_squash_handler()
  1029          self.first_state_root = self.context_manager.get_first_root()
  1030          self.scheduler = SerialScheduler(squash_handler,
  1031                                           self.first_state_root,
  1032                                           always_persist=False)
  1033  
  1034          self._context = create_context('secp256k1')
  1035          self._crypto_factory = CryptoFactory(self._context)
  1036  
  1037      def tearDown(self):
  1038          self.context_manager.stop()
  1039          shutil.rmtree(self._temp_dir)
  1040  
  1041      def test_transaction_order(self):
  1042          """Tests the that transactions are returned in order added.
  1043  
  1044          Adds three batches with varying number of transactions, then tests
  1045          that they are returned in the appropriate order when using an iterator.
  1046  
  1047          This test also creates a second iterator and verifies that both
  1048          iterators return the same transactions.
  1049  
  1050          This test also finalizes the scheduler and verifies that StopIteration
  1051          is thrown by the iterator.
  1052          """
  1053          private_key = self._context.new_random_private_key()
  1054          signer = self._crypto_factory.new_signer(private_key)
  1055  
  1056          txns = []
  1057  
  1058          for names in [['a', 'b', 'c'], ['d', 'e'], ['f', 'g', 'h', 'i']]:
  1059              batch_txns = []
  1060              for name in names:
  1061                  txn, _ = create_transaction(
  1062                      payload=name.encode(),
  1063                      signer=signer)
  1064  
  1065                  batch_txns.append(txn)
  1066                  txns.append(txn)
  1067  
  1068              batch = create_batch(
  1069                  transactions=batch_txns,
  1070                  signer=signer)
  1071  
  1072              self.scheduler.add_batch(batch)
  1073  
  1074          self.scheduler.finalize()
  1075  
  1076          iterable1 = iter(self.scheduler)
  1077          iterable2 = iter(self.scheduler)
  1078          for txn in txns:
  1079              scheduled_txn_info = next(iterable1)
  1080              self.assertEqual(scheduled_txn_info, next(iterable2))
  1081              self.assertIsNotNone(scheduled_txn_info)
  1082              self.assertEqual(txn.payload, scheduled_txn_info.txn.payload)
  1083              c_id = self.context_manager.create_context(
  1084                  self.first_state_root,
  1085                  base_contexts=scheduled_txn_info.base_context_ids,
  1086                  inputs=[],
  1087                  outputs=[])
  1088              self.scheduler.set_transaction_execution_result(
  1089                  txn.header_signature, True, c_id)
  1090  
  1091          with self.assertRaises(StopIteration):
  1092              next(iterable1)
  1093  
  1094      def test_completion_on_last_result(self):
  1095          """Tests the that the schedule is not marked complete until the last
  1096          result is set.
  1097  
  1098          Adds three batches with varying number of transactions, then tests
  1099          that they are returned in the appropriate order when using an iterator.
  1100          Test that the value of `complete` is false until the last value.
  1101  
  1102          This test also finalizes the scheduler and verifies that StopIteration
  1103          is thrown by the iterator, and the complete is true in the at the end.
  1104          """
  1105          private_key = self._context.new_random_private_key()
  1106          signer = self._crypto_factory.new_signer(private_key)
  1107  
  1108          txns = []
  1109  
  1110          for names in [['a', 'b', 'c'], ['d', 'e'], ['f', 'g', 'h', 'i']]:
  1111              batch_txns = []
  1112              for name in names:
  1113                  txn, _ = create_transaction(
  1114                      payload=name.encode(),
  1115                      signer=signer)
  1116  
  1117                  batch_txns.append(txn)
  1118                  txns.append(txn)
  1119  
  1120              batch = create_batch(
  1121                  transactions=batch_txns,
  1122                  signer=signer)
  1123  
  1124              self.scheduler.add_batch(batch)
  1125  
  1126          self.scheduler.finalize()
  1127  
  1128          iterable1 = iter(self.scheduler)
  1129          for txn in txns:
  1130              scheduled_txn_info = next(iterable1)
  1131              self.assertFalse(self.scheduler.complete(block=False))
  1132  
  1133              c_id = self.context_manager.create_context(
  1134                  self.first_state_root,
  1135                  base_contexts=scheduled_txn_info.base_context_ids,
  1136                  inputs=[],
  1137                  outputs=[])
  1138              self.scheduler.set_transaction_execution_result(
  1139                  txn.header_signature, True, c_id)
  1140  
  1141          self.assertTrue(self.scheduler.complete(block=False))
  1142  
  1143          with self.assertRaises(StopIteration):
  1144              next(iterable1)
  1145  
  1146      def test_set_status(self):
  1147          """Tests that set_status() has the correct behavior.
  1148  
  1149          Basically:
  1150              1. Adds a batch which has two transactions.
  1151              2. Calls next_transaction() to get the first Transaction.
  1152              3. Calls next_transaction() to verify that it returns None.
  1153              4. Calls set_status() to mark the first transaction applied.
  1154              5. Calls next_transaction() to  get the second Transaction.
  1155  
  1156          Step 3 returns None because the first transaction hasn't been marked
  1157          as applied, and the SerialScheduler will only return one
  1158          not-applied Transaction at a time.
  1159  
  1160          Step 5 is expected to return the second Transaction, not None,
  1161          since the first Transaction was marked as applied in the previous
  1162          step.
  1163          """
  1164          private_key = self._context.new_random_private_key()
  1165          signer = self._crypto_factory.new_signer(private_key)
  1166  
  1167          txns = []
  1168  
  1169          for name in ['a', 'b']:
  1170              txn, _ = create_transaction(
  1171                  payload=name.encode(),
  1172                  signer=signer)
  1173  
  1174              txns.append(txn)
  1175  
  1176          batch = create_batch(
  1177              transactions=txns,
  1178              signer=signer)
  1179  
  1180          self.scheduler.add_batch(batch)
  1181  
  1182          scheduled_txn_info = self.scheduler.next_transaction()
  1183          self.assertIsNotNone(scheduled_txn_info)
  1184          self.assertEqual('a', scheduled_txn_info.txn.payload.decode())
  1185  
  1186          self.assertIsNone(self.scheduler.next_transaction())
  1187          c_id = self.context_manager.create_context(
  1188              self.first_state_root,
  1189              base_contexts=scheduled_txn_info.base_context_ids,
  1190              inputs=[],
  1191              outputs=[])
  1192  
  1193          self.scheduler.set_transaction_execution_result(
  1194              scheduled_txn_info.txn.header_signature,
  1195              is_valid=True,
  1196              context_id=c_id)
  1197  
  1198          scheduled_txn_info = self.scheduler.next_transaction()
  1199          self.assertIsNotNone(scheduled_txn_info)
  1200          self.assertEqual('b', scheduled_txn_info.txn.payload.decode())
  1201  
  1202      def test_unschedule_incomplete_transactions(self):
  1203          """Tests that unschedule_incomplete_batches will remove
  1204          batches above the mimimum.
  1205  
  1206          Given a schedule with two batches, ensure that a call to
  1207          unschedule_incomplete_batches will leave one batch in the schedule.
  1208          """
  1209          private_key = self._context.new_random_private_key()
  1210          signer = self._crypto_factory.new_signer(private_key)
  1211  
  1212          txn_a, _ = create_transaction(
  1213              payload='A'.encode(),
  1214              signer=signer)
  1215  
  1216          txn_b, _ = create_transaction(
  1217              payload='B'.encode(),
  1218              signer=signer)
  1219  
  1220          batch_1 = create_batch(transactions=[txn_a],
  1221                                 signer=signer)
  1222          batch_2 = create_batch(transactions=[txn_b],
  1223                                 signer=signer)
  1224  
  1225          self.scheduler.add_batch(batch_1)
  1226          self.scheduler.add_batch(batch_2)
  1227  
  1228          self.scheduler.unschedule_incomplete_batches()
  1229          self.scheduler.finalize()
  1230          self.assertFalse(self.scheduler.complete(block=False))
  1231  
  1232          scheduled_txn_info = self.scheduler.next_transaction()
  1233          self.assertIsNotNone(scheduled_txn_info)
  1234          self.assertEqual('A', scheduled_txn_info.txn.payload.decode())
  1235  
  1236          c_id = self.context_manager.create_context(
  1237              self.first_state_root,
  1238              base_contexts=scheduled_txn_info.base_context_ids,
  1239              inputs=[],
  1240              outputs=[])
  1241  
  1242          self.scheduler.set_transaction_execution_result(
  1243              scheduled_txn_info.txn.header_signature,
  1244              is_valid=True,
  1245              context_id=c_id)
  1246  
  1247          with self.assertRaises(StopIteration):
  1248              scheduled_txn_info = self.scheduler.next_transaction()
  1249  
  1250  
  1251  class TestParallelScheduler(unittest.TestCase):
  1252      def __init__(self, test_name):
  1253          super().__init__(test_name)
  1254          self._temp_dir = None
  1255  
  1256      def setUp(self):
  1257          self._temp_dir = tempfile.mkdtemp()
  1258  
  1259          database = NativeLmdbDatabase(
  1260              os.path.join(self._temp_dir, 'test_serial_schedulers.lmdb'),
  1261              indexes=MerkleDatabase.create_index_configuration(),
  1262              _size=10 * 1024 * 1024)
  1263  
  1264          self.context_manager = ContextManager(database)
  1265          squash_handler = self.context_manager.get_squash_handler()
  1266          self.first_state_root = self.context_manager.get_first_root()
  1267          self.scheduler = ParallelScheduler(squash_handler,
  1268                                             self.first_state_root,
  1269                                             always_persist=False)
  1270  
  1271          self._context = create_context('secp256k1')
  1272          self._crypto_factory = CryptoFactory(self._context)
  1273  
  1274      def tearDown(self):
  1275          self.context_manager.stop()
  1276          shutil.rmtree(self._temp_dir)
  1277  
  1278      def test_add_to_finalized_scheduler(self):
  1279          """Tests that a finalized scheduler raise exception on add_batch().
  1280  
  1281          This test creates a scheduler, finalizes it, and calls add_batch().
  1282          The result is expected to be a SchedulerError, since adding a batch
  1283          to a finalized scheduler is invalid.
  1284          """
  1285          private_key = self._context.new_random_private_key()
  1286          signer = self._crypto_factory.new_signer(private_key)
  1287  
  1288          # Finalize prior to attempting to add a batch.
  1289          self.scheduler.finalize()
  1290  
  1291          txn, _ = create_transaction(
  1292              payload='a'.encode(),
  1293              signer=signer)
  1294  
  1295          batch = create_batch(
  1296              transactions=[txn],
  1297              signer=signer)
  1298  
  1299          # scheduler.add_batch(batch) should throw a SchedulerError due to
  1300          # the finalized status of the scheduler.
  1301          self.assertRaises(
  1302              SchedulerError, lambda: self.scheduler.add_batch(batch))
  1303  
  1304      def test_set_result_on_unscheduled_txn(self):
  1305          """Tests that a scheduler will reject a result on an unscheduled
  1306          transaction.
  1307  
  1308          Creates a batch with a single transaction, adds the batch to the
  1309          scheduler, then immediately attempts to set the result for the
  1310          transaction without first causing it to be scheduled (by using an
  1311          iterator or calling next_transaction()).
  1312          """
  1313          private_key = self._context.new_random_private_key()
  1314          signer = self._crypto_factory.new_signer(private_key)
  1315  
  1316          txn, _ = create_transaction(
  1317              payload='a'.encode(),
  1318              signer=signer)
  1319  
  1320          batch = create_batch(
  1321              transactions=[txn],
  1322              signer=signer)
  1323  
  1324          self.scheduler.add_batch(batch)
  1325  
  1326          self.assertRaises(
  1327              SchedulerError,
  1328              lambda: self.scheduler.set_transaction_execution_result(
  1329                  txn.header_signature, False, None))
  1330  
  1331      def test_transaction_order(self):
  1332          """Tests the that transactions are returned in order added.
  1333  
  1334          Adds three batches with varying number of transactions, then tests
  1335          that they are returned in the appropriate order when using an iterator.
  1336  
  1337          This test also creates a second iterator and verifies that both
  1338          iterators return the same transactions.
  1339  
  1340          This test also finalizes the scheduler and verifies that StopIteration
  1341          is thrown by the iterator.
  1342          """
  1343          private_key = self._context.new_random_private_key()
  1344          signer = self._crypto_factory.new_signer(private_key)
  1345  
  1346          txns = []
  1347  
  1348          for names in [['a', 'b', 'c'], ['d', 'e'], ['f', 'g', 'h', 'i']]:
  1349              batch_txns = []
  1350              for name in names:
  1351                  txn, _ = create_transaction(
  1352                      payload=name.encode(),
  1353                      signer=signer)
  1354  
  1355                  batch_txns.append(txn)
  1356                  txns.append(txn)
  1357  
  1358              batch = create_batch(
  1359                  transactions=batch_txns,
  1360                  signer=signer)
  1361  
  1362              self.scheduler.add_batch(batch)
  1363  
  1364          iterable1 = iter(self.scheduler)
  1365          iterable2 = iter(self.scheduler)
  1366          for txn in txns:
  1367              scheduled_txn_info = next(iterable1)
  1368              self.assertEqual(scheduled_txn_info, next(iterable2))
  1369              self.assertIsNotNone(scheduled_txn_info)
  1370              self.assertEqual(txn.payload, scheduled_txn_info.txn.payload)
  1371              c_id = self.context_manager.create_context(
  1372                  self.first_state_root,
  1373                  base_contexts=scheduled_txn_info.base_context_ids,
  1374                  inputs=[],
  1375                  outputs=[])
  1376              self.scheduler.set_transaction_execution_result(
  1377                  txn.header_signature, True, c_id)
  1378  
  1379          self.scheduler.finalize()
  1380          self.assertTrue(self.scheduler.complete(block=False))
  1381          with self.assertRaises(StopIteration):
  1382              next(iterable1)
  1383  
  1384      def test_transaction_order_with_dependencies(self):
  1385          """Tests the that transactions are returned in the expected order given
  1386          dependencies implied by state.
  1387  
  1388          Creates one batch with four transactions.
  1389          """
  1390          private_key = self._context.new_random_private_key()
  1391          signer = self._crypto_factory.new_signer(private_key)
  1392  
  1393          txns = []
  1394          headers = []
  1395  
  1396          txn, header = create_transaction(
  1397              payload='a'.encode(),
  1398              signer=signer)
  1399          txns.append(txn)
  1400          headers.append(header)
  1401  
  1402          txn, header = create_transaction(
  1403              payload='b'.encode(),
  1404              signer=signer)
  1405          txns.append(txn)
  1406          headers.append(header)
  1407  
  1408          txn, header = create_transaction(
  1409              payload='aa'.encode(),
  1410              signer=signer,
  1411              inputs=['000000' + hashlib.sha512('a'.encode()).hexdigest()[:64]],
  1412              outputs=['000000' + hashlib.sha512('a'.encode()).hexdigest()[:64]])
  1413          txns.append(txn)
  1414          headers.append(header)
  1415  
  1416          txn, header = create_transaction(
  1417              payload='bb'.encode(),
  1418              signer=signer,
  1419              inputs=['000000' + hashlib.sha512('b'.encode()).hexdigest()[:64]],
  1420              outputs=['000000' + hashlib.sha512('b'.encode()).hexdigest()[:64]])
  1421          txns.append(txn)
  1422          headers.append(header)
  1423  
  1424          batch = create_batch(
  1425              transactions=txns,
  1426              signer=signer)
  1427  
  1428          self.scheduler.add_batch(batch)
  1429          self.scheduler.finalize()
  1430          self.assertFalse(self.scheduler.complete(block=False))
  1431  
  1432          iterable = iter(self.scheduler)
  1433          scheduled_txn_info = []
  1434  
  1435          self.assertEqual(2, self.scheduler.available())
  1436          scheduled_txn_info.append(next(iterable))
  1437          self.assertIsNotNone(scheduled_txn_info[0])
  1438          self.assertEqual(txns[0].payload, scheduled_txn_info[0].txn.payload)
  1439          self.assertFalse(self.scheduler.complete(block=False))
  1440  
  1441          self.assertEqual(1, self.scheduler.available())
  1442          scheduled_txn_info.append(next(iterable))
  1443          self.assertIsNotNone(scheduled_txn_info[1])
  1444          self.assertEqual(txns[1].payload, scheduled_txn_info[1].txn.payload)
  1445          self.assertFalse(self.scheduler.complete(block=False))
  1446  
  1447          self.assertEqual(0, self.scheduler.available())
  1448          context_id1 = self.context_manager.create_context(
  1449              state_hash=self.first_state_root,
  1450              inputs=list(headers[1].inputs),
  1451              outputs=list(headers[1].outputs),
  1452              base_contexts=[])
  1453          self.scheduler.set_transaction_execution_result(
  1454              txns[1].header_signature, True, context_id1)
  1455  
  1456          self.assertEqual(1, self.scheduler.available())
  1457          scheduled_txn_info.append(next(iterable))
  1458          self.assertIsNotNone(scheduled_txn_info[2])
  1459          self.assertEqual(txns[3].payload, scheduled_txn_info[2].txn.payload)
  1460          self.assertFalse(self.scheduler.complete(block=False))
  1461  
  1462          self.assertEqual(0, self.scheduler.available())
  1463          context_id2 = self.context_manager.create_context(
  1464              state_hash=self.first_state_root,
  1465              inputs=list(headers[0].inputs),
  1466              outputs=list(headers[0].outputs),
  1467              base_contexts=[context_id1])
  1468          self.scheduler.set_transaction_execution_result(
  1469              txns[0].header_signature, True, context_id2)
  1470  
  1471          self.assertEqual(1, self.scheduler.available())
  1472          scheduled_txn_info.append(next(iterable))
  1473          self.assertIsNotNone(scheduled_txn_info[3])
  1474          self.assertEqual(txns[2].payload, scheduled_txn_info[3].txn.payload)
  1475          self.assertFalse(self.scheduler.complete(block=False))
  1476  
  1477          self.assertEqual(0, self.scheduler.available())
  1478          context_id3 = self.context_manager.create_context(
  1479              state_hash=self.first_state_root,
  1480              inputs=list(headers[2].inputs),
  1481              outputs=list(headers[2].outputs),
  1482              base_contexts=[context_id2])
  1483          self.scheduler.set_transaction_execution_result(
  1484              txns[2].header_signature, True, context_id3)
  1485          context_id4 = self.context_manager.create_context(
  1486              state_hash=self.first_state_root,
  1487              inputs=list(headers[3].inputs),
  1488              outputs=list(headers[3].outputs),
  1489              base_contexts=[context_id3])
  1490          self.scheduler.set_transaction_execution_result(
  1491              txns[3].header_signature, True, context_id4)
  1492  
  1493          self.assertEqual(0, self.scheduler.available())
  1494          self.assertTrue(self.scheduler.complete(block=False))
  1495          with self.assertRaises(StopIteration):
  1496              next(iterable)
  1497  
  1498          result = self.scheduler.get_batch_execution_result(
  1499              batch.header_signature)
  1500          self.assertIsNotNone(result)
  1501          self.assertTrue(result.is_valid)
  1502  
  1503      def test_unschedule_incomplete_transactions(self):
  1504          """Tests that unschedule_incomplete_batches will remove
  1505          batches above the mimimum.
  1506  
  1507          Given a schedule with two batches, ensure that a call to
  1508          unschedule_incomplete_batches will leave one batch in the schedule.
  1509          """
  1510          private_key = self._context.new_random_private_key()
  1511          signer = self._crypto_factory.new_signer(private_key)
  1512  
  1513          txn_a, _ = create_transaction(
  1514              payload='A'.encode(),
  1515              signer=signer)
  1516  
  1517          txn_b, _ = create_transaction(
  1518              payload='B'.encode(),
  1519              signer=signer)
  1520  
  1521          batch_1 = create_batch(transactions=[txn_a],
  1522                                 signer=signer)
  1523          batch_2 = create_batch(transactions=[txn_b],
  1524                                 signer=signer)
  1525  
  1526          self.scheduler.add_batch(batch_1)
  1527          self.scheduler.add_batch(batch_2)
  1528  
  1529          self.scheduler.unschedule_incomplete_batches()
  1530          self.scheduler.finalize()
  1531          self.assertFalse(self.scheduler.complete(block=False))
  1532  
  1533          scheduled_txn_info = self.scheduler.next_transaction()
  1534          self.assertIsNotNone(scheduled_txn_info)
  1535          self.assertEqual('A', scheduled_txn_info.txn.payload.decode())
  1536  
  1537          c_id = self.context_manager.create_context(
  1538              self.first_state_root,
  1539              base_contexts=scheduled_txn_info.base_context_ids,
  1540              inputs=[],
  1541              outputs=[])
  1542  
  1543          self.scheduler.set_transaction_execution_result(
  1544              scheduled_txn_info.txn.header_signature,
  1545              is_valid=True,
  1546              context_id=c_id)
  1547  
  1548          scheduled_txn_info = self.scheduler.next_transaction()
  1549          self.assertIsNone(scheduled_txn_info)