Skip to content

Commit 2dff08b

Browse files
committed
feat(transaction): add nonce to unstake transaction to prevent rebroadcasting
1 parent a089ad1 commit 2dff08b

File tree

7 files changed

+81
-39
lines changed

7 files changed

+81
-39
lines changed

data_structures/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ pub enum TransactionError {
307307
unstake, stake
308308
)]
309309
UnstakingMoreThanStaked { stake: u64, unstake: u64 },
310+
/// Tried to perform an unstake action with an invalid nonce.
311+
#[fail(
312+
display = "Cannot unstake with an invalid nonce: {} < {}",
313+
used, current
314+
)]
315+
UnstakeInvalidNonce { used: u64, current: u64 },
310316
/// An stake output with zero value does not make sense
311317
#[fail(display = "Transaction {} has a zero value stake output", tx_hash)]
312318
ZeroValueStakeOutput { tx_hash: Hash },

data_structures/src/transaction.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ pub struct UnstakeTransactionBody {
864864
pub operator: PublicKeyHash,
865865
pub withdrawal: ValueTransferOutput,
866866
pub fee: u64,
867+
pub nonce: u64,
867868

868869
#[protobuf_convert(skip)]
869870
#[serde(skip)]
@@ -872,11 +873,17 @@ pub struct UnstakeTransactionBody {
872873

873874
impl UnstakeTransactionBody {
874875
/// Creates a new stake transaction body.
875-
pub fn new(operator: PublicKeyHash, withdrawal: ValueTransferOutput, fee: u64) -> Self {
876+
pub fn new(
877+
operator: PublicKeyHash,
878+
withdrawal: ValueTransferOutput,
879+
fee: u64,
880+
nonce: u64,
881+
) -> Self {
876882
UnstakeTransactionBody {
877883
operator,
878884
withdrawal,
879885
fee,
886+
nonce,
880887
..Default::default()
881888
}
882889
}

data_structures/src/transaction_factory.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,8 +778,9 @@ pub fn build_ut(
778778
operator: PublicKeyHash,
779779
withdrawal: ValueTransferOutput,
780780
fee: u64,
781+
nonce: u64,
781782
) -> Result<UnstakeTransactionBody, TransactionError> {
782-
let body = UnstakeTransactionBody::new(operator, withdrawal, fee);
783+
let body = UnstakeTransactionBody::new(operator, withdrawal, fee, nonce);
783784

784785
Ok(body)
785786
}

node/src/actors/chain_manager/handlers.rs

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use witnet_data_structures::{
2222
},
2323
error::{ChainInfoError, TransactionError::DataRequestNotFound},
2424
proto::versioning::ProtocolVersion,
25-
staking::errors::StakesError,
25+
staking::{errors::StakesError, prelude::StakeKey},
2626
transaction::{
2727
DRTransaction, StakeTransaction, Transaction, UnstakeTransaction, VTTransaction,
2828
},
@@ -1410,47 +1410,60 @@ impl Handler<BuildUnstake> for ChainManager {
14101410
));
14111411
}
14121412

1413-
let withdrawal = ValueTransferOutput {
1414-
time_lock: PSEUDO_CONSENSUS_CONSTANTS_POS_UNSTAKING_DELAY_SECONDS,
1415-
pkh: self.own_pkh.unwrap(),
1416-
value: msg.value,
1417-
};
1418-
match transaction_factory::build_ut(msg.operator, withdrawal, msg.fee) {
1413+
match self.chain_state.stakes.query_nonce(StakeKey {
1414+
validator: msg.operator,
1415+
withdrawer: self.own_pkh.unwrap(),
1416+
}) {
14191417
Err(e) => {
14201418
log::error!("Error when building stake transaction: {}", e);
14211419
Box::pin(actix::fut::err(e.into()))
14221420
}
1423-
Ok(ut) => {
1424-
let fut = signature_mngr::sign_transaction(&ut, 1)
1425-
.into_actor(self)
1426-
.then(move |s, act, _ctx| match s {
1427-
Ok(signature) => {
1428-
let ut =
1429-
UnstakeTransaction::new(ut, signature.first().unwrap().clone());
1421+
Ok(nonce) => {
1422+
let withdrawal = ValueTransferOutput {
1423+
time_lock: PSEUDO_CONSENSUS_CONSTANTS_POS_UNSTAKING_DELAY_SECONDS,
1424+
pkh: self.own_pkh.unwrap(),
1425+
value: msg.value,
1426+
};
1427+
match transaction_factory::build_ut(msg.operator, withdrawal, msg.fee, nonce) {
1428+
Err(e) => {
1429+
log::error!("Error when building stake transaction: {}", e);
1430+
Box::pin(actix::fut::err(e.into()))
1431+
}
1432+
Ok(ut) => {
1433+
let fut = signature_mngr::sign_transaction(&ut, 1)
1434+
.into_actor(self)
1435+
.then(move |s, act, _ctx| match s {
1436+
Ok(signature) => {
1437+
let ut = UnstakeTransaction::new(
1438+
ut,
1439+
signature.first().unwrap().clone(),
1440+
);
14301441

1431-
if msg.dry_run {
1432-
Either::Right(actix::fut::result(Ok(ut)))
1433-
} else {
1434-
let transaction = Transaction::Unstake(ut.clone());
1435-
Either::Left(
1436-
act.add_transaction(
1437-
AddTransaction {
1438-
transaction,
1439-
broadcast_flag: true,
1440-
},
1441-
get_timestamp(),
1442-
)
1443-
.map_ok(move |_, _, _| ut),
1444-
)
1445-
}
1446-
}
1447-
Err(e) => {
1448-
log::error!("Failed to sign stake transaction: {}", e);
1449-
Either::Right(actix::fut::result(Err(e)))
1450-
}
1451-
});
1442+
if msg.dry_run {
1443+
Either::Right(actix::fut::result(Ok(ut)))
1444+
} else {
1445+
let transaction = Transaction::Unstake(ut.clone());
1446+
Either::Left(
1447+
act.add_transaction(
1448+
AddTransaction {
1449+
transaction,
1450+
broadcast_flag: true,
1451+
},
1452+
get_timestamp(),
1453+
)
1454+
.map_ok(move |_, _, _| ut),
1455+
)
1456+
}
1457+
}
1458+
Err(e) => {
1459+
log::error!("Failed to sign stake transaction: {}", e);
1460+
Either::Right(actix::fut::result(Err(e)))
1461+
}
1462+
});
14521463

1453-
Box::pin(fut)
1464+
Box::pin(fut)
1465+
}
1466+
}
14541467
}
14551468
}
14561469
}

node/src/actors/chain_manager/mining.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ pub fn build_block(
946946
let min_st_weight =
947947
StakeTransactionBody::new(vec![Input::default()], Default::default(), None).weight();
948948
let min_ut_weight =
949-
UnstakeTransactionBody::new(PublicKeyHash::default(), Default::default(), 0).weight();
949+
UnstakeTransactionBody::new(PublicKeyHash::default(), Default::default(), 0, 0).weight();
950950

951951
for vt_tx in transactions_pool.vt_iter() {
952952
let transaction_weight = vt_tx.weight();

schemas/witnet/witnet.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ message UnstakeTransactionBody {
314314
PublicKeyHash operator = 1;
315315
ValueTransferOutput withdrawal = 2;
316316
uint64 fee = 3;
317+
uint64 nonce = 4;
317318
}
318319

319320
message UnstakeTransaction {

validations/src/validations.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,20 @@ pub fn validate_unstake_transaction<'a>(
14071407
.into());
14081408
}
14091409

1410+
// TODO: modify this to enable delegated staking with multiple withdrawer addresses on a single validator
1411+
let nonce = stake_entry
1412+
.first()
1413+
.map(|stake| stake.value.nonce)
1414+
.unwrap()
1415+
.into();
1416+
if ut_tx.body.nonce != nonce {
1417+
return Err(TransactionError::UnstakeInvalidNonce {
1418+
used: ut_tx.body.nonce,
1419+
current: nonce,
1420+
}
1421+
.into());
1422+
}
1423+
14101424
staked_amount
14111425
}
14121426
Err(_) => {

0 commit comments

Comments
 (0)