Skip to content

Commit 942bf3c

Browse files
authored
Merge pull request #13 from strategyobject/feature/pallet-query-api
Annotations for pallets and their storages
2 parents e47b33e + 8b5fe92 commit 942bf3c

73 files changed

Lines changed: 1505 additions & 118 deletions

File tree

Some content is hidden

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

api/build.gradle

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
11
dependencies {
22
implementation project(':rpc')
3+
implementation project(':rpc:rpc-core')
4+
implementation project(':rpc:rpc-sections')
5+
implementation project(':transport')
6+
implementation project(':pallet')
7+
implementation project(':scale')
8+
implementation project(':types')
9+
implementation project(':rpc:rpc-types')
10+
implementation project(':storage')
11+
12+
testImplementation project(':tests')
13+
14+
testImplementation 'org.testcontainers:testcontainers:1.16.3'
15+
testImplementation 'org.testcontainers:junit-jupiter:1.16.3'
16+
17+
testAnnotationProcessor project(':pallet:pallet-codegen')
318
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,33 @@
11
package com.strategyobject.substrateclient.api;
22

3+
import com.strategyobject.substrateclient.pallet.GeneratedPalletResolver;
34
import com.strategyobject.substrateclient.rpc.Rpc;
5+
import com.strategyobject.substrateclient.rpc.RpcImpl;
6+
import com.strategyobject.substrateclient.transport.ProviderInterface;
7+
import lombok.val;
48

9+
/**
10+
* Provides the ability to query a node and interact with the Polkadot or Substrate chains.
11+
* It allows interacting with blockchain in various ways: using RPC's queries directly or
12+
* accessing Pallets and its APIs, such as storages, transactions, etc.
13+
*/
514
public interface Api {
15+
static DefaultApi with(ProviderInterface provider) {
16+
val rpc = RpcImpl.with(provider);
17+
18+
return DefaultApi.with(rpc, GeneratedPalletResolver.with(rpc));
19+
}
20+
21+
/**
22+
* @return the instance that provides a proper API for querying the RPC's methods.
23+
*/
624
Rpc rpc();
25+
26+
/**
27+
* Resolves the instance of a pallet by its definition.
28+
* @param clazz the class of the pallet
29+
* @param <T> the type of the pallet
30+
* @return appropriate instance of the pallet
31+
*/
32+
<T> T pallet(Class<T> clazz);
733
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.strategyobject.substrateclient.pallet.PalletResolver;
4+
import com.strategyobject.substrateclient.rpc.Rpc;
5+
import lombok.NonNull;
6+
import lombok.RequiredArgsConstructor;
7+
8+
import java.util.Map;
9+
import java.util.concurrent.ConcurrentHashMap;
10+
11+
@RequiredArgsConstructor(staticName = "with")
12+
public class DefaultApi implements Api, AutoCloseable {
13+
private final @NonNull Rpc rpc;
14+
private final @NonNull PalletResolver palletResolver;
15+
private final Map<Class<?>, Object> palletCache = new ConcurrentHashMap<>();
16+
17+
@Override
18+
public Rpc rpc() {
19+
return rpc;
20+
}
21+
22+
@Override
23+
public <T> T pallet(@NonNull Class<T> clazz) {
24+
return clazz.cast(palletCache
25+
.computeIfAbsent(clazz, palletResolver::resolve));
26+
}
27+
28+
@Override
29+
public void close() throws Exception {
30+
if (rpc instanceof AutoCloseable) {
31+
((AutoCloseable) rpc).close();
32+
}
33+
}
34+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.strategyobject.substrateclient.tests.containers.SubstrateVersion;
4+
import com.strategyobject.substrateclient.tests.containers.TestSubstrateContainer;
5+
import com.strategyobject.substrateclient.transport.ws.WsProvider;
6+
import lombok.val;
7+
import org.junit.jupiter.api.Test;
8+
import org.testcontainers.junit.jupiter.Container;
9+
import org.testcontainers.junit.jupiter.Testcontainers;
10+
11+
import java.math.BigInteger;
12+
import java.util.concurrent.TimeUnit;
13+
14+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
15+
import static org.junit.jupiter.api.Assertions.assertNotNull;
16+
17+
@Testcontainers
18+
public class ApiTests {
19+
private static final int WAIT_TIMEOUT = 1000;
20+
21+
@Container
22+
private final TestSubstrateContainer substrate = new TestSubstrateContainer(SubstrateVersion.V3_0_0);
23+
24+
@Test
25+
public void getSystemPalletAndCall() throws Exception { // TODO move the test out of the project
26+
val wsProvider = WsProvider.builder()
27+
.setEndpoint(substrate.getWsAddress())
28+
.build();
29+
wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS);
30+
31+
try (val api = Api.with(wsProvider)) {
32+
val systemPallet = api.pallet(SystemPallet.class);
33+
val blockHash = systemPallet
34+
.blockHash()
35+
.get(0)
36+
.get(WAIT_TIMEOUT, TimeUnit.SECONDS);
37+
38+
assertNotNull(blockHash);
39+
assertNotEquals(BigInteger.ZERO, new BigInteger(blockHash.getData()));
40+
}
41+
}
42+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.strategyobject.substrateclient.api;
2+
3+
import com.strategyobject.substrateclient.pallet.annotations.Pallet;
4+
import com.strategyobject.substrateclient.pallet.annotations.Storage;
5+
import com.strategyobject.substrateclient.pallet.annotations.StorageHasher;
6+
import com.strategyobject.substrateclient.pallet.annotations.StorageKey;
7+
import com.strategyobject.substrateclient.rpc.types.BlockHash;
8+
import com.strategyobject.substrateclient.scale.annotations.Scale;
9+
import com.strategyobject.substrateclient.storage.StorageNMap;
10+
11+
@Pallet("System")
12+
public interface SystemPallet {
13+
@Storage(
14+
value = "BlockHash",
15+
keys = {
16+
@StorageKey(
17+
type = @Scale(Integer.class),
18+
hasher = StorageHasher.TwoX64Concat
19+
)
20+
})
21+
StorageNMap<BlockHash> blockHash();
22+
}

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55

66
allprojects {
77
group = 'com.strategyobject.substrateclient'
8-
version = '0.0.4-SNAPSHOT'
8+
version = '0.1.0-SNAPSHOT'
99

1010
repositories {
1111
mavenLocal()

common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@ public String getPackageName(@NonNull TypeElement classElement) {
2626
return elementUtils.getPackageOf(classElement).getQualifiedName().toString();
2727
}
2828

29-
public boolean isSubtypeOf(@NonNull TypeMirror candidate, @NonNull TypeMirror supertype) {
29+
public boolean isAssignable(@NonNull TypeMirror candidate, @NonNull TypeMirror supertype) {
3030
return typeUtils.isAssignable(candidate, supertype);
3131
}
32+
33+
public boolean isSubtype(@NonNull TypeMirror candidate, @NonNull TypeMirror supertype) {
34+
return typeUtils.isSubtype(candidate, supertype);
35+
}
3236

3337
public boolean isGeneric(@NonNull TypeMirror type) {
3438
return ((TypeElement) typeUtils.asElement(type))

common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeTraverser.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,29 @@ public T traverse(@NonNull TypeMirror type, @NonNull TypeTraverser.TypeTreeNode
101101
.toArray(x -> (T[]) Array.newInstance(clazz, typeArguments.size())));
102102
}
103103

104+
@SuppressWarnings({"unchecked"})
105+
public T traverse(@NonNull TypeTraverser.TypeTreeNode typeOverride) {
106+
if (typeOverride.type.getKind().isPrimitive()) {
107+
return whenPrimitiveType((PrimitiveType) typeOverride.type, typeOverride.type);
108+
}
109+
110+
if (!(typeOverride.type instanceof DeclaredType)) {
111+
throw new IllegalArgumentException("Type is not supported: " + typeOverride.type);
112+
}
113+
114+
val declaredType = (DeclaredType) typeOverride.type;
115+
if (typeOverride.children.size() == 0) {
116+
return whenNonGenericType(declaredType, typeOverride.type);
117+
}
118+
119+
return whenGenericType(
120+
declaredType,
121+
typeOverride.type,
122+
typeOverride.children.stream()
123+
.map(this::traverse)
124+
.toArray(x -> (T[]) Array.newInstance(clazz, typeOverride.children.size())));
125+
}
126+
104127
private List<? extends TypeMirror> getTypeArgumentsOrDefault(DeclaredType declaredType, TypeMirror override) {
105128
return (doTraverseArguments(declaredType, override) ?
106129
declaredType.getTypeArguments() :
@@ -125,6 +148,7 @@ private boolean typeIsOverriddenByNonGeneric(int typeOverrideSize) {
125148

126149

127150
public static class TypeTreeNode {
151+
@Getter
128152
private final TypeMirror type;
129153
private final List<TypeTreeNode> children;
130154

pallet/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dependencies {
2+
implementation project(':scale')
3+
implementation project(':rpc')
4+
}

pallet/pallet-codegen/build.gradle

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
dependencies {
2+
implementation project(':common')
3+
implementation project(':rpc')
4+
implementation project(':types')
5+
implementation project(':scale')
6+
implementation project(':scale:scale-codegen')
7+
implementation project(':storage')
8+
implementation project(':pallet')
9+
10+
implementation 'com.squareup:javapoet:1.13.0'
11+
12+
compileOnly 'com.google.auto.service:auto-service-annotations:1.0.1'
13+
annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
14+
15+
testImplementation 'com.google.testing.compile:compile-testing:0.19'
16+
}

0 commit comments

Comments
 (0)