Skip to content

Commit

Permalink
Add rpc getLedgerEntry method
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Bellamy committed Nov 30, 2022
1 parent 4a97f1d commit b10c416
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 1 deletion.
13 changes: 12 additions & 1 deletion cmd/soroban-cli/src/rpc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use jsonrpsee_core::{client::ClientT, rpc_params};
use jsonrpsee_http_client::{HeaderMap, HttpClient, HttpClientBuilder};
use soroban_env_host::xdr::{Error as XdrError, ScVal, TransactionEnvelope, WriteXdr};
use soroban_env_host::xdr::{Error as XdrError, LedgerKey, ScVal, TransactionEnvelope, WriteXdr};
use std::time::{Duration, Instant};
use tokio::time::sleep;

Expand Down Expand Up @@ -185,4 +185,15 @@ impl Client {
.request("getContractData", rpc_params![contract_id, base64_key])
.await?)
}

pub async fn get_ledger_entry(
&self,
key: LedgerKey,
) -> Result<GetLedgerEntryResponse, Error> {
let base64_key = key.to_xdr_base64()?;
Ok(self
.client()?
.request("getLedgerEntry", rpc_params![base64_key])
.await?)
}
}
25 changes: 25 additions & 0 deletions cmd/soroban-cli/src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ async fn handler(
("getContractData", Some(Requests::GetContractData((contract_id, key)))) => {
get_contract_data(&contract_id, key, &ledger_file)
}
("getLedgerEntry", Some(Requests::StringArg(key))) => {
get_ledger_entry(key, &ledger_file)
}
("getTransactionStatus", Some(Requests::StringArg(b))) => {
get_transaction_status(&transaction_status_map, b).await
}
Expand Down Expand Up @@ -233,6 +236,28 @@ fn get_contract_data(
}))
}

fn get_ledger_entry(
key_xdr: String,
ledger_file: &PathBuf,
) -> Result<Value, Error> {
// Initialize storage and host
let state = snapshot::read(ledger_file)?;
let key = LedgerKey::from_xdr_base64(key_xdr)?;

let snap = Rc::new(snapshot::Snap {
ledger_entries: state.1,
});
let mut storage = Storage::with_recording_footprint(snap);
let ledger_entry = storage.get(&key)?;

Ok(json!({
"xdr": ledger_entry.data.to_xdr_base64()?,
"lastModifiedLedgerSeq": ledger_entry.last_modified_ledger_seq,
// TODO: Find "real" ledger seq number here
"latestLedger": 1,
}))
}

fn parse_transaction(
txn_xdr: &str,
passphrase: &str,
Expand Down
1 change: 1 addition & 0 deletions cmd/soroban-rpc/internal/jsonrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func NewJSONRPCHandler(params HandlerParams) (Handler, error) {
bridge := jhttp.NewBridge(handler.Map{
"getHealth": methods.NewHealthCheck(),
"getAccount": methods.NewAccountHandler(params.AccountStore),
"getLedgerEntry": methods.NewGetLedgerEntryHandler(params.Logger, params.CoreClient),
"getTransactionStatus": methods.NewGetTransactionStatusHandler(params.TransactionProxy),
"sendTransaction": methods.NewSendTransactionHandler(params.TransactionProxy),
"simulateTransaction": methods.NewSimulateTransactionHandler(params.Logger, params.CoreClient),
Expand Down
83 changes: 83 additions & 0 deletions cmd/soroban-rpc/internal/methods/get_ledger_entry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package methods

import (
"context"

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/code"
"github.com/creachadair/jrpc2/handler"

"github.com/stellar/go/clients/stellarcore"
proto "github.com/stellar/go/protocols/stellarcore"
"github.com/stellar/go/support/log"
"github.com/stellar/go/xdr"
)

type GetLedgerEntryRequest struct {
Key string `json:"key"`
}

type GetLedgerEntryResponse struct {
XDR string `json:"xdr"`
LastModifiedLedger int64 `json:"lastModifiedLedgerSeq,string"`
LatestLedger int64 `json:"latestLedger,string"`
}

// NewGetLedgerEntryHandler returns a json rpc handler to retrieve a contract data ledger entry from stellar cre
func NewGetLedgerEntryHandler(logger *log.Entry, coreClient *stellarcore.Client) jrpc2.Handler {
return handler.New(func(ctx context.Context, request GetLedgerEntryRequest) (GetLedgerEntryResponse, error) {
var key xdr.LedgerKey
if err := xdr.SafeUnmarshalBase64(request.Key, &key); err != nil {
logger.WithError(err).WithField("request", request).
Info("could not unmarshal ledgerKey from getLedgerEntry request")
return GetLedgerEntryResponse{}, &jrpc2.Error{
Code: code.InvalidParams,
Message: "cannot unmarshal key value",
}
}

coreResponse, err := coreClient.GetLedgerEntry(ctx, key)
if err != nil {
logger.WithError(err).WithField("request", request).
Info("could not submit getLedgerEntry request to core")
return GetLedgerEntryResponse{}, &jrpc2.Error{
Code: code.InternalError,
Message: "could not submit request to core",
}
}

if coreResponse.State == proto.DeadState {
return GetLedgerEntryResponse{}, &jrpc2.Error{
Code: code.InvalidRequest,
Message: "not found",
}
}

var ledgerEntry xdr.LedgerEntry
if err = xdr.SafeUnmarshalBase64(coreResponse.Entry, &ledgerEntry); err != nil {
logger.WithError(err).WithField("request", request).
WithField("response", coreResponse).
Info("could not parse ledger entry")
return GetLedgerEntryResponse{}, &jrpc2.Error{
Code: code.InternalError,
Message: "could not parse core response",
}
}

response := GetLedgerEntryResponse{
LastModifiedLedger: int64(ledgerEntry.LastModifiedLedgerSeq),
LatestLedger: coreResponse.Ledger,
}
if response.XDR, err = xdr.MarshalBase64(ledgerEntry.Data); err != nil {
logger.WithError(err).WithField("request", request).
WithField("response", coreResponse).
Info("could not serialize ledger entry data")
return GetLedgerEntryResponse{}, &jrpc2.Error{
Code: code.InternalError,
Message: "could not serialize ledger entry data",
}
}

return response, nil
})
}
139 changes: 139 additions & 0 deletions cmd/soroban-rpc/internal/test/get_ledger_entry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package test

import (
"context"
"net/http"
"testing"
"time"

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/code"
"github.com/creachadair/jrpc2/jhttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/stellar/go/keypair"
"github.com/stellar/go/txnbuild"
"github.com/stellar/go/xdr"
"github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/methods"
)

func TestGetLedgerEntryNotFound(t *testing.T) {
test := NewTest(t)

ch := jhttp.NewChannel(test.server.URL, nil)
client := jrpc2.NewClient(ch, nil)

sourceAccount := keypair.Root(StandaloneNetworkPassphrase).Address()
contractID := getContractID(t, sourceAccount, testSalt, StandaloneNetworkPassphrase)
keyB64, err := xdr.MarshalBase64(xdr.LedgerKey{
Type: xdr.LedgerEntryTypeContractData,
ContractData: &xdr.LedgerKeyContractData{
ContractId: contractID,
Key: getContractCodeLedgerKey(),
},
})
require.NoError(t, err)
request := methods.GetLedgerEntryRequest{
Key: keyB64,
}

var result methods.GetLedgerEntryResponse
jsonRPCErr := client.CallResult(context.Background(), "getLedgerEntry", request, &result).(*jrpc2.Error)
assert.Equal(t, "not found", jsonRPCErr.Message)
assert.Equal(t, code.InvalidRequest, jsonRPCErr.Code)
}

func TestGetLedgerEntryInvalidParams(t *testing.T) {
test := NewTest(t)

ch := jhttp.NewChannel(test.server.URL, nil)
client := jrpc2.NewClient(ch, nil)

request := methods.GetLedgerEntryRequest{
Key: "<>@@#$",
}

var result methods.GetLedgerEntryResponse
jsonRPCErr := client.CallResult(context.Background(), "getLedgerEntry", request, &result).(*jrpc2.Error)
assert.Equal(t, "cannot unmarshal key value", jsonRPCErr.Message)
assert.Equal(t, code.InvalidParams, jsonRPCErr.Code)
}

func TestGetLedgerEntryDeadlineError(t *testing.T) {
test := NewTest(t)
test.coreClient.HTTP = &http.Client{
Timeout: time.Microsecond,
}

ch := jhttp.NewChannel(test.server.URL, nil)
client := jrpc2.NewClient(ch, nil)

sourceAccount := keypair.Root(StandaloneNetworkPassphrase).Address()
contractID := getContractID(t, sourceAccount, testSalt, StandaloneNetworkPassphrase)
keyB64, err := xdr.MarshalBase64(xdr.LedgerKey{
Type: xdr.LedgerEntryTypeContractData,
ContractData: &xdr.LedgerKeyContractData{
ContractId: contractID,
Key: getContractCodeLedgerKey(),
},
})
require.NoError(t, err)
request := methods.GetLedgerEntryRequest{
Key: keyB64,
}

var result methods.GetLedgerEntryResponse
jsonRPCErr := client.CallResult(context.Background(), "getLedgerEntry", request, &result).(*jrpc2.Error)
assert.Equal(t, "could not submit request to core", jsonRPCErr.Message)
assert.Equal(t, code.InternalError, jsonRPCErr.Code)
}

func TestGetLedgerEntrySucceeds(t *testing.T) {
test := NewTest(t)

ch := jhttp.NewChannel(test.server.URL, nil)
client := jrpc2.NewClient(ch, nil)

kp := keypair.Root(StandaloneNetworkPassphrase)
account := txnbuild.NewSimpleAccount(kp.Address(), 0)

// Install and create the contract first
for _, op := range []txnbuild.Operation{
createInstallContractCodeOperation(t, account.AccountID, testContract, true),
createCreateContractOperation(t, account.AccountID, testContract, StandaloneNetworkPassphrase, true),
} {
assertSendTransaction(t, client, kp, txnbuild.TransactionParams{
SourceAccount: &account,
IncrementSequenceNum: true,
Operations: []txnbuild.Operation{op},
BaseFee: txnbuild.MinBaseFee,
Preconditions: txnbuild.Preconditions{
TimeBounds: txnbuild.NewInfiniteTimeout(),
},
})
}

sourceAccount := keypair.Root(StandaloneNetworkPassphrase).Address()
contractID := getContractID(t, sourceAccount, testSalt, StandaloneNetworkPassphrase)
keyB64, err := xdr.MarshalBase64(xdr.LedgerKey{
Type: xdr.LedgerEntryTypeContractData,
ContractData: &xdr.LedgerKeyContractData{
ContractId: contractID,
Key: getContractCodeLedgerKey(),
},
})
require.NoError(t, err)
request := methods.GetLedgerEntryRequest{
Key: keyB64,
}

var result methods.GetLedgerEntryResponse
err = client.CallResult(context.Background(), "getLedgerEntry", request, &result)
assert.NoError(t, err)
assert.Greater(t, result.LatestLedger, int64(0))
assert.GreaterOrEqual(t, result.LatestLedger, result.LastModifiedLedger)
var scVal xdr.ScVal
assert.NoError(t, xdr.SafeUnmarshalBase64(result.XDR, &scVal))
assert.Equal(t, testContract, scVal.MustObj().MustContractCode().MustWasmId())
}

0 comments on commit b10c416

Please sign in to comment.