본문으로 건너뛰기

gRPC 사용하기

이 가이드는 gRPC를 사용해 Sui network를 질의하는 실용적인 예시를 제공한다. 핵심 개념은 corresponding concepts page를 참조한다.

시작하기 전에 gRPC가 활성화된 Sui full node에 접근할 수 있는지 확인한다. full node에서 gRPC를 지원하는 list of RPC and data providers를 확인하고, access를 요청하려면 provider에 직접 연락한다.

provider가 아직 gRPC를 지원하지 않는다면 다음과 같이 할 수 있다:

  • 이를 활성화해 달라고 요청한다.

  • 도움을 받기 위해 Discord 또는 Telegram의 Sui Foundation team에 연락한다.

Field masks

protocol buffer의 FieldMask는 message 안의 field 부분집합을 읽거나, 업데이트하거나, 반환하도록 지정하는 메커니즘이다. 전체 object를 가져오는 대신 client는 field path 목록을 제공해 필요한 field만 요청할 수 있다. 이렇게 하면 성능이 향상되고 불필요한 데이터 전송이 줄어든다.

Sui gRPC API에서 FieldMaskGetTransaction, GetObject 같은 request에 사용되어 응답에 어떤 부분을 포함할지 제어한다(transaction의 effectsevents 같은 부분).

Field mask는 google.protobuf.FieldMask를 사용해 정의되며, 보통 request message 안의 read_mask로 나타난다. 모든 field를 요청하려면 *를 명시적으로 전달할 수 있다.

read_mask를 생략하면 특별히 다르게 문서화되지 않은 한 기본값은 *(모든 field)이다.

  • mask의 각 field path는 응답 proto message의 field 구조와 일치해야 한다. nested field는 dot 표기법으로 지원된다.

  • batch API에서는 최상위 read_mask만 존중된다. API는 sub-request 안의 mask는 무시한다.

  • 어떤 경우에는 standard FieldMask 동작으로는 전형적이지 않더라도, non-terminal repeated field가 mask에서 지원될 수 있다.

Field presence

Sui와 함께 gRPC를 사용할 때는 특히 proto3 syntax를 다룰 때 field presence가 어떻게 동작하는지 이해하는 것이 중요하다. proto3에서는 숫자, Boolean, string 같은 primitive field가 message에 없으면 항상 기본값으로 초기화된다. 즉 값이 명시적으로 설정된 것인지 아니면 단순히 생략된 것인지를 구분할 수 없다. 이 구분을 제공하기 위해 Sui는 API에서 required하더라도 모든 field를 optional로 표시한다.

API 사용자 입장에서는 이것이 field 값이 실제로 제공된 것인지 아니면 단순히 기본값인지 감지할 수 있게 해 주며, 명시적으로 빈 입력과 누락된 입력을 구분하는 의도를 시뮬레이션하거나 partial update를 수행하는 client를 작성할 수 있게 해 준다.

정보

proto에서 field가 optional로 표시되어 있더라도 request가 유효하려면 여전히 required일 수 있다. 이것은 protobuf의 특성이며 실제 business logic을 나타내는 것은 아니다.

Encoding

Sui gRPC API에서 standard human-readable format을 가지는 identifier는 proto schema에서 string으로 표현된다:

  • AddressObjectId: 앞에 0x가 붙은 64개의 hexadecimal 문자로 표현된다.

  • Digest: Base58로 표현된다.

  • TypeTagStructTag: canonical string format으로 표현된다(예: 0x0000000000000000000000000000000000000000000000000000000000000002::coin::Coin<0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI>)

Pagination

account balance나 owned object처럼 데이터 목록을 반환하는 gRPC API를 사용할 때는 보통 pagination을 처리해야 한다. 이 API는 결과를 chunk 단위로 반환하고 다음 batch를 요청하는 데 도움이 되는 token을 포함한다.

request에서는 page_size를 제공해 몇 개의 항목을 반환받을지 제어한다. 이를 비워 두거나 0으로 설정하면 API는 적절한 기본값을 사용한다. request에 page_token을 포함할 수도 있는데, 이는 server에 어디서부터 이어서 반환할지 알려 준다. 이 token은 이전 응답에서 받는다.

응답에는 결과 목록과 next_page_token 값이 포함되며, 다음 page를 가져오기 위해 이를 다음 request에 전달할 수 있다. server가 비어 있는 next_page_token을 반환하면 목록의 끝에 도달한 것이다.

paginated call 사이에는 request의 나머지 parameter를 모두 동일하게 유지해야 한다. 그렇지 않으면 server가 INVALID_ARGUMENT error로 request를 거부할 수 있다.

Errors

Sui gRPC service는 AIP-193에 정의된 richer error model을 따른다. RPC가 non-OK status code를 반환할 때는 보통 자세한 error 정보가 grpc-status-details-bin header에 포함된다. 이 header에는 Base64로 인코딩된 google.rpc.Status message가 들어 있다.

이 message를 decode하면 특정 원인, context, metadata를 포함할 수 있는 구조화된 error detail에 접근할 수 있다. 이 덕분에 client application에서 error를 이해하고 프로그래밍 방식으로 처리하기가 더 쉬워진다.

HTTP headers

많은 gRPC 응답에서 Sui API는 추가 metadata를 HTTP header 형태로 포함한다. 이 header는 현재 network 상태에 대한 contextual information을 제공하며, debugging, telemetry, 또는 데이터 신선도 이해에 유용할 수 있다.

마주칠 수 있는 header는 다음과 같다:

  • x-sui-chain-id: 현재 network의 chain ID.

  • x-sui-chain: 현재 network의 human-readable name(mainnet, testnet, 또는 devnet).

  • x-sui-checkpoint-height: 응답 시점의 최신 checkpoint 높이.

  • x-sui-lowest-available-checkpoint: 여전히 transaction checkpoint 데이터를 질의할 수 있는 가장 이른 checkpoint.

  • x-sui-lowest-available-checkpoint-objects: object 데이터(input 및 output)를 사용할 수 있는 가장 이른 checkpoint.

  • x-sui-epoch: 현재 network의 epoch.

  • x-sui-timestamp-ms: Unix epoch 이후 millisecond 단위의 network timestamp.

  • x-sui-timestamp: Unix epoch 이후 millisecond 단위 network timestamp의 human-readable RFC 3339 format.

모든 API 응답에 모든 header가 반드시 존재하는 것은 아니다. 주어진 RPC에 적용될 때만 포함된다.

Access data using grpcurl

grpcurl을 사용해 gRPC와 상호작용한다.

List available gRPC services

$ grpcurl <full node URL:port> list

Sui Foundation이 관리하는 full node의 port는 443이다.

List available APIs in the LedgerService

$ grpcurl <full node URL:port> list sui.rpc.v2.LedgerService

Get the events and effects details of a particular transaction

$ grpcurl -d '{ "digest": "J4NvV5iQZQFm1xKPYv9ffDCCPW6cZ4yFKsCqFUiDX5L4" }' <full node URL:port> sui.rpc.v2.LedgerService/GetTransaction

Get the transactions in a particular checkpoint

$ grpcurl -d '{ "sequence_number": "164329987", "read_mask": { "paths": ["transactions"]} }' <full node URL:port> sui.rpc.v2.LedgerService/GetCheckpoint

Get the latest information for a coin type

$ grpcurl -d '{ "coin_type": "0x2::sui::SUI" }' <full node URL:port> sui.rpc.v2.StateService/GetCoinInfo

List the objects owned by a particular address

$ grpcurl -d '{ "owner": "0x94096a6a54129234237759c66e6ef1037224fb3102a0ae29d33b490281c8e4d5" }' <full node URL:port> sui.rpc.v2.StateService/ListOwnedObjects

List the dynamic fields in a particular object

$ grpcurl -d '{ "parent": "0xb57fba584a700a5bcb40991e1b2e6bf68b0f3896d767a0da92e69de73de226ac" }' <full node URL:port> sui.rpc.v2.StateService/ListDynamicFields

Access streaming data with Buf

grpcurl은 server-side streaming RPC를 지원하지 않는다. SubscriptionService를 테스트하거나 실험하려면 대신 Buf CLI를 사용한다.

$ buf curl --protocol grpc https://<full node URL>/sui.rpc.v2.SubscriptionService/SubscribeCheckpoints -d '{ "readMask": "sequenceNumber,digest,summary.timestamp" }'  --timeout 1m

Build gRPC clients

정보

gRPC client를 만들 때는 supported SDKs 중 하나를 사용하는 것을 권장한다. 해당 SDK가 사용 사례를 충족하지 못하면 아래 예시를 참조한다.

이 예시는 Sui gRPC API용 TypeScript client를 만드는 방법을 보여준다.

Step 1: Install dependencies

npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
npm i -D tsx

기본 project는 다음 구조를 가진다:

.
├── protos/
│ └── sui/
│ └── node/
│ └── v2/
│ ├── ledger_service.proto
│ └── *.proto
├── client.ts
├── package.json

모든 sui/rpc/v2 proto file을 Github에서 같은 folder로 다운로드한다.

Step 2: Edit client.ts to get events and effects details of a particular transaction

import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import * as path from 'path';

const PROTO_PATH = path.join(__dirname, 'protos/sui/rpc/v2/ledger_service.proto');

// proto 정의를 로드한다
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [path.join(__dirname, 'protos')],
});

const suiProto = grpc.loadPackageDefinition(packageDefinition) as any;
const LedgerService = suiProto.sui.rpc.v2.LedgerService;

// gRPC client를 생성한다
const client = new LedgerService(
'<full node URL>:443',
grpc.credentials.createSsl()
);

// Base58 형식의 sample transaction digest
const base58Digest = '3ByWphQ5sAVojiTrTrGXGM5FmCVzpzYmhsjbhYESJtxp';

// request를 구성한다
const request = {
digest: base58Digest,
read_mask: {
paths: ['events', 'effects'],
},
};

// gRPC 호출을 수행한다
client.GetTransaction(request, (err: any, response: any) => {
if (err) {
console.error('Error:', err);
} else {
console.log('Response:', JSON.stringify(response, null, 2));
}
});

Step 3: Run the sample client

npx tsx c
  • proto-loader는 nested .proto file도 처리한다. path와 import만 올바른지 확인하면 된다.

  • 이 예시는 port 443에서 gRPC를 사용할 수 있다고 가정하며, 이는 SSL을 요구한다.

  • request의 digest는 Base58 format으로 직접 제공되지만, source format에서 decode가 필요한지 확인한다.