TON의 샤드 최적화
아키텍처 기본 사항
TON은 수많은 트랜잭션을 병렬로 처리하도록 설계되었습니다. 이 기능은 무한 샤딩 패러다임을 기반으로 하며, 이는 검증자 그룹에 대한 부하가 처리량 한계에 도달하면 분할(샤딩)된다는 것을 의미합니다. 두 검증자 그룹이 이 부하를 독립적으로 병렬 처리합니다. 이러한 분할은 결정적으로 발생하며, 트랜잭션이 특정 그룹에 의해 처리되는지 여부는 트랜잭션과 연관된 컨트랙트 주소에 따라 달라집니다. 서로 가까운(동일한 접두사를 공유하는) 주소는 동일한 샤드에서 처리됩니다.
한 컨트랙트에서 다른 컨트랙트로 메시지를 보낼 때는 두 가지 가능성이 있습니다: 두 컨트랙트가 동일한 샤드에 있거나, 서로 다른 샤드에 있는 경우입니다. 전자의 경우, 현재 그룹은 필요한 모든 데이터를 이미 가지고 있어 즉시 메시지를 처리할 수 있습니다. 후자의 경우, 메시지는 한 그룹에서 다른 그룹으로 라우팅되어야 합니다. 메시지 손실이나 이중 처리를 방지하기 위해 적절한 회계가 필요합니다. 이는 발신자 샤드의 발신 메시지 큐를 마스터체인 블록에 등록하고, 수신자 샤드가 이 큐를 처리했음을 명시적으로 확인함으로써 이루어집니다. 이러한 오버헤드로 인해 크로스-샤드 메시지 전달이 더 느려집니다; 메시지가 전송된 블록과 소비된 블록 사이에 최소한 하나의 마스터체인 블록이 필요합니다. 이 지연은 보통 12-13초 정도입니다.
한 계정의 트랜잭션은 항상 하나의 샤드에서 처리되므로 단일 계정의 초당 트랜잭션(TPS) 속도는 제한됩니다. 이는 새로운 대규모 프로토콜을 위한 아키텍처를 개발할 때 중앙 집중점을 피하도록 노력해야 한다는 것을 의미합니다. 또한 트랜잭션 체인이 동일한 경로를 따르는 경우, 샤딩으로 인해 더 빨리 처리되지 않을 것입니다: 체인의 각 컨트랙트에 대한 TPS 제한은 동일하지만, 전달 지연으로 인해 전체 체인 처리 시간이 더 길어질 것입니다.
대규모 시스템에서 지연 시간과 처리량 사이의 트레이드오프는 좋은 프로토콜과 훌륭한 프로토콜을 구분하는 포인트가 됩니다.
샤딩 또는 샤딩하지 않기
사용자 경험과 처리 시간을 개선하기 위해, 프로토콜은 시스템의 어느 부분이 병렬로 처리될 수 있어 처리량을 개선하기 위해 샤딩되어야 하는지, 그리고 어느 부분이 순차적이어서 하나의 샤드에 배치하면 지연 시간이 낮아질지 이해해야 합니다.
처리량 최적화의 좋은 예는 Jetton입니다. A에서 B로의 전송과 C에서 D로의 전송은 서로에게 의존하지 않으므로 병렬로 처리될 수 있습니다. 모든 jetton-wallet을 주소 공간에 무작위로 균일하게 분산시킴으로써 블록체인 전체에 걸쳐 부하를 완벽하게 분산시킬 수 있으며, 적절한 지연 시간으로 초당 수백 번의 전송(미래에는 수천 번) 처리량을 달성할 수 있습니다.
반대로, jetton을 다루는 다른 스마트 컨트랙트, 예를 들어 컨트랙트 A가 jetton X를 받을 때 무언가를 하는 경우(A의 jetton-wallet 컨트랙트는 B), 컨트랙트 A와 지갑 B를 서로 다른 샤드에 배치하는 것은 처리량을 증가시키지 않을 것입니다. 실제로 각 수신 전송은 동일한 주소 체인을 통과하며, 각 주소가 병목 지점이 될 것입니다. 이 시나리오에서는 A와 B를 같은 샤드에 배치하여 지연 시간을 개선하는 것이 효율적이며, 이를 통해 전체 체인 시간을 줄일 수 있습니다.
스마트 컨트랙트 개발자를 위한 실용적 결론
비즈니스 로직을 실행하는 단일 스마트 컨트랙트가 있는 경우, TON의 병렬성을 활용하기 위해 이러한 컨트랙트를 여러 개 배포하는 것을 고려하세요. 이것이 불가능하고 스마트 컨트랙트가 미리 정의된 다른 스마트 컨트랙트 세트(예: jetton-wallet)와 상호작용하는 경우, 이들을 같은 샤드에 배치하는 것을 고려하세요. 이는 종종 오프체인에서 수행될 수 있으며(모든 원하는 jetton-wallet이 인접한 주소를 가지도록 특정 컨 트랙트 주소를 브루트 포스), 때로는 온체인 브루트 포스도 허용됩니다.
노드와 네트워크 성능의 향후 개선은 한 샤드의 처리량을 증가시키고 전달 지연을 줄일 것으로 예상되지만, 이는 사용자 수의 증가와 함께 진행될 것입니다. 더 많은 사용자가 참여함에 따라 샤드 최적화는 점점 더 중요해질 것입니다. 결국 이는 대규모 애플리케이션의 성패를 가르는 요소가 될 것입니다: 사용자들은 자신에게 가장 편리한 애플리케이션, 즉 지연 시간이 더 낮은 애플리케이션을 선택할 것입니다. 그러므로 전체 네트워크 개선을 기다리지 말고 지금 애플리케이션을 샤드-최적화하세요! 많은 경우에 이는 가스 최적화보다 더 중요할 수 있습니다.
서비스를 위한 실용적 결론
입금
초당 30회 이상의 전송률로 입금이 예상되는 경우, 병렬로 수락하고 높은 처리량을 즐길 수 있도록 여러 주소를 가지는 것이 좋습니다. TON Connect를 통해 시작된 트랜잭션처럼 사용자가 입금할 주소를 알고 있는 경우, 사용자의 지갑 주소와 가장 가까운 입금 주소를 선택하세요. 가장 가까운 주소를 선택하기 위한 사용 가능한 Typescript 코드는 다음과 같을 수 있습니다:
import { Address } from '@ton/ton';
function findMatchingBits (a: number, b: number, start_from: number) {
let bitPos = start_from;
let keepGoing = true;
do {
const bitCount = bitPos + 1;
const mask = (1 << (bitCount)) - 1;
const shift = 8 - bitCount;
if(((a >> shift) & mask) == ((b >> shift) & mask)) {
bitPos++;
}
else {
keepGoing = false;
}
} while(keepGoing && bitPos < 7);
return bitPos;
}
function chooseAddress(user: Address, contracts: Address[]) {
const maxBytes = 32;
let byteIdx = 0;
let bitIdx = 0;
let bestMatch: Address | undefined;
if(user.workChain !== 0) {
throw new TypeError(`Only basechain user address allowed:${user}`);
}
for(let testContract of contracts) {
if(testContract.workChain !== 0) {
throw new TypeError(`Only basechain deposit address allowed:${testContract}`);
}
if(byteIdx >= maxBytes) {
break;
}
if(byteIdx == 0 || testContract.hash.subarray(0, byteIdx).equals(user.hash.subarray(0, byteIdx))) {
let keepGoing = true;
do {
if(keepGoing && testContract.hash[byteIdx] == user.hash[byteIdx]) {
bestMatch = testContract;
byteIdx++;
bitIdx = 0;
if(byteIdx == maxBytes) {
break;
}
}
else {
keepGoing = false;
if(bitIdx < 7) {
const resIdx = findMatchingBits(user.hash[byteIdx], testContract.hash[byteIdx], bitIdx);
if(resIdx > bitIdx) {
bitIdx = resIdx;
bestMatch = testContract;
}
}
}
} while(keepGoing);
}
}
return {
match: bestMatch,
prefixLength: byteIdx * 8 + bitIdx
}
}
jetton의 입금이 예상되는 경우, 여러 입금 주소를 생성하는 것 외에도, 이러한 주소를 샤드-최적화하는 것이 좋습니다: 각 입금 주소가 jetton-wallet과 같은 샤드에 있도록 주소를 선택하세요. 이러한 주소의 생성기는 여기에서 찾을 수 있습니다. 사용자와 가장 가까운 주소를 선택하는 것도 효율적일 것 입니다.
출금
출금에도 동일한 원칙이 적용됩니다; 초당 많은 수의 전송을 보내야 하는 경우, 여러 개의 발신 주소를 가지고 필요한 경우 jetton-wallet과 함께 샤드-최적화하는 것이 좋습니다.
샤드 최적화 101
web 2 관점에서 샤드 설명
TON 블록체인은 다른 블록체인과 마찬가지로 네트워크이므로, web2(ipv4) 네트워킹 용어로 설명하는 것이 타당합니다.
엔드포인트
일반 네트워킹에서 엔드포인트는 물리적 장치이고, 블록체인에서는 스마트 컨트랙트입니다.
샤드
이러한 관점에서 샤드는 서브넷에 불과합니다. ipv4는 32비트 주소 체계를 가지고 TON은 256비트를 가진다는 점만 다릅니다. 따라서 컨트랙트 주소 샤드 접두사는 컨트랙트 주소의 일부로, 수신 메시지 결과를 계산할 검증자 그룹을 식별합니다. 네트워킹 관점에서 보면, 같은 네트워크 세그먼트의 요청이 다른 곳으로 라우팅되는 것보다 더 빨리 처리될 것이라는 점이 분명합니다. CDN을 사용하여 최종 사용자와 더 가까운 곳에 콘텐츠를 호스팅하는 것과 비슷하게, TON에서는 최종 사용자와 더 가까운 곳에 컨트랙트를 배포합니다.
샤드의 부하가 특정 수준을 초과하면 샤드가 분할됩니다. 목표는 부하가 걸린 컨트랙트에 전용 계산 리소스를 제공하고 전체 네트워크에 미치는 영향을 격리하는 것입니다. 현재 최대 샤드 접두사 길이는 4비트이므로 블록체인은 접두사 0부터 15까지 최대 16개의 샤드로 분할될 수 있습니다.
샤드 최적화 중 발생하는 문제들
더 실용적으로 살펴보겠습니다
두 주소가 같은 샤드에 속하는지 확인하기
샤드 접두사가 최대 4비트라는 것을 알고 있으므로, 코드는 다음과 같을 수 있습니다:
import { Address } from '@ton/core';
const addressA = Address.parse(...);
const addressB = Address.parse(...);
if((addressA.hash[0] >> 4) == (addressb.hash[0] >> 4)) {
console.log("Same shard");
} else {
console.log("Nope");
}
사람의 관점에서 주소의 샤드를 확인하는 가장 쉬운 방법은 주소의 원시 형태를 보는 것입니다.
주소 페이지를 사용할 수 있습니다.
예를 들어 USDT 주소로 테스트해보겠습니다: EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs
.
0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe
를 원시 표현으로 볼 수 있으며 처음 4비트는 본질적으로 첫 번째 16진수 기호인 b
입니다.
이제 USDT 민터가 샤드 b
(16진수) 또는 11
(10진수)에 위치해 있다는 것을 알 수 있습니다.
특정 샤드에 컨트랙트를 배포 하는 방법
이것이 어떻게 작동하는지 이해하려면 컨트랙트 주소가 코드와 데이터에 어떻게 의존하는지 이해해야 합니다.
본질적으로 이는 배포 시점의 코드와 데이터의 SHA256 해시입니다.
이를 알면, 다른 샤드에 동일한 코드를 가진 컨트랙트를 배포하는 유일한 방법은 초기 데이터를 조작하는 것입니다.
결과 컨트랙트 주소를 조작하는 데 사용되는 데이터 필드를 _nonce_라고 합니다.
배포 후 업데이트해도 안전하거나 컨트랙트 실행에 직접적인 영향을 미치지 않는 필드는 이러한 목적으로 사용될 수 있습니다.
이 원칙을 사용한 최초의 알려진 컨트랙트 중 하나는 vanity contract입니다.
이는 salt
데이터 필드를 가지고 있으며, 그 유일한 목적은 원하는 주소 패턴이 나오는 값을 _브루트 포스_하는 것입니다.
특정 샤드에 컨트랙트를 넣는 것도 정확히 같은 방식으로 수행되지만, 일치시켜야 하는 접두사가 훨씬 짧습니다.
시작하기 가장 쉬운 예시 중 하나는 지갑 컨트랙트입니다.
- 다른 샤드를 위한 지갑 생성 문서는 공개키를 nonce로 사용하여 지갑을 특정 샤드에 넣는 경우를 보여줍니다.
- 다른 예시는 turbo-wallet이 subwalletId를 같은 목적으로 사용하는 것입니다. 컨트랙트 생성자를 사용하여 ShardedContract 인터페이스를 빠르게 확장하여 샤딩된 계약으로 만들 수 있습니다.
대규모 Jetton 배포 솔루션
수만/수십만 또는 수백만 명의 사용자에게 jetton을 배포해야 하는 경우 이 게시물을 확인하세요. 기존의 검증된 서비스를 고려할 것을 제안합니다. 일부는 깊이 최적화되어 있어 샤드 최적화될 뿐만 아니라 수제 솔루션보다 저렴할 것입니다:
- Mintless Jettons: Token Generation Event(TGE) 동안 jetton을 배포해야 할 때, 사용자가 jetton-wallets 컨트랙트에서 직접 미리 정의된 에어드롭을 청구할 수 있게 할 수 있습니다. 이는 저렴하고, 추가 트랜잭션이 필요하지 않으며, 온디맨드로 이용 가능합니다(jetton을 지금 사용해야 하는 사용자만 청구할 것입니다). [LINK]
- Jetton 대량 전송을 위한 Tonapi 솔루션: 기존 jetton을 사용자 지갑으로 직접 전송하여 배포할 수 있습니다. Notcoin과 DOGS(각각 수백만 건의 전송)에 의해 검증되었으며, 지연 시간, 처리량, 비용을 줄이도록 최적화되어 있습니다. Mass jetton sending
- 분산 청구를 위한 TokenTable 솔루션: 사용자가 특정 청구 트랜잭션에서 jetton을 청구할 수 있게 합니다(사용자가 수수료를 지불). Avacoin과 DOGS(수백만 건의 전송)에 의해 검증되었으며, 처리량과 비용을 증가시키도록 최적화되어 있습니다. 소개