Skip to content

Commit

Permalink
feat: Add Solana Runtime API to handle state_call requests (#115)
Browse files Browse the repository at this point in the history
* feat: Add solana runtime apis

* feat: Implement scale feature for np-solana

* fix: Rename RpcKeyedAccount to KeyedAccount

* feat: Add api_version to ResponseContext and OptionalContext type

* feat: Derive PartialEq and Eq for solana types

* feat: Make api_version optional in ResponseContext

* refactor: Consolidate runtime APIs into a single call method

* feat: Expose error module in solana-runtime-api

* feat: Remove SCALE codec derivation from Solana types

* feat: Add error definitions for SolanaRuntimeApi

* feat: Add get_balance method

* feat: Add get_account_info to pallet-solana

* feat: Add get_multiple_accounts to pallet-solana

* feat: Integrate solana-runtime to handle RPC method calls

* feat: Add handler for getProgramAccounts method

* feat: Add handler for getTransactionCount method

* feat: Add cfg_attr for no_std support in solana-inline-spl

* refactor: Replace alloc with nostd crate for no_std environment

* feat: Add handler for getFeeForMessage method

* feat: Add handler for simulateTransaction method

* feat: Include account_keys in simulate_transaction response

* feat: Add ScanResultsLimitBytes

* feat: Implement byte limit check for account scan

* fix: Resolve compilation errors after rebasing onto main

* refactor: Enhance readability of verify_transaction function

* chore: Update sources for multiple dependencies in Cargo.toml

* refactor: update transaction count mutation to use saturating_add

* refactor: Merge solana-runtime and solana-runtime-api packages

* refactor: Remove solana-account-decoder package
  • Loading branch information
code0xff authored Jan 8, 2025
1 parent 3254dd4 commit 37d6865
Show file tree
Hide file tree
Showing 18 changed files with 1,241 additions and 5 deletions.
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ members = [
"frame/cosmos/x/wasm/types",
"frame/multimap",
"frame/solana",
"frame/solana/runtime-api",
"primitives/babel",
"primitives/cosmos",
"primitives/ethereum",
Expand All @@ -41,6 +42,7 @@ members = [
"vendor/moonbeam/precompiles/balances-erc20",
"vendor/solana/compute-budget",
"vendor/solana/curves/curve25519",
"vendor/solana/inline-spl",
"vendor/solana/measure",
"vendor/solana/metrics",
"vendor/solana/poseidon",
Expand All @@ -51,6 +53,7 @@ members = [
"vendor/solana/programs/loader-v4",
"vendor/solana/programs/system",
"vendor/solana/programs/token",
"vendor/solana/rpc-client-api",
]
default-members = [
"core-primitives",
Expand Down Expand Up @@ -100,6 +103,7 @@ rustc_version = "0.4"
scale-info = { version = "2.11", default-features = false }
scopeguard = { version = "1.2.0", default-features = false }
serde = { version = "1.0.203", default-features = false }
serde_json = { version = "1.0.134", default-features = false }
serde-json-wasm = { version = "1.0", default-features = false }
serde_derive = { version = "1.0.203", default-features = false }
smallvec = "1.13"
Expand All @@ -121,6 +125,7 @@ pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch =
pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2409", default-features = false }
pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2409", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2409", default-features = false }
sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2409", default-features = false }
sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2409", default-features = false }
sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2409", default-features = false }
sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2409", default-features = false }
Expand Down Expand Up @@ -177,14 +182,18 @@ solana-bpf-loader-program = { path = "vendor/solana/programs/bpf_loader", defaul
solana-compute-budget = { path = "vendor/solana/compute-budget", default-features = false }
solana-compute-budget-program = { path = "vendor/solana/programs/compute-budget", default-features = false }
solana-curve25519 = { path = "vendor/solana/curves/curve25519", default-features = false }
solana-inline-spl = { path = "vendor/solana/inline-spl", default-features = false }
solana-loader-v4-program = { path = "vendor/solana/programs/loader-v4", default-features = false }
solana-measure = { path = "vendor/solana/measure", default-features = false }
solana-metrics = { path = "vendor/solana/metrics" }
solana-poseidon = { path = "vendor/solana/poseidon", default-features = false }
solana-program-runtime = { path = "vendor/solana/program-runtime", default-features = false }
solana-rpc-client-api = { path = "vendor/solana/rpc-client-api", default-features = false }
solana-system-program = { path = "vendor/solana/programs/system", default-features = false }
spl-token = { path = "vendor/solana/programs/token" }

solana-runtime-api = { path = "frame/solana/runtime-api", default-features = false }

[profile.release]
panic = "unwind"

Expand Down
42 changes: 42 additions & 0 deletions frame/solana/runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = "solana-runtime-api"
license = "Apache-2.0"
authors = { workspace = true }
version = { workspace = true }
edition = { workspace = true }
repository = { workspace = true }
publish = false

[dependencies]
frame-support = { workspace = true, default-features = false }
nostd = { workspace = true, features = ["alloc"] }
pallet-solana = { workspace = true, default-features = false }
parity-scale-codec = { workspace = true, default-features = false, features = [
"derive",
] }
scale-info = { workspace = true, default-features = false, features = [
"derive",
] }
serde = { workspace = true, default-features = false }
serde_json = { workspace = true, default-features = false }
solana-compute-budget = { workspace = true, default-features = false }
solana-inline-spl = { workspace = true, default-features = false }
solana-rpc-client-api = { workspace = true, default-features = false }
solana-sdk = { workspace = true, default-features = false }
sp-api = { workspace = true, default-features = false }

[features]
default = ["std"]
std = [
"frame-support/std",
"pallet-solana/std",
"parity-scale-codec/std",
"scale-info/std",
"serde/std",
"serde_json/std",
"solana-compute-budget/std",
"solana-inline-spl/std",
"solana-rpc-client-api/std",
"solana-sdk/std",
"sp-api/std",
]
115 changes: 115 additions & 0 deletions frame/solana/runtime-api/src/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// This file is part of Noir.

// Copyright (c) Haderech Pte. Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::{error::Error, SolanaRuntimeCall};
use frame_support::traits::Get;
use nostd::marker::PhantomData;
use pallet_solana::Pubkey;
use solana_inline_spl::token::GenericTokenAccount;
use solana_rpc_client_api::filter::RpcFilterType;
use solana_sdk::account::{Account, ReadableAccount};

pub struct AccountInfo<T>(PhantomData<T>);
impl<T> SolanaRuntimeCall<Pubkey, Option<Account>> for AccountInfo<T>
where
T: pallet_solana::Config,
{
fn call(pubkey: Pubkey) -> Result<Option<Account>, Error> {
Ok(pallet_solana::Pallet::<T>::get_account_info(pubkey))
}
}

pub struct MultipleAccounts<T>(PhantomData<T>);
impl<T> SolanaRuntimeCall<Vec<Pubkey>, Vec<Option<Account>>> for MultipleAccounts<T>
where
T: pallet_solana::Config,
{
fn call(pubkeys: Vec<Pubkey>) -> Result<Vec<Option<Account>>, Error> {
Ok(pubkeys
.into_iter()
.map(|pubkey| pallet_solana::Pallet::<T>::get_account_info(pubkey))
.collect())
}
}

pub struct ProgramAccounts<T>(PhantomData<T>);
impl<T> SolanaRuntimeCall<(Pubkey, Vec<Pubkey>, Vec<RpcFilterType>), Vec<(Pubkey, Account)>>
for ProgramAccounts<T>
where
T: pallet_solana::Config,
{
fn call(
(program_id, pubkeys, filters): (Pubkey, Vec<Pubkey>, Vec<RpcFilterType>),
) -> Result<Vec<(Pubkey, Account)>, Error> {
let filter_closure = |account: &Account| {
filters.iter().all(|filter_type| filter_allows(filter_type, account))
};

let byte_limit_for_scan =
T::ScanResultsLimitBytes::get().map(|byte_limit| byte_limit as usize);
let mut sum: usize = 0;
let mut accounts = Vec::new();

for pubkey in pubkeys.iter() {
if let Some(account) = pallet_solana::Pallet::<T>::get_account_info(*pubkey) {
if account.owner == program_id && filter_closure(&account) {
if Self::accumulate_and_check_scan_result_size(
&mut sum,
&account,
byte_limit_for_scan,
) {
break;
}
accounts.push((*pubkey, account));
}
}
}

Ok(accounts)
}
}

impl<T> ProgramAccounts<T> {
/// Accumulate size of (pubkey + account) into sum.
/// Return true if sum > 'byte_limit_for_scan'
fn accumulate_and_check_scan_result_size(
sum: &mut usize,
account: &Account,
byte_limit_for_scan: Option<usize>,
) -> bool {
if let Some(byte_limit) = byte_limit_for_scan {
let added = Self::calc_scan_result_size(account);
*sum = sum.saturating_add(added);
*sum > byte_limit
} else {
false
}
}

fn calc_scan_result_size(account: &Account) -> usize {
account.data().len() + std::mem::size_of::<Account>() + std::mem::size_of::<Pubkey>()
}
}

pub fn filter_allows(filter: &RpcFilterType, account: &Account) -> bool {
match filter {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
RpcFilterType::Memcmp(compare) => compare.bytes_match(account.data()),
RpcFilterType::TokenAccountState =>
solana_inline_spl::token_2022::Account::valid_account_data(account.data()),
}
}
30 changes: 30 additions & 0 deletions frame/solana/runtime-api/src/balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This file is part of Noir.

// Copyright (c) Haderech Pte. Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::{error::Error, SolanaRuntimeCall};
use nostd::marker::PhantomData;
use pallet_solana::Pubkey;

pub struct Balance<T>(PhantomData<T>);
impl<T> SolanaRuntimeCall<Pubkey, u64> for Balance<T>
where
T: pallet_solana::Config,
{
fn call(pubkey: Pubkey) -> Result<u64, Error> {
Ok(pallet_solana::Pallet::<T>::get_balance(pubkey))
}
}
28 changes: 28 additions & 0 deletions frame/solana/runtime-api/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// This file is part of Noir.

// Copyright (c) Haderech Pte. Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)]
pub enum Error {
ParseError,
UnsupportedMethod,
InvalidParams,
TransactionSignatureVerificationFailure,
TransactionPrecompileVerificationFailure,
}
62 changes: 62 additions & 0 deletions frame/solana/runtime-api/src/fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// This file is part of Noir.

// Copyright (c) Haderech Pte. Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::{error::Error, SolanaRuntimeCall};
use nostd::marker::PhantomData;
use solana_compute_budget::compute_budget_processor::process_compute_budget_instructions;
use solana_sdk::{
feature_set::{
include_loaded_accounts_data_size_in_fee_calculation, remove_rounding_in_fee_calculation,
FeatureSet,
},
fee::FeeStructure,
message::{SanitizedMessage, SanitizedVersionedMessage, SimpleAddressLoader, VersionedMessage},
reserved_account_keys::ReservedAccountKeys,
};

pub struct FeeForMessage<T>(PhantomData<T>);
impl<T> SolanaRuntimeCall<VersionedMessage, u64> for FeeForMessage<T>
where
T: pallet_solana::Config,
{
fn call(message: VersionedMessage) -> Result<u64, Error> {
let sanitized_versioned_message =
SanitizedVersionedMessage::try_from(message).map_err(|_| Error::InvalidParams)?;
// TODO: Get address_loader and reserved_account_keys
let sanitized_message = SanitizedMessage::try_new(
sanitized_versioned_message,
SimpleAddressLoader::Disabled,
&ReservedAccountKeys::empty_key_set(),
)
.map_err(|_| Error::InvalidParams)?;

// TODO: Get fee_structure, lamports_per_signature and feature_set
let fee_structure = FeeStructure::default();
let lamports_per_signature = Default::default();
let feature_set = FeatureSet::default();

Ok(fee_structure.calculate_fee(
&sanitized_message,
lamports_per_signature,
&process_compute_budget_instructions(sanitized_message.program_instructions_iter())
.unwrap_or_default()
.into(),
feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()),
feature_set.is_active(&remove_rounding_in_fee_calculation::id()),
))
}
}
48 changes: 48 additions & 0 deletions frame/solana/runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// This file is part of Noir.

// Copyright (c) Haderech Pte. Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![cfg_attr(not(feature = "std"), no_std)]

pub mod account;
pub mod balance;
pub mod error;
pub mod fee;
pub mod transaction;

use error::Error;
use nostd::{string::String, vec::Vec};
use sp_api::decl_runtime_apis;

decl_runtime_apis! {
pub trait SolanaRuntimeApi {
fn call(method: String, params: Vec<u8>) -> Result<Vec<u8>, Error>;
}
}

pub trait SolanaRuntimeCall<I = (), O = ()>
where
I: for<'de> serde::Deserialize<'de> + Send + Sync,
O: serde::Serialize + Send + Sync,
{
fn call_raw(params: Vec<u8>) -> Result<Vec<u8>, Error> {
let input: I = serde_json::from_slice(&params).map_err(|_| Error::ParseError)?;
let output = Self::call(input)?;
serde_json::to_vec(&output).map_err(|_| Error::ParseError)
}

fn call(input: I) -> Result<O, Error>;
}
Loading

0 comments on commit 37d6865

Please sign in to comment.