node-authorization
팔레트를 사용해 Substrate에서 허가형 네트워크를 구성하는 방법에 대해 다룰 것
퍼블릭 블록체인 : 모두가 노드를 운영함으로써 네트워크에 참여 가능
허가형 블록체인 : 권한있는 노드들만 블록 검증하거나 거래 전파시킬 수 있음
Proof of Existence (2번 튜토리얼)을 완료했고 Substrate의 P2P 네트워킹에 익숙하다(Private Network Tutorial 5번 튜토리얼을 완료했다)고 가정할 것임
node-authorization
팔레트 추가node-authorization
팔레트를 런타임에서 사용하는 방법 배우기node-authorization
팔레트 추가node-authorization
팔레트는 Substrate의 FRAME에 내장된 팔레트로, 허가형 네트워크를 위한 노드 설정을 관리
각 노드는Vec<u8>
타입의 PeerId에 의해 식별됨
각 노드를 claim한 AccountId가 PeerId를 소유함
이 팔레트에서 네트워크에 참여하고 싶은 노드들에게 권한을 주는 두 가지 방법
그 사이에 어떤 연결이 허가되었는지 잘 알려진 노드들의 집합에 참여한다. 그러기 위해 거버넌스(또는 Sudo)에 의해 허가받아야 함 -> 뭔 말???
특정 노드로부터 짝 연결을 요청하기. 잘 알려진 노드일 수도, 일반 노드일 수도 있음
PeerId와 연결된 노드는 반드시 하나의 주인을 가져야 함
잘 알려진 노드의 주인은 그 노드를 추가할 때 명시됨
만약 일바 노드라면, 어떤 사용자든지 해당 노드의 주인으로써 PeerId를 등록할 수 있음
가짜 등록을 막기 위해 노드 관리자는 노드를 시작하기 전에 등록해야 하고, 따라서 뒤에 그 네트워크에 등록하는 누구에게나 PeerId를 드러냄
노드의 주인은 자기의 노드에 대해 연결을 추가하고 삭제할 수 있음
정확히 말하자면, 잘 알려진 노드들 사이의 연결은 변경할 수 없고, 언제나 서로 연결될 수 있음.
대신, 잘 알려진 노드와 일반 노드 사이 또는 두 개의 일반 노드와 서브 노드들 사이의 연결을 조절할 수 있음
The node-authorization
팔레트는 노드의 연결을 설정하기 위해 offchain worker 시스템을 통합함
offchain worker가 권한이 없는 노드들에게 기본적으로 사용불가능하도록 정확한 CLI 플래그로 offchain worker를 사용해야 함
Off-chain worker : 외부 세상과 소통하기 위한 확장된 API들에 대한 접근 권한을 가짐
- 연산 결과를 발표하기 위해 체인에 거래를 제출하는 능력
- worker가 외부 서비스로부터 데이터를 가져오고 접근할 수 있게 해 주는 완전기능 HTTP client
- 선언문이나 거래를 검증하고 사인하기 위한 지역 keystore에 대한 접근권한
- 모든 off-chain worker 사이에 공유되는 지역 key-value 데이터베이스
- 랜덤 숫자 생성을 위한 안전한 지역적 엔트로피 소스
- 노드의 정확한 지역 시간에 대한 접근 권한
- 일을 쉬고 재개하는 능력
off-chain worker의 결과물은 일반 거래 검증 대상이 아님. 어떤 정보가 체인에 들어갈지 결정하기 위해 검증 메커니즘을 반드시 구현해야 함
git clone https://github.com/substrate-developer-hub/substrate-node-template
git checkout latest
cd substrate-node-template/
cargo build --release
node-authorization
팔레트 추가runtime/Cargo.toml
pallet-node-authorization = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.20" }
[features]
default = ['std']
std = [
'pallet-node-authorization/std',
]
간단한 블록체인에서 거버넌스를 시뮬레이션해야 하므로 sudo
관리자 규칙을 만들어서 EnsureRoot
에 대한 팔레트의 인터페이스를 설정할 것
runtime/src/lib.rs
use frame_system::EnsureRoot;
parameter_types! {
pub const MaxWellKnownNodes: u32 = 8;
pub const MaxPeerIdLength: u32 = 128;
}
impl pallet_node_authorization::Config for Runtime {
type Event = Event;
type MaxWellKnownNodes = MaxWellKnownNodes;
type MaxPeerIdLength = MaxPeerIdLength;
type AddOrigin = EnsureRoot<AccountId>;
type RemoveOrigin = EnsureRoot<AccountId>;
type SwapOrigin = EnsureRoot<AccountId>;
type ResetOrigin = EnsureRoot<AccountId>;
type WeightInfo = ();
}
construct_runtime
매크로에 우리의 팔레트를 추가하기
runtime/src/lib.rs
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
NodeAuthorization: pallet_node_authorization,
}
);
node/Cargo.toml
[dependencies]
bs58 = "0.4.0"
PeerId
는 bs58 형식으로 인코딩되어 있기 때문에, 해독해서 바이트 얻어내려면 bs58 라이브러리 추가
node/src/chain_spec.rs
use sp_core::OpaquePeerId;
use node_template_runtime::NodeAuthorizationConfig;
적절한 genesis storage와 필요한 의존성 추가
fn testnet_genesis(
wasm_binary: &[u8],
initial_authorities: Vec<(AuraId, GrandpaId)>,
root_key: AccountId,
endowed_accounts: Vec<AccountId>,
_enable_println: bool,
) -> GenesisConfig {
node_authorization: NodeAuthorizationConfig {
nodes: vec![
(
OpaquePeerId(bs58::decode("12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2").into_vec().unwrap()),
endowed_accounts[0].clone()
),
(
OpaquePeerId(bs58::decode("12D3KooWQYV9dGMFoRzNStwpXztXaBUjtPqi6aU76ZgUriHhKust").into_vec().unwrap()),
endowed_accounts[1].clone()
),
],
},
}
헬퍼 함수 testnet_genesis()에 genesis 설정 추가
NodeAuthorizationConfig
는 nodes
라는 튜플의 벡터인 property를 가짐
튜플의 첫 번째 요소는 OpaquePeerId
이고 이걸 사람이 읽을 수 있는 형식에서 byte로 바꾸기 위해 bs58::decode
사용
튜플의 두 번째 요소는 AccountId
이고, 이 노드의 주인을 나타내며, 설명을 위해 제공된 Alice와 Bob 계정을 사용할 것
12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2
가 어디서 나왔는지 궁금할 텐데, 위의 사람이 읽을 수 있는 PeerId
를 생성하기 위해 subkey(CLI 툴)를 사용할 수 있음
subkey generate-node-key
실행 결과는 다음과 같음:
12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2 // this is PeerId.
c12b6d18942f5ee8528c8e2baf4e147b5c5c18710926ea492d09cbd9f6c9f82a // This is node-key.
이제 허가형 네트워크 시작할 준비 끝
허가형 체인을 어떻게 시작하고 새 노드를 추가하는지 알아볼 것임
먼저 다 잘 컴파일 되는지 확인:
cargo build --release
4개의 노드를 시작할 것 : 3개의 잘 알려진 노드로 주인에게 허가받고 블록을 검증하는 것과, 선택된 잘 알려진 노드로부터 읽기 전용 권한만을 갖는 1개의 서브노드
Alice의 잘 알려진 노드:
# Node Key
c12b6d18942f5ee8528c8e2baf4e147b5c5c18710926ea492d09cbd9f6c9f82a
# Peer ID, generated from node key
12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2
# BS58 decoded Peer ID in hex:
0x0024080112201ce5f00ef6e89374afb625f1ae4c1546d31234e87e3c3f51a62b91dd6bfa57df
Bob의 잘 알려진 노드
# Node Key
6ce3be907dbcabf20a9a5a60a712b4256a54196000a8ed4050d352bc113f8c58
# Peer ID, generated from node key
12D3KooWQYV9dGMFoRzNStwpXztXaBUjtPqi6aU76ZgUriHhKust
# BS58 decoded Peer ID in hex:
0x002408011220dacde7714d8551f674b8bb4b54239383c76a2b286fa436e93b2b7eb226bf4de7
Charlie의 일반 노드
# Node Key
3a9d5b35b9fb4c42aafadeca046f6bf56107bd2579687f069b42646684b94d9e
# Peer ID, generated from node key
12D3KooWJvyP3VJYymTqG7eH4PM5rN4T2agk5cdNCfNymAqwqcvZ
# BS58 decoded Peer ID in hex:
0x002408011220876a7b4984f98006dc8d666e28b60de307309835d775e7755cc770328cdacf2e
Dave의 (Charlie에 대한) 서브노드
# Node Key
a99331ff4f0e0a0434a6263da0a5823ea3afcfffe590c9f3014e6cf620f2b19a
# Peer ID, generated from node key
12D3KooWPHWFrfaJzxPnqnAYAoRUyAHHKqACmEycGTVmeVhQYuZN
# BS58 decoded Peer ID in hex:
0x002408011220c81bc1d7057a1511eb9496f056f6f53cdfe0e14c8bd5ffca47c70a8d76c1326d
Alice와 Bob의 노드는 미리 genesis storage에 잘 알려진 노드로 설정해둠.
앞으로 Charlie의 노드를 잘 알려진 노드로 추가하고 Charlie의 노드와 Dave의 노드 사이의 연결을 Dave의 노드를 잘 알려진 노드로 만들지 않으면서 추가할 것
먼저 Alice 노드 시작:
./target/release/node-template \
--chain=local \
--base-path /tmp/validator1 \
--alice \
--node-key=c12b6d18942f5ee8528c8e2baf4e147b5c5c18710926ea492d09cbd9f6c9f82a \
--port 30333 \
--ws-port 9944
여기서 --node-key
를 사용해 네트워크 연결의 보안을 위해 사용할 키를 명시하고 있음
이 키는 위에 있는 사람이 읽을 수 있는 PeerId를 생성하는 데에도 내부적으로 사용됨
--chain=local
: --dev
랑은 다른 로컬 테스트넷을 위해 사용--alice
: 노드에게 alice라는 이름을 붙여주면서 누가 주인이고 블록을 완성할 것인지에 대한 권한을 줌--port
: P2P 연결을 위한 포트 할당--ws-port
: Websocket 연결을 위한 수신 포트 할당Bob 노드 시작
# In a new terminal, leave Alice running
./target/release/node-template \
--chain=local \
--base-path /tmp/validator2 \
--bob \
--node-key=6ce3be907dbcabf20a9a5a60a712b4256a54196000a8ed4050d352bc113f8c58 \
--port 30334 \
--ws-port 9945
두 개의 노드 다 실행시키면 터미널 로그로 새로운 블록들이 생성되고 완료되는 걸 볼 수 있음
이제 polkadot.js app으로 잘 알려진 노드들을 확인해볼 것
로컬 노드 중 하나를 127.0.0.1:9944
나 127.0.0.1:9945
로 바꾸는 거 잊지 말기
Developer page로 가서,Chain State sub-tab에서 nodeAuthorization
팔레트의 wellKnownNodes
storage에 저장된 데이터 확인
Alice와 Bob 노드의 peer id가 0x가 붙은 16진수 형식으로 보일 것임
스토리지에 We can also check the owner of one node by querying the storage owners
스토리지에 노드의 peer id를 입력값으로 쿼리 날려서 주인의 계정 주소를 얻음으로써 노드의 주인을 확인할 수 있음
Charlie 노드 시작
./target/release/node-template \
--chain=local \
--base-path /tmp/validator3 \
--name charlie \
--node-key=3a9d5b35b9fb4c42aafadeca046f6bf56107bd2579687f069b42646684b94d9e \
--port 30335 \
--ws-port=9946 \
--offchain-worker always
시작한 뒤에도 이 노드에 연결된 peer가 없는 것을 볼 수 있을 것임
이건 허가형 네트워크이기 때문에 연결되기 위해선 허가를 받아야 함!
Alice와 Bob은 처음에 chain_spec.rs
에서 미리 설정해뒀음
거버넌스를 위해 sudo
팔레트를 사용하고 있어서 노드를 추가하기 위해node-authorization
팔레트가 제공하는add_well_known_node
dispatch를 sudo 호출할 수 있다는 걸 기억해라
Developer page의 Sudo tab으로 가서 Charlie를 주인으로 해서 Charlie의 16진수 peer id를 넣고 nodeAuthorization
의 add_well_known_node
을호출
이 호출에 대해 Alice가 유효한 sudo origin이라는 거에 주목해라
이 거래가 블록에 포함된 뒤, Charlie의 노드가 Alice와 Bob의 노드에 연결되고 블록을 맞춰 나가는 걸 불 수 있을 것임
세 노드들이 서로를 찾을 수 있는 이유는 mDNS discovery 메커니즘이 로컬 네트워크에 기본 설정으로 사용가능하기 때문
이제 모든 블록들을 같이 검증하는 3개의 잘 알려진 노드들이 있음
이제 Dave의 노드를 잘 알려진 노드가 아니라 Charlie의 "서브 노드"로 추가할 것
Dave는 네트워크에 접근하기 위해 오직 Charlie에게만 연결할 수 있음
보안 기능 때문임: Charlie는 어떤 연결된 서브노드에 대해 혼자 책임을 짐
제거될 때 David에 대한 접근 권한은 한 곳에서만 하면 됨
Dave의 노드 시작
./target/release/node-template \
--chain=local \
--base-path /tmp/validator4 \
--name dave \
--node-key=a99331ff4f0e0a0434a6263da0a5823ea3afcfffe590c9f3014e6cf620f2b19a \
--port 30336 \
--ws-port 9947 \
--offchain-worker always
시작하면, 가능한 연결이 없을 것임
먼저, Charlie가 Dave의 노드로부터 연결을 허용하기 위해 노드 설정해줘야 함
Developer Extrinsics page에 가서 Charlie 계정으로 addConnections
extrinsic 호출
peerId는 Charlie 노드의 16진수 peer id임.
connections는 Charlie의 노드에 대해 허용된 노드들의 리스트인데, 우리는 Dave 노드만 추가할 것임
이제 Chalie의 노드로부터 연결을 허락받기 위해 Dave가 노드 설정해야 함
그 전에 너무 늦기 않았기를 바라면서 Dave의 노드를 먼저 등록해야 함!
비슷하게, Dave는 Charlie 노드로부터 연결 추가할 수 있음
이제 Dave가 블록들을 따라잡고 있고, 오직 Chalie만을 peer로 갖는 걸 볼 수 있음! Chalie와 바로 연결되지 않는 경우에는 Dave의 노드를 다시 시작하면 됨
remove_well_known_node
이나 remove_connections
등의 extrinsic으로도 놀아보기
Private Network Tutorial 완료하기
Subkey tool 더 알아보기