16
16
17
17
package com .exonum .binding .common .proofs .list ;
18
18
19
+ import com .google .common .collect .Multimap ;
20
+ import com .google .common .collect .Multimaps ;
19
21
import java .util .ArrayList ;
20
22
import java .util .Arrays ;
23
+ import java .util .Collection ;
21
24
import java .util .Collections ;
25
+ import java .util .EnumMap ;
22
26
import java .util .List ;
23
27
24
28
/**
@@ -29,11 +33,7 @@ final class ListProofStructureValidator implements ListProofVisitor {
29
33
static final int MAX_NODE_DEPTH = 63 ;
30
34
31
35
// TODO: optimize data storage and algorithms of validity checks, https://jira.bf.local/browse/ECR-2443
32
- private final List <NodeInfo > listProofBranchesInfo ;
33
-
34
- private final List <NodeInfo > listProofElementsInfo ;
35
-
36
- private final List <NodeInfo > listProofHashNodesInfo ;
36
+ private final Multimap <NodeType , NodeInfo > listProofInfo ;
37
37
38
38
private int depth ;
39
39
@@ -45,9 +45,7 @@ final class ListProofStructureValidator implements ListProofVisitor {
45
45
ListProofStructureValidator (ListProofNode listProof ) {
46
46
depth = 0 ;
47
47
proofStatus = ListProofStatus .VALID ;
48
- listProofBranchesInfo = new ArrayList <>();
49
- listProofElementsInfo = new ArrayList <>();
50
- listProofHashNodesInfo = new ArrayList <>();
48
+ listProofInfo = Multimaps .newListMultimap (new EnumMap <>(NodeType .class ), ArrayList ::new );
51
49
52
50
listProof .accept (this );
53
51
this .check ();
@@ -59,7 +57,7 @@ public void visit(ListProofBranch branch) {
59
57
60
58
NodeType leftElementType = getNodeType (branch .getLeft ());
61
59
NodeType rightElementType = branch .getRight ().map (this ::getNodeType ).orElse (NodeType .NONE );
62
- listProofBranchesInfo .add (
60
+ listProofInfo . get ( NodeType . BRANCH ) .add (
63
61
new NodeInfo (branch , depth , Arrays .asList (leftElementType , rightElementType ))
64
62
);
65
63
@@ -69,12 +67,17 @@ public void visit(ListProofBranch branch) {
69
67
70
68
@ Override
71
69
public void visit (ListProofHashNode hashNode ) {
72
- listProofHashNodesInfo .add (new NodeInfo (hashNode , depth ));
70
+ listProofInfo . get ( NodeType . HASHNODE ) .add (new NodeInfo (hashNode , depth ));
73
71
}
74
72
75
73
@ Override
76
74
public void visit (ListProofElement element ) {
77
- listProofElementsInfo .add (new NodeInfo (element , depth ));
75
+ listProofInfo .get (NodeType .ELEMENT ).add (new NodeInfo (element , depth ));
76
+ }
77
+
78
+ @ Override
79
+ public void visit (ListProofOfAbsence listProofOfAbsence ) {
80
+ listProofInfo .get (NodeType .ABSENCE ).add (new NodeInfo (listProofOfAbsence , depth ));
78
81
}
79
82
80
83
private void visitLeft (ListProofBranch branch , int branchDepth ) {
@@ -95,16 +98,19 @@ private int getChildDepth(int branchDepth) {
95
98
* Performs list proof structure checks and assigns proofStatus based on results of these checks.
96
99
*/
97
100
private void check () {
98
- if (exceedsMaxDepth (listProofElementsInfo )) {
101
+ if (exceedsMaxDepth (listProofInfo . get ( NodeType . ELEMENT ) )) {
99
102
proofStatus = ListProofStatus .INVALID_ELEMENT_NODE_DEPTH ;
100
- } else if (exceedsMaxDepth (listProofHashNodesInfo )) {
103
+ } else if (exceedsMaxDepth (listProofInfo . get ( NodeType . HASHNODE ) )) {
101
104
proofStatus = ListProofStatus .INVALID_HASH_NODE_DEPTH ;
102
- } else if (hasInvalidNodesDepth (listProofElementsInfo )) {
105
+ } else if (hasInvalidNodesDepth (listProofInfo . get ( NodeType . ELEMENT ) )) {
103
106
proofStatus = ListProofStatus .INVALID_NODE_DEPTH ;
104
- } else if (hasNoElementNodes (listProofBranchesInfo , listProofElementsInfo )) {
107
+ } else if (hasNoElementNodes (listProofInfo .get (NodeType .BRANCH ),
108
+ listProofInfo .get (NodeType .ELEMENT ))) {
105
109
proofStatus = ListProofStatus .INVALID_TREE_NO_ELEMENTS ;
106
- } else if (hashNodesLimitExceeded (listProofBranchesInfo )) {
110
+ } else if (hashNodesLimitExceeded (listProofInfo . get ( NodeType . BRANCH ) )) {
107
111
proofStatus = ListProofStatus .INVALID_HASH_NODES_COUNT ;
112
+ } else if (hasInvalidAbsentNodes ()) {
113
+ proofStatus = ListProofStatus .INVALID_PROOF_OF_ABSENCE ;
108
114
}
109
115
}
110
116
@@ -120,6 +126,8 @@ private NodeType getNodeType(ListProofNode node) {
120
126
return NodeType .ELEMENT ;
121
127
} else if (node instanceof ListProofHashNode ) {
122
128
return NodeType .HASHNODE ;
129
+ } else if (node instanceof ListProofOfAbsence ) {
130
+ return NodeType .ABSENCE ;
123
131
} else {
124
132
throw new RuntimeException ("Unknown tree node type: " + node );
125
133
}
@@ -131,7 +139,7 @@ private NodeType getNodeType(ListProofNode node) {
131
139
* @param nodes collection of node info
132
140
* @return true if node depth is invalid
133
141
*/
134
- private boolean exceedsMaxDepth (List <NodeInfo > nodes ) {
142
+ private boolean exceedsMaxDepth (Collection <NodeInfo > nodes ) {
135
143
return nodes .stream ()
136
144
.anyMatch (nodeInfo -> nodeInfo .getDepth () > MAX_NODE_DEPTH );
137
145
}
@@ -142,7 +150,7 @@ private boolean exceedsMaxDepth(List<NodeInfo> nodes) {
142
150
* @param nodes collection of node info
143
151
* @return true if node depths vary
144
152
*/
145
- private boolean hasInvalidNodesDepth (List <NodeInfo > nodes ) {
153
+ private boolean hasInvalidNodesDepth (Collection <NodeInfo > nodes ) {
146
154
long depthsCount = nodes .stream ()
147
155
.map (NodeInfo ::getDepth )
148
156
.distinct ()
@@ -157,7 +165,7 @@ private boolean hasInvalidNodesDepth(List<NodeInfo> nodes) {
157
165
* @param nodes collection of node info
158
166
* @return true if node depths vary
159
167
*/
160
- private boolean hasNoElementNodes (List <NodeInfo > branches , List <NodeInfo > nodes ) {
168
+ private boolean hasNoElementNodes (Collection <NodeInfo > branches , Collection <NodeInfo > nodes ) {
161
169
return branches .size () > 0 && nodes .size () == 0 ;
162
170
}
163
171
@@ -167,7 +175,7 @@ private boolean hasNoElementNodes(List<NodeInfo> branches, List<NodeInfo> nodes)
167
175
* @param branches collection of branches info
168
176
* @return true if branch contains only hash nodes.
169
177
*/
170
- private boolean hashNodesLimitExceeded (List <NodeInfo > branches ) {
178
+ private boolean hashNodesLimitExceeded (Collection <NodeInfo > branches ) {
171
179
return branches .stream ()
172
180
.anyMatch (this ::invalidBranch );
173
181
}
@@ -181,6 +189,26 @@ private boolean invalidBranch(NodeInfo branch) {
181
189
.allMatch (nodeType -> (nodeType == NodeType .HASHNODE ) || (nodeType == NodeType .NONE ));
182
190
}
183
191
192
+ /**
193
+ * Returns true if this proof is a proof of absence and doesn't have a valid structure:
194
+ * contains more than one node of absence, or contains any nodes other than a node of absence,
195
+ * or the node of absence has non-zero depth.
196
+ */
197
+ private boolean hasInvalidAbsentNodes () {
198
+ Collection <NodeInfo > absentNodes = listProofInfo .get (NodeType .ABSENCE );
199
+ int absentNodesSize = absentNodes .size ();
200
+ if (absentNodesSize > 1 ) {
201
+ return true ;
202
+ } else if (absentNodesSize == 1 ) {
203
+ boolean singleNodeTree = listProofInfo .get (NodeType .BRANCH ).isEmpty ()
204
+ && listProofInfo .get (NodeType .HASHNODE ).isEmpty ()
205
+ && listProofInfo .get (NodeType .ELEMENT ).isEmpty ();
206
+ return !singleNodeTree || absentNodes .stream ().allMatch (node -> node .getDepth () != 0 );
207
+ } else {
208
+ return false ;
209
+ }
210
+ }
211
+
184
212
/**
185
213
* Returns proof status.
186
214
*/
@@ -238,6 +266,6 @@ List<NodeType> getChildElementsTypes() {
238
266
* Enum used to identify tree node type.
239
267
*/
240
268
private enum NodeType {
241
- BRANCH , ELEMENT , HASHNODE , NONE
269
+ BRANCH , ELEMENT , HASHNODE , ABSENCE , NONE
242
270
}
243
271
}
0 commit comments