Only encode data with <128 bytes in base58 (#19317)

* Only encode data with <128 bytes in bs58

* Use same MAX_BS58_BYTES const in rpc

* Fix test

* Spell out base

Co-authored-by: Tyera Eulberg <tyera@solana.com>
This commit is contained in:
sakridge 2021-08-20 22:30:59 +02:00 committed by GitHub
parent 4d361af976
commit 967746abbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 110 additions and 114 deletions

View File

@ -28,6 +28,7 @@ use {
pub type StringAmount = String; pub type StringAmount = String;
pub type StringDecimals = String; pub type StringDecimals = String;
pub const MAX_BASE58_BYTES: usize = 128;
/// A duplicate representation of an Account for pretty JSON serialization /// A duplicate representation of an Account for pretty JSON serialization
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -60,6 +61,17 @@ pub enum UiAccountEncoding {
} }
impl UiAccount { impl UiAccount {
fn encode_bs58<T: ReadableAccount>(
account: &T,
data_slice_config: Option<UiDataSliceConfig>,
) -> String {
if account.data().len() <= MAX_BASE58_BYTES {
bs58::encode(slice_data(account.data(), data_slice_config)).into_string()
} else {
"error: data too large for bs58 encoding".to_string()
}
}
pub fn encode<T: ReadableAccount>( pub fn encode<T: ReadableAccount>(
pubkey: &Pubkey, pubkey: &Pubkey,
account: &T, account: &T,
@ -68,13 +80,14 @@ impl UiAccount {
data_slice_config: Option<UiDataSliceConfig>, data_slice_config: Option<UiDataSliceConfig>,
) -> Self { ) -> Self {
let data = match encoding { let data = match encoding {
UiAccountEncoding::Binary => UiAccountData::LegacyBinary( UiAccountEncoding::Binary => {
bs58::encode(slice_data(account.data(), data_slice_config)).into_string(), let data = Self::encode_bs58(account, data_slice_config);
), UiAccountData::LegacyBinary(data)
UiAccountEncoding::Base58 => UiAccountData::Binary( }
bs58::encode(slice_data(account.data(), data_slice_config)).into_string(), UiAccountEncoding::Base58 => {
encoding, let data = Self::encode_bs58(account, data_slice_config);
), UiAccountData::Binary(data, encoding)
}
UiAccountEncoding::Base64 => UiAccountData::Binary( UiAccountEncoding::Base64 => UiAccountData::Binary(
base64::encode(slice_data(account.data(), data_slice_config)), base64::encode(slice_data(account.data(), data_slice_config)),
encoding, encoding,

View File

@ -14,7 +14,7 @@ use {
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
solana_account_decoder::{ solana_account_decoder::{
parse_token::{spl_token_id_v2_0, token_amount_to_ui_amount, UiTokenAmount}, parse_token::{spl_token_id_v2_0, token_amount_to_ui_amount, UiTokenAmount},
UiAccount, UiAccountEncoding, UiDataSliceConfig, UiAccount, UiAccountEncoding, UiDataSliceConfig, MAX_BASE58_BYTES,
}, },
solana_client::{ solana_client::{
rpc_cache::LargestAccountsCache, rpc_cache::LargestAccountsCache,
@ -2103,9 +2103,9 @@ fn encode_account<T: ReadableAccount>(
data_slice: Option<UiDataSliceConfig>, data_slice: Option<UiDataSliceConfig>,
) -> Result<UiAccount> { ) -> Result<UiAccount> {
if (encoding == UiAccountEncoding::Binary || encoding == UiAccountEncoding::Base58) if (encoding == UiAccountEncoding::Binary || encoding == UiAccountEncoding::Base58)
&& account.data().len() > 128 && account.data().len() > MAX_BASE58_BYTES
{ {
let message = "Encoded binary (base 58) data should be less than 128 bytes, please use Base64 encoding.".to_string(); let message = format!("Encoded binary (base 58) data should be less than {} bytes, please use Base64 encoding.", MAX_BASE58_BYTES);
Err(error::Error { Err(error::Error {
code: error::ErrorCode::InvalidRequest, code: error::ErrorCode::InvalidRequest,
message, message,

View File

@ -827,6 +827,7 @@ mod tests {
max_active_subscriptions: MAX_ACTIVE_SUBSCRIPTIONS, max_active_subscriptions: MAX_ACTIVE_SUBSCRIPTIONS,
}; };
let session = create_session(); let session = create_session();
let encoding = UiAccountEncoding::Base64;
let (subscriber, _id_receiver, receiver) = Subscriber::new_test("accountNotification"); let (subscriber, _id_receiver, receiver) = Subscriber::new_test("accountNotification");
rpc.account_subscribe( rpc.account_subscribe(
session, session,
@ -834,7 +835,7 @@ mod tests {
stake_account.pubkey().to_string(), stake_account.pubkey().to_string(),
Some(RpcAccountInfoConfig { Some(RpcAccountInfoConfig {
commitment: Some(CommitmentConfig::processed()), commitment: Some(CommitmentConfig::processed()),
encoding: None, encoding: Some(encoding),
data_slice: None, data_slice: None,
}), }),
); );
@ -873,7 +874,7 @@ mod tests {
"value": { "value": {
"owner": stake_program_id.to_string(), "owner": stake_program_id.to_string(),
"lamports": 51, "lamports": 51,
"data": bs58::encode(expected_data).into_string(), "data": [base64::encode(expected_data), encoding],
"executable": false, "executable": false,
"rentEpoch": 0, "rentEpoch": 0,
}, },

View File

@ -1384,6 +1384,26 @@ pub(crate) mod tests {
inner_receiver.recv().expect("recv error") inner_receiver.recv().expect("recv error")
} }
fn make_account_result(lamports: u64, subscription: u64, data: &str) -> serde_json::Value {
json!({
"jsonrpc": "2.0",
"method": "accountNotification",
"params": {
"result": {
"context": { "slot": 1 },
"value": {
"data": data,
"executable": false,
"lamports": lamports,
"owner": "11111111111111111111111111111111",
"rentEpoch": 0,
},
},
"subscription": subscription,
}
})
}
#[test] #[test]
#[serial] #[serial]
fn test_check_account_subscribe() { fn test_check_account_subscribe() {
@ -1400,12 +1420,6 @@ pub(crate) mod tests {
bank_forks.write().unwrap().insert(bank1); bank_forks.write().unwrap().insert(bank1);
let alice = Keypair::new(); let alice = Keypair::new();
let (create_sub, _id_receiver, create_recv) = Subscriber::new_test("accountNotification");
let (close_sub, _id_receiver, close_recv) = Subscriber::new_test("accountNotification");
let create_sub_id = SubscriptionId::Number(0);
let close_sub_id = SubscriptionId::Number(1);
let exit = Arc::new(AtomicBool::new(false)); let exit = Arc::new(AtomicBool::new(false));
let subscriptions = RpcSubscriptions::new( let subscriptions = RpcSubscriptions::new(
&exit, &exit,
@ -1415,25 +1429,8 @@ pub(crate) mod tests {
))), ))),
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks), OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
); );
subscriptions.add_account_subscription(
alice.pubkey(),
Some(RpcAccountInfoConfig {
commitment: Some(CommitmentConfig::processed()),
encoding: None,
data_slice: None,
}),
create_sub_id.clone(),
create_sub,
);
assert!(subscriptions let tx0 = system_transaction::create_account(
.subscriptions
.account_subscriptions
.read()
.unwrap()
.contains_key(&alice.pubkey()));
let tx = system_transaction::create_account(
&mint_keypair, &mint_keypair,
&alice, &alice,
blockhash, blockhash,
@ -1441,92 +1438,77 @@ pub(crate) mod tests {
0, 0,
&system_program::id(), &system_program::id(),
); );
bank_forks let expected0 = make_account_result(1, 0, "");
.write()
.unwrap()
.get(1)
.unwrap()
.process_transaction(&tx)
.unwrap();
let commitment_slots = CommitmentSlots {
slot: 1,
..CommitmentSlots::default()
};
subscriptions.notify_subscribers(commitment_slots);
let (response, _) = robust_poll_or_panic(create_recv);
let expected = json!({
"jsonrpc": "2.0",
"method": "accountNotification",
"params": {
"result": {
"context": { "slot": 1 },
"value": {
"data": "",
"executable": false,
"lamports": 1,
"owner": "11111111111111111111111111111111",
"rentEpoch": 0,
},
},
"subscription": 0,
}
});
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
subscriptions.remove_account_subscription(&create_sub_id);
subscriptions.add_account_subscription( let tx1 = {
alice.pubkey(),
Some(RpcAccountInfoConfig {
commitment: Some(CommitmentConfig::processed()),
encoding: None,
data_slice: None,
}),
close_sub_id.clone(),
close_sub,
);
let tx = {
let instruction = let instruction =
system_instruction::transfer(&alice.pubkey(), &mint_keypair.pubkey(), 1); system_instruction::transfer(&alice.pubkey(), &mint_keypair.pubkey(), 1);
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
Transaction::new(&[&alice, &mint_keypair], message, blockhash) Transaction::new(&[&alice, &mint_keypair], message, blockhash)
}; };
let expected1 = make_account_result(0, 1, "");
bank_forks let tx2 = system_transaction::create_account(
.write() &mint_keypair,
.unwrap() &alice,
.get(1) blockhash,
.unwrap() 1,
.process_transaction(&tx) 1024,
.unwrap(); &system_program::id(),
subscriptions.notify_subscribers(commitment_slots); );
let (response, _) = robust_poll_or_panic(close_recv); let expected2 = make_account_result(1, 2, "error: data too large for bs58 encoding");
let expected = json!({
"jsonrpc": "2.0",
"method": "accountNotification",
"params": {
"result": {
"context": { "slot": 1 },
"value": {
"data": "",
"executable": false,
"lamports": 0,
"owner": "11111111111111111111111111111111",
"rentEpoch": 0,
},
},
"subscription": 1,
}
});
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
subscriptions.remove_account_subscription(&close_sub_id);
assert!(!subscriptions let subscribe_cases = vec![
.subscriptions (alice.pubkey(), tx0, expected0),
.account_subscriptions (alice.pubkey(), tx1, expected1),
.read() (alice.pubkey(), tx2, expected2),
.unwrap() ];
.contains_key(&alice.pubkey()));
for (i, (pubkey, tx, expected)) in subscribe_cases.iter().enumerate() {
let (sub, _id_receiver, recv) = Subscriber::new_test("accountNotification");
let sub_id = SubscriptionId::Number(i as u64);
subscriptions.add_account_subscription(
*pubkey,
Some(RpcAccountInfoConfig {
commitment: Some(CommitmentConfig::processed()),
encoding: None,
data_slice: None,
}),
sub_id.clone(),
sub,
);
assert!(subscriptions
.subscriptions
.account_subscriptions
.read()
.unwrap()
.contains_key(pubkey));
bank_forks
.read()
.unwrap()
.get(1)
.unwrap()
.process_transaction(tx)
.unwrap();
let commitment_slots = CommitmentSlots {
slot: 1,
..CommitmentSlots::default()
};
subscriptions.notify_subscribers(commitment_slots);
let (response, _) = robust_poll_or_panic(recv);
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
subscriptions.remove_account_subscription(&sub_id);
assert!(!subscriptions
.subscriptions
.account_subscriptions
.read()
.unwrap()
.contains_key(pubkey));
}
} }
#[test] #[test]