Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

soroban-cli: Implement soroban contract read with real rpc support #756

Merged
merged 32 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
71670d0
add contract read --durability filter
Jul 5, 2023
73dba47
Add soroban contract bump subcommand
Jul 5, 2023
14607e0
Add soroban contract restore command
Jul 5, 2023
f4bcd99
Add support for restoring multiple keys at once
Jul 5, 2023
f4b7ecd
cargo md-gen
Jul 5, 2023
5d626e2
Merge remote-tracking branch 'origin/soroban-xdr-next' into cli-bump-…
Jul 7, 2023
274fb86
implement soroban contract read with a real network
Jul 7, 2023
95e57d2
Clean up an unused error
Jul 7, 2023
67d9203
Merge remote-tracking branch 'origin/soroban-xdr-next' into cli-bump-…
Jul 7, 2023
c132092
Make fmt
Jul 7, 2023
c2de82b
cargo md-gen
Jul 7, 2023
ac578b6
Merge remote-tracking branch 'origin/soroban-xdr-next' into cli-read-rpc
Jul 10, 2023
3af38fb
getLedgerEntries.results can be null
Jul 10, 2023
c0c1930
Need to increment the account sequence for bump+restore
Jul 10, 2023
ef37f69
no --ledgers-to-expire for restore command
Jul 10, 2023
f268a8b
Support bump+restore commands in preflight+transaction::assemble
Jul 10, 2023
ba81e14
Merge remote-tracking branch 'origin/soroban-xdr-next' into cli-bump-…
Jul 11, 2023
50553c4
Merge branch 'cli-bump-restore-subcommands' into cli-read-rpc
Jul 11, 2023
6e1f367
rust fmt
Jul 11, 2023
8bc8ce7
Merge branch 'cli-bump-restore-subcommands' into cli-read-rpc
Jul 11, 2023
d6780b9
cargo md-gen
Jul 11, 2023
1c95f7c
Merge branch 'cli-bump-restore-subcommands' into cli-read-rpc
Jul 11, 2023
26b2308
Require --durability for bump because the default is confusing
Jul 11, 2023
2ff100d
Merge remote-tracking branch 'origin/soroban-xdr-next' into cli-bump-…
Jul 11, 2023
52b5d4b
Merge branch 'cli-bump-restore-subcommands' into cli-read-rpc
Jul 11, 2023
792f2e3
Fix clippy
Jul 11, 2023
fae445f
Merge branch 'cli-bump-restore-subcommands' into cli-read-rpc
Jul 11, 2023
cde4856
Merge remote-tracking branch 'origin/soroban-xdr-next' into cli-bump-…
Jul 11, 2023
f505396
Merge branch 'cli-bump-restore-subcommands' into cli-read-rpc
Jul 11, 2023
b53d465
cargo md-gen
Jul 11, 2023
c2c17f9
Merge branch 'cli-bump-restore-subcommands' into cli-read-rpc
Jul 11, 2023
419ce49
Merge branch 'soroban-xdr-next' into cli-read-rpc
tsachiherman Jul 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl Cmd {
Cmd::Invoke(invoke) => invoke.run().await?,
Cmd::Optimize(optimize) => optimize.run()?,
Cmd::Fetch(fetch) => fetch.run().await?,
Cmd::Read(read) => read.run()?,
Cmd::Read(read) => read.run().await?,
Cmd::Restore(restore) => restore.run().await?,
}
Ok(())
Expand Down
116 changes: 95 additions & 21 deletions cmd/soroban-cli/src/commands/contract/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ use std::{
use clap::{command, Parser, ValueEnum};
use soroban_env_host::{
xdr::{
self, ContractDataEntry, ContractDataEntryBody, ContractDataEntryData,
ContractEntryBodyType, Error as XdrError, LedgerEntryData, LedgerKey,
LedgerKeyContractData, ReadXdr, ScAddress, ScSpecTypeDef, ScVal, WriteXdr,
self, ContractDataDurability, ContractDataEntry, ContractDataEntryBody,
ContractDataEntryData, ContractEntryBodyType, Error as XdrError, Hash, LedgerEntryData,
LedgerKey, LedgerKeyContractData, ReadXdr, ScAddress, ScSpecTypeDef, ScVal, WriteXdr,
},
HostError,
};

use crate::{
commands::config::{ledger_file, locator},
commands::config,
commands::contract::Durability,
rpc::{self, Client},
utils,
};

Expand All @@ -41,10 +42,7 @@ pub struct Cmd {
output: Output,

#[command(flatten)]
ledger: ledger_file::Args,

#[command(flatten)]
locator: locator::Args,
config: config::Args,
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
Expand All @@ -59,8 +57,6 @@ pub enum Output {

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Ledger(#[from] ledger_file::Error),
#[error("parsing key {key}: {error}")]
CannotParseKey {
key: String,
Expand All @@ -87,19 +83,22 @@ pub enum Error {
CannotPrintAsCsv { error: csv::Error },
#[error("cannot print: {error}")]
CannotPrintFlush { error: io::Error },
#[error(transparent)]
Config(#[from] config::Error),
#[error("either `--key` or `--key-xdr` are required when querying a network")]
KeyIsRequired,
#[error(transparent)]
Rpc(#[from] rpc::Error),
#[error("xdr processing error: {0}")]
Xdr(#[from] XdrError),
#[error(transparent)]
// TODO: the Display impl of host errors is pretty user-unfriendly
// (it just calls Debug). I think we can do better than that
Host(#[from] HostError),
#[error(transparent)]
Locator(#[from] locator::Error),
}

impl Cmd {
#[allow(clippy::too_many_lines)]
pub fn run(&self) -> Result<(), Error> {
pub async fn run(&self) -> Result<(), Error> {
let contract_id: [u8; 32] =
utils::contract_id_from_str(&self.contract_id).map_err(|e| {
Error::CannotParseContractId {
Expand Down Expand Up @@ -127,12 +126,81 @@ impl Cmd {
None
};

let state = self.ledger.read(&self.locator.config_dir()?)?;
let entries = if self.config.is_no_network() {
self.run_in_sandbox(contract_id, &key)?
} else {
self.run_against_rpc_server(contract_id, key).await?
};
self.output_entries(&entries)
}

async fn run_against_rpc_server(
&self,
contract_id: [u8; 32],
maybe_key: Option<ScVal>,
) -> Result<Vec<(LedgerKey, LedgerEntryData)>, Error> {
let network = self.config.get_network()?;
tracing::trace!(?network);
let network = &self.config.get_network()?;
let client = Client::new(&network.rpc_url)?;

let key = maybe_key.ok_or(Error::KeyIsRequired)?;

let keys: Vec<LedgerKey> = match self.durability {
Some(Durability::Persistent) => {
vec![Durability::Persistent]
}
Some(Durability::Temporary) => {
vec![Durability::Temporary]
}
None => {
vec![Durability::Persistent, Durability::Temporary]
}
}
.iter()
.map(|durability| {
LedgerKey::ContractData(LedgerKeyContractData {
contract: ScAddress::Contract(Hash(contract_id)),
key: key.clone(),
durability: (*durability).into(),
body_type: ContractEntryBodyType::DataEntry,
})
})
.collect::<Vec<_>>();

tracing::trace!(?keys);

client
.get_ledger_entries(keys)
.await?
.entries
.unwrap_or_default()
.iter()
.map(|result| {
let key = LedgerKey::from_xdr_base64(result.key.as_bytes());
let entry = LedgerEntryData::from_xdr_base64(result.xdr.as_bytes());
match (key, entry) {
(Ok(k), Ok(e)) => Ok((k, e)),
(Err(e), _) | (_, Err(e)) => Err(e),
}
})
.collect::<Result<Vec<(LedgerKey, LedgerEntryData)>, _>>()
.map_err(Error::Xdr)
}

#[allow(clippy::too_many_lines)]
fn run_in_sandbox(
&self,
contract_id: [u8; 32],
key: &Option<ScVal>,
) -> Result<Vec<(LedgerKey, LedgerEntryData)>, Error> {
let state = self.config.get_state()?;
let ledger_entries = &state.ledger_entries;

let contract = ScAddress::Contract(xdr::Hash(contract_id));
let durability: Option<xdr::ContractDataDurability> = self.durability.map(Into::into);
let entries: Vec<(ScVal, ScVal)> = ledger_entries
let durability: Option<ContractDataDurability> = self.durability.map(Into::into);

Ok(ledger_entries
.iter()
.map(|(k, v)| (k.as_ref().clone(), v.as_ref().clone()))
.filter(|(k, _v)| {
Expand Down Expand Up @@ -173,20 +241,26 @@ impl Cmd {
}
false
})
.map(|(_k, v)| v)
.filter_map(|val| {
.map(|(k, v)| (k, v.data))
.collect::<Vec<_>>())
}

fn output_entries(&self, raw_entries: &[(LedgerKey, LedgerEntryData)]) -> Result<(), Error> {
let entries = raw_entries
.iter()
.filter_map(|(_k, data)| {
if let LedgerEntryData::ContractData(ContractDataEntry {
key,
body: ContractDataEntryBody::DataEntry(ContractDataEntryData { val, .. }),
..
}) = &val.data
}) = &data
{
Some((key.clone(), val.clone()))
} else {
None
}
})
.collect();
.collect::<Vec<_>>();

let mut out = csv::Writer::from_writer(stdout());
for (key, val) in entries {
Expand Down
17 changes: 10 additions & 7 deletions cmd/soroban-cli/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ pub struct LedgerEntryResult {

#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct GetLedgerEntriesResponse {
pub entries: Vec<LedgerEntryResult>,
pub entries: Option<Vec<LedgerEntryResult>>,
#[serde(rename = "latestLedger")]
pub latest_ledger: String,
}
Expand Down Expand Up @@ -447,10 +447,11 @@ impl Client {
});
let keys = Vec::from([key]);
let response = self.get_ledger_entries(keys).await?;
if response.entries.is_empty() {
let entries = response.entries.unwrap_or_default();
if entries.is_empty() {
return Err(Error::MissingResult);
}
let ledger_entry = &response.entries[0];
let ledger_entry = &entries[0];
if let LedgerEntryData::Account(entry) =
LedgerEntryData::read_xdr_base64(&mut ledger_entry.xdr.as_bytes())?
{
Expand Down Expand Up @@ -653,10 +654,11 @@ impl Client {
body_type: xdr::ContractEntryBodyType::DataEntry,
});
let contract_ref = self.get_ledger_entries(Vec::from([contract_key])).await?;
if contract_ref.entries.is_empty() {
let entries = contract_ref.entries.unwrap_or_default();
if entries.is_empty() {
return Err(Error::MissingResult);
}
let contract_ref_entry = &contract_ref.entries[0];
let contract_ref_entry = &entries[0];
match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr)? {
LedgerEntryData::ContractData(contract_data) => Ok(contract_data),
scval => Err(Error::UnexpectedContractCodeDataType(scval)),
Expand Down Expand Up @@ -687,10 +689,11 @@ impl Client {
body_type: xdr::ContractEntryBodyType::DataEntry,
});
let contract_data = self.get_ledger_entries(Vec::from([code_key])).await?;
if contract_data.entries.is_empty() {
let entries = contract_data.entries.unwrap_or_default();
if entries.is_empty() {
return Err(Error::MissingResult);
}
let contract_data_entry = &contract_data.entries[0];
let contract_data_entry = &entries[0];
match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr)? {
LedgerEntryData::ContractCode(xdr::ContractCodeEntry {
body: xdr::ContractCodeEntryBody::DataEntry(code),
Expand Down
5 changes: 5 additions & 0 deletions docs/soroban-cli-full-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,12 @@ Print the current value of a contract-data ledger entry
- `xdr`:
XDR

* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
* `--network <NETWORK>` — Name of network to use from config
* `--ledger-file <LEDGER_FILE>` — File to persist ledger state, default is `.soroban/ledger.json`
* `--source-account <SOURCE_ACCOUNT>` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed`
* `--hd-path <HD_PATH>` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0`
* `--global` — Use global config
* `--config-dir <CONFIG_DIR>`

Expand Down