프로그램 이해하기

이 페이지는 Solana에서 프로그램이 작동하는 방식에 대한 빠른 개요를 제공하고 특정 주제에 대해 더 자세히 알고 싶은 분들을 위한 추가 읽기 자료를 제공하는 것을 목표로 합니다.

소개

대부분의 블록체인과 달리 Solana는 로직과 데이터를 두 개의 별도 구성 요소로 분리합니다. 이들은 각각 프로그램어카운트입니다. 이것이 의미하는 바는 프로그램이 데이터를 내부 변수에 저장하는 대신 변경할 수 있는 능력을 가진 어카운트에 저장된 외부 데이터와 상호 작용한다는 것입니다.

이 아키텍처는 프로그램과 상호 작용하는 데이터가 프로그램 자체에 바인딩되지 않고 새로운 규모로 확장할 수 있기 때문에 프로그램을 더욱 모듈화하는 데 훌륭합니다. 또한 블록체인이 다른 어카운트로 동일한 프로그램을 병렬로 실행할 수 있기 때문에 프로그램을 더욱 성능적으로 만드는 데 훌륭합니다.

프로그램과 상호 작용하려면 해당 프로그램에서 정의한 인스트럭션을 사용해야 합니다. 인스트럭션을 프로그램에서 노출하는 API 엔드포인트로 생각할 수 있습니다. 각 인스트럭션에는 실행하기 위해 충족해야 하는 매개변수 및 제약 조건 세트가 포함됩니다.

요약하자면: Solana에서 프로그램은 어카운트라고 하는 외부 데이터 저장소와 상호 작용하는 데 사용할 수 있는 인스트럭션을 정의합니다.

기술적으로 프로그램은 프로그램의 컴파일된 코드를 저장하는 전체 목적이 executable로 표시된 특수한 종류의 어카운트입니다. 그러나 단순성을 위해 이러한 정의를 구분하고 "어카운트"라는 용어를 사용하여 실행 불가능한 어카운트를 참조합니다.

이 가이드의 나머지 부분에서는 문서 전체의 다이어그램에서 사용할 시각적 표현을 설명하기 전에 어카운트와 인스트럭션에 대해 더 자세히 설명합니다.

📚 추가 읽기:

어카운트

Solana에서 어카운트는 데이터를 저장하는 데 사용됩니다. 본질적으로 특정 주소에 저장된 간단한 바이트 배열입니다. 어카운트의 주소는 암호화 키 쌍의 공개 키입니다.

해당 키 쌍의 개인 키에 액세스할 수 있는 사람은 누구나 해당 어카운트를 대신하여 서명할 수 있으며, 프로그램에 따라 해당 어카운트에 저장된 데이터를 변경할 수 있는 능력을 부여할 수 있습니다.

어카운트가 생성되면 일반적으로 프로그램에 의해 즉시 초기화되며, 이 프로그램은 소유자로 표시되고 할당된 바이트 배열의 데이터 구조를 정의합니다. 어카운트를 소유한 프로그램은 어카운트와 상호 작용하는 데 사용할 수 있는 인스트럭션을 제공할 책임이 있습니다.

📚 추가 읽기:

프로그램 파생 주소 (PDA)

주소가 암호화 키 쌍의 공개 키가 아니라 대신 어카운트를 소유한 프로그램의 공개 키에서 알고리즘적으로 파생된 프로그램 파생 어카운트라는 다른 유형의 어카운트가 있습니다. 우리는 이 주소를 프로그램 파생 주소 또는 줄여서 PDA라고 합니다.

주소는 항상 프로그램의 공개 키에서 파생되므로 다른 프로그램은 알고리즘적으로 동일한 주소를 파생할 수 없습니다. 또한 추가 시드를 알고리즘에 제공하여 주소에 더 많은 컨텍스트를 추가할 수 있습니다.

이는 프로그램이 크로스 프로그램 호출에 서명할 수 있게 하거나 결정적으로 파생될 수 있는 주소 내에서 어카운트 생성을 가능하게 하는 등 다양한 사용 사례가 있습니다.

설계상 프로그램 파생 주소는 암호학적으로 생성된 공개 키와 충돌하지 않습니다. 모든 암호화 공개 키는 우리가 타원 곡선이라고 부르는 것의 일부입니다. PDA를 생성할 때 알고리즘이 해당 곡선에 속하는 키를 생성하면 범프가 주소에 추가되고 생성된 주소가 더 이상 곡선에 속하지 않을 때까지 1씩 증가합니다.

📚 추가 읽기:

어카운트 데이터

일반 어카운트를 다루든 프로그램 파생 어카운트를 다루든 어카운트는 데이터를 직렬화된 바이트 배열로 저장합니다. 따라서 프로그램은 관리하는 각 어카운트에 대한 데이터 구조를 정의하고 이러한 어카운트를 구별하는 방법을 제공하여 어떤 데이터 구조를 적용해야 하는지 알 수 있도록 하는 것이 프로그램의 책임입니다.

판별자

판별자는 프로그램 내의 다른 유형의 어카운트를 구별하는 데 사용됩니다. 여러 가지 방법으로 구현될 수 있지만 다음은 가장 일반적인 세 가지입니다:

  • 모든 어카운트의 첫 번째 바이트로 공유 Enum 사용. 공유 Enum으로 모든 어카운트에 접두사를 붙임으로써 직렬화된 데이터의 첫 번째 바이트를 사용하여 어카운트를 식별할 수 있습니다. 이것은 판별자를 구현하는 간단하고 효율적인 방법입니다. Metaplex가 유지 관리하는 대부분의 프로그램은 이 접근 방식을 사용합니다.
  • 모든 어카운트의 첫 번째 바이트로 결정론적 해시 사용. 이것은 이전 방법과 매우 유사하지만 Enum 대신 해시를 사용합니다. Anchor 프레임워크를 사용하여 생성된 프로그램은 Anchor가 어카운트 이름을 기반으로 해시를 자동으로 생성하기 때문에 암시적으로 이 접근 방식을 사용하게 됩니다.
  • 판별자 없음, 어카운트 크기 사용. 프로그램이 관리하는 모든 어카운트의 크기가 다르면 해당 바이트 배열의 길이를 검사하여 어떤 어카운트를 다루는지 결정할 수 있습니다. 이것은 데이터에 추가 바이트를 추가할 필요가 없기 때문에 성능적인 접근 방식이지만 프로그램이 어카운트로 유연할 수 있는 방법을 제한합니다. Solana의 SPL Token Program은 서로 다른 고정 크기의 두 개의 어카운트만 유지 관리하기 때문에 이 접근 방식을 사용합니다.

필드 타입, 크기 및 오프셋

각 어카운트는 다양한 타입의 필드를 사용하여 자체 데이터 구조를 정의합니다. 이러한 타입은 필드를 저장하는 데 필요한 바이트 수에 영향을 미칩니다. 예를 들어 i8은 1바이트를 저장해야 하는 8비트 정수인 반면 i64는 8바이트를 저장해야 하는 64비트 정수입니다.

블록체인에서 어카운트는 단지 바이트 배열이기 때문에 각 필드의 크기와 이 배열에서 시작하는 위치, 즉 오프셋을 이해하는 것이 중요합니다. 이것은 memcmp 필터를 사용하여 주어진 프로그램에서 여러 어카운트를 가져올 때 유용할 수 있습니다.

모든 필드가 고정 크기를 갖는 것은 아닙니다. 예를 들어 Vec<i8>은 없음, 하나 또는 많은 항목을 포함할 수 있는 8비트 정수의 벡터입니다. 따라서 첫 번째 가변 크기 필드 이후에 위치한 필드를 기반으로 어카운트를 필터링하는 것이 훨씬 더 복잡해집니다.

선택적 필드

필드를 선택적으로 정의할 수도 있습니다. 즉, 해당 필드가 비어 있을 수 있는 시나리오가 있습니다.

이 필드는 필드가 비어 있는지 여부를 나타내기 위해 접두사로 추가 바이트를 사용합니다.

구체적으로 값 None이 할당되고 해당 필드를 사용할 때 프로그램이 그에 따라 작동합니다.

표시적 필드

이것은 데이터 구조에 명시적으로 정의된 것이 아니지만 문서는 특정 필드를 표시적으로 표시합니다.

표시적 필드는 필드에서 제공하는 정보가 프로그램 자체에서 사용되지 않음을 의미합니다. 대신 제3자에게 정보를 나타냅니다. 프로그램은 여전히 데이터의 무결성을 시행하지만 단순히 해당 정보를 내부적으로 사용하지 않습니다.

메타데이터 어카운트를 예로 들어 보겠습니다.

Creators 배열의 각 크리에이터의 Share 속성은 표시적입니다. Token Metadata 프로그램은 모든 크리에이터의 Share 값이 100%가 되도록 보장하지만 해당 정보로는 아무것도 하지 않습니다. 대신 NFT 마켓플레이스가 로열티를 분배할 때 이 정보를 사용할 것으로 예상합니다.

반면에 Is Mutable 속성은 Token Metadata 프로그램이 불변 메타데이터 어카운트가 업데이트되는 것을 방지하기 위해 해당 정보를 내부적으로 사용하기 때문에 표시적이지 않습니다.

인스트럭션

프로그램이 제공하는 인스트럭션을 사용하여 프로그램과 상호 작용할 수 있습니다. 여러 인스트럭션을 블록체인에 보낼 단일 트랜잭션으로 묶을 수 있습니다. 각 트랜잭션은 원자적이므로 인스트럭션 중 하나라도 실패하면 전체 트랜잭션이 취소됩니다.

어카운트와 마찬가지로 인스트럭션은 네트워크에 보내기 전에 바이트 배열로 직렬화되어야 합니다. 직렬화할 데이터에는 프로그램이 실행하기 위해 다음 정보가 포함되어야 합니다.

  • 판별자: 어카운트와 마찬가지로 인스트럭션은 일반적으로 판별자를 접두사로 사용하여 프로그램이 실행 중인 인스트럭션을 식별할 수 있습니다.
  • 어카운트: 이 인스트럭션의 영향을 받는 어카운트 주소 배열입니다. 이는 어카운트가 읽히거나 변경되거나 둘 다이기 때문일 수 있습니다. 프로그램은 위치에 따라 제공된 어카운트 유형을 식별하므로 이 배열의 순서가 중요합니다.
  • 인수: 인스트럭션에 필요한 데이터 필드 배열입니다. 인스트럭션이 어카운트에서 직접 대부분의 정보를 가져올 수 있기 때문에 이 배열이 비어 있는 것은 드문 일이 아닙니다. 이러한 인수는 어카운트의 필드와 비교할 수 있으므로 "선택적" 및 "표시적"과 같이 위에서 언급한 동일한 속성을 가질 수 있습니다.
  • 서명자: 제공된 어카운트의 하위 집합에 대한 서명 배열입니다. 이것은 인스트럭션에 서명해야 하는 어카운트에만 필요합니다. 다음 섹션에서 이에 대해 좀 더 자세히 설명합니다.

📚 추가 읽기:

서명자 및/또는 쓰기 가능 어카운트

프로그램은 인스트럭션 내에 제공된 어카운트가 서명자 및/또는 쓰기 가능이어야 할 수 있습니다.

  • 서명자: 서명자 어카운트는 인스트럭션이 성공하려면 트랜잭션에 서명해야 합니다. 서명을 첨부함으로써 사용자는 어카운트의 소유자임을 증명할 수 있습니다.
  • 쓰기 가능: 쓰기 가능 어카운트는 인스트럭션에 의해 변경됩니다. 이 정보는 블록체인이 어떤 트랜잭션을 병렬로 실행할 수 있고 어떤 트랜잭션을 실행할 수 없는지 알아야 하므로 중요합니다.

따라서 이 두 불린으로 다음 네 가지 가능한 시나리오를 갖게 됩니다:

  • 비서명자 및 비쓰기 가능: 이 어카운트는 데이터를 읽는 데만 사용됩니다. 변경할 수 없으며 소유권에 대한 가정을 할 수 없습니다.
  • 서명자 및 비쓰기 가능: 이 어카운트도 변경될 수 없지만 트랜잭션을 보낸 사용자가 개인 키를 소유하고 있음을 알고 있습니다. 이를 통해 프로그램은 특정 작업에 대한 액세스를 허용하거나 거부할 수 있습니다.
  • 서명자 및 쓰기 가능: 이 어카운트는 트랜잭션에 서명_하고_ 인스트럭션에 의해 변경될 수 있습니다. 프로그램은 일반적으로 해당 어카운트를 변경하기 전에 어카운트 소유자가 자신이 누구인지 증명하도록 요구하기 때문에 이 조합은 꽤 일반적입니다. 그렇지 않으면 누구나 소유하지 않은 어카운트를 변경할 수 있습니다.
  • 비서명자 및 쓰기 가능: 이 어카운트는 변경될 수 있지만 소유권에 대한 가정을 할 수 없습니다. 이것은 일반적으로 프로그램이 다른 서명자 어카운트를 사용하여 해당 어카운트를 변경할 수 있음을 증명하고 있음을 의미합니다. 이것은 프로그램이 소유하고 있고 프로그램이 이를 변경할 수 있는 권한을 추적해야 하므로 PDA 어카운트의 경우도 마찬가지입니다. 또한 어카운트에 lamports를 크레딧하는 것과 같은 특정 작업은 어카운트가 트랜잭션에 서명할 필요가 없습니다.

크로스 프로그램 호출 (CPI)

크로스 프로그램 호출을 통해 프로그램은 인스트럭션 내에서 중첩된 인스트럭션을 실행할 수 있습니다. 자체 프로그램 및/또는 다른 프로그램의 인스트럭션을 사용할 수 있습니다.

📚 추가 읽기: