Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add no key hashing option to ProofMapIndexProxy [ECR-3779] #1222

Merged
merged 15 commits into from
Nov 21, 2019

Conversation

MakarovS
Copy link
Contributor

@MakarovS MakarovS commented Nov 12, 2019

Overview

Add no key hashing option to ProofMapIndexProxy, update tests.


See: https://jira.bf.local/browse/ECR-3779

Definition of Done

  • There are no TODOs left in the code
  • Change is covered by automated tests
  • The coding guidelines are followed
  • Public API has Javadoc
  • Method preconditions are checked and documented in the Javadoc of the method
  • Changelog is updated if needed (in case of notable or breaking changes)
  • The continuous integration build passes

@dmitry-timofeev
Copy link
Contributor

Github shows the modifications weirdly, use git diff master:exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/ProofMapIndexProxyIntegrationTest.java HEAD:exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/BaseProofMapIndexProxyIntegrationTest.java to see the actual differences between the previous tests and the new base tests.

* and requires that keys are 32-byte long.
* <p>This map is implemented as a Merkle-Patricia tree. It does not permit null keys and values.
*
* <p>This map can be instantiated in two ways - either with keys hashing with methods
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with..with...
how about : ...either with keys hashing using methods...

* [(00…0PK1, V1), (00…0PK2, V2), … (00…0PKi, Vi)].
*/
List<MapEntry<HashCode, String>> createSortedMapEntries() {
// Use PROOF_KEYS which are already sorted.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation and documentation are not correct. Nowhere the base class says that getProofKeys are expected to be sorted
and how. The usages of this method must be reviewed to determine if they need sorting.
This method/getProofKeys and documentation must be updated accordingly.

}

/**
* Creates 257 entries for a ProofMap that, when added to it, will make the underlying
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests that rely on keys being equal to proof paths make no sense in the base but
non-hashing flavour of the ProofMap where this property holds. In hashing variant
there is no apparent connection between a key bits and the bits in the corresponding proof path.

The tests that set particular key bits to achieve a certain tree structure to exercise the proof validation must be moved to non-hashing variant.

void getMultiProof_FourEntryMap_DoesNotContain() {
runTestWithView(database::createFork, (map) -> {
/*
This map will have the following structure:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is incorrect in the base, see below.

* {@link #newInGroupUnsafe(String, byte[], View, Serializer, Serializer)} or with no key hashing
* with methods {@link #newInstanceNoKeyHashing(String, View, Serializer, Serializer)} and
* {@link #newInGroupUnsafeNoKeyHashing(String, byte[], View, Serializer, Serializer)}. In case of
* no key hashing keys are required to be 32-byte long. Note that the former option is considered
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed why it shall be the default option, and the documentation must properly reflect that.

}

/**
* Creates a ProofMapIndexProxy with no key hashing. Requires that keys are 32-byte long.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to add a short warning with a link to the class-level section?

ProofMapKeySizeCheckingSerializerDecorator.from(keySerializer);
return ProofMapKeyCheckingSerializerDecorator.from(sizeCheckingSerializerDecorator);
} else {
return ProofMapKeyCheckingSerializerDecorator.from(keySerializer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed at all if it can return just keySerializer, with no extra decorator types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To check for not null values?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one for that already.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

com.exonum.binding.common.serialization.CheckingSerializerDecorator

* @param value a storage value to associate with the key
* @throws IllegalStateException if this map is not valid
* @throws IllegalArgumentException if the size of the key is not 32 bytes
* @throws IllegalArgumentException if the size of the key is not 32 bytes (in case of a
* no key hashing proof map)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere 'a no key hashing proof map' does not sound correct.

  • ... a non-key-hashing proof map
  • ... a proof map that does not hash keys
  • if the size of the key is not 32 bytes and this map uses non-hashed keys ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Maria-Nosyk Would you tell us please which works best?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The third variant is absolutely perfect!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brilliant, thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is non-key-hashing proof map a bad option though? Third variant is a bit too wordy in some cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, the construction you suggest is, first, hard to understand, second, contradicts general requirements to technical documentation. The second variant suggested by @dmitry-timofeev is also acceptable.

@@ -27,6 +27,10 @@

class ProofMapIndexProxyGroupIntegrationTest extends BaseMapIndexGroupTestable<HashCode> {

private static final HashCode PK1 = HashCode.fromString(K1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: may just use Strings (Kx are already strings).


putAll(map, entries);

Iterator<MapEntry<HashCode, String>> entriesIterator = map.entries();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(keys, values, entries) have incomplete documentation of the order of the elements missing the default variant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional documentation (with the fact that the API users always need to know if hashing happens or not to access the map from other services or verify the proofs it produces) makes me wonder if we shall add two separate types. But that's certainly for a separate issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* A serializer decorator that checks proof map keys for correctness.
*
* @see StoragePreconditions#checkProofKey(byte[])
* A serializer decorator that checks proof map keys are not null.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert the class to its past form.

@dmitry-timofeev
Copy link
Contributor

I expect this PR to have non-hashing tests @Disabled (since there is no support of them in native yet); and hashing one — enabled and passing (the appropriate native method requires a not-yet used jboolean flag, probably, prefixed with underscore so that clippy does not complain).

Also, the client code in various schemas must be updated to use the appropriate constructor.

import java.util.stream.Stream;
import org.junit.jupiter.api.Test;

abstract class BaseProofMapIndexProxyIntegrationTest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This package uses Testable as a suffix to indicate it is not a concrete test but abstract (I don't remember exactly, but I think surefire might also fail to instantiate a non-instantiable 'test' class, as it detects them based on the naming pattern *Test.java).

for (MapEntry<HashCode, String> e : entries) {
assertThat(map, provesThatPresent(e.getKey(), e.getValue()));
}
assertThat(map, provesThatPresent(entries));
});
}

@Test
void getProof_MultiEntryMapDoesNotContain() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test creates 32-byte keys, but it still seems to be appropriate for both proof map cases.

@MakarovS
Copy link
Contributor Author

I expect this PR to have non-hashing tests @disabled (since there is no support of them in native yet); and hashing one — enabled and passing (the appropriate native method requires a not-yet used jboolean flag, probably, prefixed with underscore so that clippy does not complain).

Ok, will disable those, I thought core changes are already merged and we'd be able to integrate this PR with native code, but I see it's still WIP.

class ProofMapIndexProxyNoKeyHashingIntegrationTest
extends BaseProofMapIndexProxyIntegrationTestable {

static final List<HashCode> PROOF_KEYS = Stream.of(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TEST_KEYS/SORTED_TEST_KEYS

private static final HashCode INVALID_PROOF_KEY = HashCode.fromString("1234");

@Override
List<HashCode> getProofKeys() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: getKeys or getTestKeys

* [(00…0PK1, V1), (00…0PK2, V2), … (00…0PKi, Vi)].
*/
List<MapEntry<HashCode, String>> createSortedMapEntries() {
// Use PROOF_KEYS which are already sorted.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use them directly instead of interface method, with appropriate
naming there won't be a need for comment

@dmitry-timofeev
Copy link
Contributor

Also, the client code in various schemas must be updated to use the appropriate constructor.

As most of ProofMaps in services and schemas continue using nonHashing, they shall be migrated to that. (The only hashing is probably DispatcherSchema).

Separately we might consider migrating some services to the default hashing variant, e.g., QaService with its two maps 🤔

@coveralls
Copy link

coveralls commented Nov 14, 2019

Coverage Status

Coverage decreased (-0.2%) to 85.9% when pulling 3c21a42 on ECR-3779 into d3ff9b6 on master.

@MakarovS
Copy link
Contributor Author

As most of ProofMaps in services and schemas continue using nonHashing, they shall be migrated to that. (The only hashing is probably DispatcherSchema).

Ok, will do that in a separate PR.

/**
* {@inheritDoc}
*
* The entries are ordered by keys in lexicographical order if this map is a non-key-hashing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... is a <a href="...">non-key-hashing proof map</a>.

@dmitry-timofeev
Copy link
Contributor

Ok, will do that in a separate PR.

I don't think it is reasonable: this one changes the specification of newInstance (and it becomes broken according to its spec until the native is done), replacing that with newInstanceNonHashing (which works in accordance with the spec). Why the clients, of which there are 6 total, shall remain on the broken version and complicate the native implementation, which, when done, will render the clients actually broken?

It is 15 seconds search-replace (6 usages in prod code) + 2–5 minutes documenting the non-hashing aspect, where applicable.

@MakarovS
Copy link
Contributor Author

It is 15 seconds search-replace (6 usages in prod code) + 2–5 minutes documenting the non-hashing aspect, where applicable.

Ok, updated those. Not sure what documentation is applicable for those, other than // Use non-hashing proof map, as keys of this map are already 32-byte long hashes, which seems redundant, given Javadoc of the corresponding method.

@dmitry-timofeev
Copy link
Contributor

dmitry-timofeev commented Nov 18, 2019

The variant in use must be documented in schemas as they are supposed to provide adequate specification of the service persistent data. Whether or not a proof map hashes its keys affects the clients verifying proofs.

as keys of this map are already 32-byte long hashes

What the API documentation must say is whether or not a hashing variant is used (it doesn't have to explain why). As 32-byte length is not enough to use a non-key-hashing map, as its documentation says, the justification might be either in the API docs or in implementation docs.

@dmitry-timofeev
Copy link
Contributor

Whether or not a proof map hashes its keys affects the clients verifying proofs.

If the proofs included the type of map from which they are created, the specification of key hashing won't be needed. I am not sure if it makes sense to include such flag in the proof.

@dmitry-timofeev
Copy link
Contributor

@MakarovS please resolve the build.

@MakarovS
Copy link
Contributor Author

@MakarovS please resolve the build.

Fixed.

Copy link
Contributor

@dmitry-timofeev dmitry-timofeev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, thanks!

@dmitry-timofeev dmitry-timofeev merged commit 4295cf8 into master Nov 21, 2019
@dmitry-timofeev dmitry-timofeev deleted the ECR-3779 branch November 21, 2019 09:12
@dmitry-timofeev
Copy link
Contributor

If the ordering is the same, then it might make sense to move the tests of iterators back to the base.

@dmitry-timofeev
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants