오프체인 워커 추가

팔렛을 수정하여 오프체인 워커를 포함시키고, 오프체인 워커에서 온체인 상태를 업데이트하기 위해 트랜잭션을 제출하는 방법을 설명합니다.

이 튜토리얼은 팔렛을 수정하여 오프체인 워커를 포함시키고, 오프체인 워커가 온체인 상태를 업데이트하는 트랜잭션을 제출할 수 있도록 팔렛과 런타임을 구성하는 방법을 설명합니다.

오프체인 워커 사용하기

오프체인 워커를 사용하여 장기 실행 계산 또는 오프라인 소스에서 데이터를 가져오는 경우, 해당 작업의 결과를 온체인에 저장하고자 할 것입니다. 그러나 오프체인 스토리지는 온체인 리소스와 별개이며, 오프체인 워커가 처리한 데이터를 직접 온체인 스토리지에 저장할 수는 없습니다. 오프체인 워커에서 처리한 데이터를 온체인 상태의 일부로 저장하려면, 오프체인 워커 스토리지에서 데이터를 온체인 스토리지 시스템으로 전송하는 트랜잭션을 생성해야 합니다.

이 튜토리얼은 오프체인 데이터를 온체인에 저장하기 위해 서명된 또는 서명되지 않은 트랜잭션을 제출할 수 있는 오프체인 워커를 생성하는 방법을 설명합니다. 일반적으로 서명된 트랜잭션은 보안성이 높지만, 호출하는 계정이 트랜잭션 수수료를 처리해야 합니다. 예를 들어:

  • 트랜잭션 호출자 계정을 기록하고 호출자 계정에서 트랜잭션 수수료를 차감하려면 서명된 트랜잭션을 사용하세요.

  • 트랜잭션 호출자를 기록하려고 하지만 호출자가 트랜잭션 수수료 지불에 대한 책임을 지지 않으려면 서명된 페이로드를 포함한 서명되지 않은 트랜잭션을 사용하세요.

서명되지 않은 트랜잭션 사용하기

서명되지 않은 트랜잭션을 서명된 페이로드 없이 제출하는 것도 가능합니다. 이는 트랜잭션 호출자를 기록하지 않으려는 경우에 사용될 수 있습니다. 그러나 서명되지 않은 트랜잭션을 사용하여 체인 상태를 수정하는 것은 상당한 위험이 따릅니다. 서명되지 않은 트랜잭션은 악의적인 사용자가 악용할 수 있는 잠재적인 공격 경로를 제공합니다. 오프체인 워커가 서명되지 않은 트랜잭션을 전송할 수 있도록 허용하는 경우, 트랜잭션이 인가되었는지 확인하는 로직을 포함해야 합니다. 서명되지 않은 트랜잭션이 온체인 상태를 확인하여 검증되는 방법에 대한 예제는 enact_authorized_upgrade 호출의 ValidateUnsigned 구현을 참조하세요. 이 예제에서는 주어진 코드 해시가 이전에 인가되었는지 확인하여 서명되지 않은 트랜잭션을 검증합니다.

또한, 서명된 페이로드를 가진 서명되지 않은 트랜잭션도 악용될 수 있습니다. 오프체인 워커는 트랜잭션의 유효성을 확인하기 위해 엄격한 로직을 구현하지 않는 한 신뢰할 수 있는 소스로 가정할 수 없습니다. 대부분의 경우, 저장소에 쓰기 전에 트랜잭션이 오프체인 워커에 의해 제출되었는지 확인하는 것만으로는 네트워크를 보호하기에 충분하지 않습니다. 오프체인 워커를 보호하기 위해 엄격한 로직을 구현하지 않는 한, 오프체인 워커를 신뢰할 수 있다고 가정해서는 안 됩니다. 대신 오프체인 워커의 프로세스에 대한 액세스와 수행할 수 있는 작업을 제한하는 제한적인 권한을 명시적으로 설정해야 합니다.

서명되지 않은 트랜잭션은 사실상 런타임으로의 개방된 문입니다. 트랜잭션이 실행되는 조건을 신중히 고려한 후에만 사용해야 합니다. 보호장치 없이는 악의적인 사용자가 오프체인 워커로 위장하고 런타임 스토리지에 액세스할 수 있습니다.

시작하기 전에

시작하기 전에 다음을 확인하세요:

  • Rust 및 Rust 툴체인을 설치하여 Substrate 개발 환경을 구성했습니다.

  • 로컬 블록체인 만들기 튜토리얼을 완료하고 개발자 허브에서 Substrate 노드 템플릿을 로컬에 설치했습니다.

  • FRAME 매크로를 사용하는 방법과 팔렛의 로직을 편집하는 방법에 익숙합니다.

  • 런타임에서 팔렛의 Config 트레이트를 수정하는 방법에 익숙합니다.

튜토리얼 목표

이 튜토리얼을 완료하면 다음을 수행할 수 있습니다:

  • 서명되지 않은 트랜잭션 사용 시 발생할 수 있는 위험을 식별합니다.

  • 팔렛에 오프체인 워커 함수를 추가합니다.

  • 팔렛과 런타임을 구성하여 오프체인 워커가 서명된 트랜잭션을 제출할 수 있도록 합니다.

  • 팔렛과 런타임을 구성하여 오프체인 워커가 서명되지 않은 트랜잭션을 제출할 수 있도록 합니다.

  • 팔렛과 런타임을 구성하여 오프체인 워커가 서명된 페이로드를 포함한 서명되지 않은 트랜잭션을 제출할 수 있도록 합니다.

서명된 트랜잭션

서명된 트랜잭션을 제출하려면 팔렛과 런타임을 구성하여 오프체인 워커가 사용할 수 있는 최소한 하나의 계정을 활성화해야 합니다. 팔렛을 오프체인 워커를 사용하고 서명된 트랜잭션을 제출할 수 있도록 구성하는 데는 다음 단계가 필요합니다:

팔렛에서 오프체인 워커 구성

오프체인 워커가 서명된 트랜잭션을 전송할 수 있도록 하려면:

  1. 팔렛의 src/lib.rs 파일을 텍스트 편집기로 엽니다.

  2. #[pallet::hooks] 매크로와 오프체인 워커의 진입점을 코드에 추가합니다.

    예시:

  3. offchain_worker 함수의 로직을 추가합니다.

  4. 팔렛의 Config 트레이트에 CreateSignedTransaction을 추가합니다. 예시:

  5. 팔렛의 Config 트레이트에 AuthorityId 타입을 추가합니다.

  6. 트랜잭션 서명을 위해 팔렛이 사용할 수 있는 계정을 보유한 crypto 모듈을 추가합니다.

    app_crypto 매크로KEY_TYPE로 식별되는 sr25519 서명을 가진 계정을 선언합니다. 이 예시에서 KEY_TYPEdemo입니다. 이 매크로는 새로운 계정을 생성하지 않습니다. 이 매크로는 팔렛에서 사용할 수 있는 crypto 계정이 있는 것을 선언하는 것입니다.

  7. 오프체인 워커가 사용할 서명된 트랜잭션을 온체인 스토리지에 전송하기 위해 사용할 계정을 초기화합니다.

    이 코드는 팔렛이 소유하는 모든 서명자를 검색할 수 있도록 합니다.

  8. send_signed_transaction()을 사용하여 서명된 트랜잭션 호출을 생성합니다.

  9. 트랜잭션이 온체인에 성공적으로 제출되었는지 확인하고 반환된 results를 통해 적절한 오류 처리를 수행합니다.

런타임에서 팔렛 구현

  1. 텍스트 편집기에서 노드 템플릿의 runtime/src/lib.rs 파일을 엽니다.

  2. 팔렛을 위한 구성에 AuthorityId를 추가하고, crypto 모듈에서 TestAuthId를 사용하도록 런타임 구성을 수정합니다.

  3. 런타임에서 CreateSignedTransaction 트레이트를 구현합니다.

    팔렛에서 CreateSignedTransaction 트레이트를 구현했으므로, 런타임에서도 해당 트레이트를 구현해야 합니다.

    CreateSignedTransaction을 살펴보면 런타임을 위해 create_transaction() 함수만 구현하면 되는 것을 알 수 있습니다. 예시:

    이 코드는 길지만, 기본적으로 다음 주요 단계를 보여줍니다:

    • extraSignedExtra 유형을 생성하고 준비하고, 다양한 체커를 설정합니다.

    • 전달된 callextra를 기반으로 원시(raw) 페이로드를 생성합니다.

    • 계정 공개 키로 원시(raw) 페이로드에 서명합니다.

    • 모든 데이터를 번들로 묶어 호출, 호출자, 서명 및 서명 확장 데이터를 포함하는 튜플을 반환합니다.

    이 코드 예제는 Substrate 코드베이스에서 확인할 수 있습니다.

  4. 런타임에서 SigningTypesSendTransactionTypes를 구현하여 서명된 또는 서명되지 않은 트랜잭션을 제출할 수 있도록 지원합니다.

    이 구현 예제는 Substrate 코드베이스에서 확인할 수 있습니다.

트랜잭션 서명을 위한 계정 추가

이제 팔렛의 오프체인 워커가 서명된 트랜잭션을 제출할 수 있도록 준비되었습니다. 팔렛을 준비하는 데는 다음 단계가 필요합니다:

  • --dev 명령줄 옵션을 사용하여 개발 모드에서 노드를 실행하는 경우, 개발 계정을 위한 계정 키를 수동으로 생성하고 삽입합니다. 이를 위해 node/src/service.rs 파일을 수정합니다.

}

  1. 다음과 같이 트레이트를 구현합니다.

  2. 호출되는 익스트린식을 확인하여 호출이 허용되는지 확인하고, 호출이 허용되는 경우 ValidTransaction을 반환하거나 호출이 허용되지 않는 경우 TransactionValidityError를 반환합니다.

    예시:

    이 예시에서는 사용자가 특정 my_unsigned_tx 함수를 서명 없이 호출할 수 있습니다. 다른 함수를 호출하는 경우 서명된 트랜잭션이 필요합니다.

    팔렛에서 ValidateUnsigned을 구현하는 예제는 offchain-worker의 코드를 참조하세요.

  3. #[pallet::hooks] 매크로와 offchain_worker 함수를 추가하여 서명되지 않은 트랜잭션을 전송합니다.

    이 코드는 let call = ... 줄에서 호출을 준비하고, SubmitTransaction::submit_unsigned_transaction을 사용하여 트랜잭션을 제출하고, 콜백 함수에서 필요한 오류 처리를 수행합니다.

런타임 구성

  1. 런타임에서 팔렛에 ValidateUnsigned 트레이트를 활성화하기 위해 construct_runtime 매크로에 ValidateUnsigned 타입을 추가합니다.

    예시:

  2. 서명된 트랜잭션 제출에서 설명한 대로 런타임에 ValidateUnsigned 타입을 추가합니다.

    전체 예시는 offchain-worker 예제 팔렛을 참조하세요.

서명된 페이로드

서명된 페이로드를 사용하여 서명되지 않은 트랜잭션을 전송하는 방법은 서명되지 않은 트랜잭션을 전송하는 방법과 유사합니다. 트랜잭션을 전송하려면 다음 단계를 수행해야 합니다.

  • 팔렛에 ValidateUnsigned 트레이트를 구현합니다.

  • 런타임에서 이 팔렛에 ValidateUnsigned 타입을 추가합니다.

  • 서명할 데이터 구조, 즉 서명된 페이로드를 준비하기 위해 SignedPayload 트레이트를 구현합니다.

  • 서명된 페이로드로 트랜잭션을 전송합니다.

자세한 내용은 서명되지 않은 트랜잭션을 전송하는 방법에 대한 섹션을 참조하세요.

서명되지 않은 트랜잭션은 항상 잠재적인 공격 경로를 제공하며, 오프체인 워커가 신뢰할 수 있는 소스로 가정할 수 없으므로 트랜잭션을 제출하기 전에 추가적인 보호장치 또는 검증 로직을 구현해야 합니다.

서명되지 않은 트랜잭션을 전송하는 방법과 서명된 페이로드를 포함한 서명되지 않은 트랜잭션을 전송하는 방법의 차이점은 다음 코드 예제에서 설명합니다.

데이터 구조를 서명할 수 있도록 하려면:

  1. SignedPayload을 구현합니다.

    예시:

서명된 페이로드의 예제는 offchain-worker의 코드를 참조하세요.

  1. offchain_worker 함수에서 서명자를 호출한 다음 트랜잭션을 전송하는 함수를 호출합니다.

    이 코드는 signer를 검색한 다음 send_unsigned_transaction()을 두 개의 함수 클로저와 함께 호출합니다. 첫 번째 함수 클로저는 사용할 페이로드를 반환하고, 두 번째 함수 클로저는 페이로드와 서명이 포함된 온체인 호출을 반환합니다. 이 호출은 Option<(Account<T>, Result<(), ()>)> 결과 타입을 반환하여 다음 결과를 처리할 수 있습니다:

    • 트랜잭션을 전송할 수 있는 계정이 없는 경우 None입니다.

    • 트랜잭션이 성공적으로 전송된 경우 Some((account, Ok(())))입니다.

    • 트랜잭션 전송 중 오류가 발생한 경우 Some((account, Err(())))입니다.

  2. 제공된 서명이 페이로드를 서명한 공개 키와 일치하는지 확인합니다.

fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { let valid_tx = |provide| ValidTransaction::with_tag_prefix("ocw-demo") .priority(UNSIGNED_TXS_PRIORITY) .and_provides([&provide]) .longevity(3) .propagate(true) .build();

} }

이 예제는 SignedPayload을 사용하여 페이로드의 공개 키가 제공된 시그니처와 동일한지 확인합니다. 그러나 이 예제의 코드는 제공된 signaturepayload 내에 포함된 public 키에 대해 유효한지만 확인합니다. 이 검사는 사인한 페이로드를 사용하여 상태를 수정하는 권한이 있는지 여부를 확인하지 않습니다. 이 간단한 검사는 권한이 없는 사용자가 사인된 페이로드를 사용하여 상태를 수정하는 것을 방지하지 않습니다.

이 코드의 작동 예제는 오프체인 함수 호출ValidateUnsigned의 구현을 참조하십시오.

다음으로 어디로 가야 할까요?

이 자습서에서는 오프체인 워커를 사용하여 온체인 저장소에 트랜잭션을 전송하는 간단한 예제를 제공합니다. 더 자세한 내용은 다음 리소스를 참조하십시오:

Last updated