Coverage for silkaj/wot/membership.py: 100%
67 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-22 12:04 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-22 12:04 +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
18import pendulum
19import rich_click as click
20from duniterpy.api import bma
21from duniterpy.documents import BlockID, Membership, get_block_id
22from duniterpy.key import SigningKey
24from silkaj import auth, tui
25from silkaj.blockchain import tools as bc_tools
26from silkaj.constants import DATE
27from silkaj.g1_monetary_license import license_approval
28from silkaj.network import client_instance, send_document
29from silkaj.public_key import gen_pubkey_checksum
30from silkaj.wot import tools as w_tools
33@click.command("membership", help="Send or renew membership.")
34@click.pass_context
35def send_membership(ctx: click.Context) -> None:
36 dry_run = ctx.obj["DRY_RUN"]
38 # Authentication
39 key = auth.auth_method()
41 # Get the identity information
42 head_block = bc_tools.get_head_block()
43 membership_block_id = BlockID(head_block["number"], head_block["hash"])
44 identity = (w_tools.choose_identity(key.pubkey))[0]
45 identity_uid = identity["uid"]
46 identity_block_id = get_block_id(identity["meta"]["timestamp"])
48 # Display license and ask for confirmation
49 currency = head_block["currency"]
50 if not dry_run:
51 license_approval(currency)
53 # Confirmation
54 display_confirmation_table(identity_uid, key.pubkey, identity_block_id)
55 if not dry_run and not ctx.obj["DISPLAY_DOCUMENT"]:
56 tui.send_doc_confirmation("membership document for this identity")
58 # Create and sign membership document
59 membership = generate_membership_document(
60 key.pubkey,
61 membership_block_id,
62 identity_uid,
63 identity_block_id,
64 currency,
65 key,
66 )
68 logging.debug(membership.signed_raw())
70 if dry_run:
71 click.echo(membership.signed_raw())
72 ctx.exit()
74 if ctx.obj["DISPLAY_DOCUMENT"]:
75 click.echo(membership.signed_raw())
76 tui.send_doc_confirmation("membership document for this identity")
78 # Send the membership signed raw document to the node
79 send_document(bma.blockchain.membership, membership)
82def display_confirmation_table(
83 identity_uid: str,
84 pubkey: str,
85 identity_block_id: BlockID,
86) -> None:
87 """
88 Check whether there is pending memberships already in the mempool
89 Display their expiration date
91 Actually, sending a membership document works even if the time
92 between two renewals is not awaited as for the certification
93 """
95 client = client_instance()
97 identities_requirements = client(bma.wot.requirements, pubkey, pubkey=True)
98 for identity_requirements in identities_requirements["identities"]:
99 if identity_requirements["uid"] == identity_uid:
100 membership_expires = identity_requirements["membershipExpiresIn"]
101 pending_expires = identity_requirements["membershipPendingExpiresIn"]
102 pending_memberships = identity_requirements["pendingMemberships"]
103 break
105 table = []
106 if membership_expires:
107 expires = pendulum.now().add(seconds=membership_expires).diff_for_humans()
108 table.append(["Expiration date of current membership", expires])
110 if pending_memberships:
111 table.append(
112 [
113 "Number of pending membership(s) in the mempool",
114 len(pending_memberships),
115 ],
116 )
118 table.append(
119 [
120 "Pending membership documents will expire",
121 pendulum.now().add(seconds=pending_expires).diff_for_humans(),
122 ],
123 )
125 table.append(["User Identifier (UID)", identity_uid])
126 table.append(["Public Key", gen_pubkey_checksum(pubkey)])
128 table.append(["Block Identity", str(identity_block_id)[:45] + "…"])
130 block = client(bma.blockchain.block, identity_block_id.number)
131 table.append(
132 [
133 "Identity published",
134 pendulum.from_timestamp(block["time"], tz="local").format(DATE),
135 ],
136 )
138 params = bc_tools.get_blockchain_parameters()
139 table.append(
140 [
141 "Expiration date of new membership",
142 pendulum.now().add(seconds=params["msValidity"]).diff_for_humans(),
143 ],
144 )
146 table.append(
147 [
148 "Expiration date of new membership from the mempool",
149 pendulum.now().add(seconds=params["msPeriod"]).diff_for_humans(),
150 ],
151 )
153 display_table = tui.Table()
154 display_table.fill_rows(table)
155 click.echo(display_table.draw())
158def generate_membership_document(
159 pubkey: str,
160 membership_block_id: BlockID,
161 identity_uid: str,
162 identity_block_id: BlockID,
163 currency: str,
164 key: SigningKey = None,
165) -> Membership:
166 return Membership(
167 issuer=pubkey,
168 membership_block_id=membership_block_id,
169 uid=identity_uid,
170 identity_block_id=identity_block_id,
171 currency=currency,
172 signing_key=key,
173 )