mirror of https://github.com/hyperledger/besu
Go quorum enclave (#1599)
* add enclave for goQuorum privacy using Tessera Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>pull/1608/head
parent
393482012a
commit
a4a8723b69
@ -0,0 +1,98 @@ |
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.enclave; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.enclave.types.GoQuorumReceiveResponse; |
||||
import org.hyperledger.besu.enclave.types.SendResponse; |
||||
|
||||
import java.net.URI; |
||||
import java.net.URISyntaxException; |
||||
import java.util.Arrays; |
||||
import java.util.Base64; |
||||
import java.util.List; |
||||
|
||||
import io.vertx.core.Vertx; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.mockito.ArgumentMatchers; |
||||
|
||||
public class GoQuorumEnclaveTest { |
||||
|
||||
private static final byte[] PAYLOAD = Base64.getDecoder().decode("EAAAAAAA"); |
||||
private static final String MOCK_KEY = "iOCzoGo5kwtZU0J41Z9xnGXHN6ZNukIa9MspvHtu3Jk="; |
||||
private static final String KEY = "key"; |
||||
private static GoQuorumEnclave enclave; |
||||
|
||||
private RequestTransmitter vertxTransmitter; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
enclave = createGoQuorumEnclaveWithMockRequestTransmitter(); |
||||
} |
||||
|
||||
@Test |
||||
public void testUpCheck() { |
||||
when(vertxTransmitter.get(any(), any(), ArgumentMatchers.contains("/upcheck"), any())) |
||||
.thenReturn("I'm up!"); |
||||
|
||||
assertThat(enclave.upCheck()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void testReceiveThrowsWhenPayloadDoesNotExist() { |
||||
when(vertxTransmitter.get(any(), any(), ArgumentMatchers.contains("/receive"), any())) |
||||
.thenThrow( |
||||
new EnclaveClientException(404, "Message with hash " + MOCK_KEY + " was not found")); |
||||
|
||||
assertThatThrownBy(() -> enclave.receive(MOCK_KEY)) |
||||
.isInstanceOf(EnclaveClientException.class) |
||||
.hasMessageContaining("Message with hash " + MOCK_KEY + " was not found"); |
||||
} |
||||
|
||||
@Test |
||||
public void testSendAndReceive() { |
||||
when(vertxTransmitter.post(any(), any(), any(), any())).thenReturn(new SendResponse(KEY)); |
||||
when(vertxTransmitter.get(any(), any(), ArgumentMatchers.contains("/receive"), any())) |
||||
.thenReturn(new GoQuorumReceiveResponse(PAYLOAD, 0, null, null)); |
||||
|
||||
final List<String> publicKeys = Arrays.asList("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc="); |
||||
|
||||
final SendResponse sr = enclave.send(PAYLOAD, publicKeys.get(0), publicKeys); |
||||
assertThat(sr.getKey()).isEqualTo(KEY); |
||||
|
||||
final GoQuorumReceiveResponse rr = enclave.receive(sr.getKey()); |
||||
assertThat(rr).isNotNull(); |
||||
assertThat(rr.getPayload()).isEqualTo(PAYLOAD); |
||||
} |
||||
|
||||
@Test |
||||
public void upcheckReturnsFalseIfNoResponseReceived() throws URISyntaxException { |
||||
final Vertx vertx = Vertx.vertx(); |
||||
final EnclaveFactory factory = new EnclaveFactory(vertx); |
||||
assertThat(factory.createGoQuorumEnclave(new URI("http://8.8.8.8:65535")).upCheck()).isFalse(); |
||||
} |
||||
|
||||
private GoQuorumEnclave createGoQuorumEnclaveWithMockRequestTransmitter() { |
||||
vertxTransmitter = mock(RequestTransmitter.class); |
||||
|
||||
return new GoQuorumEnclave(vertxTransmitter); |
||||
} |
||||
} |
@ -0,0 +1,145 @@ |
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.enclave; |
||||
|
||||
import org.hyperledger.besu.enclave.RequestTransmitter.ResponseBodyHandler; |
||||
import org.hyperledger.besu.enclave.types.GoQuorumReceiveResponse; |
||||
import org.hyperledger.besu.enclave.types.GoQuorumSendRequest; |
||||
import org.hyperledger.besu.enclave.types.ReceiveRequest; |
||||
import org.hyperledger.besu.enclave.types.SendResponse; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.List; |
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
||||
public class GoQuorumEnclave { |
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper(); |
||||
private static final String JSON = "application/json"; |
||||
|
||||
private final RequestTransmitter requestTransmitter; |
||||
|
||||
public GoQuorumEnclave(final RequestTransmitter requestTransmitter) { |
||||
this.requestTransmitter = requestTransmitter; |
||||
} |
||||
|
||||
public boolean upCheck() { |
||||
try { |
||||
final String upcheckResponse = |
||||
requestTransmitter.get(null, null, "/upcheck", this::handleRawResponse); |
||||
return upcheckResponse.equals("I'm up!"); |
||||
} catch (final Exception e) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public SendResponse send( |
||||
final byte[] payload, final String privateFrom, final List<String> privateFor) { |
||||
final GoQuorumSendRequest request = new GoQuorumSendRequest(payload, privateFrom, privateFor); |
||||
return post( |
||||
JSON, |
||||
request, |
||||
"/send", |
||||
(statusCode, body) -> handleJsonResponse(statusCode, body, SendResponse.class, 201)); |
||||
} |
||||
|
||||
public GoQuorumReceiveResponse receive(final String payloadKey) { |
||||
final ReceiveRequest request = new ReceiveRequest(payloadKey); |
||||
return get( |
||||
JSON, |
||||
request, |
||||
"/receive", |
||||
(statusCode, body) -> |
||||
handleJsonResponse(statusCode, body, GoQuorumReceiveResponse.class, 200)); |
||||
} |
||||
|
||||
private <T> T post( |
||||
final String mediaType, |
||||
final Object content, |
||||
final String endpoint, |
||||
final ResponseBodyHandler<T> responseBodyHandler) { |
||||
final String bodyText; |
||||
try { |
||||
bodyText = objectMapper.writeValueAsString(content); |
||||
} catch (final JsonProcessingException e) { |
||||
throw new EnclaveClientException(400, "Unable to serialize request."); |
||||
} |
||||
|
||||
return requestTransmitter.post(mediaType, bodyText, endpoint, responseBodyHandler); |
||||
} |
||||
|
||||
private <T> T get( |
||||
final String mediaType, |
||||
final Object content, |
||||
final String endpoint, |
||||
final ResponseBodyHandler<T> responseBodyHandler) { |
||||
final String bodyText; |
||||
try { |
||||
bodyText = objectMapper.writeValueAsString(content); |
||||
} catch (final JsonProcessingException e) { |
||||
throw new EnclaveClientException(400, "Unable to serialize request."); |
||||
} |
||||
|
||||
final T t = requestTransmitter.get(mediaType, bodyText, endpoint, responseBodyHandler); |
||||
return t; |
||||
} |
||||
|
||||
private <T> T handleJsonResponse( |
||||
final int statusCode, |
||||
final byte[] body, |
||||
final Class<T> responseType, |
||||
final int expectedStatusCode) { |
||||
|
||||
if (isSuccess(statusCode, expectedStatusCode)) { |
||||
return parseResponse(statusCode, body, responseType); |
||||
} else if (clientError(statusCode)) { |
||||
final String utf8EncodedBody = new String(body, StandardCharsets.UTF_8); |
||||
throw new EnclaveClientException(statusCode, utf8EncodedBody); |
||||
} else { |
||||
final String utf8EncodedBody = new String(body, StandardCharsets.UTF_8); |
||||
throw new EnclaveServerException(statusCode, utf8EncodedBody); |
||||
} |
||||
} |
||||
|
||||
private <T> T parseResponse( |
||||
final int statusCode, final byte[] body, final Class<T> responseType) { |
||||
try { |
||||
return objectMapper.readValue(body, responseType); |
||||
} catch (final IOException e) { |
||||
final String utf8EncodedBody = new String(body, StandardCharsets.UTF_8); |
||||
throw new EnclaveClientException(statusCode, utf8EncodedBody); |
||||
} |
||||
} |
||||
|
||||
private boolean clientError(final int statusCode) { |
||||
return statusCode >= 400 && statusCode < 500; |
||||
} |
||||
|
||||
private boolean isSuccess(final int statusCode, final int expectedStatusCode) { |
||||
return statusCode == expectedStatusCode; |
||||
} |
||||
|
||||
private String handleRawResponse(final int statusCode, final byte[] body) { |
||||
final String bodyText = new String(body, StandardCharsets.UTF_8); |
||||
if (isSuccess(statusCode, 200)) { |
||||
return bodyText; |
||||
} |
||||
throw new EnclaveClientException( |
||||
statusCode, String.format("Request failed with %d; body={%s}", statusCode, bodyText)); |
||||
} |
||||
} |
@ -0,0 +1,55 @@ |
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.enclave.types; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator; |
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
||||
public class GoQuorumReceiveResponse { |
||||
|
||||
private final byte[] payload; |
||||
private final int privacyFlag; |
||||
private final String affectedContractTransactions[]; |
||||
private final String execHash; |
||||
|
||||
@JsonCreator |
||||
public GoQuorumReceiveResponse( |
||||
@JsonProperty(value = "payload") final byte[] payload, |
||||
@JsonProperty(value = "privacyFlag") final int privacyFlag, |
||||
@JsonProperty(value = "affectedContractTransactions") |
||||
final String affectedContractTransactions[], |
||||
@JsonProperty(value = "execHash") final String execHash) { |
||||
this.payload = payload; |
||||
this.privacyFlag = privacyFlag; |
||||
this.affectedContractTransactions = affectedContractTransactions; |
||||
this.execHash = execHash; |
||||
} |
||||
|
||||
public byte[] getPayload() { |
||||
return payload; |
||||
} |
||||
|
||||
public int getPrivacyFlag() { |
||||
return privacyFlag; |
||||
} |
||||
|
||||
public String[] getAffectedContractTransactions() { |
||||
return affectedContractTransactions; |
||||
} |
||||
|
||||
public String getExecHash() { |
||||
return execHash; |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
/* |
||||
* Copyright ConsenSys AG. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.enclave.types; |
||||
|
||||
import java.util.List; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder; |
||||
|
||||
@JsonPropertyOrder({"payload", "from", "to"}) |
||||
public class GoQuorumSendRequest { |
||||
private final byte[] payload; |
||||
private final String from; |
||||
private final List<String> to; |
||||
|
||||
public GoQuorumSendRequest( |
||||
@JsonProperty(value = "payload") final byte[] payload, |
||||
@JsonProperty(value = "from") final String from, |
||||
@JsonProperty(value = "to") final List<String> to) { |
||||
this.payload = payload; |
||||
this.from = from; |
||||
this.to = to; |
||||
} |
||||
|
||||
public byte[] getPayload() { |
||||
return payload; |
||||
} |
||||
|
||||
public String getFrom() { |
||||
return from; |
||||
} |
||||
|
||||
public List<String> getTo() { |
||||
return to; |
||||
} |
||||
} |
Loading…
Reference in new issue