2023/03/31

Concordiumノードをローカルで動かしてみた

concordiumrusthaskell

概要

Concordiumというブロックチェーンの調査をかねて、Concordiumのノードをローカルで動かすところまでやってみました。
今回はその手順の備忘録になります。

(Versionは5.2.4以降のmain branchを引っ張ってきています。)

Concordiumについて

Concordiumは個人IDの取り扱いに関する実装がブロックチェーンレイヤーに組み込まれていることが特徴的です。
例えば、特定の国には居住していて、20歳以上の人みたいな情報をサードパーティであるDAPPが(比較的)簡単に利用できるようになっています。

また、個人IDの情報(年齢・氏名・性別・居住国等)はブロックチェーン上に平文で保存されるわけではなく、
あくまでも、個人情報取扱業者(ID Providerというみたいです。)が保持していて
ブロックチェーンでID情報を扱う際は、ゼロ知識証明という技術をうまく使って「Aさんのアカウントは20歳以上のXX国以外の居住者である」みたいな証明をDappがID Providerに求めて、
承認されると、処理が続行されるような感じのフローみたいでした。
・・ちょっとこの辺りは具体的にどういうふうに実装されているか不明なので、ドキュメントやソースコード等みてみたいと思っています。

事前準備

以下のリポジトリをすべてforkしました。

(「私はいくつかソースコードをいじるかも」という想定があったので、forkしましたが、ただ動かすだけならforkする必要はありません。)

concordium-nodeのチェックアウト

  • concordium-nodeをチェックアウトしてきて、git submoduleを更新します。
% git clone [email protected]:kumanote/concordium-node.git
% cd concordium-node
% git submodule update --init --recursive
% git remote add upstream [email protected]:Concordium/concordium-node.git

build dependencieseの準備

llvm@12をインストールしないとビルド時にエラーが出ました。(最初 brew install llvmでインストールした際はビルド時にエラーが出てビルドが完了しませんでした。)

# install rustup
% curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
% rustup install stable
% rustup default stable
% rustc --version
rustc 1.68.2 (9eb3afe9e 2023-03-27)

# install binutils provided by xcode
% xcode-select --install

# install flat buffers
# 以下は真似しないでください。本来ならば、v22.12.06のバイナリーをダウンロードしてきて、パスを通す必要があります。
# 今回はローカルノードで他のノードと協調する必要がないため、最新のものをインストールしました。
# これをやると、serialize/deserializeで他のノードとは違う構造のデータができてしまう可能性があり、hash値が異なってきてしまい。思わぬトラブル?がある可能性がありそうです。
% brew install flatbuffers
% flatc --version
# flatc version 23.3.3

# install protocol buffers
% brew install protobuf
% protoc --version
libprotoc 3.21.12

# install llvm & clang
# the latest llvm did not work(build failed) `brew install llvm`
% brew install llvm@12
## define env vars to use the installed llvm (I defined these variables by using `direnv`.)
% export PATH="/usr/local/opt/llvm@12/bin:$PATH"
% export LDFLAGS="-L/usr/local/opt/llvm@12/lib"
% export CPPFLAGS="-I/usr/local/opt/llvm@12/include"

% clang -v
Homebrew clang version 12.0.1
Target: x86_64-apple-darwin22.4.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm@12/bin

# install haskell stack
% brew install haskell-stack
% stack upgrade --force-download
% stack --version               
Version 2.9.3, Git revision 6cf638947a863f49857f9cfbf72a38a48b183e7e x86_64 hpack-0.35.1

# install lmdb
% brew install lmdb

build concordium-node

以下の順にビルドしました。

  • concordium-consensus
  • concordium-node
# build consensus libraries first
% stack --stack-yaml ./concordium-consensus/stack.yaml build
# then build node
% cd concordium-node
% cargo build

prepare genesis data

ノードを動かす前に、genesis dataというチェーンの初回台帳的なものを作成する必要があります。

concordium-misc-toolsというツールがありますので、そちらをクローンして使いました。

注意点としては

baker accountを1つ以上は作成しておかないと、後でノードを動かした時にブロックが生成されなくて悲しいことになります。

# clone concordium-misc-tools
% git clone [email protected]:kumanote/concordium-misc-tools.git
% cd concordium-misc-tools
% git submodule update --init --recursive
% git remote add upstream [email protected]:Concordium/concordium-misc-tools.git
# build genesis-creator
% cd genesis-creator
% cargo build
# prepare genesis-config.toml
% cat <<'EOF' > examples/genesis-kumanote-local-single.toml
# Protocol P5 is a minor upgrade that adds support for smart contract upgradability, smart contract queries, relaxes some limitations and improves the structure of internal node datastructures related to accounts.
protocolVersion = 5

[out]
updateKeys = "./update-keys"
accountKeys = "./accounts"
bakerKeys = "./bakers"
identityProviders = "./idps"
anonymityRevokers = "./ars"
genesis = "./genesis.dat"
cryptographicParameters = "./global"
deleteExisting = true
genesisHash = "./genesis_hash"

[cryptographicParameters]
kind = "generate"
# A free-form string used to distinguish between different chains even if they share other parameters.
genesisString = "kumanote local single"

[[anonymityRevokers]]
kind = "fresh"
id = 1
repeat = 3

[[identityProviders]]
kind = "fresh"
id = 0
repeat = 3

[[accounts]]
kind = "fresh"
balance = "1000000000000000"
stake = "500000000000000"
template = "baker"
identityProvider = 0
numKeys = 1
threshold = 1
repeat = 5

[[accounts]]
kind = "fresh"
balance = "1000000000000000"
template = "foundation"
identityProvider = 0
numKeys = 1
threshold = 1
repeat = 1
foundation = true

[updates]
# 7 freshly generated root keys with threshold 5
root = { threshold = 5, keys = [{kind = "fresh", repeat = 7}]}
# 15 freshly generated level1 keys with threshold 7
level1 = { threshold = 7, keys = [{kind = "fresh", repeat = 15}]}
# 15 freshly generated level2 keys. All of level 2 chain updates can be done by the first 7 of the level 2 keys
[updates.level2]
keys = [{kind = "fresh", repeat = 15}]
emergency = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
protocol = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
electionDifficulty = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
euroPerEnergy = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
microCCDPerEuro = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
foundationAccount = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
mintDistribution = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
transactionFeeDistribution = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
gasRewards = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
poolParameters = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
addAnonymityRevoker = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
addIdentityProvider = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
cooldownParameters = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}
timeParameters = {authorizedKeys = [0,1,2,3,4,5,6], threshold = 7}

[parameters]
# comment out genesisTime to use current time for genesis time.
# genesisTime = "YYYY-MM-DDTHH:mm:SSZ"
slotDuration = 250 # in ms
leadershipElectionNonce = "d1bc8d3ba4afc7e109612cb73acbdddac052c93025aa1f82942edabb7deb82a1"
epochLength = 400 # in slots, so 100s
maxBlockEnergy = 3_000_000

[parameters.finalization]
minimumSkip = 0
committeeMaxSize = 1000
waitingTime = 100
skipShrinkFactor = 0.5
skipGrowFactor = 2
delayShrinkFactor = 0.5
delayGrowFactor = 2
allowZeroDelay = true

[parameters.chain]
version = "v1"
electionDifficulty = 0.05
euroPerEnergy = 0.00002
microCCDPerEuro = 500_000
accountCreationLimit = 10
[parameters.chain.timeParameters]
rewardPeriodLength = 4 # 4 epochs
mintPerPayday = 2.61157877e-4
[parameters.chain.poolParameters]
passiveFinalizationCommission = 1.0
passiveBakingCommission = 0.12
passiveTransactionCommission = 0.12
finalizationCommissionRange = {max = 1.0,min = 1.0}
bakingCommissionRange = {max = 0.1,min = 0.1}
transactionCommissionRange = {max = 0.1,min = 0.1}
minimumEquityCapital = "1000"
capitalBound = 0.1
leverageBound = {denominator = 1, numerator = 3}
[parameters.chain.cooldownParameters]
poolOwnerCooldown = 800 # in seconds
delegatorCooldown = 1000 # in seconds
[parameters.chain.rewardParameters]
mintDistribution = { bakingReward = 0.85, finalizationReward = 0.05 }
transactionFeeDistribution = { baker = 0.45, gasAccount = 0.45 }
gASRewards = { baker = 0.25, finalizationProof = 0.005, accountCreation = 0.02, chainUpdate = 0.005 }
EOF

% ./target/debug/genesis-creator generate --config examples/genesis-kumanote-local-single.toml 

run single local node

準備が整ったので、ノードを実行します。以下のコマンドで実行しました。

# change directory into concordium-node directory
% cd concordium-node
% mkdir -p workdir/node-0
% cd concordium-node
% cargo run --release -- \
   --genesis-data-file /path/to/concordium-misc-tools/genesis-creator/genesis.dat \
   --no-bootstrap= \
   --listen-port 8000 \
   --rpc-server-port 10000 \
   --grpc2-listen-addr 127.0.0.1 \
   --grpc2-listen-port 11000 \
   --data-dir /path/to/concordium-node/workdir/node-0 \
   --config-dir /path/to/concordium-node/workdir/node-0 \
   --baker-credentials-file /path/to/concordium-misc-tools/genesis-creator/bakers/baker-0-credentials.json \
   --debug=

check some data via GRPC v2

最後に、ローカルノードに対してRPC/GRPC V2を使って通信し、ちゃんと動作するか確認しました。

ちょうど、concordium-rust-sdk/examples ディレクトリにいくつか良さそうなものがあったので、そちらを動かしてみました。
以下はその結果になります。

% cd genesis-creato/deps/concordium-rust-sdk
% cargo run --package concordium-rust-sdk --example find-account -- --node=http://localhost:10000 --account=3jVx39ZSrxFTXBeZaJ9ECNUoeyHSkobtAkh6FGEhL3dU3HpEpf
Processing block at height 0.
Account created in block a67131c1fd4561792150721ae4b8244d510a0a05c01791b384d33d1f30cb21a3.
Timestamp of this block 2023-03-31 06:19:56.416 UTC.

% cargo run --package concordium-rust-sdk --example v2_get_block_info -- --node=http://localhost:11000
Best block QueryResponse {
    block_hash: e33fcd72,
    response: BlockInfo {
        transactions_size: 0,
        block_parent: cd9221f5,
        block_hash: e33fcd72,
        finalized: false,
        block_state_hash: ec583795,
        block_arrive_time: 2023-03-31T06:33:50.918Z,
        block_receive_time: 2023-03-31T06:33:50.918Z,
        transaction_count: 0,
        transaction_energy_cost: Energy {
            energy: 0,
        },
        block_slot: Slot {
            slot: 3338,
        },
        block_last_finalized: a67131c1,
        block_slot_time: 2023-03-31T06:33:50.916Z,
        block_height: AbsoluteBlockHeight {
            height: 3,
        },
        era_block_height: BlockHeight {
            height: 3,
        },
        genesis_index: GenesisIndex {
            height: 0,
        },
        block_baker: Some(
            BakerId {
                id: AccountIndex {
                    index: 0,
                },
            },
        ),
    },
}
Last finalized QueryResponse {
    block_hash: a67131c1,
    response: BlockInfo {
        transactions_size: 0,
        block_parent: a67131c1,
        block_hash: a67131c1,
        finalized: true,
        block_state_hash: 00000000,
        block_arrive_time: 2023-03-31T06:19:56.416Z,
        block_receive_time: 2023-03-31T06:19:56.416Z,
        transaction_count: 0,
        transaction_energy_cost: Energy {
            energy: 0,
        },
        block_slot: Slot {
            slot: 0,
        },
        block_last_finalized: a67131c1,
        block_slot_time: 2023-03-31T06:19:56.416Z,
        block_height: AbsoluteBlockHeight {
            height: 0,
        },
        era_block_height: BlockHeight {
            height: 0,
        },
        genesis_index: GenesisIndex {
            height: 0,
        },
        block_baker: None,
    },
}

ちゃんと動きました!よかったです。

参考記事

以下参考にさせていただきました。

Noise Protocol

感想

ConcordiumはほとんどRustで書かれているのですが、consensus layerだけはhaskellで作られており、それをRustから呼べるようにして動作していました。
Haskellに関してはビルド時の環境によって、(特にmacやwindowsだと)エラーになったりするようで・・ビルドが通るまでがかなり苦労しました。

Haskellはかなり苦労しそうだなーと思うものの、concordiumと連携して開発する場合には、インターフェースは基本的にRust(かTypescriptやPython等の超メジャー言語)になるようなので、その辺りは一安心です。
ただソースコードを読む際はHaskellの知識が必須になりそうなので、苦労しそうです。(なぜRustに統一してくれなかったのでしょうか。。。)

以上です。