Skip to content

Commit 793b17b

Browse files
committed
test: fix test_block_author
fixes: - test_block_authors_match_committee_seats was failing in some cases which wasn't handled properly, e.g. updated DParam or runtime upgrade. Now it's getting the author based on slot number and modulo operation. Refs: ETCM-8983
1 parent 3632997 commit 793b17b

File tree

3 files changed

+40
-82
lines changed

3 files changed

+40
-82
lines changed

E2E-tests/src/blockchain_api.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -473,14 +473,14 @@ def get_block(self, block_no: int) -> str:
473473
pass
474474

475475
@abstractmethod
476-
def extract_block_author(self, block, candidates_pub_keys) -> str:
477-
"""
478-
Searches for the author of a block in the provided candidate list. If not found returns False.
476+
def get_block_author(self, block_number: int) -> str:
477+
"""Gets the author of a block.
479478
480-
Arguments: Block (dict), List of candidates public keys
479+
Arguments:
480+
block_number {int} -- block number
481481
482482
Returns:
483-
(string/False) - The public key of the author of the block
483+
str -- block author public key
484484
"""
485485
pass
486486

E2E-tests/src/substrate_api.py

Lines changed: 33 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from .partner_chain_rpc import PartnerChainRpc, PartnerChainRpcResponse, PartnerChainRpcException, DParam
1818
import string
1919
import time
20-
from scalecodec.base import RuntimeConfiguration
20+
from scalecodec.base import RuntimeConfiguration, ScaleBytes
2121

2222

2323
def _keypair_type_to_name(type):
@@ -650,73 +650,38 @@ def get_block(self, block_no):
650650
block_hash = self.substrate.get_block_hash(block_no)
651651
return self.substrate.get_block(block_hash)
652652

653-
def _block_header_encoder_and_signature_extractor(self, header: dict):
654-
signature = False
655-
header_encoded = bytes.fromhex(header["parentHash"][2:]).hex()
656-
# Convert block number to compact
657-
header["number"] = self.compact_encoder.encode(header["number"]).to_hex()
658-
header_encoded += bytes.fromhex(header["number"][2:]).hex()
659-
header_encoded += bytes.fromhex(header["stateRoot"][2:]).hex()
660-
header_encoded += bytes.fromhex(header["extrinsicsRoot"][2:]).hex()
661-
logs_encoded = ""
662-
consensus_cnt = 0
663-
consensus_encoded = ""
664-
for log in header["digest"]["logs"]:
665-
log = log.value_serialized
666-
if "Seal" in log.keys():
667-
# Do not include the signature in the encoded header.
668-
# We want to hash the header and sign to get this signature
669-
signature = log["Seal"][1]
670-
elif "PreRuntime" in log.keys():
671-
if is_hex(log["PreRuntime"][0]):
672-
prefix = str(log["PreRuntime"][0])[2:]
673-
else:
674-
logger.error(f"PreRuntime key is not hex: {log['PreRuntime'][0]}")
675-
return None, None
676-
if is_hex(log["PreRuntime"][1]):
677-
suffix = str(log["PreRuntime"][1])[2:]
678-
else:
679-
suffix = str(log["PreRuntime"][1]).encode("utf-8").hex()
680-
suffix_length = str(hex(2 * len(suffix)))[2:]
681-
logs_encoded += "06" + prefix + suffix_length + suffix
682-
elif "Consensus" in log.keys():
683-
consensus_cnt += 1
684-
prefix = str(log["Consensus"][0])[2:]
685-
suffix = str(log["Consensus"][1])[2:]
686-
if "0100000000000000" in suffix: # Grandpa committee keys
687-
suffix_prepend = self.config.block_encoding_suffix_grandpa
688-
else: # Aura committee keys
689-
suffix_prepend = self.config.block_encoding_suffix_aura
690-
consensus_encoded += "04" + prefix + suffix_prepend + suffix
691-
# Keep adding key to decode as the are added to the block header
692-
if consensus_cnt == 0:
693-
logs_prefix = "08"
694-
elif consensus_cnt == 1:
695-
logs_prefix = "0c"
696-
elif consensus_cnt == 2:
697-
logs_prefix = "10"
698-
else:
699-
logger.debug("New block type detected with more than 2 consensus logs. Please update encoder")
700-
return False, False
701-
header_encoded += logs_prefix + logs_encoded + consensus_encoded
702-
return header_encoded, signature
703-
704-
def extract_block_author(self, block, candidates_pub_keys):
705-
block_header = block["header"]
706-
scale_header, signature = self._block_header_encoder_and_signature_extractor(block_header)
707-
if not scale_header or not signature:
708-
raise Exception(f'Could not encode header of block {block_header["number"]}')
709-
header_hash = hashlib.blake2b(bytes.fromhex(scale_header), digest_size=32).hexdigest()
710-
711-
for pub_key in candidates_pub_keys:
712-
keypair_public = Keypair(
713-
ss58_address=self.substrate.ss58_encode(pub_key),
714-
crypto_type=KeypairType.SR25519, # For our substrate implementation SR25519 is block authorship type
715-
)
716-
is_author = keypair_public.verify(bytes.fromhex(header_hash), bytes.fromhex(signature[2:]))
717-
if is_author:
718-
return pub_key
719-
return None
653+
def get_block_author(self, block_number):
654+
"""Custom implementation of substrate.get_block(include_author=True) to get block author.
655+
py-substrate-interface does not work because it calls "Validators" function from "Session" pallet,
656+
which in our node is disabled and returns empty list. Here we use "ValidatorsAndKeys".
657+
The function then iterates over "PreRuntime" logs and once it finds aura engine, it gets the slot
658+
number and uses the result of modulo to get the author by index from the validator set.
659+
Note: py-substrate-interface was also breaking at this point because we have another "PreRuntime" log
660+
for mcsh engine (main chain hash) which is not supported by py-substrate-interface.
661+
"""
662+
block_data = self.get_block(block_number)
663+
validator_set = self.substrate.query(
664+
"Session", "ValidatorsAndKeys", block_hash=block_data["header"]["parentHash"]
665+
)
666+
for log_data in block_data["header"]["digest"]["logs"]:
667+
engine = bytes(log_data[1][0])
668+
if "PreRuntime" in log_data and engine == b'aura':
669+
aura_predigest = self.substrate.runtime_config.create_scale_object(
670+
type_string='RawAuraPreDigest', data=ScaleBytes(bytes(log_data[1][1]))
671+
)
672+
673+
aura_predigest.decode(check_remaining=self.config.get("strict_scale_decode"))
674+
675+
rank_validator = aura_predigest.value["slot_number"] % len(validator_set)
676+
677+
block_author = validator_set[rank_validator]
678+
block_data["author"] = block_author.value[1]["aura"]
679+
break
680+
681+
if "author" not in block_data:
682+
logger.error(f"Could not find author for block {block_number}. No PreRuntime log found with aura engine.")
683+
return None
684+
return block_data["author"]
720685

721686
def get_mc_hash_from_pc_block_header(self, block):
722687
mc_hash_key = "0x6d637368"

E2E-tests/tests/committee/test_blocks.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def test_block_beneficiaries_match_committee_seats(
7272
@mark.skip_on_new_chain
7373
@mark.test_key('ETCM-7020')
7474
@mark.committee_rotation
75-
@mark.xfail(reason="ETCM-8983: block header encoding issue, we can't verify author.")
7675
def test_block_authors_match_committee_seats(
7776
api: BlockchainApi,
7877
config: ApiConfig,
@@ -98,16 +97,10 @@ def test_block_authors_match_committee_seats(
9897
for member in committee:
9998
committee_block_auth_pub_keys.append(get_block_authorship_keys_dict[member["sidechainPubKey"]])
10099

101-
all_candidates_block_authoring_pub_keys = []
102-
for candidate in config.nodes_config.nodes:
103-
all_candidates_block_authoring_pub_keys.append(config.nodes_config.nodes[candidate].aura_public_key)
104-
105100
block_authors = []
106101
for block_no in get_pc_epoch_blocks(pc_epoch)["range"]:
107-
block_author = api.extract_block_author(
108-
get_pc_epoch_blocks(pc_epoch)[block_no], all_candidates_block_authoring_pub_keys
109-
)
110-
assert block_author, f"Could not get author of block {block_no}. Please check decoder."
102+
block_author = api.get_block_author(block_number=block_no)
103+
assert block_author, f"Could not get author of block {block_no}."
111104
assert (
112105
block_author in committee_block_auth_pub_keys
113106
), f"Block {block_no} was authored by non-committee member {block_author}"

0 commit comments

Comments
 (0)