separates durable nonce and blockhash domains

AdvanceNonceAccount instruction updates nonce to blockhash. This makes it
possible that a durable transaction is executed twice both as a normal
transaction and a nonce transaction if it uses blockhash (as opposed to nonce)
for its recent_blockhash field.

The commit prevents this double execution by separating nonce and blockhash
domains; when advancing nonce account, blockhash is hashed with a fixed string.
As a result a blockhash cannot be a valid nonce value; and if transaction was
once executed as a normal transaction it cannot be re-executed as a durable
transaction again and vice-versa.
This commit is contained in:
behzad nouri 2022-06-03 10:36:27 -04:00
parent 8ca8a61272
commit 5ee157f43d
19 changed files with 221 additions and 118 deletions

View File

@ -20,7 +20,7 @@ pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
)),
State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
authority: data.authority.to_string(),
blockhash: data.blockhash.to_string(),
blockhash: data.blockhash().to_string(),
fee_calculator: data.fee_calculator.into(),
})),
}

View File

@ -333,10 +333,10 @@ pub fn check_nonce_account(
) -> Result<(), CliError> {
match state_from_account(nonce_account)? {
State::Initialized(ref data) => {
if &data.blockhash != nonce_hash {
if &data.blockhash() != nonce_hash {
Err(Error::InvalidHash {
provided: *nonce_hash,
expected: data.blockhash,
expected: data.blockhash(),
}
.into())
} else if nonce_authority != &data.authority {
@ -524,7 +524,7 @@ pub fn process_get_nonce(
.and_then(|ref a| state_from_account(a))?
{
State::Uninitialized => Ok("Nonce account is uninitialized".to_string()),
State::Initialized(ref data) => Ok(format!("{:?}", data.blockhash)),
State::Initialized(ref data) => Ok(format!("{:?}", data.blockhash())),
}
}
@ -598,7 +598,7 @@ pub fn process_show_nonce_account(
..CliNonceAccount::default()
};
if let Some(data) = data {
nonce_account.nonce = Some(data.blockhash.to_string());
nonce_account.nonce = Some(data.blockhash().to_string());
nonce_account.lamports_per_signature = Some(data.fee_calculator.lamports_per_signature);
nonce_account.authority = Some(data.authority.to_string());
}
@ -665,7 +665,11 @@ mod tests {
account::Account,
account_utils::StateMut,
hash::hash,
nonce::{self, state::Versions, State},
nonce::{
self,
state::{DurableNonce, Versions},
State,
},
nonce_account,
signature::{read_keypair_file, write_keypair, Keypair, Signer},
system_program,
@ -925,11 +929,13 @@ mod tests {
#[test]
fn test_check_nonce_account() {
let blockhash = Hash::default();
let durable_nonce =
DurableNonce::from_blockhash(&Hash::default(), /*separate_domains:*/ true);
let blockhash = *durable_nonce.as_hash();
let nonce_pubkey = solana_sdk::pubkey::new_rand();
let data = Versions::new_current(State::Initialized(nonce::state::Data::new(
nonce_pubkey,
blockhash,
durable_nonce,
0,
)));
let valid = Account::new_data(1, &data, &system_program::ID);
@ -949,9 +955,11 @@ mod tests {
assert_eq!(err, Error::InvalidAccountData,);
}
let invalid_durable_nonce =
DurableNonce::from_blockhash(&hash(b"invalid"), /*separate_domains:*/ true);
let data = Versions::new_current(State::Initialized(nonce::state::Data::new(
nonce_pubkey,
hash(b"invalid"),
invalid_durable_nonce,
0,
)));
let invalid_hash = Account::new_data(1, &data, &system_program::ID).unwrap();
@ -962,7 +970,7 @@ mod tests {
err,
Error::InvalidHash {
provided: blockhash,
expected: hash(b"invalid"),
expected: *invalid_durable_nonce.as_hash(),
}
);
}
@ -970,7 +978,7 @@ mod tests {
let new_nonce_authority = solana_sdk::pubkey::new_rand();
let data = Versions::new_current(State::Initialized(nonce::state::Data::new(
new_nonce_authority,
blockhash,
durable_nonce,
0,
)));
let invalid_authority = Account::new_data(1, &data, &system_program::ID);
@ -1019,7 +1027,9 @@ mod tests {
let mut nonce_account = nonce_account::create_account(1).into_inner();
assert_eq!(state_from_account(&nonce_account), Ok(State::Uninitialized));
let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), Hash::new(&[42u8; 32]), 42);
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new(&[42u8; 32]), /*separate_domains:*/ true);
let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), durable_nonce, 42);
nonce_account
.set_state(&Versions::new_current(State::Initialized(data.clone())))
.unwrap();
@ -1048,7 +1058,9 @@ mod tests {
Err(Error::InvalidStateForOperation)
);
let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), Hash::new(&[42u8; 32]), 42);
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new(&[42u8; 32]), /*separate_domains:*/ true);
let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), durable_nonce, 42);
nonce_account
.set_state(&Versions::new_current(State::Initialized(data.clone())))
.unwrap();

View File

@ -325,7 +325,7 @@ fn test_create_account_with_seed() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Test by creating transfer TX with nonce, fully offline
let mut authority_config = CliConfig::recent_for_tests();

View File

@ -539,7 +539,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Delegate stake
config.signers = vec![&config_keypair];
@ -569,7 +569,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Deactivate stake
config.command = CliCommand::DeactivateStake {
@ -838,7 +838,7 @@ fn test_stake_authorize() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Nonced assignment of new online stake authority
let online_authority = Keypair::new();
@ -906,7 +906,7 @@ fn test_stake_authorize() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
assert_ne!(nonce_hash, new_nonce_hash);
}
@ -1188,7 +1188,7 @@ fn test_stake_split() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Nonced offline split
let split_account = keypair_from_seed(&[2u8; 32]).unwrap();
@ -1458,7 +1458,7 @@ fn test_stake_set_lockup() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Nonced offline set lockup
let lockup = LockupArgs {
@ -1584,7 +1584,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Create stake account offline
let stake_keypair = keypair_from_seed(&[4u8; 32]).unwrap();
@ -1645,7 +1645,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Offline, nonced stake-withdraw
let recipient = keypair_from_seed(&[5u8; 32]).unwrap();
@ -1699,7 +1699,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Create another stake account. This time with seed
let seed = "seedy";

View File

@ -200,7 +200,7 @@ fn test_transfer() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Nonced transfer
config.signers = vec![&default_signer];
@ -237,7 +237,7 @@ fn test_transfer() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
assert_ne!(nonce_hash, new_nonce_hash);
// Assign nonce authority to offline
@ -263,7 +263,7 @@ fn test_transfer() {
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
.blockhash();
// Offline, nonced transfer
offline.signers = vec![&default_offline_signer];

View File

@ -37,7 +37,7 @@ impl Source {
#[allow(clippy::redundant_closure)]
let data = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
.and_then(|ref a| nonce_utils::data_from_account(a))?;
Ok((data.blockhash, data.fee_calculator))
Ok((data.blockhash(), data.fee_calculator))
}
}
}
@ -64,7 +64,7 @@ impl Source {
let res = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)?;
let res = nonce_utils::data_from_account(&res)?;
Ok(Some(res)
.filter(|d| d.blockhash == *blockhash)
.filter(|d| d.blockhash() == *blockhash)
.map(|d| d.fee_calculator))
}
}
@ -84,7 +84,7 @@ impl Source {
#[allow(clippy::redundant_closure)]
let data = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
.and_then(|ref a| nonce_utils::data_from_account(a))?;
Ok(data.blockhash)
Ok(data.blockhash())
}
}
}
@ -193,7 +193,12 @@ mod tests {
clap::App,
serde_json::{self, json},
solana_account_decoder::{UiAccount, UiAccountEncoding},
solana_sdk::{account::Account, hash::hash, nonce, system_program},
solana_sdk::{
account::Account,
hash::hash,
nonce::{self, state::DurableNonce},
system_program,
},
std::collections::HashMap,
};
@ -411,11 +416,13 @@ mod tests {
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
.is_err());
let nonce_blockhash = Hash::new(&[2u8; 32]);
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new(&[2u8; 32]), /*separate_domains:*/ true);
let nonce_blockhash = *durable_nonce.as_hash();
let nonce_fee_calc = FeeCalculator::new(4242);
let data = nonce::state::Data {
authority: Pubkey::new(&[3u8; 32]),
blockhash: nonce_blockhash,
durable_nonce,
fee_calculator: nonce_fee_calc,
};
let nonce_account = Account::new_data_with_space(

View File

@ -199,7 +199,7 @@ pub fn state_from_account<T: ReadableAccount + StateMut<Versions>>(
/// // network's latest blockhash.
/// let nonce_account = client.get_account(nonce_account_pubkey)?;
/// let nonce_data = nonce_utils::data_from_account(&nonce_account)?;
/// let blockhash = nonce_data.blockhash;
/// let blockhash = nonce_data.blockhash();
///
/// tx.try_sign(&[payer], blockhash)?;
///

View File

@ -4570,7 +4570,8 @@ pub mod tests {
hash::{hash, Hash},
instruction::InstructionError,
message::{v0, v0::MessageAddressTableLookup, MessageHeader, VersionedMessage},
nonce, rpc_port,
nonce::{self, state::DurableNonce},
rpc_port,
signature::{Keypair, Signer},
slot_hashes::SlotHashes,
system_program, system_transaction,
@ -5550,7 +5551,7 @@ pub mod tests {
42,
&nonce::state::Versions::new_current(nonce::State::new_initialized(
&authority,
&Hash::default(),
DurableNonce::default(),
1000,
)),
&system_program::id(),

View File

@ -223,7 +223,8 @@ pub(crate) mod tests {
hash::Hash,
instruction::CompiledInstruction,
message::{Message, MessageHeader, SanitizedMessage},
nonce, nonce_account,
nonce::{self, state::DurableNonce},
nonce_account,
pubkey::Pubkey,
signature::{Keypair, Signature, Signer},
system_transaction,
@ -323,7 +324,9 @@ pub(crate) mod tests {
let pubkey = Pubkey::new_unique();
let mut nonce_account = nonce_account::create_account(1).into_inner();
let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), Hash::new(&[42u8; 32]), 42);
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new(&[42u8; 32]), /*separate_domains:*/ true);
let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), durable_nonce, 42);
nonce_account
.set_state(&nonce::state::Versions::new_current(
nonce::State::Initialized(data),

View File

@ -40,7 +40,10 @@ use {
SanitizedMessage,
},
native_loader,
nonce::{state::Versions as NonceVersions, State as NonceState},
nonce::{
state::{DurableNonce, Versions as NonceVersions},
State as NonceState,
},
pubkey::Pubkey,
slot_hashes::SlotHashes,
system_program,
@ -1193,7 +1196,7 @@ impl Accounts {
res: &'a [TransactionExecutionResult],
loaded: &'a mut [TransactionLoadResult],
rent_collector: &RentCollector,
blockhash: &Hash,
durable_nonce: &DurableNonce,
lamports_per_signature: u64,
leave_nonce_on_success: bool,
) {
@ -1202,7 +1205,7 @@ impl Accounts {
res,
loaded,
rent_collector,
blockhash,
durable_nonce,
lamports_per_signature,
leave_nonce_on_success,
);
@ -1225,7 +1228,7 @@ impl Accounts {
execution_results: &'a [TransactionExecutionResult],
load_results: &'a mut [TransactionLoadResult],
rent_collector: &RentCollector,
blockhash: &Hash,
durable_nonce: &DurableNonce,
lamports_per_signature: u64,
leave_nonce_on_success: bool,
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
@ -1279,7 +1282,7 @@ impl Accounts {
execution_status,
is_fee_payer,
maybe_nonce,
blockhash,
durable_nonce,
lamports_per_signature,
);
@ -1306,13 +1309,13 @@ impl Accounts {
}
}
pub fn prepare_if_nonce_account<'a>(
fn prepare_if_nonce_account<'a>(
address: &Pubkey,
account: &mut AccountSharedData,
execution_result: &Result<()>,
is_fee_payer: bool,
maybe_nonce: Option<(&'a NonceFull, bool)>,
blockhash: &Hash,
durable_nonce: &DurableNonce,
lamports_per_signature: u64,
) -> bool {
if let Some((nonce, rollback)) = maybe_nonce {
@ -1338,7 +1341,7 @@ pub fn prepare_if_nonce_account<'a>(
account
.set_state(&NonceVersions::new_current(NonceState::new_initialized(
&data.authority,
blockhash,
*durable_nonce,
lamports_per_signature,
)))
.unwrap();
@ -3025,7 +3028,7 @@ mod tests {
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&Hash::default(),
&DurableNonce::default(),
0,
true, // leave_nonce_on_success
);
@ -3171,7 +3174,7 @@ mod tests {
Pubkey,
AccountSharedData,
AccountSharedData,
Hash,
DurableNonce,
u64,
Option<AccountSharedData>,
) {
@ -3184,7 +3187,7 @@ mod tests {
Pubkey::default(),
pre_account,
account,
Hash::new(&[1u8; 32]),
DurableNonce::from_blockhash(&Hash::new(&[1u8; 32]), /*separate_domains:*/ true),
1234,
None,
)
@ -3196,7 +3199,7 @@ mod tests {
tx_result: &Result<()>,
is_fee_payer: bool,
maybe_nonce: Option<(&NonceFull, bool)>,
blockhash: &Hash,
durable_nonce: &DurableNonce,
lamports_per_signature: u64,
expect_account: &AccountSharedData,
) -> bool {
@ -3216,7 +3219,7 @@ mod tests {
tx_result,
is_fee_payer,
maybe_nonce,
blockhash,
durable_nonce,
lamports_per_signature,
);
assert_eq!(expect_account, account);
@ -3370,7 +3373,7 @@ mod tests {
)),
false,
Some((&nonce, true)),
&Hash::default(),
&DurableNonce::default(),
1,
&post_fee_payer_account.clone(),
));
@ -3381,7 +3384,7 @@ mod tests {
&Ok(()),
true,
Some((&nonce, true)),
&Hash::default(),
&DurableNonce::default(),
1,
&post_fee_payer_account.clone(),
));
@ -3395,7 +3398,7 @@ mod tests {
)),
true,
None,
&Hash::default(),
&DurableNonce::default(),
1,
&post_fee_payer_account.clone(),
));
@ -3409,7 +3412,7 @@ mod tests {
)),
true,
Some((&nonce, true)),
&Hash::default(),
&DurableNonce::default(),
1,
&pre_fee_payer_account,
));
@ -3424,8 +3427,10 @@ mod tests {
let from = keypair_from_seed(&[1; 32]).unwrap();
let from_address = from.pubkey();
let to_address = Pubkey::new_unique();
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let nonce_state = NonceVersions::new_current(NonceState::Initialized(
nonce::state::Data::new(nonce_authority.pubkey(), Hash::new_unique(), 0),
nonce::state::Data::new(nonce_authority.pubkey(), durable_nonce, 0),
));
let nonce_account_post =
AccountSharedData::new_data(43, &nonce_state, &system_program::id()).unwrap();
@ -3449,8 +3454,10 @@ mod tests {
];
let tx = new_sanitized_tx(&[&nonce_authority, &from], message, blockhash);
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let nonce_state = NonceVersions::new_current(NonceState::Initialized(
nonce::state::Data::new(nonce_authority.pubkey(), Hash::new_unique(), 0),
nonce::state::Data::new(nonce_authority.pubkey(), durable_nonce, 0),
));
let nonce_account_pre =
AccountSharedData::new_data(42, &nonce_state, &system_program::id()).unwrap();
@ -3474,7 +3481,8 @@ mod tests {
let mut loaded = vec![loaded];
let next_blockhash = Hash::new_unique();
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
@ -3495,7 +3503,7 @@ mod tests {
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&next_blockhash,
&durable_nonce,
0,
true, // leave_nonce_on_success
);
@ -3521,7 +3529,7 @@ mod tests {
);
assert!(nonce_account::verify_nonce_account(
&collected_nonce_account,
&next_blockhash
durable_nonce.as_hash(),
));
}
@ -3534,8 +3542,10 @@ mod tests {
let from = keypair_from_seed(&[1; 32]).unwrap();
let from_address = from.pubkey();
let to_address = Pubkey::new_unique();
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let nonce_state = NonceVersions::new_current(NonceState::Initialized(
nonce::state::Data::new(nonce_authority.pubkey(), Hash::new_unique(), 0),
nonce::state::Data::new(nonce_authority.pubkey(), durable_nonce, 0),
));
let nonce_account_post =
AccountSharedData::new_data(43, &nonce_state, &system_program::id()).unwrap();
@ -3559,8 +3569,10 @@ mod tests {
];
let tx = new_sanitized_tx(&[&nonce_authority, &from], message, blockhash);
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let nonce_state = NonceVersions::new_current(NonceState::Initialized(
nonce::state::Data::new(nonce_authority.pubkey(), Hash::new_unique(), 0),
nonce::state::Data::new(nonce_authority.pubkey(), durable_nonce, 0),
));
let nonce_account_pre =
AccountSharedData::new_data(42, &nonce_state, &system_program::id()).unwrap();
@ -3583,7 +3595,8 @@ mod tests {
let mut loaded = vec![loaded];
let next_blockhash = Hash::new_unique();
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
@ -3604,7 +3617,7 @@ mod tests {
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&next_blockhash,
&durable_nonce,
0,
true, // leave_nonce_on_success
);
@ -3621,7 +3634,7 @@ mod tests {
);
assert!(nonce_account::verify_nonce_account(
&collected_nonce_account,
&next_blockhash
durable_nonce.as_hash(),
));
}

View File

@ -124,7 +124,8 @@ use {
message::{AccountKeys, SanitizedMessage},
native_loader,
native_token::sol_to_lamports,
nonce, nonce_account,
nonce::{self, state::DurableNonce},
nonce_account,
packet::PACKET_DATA_SIZE,
precompiles::get_precompiles,
pubkey::Pubkey,
@ -4909,13 +4910,19 @@ impl Bank {
}
let mut write_time = Measure::start("write_time");
let durable_nonce = {
let separate_nonce_from_blockhash = self
.feature_set
.is_active(&feature_set::separate_nonce_from_blockhash::id());
DurableNonce::from_blockhash(&last_blockhash, separate_nonce_from_blockhash)
};
self.rc.accounts.store_cached(
self.slot(),
sanitized_txs,
&execution_results,
loaded_txs,
&self.rent_collector,
&last_blockhash,
&durable_nonce,
lamports_per_signature,
self.leave_nonce_on_success(),
);
@ -7622,14 +7629,12 @@ pub(crate) mod tests {
let from_address = from.pubkey();
let to_address = Pubkey::new_unique();
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let nonce_account = AccountSharedData::new_data(
43,
&nonce::state::Versions::new_current(nonce::State::Initialized(
nonce::state::Data::new(
Pubkey::default(),
Hash::new_unique(),
lamports_per_signature,
),
nonce::state::Data::new(Pubkey::default(), durable_nonce, lamports_per_signature),
)),
&system_program::id(),
)
@ -12347,7 +12352,7 @@ pub(crate) mod tests {
let state =
StateMut::<nonce::state::Versions>::state(&acc).map(|v| v.convert_to_current());
match state {
Ok(nonce::State::Initialized(ref data)) => Some(data.blockhash),
Ok(nonce::State::Initialized(ref data)) => Some(data.blockhash()),
_ => None,
}
})
@ -13049,7 +13054,7 @@ pub(crate) mod tests {
StateMut::<nonce::state::Versions>::state(&acc).map(|v| v.convert_to_current());
match state {
Ok(nonce::State::Initialized(ref data)) => {
Some((data.blockhash, data.fee_calculator))
Some((data.blockhash(), data.fee_calculator))
}
_ => None,
}
@ -13086,7 +13091,7 @@ pub(crate) mod tests {
StateMut::<nonce::state::Versions>::state(&acc).map(|v| v.convert_to_current());
match state {
Ok(nonce::State::Initialized(ref data)) => {
Some((data.blockhash, data.fee_calculator))
Some((data.blockhash(), data.fee_calculator))
}
_ => None,
}
@ -13119,7 +13124,7 @@ pub(crate) mod tests {
StateMut::<nonce::state::Versions>::state(&acc).map(|v| v.convert_to_current());
match state {
Ok(nonce::State::Initialized(ref data)) => {
Some((data.blockhash, data.fee_calculator))
Some((data.blockhash(), data.fee_calculator))
}
_ => None,
}
@ -13156,7 +13161,7 @@ pub(crate) mod tests {
StateMut::<nonce::state::Versions>::state(&acc).map(|v| v.convert_to_current());
match state {
Ok(nonce::State::Initialized(ref data)) => {
Some((data.blockhash, data.fee_calculator))
Some((data.blockhash(), data.fee_calculator))
}
_ => None,
}
@ -13198,13 +13203,13 @@ pub(crate) mod tests {
&[&custodian_keypair, &nonce_keypair],
nonce_hash,
);
// Caught by the system program because the tx hash is valid
// SanitizedMessage::get_durable_nonce returns None because nonce
// account is not writable. Durable nonce and blockhash domains are
// separate, so the recent_blockhash (== durable nonce) in the
// transaction is not found in the hash queue.
assert_eq!(
bank.process_transaction(&tx),
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidArgument
))
Err(TransactionError::BlockhashNotFound),
);
// Kick nonce hash off the blockhash_queue
for _ in 0..MAX_RECENT_BLOCKHASHES + 1 {

View File

@ -3,7 +3,11 @@ use {
solana_sdk::{
feature_set::{self, nonce_must_be_writable},
instruction::{checked_add, InstructionError},
nonce::{self, state::Versions, State},
nonce::{
self,
state::{DurableNonce, Versions},
State,
},
pubkey::Pubkey,
system_instruction::{nonce_to_instruction_error, NonceError},
sysvar::rent::Rent,
@ -12,6 +16,13 @@ use {
std::collections::HashSet,
};
fn get_durable_nonce(invoke_context: &InvokeContext) -> DurableNonce {
let separate_nonce_from_blockhash = invoke_context
.feature_set
.is_active(&feature_set::separate_nonce_from_blockhash::id());
DurableNonce::from_blockhash(&invoke_context.blockhash, separate_nonce_from_blockhash)
}
pub fn advance_nonce_account(
account: &mut BorrowedAccount,
signers: &HashSet<Pubkey>,
@ -45,8 +56,8 @@ pub fn advance_nonce_account(
);
return Err(InstructionError::MissingRequiredSignature);
}
let recent_blockhash = invoke_context.blockhash;
if data.blockhash == recent_blockhash {
let next_durable_nonce = get_durable_nonce(invoke_context);
if data.durable_nonce == next_durable_nonce {
ic_msg!(
invoke_context,
"Advance nonce account: nonce can only advance once per slot"
@ -59,7 +70,7 @@ pub fn advance_nonce_account(
let new_data = nonce::state::Data::new(
data.authority,
recent_blockhash,
next_durable_nonce,
invoke_context.lamports_per_signature,
);
account.set_state(&Versions::new_current(State::Initialized(new_data)))
@ -123,7 +134,7 @@ pub fn withdraw_nonce_account(
}
State::Initialized(ref data) => {
if lamports == from.get_lamports() {
if data.blockhash == invoke_context.blockhash {
if data.durable_nonce == get_durable_nonce(invoke_context) {
ic_msg!(
invoke_context,
"Withdraw nonce account: nonce can only advance once per slot"
@ -208,7 +219,7 @@ pub fn initialize_nonce_account(
}
let data = nonce::state::Data::new(
*nonce_authority,
invoke_context.blockhash,
get_durable_nonce(invoke_context),
invoke_context.lamports_per_signature,
);
account.set_state(&Versions::new_current(State::Initialized(data)))
@ -263,7 +274,7 @@ pub fn authorize_nonce_account(
}
let new_data = nonce::state::Data::new(
*nonce_authority,
data.blockhash,
data.durable_nonce,
data.get_lamports_per_signature(),
);
account.set_state(&Versions::new_current(State::Initialized(new_data)))
@ -394,7 +405,7 @@ mod test {
.convert_to_current();
let data = nonce::state::Data::new(
data.authority,
invoke_context.blockhash,
get_durable_nonce(&invoke_context),
invoke_context.lamports_per_signature,
);
// First nonce instruction drives state from Uninitialized to Initialized
@ -407,7 +418,7 @@ mod test {
.convert_to_current();
let data = nonce::state::Data::new(
data.authority,
invoke_context.blockhash,
get_durable_nonce(&invoke_context),
invoke_context.lamports_per_signature,
);
// Second nonce instruction consumes and replaces stored nonce
@ -420,7 +431,7 @@ mod test {
.convert_to_current();
let data = nonce::state::Data::new(
data.authority,
invoke_context.blockhash,
get_durable_nonce(&invoke_context),
invoke_context.lamports_per_signature,
);
// Third nonce instruction for fun and profit
@ -485,7 +496,7 @@ mod test {
.convert_to_current();
let data = nonce::state::Data::new(
authority,
invoke_context.blockhash,
get_durable_nonce(&invoke_context),
invoke_context.lamports_per_signature,
);
assert_eq!(state, State::Initialized(data));
@ -818,7 +829,7 @@ mod test {
.convert_to_current();
let data = nonce::state::Data::new(
authority,
invoke_context.blockhash,
get_durable_nonce(&invoke_context),
invoke_context.lamports_per_signature,
);
assert_eq!(state, State::Initialized(data.clone()));
@ -850,7 +861,7 @@ mod test {
.convert_to_current();
let data = nonce::state::Data::new(
data.authority,
invoke_context.blockhash,
get_durable_nonce(&invoke_context),
invoke_context.lamports_per_signature,
);
assert_eq!(state, State::Initialized(data));
@ -1048,7 +1059,7 @@ mod test {
initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context);
let data = nonce::state::Data::new(
authorized,
invoke_context.blockhash,
get_durable_nonce(&invoke_context),
invoke_context.lamports_per_signature,
);
assert_eq!(result, Ok(()));
@ -1120,7 +1131,7 @@ mod test {
let authority = Pubkey::default();
let data = nonce::state::Data::new(
authority,
invoke_context.blockhash,
get_durable_nonce(&invoke_context),
invoke_context.lamports_per_signature,
);
authorize_nonce_account(&mut nonce_account, &authority, &signers, &invoke_context).unwrap();
@ -1202,7 +1213,7 @@ mod test {
.get_account_at_index(NONCE_ACCOUNT_INDEX)
.unwrap()
.borrow(),
&invoke_context.blockhash,
get_durable_nonce(&invoke_context).as_hash(),
));
}
@ -1257,7 +1268,7 @@ mod test {
.get_account_at_index(NONCE_ACCOUNT_INDEX)
.unwrap()
.borrow(),
&invoke_context.blockhash,
get_durable_nonce(&invoke_context).as_hash(),
));
}
}

View File

@ -24,9 +24,9 @@ pub mod solana_client {
pub mod nonce_utils {
use {
super::super::solana_sdk::{
account::ReadableAccount, account_utils::StateMut, hash::Hash, pubkey::Pubkey,
account::ReadableAccount, account_utils::StateMut, pubkey::Pubkey,
},
crate::nonce::state::{Data, Versions},
crate::nonce::state::{Data, DurableNonce, Versions},
};
#[derive(thiserror::Error, Debug)]
@ -36,7 +36,11 @@ pub mod solana_client {
pub fn data_from_account<T: ReadableAccount + StateMut<Versions>>(
_account: &T,
) -> Result<Data, Error> {
Ok(Data::new(Pubkey::new_unique(), Hash::default(), 5000))
Ok(Data::new(
Pubkey::new_unique(),
DurableNonce::default(),
5000,
))
}
}

View File

@ -1,8 +1,17 @@
use {
crate::{fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey},
crate::{
fee_calculator::FeeCalculator,
hash::{hashv, Hash},
pubkey::Pubkey,
},
serde_derive::{Deserialize, Serialize},
};
const DURABLE_NONCE_HASH_PREFIX: &[u8] = "DURABLE_NONCE".as_bytes();
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct DurableNonce(Hash);
/// Initialized data of a durable transaction nonce account.
///
/// This is stored within [`State`] for initialized nonce accounts.
@ -10,28 +19,54 @@ use {
pub struct Data {
/// Address of the account that signs transactions using the nonce account.
pub authority: Pubkey,
/// A valid previous blockhash.
pub blockhash: Hash,
/// Durable nonce value derived from a valid previous blockhash.
pub durable_nonce: DurableNonce,
/// The fee calculator associated with the blockhash.
pub fee_calculator: FeeCalculator,
}
impl Data {
/// Create new durable transaction nonce data.
pub fn new(authority: Pubkey, blockhash: Hash, lamports_per_signature: u64) -> Self {
pub fn new(
authority: Pubkey,
durable_nonce: DurableNonce,
lamports_per_signature: u64,
) -> Self {
Data {
authority,
blockhash,
durable_nonce,
fee_calculator: FeeCalculator::new(lamports_per_signature),
}
}
/// Hash value used as recent_blockhash field in Transactions.
/// Named blockhash for legacy reasons, but durable nonce and blockhash
/// have separate domains.
pub fn blockhash(&self) -> Hash {
self.durable_nonce.0
}
/// Get the cost per signature for the next transaction to use this nonce.
pub fn get_lamports_per_signature(&self) -> u64 {
self.fee_calculator.lamports_per_signature
}
}
impl DurableNonce {
pub fn from_blockhash(blockhash: &Hash, separate_domains: bool) -> Self {
Self(if separate_domains {
hashv(&[DURABLE_NONCE_HASH_PREFIX, blockhash.as_ref()])
} else {
*blockhash
})
}
/// Hash value used as recent_blockhash field in Transactions.
pub fn as_hash(&self) -> &Hash {
&self.0
}
}
/// The state of a durable transaction nonce account.
///
/// When created in memory with [`State::default`] or when deserialized from an
@ -52,10 +87,10 @@ impl State {
/// Create new durable transaction nonce state.
pub fn new_initialized(
authority: &Pubkey,
blockhash: &Hash,
durable_nonce: DurableNonce,
lamports_per_signature: u64,
) -> Self {
Self::Initialized(Data::new(*authority, *blockhash, lamports_per_signature))
Self::Initialized(Data::new(*authority, durable_nonce, lamports_per_signature))
}
/// Get the serialized size of the nonce state.

View File

@ -1,7 +1,7 @@
//! State for durable transaction nonces.
mod current;
pub use current::{Data, State};
pub use current::{Data, DurableNonce, State};
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]

View File

@ -736,7 +736,7 @@ pub fn create_nonce_account(
/// # });
/// let nonce_account = client.get_account(nonce_account_pubkey)?;
/// let nonce_data = nonce_utils::data_from_account(&nonce_account)?;
/// let blockhash = nonce_data.blockhash;
/// let blockhash = nonce_data.blockhash();
///
/// tx.try_sign(&[payer], blockhash)?;
///

View File

@ -416,6 +416,10 @@ pub mod warp_timestamp_with_a_vengeance {
solana_sdk::declare_id!("3BX6SBeEBibHaVQXywdkcgyUk6evfYZkHdztXiDtEpFS");
}
pub mod separate_nonce_from_blockhash {
solana_sdk::declare_id!("Gea3ZkK2N4pHuVZVxWcnAtS6UEDdyumdYt4pFcKjA3ar");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -513,6 +517,7 @@ lazy_static! {
(include_account_index_in_rent_error::id(), "include account index in rent tx error #25190"),
(add_shred_type_to_shred_seed::id(), "add shred-type to shred seed #25556"),
(warp_timestamp_with_a_vengeance::id(), "warp timestamp again, adjust bounding to 150% slow #25666"),
(separate_nonce_from_blockhash::id(), "separate durable nonce and blockhash domains #25744"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@ -20,12 +20,13 @@ pub fn create_account(lamports: u64) -> RefCell<AccountSharedData> {
)
}
// TODO: Consider changing argument from Hash to DurableNonce.
pub fn verify_nonce_account(acc: &AccountSharedData, hash: &Hash) -> bool {
if acc.owner() != &crate::system_program::id() {
return false;
}
match StateMut::<Versions>::state(acc).map(|v| v.convert_to_current()) {
Ok(State::Initialized(ref data)) => *hash == data.blockhash,
Ok(State::Initialized(ref data)) => hash == &data.blockhash(),
_ => false,
}
}

View File

@ -764,8 +764,12 @@ mod test {
crate::tpu_info::NullTpuInfo,
crossbeam_channel::unbounded,
solana_sdk::{
account::AccountSharedData, genesis_config::create_genesis_config, nonce,
pubkey::Pubkey, signature::Signer, system_program, system_transaction,
account::AccountSharedData,
genesis_config::create_genesis_config,
nonce::{self, state::DurableNonce},
pubkey::Pubkey,
signature::Signer,
system_program, system_transaction,
},
std::ops::Sub,
};
@ -1068,7 +1072,8 @@ mod test {
.unwrap();
let nonce_address = Pubkey::new_unique();
let durable_nonce = Hash::new_unique();
let durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let nonce_state = nonce::state::Versions::new_current(nonce::State::Initialized(
nonce::state::Data::new(Pubkey::default(), durable_nonce, 42),
));
@ -1101,7 +1106,7 @@ mod test {
rooted_signature,
vec![],
last_valid_block_height,
Some((nonce_address, durable_nonce)),
Some((nonce_address, *durable_nonce.as_hash())),
None,
Some(Instant::now()),
),
@ -1192,7 +1197,7 @@ mod test {
Signature::default(),
vec![],
root_bank.block_height() - 1,
Some((nonce_address, durable_nonce)),
Some((nonce_address, *durable_nonce.as_hash())),
None,
Some(Instant::now()),
),
@ -1284,7 +1289,7 @@ mod test {
Signature::default(),
vec![],
last_valid_block_height,
Some((nonce_address, durable_nonce)),
Some((nonce_address, *durable_nonce.as_hash())),
None,
Some(Instant::now().sub(Duration::from_millis(4000))),
),
@ -1311,7 +1316,8 @@ mod test {
for mut transaction in transactions.values_mut() {
transaction.last_sent_time = Some(Instant::now().sub(Duration::from_millis(4000)));
}
let new_durable_nonce = Hash::new_unique();
let new_durable_nonce =
DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true);
let new_nonce_state = nonce::state::Versions::new_current(nonce::State::Initialized(
nonce::state::Data::new(Pubkey::default(), new_durable_nonce, 42),
));