Skip to content

Commit 7d4aef9

Browse files
committed
feat(node): reduce duplicated stake and unstake validation code by relying on the validation module
1 parent f58517e commit 7d4aef9

File tree

2 files changed

+38
-98
lines changed

2 files changed

+38
-98
lines changed

data_structures/src/chain/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3190,7 +3190,7 @@ impl TransactionsPool {
31903190
}
31913191

31923192
/// Remove stake transactions that would result in overstaking on a validator
3193-
pub fn remove_overstake_transactions(&mut self, transactions: Vec<Hash>) {
3193+
pub fn remove_invalid_stake_transactions(&mut self, transactions: Vec<Hash>) {
31943194
for st_tx_hash in transactions.iter() {
31953195
if let Some(st_tx) = self
31963196
.st_transactions

node/src/actors/chain_manager/mining.rs

+37-97
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ use witnet_validations::{
6363
validations::{
6464
block_reward, calculate_liars_and_errors_count_from_tally, dr_transaction_fee,
6565
merkle_tree_root, run_tally, st_transaction_fee, tally_bytes_on_encode_error,
66-
update_utxo_diff, vt_transaction_fee,
66+
update_utxo_diff, validate_stake_transaction, validate_unstake_transaction,
67+
vt_transaction_fee,
6768
},
6869
};
6970

@@ -1180,7 +1181,7 @@ pub fn build_block(
11801181

11811182
if protocol_version > V1_7 {
11821183
let max_st_weight = consensus_constants_wit2.get_maximum_stake_block_weight(epoch);
1183-
let mut overstake_transactions = Vec::<Hash>::new();
1184+
let mut invalid_stake_transactions = Vec::<Hash>::new();
11841185
let mut included_validators = HashSet::<PublicKeyHash>::new();
11851186
for st_tx in transactions_pool.st_iter() {
11861187
let validator_pkh = st_tx.body.output.authorization.public_key.pkh();
@@ -1192,37 +1193,27 @@ pub fn build_block(
11921193
continue;
11931194
}
11941195

1195-
// If a set of staking transactions is sent simultaneously to the transactions pool using a staking amount smaller
1196-
// than 'max_stake' they can all be accepted since they do not introduce overstaking yet. However, accepting all of
1197-
// them in subsequent blocks could violate the 'max_stake' rule. Thus we still need to check that we do not include
1198-
// all these staking transactions in a block so we do not produce an invalid block.
1199-
let stakes_key = QueryStakesKey::Key(StakeKey {
1200-
validator: st_tx.body.output.key.validator,
1201-
withdrawer: st_tx.body.output.key.withdrawer,
1202-
});
1196+
// Revalidate stake transaction since concurrent stake transactions could have invalidated the transaction
1197+
// we want to include here which would result in producing an invalid block.
1198+
let min_stake = consensus_constants_wit2.get_validator_min_stake_nanowits(epoch);
12031199
let max_stake = consensus_constants_wit2.get_validator_max_stake_nanowits(epoch);
1204-
match stakes.query_stakes(stakes_key) {
1205-
Ok(stake_entry) => {
1206-
// TODO: modify this to enable delegated staking with multiple withdrawer addresses on a single validator
1207-
let staked_amount: u64 = stake_entry
1208-
.first()
1209-
.map(|stake| stake.value.coins)
1210-
.unwrap()
1211-
.into();
1212-
if st_tx.body.output.value + staked_amount > max_stake {
1213-
overstake_transactions.push(st_tx.hash());
1214-
continue;
1215-
}
1216-
}
1217-
Err(_) => {
1218-
// This should never happen since a staking transaction to a non-existing (validator, withdrawer) pair
1219-
// with a value higher than 'max_stake' should not have been accepted in the transactions pool.
1220-
if st_tx.body.output.value > max_stake {
1221-
overstake_transactions.push(st_tx.hash());
1222-
continue;
1223-
}
1224-
}
1225-
};
1200+
if let Err(e) = validate_stake_transaction(
1201+
st_tx,
1202+
utxo_diff,
1203+
epoch,
1204+
epoch_constants,
1205+
&vec![],
1206+
stakes,
1207+
min_stake,
1208+
max_stake,
1209+
) {
1210+
log::warn!(
1211+
"Cannot build block with stake transaction {}: {}",
1212+
ut_tx.hash(),
1213+
e
1214+
);
1215+
invalid_stake_transactions.push(st_tx.hash());
1216+
}
12261217

12271218
let transaction_weight = st_tx.weight();
12281219
let transaction_fee =
@@ -1262,7 +1253,7 @@ pub fn build_block(
12621253
included_validators.insert(validator_pkh);
12631254
}
12641255

1265-
transactions_pool.remove_overstake_transactions(overstake_transactions);
1256+
transactions_pool.remove_invalid_stake_transactions(invalid_stake_transactions);
12661257
} else {
12671258
transactions_pool.clear_stake_transactions();
12681259
}
@@ -1281,71 +1272,20 @@ pub fn build_block(
12811272
continue;
12821273
}
12831274

1284-
// If a set of unstaking transactions is sent simultaneously to the transactions pool using an amount which leaves
1285-
// more than 'min_stake' staked they can all be accepted since they do not introduce understaking yet. However,
1286-
// accepting all of them in subsequent blocks could violate the 'min_stake' rule. Thus we still need to check that
1287-
// we do not include all these unstaking transactions in a block so we do not produce an invalid block.
1288-
let stakes_key = QueryStakesKey::Key(StakeKey {
1289-
validator: ut_tx.body.operator,
1290-
withdrawer: ut_tx.body.withdrawal.pkh,
1291-
});
1275+
// Revalidate unstake transaction since concurrent unstake transactions could have invalidated the transaction
1276+
// we want to include here which would result in producing an invalid block.
12921277
let min_stake = consensus_constants_wit2.get_validator_min_stake_nanowits(epoch);
1293-
match stakes.query_stakes(stakes_key) {
1294-
Ok(stake_entry) => {
1295-
// TODO: modify this to enable delegated staking with multiple withdrawer addresses on a single validator
1296-
let staked_amount: u64 = stake_entry
1297-
.first()
1298-
.map(|stake| stake.value.coins)
1299-
.unwrap()
1300-
.into();
1301-
if staked_amount - ut_tx.body.withdrawal.value - ut_tx.body.fee < min_stake {
1302-
log::info!(
1303-
"Unstaking with {} would result in understaking",
1304-
ut_tx.hash()
1305-
);
1306-
invalid_unstake_transactions.push(ut_tx.hash());
1307-
continue;
1308-
}
1309-
}
1310-
Err(_) => {
1311-
// Not finding a stake entry is possible if there are two concurrent unstake transactions where at least
1312-
// one of them unstakes all balance before the second one is included in a block. In that case, remove
1313-
// the latter one from our transaction pool.
1314-
log::info!("Cannot process unstake transaction {} since the full balance was already unstaked",
1315-
ut_tx.hash(),
1316-
);
1317-
invalid_unstake_transactions.push(ut_tx.hash());
1318-
continue;
1319-
}
1320-
};
1321-
1322-
// Double check the nonce before building a block. A nonce that was valid when the transaction was received
1323-
// and inserted into the transaction pool once validated, may not be anymore when we build the actual block
1324-
// due to concurrent transactions.
1325-
let key = StakeKey {
1326-
validator: ut_tx.body.operator,
1327-
withdrawer: ut_tx.body.withdrawal.pkh,
1328-
};
1329-
match stakes.query_nonce(key) {
1330-
Ok(nonce) => {
1331-
if ut_tx.body.nonce != nonce {
1332-
log::info!("Cannot process unstake transaction {} since the nonce does not match: {} != {}",
1333-
ut_tx.hash(),
1334-
ut_tx.body.nonce,
1335-
nonce,
1336-
);
1337-
invalid_unstake_transactions.push(ut_tx.hash());
1338-
continue;
1339-
}
1340-
}
1341-
Err(_) => {
1342-
log::info!("Cannot process unstake transaction {} since the associated stake entry does not exist anymore",
1343-
ut_tx.hash()
1344-
);
1345-
invalid_unstake_transactions.push(ut_tx.hash());
1346-
continue;
1347-
}
1348-
};
1278+
let unstake_delay = consensus_constants_wit2.get_unstaking_delay_seconds(epoch);
1279+
if let Err(e) =
1280+
validate_unstake_transaction(ut_tx, epoch, stakes, min_stake, unstake_delay)
1281+
{
1282+
log::warn!(
1283+
"Cannot build block with unstake transaction {}: {}",
1284+
ut_tx.hash(),
1285+
e
1286+
);
1287+
invalid_unstake_transactions.push(ut_tx.hash());
1288+
}
13491289

13501290
let transaction_weight = ut_tx.weight();
13511291
let new_ut_weight = ut_weight.saturating_add(transaction_weight);

0 commit comments

Comments
 (0)