From 0bd83d401f441de467ff7e89fa1337474e6aea58 Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 19 Apr 2023 10:47:15 -0700 Subject: [PATCH 1/2] Added getLedgerEntries and deprecation warning for getLedgerEntry --- api/methods/getLedgerEntries.mdx | 318 +++++++++++++++++++++++++++++++ api/methods/getLedgerEntry.mdx | 2 +- 2 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 api/methods/getLedgerEntries.mdx diff --git a/api/methods/getLedgerEntries.mdx b/api/methods/getLedgerEntries.mdx new file mode 100644 index 00000000..8c1f5d25 --- /dev/null +++ b/api/methods/getLedgerEntries.mdx @@ -0,0 +1,318 @@ +--- +sidebar_position: 4 +--- +For reading the current value of ledger entries directly. + +Allows you to directly inspect the _current state_ of a contract, a contract's code, or any other ledger entry. This is a backup way to access your contract data which may not be available via events or `simulateTransaction`. + +To fetch contract wasm byte-code, use the ContractCode ledger entry key. + +## Parameters + +- `keys`: `` - The keys of the ledger entries you wish to retrieve (serialized in a base64 string) + +## Return + +- `` + - `entries`: `` - Found ledger entries returned in the same order as given + - `xdr`: `` - The current value of this ledger entry (serialized in a base64 string) + - `lastModifiedLedgerSeq`: `` - The ledger number of the last time this entry was updated (optional) + - `latestLedger`: `` - The current latest ledger observed by the node when this response was generated. + +## Examples + +### Request + +```json +{ + "jsonrpc": "2.0", + "id": 8675309, + "method": "getLedgerEntries", + "params": { + "keys": [ + "AAAABhv6ziOnWcVRdGMZjtFKSWnLSndMp9JPVLLXxQqAvKqJAAAABQAAAAdDT1VOVEVSAA==" + ] + } +} +``` + +### Response + +```json +{ + "jsonrpc": "2.0", + "id": 8675309, + "result": { + "entries": [ + { + "xdr": "AAAABhv6ziOnWcVRdGMZjtFKSWnLSndMp9JPVLLXxQqAvKqJAAAABQAAAAdDT1VOVEVSAAAAAAEAAAAD", + "lastModifiedLedgerSeq": "164986", + } + ], + "latestLedger": "179436" + } +} +``` + +### Generating `keys` Parameters + +The example above is querying a deployment of the [`increment` example contract] to find out what value is stored in the `COUNTER` ledger entry. This value can be derived using the following code snippets. You should be able to extrapolate from the provided examples how to get `keys` parameters for other types and values. + +#### Python + +:::note + +If you are using the Python `stellar_sdk` to generate these keys, you will need to install the latest version of the `soroban` branch of the SDK. This can be done like so: + +```bash +pip install git+https://github.com/StellarCN/py-stellar-base.git@soroban +``` + +::: + +```python +import stellar_sdk.xdr as xdr, stellar_sdk.soroban_types as soroban_types + +def get_ledger_key_symbol(contract_id, symbol_text): + ledger_key = xdr.ledger_key.LedgerKey( + type=xdr.ledger_entry_type.LedgerEntryType.CONTRACT_DATA, + contract_data=xdr.ledger_key_contract_data.LedgerKeyContractData( + contract_id=xdr.hash.Hash(bytes.fromhex(contract_id)), + key=soroban_types.Symbol(symbol_text)._to_xdr_sc_val() + ) + ) + return ledger_key.to_xdr() + +print(get_ledger_key_symbol( + "1bface23a759c5517463198ed14a4969cb4a774ca7d24f54b2d7c50a80bcaa89", + "COUNTER" +)) +``` + +#### JavaScript + +```js +const SorobanClient = require('soroban-client') +const xdr = SorobanClient.xdr + +const getLedgerKeySymbol = (contractId, symbolText) => { + const ledgerKey = xdr.LedgerKey.contractData( + new xdr.LedgerKeyContractData({ + contractId: Buffer.from(contractId, 'hex'), + key: xdr.ScVal.scvSymbol(symbolText) + }) + ) + return ledgerKey.toXDR('base64') +} +console.log(getLedgerKeySymbol( + '1bface23a759c5517463198ed14a4969cb4a774ca7d24f54b2d7c50a80bcaa89', + 'COUNTER' +)) +``` + +### Requesting an Account + +:::note + +This functionality is included in the js [soroban-client](https://www.npmjs.com/package/soroban-client) package as `Server.getAccount(address)`. + +::: + +Accounts are stored as ledger entries, so we can use this method to look up an account along with it's current sequence number. + +```js +const SorobanClient = require('soroban-client') +const xdr = SorobanClient.xdr + +const getLedgerKeyAccount = (address) => { + // We can use the `StrKey` library here to decode the address into the public + // key. + const publicKey = SorobanClient.StrKey.decodeEd25519PublicKey(address); + + const ledgerKey = xdr.LedgerKey.account( + new xdr.LedgerKeyAccount({ + accountId: xdr.PublicKey.publicKeyTypeEd25519( + publicKey + ), + }) + ) + return ledgerKey.toXDR('base64') +} + +console.log(getLedgerKeyAccount( + 'GCU5YE6IVBOEZ5LUU5N2NB55VPB5YZNFERT65SSTVXTNMS7IEQWXKBM2' +)) + +# OUTPUT: AAAAAAAAAACp3BPIqFxM9XSnW6aHvavD3GWlJGfuylOt5tZL6CQtdQ== +``` + + +We then take our output from this function, and use it as the `keys` parameter in our call to the `getLedgerEntries` method. + +```json +{ + "jsonrpc": "2.0", + "id": 8675309, + "method": "getLedgerEntries", + "params": { + "keys": [ + "AAAAAAAAAACp3BPIqFxM9XSnW6aHvavD3GWlJGfuylOt5tZL6CQtdQ==" + ] + } +} +``` + +And the response we get contains the `LedgerEntryData` with the current information about this account. + +```json +{ + "jsonrpc": "2.0", + "id": 8675309, + "result": { + "entries": [ + { + "xdr": "AAAAAAAAAACp3BPIqFxM9XSnW6aHvavD3GWlJGfuylOt5tZL6CQtdQAAABdIdugAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA", + "lastModifiedLedgerSeq": "164303", + }, + ], + "latestLedger": "246819" + } +} +``` + +We can then parse this result as an `xdr.LedgerEntryData` type. + +```js +const parsed = xdr.LedgerEntryData.fromXDR( + 'AAAAAAAAAACp3BPIqFxM9XSnW6aHvavD3GWlJGfuylOt5tZL6CQtdQAAABdIdugAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA', + 'base64' +) +console.log(parsed); +``` + +### Requesting a Contract's WASM Code + +This can be a bit tricky to wrap your head around, but the conventions do make sense once you let it sink in. + +In the previous examples, the `COUNTER` _key_ was used as a `LedgerKey` while the incremented _value_ was stored in a **`LedgerEntry`**. "Ledger Entry" is the relevant term to keep in mind during this discussion. That `LedgerEntry` was stored on the Stellar ledger, and was associated with a corresponding `LedgerKey`. `LedgerKey: LedgerEntry` works the same way you would think of almost any `key: value` storage system. + +#### How Soroban Contract Deployment Works + +When you deploy a contract, first the code is "installed" (i.e. it is uploaded onto the blockchain). This creates a `LedgerEntry` containing the WASM byte-code, which is uniquely identified by its hash (that is, the hash of the uploaded code itself). Then, when the contract is "deployed," we create a `LedgerEntry` with a reference to that code's hash. So fetching the contract code is a two-step process: + +1. First, we look up the contract itself, to see which code hash it is referencing. +2. Then, we can look up the raw WASM byte-code using that hash. + +#### Request the `LedgerKey` for the Contract Code + +```python +def get_ledger_key_contract_code(contract_id): + ledger_key = xdr.ledger_key.LedgerKey( + type=xdr.ledger_entry_type.LedgerEntryType.CONTRACT_DATA, + contract_data=xdr.ledger_key_contract_data.LedgerKeyContractData( + contract_id=xdr.hash.Hash(bytes.fromhex(contract_id)), + key=xdr.sc_val.SCVal( + type=xdr.sc_val_type.SCValType.SCV_STATIC, + ic=xdr.sc_static.SCStatic.SCS_LEDGER_KEY_CONTRACT_CODE + ) + ) + ) + return ledger_key.to_xdr() +print(get_ledger_key_contract_code( + "1bface23a759c5517463198ed14a4969cb4a774ca7d24f54b2d7c50a80bcaa89" +)) +# OUTPUT: AAAABhv6ziOnWcVRdGMZjtFKSWnLSndMp9JPVLLXxQqAvKqJAAAAAwAAAAM= +``` + +We then take our output from this function, and use it as the `keys` parameter in our call to the `getLedgerEntries` method. + +```json +{ + "jsonrpc": "2.0", + "id": 8675309, + "method": "getLedgerEntries", + "params": { + "keys": [ + "AAAABhv6ziOnWcVRdGMZjtFKSWnLSndMp9JPVLLXxQqAvKqJAAAAAwAAAAM=" + ] + } +} +``` + +And the response we get contains the `LedgerEntryData` that can be used to find the `hash` we must use to request the WASM byte-code. This hash is the `LedgerKey` that's been associated with the deployed contract code. + +```json +{ + "jsonrpc": "2.0", + "id": 8675309, + "result": { + "entries": [ + { + "xdr": "AAAABhv6ziOnWcVRdGMZjtFKSWnLSndMp9JPVLLXxQqAvKqJAAAAAwAAAAMAAAAEAAAAAQAAAAcAAAAA1CpZRz0BSN27PmqtsmBhv+AAJJwHgmrvJNPrHRAl9l8=", + "lastModifiedLedgerSeq": "164303", + } + ], + "latestLedger": "246819" + } +} +``` + +#### Request the `ContractCode` Using the Retrieved `LedgerKey` + +Now take the `xdr` field from the previous response's `result` object, and create a `LedgerKey` from the hash contained inside. + +```python +def get_ledger_key_wasm_id(contract_code_ledger_entry_data): + # First, we dig the wasm_id hash out of the xdr we received from RPC + contract_code_wasm_hash = xdr.ledger_entry_data.LedgerEntryData.from_xdr( + contract_code_ledger_entry_data + ).contract_data.val.obj.contract_code.wasm_id.hash + # Now, we can create the `LedgerKey` as we've done in previous examples + ledger_key = xdr.ledger_key.LedgerKey( + type=xdr.ledger_entry_type.LedgerEntryType.CONTRACT_CODE, + contract_code=xdr.ledger_key_contract_code.LedgerKeyContractCode( + hash=xdr.hash.Hash(contract_code_wasm_hash) + ) + ) + return ledger_key.to_xdr() +print(get_ledger_key_wasm_id( + "AAAABhv6ziOnWcVRdGMZjtFKSWnLSndMp9JPVLLXxQqAvKqJAAAAAwAAAAMAAAAEAAAAAQAAAAcAAAAA1CpZRz0BSN27PmqtsmBhv+AAJJwHgmrvJNPrHRAl9l8=" +)) +# OUTPUT: AAAAB9QqWUc9AUjduz5qrbJgYb/gACScB4Jq7yTT6x0QJfZf +``` + +Now, finally we have a `LedgerKey` that correspond to the WASM byte-code that has been deployed under the `ContractId` we started out with so very long ago. This `LedgerKey` can be used in a final request to the Soroban-RPC endpoint. + +```json +{ + "jsonrpc": "2.0", + "id": 8675309, + "method": "getLedgerEntries", + "params": { + "keys": [ + "AAAAB9QqWUc9AUjduz5qrbJgYb/gACScB4Jq7yTT6x0QJfZf" + ] + } +} +``` + +And the response we get contains (even more) `LedgerEntryData` that we can decode and parse to get the actual, deployed, real-life contract byte-code. We'll leave that exercise up to you. You can check out what is contained using the ["View XDR" page of the Stellar Laboratory]. + +```json +{ + "jsonrpc": "2.0", + "id": 8675309, + "result": { + "entries": [ + { + "xdr": "AAAABwAAAADUKllHPQFI3bs+aq2yYGG/4AAknAeCau8k0+sdECX2XwAAAakAYXNtAQAAAAEXBWABfgF+YAJ+fgF+YAABfmABfwBgAAACEwMBbAEwAAABbAExAAABbAFfAAEDBgUCAwQEBAUDAQAQBhkDfwFBgIDAAAt/AEGAgMAAC38AQYCAwAALBzUFBm1lbW9yeQIACWluY3JlbWVudAADAV8ABwpfX2RhdGFfZW5kAwELX19oZWFwX2Jhc2UDAgrAAQWhAQMBfwF+AX8jgICAgABBEGsiACSAgICAAAJAAkBC2YP9sqDNAxCAgICAAEIVUg0AAkBC2YP9sqDNAxCBgICAACIBQg+DQgFSDQAgAUIEiKchAgwCCyAAQQhqEISAgIAAAAtBACECCwJAIAJBAWoiAg0AEIWAgIAAAAtC2YP9sqDNAyACrUIEhkIBhCIBEIKAgIAAGiAAQRBqJICAgIAAIAELCQAQhoCAgAAACwkAEIaAgIAAAAsEAAAACwIACwAeEWNvbnRyYWN0ZW52bWV0YXYwAAAAAAAAAAAAAAAbAC8OY29udHJhY3RzcGVjdjAAAAAAAAAACWluY3JlbWVudAAAAAAAAAAAAAABAAAAAQAAAA==", + "lastModifiedLedgerSeq": "164302", + } + ], + "latestLedger": "246883" + } +} +``` + +[`increment` example contract]: /docs/getting-started/storing-data +["View XDR" page of the Stellar Laboratory]: diff --git a/api/methods/getLedgerEntry.mdx b/api/methods/getLedgerEntry.mdx index 6b1c3904..208fc4f5 100644 --- a/api/methods/getLedgerEntry.mdx +++ b/api/methods/getLedgerEntry.mdx @@ -1,7 +1,7 @@ --- sidebar_position: 4 --- - +:::note **This function is deprecated and will be removed in the next Soroban release. Please use getLedgerEntries instead.** For reading the current value of ledger entries directly. Allows you to directly inspect the _current state_ of a contract, a contract's code, or any other ledger entry. This is a backup way to access your contract data which may not be available via events or `simulateTransaction`. From 1cd85cbf52f949636abbdbe352fc606184c32b49 Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 19 Apr 2023 10:48:10 -0700 Subject: [PATCH 2/2] Updated sidebar --- sidebarsApi.js | 1 + 1 file changed, 1 insertion(+) diff --git a/sidebarsApi.js b/sidebarsApi.js index 933d325c..e6dad21c 100644 --- a/sidebarsApi.js +++ b/sidebarsApi.js @@ -33,6 +33,7 @@ module.exports = { 'methods/getHealth', 'methods/getLatestLedger', 'methods/getLedgerEntry', + 'methods/getLedgerEntries', 'methods/getNetwork', 'methods/getTransaction', 'methods/sendTransaction',