Skip to content

Commit 9974e04

Browse files
authored
Fix numeric value handling and add integration tests for table-based generic contracts (#138)
1 parent 92c1620 commit 9974e04

File tree

14 files changed

+2563
-367
lines changed

14 files changed

+2563
-367
lines changed

common/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/Constants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ public class Constants {
77
// Metadata
88
public static final String PACKAGE = "table";
99
public static final String VERSION = "v1_0_0";
10+
public static final String CONTRACT_GET_ASSET_ID = PACKAGE + "." + VERSION + ".GetAssetId";
1011
public static final String CONTRACT_SCAN = PACKAGE + "." + VERSION + ".Scan";
1112

1213
// Constants
1314
public static final String PREFIX_TABLE = "tbl_";
1415
public static final String PREFIX_INDEX = "idx_";
1516
public static final String PREFIX_RECORD = "rec_";
1617
public static final String ASSET_ID_METADATA_TABLES = "metadata_tables";
18+
public static final String ASSET_ID_PREFIX = "prefix";
19+
public static final String ASSET_ID_VALUES = "values";
1720
public static final String ASSET_ID_SEPARATOR = "_";
1821
public static final String TABLE_NAME = "name";
1922
public static final String TABLE_KEY = "key";

common/src/main/java/com/scalar/dl/ledger/server/BaseServer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.scalar.dl.ledger.server;
22

3+
import com.google.common.annotations.VisibleForTesting;
34
import com.google.common.util.concurrent.Uninterruptibles;
45
import com.google.inject.Injector;
56
import com.scalar.dl.ledger.config.ServerConfig;
@@ -124,7 +125,8 @@ private void decommission() {
124125
config.getDecommissioningDurationSecs(), TimeUnit.SECONDS);
125126
}
126127

127-
private void stop() throws InterruptedException {
128+
@VisibleForTesting
129+
public void stop() throws InterruptedException {
128130
if (server != null) {
129131
server.shutdown();
130132
}

generic-contracts/conf/table-authenticity-management-contracts.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ contract-id = "table.v1_0_0.Select"
1313
contract-binary-name = "com.scalar.dl.genericcontracts.table.v1_0_0.Select"
1414
contract-class-file = "generic-contracts/build/classes/java/main/com/scalar/dl/genericcontracts/table/v1_0_0/Select.class"
1515

16+
[[contracts]]
17+
contract-id = "table.v1_0_0.GetAssetId"
18+
contract-binary-name = "com.scalar.dl.genericcontracts.table.v1_0_0.GetAssetId"
19+
contract-class-file = "generic-contracts/build/classes/java/main/com/scalar/dl/genericcontracts/table/v1_0_0/GetAssetId.class"
20+
1621
[[contracts]]
1722
contract-id = "table.v1_0_0.Scan"
1823
contract-binary-name = "com.scalar.dl.genericcontracts.table.v1_0_0.Scan"
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
package com.scalar.dl.genericcontracts;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.google.common.collect.ImmutableMap;
5+
import com.google.inject.Guice;
6+
import com.google.inject.Injector;
7+
import com.scalar.db.api.DistributedStorage;
8+
import com.scalar.db.api.DistributedStorageAdmin;
9+
import com.scalar.db.config.DatabaseConfig;
10+
import com.scalar.db.exception.storage.ExecutionException;
11+
import com.scalar.db.schemaloader.SchemaLoader;
12+
import com.scalar.db.schemaloader.SchemaLoaderException;
13+
import com.scalar.db.service.StorageFactory;
14+
import com.scalar.db.storage.dynamo.DynamoAdmin;
15+
import com.scalar.db.storage.dynamo.DynamoConfig;
16+
import com.scalar.dl.client.config.ClientConfig;
17+
import com.scalar.dl.client.service.ClientServiceFactory;
18+
import com.scalar.dl.client.service.GenericContractClientService;
19+
import com.scalar.dl.ledger.config.LedgerConfig;
20+
import com.scalar.dl.ledger.server.AdminService;
21+
import com.scalar.dl.ledger.server.BaseServer;
22+
import com.scalar.dl.ledger.server.LedgerPrivilegedService;
23+
import com.scalar.dl.ledger.server.LedgerServerModule;
24+
import java.io.File;
25+
import java.io.IOException;
26+
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.nio.file.Paths;
29+
import java.util.HashMap;
30+
import java.util.List;
31+
import java.util.Map;
32+
import java.util.Properties;
33+
import java.util.concurrent.Callable;
34+
import java.util.concurrent.ExecutorService;
35+
import java.util.concurrent.Executors;
36+
import java.util.concurrent.Future;
37+
import org.junit.jupiter.api.AfterAll;
38+
import org.junit.jupiter.api.AfterEach;
39+
import org.junit.jupiter.api.BeforeAll;
40+
import org.junit.jupiter.api.BeforeEach;
41+
import org.junit.jupiter.api.TestInstance;
42+
43+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
44+
public abstract class GenericContractEndToEndTestBase {
45+
private static final int THREAD_NUM = 10;
46+
private static final String SCALAR_NAMESPACE = "scalar";
47+
private static final String ASSET_TABLE = "asset";
48+
private static final String ASSET_METADATA_TABLE = "asset_metadata";
49+
private static final String LEDGER_SCHEMA_PATH = "/scripts/ledger-schema.json";
50+
private static final String FUNCTION_DB_SCHEMA_PATH = "/scripts/objects-table-schema.json";
51+
private static final String PACKAGE_PREFIX = "com.scalar.dl.genericcontracts.";
52+
private static final String CLASS_DIR = "build/classes/java/main/";
53+
private static final String FUNCTION_NAMESPACE = "test";
54+
private static final String FUNCTION_TABLE = "objects";
55+
56+
private static final String JDBC_TRANSACTION_MANAGER = "jdbc";
57+
private static final String PROP_STORAGE = "scalardb.storage";
58+
private static final String PROP_CONTACT_POINTS = "scalardb.contact_points";
59+
private static final String PROP_USERNAME = "scalardb.username";
60+
private static final String PROP_PASSWORD = "scalardb.password";
61+
private static final String PROP_TRANSACTION_MANAGER = "scalardb.transaction_manager";
62+
private static final String PROP_DYNAMO_ENDPOINT_OVERRIDE = "scalardb.dynamo.endpoint_override";
63+
private static final String DEFAULT_STORAGE = "jdbc";
64+
private static final String DEFAULT_CONTACT_POINTS = "jdbc:mysql://localhost/";
65+
private static final String DEFAULT_USERNAME = "root";
66+
private static final String DEFAULT_PASSWORD = "mysql";
67+
private static final String DEFAULT_TRANSACTION_MANAGER = "consensus-commit";
68+
private static final String DEFAULT_DYNAMO_ENDPOINT_OVERRIDE = "http://localhost:8000";
69+
70+
private static final String SOME_ENTITY_1 = "entity1";
71+
private static final String SOME_ENTITY_2 = "entity2";
72+
private static final String SOME_PRIVATE_KEY =
73+
"-----BEGIN EC PRIVATE KEY-----\n"
74+
+ "MHcCAQEEIF4SjQxTArRcZaROSFjlBP2rR8fAKtL8y+kmGiSlM5hEoAoGCCqGSM49\n"
75+
+ "AwEHoUQDQgAEY0i/iAFxIBS3etbjoSC1/aUKQV66+wiawL4bZqklu86ObIc7wrif\n"
76+
+ "HExPmVhKFSklOyZqGoOiVZA0zf0LZeFaPA==\n"
77+
+ "-----END EC PRIVATE KEY-----";
78+
public static final String SOME_CERTIFICATE =
79+
"-----BEGIN CERTIFICATE-----\n"
80+
+ "MIICQTCCAeagAwIBAgIUEKARigcZQ3sLEXdlEtjYissVx0cwCgYIKoZIzj0EAwIw\n"
81+
+ "QTELMAkGA1UEBhMCSlAxDjAMBgNVBAgTBVRva3lvMQ4wDAYDVQQHEwVUb2t5bzES\n"
82+
+ "MBAGA1UEChMJU2FtcGxlIENBMB4XDTE4MDYyMTAyMTUwMFoXDTE5MDYyMTAyMTUw\n"
83+
+ "MFowRTELMAkGA1UEBhMCSlAxDjAMBgNVBAgTBVRva3lvMQ4wDAYDVQQHEwVUb2t5\n"
84+
+ "bzEWMBQGA1UEChMNU2FtcGxlIENsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEH\n"
85+
+ "A0IABGNIv4gBcSAUt3rW46Egtf2lCkFeuvsImsC+G2apJbvOjmyHO8K4nxxMT5lY\n"
86+
+ "ShUpJTsmahqDolWQNM39C2XhWjyjgbcwgbQwDgYDVR0PAQH/BAQDAgWgMB0GA1Ud\n"
87+
+ "JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW\n"
88+
+ "BBTpBQl/JxB7yr77uMVT9mMicPeVJTAfBgNVHSMEGDAWgBQrJo3N3/0j3oPS6F6m\n"
89+
+ "wunHe8xLpzA1BgNVHREELjAsghJjbGllbnQuZXhhbXBsZS5jb22CFnd3dy5jbGll\n"
90+
+ "bnQuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDSQAwRgIhAJPtXSzuncDJXnM+7us8\n"
91+
+ "46MEVjGHJy70bRY1My23RkxbAiEA5oFgTKMvls8e4UpnmUgFNP+FH8a5bF4tUPaV\n"
92+
+ "BQiBbgk=\n"
93+
+ "-----END CERTIFICATE-----";
94+
private static final int SOME_KEY_VERSION = 1;
95+
96+
private BaseServer ledgerServer;
97+
private ExecutorService executorService;
98+
private Properties props;
99+
private Map<String, String> creationOptions = new HashMap<>();
100+
private Path ledgerSchemaPath;
101+
private Path databaseSchemaPath;
102+
private String functionNamespace;
103+
private String functionTable;
104+
105+
protected DistributedStorage storage;
106+
protected DistributedStorageAdmin storageAdmin;
107+
protected final ClientServiceFactory clientServiceFactory = new ClientServiceFactory();
108+
protected GenericContractClientService clientService;
109+
protected GenericContractClientService anotherClientService;
110+
111+
@BeforeAll
112+
public void setUpBeforeClass() throws Exception {
113+
executorService = Executors.newFixedThreadPool(getThreadNum());
114+
props = createLedgerProperties();
115+
StorageFactory factory = StorageFactory.create(props);
116+
storage = factory.getStorage();
117+
storageAdmin = factory.getStorageAdmin();
118+
ledgerSchemaPath = Paths.get(System.getProperty("user.dir") + LEDGER_SCHEMA_PATH);
119+
databaseSchemaPath = Paths.get(getFunctionDatabaseSchema());
120+
functionNamespace = getFunctionNamespace();
121+
functionTable = getFunctionTable();
122+
createSchema();
123+
124+
createServer(new LedgerConfig(props));
125+
126+
clientService = createClientService(SOME_ENTITY_1);
127+
clientService.registerCertificate();
128+
registerContracts(clientService, getContractsMap(), getContractPropertiesMap());
129+
registerFunction(clientService, getFunctionsMap());
130+
131+
anotherClientService = createClientService(SOME_ENTITY_2);
132+
anotherClientService.registerCertificate();
133+
registerContracts(anotherClientService, getContractsMap(), getAnotherContractPropertiesMap());
134+
}
135+
136+
@AfterAll
137+
public void tearDownAfterClass() throws SchemaLoaderException, InterruptedException {
138+
ledgerServer.stop();
139+
storage.close();
140+
storageAdmin.close();
141+
SchemaLoader.unload(props, ledgerSchemaPath, true);
142+
SchemaLoader.unload(props, databaseSchemaPath, true);
143+
}
144+
145+
@BeforeEach
146+
public void setUp() {}
147+
148+
@AfterEach
149+
public void tearDown() throws ExecutionException {
150+
storageAdmin.truncateTable(SCALAR_NAMESPACE, ASSET_TABLE);
151+
storageAdmin.truncateTable(SCALAR_NAMESPACE, ASSET_METADATA_TABLE);
152+
storageAdmin.truncateTable(functionNamespace, functionTable);
153+
}
154+
155+
abstract Map<String, String> getContractsMap();
156+
157+
abstract Map<String, String> getFunctionsMap();
158+
159+
protected Map<String, JsonNode> getContractPropertiesMap() {
160+
return ImmutableMap.of();
161+
}
162+
163+
protected Map<String, JsonNode> getAnotherContractPropertiesMap() {
164+
return ImmutableMap.of();
165+
}
166+
167+
protected String getScalarNamespace() {
168+
return SCALAR_NAMESPACE;
169+
}
170+
171+
protected String getAssetTable() {
172+
return ASSET_TABLE;
173+
}
174+
175+
protected String getAssetMetadataTable() {
176+
return ASSET_METADATA_TABLE;
177+
}
178+
179+
protected String getFunctionDatabaseSchema() {
180+
return System.getProperty("user.dir") + FUNCTION_DB_SCHEMA_PATH;
181+
}
182+
183+
protected String getFunctionNamespace() {
184+
return FUNCTION_NAMESPACE;
185+
}
186+
187+
protected String getFunctionTable() {
188+
return FUNCTION_TABLE;
189+
}
190+
191+
protected static String getContractId(String prefix, String contractName) {
192+
return prefix + "." + contractName;
193+
}
194+
195+
protected static String getContractBinaryName(String prefix, String contractName) {
196+
return PACKAGE_PREFIX + getContractId(prefix, contractName);
197+
}
198+
199+
protected static String getFunctionId(String prefix, String functionName) {
200+
return prefix + "." + functionName;
201+
}
202+
203+
protected static String getFunctionBinaryName(String prefix, String functionName) {
204+
return PACKAGE_PREFIX + getFunctionId(prefix, functionName);
205+
}
206+
207+
protected int getThreadNum() {
208+
return THREAD_NUM;
209+
}
210+
211+
protected void executeInParallel(List<Callable<Void>> testCallables)
212+
throws InterruptedException, java.util.concurrent.ExecutionException {
213+
List<Future<Void>> futures = executorService.invokeAll(testCallables);
214+
for (Future<Void> future : futures) {
215+
future.get();
216+
}
217+
}
218+
219+
private Properties createLedgerProperties() {
220+
String storage = System.getProperty(PROP_STORAGE, DEFAULT_STORAGE);
221+
String contactPoints = System.getProperty(PROP_CONTACT_POINTS, DEFAULT_CONTACT_POINTS);
222+
String username = System.getProperty(PROP_USERNAME, DEFAULT_USERNAME);
223+
String password = System.getProperty(PROP_PASSWORD, DEFAULT_PASSWORD);
224+
String transactionManager =
225+
System.getProperty(PROP_TRANSACTION_MANAGER, DEFAULT_TRANSACTION_MANAGER);
226+
String endpointOverride =
227+
System.getProperty(PROP_DYNAMO_ENDPOINT_OVERRIDE, DEFAULT_DYNAMO_ENDPOINT_OVERRIDE);
228+
229+
Properties props = new Properties();
230+
props.put(DatabaseConfig.STORAGE, storage);
231+
props.put(DatabaseConfig.CONTACT_POINTS, contactPoints);
232+
props.put(DatabaseConfig.USERNAME, username);
233+
props.put(DatabaseConfig.PASSWORD, password);
234+
props.put(DatabaseConfig.TRANSACTION_MANAGER, transactionManager);
235+
if (transactionManager.equals(JDBC_TRANSACTION_MANAGER)) {
236+
props.put(LedgerConfig.TX_STATE_MANAGEMENT_ENABLED, "true");
237+
}
238+
props.put(LedgerConfig.PROOF_ENABLED, "true");
239+
props.put(LedgerConfig.PROOF_PRIVATE_KEY_PEM, SOME_PRIVATE_KEY);
240+
241+
if (storage.equals(DynamoConfig.STORAGE_NAME)) {
242+
props.put(DynamoConfig.ENDPOINT_OVERRIDE, endpointOverride);
243+
props.put(
244+
DynamoConfig.TABLE_METADATA_NAMESPACE, DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME);
245+
creationOptions =
246+
ImmutableMap.of(DynamoAdmin.NO_SCALING, "true", DynamoAdmin.NO_BACKUP, "true");
247+
}
248+
249+
return props;
250+
}
251+
252+
private void createSchema() throws SchemaLoaderException {
253+
SchemaLoader.load(props, ledgerSchemaPath, creationOptions, true);
254+
SchemaLoader.load(props, databaseSchemaPath, creationOptions, true);
255+
}
256+
257+
private void createServer(LedgerConfig config) throws IOException, InterruptedException {
258+
Injector injector = Guice.createInjector(new LedgerServerModule(config));
259+
ledgerServer = new BaseServer(injector, config);
260+
261+
ledgerServer.start(com.scalar.dl.ledger.server.LedgerService.class);
262+
ledgerServer.startPrivileged(LedgerPrivilegedService.class);
263+
ledgerServer.startAdmin(AdminService.class);
264+
}
265+
266+
private GenericContractClientService createClientService(String entity) throws IOException {
267+
Properties props = new Properties();
268+
props.put(ClientConfig.ENTITY_ID, entity);
269+
props.put(ClientConfig.DS_CERT_VERSION, String.valueOf(SOME_KEY_VERSION));
270+
props.put(ClientConfig.DS_CERT_PEM, SOME_CERTIFICATE);
271+
props.put(ClientConfig.DS_PRIVATE_KEY_PEM, SOME_PRIVATE_KEY);
272+
return clientServiceFactory.createForGenericContract(new ClientConfig(props));
273+
}
274+
275+
private void registerContracts(
276+
GenericContractClientService clientService,
277+
Map<String, String> contractsMap,
278+
Map<String, JsonNode> propertiesMap)
279+
throws IOException {
280+
for (Map.Entry<String, String> entry : contractsMap.entrySet()) {
281+
String contractId = entry.getKey();
282+
String contractBinaryName = entry.getValue();
283+
String contractFilePath = CLASS_DIR + contractBinaryName.replace('.', '/') + ".class";
284+
byte[] bytes = Files.readAllBytes(new File(contractFilePath).toPath());
285+
JsonNode properties = propertiesMap.getOrDefault(contractId, null);
286+
clientService.registerContract(contractId, contractBinaryName, bytes, properties);
287+
}
288+
}
289+
290+
private void registerFunction(
291+
GenericContractClientService clientService, Map<String, String> functionsMap)
292+
throws IOException {
293+
for (Map.Entry<String, String> entry : functionsMap.entrySet()) {
294+
String functionId = entry.getKey();
295+
String functionBinaryName = entry.getValue();
296+
String functionFilePath = CLASS_DIR + functionBinaryName.replace('.', '/') + ".class";
297+
byte[] bytes = Files.readAllBytes(new File(functionFilePath).toPath());
298+
clientService.registerFunction(functionId, functionBinaryName, bytes);
299+
}
300+
}
301+
}

0 commit comments

Comments
 (0)