폴카닷에서 무료 트랜잭션을 보내는 방법

CREAM ER
Equilibrium
Published in
12 min readSep 6, 2021

블록체인 네트워크는 전체 네트워크를 작동시키는 노드와 채굴자에게 인센티브를 제공하기 위해 트랜잭션 수수료에 의존합니다. 이러한 트랜잭션 수수료는 악의적인 해커의 DDoS 공격으로부터 네트워크를 보호하는 역할도 합니다. 각 트랜잭션에 일정 금액의 비용이 든다면 관리할 수 없는 트랜잭션 활동으로 네트워크를 무력화시키는 것이 쉽지 않습니다.

그러나 폴카닷에서 0달러의 비용으로 트랜잭션을 보낼 수 있는 특정 방법이 있습니다. 제로 수수료 트랜잭션이 의미가 있는 경우와 사이버 공격으로부터 보호하면서 실제로 구현하는 방법에 대해 논의해 보겠습니다.

왜 폴카닷에서 수수료가 없는 트랜잭션을 보내고 싶습니까?

트랜잭션 수수료를 없애는 것이 합리적인 두 가지 주요 경우가 있습니다. 첫 번째는 신규 사용자를 온보딩하는 것이고 두 번째는 시스템의 비즈니스 로직을 유지하기 위한 기술적 조치를 취하는 것입니다.

신규 사용자가 이미 토큰을 보유하고 있고 이를 청구하려는 경우 트랜잭션 수수료를 지불할 계정이 없는 문제에 직면합니다. 이러한 토큰이 초기 제안, 잠금 드롭 또는 기타 관련 메커니즘을 통해 배포되었는지 여부에 관계없이 수수료가 없는 트랜잭션은 해당 사용자에게 토큰 할당을 전달하는 문제를 해결합니다.

이러한 종류의 트랜잭션은 또한 예를 들어 스테이킹 보상 분배를 결정하는 것과 같은 몇 가지 배후의 기술 목적을 수행합니다. DeFi 시스템이 오라클로부터 관련 가격을 얻고 즉시 개최되는 청산 경매를 개최하여 최신 상태를 유지하는 것이 중요합니다. 그러나 표준 블록체인에서 이러한 종류의 작업은 트랜잭션 수수료가 필요한 일반적인 트랜잭션일 뿐입니다. 누군가는 시스템을 지원하기 위해 이러한 비용을 지불해야 하며 많은 소스에서 최신 가격을 유지하는 데 상당한 비용이 소요될 수 있습니다. 이익이 네트워크의 트랜잭션 비용보다 적으면 경매에서 부채를 갚는 것도 수익성이 없습니다.

고맙게도 서브스트레이트 블록체인을 사용하면 이러한 종류의 중요한 트랜잭션을 무료로 수행할 수 있으므로 시스템을 고객에게 더 저렴하고 안정적으로 만들 수 있습니다.

그렇다면 DDoS 공격으로부터 안전하게 보호하면서 이 작업을 수행하려면 어떻게 해야 할까요?

존재하는 세 가지 유형의 무료 트랜잭션에 대해 살펴보고 이러한 트랜잭션이 사이버 공격을 얼마나 효과적으로 방지하는지 살펴보겠습니다. 이러한 트랜잭션은 서명된 유료 리펀드 트랜잭션, 무료 서명 트랜잭션 및 서명된 페이로드가 있는 서명되지 않은 트랜잭션입니다.

서명된 유료 리펀드 트랜잭션

이것은 아마도 무료 트랜잭션을 구현하는 가장 쉬운 방법일 것입니다. 서브스트레이트 리포지토리에서 sudo 팔레트를 사용하는 것과 같이 기존 사용자가 비용을 지불하지 않고 기술적 조치를 취하려는 경우에 가장 적합합니다.

#[pallet::weight({
let dispatch_info = call.get_dispatch_info();(dispatch_info.weight.saturating_add(10_000), dispatch_info.class)})]pub fn sudo(origin: OriginFor<T>,call: Box<<T as Config>::Call>,) -> DispatchResultWithPostInfo {// This is a public call, so we ensure that the origin is some signed account.let sender = ensure_signed(origin)?;ensure!(sender == Self::key(), Error::<T>::RequireSudo);let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());Self::deposit_event(Event::Sudid(res.map(|_| ()).map_err(|e| e.error)));// Sudo user does not pay a fee.Ok(Pays::No.into())}

여기서 주요 아이디어는 실제로 트랜잭션이 시작될 때 사용자로부터 수수료를 받지만 성공적인 트랜잭션이 끝나면 해당 자금이 다시 사용자에게 반환된다는 것입니다.

이는 유효하지 않은 트랜잭션을 보내는 잠재적인 공격자만이 실제로 수수료를 지불한다는 것을 의미합니다.

무료 서명 트랜잭션
이퀼리브리엄 네트워크에서 EQ 토큰을 락업하는 대가로 GENS 토큰을 얻는 예를 생각해보세요.

#[pallet::weight((10_000 + T::DbWeight::get().writes(1), Pays::No))]
pub fn claim(origin: OriginFor<T>) -> DispatchResultWithPostInfo {let who = ensure_signed(origin)?;Self::do_claim(who)?;Ok(().into())}

이 무료 트랜잭션 방법은 호출 계정이 신규이고 트랜잭션 수수료를 지불할 자산이 없을 때 적합합니다. (이것은 일반적으로 새 계정에 대한 토큰을 받기 위한 요청입니다.) 여기서 DDoS 취약점이 가장 눈에 띕니다. 즉, 새로 생성된 계정은 비용을 지불하지 않고도 트랜잭션으로 전체 블록을 포화시킬 수 있습니다. 이 문제를 방지하려면 다음 유형의 SignedExtension을 만들어야 합니다.:

pub struct CheckAllocation<T: Config + Send + Sync>(PhantomData<T>);
impl<T: Config + Send + Sync> SignedExtension for CheckAllocation<T>whereT::Call: IsSubType<Call<T>>,{type AccountId = T::AccountId;type Call = T::Call;type AdditionalSigned = ();type Pre = ();const IDENTIFIER: &’static str = “CheckAllocation”;fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {Ok(())}fn validate(&self,who: &Self::AccountId,call: &Self::Call,_info: &DispatchInfoOf<Self::Call>,_len: usize,) -> TransactionValidity {match call.is_sub_type() {Some(Call::claim(..)) => {if !Allocations::<T>::contains_key(&who) ||ClaimStart::<T>::get().is_none() {InvalidTransaction::Call.into()} else {Ok(Default::default())}}_ => Ok(Default::default())}}}

이제 트랜잭션이 트랜잭션 풀에 추가되면 트랜잭션의 유효성이 확인됩니다. 우리의 경우 계정에 실제로 할당이 있는지 확인합니다. validate 메소드는 런타임 메소드 자체에서 발견된 것과 정확히 동일한 검사를 포함해야 합니다. 다른 호출은 기본값이어야 합니다. — 위의 코드를 참조하십시오.

서명된 페이로드가 있는 서명되지 않은 트랜잭션
이것은 무료 트랜잭션을 생성하는 가장 유연한 방법이지만 구현하기 가장 어렵습니다. 작동 방식은 다음과 같습니다.

요청을 보내는 계정에는 자금이 없을 수 있으며, 이는 해당 자금이 도난당할 위험이 없기 때문에 기술 노드에 매우 편리합니다. 발신자와 독립적으로 기술적 조치를 최적으로 처리할 가능성이 있습니다. 예를 들어, 이퀼리브리엄의 대출 플랫폼에서 고객의 위치에 대한 마진 콜 요청을 보낸 사람이 누구인지는 중요하지 않습니다. 요청이 유효하고 정확히 하나의 요청이 있다는 것만 중요합니다. 무료 서명된 트랜잭션을 사용했다면 다른 계정에서 보낸 한 클라이언트에 대한 여러 마진 콜이 트랜잭션 풀에 포함될 수 있습니다. 원하는 순서나 우선순위로 트랜잭션을 정렬할 수 있습니다. 예를 들어, 우리는 모든 마진 콜 트랜잭션이 블록의 첫 번째 트랜잭션이 되기를 원합니다.

그러나 이러한 기회 각각에는 몇 가지 잠재적인 위험과 취약점이 있습니다.
무료 서명된 트랜잭션과 마찬가지로 트랜잭션을 검증할 때 런타임에서 모든 검사를 복제하는 것이 중요합니다. 트랜잭션이 서명되지 않았기 때문에 nonces 기능을 사용할 수 없습니다. 우리는 동일한 트랜잭션을 “재생”하고 필요한 트랜잭션 시퀀스를 직접 구축하기 위한 검사를 수행해야 합니다. 그리고 트랜잭션 우선 순위 지정에 실수를 하면 기술적인 조치만으로 전체 트랜잭션 풀을 스팸 처리하여 다른 모든 사람의 나머지 기능을 방해할 수 있습니다.

이퀼리브리엄 및 겐시로에 대한 오라클 구현을 더 자세히 살펴보겠습니다.

impl<T: Config> frame_support::unsigned::ValidateUnsigned for Pallet<T> {type Call = Call<T>;fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {if let Call::set_price_unsigned(payload, signature) = call {let signature_valid =SignedPayload::<T>::verify::<T::AuthorityId>(payload, signature.clone());if !signature_valid {return InvalidTransaction::BadProof.into();}let current_block = <frame_system::Pallet<T>>::block_number();if payload.block_number > current_block {// transaction in future?return InvalidTransaction::Stale.into();} else if payload.block_number + 5u32.into() < current_block {// transaction was in pool for 5 blocksreturn InvalidTransaction::Stale.into();}let account = payload.public.clone().into_account();Self::validate_params(account, payload.asset, payload.price).map_err(|_| InvalidTransaction::Call)?;let priority = T::UnsignedPriority::get().saturating_add(// BlockNumber is less than u64::MAX, so it is never default value on unwrappingTryInto::<u64>::try_into(payload.block_number).unwrap_or(0));ValidTransaction::with_tag_prefix("EqPrice").priority(priority).and_provides((payload.public.clone(), payload.asset)).longevity(5).propagate(true).build()} else {InvalidTransaction::Call.into()}}}

유효성 검사 방법에서 단일 방법에 대해서만 서명되지 않은 트랜잭션을 허용하고 나머지 방법에는 즉시 오류를 보내도록 해야 합니다.

또한 서명이 유효한지 확인해야 합니다. 이 확인은 PoS 또는 PoA 시스템과 같은 특정 유형의 계정에 대해서만 트랜잭션이 허용되는 경우에 필요합니다. 우리는 트랜잭션이 5블록 이하 동안 풀에 머물도록 허용합니다. 그 이후에는 가격이 구식이라고 판단합니다. 이것은 트랜잭션이 풀에 무한히 추가될 수 있는 경우를 제거합니다. (다른 모든 검사가 올바르게 수행되면 이 검사가 필요하지 않지만 항상 다시 확인하는 것이 좋습니다.)

런타임에 검증되는 것과 동일한 방식으로 트랜잭션을 검증해야 합니다. 우리의 경우 모든 검사는 검사가 100% 동일하도록 별도의 메서드인 validate_params로 이동됩니다.
마지막으로 태그와 우선순위를 설정해야 합니다. 이것이 이전 구현과의 가장 큰 차이점입니다. 우리의 경우 풀의 하나의 통화에 대해 하나의 계정에서 둘 이상의 트랜잭션을 원하지 않습니다. 이것은 풀에 대해 고유한 값으로 and_provides를 설정하여 달성됩니다(이 매개변수는 각 팔레트에 대해 고유한 값을 갖습니다). 또한 풀 내에 동일한 and_provides 속성이 있는 여러 트랜잭션이 있는 경우 트랜잭션에서 최신 가격을 가져오려고 합니다. 우리는 올바른 우선 순위를 설정하여 이를 달성합니다. 우리의 경우 트랜잭션이 포함된 블록이 높을수록 우선 순위가 높아집니다.

결론은 다음과 같습니다. 각 팔레트에 대한 올바른 구현에 대해 신중하게 생각해야 합니다. 이는 개발 시간에 영향을 미치고 실수할 가능성을 높입니다. 이 접근 방식은 위에서 설명한 처음 두 가지 접근 방식이 부족한 경우에만 사용해야 합니다.

서브스트레이트의 무료 트랜잭션 시스템 안정성을 유지하고 신규 고객을 유치하기 위한 강력한 도구이지만, 이를 구현하는 올바른 방법을 선택하는 것이 매우 중요합니다. 그렇지 않으면 한 번의 작은 실수로 DDoS 공격자가 될 수 있습니다.

Follow us:

Website | Twitter | Telegram |Facebook

--

--