Coverage for silkaj/wot/membership.py: 100%
68 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-20 12:29 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-20 12:29 +0000
1# Copyright 2016-2025 Maël Azimi <m.a@moul.re>
2#
3# Silkaj is free software: you can redistribute it and/or modify
4# it under the terms of the GNU Affero General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# Silkaj is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU Affero General Public License for more details.
12#
13# You should have received a copy of the GNU Affero General Public License
14# along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
16import logging
17import sys
19import arrow
20import rich_click as click
21from duniterpy.api import bma
22from duniterpy.documents import BlockID, Membership, get_block_id
23from duniterpy.key import SigningKey
25from silkaj import auth, tui
26from silkaj.blockchain import tools as bc_tools
27from silkaj.constants import DATE, SUCCESS_EXIT_STATUS
28from silkaj.g1_monetary_license import license_approval
29from silkaj.network import client_instance, send_document
30from silkaj.public_key import gen_pubkey_checksum
31from silkaj.wot import tools as w_tools
34@click.command(
35 "membership",
36 help="Send and sign membership document: \n\
37for first emission and for renewal",
38)
39@click.pass_context
40def send_membership(ctx: click.Context) -> None:
41 dry_run = ctx.obj["DRY_RUN"]
43 # Authentication
44 key = auth.auth_method()
46 # Get the identity information
47 head_block = bc_tools.get_head_block()
48 membership_block_id = BlockID(head_block["number"], head_block["hash"])
49 identity = (w_tools.choose_identity(key.pubkey))[0]
50 identity_uid = identity["uid"]
51 identity_block_id = get_block_id(identity["meta"]["timestamp"])
53 # Display license and ask for confirmation
54 currency = head_block["currency"]
55 if not dry_run:
56 license_approval(currency)
58 # Confirmation
59 display_confirmation_table(identity_uid, key.pubkey, identity_block_id)
60 if not dry_run and not ctx.obj["DISPLAY_DOCUMENT"]:
61 tui.send_doc_confirmation("membership document for this identity")
63 # Create and sign membership document
64 membership = generate_membership_document(
65 key.pubkey,
66 membership_block_id,
67 identity_uid,
68 identity_block_id,
69 currency,
70 key,
71 )
73 logging.debug(membership.signed_raw())
75 if dry_run:
76 click.echo(membership.signed_raw())
77 sys.exit(SUCCESS_EXIT_STATUS)
79 if ctx.obj["DISPLAY_DOCUMENT"]:
80 click.echo(membership.signed_raw())
81 tui.send_doc_confirmation("membership document for this identity")
83 # Send the membership signed raw document to the node
84 send_document(bma.blockchain.membership, membership)
87def display_confirmation_table(
88 identity_uid: str,
89 pubkey: str,
90 identity_block_id: BlockID,
91) -> None:
92 """
93 Check whether there is pending memberships already in the mempool
94 Display their expiration date
96 Actually, sending a membership document works even if the time
97 between two renewals is not awaited as for the certification
98 """
100 client = client_instance()
102 identities_requirements = client(bma.wot.requirements, pubkey, pubkey=True)
103 for identity_requirements in identities_requirements["identities"]:
104 if identity_requirements["uid"] == identity_uid:
105 membership_expires = identity_requirements["membershipExpiresIn"]
106 pending_expires = identity_requirements["membershipPendingExpiresIn"]
107 pending_memberships = identity_requirements["pendingMemberships"]
108 break
110 table = []
111 if membership_expires:
112 expires = arrow.now().shift(seconds=membership_expires).humanize()
113 table.append(["Expiration date of current membership", expires])
115 if pending_memberships:
116 table.append(
117 [
118 "Number of pending membership(s) in the mempool",
119 len(pending_memberships),
120 ],
121 )
123 table.append(
124 [
125 "Pending membership documents will expire",
126 arrow.now().shift(seconds=pending_expires).humanize(),
127 ],
128 )
130 table.append(["User Identifier (UID)", identity_uid])
131 table.append(["Public Key", gen_pubkey_checksum(pubkey)])
133 table.append(["Block Identity", str(identity_block_id)[:45] + "…"])
135 block = client(bma.blockchain.block, identity_block_id.number)
136 table.append(
137 [
138 "Identity published",
139 arrow.get(block["time"]).to("local").format(DATE),
140 ],
141 )
143 params = bc_tools.get_blockchain_parameters()
144 table.append(
145 [
146 "Expiration date of new membership",
147 arrow.now().shift(seconds=params["msValidity"]).humanize(),
148 ],
149 )
151 table.append(
152 [
153 "Expiration date of new membership from the mempool",
154 arrow.now().shift(seconds=params["msPeriod"]).humanize(),
155 ],
156 )
158 display_table = tui.Table()
159 display_table.fill_rows(table)
160 click.echo(display_table.draw())
163def generate_membership_document(
164 pubkey: str,
165 membership_block_id: BlockID,
166 identity_uid: str,
167 identity_block_id: BlockID,
168 currency: str,
169 key: SigningKey = None,
170) -> Membership:
171 return Membership(
172 issuer=pubkey,
173 membership_block_id=membership_block_id,
174 uid=identity_uid,
175 identity_block_id=identity_block_id,
176 currency=currency,
177 signing_key=key,
178 )