From 8f65446a6a69f37b5388ed2509f614bb6cb829e7 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Wed, 22 Jan 2025 02:03:25 +0530 Subject: [PATCH] rpc-alt: getTransactionBlock ## Description Implement `sui_getTransactionBlock`, excluding: - `showBalanceChanges` - `showObjectChanges` Which will take a bit more work and will appear in follow-up PRs. ## Test plan New E2E tests: ``` sui$ cargo nextest run -p sui-indexer-alt-e2e-tests ``` --- Cargo.lock | 1 + .../transactions/get_transaction_block.exp | 903 ++++++++++++++++++ .../transactions/get_transaction_block.move | 104 ++ crates/sui-indexer-alt-jsonrpc/Cargo.toml | 1 + crates/sui-indexer-alt-jsonrpc/src/api/mod.rs | 1 + .../src/api/transactions.rs | 166 ++++ .../sui-indexer-alt-jsonrpc/src/data/mod.rs | 1 + .../src/data/transactions.rs | 52 + crates/sui-indexer-alt-jsonrpc/src/error.rs | 9 +- crates/sui-indexer-alt-jsonrpc/src/lib.rs | 2 + 10 files changed, 1239 insertions(+), 1 deletion(-) create mode 100644 crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/transactions/get_transaction_block.exp create mode 100644 crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/transactions/get_transaction_block.move create mode 100644 crates/sui-indexer-alt-jsonrpc/src/api/transactions.rs create mode 100644 crates/sui-indexer-alt-jsonrpc/src/data/transactions.rs diff --git a/Cargo.lock b/Cargo.lock index be15c40029296..f8aa28c7e14f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14054,6 +14054,7 @@ dependencies = [ "serde_json", "sui-indexer-alt-metrics", "sui-indexer-alt-schema", + "sui-json-rpc-types", "sui-open-rpc", "sui-open-rpc-macros", "sui-package-resolver", diff --git a/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/transactions/get_transaction_block.exp b/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/transactions/get_transaction_block.exp new file mode 100644 index 0000000000000..82ea3a9f7b5e4 --- /dev/null +++ b/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/transactions/get_transaction_block.exp @@ -0,0 +1,903 @@ +processed 9 tasks + +init: +A: object(0,0) + +task 1, lines 12-46: +//# publish +created: object(1,0), object(1,1) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 9211200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, lines 48-54: +//# programmable --sender A --inputs object(1,0) 42 @A +//> 0: test::counter::inc(Input(0)); +//> 1: test::counter::inc_by(Input(0), Input(1)); +//> 2: sui::coin::value(Gas); +//> 3: test::counter::inc_by(Input(0), Result(2)); +//> 4: test::counter::take(Input(0), Input(1)); +//> 5: TransferObjects([Result(4)], Input(2)) +events: Event { package_id: test, transaction_module: Identifier("counter"), sender: A, type_: StructTag { address: test, module: Identifier("counter"), name: Identifier("NFTMinted"), type_params: [] }, contents: [41, 96, 231, 235, 135, 134, 117, 102, 3, 204, 210, 31, 140, 81, 38, 14, 171, 188, 201, 196, 121, 128, 155, 196, 248, 198, 125, 113, 60, 127, 239, 247] } +created: object(2,0) +mutated: object(0,0), object(1,0) +gas summary: computation_cost: 1000000, storage_cost: 3678400, storage_rebate: 1346796, non_refundable_storage_fee: 13604 + +task 3, line 56: +//# create-checkpoint +Checkpoint created: 1 + +task 4, lines 58-62: +//# run-jsonrpc +Response: { + "jsonrpc": "2.0", + "id": 0, + "result": { + "digest": "FqrQYPLunNvAQKn2EKrWAHc32942bjovH6VeFDgPQUQs" + } +} + +task 5, lines 64-68: +//# run-jsonrpc +Response: { + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -32602, + "message": "Transaction not found: 11111111111111111111111111111111" + } +} + +task 6, lines 70-80: +//# run-jsonrpc +Response: { + "jsonrpc": "2.0", + "id": 2, + "result": { + "digest": "FqrQYPLunNvAQKn2EKrWAHc32942bjovH6VeFDgPQUQs", + "rawTransaction": "AAADAQFVgoNgXuarD3xAE3cOojA9IQQR26dH+uxMEfbX1R6dhgIAAAAAAAAAAQAIKgAAAAAAAAAAIPzMmkIbuxPBpmoaqY8K11Ap7elIV3ecaRW0T5QGi5IeBgDsz8HPY4GWIqXu67rnTG9rf6ekBFETZGH2d5atP48z/gdjb3VudGVyA2luYwABAQAAAOzPwc9jgZYipe7ruudMb2t/p6QEURNkYfZ3lq0/jzP+B2NvdW50ZXIGaW5jX2J5AAIBAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEY29pbgV2YWx1ZQEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDc3VpA1NVSQABAADsz8HPY4GWIqXu67rnTG9rf6ekBFETZGH2d5atP48z/gdjb3VudGVyBmluY19ieQACAQAAAgIAAOzPwc9jgZYipe7ruudMb2t/p6QEURNkYfZ3lq0/jzP+B2NvdW50ZXIEdGFrZQACAQAAAQEAAQECBAABAgD8zJpCG7sTwaZqGqmPCtdQKe3pSFd3nGkVtE+UBouSHgG/9elqSlwPc0glnHq/39mZQFwCuenA0NWexmmlOxBMegEAAAAAAAAAIKEd8xRTuu+n68ZlWvsLrCmzFa8z+W8E5apBshZjx/M//MyaQhu7E8GmahqpjwrXUCnt6UhXd5xpFbRPlAaLkh7oAwAAAAAAAADyBSoBAAAAAA==", + "rawEffects": [ + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 64, + 66, + 15, + 0, + 0, + 0, + 0, + 0, + 192, + 32, + 56, + 0, + 0, + 0, + 0, + 0, + 236, + 140, + 20, + 0, + 0, + 0, + 0, + 0, + 36, + 53, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 220, + 135, + 91, + 245, + 94, + 173, + 245, + 146, + 181, + 140, + 59, + 176, + 38, + 179, + 59, + 185, + 90, + 176, + 34, + 173, + 99, + 238, + 151, + 236, + 47, + 147, + 95, + 134, + 165, + 19, + 235, + 140, + 1, + 2, + 0, + 0, + 0, + 1, + 32, + 35, + 234, + 81, + 115, + 57, + 243, + 103, + 224, + 175, + 29, + 141, + 219, + 189, + 113, + 214, + 68, + 130, + 116, + 106, + 7, + 113, + 255, + 34, + 214, + 131, + 51, + 107, + 133, + 226, + 136, + 224, + 247, + 2, + 32, + 33, + 96, + 204, + 109, + 165, + 71, + 188, + 85, + 187, + 139, + 69, + 34, + 243, + 29, + 142, + 35, + 254, + 32, + 23, + 233, + 154, + 186, + 96, + 24, + 108, + 237, + 183, + 181, + 10, + 7, + 247, + 26, + 32, + 221, + 81, + 192, + 236, + 185, + 4, + 19, + 137, + 211, + 93, + 227, + 108, + 152, + 189, + 2, + 229, + 194, + 247, + 14, + 181, + 1, + 61, + 241, + 62, + 33, + 28, + 12, + 188, + 144, + 73, + 7, + 241, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 41, + 96, + 231, + 235, + 135, + 134, + 117, + 102, + 3, + 204, + 210, + 31, + 140, + 81, + 38, + 14, + 171, + 188, + 201, + 196, + 121, + 128, + 155, + 196, + 248, + 198, + 125, + 113, + 60, + 127, + 239, + 247, + 0, + 1, + 32, + 126, + 22, + 145, + 146, + 124, + 26, + 49, + 95, + 145, + 147, + 125, + 201, + 39, + 120, + 225, + 15, + 254, + 207, + 64, + 27, + 22, + 94, + 36, + 157, + 253, + 97, + 75, + 239, + 138, + 119, + 175, + 171, + 0, + 252, + 204, + 154, + 66, + 27, + 187, + 19, + 193, + 166, + 106, + 26, + 169, + 143, + 10, + 215, + 80, + 41, + 237, + 233, + 72, + 87, + 119, + 156, + 105, + 21, + 180, + 79, + 148, + 6, + 139, + 146, + 30, + 1, + 85, + 130, + 131, + 96, + 94, + 230, + 171, + 15, + 124, + 64, + 19, + 119, + 14, + 162, + 48, + 61, + 33, + 4, + 17, + 219, + 167, + 71, + 250, + 236, + 76, + 17, + 246, + 215, + 213, + 30, + 157, + 134, + 1, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 8, + 187, + 214, + 160, + 189, + 68, + 36, + 168, + 209, + 42, + 181, + 76, + 230, + 154, + 178, + 60, + 232, + 212, + 184, + 234, + 131, + 17, + 90, + 251, + 62, + 243, + 106, + 204, + 220, + 131, + 101, + 241, + 2, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 32, + 51, + 146, + 165, + 254, + 55, + 171, + 56, + 11, + 40, + 150, + 38, + 170, + 157, + 12, + 67, + 243, + 59, + 11, + 42, + 189, + 229, + 229, + 71, + 29, + 100, + 7, + 175, + 219, + 213, + 162, + 167, + 36, + 2, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 191, + 245, + 233, + 106, + 74, + 92, + 15, + 115, + 72, + 37, + 156, + 122, + 191, + 223, + 217, + 153, + 64, + 92, + 2, + 185, + 233, + 192, + 208, + 213, + 158, + 198, + 105, + 165, + 59, + 16, + 76, + 122, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 161, + 29, + 243, + 20, + 83, + 186, + 239, + 167, + 235, + 198, + 101, + 90, + 251, + 11, + 172, + 41, + 179, + 21, + 175, + 51, + 249, + 111, + 4, + 229, + 170, + 65, + 178, + 22, + 99, + 199, + 243, + 63, + 0, + 252, + 204, + 154, + 66, + 27, + 187, + 19, + 193, + 166, + 106, + 26, + 169, + 143, + 10, + 215, + 80, + 41, + 237, + 233, + 72, + 87, + 119, + 156, + 105, + 21, + 180, + 79, + 148, + 6, + 139, + 146, + 30, + 1, + 32, + 114, + 196, + 58, + 191, + 102, + 185, + 166, + 160, + 211, + 88, + 87, + 226, + 103, + 38, + 180, + 219, + 201, + 133, + 52, + 178, + 16, + 75, + 6, + 13, + 233, + 0, + 163, + 95, + 139, + 44, + 222, + 167, + 0, + 252, + 204, + 154, + 66, + 27, + 187, + 19, + 193, + 166, + 106, + 26, + 169, + 143, + 10, + 215, + 80, + 41, + 237, + 233, + 72, + 87, + 119, + 156, + 105, + 21, + 180, + 79, + 148, + 6, + 139, + 146, + 30, + 0, + 0, + 0 + ] + } +} + +task 7, lines 82-92: +//# run-jsonrpc +Response: { + "jsonrpc": "2.0", + "id": 3, + "result": { + "digest": "FqrQYPLunNvAQKn2EKrWAHc32942bjovH6VeFDgPQUQs", + "transaction": { + "data": { + "messageVersion": "v1", + "transaction": { + "kind": "ProgrammableTransaction", + "inputs": [ + { + "type": "object", + "objectType": "sharedObject", + "objectId": "0x558283605ee6ab0f7c4013770ea2303d210411dba747faec4c11f6d7d51e9d86", + "initialSharedVersion": "2", + "mutable": true + }, + { + "type": "pure", + "valueType": "u64", + "value": "42" + }, + { + "type": "pure", + "valueType": "address", + "value": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + ], + "transactions": [ + { + "MoveCall": { + "package": "0xeccfc1cf63819622a5eeebbae74c6f6b7fa7a40451136461f67796ad3f8f33fe", + "module": "counter", + "function": "inc", + "arguments": [ + { + "Input": 0 + } + ] + } + }, + { + "MoveCall": { + "package": "0xeccfc1cf63819622a5eeebbae74c6f6b7fa7a40451136461f67796ad3f8f33fe", + "module": "counter", + "function": "inc_by", + "arguments": [ + { + "Input": 0 + }, + { + "Input": 1 + } + ] + } + }, + { + "MoveCall": { + "package": "0x0000000000000000000000000000000000000000000000000000000000000002", + "module": "coin", + "function": "value", + "type_arguments": [ + "0x2::sui::SUI" + ], + "arguments": [ + "GasCoin" + ] + } + }, + { + "MoveCall": { + "package": "0xeccfc1cf63819622a5eeebbae74c6f6b7fa7a40451136461f67796ad3f8f33fe", + "module": "counter", + "function": "inc_by", + "arguments": [ + { + "Input": 0 + }, + { + "Result": 2 + } + ] + } + }, + { + "MoveCall": { + "package": "0xeccfc1cf63819622a5eeebbae74c6f6b7fa7a40451136461f67796ad3f8f33fe", + "module": "counter", + "function": "take", + "arguments": [ + { + "Input": 0 + }, + { + "Input": 1 + } + ] + } + }, + { + "TransferObjects": [ + [ + { + "Result": 4 + } + ], + { + "Input": 2 + } + ] + } + ] + }, + "sender": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e", + "gasData": { + "payment": [ + { + "objectId": "0xbff5e96a4a5c0f7348259c7abfdfd999405c02b9e9c0d0d59ec669a53b104c7a", + "version": 1, + "digest": "Bqw8MjtPSgoFarHYSq7HN77R9utjvxqtSFD32ydKXsiW" + } + ], + "owner": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e", + "price": "1000", + "budget": "5000000000" + } + }, + "txSignatures": [ + "AER38jSf17YdkZqbJEPRwjAuX3kp0JL4vbLqU/pxl7D7X2MREYLcCnMbT+jF+txsUbVMfJroYTwYfx3t0ish7gR/UUY663bYjcm3XmNyULIgxJz1t5Z9vxfB+fp8WUoJKA==" + ] + }, + "effects": { + "messageVersion": "v1", + "status": { + "status": "success" + }, + "executedEpoch": "0", + "gasUsed": { + "computationCost": "1000000", + "storageCost": "3678400", + "storageRebate": "1346796", + "nonRefundableStorageFee": "13604" + }, + "modifiedAtVersions": [ + { + "objectId": "0x558283605ee6ab0f7c4013770ea2303d210411dba747faec4c11f6d7d51e9d86", + "sequenceNumber": "2" + }, + { + "objectId": "0xbff5e96a4a5c0f7348259c7abfdfd999405c02b9e9c0d0d59ec669a53b104c7a", + "sequenceNumber": "1" + } + ], + "sharedObjects": [ + { + "objectId": "0x558283605ee6ab0f7c4013770ea2303d210411dba747faec4c11f6d7d51e9d86", + "version": 2, + "digest": "b6PSDcJ7F2jMDTvZMSsbuqexkSi2hDoiZQL46YxECNC" + } + ], + "transactionDigest": "FqrQYPLunNvAQKn2EKrWAHc32942bjovH6VeFDgPQUQs", + "created": [ + { + "owner": { + "AddressOwner": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "reference": { + "objectId": "0x2960e7eb8786756603ccd21f8c51260eabbcc9c479809bc4f8c67d713c7feff7", + "version": 3, + "digest": "9VCL8EztoKtT8h9294KcMTBy5TLZRncvYdRMvmepmKCv" + } + } + ], + "mutated": [ + { + "owner": { + "Shared": { + "initial_shared_version": 2 + } + }, + "reference": { + "objectId": "0x558283605ee6ab0f7c4013770ea2303d210411dba747faec4c11f6d7d51e9d86", + "version": 3, + "digest": "4UKVNcXyHWS2CtNG7pDLdMZjsibnRBsXQaiRP5sARUHh" + } + }, + { + "owner": { + "AddressOwner": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "reference": { + "objectId": "0xbff5e96a4a5c0f7348259c7abfdfd999405c02b9e9c0d0d59ec669a53b104c7a", + "version": 3, + "digest": "8j12PP5NaZ6Tn5rGVHb2SkU5rtYeV9G1md1fRRqBZ15Y" + } + } + ], + "gasObject": { + "owner": { + "AddressOwner": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "reference": { + "objectId": "0xbff5e96a4a5c0f7348259c7abfdfd999405c02b9e9c0d0d59ec669a53b104c7a", + "version": 3, + "digest": "8j12PP5NaZ6Tn5rGVHb2SkU5rtYeV9G1md1fRRqBZ15Y" + } + }, + "eventsDigest": "3RCW6N38LzHG9yvdaWzk1Beaum5a3JSWP73MDLmygo3t", + "dependencies": [ + "3FJ4fSrf7toVCANccxAbeJ5A1iSzwKLghCYcaz9atbCD", + "FtwQTnaYQ7BDSpxR9gtbr2B5XBjMGYXgv3KQngEak3ix" + ] + } + } +} + +task 8, lines 95-104: +//# run-jsonrpc +Response: { + "jsonrpc": "2.0", + "id": 4, + "result": { + "digest": "FqrQYPLunNvAQKn2EKrWAHc32942bjovH6VeFDgPQUQs", + "events": [ + { + "id": { + "txDigest": "FqrQYPLunNvAQKn2EKrWAHc32942bjovH6VeFDgPQUQs", + "eventSeq": "0" + }, + "packageId": "0xeccfc1cf63819622a5eeebbae74c6f6b7fa7a40451136461f67796ad3f8f33fe", + "transactionModule": "counter", + "sender": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e", + "type": "0xeccfc1cf63819622a5eeebbae74c6f6b7fa7a40451136461f67796ad3f8f33fe::counter::NFTMinted", + "parsedJson": { + "id": "0x2960e7eb8786756603ccd21f8c51260eabbcc9c479809bc4f8c67d713c7feff7" + }, + "bcsEncoding": "base64", + "bcs": "KWDn64eGdWYDzNIfjFEmDqu8ycR5gJvE+MZ9cTx/7/c=", + "timestampMs": "0" + } + ] + } +} diff --git a/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/transactions/get_transaction_block.move b/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/transactions/get_transaction_block.move new file mode 100644 index 0000000000000..8751b3371a04e --- /dev/null +++ b/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/transactions/get_transaction_block.move @@ -0,0 +1,104 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 70 --accounts A --addresses test=0x0 --simulator + +// 1. Default behavior of getTransactionBlock (no options) +// 2. "Not found" case +// 3. Raw transaction and effects +// 4. Structured transaction and effects +// 5. Events + +//# publish +module test::counter { + public struct Counter has key { + id: UID, + x: u64, + } + + public struct NFT has key, store { + id: UID, + x: u64 + } + + public struct NFTMinted has copy, drop, store { + id: ID, + } + + fun init(ctx: &mut TxContext) { + transfer::share_object(Counter { + id: object::new(ctx), + x: 0, + }) + } + + public fun inc(c: &mut Counter) { c.x = c.x + 1 } + public fun inc_by(c: &mut Counter, x: u64) { c.x = c.x + x } + + public fun take(c: &mut Counter, x: u64, ctx: &mut TxContext): NFT { + assert!(c.x >= x); + c.x = c.x - x; + let nft = NFT { id: object::new(ctx), x }; + + sui::event::emit(NFTMinted { id: object::id(&nft) }); + nft + } +} + +//# programmable --sender A --inputs object(1,0) 42 @A +//> 0: test::counter::inc(Input(0)); +//> 1: test::counter::inc_by(Input(0), Input(1)); +//> 2: sui::coin::value(Gas); +//> 3: test::counter::inc_by(Input(0), Result(2)); +//> 4: test::counter::take(Input(0), Input(1)); +//> 5: TransferObjects([Result(4)], Input(2)) + +//# create-checkpoint + +//# run-jsonrpc +{ + "method": "sui_getTransactionBlock", + "params": ["@{digest_2}", {}] +} + +//# run-jsonrpc +{ + "method": "sui_getTransactionBlock", + "params": ["11111111111111111111111111111111", {}] +} + +//# run-jsonrpc +{ + "method": "sui_getTransactionBlock", + "params": [ + "@{digest_2}", + { + "showRawInput": true, + "showRawEffects": true + } + ] +} + +//# run-jsonrpc +{ + "method": "sui_getTransactionBlock", + "params": [ + "@{digest_2}", + { + "showInput": true, + "showEffects": true + } + ] +} + + +//# run-jsonrpc +{ + "method": "sui_getTransactionBlock", + "params": [ + "@{digest_2}", + { + "showEvents": true + } + ] +} diff --git a/crates/sui-indexer-alt-jsonrpc/Cargo.toml b/crates/sui-indexer-alt-jsonrpc/Cargo.toml index 83147d583c541..bc1c09bab321a 100644 --- a/crates/sui-indexer-alt-jsonrpc/Cargo.toml +++ b/crates/sui-indexer-alt-jsonrpc/Cargo.toml @@ -35,6 +35,7 @@ move-core-types.workspace = true sui-indexer-alt-metrics.workspace = true sui-indexer-alt-schema.workspace = true +sui-json-rpc-types.workspace = true sui-package-resolver.workspace = true sui-open-rpc.workspace = true sui-open-rpc-macros.workspace = true diff --git a/crates/sui-indexer-alt-jsonrpc/src/api/mod.rs b/crates/sui-indexer-alt-jsonrpc/src/api/mod.rs index 67a8c3040cbbe..885cd3b02da68 100644 --- a/crates/sui-indexer-alt-jsonrpc/src/api/mod.rs +++ b/crates/sui-indexer-alt-jsonrpc/src/api/mod.rs @@ -3,3 +3,4 @@ pub(crate) mod governance; pub(crate) mod rpc_module; +pub(crate) mod transactions; diff --git a/crates/sui-indexer-alt-jsonrpc/src/api/transactions.rs b/crates/sui-indexer-alt-jsonrpc/src/api/transactions.rs new file mode 100644 index 0000000000000..133b8ae56c9d8 --- /dev/null +++ b/crates/sui-indexer-alt-jsonrpc/src/api/transactions.rs @@ -0,0 +1,166 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::anyhow; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use move_core_types::annotated_value::{MoveDatatypeLayout, MoveTypeLayout}; +use sui_indexer_alt_schema::transactions::StoredTransaction; +use sui_json_rpc_types::{ + SuiEvent, SuiTransactionBlock, SuiTransactionBlockData, SuiTransactionBlockEvents, + SuiTransactionBlockResponse, SuiTransactionBlockResponseOptions, +}; +use sui_open_rpc::Module; +use sui_open_rpc_macros::open_rpc; +use sui_types::{ + digests::TransactionDigest, effects::TransactionEffects, error::SuiError, event::Event, + signature::GenericSignature, transaction::TransactionData, +}; + +use crate::{ + context::Context, + error::{internal_error, invalid_params}, +}; + +use super::rpc_module::RpcModule; + +#[open_rpc(namespace = "sui", tag = "Transactions API")] +#[rpc(server, namespace = "sui")] +trait TransactionsApi { + /// Fetch a transaction by its transaction digest. + #[method(name = "getTransactionBlock")] + async fn get_transaction_block( + &self, + /// The digest of the queried transaction. + digest: TransactionDigest, + /// Options controlling the output format. + options: SuiTransactionBlockResponseOptions, + ) -> RpcResult; +} + +pub(crate) struct Transactions(pub Context); + +#[derive(thiserror::Error, Debug)] +pub(crate) enum Error { + #[error("Transaction not found: {0}")] + NotFound(TransactionDigest), + + #[error("Error converting to response: {0}")] + Conversion(SuiError), + + #[error("Error resolving type information: {0}")] + Resolution(anyhow::Error), + + #[error("Deserialization error: {0}")] + Deserialization(#[from] bcs::Error), +} + +#[async_trait::async_trait] +impl TransactionsApiServer for Transactions { + async fn get_transaction_block( + &self, + digest: TransactionDigest, + options: SuiTransactionBlockResponseOptions, + ) -> RpcResult { + let Self(ctx) = self; + let Some(stored) = ctx + .loader() + .load_one(digest) + .await + .map_err(internal_error)? + else { + return Err(invalid_params(Error::NotFound(digest))); + }; + + response(ctx, &stored, &options) + .await + .map_err(internal_error) + } +} + +impl RpcModule for Transactions { + fn schema(&self) -> Module { + TransactionsApiOpenRpc::module_doc() + } + + fn into_impl(self) -> jsonrpsee::RpcModule { + self.into_rpc() + } +} + +/// Convert the representation of a transaction from the database into the response format, +/// including the fields requested in the `options`. +pub(crate) async fn response( + ctx: &Context, + tx: &StoredTransaction, + options: &SuiTransactionBlockResponseOptions, +) -> Result { + use Error as E; + + let digest = TransactionDigest::try_from(tx.tx_digest.clone()).map_err(E::Conversion)?; + let mut response = SuiTransactionBlockResponse::new(digest); + + if options.show_input { + let data: TransactionData = bcs::from_bytes(&tx.raw_transaction)?; + let tx_signatures: Vec = bcs::from_bytes(&tx.user_signatures)?; + response.transaction = Some(SuiTransactionBlock { + data: SuiTransactionBlockData::try_from_with_package_resolver( + data, + ctx.package_resolver(), + ) + .await + .map_err(E::Resolution)?, + tx_signatures, + }) + } + + if options.show_raw_input { + response.raw_transaction = tx.raw_transaction.clone(); + } + + if options.show_effects { + let effects: TransactionEffects = bcs::from_bytes(&tx.raw_effects)?; + response.effects = Some(effects.try_into().map_err(E::Conversion)?); + } + + if options.show_raw_effects { + response.raw_effects = tx.raw_effects.clone(); + } + + if options.show_events { + let events: Vec = bcs::from_bytes(&tx.events)?; + let mut sui_events = Vec::with_capacity(events.len()); + + for (ix, event) in events.into_iter().enumerate() { + let layout = match ctx + .package_resolver() + .type_layout(event.type_.clone().into()) + .await + .map_err(|e| E::Resolution(e.into()))? + { + MoveTypeLayout::Struct(s) => MoveDatatypeLayout::Struct(s), + MoveTypeLayout::Enum(e) => MoveDatatypeLayout::Enum(e), + _ => { + return Err(E::Resolution(anyhow!( + "Event {ix} from {digest} is not a struct or enum: {}", + event.type_.to_canonical_string(/* with_prefix */ true) + ))); + } + }; + + let sui_event = SuiEvent::try_from( + event, + digest, + ix as u64, + Some(tx.timestamp_ms as u64), + layout, + ) + .map_err(E::Conversion)?; + + sui_events.push(sui_event) + } + + response.events = Some(SuiTransactionBlockEvents { data: sui_events }); + } + + Ok(response) +} diff --git a/crates/sui-indexer-alt-jsonrpc/src/data/mod.rs b/crates/sui-indexer-alt-jsonrpc/src/data/mod.rs index 373a63a5da64e..74f0034f2b978 100644 --- a/crates/sui-indexer-alt-jsonrpc/src/data/mod.rs +++ b/crates/sui-indexer-alt-jsonrpc/src/data/mod.rs @@ -4,3 +4,4 @@ pub(crate) mod package_resolver; pub(crate) mod reader; pub mod system_package_task; +pub(crate) mod transactions; diff --git a/crates/sui-indexer-alt-jsonrpc/src/data/transactions.rs b/crates/sui-indexer-alt-jsonrpc/src/data/transactions.rs new file mode 100644 index 0000000000000..f53c24ad1c0e9 --- /dev/null +++ b/crates/sui-indexer-alt-jsonrpc/src/data/transactions.rs @@ -0,0 +1,52 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + collections::{BTreeSet, HashMap}, + sync::Arc, +}; + +use async_graphql::dataloader::Loader; +use diesel::{ExpressionMethods, QueryDsl}; +use sui_indexer_alt_schema::{schema::kv_transactions, transactions::StoredTransaction}; +use sui_types::digests::TransactionDigest; + +use super::reader::{ReadError, Reader}; + +#[async_trait::async_trait] +impl Loader for Reader { + type Value = StoredTransaction; + type Error = Arc; + + async fn load( + &self, + keys: &[TransactionDigest], + ) -> Result, Self::Error> { + use kv_transactions::dsl as t; + + if keys.is_empty() { + return Ok(HashMap::new()); + } + + let mut conn = self.connect().await.map_err(Arc::new)?; + + let digests: BTreeSet<_> = keys.iter().map(|d| d.into_inner()).collect(); + let transactions: Vec = conn + .results(t::kv_transactions.filter(t::tx_digest.eq_any(digests))) + .await + .map_err(Arc::new)?; + + let digest_to_stored: HashMap<_, _> = transactions + .into_iter() + .map(|stored| (stored.tx_digest.clone(), stored)) + .collect(); + + Ok(keys + .iter() + .filter_map(|key| { + let slice: &[u8] = key.as_ref(); + Some((*key, digest_to_stored.get(slice).cloned()?)) + }) + .collect()) + } +} diff --git a/crates/sui-indexer-alt-jsonrpc/src/error.rs b/crates/sui-indexer-alt-jsonrpc/src/error.rs index 180e1ac85986a..b5a15663097a8 100644 --- a/crates/sui-indexer-alt-jsonrpc/src/error.rs +++ b/crates/sui-indexer-alt-jsonrpc/src/error.rs @@ -8,8 +8,15 @@ //! in the service may return errors in a variety of types. Bodies of JSON-RPC method handlers are //! responsible for assigning an error code for these errors. -use jsonrpsee::types::{error::INTERNAL_ERROR_CODE, ErrorObject}; +use jsonrpsee::types::{ + error::{INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE}, + ErrorObject, +}; pub(crate) fn internal_error(err: impl ToString) -> ErrorObject<'static> { ErrorObject::owned(INTERNAL_ERROR_CODE, err.to_string(), None::<()>) } + +pub(crate) fn invalid_params(err: impl ToString) -> ErrorObject<'static> { + ErrorObject::owned(INVALID_PARAMS_CODE, err.to_string(), None::<()>) +} diff --git a/crates/sui-indexer-alt-jsonrpc/src/lib.rs b/crates/sui-indexer-alt-jsonrpc/src/lib.rs index 813aea2643d36..15e318cd0defb 100644 --- a/crates/sui-indexer-alt-jsonrpc/src/lib.rs +++ b/crates/sui-indexer-alt-jsonrpc/src/lib.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use anyhow::Context as _; use api::rpc_module::RpcModule; +use api::transactions::Transactions; use data::system_package_task::{SystemPackageTask, SystemPackageTaskArgs}; use jsonrpsee::server::{RpcServiceBuilder, ServerBuilder}; use metrics::middleware::MetricsLayer; @@ -209,6 +210,7 @@ pub async fn start_rpc( ); rpc.add_module(Governance(context.clone()))?; + rpc.add_module(Transactions(context.clone()))?; let h_rpc = rpc.run().await.context("Failed to start RPC service")?; let h_system_package_task = system_package_task.run();