dkg 8.0.0a2__py3-none-any.whl → 8.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dkg/__init__.py +1 -1
- dkg/assertion.py +2 -2
- dkg/clients/__init__.py +4 -0
- dkg/clients/async_dkg.py +109 -0
- dkg/{main.py → clients/dkg.py} +42 -21
- dkg/constants.py +117 -6
- dkg/data/interfaces/AskStorage.json +366 -0
- dkg/data/interfaces/Chronos.json +202 -0
- dkg/data/interfaces/Hub.json +294 -2
- dkg/data/interfaces/IdentityStorage.json +58 -0
- dkg/data/interfaces/{ContentAsset.json → KnowledgeCollection.json} +256 -343
- dkg/data/interfaces/KnowledgeCollectionStorage.json +2312 -0
- dkg/data/interfaces/Paranet.json +30 -214
- dkg/data/interfaces/ParanetIncentivesPoolFactory.json +18 -2
- dkg/data/interfaces/ParanetKnowledgeMinersRegistry.json +20 -4
- dkg/data/interfaces/{ParanetNeurowebIncentivesPool.json → ParanetNeuroIncentivesPool.json} +7 -7
- dkg/data/interfaces/ParanetsRegistry.json +102 -32
- dkg/data/interfaces/Token.json +146 -17
- dkg/managers/__init__.py +0 -0
- dkg/managers/async_manager.py +69 -0
- dkg/{manager.py → managers/manager.py} +5 -3
- dkg/method.py +5 -2
- dkg/modules/__init__.py +0 -0
- dkg/modules/asset/__init__.py +0 -0
- dkg/modules/asset/asset.py +739 -0
- dkg/modules/asset/async_asset.py +751 -0
- dkg/modules/async_module.py +66 -0
- dkg/modules/graph/__init__.py +0 -0
- dkg/modules/graph/async_graph.py +118 -0
- dkg/modules/graph/graph.py +94 -0
- dkg/{module.py → modules/module.py} +1 -1
- dkg/modules/network/__init__.py +0 -0
- dkg/{network.py → modules/network/network.py} +4 -4
- dkg/modules/node/__init__.py +0 -0
- dkg/modules/node/async_node.py +39 -0
- dkg/{node.py → modules/node/node.py} +2 -2
- dkg/modules/paranet/__init__.py +0 -0
- dkg/{paranet.py → modules/paranet/paranet.py} +2 -2
- dkg/providers/__init__.py +9 -2
- dkg/providers/blockchain/__init__.py +4 -0
- dkg/providers/blockchain/async_blockchain.py +245 -0
- dkg/providers/blockchain/base_blockchain.py +102 -0
- dkg/providers/{blockchain.py → blockchain/blockchain.py} +15 -96
- dkg/providers/node/__init__.py +4 -0
- dkg/providers/node/async_node_http.py +72 -0
- dkg/providers/node/base_node_http.py +25 -0
- dkg/providers/{node_http.py → node/node_http.py} +12 -10
- dkg/services/__init__.py +0 -0
- dkg/services/blockchain_services/__init__.py +0 -0
- dkg/services/blockchain_services/async_blockchain_service.py +180 -0
- dkg/services/blockchain_services/blockchain_service.py +174 -0
- dkg/services/input_service.py +183 -0
- dkg/services/node_services/__init__.py +0 -0
- dkg/services/node_services/async_node_service.py +184 -0
- dkg/services/node_services/node_service.py +167 -0
- dkg/types/__init__.py +11 -11
- dkg/utils/blockchain_request.py +68 -42
- dkg/utils/knowledge_asset_tools.py +5 -0
- dkg/utils/knowledge_collection_tools.py +248 -0
- dkg/utils/node_request.py +60 -13
- dkg/utils/rdf.py +9 -3
- {dkg-8.0.0a2.dist-info → dkg-8.0.1.dist-info}/METADATA +28 -19
- dkg-8.0.1.dist-info/RECORD +82 -0
- {dkg-8.0.0a2.dist-info → dkg-8.0.1.dist-info}/WHEEL +1 -1
- dkg/asset.py +0 -912
- dkg/data/interfaces/AssertionStorage.json +0 -229
- dkg/data/interfaces/ContentAssetStorage.json +0 -706
- dkg/data/interfaces/ServiceAgreementStorageProxy.json +0 -1314
- dkg/graph.py +0 -63
- dkg-8.0.0a2.dist-info/RECORD +0 -52
- {dkg-8.0.0a2.dist-info → dkg-8.0.1.dist-info}/LICENSE +0 -0
- {dkg-8.0.0a2.dist-info → dkg-8.0.1.dist-info}/NOTICE +0 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
from dataclasses import asdict
|
19
|
+
from typing import Any, Callable, Sequence
|
20
|
+
|
21
|
+
from dkg.exceptions import ValidationError
|
22
|
+
from dkg.managers.async_manager import AsyncRequestManager
|
23
|
+
from dkg.method import Method
|
24
|
+
from dkg.types import TReturn
|
25
|
+
|
26
|
+
|
27
|
+
class AsyncModule:
|
28
|
+
manager: AsyncRequestManager
|
29
|
+
|
30
|
+
def retrieve_caller_fn(
|
31
|
+
self, method: Method[Callable[..., TReturn]]
|
32
|
+
) -> Callable[..., TReturn]:
|
33
|
+
async def caller(*args: Any, **kwargs: Any) -> TReturn:
|
34
|
+
processed_args = method.process_args(*args, **kwargs)
|
35
|
+
request_params = asdict(method.action)
|
36
|
+
request_params.update(processed_args)
|
37
|
+
|
38
|
+
return await self.manager.blocking_request(
|
39
|
+
type(method.action), request_params
|
40
|
+
)
|
41
|
+
|
42
|
+
return caller
|
43
|
+
|
44
|
+
def _attach_modules(self, module_definitions: dict[str, Any]) -> None:
|
45
|
+
for module_name, module_info in module_definitions.items():
|
46
|
+
module_info_is_list_like = isinstance(module_info, Sequence)
|
47
|
+
|
48
|
+
module = module_info[0] if module_info_is_list_like else module_info
|
49
|
+
|
50
|
+
if hasattr(self, module_name):
|
51
|
+
raise AttributeError(
|
52
|
+
f"Cannot set {self} module named '{module_name}'. "
|
53
|
+
" The dkg object already has an attribute with that name"
|
54
|
+
)
|
55
|
+
|
56
|
+
setattr(self, module_name, module)
|
57
|
+
|
58
|
+
if module_info_is_list_like:
|
59
|
+
if len(module_info) == 2:
|
60
|
+
submodule_definitions = module_info[1]
|
61
|
+
module: "AsyncModule" = getattr(self, module_name)
|
62
|
+
module._attach_modules(submodule_definitions)
|
63
|
+
elif len(module_info) != 1:
|
64
|
+
raise ValidationError(
|
65
|
+
"Module definitions can only have 1 or 2 elements."
|
66
|
+
)
|
File without changes
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
|
19
|
+
from rdflib.plugins.sparql.parser import parseQuery
|
20
|
+
|
21
|
+
from dkg.managers.async_manager import AsyncRequestManager
|
22
|
+
from dkg.modules.async_module import AsyncModule
|
23
|
+
from dkg.types import NQuads
|
24
|
+
from dkg.constants import Operations, Status
|
25
|
+
from dkg.services.input_service import InputService
|
26
|
+
from dkg.services.node_services.async_node_service import AsyncNodeService
|
27
|
+
from dkg.types import UAL
|
28
|
+
|
29
|
+
|
30
|
+
class AsyncGraph(AsyncModule):
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
manager: AsyncRequestManager,
|
34
|
+
input_service: InputService,
|
35
|
+
node_service: AsyncNodeService,
|
36
|
+
):
|
37
|
+
self.manager = manager
|
38
|
+
self.input_service = input_service
|
39
|
+
self.node_service = node_service
|
40
|
+
|
41
|
+
async def query(
|
42
|
+
self,
|
43
|
+
query: str,
|
44
|
+
options: dict = None,
|
45
|
+
) -> NQuads:
|
46
|
+
if options is None:
|
47
|
+
options = {}
|
48
|
+
|
49
|
+
arguments = self.input_service.get_query_arguments(options)
|
50
|
+
|
51
|
+
max_number_of_retries = arguments.get("max_number_of_retries")
|
52
|
+
frequency = arguments.get("frequency")
|
53
|
+
paranet_ual = arguments.get("paranet_ual")
|
54
|
+
repository = arguments.get("repository")
|
55
|
+
|
56
|
+
parsed_query = parseQuery(query)
|
57
|
+
query_type = parsed_query[1].name.replace("Query", "").upper()
|
58
|
+
|
59
|
+
result = await self.node_service.query(
|
60
|
+
query, query_type, repository, paranet_ual
|
61
|
+
)
|
62
|
+
operation_id = result.get("operationId")
|
63
|
+
operation_result = await self.node_service.get_operation_result(
|
64
|
+
operation_id, Operations.QUERY.value, max_number_of_retries, frequency
|
65
|
+
)
|
66
|
+
|
67
|
+
return operation_result["data"]
|
68
|
+
|
69
|
+
async def publish_finality(self, UAL: UAL, options=None):
|
70
|
+
if options is None:
|
71
|
+
options = {}
|
72
|
+
|
73
|
+
arguments = self.input_service.get_publish_finality_arguments(options)
|
74
|
+
max_number_of_retries = arguments.get("max_number_of_retries")
|
75
|
+
minimum_number_of_finalization_confirmations = arguments.get(
|
76
|
+
"minimum_number_of_finalization_confirmations"
|
77
|
+
)
|
78
|
+
frequency = arguments.get("frequency")
|
79
|
+
try:
|
80
|
+
finality_status_result = await self.node_service.finality_status(
|
81
|
+
UAL,
|
82
|
+
minimum_number_of_finalization_confirmations,
|
83
|
+
max_number_of_retries,
|
84
|
+
frequency,
|
85
|
+
)
|
86
|
+
except Exception as e:
|
87
|
+
return {"status": Status.ERROR.value, "error": str(e)}
|
88
|
+
|
89
|
+
if finality_status_result == 0:
|
90
|
+
try:
|
91
|
+
finality_operation_id = await self.node_service.finality(
|
92
|
+
UAL,
|
93
|
+
minimum_number_of_finalization_confirmations,
|
94
|
+
max_number_of_retries,
|
95
|
+
frequency,
|
96
|
+
)
|
97
|
+
except Exception as e:
|
98
|
+
return {"status": Status.ERROR.value, "error": str(e)}
|
99
|
+
|
100
|
+
try:
|
101
|
+
return await self.node_service.get_operation_result(
|
102
|
+
finality_operation_id, "finality", max_number_of_retries, frequency
|
103
|
+
)
|
104
|
+
except Exception as e:
|
105
|
+
return {"status": Status.NOT_FINALIZED.value, "error": str(e)}
|
106
|
+
|
107
|
+
elif finality_status_result >= minimum_number_of_finalization_confirmations:
|
108
|
+
return {
|
109
|
+
"status": Status.FINALIZED.value,
|
110
|
+
"numberOfConfirmations": finality_status_result,
|
111
|
+
"requiredConfirmations": minimum_number_of_finalization_confirmations,
|
112
|
+
}
|
113
|
+
else:
|
114
|
+
return {
|
115
|
+
"status": Status.NOT_FINALIZED.value,
|
116
|
+
"numberOfConfirmations": finality_status_result,
|
117
|
+
"requiredConfirmations": minimum_number_of_finalization_confirmations,
|
118
|
+
}
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
from rdflib.plugins.sparql.parser import parseQuery
|
19
|
+
|
20
|
+
|
21
|
+
from dkg.managers.manager import DefaultRequestManager
|
22
|
+
from dkg.modules.module import Module
|
23
|
+
from dkg.types import NQuads
|
24
|
+
from dkg.services.node_services.node_service import NodeService
|
25
|
+
from dkg.services.input_service import InputService
|
26
|
+
from dkg.constants import Operations, Status
|
27
|
+
|
28
|
+
|
29
|
+
class Graph(Module):
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
manager: DefaultRequestManager,
|
33
|
+
input_service: InputService,
|
34
|
+
node_service: NodeService,
|
35
|
+
):
|
36
|
+
self.manager = manager
|
37
|
+
self.input_service = input_service
|
38
|
+
self.node_service = node_service
|
39
|
+
|
40
|
+
def query(
|
41
|
+
self,
|
42
|
+
query: str,
|
43
|
+
options: dict = {},
|
44
|
+
) -> NQuads:
|
45
|
+
arguments = self.input_service.get_query_arguments(options)
|
46
|
+
|
47
|
+
max_number_of_retries = arguments.get("max_number_of_retries")
|
48
|
+
frequency = arguments.get("frequency")
|
49
|
+
paranet_ual = arguments.get("paranet_ual")
|
50
|
+
repository = arguments.get("repository")
|
51
|
+
|
52
|
+
parsed_query = parseQuery(query)
|
53
|
+
query_type = parsed_query[1].name.replace("Query", "").upper()
|
54
|
+
|
55
|
+
result = self.node_service.query(query, query_type, repository, paranet_ual)
|
56
|
+
operation_id = result.get("operationId")
|
57
|
+
operation_result = self.node_service.get_operation_result(
|
58
|
+
operation_id, Operations.QUERY.value, max_number_of_retries, frequency
|
59
|
+
)
|
60
|
+
|
61
|
+
return operation_result["data"]
|
62
|
+
|
63
|
+
def publish_finality(self, UAL, options=None):
|
64
|
+
if options is None:
|
65
|
+
options = {}
|
66
|
+
|
67
|
+
arguments = self.input_service.get_publish_finality_arguments(options)
|
68
|
+
max_number_of_retries = arguments.get("max_number_of_retries")
|
69
|
+
minimum_number_of_finalization_confirmations = arguments.get(
|
70
|
+
"minimum_number_of_finalization_confirmations"
|
71
|
+
)
|
72
|
+
frequency = arguments.get("frequency")
|
73
|
+
try:
|
74
|
+
finality_status_result = self.node_service.finality_status(
|
75
|
+
UAL,
|
76
|
+
minimum_number_of_finalization_confirmations,
|
77
|
+
max_number_of_retries,
|
78
|
+
frequency,
|
79
|
+
)
|
80
|
+
except Exception as e:
|
81
|
+
return {"status": Status.ERROR.value, "error": str(e)}
|
82
|
+
|
83
|
+
if finality_status_result >= minimum_number_of_finalization_confirmations:
|
84
|
+
return {
|
85
|
+
"status": Status.FINALIZED.value,
|
86
|
+
"numberOfConfirmations": finality_status_result,
|
87
|
+
"requiredConfirmations": minimum_number_of_finalization_confirmations,
|
88
|
+
}
|
89
|
+
else:
|
90
|
+
return {
|
91
|
+
"status": Status.NOT_FINALIZED.value,
|
92
|
+
"numberOfConfirmations": finality_status_result,
|
93
|
+
"requiredConfirmations": minimum_number_of_finalization_confirmations,
|
94
|
+
}
|
@@ -19,7 +19,7 @@ from dataclasses import asdict
|
|
19
19
|
from typing import Any, Callable, Sequence
|
20
20
|
|
21
21
|
from dkg.exceptions import ValidationError
|
22
|
-
from dkg.manager import DefaultRequestManager
|
22
|
+
from dkg.managers.manager import DefaultRequestManager
|
23
23
|
from dkg.method import Method
|
24
24
|
from dkg.types import TReturn
|
25
25
|
|
File without changes
|
@@ -15,11 +15,11 @@
|
|
15
15
|
# specific language governing permissions and limitations
|
16
16
|
# under the License.
|
17
17
|
|
18
|
-
from dkg.constants import
|
18
|
+
from dkg.constants import DefaultParameters
|
19
19
|
from dkg.dataclasses import BidSuggestionRange
|
20
|
-
from dkg.manager import DefaultRequestManager
|
20
|
+
from dkg.managers.manager import DefaultRequestManager
|
21
21
|
from dkg.method import Method
|
22
|
-
from dkg.module import Module
|
22
|
+
from dkg.modules.module import Module
|
23
23
|
from dkg.types import DataHexStr
|
24
24
|
from dkg.utils.blockchain_request import BlockchainRequest
|
25
25
|
from dkg.utils.node_request import NodeRequest
|
@@ -50,7 +50,7 @@ class Network(Module):
|
|
50
50
|
size_in_bytes,
|
51
51
|
content_asset_storage_address,
|
52
52
|
public_assertion_id,
|
53
|
-
|
53
|
+
DefaultParameters.HASH_FUNCTION_ID.value,
|
54
54
|
range,
|
55
55
|
)
|
56
56
|
|
File without changes
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
from dkg.dataclasses import NodeResponseDict
|
19
|
+
from dkg.managers.async_manager import AsyncRequestManager
|
20
|
+
from dkg.method import Method
|
21
|
+
from dkg.modules.async_module import AsyncModule
|
22
|
+
from dkg.utils.blockchain_request import BlockchainRequest
|
23
|
+
from dkg.types import Address
|
24
|
+
from dkg.services.node_services.async_node_service import AsyncNodeService
|
25
|
+
|
26
|
+
|
27
|
+
class AsyncNode(AsyncModule):
|
28
|
+
def __init__(self, manager: AsyncRequestManager, node_service: AsyncNodeService):
|
29
|
+
self.manager = manager
|
30
|
+
self.node_service = node_service
|
31
|
+
|
32
|
+
@property
|
33
|
+
async def info(self) -> NodeResponseDict:
|
34
|
+
return await self.node_service.info()
|
35
|
+
|
36
|
+
_get_identity_id = Method(BlockchainRequest.get_identity_id)
|
37
|
+
|
38
|
+
async def get_identity_id(self, operational: Address) -> int:
|
39
|
+
return await self._get_identity_id(operational)
|
@@ -16,9 +16,9 @@
|
|
16
16
|
# under the License.
|
17
17
|
|
18
18
|
from dkg.dataclasses import NodeResponseDict
|
19
|
-
from dkg.manager import DefaultRequestManager
|
19
|
+
from dkg.managers.manager import DefaultRequestManager
|
20
20
|
from dkg.method import Method
|
21
|
-
from dkg.module import Module
|
21
|
+
from dkg.modules.module import Module
|
22
22
|
from dkg.utils.node_request import NodeRequest
|
23
23
|
from dkg.utils.blockchain_request import BlockchainRequest
|
24
24
|
from dkg.types import Address
|
File without changes
|
@@ -27,9 +27,9 @@ from dkg.dataclasses import (
|
|
27
27
|
ParanetNodesAccessPolicy,
|
28
28
|
ParanetMinersAccessPolicy,
|
29
29
|
)
|
30
|
-
from dkg.manager import DefaultRequestManager
|
30
|
+
from dkg.managers.manager import DefaultRequestManager
|
31
31
|
from dkg.method import Method
|
32
|
-
from dkg.module import Module
|
32
|
+
from dkg.modules.module import Module
|
33
33
|
from dkg.types import Address, UAL, HexStr
|
34
34
|
from dkg.utils.blockchain_request import BlockchainRequest
|
35
35
|
from dkg.utils.ual import parse_ual
|
dkg/providers/__init__.py
CHANGED
@@ -1,2 +1,9 @@
|
|
1
|
-
from .blockchain import BlockchainProvider
|
2
|
-
from .
|
1
|
+
from .blockchain import BlockchainProvider, AsyncBlockchainProvider
|
2
|
+
from .node import NodeHTTPProvider, AsyncNodeHTTPProvider
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"BlockchainProvider",
|
6
|
+
"AsyncBlockchainProvider",
|
7
|
+
"NodeHTTPProvider",
|
8
|
+
"AsyncNodeHTTPProvider",
|
9
|
+
]
|
@@ -0,0 +1,245 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
import os
|
19
|
+
import asyncio
|
20
|
+
from dotenv import load_dotenv
|
21
|
+
from functools import wraps
|
22
|
+
from typing import Any
|
23
|
+
|
24
|
+
import aiohttp
|
25
|
+
from dkg.constants import BLOCKCHAINS
|
26
|
+
from dkg.exceptions import (
|
27
|
+
AccountMissing,
|
28
|
+
NetworkNotSupported,
|
29
|
+
)
|
30
|
+
from dkg.types import URI, Address, Environment, Wei
|
31
|
+
from web3.contract import Contract
|
32
|
+
from web3.contract.contract import ContractFunction
|
33
|
+
from web3.types import TxReceipt
|
34
|
+
from web3.providers import AsyncHTTPProvider
|
35
|
+
from web3 import AsyncWeb3
|
36
|
+
from dkg.providers.blockchain.base_blockchain import BaseBlockchainProvider
|
37
|
+
|
38
|
+
|
39
|
+
class AsyncBlockchainProvider(BaseBlockchainProvider):
|
40
|
+
def __init__(
|
41
|
+
self,
|
42
|
+
environment: Environment,
|
43
|
+
blockchain_id: str,
|
44
|
+
rpc_uri: URI | None = None,
|
45
|
+
gas_price: Wei | None = None,
|
46
|
+
verify: bool = True,
|
47
|
+
):
|
48
|
+
super().__init__(environment, blockchain_id, rpc_uri, gas_price)
|
49
|
+
|
50
|
+
ssl_context = None if verify else False
|
51
|
+
self.w3 = AsyncWeb3(
|
52
|
+
AsyncHTTPProvider(self.rpc_uri, request_kwargs={"ssl": ssl_context})
|
53
|
+
)
|
54
|
+
|
55
|
+
if self.blockchain_id is None:
|
56
|
+
self.blockchain_id = f"{blockchain_id}:{self.w3.eth.chain_id}"
|
57
|
+
if self.blockchain_id not in BLOCKCHAINS[self.environment]:
|
58
|
+
raise NetworkNotSupported(
|
59
|
+
f"Network with blockchain ID {self.blockchain_id} isn't supported!"
|
60
|
+
)
|
61
|
+
|
62
|
+
self.gas_price_oracle = BLOCKCHAINS[self.environment][self.blockchain_id].get(
|
63
|
+
"gas_price_oracle",
|
64
|
+
None,
|
65
|
+
)
|
66
|
+
|
67
|
+
hub_address: Address = BLOCKCHAINS[self.environment][self.blockchain_id]["hub"]
|
68
|
+
self.contracts: dict[str, Contract] = {
|
69
|
+
"Hub": self.w3.eth.contract(
|
70
|
+
address=hub_address,
|
71
|
+
abi=self.abi["Hub"],
|
72
|
+
decode_tuples=True,
|
73
|
+
)
|
74
|
+
}
|
75
|
+
|
76
|
+
self.contracts_initialized = False
|
77
|
+
|
78
|
+
load_dotenv()
|
79
|
+
if private_key := os.environ.get("PRIVATE_KEY"):
|
80
|
+
self.set_account(private_key)
|
81
|
+
|
82
|
+
async def ensure_contracts_initialized(self):
|
83
|
+
if not self.contracts_initialized:
|
84
|
+
await self._init_contracts()
|
85
|
+
self.contracts_initialized = True
|
86
|
+
|
87
|
+
async def make_json_rpc_request(
|
88
|
+
self, endpoint: str, args: dict[str, Any] = {}
|
89
|
+
) -> Any:
|
90
|
+
web3_method = getattr(self.w3.eth, endpoint)
|
91
|
+
|
92
|
+
if callable(web3_method):
|
93
|
+
return await web3_method(**args)
|
94
|
+
else:
|
95
|
+
return web3_method
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def handle_updated_contract(func):
|
99
|
+
@wraps(func)
|
100
|
+
async def wrapper(self, *args, **kwargs):
|
101
|
+
contract_name = kwargs.get("contract") or (args[0] if args else None)
|
102
|
+
|
103
|
+
try:
|
104
|
+
return await func(self, *args, **kwargs)
|
105
|
+
except Exception as err:
|
106
|
+
if (
|
107
|
+
contract_name
|
108
|
+
and isinstance(contract_name, str)
|
109
|
+
and any(msg in str(err) for msg in ["revert", "VM Exception"])
|
110
|
+
and not await self._check_contract_status(contract_name)
|
111
|
+
):
|
112
|
+
is_updated = await self._update_contract_instance(contract_name)
|
113
|
+
if is_updated:
|
114
|
+
return await func(self, *args, **kwargs)
|
115
|
+
raise err
|
116
|
+
|
117
|
+
return wrapper
|
118
|
+
|
119
|
+
@handle_updated_contract
|
120
|
+
async def call_function(
|
121
|
+
self,
|
122
|
+
contract: str | dict[str, str],
|
123
|
+
function: str,
|
124
|
+
args: dict[str, Any] = {},
|
125
|
+
state_changing: bool = False,
|
126
|
+
gas_price: Wei | None = None,
|
127
|
+
gas_limit: Wei | None = None,
|
128
|
+
) -> TxReceipt | Any:
|
129
|
+
await self.ensure_contracts_initialized()
|
130
|
+
if isinstance(contract, str):
|
131
|
+
contract_name = contract
|
132
|
+
contract_instance = self.contracts[contract_name]
|
133
|
+
else:
|
134
|
+
contract_name = contract["name"]
|
135
|
+
contract_instance = self.w3.eth.contract(
|
136
|
+
address=contract["address"],
|
137
|
+
abi=self.abi[contract_name],
|
138
|
+
decode_tuples=True,
|
139
|
+
)
|
140
|
+
self.contracts[contract_name] = contract_instance
|
141
|
+
|
142
|
+
contract_function: ContractFunction = getattr(
|
143
|
+
contract_instance.functions, function
|
144
|
+
)
|
145
|
+
|
146
|
+
if not state_changing:
|
147
|
+
result = await contract_function(**args).call()
|
148
|
+
if function in (
|
149
|
+
output_named_tuples := self.output_named_tuples[contract_name]
|
150
|
+
):
|
151
|
+
result = output_named_tuples[function](*result)
|
152
|
+
return result
|
153
|
+
else:
|
154
|
+
if not hasattr(self, "account"):
|
155
|
+
raise AccountMissing(
|
156
|
+
"State-changing transactions can be performed only with specified "
|
157
|
+
"account."
|
158
|
+
)
|
159
|
+
|
160
|
+
options = {
|
161
|
+
"gas": gas_limit or await contract_function(**args).estimate_gas(),
|
162
|
+
}
|
163
|
+
|
164
|
+
gas_price = (
|
165
|
+
self.gas_price or gas_price or await self._get_network_gas_price()
|
166
|
+
)
|
167
|
+
|
168
|
+
if gas_price is not None:
|
169
|
+
options["gasPrice"] = gas_price
|
170
|
+
|
171
|
+
tx_hash = await contract_function(**args).transact(options)
|
172
|
+
tx_receipt = await self.w3.eth.wait_for_transaction_receipt(tx_hash)
|
173
|
+
|
174
|
+
return tx_receipt
|
175
|
+
|
176
|
+
async def _get_network_gas_price(self) -> Wei | None:
|
177
|
+
if self.environment == "development":
|
178
|
+
return None
|
179
|
+
|
180
|
+
async def fetch_gas_price(oracle_url: str) -> Wei | None:
|
181
|
+
try:
|
182
|
+
async with aiohttp.ClientSession() as session:
|
183
|
+
async with session.get(oracle_url) as response:
|
184
|
+
response.raise_for_status()
|
185
|
+
data: dict = await response.json()
|
186
|
+
|
187
|
+
if "result" in data:
|
188
|
+
return int(data["result"], 16)
|
189
|
+
elif "average" in data:
|
190
|
+
return self.w3.to_wei(data["average"], "gwei")
|
191
|
+
else:
|
192
|
+
return None
|
193
|
+
except Exception:
|
194
|
+
return None
|
195
|
+
|
196
|
+
oracles = self.gas_price_oracle
|
197
|
+
if oracles is not None:
|
198
|
+
if isinstance(oracles, str):
|
199
|
+
oracles = [oracles]
|
200
|
+
|
201
|
+
for oracle_url in oracles:
|
202
|
+
gas_price = await fetch_gas_price(oracle_url)
|
203
|
+
if gas_price is not None:
|
204
|
+
return gas_price
|
205
|
+
|
206
|
+
return None
|
207
|
+
|
208
|
+
async def _init_contracts(self):
|
209
|
+
init_tasks = []
|
210
|
+
for contract in self.abi.keys():
|
211
|
+
if contract == "Hub":
|
212
|
+
continue
|
213
|
+
init_tasks.append(self._update_contract_instance(contract))
|
214
|
+
await asyncio.gather(*init_tasks)
|
215
|
+
|
216
|
+
async def _update_contract_instance(self, contract: str) -> bool:
|
217
|
+
[is_contract, is_storage] = await asyncio.gather(
|
218
|
+
self.contracts["Hub"].functions.isContract(contractName=contract).call(),
|
219
|
+
self.contracts["Hub"]
|
220
|
+
.functions.isAssetStorage(assetStorageName=contract)
|
221
|
+
.call(),
|
222
|
+
)
|
223
|
+
if is_contract or is_storage:
|
224
|
+
self.contracts[contract] = self.w3.eth.contract(
|
225
|
+
address=(
|
226
|
+
await self.contracts["Hub"]
|
227
|
+
.functions.getAssetStorageAddress(contract)
|
228
|
+
.call()
|
229
|
+
if contract.endswith("AssetStorage")
|
230
|
+
or contract.endswith("CollectionStorage")
|
231
|
+
else await self.contracts["Hub"]
|
232
|
+
.functions.getContractAddress(contract)
|
233
|
+
.call()
|
234
|
+
),
|
235
|
+
abi=self.abi[contract],
|
236
|
+
decode_tuples=True,
|
237
|
+
)
|
238
|
+
return True
|
239
|
+
return False
|
240
|
+
|
241
|
+
async def _check_contract_status(self, contract: str) -> bool:
|
242
|
+
try:
|
243
|
+
return await self.call_function(contract, "status")
|
244
|
+
except Exception:
|
245
|
+
return False
|