Skip to content

Commit 015d820

Browse files
WIP Make QA service configurable [ECR-3869]
[skip ci]
1 parent 92de342 commit 015d820

File tree

6 files changed

+174
-17
lines changed

6 files changed

+174
-17
lines changed

exonum-java-binding/qa-service/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
<execution>
173173
<goals>
174174
<goal>compile</goal>
175+
<goal>test-compile</goal>
175176
</goals>
176177
<configuration>
177178
<protocArtifact>

exonum-java-binding/qa-service/src/main/java/com/exonum/binding/qaservice/QaService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.exonum.binding.common.crypto.PublicKey;
2020
import com.exonum.binding.common.hash.HashCode;
21+
import com.exonum.binding.core.service.Configurable;
2122
import com.exonum.binding.core.service.Node;
2223
import com.exonum.binding.core.service.Service;
2324
import com.exonum.binding.core.transaction.RawTransaction;
@@ -29,7 +30,7 @@
2930
/**
3031
* A simple service for QA purposes.
3132
*/
32-
public interface QaService extends Service {
33+
public interface QaService extends Service, Configurable {
3334

3435
/**
3536
* Creates a new self-signed 'increment counter' transaction and submits

exonum-java-binding/qa-service/src/main/java/com/exonum/binding/qaservice/QaServiceImpl.java

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public List<HashCode> getStateHashes(Snapshot snapshot) {
101101
@Override
102102
public void initialize(Fork fork, Configuration configuration) {
103103
// Init the time oracle
104-
initTimeOracle(fork, configuration);
104+
updateTimeOracle(fork, configuration);
105105

106106
// Add a default counter to the blockchain.
107107
createCounter(DEFAULT_COUNTER_NAME, fork);
@@ -110,20 +110,6 @@ public void initialize(Fork fork, Configuration configuration) {
110110
createCounter(AFTER_COMMIT_COUNTER_NAME, fork);
111111
}
112112

113-
private void initTimeOracle(Fork fork, Configuration configuration) {
114-
QaSchema schema = createDataSchema(fork);
115-
InitialConfiguration config = configuration.getAsMessage(InitialConfiguration.class);
116-
String timeOracleName = config.getTimeOracleName();
117-
// Check the time oracle name is non-empty.
118-
// We do *not* check if the time oracle is active to (a) allow running this service with
119-
// reduced read functionality without time oracle; (b) testing time schema when it is not
120-
// active.
121-
checkArgument(!Strings.isNullOrEmpty(timeOracleName), "Empty time oracle name: %s",
122-
timeOracleName);
123-
// Save the configuration
124-
schema.timeOracleName().set(timeOracleName);
125-
}
126-
127113
private void createCounter(String name, Fork fork) {
128114
QaSchema schema = createDataSchema(fork);
129115
MapIndex<HashCode, Long> counters = schema.counters();
@@ -231,4 +217,37 @@ private HashCode submitTransaction(RawTransaction rawTransaction) {
231217
private void checkBlockchainInitialized() {
232218
checkState(node != null, "Service has not been fully initialized yet");
233219
}
220+
221+
@Override
222+
public void verifyConfiguration(Fork fork, Configuration configuration) {
223+
InitialConfiguration config = configuration.getAsMessage(InitialConfiguration.class);
224+
checkConfiguration(config);
225+
}
226+
227+
@Override
228+
public void applyConfiguration(Fork fork, Configuration configuration) {
229+
updateTimeOracle(fork, configuration);
230+
}
231+
232+
private void checkConfiguration(InitialConfiguration config) {
233+
String timeOracleName = config.getTimeOracleName();
234+
// Check the time oracle name is non-empty.
235+
// We do *not* check if the time oracle is active to (a) allow running this service with
236+
// reduced read functionality without time oracle; (b) testing time schema when it is not
237+
// active.
238+
checkArgument(!Strings.isNullOrEmpty(timeOracleName), "Empty time oracle name: %s",
239+
timeOracleName);
240+
}
241+
242+
private void updateTimeOracle(Fork fork, Configuration configuration) {
243+
QaSchema schema = createDataSchema(fork);
244+
InitialConfiguration config = configuration.getAsMessage(InitialConfiguration.class);
245+
246+
// Verify the configuration
247+
checkConfiguration(config);
248+
249+
// Save the configuration
250+
String timeOracleName = config.getTimeOracleName();
251+
schema.timeOracleName().set(timeOracleName);
252+
}
234253
}

exonum-java-binding/qa-service/src/test/java/com/exonum/binding/qaservice/QaServiceImplTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import static org.assertj.core.api.Assertions.assertThat;
3939
import static org.junit.jupiter.api.Assertions.assertThrows;
4040

41+
import com.exonum.binding.common.blockchain.ExecutionStatuses;
4142
import com.exonum.binding.common.crypto.KeyPair;
4243
import com.exonum.binding.common.crypto.PublicKey;
4344
import com.exonum.binding.common.hash.HashCode;
@@ -46,12 +47,16 @@
4647
import com.exonum.binding.core.storage.database.Snapshot;
4748
import com.exonum.binding.core.storage.indices.MapIndex;
4849
import com.exonum.binding.qaservice.Config.InitialConfiguration;
50+
import com.exonum.binding.qaservice.Service.ConfigChange;
51+
import com.exonum.binding.qaservice.Service.ConfigPropose;
52+
import com.exonum.binding.qaservice.Service.ServiceConfig;
4953
import com.exonum.binding.testkit.EmulatedNode;
5054
import com.exonum.binding.testkit.FakeTimeProvider;
5155
import com.exonum.binding.testkit.TestKit;
5256
import com.exonum.binding.testkit.TestKitExtension;
5357
import com.exonum.binding.testkit.TimeProvider;
5458
import com.exonum.core.messages.Blockchain.Config;
59+
import com.exonum.core.messages.Runtime.ExecutionStatus;
5560
import com.google.common.collect.ImmutableMap;
5661
import com.google.gson.reflect.TypeToken;
5762
import io.vertx.core.MultiMap;
@@ -137,6 +142,47 @@ void initializeBadConfig() {
137142
assertThrows(RuntimeException.class, testKitBuilder::build);
138143
}
139144

145+
@Test
146+
void reconfigureService(TestKit testKit) {
147+
KeyPair serviceKeyPair = testKit.getEmulatedNode().getServiceKeyPair();
148+
// Determine the config application height
149+
long nextHeight = 1 + testKit.applySnapshot(s -> Blockchain.newInstance(s)
150+
.getHeight());
151+
152+
// Propose the config change
153+
String newTimeOracleName = "mars-time";
154+
ConfigPropose propose = ConfigPropose.newBuilder()
155+
.setActualFrom(nextHeight)
156+
.addChanges(ConfigChange.newBuilder()
157+
.setService(ServiceConfig.newBuilder()
158+
.setInstanceId(QA_SERVICE_ID)
159+
.setParams(InitialConfiguration.newBuilder()
160+
.setTimeOracleName(newTimeOracleName)
161+
.build()
162+
.toByteString())
163+
.build())
164+
.build())
165+
.build();
166+
TransactionMessage proposeTx = TransactionMessage.builder()
167+
// fixme: constants
168+
.serviceId(0)
169+
.transactionId(2)
170+
.payload(propose.toByteArray())
171+
.sign(serviceKeyPair);
172+
testKit.createBlockWithTransactions(proposeTx);
173+
174+
// Check the proposal status
175+
Snapshot s = testKit.getSnapshot();
176+
Optional<ExecutionStatus> proposalStatus = Blockchain.newInstance(s)
177+
.getTxResult(proposeTx.hash());
178+
assertThat(proposalStatus).hasValue(ExecutionStatuses.success());
179+
180+
// Check the application status
181+
QaSchema qaSchema = new QaSchema(s, QA_SERVICE_NAME);
182+
Optional<String> actualTimeOracle = qaSchema.timeOracleName().toOptional();
183+
assertThat(actualTimeOracle).hasValue(newTimeOracleName);
184+
}
185+
140186
@Test
141187
void afterCommit(TestKit testKit) {
142188
// Create a block so that the transaction, submitted in after commit handler,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2019 The Exonum Team
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
package exonum.supervisor;
18+
19+
option java_package = "com.exonum.binding.qaservice";
20+
21+
import "types.proto";
22+
import "runtime.proto";
23+
import "blockchain.proto";
24+
25+
// Transactions
26+
27+
// Request for the artifact deployment.
28+
message DeployRequest {
29+
// Artifact identifier.
30+
exonum.runtime.ArtifactId artifact = 1;
31+
// Additional information for Runtime to deploy.
32+
bytes spec = 2;
33+
// The height until which the deployment procedure should be completed.
34+
uint64 deadline_height = 3;
35+
}
36+
37+
// Confirmation that the artifact was successfully deployed by the validator.
38+
message DeployConfirmation {
39+
// Artifact identifier.
40+
exonum.runtime.ArtifactId artifact = 1;
41+
}
42+
43+
// Request for the start service instance
44+
message StartService {
45+
// Artifact identifier.
46+
exonum.runtime.ArtifactId artifact = 1;
47+
// Instance name.
48+
string name = 2;
49+
// Instance configuration.
50+
bytes config = 3;
51+
}
52+
53+
// Configuration parameters of the certain service instance.
54+
message ServiceConfig {
55+
// Corresponding service instance ID.
56+
uint32 instance_id = 1;
57+
// Raw bytes representation of service configuration parameters.
58+
bytes params = 2;
59+
}
60+
61+
// This message contains one atomic configuration change.
62+
message ConfigChange {
63+
oneof message {
64+
// New consensus config.
65+
exonum.Config consensus = 1;
66+
// New service instance config.
67+
ServiceConfig service = 2;
68+
// New service instance start request.
69+
StartService start_service = 3;
70+
}
71+
}
72+
73+
// Request for the configuration change
74+
message ConfigPropose {
75+
// The height until which the update configuration procedure should be completed.
76+
uint64 actual_from = 1;
77+
// New configuration proposition.
78+
repeated ConfigChange changes = 2;
79+
// Configuration proposal number to avoid conflicting proposals.
80+
// For every proposal, this field should be equal to the amount of
81+
// valid processed proposals (no matter accepted or not).
82+
// Appropriate value for this field can be obtained via "configuration-number" API endpoint.
83+
uint64 configuration_number = 3;
84+
}
85+
86+
// Confirmation vote for the configuration change
87+
message ConfigVote {
88+
// Hash of configuration proposition.
89+
exonum.crypto.Hash propose_hash = 1;
90+
}

exonum-java-binding/testkit/src/main/java/com/exonum/binding/testkit/TestKit.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ private void checkTransaction(TransactionMessage transactionMessage) {
238238
// As transactions of time service might be submitted in TestKit that has this service
239239
// activated, those transactions should be considered valid, as time service is not
240240
// contained in Java runtime
241-
if (serviceId.equals(timeServiceId)) {
241+
if (serviceId.equals(timeServiceId) || serviceId == 0 /* fixme */) {
242242
return;
243243
}
244244
try {

0 commit comments

Comments
 (0)