Skip to content

Commit c5bf5c9

Browse files
committed
Etcm 8983 fix get block author test (#361)
* 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 * test: update staging deployment
1 parent 2969fff commit c5bf5c9

File tree

4 files changed

+42
-83
lines changed

4 files changed

+42
-83
lines changed

E2E-tests/src/blockchain_api.py

+5-5
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

+33-68
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

+2-8
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,10 @@ def test_block_authors_match_committee_seats(
9797
for member in committee:
9898
committee_block_auth_pub_keys.append(get_block_authorship_keys_dict[member["sidechainPubKey"]])
9999

100-
all_candidates_block_authoring_pub_keys = []
101-
for candidate in config.nodes_config.nodes:
102-
all_candidates_block_authoring_pub_keys.append(config.nodes_config.nodes[candidate].aura_public_key)
103-
104100
block_authors = []
105101
for block_no in get_pc_epoch_blocks(pc_epoch)["range"]:
106-
block_author = api.extract_block_author(
107-
get_pc_epoch_blocks(pc_epoch)[block_no], all_candidates_block_authoring_pub_keys
108-
)
109-
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}."
110104
assert (
111105
block_author in committee_block_auth_pub_keys
112106
), f"Block {block_no} was authored by non-committee member {block_author}"

E2E-tests/utils/pc_epoch_calc.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
def mc_2_pc(mc_epoch: int):
2525
epochs_range = calc.find_pc_epochs(mc_epoch)
2626
print(
27-
f"PC epochs range is {calc.range_in_math_notation(epochs_range)}, both included."
27+
f"PC epochs range is {calc.range_in_math_notation(epochs_range)}, both included. "
2828
f"It's {len(epochs_range)} epochs."
2929
)
3030
return epochs_range
@@ -46,7 +46,7 @@ def main():
4646
mode = int(input("Which mode you want to enter? Type: (1) MC->PC, or (2) PC->MC\n"))
4747
if mode == 1:
4848
mc_epoch = int(input("Enter MC epoch: "))
49-
mc_2_pc(mc_epoch, config)
49+
mc_2_pc(mc_epoch)
5050
elif mode == 2:
5151
pc_epoch = int(input("Enter PC epoch: "))
5252
current_mc_epoch = int(input("Enter current MC epoch: "))

0 commit comments

Comments
 (0)