Skip to content

Commit d7cb500

Browse files
committed
Add bootstrap for client and table-store and bundle ValidateLedger contract (#260)
* Add bootstrap command for the table store * Add bootstrap and bundle ValidateLedger contract (#261)
1 parent 2fd9dfa commit d7cb500

File tree

41 files changed

+1036
-629
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1036
-629
lines changed

client/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ configurations {
2626

2727
dependencies {
2828
api project(':common')
29+
implementation project(':ledger') // to bundle ValidateLedger contract
2930
implementation group: 'com.moandjiezana.toml', name: 'toml4j', version: "${toml4jVersion}"
3031
implementation group: 'info.picocli', name: 'picocli', version: "${picoCliVersion}"
3132
api group: 'com.scalar-labs', name: 'scalardb', version: "${scalarDbVersion}"

client/src/main/java/com/scalar/dl/client/service/ClientService.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import com.scalar.dl.client.exception.ClientException;
1515
import com.scalar.dl.client.util.Common;
1616
import com.scalar.dl.client.util.RequestSigner;
17+
import com.scalar.dl.client.validation.contract.v1_0_0.ValidateLedger;
18+
import com.scalar.dl.ledger.config.AuthenticationMethod;
1719
import com.scalar.dl.ledger.model.ContractExecutionResult;
1820
import com.scalar.dl.ledger.model.LedgerValidationResult;
1921
import com.scalar.dl.ledger.service.StatusCode;
@@ -87,6 +89,48 @@ public ClientService(
8789
this.signer = signer;
8890
}
8991

92+
/**
93+
* Bootstraps the ledger by registering the identity (certificate or secret key) and system
94+
* contracts if necessary, based on {@code ClientConfig}. The authentication method (digital
95+
* signature or HMAC) is determined by the configuration. If the identity or contract is already
96+
* registered, it is simply skipped without throwing an exception.
97+
*
98+
* @throws ClientException if a request fails for some reason
99+
*/
100+
public void bootstrap() {
101+
try {
102+
if (config.getAuthenticationMethod().equals(AuthenticationMethod.DIGITAL_SIGNATURE)) {
103+
registerCertificate();
104+
} else {
105+
registerSecret();
106+
}
107+
} catch (ClientException e) {
108+
if (!e.getStatusCode().equals(StatusCode.CERTIFICATE_ALREADY_REGISTERED)
109+
&& !e.getStatusCode().equals(StatusCode.SECRET_ALREADY_REGISTERED)) {
110+
throw e;
111+
}
112+
}
113+
114+
registerValidateLedgerContract();
115+
}
116+
117+
private void registerValidateLedgerContract() {
118+
if (config.isAuditorEnabled()) {
119+
Class<?> clazz = ValidateLedger.class;
120+
try {
121+
registerContract(
122+
config.getAuditorLinearizableValidationContractId(),
123+
clazz.getName(),
124+
Common.getClassBytes(clazz),
125+
(String) null);
126+
} catch (ClientException e) {
127+
if (!e.getStatusCode().equals(StatusCode.CONTRACT_ALREADY_REGISTERED)) {
128+
throw e;
129+
}
130+
}
131+
}
132+
}
133+
90134
/**
91135
* Registers the certificate specified in the given {@code ClientConfig} for digital signature
92136
* authentication.
Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
1-
package com.scalar.dl.tablestore.client.tool;
1+
package com.scalar.dl.client.tool;
22

33
import com.google.common.annotations.VisibleForTesting;
44
import com.scalar.dl.client.config.ClientConfig;
55
import com.scalar.dl.client.config.GatewayClientConfig;
66
import com.scalar.dl.client.exception.ClientException;
7-
import com.scalar.dl.client.tool.Common;
8-
import com.scalar.dl.client.tool.CommonOptions;
9-
import com.scalar.dl.tablestore.client.service.ClientService;
10-
import com.scalar.dl.tablestore.client.service.ClientServiceFactory;
7+
import com.scalar.dl.client.service.ClientService;
8+
import com.scalar.dl.client.service.ClientServiceFactory;
119
import java.io.File;
1210
import java.util.concurrent.Callable;
13-
import picocli.CommandLine;
1411
import picocli.CommandLine.Command;
1512

16-
@Command(name = "register-contracts", description = "Register all necessary contracts.")
17-
public class ContractsRegistration extends CommonOptions implements Callable<Integer> {
18-
19-
public static void main(String[] args) {
20-
int exitCode = new CommandLine(new ContractsRegistration()).execute(args);
21-
System.exit(exitCode);
22-
}
13+
@Command(
14+
name = "bootstrap",
15+
description = "Bootstrap the ledger by registering identity and system contracts.")
16+
public class Bootstrap extends CommonOptions implements Callable<Integer> {
2317

2418
@Override
2519
public Integer call() throws Exception {
@@ -30,15 +24,16 @@ public Integer call() throws Exception {
3024
Integer call(ClientServiceFactory factory) throws Exception {
3125
ClientService service =
3226
useGateway
33-
? factory.create(new GatewayClientConfig(new File(properties)), false)
34-
: factory.create(new ClientConfig(new File(properties)), false);
27+
? factory.create(new GatewayClientConfig(new File(properties)))
28+
: factory.create(new ClientConfig(new File(properties)));
3529
return call(factory, service);
3630
}
3731

3832
@VisibleForTesting
3933
Integer call(ClientServiceFactory factory, ClientService service) {
4034
try {
41-
service.registerContracts();
35+
service.bootstrap();
36+
Common.printOutput(null);
4237
return 0;
4338
} catch (ClientException e) {
4439
Common.printError(e);

client/src/main/java/com/scalar/dl/client/tool/Common.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ private static JsonNode getProof(@Nullable AssetProof proof) {
4646
.put("signature", Base64.getEncoder().encodeToString(proof.getSignature()));
4747
}
4848

49-
static void printOutput(@Nullable JsonNode value) {
49+
public static void printOutput(@Nullable JsonNode value) {
5050
JsonNode json =
5151
mapper
5252
.createObjectNode()

client/src/main/java/com/scalar/dl/client/tool/GenericContractCommandLine.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
name = Common.SCALARDL_GC_SUBCOMMAND_NAME,
1616
aliases = Common.SCALARDL_GC_ALIAS,
1717
subcommands = {
18+
Bootstrap.class,
1819
CertificateRegistration.class,
1920
ContractExecution.class,
2021
ContractRegistration.class,
@@ -40,9 +41,11 @@ public static void main(String[] args) {
4041

4142
static void setupSections(CommandLine cmd) {
4243
ImmutableMap.Builder<String, List<Class<?>>> sections = ImmutableMap.builder();
44+
// Section: bootstrap.
45+
sections.put("%nbootstrap the ledger%n", Collections.singletonList(Bootstrap.class));
4346
// Section: register identity information.
4447
sections.put(
45-
"%nregister identity information%n",
48+
"%nregister identity information manually%n",
4649
Arrays.asList(CertificateRegistration.class, SecretRegistration.class));
4750
// Section: register business logic.
4851
sections.put(

client/src/main/java/com/scalar/dl/client/tool/ScalarDlCommandLine.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
@Command(
1616
name = "scalardl",
1717
subcommands = {
18+
Bootstrap.class,
1819
GenericContractCommandLine.class,
1920
CertificateRegistration.class,
2021
ContractExecution.class,
@@ -58,9 +59,11 @@ static void setupSections(CommandLine cmd) {
5859
// https://github.com/remkop/picocli/issues/978#issuecomment-604174211
5960

6061
ImmutableMap.Builder<String, List<Class<?>>> sections = ImmutableMap.builder();
62+
// Section: bootstrap.
63+
sections.put("%nbootstrap the ledger%n", Collections.singletonList(Bootstrap.class));
6164
// Section: register identity information.
6265
sections.put(
63-
"%nregister identity information%n",
66+
"%nregister identity information manually%n",
6467
Arrays.asList(CertificateRegistration.class, SecretRegistration.class));
6568
// Section: register business logic.
6669
sections.put(
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.scalar.dl.client.validation.contract.v1_0_0;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.node.ArrayNode;
6+
import com.scalar.dl.ledger.contract.JacksonBasedContract;
7+
import com.scalar.dl.ledger.database.AssetFilter;
8+
import com.scalar.dl.ledger.database.AssetFilter.AgeOrder;
9+
import com.scalar.dl.ledger.exception.ContractContextException;
10+
import com.scalar.dl.ledger.statemachine.Asset;
11+
import com.scalar.dl.ledger.statemachine.Ledger;
12+
import java.util.List;
13+
14+
public class ValidateLedger extends JacksonBasedContract {
15+
static final String ASSET_ID_KEY = "asset_id";
16+
static final String AGE_KEY = "age";
17+
static final String START_AGE_KEY = "start_age";
18+
static final String END_AGE_KEY = "end_age";
19+
static final String DATA_KEY = "data";
20+
21+
@Override
22+
public JsonNode invoke(Ledger<JsonNode> ledger, JsonNode argument, JsonNode properties) {
23+
if (!argument.has(ASSET_ID_KEY)) {
24+
throw new ContractContextException("please set asset_id in the argument");
25+
}
26+
27+
String assetId = argument.get(ASSET_ID_KEY).asText();
28+
29+
int startAge = 0;
30+
int endAge = Integer.MAX_VALUE;
31+
if (argument.has(AGE_KEY)) {
32+
int age = argument.get(AGE_KEY).asInt();
33+
startAge = age;
34+
endAge = age;
35+
} else {
36+
if (argument.has(START_AGE_KEY)) {
37+
startAge = argument.get(START_AGE_KEY).asInt();
38+
}
39+
if (argument.has(END_AGE_KEY)) {
40+
endAge = argument.get(END_AGE_KEY).asInt();
41+
}
42+
}
43+
44+
AssetFilter filter =
45+
new AssetFilter(assetId)
46+
.withStartAge(startAge, true)
47+
.withEndAge(endAge, true)
48+
.withAgeOrder(AgeOrder.ASC);
49+
List<Asset<JsonNode>> assets = ledger.scan(filter);
50+
51+
ObjectMapper mapper = getObjectMapper();
52+
ArrayNode array = mapper.createArrayNode();
53+
54+
assets.forEach(
55+
a -> array.add(mapper.createObjectNode().put(AGE_KEY, a.age()).set(DATA_KEY, a.data())));
56+
57+
return mapper.createObjectNode().set(assetId, array);
58+
}
59+
}

client/src/test/java/com/scalar/dl/client/service/ClientServiceTest.java

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import static org.assertj.core.api.Assertions.catchThrowable;
55
import static org.mockito.ArgumentMatchers.any;
66
import static org.mockito.ArgumentMatchers.anyString;
7+
import static org.mockito.ArgumentMatchers.eq;
78
import static org.mockito.Mockito.doReturn;
9+
import static org.mockito.Mockito.doThrow;
810
import static org.mockito.Mockito.inOrder;
911
import static org.mockito.Mockito.mock;
1012
import static org.mockito.Mockito.never;
@@ -17,7 +19,9 @@
1719
import com.scalar.dl.client.config.ClientMode;
1820
import com.scalar.dl.client.config.DigitalSignatureIdentityConfig;
1921
import com.scalar.dl.client.config.HmacIdentityConfig;
22+
import com.scalar.dl.client.exception.ClientException;
2023
import com.scalar.dl.client.util.RequestSigner;
24+
import com.scalar.dl.ledger.config.AuthenticationMethod;
2125
import com.scalar.dl.ledger.crypto.DigitalSignatureSigner;
2226
import com.scalar.dl.ledger.model.ContractExecutionResult;
2327
import com.scalar.dl.ledger.model.LedgerValidationResult;
@@ -96,6 +100,136 @@ public void setUp() throws IOException {
96100
anyFilePath = File.createTempFile(ANY_FILE_NAME, "").getPath();
97101
}
98102

103+
@Test
104+
public void bootstrap_DigitalSignature_ShouldCallRegisterCertificateAndContracts() {
105+
// Arrange
106+
when(config.getClientMode()).thenReturn(ClientMode.CLIENT);
107+
when(config.getAuthenticationMethod()).thenReturn(AuthenticationMethod.DIGITAL_SIGNATURE);
108+
when(config.isAuditorEnabled()).thenReturn(true);
109+
when(config.getAuditorLinearizableValidationContractId()).thenReturn(ANY_CONTRACT_ID);
110+
111+
// Act
112+
service.bootstrap();
113+
114+
// Assert
115+
verify(service).registerCertificate();
116+
verify(service, never()).registerSecret();
117+
verify(service)
118+
.registerContract(eq(ANY_CONTRACT_ID), anyString(), any(byte[].class), eq((String) null));
119+
}
120+
121+
@Test
122+
public void bootstrap_HmacSignature_ShouldCallRegisterSecretAndContracts() {
123+
// Arrange
124+
when(config.getClientMode()).thenReturn(ClientMode.CLIENT);
125+
when(config.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HMAC);
126+
when(config.isAuditorEnabled()).thenReturn(true);
127+
when(config.getAuditorLinearizableValidationContractId()).thenReturn(ANY_CONTRACT_ID);
128+
129+
// Act
130+
service.bootstrap();
131+
132+
// Assert
133+
verify(service, never()).registerCertificate();
134+
verify(service).registerSecret();
135+
verify(service)
136+
.registerContract(eq(ANY_CONTRACT_ID), anyString(), any(byte[].class), eq((String) null));
137+
}
138+
139+
@Test
140+
public void bootstrap_CertificateAlreadyRegistered_ShouldContinueWithContracts() {
141+
// Arrange
142+
when(config.getClientMode()).thenReturn(ClientMode.CLIENT);
143+
when(config.getAuthenticationMethod()).thenReturn(AuthenticationMethod.DIGITAL_SIGNATURE);
144+
when(config.isAuditorEnabled()).thenReturn(true);
145+
when(config.getAuditorLinearizableValidationContractId()).thenReturn(ANY_CONTRACT_ID);
146+
ClientException exception =
147+
new ClientException("Already registered", StatusCode.CERTIFICATE_ALREADY_REGISTERED);
148+
doThrow(exception).when(service).registerCertificate();
149+
150+
// Act
151+
service.bootstrap();
152+
153+
// Assert
154+
verify(service).registerCertificate();
155+
verify(service)
156+
.registerContract(eq(ANY_CONTRACT_ID), anyString(), any(byte[].class), eq((String) null));
157+
}
158+
159+
@Test
160+
public void bootstrap_SecretAlreadyRegistered_ShouldContinueWithContracts() {
161+
// Arrange
162+
when(config.getClientMode()).thenReturn(ClientMode.CLIENT);
163+
when(config.getAuthenticationMethod()).thenReturn(AuthenticationMethod.HMAC);
164+
when(config.isAuditorEnabled()).thenReturn(true);
165+
when(config.getAuditorLinearizableValidationContractId()).thenReturn(ANY_CONTRACT_ID);
166+
ClientException exception =
167+
new ClientException("Already registered", StatusCode.SECRET_ALREADY_REGISTERED);
168+
doThrow(exception).when(service).registerSecret();
169+
170+
// Act
171+
service.bootstrap();
172+
173+
// Assert
174+
verify(service).registerSecret();
175+
verify(service)
176+
.registerContract(eq(ANY_CONTRACT_ID), anyString(), any(byte[].class), eq((String) null));
177+
}
178+
179+
@Test
180+
public void bootstrap_ContractAlreadyRegistered_ShouldContinueWithoutRegisteringContract() {
181+
// Arrange
182+
when(config.getClientMode()).thenReturn(ClientMode.CLIENT);
183+
when(config.getAuthenticationMethod()).thenReturn(AuthenticationMethod.DIGITAL_SIGNATURE);
184+
when(config.isAuditorEnabled()).thenReturn(true);
185+
when(config.getAuditorLinearizableValidationContractId()).thenReturn(ANY_CONTRACT_ID);
186+
ClientException exception =
187+
new ClientException("Already registered", StatusCode.CONTRACT_ALREADY_REGISTERED);
188+
doThrow(exception)
189+
.when(service)
190+
.registerContract(eq(ANY_CONTRACT_ID), anyString(), any(byte[].class), eq((String) null));
191+
192+
// Act
193+
service.bootstrap();
194+
195+
// Assert
196+
verify(service).registerCertificate();
197+
verify(service)
198+
.registerContract(eq(ANY_CONTRACT_ID), anyString(), any(byte[].class), eq((String) null));
199+
}
200+
201+
@Test
202+
public void bootstrap_OtherExceptionThrown_ShouldThrowException() {
203+
// Arrange
204+
ClientException exception = new ClientException("Invalid request", StatusCode.INVALID_REQUEST);
205+
when(config.getAuthenticationMethod()).thenReturn(AuthenticationMethod.DIGITAL_SIGNATURE);
206+
doThrow(exception).when(service).registerCertificate();
207+
208+
// Act
209+
Throwable thrown = catchThrowable(() -> service.bootstrap());
210+
211+
// Assert
212+
assertThat(thrown).isExactlyInstanceOf(ClientException.class);
213+
assertThat(((ClientException) thrown).getStatusCode()).isEqualTo(StatusCode.INVALID_REQUEST);
214+
}
215+
216+
@Test
217+
public void bootstrap_AuditorDisabled_ShouldContinueWithoutRegisteringContract() {
218+
// Arrange
219+
when(config.getClientMode()).thenReturn(ClientMode.CLIENT);
220+
when(config.getAuthenticationMethod()).thenReturn(AuthenticationMethod.DIGITAL_SIGNATURE);
221+
when(config.isAuditorEnabled()).thenReturn(false);
222+
when(config.getAuditorLinearizableValidationContractId()).thenReturn(ANY_CONTRACT_ID);
223+
224+
// Act
225+
service.bootstrap();
226+
227+
// Assert
228+
verify(service).registerCertificate();
229+
verify(service, never())
230+
.registerContract(anyString(), anyString(), any(byte[].class), eq((String) null));
231+
}
232+
99233
@Test
100234
public void registerCertificate_CorrectInputsGiven_ShouldRegisterProperly() {
101235
// Arrange

0 commit comments

Comments
 (0)