mirror of
https://github.com/planetmint/planetmint.git
synced 2025-11-24 14:35:45 +00:00
moved process_block to planetmint
Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
parent
11083c725f
commit
137025147f
@ -209,7 +209,7 @@ class App(BaseApplication):
|
|||||||
else:
|
else:
|
||||||
self.block_txn_hash = block["app_hash"]
|
self.block_txn_hash = block["app_hash"]
|
||||||
|
|
||||||
validator_update = Election.process_block(self.planetmint_node, self.new_height, self.block_transactions)
|
validator_update = self.planetmint_node.process_block(self.new_height, self.block_transactions)
|
||||||
|
|
||||||
return ResponseEndBlock(validator_updates=validator_update)
|
return ResponseEndBlock(validator_updates=validator_update)
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ MongoDB.
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from collections import namedtuple
|
from collections import namedtuple, OrderedDict
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import rapidjson
|
import rapidjson
|
||||||
@ -812,4 +812,71 @@ class Planetmint(object):
|
|||||||
txns = list(backend.query.get_asset_tokens_for_public_key(self.connection, transaction.id, election_pk))
|
txns = list(backend.query.get_asset_tokens_for_public_key(self.connection, transaction.id, election_pk))
|
||||||
return self.count_votes(election_pk, txns, dict.get)
|
return self.count_votes(election_pk, txns, dict.get)
|
||||||
|
|
||||||
|
def _get_initiated_elections(self, height, txns): # TODO: move somewhere else
|
||||||
|
elections = []
|
||||||
|
for tx in txns:
|
||||||
|
if not isinstance(tx, Election):
|
||||||
|
continue
|
||||||
|
|
||||||
|
elections.append({"election_id": tx.id, "height": height, "is_concluded": False})
|
||||||
|
return elections
|
||||||
|
|
||||||
|
def _get_votes(self, txns): # TODO: move somewhere else
|
||||||
|
elections = OrderedDict()
|
||||||
|
for tx in txns:
|
||||||
|
if not isinstance(tx, Vote):
|
||||||
|
continue
|
||||||
|
|
||||||
|
election_id = tx.asset["id"]
|
||||||
|
if election_id not in elections:
|
||||||
|
elections[election_id] = []
|
||||||
|
elections[election_id].append(tx)
|
||||||
|
return elections
|
||||||
|
|
||||||
|
def process_block(self, new_height, txns): # TODO: move somewhere else
|
||||||
|
"""Looks for election and vote transactions inside the block, records
|
||||||
|
and processes elections.
|
||||||
|
|
||||||
|
Every election is recorded in the database.
|
||||||
|
|
||||||
|
Every vote has a chance to conclude the corresponding election. When
|
||||||
|
an election is concluded, the corresponding database record is
|
||||||
|
marked as such.
|
||||||
|
|
||||||
|
Elections and votes are processed in the order in which they
|
||||||
|
appear in the block. Elections are concluded in the order of
|
||||||
|
appearance of their first votes in the block.
|
||||||
|
|
||||||
|
For every election concluded in the block, calls its `on_approval`
|
||||||
|
method. The returned value of the last `on_approval`, if any,
|
||||||
|
is a validator set update to be applied in one of the following blocks.
|
||||||
|
|
||||||
|
`on_approval` methods are implemented by elections of particular type.
|
||||||
|
The method may contain side effects but should be idempotent. To account
|
||||||
|
for other concluded elections, if it requires so, the method should
|
||||||
|
rely on the database state.
|
||||||
|
"""
|
||||||
|
# elections initiated in this block
|
||||||
|
initiated_elections = self._get_initiated_elections(new_height, txns)
|
||||||
|
|
||||||
|
if initiated_elections:
|
||||||
|
self.store_elections(initiated_elections)
|
||||||
|
|
||||||
|
# elections voted for in this block and their votes
|
||||||
|
elections = self._get_votes(txns)
|
||||||
|
|
||||||
|
validator_update = None
|
||||||
|
for election_id, votes in elections.items():
|
||||||
|
election = self.get_transaction(election_id)
|
||||||
|
if election is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not election.has_concluded(self, votes):
|
||||||
|
continue
|
||||||
|
|
||||||
|
validator_update = election.on_approval(self, new_height)
|
||||||
|
self.store_election(election.id, new_height, is_concluded=True)
|
||||||
|
|
||||||
|
return [validator_update] if validator_update else []
|
||||||
|
|
||||||
Block = namedtuple("Block", ("app_hash", "height", "transactions"))
|
Block = namedtuple("Block", ("app_hash", "height", "transactions"))
|
||||||
|
|||||||
@ -4,11 +4,9 @@
|
|||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import base58
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from planetmint import backend
|
|
||||||
from planetmint.transactions.types.elections.vote import Vote
|
from planetmint.transactions.types.elections.vote import Vote
|
||||||
from planetmint.transactions.common.transaction import Transaction
|
from planetmint.transactions.common.transaction import Transaction
|
||||||
from planetmint.transactions.common.schema import _validate_schema, TX_SCHEMA_COMMON
|
from planetmint.transactions.common.schema import _validate_schema, TX_SCHEMA_COMMON
|
||||||
@ -95,76 +93,6 @@ class Election(Transaction):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_initiated_elections(cls, height, txns): # TODO: move somewhere else
|
|
||||||
elections = []
|
|
||||||
for tx in txns:
|
|
||||||
if not isinstance(tx, Election):
|
|
||||||
continue
|
|
||||||
|
|
||||||
elections.append({"election_id": tx.id, "height": height, "is_concluded": False})
|
|
||||||
return elections
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_votes(cls, txns): # TODO: move somewhere else
|
|
||||||
elections = OrderedDict()
|
|
||||||
for tx in txns:
|
|
||||||
if not isinstance(tx, Vote):
|
|
||||||
continue
|
|
||||||
|
|
||||||
election_id = tx.asset["id"]
|
|
||||||
if election_id not in elections:
|
|
||||||
elections[election_id] = []
|
|
||||||
elections[election_id].append(tx)
|
|
||||||
return elections
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def process_block(cls, planet, new_height, txns): # TODO: move somewhere else
|
|
||||||
"""Looks for election and vote transactions inside the block, records
|
|
||||||
and processes elections.
|
|
||||||
|
|
||||||
Every election is recorded in the database.
|
|
||||||
|
|
||||||
Every vote has a chance to conclude the corresponding election. When
|
|
||||||
an election is concluded, the corresponding database record is
|
|
||||||
marked as such.
|
|
||||||
|
|
||||||
Elections and votes are processed in the order in which they
|
|
||||||
appear in the block. Elections are concluded in the order of
|
|
||||||
appearance of their first votes in the block.
|
|
||||||
|
|
||||||
For every election concluded in the block, calls its `on_approval`
|
|
||||||
method. The returned value of the last `on_approval`, if any,
|
|
||||||
is a validator set update to be applied in one of the following blocks.
|
|
||||||
|
|
||||||
`on_approval` methods are implemented by elections of particular type.
|
|
||||||
The method may contain side effects but should be idempotent. To account
|
|
||||||
for other concluded elections, if it requires so, the method should
|
|
||||||
rely on the database state.
|
|
||||||
"""
|
|
||||||
# elections initiated in this block
|
|
||||||
initiated_elections = cls._get_initiated_elections(new_height, txns)
|
|
||||||
|
|
||||||
if initiated_elections:
|
|
||||||
planet.store_elections(initiated_elections)
|
|
||||||
|
|
||||||
# elections voted for in this block and their votes
|
|
||||||
elections = cls._get_votes(txns)
|
|
||||||
|
|
||||||
validator_update = None
|
|
||||||
for election_id, votes in elections.items():
|
|
||||||
election = planet.get_transaction(election_id)
|
|
||||||
if election is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not election.has_concluded(planet, votes):
|
|
||||||
continue
|
|
||||||
|
|
||||||
validator_update = election.on_approval(planet, new_height)
|
|
||||||
planet.store_election(election.id, new_height, is_concluded=True)
|
|
||||||
|
|
||||||
return [validator_update] if validator_update else []
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def rollback(cls, planet, new_height, txn_ids): # TODO: move somewhere else
|
def rollback(cls, planet, new_height, txn_ids): # TODO: move somewhere else
|
||||||
"""Looks for election and vote transactions inside the block and
|
"""Looks for election and vote transactions inside the block and
|
||||||
@ -179,7 +107,7 @@ class Election(Transaction):
|
|||||||
|
|
||||||
txns = [planet.get_transaction(tx_id) for tx_id in txn_ids]
|
txns = [planet.get_transaction(tx_id) for tx_id in txn_ids]
|
||||||
|
|
||||||
elections = cls._get_votes(txns)
|
elections = planet._get_votes(txns)
|
||||||
for election_id in elections:
|
for election_id in elections:
|
||||||
election = planet.get_transaction(election_id)
|
election = planet.get_transaction(election_id)
|
||||||
election.on_rollback(planet, new_height)
|
election.on_rollback(planet, new_height)
|
||||||
|
|||||||
@ -524,7 +524,7 @@ def test_chain_migration_election_show_shows_inconclusive(b):
|
|||||||
|
|
||||||
assert not run_election_show(Namespace(election_id=election.id), b)
|
assert not run_election_show(Namespace(election_id=election.id), b)
|
||||||
|
|
||||||
Election.process_block(b, 1, [election])
|
b.process_block(1, [election])
|
||||||
b.store_bulk_transactions([election])
|
b.store_bulk_transactions([election])
|
||||||
|
|
||||||
assert run_election_show(Namespace(election_id=election.id), b) == "status=ongoing"
|
assert run_election_show(Namespace(election_id=election.id), b) == "status=ongoing"
|
||||||
@ -554,13 +554,13 @@ def test_chain_migration_election_show_shows_concluded(b):
|
|||||||
assert not run_election_show(Namespace(election_id=election.id), b)
|
assert not run_election_show(Namespace(election_id=election.id), b)
|
||||||
|
|
||||||
b.store_bulk_transactions([election])
|
b.store_bulk_transactions([election])
|
||||||
Election.process_block(b, 1, [election])
|
b.process_block(1, [election])
|
||||||
|
|
||||||
assert run_election_show(Namespace(election_id=election.id), b) == "status=ongoing"
|
assert run_election_show(Namespace(election_id=election.id), b) == "status=ongoing"
|
||||||
|
|
||||||
b.store_abci_chain(1, "chain-X")
|
b.store_abci_chain(1, "chain-X")
|
||||||
b.store_block(Block(height=1, transactions=[v.id for v in votes], app_hash="last_app_hash")._asdict())
|
b.store_block(Block(height=1, transactions=[v.id for v in votes], app_hash="last_app_hash")._asdict())
|
||||||
Election.process_block(b, 2, votes)
|
b.process_block(2, votes)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
run_election_show(Namespace(election_id=election.id), b)
|
run_election_show(Namespace(election_id=election.id), b)
|
||||||
|
|||||||
@ -31,11 +31,11 @@ def test_process_block_concludes_all_elections(b):
|
|||||||
total_votes += votes
|
total_votes += votes
|
||||||
|
|
||||||
b.store_abci_chain(1, "chain-X")
|
b.store_abci_chain(1, "chain-X")
|
||||||
Election.process_block(b, 1, txs)
|
b.process_block(1, txs)
|
||||||
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
||||||
b.store_bulk_transactions(txs)
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
Election.process_block(b, 2, total_votes)
|
b.process_block(2, total_votes)
|
||||||
|
|
||||||
validators = b.get_validators()
|
validators = b.get_validators()
|
||||||
assert len(validators) == 5
|
assert len(validators) == 5
|
||||||
@ -78,11 +78,11 @@ def test_process_block_approves_only_one_validator_update(b):
|
|||||||
txs += [election]
|
txs += [election]
|
||||||
total_votes += votes
|
total_votes += votes
|
||||||
|
|
||||||
Election.process_block(b, 1, txs)
|
b.process_block(1, txs)
|
||||||
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
||||||
b.store_bulk_transactions(txs)
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
Election.process_block(b, 2, total_votes)
|
b.process_block(2, total_votes)
|
||||||
|
|
||||||
validators = b.get_validators()
|
validators = b.get_validators()
|
||||||
assert len(validators) == 5
|
assert len(validators) == 5
|
||||||
@ -124,11 +124,11 @@ def test_process_block_approves_after_pending_validator_update(b):
|
|||||||
total_votes += votes
|
total_votes += votes
|
||||||
|
|
||||||
b.store_abci_chain(1, "chain-X")
|
b.store_abci_chain(1, "chain-X")
|
||||||
Election.process_block(b, 1, txs)
|
b.process_block(1, txs)
|
||||||
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
||||||
b.store_bulk_transactions(txs)
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
Election.process_block(b, 2, total_votes)
|
b.process_block(2, total_votes)
|
||||||
|
|
||||||
validators = b.get_validators()
|
validators = b.get_validators()
|
||||||
assert len(validators) == 5
|
assert len(validators) == 5
|
||||||
@ -160,19 +160,19 @@ def test_process_block_does_not_approve_after_validator_update(b):
|
|||||||
total_votes = votes
|
total_votes = votes
|
||||||
|
|
||||||
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
||||||
Election.process_block(b, 1, txs)
|
b.process_block(1, txs)
|
||||||
b.store_bulk_transactions(txs)
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
second_election, second_votes = generate_election(
|
second_election, second_votes = generate_election(
|
||||||
b, ChainMigrationElection, public_key, private_key, {}, voter_keys
|
b, ChainMigrationElection, public_key, private_key, {}, voter_keys
|
||||||
)
|
)
|
||||||
|
|
||||||
Election.process_block(b, 2, total_votes + [second_election])
|
b.process_block(2, total_votes + [second_election])
|
||||||
|
|
||||||
b.store_block(Block(height=2, transactions=[v.id for v in total_votes + [second_election]], app_hash="")._asdict())
|
b.store_block(Block(height=2, transactions=[v.id for v in total_votes + [second_election]], app_hash="")._asdict())
|
||||||
|
|
||||||
b.store_abci_chain(1, "chain-X")
|
b.store_abci_chain(1, "chain-X")
|
||||||
Election.process_block(b, 3, second_votes)
|
b.process_block(3, second_votes)
|
||||||
|
|
||||||
assert not b.get_election(second_election.id)["is_concluded"]
|
assert not b.get_election(second_election.id)["is_concluded"]
|
||||||
assert b.get_latest_abci_chain() == {"height": 1, "chain_id": "chain-X", "is_synced": True}
|
assert b.get_latest_abci_chain() == {"height": 1, "chain_id": "chain-X", "is_synced": True}
|
||||||
@ -197,11 +197,11 @@ def test_process_block_applies_only_one_migration(b):
|
|||||||
total_votes += votes
|
total_votes += votes
|
||||||
|
|
||||||
b.store_abci_chain(1, "chain-X")
|
b.store_abci_chain(1, "chain-X")
|
||||||
Election.process_block(b, 1, txs)
|
b.process_block(1, txs)
|
||||||
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
b.store_block(Block(height=1, transactions=[tx.id for tx in txs], app_hash="")._asdict())
|
||||||
b.store_bulk_transactions(txs)
|
b.store_bulk_transactions(txs)
|
||||||
|
|
||||||
Election.process_block(b, 1, total_votes)
|
b.process_block(1, total_votes)
|
||||||
chain = b.get_latest_abci_chain()
|
chain = b.get_latest_abci_chain()
|
||||||
assert chain
|
assert chain
|
||||||
assert chain == {
|
assert chain == {
|
||||||
@ -215,4 +215,4 @@ def test_process_block_applies_only_one_migration(b):
|
|||||||
|
|
||||||
|
|
||||||
def test_process_block_gracefully_handles_empty_block(b):
|
def test_process_block_gracefully_handles_empty_block(b):
|
||||||
Election.process_block(b, 1, [])
|
b.process_block(1, [])
|
||||||
|
|||||||
@ -341,7 +341,7 @@ def test_end_block_return_validator_updates(b, init_chain_request):
|
|||||||
)
|
)
|
||||||
b.store_block(Block(height=1, transactions=[election.id], app_hash="")._asdict())
|
b.store_block(Block(height=1, transactions=[election.id], app_hash="")._asdict())
|
||||||
b.store_bulk_transactions([election])
|
b.store_bulk_transactions([election])
|
||||||
Election.process_block(b, 1, [election])
|
b.process_block(1, [election])
|
||||||
|
|
||||||
app.block_transactions = votes
|
app.block_transactions = votes
|
||||||
|
|
||||||
|
|||||||
@ -288,10 +288,10 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys):
|
|||||||
assert not election.has_concluded(b, [tx_vote0, tx_vote1])
|
assert not election.has_concluded(b, [tx_vote0, tx_vote1])
|
||||||
assert election.has_concluded(b, [tx_vote0, tx_vote1, tx_vote2])
|
assert election.has_concluded(b, [tx_vote0, tx_vote1, tx_vote2])
|
||||||
|
|
||||||
assert Election.process_block(b, 4, [tx_vote0]) == []
|
assert b.process_block(4, [tx_vote0]) == []
|
||||||
assert Election.process_block(b, 4, [tx_vote0, tx_vote1]) == []
|
assert b.process_block(4, [tx_vote0, tx_vote1]) == []
|
||||||
|
|
||||||
update = Election.process_block(b, 4, [tx_vote0, tx_vote1, tx_vote2])
|
update = b.process_block(4, [tx_vote0, tx_vote1, tx_vote2])
|
||||||
assert len(update) == 1
|
assert len(update) == 1
|
||||||
update_public_key = codecs.encode(update[0].pub_key.ed25519, "base64").decode().rstrip("\n")
|
update_public_key = codecs.encode(update[0].pub_key.ed25519, "base64").decode().rstrip("\n")
|
||||||
assert update_public_key == public_key64
|
assert update_public_key == public_key64
|
||||||
@ -314,7 +314,7 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys):
|
|||||||
|
|
||||||
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
b.store_bulk_transactions([tx_vote0, tx_vote1])
|
||||||
|
|
||||||
update = Election.process_block(b, 9, [tx_vote2])
|
update = b.process_block(9, [tx_vote2])
|
||||||
assert len(update) == 1
|
assert len(update) == 1
|
||||||
update_public_key = codecs.encode(update[0].pub_key.ed25519, "base64").decode().rstrip("\n")
|
update_public_key = codecs.encode(update[0].pub_key.ed25519, "base64").decode().rstrip("\n")
|
||||||
assert update_public_key == public_key64
|
assert update_public_key == public_key64
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user