Create abstract election class#2498
Conversation
…from a common `Election` class Solution: Factored the common logic out of `ValidatorElection` and moved it to `Election` parent class
Solution: Reverting the changes
Codecov Report
@@ Coverage Diff @@
## master #2498 +/- ##
==========================================
- Coverage 93.53% 93.37% -0.16%
==========================================
Files 42 43 +1
Lines 2551 2582 +31
==========================================
+ Hits 2386 2411 +25
- Misses 165 171 +6 |
|
|
||
| @register_query(LocalMongoDBConnection) | ||
| def get_validator_set_by_election_id(conn, election_id): | ||
| def get_result_by_election_id(conn, election_id, table): |
There was a problem hiding this comment.
- A more appropriate function name could be
get_election_result/get_election_result_by_id? - What is the point of this generalization?
There was a problem hiding this comment.
-
Good suggestion. Changed the name.
-
When checking the election status, the first thing I want to do is look and see if anything with this
election_idhas been written to the relevant Mongo table, (ie has the vote been previoously concluded.)
|
|
||
| @register_query(LocalMongoDBConnection) | ||
| def get_validator_set_by_election_id(conn, election_id): | ||
| def get_result_by_election_id(conn, election_id, table): |
There was a problem hiding this comment.
Creating a new collection for every election might be cumbersome. Instead, we can introduce an elections collection storing documents of the form:
{
'election_id': '...',
'type': '...',
'height': X
}
There was a problem hiding this comment.
What would be the use of 'type'?
There was a problem hiding this comment.
type is what is operation in the transaction schema. For instance, if you want to find out if there was a validator update caused by the given election ID, you need to filter by type.
There was a problem hiding this comment.
I don't think you need type once you have the election id, there shouldn't be two elections with same election id and yet have different operations
There was a problem hiding this comment.
Yes, but when you receive an election ID in the command line, you do not know its type for sure. A user may look for a validator election status providing a migration election ID by mistake.
| 'election_id': <election_id_that_approved_the_change> | ||
| } | ||
| } | ||
| """ |
There was a problem hiding this comment.
I think this doc string is wrong?
:return: {
'height': <block_height>,
'validators': <validator_set>,
'election_id': <election_id_that_approved_the_change>
}
There was a problem hiding this comment.
Nope. I checked it. It's correct.
There was a problem hiding this comment.
Don't think so, bigchain.get_validator_change(height) calls backend.query.get_validator_set(self.connection, height) which returns the validator object of the following format,
{'height': <block_height>,
'validators': <validator_set>,
'election_id': <election_id_that_approved_the_change>
}
bigchaindb/common/election.py
Outdated
| def count_votes(cls, election_pk, transactions, getter=getattr): | ||
| votes = 0 | ||
| for txn in transactions: | ||
| if getter(txn, 'operation') == 'VALIDATOR_ELECTION_VOTE': |
There was a problem hiding this comment.
The generalized class cannot contain hard-coded value.
There was a problem hiding this comment.
But 'VALIDATOR_ELECTION_VOTE' still exists
bigchaindb/common/election.py
Outdated
| return result | ||
|
|
||
| @classmethod | ||
| def is_approved(cls, bigchain, new_height, txns): |
There was a problem hiding this comment.
is_approved as a name suggests the function returns a boolean, while in fact it returns either [] or an arbitrary on_approve result.
I would add a function called get_approved_election_or_none, which returns what its name says. Additionally, in the ValidatorElection class I would keep get_validators_update, which in turn would use the generic get_approved_election_or_none.
What do you think?
Solution: Refactored `get_result_by_election_id` to `get_election_result_by_id`
| @singledispatch | ||
| def get_asset_tokens_for_public_key(connection, asset_id, | ||
| public_key, operation): | ||
| def get_asset_tokens_for_public_key(connection, asset_id, public_key): |
…ables Solution: Remove `DB_TABLE` property from `Election` class
Solution: Election ID has to be unique but not every validator set record has it. MongoDB does not support partial indexes, does not even allow for multiple Nones. This is a temporary fix since we are introducing an `election` collection to store election IDs in bigchaindb#2498.
Solution: Election ID has to be unique but not every validator set record has it. MongoDB does not support partial indexes, does not even allow for multiple Nones. This is a temporary fix since we are introducing an `election` collection to store election IDs in #2498.
bigchaindb/common/election.py
Outdated
|
|
||
| # NOTE: this transaction class extends create so the operation inheritance is achieved | ||
| # by setting an ELECTION_TYPE and renaming CREATE = ELECTION_TYPE and ALLOWED_OPERATIONS = (ELECTION_TYPE,) | ||
| ELECTION_TYPE = None |
There was a problem hiding this comment.
In an attempt to make the code more uniform it would good to rename ELECTION_TYPE to OPERATION so that further in future when CREATE and TRANSFER both become an independent class they would also have OPERATION field and the registeration would look as follows,
Transaction.register_type(Create.OPERATION, Create)
Transaction.register_type(Transfer.OPEATION, Transfer)
Transaction.register_type(ValidatorElection.OPERATION, ValidatorElection)
Transaction.register_type(ValidatorElectionVote.OPERATION, ValidatorElectionVote)…ir own tables" This reverts commit db45374.
… for an election method Solution: Finished the refactoring
Solution: Created the `elections` table with secondary_index `election_id`
…tions in the `elections` table Solution: Updated the class to use the new table
Solution: Renamed, refactored and moved the `Vote` class to tie in with the more general `Election` base class
Solution: Fixed the docstring
Solution: Pointed the reference to the class variable
…ote` Solution: Renamed `TX_SCHEMA_VALIDATOR_ELECTION_VOTE` as `TX_SCHEMA_VOTE`
Solution: Renamed `ELECTION_TYPE` to `OPERATION`
| operation: | ||
| type: string | ||
| value: "VALIDATOR_ELECTION_VOTE" | ||
| value: "OPERATION" |
There was a problem hiding this comment.
It is weird that the CI tests are still passing even when the election created using .generate would not match the above schema!
There was a problem hiding this comment.
but the value is still "OPERATION"?
bigchaindb/common/vote.py
Outdated
| TRANSFER = VALIDATOR_ELECTION_VOTE | ||
| ALLOWED_OPERATIONS = (VALIDATOR_ELECTION_VOTE,) | ||
| TRANSFER = VOTE | ||
| ALLOWED_OPERATIONS = (VOTE,) |
There was a problem hiding this comment.
OPERATION = VOTE should also be specified.
There was a problem hiding this comment.
I don't see OPERATION = VOTE specified anywhere
bigchaindb/common/election.py
Outdated
| bigchain.store_election(height, election) | ||
|
|
||
| @classmethod | ||
| def is_approved(cls, bigchain, new_height, txns): |
There was a problem hiding this comment.
is_approved as the name suggests should return True or False but it is returning [] or the respective election's proposed change.
bigchaindb/lib.py
Outdated
|
|
||
| self.store_abci_chain(block['height'] + 1, new_chain_id, False) | ||
|
|
||
| def store_election(self, height, election): |
There was a problem hiding this comment.
The doc string says Store election results but the function name says store_election. It would be better to name the function too as store_election_results
There was a problem hiding this comment.
I still see the function name as store_election
Solution: Addressed comments from PR
bigchaindb/elections/election.py
Outdated
| :param current_transactions: (list) A list of transactions to be validated along with the election | ||
|
|
||
| Returns: | ||
| ValidatorElection object |
There was a problem hiding this comment.
Return value should also have a generalized name
tests/tendermint/test_core.py
Outdated
| assert resp.validator_updates[0] == encode_validator(validator) | ||
|
|
||
| updates = b.get_validator_update() | ||
| updates = b.is_approved() |
There was a problem hiding this comment.
There is no is_approved I think. It seems to be changed to on_approved
There was a problem hiding this comment.
approved_updates is the replacement method for is_approved. This test is skipped though. Are we planning on fixing it?
tests/tendermint/test_lib.py
Outdated
| query.store_validator_update(b.connection, validator_update) | ||
|
|
||
| updates = b.get_validator_update() | ||
| updates = b.is_approved() |
There was a problem hiding this comment.
There is no is_approved. It seems to be changed to on_approved
There was a problem hiding this comment.
Same here. Are we going to un-skip this test?
Solution: Removed the variable and hardcoded everything to use the `Vote` class
Solution: Addressing comments
| else: | ||
| return self.ONGOING | ||
| updated_validator_set = [v for v in updated_validator_set if v['voting_power'] > 0] | ||
| bigchain.store_validator_set(new_height+1, updated_validator_set, election.id) |
There was a problem hiding this comment.
As we decided in our discussion we will not store election.id in the validators collection instead store the fact that election.id concluded in the elections collections
bigchaindb/elections/election.py
Outdated
| input_conditions = [] | ||
|
|
||
| duplicates = any(txn for txn in current_transactions if txn.id == self.id) | ||
| if bigchain.get_transaction(self.id) or duplicates: |
There was a problem hiding this comment.
It would be better to use bigchain.is_committed instead of bigchain.get_transaction
There was a problem hiding this comment.
I don't see anything by that name in bigchaindb/lib.py, and it doesn't work when I try to execute bigchain.get_commited in the tests.
If we want to create this method can we do it in another PR? It doesn't seem necessary to get Election working
There was a problem hiding this comment.
Sorry, I was talking about this. Fixed the comment. Also, the reason I am suggesting this change is that the get_transaction required 3 database calls while is_committed requires only 1.
…a `validator_set` Solution: Removed the code to store the `election_id` and aligned the tests
…roperties Solution: Added a random `uuid4` seed to enforce uniqueness
| update = ValidatorElection.approved_update(b, 4, [tx_vote2]) | ||
| if update: | ||
| update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n') | ||
| assert update |
There was a problem hiding this comment.
It seems that we expect the update to have the new validator set. Since this is a test we don't need to play safe by using if update, we could do the following too,
update = ValidatorElection.approved_update(b, 4, [tx_vote2])
assert update
update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n')| update_public_key = None | ||
| if update: | ||
| update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n') | ||
| assert update |
There was a problem hiding this comment.
We could do the following and avoid if and update_public_key = None
update = ValidatorElection.approved_update(b, 4, [tx_vote0, tx_vote1, tx_vote2])
assert update
update_public_key = codecs.encode(update.pub_key.data, 'base64').decode().rstrip('\n') …the tx is in the db Solution: use `is_committed` instead of `get_transaction`
| name='height', | ||
| unique=True,) | ||
|
|
||
| logger.info('Create `abci_chains.chain_id` secondary index.') |
There was a problem hiding this comment.
There is a conflict resolution leftover.
bigchaindb/backend/query.py
Outdated
|
|
||
|
|
||
| @singledispatch | ||
| def store_election_results(conn, validator_update): |
There was a problem hiding this comment.
The parameter was not renamed.
| @@ -63,7 +63,7 @@ definitions: | |||
| - CREATE | |||
| - TRANSFER | |||
| - VALIDATOR_ELECTION | |||
There was a problem hiding this comment.
We also need a generic name instead of VALIDATOR_ELECTION now.
There was a problem hiding this comment.
What purpose would that serve since the name would always be overriden by the specific election type?
There was a problem hiding this comment.
True, there is no purpose in such an operation..
But extending the base schema every time a new election is introduced does not sound necessary either.
I would not require the operation in the base spec. Every specific spec can require the particular operation itself.
Does not have to be done in this PR though.
There was a problem hiding this comment.
I don't think we do...
Since Election is a base class, and will be extended with distinct election classes, shouldn't each of those get their own operations? The ValidatorElection is still using VALIDATOR_ELECTION as it's operation.
There was a problem hiding this comment.
Yes. An extra point (we can address it later) is that bigchaindb/common/schema/transaction_v2.0.yaml does not have to require the operation to be from the given set. It just makes us extend this list every time a new operation is introduced.
| - node_id | ||
| - public_key | ||
| - power | ||
| - seed |
There was a problem hiding this comment.
We don't have to make it required.
There was a problem hiding this comment.
Should it be required for the validator election?
There was a problem hiding this comment.
We do not have to require it there either.
There was a problem hiding this comment.
It's generated automatically on creation of the election, so it will always be present, regardless. But I can remove the required tag, anyway.
Solution: Removed the parameter
| :return: { | ||
| 'height': <block_height>, | ||
| 'validators': <validator_set>, | ||
| 'election_id': <election_id_that_approved_the_change> |
There was a problem hiding this comment.
election_id is not there anymore.
…_update` Solution: Renamed to `election`
| (inputs, outputs) = cls.validate_transfer(inputs, recipients, election_id, metadata) | ||
| election_vote = cls(cls.VALIDATOR_ELECTION_VOTE, {'id': election_id}, inputs, outputs, metadata) | ||
| election_vote = cls(cls.OPERATION, {'id': election_id}, inputs, outputs, metadata) | ||
| cls.validate_schema(election_vote.to_dict(), skip_id=True) |
There was a problem hiding this comment.
We can also remove skip_id from here.
There was a problem hiding this comment.
Because Vote is always a TRANSFER, and we always validate how identifiers are built for transfers.
This is not true actually..
Discussed offline - we can remove skip_id but then we also need update from_dict in common/transaction.py so that it does not pass skip_id but calls validate_id after validating the schema.
| return self | ||
|
|
||
| @classmethod | ||
| def generate(cls, initiator, voters, election_data, metadata=None): |
There was a problem hiding this comment.
What do you think about adding an additional parameter generate_random_seed=True? - it will simplify those tests that currently have to mock uuid4 and will clearly display to the reader that generating a seed is optional.
Solution: Replaced it
Solution: Removed the requirement
**Problem:
ValidatorElectionandMigrationElectionneed to inherit from a genericElectionclassSolution
Factored the common logic out of
ValidatorElectionand moved it to a common parent class,Election