Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 6fd0dcb

Browse files
authored
Add support for fetching program in cargo registry (#33759)
* Rename publisher.rs to crate_handler.rs * support for fetching program in cargo registry
1 parent e0b59a6 commit 6fd0dcb

File tree

5 files changed

+410
-190
lines changed

5 files changed

+410
-190
lines changed

cargo-registry/src/crate_handler.rs

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
use {
2+
crate::{
3+
client::{Client, RPCCommandConfig},
4+
sparse_index::{IndexEntry, RegistryIndex},
5+
},
6+
flate2::{
7+
read::{GzDecoder, GzEncoder},
8+
Compression,
9+
},
10+
hyper::body::Bytes,
11+
log::*,
12+
serde::{Deserialize, Serialize},
13+
serde_json::from_slice,
14+
sha2::{Digest, Sha256},
15+
solana_cli::program_v4::{process_deploy_program, process_dump, read_and_verify_elf},
16+
solana_sdk::{
17+
pubkey::Pubkey,
18+
signature::{Keypair, Signer},
19+
signer::EncodableKey,
20+
},
21+
std::{
22+
collections::BTreeMap,
23+
fs,
24+
io::{Cursor, Read},
25+
mem::size_of,
26+
ops::Deref,
27+
path::{Path, PathBuf},
28+
str::FromStr,
29+
sync::Arc,
30+
},
31+
tar::{Archive, Builder},
32+
tempfile::{tempdir, TempDir},
33+
};
34+
35+
pub(crate) type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
36+
37+
#[derive(Clone, Debug, Deserialize, Serialize)]
38+
#[serde(rename_all = "lowercase")]
39+
pub(crate) enum DependencyType {
40+
Dev,
41+
Build,
42+
Normal,
43+
}
44+
45+
#[allow(dead_code)]
46+
#[derive(Clone, Debug, Deserialize, Serialize)]
47+
pub(crate) struct Dependency {
48+
pub name: String,
49+
pub version_req: String,
50+
pub features: Vec<String>,
51+
pub optional: bool,
52+
pub default_features: bool,
53+
pub target: Option<String>,
54+
pub kind: DependencyType,
55+
pub registry: Option<String>,
56+
pub explicit_name_in_toml: Option<String>,
57+
}
58+
59+
#[derive(Clone, Debug, Deserialize, Serialize)]
60+
#[allow(unused)]
61+
pub(crate) struct PackageMetaData {
62+
pub name: String,
63+
pub vers: String,
64+
pub deps: Vec<Dependency>,
65+
pub features: BTreeMap<String, Vec<String>>,
66+
pub authors: Vec<String>,
67+
pub description: Option<String>,
68+
pub documentation: Option<String>,
69+
pub homepage: Option<String>,
70+
pub readme: Option<String>,
71+
pub readme_file: Option<String>,
72+
pub keywords: Vec<String>,
73+
pub categories: Vec<String>,
74+
pub license: Option<String>,
75+
pub license_file: Option<String>,
76+
pub repository: Option<String>,
77+
pub badges: BTreeMap<String, BTreeMap<String, String>>,
78+
pub links: Option<String>,
79+
pub rust_version: Option<String>,
80+
}
81+
82+
impl PackageMetaData {
83+
fn new(bytes: &Bytes) -> serde_json::Result<(PackageMetaData, usize)> {
84+
let (json_length, sizeof_length) = Self::read_u32_length(bytes)?;
85+
let end_of_meta_data = sizeof_length.saturating_add(json_length as usize);
86+
let json_body = bytes.slice(sizeof_length..end_of_meta_data);
87+
from_slice::<PackageMetaData>(json_body.deref()).map(|data| (data, end_of_meta_data))
88+
}
89+
90+
fn read_u32_length(bytes: &Bytes) -> serde_json::Result<(u32, usize)> {
91+
let sizeof_length = size_of::<u32>();
92+
let length_le = bytes.slice(0..sizeof_length);
93+
let length =
94+
u32::from_le_bytes(length_le.deref().try_into().expect("Failed to read length"));
95+
Ok((length, sizeof_length))
96+
}
97+
}
98+
99+
pub(crate) struct Program {
100+
path: String,
101+
id: Pubkey,
102+
_tempdir: Arc<TempDir>,
103+
}
104+
105+
impl Program {
106+
fn deploy(&self, client: Arc<Client>, signer: &dyn Signer) -> Result<(), Error> {
107+
if self.id != signer.pubkey() {
108+
return Err("Signer doesn't match program ID".into());
109+
}
110+
111+
let program_data = read_and_verify_elf(self.path.as_ref())
112+
.map_err(|e| format!("failed to read the program: {}", e))?;
113+
114+
let command_config = RPCCommandConfig::new(client.as_ref());
115+
116+
process_deploy_program(
117+
client.rpc_client.clone(),
118+
&command_config.0,
119+
&program_data,
120+
program_data.len() as u32,
121+
&signer.pubkey(),
122+
Some(signer),
123+
)
124+
.map_err(|e| {
125+
error!("Failed to deploy the program: {}", e);
126+
format!("Failed to deploy the program: {}", e)
127+
})?;
128+
129+
Ok(())
130+
}
131+
132+
fn dump(&self, client: Arc<Client>) -> Result<(), Error> {
133+
info!("Fetching program {:?}", self.id);
134+
let command_config = RPCCommandConfig::new(client.as_ref());
135+
136+
process_dump(
137+
client.rpc_client.clone(),
138+
command_config.0.commitment,
139+
Some(self.id),
140+
&self.path,
141+
)
142+
.map_err(|e| {
143+
error!("Failed to fetch the program: {}", e);
144+
format!("Failed to fetch the program: {}", e)
145+
})?;
146+
147+
Ok(())
148+
}
149+
150+
pub(crate) fn crate_name_to_program_id(crate_name: &str) -> Option<Pubkey> {
151+
crate_name
152+
.split_once('-')
153+
.and_then(|(_prefix, id_str)| Pubkey::from_str(id_str).ok())
154+
}
155+
}
156+
157+
impl From<&UnpackedCrate> for Program {
158+
fn from(value: &UnpackedCrate) -> Self {
159+
Self {
160+
path: value.program_path.clone(),
161+
id: value.program_id,
162+
_tempdir: value.tempdir.clone(),
163+
}
164+
}
165+
}
166+
167+
pub(crate) struct CratePackage(pub(crate) Bytes);
168+
169+
impl From<UnpackedCrate> for Result<CratePackage, Error> {
170+
fn from(value: UnpackedCrate) -> Self {
171+
let mut archive = Builder::new(Vec::new());
172+
archive.append_dir_all(".", value.tempdir.path())?;
173+
let data = archive.into_inner()?;
174+
let reader = Cursor::new(data);
175+
let mut encoder = GzEncoder::new(reader, Compression::fast());
176+
let mut zipped_data = Vec::new();
177+
encoder.read_to_end(&mut zipped_data)?;
178+
179+
let meta_str = serde_json::to_string(&value.meta)?;
180+
181+
let sizeof_length = size_of::<u32>();
182+
let mut packed = Vec::with_capacity(
183+
sizeof_length
184+
.saturating_add(meta_str.len())
185+
.saturating_add(sizeof_length)
186+
.saturating_add(zipped_data.len()),
187+
);
188+
189+
packed[..sizeof_length].copy_from_slice(&u32::to_le_bytes(meta_str.len() as u32));
190+
let offset = sizeof_length;
191+
let end = offset.saturating_add(meta_str.len());
192+
packed[offset..end].copy_from_slice(meta_str.as_bytes());
193+
let offset = end;
194+
let end = offset.saturating_add(sizeof_length);
195+
packed[offset..end].copy_from_slice(&u32::to_le_bytes(zipped_data.len() as u32));
196+
let offset = end;
197+
packed[offset..].copy_from_slice(&zipped_data);
198+
199+
Ok(CratePackage(Bytes::from(packed)))
200+
}
201+
}
202+
203+
pub(crate) struct UnpackedCrate {
204+
meta: PackageMetaData,
205+
cksum: String,
206+
tempdir: Arc<TempDir>,
207+
program_path: String,
208+
program_id: Pubkey,
209+
keypair: Option<Keypair>,
210+
}
211+
212+
impl From<CratePackage> for Result<UnpackedCrate, Error> {
213+
fn from(value: CratePackage) -> Self {
214+
let bytes = value.0;
215+
let (meta, offset) = PackageMetaData::new(&bytes)?;
216+
217+
let (_crate_file_length, length_size) =
218+
PackageMetaData::read_u32_length(&bytes.slice(offset..))?;
219+
let crate_bytes = bytes.slice(offset.saturating_add(length_size)..);
220+
let cksum = format!("{:x}", Sha256::digest(&crate_bytes));
221+
222+
let decoder = GzDecoder::new(crate_bytes.as_ref());
223+
let mut archive = Archive::new(decoder);
224+
225+
let tempdir = tempdir()?;
226+
archive.unpack(tempdir.path())?;
227+
228+
let lib_name = UnpackedCrate::program_library_name(&tempdir, &meta)?;
229+
230+
let program_path =
231+
UnpackedCrate::make_path(&tempdir, &meta, format!("out/{}.so", lib_name))
232+
.into_os_string()
233+
.into_string()
234+
.map_err(|_| "Failed to get program file path")?;
235+
236+
let keypair = Keypair::read_from_file(UnpackedCrate::make_path(
237+
&tempdir,
238+
&meta,
239+
format!("out/{}-keypair.json", lib_name),
240+
))
241+
.map_err(|e| format!("Failed to get keypair from the file: {}", e))?;
242+
243+
Ok(UnpackedCrate {
244+
meta,
245+
cksum,
246+
tempdir: Arc::new(tempdir),
247+
program_path,
248+
program_id: keypair.pubkey(),
249+
keypair: Some(keypair),
250+
})
251+
}
252+
}
253+
254+
impl UnpackedCrate {
255+
pub(crate) fn publish(
256+
&self,
257+
client: Arc<Client>,
258+
index: Arc<RegistryIndex>,
259+
) -> Result<(), Error> {
260+
let Some(signer) = &self.keypair else {
261+
return Err("No signer provided for the program deployment".into());
262+
};
263+
264+
Program::from(self).deploy(client, signer)?;
265+
266+
let mut entry: IndexEntry = self.meta.clone().into();
267+
entry.cksum = self.cksum.clone();
268+
index.insert_entry(entry)?;
269+
270+
info!("Successfully deployed the program");
271+
Ok(())
272+
}
273+
274+
pub(crate) fn fetch_index(id: Pubkey, client: Arc<Client>) -> Result<IndexEntry, Error> {
275+
let (_program, unpacked_crate) = Self::fetch_program(id, client)?;
276+
277+
let mut entry: IndexEntry = unpacked_crate.meta.clone().into();
278+
entry.cksum = unpacked_crate.cksum.clone();
279+
280+
Ok(entry)
281+
}
282+
283+
#[allow(dead_code)]
284+
pub(crate) fn fetch(id: Pubkey, client: Arc<Client>) -> Result<CratePackage, Error> {
285+
let (_program, unpacked_crate) = Self::fetch_program(id, client)?;
286+
UnpackedCrate::into(unpacked_crate)
287+
}
288+
289+
fn fetch_program(id: Pubkey, client: Arc<Client>) -> Result<(Program, UnpackedCrate), Error> {
290+
let crate_obj = Self::new_empty(id)?;
291+
let program = Program::from(&crate_obj);
292+
program.dump(client)?;
293+
294+
// Decompile the program
295+
// Generate a Cargo.toml
296+
297+
Ok((program, crate_obj))
298+
}
299+
300+
fn new_empty(id: Pubkey) -> Result<Self, Error> {
301+
let meta = PackageMetaData {
302+
name: id.to_string(),
303+
vers: "0.1".to_string(),
304+
deps: vec![],
305+
features: BTreeMap::new(),
306+
authors: vec![],
307+
description: None,
308+
documentation: None,
309+
homepage: None,
310+
readme: None,
311+
readme_file: None,
312+
keywords: vec![],
313+
categories: vec![],
314+
license: None,
315+
license_file: None,
316+
repository: None,
317+
badges: BTreeMap::new(),
318+
links: None,
319+
rust_version: None,
320+
};
321+
322+
let tempdir = tempdir()?;
323+
324+
let program_path = Self::make_path(&tempdir, &meta, format!("out/{}.so", id))
325+
.into_os_string()
326+
.into_string()
327+
.map_err(|_| "Failed to get program file path")?;
328+
329+
Ok(Self {
330+
meta,
331+
cksum: "".to_string(),
332+
tempdir: Arc::new(tempdir),
333+
program_path,
334+
program_id: id,
335+
keypair: None,
336+
})
337+
}
338+
339+
fn make_path<P: AsRef<Path>>(tempdir: &TempDir, meta: &PackageMetaData, append: P) -> PathBuf {
340+
let mut path = tempdir.path().to_path_buf();
341+
path.push(format!("{}-{}/", meta.name, meta.vers));
342+
path.push(append);
343+
path
344+
}
345+
346+
fn program_library_name(tempdir: &TempDir, meta: &PackageMetaData) -> Result<String, Error> {
347+
let toml_content = fs::read_to_string(Self::make_path(tempdir, meta, "Cargo.toml.orig"))?;
348+
let toml = toml_content.parse::<toml::Table>()?;
349+
let library_name = toml
350+
.get("lib")
351+
.and_then(|v| v.get("name"))
352+
.and_then(|v| v.as_str())
353+
.ok_or("Failed to get module name")?;
354+
Ok(library_name.to_string())
355+
}
356+
}

0 commit comments

Comments
 (0)