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)