Skip to content

Commit 0cc0782

Browse files
authored
feat(modules): babe (#405)
* support building different runtime templates * implement babe API * add separate integration tests for poa and pos * babe api * implement babe * rebase. remove duplicate tests. docs
1 parent c0a84d3 commit 0cc0782

File tree

170 files changed

+8317
-765
lines changed

Some content is hidden

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

170 files changed

+8317
-765
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@
1010
[submodule "polkadot-sdk"]
1111
path = polkadot-sdk
1212
url = git@github.com:LimeChain/polkadot-sdk.git
13+
[submodule "go-schnorrkel"]
14+
path = go-schnorrkel
15+
url = git@github.com:LimeChain/go-schnorrkel.git

Makefile

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
CURRENT_DIR = $(shell pwd)
2-
SRC_DIR = /src/examples/wasm/gosemble
2+
3+
# runtime template configuration
34
BUILD_PATH = build/runtime.wasm
4-
TARGET = polkawasm
5-
GC = extalloc # (extalloc, extalloc_leaking)
6-
VERSION = 0.31.0-dev
5+
RUNTIME_TEMPLATE_DIR = runtime/templates/poa
6+
7+
# docker image configuration
8+
SRC_DIR = /src/examples/wasm/gosemble
79
IMAGE = tinygo/${TARGET}
810

11+
# tinygo compiler configuration
12+
VERSION = 0.31.0-dev
13+
TARGET = polkawasm
14+
OPT_LEVEL = s # 0, 1, 2, s, z
15+
GC = extalloc # extalloc, extalloc_leaking
916
WASMOPT_PATH = /tinygo/lib/binaryen/bin/wasm-opt
1017

18+
# runtime build commands
1119
DOCKER_BUILD_TINYGO = docker build --tag $(IMAGE):$(VERSION)-$(GC) -f tinygo/Dockerfile.$(TARGET) tinygo
1220
DOCKER_RUN_TINYGO = docker run --rm -v $(CURRENT_DIR):$(SRC_DIR) -w $(SRC_DIR) $(IMAGE):$(VERSION)-$(GC) /bin/bash -c
1321

14-
TINYGO_BUILD_COMMAND_NODEBUG = tinygo build --no-debug -opt s -gc=$(GC) -target=$(TARGET)
15-
TINYGO_BUILD_COMMAND = tinygo build -gc=$(GC) -opt s -target=$(TARGET)
22+
TINYGO_BUILD_COMMAND_NODEBUG = tinygo build --no-debug -opt=$(OPT_LEVEL) -gc=$(GC) -target=$(TARGET)
23+
TINYGO_BUILD_COMMAND = tinygo build -opt=$(OPT_LEVEL) -gc=$(GC) -target=$(TARGET)
1624

1725
RUNTIME_BUILD_NODEBUG = "WASMOPT="$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND_NODEBUG) -o=$(SRC_DIR)/$(BUILD_PATH) $(SRC_DIR)/runtime/"
1826
RUNTIME_BUILD = "WASMOPT="$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND) -o=$(SRC_DIR)/$(BUILD_PATH) $(SRC_DIR)/runtime/"
@@ -78,38 +86,31 @@ build-tinygo:
7886

7987
build-release: build-tinygo
8088
@echo "Building \"runtime.wasm\" (no-debug)"; \
81-
WASMOPT="$(CURRENT_DIR)/$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND_NODEBUG) -o=$(BUILD_PATH) runtime/runtime.go
89+
WASMOPT="$(CURRENT_DIR)/$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND_NODEBUG) -o=$(BUILD_PATH) $(RUNTIME_TEMPLATE_DIR)/runtime.go
8290

8391
build-dev: build-tinygo
8492
@echo "Building \"runtime.wasm\""; \
85-
WASMOPT="$(CURRENT_DIR)/$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND) -o=$(BUILD_PATH) runtime/runtime.go
93+
WASMOPT="$(CURRENT_DIR)/$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND) -o=$(BUILD_PATH) $(RUNTIME_TEMPLATE_DIR)/runtime.go
8694

8795
build-benchmarking: build-tinygo
8896
@echo "Building \"runtime.wasm\" (no-debug)"; \
89-
WASMOPT="$(CURRENT_DIR)/$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND_NODEBUG) -tags benchmarking -o=$(BUILD_PATH) runtime/runtime.go
97+
WASMOPT="$(CURRENT_DIR)/$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND_NODEBUG) -tags benchmarking -o=$(BUILD_PATH) $(RUNTIME_TEMPLATE_DIR)/runtime.go
9098

91-
start-network:
92-
cp build/runtime.wasm polkadot-sdk/substrate/bin/node-template/runtime.wasm; \
93-
cd polkadot-sdk/substrate/bin/node-template/node; \
94-
cargo build --release; \
95-
cd ../../../..; \
96-
WASMTIME_BACKTRACE_DETAILS=1 RUST_LOG=runtime=trace ./target/release/node-template --dev --execution=wasm
99+
test-coverage:
100+
@set -e; \
101+
./scripts/coverage.sh
97102

98103
test: test-unit test-integration
99104

100105
test-unit:
101106
@go test --tags "nonwasmenv" -cover -v `go list ./... | grep -v runtime`
102107

103108
test-integration:
104-
@go test --tags="nonwasmenv" -v ./runtime/...
109+
@go test --tags="nonwasmenv" -v -count=1 ./$(RUNTIME_TEMPLATE_DIR)/...
105110

106-
test-coverage:
107-
@set -e; \
108-
./scripts/coverage.sh
109-
110-
GENERATE_WEIGHT_FILES=true
111+
GENERATE_WEIGHT_FILES = true
111112
benchmark: build-benchmarking
112-
@go test --tags="nonwasmenv" -bench=. ./runtime/... -run=XXX -benchtime=1x \
113+
@go test --tags="nonwasmenv" -bench=. ./$(RUNTIME_TEMPLATE_DIR)/... -run=XXX -benchtime=1x \
113114
-steps=50 \
114115
-repeat=20 \
115116
-heap-pages=4096 \
@@ -124,4 +125,52 @@ benchmark-overhead: build-benchmarking
124125
-gc=$(GC) \
125126
-target=$(TARGET) \
126127
-tinygoversion=$(VERSION) \
127-
-generate-weight-files=$(GENERATE_WEIGHT_FILES)
128+
-generate-weight-files=$(GENERATE_WEIGHT_FILES)
129+
130+
# substrate node configuration
131+
start-network:
132+
cp $(BUILD_PATH) polkadot-sdk/substrate/bin/node-template/runtime.wasm; \
133+
cd polkadot-sdk/substrate/bin/node-template/node; \
134+
cargo build --release; \
135+
cd ../../../..; \
136+
WASMTIME_BACKTRACE_DETAILS=1 RUST_LOG=runtime=trace ./target/release/node-template --dev --execution=wasm
137+
138+
start-network-babe:
139+
cd polkadot-sdk/substrate/bin/node/cli; \
140+
cargo build --release; \
141+
cd ../../../..; \
142+
WASMTIME_BACKTRACE_DETAILS=1 RUST_LOG=runtime=trace ./target/release/substrate-node --dev --execution=wasm
143+
144+
# gossamer node configuration
145+
CHAIN_SPEC_PLAIN = ../testdata/chain-spec/plain.json
146+
CHAIN_SPEC_UPDATED = ../testdata/chain-spec/plain-updated.json
147+
CHAIN_SPEC_RAW = ../testdata/chain-spec/raw-updated.json
148+
GOSSAMER_BASE_PATH = tmp/gossamer
149+
150+
gossamer-build:
151+
@cd gossamer; \
152+
make build;
153+
154+
gossamer-import-runtime:
155+
@cd gossamer; \
156+
rm -f $(CHAIN_SPEC_UPDATED); \
157+
./bin/gossamer import-runtime --wasm-file ../$(BUILD_PATH) --chain $(CHAIN_SPEC_PLAIN) > $(CHAIN_SPEC_UPDATED); \
158+
rm -f $(CHAIN_SPEC_RAW); \
159+
./bin/gossamer build-spec --chain $(CHAIN_SPEC_UPDATED) --raw --output-path $(CHAIN_SPEC_RAW)
160+
161+
gossamer-init:
162+
@cd gossamer; \
163+
rm -rf $(GOSSAMER_BASE_PATH); \
164+
./bin/gossamer init --force \
165+
--base-path $(GOSSAMER_BASE_PATH) \
166+
--chain $(CHAIN_SPEC_RAW) \
167+
--key alice;
168+
169+
gossamer-start: gossamer-build gossamer-import-runtime gossamer-init
170+
@cd gossamer; \
171+
./bin/gossamer \
172+
--base-path $(GOSSAMER_BASE_PATH) \
173+
--rpc-external \
174+
--ws-external \
175+
--ws-port 8546 \
176+
--key alice;

api/babe/module.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package babe
2+
3+
import (
4+
"reflect"
5+
6+
sc "github.com/LimeChain/goscale"
7+
"github.com/LimeChain/gosemble/constants/metadata"
8+
"github.com/LimeChain/gosemble/frame/babe"
9+
babetypes "github.com/LimeChain/gosemble/primitives/babe"
10+
"github.com/LimeChain/gosemble/primitives/hashing"
11+
"github.com/LimeChain/gosemble/primitives/log"
12+
primitives "github.com/LimeChain/gosemble/primitives/types"
13+
"github.com/LimeChain/gosemble/utils"
14+
)
15+
16+
const (
17+
ApiModuleName = "BabeApi"
18+
apiVersion = 2
19+
)
20+
21+
// Module implements the BabeApi Runtime API definition, necessary for block authorship.
22+
type Module struct {
23+
babe babe.Module
24+
memUtils utils.WasmMemoryTranslator
25+
logger log.Logger
26+
}
27+
28+
func New(babe babe.Module, logger log.Logger) Module {
29+
return Module{
30+
babe: babe,
31+
memUtils: utils.NewMemoryTranslator(),
32+
logger: logger,
33+
}
34+
}
35+
36+
// Name returns the name of the api module.
37+
func (m Module) Name() string {
38+
return ApiModuleName
39+
}
40+
41+
// Item returns the first 8 bytes of the Blake2b hash of the name and version of the api module.
42+
func (m Module) Item() primitives.ApiItem {
43+
hash := hashing.MustBlake2b8([]byte(ApiModuleName))
44+
return primitives.NewApiItem(hash, apiVersion)
45+
}
46+
47+
// Metadata returns the runtime api metadata of the module.
48+
func (m Module) Metadata() primitives.RuntimeApiMetadata {
49+
methods := sc.Sequence[primitives.RuntimeApiMethodMetadata]{
50+
primitives.RuntimeApiMethodMetadata{
51+
Name: "configuration",
52+
Inputs: sc.Sequence[primitives.RuntimeApiMethodParamMetadata]{},
53+
Output: sc.ToCompact(metadata.TypesBabeConfiguration),
54+
Docs: sc.Sequence[sc.Str]{""},
55+
},
56+
primitives.RuntimeApiMethodMetadata{
57+
Name: "current_epoch_start",
58+
Inputs: sc.Sequence[primitives.RuntimeApiMethodParamMetadata]{},
59+
Output: sc.ToCompact(metadata.PrimitiveTypesU64),
60+
Docs: sc.Sequence[sc.Str]{""},
61+
},
62+
primitives.RuntimeApiMethodMetadata{
63+
Name: "current_epoch",
64+
Inputs: sc.Sequence[primitives.RuntimeApiMethodParamMetadata]{},
65+
Output: sc.ToCompact(metadata.TypesBabeEpoch),
66+
Docs: sc.Sequence[sc.Str]{""},
67+
},
68+
primitives.RuntimeApiMethodMetadata{
69+
Name: "next_epoch",
70+
Inputs: sc.Sequence[primitives.RuntimeApiMethodParamMetadata]{},
71+
Output: sc.ToCompact(metadata.TypesBabeEpoch),
72+
Docs: sc.Sequence[sc.Str]{""},
73+
},
74+
// TODO: add metadata for GenerateKeyOwnershipProof and SubmitReportEquivocationUnsignedExtrinsic
75+
}
76+
77+
return primitives.RuntimeApiMetadata{
78+
Name: ApiModuleName,
79+
Methods: methods,
80+
Docs: sc.Sequence[sc.Str]{"Babe consensus API module."},
81+
}
82+
}
83+
84+
// Returns configuration data used by the Babe consensus engine.
85+
func (m Module) Configuration() int64 {
86+
epochConfig, err := m.babe.StorageEpochConfig()
87+
if err != nil {
88+
m.logger.Critical(err.Error())
89+
}
90+
91+
if reflect.DeepEqual(epochConfig, babetypes.EpochConfiguration{}) {
92+
epochConfig = m.babe.EpochConfig()
93+
}
94+
95+
authorities, err := m.babe.StorageAuthorities()
96+
if err != nil {
97+
m.logger.Critical(err.Error())
98+
}
99+
100+
randomness, err := m.babe.StorageRandomness()
101+
if err != nil {
102+
m.logger.Critical(err.Error())
103+
}
104+
105+
config := babetypes.Configuration{
106+
// The slot duration in milliseconds. Currently, only the value provided by this
107+
// type at genesis will be used. Dynamic slot duration may be supported in the future.
108+
SlotDuration: m.babe.SlotDuration(),
109+
// The duration of epochs in slots.
110+
EpochLength: m.babe.EpochDuration(),
111+
// A constant value that is used in the threshold calculation formula as defined in
112+
// https://spec.polkadot.network/sect-block-production#defn-babe-constant
113+
C: epochConfig.C,
114+
// The authority list for the genesis epoch as defined in
115+
// https://spec.polkadot.network/chap-sync#defn-authority-list
116+
Authorities: authorities,
117+
// The randomness for the genesis epoch.
118+
Randomness: randomness,
119+
// Whether this chain should run with a round-robin-style secondary slot and
120+
// if this secondary slot requires the inclusion of an auxiliary VRF output.
121+
AllowedSlots: epochConfig.AllowedSlots,
122+
}
123+
124+
return m.memUtils.BytesToOffsetAndSize(config.Bytes())
125+
}
126+
127+
// Returns the start slot of the current epoch.
128+
func (m Module) CurrentEpochStart() int64 {
129+
epochStart, err := m.babe.CurrentEpochStart()
130+
if err != nil {
131+
m.logger.Critical(err.Error())
132+
}
133+
134+
return m.memUtils.BytesToOffsetAndSize(epochStart.Bytes())
135+
}
136+
137+
// Returns information regarding the current epoch.
138+
func (m Module) CurrentEpoch() int64 {
139+
epoch, err := m.babe.CurrentEpoch()
140+
if err != nil {
141+
m.logger.Critical(err.Error())
142+
}
143+
144+
return m.memUtils.BytesToOffsetAndSize(epoch.Bytes())
145+
}
146+
147+
// Returns information regarding the next epoch (which was already
148+
// previously announced).
149+
func (m Module) NextEpoch() int64 {
150+
epoch, err := m.babe.NextEpoch()
151+
if err != nil {
152+
m.logger.Critical(err.Error())
153+
}
154+
155+
return m.memUtils.BytesToOffsetAndSize(epoch.Bytes())
156+
}
157+
158+
// Generates a proof of key ownership for the given authority in the
159+
// current epoch. An example usage of this module is coupled with the
160+
// session historical module to prove that a given authority key is
161+
// tied to a given staking identity during a specific session. Proofs
162+
// of key ownership are necessary for submitting equivocation reports.
163+
// NOTE: even though the API takes a `slot` as parameter the current
164+
// implementations ignores this parameter and instead relies on this
165+
// method being called at the correct block height, i.e. any point at
166+
// which the epoch for the given slot is live on-chain. Future
167+
// implementations will instead use indexed data through an offchain
168+
// worker, not requiring older states to be available.
169+
func (m Module) GenerateKeyOwnershipProof(dataPtr int32, dataLen int32) int64 {
170+
// TODO: Implement
171+
172+
m.logger.Critical("GenerateKeyOwnershipProof is not implemented")
173+
174+
return m.memUtils.BytesToOffsetAndSize(sc.NewOption[sc.Empty](nil).Bytes())
175+
}
176+
177+
// Submits an unsigned extrinsic to report an equivocation. The caller
178+
// must provide the equivocation proof and a key ownership proof
179+
// (should be obtained using `generate_key_ownership_proof`). The
180+
// extrinsic will be unsigned and should only be accepted for local
181+
// authorship (not to be broadcast to the network). This method returns
182+
// `None` when creation of the extrinsic fails, e.g. if equivocation
183+
// reporting is disabled for the given runtime (i.e. this method is
184+
// hardcoded to return `None`). Only useful in an offchain context.
185+
func (m Module) SubmitReportEquivocationUnsignedExtrinsic(dataPtr int32, dataLen int32) int64 {
186+
// TODO: Implement
187+
188+
m.logger.Critical("SubmitReportEquivocationUnsignedExtrinsic is not implemented")
189+
190+
return m.memUtils.BytesToOffsetAndSize(sc.NewOption[sc.Empty](nil).Bytes())
191+
}

0 commit comments

Comments
 (0)