Reduce remaining program crates to boilerplate crates
This commit is contained in:
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -2445,8 +2445,11 @@ name = "solana-storage-api"
|
|||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"solana-logger 0.13.0",
|
||||||
|
"solana-runtime 0.13.0",
|
||||||
"solana-sdk 0.13.0",
|
"solana-sdk 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2454,12 +2457,8 @@ dependencies = [
|
|||||||
name = "solana-storage-program"
|
name = "solana-storage-program"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"solana-logger 0.13.0",
|
"solana-logger 0.13.0",
|
||||||
"solana-runtime 0.13.0",
|
|
||||||
"solana-sdk 0.13.0",
|
"solana-sdk 0.13.0",
|
||||||
"solana-storage-api 0.13.0",
|
"solana-storage-api 0.13.0",
|
||||||
]
|
]
|
||||||
@ -2469,8 +2468,10 @@ name = "solana-token-api"
|
|||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"solana-logger 0.13.0",
|
||||||
"solana-sdk 0.13.0",
|
"solana-sdk 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2478,12 +2479,10 @@ dependencies = [
|
|||||||
name = "solana-token-program"
|
name = "solana-token-program"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"solana-logger 0.13.0",
|
"solana-logger 0.13.0",
|
||||||
"solana-sdk 0.13.0",
|
"solana-sdk 0.13.0",
|
||||||
|
"solana-token-api 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2502,6 +2501,9 @@ dependencies = [
|
|||||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"solana-logger 0.13.0",
|
||||||
|
"solana-metrics 0.13.0",
|
||||||
|
"solana-runtime 0.13.0",
|
||||||
"solana-sdk 0.13.0",
|
"solana-sdk 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2509,11 +2511,8 @@ dependencies = [
|
|||||||
name = "solana-vote-program"
|
name = "solana-vote-program"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"solana-logger 0.13.0",
|
"solana-logger 0.13.0",
|
||||||
"solana-metrics 0.13.0",
|
|
||||||
"solana-runtime 0.13.0",
|
|
||||||
"solana-sdk 0.13.0",
|
"solana-sdk 0.13.0",
|
||||||
"solana-vote-api 0.13.0",
|
"solana-vote-api 0.13.0",
|
||||||
]
|
]
|
||||||
|
@ -141,7 +141,7 @@ pub fn process_instruction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::budget_instruction::BudgetInstruction;
|
use crate::budget_instruction::BudgetInstruction;
|
||||||
use crate::budget_script::BudgetScript;
|
use crate::budget_script::BudgetScript;
|
||||||
|
@ -10,10 +10,15 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.1.2"
|
bincode = "1.1.2"
|
||||||
|
log = "0.4.2"
|
||||||
serde = "1.0.89"
|
serde = "1.0.89"
|
||||||
serde_derive = "1.0.89"
|
serde_derive = "1.0.89"
|
||||||
|
solana-logger = { path = "../../logger", version = "0.13.0" }
|
||||||
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
solana-runtime = { path = "../../runtime", version = "0.13.0" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "solana_storage_api"
|
name = "solana_storage_api"
|
||||||
crate-type = ["lib"]
|
crate-type = ["lib"]
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
pub mod storage_processor;
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
460
programs/storage_api/src/storage_processor.rs
Normal file
460
programs/storage_api/src/storage_processor.rs
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
//! storage program
|
||||||
|
//! Receive mining proofs from miners, validate the answers
|
||||||
|
//! and give reward for good proofs.
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
use log::*;
|
||||||
|
use solana_sdk::account::KeyedAccount;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::transaction::InstructionError;
|
||||||
|
|
||||||
|
pub const TOTAL_VALIDATOR_REWARDS: u64 = 1000;
|
||||||
|
pub const TOTAL_REPLICATOR_REWARDS: u64 = 1000;
|
||||||
|
|
||||||
|
fn count_valid_proofs(proofs: &[ProofStatus]) -> u64 {
|
||||||
|
let mut num = 0;
|
||||||
|
for proof in proofs {
|
||||||
|
if let ProofStatus::Valid = proof {
|
||||||
|
num += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
num
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_instruction(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
keyed_accounts: &mut [KeyedAccount],
|
||||||
|
data: &[u8],
|
||||||
|
_tick_height: u64,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
solana_logger::setup();
|
||||||
|
|
||||||
|
if keyed_accounts.len() != 1 {
|
||||||
|
// keyed_accounts[1] should be the main storage key
|
||||||
|
// to access its data
|
||||||
|
Err(InstructionError::InvalidArgument)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// accounts_keys[0] must be signed
|
||||||
|
if keyed_accounts[0].signer_key().is_none() {
|
||||||
|
info!("account[0] is unsigned");
|
||||||
|
Err(InstructionError::GenericError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(syscall) = bincode::deserialize(data) {
|
||||||
|
let mut storage_account_state = if let Ok(storage_account_state) =
|
||||||
|
bincode::deserialize(&keyed_accounts[0].account.data)
|
||||||
|
{
|
||||||
|
storage_account_state
|
||||||
|
} else {
|
||||||
|
StorageProgramState::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"deserialized state height: {}",
|
||||||
|
storage_account_state.entry_height
|
||||||
|
);
|
||||||
|
match syscall {
|
||||||
|
StorageProgram::SubmitMiningProof {
|
||||||
|
sha_state,
|
||||||
|
entry_height,
|
||||||
|
signature,
|
||||||
|
} => {
|
||||||
|
let segment_index = get_segment_from_entry(entry_height);
|
||||||
|
let current_segment_index =
|
||||||
|
get_segment_from_entry(storage_account_state.entry_height);
|
||||||
|
if segment_index >= current_segment_index {
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Mining proof submitted with state {:?} entry_height: {}",
|
||||||
|
sha_state, entry_height
|
||||||
|
);
|
||||||
|
|
||||||
|
let proof_info = ProofInfo {
|
||||||
|
id: *keyed_accounts[0].signer_key().unwrap(),
|
||||||
|
sha_state,
|
||||||
|
signature,
|
||||||
|
};
|
||||||
|
storage_account_state.proofs[segment_index].push(proof_info);
|
||||||
|
}
|
||||||
|
StorageProgram::AdvertiseStorageRecentBlockhash { hash, entry_height } => {
|
||||||
|
let original_segments = storage_account_state.entry_height / ENTRIES_PER_SEGMENT;
|
||||||
|
let segments = entry_height / ENTRIES_PER_SEGMENT;
|
||||||
|
debug!(
|
||||||
|
"advertise new last id segments: {} orig: {}",
|
||||||
|
segments, original_segments
|
||||||
|
);
|
||||||
|
if segments <= original_segments {
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_account_state.entry_height = entry_height;
|
||||||
|
storage_account_state.hash = hash;
|
||||||
|
|
||||||
|
// move the proofs to previous_proofs
|
||||||
|
storage_account_state.previous_proofs = storage_account_state.proofs.clone();
|
||||||
|
storage_account_state.proofs.clear();
|
||||||
|
storage_account_state
|
||||||
|
.proofs
|
||||||
|
.resize(segments as usize, Vec::new());
|
||||||
|
|
||||||
|
// move lockout_validations to reward_validations
|
||||||
|
storage_account_state.reward_validations =
|
||||||
|
storage_account_state.lockout_validations.clone();
|
||||||
|
storage_account_state.lockout_validations.clear();
|
||||||
|
storage_account_state
|
||||||
|
.lockout_validations
|
||||||
|
.resize(segments as usize, Vec::new());
|
||||||
|
}
|
||||||
|
StorageProgram::ProofValidation {
|
||||||
|
entry_height,
|
||||||
|
proof_mask,
|
||||||
|
} => {
|
||||||
|
if entry_height >= storage_account_state.entry_height {
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
let segment_index = get_segment_from_entry(entry_height);
|
||||||
|
if storage_account_state.previous_proofs[segment_index].len() != proof_mask.len() {
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check that each proof mask matches the signature
|
||||||
|
/*for (i, entry) in proof_mask.iter().enumerate() {
|
||||||
|
if storage_account_state.previous_proofs[segment_index][i] != signature.as_ref[0] {
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
let info = ValidationInfo {
|
||||||
|
id: *keyed_accounts[0].signer_key().unwrap(),
|
||||||
|
proof_mask,
|
||||||
|
};
|
||||||
|
storage_account_state.lockout_validations[segment_index].push(info);
|
||||||
|
}
|
||||||
|
StorageProgram::ClaimStorageReward { entry_height } => {
|
||||||
|
let claims_index = get_segment_from_entry(entry_height);
|
||||||
|
let account_key = keyed_accounts[0].signer_key().unwrap();
|
||||||
|
let mut num_validations = 0;
|
||||||
|
let mut total_validations = 0;
|
||||||
|
for validation in &storage_account_state.reward_validations[claims_index] {
|
||||||
|
if *account_key == validation.id {
|
||||||
|
num_validations += count_valid_proofs(&validation.proof_mask);
|
||||||
|
} else {
|
||||||
|
total_validations += count_valid_proofs(&validation.proof_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total_validations += num_validations;
|
||||||
|
if total_validations > 0 {
|
||||||
|
keyed_accounts[0].account.lamports +=
|
||||||
|
(TOTAL_VALIDATOR_REWARDS * num_validations) / total_validations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bincode::serialize_into(
|
||||||
|
&mut keyed_accounts[0].account.data[..],
|
||||||
|
&storage_account_state,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(InstructionError::AccountDataTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
info!("Invalid instruction data: {:?}", data);
|
||||||
|
Err(InstructionError::InvalidInstructionData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{ProofStatus, StorageTransaction, ENTRIES_PER_SEGMENT};
|
||||||
|
use bincode::deserialize;
|
||||||
|
use solana_runtime::bank::Bank;
|
||||||
|
use solana_sdk::account::{create_keyed_accounts, Account};
|
||||||
|
use solana_sdk::genesis_block::GenesisBlock;
|
||||||
|
use solana_sdk::hash::{hash, Hash};
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
|
||||||
|
use solana_sdk::system_transaction::SystemTransaction;
|
||||||
|
use solana_sdk::transaction::{CompiledInstruction, Transaction};
|
||||||
|
|
||||||
|
fn test_transaction(
|
||||||
|
tx: &Transaction,
|
||||||
|
program_accounts: &mut [Account],
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
assert_eq!(tx.instructions.len(), 1);
|
||||||
|
let CompiledInstruction {
|
||||||
|
ref accounts,
|
||||||
|
ref data,
|
||||||
|
..
|
||||||
|
} = tx.instructions[0];
|
||||||
|
|
||||||
|
info!("accounts: {:?}", accounts);
|
||||||
|
|
||||||
|
let mut keyed_accounts: Vec<_> = accounts
|
||||||
|
.iter()
|
||||||
|
.map(|&index| {
|
||||||
|
let index = index as usize;
|
||||||
|
let key = &tx.account_keys[index];
|
||||||
|
(key, index < tx.signatures.len())
|
||||||
|
})
|
||||||
|
.zip(program_accounts.iter_mut())
|
||||||
|
.map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let ret = process_instruction(&id(), &mut keyed_accounts, &data, 42);
|
||||||
|
info!("ret: {:?}", ret);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_tx() {
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let mut accounts = [(keypair.pubkey(), Account::default())];
|
||||||
|
let mut keyed_accounts = create_keyed_accounts(&mut accounts);
|
||||||
|
assert!(process_instruction(&id(), &mut keyed_accounts, &[], 42).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialize_overflow() {
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let mut keyed_accounts = Vec::new();
|
||||||
|
let mut user_account = Account::default();
|
||||||
|
let pubkey = keypair.pubkey();
|
||||||
|
keyed_accounts.push(KeyedAccount::new(&pubkey, true, &mut user_account));
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
ENTRIES_PER_SEGMENT,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
process_instruction(&id(), &mut keyed_accounts, &tx.instructions[0].data, 42),
|
||||||
|
Err(InstructionError::AccountDataTooSmall)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_accounts_len() {
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let mut accounts = [Account::default()];
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_mining_proof(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
0,
|
||||||
|
Signature::default(),
|
||||||
|
);
|
||||||
|
assert!(test_transaction(&tx, &mut accounts).is_err());
|
||||||
|
|
||||||
|
let mut accounts = [Account::default(), Account::default(), Account::default()];
|
||||||
|
|
||||||
|
assert!(test_transaction(&tx, &mut accounts).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_submit_mining_invalid_entry_height() {
|
||||||
|
solana_logger::setup();
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let mut accounts = [Account::default(), Account::default()];
|
||||||
|
accounts[1].data.resize(16 * 1024, 0);
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_mining_proof(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
0,
|
||||||
|
Signature::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Haven't seen a transaction to roll over the epoch, so this should fail
|
||||||
|
assert!(test_transaction(&tx, &mut accounts).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_submit_mining_ok() {
|
||||||
|
solana_logger::setup();
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let mut accounts = [Account::default(), Account::default()];
|
||||||
|
accounts[0].data.resize(16 * 1024, 0);
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
ENTRIES_PER_SEGMENT,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_transaction(&tx, &mut accounts).unwrap();
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_mining_proof(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
0,
|
||||||
|
Signature::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
test_transaction(&tx, &mut accounts).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_mining() {
|
||||||
|
solana_logger::setup();
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let mut accounts = [Account::default(), Account::default()];
|
||||||
|
accounts[0].data.resize(16 * 1024, 0);
|
||||||
|
|
||||||
|
let entry_height = 0;
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
ENTRIES_PER_SEGMENT,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_transaction(&tx, &mut accounts).unwrap();
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_mining_proof(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
entry_height,
|
||||||
|
Signature::default(),
|
||||||
|
);
|
||||||
|
test_transaction(&tx, &mut accounts).unwrap();
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
ENTRIES_PER_SEGMENT * 2,
|
||||||
|
);
|
||||||
|
test_transaction(&tx, &mut accounts).unwrap();
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_proof_validation(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
entry_height,
|
||||||
|
vec![ProofStatus::Valid],
|
||||||
|
);
|
||||||
|
test_transaction(&tx, &mut accounts).unwrap();
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
||||||
|
&keypair,
|
||||||
|
Hash::default(),
|
||||||
|
Hash::default(),
|
||||||
|
ENTRIES_PER_SEGMENT * 3,
|
||||||
|
);
|
||||||
|
test_transaction(&tx, &mut accounts).unwrap();
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_reward_claim(&keypair, Hash::default(), entry_height);
|
||||||
|
test_transaction(&tx, &mut accounts).unwrap();
|
||||||
|
|
||||||
|
assert!(accounts[0].lamports == TOTAL_VALIDATOR_REWARDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_storage_entry_height(bank: &Bank, account: &Pubkey) -> u64 {
|
||||||
|
match bank.get_account(&account) {
|
||||||
|
Some(storage_system_account) => {
|
||||||
|
let state = deserialize(&storage_system_account.data);
|
||||||
|
if let Ok(state) = state {
|
||||||
|
let state: StorageProgramState = state;
|
||||||
|
return state.entry_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
info!("error in reading entry_height");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_storage_blockhash(bank: &Bank, account: &Pubkey) -> Hash {
|
||||||
|
if let Some(storage_system_account) = bank.get_account(&account) {
|
||||||
|
let state = deserialize(&storage_system_account.data);
|
||||||
|
if let Ok(state) = state {
|
||||||
|
let state: StorageProgramState = state;
|
||||||
|
return state.hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Hash::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bank_storage() {
|
||||||
|
let (mut genesis_block, alice) = GenesisBlock::new(1000);
|
||||||
|
genesis_block
|
||||||
|
.native_programs
|
||||||
|
.push(("solana_storage_program".to_string(), id()));
|
||||||
|
let bank = Bank::new(&genesis_block);
|
||||||
|
|
||||||
|
let bob = Keypair::new();
|
||||||
|
let jack = Keypair::new();
|
||||||
|
let jill = Keypair::new();
|
||||||
|
|
||||||
|
let x = 42;
|
||||||
|
let blockhash = genesis_block.hash();
|
||||||
|
let x2 = x * 2;
|
||||||
|
let storage_blockhash = hash(&[x2]);
|
||||||
|
|
||||||
|
bank.register_tick(&blockhash);
|
||||||
|
|
||||||
|
bank.transfer(10, &alice, &jill.pubkey(), blockhash)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
bank.transfer(10, &alice, &bob.pubkey(), blockhash).unwrap();
|
||||||
|
bank.transfer(10, &alice, &jack.pubkey(), blockhash)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let tx = SystemTransaction::new_program_account(
|
||||||
|
&alice,
|
||||||
|
&bob.pubkey(),
|
||||||
|
blockhash,
|
||||||
|
1,
|
||||||
|
4 * 1024,
|
||||||
|
&id(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
bank.process_transaction(&tx).unwrap();
|
||||||
|
|
||||||
|
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
||||||
|
&bob,
|
||||||
|
storage_blockhash,
|
||||||
|
blockhash,
|
||||||
|
ENTRIES_PER_SEGMENT,
|
||||||
|
);
|
||||||
|
|
||||||
|
bank.process_transaction(&tx).unwrap();
|
||||||
|
|
||||||
|
let entry_height = 0;
|
||||||
|
let tx = StorageTransaction::new_mining_proof(
|
||||||
|
&bob,
|
||||||
|
Hash::default(),
|
||||||
|
blockhash,
|
||||||
|
entry_height,
|
||||||
|
Signature::default(),
|
||||||
|
);
|
||||||
|
let _result = bank.process_transaction(&tx).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_storage_entry_height(&bank, &bob.pubkey()),
|
||||||
|
ENTRIES_PER_SEGMENT
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_storage_blockhash(&bank, &bob.pubkey()),
|
||||||
|
storage_blockhash
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -9,17 +9,11 @@ homepage = "https://solana.com/"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.1.2"
|
|
||||||
log = "0.4.2"
|
log = "0.4.2"
|
||||||
serde = "1.0.89"
|
|
||||||
serde_derive = "1.0.89"
|
|
||||||
solana-logger = { path = "../../logger", version = "0.13.0" }
|
solana-logger = { path = "../../logger", version = "0.13.0" }
|
||||||
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
||||||
solana-storage-api = { path = "../storage_api", version = "0.13.0" }
|
solana-storage-api = { path = "../storage_api", version = "0.13.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
solana-runtime = { path = "../../runtime", version = "0.13.0" }
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "solana_storage_program"
|
name = "solana_storage_program"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
@ -1,365 +1,3 @@
|
|||||||
//! storage program
|
use solana_storage_api::storage_processor::process_instruction;
|
||||||
//! Receive mining proofs from miners, validate the answers
|
|
||||||
//! and give reward for good proofs.
|
|
||||||
|
|
||||||
use log::*;
|
solana_sdk::process_instruction_entrypoint!(process_instruction);
|
||||||
extern crate solana_sdk;
|
|
||||||
|
|
||||||
use solana_sdk::account::KeyedAccount;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::solana_entrypoint;
|
|
||||||
use solana_sdk::transaction::InstructionError;
|
|
||||||
use solana_storage_api::*;
|
|
||||||
|
|
||||||
pub const TOTAL_VALIDATOR_REWARDS: u64 = 1000;
|
|
||||||
pub const TOTAL_REPLICATOR_REWARDS: u64 = 1000;
|
|
||||||
|
|
||||||
fn count_valid_proofs(proofs: &[ProofStatus]) -> u64 {
|
|
||||||
let mut num = 0;
|
|
||||||
for proof in proofs {
|
|
||||||
if let ProofStatus::Valid = proof {
|
|
||||||
num += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
num
|
|
||||||
}
|
|
||||||
|
|
||||||
solana_entrypoint!(entrypoint);
|
|
||||||
fn entrypoint(
|
|
||||||
_program_id: &Pubkey,
|
|
||||||
keyed_accounts: &mut [KeyedAccount],
|
|
||||||
data: &[u8],
|
|
||||||
_tick_height: u64,
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
solana_logger::setup();
|
|
||||||
|
|
||||||
if keyed_accounts.len() != 1 {
|
|
||||||
// keyed_accounts[1] should be the main storage key
|
|
||||||
// to access its data
|
|
||||||
Err(InstructionError::InvalidArgument)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// accounts_keys[0] must be signed
|
|
||||||
if keyed_accounts[0].signer_key().is_none() {
|
|
||||||
info!("account[0] is unsigned");
|
|
||||||
Err(InstructionError::GenericError)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(syscall) = bincode::deserialize(data) {
|
|
||||||
let mut storage_account_state = if let Ok(storage_account_state) =
|
|
||||||
bincode::deserialize(&keyed_accounts[0].account.data)
|
|
||||||
{
|
|
||||||
storage_account_state
|
|
||||||
} else {
|
|
||||||
StorageProgramState::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"deserialized state height: {}",
|
|
||||||
storage_account_state.entry_height
|
|
||||||
);
|
|
||||||
match syscall {
|
|
||||||
StorageProgram::SubmitMiningProof {
|
|
||||||
sha_state,
|
|
||||||
entry_height,
|
|
||||||
signature,
|
|
||||||
} => {
|
|
||||||
let segment_index = get_segment_from_entry(entry_height);
|
|
||||||
let current_segment_index =
|
|
||||||
get_segment_from_entry(storage_account_state.entry_height);
|
|
||||||
if segment_index >= current_segment_index {
|
|
||||||
return Err(InstructionError::InvalidArgument);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"Mining proof submitted with state {:?} entry_height: {}",
|
|
||||||
sha_state, entry_height
|
|
||||||
);
|
|
||||||
|
|
||||||
let proof_info = ProofInfo {
|
|
||||||
id: *keyed_accounts[0].signer_key().unwrap(),
|
|
||||||
sha_state,
|
|
||||||
signature,
|
|
||||||
};
|
|
||||||
storage_account_state.proofs[segment_index].push(proof_info);
|
|
||||||
}
|
|
||||||
StorageProgram::AdvertiseStorageRecentBlockhash { hash, entry_height } => {
|
|
||||||
let original_segments = storage_account_state.entry_height / ENTRIES_PER_SEGMENT;
|
|
||||||
let segments = entry_height / ENTRIES_PER_SEGMENT;
|
|
||||||
debug!(
|
|
||||||
"advertise new last id segments: {} orig: {}",
|
|
||||||
segments, original_segments
|
|
||||||
);
|
|
||||||
if segments <= original_segments {
|
|
||||||
return Err(InstructionError::InvalidArgument);
|
|
||||||
}
|
|
||||||
|
|
||||||
storage_account_state.entry_height = entry_height;
|
|
||||||
storage_account_state.hash = hash;
|
|
||||||
|
|
||||||
// move the proofs to previous_proofs
|
|
||||||
storage_account_state.previous_proofs = storage_account_state.proofs.clone();
|
|
||||||
storage_account_state.proofs.clear();
|
|
||||||
storage_account_state
|
|
||||||
.proofs
|
|
||||||
.resize(segments as usize, Vec::new());
|
|
||||||
|
|
||||||
// move lockout_validations to reward_validations
|
|
||||||
storage_account_state.reward_validations =
|
|
||||||
storage_account_state.lockout_validations.clone();
|
|
||||||
storage_account_state.lockout_validations.clear();
|
|
||||||
storage_account_state
|
|
||||||
.lockout_validations
|
|
||||||
.resize(segments as usize, Vec::new());
|
|
||||||
}
|
|
||||||
StorageProgram::ProofValidation {
|
|
||||||
entry_height,
|
|
||||||
proof_mask,
|
|
||||||
} => {
|
|
||||||
if entry_height >= storage_account_state.entry_height {
|
|
||||||
return Err(InstructionError::InvalidArgument);
|
|
||||||
}
|
|
||||||
|
|
||||||
let segment_index = get_segment_from_entry(entry_height);
|
|
||||||
if storage_account_state.previous_proofs[segment_index].len() != proof_mask.len() {
|
|
||||||
return Err(InstructionError::InvalidArgument);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check that each proof mask matches the signature
|
|
||||||
/*for (i, entry) in proof_mask.iter().enumerate() {
|
|
||||||
if storage_account_state.previous_proofs[segment_index][i] != signature.as_ref[0] {
|
|
||||||
return Err(InstructionError::InvalidArgument);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
let info = ValidationInfo {
|
|
||||||
id: *keyed_accounts[0].signer_key().unwrap(),
|
|
||||||
proof_mask,
|
|
||||||
};
|
|
||||||
storage_account_state.lockout_validations[segment_index].push(info);
|
|
||||||
}
|
|
||||||
StorageProgram::ClaimStorageReward { entry_height } => {
|
|
||||||
let claims_index = get_segment_from_entry(entry_height);
|
|
||||||
let account_key = keyed_accounts[0].signer_key().unwrap();
|
|
||||||
let mut num_validations = 0;
|
|
||||||
let mut total_validations = 0;
|
|
||||||
for validation in &storage_account_state.reward_validations[claims_index] {
|
|
||||||
if *account_key == validation.id {
|
|
||||||
num_validations += count_valid_proofs(&validation.proof_mask);
|
|
||||||
} else {
|
|
||||||
total_validations += count_valid_proofs(&validation.proof_mask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total_validations += num_validations;
|
|
||||||
if total_validations > 0 {
|
|
||||||
keyed_accounts[0].account.lamports +=
|
|
||||||
(TOTAL_VALIDATOR_REWARDS * num_validations) / total_validations;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bincode::serialize_into(
|
|
||||||
&mut keyed_accounts[0].account.data[..],
|
|
||||||
&storage_account_state,
|
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return Err(InstructionError::AccountDataTooSmall);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
info!("Invalid instruction data: {:?}", data);
|
|
||||||
Err(InstructionError::InvalidInstructionData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use solana_sdk::account::{create_keyed_accounts, Account};
|
|
||||||
use solana_sdk::hash::Hash;
|
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
|
|
||||||
use solana_sdk::transaction::{CompiledInstruction, Transaction};
|
|
||||||
use solana_storage_api::{ProofStatus, StorageTransaction};
|
|
||||||
|
|
||||||
fn test_transaction(
|
|
||||||
tx: &Transaction,
|
|
||||||
program_accounts: &mut [Account],
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
assert_eq!(tx.instructions.len(), 1);
|
|
||||||
let CompiledInstruction {
|
|
||||||
ref accounts,
|
|
||||||
ref data,
|
|
||||||
..
|
|
||||||
} = tx.instructions[0];
|
|
||||||
|
|
||||||
info!("accounts: {:?}", accounts);
|
|
||||||
|
|
||||||
let mut keyed_accounts: Vec<_> = accounts
|
|
||||||
.iter()
|
|
||||||
.map(|&index| {
|
|
||||||
let index = index as usize;
|
|
||||||
let key = &tx.account_keys[index];
|
|
||||||
(key, index < tx.signatures.len())
|
|
||||||
})
|
|
||||||
.zip(program_accounts.iter_mut())
|
|
||||||
.map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let ret = entrypoint(&id(), &mut keyed_accounts, &data, 42);
|
|
||||||
info!("ret: {:?}", ret);
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_storage_tx() {
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let mut accounts = [(keypair.pubkey(), Account::default())];
|
|
||||||
let mut keyed_accounts = create_keyed_accounts(&mut accounts);
|
|
||||||
assert!(entrypoint(&id(), &mut keyed_accounts, &[], 42).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_serialize_overflow() {
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let mut keyed_accounts = Vec::new();
|
|
||||||
let mut user_account = Account::default();
|
|
||||||
let pubkey = keypair.pubkey();
|
|
||||||
keyed_accounts.push(KeyedAccount::new(&pubkey, true, &mut user_account));
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
ENTRIES_PER_SEGMENT,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
entrypoint(&id(), &mut keyed_accounts, &tx.instructions[0].data, 42),
|
|
||||||
Err(InstructionError::AccountDataTooSmall)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_accounts_len() {
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let mut accounts = [Account::default()];
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_mining_proof(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
0,
|
|
||||||
Signature::default(),
|
|
||||||
);
|
|
||||||
assert!(test_transaction(&tx, &mut accounts).is_err());
|
|
||||||
|
|
||||||
let mut accounts = [Account::default(), Account::default(), Account::default()];
|
|
||||||
|
|
||||||
assert!(test_transaction(&tx, &mut accounts).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_submit_mining_invalid_entry_height() {
|
|
||||||
solana_logger::setup();
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let mut accounts = [Account::default(), Account::default()];
|
|
||||||
accounts[1].data.resize(16 * 1024, 0);
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_mining_proof(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
0,
|
|
||||||
Signature::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Haven't seen a transaction to roll over the epoch, so this should fail
|
|
||||||
assert!(test_transaction(&tx, &mut accounts).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_submit_mining_ok() {
|
|
||||||
solana_logger::setup();
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let mut accounts = [Account::default(), Account::default()];
|
|
||||||
accounts[0].data.resize(16 * 1024, 0);
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
ENTRIES_PER_SEGMENT,
|
|
||||||
);
|
|
||||||
|
|
||||||
test_transaction(&tx, &mut accounts).unwrap();
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_mining_proof(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
0,
|
|
||||||
Signature::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
test_transaction(&tx, &mut accounts).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_validate_mining() {
|
|
||||||
solana_logger::setup();
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let mut accounts = [Account::default(), Account::default()];
|
|
||||||
accounts[0].data.resize(16 * 1024, 0);
|
|
||||||
|
|
||||||
let entry_height = 0;
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
ENTRIES_PER_SEGMENT,
|
|
||||||
);
|
|
||||||
|
|
||||||
test_transaction(&tx, &mut accounts).unwrap();
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_mining_proof(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
entry_height,
|
|
||||||
Signature::default(),
|
|
||||||
);
|
|
||||||
test_transaction(&tx, &mut accounts).unwrap();
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
ENTRIES_PER_SEGMENT * 2,
|
|
||||||
);
|
|
||||||
test_transaction(&tx, &mut accounts).unwrap();
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_proof_validation(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
entry_height,
|
|
||||||
vec![ProofStatus::Valid],
|
|
||||||
);
|
|
||||||
test_transaction(&tx, &mut accounts).unwrap();
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
|
||||||
&keypair,
|
|
||||||
Hash::default(),
|
|
||||||
Hash::default(),
|
|
||||||
ENTRIES_PER_SEGMENT * 3,
|
|
||||||
);
|
|
||||||
test_transaction(&tx, &mut accounts).unwrap();
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_reward_claim(&keypair, Hash::default(), entry_height);
|
|
||||||
test_transaction(&tx, &mut accounts).unwrap();
|
|
||||||
|
|
||||||
assert!(accounts[0].lamports == TOTAL_VALIDATOR_REWARDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
use bincode::deserialize;
|
|
||||||
use log::*;
|
|
||||||
use solana_runtime::bank::Bank;
|
|
||||||
use solana_sdk::genesis_block::GenesisBlock;
|
|
||||||
use solana_sdk::hash::{hash, Hash};
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
|
|
||||||
use solana_sdk::system_transaction::SystemTransaction;
|
|
||||||
use solana_storage_api::{StorageTransaction, ENTRIES_PER_SEGMENT};
|
|
||||||
|
|
||||||
fn get_storage_entry_height(bank: &Bank, account: &Pubkey) -> u64 {
|
|
||||||
match bank.get_account(&account) {
|
|
||||||
Some(storage_system_account) => {
|
|
||||||
let state = deserialize(&storage_system_account.data);
|
|
||||||
if let Ok(state) = state {
|
|
||||||
let state: solana_storage_api::StorageProgramState = state;
|
|
||||||
return state.entry_height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
info!("error in reading entry_height");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_storage_blockhash(bank: &Bank, account: &Pubkey) -> Hash {
|
|
||||||
if let Some(storage_system_account) = bank.get_account(&account) {
|
|
||||||
let state = deserialize(&storage_system_account.data);
|
|
||||||
if let Ok(state) = state {
|
|
||||||
let state: solana_storage_api::StorageProgramState = state;
|
|
||||||
return state.hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Hash::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bank_storage() {
|
|
||||||
let (mut genesis_block, alice) = GenesisBlock::new(1000);
|
|
||||||
genesis_block.native_programs.push((
|
|
||||||
"solana_storage_program".to_string(),
|
|
||||||
solana_storage_api::id(),
|
|
||||||
));
|
|
||||||
let bank = Bank::new(&genesis_block);
|
|
||||||
|
|
||||||
let bob = Keypair::new();
|
|
||||||
let jack = Keypair::new();
|
|
||||||
let jill = Keypair::new();
|
|
||||||
|
|
||||||
let x = 42;
|
|
||||||
let blockhash = genesis_block.hash();
|
|
||||||
let x2 = x * 2;
|
|
||||||
let storage_blockhash = hash(&[x2]);
|
|
||||||
|
|
||||||
bank.register_tick(&blockhash);
|
|
||||||
|
|
||||||
bank.transfer(10, &alice, &jill.pubkey(), blockhash)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
bank.transfer(10, &alice, &bob.pubkey(), blockhash).unwrap();
|
|
||||||
bank.transfer(10, &alice, &jack.pubkey(), blockhash)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let tx = SystemTransaction::new_program_account(
|
|
||||||
&alice,
|
|
||||||
&bob.pubkey(),
|
|
||||||
blockhash,
|
|
||||||
1,
|
|
||||||
4 * 1024,
|
|
||||||
&solana_storage_api::id(),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
bank.process_transaction(&tx).unwrap();
|
|
||||||
|
|
||||||
let tx = StorageTransaction::new_advertise_recent_blockhash(
|
|
||||||
&bob,
|
|
||||||
storage_blockhash,
|
|
||||||
blockhash,
|
|
||||||
ENTRIES_PER_SEGMENT,
|
|
||||||
);
|
|
||||||
|
|
||||||
bank.process_transaction(&tx).unwrap();
|
|
||||||
|
|
||||||
let entry_height = 0;
|
|
||||||
let tx = StorageTransaction::new_mining_proof(
|
|
||||||
&bob,
|
|
||||||
Hash::default(),
|
|
||||||
blockhash,
|
|
||||||
entry_height,
|
|
||||||
Signature::default(),
|
|
||||||
);
|
|
||||||
let _result = bank.process_transaction(&tx).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
get_storage_entry_height(&bank, &bob.pubkey()),
|
|
||||||
ENTRIES_PER_SEGMENT
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
get_storage_blockhash(&bank, &bob.pubkey()),
|
|
||||||
storage_blockhash
|
|
||||||
);
|
|
||||||
}
|
|
@ -10,8 +10,10 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.1.2"
|
bincode = "1.1.2"
|
||||||
|
log = "0.4.2"
|
||||||
serde = "1.0.89"
|
serde = "1.0.89"
|
||||||
serde_derive = "1.0.89"
|
serde_derive = "1.0.89"
|
||||||
|
solana-logger = { path = "../../logger", version = "0.13.0" }
|
||||||
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
pub mod token_processor;
|
||||||
|
mod token_state;
|
||||||
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
const TOKEN_PROGRAM_ID: [u8; 32] = [
|
const TOKEN_PROGRAM_ID: [u8; 32] = [
|
||||||
|
20
programs/token_api/src/token_processor.rs
Normal file
20
programs/token_api/src/token_processor.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use crate::token_state::TokenState;
|
||||||
|
use bincode::serialize;
|
||||||
|
use log::*;
|
||||||
|
use solana_sdk::account::KeyedAccount;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::transaction::InstructionError;
|
||||||
|
|
||||||
|
pub fn process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
info: &mut [KeyedAccount],
|
||||||
|
input: &[u8],
|
||||||
|
_tick_height: u64,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
solana_logger::setup();
|
||||||
|
|
||||||
|
TokenState::process(program_id, info, input).map_err(|e| {
|
||||||
|
error!("error: {:?}", e);
|
||||||
|
InstructionError::CustomError(serialize(&e).unwrap())
|
||||||
|
})
|
||||||
|
}
|
@ -92,54 +92,54 @@ enum Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum TokenProgram {
|
pub enum TokenState {
|
||||||
Unallocated,
|
Unallocated,
|
||||||
Token(TokenInfo),
|
Token(TokenInfo),
|
||||||
Account(TokenAccountInfo),
|
Account(TokenAccountInfo),
|
||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
impl Default for TokenProgram {
|
impl Default for TokenState {
|
||||||
fn default() -> TokenProgram {
|
fn default() -> TokenState {
|
||||||
TokenProgram::Unallocated
|
TokenState::Unallocated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenProgram {
|
impl TokenState {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn map_to_invalid_args(err: std::boxed::Box<bincode::ErrorKind>) -> Error {
|
fn map_to_invalid_args(err: std::boxed::Box<bincode::ErrorKind>) -> Error {
|
||||||
warn!("invalid argument: {:?}", err);
|
warn!("invalid argument: {:?}", err);
|
||||||
Error::InvalidArgument
|
Error::InvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize(input: &[u8]) -> Result<TokenProgram> {
|
pub fn deserialize(input: &[u8]) -> Result<TokenState> {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
match input[0] {
|
match input[0] {
|
||||||
0 => Ok(TokenProgram::Unallocated),
|
0 => Ok(TokenState::Unallocated),
|
||||||
1 => Ok(TokenProgram::Token(
|
1 => Ok(TokenState::Token(
|
||||||
bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?,
|
bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?,
|
||||||
)),
|
)),
|
||||||
2 => Ok(TokenProgram::Account(
|
2 => Ok(TokenState::Account(
|
||||||
bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?,
|
bincode::deserialize(&input[1..]).map_err(Self::map_to_invalid_args)?,
|
||||||
)),
|
)),
|
||||||
_ => Err(Error::InvalidArgument),
|
_ => Err(Error::InvalidArgument),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize(self: &TokenProgram, output: &mut [u8]) -> Result<()> {
|
fn serialize(self: &TokenState, output: &mut [u8]) -> Result<()> {
|
||||||
if output.is_empty() {
|
if output.is_empty() {
|
||||||
warn!("serialize fail: ouput.len is 0");
|
warn!("serialize fail: ouput.len is 0");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
match self {
|
match self {
|
||||||
TokenProgram::Unallocated | TokenProgram::Invalid => Err(Error::InvalidArgument),
|
TokenState::Unallocated | TokenState::Invalid => Err(Error::InvalidArgument),
|
||||||
TokenProgram::Token(token_info) => {
|
TokenState::Token(token_info) => {
|
||||||
output[0] = 1;
|
output[0] = 1;
|
||||||
let writer = std::io::BufWriter::new(&mut output[1..]);
|
let writer = std::io::BufWriter::new(&mut output[1..]);
|
||||||
bincode::serialize_into(writer, &token_info).map_err(Self::map_to_invalid_args)
|
bincode::serialize_into(writer, &token_info).map_err(Self::map_to_invalid_args)
|
||||||
}
|
}
|
||||||
TokenProgram::Account(account_info) => {
|
TokenState::Account(account_info) => {
|
||||||
output[0] = 2;
|
output[0] = 2;
|
||||||
let writer = std::io::BufWriter::new(&mut output[1..]);
|
let writer = std::io::BufWriter::new(&mut output[1..]);
|
||||||
bincode::serialize_into(writer, &account_info).map_err(Self::map_to_invalid_args)
|
bincode::serialize_into(writer, &account_info).map_err(Self::map_to_invalid_args)
|
||||||
@ -149,7 +149,7 @@ impl TokenProgram {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn amount(&self) -> Result<u64> {
|
pub fn amount(&self) -> Result<u64> {
|
||||||
if let TokenProgram::Account(account_info) = self {
|
if let TokenState::Account(account_info) = self {
|
||||||
Ok(account_info.amount)
|
Ok(account_info.amount)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidArgument)
|
Err(Error::InvalidArgument)
|
||||||
@ -159,28 +159,28 @@ impl TokenProgram {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn only_owner(&self, key: &Pubkey) -> Result<()> {
|
pub fn only_owner(&self, key: &Pubkey) -> Result<()> {
|
||||||
if *key != Pubkey::default() {
|
if *key != Pubkey::default() {
|
||||||
if let TokenProgram::Account(account_info) = self {
|
if let TokenState::Account(account_info) = self {
|
||||||
if account_info.owner == *key {
|
if account_info.owner == *key {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
warn!("TokenProgram: non-owner rejected");
|
warn!("TokenState: non-owner rejected");
|
||||||
Err(Error::NotOwner)
|
Err(Error::NotOwner)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_command_newtoken(
|
pub fn process_command_newtoken(
|
||||||
info: &mut [KeyedAccount],
|
info: &mut [KeyedAccount],
|
||||||
token_info: TokenInfo,
|
token_info: TokenInfo,
|
||||||
input_program_accounts: &[TokenProgram],
|
input_program_accounts: &[TokenState],
|
||||||
output_program_accounts: &mut Vec<(usize, TokenProgram)>,
|
output_program_accounts: &mut Vec<(usize, TokenState)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if input_program_accounts.len() != 2 {
|
if input_program_accounts.len() != 2 {
|
||||||
error!("Expected 2 accounts");
|
error!("Expected 2 accounts");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let TokenProgram::Account(dest_account) = &input_program_accounts[1] {
|
if let TokenState::Account(dest_account) = &input_program_accounts[1] {
|
||||||
if info[0].signer_key().unwrap() != &dest_account.token {
|
if info[0].signer_key().unwrap() != &dest_account.token {
|
||||||
error!("account 1 token mismatch");
|
error!("account 1 token mismatch");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
@ -193,24 +193,24 @@ impl TokenProgram {
|
|||||||
|
|
||||||
let mut output_dest_account = dest_account.clone();
|
let mut output_dest_account = dest_account.clone();
|
||||||
output_dest_account.amount = token_info.supply;
|
output_dest_account.amount = token_info.supply;
|
||||||
output_program_accounts.push((1, TokenProgram::Account(output_dest_account)));
|
output_program_accounts.push((1, TokenState::Account(output_dest_account)));
|
||||||
} else {
|
} else {
|
||||||
error!("account 1 invalid");
|
error!("account 1 invalid");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if input_program_accounts[0] != TokenProgram::Unallocated {
|
if input_program_accounts[0] != TokenState::Unallocated {
|
||||||
error!("account 0 not available");
|
error!("account 0 not available");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
output_program_accounts.push((0, TokenProgram::Token(token_info)));
|
output_program_accounts.push((0, TokenState::Token(token_info)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_command_newaccount(
|
pub fn process_command_newaccount(
|
||||||
info: &mut [KeyedAccount],
|
info: &mut [KeyedAccount],
|
||||||
input_program_accounts: &[TokenProgram],
|
input_program_accounts: &[TokenState],
|
||||||
output_program_accounts: &mut Vec<(usize, TokenProgram)>,
|
output_program_accounts: &mut Vec<(usize, TokenState)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// key 0 - Destination new token account
|
// key 0 - Destination new token account
|
||||||
// key 1 - Owner of the account
|
// key 1 - Owner of the account
|
||||||
@ -220,7 +220,7 @@ impl TokenProgram {
|
|||||||
error!("Expected 3 accounts");
|
error!("Expected 3 accounts");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
if input_program_accounts[0] != TokenProgram::Unallocated {
|
if input_program_accounts[0] != TokenState::Unallocated {
|
||||||
error!("account 0 is already allocated");
|
error!("account 0 is already allocated");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
@ -236,22 +236,22 @@ impl TokenProgram {
|
|||||||
original_amount: 0,
|
original_amount: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
output_program_accounts.push((0, TokenProgram::Account(token_account_info)));
|
output_program_accounts.push((0, TokenState::Account(token_account_info)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_command_transfer(
|
pub fn process_command_transfer(
|
||||||
info: &mut [KeyedAccount],
|
info: &mut [KeyedAccount],
|
||||||
amount: u64,
|
amount: u64,
|
||||||
input_program_accounts: &[TokenProgram],
|
input_program_accounts: &[TokenState],
|
||||||
output_program_accounts: &mut Vec<(usize, TokenProgram)>,
|
output_program_accounts: &mut Vec<(usize, TokenState)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if input_program_accounts.len() < 3 {
|
if input_program_accounts.len() < 3 {
|
||||||
error!("Expected 3 accounts");
|
error!("Expected 3 accounts");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (TokenProgram::Account(source_account), TokenProgram::Account(dest_account)) =
|
if let (TokenState::Account(source_account), TokenState::Account(dest_account)) =
|
||||||
(&input_program_accounts[1], &input_program_accounts[2])
|
(&input_program_accounts[1], &input_program_accounts[2])
|
||||||
{
|
{
|
||||||
if source_account.token != dest_account.token {
|
if source_account.token != dest_account.token {
|
||||||
@ -275,7 +275,7 @@ impl TokenProgram {
|
|||||||
|
|
||||||
let mut output_source_account = source_account.clone();
|
let mut output_source_account = source_account.clone();
|
||||||
output_source_account.amount -= amount;
|
output_source_account.amount -= amount;
|
||||||
output_program_accounts.push((1, TokenProgram::Account(output_source_account)));
|
output_program_accounts.push((1, TokenState::Account(output_source_account)));
|
||||||
|
|
||||||
if let Some(ref delegate_info) = source_account.delegate {
|
if let Some(ref delegate_info) = source_account.delegate {
|
||||||
if input_program_accounts.len() != 4 {
|
if input_program_accounts.len() != 4 {
|
||||||
@ -284,7 +284,7 @@ impl TokenProgram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let delegate_account = source_account;
|
let delegate_account = source_account;
|
||||||
if let TokenProgram::Account(source_account) = &input_program_accounts[3] {
|
if let TokenState::Account(source_account) = &input_program_accounts[3] {
|
||||||
if source_account.token != delegate_account.token {
|
if source_account.token != delegate_account.token {
|
||||||
error!("account 1/3 token mismatch");
|
error!("account 1/3 token mismatch");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
@ -300,7 +300,7 @@ impl TokenProgram {
|
|||||||
|
|
||||||
let mut output_source_account = source_account.clone();
|
let mut output_source_account = source_account.clone();
|
||||||
output_source_account.amount -= amount;
|
output_source_account.amount -= amount;
|
||||||
output_program_accounts.push((3, TokenProgram::Account(output_source_account)));
|
output_program_accounts.push((3, TokenState::Account(output_source_account)));
|
||||||
} else {
|
} else {
|
||||||
error!("account 3 is an invalid account");
|
error!("account 3 is an invalid account");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
@ -309,7 +309,7 @@ impl TokenProgram {
|
|||||||
|
|
||||||
let mut output_dest_account = dest_account.clone();
|
let mut output_dest_account = dest_account.clone();
|
||||||
output_dest_account.amount += amount;
|
output_dest_account.amount += amount;
|
||||||
output_program_accounts.push((2, TokenProgram::Account(output_dest_account)));
|
output_program_accounts.push((2, TokenState::Account(output_dest_account)));
|
||||||
} else {
|
} else {
|
||||||
error!("account 1 and/or 2 are invalid accounts");
|
error!("account 1 and/or 2 are invalid accounts");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
@ -320,15 +320,15 @@ impl TokenProgram {
|
|||||||
pub fn process_command_approve(
|
pub fn process_command_approve(
|
||||||
info: &mut [KeyedAccount],
|
info: &mut [KeyedAccount],
|
||||||
amount: u64,
|
amount: u64,
|
||||||
input_program_accounts: &[TokenProgram],
|
input_program_accounts: &[TokenState],
|
||||||
output_program_accounts: &mut Vec<(usize, TokenProgram)>,
|
output_program_accounts: &mut Vec<(usize, TokenState)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if input_program_accounts.len() != 3 {
|
if input_program_accounts.len() != 3 {
|
||||||
error!("Expected 3 accounts");
|
error!("Expected 3 accounts");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (TokenProgram::Account(source_account), TokenProgram::Account(delegate_account)) =
|
if let (TokenState::Account(source_account), TokenState::Account(delegate_account)) =
|
||||||
(&input_program_accounts[1], &input_program_accounts[2])
|
(&input_program_accounts[1], &input_program_accounts[2])
|
||||||
{
|
{
|
||||||
if source_account.token != delegate_account.token {
|
if source_account.token != delegate_account.token {
|
||||||
@ -363,8 +363,7 @@ impl TokenProgram {
|
|||||||
source: delegate_info.source,
|
source: delegate_info.source,
|
||||||
original_amount: amount,
|
original_amount: amount,
|
||||||
});
|
});
|
||||||
output_program_accounts
|
output_program_accounts.push((2, TokenState::Account(output_delegate_account)));
|
||||||
.push((2, TokenProgram::Account(output_delegate_account)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -376,15 +375,15 @@ impl TokenProgram {
|
|||||||
|
|
||||||
pub fn process_command_setowner(
|
pub fn process_command_setowner(
|
||||||
info: &mut [KeyedAccount],
|
info: &mut [KeyedAccount],
|
||||||
input_program_accounts: &[TokenProgram],
|
input_program_accounts: &[TokenState],
|
||||||
output_program_accounts: &mut Vec<(usize, TokenProgram)>,
|
output_program_accounts: &mut Vec<(usize, TokenState)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if input_program_accounts.len() < 3 {
|
if input_program_accounts.len() < 3 {
|
||||||
error!("Expected 3 accounts");
|
error!("Expected 3 accounts");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let TokenProgram::Account(source_account) = &input_program_accounts[1] {
|
if let TokenState::Account(source_account) = &input_program_accounts[1] {
|
||||||
if info[0].signer_key().unwrap() != &source_account.owner {
|
if info[0].signer_key().unwrap() != &source_account.owner {
|
||||||
info!("owner of account 1 not present");
|
info!("owner of account 1 not present");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
@ -392,7 +391,7 @@ impl TokenProgram {
|
|||||||
|
|
||||||
let mut output_source_account = source_account.clone();
|
let mut output_source_account = source_account.clone();
|
||||||
output_source_account.owner = *info[2].unsigned_key();
|
output_source_account.owner = *info[2].unsigned_key();
|
||||||
output_program_accounts.push((1, TokenProgram::Account(output_source_account)));
|
output_program_accounts.push((1, TokenState::Account(output_source_account)));
|
||||||
} else {
|
} else {
|
||||||
info!("account 1 is invalid");
|
info!("account 1 is invalid");
|
||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
@ -408,7 +407,7 @@ impl TokenProgram {
|
|||||||
Err(Error::InvalidArgument)?;
|
Err(Error::InvalidArgument)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let input_program_accounts: Vec<TokenProgram> = info
|
let input_program_accounts: Vec<TokenState> = info
|
||||||
.iter()
|
.iter()
|
||||||
.map(|keyed_account| {
|
.map(|keyed_account| {
|
||||||
let account = &keyed_account.account;
|
let account = &keyed_account.account;
|
||||||
@ -417,11 +416,11 @@ impl TokenProgram {
|
|||||||
Ok(token_program) => token_program,
|
Ok(token_program) => token_program,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("deserialize failed: {:?}", err);
|
error!("deserialize failed: {:?}", err);
|
||||||
TokenProgram::Invalid
|
TokenState::Invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TokenProgram::Invalid
|
TokenState::Invalid
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -482,47 +481,47 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
pub fn serde() {
|
pub fn serde() {
|
||||||
assert_eq!(TokenProgram::deserialize(&[0]), Ok(TokenProgram::default()));
|
assert_eq!(TokenState::deserialize(&[0]), Ok(TokenState::default()));
|
||||||
|
|
||||||
let mut data = vec![0; 256];
|
let mut data = vec![0; 256];
|
||||||
|
|
||||||
let account = TokenProgram::Account(TokenAccountInfo {
|
let account = TokenState::Account(TokenAccountInfo {
|
||||||
token: Pubkey::new(&[1; 32]),
|
token: Pubkey::new(&[1; 32]),
|
||||||
owner: Pubkey::new(&[2; 32]),
|
owner: Pubkey::new(&[2; 32]),
|
||||||
amount: 123,
|
amount: 123,
|
||||||
delegate: None,
|
delegate: None,
|
||||||
});
|
});
|
||||||
account.serialize(&mut data).unwrap();
|
account.serialize(&mut data).unwrap();
|
||||||
assert_eq!(TokenProgram::deserialize(&data), Ok(account));
|
assert_eq!(TokenState::deserialize(&data), Ok(account));
|
||||||
|
|
||||||
let account = TokenProgram::Token(TokenInfo {
|
let account = TokenState::Token(TokenInfo {
|
||||||
supply: 12345,
|
supply: 12345,
|
||||||
decimals: 2,
|
decimals: 2,
|
||||||
name: "A test token".to_string(),
|
name: "A test token".to_string(),
|
||||||
symbol: "TEST".to_string(),
|
symbol: "TEST".to_string(),
|
||||||
});
|
});
|
||||||
account.serialize(&mut data).unwrap();
|
account.serialize(&mut data).unwrap();
|
||||||
assert_eq!(TokenProgram::deserialize(&data), Ok(account));
|
assert_eq!(TokenState::deserialize(&data), Ok(account));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn serde_expect_fail() {
|
pub fn serde_expect_fail() {
|
||||||
let mut data = vec![0; 256];
|
let mut data = vec![0; 256];
|
||||||
|
|
||||||
// Certain TokenProgram's may not be serialized
|
// Certain TokenState's may not be serialized
|
||||||
let account = TokenProgram::default();
|
let account = TokenState::default();
|
||||||
assert_eq!(account, TokenProgram::Unallocated);
|
assert_eq!(account, TokenState::Unallocated);
|
||||||
assert!(account.serialize(&mut data).is_err());
|
assert!(account.serialize(&mut data).is_err());
|
||||||
assert!(account.serialize(&mut data).is_err());
|
assert!(account.serialize(&mut data).is_err());
|
||||||
let account = TokenProgram::Invalid;
|
let account = TokenState::Invalid;
|
||||||
assert!(account.serialize(&mut data).is_err());
|
assert!(account.serialize(&mut data).is_err());
|
||||||
|
|
||||||
// Bad deserialize data
|
// Bad deserialize data
|
||||||
assert!(TokenProgram::deserialize(&[]).is_err());
|
assert!(TokenState::deserialize(&[]).is_err());
|
||||||
assert!(TokenProgram::deserialize(&[1]).is_err());
|
assert!(TokenState::deserialize(&[1]).is_err());
|
||||||
assert!(TokenProgram::deserialize(&[1, 2]).is_err());
|
assert!(TokenState::deserialize(&[1, 2]).is_err());
|
||||||
assert!(TokenProgram::deserialize(&[2, 2]).is_err());
|
assert!(TokenState::deserialize(&[2, 2]).is_err());
|
||||||
assert!(TokenProgram::deserialize(&[3]).is_err());
|
assert!(TokenState::deserialize(&[3]).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: business logic tests are located in the @solana/web3.js test suite
|
// Note: business logic tests are located in the @solana/web3.js test suite
|
@ -9,12 +9,10 @@ homepage = "https://solana.com/"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.1.2"
|
|
||||||
log = "0.4.2"
|
log = "0.4.2"
|
||||||
serde = "1.0.89"
|
|
||||||
serde_derive = "1.0.89"
|
|
||||||
solana-logger = { path = "../../logger", version = "0.13.0" }
|
solana-logger = { path = "../../logger", version = "0.13.0" }
|
||||||
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
||||||
|
solana-token-api = { path = "../token_api", version = "0.13.0" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "solana_token_program"
|
name = "solana_token_program"
|
||||||
|
@ -1,23 +1,3 @@
|
|||||||
use bincode::serialize;
|
use solana_token_api::token_processor::process_instruction;
|
||||||
use log::*;
|
|
||||||
use solana_sdk::account::KeyedAccount;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::solana_entrypoint;
|
|
||||||
use solana_sdk::transaction::InstructionError;
|
|
||||||
|
|
||||||
mod token_program;
|
solana_sdk::process_instruction_entrypoint!(process_instruction);
|
||||||
|
|
||||||
solana_entrypoint!(entrypoint);
|
|
||||||
fn entrypoint(
|
|
||||||
program_id: &Pubkey,
|
|
||||||
info: &mut [KeyedAccount],
|
|
||||||
input: &[u8],
|
|
||||||
_tick_height: u64,
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
solana_logger::setup();
|
|
||||||
|
|
||||||
token_program::TokenProgram::process(program_id, info, input).map_err(|e| {
|
|
||||||
error!("error: {:?}", e);
|
|
||||||
InstructionError::CustomError(serialize(&e).unwrap())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -13,8 +13,13 @@ bincode = "1.1.2"
|
|||||||
log = "0.4.2"
|
log = "0.4.2"
|
||||||
serde = "1.0.89"
|
serde = "1.0.89"
|
||||||
serde_derive = "1.0.89"
|
serde_derive = "1.0.89"
|
||||||
|
solana-logger = { path = "../../logger", version = "0.13.0" }
|
||||||
|
solana-metrics = { path = "../../metrics", version = "0.13.0" }
|
||||||
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
solana-runtime = { path = "../../runtime", version = "0.13.0" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "solana_vote_api"
|
name = "solana_vote_api"
|
||||||
crate-type = ["lib"]
|
crate-type = ["lib"]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub mod vote_instruction;
|
pub mod vote_instruction;
|
||||||
|
pub mod vote_processor;
|
||||||
pub mod vote_state;
|
pub mod vote_state;
|
||||||
pub mod vote_transaction;
|
pub mod vote_transaction;
|
||||||
|
|
||||||
|
192
programs/vote_api/src/vote_processor.rs
Normal file
192
programs/vote_api/src/vote_processor.rs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
//! Vote program
|
||||||
|
//! Receive and processes votes from validators
|
||||||
|
|
||||||
|
use crate::vote_instruction::VoteInstruction;
|
||||||
|
use crate::vote_state;
|
||||||
|
use bincode::deserialize;
|
||||||
|
use log::*;
|
||||||
|
use solana_sdk::account::KeyedAccount;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::transaction::InstructionError;
|
||||||
|
|
||||||
|
pub fn process_instruction(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
keyed_accounts: &mut [KeyedAccount],
|
||||||
|
data: &[u8],
|
||||||
|
_tick_height: u64,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
solana_logger::setup();
|
||||||
|
|
||||||
|
trace!("process_instruction: {:?}", data);
|
||||||
|
trace!("keyed_accounts: {:?}", keyed_accounts);
|
||||||
|
|
||||||
|
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
|
||||||
|
VoteInstruction::InitializeAccount => vote_state::initialize_account(keyed_accounts),
|
||||||
|
VoteInstruction::DelegateStake(delegate_id) => {
|
||||||
|
vote_state::delegate_stake(keyed_accounts, &delegate_id)
|
||||||
|
}
|
||||||
|
VoteInstruction::AuthorizeVoter(voter_id) => {
|
||||||
|
vote_state::authorize_voter(keyed_accounts, &voter_id)
|
||||||
|
}
|
||||||
|
VoteInstruction::Vote(vote) => {
|
||||||
|
debug!("{:?} by {}", vote, keyed_accounts[0].signer_key().unwrap());
|
||||||
|
solana_metrics::submit(
|
||||||
|
solana_metrics::influxdb::Point::new("vote-native")
|
||||||
|
.add_field("count", solana_metrics::influxdb::Value::Integer(1))
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
vote_state::process_vote(keyed_accounts, vote)
|
||||||
|
}
|
||||||
|
VoteInstruction::ClearCredits => vote_state::clear_credits(keyed_accounts),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::id;
|
||||||
|
use crate::vote_instruction::{Vote, VoteInstruction};
|
||||||
|
use crate::vote_state::VoteState;
|
||||||
|
use crate::vote_transaction::VoteTransaction;
|
||||||
|
use solana_runtime::bank::{Bank, Result};
|
||||||
|
use solana_sdk::genesis_block::GenesisBlock;
|
||||||
|
use solana_sdk::hash::hash;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
use solana_sdk::system_instruction::SystemInstruction;
|
||||||
|
use solana_sdk::transaction::{
|
||||||
|
AccountMeta, Instruction, InstructionError, Transaction, TransactionError,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn create_bank(lamports: u64) -> (Bank, Keypair) {
|
||||||
|
let (genesis_block, mint_keypair) = GenesisBlock::new(lamports);
|
||||||
|
let mut bank = Bank::new(&genesis_block);
|
||||||
|
bank.add_instruction_processor(id(), process_instruction);
|
||||||
|
(bank, mint_keypair)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VoteBank<'a> {
|
||||||
|
bank: &'a Bank,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> VoteBank<'a> {
|
||||||
|
fn new(bank: &'a Bank) -> Self {
|
||||||
|
Self { bank }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_vote_account(
|
||||||
|
&self,
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
vote_id: &Pubkey,
|
||||||
|
lamports: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let blockhash = self.bank.last_blockhash();
|
||||||
|
let tx = VoteTransaction::new_account(from_keypair, vote_id, blockhash, lamports, 0);
|
||||||
|
self.bank.process_transaction(&tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_vote_account_with_delegate(
|
||||||
|
&self,
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
vote_keypair: &Keypair,
|
||||||
|
delegate_id: &Pubkey,
|
||||||
|
lamports: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let blockhash = self.bank.last_blockhash();
|
||||||
|
let tx = VoteTransaction::new_account_with_delegate(
|
||||||
|
from_keypair,
|
||||||
|
vote_keypair,
|
||||||
|
delegate_id,
|
||||||
|
blockhash,
|
||||||
|
lamports,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
self.bank.process_transaction(&tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_vote(
|
||||||
|
&self,
|
||||||
|
staking_account: &Pubkey,
|
||||||
|
vote_keypair: &Keypair,
|
||||||
|
tick_height: u64,
|
||||||
|
) -> Result<VoteState> {
|
||||||
|
let blockhash = self.bank.last_blockhash();
|
||||||
|
let tx =
|
||||||
|
VoteTransaction::new_vote(staking_account, vote_keypair, tick_height, blockhash, 0);
|
||||||
|
self.bank.process_transaction(&tx)?;
|
||||||
|
self.bank.register_tick(&hash(blockhash.as_ref()));
|
||||||
|
|
||||||
|
let vote_account = self.bank.get_account(&vote_keypair.pubkey()).unwrap();
|
||||||
|
Ok(VoteState::deserialize(&vote_account.data).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vote_bank_basic() {
|
||||||
|
let (bank, from_keypair) = create_bank(10_000);
|
||||||
|
let vote_bank = VoteBank::new(&bank);
|
||||||
|
|
||||||
|
let vote_keypair = Keypair::new();
|
||||||
|
let vote_id = vote_keypair.pubkey();
|
||||||
|
vote_bank
|
||||||
|
.create_vote_account(&from_keypair, &vote_id, 100)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let vote_state = vote_bank.submit_vote(&vote_id, &vote_keypair, 0).unwrap();
|
||||||
|
assert_eq!(vote_state.votes.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vote_bank_delegate() {
|
||||||
|
let (bank, from_keypair) = create_bank(10_000);
|
||||||
|
let vote_bank = VoteBank::new(&bank);
|
||||||
|
let vote_keypair = Keypair::new();
|
||||||
|
let delegate_keypair = Keypair::new();
|
||||||
|
let delegate_id = delegate_keypair.pubkey();
|
||||||
|
vote_bank
|
||||||
|
.create_vote_account_with_delegate(&from_keypair, &vote_keypair, &delegate_id, 100)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vote_via_bank_with_no_signature() {
|
||||||
|
let (bank, mallory_keypair) = create_bank(10_000);
|
||||||
|
let vote_bank = VoteBank::new(&bank);
|
||||||
|
|
||||||
|
let vote_keypair = Keypair::new();
|
||||||
|
let vote_id = vote_keypair.pubkey();
|
||||||
|
vote_bank
|
||||||
|
.create_vote_account(&mallory_keypair, &vote_id, 100)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mallory_id = mallory_keypair.pubkey();
|
||||||
|
let blockhash = bank.last_blockhash();
|
||||||
|
let vote_ix = Instruction::new(
|
||||||
|
id(),
|
||||||
|
&VoteInstruction::Vote(Vote::new(0)),
|
||||||
|
vec![AccountMeta::new(vote_id, false)], // <--- attack!! No signer required.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sneak in an instruction so that the transaction is signed but
|
||||||
|
// the 0th account in the second instruction is not! The program
|
||||||
|
// needs to check that it's signed.
|
||||||
|
let move_ix = SystemInstruction::new_move(&mallory_id, &vote_id, 1);
|
||||||
|
let mut tx = Transaction::new(vec![move_ix, vote_ix]);
|
||||||
|
tx.sign(&[&mallory_keypair], blockhash);
|
||||||
|
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
|
||||||
|
// And ensure there's no vote.
|
||||||
|
let vote_account = bank.get_account(&vote_id).unwrap();
|
||||||
|
let vote_state = VoteState::deserialize(&vote_account.data).unwrap();
|
||||||
|
assert_eq!(vote_state.votes.len(), 0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Err(TransactionError::InstructionError(
|
||||||
|
1,
|
||||||
|
InstructionError::InvalidArgument
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -9,16 +9,11 @@ homepage = "https://solana.com/"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.1.2"
|
|
||||||
log = "0.4.2"
|
log = "0.4.2"
|
||||||
solana-logger = { path = "../../logger", version = "0.13.0" }
|
solana-logger = { path = "../../logger", version = "0.13.0" }
|
||||||
solana-metrics = { path = "../../metrics", version = "0.13.0" }
|
|
||||||
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
||||||
solana-vote-api = { path = "../vote_api", version = "0.13.0" }
|
solana-vote-api = { path = "../vote_api", version = "0.13.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
solana-runtime = { path = "../../runtime", version = "0.13.0" }
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "solana_vote_program"
|
name = "solana_vote_program"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
@ -1,44 +1,3 @@
|
|||||||
//! Vote program
|
use solana_vote_api::vote_processor::process_instruction;
|
||||||
//! Receive and processes votes from validators
|
|
||||||
|
|
||||||
use bincode::deserialize;
|
solana_sdk::process_instruction_entrypoint!(process_instruction);
|
||||||
use log::*;
|
|
||||||
use solana_sdk::account::KeyedAccount;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::solana_entrypoint;
|
|
||||||
use solana_sdk::transaction::InstructionError;
|
|
||||||
use solana_vote_api::vote_instruction::VoteInstruction;
|
|
||||||
use solana_vote_api::vote_state;
|
|
||||||
|
|
||||||
solana_entrypoint!(entrypoint);
|
|
||||||
fn entrypoint(
|
|
||||||
_program_id: &Pubkey,
|
|
||||||
keyed_accounts: &mut [KeyedAccount],
|
|
||||||
data: &[u8],
|
|
||||||
_tick_height: u64,
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
solana_logger::setup();
|
|
||||||
|
|
||||||
trace!("process_instruction: {:?}", data);
|
|
||||||
trace!("keyed_accounts: {:?}", keyed_accounts);
|
|
||||||
|
|
||||||
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
|
|
||||||
VoteInstruction::InitializeAccount => vote_state::initialize_account(keyed_accounts),
|
|
||||||
VoteInstruction::DelegateStake(delegate_id) => {
|
|
||||||
vote_state::delegate_stake(keyed_accounts, &delegate_id)
|
|
||||||
}
|
|
||||||
VoteInstruction::AuthorizeVoter(voter_id) => {
|
|
||||||
vote_state::authorize_voter(keyed_accounts, &voter_id)
|
|
||||||
}
|
|
||||||
VoteInstruction::Vote(vote) => {
|
|
||||||
debug!("{:?} by {}", vote, keyed_accounts[0].signer_key().unwrap());
|
|
||||||
solana_metrics::submit(
|
|
||||||
solana_metrics::influxdb::Point::new("vote-native")
|
|
||||||
.add_field("count", solana_metrics::influxdb::Value::Integer(1))
|
|
||||||
.to_owned(),
|
|
||||||
);
|
|
||||||
vote_state::process_vote(keyed_accounts, vote)
|
|
||||||
}
|
|
||||||
VoteInstruction::ClearCredits => vote_state::clear_credits(keyed_accounts),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
use solana_runtime::bank::{Bank, Result};
|
|
||||||
use solana_sdk::genesis_block::GenesisBlock;
|
|
||||||
use solana_sdk::hash::hash;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
|
||||||
use solana_sdk::system_instruction::SystemInstruction;
|
|
||||||
use solana_sdk::transaction::{
|
|
||||||
AccountMeta, Instruction, InstructionError, Transaction, TransactionError,
|
|
||||||
};
|
|
||||||
use solana_vote_api::vote_instruction::{Vote, VoteInstruction};
|
|
||||||
use solana_vote_api::vote_state::VoteState;
|
|
||||||
use solana_vote_api::vote_transaction::VoteTransaction;
|
|
||||||
|
|
||||||
struct VoteBank<'a> {
|
|
||||||
bank: &'a Bank,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VoteBank<'a> {
|
|
||||||
fn new(bank: &'a Bank) -> Self {
|
|
||||||
bank.add_native_program("solana_vote_program", &solana_vote_api::id());
|
|
||||||
Self { bank }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_vote_account(
|
|
||||||
&self,
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
vote_id: &Pubkey,
|
|
||||||
lamports: u64,
|
|
||||||
) -> Result<()> {
|
|
||||||
let blockhash = self.bank.last_blockhash();
|
|
||||||
let tx = VoteTransaction::new_account(from_keypair, vote_id, blockhash, lamports, 0);
|
|
||||||
self.bank.process_transaction(&tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_vote_account_with_delegate(
|
|
||||||
&self,
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
vote_keypair: &Keypair,
|
|
||||||
delegate_id: &Pubkey,
|
|
||||||
lamports: u64,
|
|
||||||
) -> Result<()> {
|
|
||||||
let blockhash = self.bank.last_blockhash();
|
|
||||||
let tx = VoteTransaction::new_account_with_delegate(
|
|
||||||
from_keypair,
|
|
||||||
vote_keypair,
|
|
||||||
delegate_id,
|
|
||||||
blockhash,
|
|
||||||
lamports,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
self.bank.process_transaction(&tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_vote(
|
|
||||||
&self,
|
|
||||||
staking_account: &Pubkey,
|
|
||||||
vote_keypair: &Keypair,
|
|
||||||
tick_height: u64,
|
|
||||||
) -> Result<VoteState> {
|
|
||||||
let blockhash = self.bank.last_blockhash();
|
|
||||||
let tx =
|
|
||||||
VoteTransaction::new_vote(staking_account, vote_keypair, tick_height, blockhash, 0);
|
|
||||||
self.bank.process_transaction(&tx)?;
|
|
||||||
self.bank.register_tick(&hash(blockhash.as_ref()));
|
|
||||||
|
|
||||||
let vote_account = self.bank.get_account(&vote_keypair.pubkey()).unwrap();
|
|
||||||
Ok(VoteState::deserialize(&vote_account.data).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vote_bank_basic() {
|
|
||||||
let (genesis_block, from_keypair) = GenesisBlock::new(10_000);
|
|
||||||
let bank = Bank::new(&genesis_block);
|
|
||||||
let vote_bank = VoteBank::new(&bank);
|
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
|
||||||
let vote_id = vote_keypair.pubkey();
|
|
||||||
vote_bank
|
|
||||||
.create_vote_account(&from_keypair, &vote_id, 100)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let vote_state = vote_bank.submit_vote(&vote_id, &vote_keypair, 0).unwrap();
|
|
||||||
assert_eq!(vote_state.votes.len(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vote_bank_delegate() {
|
|
||||||
let (genesis_block, from_keypair) = GenesisBlock::new(10_000);
|
|
||||||
let bank = Bank::new(&genesis_block);
|
|
||||||
let vote_bank = VoteBank::new(&bank);
|
|
||||||
let vote_keypair = Keypair::new();
|
|
||||||
let delegate_keypair = Keypair::new();
|
|
||||||
let delegate_id = delegate_keypair.pubkey();
|
|
||||||
vote_bank
|
|
||||||
.create_vote_account_with_delegate(&from_keypair, &vote_keypair, &delegate_id, 100)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vote_via_bank_with_no_signature() {
|
|
||||||
let (genesis_block, mallory_keypair) = GenesisBlock::new(10_000);
|
|
||||||
let bank = Bank::new(&genesis_block);
|
|
||||||
let vote_bank = VoteBank::new(&bank);
|
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
|
||||||
let vote_id = vote_keypair.pubkey();
|
|
||||||
vote_bank
|
|
||||||
.create_vote_account(&mallory_keypair, &vote_id, 100)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mallory_id = mallory_keypair.pubkey();
|
|
||||||
let blockhash = bank.last_blockhash();
|
|
||||||
let vote_ix = Instruction::new(
|
|
||||||
solana_vote_api::id(),
|
|
||||||
&VoteInstruction::Vote(Vote::new(0)),
|
|
||||||
vec![AccountMeta::new(vote_id, false)], // <--- attack!! No signer required.
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sneak in an instruction so that the transaction is signed but
|
|
||||||
// the 0th account in the second instruction is not! The program
|
|
||||||
// needs to check that it's signed.
|
|
||||||
let move_ix = SystemInstruction::new_move(&mallory_id, &vote_id, 1);
|
|
||||||
let mut tx = Transaction::new(vec![move_ix, vote_ix]);
|
|
||||||
tx.sign(&[&mallory_keypair], blockhash);
|
|
||||||
|
|
||||||
let result = bank.process_transaction(&tx);
|
|
||||||
|
|
||||||
// And ensure there's no vote.
|
|
||||||
let vote_account = bank.get_account(&vote_id).unwrap();
|
|
||||||
let vote_state = VoteState::deserialize(&vote_account.data).unwrap();
|
|
||||||
assert_eq!(vote_state.votes.len(), 0);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
result,
|
|
||||||
Err(TransactionError::InstructionError(
|
|
||||||
1,
|
|
||||||
InstructionError::InvalidArgument
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
Reference in New Issue
Block a user