From 71ec1a02535a3190044afb7574d9d67ef9e1b78e Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Thu, 2 Nov 2023 11:28:15 +0100 Subject: [PATCH 01/12] Added support for OT-Node Token-based Auth --- dkg/providers/node_http.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/dkg/providers/node_http.py b/dkg/providers/node_http.py index 9523a00..3b7a1da 100644 --- a/dkg/providers/node_http.py +++ b/dkg/providers/node_http.py @@ -25,8 +25,9 @@ class NodeHTTPProvider: - def __init__(self, endpoint_uri: URI | str): + def __init__(self, endpoint_uri: URI | str, auth_token: str | None = None): self.endpoint_uri = URI(endpoint_uri) + self.auth_token = auth_token def make_request( self, @@ -37,26 +38,48 @@ def make_request( ) -> Response: request_func = getattr(self, method.name.lower()) + headers = self._prepare_headers() + match method: case HTTPRequestMethod.GET: - return request_func(path, params) + return request_func(path, params, headers) case HTTPRequestMethod.POST: - return request_func(path, data) + return request_func(path, data, headers) case HTTPRequestMethod(): raise HTTPRequestMethodNotSupported( f"{method.name} method isn't supported" ) - def get(self, path: str, params: dict[str, Any] = {}) -> NodeResponseDict: + def get( + self, path: str, params: dict[str, Any] = {}, headers: dict[str, str] = {} + ) -> NodeResponseDict: try: - response = requests.get(f"{self.endpoint_uri}/{path}", params=params) + response = requests.get( + f"{self.endpoint_uri}/{path}", + params=params, + headers=headers, + ) return NodeResponseDict(response.json()) except requests.exceptions.HTTPError as err: raise NodeRequestError(err) - def post(self, path: str, data: dict[str, Any] = {}) -> NodeResponseDict: + def post( + self, path: str, data: dict[str, Any] = {}, headers: dict[str, str] = {} + ) -> NodeResponseDict: try: - response = requests.post(f"{self.endpoint_uri}/{path}", json=data) + response = requests.post( + f"{self.endpoint_uri}/{path}", + json=data, + headers=headers, + ) return NodeResponseDict(response.json()) except requests.exceptions.HTTPError as err: raise NodeRequestError(err) + + def _prepare_headers(self) -> dict[str, str]: + return self._prepare_auth_header() + + def _prepare_auth_header(self) -> dict[str, str]: + if self.auth_token: + return {"Authorization": f"Bearer {self.auth_token}"} + return {} From 4db792c92bfa3188db9909bcd4eb1a54332ceefd Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Tue, 12 Dec 2023 12:02:43 +0100 Subject: [PATCH 02/12] Added 'is_valid_ual' function, config for gnosis blockchains, updated UAL parsing to support extended format, added automatic contracts update handling, updated BlockchainProvider __init__ to support new blockchain_id and handle default RPCs --- dkg/asset.py | 122 +++++++++++++++++++++++++++--------- dkg/constants.py | 54 ++++++++++++---- dkg/exceptions.py | 16 +++++ dkg/network.py | 6 +- dkg/providers/blockchain.py | 108 ++++++++++++++++++++++++------- dkg/types/__init__.py | 2 +- dkg/types/blockchain.py | 5 +- dkg/utils/ual.py | 5 +- pyproject.toml | 2 +- 9 files changed, 247 insertions(+), 73 deletions(-) diff --git a/dkg/asset.py b/dkg/asset.py index 9967927..b150aa0 100644 --- a/dkg/asset.py +++ b/dkg/asset.py @@ -21,10 +21,10 @@ from pyld import jsonld from web3 import Web3 -from web3.constants import HASH_ZERO +from web3.constants import ADDRESS_ZERO, HASH_ZERO from web3.exceptions import ContractLogicError -from dkg.constants import (BLOCKCHAINS, DEFAULT_HASH_FUNCTION_ID, +from dkg.constants import (DEFAULT_HASH_FUNCTION_ID, DEFAULT_SCORE_FUNCTION_ID, PRIVATE_ASSERTION_PREDICATE, PRIVATE_CURRENT_REPOSITORY, @@ -54,6 +54,74 @@ class ContentAsset(Module): def __init__(self, manager: DefaultRequestManager): self.manager = manager + _owner = Method(BlockchainRequest.owner_of) + + def is_valid_ual(self, ual: UAL) -> bool: + if not ual or not isinstance(ual, str): + raise ValueError("UAL must be a non-empty string.") + + parts = ual.split("/") + if len(parts) != 3: + raise ValueError("UAL format is incorrect.") + + prefixes = parts[0].split(":") + prefixes_number = len(prefixes) + if prefixes_number != 3 and prefixes_number != 4: + raise ValueError("Prefix format in UAL is incorrect.") + + if prefixes[0] != "did": + raise ValueError( + f"Invalid DID prefix. Expected: 'did'. Received: '{prefixes[0]}'." + ) + + if prefixes[1] != "dkg": + raise ValueError( + f"Invalid DKG prefix. Expected: 'dkg'. Received: '{prefixes[1]}'." + ) + + if ( + prefixes[2] != ( + blockchain_name := (self.manager + .blockchain_provider + .blockchain_id + .split(":")[0]) + ) + ): + raise ValueError( + "Invalid blockchain name in the UAL prefix. " + f"Expected: '{blockchain_name}'. Received: '${prefixes[2]}'." + ) + + if prefixes_number == 4: + chain_id = self.manager.blockchain_provider.blockchain_id.split(":")[1] + + if int(prefixes[3]) != int(chain_id): + raise ValueError( + "Chain ID in UAL does not match the blockchain. " + f"Expected: '${chain_id}'. Received: '${prefixes[3]}'." + ) + + contract_address: Address = self.manager.blockchain_provider.contracts[ + "ContentAssetStorage" + ] + + if parts[1].lower() != contract_address.lower(): + raise ValueError( + "Contract address in UAL does not match. " + f"Expected: '${contract_address.lower()}'. " + f"Received: '${parts[1].lower()}'." + ) + + try: + owner = self._owner(parts[2]) + + if not owner or owner == ADDRESS_ZERO: + raise ValueError("Token does not exist or has no owner.") + + return True + except Exception as err: + raise ValueError(f"Error fetching asset owner: {err}") + _get_contract_address = Method(BlockchainRequest.get_contract_address) _get_current_allowance = Method(BlockchainRequest.allowance) @@ -127,6 +195,7 @@ def create( immutable: bool = False, content_type: Literal["JSON-LD", "N-Quads"] = "JSON-LD", ) -> dict[str, HexStr | dict[str, str]]: + blockchain_id = self.manager.blockchain_provider.blockchain_id assertions = format_content(content, content_type) public_assertion_id = MerkleTree( @@ -135,7 +204,6 @@ def create( ).root public_assertion_metadata = generate_assertion_metadata(assertions["public"]) - chain_name = BLOCKCHAINS[self._chain_id()]["name"] content_asset_storage_address = self._get_asset_storage_address( "ContentAssetStorage" ) @@ -143,7 +211,7 @@ def create( if token_amount is None: token_amount = int( self._get_bid_suggestion( - chain_name, + blockchain_id, epochs_number, public_assertion_metadata["size"], content_asset_storage_address, @@ -183,7 +251,7 @@ def create( assertions_list = [ { - "blockchain": chain_name, + "blockchain": blockchain_id, "contract": content_asset_storage_address, "tokenId": token_id, "assertionId": public_assertion_id, @@ -195,7 +263,7 @@ def create( if content.get("private", None): assertions_list.append( { - "blockchain": chain_name, + "blockchain": blockchain_id, "contract": content_asset_storage_address, "tokenId": token_id, "assertionId": MerkleTree( @@ -213,7 +281,7 @@ def create( operation_id = self._publish( public_assertion_id, assertions["public"], - chain_name, + blockchain_id, content_asset_storage_address, token_id, DEFAULT_HASH_FUNCTION_ID, @@ -221,7 +289,7 @@ def create( operation_result = self.get_operation_result(operation_id, "publish") return { - "UAL": format_ual(chain_name, content_asset_storage_address, token_id), + "UAL": format_ual(blockchain_id, content_asset_storage_address, token_id), "publicAssertionId": public_assertion_id, "operation": { "operationId": operation_id, @@ -265,7 +333,8 @@ def update( content_type: Literal["JSON-LD", "N-Quads"] = "JSON-LD", ) -> dict[str, HexStr | dict[str, str]]: parsed_ual = parse_ual(ual) - content_asset_storage_address, token_id = ( + blockchain_id, content_asset_storage_address, token_id = ( + parsed_ual["blockchain"], parsed_ual["contract_address"], parsed_ual["token_id"], ) @@ -278,8 +347,6 @@ def update( ).root public_assertion_metadata = generate_assertion_metadata(assertions["public"]) - chain_name = BLOCKCHAINS[self._chain_id()]["name"] - if token_amount is None: agreement_id = self.get_agreement_id( content_asset_storage_address, token_id @@ -297,7 +364,7 @@ def update( token_amount = int( self._get_bid_suggestion( - chain_name, + blockchain_id, epochs_left, public_assertion_metadata["size"], content_asset_storage_address, @@ -329,7 +396,7 @@ def update( assertions_list = [ { - "blockchain": chain_name, + "blockchain": blockchain_id, "contract": content_asset_storage_address, "tokenId": token_id, "assertionId": public_assertion_id, @@ -341,7 +408,7 @@ def update( if content.get("private", None): assertions_list.append( { - "blockchain": chain_name, + "blockchain": blockchain_id, "contract": content_asset_storage_address, "tokenId": token_id, "assertionId": MerkleTree( @@ -359,7 +426,7 @@ def update( operation_id = self._update( public_assertion_id, assertions["public"], - chain_name, + blockchain_id, content_asset_storage_address, token_id, DEFAULT_HASH_FUNCTION_ID, @@ -367,7 +434,7 @@ def update( operation_result = self.get_operation_result(operation_id, "update") return { - "UAL": format_ual(chain_name, content_asset_storage_address, token_id), + "UAL": format_ual(blockchain_id, content_asset_storage_address, token_id), "publicAssertionId": public_assertion_id, "operation": { "operationId": operation_id, @@ -638,14 +705,13 @@ def extend_storing_period( token_amount: Wei | None = None, ) -> dict[str, UAL | dict[str, str]]: parsed_ual = parse_ual(ual) - content_asset_storage_address, token_id = ( + blockchain_id, content_asset_storage_address, token_id = ( + parsed_ual["blockchain"], parsed_ual["contract_address"], parsed_ual["token_id"], ) if token_amount is None: - chain_name = BLOCKCHAINS[self._chain_id()]["name"] - latest_finalized_state = self._get_latest_assertion_id(token_id) latest_finalized_state_size = self._get_assertion_size( latest_finalized_state @@ -653,7 +719,7 @@ def extend_storing_period( token_amount = int( self._get_bid_suggestion( - chain_name, + blockchain_id, additional_epochs, latest_finalized_state_size, content_asset_storage_address, @@ -678,14 +744,13 @@ def add_tokens( token_amount: Wei | None = None, ) -> dict[str, UAL | dict[str, str]]: parsed_ual = parse_ual(ual) - content_asset_storage_address, token_id = ( + blockchain_id, content_asset_storage_address, token_id = ( + parsed_ual["blockchain"], parsed_ual["contract_address"], parsed_ual["token_id"], ) if token_amount is None: - chain_name = BLOCKCHAINS[self._chain_id()]["name"] - agreement_id = self.get_agreement_id( content_asset_storage_address, token_id ) @@ -707,7 +772,7 @@ def add_tokens( token_amount = int( self._get_bid_suggestion( - chain_name, + blockchain_id, epochs_left, latest_finalized_state_size, content_asset_storage_address, @@ -738,14 +803,13 @@ def add_update_tokens( token_amount: Wei | None = None, ) -> dict[str, UAL | dict[str, str]]: parsed_ual = parse_ual(ual) - content_asset_storage_address, token_id = ( + blockchain_id, content_asset_storage_address, token_id = ( + parsed_ual["blockchain"], parsed_ual["contract_address"], parsed_ual["token_id"], ) if token_amount is None: - chain_name = BLOCKCHAINS[self._chain_id()]["name"] - agreement_id = self.get_agreement_id( content_asset_storage_address, token_id ) @@ -765,7 +829,7 @@ def add_update_tokens( token_amount = int( self._get_bid_suggestion( - chain_name, + blockchain_id, epochs_left, unfinalized_state_size, content_asset_storage_address, @@ -788,8 +852,6 @@ def add_update_tokens( "operation": {"status": "COMPLETED"}, } - _owner = Method(BlockchainRequest.owner_of) - def get_owner(self, ual: UAL) -> Address: token_id = parse_ual(ual)["token_id"] diff --git a/dkg/constants.py b/dkg/constants.py index 3bd66ec..6abbbb5 100644 --- a/dkg/constants.py +++ b/dkg/constants.py @@ -20,23 +20,51 @@ ) BLOCKCHAINS = { - 2043: { - "name": "otp", - "hubAddress": "0x5fA7916c48Fe6D5F1738d12Ad234b78c90B4cAdA" + "development": { + "hardhat1:31337": { + "hubAddress": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "rpc": "http://localhost:8545", }, - 2160: { - "name": "otp", - "hubAddress": "0x833048F6e6BEa78E0AAdedeCd2Dc2231dda443FB" + "hardhat2:31337": { + "hubAddress": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "rpc": "http://localhost:9545", }, - 20430: { - "name": "otp", - "hubAddress": "0xBbfF7Ea6b2Addc1f38A0798329e12C08f03750A6" + "otp:2043": { + "hubAddress": "0x7585a99C5C150a08f5CDeFD16465C6De8D41EbbD", + "rpc": "http://parachain-alphanet-02.origin-trail.network:9933", }, - 31337: { - "name": "hardhat", - "hubAddress": "0x5FbDB2315678afecb367f032d93F642f64180aa3" + }, + "devnet": { + "otp:2160": { + "hubAddress": "0x833048F6e6BEa78E0AAdedeCd2Dc2231dda443FB", + "rpc": "https://lofar-tm-rpc.origin-trail.network", }, - } + "gnosis:10200": { + "hubAddress": "0xD2bA102A0b11944d00180eE8136208ccF87bC39A", + "rpc": "https://rpc.chiadochain.net", + }, + }, + "testnet": { + "otp:20430": { + "hubAddress": "0xBbfF7Ea6b2Addc1f38A0798329e12C08f03750A6", + "rpc": "https://lofar-testnet.origin-trail.network", + }, + "gnosis:10200": { + "hubAddress": "0xC06210312C9217A0EdF67453618F5eB96668679A", + "rpc": "https://rpc.chiadochain.net", + }, + }, + "mainnet": { + "otp:2043": { + "hubAddress": "0x5fA7916c48Fe6D5F1738d12Ad234b78c90B4cAdA", + "rpc": "https://astrosat-parachain-rpc.origin-trail.network", + }, + "gnosis:100": { + "hubAddress": "", + "rpc": "https://rpc.gnosischain.com/", + }, + }, +} DEFAULT_HASH_FUNCTION_ID = 1 DEFAULT_SCORE_FUNCTION_ID = 1 diff --git a/dkg/exceptions.py b/dkg/exceptions.py index 70033de..4e0cf19 100644 --- a/dkg/exceptions.py +++ b/dkg/exceptions.py @@ -28,6 +28,22 @@ class DKGException(Exception): """ +class EnvironmentNotSupported(DKGException): + """ + Raised when blockchain provider is initialized for unsupported environment. + """ + + pass + + +class RPCURINotDefined(DKGException): + """ + Raised when blockchain provider is initialized without RPC URI defined. + """ + + pass + + class NetworkNotSupported(DKGException): """ Raised when blockchain provider is initialized for unsupported network. diff --git a/dkg/network.py b/dkg/network.py index 7c9c0b8..25da8ae 100644 --- a/dkg/network.py +++ b/dkg/network.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -from dkg.constants import BLOCKCHAINS, DEFAULT_HASH_FUNCTION_ID +from dkg.constants import DEFAULT_HASH_FUNCTION_ID from dkg.manager import DefaultRequestManager from dkg.method import Method from dkg.module import Module @@ -28,7 +28,6 @@ class Network(Module): def __init__(self, manager: DefaultRequestManager): self.manager = manager - _chain_id = Method(BlockchainRequest.chain_id) _get_asset_storage_address = Method(BlockchainRequest.get_asset_storage_address) _get_bid_suggestion = Method(NodeRequest.bid_suggestion) @@ -36,14 +35,13 @@ def __init__(self, manager: DefaultRequestManager): def get_bid_suggestion( self, public_assertion_id: DataHexStr, size_in_bytes: int, epochs_number: int, ) -> int: - chain_name = BLOCKCHAINS[self._chain_id()]["name"] content_asset_storage_address = self._get_asset_storage_address( "ContentAssetStorage" ) return int( self._get_bid_suggestion( - chain_name, + self.manager.blockchain_provider.blockchain_id, epochs_number, size_in_bytes, content_asset_storage_address, diff --git a/dkg/providers/blockchain.py b/dkg/providers/blockchain.py index eba5d2f..de68b36 100644 --- a/dkg/providers/blockchain.py +++ b/dkg/providers/blockchain.py @@ -17,10 +17,13 @@ import json from collections import namedtuple +from functools import wraps from pathlib import Path from typing import Any, Type -from dkg.exceptions import AccountMissing, NetworkNotSupported +from dkg.exceptions import ( + AccountMissing, EnvironmentNotSupported, NetworkNotSupported, RPCURINotDefined +) from dkg.types import URI, Address, DataHexStr, Wei from eth_account.signers.local import LocalAccount from web3 import Web3 @@ -28,7 +31,7 @@ from web3.contract.contract import ContractFunction from web3.logs import DISCARD from web3.middleware import construct_sign_and_send_raw_middleware -from web3.types import ABI, ABIFunction, TxReceipt +from web3.types import ABI, ABIFunction, Environment, TxReceipt from dkg.constants import BLOCKCHAINS @@ -39,20 +42,53 @@ class BlockchainProvider: def __init__( self, - rpc_uri: URI, + environment: Environment, + blockchain_id: str, + rpc_uri: URI | None = None, private_key: DataHexStr | None = None, verify: bool = True, ): - self.w3 = Web3(Web3.HTTPProvider(rpc_uri, request_kwargs={"verify": verify})) + if environment not in BLOCKCHAINS.keys(): + raise EnvironmentNotSupported( + f"Environment {environment} isn't supported!" + ) + + self.environment = environment + self.rpc_uri = rpc_uri + self.blockchain_id = ( + blockchain_id + if blockchain_id in BLOCKCHAINS[self.environment].keys() + else None + ) + + if self.rpc_uri is None and self.blockchain_id is not None: + self.blockchain_id = blockchain_id + self.rpc_uri = ( + self.rpc_uri or + BLOCKCHAINS[self.environment][self.blockchain_id].get("rpc", None) + ) - if (chain_id := self.w3.eth.chain_id) not in BLOCKCHAINS.keys(): - raise NetworkNotSupported( - f"Network with chain ID {chain_id} isn't supported!" + if self.rpc_uri is None: + raise RPCURINotDefined( + "No RPC URI provided for unrecognized " + f"blockchain ID {self.blockchain_id}" ) - hub_address: Address = BLOCKCHAINS[chain_id]["hubAddress"] + + self.w3 = Web3( + Web3.HTTPProvider(self.rpc_uri, request_kwargs={"verify": verify}) + ) + + if self.blockchain_id is None: + self.blockchain_id = f"{blockchain_id}:{self.w3.eth.chain_id}" + if self.blockchain_id not in BLOCKCHAINS[self.environment]: + raise NetworkNotSupported( + f"Network with blockchain ID {self.blockchain_id} isn't supported!" + ) self.abi = self._load_abi() self.output_named_tuples = self._generate_output_named_tuples() + + hub_address: Address = BLOCKCHAINS[self.blockchain_id]["hubAddress"] self.contracts: dict[str, Contract] = { "Hub": self.w3.eth.contract( address=hub_address, @@ -72,6 +108,28 @@ def make_json_rpc_request(self, endpoint: str, args: dict[str, Any] = {}) -> Any else: return web3_method + @staticmethod + def handle_updated_contract(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + contract_name = ( + kwargs.get('contract_name') or (args[0] if args else None) + ) + + try: + return func(self, *args, **kwargs) + except Exception as err: + if ( + contract_name and + any(msg in str(err) for msg in ["revert", "VM Exception"]) and + not self._check_contract_status(contract_name) + ): + self._update_contract_instance(contract_name) + return func(self, *args, **kwargs) + raise + return wrapper + + @handle_updated_contract def call_function( self, contract: str, @@ -135,20 +193,26 @@ def _init_contracts(self): if contract == "Hub": continue - self.contracts[contract] = self.w3.eth.contract( - address=( - self.contracts["Hub"] - .functions.getContractAddress( - contract if contract != "ERC20Token" else "Token" - ) - .call() - if not contract.endswith("AssetStorage") - else self.contracts["Hub"] - .functions.getAssetStorageAddress(contract) - .call() - ), - abi=self.abi[contract], - ) + self._update_contract_instance(contract) + + def _update_contract_instance(self, contract: str): + self.contracts[contract] = self.w3.eth.contract( + address=( + self.contracts["Hub"] + .functions.getContractAddress( + contract if contract != "ERC20Token" else "Token" + ) + .call() + if not contract.endswith("AssetStorage") + else self.contracts["Hub"] + .functions.getAssetStorageAddress(contract) + .call() + ), + abi=self.abi[contract], + ) + + def _check_contract_status(self, contract: str) -> bool: + return self.call_function(contract, "status") def _generate_output_named_tuples(self) -> dict[str, dict[str, Type[tuple]]]: def generate_output_namedtuple(function_abi: ABIFunction) -> Type[tuple] | None: diff --git a/dkg/types/__init__.py b/dkg/types/__init__.py index a3076b2..25e79fb 100644 --- a/dkg/types/__init__.py +++ b/dkg/types/__init__.py @@ -1,5 +1,5 @@ from .blockchain import (ABI, ABIElement, ABIError, ABIEvent, # NOQA: F401 - ABIFunction, ABIParameter, AgreementData) + ABIFunction, ABIParameter, AgreementData, Environment) from .dkg_node import UAL # NOQA: F401 from .encoding import BytesLike, DataHexStr, HexStr # NOQA: F401 from .evm import Address, ChecksumAddress, Wei # NOQA: F401 diff --git a/dkg/types/blockchain.py b/dkg/types/blockchain.py index 617b92c..57a40b0 100644 --- a/dkg/types/blockchain.py +++ b/dkg/types/blockchain.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -from typing import NamedTuple, TypedDict +from typing import Literal, NamedTuple, TypedDict class ABIParameter(TypedDict, total=False): @@ -50,6 +50,9 @@ class ABIError(TypedDict): ABI = list[ABIElement] +Environment = Literal["development", "devnet", "testnet", "mainnet"] + + class AgreementData(NamedTuple): startTime: int epochsNumber: int diff --git a/dkg/utils/ual.py b/dkg/utils/ual.py index 98ae4c3..61ceb65 100644 --- a/dkg/utils/ual.py +++ b/dkg/utils/ual.py @@ -27,7 +27,10 @@ def format_ual( def parse_ual(ual: UAL) -> dict[str, str | Address | int]: - args = ual.split(":")[-1].split("/") + if ual.startswith("did:dkg:"): + raise ValidationError("Invalid UAL!") + + args: list[str] = ual.replace("did:dkg:").split("/") if len(args) != 3: raise ValidationError("Invalid UAL!") diff --git a/pyproject.toml b/pyproject.toml index 32e24ef..b1bed9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "dkg" -version = "0.1.0-beta.2" +version = "0.1.0-beta.3" description = "Python library for interacting with the OriginTrail Decentralized Knowledge Graph" authors = ["Uladzislau Hubar "] license = "Apache-2.0" From bfacaa1c6de49fb2634900a13953f2ba08cef60c Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Wed, 13 Dec 2023 11:23:44 +0100 Subject: [PATCH 03/12] Added oracle gas price estimation for gnosis --- dkg/asset.py | 20 ++++++---------- dkg/constants.py | 23 +++++++++++-------- dkg/providers/blockchain.py | 46 +++++++++++++++++++++++++++---------- 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/dkg/asset.py b/dkg/asset.py index b150aa0..f13d018 100644 --- a/dkg/asset.py +++ b/dkg/asset.py @@ -24,8 +24,7 @@ from web3.constants import ADDRESS_ZERO, HASH_ZERO from web3.exceptions import ContractLogicError -from dkg.constants import (DEFAULT_HASH_FUNCTION_ID, - DEFAULT_SCORE_FUNCTION_ID, +from dkg.constants import (DEFAULT_HASH_FUNCTION_ID, DEFAULT_SCORE_FUNCTION_ID, PRIVATE_ASSERTION_PREDICATE, PRIVATE_CURRENT_REPOSITORY, PRIVATE_HISTORICAL_REPOSITORY) @@ -79,12 +78,9 @@ def is_valid_ual(self, ual: UAL) -> bool: f"Invalid DKG prefix. Expected: 'dkg'. Received: '{prefixes[1]}'." ) - if ( - prefixes[2] != ( - blockchain_name := (self.manager - .blockchain_provider - .blockchain_id - .split(":")[0]) + if prefixes[2] != ( + blockchain_name := ( + self.manager.blockchain_provider.blockchain_id.split(":")[0] ) ): raise ValueError( @@ -138,9 +134,7 @@ def get_current_allowance(self, spender: Address | None = None) -> Wei: _increase_allowance = Method(BlockchainRequest.increase_allowance) _decrease_allowance = Method(BlockchainRequest.decrease_allowance) - def set_allowance( - self, token_amount: Wei, spender: Address | None = None - ) -> Wei: + def set_allowance(self, token_amount: Wei, spender: Address | None = None) -> Wei: if spender is None: spender = self._get_contract_address("ServiceAgreementV1") @@ -221,7 +215,7 @@ def create( ) current_allowance = self.get_current_allowance() - if (is_allowance_increased := current_allowance < token_amount): + if is_allowance_increased := current_allowance < token_amount: self.increase_allowance(token_amount) try: @@ -377,7 +371,7 @@ def update( token_amount = token_amount if token_amount > 0 else 0 current_allowance = self.get_current_allowance() - if (is_allowance_increased := current_allowance < token_amount): + if is_allowance_increased := current_allowance < token_amount: self.increase_allowance(token_amount) try: diff --git a/dkg/constants.py b/dkg/constants.py index 6abbbb5..dbf0e4a 100644 --- a/dkg/constants.py +++ b/dkg/constants.py @@ -22,50 +22,55 @@ BLOCKCHAINS = { "development": { "hardhat1:31337": { - "hubAddress": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "hub": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "rpc": "http://localhost:8545", }, "hardhat2:31337": { - "hubAddress": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "hub": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "rpc": "http://localhost:9545", }, "otp:2043": { - "hubAddress": "0x7585a99C5C150a08f5CDeFD16465C6De8D41EbbD", + "hub": "0x7585a99C5C150a08f5CDeFD16465C6De8D41EbbD", "rpc": "http://parachain-alphanet-02.origin-trail.network:9933", }, }, "devnet": { "otp:2160": { - "hubAddress": "0x833048F6e6BEa78E0AAdedeCd2Dc2231dda443FB", + "hub": "0x833048F6e6BEa78E0AAdedeCd2Dc2231dda443FB", "rpc": "https://lofar-tm-rpc.origin-trail.network", }, "gnosis:10200": { - "hubAddress": "0xD2bA102A0b11944d00180eE8136208ccF87bC39A", + "hub": "0xD2bA102A0b11944d00180eE8136208ccF87bC39A", "rpc": "https://rpc.chiadochain.net", + "gas_price_oracle": "https://blockscout.chiadochain.net/api/v1/gas-price-oracle", }, }, "testnet": { "otp:20430": { - "hubAddress": "0xBbfF7Ea6b2Addc1f38A0798329e12C08f03750A6", + "hub": "0xBbfF7Ea6b2Addc1f38A0798329e12C08f03750A6", "rpc": "https://lofar-testnet.origin-trail.network", }, "gnosis:10200": { - "hubAddress": "0xC06210312C9217A0EdF67453618F5eB96668679A", + "hub": "0xC06210312C9217A0EdF67453618F5eB96668679A", "rpc": "https://rpc.chiadochain.net", + "gas_price_oracle": "https://blockscout.chiadochain.net/api/v1/gas-price-oracle", }, }, "mainnet": { "otp:2043": { - "hubAddress": "0x5fA7916c48Fe6D5F1738d12Ad234b78c90B4cAdA", + "hub": "0x5fA7916c48Fe6D5F1738d12Ad234b78c90B4cAdA", "rpc": "https://astrosat-parachain-rpc.origin-trail.network", }, "gnosis:100": { - "hubAddress": "", + "hub": "", "rpc": "https://rpc.gnosischain.com/", + "gas_price_oracle": "https://api.gnosisscan.io/api?module=proxy&action=eth_gasPrice", }, }, } +DEFAULT_GAS_PRICE_GWEI = 100 + DEFAULT_HASH_FUNCTION_ID = 1 DEFAULT_SCORE_FUNCTION_ID = 1 diff --git a/dkg/providers/blockchain.py b/dkg/providers/blockchain.py index de68b36..beb3952 100644 --- a/dkg/providers/blockchain.py +++ b/dkg/providers/blockchain.py @@ -16,6 +16,7 @@ # under the License. import json +import requests from collections import namedtuple from functools import wraps from pathlib import Path @@ -32,14 +33,12 @@ from web3.logs import DISCARD from web3.middleware import construct_sign_and_send_raw_middleware from web3.types import ABI, ABIFunction, Environment, TxReceipt -from dkg.constants import BLOCKCHAINS +from dkg.constants import BLOCKCHAINS, DEFAULT_GAS_PRICE_GWEI class BlockchainProvider: CONTRACTS_METADATA_DIR = Path(__file__).parents[1] / "data/interfaces" - GAS_BUFFER = 1.2 # 20% gas buffer for estimateGas() - def __init__( self, environment: Environment, @@ -85,10 +84,15 @@ def __init__( f"Network with blockchain ID {self.blockchain_id} isn't supported!" ) + self.gas_price_oracle = BLOCKCHAINS[self.environment][self.blockchain_id].get( + "gas_price_oracle", + None, + ) + self.abi = self._load_abi() self.output_named_tuples = self._generate_output_named_tuples() - hub_address: Address = BLOCKCHAINS[self.blockchain_id]["hubAddress"] + hub_address: Address = BLOCKCHAINS[self.blockchain_id]["hub"] self.contracts: dict[str, Contract] = { "Hub": self.w3.eth.contract( address=hub_address, @@ -157,15 +161,17 @@ def call_function( ) nonce = self.w3.eth.get_transaction_count(self.w3.eth.default_account) - gas_price = gas_price if gas_price is not None else self.w3.eth.gas_price - - options = {"nonce": nonce, "gasPrice": gas_price} - gas_estimate = contract_function(**args).estimate_gas(options) + gas_price = ( + gas_price or + self._get_oracle_gas_price() or + self.w3.eth.gas_price + ) - if gas_limit is None: - options["gas"] = int(gas_estimate * self.GAS_BUFFER) - else: - options["gas"] = gas_limit + options = { + "nonce": nonce, + "gasPrice": gas_price, + "gas": gas_limit or contract_function(**args).estimate_gas() + } tx_hash = contract_function(**args).transact(options) tx_receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash) @@ -188,6 +194,22 @@ def set_account(self, private_key: DataHexStr): ) self.w3.eth.default_account = self.account.address + def _get_oracle_gas_price(self) -> int | None: + if self.gas_price_oracle is None: + return None + + response_json = requests.get(self.gas_price_oracle).json() + + match self.blockchain_id: + case "gnosis:100": + gas_price = int(response_json["result"], 16) + case "gnosis:10200": + gas_price = self.w3.to_wei(response_json["average"], "gwei") + case _: + gas_price = self.w3.to_wei(DEFAULT_GAS_PRICE_GWEI, "gwei") + + return gas_price + def _init_contracts(self): for contract in self.abi.keys(): if contract == "Hub": From 359f92980264f69b1c3f6bfbfb1546ff65bf145e Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Wed, 13 Dec 2023 11:34:14 +0100 Subject: [PATCH 04/12] Updated ABI files --- dkg/data/interfaces/Assertion.json | 26 +++++++ dkg/data/interfaces/CommitManagerV1.json | 37 ++++++--- dkg/data/interfaces/CommitManagerV1U1.json | 37 ++++++--- dkg/data/interfaces/ContentAssetStorage.json | 76 +++++++++++++++++++ dkg/data/interfaces/HashingProxy.json | 26 +++++++ dkg/data/interfaces/Hub.json | 4 +- dkg/data/interfaces/Identity.json | 26 +++++++ dkg/data/interfaces/ParametersStorage.json | 19 +++++ dkg/data/interfaces/Profile.json | 26 +++++++ dkg/data/interfaces/ProfileStorage.json | 6 +- dkg/data/interfaces/ProofManagerV1.json | 41 ++++++---- dkg/data/interfaces/ProofManagerV1U1.json | 41 ++++++---- dkg/data/interfaces/ScoringProxy.json | 26 +++++++ .../ServiceAgreementStorageProxy.json | 39 +++++++--- .../interfaces/ServiceAgreementStorageV1.json | 10 +-- .../ServiceAgreementStorageV1U1.json | 10 +-- dkg/data/interfaces/ServiceAgreementV1.json | 52 +++++++------ dkg/data/interfaces/ShardingTable.json | 26 +++++++ dkg/data/interfaces/Staking.json | 26 +++++++ dkg/data/interfaces/Token.json | 76 +++++++++++++++---- 20 files changed, 515 insertions(+), 115 deletions(-) diff --git a/dkg/data/interfaces/Assertion.json b/dkg/data/interfaces/Assertion.json index 98e389a..a33fbbb 100644 --- a/dkg/data/interfaces/Assertion.json +++ b/dkg/data/interfaces/Assertion.json @@ -115,6 +115,32 @@ "stateMutability": "pure", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/dkg/data/interfaces/CommitManagerV1.json b/dkg/data/interfaces/CommitManagerV1.json index 65f79cb..f3d59a8 100644 --- a/dkg/data/interfaces/CommitManagerV1.json +++ b/dkg/data/interfaces/CommitManagerV1.json @@ -124,17 +124,6 @@ "name": "NodeNotInShardingTable", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "OnlyHubOwnerFunction", - "type": "error" - }, { "inputs": [ { @@ -439,6 +428,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "shardingTableStorage", @@ -478,6 +480,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/dkg/data/interfaces/CommitManagerV1U1.json b/dkg/data/interfaces/CommitManagerV1U1.json index fce7057..f169e94 100644 --- a/dkg/data/interfaces/CommitManagerV1U1.json +++ b/dkg/data/interfaces/CommitManagerV1U1.json @@ -155,17 +155,6 @@ "name": "NodeNotInShardingTable", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "OnlyHubOwnerFunction", - "type": "error" - }, { "inputs": [ { @@ -572,6 +561,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "shardingTableStorage", @@ -611,6 +613,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/dkg/data/interfaces/ContentAssetStorage.json b/dkg/data/interfaces/ContentAssetStorage.json index b931831..418789e 100644 --- a/dkg/data/interfaces/ContentAssetStorage.json +++ b/dkg/data/interfaces/ContentAssetStorage.json @@ -10,6 +10,11 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "NoMintedAssets", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -60,6 +65,38 @@ "name": "ApprovalForAll", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_fromTokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_toTokenId", + "type": "uint256" + } + ], + "name": "BatchMetadataUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "MetadataUpdate", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -438,6 +475,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "lastTokenId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -598,6 +648,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "string", + "name": "baseURI", + "type": "string" + } + ], + "name": "setBaseURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -648,6 +711,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "tokenBaseURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/dkg/data/interfaces/HashingProxy.json b/dkg/data/interfaces/HashingProxy.json index ca04298..03008aa 100644 --- a/dkg/data/interfaces/HashingProxy.json +++ b/dkg/data/interfaces/HashingProxy.json @@ -211,6 +211,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/dkg/data/interfaces/Hub.json b/dkg/data/interfaces/Hub.json index 1c53f39..01ee1d2 100644 --- a/dkg/data/interfaces/Hub.json +++ b/dkg/data/interfaces/Hub.json @@ -111,7 +111,7 @@ "type": "address" } ], - "internalType": "struct UnorderedNamedContractDynamicSetLib.Contract[]", + "internalType": "struct UnorderedNamedContractDynamicSetStructs.Contract[]", "name": "", "type": "tuple[]" } @@ -136,7 +136,7 @@ "type": "address" } ], - "internalType": "struct UnorderedNamedContractDynamicSetLib.Contract[]", + "internalType": "struct UnorderedNamedContractDynamicSetStructs.Contract[]", "name": "", "type": "tuple[]" } diff --git a/dkg/data/interfaces/Identity.json b/dkg/data/interfaces/Identity.json index 6b20984..7094201 100644 --- a/dkg/data/interfaces/Identity.json +++ b/dkg/data/interfaces/Identity.json @@ -177,6 +177,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/dkg/data/interfaces/ParametersStorage.json b/dkg/data/interfaces/ParametersStorage.json index 04310bf..ad6a7f7 100644 --- a/dkg/data/interfaces/ParametersStorage.json +++ b/dkg/data/interfaces/ParametersStorage.json @@ -10,6 +10,25 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "parameterName", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "parameterValue", + "type": "uint256" + } + ], + "name": "ParameterChanged", + "type": "event" + }, { "inputs": [], "name": "commitWindowDurationPerc", diff --git a/dkg/data/interfaces/Profile.json b/dkg/data/interfaces/Profile.json index 4b7c74e..3c599ab 100644 --- a/dkg/data/interfaces/Profile.json +++ b/dkg/data/interfaces/Profile.json @@ -211,6 +211,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -250,6 +263,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/dkg/data/interfaces/ProfileStorage.json b/dkg/data/interfaces/ProfileStorage.json index cb04fd5..49cef66 100644 --- a/dkg/data/interfaces/ProfileStorage.json +++ b/dkg/data/interfaces/ProfileStorage.json @@ -240,17 +240,17 @@ "outputs": [ { "internalType": "bytes", - "name": "", + "name": "nodeId", "type": "bytes" }, { "internalType": "uint96[2]", - "name": "", + "name": "profileSettings", "type": "uint96[2]" }, { "internalType": "address", - "name": "", + "name": "sharesContractAddress", "type": "address" } ], diff --git a/dkg/data/interfaces/ProofManagerV1.json b/dkg/data/interfaces/ProofManagerV1.json index d8a4258..81c3e38 100644 --- a/dkg/data/interfaces/ProofManagerV1.json +++ b/dkg/data/interfaces/ProofManagerV1.json @@ -67,17 +67,6 @@ "name": "NodeNotAwarded", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "OnlyHubOwnerFunction", - "type": "error" - }, { "inputs": [ { @@ -275,12 +264,12 @@ "outputs": [ { "internalType": "bytes32", - "name": "", + "name": "assertionId", "type": "bytes32" }, { "internalType": "uint256", - "name": "", + "name": "challenge", "type": "uint256" } ], @@ -496,6 +485,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "stakingContract", @@ -509,6 +511,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/dkg/data/interfaces/ProofManagerV1U1.json b/dkg/data/interfaces/ProofManagerV1U1.json index e8d44c2..0642bad 100644 --- a/dkg/data/interfaces/ProofManagerV1U1.json +++ b/dkg/data/interfaces/ProofManagerV1U1.json @@ -77,17 +77,6 @@ "name": "NodeNotAwarded", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "OnlyHubOwnerFunction", - "type": "error" - }, { "inputs": [ { @@ -296,12 +285,12 @@ "outputs": [ { "internalType": "bytes32", - "name": "", + "name": "assertionId", "type": "bytes32" }, { "internalType": "uint256", - "name": "", + "name": "challenge", "type": "uint256" } ], @@ -517,6 +506,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "stakingContract", @@ -530,6 +532,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/dkg/data/interfaces/ScoringProxy.json b/dkg/data/interfaces/ScoringProxy.json index 24391b0..1d591f7 100644 --- a/dkg/data/interfaces/ScoringProxy.json +++ b/dkg/data/interfaces/ScoringProxy.json @@ -226,6 +226,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/dkg/data/interfaces/ServiceAgreementStorageProxy.json b/dkg/data/interfaces/ServiceAgreementStorageProxy.json index 3d02b9f..342afe4 100644 --- a/dkg/data/interfaces/ServiceAgreementStorageProxy.json +++ b/dkg/data/interfaces/ServiceAgreementStorageProxy.json @@ -10,17 +10,6 @@ "stateMutability": "nonpayable", "type": "constructor" }, - { - "inputs": [ - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "OnlyHubOwnerFunction", - "type": "error" - }, { "inputs": [ { @@ -377,6 +366,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "agreementId", + "type": "bytes32" + } + ], + "name": "deleteServiceAgreementV1Object", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "agreementId", + "type": "bytes32" + } + ], + "name": "deleteServiceAgreementV1U1Object", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -417,7 +432,7 @@ }, { "internalType": "uint96[2]", - "name": "tokensInfo", + "name": "tokens", "type": "uint96[2]" }, { diff --git a/dkg/data/interfaces/ServiceAgreementStorageV1.json b/dkg/data/interfaces/ServiceAgreementStorageV1.json index 6fc1e97..5bd2c38 100644 --- a/dkg/data/interfaces/ServiceAgreementStorageV1.json +++ b/dkg/data/interfaces/ServiceAgreementStorageV1.json @@ -219,27 +219,27 @@ "outputs": [ { "internalType": "uint256", - "name": "", + "name": "startTime", "type": "uint256" }, { "internalType": "uint16", - "name": "", + "name": "epochsNumber", "type": "uint16" }, { "internalType": "uint128", - "name": "", + "name": "epochLength", "type": "uint128" }, { "internalType": "uint96", - "name": "", + "name": "tokenAmount", "type": "uint96" }, { "internalType": "uint8[2]", - "name": "", + "name": "scoreFunctionIdAndProofWindowOffsetPerc", "type": "uint8[2]" } ], diff --git a/dkg/data/interfaces/ServiceAgreementStorageV1U1.json b/dkg/data/interfaces/ServiceAgreementStorageV1U1.json index 428d950..30e53d6 100644 --- a/dkg/data/interfaces/ServiceAgreementStorageV1U1.json +++ b/dkg/data/interfaces/ServiceAgreementStorageV1U1.json @@ -281,27 +281,27 @@ "outputs": [ { "internalType": "uint256", - "name": "", + "name": "startTime", "type": "uint256" }, { "internalType": "uint16", - "name": "", + "name": "epochsNumber", "type": "uint16" }, { "internalType": "uint128", - "name": "", + "name": "epochLength", "type": "uint128" }, { "internalType": "uint96[2]", - "name": "", + "name": "tokens", "type": "uint96[2]" }, { "internalType": "uint8[2]", - "name": "", + "name": "scoreFunctionIdAndProofWindowOffsetPerc", "type": "uint8[2]" } ], diff --git a/dkg/data/interfaces/ServiceAgreementV1.json b/dkg/data/interfaces/ServiceAgreementV1.json index 003cd43..a63f7f3 100644 --- a/dkg/data/interfaces/ServiceAgreementV1.json +++ b/dkg/data/interfaces/ServiceAgreementV1.json @@ -10,28 +10,6 @@ "stateMutability": "nonpayable", "type": "constructor" }, - { - "inputs": [ - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "OnlyHubContractsFunction", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "OnlyHubOwnerFunction", - "type": "error" - }, { "inputs": [], "name": "ScoreError", @@ -387,12 +365,12 @@ "outputs": [ { "internalType": "bytes32", - "name": "", + "name": "assertionId", "type": "bytes32" }, { "internalType": "uint256", - "name": "", + "name": "challenge", "type": "uint256" } ], @@ -654,6 +632,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/dkg/data/interfaces/ShardingTable.json b/dkg/data/interfaces/ShardingTable.json index 0ce6434..28a5cc1 100644 --- a/dkg/data/interfaces/ShardingTable.json +++ b/dkg/data/interfaces/ShardingTable.json @@ -226,6 +226,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "shardingTableStorage", @@ -252,6 +265,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/dkg/data/interfaces/Staking.json b/dkg/data/interfaces/Staking.json index c91a614..f2edd99 100644 --- a/dkg/data/interfaces/Staking.json +++ b/dkg/data/interfaces/Staking.json @@ -344,6 +344,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "shardingTableContract", @@ -414,6 +427,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "tokenContract", diff --git a/dkg/data/interfaces/Token.json b/dkg/data/interfaces/Token.json index 996f7e1..8a5ce2b 100644 --- a/dkg/data/interfaces/Token.json +++ b/dkg/data/interfaces/Token.json @@ -2,9 +2,14 @@ { "inputs": [ { - "internalType": "address", - "name": "hubAddress", - "type": "address" + "internalType": "string", + "name": "tokenName", + "type": "string" + }, + { + "internalType": "string", + "name": "tokenSymbol", + "type": "string" } ], "stateMutability": "nonpayable", @@ -35,6 +40,25 @@ "name": "Approval", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -326,19 +350,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "hub", - "outputs": [ - { - "internalType": "contract Hub", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -394,6 +405,26 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -540,5 +571,18 @@ ], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ] From 8715cb3b24bc8fabc02e551cade4e3e2b5008f4f Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Thu, 14 Dec 2023 11:47:39 +0100 Subject: [PATCH 05/12] Fixed gas price fetching from the Oracle API --- dkg/providers/blockchain.py | 83 ++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/dkg/providers/blockchain.py b/dkg/providers/blockchain.py index beb3952..47b840e 100644 --- a/dkg/providers/blockchain.py +++ b/dkg/providers/blockchain.py @@ -16,15 +16,15 @@ # under the License. import json -import requests from collections import namedtuple from functools import wraps from pathlib import Path from typing import Any, Type -from dkg.exceptions import ( - AccountMissing, EnvironmentNotSupported, NetworkNotSupported, RPCURINotDefined -) +import requests +from dkg.constants import BLOCKCHAINS, DEFAULT_GAS_PRICE_GWEI +from dkg.exceptions import (AccountMissing, EnvironmentNotSupported, + NetworkNotSupported, RPCURINotDefined) from dkg.types import URI, Address, DataHexStr, Wei from eth_account.signers.local import LocalAccount from web3 import Web3 @@ -33,7 +33,6 @@ from web3.logs import DISCARD from web3.middleware import construct_sign_and_send_raw_middleware from web3.types import ABI, ABIFunction, Environment, TxReceipt -from dkg.constants import BLOCKCHAINS, DEFAULT_GAS_PRICE_GWEI class BlockchainProvider: @@ -48,9 +47,7 @@ def __init__( verify: bool = True, ): if environment not in BLOCKCHAINS.keys(): - raise EnvironmentNotSupported( - f"Environment {environment} isn't supported!" - ) + raise EnvironmentNotSupported(f"Environment {environment} isn't supported!") self.environment = environment self.rpc_uri = rpc_uri @@ -62,10 +59,9 @@ def __init__( if self.rpc_uri is None and self.blockchain_id is not None: self.blockchain_id = blockchain_id - self.rpc_uri = ( - self.rpc_uri or - BLOCKCHAINS[self.environment][self.blockchain_id].get("rpc", None) - ) + self.rpc_uri = self.rpc_uri or BLOCKCHAINS[self.environment][ + self.blockchain_id + ].get("rpc", None) if self.rpc_uri is None: raise RPCURINotDefined( @@ -116,21 +112,20 @@ def make_json_rpc_request(self, endpoint: str, args: dict[str, Any] = {}) -> Any def handle_updated_contract(func): @wraps(func) def wrapper(self, *args, **kwargs): - contract_name = ( - kwargs.get('contract_name') or (args[0] if args else None) - ) + contract_name = kwargs.get("contract_name") or (args[0] if args else None) try: return func(self, *args, **kwargs) except Exception as err: if ( - contract_name and - any(msg in str(err) for msg in ["revert", "VM Exception"]) and - not self._check_contract_status(contract_name) + contract_name + and any(msg in str(err) for msg in ["revert", "VM Exception"]) + and not self._check_contract_status(contract_name) ): self._update_contract_instance(contract_name) return func(self, *args, **kwargs) raise + return wrapper @handle_updated_contract @@ -161,16 +156,12 @@ def call_function( ) nonce = self.w3.eth.get_transaction_count(self.w3.eth.default_account) - gas_price = ( - gas_price or - self._get_oracle_gas_price() or - self.w3.eth.gas_price - ) + gas_price = gas_price or self._get_network_gas_price() options = { "nonce": nonce, "gasPrice": gas_price, - "gas": gas_limit or contract_function(**args).estimate_gas() + "gas": gas_limit or contract_function(**args).estimate_gas(), } tx_hash = contract_function(**args).transact(options) @@ -194,21 +185,37 @@ def set_account(self, private_key: DataHexStr): ) self.w3.eth.default_account = self.account.address - def _get_oracle_gas_price(self) -> int | None: - if self.gas_price_oracle is None: - return None - - response_json = requests.get(self.gas_price_oracle).json() - - match self.blockchain_id: - case "gnosis:100": - gas_price = int(response_json["result"], 16) - case "gnosis:10200": - gas_price = self.w3.to_wei(response_json["average"], "gwei") + def _get_network_gas_price(self) -> int | None: + blockchain_name, chain_id = self.blockchain_id.split(":") + + default_gas_price = self.w3.to_wei(DEFAULT_GAS_PRICE_GWEI, "gwei") + + match blockchain_name: + case "otp": + return self.w3.eth.gas_price + case "gnosis": + if self.gas_price_oracle is None: + return None + + try: + response_json: dict = requests.get(self.gas_price_oracle).json() + except Exception: + return default_gas_price + + gas_price = None + match chain_id: + case "100": + gas_price_hex = response_json.get("result") + if gas_price_hex: + gas_price = int(gas_price_hex, 16) + case "10200": + gas_price_avg = response_json.get("average") + if gas_price_avg: + gas_price = self.w3.to_wei(gas_price_avg, "gwei") + + return gas_price if gas_price is not None else default_gas_price case _: - gas_price = self.w3.to_wei(DEFAULT_GAS_PRICE_GWEI, "gwei") - - return gas_price + return default_gas_price def _init_contracts(self): for contract in self.abi.keys(): From fe46efbaead9d0e26d32c8766ded656e7fc969f5 Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Thu, 14 Dec 2023 12:42:06 +0100 Subject: [PATCH 06/12] Wrapped contract status check into try-catch block --- dkg/providers/blockchain.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dkg/providers/blockchain.py b/dkg/providers/blockchain.py index 47b840e..66a7bb5 100644 --- a/dkg/providers/blockchain.py +++ b/dkg/providers/blockchain.py @@ -241,7 +241,10 @@ def _update_contract_instance(self, contract: str): ) def _check_contract_status(self, contract: str) -> bool: - return self.call_function(contract, "status") + try: + return self.call_function(contract, "status") + except Exception: + return False def _generate_output_named_tuples(self) -> dict[str, dict[str, Type[tuple]]]: def generate_output_namedtuple(function_abi: ABIFunction) -> Type[tuple] | None: From 77be2017178de8226b06c69e95c3967f0b8ac54a Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Tue, 19 Dec 2023 12:22:39 +0100 Subject: [PATCH 07/12] Fixed small bugs, updated blockchain provider init in the demo.py --- dkg/asset.py | 8 ++++---- dkg/providers/blockchain.py | 6 +++--- dkg/types/blockchain.py | 2 +- dkg/utils/ual.py | 4 ++-- examples/demo.py | 12 +++++++++--- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/dkg/asset.py b/dkg/asset.py index f13d018..08d6fd1 100644 --- a/dkg/asset.py +++ b/dkg/asset.py @@ -97,9 +97,9 @@ def is_valid_ual(self, ual: UAL) -> bool: f"Expected: '${chain_id}'. Received: '${prefixes[3]}'." ) - contract_address: Address = self.manager.blockchain_provider.contracts[ + contract_address = self.manager.blockchain_provider.contracts[ "ContentAssetStorage" - ] + ].address if parts[1].lower() != contract_address.lower(): raise ValueError( @@ -109,7 +109,7 @@ def is_valid_ual(self, ual: UAL) -> bool: ) try: - owner = self._owner(parts[2]) + owner = self._owner(int(parts[2])) if not owner or owner == ADDRESS_ZERO: raise ValueError("Token does not exist or has no owner.") @@ -367,7 +367,7 @@ def update( )["bidSuggestion"] ) - token_amount -= agreement_data.tokensInfo[0] + token_amount -= agreement_data.tokens[0] token_amount = token_amount if token_amount > 0 else 0 current_allowance = self.get_current_allowance() diff --git a/dkg/providers/blockchain.py b/dkg/providers/blockchain.py index 66a7bb5..0a5815f 100644 --- a/dkg/providers/blockchain.py +++ b/dkg/providers/blockchain.py @@ -25,14 +25,14 @@ from dkg.constants import BLOCKCHAINS, DEFAULT_GAS_PRICE_GWEI from dkg.exceptions import (AccountMissing, EnvironmentNotSupported, NetworkNotSupported, RPCURINotDefined) -from dkg.types import URI, Address, DataHexStr, Wei +from dkg.types import URI, Address, DataHexStr, Environment, Wei from eth_account.signers.local import LocalAccount from web3 import Web3 from web3.contract import Contract from web3.contract.contract import ContractFunction from web3.logs import DISCARD from web3.middleware import construct_sign_and_send_raw_middleware -from web3.types import ABI, ABIFunction, Environment, TxReceipt +from web3.types import ABI, ABIFunction, TxReceipt class BlockchainProvider: @@ -88,7 +88,7 @@ def __init__( self.abi = self._load_abi() self.output_named_tuples = self._generate_output_named_tuples() - hub_address: Address = BLOCKCHAINS[self.blockchain_id]["hub"] + hub_address: Address = BLOCKCHAINS[self.environment][self.blockchain_id]["hub"] self.contracts: dict[str, Contract] = { "Hub": self.w3.eth.contract( address=hub_address, diff --git a/dkg/types/blockchain.py b/dkg/types/blockchain.py index 57a40b0..c7dbaf8 100644 --- a/dkg/types/blockchain.py +++ b/dkg/types/blockchain.py @@ -57,5 +57,5 @@ class AgreementData(NamedTuple): startTime: int epochsNumber: int epochLength: int - tokensInfo: list[int] + tokens: list[int] scoreFunctionIdAndProofWindowOffsetPerc: list[int] diff --git a/dkg/utils/ual.py b/dkg/utils/ual.py index 61ceb65..4393fd8 100644 --- a/dkg/utils/ual.py +++ b/dkg/utils/ual.py @@ -27,10 +27,10 @@ def format_ual( def parse_ual(ual: UAL) -> dict[str, str | Address | int]: - if ual.startswith("did:dkg:"): + if not ual.startswith("did:dkg:"): raise ValidationError("Invalid UAL!") - args: list[str] = ual.replace("did:dkg:").split("/") + args = ual.replace("did:dkg:", "").split("/") if len(args) != 3: raise ValidationError("Invalid UAL!") diff --git a/examples/demo.py b/examples/demo.py index f24fc14..555e351 100644 --- a/examples/demo.py +++ b/examples/demo.py @@ -24,8 +24,9 @@ node_provider = NodeHTTPProvider("http://localhost:8900") blockchain_provider = BlockchainProvider( - "http://localhost:8545", - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "development", + "hardhat1:31337", + private_key="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ) dkg = DKG(node_provider, blockchain_provider) @@ -133,6 +134,11 @@ def divider(): print(create_asset_result) divider() +validate_ual = dkg.asset.is_valid_ual(create_asset_result["UAL"]) +print("======================== VALIDATE UAL") +print(f"Is {create_asset_result['UAL']} a valid UAL: {validate_ual}") +divider() + owner_result = dkg.asset.get_owner(create_asset_result["UAL"]) print("======================== GET ASSET OWNER") print(owner_result) @@ -183,7 +189,7 @@ def divider(): divider() # TODO: Remove when wait_for_finalization is implemented -time.sleep(30) +time.sleep(60) get_second_state_by_index = dkg.asset.get(create_asset_result["UAL"], 1, "all") print("======================== ASSET SECOND STATE (GET BY STATE INDEX) RESOLVED") From 4c48755c75a8ea5f12a8b3188e48111ba5248521 Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Tue, 19 Dec 2023 13:12:22 +0100 Subject: [PATCH 08/12] Added possibility to set PRIVATE_KEY as env variable --- dkg/providers/blockchain.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dkg/providers/blockchain.py b/dkg/providers/blockchain.py index 0a5815f..6c2bf61 100644 --- a/dkg/providers/blockchain.py +++ b/dkg/providers/blockchain.py @@ -16,6 +16,7 @@ # under the License. import json +import os from collections import namedtuple from functools import wraps from pathlib import Path @@ -97,8 +98,11 @@ def __init__( } self._init_contracts() - if private_key is not None: - self.set_account(private_key) + if ( + private_key is not None or + (private_key_env := os.environ.get("PRIVATE_KEY", None)) is not None + ): + self.set_account(private_key or private_key_env) def make_json_rpc_request(self, endpoint: str, args: dict[str, Any] = {}) -> Any: web3_method = getattr(self.w3.eth, endpoint) From 046b528c2160eae46983c43e5ae6b2ddd3a0475c Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Thu, 21 Dec 2023 11:42:17 +0100 Subject: [PATCH 09/12] Disabled automatic contracts update --- dkg/providers/blockchain.py | 1 - examples/demo.py | 27 ++++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/dkg/providers/blockchain.py b/dkg/providers/blockchain.py index 6c2bf61..f600694 100644 --- a/dkg/providers/blockchain.py +++ b/dkg/providers/blockchain.py @@ -132,7 +132,6 @@ def wrapper(self, *args, **kwargs): return wrapper - @handle_updated_contract def call_function( self, contract: str, diff --git a/examples/demo.py b/examples/demo.py index 555e351..f302584 100644 --- a/examples/demo.py +++ b/examples/demo.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. +import json import math import random import time @@ -38,6 +39,10 @@ def divider(): print("==================================================") +def print_json(json_dict: dict): + print(json.dumps(json_dict, indent=4)) + + content = { "public": { "@context": ["http://schema.org"], @@ -61,13 +66,13 @@ def divider(): info_result = dkg.node.info print("======================== NODE INFO RECEIVED") -print(info_result) +print_json(info_result) divider() formatted_assertions = dkg.assertion.format_graph(content) print("======================== ASSET FORMATTED") -print(formatted_assertions) +print_json(formatted_assertions) divider() @@ -131,7 +136,7 @@ def divider(): create_asset_result = dkg.asset.create(content, 2) print("======================== ASSET CREATED") -print(create_asset_result) +print_json(create_asset_result) divider() validate_ual = dkg.asset.is_valid_ual(create_asset_result["UAL"]) @@ -146,7 +151,7 @@ def divider(): get_asset_result = dkg.asset.get(create_asset_result["UAL"]) print("======================== ASSET RESOLVED") -print(get_asset_result) +print_json(get_asset_result) divider() update_asset_result = dkg.asset.update( @@ -166,26 +171,26 @@ def divider(): }, ) print("======================== ASSET UPDATED") -print(update_asset_result) +print_json(update_asset_result) divider() get_latest_asset_result = dkg.asset.get( create_asset_result["UAL"], "latest", "all" ) print("======================== ASSET LATEST RESOLVED") -print(get_latest_asset_result) +print_json(get_latest_asset_result) divider() get_latest_finalized_asset_result = dkg.asset.get( create_asset_result["UAL"], "latest_finalized", "all" ) print("======================== ASSET LATEST FINALIZED RESOLVED") -print(get_latest_finalized_asset_result) +print_json(get_latest_finalized_asset_result) divider() get_first_state_by_index = dkg.asset.get(create_asset_result["UAL"], 0, "all") print("======================== ASSET FIRST STATE (GET BY STATE INDEX) RESOLVED") -print(get_first_state_by_index) +print_json(get_first_state_by_index) divider() # TODO: Remove when wait_for_finalization is implemented @@ -193,21 +198,21 @@ def divider(): get_second_state_by_index = dkg.asset.get(create_asset_result["UAL"], 1, "all") print("======================== ASSET SECOND STATE (GET BY STATE INDEX) RESOLVED") -print(get_second_state_by_index) +print_json(get_second_state_by_index) divider() get_first_state_by_hash = dkg.asset.get( create_asset_result["UAL"], create_asset_result["publicAssertionId"], "all" ) print("======================== ASSET FIRST STATE (GET BY STATE HASH) RESOLVED") -print(get_first_state_by_hash) +print_json(get_first_state_by_hash) divider() get_second_state_by_hash = dkg.asset.get( create_asset_result["UAL"], update_asset_result["publicAssertionId"], "all" ) print("======================== ASSET SECOND STATE (GET BY STATE HASH) RESOLVED") -print(get_second_state_by_hash) +print_json(get_second_state_by_hash) divider() query_result = dkg.graph.query( From 965f0ee0688923bd422c51f10851e64c0e6b87de Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Thu, 21 Dec 2023 13:09:13 +0100 Subject: [PATCH 10/12] Added proper error handling for DKG Node requests --- dkg/providers/node_http.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/dkg/providers/node_http.py b/dkg/providers/node_http.py index 3b7a1da..08ff7a2 100644 --- a/dkg/providers/node_http.py +++ b/dkg/providers/node_http.py @@ -22,6 +22,7 @@ from dkg.exceptions import HTTPRequestMethodNotSupported, NodeRequestError from dkg.types import URI from requests import Response +from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException class NodeHTTPProvider: @@ -59,9 +60,16 @@ def get( params=params, headers=headers, ) - return NodeResponseDict(response.json()) - except requests.exceptions.HTTPError as err: - raise NodeRequestError(err) + + response.raise_for_status() + + try: + return NodeResponseDict(response.json()) + except ValueError as err: + raise NodeRequestError(f"JSON decoding failed: {err}") + + except (HTTPError, ConnectionError, Timeout, RequestException) as err: + raise NodeRequestError(f"Request failed: {err}") def post( self, path: str, data: dict[str, Any] = {}, headers: dict[str, str] = {} @@ -72,9 +80,15 @@ def post( json=data, headers=headers, ) - return NodeResponseDict(response.json()) - except requests.exceptions.HTTPError as err: - raise NodeRequestError(err) + response.raise_for_status() + + try: + return NodeResponseDict(response.json()) + except ValueError as err: + raise NodeRequestError(f"JSON decoding failed: {err}") + + except (HTTPError, ConnectionError, Timeout, RequestException) as err: + raise NodeRequestError(f"Request failed: {err}") def _prepare_headers(self) -> dict[str, str]: return self._prepare_auth_header() From e952751ccfacf2589a033984939f399521430896 Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Thu, 21 Dec 2023 14:40:32 +0100 Subject: [PATCH 11/12] Added better gas oracle api get error handling, removed Gnosis Mainnet from the config --- dkg/constants.py | 5 ----- dkg/providers/blockchain.py | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dkg/constants.py b/dkg/constants.py index dbf0e4a..946b5cb 100644 --- a/dkg/constants.py +++ b/dkg/constants.py @@ -61,11 +61,6 @@ "hub": "0x5fA7916c48Fe6D5F1738d12Ad234b78c90B4cAdA", "rpc": "https://astrosat-parachain-rpc.origin-trail.network", }, - "gnosis:100": { - "hub": "", - "rpc": "https://rpc.gnosischain.com/", - "gas_price_oracle": "https://api.gnosisscan.io/api?module=proxy&action=eth_gasPrice", - }, }, } diff --git a/dkg/providers/blockchain.py b/dkg/providers/blockchain.py index f600694..a98f82b 100644 --- a/dkg/providers/blockchain.py +++ b/dkg/providers/blockchain.py @@ -28,6 +28,7 @@ NetworkNotSupported, RPCURINotDefined) from dkg.types import URI, Address, DataHexStr, Environment, Wei from eth_account.signers.local import LocalAccount +from requests.exceptions import ConnectionError, HTTPError, RequestException, Timeout from web3 import Web3 from web3.contract import Contract from web3.contract.contract import ContractFunction @@ -198,11 +199,19 @@ def _get_network_gas_price(self) -> int | None: return self.w3.eth.gas_price case "gnosis": if self.gas_price_oracle is None: - return None + return default_gas_price try: - response_json: dict = requests.get(self.gas_price_oracle).json() - except Exception: + response = requests.get(self.gas_price_oracle) + + response.raise_for_status() + + try: + response_json: dict = response.json() + except ValueError: + return default_gas_price + + except (HTTPError, ConnectionError, Timeout, RequestException): return default_gas_price gas_price = None From cacf7a0cdaf32a88efc25d1cfef4febeef950559 Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Thu, 21 Dec 2023 16:39:48 +0100 Subject: [PATCH 12/12] Removed redundant code from the Node Provider --- dkg/providers/node_http.py | 59 +++++++------------------------------- 1 file changed, 11 insertions(+), 48 deletions(-) diff --git a/dkg/providers/node_http.py b/dkg/providers/node_http.py index 08ff7a2..1286655 100644 --- a/dkg/providers/node_http.py +++ b/dkg/providers/node_http.py @@ -21,7 +21,6 @@ from dkg.dataclasses import HTTPRequestMethod, NodeResponseDict from dkg.exceptions import HTTPRequestMethodNotSupported, NodeRequestError from dkg.types import URI -from requests import Response from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException @@ -36,50 +35,22 @@ def make_request( path: str, params: dict[str, Any] = {}, data: dict[str, Any] = {}, - ) -> Response: - request_func = getattr(self, method.name.lower()) - - headers = self._prepare_headers() + ) -> NodeResponseDict: + url = f"{self.endpoint_uri}/{path}" + headers = ( + {"Authorization": f"Bearer {self.auth_token}"} if self.auth_token else {} + ) - match method: - case HTTPRequestMethod.GET: - return request_func(path, params, headers) - case HTTPRequestMethod.POST: - return request_func(path, data, headers) - case HTTPRequestMethod(): + try: + if method == HTTPRequestMethod.GET: + response = requests.get(url, params=params, headers=headers) + elif method == HTTPRequestMethod.POST: + response = requests.post(url, json=data, headers=headers) + else: raise HTTPRequestMethodNotSupported( f"{method.name} method isn't supported" ) - def get( - self, path: str, params: dict[str, Any] = {}, headers: dict[str, str] = {} - ) -> NodeResponseDict: - try: - response = requests.get( - f"{self.endpoint_uri}/{path}", - params=params, - headers=headers, - ) - - response.raise_for_status() - - try: - return NodeResponseDict(response.json()) - except ValueError as err: - raise NodeRequestError(f"JSON decoding failed: {err}") - - except (HTTPError, ConnectionError, Timeout, RequestException) as err: - raise NodeRequestError(f"Request failed: {err}") - - def post( - self, path: str, data: dict[str, Any] = {}, headers: dict[str, str] = {} - ) -> NodeResponseDict: - try: - response = requests.post( - f"{self.endpoint_uri}/{path}", - json=data, - headers=headers, - ) response.raise_for_status() try: @@ -89,11 +60,3 @@ def post( except (HTTPError, ConnectionError, Timeout, RequestException) as err: raise NodeRequestError(f"Request failed: {err}") - - def _prepare_headers(self) -> dict[str, str]: - return self._prepare_auth_header() - - def _prepare_auth_header(self) -> dict[str, str]: - if self.auth_token: - return {"Authorization": f"Bearer {self.auth_token}"} - return {}