From 2a15db3ddb9db61006ad041582f34647e004c38a Mon Sep 17 00:00:00 2001 From: Hellblazer Date: Wed, 27 Dec 2023 07:22:54 -0800 Subject: [PATCH] add BootstrapVerifiers to provide key state resolution via delegation to successors in joined group --- .../apollo/fireflies/BootstrapVerifiers.java | 220 ++++++++++++++++++ .../com/salesforce/apollo/fireflies/View.java | 26 ++- .../fireflies/comm/entrance/Entrance.java | 17 +- .../comm/entrance/EntranceClient.java | 28 ++- .../comm/entrance/EntranceServer.java | 42 +++- .../comm/entrance/EntranceService.java | 12 +- grpc/src/main/proto/fireflies.proto | 10 +- leyden/pom.xml | 2 + .../apollo/model/ProcessDomain.java | 4 +- model/src/test/resources/logback-test.xml | 6 +- .../apollo/stereotomy/EventValidation.java | 33 +-- .../apollo/stereotomy/KerlVerifier.java | 49 +--- .../apollo/stereotomy/KeyStateVerifier.java | 67 ++++++ .../apollo/stereotomy/StereotomyImpl.java | 2 +- .../apollo/stereotomy/Verifiers.java | 10 +- .../java/com/salesforce/apollo/thoth/Ani.java | 7 + 16 files changed, 436 insertions(+), 99 deletions(-) create mode 100644 fireflies/src/main/java/com/salesforce/apollo/fireflies/BootstrapVerifiers.java create mode 100644 stereotomy/src/main/java/com/salesforce/apollo/stereotomy/KeyStateVerifier.java diff --git a/fireflies/src/main/java/com/salesforce/apollo/fireflies/BootstrapVerifiers.java b/fireflies/src/main/java/com/salesforce/apollo/fireflies/BootstrapVerifiers.java new file mode 100644 index 000000000..8e210755f --- /dev/null +++ b/fireflies/src/main/java/com/salesforce/apollo/fireflies/BootstrapVerifiers.java @@ -0,0 +1,220 @@ +package com.salesforce.apollo.fireflies; + +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.github.benmanes.caffeine.cache.RemovalCause; +import com.google.common.collect.HashMultiset; +import com.google.common.util.concurrent.ListenableFuture; +import com.salesforce.apollo.archipelago.RouterImpl; +import com.salesforce.apollo.cryptography.Verifier; +import com.salesforce.apollo.fireflies.comm.entrance.EntranceClient; +import com.salesforce.apollo.fireflies.proto.IdentifierSequenceNumber; +import com.salesforce.apollo.membership.Member; +import com.salesforce.apollo.ring.SliceIterator; +import com.salesforce.apollo.stereotomy.EventCoordinates; +import com.salesforce.apollo.stereotomy.KeyState; +import com.salesforce.apollo.stereotomy.KeyStateVerifier; +import com.salesforce.apollo.stereotomy.Verifiers; +import com.salesforce.apollo.stereotomy.event.proto.KeyState_; +import com.salesforce.apollo.stereotomy.event.protobuf.KeyStateImpl; +import com.salesforce.apollo.stereotomy.identifier.Identifier; +import io.grpc.StatusRuntimeException; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.joou.ULong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; + +/** + * Verifiers that delegate to the joining member's successors in the full context for key state retrieval + *

+ * This is used to bootstrap the node via delegated key state resolution of the joined group + *

+ * + * @author hal.hildebrand + **/ +public class BootstrapVerifiers implements Verifiers { + private final static Logger log = LoggerFactory.getLogger( + BootstrapVerifiers.class); + private final List successors; + private final View.Node member; + private final int majority; + private final LoadingCache ksCoords; + private final LoadingCache ksSeq; + private final RouterImpl.CommonCommunications communications; + private final Duration operationTimeout; + private final Duration operationsFrequency; + + public BootstrapVerifiers(View.Node member, Duration operationTimeout, List successors, + int majority, Duration operationsFrequency, + RouterImpl.CommonCommunications communications) { + this.member = member; + this.successors = successors; + this.majority = majority; + this.communications = communications; + this.operationTimeout = operationTimeout; + this.operationsFrequency = operationsFrequency; + ksCoords = Caffeine.newBuilder() + .maximumSize(10) + .expireAfterWrite(Duration.ofMinutes(10)) + .removalListener((EventCoordinates coords, KeyState ks, RemovalCause cause) -> log.trace( + "KeyState {} was removed ({})", coords, cause)) + .build(new CacheLoader() { + + @Override + public @Nullable KeyState load(EventCoordinates key) throws Exception { + return delegate(key); + } + }); + ksSeq = Caffeine.newBuilder() + .maximumSize(10) + .expireAfterWrite(Duration.ofMinutes(10)) + .removalListener((IdentifierSequence seq, KeyState ks, RemovalCause cause) -> log.trace( + "KeyState {} was removed ({})", seq, cause)) + .build(new CacheLoader() { + + @Override + public @Nullable KeyState load(IdentifierSequence key) throws Exception { + return delegate(key); + } + }); + } + + @Override + public Optional verifierFor(EventCoordinates coordinates) { + return Optional.of(new BootstrapVerifier(coordinates.getIdentifier())); + } + + @Override + public Optional verifierFor(Identifier identifier) { + return Optional.of(new BootstrapVerifier(identifier)); + } + + private boolean complete(CompletableFuture ksFuture, + Optional> futureSailor, HashMultiset keystates, + Member m) { + if (futureSailor.isEmpty()) { + return true; + } + try { + final var ks = futureSailor.get().get(); + keystates.add(ks); + } catch (ExecutionException ex) { + if (ex.getCause() instanceof StatusRuntimeException sre) { + switch (sre.getStatus().getCode()) { + case RESOURCE_EXHAUSTED: + log.trace("SRE in redirect: {} on: {}", sre.getStatus(), member.getId()); + break; + default: + log.trace("SRE in redirect: {} on: {}", sre.getStatus(), member.getId()); + } + } else { + log.error("Error in redirect: {} on: {}", ex.getCause(), member.getId()); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (CancellationException e) { + // noop + } + + var vs = keystates.entrySet() + .stream() + .filter(e -> e.getCount() >= majority) + .map(e -> e.getElement()) + .findFirst() + .orElse(null); + if (vs != null) { + var keyState = new KeyStateImpl(vs); + if (ksFuture.complete(keyState)) { + log.debug("Key state: {} received majority on: {}", keyState.getCoordinates(), member.getId()); + return false; + } + } + return true; + } + + private KeyState delegate(IdentifierSequence idSeq) { + var iterator = new SliceIterator<>("Retrieve KeyState", member, successors, communications); + final var identifierSeq = idSeq.toIdSeq(); + var ks = new CompletableFuture(); + HashMultiset keystates = HashMultiset.create(); + iterator.iterate((link, m) -> { + log.debug("Requesting Seeding from: {} on: {}", link.getMember().getId(), member.getId()); + return link.getKeyState(identifierSeq); + }, (futureSailor, link, m) -> complete(ks, futureSailor, keystates, m), () -> { + if (!ks.isDone()) { + + } + }, Executors.newScheduledThreadPool(1, Thread.ofVirtual().factory()), Duration.ofMillis(10)); + try { + return ks.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + log.warn("Unable to retrieve key state: {} on: {}", idSeq, member.getId()); + } + return null; + } + + private KeyState delegate(EventCoordinates coordinates) { + var iterator = new SliceIterator<>("Retrieve KeyState", member, successors, communications); + final var coords = coordinates.toEventCoords(); + var ks = new CompletableFuture(); + HashMultiset keystates = HashMultiset.create(); + iterator.iterate((link, m) -> { + log.debug("Requesting Seeding from: {} on: {}", link.getMember().getId(), member.getId()); + return link.getKeyState(coords); + }, (futureSailor, link, m) -> complete(ks, futureSailor, keystates, m), () -> { + if (!ks.isDone()) { + + } + }, Executors.newScheduledThreadPool(1, Thread.ofVirtual().factory()), Duration.ofMillis(10)); + try { + return ks.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + log.warn("Unable to retrieve key state: {} on: {}", coordinates, member.getId()); + } + return null; + } + + private record IdentifierSequence(Identifier identifier, ULong seqNum) { + public IdentifierSequenceNumber toIdSeq() { + return IdentifierSequenceNumber.newBuilder() + .setIdentifier(identifier.toIdent()) + .setSequenceNumber(seqNum.longValue()) + .build(); + } + + @Override + public String toString() { + return "{" + "identifier=" + identifier + ", seqNum=" + seqNum + '}'; + } + } + + private class BootstrapVerifier extends KeyStateVerifier { + + public BootstrapVerifier(Identifier identifier) { + super(identifier); + } + + @Override + protected KeyState getKeyState(ULong sequenceNumber) { + return ksSeq.get(new IdentifierSequence(identifier, sequenceNumber)); + } + + @Override + protected KeyState getKeyState(EventCoordinates coordinates) { + return ksCoords.get(coordinates); + } + } +} diff --git a/fireflies/src/main/java/com/salesforce/apollo/fireflies/View.java b/fireflies/src/main/java/com/salesforce/apollo/fireflies/View.java index 117264df9..32822ad87 100644 --- a/fireflies/src/main/java/com/salesforce/apollo/fireflies/View.java +++ b/fireflies/src/main/java/com/salesforce/apollo/fireflies/View.java @@ -34,14 +34,17 @@ import com.salesforce.apollo.stereotomy.ControlledIdentifier; import com.salesforce.apollo.stereotomy.EventCoordinates; import com.salesforce.apollo.stereotomy.EventValidation; +import com.salesforce.apollo.stereotomy.KeyState; import com.salesforce.apollo.stereotomy.event.proto.KERL_; import com.salesforce.apollo.stereotomy.event.proto.KeyState_; +import com.salesforce.apollo.stereotomy.identifier.Identifier; import com.salesforce.apollo.stereotomy.identifier.SelfAddressingIdentifier; import com.salesforce.apollo.utils.Entropy; import com.salesforce.apollo.utils.Utils; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; +import org.joou.ULong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1841,6 +1844,26 @@ boolean setNote(NoteWrapper next) { public class Service implements EntranceService, FFService, ServiceRouting { + @Override + public KeyState getKeyState(Identifier identifier, ULong seqNum, Digest from) { + if (!introduced.get()) { + log.trace("Not introduced!, ignoring key state request from: {} on: {}", from, node.getId()); + return null; + } + var keyState = validation.getKeyState(identifier, seqNum); + return keyState.isEmpty() ? null : keyState.get(); + } + + @Override + public KeyState getKeyState(EventCoordinates coordinates, Digest from) { + if (!introduced.get()) { + log.trace("Not introduced!, ignoring key state request from: {} on: {}", from, node.getId()); + return null; + } + var keyState = validation.getKeyState(coordinates); + return keyState.isEmpty() ? null : keyState.get(); + } + /** * Asynchronously add a member to the next view */ @@ -1859,7 +1882,6 @@ public void join(Join join, Digest from, StreamObserver responseObserve * with the Gossip that represents the digests newer or not known in this view, as well as updates from this * node based on out of date information in the supplied digests. * - * @param ring - the index of the gossip ring the inbound member is gossiping on * @param request - the Gossip from our partner * @return Teh response for Moar gossip - updates this node has which the sender is out of touch with, and * digests from the sender that this node would like updated. @@ -1928,7 +1950,7 @@ public Redirect seed(Registration registration, Digest from) { /** * The third and final message in the anti-entropy protocol. Process the inbound update from another member. * - * @param state - update state + * @param request - update state * @param from */ @Override diff --git a/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/Entrance.java b/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/Entrance.java index b24a38584..0f1ebef0e 100644 --- a/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/Entrance.java +++ b/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/Entrance.java @@ -6,17 +6,16 @@ */ package com.salesforce.apollo.fireflies.comm.entrance; -import java.io.IOException; -import java.time.Duration; - import com.google.common.util.concurrent.ListenableFuture; -import com.salesforce.apollo.fireflies.proto.Gateway; -import com.salesforce.apollo.fireflies.proto.Join; -import com.salesforce.apollo.fireflies.proto.Redirect; -import com.salesforce.apollo.fireflies.proto.Registration; import com.salesforce.apollo.archipelago.Link; import com.salesforce.apollo.fireflies.View.Node; +import com.salesforce.apollo.fireflies.proto.*; import com.salesforce.apollo.membership.Member; +import com.salesforce.apollo.stereotomy.event.proto.EventCoords; +import com.salesforce.apollo.stereotomy.event.proto.KeyState_; + +import java.io.IOException; +import java.time.Duration; /** * @author hal.hildebrand @@ -47,6 +46,10 @@ public ListenableFuture seed(Registration registration) { }; } + ListenableFuture getKeyState(IdentifierSequenceNumber idSeq); + + ListenableFuture getKeyState(EventCoords coords); + ListenableFuture join(Join join, Duration timeout); ListenableFuture seed(Registration registration); diff --git a/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceClient.java b/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceClient.java index 5be52936a..8ec311a0f 100644 --- a/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceClient.java +++ b/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceClient.java @@ -6,20 +6,18 @@ */ package com.salesforce.apollo.fireflies.comm.entrance; -import java.time.Duration; -import java.util.concurrent.TimeUnit; - import com.google.common.util.concurrent.ListenableFuture; -import com.salesforce.apollo.fireflies.proto.EntranceGrpc; -import com.salesforce.apollo.fireflies.proto.EntranceGrpc.EntranceFutureStub; -import com.salesforce.apollo.fireflies.proto.Gateway; -import com.salesforce.apollo.fireflies.proto.Join; -import com.salesforce.apollo.fireflies.proto.Redirect; -import com.salesforce.apollo.fireflies.proto.Registration; import com.salesforce.apollo.archipelago.ManagedServerChannel; import com.salesforce.apollo.archipelago.ServerConnectionCache.CreateClientCommunications; import com.salesforce.apollo.fireflies.FireflyMetrics; +import com.salesforce.apollo.fireflies.proto.*; +import com.salesforce.apollo.fireflies.proto.EntranceGrpc.EntranceFutureStub; import com.salesforce.apollo.membership.Member; +import com.salesforce.apollo.stereotomy.event.proto.EventCoords; +import com.salesforce.apollo.stereotomy.event.proto.KeyState_; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; /** * @author hal.hildebrand @@ -29,6 +27,7 @@ public class EntranceClient implements Entrance { private final ManagedServerChannel channel; private final EntranceFutureStub client; private final FireflyMetrics metrics; + public EntranceClient(ManagedServerChannel channel, FireflyMetrics metrics) { this.channel = channel; this.client = EntranceGrpc.newFutureStub(channel).withCompression("gzip"); @@ -45,6 +44,16 @@ public void close() { channel.release(); } + @Override + public ListenableFuture getKeyState(IdentifierSequenceNumber idSeq) { + return client.getKeyStateIdentifier(idSeq); + } + + @Override + public ListenableFuture getKeyState(EventCoords coords) { + return client.getKeyStateCoords(coords); + } + @Override public Member getMember() { return channel.getMember(); @@ -94,5 +103,4 @@ public ListenableFuture seed(Registration registration) { }, r -> r.run()); return result; } - } diff --git a/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceServer.java b/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceServer.java index 83fff398a..0be9c6d9c 100644 --- a/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceServer.java +++ b/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceServer.java @@ -7,18 +7,19 @@ package com.salesforce.apollo.fireflies.comm.entrance; import com.codahale.metrics.Timer.Context; -import com.salesforce.apollo.fireflies.proto.EntranceGrpc.EntranceImplBase; -import com.salesforce.apollo.fireflies.proto.Gateway; -import com.salesforce.apollo.fireflies.proto.Join; -import com.salesforce.apollo.fireflies.proto.Redirect; -import com.salesforce.apollo.fireflies.proto.Registration; import com.salesforce.apollo.archipelago.RoutableService; import com.salesforce.apollo.cryptography.Digest; import com.salesforce.apollo.fireflies.FireflyMetrics; import com.salesforce.apollo.fireflies.View.Service; +import com.salesforce.apollo.fireflies.proto.EntranceGrpc.EntranceImplBase; +import com.salesforce.apollo.fireflies.proto.*; import com.salesforce.apollo.protocols.ClientIdentity; - +import com.salesforce.apollo.stereotomy.EventCoordinates; +import com.salesforce.apollo.stereotomy.event.proto.EventCoords; +import com.salesforce.apollo.stereotomy.event.proto.KeyState_; +import com.salesforce.apollo.stereotomy.identifier.Identifier; import io.grpc.stub.StreamObserver; +import org.joou.ULong; /** * @author hal.hildebrand @@ -35,6 +36,35 @@ public EntranceServer(ClientIdentity identity, RoutableService r, Firef this.router = r; } + @Override + public void getKeyStateCoords(EventCoords request, StreamObserver responseObserver) { + Digest from = identity.getFrom(); + if (from == null) { + responseObserver.onError(new IllegalStateException("Member has been removed")); + return; + } + router.evaluate(responseObserver, s -> { + var keyState = s.getKeyState(EventCoordinates.from(request), from); + responseObserver.onNext(keyState == null ? KeyState_.getDefaultInstance() : keyState.toKeyState_()); + responseObserver.onCompleted(); + }); + } + + @Override + public void getKeyStateIdentifier(IdentifierSequenceNumber request, StreamObserver responseObserver) { + Digest from = identity.getFrom(); + if (from == null) { + responseObserver.onError(new IllegalStateException("Member has been removed")); + return; + } + router.evaluate(responseObserver, s -> { + var keyState = s.getKeyState(Identifier.from(request.getIdentifier()), + ULong.valueOf(request.getSequenceNumber()), from); + responseObserver.onNext(keyState == null ? KeyState_.getDefaultInstance() : keyState.toKeyState_()); + responseObserver.onCompleted(); + }); + } + @Override public void join(Join request, StreamObserver responseObserver) { Context timer = metrics == null ? null : metrics.inboundJoinDuration().time(); diff --git a/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceService.java b/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceService.java index de00686a9..72b2297c4 100644 --- a/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceService.java +++ b/fireflies/src/main/java/com/salesforce/apollo/fireflies/comm/entrance/EntranceService.java @@ -7,21 +7,27 @@ package com.salesforce.apollo.fireflies.comm.entrance; import com.codahale.metrics.Timer.Context; +import com.salesforce.apollo.cryptography.Digest; import com.salesforce.apollo.fireflies.proto.Gateway; import com.salesforce.apollo.fireflies.proto.Join; import com.salesforce.apollo.fireflies.proto.Redirect; import com.salesforce.apollo.fireflies.proto.Registration; -import com.salesforce.apollo.cryptography.Digest; - +import com.salesforce.apollo.stereotomy.EventCoordinates; +import com.salesforce.apollo.stereotomy.KeyState; +import com.salesforce.apollo.stereotomy.identifier.Identifier; import io.grpc.stub.StreamObserver; +import org.joou.ULong; /** * @author hal.hildebrand */ public interface EntranceService { + KeyState getKeyState(Identifier identifier, ULong uLong, Digest from); + + KeyState getKeyState(EventCoordinates coordinates, Digest from); + void join(Join request, Digest from, StreamObserver responseObserver, Context timer); Redirect seed(Registration request, Digest from); - } diff --git a/grpc/src/main/proto/fireflies.proto b/grpc/src/main/proto/fireflies.proto index 05ff7e9b5..cd5dcc1c7 100644 --- a/grpc/src/main/proto/fireflies.proto +++ b/grpc/src/main/proto/fireflies.proto @@ -117,6 +117,13 @@ message Update { service Entrance { rpc seed (Registration) returns (Redirect) {} rpc join (Join) returns (Gateway) {} + rpc getKeyStateCoords(stereotomy.EventCoords) returns (stereotomy.KeyState_) {} + rpc getKeyStateIdentifier(IdentifierSequenceNumber) returns (stereotomy.KeyState_) {} +} + +message IdentifierSequenceNumber { + stereotomy.Ident identifier = 1; + uint64 sequenceNumber = 2; } message Registration { @@ -144,5 +151,6 @@ message Join { message Gateway { crypto.HexBloome diadem = 1; - repeated SignedNote initialSeedSet = 2; + repeated SignedNote successors = 2; + repeated SignedNote initialSeedSet = 3; } diff --git a/leyden/pom.xml b/leyden/pom.xml index 9fd3261ba..80cff0936 100644 --- a/leyden/pom.xml +++ b/leyden/pom.xml @@ -9,6 +9,8 @@ 0.0.1-SNAPSHOT leyden + Leyden + Experimental BFT DHT diff --git a/model/src/main/java/com/salesforce/apollo/model/ProcessDomain.java b/model/src/main/java/com/salesforce/apollo/model/ProcessDomain.java index be9b1870d..fc2d00c4b 100644 --- a/model/src/main/java/com/salesforce/apollo/model/ProcessDomain.java +++ b/model/src/main/java/com/salesforce/apollo/model/ProcessDomain.java @@ -35,9 +35,7 @@ * The logical domain of the current "Process" - OS and Simulation defined, 'natch. *

* The ProcessDomain represents a member node in the top level domain and represents the top level container model for - * the distributed system. This top level domain contains every sub domain as decendents. The membership of this domain - * is the entirety of all process members in the system. The Context of this domain is also the foundational fireflies - * membership domain of the entire system. + * the distributed system. The Context of this domain is the foundational fireflies membership domain for the group id. * * @author hal.hildebrand */ diff --git a/model/src/test/resources/logback-test.xml b/model/src/test/resources/logback-test.xml index 9141db2ba..6c69bdf98 100644 --- a/model/src/test/resources/logback-test.xml +++ b/model/src/test/resources/logback-test.xml @@ -45,11 +45,11 @@ - + - + @@ -57,7 +57,7 @@ - + diff --git a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/EventValidation.java b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/EventValidation.java index d97d495c4..e3429aa62 100644 --- a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/EventValidation.java +++ b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/EventValidation.java @@ -6,21 +6,22 @@ */ package com.salesforce.apollo.stereotomy; -import java.io.InputStream; -import java.util.Optional; - import com.google.protobuf.ByteString; import com.salesforce.apollo.cryptography.JohnHancock; import com.salesforce.apollo.cryptography.SigningThreshold; import com.salesforce.apollo.cryptography.Verifier.Filtered; import com.salesforce.apollo.stereotomy.event.EstablishmentEvent; +import com.salesforce.apollo.stereotomy.identifier.Identifier; import com.salesforce.apollo.utils.BbBackedInputStream; +import org.joou.ULong; + +import java.io.InputStream; +import java.util.Optional; /** * The EventValidation provides validation predicates for EstablishmentEvents - * - * @author hal.hildebrand * + * @author hal.hildebrand */ public interface EventValidation { @@ -36,6 +37,11 @@ public Optional getKeyState(EventCoordinates coordinates) { return Optional.empty(); } + @Override + public Optional getKeyState(Identifier identifier, ULong seqNum) { + return Optional.empty(); + } + @Override public boolean validate(EstablishmentEvent event) { return true; @@ -63,25 +69,26 @@ Filtered filtered(EventCoordinates coordinates, SigningThreshold threshold, John Optional getKeyState(EventCoordinates coordinates); + Optional getKeyState(Identifier identifier, ULong seqNum); + /** - * Answer true if the event is validated. This means that thresholds have been - * met from indicated witnesses and trusted validators. + * Answer true if the event is validated. This means that thresholds have been met from indicated witnesses and + * trusted validators. */ boolean validate(EstablishmentEvent event); /** - * Answer true if the event indicated by the coordinates is validated. This - * means that thresholds have been met from indicated witnesses and trusted - * validators. + * Answer true if the event indicated by the coordinates is validated. This means that thresholds have been met from + * indicated witnesses and trusted validators. */ boolean validate(EventCoordinates coordinates); + boolean verify(EventCoordinates coordinates, SigningThreshold threshold, JohnHancock signature, + InputStream message); + default boolean verify(EventCoordinates coordinates, JohnHancock signature, ByteString byteString) { return verify(coordinates, signature, BbBackedInputStream.aggregate(byteString)); } boolean verify(EventCoordinates coordinates, JohnHancock signature, InputStream message); - - boolean verify(EventCoordinates coordinates, SigningThreshold threshold, JohnHancock signature, - InputStream message); } diff --git a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/KerlVerifier.java b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/KerlVerifier.java index 5de68d968..b19f854b8 100644 --- a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/KerlVerifier.java +++ b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/KerlVerifier.java @@ -1,66 +1,29 @@ package com.salesforce.apollo.stereotomy; -import com.salesforce.apollo.cryptography.JohnHancock; -import com.salesforce.apollo.cryptography.SigningThreshold; -import com.salesforce.apollo.cryptography.Verifier; import com.salesforce.apollo.stereotomy.identifier.Identifier; import org.joou.ULong; -import java.io.InputStream; -import java.util.Optional; - /** * A Verifier that uses the backing Stereotomy for keys used for Signature verification for the Identifier * * @author hal.hildebrand **/ -public class KerlVerifier implements Verifier { +public class KerlVerifier extends KeyStateVerifier { - private final D identifier; private final KERL kerl; public KerlVerifier(D identifier, KERL kerl) { - this.identifier = identifier; + super(identifier); this.kerl = kerl; } - public D identifier() { - return identifier; - } - - @Override - public Filtered filtered(SigningThreshold threshold, JohnHancock signature, InputStream message) { - var verifier = verifierFor(signature.getSequenceNumber()); - return verifier.isEmpty() ? new Filtered(false, 0, - new JohnHancock(signature.getAlgorithm(), new byte[] {}, ULong.MIN)) - : verifier.get().filtered(threshold, signature, message); - } - @Override - public boolean verify(JohnHancock signature, InputStream message) { - var verifier = verifierFor(signature.getSequenceNumber()); - return verifier.isEmpty() ? false : verifier.get().verify(signature, message); + protected KeyState getKeyState(ULong sequenceNumber) { + return kerl.getKeyState(identifier, sequenceNumber); } @Override - public boolean verify(SigningThreshold threshold, JohnHancock signature, InputStream message) { - var verifier = verifierFor(signature.getSequenceNumber()); - return verifier.isEmpty() ? false : verifier.get().verify(threshold, signature, message); - } - - private Optional verifierFor(ULong sequenceNumber) { - KeyState keyState = kerl.getKeyState(identifier, sequenceNumber); - if (keyState == null) { - return Optional.empty(); - } - return Optional.of(new DefaultVerifier(keyState.getKeys())); - } - - public Optional verifierFor(EventCoordinates coordinates) { - KeyState keyState = kerl.getKeyState(coordinates); - if (keyState == null) { - return Optional.empty(); - } - return Optional.of(new DefaultVerifier(keyState.getKeys())); + protected KeyState getKeyState(EventCoordinates coordinates) { + return kerl.getKeyState(coordinates); } } diff --git a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/KeyStateVerifier.java b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/KeyStateVerifier.java new file mode 100644 index 000000000..91bc54b7c --- /dev/null +++ b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/KeyStateVerifier.java @@ -0,0 +1,67 @@ +package com.salesforce.apollo.stereotomy; + +import com.salesforce.apollo.cryptography.JohnHancock; +import com.salesforce.apollo.cryptography.SigningThreshold; +import com.salesforce.apollo.cryptography.Verifier; +import com.salesforce.apollo.stereotomy.identifier.Identifier; +import org.joou.ULong; + +import java.io.InputStream; +import java.util.Optional; + +/** + * An abstract verifier that uses KeyState + * + * @author hal.hildebrand + **/ +public abstract class KeyStateVerifier implements Verifier { + protected final D identifier; + + public KeyStateVerifier(D identifier) { + this.identifier = identifier; + } + + @Override + public Filtered filtered(SigningThreshold threshold, JohnHancock signature, InputStream message) { + var verifier = verifierFor(signature.getSequenceNumber()); + return verifier.isEmpty() ? new Filtered(false, 0, + new JohnHancock(signature.getAlgorithm(), new byte[] {}, ULong.MIN)) + : verifier.get().filtered(threshold, signature, message); + } + + public D identifier() { + return identifier; + } + + public Optional verifierFor(EventCoordinates coordinates) { + KeyState keyState = getKeyState(coordinates); + if (keyState == null) { + return Optional.empty(); + } + return Optional.of(new DefaultVerifier(keyState.getKeys())); + } + + @Override + public boolean verify(SigningThreshold threshold, JohnHancock signature, InputStream message) { + var verifier = verifierFor(signature.getSequenceNumber()); + return verifier.isEmpty() ? false : verifier.get().verify(threshold, signature, message); + } + + @Override + public boolean verify(JohnHancock signature, InputStream message) { + var verifier = verifierFor(signature.getSequenceNumber()); + return verifier.isEmpty() ? false : verifier.get().verify(signature, message); + } + + protected abstract KeyState getKeyState(ULong sequenceNumber); + + protected abstract KeyState getKeyState(EventCoordinates coordinates); + + protected Optional verifierFor(ULong sequenceNumber) { + KeyState keyState = getKeyState(sequenceNumber); + if (keyState == null) { + return Optional.empty(); + } + return Optional.of(new DefaultVerifier(keyState.getKeys())); + } +} diff --git a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/StereotomyImpl.java b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/StereotomyImpl.java index 097e29fa9..94720287f 100644 --- a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/StereotomyImpl.java +++ b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/StereotomyImpl.java @@ -6,7 +6,6 @@ */ package com.salesforce.apollo.stereotomy; -import com.salesforce.apollo.stereotomy.event.proto.KeyState_; import com.salesforce.apollo.cryptography.*; import com.salesforce.apollo.cryptography.Signer.SignerImpl; import com.salesforce.apollo.cryptography.cert.BcX500NameDnImpl; @@ -18,6 +17,7 @@ import com.salesforce.apollo.stereotomy.event.AttachmentEvent.AttachmentImpl; import com.salesforce.apollo.stereotomy.event.InceptionEvent.ConfigurationTrait; import com.salesforce.apollo.stereotomy.event.Seal.EventSeal; +import com.salesforce.apollo.stereotomy.event.proto.KeyState_; import com.salesforce.apollo.stereotomy.event.protobuf.ProtobufEventFactory; import com.salesforce.apollo.stereotomy.identifier.BasicIdentifier; import com.salesforce.apollo.stereotomy.identifier.Identifier; diff --git a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/Verifiers.java b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/Verifiers.java index 6cfa33245..e6add892d 100644 --- a/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/Verifiers.java +++ b/stereotomy/src/main/java/com/salesforce/apollo/stereotomy/Verifiers.java @@ -6,20 +6,16 @@ */ package com.salesforce.apollo.stereotomy; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import com.salesforce.apollo.stereotomy.event.proto.KeyState_; import com.salesforce.apollo.cryptography.Verifier; import com.salesforce.apollo.cryptography.Verifier.DefaultVerifier; import com.salesforce.apollo.stereotomy.event.InceptionEvent; +import com.salesforce.apollo.stereotomy.event.proto.KeyState_; import com.salesforce.apollo.stereotomy.event.protobuf.KeyStateImpl; import com.salesforce.apollo.stereotomy.event.protobuf.ProtobufEventFactory; import com.salesforce.apollo.stereotomy.identifier.Identifier; +import java.util.*; + /** * @author hal.hildebrand */ diff --git a/thoth/src/main/java/com/salesforce/apollo/thoth/Ani.java b/thoth/src/main/java/com/salesforce/apollo/thoth/Ani.java index f7430692d..d299acc1b 100644 --- a/thoth/src/main/java/com/salesforce/apollo/thoth/Ani.java +++ b/thoth/src/main/java/com/salesforce/apollo/thoth/Ani.java @@ -16,6 +16,7 @@ import com.salesforce.apollo.stereotomy.event.KeyEvent; import com.salesforce.apollo.stereotomy.identifier.Identifier; import com.salesforce.apollo.utils.BbBackedInputStream; +import org.joou.ULong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,6 +64,12 @@ public Optional getKeyState(EventCoordinates coordinates) { return Optional.of(kerl.getKeyState(coordinates)); } + @Override + public Optional getKeyState(Identifier identifier, ULong seqNum) { + log.trace("Get key state: {}:{} on: {}", identifier, seqNum, member); + return Optional.of(kerl.getKeyState(identifier, seqNum)); + } + @Override public boolean validate(EstablishmentEvent event) { log.trace("Validate event: {} on: {}", event, member);