런타임 스토리지 구조

런타임 로직을 개발하면 저장할 정보와 이를 저장하는 방법에 대해 중요한 결정을 내려야 합니다. 상태 전이와 스토리지에서 설명한 대로 스토리지에 데이터를 읽고 쓰는 것은 비용이 많이 듭니다. 또한 불필요하게 큰 데이터 세트를 저장하면 네트워크 속도가 느려지고 시스템 리소스가 과도하게 사용될 수 있습니다.

Substrate는 필요에 맞는 블록체인을 구축할 수 있는 유연한 프레임워크를 제공하기 위해 설계되었습니다. 그러나 런타임 스토리지를 설계할 때는 장기적으로 안전하고 성능이 우수하며 유지 관리 가능한 블록체인을 구축하기 위해 몇 가지 기본 지침을 염두에 두어야 합니다.

저장할 내용 결정하기

블록체인 런타임 스토리지에 대한 기본 원칙은 저장할 데이터 항목의 수와 크기를 최소화하는 것입니다. 예를 들어, 런타임에는 합의에 중요한 정보만 저장해야 합니다. 런타임에 중간 또는 임시 데이터나 작업이 실패할 경우 필요하지 않은 데이터를 저장해서는 안 됩니다.

해시된 데이터 사용하기

가능한 경우 해시와 같은 기술을 사용하여 저장해야 하는 데이터 양을 줄일 수 있습니다. 예를 들어, 많은 거버넌스 기능(예: 민주주의 팔레트의 propose 함수)은 네트워크 참여자가 트랜잭션 자체가 아닌 트랜잭션의 _해시_에 대해 투표할 수 있도록 합니다. 트랜잭션의 해시는 항상 크기가 제한되어 있지만 트랜잭션은 길이가 제한되지 않을 수 있습니다.

트랜잭션의 해시를 사용하는 것은 런타임 업그레이드의 경우 특히 중요합니다. 이 경우 트랜잭션이 전체 런타임 Wasm blob이 함수 파라미터로 사용되기 때문입니다. 이러한 거버넌스 메커니즘은 체인 상에서 구현되므로 특정 거버넌스 제안의 상태에 대해 합의에 도달하기 위해 필요한 모든 정보도 체인 상에 저장해야 합니다. 이는 투표 대상인 _무엇_을 포함합니다. 그러나 체인에 제안을 해시에 바인딩함으로써 Substrate의 거버넌스 메커니즘은 제안이 승인된 _후_에 제안과 관련된 모든 데이터를 체인 상에 가져오지 않고도 이를 수행할 수 있도록 합니다. 이는 실패한 제안에 대해 스토리지가 낭비되지 않도록 합니다.

거버넌스 제안이 통과되면 실제 트랜잭션 가능한 트랜잭션(모든 매개변수 포함)을 시작할 수 있으며, 이 트랜잭션은 위의 제안의 해시와 비교됩니다.

체인 상에 저장되는 데이터 양을 최소화하기 위해 해시를 사용하는 또 다른 일반적인 패턴은 IPFS에 연결된 개체와 관련된 사전 이미지를 저장하는 것입니다. 이렇게 하면 체인 상에 저장해야 하는 것은 크기가 제한된 해시인 IPFS 위치만 저장하면 됩니다.

일시적인 데이터 저장 피하기

런타임 스토리지를 사용하여 논리적으로 원자적인(atomic) 작업의 맥락 내에서 일시적인 데이터를 저장해서는 안 됩니다. 이는 런타임 스토리지를 사용하여 여러 원자적 작업을 필요로 하는 작업의 상태를 추적하는 데 사용하지 않아야 한다는 것을 의미하지 않습니다. 예를 들어, 유틸리티 팔레트의 다중 서명 기능과 같은 경우, 특정 트랜잭션이 실제로 트랜잭션되기 위해 충분한 서명을 받지 못할 수 있지만, 런타임 스토리지는 여전히 해당 트랜잭션의 서명자를 추적하는 데 사용됩니다. 이 경우 각 서명은 진행 중인 다중 서명 작업에서 원자적 이벤트로 간주됩니다. 단일 서명을 기록하기 위해 필요한 데이터는 해당 서명과 관련된 모든 사전 조건이 충족된 후에만 저장됩니다.

한계 설정하기

스토리지 항목의 크기에 한계를 설정하는 것은 런타임 스토리지의 사용을 효과적으로 제어하는 매우 효과적인 방법입니다. 이는 Substrate 코드베이스 전체에서 반복적으로 사용되는 방법 중 하나입니다. 일반적으로 사용자 작업에 의해 결정되는 스토리지 항목은 한계를 설정해야 합니다. 앞서 설명한 다중 서명 팔레트의 다중 서명 기능은 이러한 예입니다. 이 경우 다중 서명 작업과 관련된 서명자 목록은 다중 서명 참가자에 의해 제공됩니다. 다중 서명 작업의 상태에 대해 합의에 도달하기 위해 이 서명자 목록은 런타임에 저장되어야 합니다. 그러나 서명자 목록이 사용할 수 있는 공간을 제어하기 위해 Utility 팔레트는 스토리지에 쓰기 전에 사전 조건으로 포함되는 이 숫자에 대한 한계를 사용자가 구성하도록 요구합니다.

트랜잭션 스토리지

상태 전이와 스토리지에서 설명한 대로 런타임 스토리지는 키-값 데이터베이스와 키와 상태 변경을 추적하는 인메모리 스토리지 오버레이 추상화를 포함합니다. 기본적으로 런타임의 함수는 주 스토리지 오버레이(캐시)에 커밋하기 전에 단일 인메모리 트랜잭션 스토리지 레이어에 변경 사항을 기록합니다. 트랜잭션 오류가 발생하면 트랜잭션 스토리지 레이어의 변경 사항은 주 스토리지 오버레이로 전달되지 않고 폐기됩니다. 그 결과 기본 스토리지 오버레이와 기본 데이터베이스의 상태는 변경되지 않습니다.

트랜잭션 스토리지 레이어 추가하기

#[transactional] 매크로를 사용하여 추가 인메모리 스토리지 오버레이를 생성하여 트랜잭션 스토리지 레이어를 확장할 수 있습니다. 추가 인메모리 트랜잭션 스토리지 오버레이를 생성함으로써 특정 변경 사항을 주 스토리지 오버레이에 커밋할지 여부를 선택할 수 있습니다. 추가 트랜잭션 스토리지 레이어를 사용하면 특정 함수 트랜잭션에 대한 변경 사항을 격리하고 언제든지 어떤 변경 사항을 커밋할지 선택할 수 있습니다.

트랜잭션 스토리지 레이어는 최대 열 개의 중첩된 트랜잭션 스토리지 레이어까지 중첩될 수 있습니다. 생성하는 각 중첩된 트랜잭션 스토리지 레이어마다 그 아래의 트랜잭션 레이어에 변경 사항을 커밋할지 여부를 선택할 수 있습니다. 이렇게 중첩된 트랜잭션 스토리지 레이어의 총 수를 제한하면 커밋할 변경 사항을 해결하는 데 필요한 계산 오버헤드를 제한할 수 있습니다.

트랜잭션 스토리지 레이어에서 트랜잭션 호출하기

자체 트랜잭션 스토리지 레이어 내에서 함수 트랜잭션을 디스패치하려면 dispatch_with_transactional(call) 함수를 사용하여 명시적으로 트랜잭션에 대한 새로운 트랜잭션 스토리지 레이어를 생성하고 해당 트랜잭션 스토리지 레이어 컨텍스트를 사용하여 결과를 처리할 수 있습니다.

트랜잭션 스토리지 레이어 없이 변경 사항 커밋하기

기본 트랜잭션 스토리지 레이어를 사용하지 않고 주 스토리지 오버레이에 변경 사항을 커밋하려면 #[without_transactional] 매크로를 사용할 수 있습니다. #[without_transactional] 매크로를 사용하면 트랜잭션 스토리지 레이어 없이 실행해도 안전한 함수를 식별할 수 있습니다.

예를 들어, 다음과 같이 함수를 정의할 수 있습니다:

/// 이 함수는 추가 트랜잭션 스토리지 레이어 없이 실행해도 안전합니다.
#[without_transactional]
fn set_value(x: u32) -> DispatchResult {
    Self::check_value(x)?;
    MyStorage::set(x);
    Ok(())
}

이 함수를 트랜잭션하면 트랜잭션 스토리지 레이어가 생성되지 않습니다.

그러나 #[without_transactional] 매크로를 사용하는 경우 스토리지에 대한 변경 사항이 주 인메모리 스토리지 오버레이의 값에 영향을 줍니다. 스토리지를 수정한 후에 오류가 발생하면 해당 변경 사항이 유지되고, 잘못된 상태로 데이터베이스가 남을 수 있습니다.

런타임 스토리지에 액세스하기

Substrate로 구축된 블록체인은 런타임 스토리지를 쿼리할 수 있는 원격 프로시저 트랜잭션(RPC) 서버를 노출합니다. Polkadot JS와 같은 소프트웨어 라이브러리를 사용하여 코드에서 RPC 서버와 상호 작용하고 스토리지 항목에 액세스할 수 있습니다. Polkadot JS 팀은 또한 Polkadot Apps UI를 유지 관리하며, 이는 Substrate 기반 블록체인과 상호 작용하기 위한 완전한 기능을 갖춘 웹 앱입니다. 이 앱에서는 스토리지를 쿼리하는 것을 포함하여 다양한 Substrate 기반 블록체인과 상호 작용할 수 있습니다.

해시 알고리즘

Substrate의 스토리지 맵은 맵의 키를 생성하는 데 사용할 해시 알고리즘을 지정할 수 있도록 개발자에게 기능을 제공합니다. 해시 알고리즘을 캡슐화하는 Rust 객체를 해시 함수(hasher) 라고 합니다. 대체로 Substrate 개발자가 사용할 수 있는 해시 함수는 (1) 암호화 해시 함수 여부와 (2) 투명한 출력을 생성 여부 두가지 방식으로 설명할 수 있습니다.

완전성을 위해 비투명한 해시 알고리즘의 특성도 아래에 설명되어 있지만, 비투명한 출력을 생성하지 않는 해시 함수는 FRAME 기반 블록체인에서 사용이 중단되었습니다.

암호화 해시 함수

암호화 해시 함수를 사용하면 해시 알고리즘의 입력을 조작하여 출력에 영향을 주는 것이 매우 어려워집니다. 예를 들어, 암호화 해시 함수는 입력이 1부터 10까지의 숫자인 경우에도 출력의 넓은 분포를 생성합니다. 사용자가 스토리지 맵의 키에 영향을 줄 수 있는 경우 암호화 해시 함수를 사용하는 것이 중요합니다. 그렇지 않으면 악의적인 사용자가 연속적인 계정 번호에 대해 많은 작은 전송을 시스템에 공격적으로 보낼 수 있는 공격 벡터가 만들어집니다. 적절한 암호화 해시 함수를 사용하지 않으면 성능에 문제가 있는 불균형적인 스토리지 구조가 만들어지게 되어 성능이 저하됩니다. Substrate에서 사용할 수 있는 일반적인 해시 함수에 대한 자세한 내용은 Substrate 해시 함수를 참조하세요.

암호화 해시 함수는 비암호화 해시 함수보다 복잡하고 리소스를 많이 사용합니다. 따라서 런타임 엔지니어는 적절한 사용법을 이해하여 Substrate가 제공하는 유연성을 최대한 활용할 수 있도록 해야 합니다.

투명한 해시 함수

투명한 해시 함수는 주어진 출력을 생성하는 데 사용된 입력을 쉽게 찾고 확인할 수 있는 해시 알고리즘입니다. Substrate에서는 해시 알고리즘의 입력을 출력에 연결하여 해시 함수를 투명하게 만듭니다. 이렇게 하면 사용자가 키의 원래 해시되지 않은 값을 검색하고 원하는 경우 확인할 수 있습니다(다시 해시하여 확인). Substrate의 생성자는 FRAME 기반 런타임에서 비투명한 해시 함수의 사용을 중단했으므로 이 정보는 주로 완전성을 위해 제공됩니다. 사실, [반복 가능한 맵](#반복 가능한(iterable)-스토리지-맵) 기능에 액세스하려면 투명한 해시 알고리즘을 사용해야 합니다.

일반적인 Substrate 해시 함수

다음 표는 Substrate에서 사용되는 일반적인 해시 함수 목록을 나열하며, 암호화되었는지 여부와 투명한지 여부를 표시합니다.

해시 함수암호화투명

X

X

X

X

Identity 해시 함수는 출력이 입력과 동일한 해시 알고리즘을 캡슐화합니다(동일성 함수). 이러한 유형의 해시 함수는 시작 키가 이미 암호화된 해시인 경우에만 사용해야 합니다.

다음 단계로 넘어가기

스토리지에 대한 다양한 주제를 다루는 가이드를 확인하세요:

Last updated