Skip to content

Commit 3e9f2a1

Browse files
Tommytrgaesedepece
authored andcommitted
feat(staking): implement unstaking logic
1 parent 5c00f10 commit 3e9f2a1

File tree

7 files changed

+197
-36
lines changed

7 files changed

+197
-36
lines changed

data_structures/src/staking/stakes.rs

+77-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ use std::{
77
use itertools::Itertools;
88
use serde::{Deserialize, Serialize};
99

10-
use crate::{chain::PublicKeyHash, get_environment, transaction::StakeTransaction, wit::Wit};
10+
use crate::{
11+
chain::PublicKeyHash,
12+
get_environment,
13+
transaction::{StakeTransaction, UnstakeTransaction},
14+
wit::Wit,
15+
};
1116

1217
use super::prelude::*;
1318

@@ -414,6 +419,49 @@ where
414419
Ok(())
415420
}
416421

422+
/// Removes stake, based on the data from a unstake transaction.
423+
///
424+
/// This function was made static instead of adding it to `impl Stakes` because it is not generic over `Address` and
425+
/// `Coins`.
426+
pub fn process_unstake_transaction<Epoch, Power>(
427+
stakes: &mut Stakes<PublicKeyHash, Wit, Epoch, Power>,
428+
transaction: &UnstakeTransaction,
429+
) -> StakingResult<(), PublicKeyHash, Wit, Epoch>
430+
where
431+
Epoch: Copy
432+
+ Default
433+
+ Sub<Output = Epoch>
434+
+ num_traits::Saturating
435+
+ From<u32>
436+
+ Debug
437+
+ Display
438+
+ Send
439+
+ Sync,
440+
Power: Add<Output = Power> + Copy + Default + Div<Output = Power> + Ord + Debug,
441+
Wit: Mul<Epoch, Output = Power>,
442+
u64: From<Wit> + From<Power>,
443+
{
444+
let key: StakeKey<PublicKeyHash> = StakeKey {
445+
validator: transaction.body.operator,
446+
withdrawer: transaction.body.withdrawal.pkh,
447+
};
448+
449+
let coins = Wit::from_nanowits(transaction.body.withdrawal.value);
450+
451+
let environment = get_environment();
452+
log::debug!(
453+
"{} removed {} Wit stake",
454+
key.validator.bech32(environment),
455+
coins.wits_and_nanowits().0,
456+
);
457+
458+
stakes.remove_stake(key, coins)?;
459+
460+
log::debug!("Current state of the stakes tracker: {:#?}", stakes);
461+
462+
Ok(())
463+
}
464+
417465
/// Adds stakes, based on the data from multiple stake transactions.
418466
///
419467
/// This function was made static instead of adding it to `impl Stakes` because it is not generic over `Address` and
@@ -443,6 +491,34 @@ where
443491

444492
Ok(())
445493
}
494+
/// Removes stakes, based on the data from multiple unstake transactions.
495+
///
496+
/// This function was made static instead of adding it to `impl Stakes` because it is not generic over `Address` and
497+
/// `Coins`.
498+
pub fn process_unstake_transactions<'a, Epoch, Power>(
499+
stakes: &mut Stakes<PublicKeyHash, Wit, Epoch, Power>,
500+
transactions: impl Iterator<Item = &'a UnstakeTransaction>,
501+
) -> Result<(), StakesError<PublicKeyHash, Wit, Epoch>>
502+
where
503+
Epoch: Copy
504+
+ Default
505+
+ Sub<Output = Epoch>
506+
+ num_traits::Saturating
507+
+ From<u32>
508+
+ Debug
509+
+ Send
510+
+ Sync
511+
+ Display,
512+
Power: Add<Output = Power> + Copy + Default + Div<Output = Power> + Ord + Debug,
513+
Wit: Mul<Epoch, Output = Power>,
514+
u64: From<Wit> + From<Power>,
515+
{
516+
for transaction in transactions {
517+
process_unstake_transaction(stakes, transaction)?;
518+
}
519+
520+
Ok(())
521+
}
446522

447523
#[cfg(test)]
448524
mod tests {

data_structures/src/transaction.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,7 @@ impl UnstakeTransaction {
837837
#[derive(Debug, Default, Eq, PartialEq, Clone, Serialize, Deserialize, ProtobufConvert, Hash)]
838838
#[protobuf_convert(pb = "witnet::UnstakeTransactionBody")]
839839
pub struct UnstakeTransactionBody {
840-
pub validator: PublicKeyHash,
840+
pub operator: PublicKeyHash,
841841
pub withdrawal: ValueTransferOutput,
842842

843843
#[protobuf_convert(skip)]
@@ -847,9 +847,9 @@ pub struct UnstakeTransactionBody {
847847

848848
impl UnstakeTransactionBody {
849849
/// Creates a new stake transaction body.
850-
pub fn new(validator: PublicKeyHash, withdrawal: ValueTransferOutput) -> Self {
850+
pub fn new(operator: PublicKeyHash, withdrawal: ValueTransferOutput) -> Self {
851851
UnstakeTransactionBody {
852-
validator,
852+
operator,
853853
withdrawal,
854854
..Default::default()
855855
}

data_structures/src/transaction_factory.rs

+12-22
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ impl NodeBalance {
8080
pub enum TransactionOutputs {
8181
DataRequest((DataRequestOutput, Option<ValueTransferOutput>)),
8282
Stake((StakeOutput, Option<ValueTransferOutput>)),
83-
Unstake(ValueTransferOutput),
8483
ValueTransfer(Vec<ValueTransferOutput>),
8584
}
8685

@@ -89,7 +88,6 @@ impl From<TransactionOutputs> for Vec<ValueTransferOutput> {
8988
match value {
9089
TransactionOutputs::DataRequest((_, change)) => change.into_iter().collect(),
9190
TransactionOutputs::Stake((_, change)) => change.into_iter().collect(),
92-
TransactionOutputs::Unstake(output) => vec![output],
9391
TransactionOutputs::ValueTransfer(outputs) => outputs,
9492
}
9593
}
@@ -207,11 +205,6 @@ pub trait OutputsCollection {
207205
output_value = body.value();
208206
current_weight = body.weight();
209207
}
210-
TransactionOutputs::Unstake(withdrawal) => {
211-
let body = UnstakeTransactionBody::new(Default::default(), withdrawal);
212-
output_value = body.value();
213-
current_weight = body.weight();
214-
}
215208
TransactionOutputs::ValueTransfer(outputs) => {
216209
let body = VTTransactionBody::new(inputs, outputs);
217210
output_value = body.value();
@@ -225,12 +218,8 @@ pub trait OutputsCollection {
225218
.checked_add(absolute_fee.as_nanowits())
226219
.ok_or(TransactionError::FeeOverflow)?;
227220

228-
// Avoid collecting UTXOs for unstake transactions, which use no inputs
229-
let inputs = if let &TransactionOutputs::Unstake(_) = &outputs {
230-
Default::default()
231-
} else {
232-
self.take_enough_utxos(amount, timestamp, block_number_limit, utxo_strategy)?
233-
};
221+
let inputs =
222+
self.take_enough_utxos(amount, timestamp, block_number_limit, utxo_strategy)?;
234223

235224
Ok(TransactionInfo {
236225
fee: absolute_fee,
@@ -241,14 +230,6 @@ pub trait OutputsCollection {
241230
}
242231
Fee::Relative(priority) => {
243232
let absolute_fee = priority.into_absolute(current_weight);
244-
if let TransactionOutputs::Unstake(withdrawal) = outputs {
245-
return Ok(TransactionInfo {
246-
fee: absolute_fee,
247-
inputs: Default::default(),
248-
output_value,
249-
outputs: vec![withdrawal],
250-
});
251-
}
252233

253234
let max_iterations = 1 + ((max_weight - current_weight) / INPUT_SIZE);
254235
for _i in 0..max_iterations {
@@ -289,7 +270,6 @@ pub trait OutputsCollection {
289270

290271
body.weight()
291272
}
292-
_ => unreachable!(),
293273
};
294274

295275
if new_weight == current_weight {
@@ -793,6 +773,16 @@ pub fn build_st(
793773
Ok(body)
794774
}
795775

776+
/// Build unstake transaction.
777+
pub fn build_ut(
778+
withdrawal: ValueTransferOutput,
779+
operator: PublicKeyHash,
780+
) -> Result<UnstakeTransactionBody, TransactionError> {
781+
let body = UnstakeTransactionBody::new(operator, withdrawal);
782+
783+
Ok(body)
784+
}
785+
796786
#[cfg(test)]
797787
mod tests {
798788
use std::{

node/src/actors/chain_manager/handlers.rs

+65-4
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ use witnet_config::defaults::{
1616
use witnet_data_structures::{
1717
chain::{
1818
tapi::ActiveWips, Block, ChainState, CheckpointBeacon, DataRequestInfo, Epoch, Hash,
19-
Hashable, NodeStats, PublicKeyHash, SuperBlockVote, SupplyInfo,
19+
Hashable, NodeStats, PublicKeyHash, SuperBlockVote, SupplyInfo, ValueTransferOutput,
2020
},
2121
error::{ChainInfoError, TransactionError::DataRequestNotFound},
2222
staking::errors::StakesError,
23-
transaction::{DRTransaction, StakeTransaction, Transaction, VTTransaction},
23+
transaction::{
24+
DRTransaction, StakeTransaction, Transaction, UnstakeTransaction, VTTransaction,
25+
},
2426
transaction_factory::{self, NodeBalance},
2527
types::LastBeacon,
2628
utxo_pool::{get_utxo_info, UtxoInfo},
@@ -33,8 +35,8 @@ use crate::{
3335
chain_manager::{handlers::BlockBatches::*, BlockCandidate},
3436
messages::{
3537
AddBlocks, AddCandidates, AddCommitReveal, AddSuperBlock, AddSuperBlockVote,
36-
AddTransaction, Broadcast, BuildDrt, BuildStake, BuildVtt, EpochNotification,
37-
EstimatePriority, GetBalance, GetBalanceTarget, GetBlocksEpochRange,
38+
AddTransaction, Broadcast, BuildDrt, BuildStake, BuildUnstake, BuildVtt,
39+
EpochNotification, EstimatePriority, GetBalance, GetBalanceTarget, GetBlocksEpochRange,
3840
GetDataRequestInfo, GetHighestCheckpointBeacon, GetMemoryTransaction, GetMempool,
3941
GetMempoolResult, GetNodeStats, GetReputation, GetReputationResult, GetSignalingInfo,
4042
GetState, GetSuperBlockVotes, GetSupplyInfo, GetUtxoInfo, IsConfirmedBlock,
@@ -1357,6 +1359,65 @@ impl Handler<BuildStake> for ChainManager {
13571359
}
13581360
}
13591361

1362+
impl Handler<BuildUnstake> for ChainManager {
1363+
type Result = ResponseActFuture<Self, <BuildUnstake as Message>::Result>;
1364+
1365+
fn handle(&mut self, msg: BuildUnstake, _ctx: &mut Self::Context) -> Self::Result {
1366+
if !msg.dry_run && self.sm_state != StateMachine::Synced {
1367+
return Box::pin(actix::fut::err(
1368+
ChainManagerError::NotSynced {
1369+
current_state: self.sm_state,
1370+
}
1371+
.into(),
1372+
));
1373+
}
1374+
1375+
let withdrawal = ValueTransferOutput {
1376+
time_lock: 0,
1377+
pkh: self.own_pkh.unwrap(),
1378+
value: msg.value,
1379+
};
1380+
match transaction_factory::build_ut(withdrawal, msg.operator) {
1381+
Err(e) => {
1382+
log::error!("Error when building stake transaction: {}", e);
1383+
Box::pin(actix::fut::err(e.into()))
1384+
}
1385+
Ok(ut) => {
1386+
let fut = signature_mngr::sign_transaction(&ut, 1)
1387+
.into_actor(self)
1388+
.then(move |s, act, _ctx| match s {
1389+
Ok(signature) => {
1390+
let ut =
1391+
UnstakeTransaction::new(ut, signature.first().unwrap().clone());
1392+
1393+
if msg.dry_run {
1394+
Either::Right(actix::fut::result(Ok(ut)))
1395+
} else {
1396+
let transaction = Transaction::Unstake(ut.clone());
1397+
Either::Left(
1398+
act.add_transaction(
1399+
AddTransaction {
1400+
transaction,
1401+
broadcast_flag: true,
1402+
},
1403+
get_timestamp(),
1404+
)
1405+
.map_ok(move |_, _, _| ut),
1406+
)
1407+
}
1408+
}
1409+
Err(e) => {
1410+
log::error!("Failed to sign stake transaction: {}", e);
1411+
Either::Right(actix::fut::result(Err(e)))
1412+
}
1413+
});
1414+
1415+
Box::pin(fut)
1416+
}
1417+
}
1418+
}
1419+
}
1420+
13601421
impl Handler<QueryStake> for ChainManager {
13611422
type Result = <QueryStake as Message>::Result;
13621423

node/src/actors/chain_manager/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,12 @@ impl ChainManager {
10381038
block_epoch,
10391039
);
10401040
}
1041-
//process_unstake_transactions(stakes, block.txns.unstake_txns.iter(), block_epoch);
1041+
1042+
let unstake_txns_count = block.txns.unstake_txns.len();
1043+
if unstake_txns_count > 0 {
1044+
let _ =
1045+
process_unstake_transactions(stakes, block.txns.unstake_txns.iter());
1046+
}
10421047
}
10431048

10441049
// Update bn256 public keys with block information

node/src/actors/messages.rs

+33-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use witnet_data_structures::{
3030
staking::{aux::StakeKey, stakes::QueryStakesKey},
3131
transaction::{
3232
CommitTransaction, DRTransaction, RevealTransaction, StakeTransaction, Transaction,
33-
VTTransaction,
33+
UnstakeTransaction, VTTransaction,
3434
},
3535
transaction_factory::NodeBalance,
3636
types::LastBeacon,
@@ -243,6 +243,36 @@ impl Message for BuildStake {
243243
type Result = Result<StakeTransaction, failure::Error>;
244244
}
245245

246+
/// Builds an `UnstakeTransaction`
247+
#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize)]
248+
pub struct BuildUnstake {
249+
/// Amount to unstake
250+
#[serde(default)]
251+
pub value: u64,
252+
/// Node address operating the staked coins
253+
pub operator: PublicKeyHash,
254+
/// Construct the transaction but do not broadcast it
255+
#[serde(default)]
256+
pub dry_run: bool,
257+
}
258+
259+
impl Message for BuildUnstake {
260+
type Result = Result<UnstakeTransaction, failure::Error>;
261+
}
262+
263+
/// Builds a `UnstakeTransaction` from a list of `ValueTransferOutput`s
264+
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
265+
pub struct BuildUnstakeParams {
266+
/// Amount to unstake
267+
#[serde(default)]
268+
pub value: u64,
269+
/// Node address operating the staked coins
270+
pub operator: MagicEither<String, PublicKeyHash>,
271+
/// Construct the transaction but do not broadcast it
272+
#[serde(default)]
273+
pub dry_run: bool,
274+
}
275+
246276
/// Builds a `StakeTransaction` from a list of `ValueTransferOutput`s
247277
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
248278
pub struct BuildStakeParams {
@@ -1446,8 +1476,8 @@ impl<L, R> MagicEither<L, R> {
14461476
}
14471477
}
14481478

1449-
impl<L, R: Default> Default for MagicEither<L, R> {
1479+
impl<L: Default, R: Default> Default for MagicEither<L, R> {
14501480
fn default() -> Self {
1451-
MagicEither::Right(R::default())
1481+
MagicEither::Left(L::default())
14521482
}
14531483
}

schemas/witnet/witnet.proto

+1-2
Original file line numberDiff line numberDiff line change
@@ -311,9 +311,8 @@ message StakeTransaction {
311311
}
312312

313313
message UnstakeTransactionBody {
314-
PublicKeyHash validator = 1;
314+
PublicKeyHash operator = 1;
315315
ValueTransferOutput withdrawal = 2;
316-
ValueTransferOutput change = 3;
317316
}
318317

319318
message UnstakeTransaction {

0 commit comments

Comments
 (0)