fabric-chaincode-python 2.5.2__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.
Files changed (40) hide show
  1. fabric_chaincode_python-2.5.2.dist-info/METADATA +101 -0
  2. fabric_chaincode_python-2.5.2.dist-info/RECORD +40 -0
  3. fabric_chaincode_python-2.5.2.dist-info/WHEEL +5 -0
  4. fabric_chaincode_python-2.5.2.dist-info/licenses/LICENSE +201 -0
  5. fabric_chaincode_python-2.5.2.dist-info/licenses/NOTICE +18 -0
  6. fabric_chaincode_python-2.5.2.dist-info/top_level.txt +1 -0
  7. src/__init__.py +5 -0
  8. src/fabric_contract_api/__init__.py +51 -0
  9. src/fabric_contract_api/contractapi/__init__.py +29 -0
  10. src/fabric_contract_api/contractapi/contract.py +141 -0
  11. src/fabric_contract_api/contractapi/contract_chaincode.py +552 -0
  12. src/fabric_contract_api/contractapi/system_contract.py +45 -0
  13. src/fabric_contract_api/contractapi/transaction_context.py +160 -0
  14. src/fabric_contract_api/contractapi/utils/__init__.py +4 -0
  15. src/fabric_contract_api/contractapi/utils/undefined_interface.py +33 -0
  16. src/fabric_contract_api/internal/__init__.py +16 -0
  17. src/fabric_contract_api/internal/contract_function.py +453 -0
  18. src/fabric_contract_api/internal/transaction_handler.py +131 -0
  19. src/fabric_contract_api/internal/types.py +180 -0
  20. src/fabric_contract_api/internal/types_handler.py +224 -0
  21. src/fabric_contract_api/internal/utils.py +59 -0
  22. src/fabric_contract_api/metadata/__init__.py +32 -0
  23. src/fabric_contract_api/metadata/metadata.py +456 -0
  24. src/fabric_contract_api/metadata/schema.py +238 -0
  25. src/fabric_contract_api/serializer/__init__.py +5 -0
  26. src/fabric_contract_api/serializer/json_transaction_serializer.py +363 -0
  27. src/fabric_contract_api/serializer/transaction_serializer.py +45 -0
  28. src/fabric_shim/__init__.py +2 -0
  29. src/fabric_shim/chaincode.py +8 -0
  30. src/fabric_shim/handler.py +336 -0
  31. src/fabric_shim/interfaces.py +134 -0
  32. src/fabric_shim/iterators.py +107 -0
  33. src/fabric_shim/logger.py +1 -0
  34. src/fabric_shim/logging.py +63 -0
  35. src/fabric_shim/msg_queue_handler.py +91 -0
  36. src/fabric_shim/response.py +49 -0
  37. src/fabric_shim/server.py +155 -0
  38. src/fabric_shim/stub.py +342 -0
  39. src/fabric_shim/utils.py +41 -0
  40. src/version.py +3 -0
@@ -0,0 +1,552 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """High-level contract chaincode.
3
+
4
+ This is the Python equivalent of Go's ``contractapi/contract_chaincode.go``.
5
+ It implements the :class:`fabric_shim.Chaincode` interface and dispatches
6
+ incoming Init/Invoke transactions to the appropriate registered contract's
7
+ transaction functions.
8
+
9
+ Typical usage::
10
+
11
+ from fabric_contract_api.contractapi import Contract, ContractChaincode
12
+ from fabric_shim import start
13
+
14
+ class MyContract(Contract):
15
+ Name = "MyContract"
16
+
17
+ def my_transaction(self, ctx, key: str) -> str:
18
+ ...
19
+
20
+ if __name__ == "__main__":
21
+ cc = ContractChaincode.new_chaincode(MyContract())
22
+ cc.start()
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import os
29
+ import re
30
+ from typing import Any, Dict, List, Optional, Tuple, Type
31
+
32
+ from src.fabric_shim.interfaces import Chaincode, ChaincodeStubInterface
33
+ from src.fabric_shim.response import ResponseCode, success as _shim_success, error as _shim_error
34
+ from src.fabric_shim.logging import LOGGER
35
+
36
+ from ..internal.contract_function import CallType, ContractFunction
37
+ from ..internal.transaction_handler import (
38
+ TransactionHandler,
39
+ TransactionHandlerType,
40
+ new_transaction_handler,
41
+ )
42
+ from ..internal.utils import string_in_slice
43
+ from ..metadata import (
44
+ ComponentMetadata,
45
+ ContractChaincodeMetadata,
46
+ ContractMetadata,
47
+ InfoMetadata,
48
+ TransactionMetadata,
49
+ get_json_schema,
50
+ read_metadata_file,
51
+ validate_against_schema,
52
+ )
53
+ from ..serializer import JSONSerializer, TransactionSerializer
54
+ from .contract import (
55
+ Contract,
56
+ ContractInterface,
57
+ EvaluationContractInterface,
58
+ IgnoreContractInterface,
59
+ )
60
+ from .system_contract import SystemContract, SystemContractName
61
+ from .transaction_context import (
62
+ ClientIdentity,
63
+ SettableTransactionContextInterface,
64
+ TransactionContext,
65
+ TransactionContextInterface,
66
+ )
67
+
68
+
69
+ #: Environment variable that holds the chaincode server address.
70
+ SERVER_ADDRESS_VARIABLE = "CHAINCODE_SERVER_ADDRESS"
71
+ #: Environment variable that holds the chaincode ID name.
72
+ CHAINCODE_ID_VARIABLE = "CORE_CHAINCODE_ID_NAME"
73
+
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Internal: per-contract metadata used by the dispatcher
77
+ # ---------------------------------------------------------------------------
78
+
79
+
80
+ class _ContractChaincodeContract:
81
+ """Internal record describing one registered contract."""
82
+
83
+ def __init__(self) -> None:
84
+ self.info: InfoMetadata = InfoMetadata()
85
+ self.functions: Dict[str, ContractFunction] = {}
86
+ self.unknown_transaction: Optional[TransactionHandler] = None
87
+ self.before_transaction: Optional[TransactionHandler] = None
88
+ self.after_transaction: Optional[TransactionHandler] = None
89
+ self.transaction_context_handler: type = TransactionContext
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # ContractChaincode
94
+ # ---------------------------------------------------------------------------
95
+
96
+
97
+ class ContractChaincode(Chaincode):
98
+ """Chaincode adapter that dispatches to registered contracts."""
99
+
100
+ DefaultContract: str = ""
101
+ Info: InfoMetadata = InfoMetadata()
102
+ TransactionSerializer: TransactionSerializer = JSONSerializer()
103
+
104
+ def __init__(self) -> None:
105
+ # We don't call ``super().__init__`` because ``Chaincode`` is abstract
106
+ # and our intent is to provide concrete implementations of ``init``
107
+ # and ``invoke``.
108
+ self._contracts: Dict[str, _ContractChaincodeContract] = {}
109
+ self._metadata: ContractChaincodeMetadata = ContractChaincodeMetadata()
110
+ self._system_contract: SystemContract = SystemContract()
111
+ self._ci_methods: List[str] = _get_ci_methods()
112
+
113
+ # ------------------------------------------------------------------
114
+ # Public API
115
+ # ------------------------------------------------------------------
116
+
117
+ @classmethod
118
+ def new_chaincode(cls, *contracts: ContractInterface) -> "ContractChaincode":
119
+ """Create a new :class:`ContractChaincode` from one or more contracts.
120
+
121
+ Mirrors Go's ``NewChaincode``.
122
+ """
123
+ cc = cls()
124
+ for contract in contracts:
125
+ additional_excludes: List[str] = []
126
+ # Mirror Go's duck-typing: if the contract exposes
127
+ # ``get_ignored_functions`` we treat it as an
128
+ # ``IgnoreContractInterface``.
129
+ ignored_fn = getattr(contract, "get_ignored_functions", None)
130
+ if callable(ignored_fn):
131
+ try:
132
+ additional_excludes = list(ignored_fn() or [])
133
+ except Exception:
134
+ additional_excludes = []
135
+ err = cc._add_contract(contract, cc._ci_methods + additional_excludes)
136
+ if err is not None:
137
+ raise err
138
+
139
+ # System contract
140
+ cc._system_contract = SystemContract()
141
+ cc._system_contract.Name = SystemContractName
142
+ err = cc._add_contract(cc._system_contract, cc._ci_methods)
143
+ if err is not None:
144
+ raise err
145
+
146
+ err = cc._augment_metadata()
147
+ if err is not None:
148
+ raise err
149
+
150
+ metadata_json = cc._metadata.to_json()
151
+ cc._system_contract._set_metadata(metadata_json)
152
+ cc.TransactionSerializer = JSONSerializer()
153
+ return cc
154
+
155
+ def start(self, cc_id: Optional[str] = None, address: Optional[str] = None,
156
+ key: Optional[bytes] = None, cert: Optional[bytes] = None,
157
+ client_ca_certs: Optional[bytes] = None) -> None:
158
+ """Start the chaincode in the Fabric shim.
159
+
160
+ Reads the chaincode ID and server address from the environment when
161
+ not explicitly provided, mirroring Go's ``ContractChaincode.Start``.
162
+ """
163
+ # Local import to avoid a hard dependency cycle when this module is
164
+ # imported eagerly by user code.
165
+ from src.fabric_shim.server import start as _shim_start
166
+
167
+ _shim_start(
168
+ cc=self,
169
+ cc_id=cc_id,
170
+ address=address,
171
+ key=key,
172
+ cert=cert,
173
+ client_ca_certs=client_ca_certs,
174
+ )
175
+
176
+ # ------------------------------------------------------------------
177
+ # Chaincode interface
178
+ # ------------------------------------------------------------------
179
+
180
+ async def init(self, stub: ChaincodeStubInterface):
181
+ """Handle the Init transaction.
182
+
183
+ Mirrors Go's ``Init``. When a function name is supplied (e.g. when
184
+ the chaincode container is instantiated with ``{Init, args}``), this
185
+ method delegates to :meth:`invoke`. Otherwise it returns a default
186
+ success message.
187
+ """
188
+ try:
189
+ ns_fcn, _ = _get_function_and_parameters(stub)
190
+ except Exception:
191
+ ns_fcn = ""
192
+
193
+ if not ns_fcn:
194
+ return _shim_success(b"Default initiator successful.")
195
+ return await self.invoke(stub)
196
+
197
+ async def invoke(self, stub: ChaincodeStubInterface):
198
+ """Handle the Invoke transaction.
199
+
200
+ Mirrors Go's ``Invoke``.
201
+ """
202
+ ns, fn, params = self._get_namespace_function_and_params(stub)
203
+
204
+ ns_contract = self._contracts.get(ns)
205
+ if ns_contract is None:
206
+ return _shim_error_response(f"Contract not found with name {ns}")
207
+
208
+ if not fn:
209
+ return _shim_error_response("Blank function name passed")
210
+
211
+ ctx = ns_contract.transaction_context_handler()
212
+ ctx.set_stub(stub)
213
+ ctx.set_client_identity(ClientIdentity(stub))
214
+
215
+ # Before-transaction
216
+ if ns_contract.before_transaction is not None:
217
+ _, _, err_res = await ns_contract.before_transaction.call(ctx, None, self.TransactionSerializer)
218
+ if err_res is not None:
219
+ return _shim_error_response(str(err_res))
220
+
221
+ # Look up the named function. Method names are case-sensitive —
222
+ # the contract API uses the original Python method names (typically
223
+ # snake_case). Callers may also use the capitalised Go-style form
224
+ # (``Create`` vs ``create``) for backwards-compatibility.
225
+ contract_fn = ns_contract.functions.get(fn) or ns_contract.functions.get(_to_first_rune_upper_case(fn))
226
+ success_return = ""
227
+ success_iface: Any = None
228
+ error_return: Optional[BaseException] = None
229
+
230
+ if contract_fn is None:
231
+ unknown = ns_contract.unknown_transaction
232
+ if unknown is None:
233
+ return _shim_error_response(
234
+ f"Function {fn} not found in contract {ns}"
235
+ )
236
+ success_return, success_iface, error_return = await unknown.call(
237
+ ctx, None, self.TransactionSerializer
238
+ )
239
+ else:
240
+ transaction_schema: Optional[TransactionMetadata] = None
241
+ contract_meta = self._metadata.contracts.get(ns)
242
+ if contract_meta is not None:
243
+ for tx in contract_meta.transactions:
244
+ if tx.name == fn or tx.name == _to_first_rune_upper_case(fn):
245
+ transaction_schema = tx
246
+ break
247
+
248
+ success_return, success_iface, error_return = await contract_fn.call(
249
+ ctx, transaction_schema, self._metadata.components,
250
+ self.TransactionSerializer, *params,
251
+ )
252
+
253
+ if error_return is not None:
254
+ return _shim_error_response(str(error_return))
255
+
256
+ # After-transaction
257
+ if ns_contract.after_transaction is not None:
258
+ _, _, err_res = await ns_contract.after_transaction.call(
259
+ ctx, success_iface, self.TransactionSerializer
260
+ )
261
+ if err_res is not None:
262
+ return _shim_error_response(str(err_res))
263
+
264
+ # ``success_return`` is whatever the contract function returned
265
+ # after serializer conversion. It is typically a JSON string but
266
+ # may be raw bytes (e.g. for ``bytes`` return types). Build the
267
+ # Fabric Response via ``shim.success()`` so the bytes land in the
268
+ # ``payload`` field — the field ``peer chaincode query`` prints.
269
+ if isinstance(success_return, (bytes, bytearray)):
270
+ payload = bytes(success_return)
271
+ elif success_return is None:
272
+ payload = b""
273
+ else:
274
+ payload = str(success_return).encode("utf-8")
275
+ return _shim_success(payload)
276
+
277
+ # ------------------------------------------------------------------
278
+ # Internal: contract registration
279
+ # ------------------------------------------------------------------
280
+
281
+ def _add_contract(self, contract: ContractInterface,
282
+ exclude_funcs: List[str]) -> Optional[Exception]:
283
+ """Register *contract* with this chaincode.
284
+
285
+ Mirrors Go's ``addContract``. Returns ``None`` on success or an
286
+ ``Exception`` describing the failure.
287
+ """
288
+ ns = contract.get_name() or type(contract).__name__
289
+ if not ns:
290
+ return ValueError("contract must have a name")
291
+ if ns in self._contracts:
292
+ return ValueError(
293
+ f"multiple contracts being merged into chaincode with name {ns}"
294
+ )
295
+
296
+ ccn = _ContractChaincodeContract()
297
+
298
+ # Resolve the transaction-context handler.
299
+ ctx_handler = contract.get_transaction_context_handler()
300
+ if isinstance(ctx_handler, type):
301
+ ccn.transaction_context_handler = ctx_handler
302
+ else:
303
+ ccn.transaction_context_handler = type(ctx_handler)
304
+
305
+ ccn.info = contract.get_info() or InfoMetadata()
306
+ if not ccn.info.version:
307
+ ccn.info.version = "latest"
308
+ if not ccn.info.title:
309
+ ccn.info.title = ns
310
+
311
+ # Before / After / Unknown transaction hooks.
312
+ ut = contract.get_unknown_transaction()
313
+ if ut is not None:
314
+ try:
315
+ ccn.unknown_transaction = new_transaction_handler(
316
+ ut, ccn.transaction_context_handler,
317
+ TransactionHandlerType.UNKNOWN,
318
+ )
319
+ except Exception as exc:
320
+ return exc
321
+
322
+ bt = contract.get_before_transaction()
323
+ if bt is not None:
324
+ try:
325
+ ccn.before_transaction = new_transaction_handler(
326
+ bt, ccn.transaction_context_handler,
327
+ TransactionHandlerType.BEFORE,
328
+ )
329
+ except Exception as exc:
330
+ return exc
331
+
332
+ at = contract.get_after_transaction()
333
+ if at is not None:
334
+ try:
335
+ ccn.after_transaction = new_transaction_handler(
336
+ at, ccn.transaction_context_handler,
337
+ TransactionHandlerType.AFTER,
338
+ )
339
+ except Exception as exc:
340
+ return exc
341
+
342
+ evaluate_methods: List[str] = []
343
+ # Mirror Go's duck-typing: if the contract exposes
344
+ # ``get_evaluate_transactions`` we treat it as an
345
+ # ``EvaluationContractInterface``.
346
+ evaluate_fn = getattr(contract, "get_evaluate_transactions", None)
347
+ if callable(evaluate_fn):
348
+ try:
349
+ evaluate_methods = list(evaluate_fn() or [])
350
+ except Exception:
351
+ evaluate_methods = []
352
+
353
+ # Walk every public method on the contract instance.
354
+ for method_name in dir(contract):
355
+ if method_name.startswith("_"):
356
+ continue
357
+ if string_in_slice(method_name, exclude_funcs):
358
+ continue
359
+ attr = getattr(contract, method_name, None)
360
+ if not callable(attr):
361
+ continue
362
+ # Skip callables that came from the Contract base class itself
363
+ # (they're already covered by _ci_methods).
364
+ if _is_contract_interface_method(method_name):
365
+ continue
366
+
367
+ call_type = CallType.EVALUATE if string_in_slice(method_name, evaluate_methods) else CallType.SUBMIT
368
+ try:
369
+ ccn.functions[method_name] = ContractFunction.from_callable(
370
+ attr, call_type, ccn.transaction_context_handler,
371
+ )
372
+ except Exception as exc:
373
+ wrapped = ValueError(
374
+ f"error registering method {method_name} on contract {ns}: {exc}"
375
+ )
376
+ wrapped.__cause__ = exc
377
+ return wrapped
378
+
379
+ if not ccn.functions:
380
+ return ValueError(
381
+ f"contracts are required to have at least 1 (non-ignored) public method. "
382
+ f"Contract {ns} has none. Method names that have been ignored: "
383
+ f"{', '.join(exclude_funcs) or '(none)'}"
384
+ )
385
+
386
+ self._contracts[ns] = ccn
387
+ if not self.DefaultContract:
388
+ self.DefaultContract = ns
389
+ return None
390
+
391
+ # ------------------------------------------------------------------
392
+ # Internal: metadata
393
+ # ------------------------------------------------------------------
394
+
395
+ def _reflect_metadata(self) -> ContractChaincodeMetadata:
396
+ reflected = ContractChaincodeMetadata()
397
+ reflected.contracts = {}
398
+ reflected.components = ComponentMetadata()
399
+ reflected.info = self.Info
400
+
401
+ # Make a defensive copy so we don't mutate ``self.Info``.
402
+ if reflected.info is not None:
403
+ reflected.info = InfoMetadata(
404
+ description=reflected.info.description,
405
+ title=reflected.info.title,
406
+ contact=reflected.info.contact,
407
+ license=reflected.info.license,
408
+ version=reflected.info.version,
409
+ terms_of_service=reflected.info.terms_of_service,
410
+ )
411
+ if not reflected.info.version:
412
+ reflected.info.version = "latest"
413
+ if not reflected.info.title:
414
+ reflected.info.title = "undefined"
415
+
416
+ for key, contract in self._contracts.items():
417
+ contract_meta = ContractMetadata()
418
+ contract_meta.name = key
419
+ contract_meta.info = contract.info
420
+ if self.DefaultContract == key:
421
+ contract_meta.default = True
422
+
423
+ for fn_name, fn in contract.functions.items():
424
+ fn_meta = fn.reflect_metadata(fn_name, reflected.components)
425
+ contract_meta.transactions.append(fn_meta)
426
+
427
+ contract_meta.transactions.sort(key=lambda t: t.name)
428
+ reflected.contracts[key] = contract_meta
429
+
430
+ return reflected
431
+
432
+ def _augment_metadata(self) -> Optional[Exception]:
433
+ """Merge reflected metadata with the optional user-supplied file."""
434
+ try:
435
+ file_metadata = read_metadata_file()
436
+ file_not_found = False
437
+ except FileNotFoundError:
438
+ file_metadata = ContractChaincodeMetadata()
439
+ file_not_found = True
440
+
441
+ reflected_metadata = self._reflect_metadata()
442
+ file_metadata.append(reflected_metadata)
443
+ err = file_metadata.compile_schemas()
444
+ if err is not None:
445
+ return err
446
+ err = validate_against_schema(file_metadata)
447
+ if err is not None:
448
+ return err
449
+ self._metadata = file_metadata
450
+ return None
451
+
452
+ # ------------------------------------------------------------------
453
+ # Internal: routing
454
+ # ------------------------------------------------------------------
455
+
456
+ def _get_namespace_function_and_params(self, stub: ChaincodeStubInterface) -> Tuple[str, str, List[str]]:
457
+ ns_fcn, params = _get_function_and_parameters(stub)
458
+ # Split on the last ':' so that contract names with embedded colons
459
+ # are still handled.
460
+ idx = ns_fcn.rfind(":")
461
+ if idx == -1:
462
+ return self.DefaultContract, ns_fcn, params
463
+ return ns_fcn[:idx], ns_fcn[idx + 1:], params
464
+
465
+
466
+ # ---------------------------------------------------------------------------
467
+ # Helpers
468
+ # ---------------------------------------------------------------------------
469
+
470
+
471
+ def _get_ci_methods() -> List[str]:
472
+ """Return the names of every method defined on the contract interfaces."""
473
+ methods: List[str] = []
474
+ seen = set()
475
+ for iface in (ContractInterface, IgnoreContractInterface, EvaluationContractInterface):
476
+ for name in getattr(iface, "__abstractmethods__", frozenset()):
477
+ if name not in seen:
478
+ methods.append(name)
479
+ seen.add(name)
480
+ # Also pick up non-abstract public methods on the base classes
481
+ # (e.g. Contract.get_info is a concrete implementation).
482
+ for name in dir(iface):
483
+ if name.startswith("_"):
484
+ continue
485
+ if name in seen:
486
+ continue
487
+ attr = getattr(iface, name, None)
488
+ if callable(attr):
489
+ methods.append(name)
490
+ seen.add(name)
491
+ # The Contract base class also has the public attributes (Name, Info,
492
+ # ...) — but those aren't methods, so we only need to consider callables.
493
+ for name in dir(Contract):
494
+ if name.startswith("_"):
495
+ continue
496
+ if name in seen:
497
+ continue
498
+ attr = getattr(Contract, name, None)
499
+ if callable(attr):
500
+ methods.append(name)
501
+ seen.add(name)
502
+ return methods
503
+
504
+
505
+ def _is_contract_interface_method(name: str) -> bool:
506
+ """Return ``True`` if *name* is a method defined on a contract interface."""
507
+ for iface in (ContractInterface, IgnoreContractInterface, EvaluationContractInterface, Contract):
508
+ if name in dir(iface):
509
+ return True
510
+ return False
511
+
512
+
513
+ def _to_first_rune_upper_case(text: str) -> str:
514
+ """Capitalize the first character of *text*.
515
+
516
+ Mirrors Go's ``toFirstRuneUpperCase``. Used so that callers can refer to
517
+ a transaction by either ``"myTransaction"`` or ``"mytransaction"`` —
518
+ Python convention is typically lower-case but we want to be lenient.
519
+ """
520
+ if not text:
521
+ return text
522
+ if text[0].isupper():
523
+ return text
524
+ return text[0].upper() + text[1:]
525
+
526
+
527
+ def _get_function_and_parameters(stub: ChaincodeStubInterface) -> Tuple[str, List[str]]:
528
+ """Return ``(function_name, params)`` from the chaincode stub.
529
+
530
+ The shim already provides ``get_function_and_parameters`` but raises when
531
+ called without arguments — we wrap it to make it safer.
532
+ """
533
+ try:
534
+ fcn, params = stub.get_function_and_parameters()
535
+ return fcn, list(params)
536
+ except Exception:
537
+ return "", []
538
+
539
+
540
+ def _shim_error_response(msg: str):
541
+ """Build a ``shim.Error()``-equivalent response.
542
+
543
+ The Response's ``message`` field is a protobuf ``string`` — the peer
544
+ prints it to stderr when ``status >= 400``. We pass the string
545
+ directly (no UTF-8 encoding) so the peer shows the error message
546
+ verbatim.
547
+ """
548
+ from fabric_protos.peer import proposal_response_pb2 as pb
549
+ return pb.Response(status=ResponseCode.ERROR, message=str(msg))
550
+
551
+
552
+ __all__ = ["ContractChaincode"]
@@ -0,0 +1,45 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """System contract added to every chaincode.
3
+
4
+ This is the Python equivalent of Go's ``contractapi/system_contract.go``.
5
+ The system contract exposes a single ``GetMetadata`` transaction that
6
+ returns the JSON-formatted metadata of the chaincode it belongs to.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import List
12
+
13
+ from .contract import Contract
14
+
15
+ #: The well-known name of the system contract.
16
+ SystemContractName = "org.hyperledger.fabric"
17
+
18
+
19
+ class SystemContract(Contract):
20
+ """Built-in contract that exposes chaincode metadata."""
21
+
22
+ Name: str = SystemContractName
23
+
24
+ def __init__(self) -> None:
25
+ super().__init__()
26
+ self._metadata: str = ""
27
+
28
+ def _set_metadata(self, metadata: str) -> None:
29
+ """Inject the metadata JSON into the system contract.
30
+
31
+ Called by :class:`ContractChaincode` once the metadata has been
32
+ compiled from the registered contracts.
33
+ """
34
+ self._metadata = metadata
35
+
36
+ def get_metadata(self) -> str:
37
+ """Return the JSON metadata of the chaincode this contract belongs to."""
38
+ return self._metadata
39
+
40
+ def get_evaluate_transactions(self) -> List[str]:
41
+ """Return the names of the transactions that should be evaluated."""
42
+ return ["get_metadata"]
43
+
44
+
45
+ __all__ = ["SystemContract", "SystemContractName"]