Skip to content

Commit 4295cf8

Browse files
MakarovSdmitry-timofeev
authored andcommitted
Add no key hashing option to ProofMapIndexProxy [ECR-3779] (#1222)
1 parent e075144 commit 4295cf8

File tree

16 files changed

+1179
-911
lines changed

16 files changed

+1179
-911
lines changed

exonum-java-binding/core/checkstyle-suppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<!-- Do not require Javadoc for native adapters -->
1616
<suppress files="service/adapters.+.java" checks="JavadocMethod"/>
1717

18-
<suppress files="ProofMapIndexProxyIntegrationTest\.java"
18+
<suppress files="ProofMapIndexProxyNoKeyHashingIntegrationTest\.java"
1919
checks="Javadoc.*"/>
2020

2121
<!-- Allow `aBlock` name -->

exonum-java-binding/core/rust/src/storage/proof_map_index.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pub extern "system" fn Java_com_exonum_binding_core_storage_indices_ProofMapInde
6363
_: JClass,
6464
name: JString,
6565
view_handle: Handle,
66+
// TODO: to be used in ECR-3765
67+
_key_hashing: jboolean,
6668
) -> Handle {
6769
let res = panic::catch_unwind(|| {
6870
let name = utils::convert_to_string(&env, name)?;
@@ -88,6 +90,8 @@ pub extern "system" fn Java_com_exonum_binding_core_storage_indices_ProofMapInde
8890
group_name: JString,
8991
map_id: jbyteArray,
9092
view_handle: Handle,
93+
// TODO: to be used in ECR-3765
94+
_key_hashing: jboolean,
9195
) -> Handle {
9296
let res = panic::catch_unwind(|| {
9397
let group_name = utils::convert_to_string(&env, group_name)?;

exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/Blockchain.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ public MapIndex<HashCode, TransactionMessage> getTxMessages() {
139139
}
140140

141141
/**
142-
* Returns a map with a key-value pair of a transaction hash and execution result.
142+
* Returns a map with a key-value pair of a transaction hash and execution result. Note that this
143+
* is a <a href="ProofMapIndexProxy.html#key-hashing">proof map that uses non-hashed keys</a>.
143144
*/
144145
public ProofMapIndexProxy<HashCode, ExecutionStatus> getTxResults() {
145146
return schema.getTxResults();

exonum-java-binding/core/src/main/java/com/exonum/binding/core/blockchain/CoreSchemaProxy.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,11 @@ MapIndex<HashCode, TransactionMessage> getTxMessages() {
149149
}
150150

151151
/**
152-
* Returns a map with a key-value pair of a transaction hash and execution result.
152+
* Returns a map with a key-value pair of a transaction hash and execution result. Note that this
153+
* is a <a href="ProofMapIndexProxy.html#key-hashing">proof map that uses non-hashed keys</a>.
153154
*/
154155
ProofMapIndexProxy<HashCode, ExecutionStatus> getTxResults() {
155-
return ProofMapIndexProxy.newInstance(CoreIndex.TRANSACTIONS_RESULTS, dbView,
156+
return ProofMapIndexProxy.newInstanceNoKeyHashing(CoreIndex.TRANSACTIONS_RESULTS, dbView,
156157
StandardSerializers.hash(), EXECUTION_STATUS_SERIALIZER);
157158
}
158159

exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/MapIndex.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,22 +94,23 @@ default void putAll(Map<? extends K, ? extends V> sourceMap) {
9494
void remove(K key);
9595

9696
/**
97-
* Returns an iterator over the map keys in lexicographical order.
97+
* Returns an iterator over the map keys. The keys are ordered in lexicographical order.
9898
*
9999
* @throws IllegalStateException if this map is not valid
100100
*/
101101
Iterator<K> keys();
102102

103103
/**
104-
* Returns an iterator over the map values in lexicographical order of <em>keys</em>.
104+
* Returns an iterator over the map values. The values are ordered in lexicographical order of
105+
* <em>keys</em>.
105106
*
106107
* @throws IllegalStateException if this map is not valid
107108
*/
108109
Iterator<V> values();
109110

110111
/**
111-
* Returns an iterator over the map entries.
112-
* The entries are ordered by keys in lexicographical order.
112+
* Returns an iterator over the map entries. The entries are ordered by keys in lexicographical
113+
* order.
113114
*
114115
* @throws IllegalStateException if this map is not valid
115116
*/

exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/ProofMapIndexProxy.java

Lines changed: 115 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,29 @@
4444
* that a certain key is mapped to a particular value <em>or</em> that there are no mapping for
4545
* the key in the map.
4646
*
47-
* <p>This map is implemented as a Merkle-Patricia tree. It does not permit null keys and values,
48-
* and requires that keys are 32-byte long.
47+
* <p>This map is implemented as a Merkle-Patricia tree. It does not permit null keys and values.
48+
*
49+
* <p>The Merkle-Patricia tree backing the proof map uses internal 32-byte keys. The tree balance
50+
* relies on the internal keys being uniformly distributed.
51+
*
52+
* <h3><a name="key-hashing">Key hashing in proof maps</a></h3>
53+
*
54+
* <p>By default, when creating the proof map using methods
55+
* {@link #newInstance(String, View, Serializer, Serializer)} and
56+
* {@link #newInGroupUnsafe(String, byte[], View, Serializer, Serializer)}, the user keys are
57+
* converted into internal keys through hashing. This allows to use keys of arbitrary size and
58+
* ensures the balance of the internal tree. It is also possible to create a proof map that will
59+
* not hash keys with methods {@link #newInstanceNoKeyHashing(String, View, Serializer, Serializer)}
60+
* and {@link #newInGroupUnsafeNoKeyHashing(String, byte[], View, Serializer, Serializer)}. In this
61+
* mode, the map will use the user keys as internal tree keys. Such mode of operation is
62+
* appropriate iff all of the following conditions hold:
63+
*
64+
* <ul>
65+
* <li>All keys are 32-byte long</li>
66+
* <li>The keys are uniformly distributed</li>
67+
* <li>The keys come from a trusted source that cannot influence their distribution and affect
68+
* the tree balance.</li>
69+
* </ul>
4970
*
5071
* <p>The "destructive" methods of the map, i.e., the one that change the map contents,
5172
* are specified to throw {@link UnsupportedOperationException} if
@@ -58,13 +79,13 @@
5879
* <p>When the view goes out of scope, this map is destroyed. Subsequent use of the closed map
5980
* is prohibited and will result in {@link IllegalStateException}.
6081
*
61-
* @param <K> the type of keys in this map. Must be 32-byte long in the serialized form
82+
* @param <K> the type of keys in this map
6283
* @param <V> the type of values in this map
6384
* @see View
6485
*/
6586
public final class ProofMapIndexProxy<K, V> extends AbstractIndexProxy implements MapIndex<K, V> {
6687

67-
private final ProofMapKeyCheckingSerializerDecorator<K> keySerializer;
88+
private final Serializer<K> keySerializer;
6889
private final CheckingSerializerDecorator<V> valueSerializer;
6990

7091
/**
@@ -74,7 +95,7 @@ public final class ProofMapIndexProxy<K, V> extends AbstractIndexProxy implement
7495
* [a-zA-Z0-9_]
7596
* @param view a database view. Must be valid.
7697
* If a view is read-only, "destructive" operations are not permitted.
77-
* @param keySerializer a serializer of keys, must always produce 32-byte long values
98+
* @param keySerializer a serializer of keys
7899
* @param valueSerializer a serializer of values
79100
* @param <K> the type of keys in the map
80101
* @param <V> the type of values in the map
@@ -86,9 +107,34 @@ public static <K, V> ProofMapIndexProxy<K, V> newInstance(
86107
String name, View view, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
87108
IndexAddress address = IndexAddress.valueOf(name);
88109
long viewNativeHandle = view.getViewNativeHandle();
89-
LongSupplier nativeMapConstructor = () -> nativeCreate(name, viewNativeHandle);
110+
LongSupplier nativeMapConstructor = () -> nativeCreate(name, viewNativeHandle, true);
111+
112+
return getOrCreate(address, view, keySerializer, valueSerializer, nativeMapConstructor, true);
113+
}
114+
115+
/**
116+
* Creates a <a href="ProofMapIndexProxy.html#key-hashing">ProofMapIndexProxy that uses non-hashed keys</a>.
117+
* Requires that keys are 32-byte long.
118+
*
119+
* @param name a unique alphanumeric non-empty identifier of this map in the underlying storage:
120+
* [a-zA-Z0-9_]
121+
* @param view a database view. Must be valid.
122+
* If a view is read-only, "destructive" operations are not permitted.
123+
* @param keySerializer a serializer of keys, must always produce 32-byte long values
124+
* @param valueSerializer a serializer of values
125+
* @param <K> the type of keys in the map
126+
* @param <V> the type of values in the map
127+
* @throws IllegalStateException if the view is not valid
128+
* @throws IllegalArgumentException if the name is empty
129+
* @see StandardSerializers
130+
*/
131+
public static <K, V> ProofMapIndexProxy<K, V> newInstanceNoKeyHashing(
132+
String name, View view, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
133+
IndexAddress address = IndexAddress.valueOf(name);
134+
long viewNativeHandle = view.getViewNativeHandle();
135+
LongSupplier nativeMapConstructor = () -> nativeCreate(name, viewNativeHandle, false);
90136

91-
return getOrCreate(address, view, keySerializer, valueSerializer, nativeMapConstructor);
137+
return getOrCreate(address, view, keySerializer, valueSerializer, nativeMapConstructor, false);
92138
}
93139

94140
/**
@@ -100,6 +146,36 @@ public static <K, V> ProofMapIndexProxy<K, V> newInstance(
100146
* @param groupName a name of the collection group
101147
* @param mapId an identifier of this collection in the group, see the caveats
102148
* @param view a database view
149+
* @param keySerializer a serializer of keys
150+
* @param valueSerializer a serializer of values
151+
* @param <K> the type of keys in the map
152+
* @param <V> the type of values in the map
153+
* @return a new map proxy
154+
* @throws IllegalStateException if the view is not valid
155+
* @throws IllegalArgumentException if the name or index id is empty
156+
* @see StandardSerializers
157+
*/
158+
public static <K, V> ProofMapIndexProxy<K, V> newInGroupUnsafe(
159+
String groupName, byte[] mapId, View view, Serializer<K> keySerializer,
160+
Serializer<V> valueSerializer) {
161+
IndexAddress address = IndexAddress.valueOf(groupName, mapId);
162+
long viewNativeHandle = view.getViewNativeHandle();
163+
LongSupplier nativeMapConstructor =
164+
() -> nativeCreateInGroup(groupName, mapId, viewNativeHandle, true);
165+
166+
return getOrCreate(address, view, keySerializer, valueSerializer, nativeMapConstructor, true);
167+
}
168+
169+
/**
170+
* Creates a new <a href="ProofMapIndexProxy.html#key-hashing">proof map that uses non-hashed keys</a>
171+
* in a <a href="package-summary.html#families">collection group</a> with the given name.
172+
* Requires that keys are 32-byte long.
173+
*
174+
* <p>See a <a href="package-summary.html#families-limitations">caveat</a> on index identifiers.
175+
*
176+
* @param groupName a name of the collection group
177+
* @param mapId an identifier of this collection in the group, see the caveats
178+
* @param view a database view
103179
* @param keySerializer a serializer of keys, must always produce 32-byte long values
104180
* @param valueSerializer a serializer of values
105181
* @param <K> the type of keys in the map
@@ -109,26 +185,24 @@ public static <K, V> ProofMapIndexProxy<K, V> newInstance(
109185
* @throws IllegalArgumentException if the name or index id is empty
110186
* @see StandardSerializers
111187
*/
112-
public static <K, V> ProofMapIndexProxy<K, V> newInGroupUnsafe(String groupName,
113-
byte[] mapId,
114-
View view,
115-
Serializer<K> keySerializer,
116-
Serializer<V> valueSerializer) {
188+
public static <K, V> ProofMapIndexProxy<K, V> newInGroupUnsafeNoKeyHashing(
189+
String groupName, byte[] mapId, View view, Serializer<K> keySerializer,
190+
Serializer<V> valueSerializer) {
117191
IndexAddress address = IndexAddress.valueOf(groupName, mapId);
118192
long viewNativeHandle = view.getViewNativeHandle();
119193
LongSupplier nativeMapConstructor =
120-
() -> nativeCreateInGroup(groupName, mapId, viewNativeHandle);
194+
() -> nativeCreateInGroup(groupName, mapId, viewNativeHandle, false);
121195

122-
return getOrCreate(address, view, keySerializer, valueSerializer, nativeMapConstructor);
196+
return getOrCreate(address, view, keySerializer, valueSerializer, nativeMapConstructor, false);
123197
}
124198

125199
private static <K, V> ProofMapIndexProxy<K, V> getOrCreate(IndexAddress address, View view,
126200
Serializer<K> keySerializer, Serializer<V> valueSerializer,
127-
LongSupplier nativeMapConstructor) {
201+
LongSupplier nativeMapConstructor, boolean keyHashing) {
128202
return view.findOpenIndex(address)
129203
.map(ProofMapIndexProxy::<K, V>checkCachedInstance)
130204
.orElseGet(() -> newMapIndexProxy(address, view, keySerializer, valueSerializer,
131-
nativeMapConstructor));
205+
nativeMapConstructor, keyHashing));
132206
}
133207

134208
@SuppressWarnings("unchecked") // The compiler is correct: the cache is not type-safe: ECR-3387
@@ -139,9 +213,8 @@ private static <K, V> ProofMapIndexProxy<K, V> checkCachedInstance(StorageIndex
139213

140214
private static <K, V> ProofMapIndexProxy<K, V> newMapIndexProxy(IndexAddress address, View view,
141215
Serializer<K> keySerializer, Serializer<V> valueSerializer,
142-
LongSupplier nativeMapConstructor) {
143-
ProofMapKeyCheckingSerializerDecorator<K> ks =
144-
ProofMapKeyCheckingSerializerDecorator.from(keySerializer);
216+
LongSupplier nativeMapConstructor, boolean keyHashing) {
217+
Serializer<K> ks = decorateKeySerializer(keySerializer, keyHashing);
145218
CheckingSerializerDecorator<V> vs = CheckingSerializerDecorator.from(valueSerializer);
146219

147220
NativeHandle mapNativeHandle = createNativeMap(view, nativeMapConstructor);
@@ -151,6 +224,15 @@ private static <K, V> ProofMapIndexProxy<K, V> newMapIndexProxy(IndexAddress add
151224
return map;
152225
}
153226

227+
private static <K> Serializer<K> decorateKeySerializer(
228+
Serializer<K> keySerializer, boolean keyHashing) {
229+
if (!keyHashing) {
230+
return ProofMapKeyCheckingSerializerDecorator.from(keySerializer);
231+
} else {
232+
return CheckingSerializerDecorator.from(keySerializer);
233+
}
234+
}
235+
154236
private static NativeHandle createNativeMap(View view, LongSupplier nativeMapConstructor) {
155237
NativeHandle mapNativeHandle = new NativeHandle(nativeMapConstructor.getAsLong());
156238

@@ -160,13 +242,13 @@ private static NativeHandle createNativeMap(View view, LongSupplier nativeMapCon
160242
return mapNativeHandle;
161243
}
162244

163-
private static native long nativeCreate(String name, long viewNativeHandle);
245+
private static native long nativeCreate(String name, long viewNativeHandle, boolean keyHashing);
164246

165247
private static native long nativeCreateInGroup(String groupName, byte[] mapId,
166-
long viewNativeHandle);
248+
long viewNativeHandle, boolean keyHashing);
167249

168250
private ProofMapIndexProxy(NativeHandle nativeHandle, IndexAddress address, View view,
169-
ProofMapKeyCheckingSerializerDecorator<K> keySerializer,
251+
Serializer<K> keySerializer,
170252
CheckingSerializerDecorator<V> valueSerializer) {
171253
super(nativeHandle, address, view);
172254
this.keySerializer = keySerializer;
@@ -184,10 +266,11 @@ public boolean containsKey(K key) {
184266
/**
185267
* {@inheritDoc}
186268
*
187-
* @param key a proof map key, must be 32-byte long when serialized
269+
* @param key a proof map key
188270
* @param value a storage value to associate with the key
189271
* @throws IllegalStateException if this map is not valid
190-
* @throws IllegalArgumentException if the size of the key is not 32 bytes
272+
* @throws IllegalArgumentException if the size of the key is not 32 bytes (in case of a
273+
* <a href="ProofMapIndexProxy.html#key-hashing">proof map that uses non-hashed keys</a>)
191274
* @throws UnsupportedOperationException if this map is read-only
192275
*/
193276
@Override
@@ -227,11 +310,11 @@ public V get(K key) {
227310
* Returns a proof that there are values mapped to the specified keys or that there are no such
228311
* mappings.
229312
*
230-
* @param key a proof map key which might be mapped to some value, must be 32-byte long
231-
* @param otherKeys other proof map keys which might be mapped to some values, each must be
232-
* 32-byte long
313+
* @param key a proof map key which might be mapped to some value
314+
* @param otherKeys other proof map keys which might be mapped to some values
233315
* @throws IllegalStateException if this map is not valid
234-
* @throws IllegalArgumentException if the size of any of the keys is not 32 bytes
316+
* @throws IllegalArgumentException if the size of any of the keys is not 32 bytes (in case of a
317+
* <a href="ProofMapIndexProxy.html#key-hashing">proof map that uses non-hashed keys</a>)
235318
*/
236319
public UncheckedMapProof getProof(K key, K... otherKeys) {
237320
if (otherKeys.length == 0) {
@@ -246,10 +329,11 @@ public UncheckedMapProof getProof(K key, K... otherKeys) {
246329
* Returns a proof that there are values mapped to the specified keys or that there are no such
247330
* mappings.
248331
*
249-
* @param keys proof map keys which might be mapped to some values, each must be 32-byte long
332+
* @param keys proof map keys which might be mapped to some values
250333
* @throws IllegalStateException if this map is not valid
251-
* @throws IllegalArgumentException if the size of any of the keys is not 32 bytes
252-
* or keys collection is empty
334+
* @throws IllegalArgumentException if the size of any of the keys is not 32 bytes (in case of a
335+
* <a href="ProofMapIndexProxy.html#key-hashing">proof map that uses non-hashed keys</a>) or
336+
* keys collection is empty
253337
*/
254338
public UncheckedMapProof getProof(Collection<? extends K> keys) {
255339
checkArgument(!keys.isEmpty(), "Keys collection should not be empty");

0 commit comments

Comments
 (0)