Skip to content

Commit 06089ae

Browse files
authored
Merge pull request #1325 from KitHat/master
ST-623 -- Init Commit multi-sig process
2 parents 653eb2f + 645b812 commit 06089ae

File tree

9 files changed

+400
-24
lines changed

9 files changed

+400
-24
lines changed

plenum/bls/bls_bft_factory.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def create_bls_bft_replica(self, is_master) -> BlsBftReplica:
2626
return BlsBftReplicaPlenum(self._node.name,
2727
self._node.bls_bft,
2828
is_master,
29+
self._node.db_manager,
2930
self._node.metrics)
3031

3132

plenum/bls/bls_bft_replica_plenum.py

Lines changed: 161 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
from crypto.bls.bls_bft import BlsBft
55
from crypto.bls.bls_bft_replica import BlsBftReplica
66
from crypto.bls.bls_multi_signature import MultiSignature, MultiSignatureValue
7-
from plenum.common.constants import DOMAIN_LEDGER_ID, BLS_PREFIX, POOL_LEDGER_ID
7+
from plenum.common.constants import DOMAIN_LEDGER_ID, BLS_PREFIX, POOL_LEDGER_ID, AUDIT_LEDGER_ID, TXN_PAYLOAD, \
8+
TXN_PAYLOAD_DATA, AUDIT_TXN_LEDGER_ROOT, AUDIT_TXN_STATE_ROOT, AUDIT_TXN_PP_SEQ_NO
89
from plenum.common.messages.node_messages import PrePrepare, Prepare, Commit
910
from plenum.common.metrics_collector import MetricsCollector, NullMetricsCollector, measure_time, MetricsName
1011
from plenum.common.types import f
1112
from plenum.common.util import compare_3PC_keys
13+
from plenum.server.database_manager import DatabaseManager
1214
from stp_core.common.log import getlogger
1315

1416
logger = getlogger()
@@ -19,10 +21,14 @@ def __init__(self,
1921
node_id,
2022
bls_bft: BlsBft,
2123
is_master,
24+
database_manager: DatabaseManager,
2225
metrics: MetricsCollector = NullMetricsCollector()):
2326
super().__init__(bls_bft, is_master)
27+
self._all_bls_latest_multi_sigs = None
2428
self.node_id = node_id
29+
self._database_manager = database_manager
2530
self._signatures = {}
31+
self._all_signatures = {}
2632
self._bls_latest_multi_sig = None # MultiSignature
2733
self.state_root_serializer = state_roots_serializer
2834
self.metrics = metrics
@@ -35,6 +41,13 @@ def _can_process_ledger(self, ledger_id):
3541

3642
@measure_time(MetricsName.BLS_VALIDATE_PREPREPARE_TIME)
3743
def validate_pre_prepare(self, pre_prepare: PrePrepare, sender):
44+
if f.BLS_MULTI_SIGS.nm in pre_prepare and pre_prepare.blsMultiSigs:
45+
multi_sigs = pre_prepare.blsMultiSigs
46+
for sig in multi_sigs:
47+
multi_sig = MultiSignature.from_list(*sig)
48+
if not self._validate_multi_sig(multi_sig):
49+
return BlsBftReplica.PPR_BLS_MULTISIG_WRONG
50+
3851
if f.BLS_MULTI_SIG.nm not in pre_prepare or \
3952
pre_prepare.blsMultiSig is None:
4053
return
@@ -48,6 +61,22 @@ def validate_prepare(self, prepare: Prepare, sender):
4861

4962
@measure_time(MetricsName.BLS_VALIDATE_COMMIT_TIME)
5063
def validate_commit(self, commit: Commit, sender, pre_prepare: PrePrepare):
64+
if f.BLS_SIGS.nm in commit:
65+
audit_txn = self._get_correct_audit_transaction(pre_prepare)
66+
if audit_txn:
67+
audit_payload = audit_txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA]
68+
for lid, sig in commit.blsSigs.items():
69+
lid = int(lid)
70+
if lid not in audit_payload[AUDIT_TXN_STATE_ROOT] or lid not in audit_payload[AUDIT_TXN_LEDGER_ROOT]:
71+
return BlsBftReplicaPlenum.CM_BLS_SIG_WRONG
72+
if not self._validate_signature(sender, sig,
73+
BlsBftReplicaPlenum._create_fake_pre_prepare_for_multi_sig(
74+
lid,
75+
audit_payload[AUDIT_TXN_STATE_ROOT][lid],
76+
audit_payload[AUDIT_TXN_LEDGER_ROOT][lid],
77+
pre_prepare
78+
)):
79+
return BlsBftReplicaPlenum.CM_BLS_SIG_WRONG
5180
if f.BLS_SIG.nm not in commit:
5281
# TODO: It's optional for now
5382
return
@@ -62,12 +91,14 @@ def update_pre_prepare(self, pre_prepare_params, ledger_id):
6291
if not self._can_process_ledger(ledger_id):
6392
return pre_prepare_params
6493

65-
if not self._bls_latest_multi_sig:
66-
return pre_prepare_params
94+
if self._bls_latest_multi_sig is not None:
95+
pre_prepare_params.append(self._bls_latest_multi_sig.as_list())
96+
self._bls_latest_multi_sig = None
6797

68-
pre_prepare_params.append(self._bls_latest_multi_sig.as_list())
69-
self._bls_latest_multi_sig = None
7098
# Send signature in COMMITs only
99+
if self._all_bls_latest_multi_sigs is not None:
100+
pre_prepare_params.append([val.as_list() for val in self._all_bls_latest_multi_sigs])
101+
self._all_bls_latest_multi_sigs = None
71102

72103
return pre_prepare_params
73104

@@ -92,6 +123,22 @@ def update_commit(self, commit_params, pre_prepare: PrePrepare):
92123
logger.debug("{}{} signed COMMIT {} for state {} with sig {}"
93124
.format(BLS_PREFIX, self, commit_params, state_root_hash, bls_signature))
94125
commit_params.append(bls_signature)
126+
127+
last_audit_txn = self._get_correct_audit_transaction(pre_prepare)
128+
if last_audit_txn:
129+
res = {}
130+
payload_data = last_audit_txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA]
131+
for ledger_id in payload_data[AUDIT_TXN_STATE_ROOT].keys():
132+
fake_pp = BlsBftReplicaPlenum._create_fake_pre_prepare_for_multi_sig(
133+
ledger_id,
134+
payload_data[AUDIT_TXN_STATE_ROOT].get(ledger_id),
135+
payload_data[AUDIT_TXN_LEDGER_ROOT].get(ledger_id),
136+
pre_prepare
137+
)
138+
bls_signature = self._sign_state(fake_pp)
139+
res[str(ledger_id)] = bls_signature
140+
commit_params.append(res)
141+
95142
return commit_params
96143

97144
# ----PROCESS----
@@ -105,15 +152,19 @@ def process_prepare(self, prepare: Prepare, sender):
105152
pass
106153

107154
def process_commit(self, commit: Commit, sender):
108-
if f.BLS_SIG.nm not in commit:
109-
return
110-
if commit.blsSig is None:
111-
return
112-
113155
key_3PC = (commit.viewNo, commit.ppSeqNo)
114-
if key_3PC not in self._signatures:
115-
self._signatures[key_3PC] = {}
116-
self._signatures[key_3PC][self.get_node_name(sender)] = commit.blsSig
156+
if f.BLS_SIG.nm in commit and commit.blsSig is not None:
157+
if key_3PC not in self._signatures:
158+
self._signatures[key_3PC] = {}
159+
self._signatures[key_3PC][self.get_node_name(sender)] = commit.blsSig
160+
161+
if f.BLS_SIGS.nm in commit and commit.blsSigs is not None:
162+
if key_3PC not in self._all_signatures:
163+
self._all_signatures[key_3PC] = {}
164+
for ledger_id in commit.blsSigs.keys():
165+
if ledger_id not in self._all_signatures[key_3PC]:
166+
self._all_signatures[key_3PC][ledger_id] = {}
167+
self._all_signatures[key_3PC][ledger_id][self.get_node_name(sender)] = commit.blsSigs[ledger_id]
117168

118169
def process_order(self, key, quorums, pre_prepare):
119170
if not self._can_process_ledger(pre_prepare.ledgerId):
@@ -126,12 +177,19 @@ def process_order(self, key, quorums, pre_prepare):
126177
# but save on master only
127178
bls_multi_sig = self._calculate_multi_sig(key, pre_prepare)
128179

180+
all_bls_multi_sigs = self._calculate_all_multi_sigs(key, pre_prepare)
181+
129182
if not self._is_master:
130183
return
131184

132-
self._save_multi_sig_local(bls_multi_sig)
185+
if all_bls_multi_sigs:
186+
for bls_multi_sig in all_bls_multi_sigs:
187+
self._save_multi_sig_local(bls_multi_sig)
188+
elif bls_multi_sig:
189+
self._save_multi_sig_local(bls_multi_sig)
133190

134191
self._bls_latest_multi_sig = bls_multi_sig
192+
self._all_bls_latest_multi_sigs = all_bls_multi_sigs
135193

136194
# ----GC----
137195

@@ -142,6 +200,7 @@ def gc(self, key_3PC):
142200
keys_to_remove.append(key)
143201
for key in keys_to_remove:
144202
self._signatures.pop(key, None)
203+
self._all_signatures.pop(key, None)
145204

146205
# ----MULT_SIG----
147206

@@ -154,7 +213,7 @@ def _create_multi_sig_value_for_pre_prepare(self, pre_prepare: PrePrepare, pool_
154213
return multi_sig_value
155214

156215
def validate_key_proof_of_possession(self, key_proof, pk):
157-
return self._bls_bft.bls_crypto_verifier\
216+
return self._bls_bft.bls_crypto_verifier \
158217
.verify_key_proof_of_possession(key_proof, pk)
159218

160219
def _validate_signature(self, sender, bls_sig, pre_prepare: PrePrepare):
@@ -198,6 +257,28 @@ def _sign_state(self, pre_prepare: PrePrepare):
198257
def _can_calculate_multi_sig(self,
199258
key_3PC,
200259
quorums) -> bool:
260+
if key_3PC in self._all_signatures:
261+
sigs_for_request = self._all_signatures[key_3PC]
262+
sigs_invalid = list(
263+
filter(
264+
lambda item: not quorums.bls_signatures.is_reached(len(list(item[1].values()))),
265+
sigs_for_request.items()
266+
)
267+
)
268+
if sigs_invalid:
269+
for lid, sigs in sigs_invalid:
270+
logger.debug(
271+
'{}Can not create bls signatures for batch {}: '
272+
'There are only {} signatures for ledger {}, '
273+
'while {} required for multi_signature'.format(BLS_PREFIX,
274+
key_3PC,
275+
len(list(sigs.values())),
276+
quorums.bls_signatures.value,
277+
lid)
278+
)
279+
else:
280+
return True
281+
201282
if key_3PC not in self._signatures:
202283
return False
203284

@@ -216,6 +297,26 @@ def _can_calculate_multi_sig(self,
216297

217298
def _calculate_multi_sig(self, key_3PC, pre_prepare) -> Optional[MultiSignature]:
218299
sigs_for_request = self._signatures[key_3PC]
300+
return self._calculate_single_multi_sig(sigs_for_request, pre_prepare)
301+
302+
def _calculate_all_multi_sigs(self, key_3PC, pre_prepare) -> Optional[list]:
303+
sigs_for_request = self._all_signatures.get(key_3PC)
304+
res = []
305+
if sigs_for_request:
306+
for lid in sigs_for_request:
307+
sig = sigs_for_request[lid]
308+
audit_txn = self._get_correct_audit_transaction(pre_prepare)
309+
if audit_txn:
310+
audit_payload = audit_txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA]
311+
fake_pp = BlsBftReplicaPlenum. \
312+
_create_fake_pre_prepare_for_multi_sig(int(lid),
313+
audit_payload[AUDIT_TXN_STATE_ROOT][int(lid)],
314+
audit_payload[AUDIT_TXN_LEDGER_ROOT][int(lid)],
315+
pre_prepare)
316+
res.append(self._calculate_single_multi_sig(sig, fake_pp))
317+
return res
318+
319+
def _calculate_single_multi_sig(self, sigs_for_request, pre_prepare) -> Optional[MultiSignature]:
219320
bls_signatures = list(sigs_for_request.values())
220321
participants = list(sigs_for_request.keys())
221322

@@ -245,7 +346,16 @@ def _save_multi_sig_local(self,
245346

246347
def _save_multi_sig_shared(self, pre_prepare: PrePrepare):
247348

248-
if f.BLS_MULTI_SIG.nm not in pre_prepare:
349+
if f.BLS_MULTI_SIGS.nm in pre_prepare and pre_prepare.blsMultiSigs is not None:
350+
multi_sigs = pre_prepare.blsMultiSigs
351+
for sig in multi_sigs:
352+
multi_sig = MultiSignature.from_list(*sig)
353+
self._bls_bft.bls_store.put(multi_sig)
354+
logger.debug("{}{} saved multi signature {} for root {} (calculated by Primary)"
355+
.format(BLS_PREFIX, self, multi_sig,
356+
multi_sig.value.state_root_hash))
357+
return
358+
elif f.BLS_MULTI_SIG.nm not in pre_prepare:
249359
return
250360
if pre_prepare.blsMultiSig is None:
251361
return
@@ -257,6 +367,41 @@ def _save_multi_sig_shared(self, pre_prepare: PrePrepare):
257367
multi_sig.value.state_root_hash))
258368
# TODO: support multiple multi-sigs for multiple previous batches
259369

370+
def _get_correct_audit_transaction(self, pp: PrePrepare):
371+
ledger = self._database_manager.get_ledger(AUDIT_LEDGER_ID)
372+
if ledger is None:
373+
return None
374+
seqNo = ledger.uncommitted_size
375+
for curSeqNo in reversed(range(1, seqNo + 1)):
376+
txn = ledger.get_by_seq_no_uncommitted(curSeqNo)
377+
if txn:
378+
payload = txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA]
379+
if pp.ppSeqNo == payload[AUDIT_TXN_PP_SEQ_NO]:
380+
return txn
381+
return None
382+
383+
@staticmethod
384+
def _create_fake_pre_prepare_for_multi_sig(lid, state_root_hash, txn_root_hash, pre_prepare):
385+
params = [
386+
pre_prepare.instId,
387+
pre_prepare.viewNo,
388+
pre_prepare.ppSeqNo,
389+
pre_prepare.ppTime,
390+
pre_prepare.reqIdr,
391+
pre_prepare.discarded,
392+
pre_prepare.digest,
393+
1, # doing it to work around the ledgers that are not in plenum -- it will fail the validation of pre-prepare
394+
state_root_hash,
395+
txn_root_hash,
396+
pre_prepare.sub_seq_no,
397+
pre_prepare.final,
398+
pre_prepare.poolStateRootHash,
399+
pre_prepare.auditTxnRootHash
400+
]
401+
pp = PrePrepare(*params)
402+
pp.ledgerId = lid
403+
return pp
404+
260405
@staticmethod
261406
def get_node_name(replica_name: str):
262407
# TODO: there is the same method in Replica

plenum/common/messages/node_messages.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
SerializedValueField, SignatureField, TieAmongField, AnyValueField, TimestampField, \
1515
LedgerIdField, MerkleRootField, Base58Field, LedgerInfoField, AnyField, ChooseField, AnyMapField, \
1616
LimitedLengthStringField, BlsMultiSignatureField, ProtocolVersionField, BooleanField, \
17-
IntegerField, BatchIDField, ViewChangeField
17+
IntegerField, BatchIDField, ViewChangeField, MapField, StringifiedNonNegativeNumberField
1818
from plenum.common.messages.message_base import \
1919
MessageBase
2020
from plenum.common.types import f
@@ -133,6 +133,8 @@ class PrePrepare(MessageBase):
133133
# TODO: support multiple multi-sigs for multiple previous batches
134134
(f.BLS_MULTI_SIG.nm, BlsMultiSignatureField(optional=True,
135135
nullable=True)),
136+
(f.BLS_MULTI_SIGS.nm, IterableField(optional=True,
137+
inner_field_type=BlsMultiSignatureField(optional=True, nullable=True))),
136138
(f.ORIGINAL_VIEW_NO.nm, NonNegativeNumberField(optional=True,
137139
nullable=True)),
138140
(f.PLUGIN_FIELDS.nm, AnyMapField(optional=True, nullable=True)),
@@ -147,6 +149,13 @@ def _post_process(self, input_as_dict: Dict) -> Dict:
147149
if bls is not None:
148150
input_as_dict[f.BLS_MULTI_SIG.nm] = (bls[0], tuple(bls[1]), tuple(bls[2]))
149151

152+
bls_sigs = input_as_dict.get(f.BLS_MULTI_SIGS.nm, None)
153+
if bls_sigs is not None:
154+
sub = []
155+
for sig in bls_sigs:
156+
sub.append((sig[0], tuple(sig[1]), tuple(sig[2])))
157+
input_as_dict[f.BLS_MULTI_SIGS.nm] = tuple(sub)
158+
150159
return input_as_dict
151160

152161

@@ -191,10 +200,12 @@ class Commit(MessageBase):
191200
(f.PP_SEQ_NO.nm, NonNegativeNumberField()),
192201
(f.BLS_SIG.nm, LimitedLengthStringField(max_length=BLS_SIG_LIMIT,
193202
optional=True)),
194-
203+
(f.BLS_SIGS.nm, MapField(optional=True,
204+
key_field=StringifiedNonNegativeNumberField(),
205+
value_field=LimitedLengthStringField(max_length=BLS_SIG_LIMIT))),
195206
# PLUGIN_FIELDS is not used in Commit as of now but adding for
196207
# consistency
197-
(f.PLUGIN_FIELDS.nm, AnyMapField(optional=True, nullable=True))
208+
(f.PLUGIN_FIELDS.nm, AnyMapField(optional=True, nullable=True)),
198209
)
199210

200211

plenum/common/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ class f: # provides a namespace for reusable field constants
6363
AUDIT_TXN_ROOT_HASH = Field("auditTxnRootHash", str)
6464
TXN_ROOT = Field("txnRootHash", str)
6565
BLS_SIG = Field("blsSig", str)
66+
BLS_SIGS = Field("blsSigs", Dict[int, str])
6667
BLS_MULTI_SIG = Field("blsMultiSig", str)
68+
BLS_MULTI_SIGS = Field("blsMultiSigs", str)
6769
BLS_MULTI_SIG_STATE_ROOT = Field("blsMultiSigStateRoot", str)
6870
MERKLE_ROOT = Field("merkleRoot", str)
6971
OLD_MERKLE_ROOT = Field("oldMerkleRoot", str)

plenum/server/database_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def get_database(self, lid):
2929
return None
3030
return self.databases[lid]
3131

32-
def get_ledger(self, lid):
32+
def get_ledger(self, lid) -> Ledger:
3333
if lid not in self.databases:
3434
return None
3535
return self.databases[lid].ledger

0 commit comments

Comments
 (0)