프로그래머블 트랜잭션 블록 (PTB)
Sui의 transaction은 transaction 결과를 정의하기 위해 입력에 대해 실행되는 명령 그룹으로 구성된다. **programmable transaction blocks (PTBs)**라고 부르는 이 명령 그룹이 Sui의 모든 사용자 transaction을 정의한다. PTB를 사용하면 사용자는 새 Move package를 publish하지 않고도 하나의 transaction 안에서 여러 Move 함수를 호출하고, 자신의 object를 관리하고, 자신의 coin을 관리할 수 있다. 자동화와 transaction builder를 염두에 두고 설계된 PTB는 transaction을 생성하는 가볍고 유연한 방식이다.
그러나 loop 같은 더 복잡한 프로그래밍 패턴은 지원되지 않는다. 이런 경우에는 새 Move package를 publish해야 한다.
PTB 안의 개별 transaction 명령은 순서대로 실행된다. 한 transaction 명령의 결과를 같은 PTB 안에 있는 이후 transaction 명령에서 사용할 수 있다. object 수정이나 transfer 같은 블록 안 각 transaction 명령의 effect는 transaction 마지막에 원자적으로 적용된다. 한 transaction 명령이 실패하면 전체 블록이 실패하고 어떤 명령의 effect도 적용되지 않는다.
PTB는 한 번의 실행에서 최대 1,024개의 고유 operation을 수행할 수 있지만, 전통적인 블록체인의 transaction은 같은 결과를 얻기 위해 1,024번의 개별 실행이 필요하다. 이 구조는 더 저렴한 gas fee도 촉진한다. 개별 transaction을 처리하는 비용은 같은 transaction을 PTB로 묶어 처리하는 비용보다 항상 더 높다.
Transaction components
실행 의미론과 관련해 PTB에는 두 부분이 있다. transaction sender나 gas limit 같은 다른 transaction 정보는 참조될 수 있지만 여기서는 범위 밖이다. PTB의 구조는 다음과 같다:
{
inputs: [Input],
commands: [Command],
}
두 주요 구성 요소를 더 자세히 보면 다음과 같다:
-
inputs값은 인자 벡터[Input]이다. 이 인자들은 command에서 사용할 수 있는 object이거나 pure value이다. object는 sender가 소유한 것이거나 shared object 또는 immutable object이다. pure value는u64나String값처럼 바이트만으로 순수하게 구성할 수 있는 단순한 Move 값이다. 역사적인 이유로 Rust 구현에서는Input을CallArg라고 부른다. -
commands값은 command 벡터[Command]이다. 가능한 command는 다음과 같다: -
tx.splitCoins(coin, amounts): 제공된 coin에서 정의한 amount만큼 분할해 새 coin을 만든다. 이후 transaction에서 사용할 수 있도록 coin을 반환한다.- 예시:
tx.splitCoins(tx.gas, [tx.pure.u64(100), tx.pure.u64(200)])
- 예시:
-
tx.mergeCoins(destinationCoin, sourceCoins):sourceCoins를destinationCoin에 병합한다.- 예시:
tx.mergeCoins(tx.object(coin1), [tx.object(coin2), tx.object(coin3)])
- 예시:
-
tx.transferObjects(objects, address): object 목록을 지정한 address로 전송한다.- 예시:
tx.transferObjects([tx.object(thing1), tx.object(thing2)], tx.pure.address(myAddress))
- 예시:
-
tx.moveCall({ target, arguments, typeArguments }): Move 호출을 실행한다. Sui Move 호출이 반환하는 값을 그대로 반환한다.- 예시:
tx.moveCall({ target: '0x2::devnet_nft::mint', arguments: [tx.pure.string(name), tx.pure.string(description), tx.pure.string(image)] })
- 예시:
-
tx.makeMoveVec({ type, elements }):moveCall에 전달할 수 있는 object vector를 구성한다. vector를 입력으로 정의할 다른 방법이 없기 때문에 이 command가 필요하다.- 예시:
tx.makeMoveVec({ elements: [tx.object(id1), tx.object(id2)] })
- 예시:
-
tx.publish(modules, dependencies): Move package를 publish한다. upgrade capability object를 반환한다. -
tx.upgrade(modules, dependencies, packageId: EXAMPLE_PACKAGE_ID, ticket): 기존 package를 업그레이드한다. 업그레이드된 module에 대해서는 어떤init함수도 호출되지 않는다.
각 PTB 명령에 대해 더 알아본다.
Inputs and results
입력은 PTB에 제공되는 값이고, 결과는 PTB command가 만들어 내는 값이다. 입력은 object이거나 단순한 Move 값이며, 결과는 object를 포함한 임의의 Move 값이다. 입력과 결과는 PTB 실행을 통과하는 데이터 흐름을 이룬다.
이를 값 배열을 채우는 것으로 볼 수 있다. 입력은 단일 배열이지만 결과는 각 개별 transaction command마다 하나씩 배열이 있어 결과 값의 2D 배열이 만들어진다. 타입이 허용한다면 이 값들은 가변 또는 불변으로 borrow하거나, 복사하거나, 이동해 접근할 수 있으며, 이동은 재인덱싱 없이 값을 배열 밖으로 가져간다.
inputs and results에 대해 더 알아본다.
Argument structure and usage
command는 사용할 입력 또는 결과를 지정하는 Argument 값을 받는다.
런타임은 command가 기대하는 타입을 바탕으로 인자를 reference로 전달할지 값으로 전달할지를 추론한다.
Argument enum에는 4개 variant가 있다:
-
Input(u16): 인덱스로 입력을 참조한다. 예를 들어 입력이[Object1, Object2, Pure1, Object3]라면Object1에는Input(0),Pure1에는Input(2)를 사용한다. -
GasCoin: gas 지불에 사용하는 SUI coin을 참조한다. 이 값은 특별한 제약이 있으므로 다른 입력과 분리되어 있다.TransferObjectscommand를 통해서만 값으로 꺼낼 수 있다. gas coin의 일부를 다른 곳에서 사용하려면 먼저SplitCoins로 coin을 분할한다. -
NestedResult(u16, u16): 이전 command의 결과를 참조한다. 첫 번째 인덱스는 command를, 두 번째 인덱스는 그 command에서 어떤 결과인지를 지정한다. 예를 들어 command 1이[Value1, Value2]를 반환한다면Value1에는NestedResult(1, 0),Value2에는NestedResult(1, 1)을 사용한다. -
Result(u16):NestedResult(i, 0)의 축약형이지만 인덱스i의 command가 정확히 1개의 결과를 반환할 때만 유효하다. 0개 또는 여러 결과를 반환하는 command에는NestedResult를 사용한다.
Execution
PTB를 실행할 때 입력 벡터는 입력 object 또는 pure value bytes로 채워진다. 그 다음 transaction command가 순서대로 실행되고, 결과는 결과 벡터에 저장된다. 마지막으로 transaction의 effect가 원자적으로 적용된다.
Start of execution
실행 시작 시 PTB 런타임은 입력 object를 가져와 입력 배열에 적재한다. 이 object들은 존재 여부와 유효한 소유권 같은 규칙을 검사하며 네트워크가 이미 검증한 상태이다. pure value bytes도 배열에 적재되지만 사용할 때까지는 검증되지 않는다.
이 단계에서 가장 중요한 점은 gas coin에 대한 effect이다. 실행 시작 시 최대 gas budget(SUI 기준)이 gas coin에서 인출된다. 사용되지 않은 gas는 실행 끝에 gas coin으로 반환되며, coin의 owner가 바뀌었더라도 마찬가지이다.
Object consumption
Move command가 생성하거나 반환한 모든 object는 소비되거나(파기, 전송, 다른 command에서 사용), 타입에 drop ability가 있다면 명시적으로 drop되어야 한다.
PTB에서 Move command를 통해 object를 생성한 뒤 이를 파기하거나 전송하거나 다음 command에서 사용하지 않으면 transaction은 error와 함께 실패한다.