diff --git a/committee/committee/availability_gateway_client.py b/committee/committee/availability_gateway_client.py index 3696be8..d9d2470 100644 --- a/committee/committee/availability_gateway_client.py +++ b/committee/committee/availability_gateway_client.py @@ -31,6 +31,11 @@ def _send_request(self, send_method, uri, data=None): raise BadRequest(res.status_code, res.text) return res.text + async def order_tree_height(self) -> int: + uri = "/availability_gateway/order_tree_height" + answer = self._send_request("GET", uri) + return int(answer) + async def get_batch_data(self, batch_id: int) -> Optional[StateUpdate]: uri = f'/availability_gateway/get_batch_data?batch_id={batch_id}' answer = self._send_request('GET', uri) @@ -48,4 +53,4 @@ async def send_signature(self, batch_id: int, sig: str, member_key: str, claim_h logger.error(f'unexpected response: {answer}') assert False, 'Signature was not accepted' - logger.debug(f'Signature for batch {batch_id} was sent successfully') + logger.debug(f'Signature for batch {batch_id} was sent successfully') \ No newline at end of file diff --git a/committee/committee/committee.py b/committee/committee/committee.py index a868a7d..f7eb75e 100644 --- a/committee/committee/committee.py +++ b/committee/committee/committee.py @@ -21,7 +21,7 @@ from starkware.storage.merkle_tree import MerkleTree from starkware.storage.storage import Storage -from .availability_gateway_client import AvailabilityGatewayClient +from .availability_gateway_client import AvailabilityGatewayClient, BadRequest from .custom_validation import is_valid logger = logging.getLogger(__package__) @@ -44,7 +44,8 @@ def deserialize(cls, data: bytes) -> 'CommitteeBatchInfo': class Committee: def __init__(self, config: dict, private_key: str, storage: Storage, - merkle_storage: Storage, hash_func, availability_gateway): + merkle_storage: Storage, hash_func, + availability_gateway: AvailabilityGatewayClient): self.storage = storage self.merkle_storage = merkle_storage self.hash_func = hash_func @@ -145,11 +146,46 @@ async def compute_order_root(storage): await self.storage.set_value( self.committee_batch_info_key(batch_id), batch_info.serialize()) + # In StarkEx version 4.5, the height of the order tree has changed. For an old committee + # (i.e. a committee from version 4.0 or below) to work with a version 4.5 backend, the order + # tree height must be checked against the availability gateway, and possibly changed. + # If the configured height doesn't match the height sent in response from the availability + # gateway, assert that the order tree is not validated (self.validate_orders must be False + # to swap order tree heights, otherwise the computed order root is incorrect anyway). + # This patch doesn't affect the calculation of the order tree root, only the `trades_height` + # used for signing the batch. Therefore, the patch relies on the committee trusting the + # order root sent from the AvailabilityGateway (This means that it will only work if the + # committee is not validating orders). + # This patch will be deleted in the version 4.5 committee. + logger.info("Trying to fetch trades height from the availability gateway") + # If the API of order_tree_height exists in the Availability Gateway, use it. Otherwise, + # use ORDERS_MERKLE_HEIGHT from the config (this can happen if the SE + # Availability Gateway is using an old SE version which doesn't have the + # order_tree_height API). + trades_height = self.orders_merkle_height + try: + trades_height = await self.availability_gateway.order_tree_height() + logger.info( + f"Trades height received from the Availability Gateway is {trades_height}. The " + f"trades height which is defined in the config is {self.orders_merkle_height}." + ) + if self.orders_merkle_height != trades_height: + assert not validate_orders, ( + f"validate_orders is {validate_orders}, but configured trades height " + f"{self.orders_merkle_height} is not equal to response from the availability " + f"gateway ({trades_height}). This indicates that the root of the order " + f"tree was computed incorrectly and the claim will not be approved by the " + f"availability gateway, so there is no point in signing and sending the " + f"signature." + ) + except BadRequest: + pass + logger.info(f'Signing batch with sequence number {batch_info.sequence_number}') availability_claim = hash_availability_claim( batch_info.vaults_root, self.vaults_merkle_height, batch_info.orders_root, - self.orders_merkle_height, batch_info.sequence_number) + trades_height, batch_info.sequence_number) signature = eth.Account._sign_hash(availability_claim, self.account.key).signature.hex() return signature, availability_claim.hex() @@ -221,4 +257,4 @@ async def async_hash_func(x, y): if __name__ == '__main__': - sys.exit(asyncio.run(main())) + sys.exit(asyncio.run(main())) \ No newline at end of file diff --git a/committee/committee/committee_test.py b/committee/committee/committee_test.py index 8b6c1ca..d6f2153 100644 --- a/committee/committee/committee_test.py +++ b/committee/committee/committee_test.py @@ -10,11 +10,22 @@ from .committee import Committee +ORDER_TREE_HEIGHT = 63 + + +class AvailabilityGatewayClientMock: + def __init__(self): + pass + + async def order_tree_height(self) -> int: + return ORDER_TREE_HEIGHT + + @pytest.fixture def committee(): config = { 'VAULTS_MERKLE_HEIGHT': 31, - 'ORDERS_MERKLE_HEIGHT': 63, + 'ORDERS_MERKLE_HEIGHT': ORDER_TREE_HEIGHT, 'POLLING_INTERVAL': 1, } @@ -24,7 +35,7 @@ def committee(): storage=MockStorage(), merkle_storage=MockStorage(), hash_func=async_pedersen_hash_func, - availability_gateway=None) + availability_gateway=AvailabilityGatewayClientMock()) @pytest.fixture