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-rpc: get expiration ledger sequence at source #916

Merged
merged 43 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
522ce05
Bump Rust and Go dependencies
2opremio Aug 31, 2023
333f364
Start migrating soroban-rpc
2opremio Aug 31, 2023
f3ddedb
Fix get_ledger_changes() invocation
2opremio Aug 31, 2023
13aae97
wip migrating the soroban-cli (incomplete!)
leighmcculloch Sep 1, 2023
43f45c2
Appease rust
2opremio Sep 1, 2023
3b76940
Bump core dependencies
2opremio Sep 1, 2023
7f8024a
Bump Go dependencies again
2opremio Sep 1, 2023
6e515b8
Fix test comparisons
2opremio Sep 1, 2023
2781026
Fix BumpAndRestoreFootprint test
2opremio Sep 1, 2023
f5a9803
Improve restore test a bit further
2opremio Sep 1, 2023
1a58165
Adjust tests further
2opremio Sep 1, 2023
eb90f6b
Fix typo
2opremio Sep 1, 2023
f1cd7c6
Fix GetPreflight test
2opremio Sep 1, 2023
0e414da
Get CLI to compile and fix most TODOs
2opremio Sep 1, 2023
ed9fb10
run cargo md-gen
2opremio Sep 1, 2023
44bc323
Appease clippy
2opremio Sep 1, 2023
f6210f5
Add comment about expiration entry efficiency
2opremio Sep 1, 2023
ff1a045
Address review feedback
2opremio Sep 1, 2023
315b6b1
Take a stab at extracting the spec from the state
2opremio Sep 1, 2023
b1519a4
Merge branch 'main' into state-expiration-updates
2opremio Sep 1, 2023
d95def2
update
tsachiherman Sep 1, 2023
5aac804
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Sep 4, 2023
ac85d7e
linter
tsachiherman Sep 4, 2023
ef33050
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Sep 4, 2023
bf51bfe
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Sep 5, 2023
a441688
limit get_ledger_entries to expiration ledger entries.
tsachiherman Sep 5, 2023
42ccee9
fix cli serialization
tsachiherman Sep 5, 2023
9b9af64
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Sep 6, 2023
ad8d4ab
update test
tsachiherman Sep 6, 2023
bd3e641
further fix..
tsachiherman Sep 6, 2023
1928ea8
further fix.
tsachiherman Sep 6, 2023
b721201
further fix
tsachiherman Sep 6, 2023
f2655d2
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Oct 11, 2023
83d044b
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Oct 11, 2023
ec013b7
update test
tsachiherman Oct 11, 2023
babcb57
fix typo
tsachiherman Oct 11, 2023
b4838f8
fix: update CLI and tests for new getLedgerEntries (#1021)
willemneal Oct 12, 2023
caccfb3
step
tsachiherman Oct 12, 2023
0b3c41a
Merge branch 'tsachi/ledgerentries' of https://github.com/stellar/sor…
tsachiherman Oct 12, 2023
1c47ba8
update
tsachiherman Oct 12, 2023
29f0ae7
fix tests
tsachiherman Oct 12, 2023
e095f83
updating
tsachiherman Oct 12, 2023
9c44d71
update per code review
tsachiherman Oct 13, 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
4 changes: 2 additions & 2 deletions cmd/crates/soroban-test/tests/it/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ fn contract_data_read() {
.arg("--durability=persistent")
.assert()
.success()
.stdout("COUNTER,1,4096\n");
.stdout("COUNTER,1,4,4096\n");

sandbox
.new_assert_cmd("contract")
Expand All @@ -331,7 +331,7 @@ fn contract_data_read() {
.arg("--durability=persistent")
.assert()
.success()
.stdout("COUNTER,2,4096\n");
.stdout("COUNTER,2,4,4096\n");
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/bump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ impl Cmd {

if operations[0].changes.is_empty() {
let entry = client.get_full_ledger_entries(&keys).await?;
let expire = entry.entries[0].expiration.expiration_ledger_seq;
let expire = entry.entries[0].expiration_ledger_seq;
if entry.latest_ledger + i64::from(ledgers_to_expire) < i64::from(expire) {
return Ok(expire);
}
Expand Down
33 changes: 18 additions & 15 deletions cmd/soroban-cli/src/commands/contract/read.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use std::{
convert::Into,
fmt::Debug,
io::{self, stdout},
};

use clap::{command, Parser, ValueEnum};
use sha2::{Digest, Sha256};
use soroban_env_host::{
xdr::{
ContractDataEntry, Error as XdrError, ExpirationEntry, Hash, LedgerEntryData, LedgerKey,
LedgerKeyContractData, ScVal, WriteXdr,
ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData,
ScVal, WriteXdr,
},
HostError,
};
Expand Down Expand Up @@ -113,18 +111,16 @@ impl Cmd {
fn run_in_sandbox(&self) -> Result<FullLedgerEntries, Error> {
let state = self.config.get_state()?;
let ledger_entries = &state.ledger_entries;

let latest_ledger = u32::try_from(state.ledger_entries.len()).unwrap();
let keys = self.key.parse_keys()?;
let entries = ledger_entries
.iter()
.map(|(k, v)| (k.as_ref().clone(), (v.0.as_ref().clone(), v.1)))
.filter(|(k, _v)| keys.contains(k))
.map(|(key, (v, expiration))| {
Ok(FullLedgerEntry {
expiration: ExpirationEntry {
key_hash: Hash(Sha256::digest(key.to_xdr()?).into()),
expiration_ledger_seq: expiration.unwrap_or_default(),
},
expiration_ledger_seq: expiration.unwrap_or_default(),
last_modified_ledger: latest_ledger,
key,
val: v.data,
})
Expand All @@ -145,7 +141,8 @@ impl Cmd {
for FullLedgerEntry {
key,
val,
expiration,
expiration_ledger_seq,
last_modified_ledger,
} in &entries.entries
{
let (
Expand All @@ -155,8 +152,6 @@ impl Cmd {
else {
return Err(Error::OnlyDataAllowed);
};
let expiration = expiration.expiration_ledger_seq;

let output = match self.output {
Output::String => [
soroban_spec_tools::to_string(key).map_err(|e| Error::CannotPrintResult {
Expand All @@ -167,7 +162,8 @@ impl Cmd {
result: val.clone(),
error: e,
})?,
expiration.to_string(),
last_modified_ledger.to_string(),
expiration_ledger_seq.to_string(),
],
Output::Json => [
serde_json::to_string_pretty(&key).map_err(|error| {
Expand All @@ -182,7 +178,13 @@ impl Cmd {
error,
}
})?,
serde_json::to_string_pretty(&expiration).map_err(|error| {
serde_json::to_string_pretty(&last_modified_ledger).map_err(|error| {
Error::CannotPrintJsonResult {
result: val.clone(),
error,
}
})?,
serde_json::to_string_pretty(&expiration_ledger_seq).map_err(|error| {
Error::CannotPrintJsonResult {
result: val.clone(),
error,
Expand All @@ -192,7 +194,8 @@ impl Cmd {
Output::Xdr => [
key.to_xdr_base64()?,
val.to_xdr_base64()?,
expiration.to_xdr_base64()?,
last_modified_ledger.to_xdr_base64()?,
expiration_ledger_seq.to_xdr_base64()?,
],
};
out.write_record(output)
Expand Down
86 changes: 46 additions & 40 deletions cmd/soroban-cli/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ use itertools::Itertools;
use jsonrpsee_core::params::ObjectParams;
use jsonrpsee_core::{self, client::ClientT, rpc_params};
use jsonrpsee_http_client::{HeaderMap, HttpClient, HttpClientBuilder};
use serde_aux::prelude::{deserialize_default_from_null, deserialize_number_from_string};
use sha2::{Digest, Sha256};
use serde_aux::prelude::{
deserialize_default_from_null, deserialize_number_from_string,
deserialize_option_number_from_string,
};
use soroban_env_host::xdr::{
self, AccountEntry, AccountId, ContractDataEntry, DiagnosticEvent, Error as XdrError,
ExpirationEntry, LedgerEntryData, LedgerFootprint, LedgerKey, LedgerKeyAccount,
LedgerKeyExpiration, PublicKey, ReadXdr, SequenceNumber, SorobanAuthorizationEntry,
SorobanResources, Transaction, TransactionEnvelope, TransactionMeta, TransactionMetaV3,
TransactionResult, TransactionV1Envelope, Uint256, VecM, WriteXdr,
LedgerEntryData, LedgerFootprint, LedgerKey, LedgerKeyAccount, PublicKey, ReadXdr,
SequenceNumber, SorobanAuthorizationEntry, SorobanResources, Transaction, TransactionEnvelope,
TransactionMeta, TransactionMetaV3, TransactionResult, TransactionV1Envelope, Uint256, VecM,
WriteXdr,
};
use soroban_env_host::xdr::{DepthLimitedRead, SorobanAuthorizedFunction};
use soroban_sdk::token;
Expand Down Expand Up @@ -96,8 +98,8 @@ pub enum Error {
SpecBase64(#[from] soroban_spec::read::ParseSpecBase64Error),
#[error("Fee was too large {0}")]
LargeFee(u64),
#[error("Failed to parse LedgerEntryData")]
FailedParseLedgerEntryData,
#[error("Failed to parse LedgerEntryData\nkey:{0:?}\nvalue:{1:?}\nexpiration:{2:?}")]
FailedParseLedgerEntryData(LedgerKey, LedgerEntryData, LedgerEntryData),
}

#[derive(serde::Deserialize, serde::Serialize, Debug)]
Expand Down Expand Up @@ -146,8 +148,18 @@ pub struct GetTransactionResponse {
pub struct LedgerEntryResult {
pub key: String,
pub xdr: String,
#[serde(rename = "lastModifiedLedgerSeq")]
pub last_modified_ledger: String,
#[serde(
rename = "lastModifiedLedgerSeq",
deserialize_with = "deserialize_number_from_string"
)]
pub last_modified_ledger: u32,
#[serde(
rename = "expirationLedgerSeq",
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_option_number_from_string",
default
)]
pub expiration_ledger_seq: Option<u32>,
}

#[derive(serde::Deserialize, serde::Serialize, Debug)]
Expand Down Expand Up @@ -427,7 +439,8 @@ pub enum EventStart {
pub struct FullLedgerEntry {
pub key: LedgerKey,
pub val: LedgerEntryData,
pub expiration: ExpirationEntry,
pub last_modified_ledger: u32,
pub expiration_ledger_seq: u32,
}

#[derive(Debug)]
Expand Down Expand Up @@ -495,10 +508,10 @@ impl Client {

pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result<String, Error> {
let server = self.get_network().await?.passphrase;
if expected != Some(&server) {
if let Some(expected_val) = expected {
if let Some(expected) = expected {
if expected != server {
return Err(Error::InvalidNetworkPassphrase {
expected: expected_val.to_string(),
expected: expected.to_string(),
server,
});
}
Expand Down Expand Up @@ -769,35 +782,35 @@ soroban config identity fund {address} --helper-url <url>"#
) -> Result<FullLedgerEntries, Error> {
let keys = ledger_keys
.iter()
.map(|key| Ok(into_keys(key.clone())?.into_iter()))
.flatten_ok()
.collect::<Result<Vec<_>, Error>>()?;
tracing::trace!("{keys:#?}");
.filter(|key| !matches!(key, LedgerKey::Expiration(_)))
.map(Clone::clone)
.collect::<Vec<_>>();
tracing::trace!("keys: {keys:#?}");
let GetLedgerEntriesResponse {
entries,
latest_ledger,
} = self.get_ledger_entries(&keys).await?;
tracing::trace!(?entries);
tracing::trace!("raw: {entries:#?}");
let entries = entries
.as_deref()
.unwrap_or_default()
.iter()
.tuple_windows()
.map(|(key_res, entry_res)| {
let expiration = LedgerEntryData::from_xdr_base64(&entry_res.xdr)?;
if let LedgerEntryData::Expiration(expiration) = expiration {
let key = LedgerKey::from_xdr_base64(&key_res.key)?;
let val = LedgerEntryData::from_xdr_base64(&key_res.xdr)?;
.map(
|LedgerEntryResult {
key,
xdr,
last_modified_ledger,
expiration_ledger_seq,
}| {
Ok(FullLedgerEntry {
key,
val,
expiration,
key: LedgerKey::from_xdr_base64(key)?,
val: LedgerEntryData::from_xdr_base64(xdr)?,
expiration_ledger_seq: expiration_ledger_seq.unwrap_or_default(),
last_modified_ledger: *last_modified_ledger,
})
} else {
Err(Error::FailedParseLedgerEntryData)
}
})
},
)
.collect::<Result<Vec<_>, Error>>()?;
tracing::trace!("parsed: {entries:#?}");
Ok(FullLedgerEntries {
entries,
latest_ledger,
Expand Down Expand Up @@ -954,13 +967,6 @@ pub fn parse_cursor(c: &str) -> Result<(u64, i32), Error> {
Ok((toid_part, start_index))
}

fn into_keys(key: LedgerKey) -> Result<[LedgerKey; 2], Error> {
let expiration = LedgerKey::Expiration(LedgerKeyExpiration {
key_hash: xdr::Hash(Sha256::digest(key.to_xdr()?).into()),
});
Ok([key, expiration])
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
78 changes: 67 additions & 11 deletions cmd/soroban-rpc/internal/db/ledgerentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package db

import (
"context"
"crypto/sha256"
"database/sql"
"fmt"

Expand All @@ -23,8 +24,9 @@ type LedgerEntryReader interface {
}

type LedgerKeyAndEntry struct {
Key xdr.LedgerKey
Entry xdr.LedgerEntry
Key xdr.LedgerKey
Entry xdr.LedgerEntry
ExpirationLedgerSeq *uint32 // optional expiration ledger seq, when applicable.
}

type LedgerEntryReadTx interface {
Expand Down Expand Up @@ -216,32 +218,71 @@ func (l *ledgerEntryReadTx) getRawLedgerEntries(keys ...string) (map[string]stri
return result, nil
}

func GetLedgerEntry(tx LedgerEntryReadTx, key xdr.LedgerKey) (bool, xdr.LedgerEntry, error) {
func GetLedgerEntry(tx LedgerEntryReadTx, key xdr.LedgerKey) (bool, xdr.LedgerEntry, *uint32, error) {
keyEntries, err := tx.GetLedgerEntries(key)
if err != nil {
return false, xdr.LedgerEntry{}, err
return false, xdr.LedgerEntry{}, nil, err
}
switch len(keyEntries) {
case 0:
return false, xdr.LedgerEntry{}, nil
return false, xdr.LedgerEntry{}, nil, nil
case 1:
// expected length
return true, keyEntries[0].Entry, nil
return true, keyEntries[0].Entry, keyEntries[0].ExpirationLedgerSeq, nil
default:
return false, xdr.LedgerEntry{}, fmt.Errorf("multiple entries (%d) for key %v", len(keyEntries), key)
return false, xdr.LedgerEntry{}, nil, fmt.Errorf("multiple entries (%d) for key %v", len(keyEntries), key)
}
}

// isExpirableKey check to see if the key type is expected to be accompanied by a LedgerExpirationEntry
func isExpirableKey(key xdr.LedgerKey) bool {
switch key.Type {
case xdr.LedgerEntryTypeContractData:
return true
case xdr.LedgerEntryTypeContractCode:
return true
default:
}
return false
}

func entryKeyToExpirationEntryKey(key xdr.LedgerKey) (xdr.LedgerKey, error) {
buf, err := key.MarshalBinary()
if err != nil {
return xdr.LedgerKey{}, err
}
var expirationEntry xdr.LedgerKey
err = expirationEntry.SetExpiration(xdr.Hash(sha256.Sum256(buf)))
if err != nil {
return xdr.LedgerKey{}, err
}
return expirationEntry, nil
}

func (l *ledgerEntryReadTx) GetLedgerEntries(keys ...xdr.LedgerKey) ([]LedgerKeyAndEntry, error) {
encodedKeys := make([]string, len(keys))
encodedKeys := make([]string, len(keys), 2*len(keys))
encodedKeyToKey := make(map[string]xdr.LedgerKey, len(keys))
for i, k := range keys {
encodedKeyToEncodedExpirationLedgerKey := make(map[string]string, len(keys))
for _, k := range keys {
encodedKey, err := encodeLedgerKey(l.buffer, k)
if err != nil {
return nil, err
}
encodedKeys[i] = encodedKey
encodedKeys = append(encodedKeys, encodedKey)
encodedKeyToKey[encodedKey] = k
if !isExpirableKey(k) {
continue
}
expirationEntryKey, err := entryKeyToExpirationEntryKey(k)
if err != nil {
return nil, err
}
encodedExpirationKey, err := encodeLedgerKey(l.buffer, expirationEntryKey)
if err != nil {
return nil, err
}
encodedKeyToEncodedExpirationLedgerKey[encodedKey] = encodedExpirationKey
encodedKeys = append(encodedKeys, encodedExpirationKey)
}

rawResult, err := l.getRawLedgerEntries(encodedKeys...)
Expand All @@ -259,7 +300,22 @@ func (l *ledgerEntryReadTx) GetLedgerEntries(keys ...xdr.LedgerKey) ([]LedgerKey
if err := xdr.SafeUnmarshal([]byte(encodedEntry), &entry); err != nil {
return nil, errors.Wrap(err, "cannot decode ledger entry from DB")
}
result = append(result, LedgerKeyAndEntry{key, entry})
encodedExpKey, has := encodedKeyToEncodedExpirationLedgerKey[encodedKey]
if !has {
result = append(result, LedgerKeyAndEntry{key, entry, nil})
continue
}
encodedExpEntry, ok := rawResult[encodedExpKey]
if !ok {
// missing expiration key. this should no happen.
return nil, errors.New("missing expiration key entry")
}
var expEntry xdr.LedgerEntry
if err := xdr.SafeUnmarshal([]byte(encodedExpEntry), &expEntry); err != nil {
return nil, errors.Wrap(err, "cannot decode expiration ledger entry from DB")
}
expSeq := uint32(expEntry.Data.Expiration.ExpirationLedgerSeq)
result = append(result, LedgerKeyAndEntry{key, entry, &expSeq})
}

return result, nil
Expand Down
Loading
Loading