-
Notifications
You must be signed in to change notification settings - Fork 152
Libplanet 실행 방법
이 문서에서는 Libplanet을 실제로 사용하는 NineChronicles.Headless 프로젝트와 Lib9c 프로젝트의 일부 코드 예시를 들어 Libplanet을 사용해 블록체인 노드를 구성하는 방법을 다룹니다.
Libplanet을 실행하여 블록을 생성하기 위해서는 크게 다음의 두 클래스를 생성해야 합니다.
-
BlockChain
: 블록을 생성하고, 상태를 관리하는 클래스 -
Swarm
: 생성한 블록을 다른 노드에 배포하고, 트랜잭션을 받아오는 클래스
아래에서는 위 두 클래스에 대해서 어떻게 생성하고 실행하는지에 대해 간단히 설명합니다. 각 클래스에 대한 심화된 설명 및 플로우는 다른 문서에서 별도로 설명합니다.
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에서 제공하는TrieStateStore
에RocksDBKeyValueStore
를 함께 사용하는 것이 권장됩니다. (사용 예시) -
genesisBlock
: 0번 인덱스를 가진 블록입니다. 스토어에서 가져오거나 없다면BlockChain.ProposeGenesisBlock()
함수를 통해 생성할 수 있습니다. -
renderers
: 블록이나 트랜잭션이 실행될 때 실행되었을 때 알림을 받아올 수 있는 렌더러입니다. -
blockChainStates
: 블록체인의 상태를 관리하는 클래스입니다. -
actionEvaluator
: 각 액션이 어떻게 실행될지의 정보를 가지고 있는 클래스입니다.
이렇게 생성된 블록체인 객체를 이용해서 다음과 같이 합의 과정 없이 블록을 반복해서 생성할 수 있습니다.
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 상황에서는 동작하지 않습니다. 합의를 통해 블록을 생성하는 것이 필요하고 이 작업은 Swarm
의 ConsensusReactor
를 통해 이루어집니다. 해당 과정에 대한 상세 설명은 ConsensusReactor
문서에서 확인할 수 있습니다.
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
객체를 생성한 후 트랜잭션을 생성하고 노드간 동기화를 한 다음 블록에 포함시키는 법에 대해 설명합니다.
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
메서드를 통해 생성한 트랜잭션을 스테이징 하려면 아래의 코드처럼 할 수 있습니다.
return _blockChain.StageTransaction(tx);
간단히 BlockChain.StageTransaction()
메서드를 통해 멤풀에 포함시킬 수 있고, Swarm이 실행되어있다면 자동으로 노드간 동기화가 이뤄집니다. 뿐만 아니라 다음 블록이 생성될 때 멤풀에서 트랜잭션을 꺼내어 블록을 생성합니다.