Add C API (#5072)
This commit is contained in:
parent
04649de6a6
commit
6b86f85916
|
@ -313,6 +313,22 @@ dependencies = [
|
|||
"ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.37"
|
||||
|
@ -855,7 +871,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.13.1"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2795,7 +2811,7 @@ dependencies = [
|
|||
"bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2813,6 +2829,20 @@ dependencies = [
|
|||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-sdk-c"
|
||||
version = "0.17.0"
|
||||
dependencies = [
|
||||
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"solana-ed25519-dalek 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"solana-sdk 0.17.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-stake-api"
|
||||
version = "0.17.0"
|
||||
|
@ -3375,6 +3405,14 @@ dependencies = [
|
|||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traitobject"
|
||||
version = "0.1.0"
|
||||
|
@ -3674,6 +3712,7 @@ dependencies = [
|
|||
"checksum bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
|
||||
"checksum bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6584aa36f5ad4c9247f5323b0a42f37802b37a836f0ad87084d7a33961abe25f"
|
||||
"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101"
|
||||
"checksum cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7e19db9a3892c88c74cbbdcd218196068a928f1b60e736c448b13a1e81f277"
|
||||
"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d"
|
||||
"checksum cexpr 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "644d693ecfa91955ed32dcc7eda4914e1be97a641fb6f0645a37348e20b230da"
|
||||
"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
|
||||
|
@ -3733,7 +3772,7 @@ dependencies = [
|
|||
"checksum futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981"
|
||||
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
|
||||
"checksum generic-array 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cba710fd46ea0501f73f0dac107a3f97b5ea3fe0546e79e45e074f58459afa82"
|
||||
"checksum generic-array 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d9582501ecd567475c637337f51232d8adac81f31e4bd241edd40ad25a12bdf"
|
||||
"checksum generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd"
|
||||
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
|
||||
"checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
|
@ -3924,6 +3963,7 @@ dependencies = [
|
|||
"checksum tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92"
|
||||
"checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445"
|
||||
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
||||
"checksum toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8c96d7873fa7ef8bdeb3a9cda3ac48389b4154f32b9803b4bc26220b677b039"
|
||||
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
|
||||
"checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
|
||||
"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382"
|
||||
|
|
|
@ -3,6 +3,7 @@ members = [
|
|||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"sdk-c",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/include/
|
||||
/farf/
|
||||
/target/
|
||||
|
||||
**/*.rs.bk
|
||||
.cargo
|
||||
|
||||
# log files
|
||||
*.log
|
||||
log-*.txt
|
||||
|
||||
# intellij files
|
||||
/.idea/
|
||||
/solana.iml
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "solana-sdk-c"
|
||||
version = "0.17.0"
|
||||
description = "Solana SDK C"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "solana_sdk_c"
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.2.0"
|
||||
libc = "0.2.58"
|
||||
rand_chacha = "0.1.1"
|
||||
rand_core = { version = ">=0.2, <0.4", default-features = false }
|
||||
solana-sdk = { path = "../sdk", version = "0.17.0" }
|
||||
solana-ed25519-dalek = "0.2.0"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.9.0"
|
|
@ -0,0 +1,12 @@
|
|||
# Solana SDK-C
|
||||
|
||||
This crate exposes a C API to Solana functions written in Rust. The crate generates a static library, and uses `cbindgen`
|
||||
to generate a header file during the build. To generate both:
|
||||
|
||||
```shell
|
||||
$ cd <path/to/solana/repo>/sdk-c
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
This will generate the static library in `<path/to/solana/repo>/target/deps` and the header file in
|
||||
`<path/to/solana/repo>/sdk-c/include`.
|
|
@ -0,0 +1,24 @@
|
|||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
|
||||
let out_path = Path::new(&crate_dir);
|
||||
let out_path = out_path.join(Path::new("include"));
|
||||
|
||||
// Ensure `out_path` exists
|
||||
fs::create_dir_all(&out_path).unwrap_or_else(|err| {
|
||||
if err.kind() != std::io::ErrorKind::AlreadyExists {
|
||||
panic!("Unable to create {:#?}: {:?}", out_path, err);
|
||||
}
|
||||
});
|
||||
|
||||
let out_path = out_path.join(Path::new("solana.h"));
|
||||
let out_path = out_path.to_str().unwrap();
|
||||
|
||||
cbindgen::generate(crate_dir)
|
||||
.unwrap()
|
||||
.write_to_file(out_path);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
language = "C"
|
||||
header = '''// Copyright 2018 Solana Labs, Inc.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
// Warning, this file is autogenerated by cbindgen. Don't modify this manually.
|
||||
'''
|
||||
include_version = true
|
||||
include_guard = "SOLANA_H"
|
||||
tab_width = 4
|
||||
documentation_style = "c99"
|
||||
|
||||
[parse]
|
||||
parse_deps = true
|
||||
include = ["solana-sdk"]
|
||||
|
||||
[export]
|
||||
item_types = ["enums", "structs", "unions", "typedefs", "opaque", "functions"]
|
|
@ -0,0 +1,574 @@
|
|||
use bincode::{deserialize, serialize};
|
||||
use libc::{c_int, size_t};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use rand_core::SeedableRng;
|
||||
use solana_ed25519_dalek::{SignatureError, KEYPAIR_LENGTH, PUBLIC_KEY_LENGTH};
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::instruction::CompiledInstruction as CompiledInstructionNative;
|
||||
use solana_sdk::message::Message as MessageNative;
|
||||
use solana_sdk::message::MessageHeader as MessageHeaderNative;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Signature as SignatureNative;
|
||||
use solana_sdk::signature::{Keypair as KeypairNative, KeypairUtil};
|
||||
use solana_sdk::transaction::Transaction as TransactionNative;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
use std::vec::Vec;
|
||||
use std::{fmt, mem, ptr, slice};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct Transaction {
|
||||
/// A set of digital signatures of `account_keys`, `program_ids`, `recent_blockhash`, and `instructions`, signed by the first
|
||||
/// signatures_len keys of account_keys
|
||||
pub signatures: CVec<Signature>,
|
||||
|
||||
/// The message to sign.
|
||||
pub message: Message,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn from_native(mut t: TransactionNative) -> Self {
|
||||
t.signatures.shrink_to_fit();
|
||||
Self {
|
||||
signatures: CVec::from_native(
|
||||
t.signatures
|
||||
.into_iter()
|
||||
.map(Signature::from_native)
|
||||
.collect(),
|
||||
),
|
||||
message: Message::from_native(t.message),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn into_native(self) -> TransactionNative {
|
||||
TransactionNative {
|
||||
signatures: CVec::into_native(self.signatures)
|
||||
.iter()
|
||||
.map(|s| s.new_native())
|
||||
.collect(),
|
||||
message: self.message.into_native(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub unsafe fn clone(&self) -> Self {
|
||||
Self {
|
||||
signatures: self.signatures.clone(),
|
||||
message: self.message.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct Message {
|
||||
/// The message header, identifying signed and credit-only `account_keys`
|
||||
pub header: MessageHeader,
|
||||
|
||||
/// All the account keys used by this transaction
|
||||
pub account_keys: CVec<Pubkey>,
|
||||
|
||||
/// The id of a recent ledger entry.
|
||||
pub recent_blockhash: Hash,
|
||||
|
||||
/// Programs that will be executed in sequence and committed in one atomic transaction if all
|
||||
/// succeed.
|
||||
pub instructions: CVec<CompiledInstruction>,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn from_native(m: MessageNative) -> Self {
|
||||
Self {
|
||||
header: MessageHeader::from_native(m.header),
|
||||
account_keys: CVec::from_native(m.account_keys),
|
||||
recent_blockhash: m.recent_blockhash,
|
||||
instructions: CVec::from_native(
|
||||
m.instructions
|
||||
.into_iter()
|
||||
.map(CompiledInstruction::from_native)
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn into_native(self) -> MessageNative {
|
||||
MessageNative {
|
||||
header: self.header.into_native(),
|
||||
account_keys: CVec::into_native(self.account_keys),
|
||||
recent_blockhash: self.recent_blockhash,
|
||||
instructions: CVec::into_native(self.instructions)
|
||||
.into_iter()
|
||||
.map(|i| i.into_native())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub unsafe fn clone(&self) -> Self {
|
||||
Self {
|
||||
header: self.header.clone(),
|
||||
account_keys: self.account_keys.clone(),
|
||||
recent_blockhash: self.recent_blockhash,
|
||||
instructions: self.instructions.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An instruction to execute a program
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct CompiledInstruction {
|
||||
/// Index into the transaction keys array indicating the program account that executes this instruction
|
||||
pub program_id_index: u8,
|
||||
|
||||
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program
|
||||
pub accounts: CVec<u8>,
|
||||
|
||||
/// The program input data
|
||||
pub data: CVec<u8>,
|
||||
}
|
||||
|
||||
impl CompiledInstruction {
|
||||
pub fn from_native(c: CompiledInstructionNative) -> Self {
|
||||
Self {
|
||||
program_id_index: c.program_id_index,
|
||||
accounts: CVec::from_native(c.accounts),
|
||||
data: CVec::from_native(c.data),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn into_native(self) -> CompiledInstructionNative {
|
||||
CompiledInstructionNative {
|
||||
program_id_index: self.program_id_index,
|
||||
accounts: CVec::into_native(self.accounts),
|
||||
data: CVec::into_native(self.data),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub unsafe fn clone(&self) -> Self {
|
||||
Self {
|
||||
program_id_index: self.program_id_index,
|
||||
accounts: self.accounts.clone(),
|
||||
data: self.data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct MessageHeader {
|
||||
/// The number of signatures required for this message to be considered valid. The
|
||||
/// signatures must match the first `num_required_signatures` of `account_keys`.
|
||||
pub num_required_signatures: u8,
|
||||
|
||||
/// The last num_credit_only_signed_accounts of the signed keys are credit-only accounts.
|
||||
/// Programs may process multiple transactions that add lamports to the same credit-only
|
||||
/// account within a single PoH entry, but are not permitted to debit lamports or modify
|
||||
/// account data. Transactions targeting the same debit account are evaluated sequentially.
|
||||
pub num_credit_only_signed_accounts: u8,
|
||||
|
||||
/// The last num_credit_only_unsigned_accounts of the unsigned keys are credit-only accounts.
|
||||
pub num_credit_only_unsigned_accounts: u8,
|
||||
}
|
||||
|
||||
impl MessageHeader {
|
||||
pub fn from_native(h: MessageHeaderNative) -> Self {
|
||||
Self {
|
||||
num_required_signatures: h.num_required_signatures,
|
||||
num_credit_only_signed_accounts: h.num_credit_only_signed_accounts,
|
||||
num_credit_only_unsigned_accounts: h.num_credit_only_unsigned_accounts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_native(self) -> MessageHeaderNative {
|
||||
MessageHeaderNative {
|
||||
num_required_signatures: self.num_required_signatures,
|
||||
num_credit_only_signed_accounts: self.num_credit_only_signed_accounts,
|
||||
num_credit_only_unsigned_accounts: self.num_credit_only_unsigned_accounts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Signature([u8; 64]);
|
||||
|
||||
impl fmt::Debug for Signature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(&self.0[..]).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Signature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(&self.0[..]).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
pub fn from_native(s: SignatureNative) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
|
||||
pub fn new_native(&self) -> SignatureNative {
|
||||
SignatureNative::new(&self.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone)]
|
||||
pub struct Keypair([u8; KEYPAIR_LENGTH]);
|
||||
|
||||
impl Keypair {
|
||||
pub fn from_native(k: &KeypairNative) -> Self {
|
||||
Self(k.to_bytes())
|
||||
}
|
||||
|
||||
pub fn new_native(&self) -> Result<KeypairNative, SignatureError> {
|
||||
KeypairNative::from_bytes(&self.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of a Rust Vec that can be passed to C. Should not be modified or copied by C code.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct CVec<T> {
|
||||
data: *mut T,
|
||||
len: size_t,
|
||||
capacity: size_t,
|
||||
}
|
||||
|
||||
impl<T> CVec<T> {
|
||||
pub fn from_native(mut v: Vec<T>) -> Self {
|
||||
let out = Self {
|
||||
data: v.as_mut_ptr(),
|
||||
len: v.len(),
|
||||
capacity: v.capacity(),
|
||||
};
|
||||
mem::forget(v);
|
||||
out
|
||||
}
|
||||
|
||||
pub unsafe fn into_native(self) -> Vec<T> {
|
||||
Vec::from_raw_parts(self.data, self.len, self.capacity)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> CVec<T> {
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub unsafe fn clone(&self) -> Self {
|
||||
let native = Vec::from_raw_parts(self.data, self.len, self.capacity);
|
||||
let mut new: Vec<T> = Vec::with_capacity(native.capacity());
|
||||
<Vec<T> as Clone>::clone_from(&mut new, &native);
|
||||
mem::forget(native);
|
||||
Self::from_native(new)
|
||||
}
|
||||
}
|
||||
|
||||
impl CVec<CompiledInstruction> {
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub unsafe fn clone(&self) -> Self {
|
||||
let native = Vec::from_raw_parts(self.data, self.len, self.capacity);
|
||||
let mut new: Vec<CompiledInstruction> = Vec::with_capacity(native.capacity());
|
||||
for elem in &native {
|
||||
new.push(elem.clone());
|
||||
}
|
||||
mem::forget(native);
|
||||
Self::from_native(new)
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free_transaction(tx: *mut Transaction) {
|
||||
Box::from_raw(tx);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free_message(m: *mut Message) {
|
||||
Box::from_raw(m);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free_message_header(mh: *mut MessageHeader) {
|
||||
Box::from_raw(mh);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free_signature(s: *mut Signature) {
|
||||
Box::from_raw(s);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free_compiled_instruction(i: *mut CompiledInstruction) {
|
||||
Box::from_raw(i);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn free_c_string(s: *mut c_char) {
|
||||
CString::from_raw(s);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn new_unsigned_transaction(message: *mut Message) -> *mut Transaction {
|
||||
let message = Box::from_raw(message);
|
||||
let tx = Box::new(Transaction::from_native(TransactionNative::new_unsigned(
|
||||
message.into_native(),
|
||||
)));
|
||||
Box::into_raw(tx)
|
||||
}
|
||||
|
||||
/// Generate a `Keypair` from a uint8_t[32] seed. Assumes the pointer is to an array of length 32.
|
||||
///
|
||||
/// # Undefined Behavior
|
||||
///
|
||||
/// Causes UB if `seed` is not a pointer to an array of length 32 or if `seed` is `NULL`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn generate_keypair(seed: *const u8) -> *mut Keypair {
|
||||
let seed = <&[u8] as TryInto<&[u8; PUBLIC_KEY_LENGTH]>>::try_into(slice::from_raw_parts(
|
||||
seed,
|
||||
PUBLIC_KEY_LENGTH,
|
||||
))
|
||||
.unwrap(); // Guaranteed not to panic
|
||||
let mut rng = ChaChaRng::from_seed(*seed);
|
||||
let keypair = KeypairNative::generate(&mut rng);
|
||||
let keypair = Box::new(Keypair::from_native(&keypair));
|
||||
Box::into_raw(keypair)
|
||||
}
|
||||
|
||||
/// Get a pubkey from a keypair. Returns `NULL` if the conversion causes an error
|
||||
///
|
||||
/// # Undefined Behavior
|
||||
///
|
||||
/// Causes UB if `keypair` is `NULL` or if `keypair` in not a pointer to a valid `Keypair`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_keypair_pubkey(keypair: *const Keypair) -> *mut Pubkey {
|
||||
let keypair = if let Ok(k) = (*keypair).new_native() {
|
||||
k
|
||||
} else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
let pubkey = Box::new(keypair.pubkey());
|
||||
Box::into_raw(pubkey)
|
||||
}
|
||||
|
||||
/// Serialize a `Transaction` and save a pointer to the resulting byte array to `serialized`, and its
|
||||
/// length to `len`. Returns `0` for success, other for failure. Consumes and frees the `Transaction`.
|
||||
///
|
||||
/// # Undefined Behavior
|
||||
///
|
||||
/// Causes UB if any of the input pointers is `NULL`, or if `tx` is not a valid `Transaction`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn serialize_transaction(
|
||||
tx: *mut Transaction,
|
||||
serialized: *mut *const u8,
|
||||
len: *mut size_t,
|
||||
) -> c_int {
|
||||
let tx = Box::from_raw(tx);
|
||||
let tx = tx.into_native();
|
||||
let mut serialized_tx = if let Ok(s) = serialize(&tx) {
|
||||
s
|
||||
} else {
|
||||
return 1;
|
||||
};
|
||||
serialized_tx.shrink_to_fit();
|
||||
*serialized = serialized_tx.as_mut_ptr();
|
||||
*len = serialized_tx.len();
|
||||
mem::forget(serialized_tx);
|
||||
0
|
||||
}
|
||||
|
||||
/// Deserialize an array of bytes into a `Transaction`. Returns `NULL` if deserialization fails
|
||||
///
|
||||
/// # Undefined Behavior
|
||||
///
|
||||
/// Causes UB if `bytes` is `NULL`, or if `bytes` does not point to a valid array of length `len`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn deserialize_transaction(
|
||||
bytes: *const u8,
|
||||
len: size_t,
|
||||
) -> *mut Transaction {
|
||||
let slice = slice::from_raw_parts(bytes, len);
|
||||
let tx = if let Ok(t) = deserialize::<TransactionNative>(slice) {
|
||||
t
|
||||
} else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
let tx = Box::new(Transaction::from_native(tx));
|
||||
Box::into_raw(tx)
|
||||
}
|
||||
|
||||
/// Sign a transaction with a subset of the required `Keypair`s. If the `recent_blockhash` supplied
|
||||
/// does not match that of the `Transaction`, the supplied one will be used, and any existing
|
||||
/// signatures will be discarded. Returns `0` for success, other for failure.
|
||||
///
|
||||
/// # Undefined Behavior
|
||||
///
|
||||
/// Causes UB if any of the pointers is `NULL`, or if `keypairs` does not point to a valid array of
|
||||
/// `Keypairs` of length `num_keypairs`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn transaction_partial_sign(
|
||||
tx: *mut Transaction,
|
||||
keypairs: *const Keypair,
|
||||
num_keypairs: size_t,
|
||||
recent_blockhash: *const Hash,
|
||||
) -> c_int {
|
||||
let mut tx = Box::from_raw(tx);
|
||||
let mut tx_native = tx.clone().into_native();
|
||||
|
||||
let keypairs = slice::from_raw_parts(keypairs, num_keypairs);
|
||||
let keypairs: Vec<Result<_, _>> = keypairs.iter().map(|k| k.new_native()).collect();
|
||||
let keypairs: Vec<KeypairNative> = if keypairs.iter().all(|k| k.is_ok()) {
|
||||
keypairs
|
||||
.into_iter()
|
||||
.map(|k| k.expect("This shouldn't ever happen"))
|
||||
.collect()
|
||||
} else {
|
||||
return 1;
|
||||
};
|
||||
let keypairs_ref: Vec<&KeypairNative> = keypairs.iter().collect();
|
||||
|
||||
let positions = if let Ok(v) = tx_native.get_signing_keypair_positions(&keypairs_ref[..]) {
|
||||
v
|
||||
} else {
|
||||
return 2;
|
||||
};
|
||||
let positions: Vec<usize> = if positions.iter().all(|pos| pos.is_some()) {
|
||||
positions
|
||||
.iter()
|
||||
.map(|pos| pos.expect("This shouldn't ever happen"))
|
||||
.collect()
|
||||
} else {
|
||||
return 3;
|
||||
};
|
||||
|
||||
tx_native.partial_sign_unchecked(&keypairs_ref[..], positions, *recent_blockhash);
|
||||
*tx = Transaction::from_native(tx_native);
|
||||
Box::into_raw(tx);
|
||||
0
|
||||
}
|
||||
|
||||
/// Get the printable c-string of a Pubkey. The returned c-string must be freed with `free_c_string()`
|
||||
/// Returns `NULL` if the conversion fails.
|
||||
///
|
||||
/// # Undefined Behavior
|
||||
///
|
||||
/// Causes UB if `pubkey` is `NULL`, or if the returned c-string is freed by any method other than
|
||||
/// calling `free_c_string()`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn get_pubkey_string(pubkey: *const Pubkey) -> *mut c_char {
|
||||
if let Ok(s) = CString::new(format!("{}", *pubkey)) {
|
||||
s.into_raw()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
use bincode::serialize;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use rand_core::SeedableRng;
|
||||
use solana_sdk::signature::{Keypair as KeypairNative, KeypairUtil};
|
||||
use solana_sdk::system_transaction;
|
||||
|
||||
#[test]
|
||||
fn test_generate_keypair() {
|
||||
let seed = [1u8; 32];
|
||||
let mut rng = ChaChaRng::from_seed(seed);
|
||||
let keypair = KeypairNative::generate(&mut rng);
|
||||
let c_keypair = unsafe { Box::from_raw(generate_keypair(seed.as_ptr())) };
|
||||
assert_eq!(c_keypair.new_native(), Ok(keypair));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_pubkey() {
|
||||
let seed = [1u8; 32];
|
||||
unsafe {
|
||||
let keypair = generate_keypair(seed.as_ptr());
|
||||
let pubkey = Box::from_raw(get_keypair_pubkey(keypair));
|
||||
let keypair = Box::from_raw(keypair);
|
||||
let keypair = keypair.new_native().unwrap();
|
||||
assert_eq!(keypair.pubkey(), *pubkey);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_transaction() {
|
||||
let key = KeypairNative::new();
|
||||
let to = Pubkey::new_rand();
|
||||
let blockhash = Hash::default();
|
||||
let tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
|
||||
let serialized = serialize(&tx).unwrap();
|
||||
let tx = Box::new(Transaction::from_native(tx));
|
||||
let tx = Box::into_raw(tx);
|
||||
let mut res: *const u8 = ptr::null_mut();
|
||||
let mut len: size_t = 0;
|
||||
let slice;
|
||||
unsafe {
|
||||
assert_eq!(0, serialize_transaction(tx, &mut res, &mut len));
|
||||
slice = slice::from_raw_parts(res, len);
|
||||
}
|
||||
assert_eq!(serialized, slice);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_transaction() {
|
||||
let key = KeypairNative::new();
|
||||
let to = Pubkey::new_rand();
|
||||
let blockhash = Hash::default();
|
||||
let tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
|
||||
let serialized = serialize(&tx).unwrap();
|
||||
let deserialized;
|
||||
unsafe {
|
||||
let ret = deserialize_transaction(serialized.as_ptr(), serialized.len());
|
||||
assert_ne!(ret, ptr::null_mut());
|
||||
let ret = Box::from_raw(ret);
|
||||
deserialized = ret.into_native();
|
||||
}
|
||||
assert_eq!(deserialized, tx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_transaction_bad() {
|
||||
let serialized_bad = vec![0u8; 3];
|
||||
let deserialized;
|
||||
unsafe {
|
||||
deserialized = deserialize_transaction(serialized_bad.as_ptr(), serialized_bad.len());
|
||||
}
|
||||
assert_eq!(deserialized, ptr::null_mut());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_pubkey_string() {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let str_native = format!("{}", pubkey);
|
||||
let str_c;
|
||||
unsafe {
|
||||
str_c = CString::from_raw(get_pubkey_string(&pubkey));
|
||||
}
|
||||
let str_c = str_c.into_string().unwrap();
|
||||
assert_eq!(str_native, str_c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_partial_sign() {
|
||||
let key_native = KeypairNative::new();
|
||||
let to = Pubkey::new_rand();
|
||||
let blockhash = Hash::default();
|
||||
let mut tx_native =
|
||||
system_transaction::create_user_account(&key_native, &to, 50, blockhash);
|
||||
let tx = Box::into_raw(Box::new(Transaction::from_native(tx_native.clone())));
|
||||
let key = Keypair::from_native(&key_native);
|
||||
let tx2;
|
||||
unsafe {
|
||||
assert_eq!(0, transaction_partial_sign(tx, &key, 1, &blockhash));
|
||||
tx2 = Box::from_raw(tx).into_native();
|
||||
}
|
||||
tx_native.partial_sign(&[&key_native], blockhash);
|
||||
assert_eq!(tx_native, tx2);
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ bincode = "1.1.4"
|
|||
bs58 = "0.2.0"
|
||||
byteorder = "1.3.2"
|
||||
chrono = { version = "0.4.7", features = ["serde"] }
|
||||
generic-array = { version = "0.13.1", default-features = false, features = ["serde"] }
|
||||
generic-array = { version = "0.13.2", default-features = false, features = ["serde", "more_lengths"] }
|
||||
hex = "0.3.2"
|
||||
itertools = "0.8.0"
|
||||
log = "0.4.2"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use generic_array::typenum::U32;
|
||||
use generic_array::GenericArray;
|
||||
use std::convert::TryFrom;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::fs::{self, File};
|
||||
|
@ -8,9 +7,9 @@ use std::mem;
|
|||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(transparent)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Pubkey(GenericArray<u8, U32>);
|
||||
pub struct Pubkey([u8; 32]);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ParsePubkeyError {
|
||||
|
@ -43,7 +42,10 @@ impl FromStr for Pubkey {
|
|||
|
||||
impl Pubkey {
|
||||
pub fn new(pubkey_vec: &[u8]) -> Self {
|
||||
Pubkey(GenericArray::clone_from_slice(&pubkey_vec))
|
||||
Self(
|
||||
<[u8; 32]>::try_from(<&[u8]>::clone(&pubkey_vec))
|
||||
.expect("Slice must be the same length as a Pubkey"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_rand() -> Self {
|
||||
|
|
|
@ -19,6 +19,7 @@ use std::str::FromStr;
|
|||
|
||||
pub type Keypair = ed25519_dalek::Keypair;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Signature(GenericArray<u8, U64>);
|
||||
|
||||
|
@ -74,6 +75,12 @@ impl fmt::Display for Signature {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<[u8; 64]> for Signature {
|
||||
fn into(self) -> [u8; 64] {
|
||||
<GenericArray<u8, U64> as Into<[u8; 64]>>::into(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ParseSignatureError {
|
||||
WrongSize,
|
||||
|
|
|
@ -193,9 +193,24 @@ impl Transaction {
|
|||
/// if recent_blockhash is not the same as currently in the transaction,
|
||||
/// clear any prior signatures and update recent_blockhash
|
||||
pub fn partial_sign<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
|
||||
let signed_keys =
|
||||
&self.message.account_keys[0..self.message.header.num_required_signatures as usize];
|
||||
let positions = self
|
||||
.get_signing_keypair_positions(keypairs)
|
||||
.expect("account_keys doesn't contain num_required_signatures keys");
|
||||
let positions: Vec<usize> = positions
|
||||
.iter()
|
||||
.map(|pos| pos.expect("keypair-pubkey mismatch"))
|
||||
.collect();
|
||||
self.partial_sign_unchecked(keypairs, positions, recent_blockhash)
|
||||
}
|
||||
|
||||
/// Sign the transaction and place the signatures in their associated positions in `signatures`
|
||||
/// without checking that the positions are correct.
|
||||
pub fn partial_sign_unchecked<T: KeypairUtil>(
|
||||
&mut self,
|
||||
keypairs: &[&T],
|
||||
positions: Vec<usize>,
|
||||
recent_blockhash: Hash,
|
||||
) {
|
||||
// if you change the blockhash, you're re-signing...
|
||||
if recent_blockhash != self.message.recent_blockhash {
|
||||
self.message.recent_blockhash = recent_blockhash;
|
||||
|
@ -204,16 +219,32 @@ impl Transaction {
|
|||
.for_each(|signature| *signature = Signature::default());
|
||||
}
|
||||
|
||||
for keypair in keypairs {
|
||||
let i = signed_keys
|
||||
.iter()
|
||||
.position(|pubkey| pubkey == &keypair.pubkey())
|
||||
.expect("keypair-pubkey mismatch");
|
||||
|
||||
self.signatures[i] = keypair.sign_message(&self.message_data())
|
||||
for i in 0..positions.len() {
|
||||
self.signatures[positions[i]] = keypairs[i].sign_message(&self.message_data())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the positions of the pubkeys in `account_keys` associated with signing keypairs
|
||||
pub fn get_signing_keypair_positions<T: KeypairUtil>(
|
||||
&self,
|
||||
keypairs: &[&T],
|
||||
) -> Result<Vec<Option<usize>>> {
|
||||
if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {
|
||||
return Err(TransactionError::InvalidAccountIndex);
|
||||
}
|
||||
let signed_keys =
|
||||
&self.message.account_keys[0..self.message.header.num_required_signatures as usize];
|
||||
|
||||
Ok(keypairs
|
||||
.iter()
|
||||
.map(|keypair| {
|
||||
signed_keys
|
||||
.iter()
|
||||
.position(|pubkey| pubkey == &keypair.pubkey())
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.signatures
|
||||
.iter()
|
||||
|
|
Loading…
Reference in New Issue