Add unix_timestap to stake lockups (#7569)

This commit is contained in:
Rob Walker
2019-12-19 14:37:47 -08:00
committed by GitHub
parent 0245847ea8
commit 3f405d8908
12 changed files with 1038 additions and 373 deletions

2
Cargo.lock generated
View File

@ -3264,6 +3264,7 @@ dependencies = [
name = "solana-clap-utils" name = "solana-clap-utils"
version = "0.22.0" version = "0.22.0"
dependencies = [ dependencies = [
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rpassword 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3497,7 +3498,6 @@ name = "solana-genesis"
version = "0.22.0" version = "0.22.0"
dependencies = [ dependencies = [
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ semver = "0.9.0"
solana-sdk = { path = "../sdk", version = "0.22.0" } solana-sdk = { path = "../sdk", version = "0.22.0" }
tiny-bip39 = "0.6.2" tiny-bip39 = "0.6.2"
url = "2.1.0" url = "2.1.0"
chrono = "0.4"
[lib] [lib]
name = "solana_clap_utils" name = "solana_clap_utils"

View File

@ -1,6 +1,8 @@
use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG}; use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG};
use chrono::DateTime;
use clap::ArgMatches; use clap::ArgMatches;
use solana_sdk::{ use solana_sdk::{
clock::UnixTimestamp,
native_token::sol_to_lamports, native_token::sol_to_lamports,
pubkey::Pubkey, pubkey::Pubkey,
signature::{read_keypair_file, Keypair, KeypairUtil, Signature}, signature::{read_keypair_file, Keypair, KeypairUtil, Signature},
@ -31,6 +33,14 @@ where
} }
} }
pub fn unix_timestamp_of(matches: &ArgMatches<'_>, name: &str) -> Option<UnixTimestamp> {
matches.value_of(name).and_then(|value| {
DateTime::parse_from_rfc3339(value)
.ok()
.map(|date_time| date_time.timestamp())
})
}
// Return the keypair for an argument with filename `name` or None if not present. // Return the keypair for an argument with filename `name` or None if not present.
pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> { pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
if let Some(value) = matches.value_of(name) { if let Some(value) = matches.value_of(name) {

View File

@ -287,7 +287,7 @@ pub struct CliConfig {
pub keypair: Keypair, pub keypair: Keypair,
pub keypair_path: Option<String>, pub keypair_path: Option<String>,
pub rpc_client: Option<RpcClient>, pub rpc_client: Option<RpcClient>,
pub print_header: bool, pub verbose: bool,
} }
impl CliConfig { impl CliConfig {
@ -313,7 +313,7 @@ impl Default for CliConfig {
keypair: Keypair::new(), keypair: Keypair::new(),
keypair_path: Some(Self::default_keypair_path()), keypair_path: Some(Self::default_keypair_path()),
rpc_client: None, rpc_client: None,
print_header: true, verbose: false,
} }
} }
} }
@ -1073,14 +1073,10 @@ fn process_witness(
} }
pub fn process_command(config: &CliConfig) -> ProcessResult { pub fn process_command(config: &CliConfig) -> ProcessResult {
if config.print_header { if config.verbose {
if let Some(keypair_path) = &config.keypair_path { if let Some(keypair_path) = &config.keypair_path {
println_name_value("Keypair:", keypair_path); println_name_value("Keypair:", keypair_path);
} }
if let CliCommand::Address = config.command {
// Get address of this client
return Ok(format!("{}", config.keypair.pubkey()));
}
println_name_value("RPC Endpoint:", &config.json_rpc_url); println_name_value("RPC Endpoint:", &config.json_rpc_url);
} }
@ -1095,6 +1091,8 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
match &config.command { match &config.command {
// Cluster Query Commands // Cluster Query Commands
// Get address of this client
CliCommand::Address => Ok(format!("{}", config.keypair.pubkey())),
// Return software version of solana-cli and cluster entrypoint node // Return software version of solana-cli and cluster entrypoint node
CliCommand::Catchup { node_pubkey } => process_catchup(&rpc_client, node_pubkey), CliCommand::Catchup { node_pubkey } => process_catchup(&rpc_client, node_pubkey),
@ -1383,8 +1381,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
// Wallet Commands // Wallet Commands
// Get address of this client
CliCommand::Address => unreachable!(),
// Request an airdrop from Solana Faucet; // Request an airdrop from Solana Faucet;
CliCommand::Airdrop { CliCommand::Airdrop {
faucet_host, faucet_host,
@ -2460,6 +2456,7 @@ mod tests {
withdrawer: None, withdrawer: None,
lockup: Lockup { lockup: Lockup {
epoch: 0, epoch: 0,
unix_timestamp: 0,
custodian, custodian,
}, },
lamports: 1234, lamports: 1234,

View File

@ -133,15 +133,13 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result<CliConfig, Box<dyn error::
(default.keypair, None) (default.keypair, None)
}; };
let print_header = !matches.is_present("no_header");
Ok(CliConfig { Ok(CliConfig {
command, command,
json_rpc_url, json_rpc_url,
keypair, keypair,
keypair_path, keypair_path,
rpc_client: None, rpc_client: None,
print_header, verbose: matches.is_present("verbose"),
}) })
} }
@ -186,10 +184,11 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.help("/path/to/id.json"), .help("/path/to/id.json"),
) )
.arg( .arg(
Arg::with_name("no_header") Arg::with_name("verbose")
.long("no-header") .long("verbose")
.short("v")
.global(true) .global(true)
.help("Disable information header"), .help("Show extra information header"),
) )
.arg( .arg(
Arg::with_name(ASK_SEED_PHRASE_ARG.name) Arg::with_name(ASK_SEED_PHRASE_ARG.name)

View File

@ -73,11 +73,18 @@ impl StakeSubCommands for App<'_, '_> {
.help("Identity of the custodian (can withdraw before lockup expires)") .help("Identity of the custodian (can withdraw before lockup expires)")
) )
.arg( .arg(
Arg::with_name("lockup") Arg::with_name("lockup_epoch")
.long("lockup") .long("lockup-epoch")
.value_name("SLOT") .value_name("EPOCH")
.takes_value(true) .takes_value(true)
.help("The slot height at which this account will be available for withdrawal") .help("The epoch height at which this account will be available for withdrawal")
)
.arg(
Arg::with_name("lockup_date")
.long("lockup-date")
.value_name("RFC3339 DATE TIME")
.takes_value(true)
.help("The date and time at which this account will be available for withdrawal")
) )
.arg( .arg(
Arg::with_name("authorized_staker") Arg::with_name("authorized_staker")
@ -322,7 +329,8 @@ impl StakeSubCommands for App<'_, '_> {
pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> { pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let stake_account = keypair_of(matches, "stake_account").unwrap(); let stake_account = keypair_of(matches, "stake_account").unwrap();
let epoch = value_of(&matches, "lockup").unwrap_or(0); let epoch = value_of(&matches, "lockup_epoch").unwrap_or(0);
let unix_timestamp = unix_timestamp_of(&matches, "lockup_date").unwrap_or(0);
let custodian = pubkey_of(matches, "custodian").unwrap_or_default(); let custodian = pubkey_of(matches, "custodian").unwrap_or_default();
let staker = pubkey_of(matches, "authorized_staker"); let staker = pubkey_of(matches, "authorized_staker");
let withdrawer = pubkey_of(matches, "authorized_withdrawer"); let withdrawer = pubkey_of(matches, "authorized_withdrawer");
@ -333,7 +341,11 @@ pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result<CliCommand
stake_account: stake_account.into(), stake_account: stake_account.into(),
staker, staker,
withdrawer, withdrawer,
lockup: Lockup { custodian, epoch }, lockup: Lockup {
custodian,
epoch,
unix_timestamp,
},
lamports, lamports,
}, },
require_keypair: true, require_keypair: true,
@ -906,7 +918,7 @@ mod tests {
&authorized_string, &authorized_string,
"--custodian", "--custodian",
&custodian_string, &custodian_string,
"--lockup", "--lockup-epoch",
"43", "43",
"lamports", "lamports",
]); ]);
@ -919,6 +931,7 @@ mod tests {
withdrawer: Some(authorized), withdrawer: Some(authorized),
lockup: Lockup { lockup: Lockup {
epoch: 43, epoch: 43,
unix_timestamp: 0,
custodian, custodian,
}, },
lamports: 50 lamports: 50

View File

@ -88,10 +88,7 @@ fn test_stake_delegation_and_deactivation() {
stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(),
staker: None, staker: None,
withdrawer: None, withdrawer: None,
lockup: Lockup { lockup: Lockup::default(),
custodian: Pubkey::default(),
epoch: 0,
},
lamports: 50_000, lamports: 50_000,
}; };
process_command(&config_validator).unwrap(); process_command(&config_validator).unwrap();
@ -175,10 +172,7 @@ fn test_stake_delegation_and_deactivation_offline() {
stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(),
staker: None, staker: None,
withdrawer: None, withdrawer: None,
lockup: Lockup { lockup: Lockup::default(),
custodian: Pubkey::default(),
epoch: 0,
},
lamports: 50_000, lamports: 50_000,
}; };
process_command(&config_validator).unwrap(); process_command(&config_validator).unwrap();

View File

@ -11,7 +11,6 @@ homepage = "https://solana.com/"
[dependencies] [dependencies]
base64 = "0.11.0" base64 = "0.11.0"
clap = "2.33.0" clap = "2.33.0"
chrono = "0.4"
hex = "0.4.0" hex = "0.4.0"
serde = "1.0.104" serde = "1.0.104"
serde_derive = "1.0.103" serde_derive = "1.0.103"

View File

@ -1,8 +1,10 @@
//! A command-line executable for generating the chain's genesis config. //! A command-line executable for generating the chain's genesis config.
use chrono::DateTime;
use clap::{crate_description, crate_name, value_t, value_t_or_exit, App, Arg, ArgMatches}; use clap::{crate_description, crate_name, value_t, value_t_or_exit, App, Arg, ArgMatches};
use solana_clap_utils::{input_parsers::pubkey_of, input_validators::is_valid_percentage}; use solana_clap_utils::{
input_parsers::{pubkey_of, unix_timestamp_of},
input_validators::is_valid_percentage,
};
use solana_genesis::{genesis_accounts::add_genesis_accounts, Base64Account}; use solana_genesis::{genesis_accounts::add_genesis_accounts, Base64Account};
use solana_ledger::{blocktree::create_new_ledger, poh::compute_hashes_per_tick}; use solana_ledger::{blocktree::create_new_ledger, poh::compute_hashes_per_tick};
use solana_sdk::{ use solana_sdk::{
@ -484,8 +486,8 @@ fn main() -> Result<(), Box<dyn error::Error>> {
..GenesisConfig::default() ..GenesisConfig::default()
}; };
if let Some(creation_time) = matches.value_of("creation_time") { if let Some(creation_time) = unix_timestamp_of(&matches, "creation_time") {
genesis_config.creation_time = DateTime::parse_from_rfc3339(creation_time)?.timestamp(); genesis_config.creation_time = creation_time;
} }
if let Some(faucet_pubkey) = faucet_pubkey { if let Some(faucet_pubkey) = faucet_pubkey {

View File

@ -109,6 +109,7 @@ pub fn create_and_add_stakes(
let lockup = Lockup { let lockup = Lockup {
epoch: unlock.epoch, epoch: unlock.epoch,
custodian, custodian,
unix_timestamp: 0,
}; };
for _ in 0..(lamports / granularity).saturating_sub(1) { for _ in 0..(lamports / granularity).saturating_sub(1) {
genesis_config.add_account( genesis_config.add_account(

View File

@ -8,7 +8,7 @@ use serde_derive::{Deserialize, Serialize};
use solana_sdk::{ use solana_sdk::{
account::{Account, KeyedAccount}, account::{Account, KeyedAccount},
account_utils::State, account_utils::State,
clock::{Clock, Epoch, Slot}, clock::{Clock, Epoch, Slot, UnixTimestamp},
instruction::InstructionError, instruction::InstructionError,
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent, rent::Rent,
@ -86,7 +86,10 @@ pub enum StakeAuthorize {
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] #[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Lockup { pub struct Lockup {
/// epoch at which this stake will allow withdrawal, unless /// UnixTimestamp at which this stake will allow withdrawal, unless
/// to the custodian.
pub unix_timestamp: UnixTimestamp,
/// epoch height at which this stake will allow withdrawal, unless
/// to the custodian /// to the custodian
pub epoch: Epoch, pub epoch: Epoch,
/// custodian account, the only account to which this stake will honor a /// custodian account, the only account to which this stake will honor a
@ -95,6 +98,13 @@ pub struct Lockup {
pub custodian: Pubkey, pub custodian: Pubkey,
} }
impl Lockup {
pub fn is_in_force(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> bool {
(self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch)
&& !signers.contains(&self.custodian)
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] #[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Authorized { pub struct Authorized {
pub staker: Pubkey, pub staker: Pubkey,
@ -754,8 +764,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
}; };
// verify that lockup has expired or that the withdrawal is signed by // verify that lockup has expired or that the withdrawal is signed by
// the custodian // the custodian, both epoch and unix_timestamp must have passed
if lockup.epoch > clock.epoch && !signers.contains(&lockup.custodian) { if lockup.is_in_force(&clock, signers) {
return Err(StakeError::LockupInForce.into()); return Err(StakeError::LockupInForce.into());
} }
@ -1404,6 +1414,7 @@ mod tests {
&Authorized::auto(&stake_pubkey), &Authorized::auto(&stake_pubkey),
&Lockup { &Lockup {
epoch: 1, epoch: 1,
unix_timestamp: 0,
custodian custodian
}, },
&Rent::free(), &Rent::free(),
@ -1415,6 +1426,7 @@ mod tests {
StakeState::from(&stake_keyed_account.account).unwrap(), StakeState::from(&stake_keyed_account.account).unwrap(),
StakeState::Initialized(Meta { StakeState::Initialized(Meta {
lockup: Lockup { lockup: Lockup {
unix_timestamp: 0,
epoch: 1, epoch: 1,
custodian custodian
}, },
@ -1555,6 +1567,7 @@ mod tests {
.initialize( .initialize(
&Authorized::auto(&stake_pubkey), &Authorized::auto(&stake_pubkey),
&Lockup { &Lockup {
unix_timestamp: 0,
epoch: 0, epoch: 0,
custodian, custodian,
}, },
@ -1759,6 +1772,7 @@ mod tests {
total_lamports, total_lamports,
&StakeState::Initialized(Meta { &StakeState::Initialized(Meta {
lockup: Lockup { lockup: Lockup {
unix_timestamp: 0,
epoch: 1, epoch: 1,
custodian, custodian,
}, },
@ -2591,6 +2605,77 @@ mod tests {
} }
} }
#[test]
fn test_lockup_is_expired() {
let custodian = Pubkey::new_rand();
let signers = [custodian].iter().cloned().collect::<HashSet<_>>();
let lockup = Lockup {
epoch: 1,
unix_timestamp: 1,
custodian,
};
// neither time
assert_eq!(
lockup.is_in_force(
&Clock {
epoch: 0,
unix_timestamp: 0,
..Clock::default()
},
&HashSet::new()
),
true
);
// not timestamp
assert_eq!(
lockup.is_in_force(
&Clock {
epoch: 2,
unix_timestamp: 0,
..Clock::default()
},
&HashSet::new()
),
true
);
// not epoch
assert_eq!(
lockup.is_in_force(
&Clock {
epoch: 0,
unix_timestamp: 2,
..Clock::default()
},
&HashSet::new()
),
true
);
// both, no custodian
assert_eq!(
lockup.is_in_force(
&Clock {
epoch: 1,
unix_timestamp: 1,
..Clock::default()
},
&HashSet::new()
),
false
);
// neither, but custodian
assert_eq!(
lockup.is_in_force(
&Clock {
epoch: 0,
unix_timestamp: 0,
..Clock::default()
},
&signers,
),
false,
);
}
#[test] #[test]
fn test_authorize_delegated_stake() { fn test_authorize_delegated_stake() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();