Skip to content

Libplanet 실행 방법

Chanhyuck Ko edited this page May 26, 2025 · 9 revisions

이 문서에서는 Libplanet을 실제로 사용하는 NineChronicles.Headless 프로젝트와 Lib9c 프로젝트의 일부 코드 예시를 들어 Libplanet을 사용해 블록체인 노드를 구성하는 방법을 다룹니다.

Libplanet을 실행하여 블록을 생성하기 위해서는 크게 다음의 두 클래스를 생성해야 합니다.

  • BlockChain: 블록을 생성하고, 상태를 관리하는 클래스
  • Swarm: 생성한 블록을 다른 노드에 배포하고, 트랜잭션을 받아오는 클래스

아래에서는 위 두 클래스에 대해서 어떻게 생성하고 실행하는지에 대해 간단히 설명합니다. 각 클래스에 대한 심화된 설명 및 플로우는 다른 문서에서 별도로 설명합니다.

BlockChain 클래스

LibplanetNodeServices.cs#L154

                BlockChain = new BlockChain(
                    policy: blockPolicy,
                    store: Store,
                    stagePolicy: stagePolicy,
                    stateStore: StateStore,
                    genesisBlock: genesisBlock,
                    renderers: renderers,
                    blockChainStates: blockChainStates,
                    actionEvaluator: actionEvaluator
                );

위 코드처럼 블록체인 객체를 생성할 수 있습니다. 각 인자는 다음의 의미가 있습니다.

  • policy: 블록체인의 여러 정책을 포함합니다. 트랜잭션과 블록을 필터링하는 로직, 그리고 트랜잭션 외에 블록체인에서 실행되는 액션들을 담은 PolicyActionsRegistry 정보가 포함되어있습니다.
  • store: 블록체인을 구성하는 데이터들을 저장할 IStore 의 구현체입니다. Libplanet에서 제공하는 RocksDBStore 를 권장하고 있습니다.
  • stagePolicy: 트랜잭션을 멤풀에 저장할 때 거르는 로직 정보가 포함되어있씁니다.
  • stateStore: 블록체인을 구성하는 데이터들 중 '상태'를 저장할 IStateStore 의 구현체입니다. Libplanet에서 제공하는 TrieStateStoreRocksDBKeyValueStore를 함께 사용하는 것이 권장됩니다. (사용 예시)
  • genesisBlock: 0번 인덱스를 가진 블록입니다. 스토어에서 가져오거나 없다면 BlockChain.ProposeGenesisBlock() 함수를 통해 생성할 수 있습니다.
  • renderers: 블록이나 트랜잭션이 실행될 때 실행되었을 때 알림을 받아올 수 있는 렌더러입니다.
  • blockChainStates: 블록체인의 상태를 관리하는 클래스입니다.
  • actionEvaluator: 각 액션이 어떻게 실행될지의 정보를 가지고 있는 클래스입니다.

이렇게 생성된 블록체인 객체를 이용해서 다음과 같이 합의 과정 없이 블록을 반복해서 생성할 수 있습니다.

Lib9c.Proposer

                var lastCommit = _chain.GetBlockCommit(_chain.Tip.Hash);
                block = _chain.ProposeBlock(
                    _privateKey,
                    lastCommit: lastCommit);

                if (!(_chain.GetNextWorldState() is IWorldState worldState))
                {
                    throw new InvalidOperationException(
                        "Failed to get next world state. Appending is not completed.");
                }

                var proposerPower = worldState.GetValidatorSet().GetValidatorsPower(
                    new List<PublicKey> { _privateKey.PublicKey });

                BlockCommit? commit = block.Index > 0
                    ? new BlockCommit(
                        block.Index,
                        0,
                        block.Hash,
                        ImmutableArray<Vote>.Empty
                            .Add(new VoteMetadata(
                                block.Index,
                                0,
                                block.Hash,
                                DateTimeOffset.UtcNow,
                                _privateKey.PublicKey,
                                proposerPower,
                                VoteFlag.PreCommit).Sign(_privateKey)))
                    : null;
                _chain.Append(block, commit);

그러나 위 코드는 Libplanet의 블록 생성 방식인 PoS 상황에서는 동작하지 않습니다. 합의를 통해 블록을 생성하는 것이 필요하고 이 작업은 SwarmConsensusReactor를 통해 이루어집니다. 해당 과정에 대한 상세 설명은 ConsensusReactor 문서에서 확인할 수 있습니다.

Swarm 클래스

생성

LibplanetNodeService.cs#L251-L257

            Swarm = new Swarm(
                BlockChain,
                Properties.SwarmPrivateKey,
                transport,
                swarmOptions,
                consensusTransport,
                consensusOption: consensusReactorOption);

위 코드처럼 스웜 객체를 생성할 수 있습니다. 각 인자는 다음의 의미가 있습니다.

  • blockChain: 블록체인 객체입니다. 앞서 생성한 블록체인 객체를 그대로 사용하면 됩니다.
  • privateKey: 네트워크 통신에 사용하는 키 정보입니다.
  • transport: 블록 및 트랜잭션을 주고받기 위해 사용하는 ITransport 객체입니다. Libplanet에서 제공하는 NetMQTransport 의 사용을 권장하고 있습니다.
  • options: 통신 주기나 시드 피어 등의 옵션을 담고 있습니다. 자세한 내용은 SwarmOptions 파일을 참고해주세요.
  • consensusTransport: 블록 합의를 위한 메세지들을 주고받는 ITransport 객체입니다. transport 인자와 마찬가지로 Libplanet에서 제공하는 NetMQTransport 의 사용을 권장하고 있습니다. 이 인자를 비워둘 시 합의에 참여하지 않지만 블록과 트랜잭션은 여전히 싱크할 수 있습니다.
  • consensusOption: 블록 합의를 위한 여러 옵션들을 담고 있습니다. 자세한 내용은 ConsensusReactorOption 파일을 참고해주세요.

프리로딩

새롭게 시작한 노드는 다른 노드들과 블록 팁이 크게 차이날 수 있습니다. 프리로딩 없이 실행해도 이론상 다른 노드들과 높이가 동기화되지만, 각 블록을 실행하며 렌더링하기 때문에 오랜 시간이 걸리게 됩니다.

시간을 단축하기 위해 블록의 정합성 정도만 체크하면서 빠르게 블록을 따라잡는 프리로딩을 다음처럼 실행하여 팁 동기화 시간을 크게 단축할 수 있습니다.

LibplanetNodeService.cs#L398-L401

                        await Swarm.PreloadAsync(
                            PreloadProgress,
                            cancellationToken: cancellationToken
                        );

PreloadProgress 를 통해 프리로딩 진행 상황을 외부에서 파악할 수 있습니다.

시작

이렇게 생성된 Swarm 객체를 다음처럼 실행할 수 있습니다.

LibplanetNodeService.cs#L459-L461

                    await await Task.WhenAny(
                        Swarm.StartAsync(cancellationToken),
                        ReconnectToSeedPeers(cancellationToken)

이렇게 하면 지정한 정책과 옵션에 맞추어 블록이 주기적으로 생성되고 노드간 동기화가 이뤄집니다.

주의 할 점: Swarm.StartAsync()await 하면 해당 태스크는 Swarm 이 종료될 때 까지 hang 되어 반환하지 않습니다.

트랜잭션 만들기

Swarm 객체를 생성한 후 트랜잭션을 생성하고 노드간 동기화를 한 다음 블록에 포함시키는 법에 대해 설명합니다.

ActionMutation.cs#L78-L95

                        var avatarName = context.GetArgument<string>("avatarName");
                        var avatarIndex = context.GetArgument<int>("avatarIndex");
                        var hairIndex = context.GetArgument<int>("hairIndex");
                        var lensIndex = context.GetArgument<int>("lensIndex");
                        var earIndex = context.GetArgument<int>("earIndex");
                        var tailIndex = context.GetArgument<int>("tailIndex");
                        var action = new CreateAvatar
                        {
                            index = avatarIndex,
                            hair = hairIndex,
                            lens = lensIndex,
                            ear = earIndex,
                            tail = tailIndex,
                            name = avatarName,
                        };

                        var actions = new ActionBase[] { action };
                        Transaction tx = blockChain.MakeTransaction(privateKey, actions);
                        return tx.Id;

위 코드는 실제 NineChronicles.Headless 저장소에서 CreateAvatar 액션을 담은 트랜잭션을 만드는 예시입니다. BlockChain.MakeTransaction 함수에 서명자의 PrivateKey 와 포함할 액션들을 전달하여 손쉽게 생성할 수 있습니다. 해당 메서드를 사용해 생성한 트랜잭션은 자동으로 스테이징까지 진행됩니다.

만약 해당 메서드를 통해 트랜잭션을 만들지 않고, raw value를 인코딩하여 만들거나 Transaction.Create 메서드를 통해 생성한 트랜잭션을 스테이징 하려면 아래의 코드처럼 할 수 있습니다.

BlockChainRepository.cs#L83

        return _blockChain.StageTransaction(tx);

간단히 BlockChain.StageTransaction() 메서드를 통해 멤풀에 포함시킬 수 있고, Swarm이 실행되어있다면 자동으로 노드간 동기화가 이뤄집니다. 뿐만 아니라 다음 블록이 생성될 때 멤풀에서 트랜잭션을 꺼내어 블록을 생성합니다.

Clone this wiki locally