Skip to content

Commit

Permalink
Etcm 8983 fix get block author test (#361)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rsporny committed Jan 18, 2025
1 parent 2969fff commit c5bf5c9
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 83 deletions.
10 changes: 5 additions & 5 deletions E2E-tests/src/blockchain_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,14 +473,14 @@ def get_block(self, block_no: int) -> str:
pass

@abstractmethod
def extract_block_author(self, block, candidates_pub_keys) -> str:
"""
Searches for the author of a block in the provided candidate list. If not found returns False.
def get_block_author(self, block_number: int) -> str:
"""Gets the author of a block.
Arguments: Block (dict), List of candidates public keys
Arguments:
block_number {int} -- block number
Returns:
(string/False) - The public key of the author of the block
str -- block author public key
"""
pass

Expand Down
101 changes: 33 additions & 68 deletions E2E-tests/src/substrate_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .partner_chain_rpc import PartnerChainRpc, PartnerChainRpcResponse, PartnerChainRpcException, DParam
import string
import time
from scalecodec.base import RuntimeConfiguration
from scalecodec.base import RuntimeConfiguration, ScaleBytes


def _keypair_type_to_name(type):
Expand Down Expand Up @@ -650,73 +650,38 @@ def get_block(self, block_no):
block_hash = self.substrate.get_block_hash(block_no)
return self.substrate.get_block(block_hash)

def _block_header_encoder_and_signature_extractor(self, header: dict):
signature = False
header_encoded = bytes.fromhex(header["parentHash"][2:]).hex()
# Convert block number to compact
header["number"] = self.compact_encoder.encode(header["number"]).to_hex()
header_encoded += bytes.fromhex(header["number"][2:]).hex()
header_encoded += bytes.fromhex(header["stateRoot"][2:]).hex()
header_encoded += bytes.fromhex(header["extrinsicsRoot"][2:]).hex()
logs_encoded = ""
consensus_cnt = 0
consensus_encoded = ""
for log in header["digest"]["logs"]:
log = log.value_serialized
if "Seal" in log.keys():
# Do not include the signature in the encoded header.
# We want to hash the header and sign to get this signature
signature = log["Seal"][1]
elif "PreRuntime" in log.keys():
if is_hex(log["PreRuntime"][0]):
prefix = str(log["PreRuntime"][0])[2:]
else:
logger.error(f"PreRuntime key is not hex: {log['PreRuntime'][0]}")
return None, None
if is_hex(log["PreRuntime"][1]):
suffix = str(log["PreRuntime"][1])[2:]
else:
suffix = str(log["PreRuntime"][1]).encode("utf-8").hex()
suffix_length = str(hex(2 * len(suffix)))[2:]
logs_encoded += "06" + prefix + suffix_length + suffix
elif "Consensus" in log.keys():
consensus_cnt += 1
prefix = str(log["Consensus"][0])[2:]
suffix = str(log["Consensus"][1])[2:]
if "0100000000000000" in suffix: # Grandpa committee keys
suffix_prepend = self.config.block_encoding_suffix_grandpa
else: # Aura committee keys
suffix_prepend = self.config.block_encoding_suffix_aura
consensus_encoded += "04" + prefix + suffix_prepend + suffix
# Keep adding key to decode as the are added to the block header
if consensus_cnt == 0:
logs_prefix = "08"
elif consensus_cnt == 1:
logs_prefix = "0c"
elif consensus_cnt == 2:
logs_prefix = "10"
else:
logger.debug("New block type detected with more than 2 consensus logs. Please update encoder")
return False, False
header_encoded += logs_prefix + logs_encoded + consensus_encoded
return header_encoded, signature

def extract_block_author(self, block, candidates_pub_keys):
block_header = block["header"]
scale_header, signature = self._block_header_encoder_and_signature_extractor(block_header)
if not scale_header or not signature:
raise Exception(f'Could not encode header of block {block_header["number"]}')
header_hash = hashlib.blake2b(bytes.fromhex(scale_header), digest_size=32).hexdigest()

for pub_key in candidates_pub_keys:
keypair_public = Keypair(
ss58_address=self.substrate.ss58_encode(pub_key),
crypto_type=KeypairType.SR25519, # For our substrate implementation SR25519 is block authorship type
)
is_author = keypair_public.verify(bytes.fromhex(header_hash), bytes.fromhex(signature[2:]))
if is_author:
return pub_key
return None
def get_block_author(self, block_number):
"""Custom implementation of substrate.get_block(include_author=True) to get block author.
py-substrate-interface does not work because it calls "Validators" function from "Session" pallet,
which in our node is disabled and returns empty list. Here we use "ValidatorsAndKeys".
The function then iterates over "PreRuntime" logs and once it finds aura engine, it gets the slot
number and uses the result of modulo to get the author by index from the validator set.
Note: py-substrate-interface was also breaking at this point because we have another "PreRuntime" log
for mcsh engine (main chain hash) which is not supported by py-substrate-interface.
"""
block_data = self.get_block(block_number)
validator_set = self.substrate.query(
"Session", "ValidatorsAndKeys", block_hash=block_data["header"]["parentHash"]
)
for log_data in block_data["header"]["digest"]["logs"]:
engine = bytes(log_data[1][0])
if "PreRuntime" in log_data and engine == b'aura':
aura_predigest = self.substrate.runtime_config.create_scale_object(
type_string='RawAuraPreDigest', data=ScaleBytes(bytes(log_data[1][1]))
)

aura_predigest.decode(check_remaining=self.config.get("strict_scale_decode"))

rank_validator = aura_predigest.value["slot_number"] % len(validator_set)

block_author = validator_set[rank_validator]
block_data["author"] = block_author.value[1]["aura"]
break

if "author" not in block_data:
logger.error(f"Could not find author for block {block_number}. No PreRuntime log found with aura engine.")
return None
return block_data["author"]

def get_mc_hash_from_pc_block_header(self, block):
mc_hash_key = "0x6d637368"
Expand Down
10 changes: 2 additions & 8 deletions E2E-tests/tests/committee/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,10 @@ def test_block_authors_match_committee_seats(
for member in committee:
committee_block_auth_pub_keys.append(get_block_authorship_keys_dict[member["sidechainPubKey"]])

all_candidates_block_authoring_pub_keys = []
for candidate in config.nodes_config.nodes:
all_candidates_block_authoring_pub_keys.append(config.nodes_config.nodes[candidate].aura_public_key)

block_authors = []
for block_no in get_pc_epoch_blocks(pc_epoch)["range"]:
block_author = api.extract_block_author(
get_pc_epoch_blocks(pc_epoch)[block_no], all_candidates_block_authoring_pub_keys
)
assert block_author, f"Could not get author of block {block_no}. Please check decoder."
block_author = api.get_block_author(block_number=block_no)
assert block_author, f"Could not get author of block {block_no}."
assert (
block_author in committee_block_auth_pub_keys
), f"Block {block_no} was authored by non-committee member {block_author}"
Expand Down
4 changes: 2 additions & 2 deletions E2E-tests/utils/pc_epoch_calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
def mc_2_pc(mc_epoch: int):
epochs_range = calc.find_pc_epochs(mc_epoch)
print(
f"PC epochs range is {calc.range_in_math_notation(epochs_range)}, both included."
f"PC epochs range is {calc.range_in_math_notation(epochs_range)}, both included. "
f"It's {len(epochs_range)} epochs."
)
return epochs_range
Expand All @@ -46,7 +46,7 @@ def main():
mode = int(input("Which mode you want to enter? Type: (1) MC->PC, or (2) PC->MC\n"))
if mode == 1:
mc_epoch = int(input("Enter MC epoch: "))
mc_2_pc(mc_epoch, config)
mc_2_pc(mc_epoch)
elif mode == 2:
pc_epoch = int(input("Enter PC epoch: "))
current_mc_epoch = int(input("Enter current MC epoch: "))
Expand Down

0 comments on commit c5bf5c9

Please sign in to comment.