コード例 #1
0
    def test_update_prep_grades_on_main_prep_unregistration(self):
        context = Mock()

        old_term = self.term
        old_preps = self.preps
        new_term = old_term.copy()
        new_preps = old_preps.copy(mutable=True)

        # Unregister 0-index main P-Rep
        prep = new_preps.get_by_index(0)
        assert prep.grade == PRepGrade.MAIN
        assert prep.address in old_term

        dirty_prep = prep.copy()
        dirty_prep.status = PRepStatus.UNREGISTERED
        new_preps.replace(dirty_prep)
        assert new_preps.is_dirty()

        assert old_preps.get_by_index(0) == prep
        assert new_preps.get_by_index(0) != prep

        # Replace main P-Rep0 with sub P-Rep0
        new_term.update_invalid_elected_preps([dirty_prep])
        PRepEngine._update_prep_grades(context, new_preps, old_term, new_term)
        assert len(new_term.main_preps) == self.main_prep_count
        assert len(new_term.sub_preps) == self.sub_prep_count - 1
        assert len(context.storage.prep.put_prep.mock_calls) == 2

        _check_prep_grades(new_preps, self.main_prep_count, len(new_term))
        _check_prep_grades2(new_preps, new_term)
        assert old_term.sub_preps[0] == new_term.main_preps[0]
コード例 #2
0
    def test_handle_get_prep_term_with_electable_preps(self):
        block_height = 100
        params = {}
        term = self.term

        context = Mock()
        context.block.height = block_height

        engine = PRepEngine()
        engine.term = term
        engine.preps = self.preps

        ret: dict = engine.handle_get_prep_term(context)

        assert ret["blockHeight"] == block_height
        assert ret["sequence"] == term.sequence
        assert ret["startBlockHeight"] == term.start_block_height
        assert ret["endBlockHeight"] == term.end_block_height
        assert ret["totalSupply"] == term.total_supply
        assert ret["totalDelegated"] == term.total_delegated
        assert ret["irep"] == term.irep

        # Main P-Reps: 22, Sub P-Reps: 78
        prep_list: List[Dict[str, Union[int, str, 'Address']]] = ret["preps"]
        assert len(prep_list) == PREP_MAIN_AND_SUB_PREPS

        for i, prep_snapshot in enumerate(term.preps):
            prep_item: Dict[str, Union[int, str, 'Address']] = prep_list[i]
            assert prep_item["address"] == prep_snapshot.address
コード例 #3
0
    def test_update_prep_grades_on_sub_prep_unregistration(self):
        context = Mock()

        old_term = self.term
        old_preps = self.preps
        new_term = old_term.copy()
        new_preps = old_preps.copy(mutable=True)

        # Unregister a sub P-Rep
        index = len(old_term.main_preps)
        prep = new_preps.get_by_index(index)
        assert prep.grade == PRepGrade.SUB
        assert prep.address in old_term

        dirty_prep = prep.copy()
        dirty_prep.status = PRepStatus.UNREGISTERED
        new_preps.replace(dirty_prep)
        assert new_preps.is_dirty()

        assert old_preps.get_by_index(index) == prep
        assert new_preps.get_by_index(index) != prep

        new_term.update_invalid_elected_preps([dirty_prep])
        PRepEngine._update_prep_grades(context, new_preps, old_term, new_term)
        _check_prep_grades(new_preps, len(new_term.main_preps), len(new_term))
        assert len(new_term.main_preps) == self.main_prep_count
        assert len(new_term.sub_preps) == self.sub_prep_count - 1
        assert len(context.storage.prep.put_prep.mock_calls) == 1

        for old_snapshot, new_snapshot in zip(old_term.main_preps, new_term.main_preps):
            assert old_snapshot == new_snapshot
        for i, new_snapshot in enumerate(new_term.sub_preps):
            assert new_snapshot == old_term.sub_preps[i + 1]
コード例 #4
0
def context(settable_inv_container: INVContainer):
    prep_engine = PRepEngine()
    prep_engine.prep_address_converter = mock.Mock()
    inv_engine = INVEngine()
    settable_inv_container.set_inv(StepPrice(10**10))
    settable_inv_container.set_inv(StepCosts(STEP_COSTS))
    settable_inv_container.set_inv(
        MaxStepLimits({IconScoreContextType.INVOKE: 2_500_000_000}))
    settable_inv_container.set_inv(RevisionCode(Revision.THREE.value))
    inv_engine._inv_container = settable_inv_container

    IconScoreContext.engine = ContextEngine(prep=prep_engine, inv=inv_engine)
    context_factory = IconScoreContextFactory()

    block = Block(block_height=1,
                  block_hash=b"1" * 40,
                  prev_hash=b"0" * 40,
                  timestamp=0)
    context = context_factory.create(IconScoreContextType.INVOKE, block)

    step_limit = 1_000_000_000
    context.set_step_counter(step_limit)

    ContextContainer._push_context(context)
    yield context
    ContextContainer._pop_context()
コード例 #5
0
    def test_handle_get_inactive_preps(self):
        expected_block_height = 1234

        context = Mock()
        context.block.height = expected_block_height

        old_term = self.term
        old_preps = self.preps
        new_term = old_term.copy()
        new_preps = old_preps.copy(mutable=True)
        expected_preps = []
        expected_total_delegated = 0

        cases = (
            (PRepStatus.UNREGISTERED, PenaltyReason.NONE),
            (PRepStatus.UNREGISTERED, PenaltyReason.BLOCK_VALIDATION),
            (PRepStatus.DISQUALIFIED, PenaltyReason.PREP_DISQUALIFICATION),
            (PRepStatus.DISQUALIFIED, PenaltyReason.LOW_PRODUCTIVITY),
            (PRepStatus.ACTIVE, PenaltyReason.BLOCK_VALIDATION),
            (PRepStatus.ACTIVE, PenaltyReason.NONE)
        )

        s_data = random.sample([i for i in range(len(new_term.main_preps))], len(cases))
        for index, case in enumerate(cases):
            prep = new_preps.get_by_index(s_data[index])

            dirty_prep = prep.copy()
            dirty_prep.status = case[0]
            dirty_prep.penalty = case[1]
            new_preps.replace(dirty_prep)
            assert new_preps.is_dirty()
            assert old_preps.get_by_address(prep.address) == prep
            assert new_preps.get_by_address(prep.address) != prep
            assert new_preps.get_by_address(prep.address) == dirty_prep

            new_term.update_invalid_elected_preps([dirty_prep])

            if dirty_prep.status != PRepStatus.ACTIVE:
                expected_preps.append(dirty_prep)
                expected_total_delegated += dirty_prep.delegated

        expected_preps = sorted(expected_preps, key=lambda node: node.order())

        engine = PRepEngine()
        engine.term = new_term
        engine.preps = new_preps

        response: dict = engine.handle_get_inactive_preps(context)
        inactive_preps: list = response["preps"]
        for i, prep in enumerate(expected_preps):
            expected_prep_data: dict = prep.to_dict(PRepDictType.FULL)
            assert expected_prep_data == inactive_preps[i]

        assert len(expected_preps) == len(inactive_preps)
        assert expected_block_height == response["blockHeight"]
        assert expected_total_delegated == response["totalDelegated"]
コード例 #6
0
    def test_update_prep_grades_on_disqualification(self):
        context = Mock()

        states = [
            PRepStatus.DISQUALIFIED, PRepStatus.DISQUALIFIED, PRepStatus.ACTIVE
        ]
        penalties = [
            PenaltyReason.PREP_DISQUALIFICATION,
            PenaltyReason.LOW_PRODUCTIVITY, PenaltyReason.BLOCK_VALIDATION
        ]

        for i in range(len(states)):
            old_term = self.term
            old_preps = self.preps
            new_term = old_term.copy()
            new_preps = old_preps.copy(mutable=True)

            # Disqualify a main P-PRep
            index = random.randint(0, len(old_term.main_preps) - 1)
            prep = new_preps.get_by_index(index)
            assert prep.grade == PRepGrade.MAIN
            assert prep.address in old_term

            dirty_prep = prep.copy()
            dirty_prep.status = states[i]
            dirty_prep.penalty = penalties[i]
            new_preps.replace(dirty_prep)
            assert new_preps.is_dirty()

            assert old_preps.get_by_index(index) == prep
            assert new_preps.get_by_index(index) != prep

            new_term.update_invalid_elected_preps([dirty_prep])
            PRepEngine._update_prep_grades(context, new_preps, old_term,
                                           new_term)
            if penalties[i] != PenaltyReason.BLOCK_VALIDATION:
                _check_prep_grades(new_preps, len(new_term.main_preps),
                                   len(new_term))
            _check_prep_grades2(new_preps, new_term)
            assert len(new_term.main_preps) == self.main_prep_count
            assert len(new_term.sub_preps) == self.sub_prep_count - 1

            j = 0
            for old_snapshot, new_snapshot in zip(old_term.main_preps,
                                                  new_term.main_preps):
                if j == index:
                    old_snapshot = old_term.sub_preps[0]

                assert old_snapshot == new_snapshot
                j += 1

            for j, new_snapshot in enumerate(new_term.sub_preps):
                assert new_snapshot == old_term.sub_preps[j + 1]

        assert len(context.storage.prep.put_prep.mock_calls) == len(states) * 2
コード例 #7
0
def _patch_service_engine(icon_service_engine, revision):
    # Mocks get_balance so, it returns always 100 icx
    # TODO : patch when use get_balance or transfer
    IconScoreContext.engine = ContextEngine(deploy=DeployEngine(),
                                            fee=FeeEngine(),
                                            icx=IcxEngine(),
                                            iiss=IISSEngine(),
                                            prep=PRepEngine(),
                                            issue=IssueEngine(),
                                            inv=INVEngine())

    db = icon_service_engine._icx_context_db
    IconScoreContext.storage = ContextStorage(deploy=DeployStorage(db),
                                              fee=FeeStorage(db),
                                              icx=IcxStorage(db),
                                              iiss=IISSStorage(db),
                                              prep=PRepStorage(db),
                                              issue=IssueStorage(db),
                                              meta=MetaDBStorage(db),
                                              rc=RewardCalcStorage(),
                                              inv=INVStorage(db))
    IconScoreContext.engine.inv._inv_container = generate_inv_container(
        False, revision)

    return icon_service_engine
コード例 #8
0
def _patch_service_engine(icon_service_engine, revision):
    # Mocks get_balance so, it returns always 100 icx
    # TODO : patch when use get_balance or transfer
    IconScoreContext.engine = ContextEngine(
        deploy=DeployEngine(),
        fee=FeeEngine(),
        icx=IcxEngine(),
        iiss=IISSEngine(),
        prep=PRepEngine(),
        issue=IssueEngine()
    )

    db = icon_service_engine._icx_context_db
    IconScoreContext.storage = ContextStorage(
        deploy=DeployStorage(db),
        fee=FeeStorage(db),
        icx=IcxStorage(db),
        iiss=IISSStorage(db),
        prep=PRepStorage(db),
        issue=IssueStorage(db),
        meta=MetaDBStorage(db),
        rc=RewardCalcStorage()
    )

    # Patch revision
    def set_revision_to_context(context):
        context.revision = revision

    icon_service_engine._set_revision_to_context = \
        Mock(side_effect=set_revision_to_context)

    return icon_service_engine
コード例 #9
0
    def test__reset_block_validation_penalty(self):
        engine = PRepEngine()
        engine.term = self.term
        engine.preps = self.preps
        engine.prep_address_converter = Mock()

        self.term.freeze()
        self.preps.freeze()

        assert engine.term.is_frozen()
        assert engine.preps.is_frozen()

        IconScoreContext.engine = Mock()
        IconScoreContext.storage = Mock()
        IconScoreContext.engine.prep = engine
        context = _create_context()

        assert engine.preps.size(active_prep_only=False) == context.preps.size(active_prep_only=False)
        for i in range(engine.preps.size(active_prep_only=True)):
            assert engine.preps.get_by_index(i) == context._preps.get_by_index(i)

        # Impose block validation penalty on 5 Main P-Reps
        indices = set()
        for _ in range(5):
            index = random.randint(0, self.main_prep_count - 1)
            indices.add(index)

        # Impose the block validation penalty on randomly chosen 5 or less than P-Reps
        for i in indices:
            prep = context.preps.get_by_index(i)
            dirty_prep = context.get_prep(prep.address, mutable=True)
            assert not dirty_prep.is_frozen()

            dirty_prep.penalty = PenaltyReason.BLOCK_VALIDATION
            dirty_prep.grade = PRepGrade.CANDIDATE

            context.put_dirty_prep(dirty_prep)

        context.update_dirty_prep_batch()

        for i in indices:
            prep = context.preps.get_by_index(i)
            assert prep.is_frozen()
            assert prep.status == PRepStatus.ACTIVE
            assert prep.penalty == PenaltyReason.BLOCK_VALIDATION
            assert prep.grade == PRepGrade.CANDIDATE

        engine._reset_block_validation_penalty(context)

        # The penalties of P-Reps in context should be reset
        # to PenaltyReason.NONE at the last block of the current term
        for prep in context.preps:
            assert prep.status == PRepStatus.ACTIVE
            assert prep.penalty == PenaltyReason.NONE

        for i in indices:
            prep = context.preps.get_by_index(i)
            assert prep.status == PRepStatus.ACTIVE
            assert prep.penalty == PenaltyReason.NONE
コード例 #10
0
    def setUp(self) -> None:
        context = Mock()

        self.total_supply = icx_to_loop(800_460_000)
        self.total_delegated = 0

        main_prep_count = PREP_MAIN_PREPS
        elected_prep_count = PREP_MAIN_AND_SUB_PREPS
        sub_prep_count = elected_prep_count - main_prep_count

        new_preps: 'PRepContainer' = _create_preps(size=elected_prep_count * 2)
        term = Term(sequence=0,
                    start_block_height=100,
                    period=43120,
                    irep=icx_to_loop(50000),
                    total_supply=self.total_supply,
                    total_delegated=self.total_delegated)
        term.set_preps(new_preps, main_prep_count, elected_prep_count)
        assert len(term.main_preps) == main_prep_count
        assert len(term.sub_preps) == elected_prep_count - main_prep_count
        assert len(term) == elected_prep_count

        # Case 0: Network has just decentralized without any delegation
        PRepEngine._update_prep_grades(context,
                                       new_preps=new_preps,
                                       old_term=None,
                                       new_term=term)
        assert len(term.main_preps) == main_prep_count
        assert len(term.sub_preps) == sub_prep_count
        assert len(term) == elected_prep_count
        assert len(term) == len(context.storage.prep.put_prep.mock_calls)

        _check_prep_grades(new_preps, main_prep_count, len(term))

        self.term = term
        self.preps = new_preps
        self.main_prep_count = PREP_MAIN_PREPS
        self.elected_prep_count = PREP_MAIN_AND_SUB_PREPS
        self.sub_prep_count = PREP_MAIN_AND_SUB_PREPS - PREP_MAIN_PREPS
コード例 #11
0
    def setUp(self):
        self._inner_task = generate_inner_task()

        self._inner_task._icon_service_engine._icon_pre_validator = \
            Mock(spec=IconPreValidator)

        factory = self._inner_task._icon_service_engine._step_counter_factory
        self.step_counter = Mock(spec=IconScoreStepCounter)
        factory.create = Mock(return_value=self.step_counter)
        self.step_counter.step_used = 0
        self.step_counter.step_price = 0
        self.step_counter.step_limit = 5000000
        self.step_cost_dict = self._init_step_cost()
        prep_engine = PRepEngine()
        prep_engine.preps = Mock()
        fee_engine = FeeEngine()
        icx_engine = IcxEngine()
        IconScoreContext.engine = ContextEngine(deploy=Mock(),
                                                fee=fee_engine,
                                                icx=icx_engine,
                                                iiss=None,
                                                prep=prep_engine,
                                                issue=None)
コード例 #12
0
ファイル: test_icon_score_api.py プロジェクト: bccenter/SSI
    def setUp(self):
        # The transaction in block 1000 of TestNet
        self.tx_v2 = {
            'from': 'hxdbc9f726ad776d9a43d5bad387eff01325178fa3',
            'to': 'hx0fb148785e4a5d77d16429c7ed2edae715a4453a',
            'value': '0x324e964b3eca80000',
            'fee': '0x2386f26fc10000',
            'timestamp': '1519709385120909',
            'tx_hash': '1257b9ea76e716b145463f0350f534f973399898a18a50d391e7d2815e72c950',
            'signature': 'WiRTA/tUNGVByc8fsZ7+U9BSDX4BcBuv2OpAuOLLbzUiCcovLPDuFE+PBaT8ovmz5wg+Bjr7rmKiu7Rl8v0DUQE=',
        }

        # The transaction in block 100000 of MainNet
        self.tx_v3 = {
            'version': '0x3',
            'nid': '0x1',
            'from': 'hx522bff55a62e0c75a1b51855b0802cfec6a92e84',
            'to': 'hx11de4e28be4845de3ea392fd8d758655bf766ca7',
            'value': '0x71afd498d0000',
            'stepLimit': '0xf4240',
            'timestamp': '0x57a4e5556cc03',
            'signature': 'fcEMXqEGlqEivXXr7YtD/F1RXgxSXF+R4gVrGKxT1zxi3HukX4NzkSl9/Es1G+nyZx+kviTAtQFUrA+/T0NrfAA=',
            'txHash': '6c71ac77b2d130a1f81d234e814974e85cabb0a3ec462c66ff3f820502d0ded2'
        }

        self.step_costs = {
            StepType.DEFAULT: 0,
            StepType.CONTRACT_CALL: 25_000,
            StepType.CONTRACT_CREATE: 1_000_000_000,
            StepType.CONTRACT_UPDATE: 1_600_000_000,
            StepType.CONTRACT_DESTRUCT: -70000,
            StepType.CONTRACT_SET: 30_000,
            StepType.GET: 0,
            StepType.SET: 320,
            StepType.REPLACE: 80,
            StepType.DELETE: -240,
            StepType.INPUT: 200,
            StepType.EVENT_LOG: 100,
            StepType.API_CALL: 10_000
        }
        self.step_limit = 1_000_000_000

        self._prep_engine = PRepEngine()

        self.context = self._create_context()
        ContextContainer._push_context(self.context)
コード例 #13
0
    def test_invoke(self):
        context = Mock(revision=Revision.IISS.value - 1)
        engine = PRepEngine()

        with pytest.raises(InvalidParamsException):
            engine.invoke(context, "registerPRep", {})
コード例 #14
0
    def test_handle_get_prep_term_with_penalized_preps(self):
        block_height = 200
        sequence = 78
        period = 43120
        start_block_height = 200
        end_block_height = 200 + period - 1
        irep = icx_to_loop(40000)
        total_supply = icx_to_loop(800_460_000)
        total_delegated = icx_to_loop(1000)

        params = {}

        context = Mock()
        context.block.height = block_height

        main_prep_count = 22
        elected_prep_count = 100
        total_prep_count = 106

        term = Term(sequence=sequence,
                    start_block_height=start_block_height,
                    period=period,
                    irep=irep,
                    total_supply=total_supply,
                    total_delegated=total_delegated)

        preps = PRepContainer()
        for i in range(total_prep_count):
            address = Address.from_prefix_and_int(AddressPrefix.EOA, i)
            delegated = icx_to_loop(1000 - i)
            penalty = PenaltyReason.NONE
            status = PRepStatus.ACTIVE

            if 0 <= i <= 4:
                # block validation penalty preps: 5
                penalty: 'PenaltyReason' = PenaltyReason.BLOCK_VALIDATION
            elif i == 5:
                # unregistered preps: 1
                status = PRepStatus.UNREGISTERED
            elif 6 <= i <= 7:
                # low productivity preps: 2
                status = PRepStatus.DISQUALIFIED
                penalty = PenaltyReason.LOW_PRODUCTIVITY
            elif 8 <= i <= 10:
                # disqualified preps: 3
                status = PRepStatus.DISQUALIFIED
                penalty = PenaltyReason.PREP_DISQUALIFICATION

            prep = PRep(address, block_height=i, delegated=delegated, penalty=penalty, status=status)
            prep.freeze()
            preps.add(prep)

        preps.freeze()
        assert preps.size(active_prep_only=False) == total_prep_count

        electable_preps = filter(lambda x: x.is_electable(), preps)
        term.set_preps(electable_preps, main_prep_count=main_prep_count, elected_prep_count=elected_prep_count)

        engine = PRepEngine()
        engine.term = term
        engine.preps = preps

        ret: dict = engine.handle_get_prep_term(context)

        assert ret["blockHeight"] == block_height
        assert ret["sequence"] == sequence
        assert ret["startBlockHeight"] == start_block_height
        assert ret["endBlockHeight"] == end_block_height
        assert ret["totalSupply"] == total_supply
        assert ret["totalDelegated"] == total_delegated
        assert ret["irep"] == irep

        prep_list: List[Dict[str, Union[int, str, 'Address']]] = ret["preps"]
        assert len(prep_list) == elected_prep_count

        for i, prep_snapshot in enumerate(term.preps):
            prep_item: Dict[str, Union[int, str, 'Address']] = prep_list[i]
            assert prep_item["address"] == prep_snapshot.address
            assert prep_item["status"] == PRepStatus.ACTIVE.value
            assert prep_item["penalty"] == PenaltyReason.NONE.value

        # The P-Reps which got penalized for consecutive 660 block validation failure
        # are located at the end of the P-Rep list
        prev_delegated = -1
        for i, prep_item in enumerate(prep_list[-5:]):
            assert prep_item["address"] == Address.from_prefix_and_int(AddressPrefix.EOA, i)
            assert prep_item["status"] == PRepStatus.ACTIVE.value
            assert prep_item["penalty"] == PenaltyReason.BLOCK_VALIDATION.value

            delegated: int = prep_item["delegated"]
            if prev_delegated >= 0:
                assert prev_delegated >= delegated

            prev_delegated = delegated
コード例 #15
0
    def test_update_prep_grades_on_multiple_cases(self):
        context = Mock()

        old_term = self.term
        old_preps = self.preps
        new_term = old_term.copy()
        new_preps = old_preps.copy(mutable=True)

        # Main P-Reps
        cases = [
            (PRepStatus.UNREGISTERED, PenaltyReason.NONE),
            (PRepStatus.DISQUALIFIED, PenaltyReason.PREP_DISQUALIFICATION),
            (PRepStatus.DISQUALIFIED, PenaltyReason.LOW_PRODUCTIVITY),
            (PRepStatus.ACTIVE, PenaltyReason.BLOCK_VALIDATION)
        ]
        for case in cases:
            index = random.randint(0, len(new_term.main_preps) - 1)
            prep = new_preps.get_by_index(index)

            dirty_prep = prep.copy()
            dirty_prep.status = case[0]
            dirty_prep.penalty = case[1]
            new_preps.replace(dirty_prep)

            assert new_preps.is_dirty()
            assert old_preps.get_by_address(prep.address) == prep
            assert new_preps.get_by_address(prep.address) != prep
            assert new_preps.get_by_address(prep.address) == dirty_prep

            new_term.update_invalid_elected_preps([dirty_prep])

        # Sub P-Rep
        main_prep_count = len(new_term.main_preps)
        assert main_prep_count == len(old_term.main_preps)
        assert len(new_term) == len(old_term) - len(cases)

        for _ in range(3):
            index = random.randint(0, len(new_term.sub_preps) - 1)
            sub_snapshot = new_term.sub_preps[index]
            address = sub_snapshot.address

            prep = new_preps.get_by_address(address)
            assert prep.grade == PRepGrade.SUB

            dirty_prep = prep.copy()
            dirty_prep.status = PRepStatus.UNREGISTERED
            new_preps.replace(dirty_prep)
            assert new_preps.is_dirty()
            assert old_preps.get_by_address(address) == prep
            assert new_preps.get_by_address(address) != prep
            assert new_preps.get_by_address(address) == dirty_prep

            new_term.update_invalid_elected_preps([dirty_prep])

        # Candidate P-Rep
        for _ in range(3):
            index = random.randint(1, new_preps.size(active_prep_only=True) - len(new_term) - 1)

            prep = new_preps.get_by_index(len(new_term) + index)
            address = prep.address
            assert prep.grade == PRepGrade.CANDIDATE

            dirty_prep = prep.copy()
            dirty_prep.status = PRepStatus.UNREGISTERED
            new_preps.replace(dirty_prep)
            assert new_preps.is_dirty()
            assert old_preps.get_by_address(address) == prep
            assert new_preps.get_by_address(address) != prep
            assert new_preps.get_by_address(address) == dirty_prep

        PRepEngine._update_prep_grades(context, new_preps, old_term, new_term)
        _check_prep_grades2(new_preps, new_term)

        assert len(new_term.main_preps) == self.main_prep_count
        assert len(new_term.sub_preps) == self.sub_prep_count - 7
        assert new_preps.size(active_prep_only=True) == old_preps.size(active_prep_only=True) - 9
        assert new_preps.size() == old_preps.size()