Skip to content

Commit ce7bcd8

Browse files
committed
fix: fix inconsistencies in node contracts states
1 parent 1c787e1 commit ce7bcd8

File tree

2 files changed

+66
-16
lines changed

2 files changed

+66
-16
lines changed

substrate-node/pallets/pallet-smart-contract/src/billing.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,14 @@ impl<T: Config> Pallet<T> {
362362
has_sufficient_fund: bool,
363363
current_block: u64,
364364
) -> DispatchResultWithPostInfo {
365+
// Add a defensive check to prevent a node contract on a rented node from transitioning to 'created' status without considering the state of the associated rent contract
366+
let rent_is_suspended = matches!(contract.contract_type, types::ContractData::NodeContract(_))
367+
&& ActiveRentContractForNode::<T>::get(contract.get_node_id())
368+
.and_then(|id| Contracts::<T>::get(&id))
369+
.map(|c| matches!(c.state, types::ContractState::GracePeriod(_)))
370+
.unwrap_or(false);
365371
match contract.state {
366-
types::ContractState::GracePeriod(_) if has_sufficient_fund => {
372+
types::ContractState::GracePeriod(_) if has_sufficient_fund && !rent_is_suspended => {
367373
// Manage transition from GracePeriod to Created
368374
log::info!("Contract {:?} is in grace period, but balance is recharged, moving to created state at block {:?}", contract.contract_id, current_block);
369375
Self::update_contract_state(contract, &types::ContractState::Created)?;
@@ -386,6 +392,26 @@ impl<T: Config> Pallet<T> {
386392
diff
387393
);
388394
if diff >= T::GracePeriod::get() {
395+
// Ensure associated node contracts have no reserved balance.
396+
// If none, proceed to move the contract to the 'deleted' state.
397+
// Otherwise, wait.
398+
let can_be_deleted = match contract.contract_type {
399+
types::ContractData::RentContract(_) => {
400+
let active_node_contracts = ActiveNodeContracts::<T>::get(contract.get_node_id());
401+
!active_node_contracts.iter().any(|id| {
402+
ContractPaymentState::<T>::get(id).map_or(false, |s| s.has_reserve())
403+
})
404+
}
405+
_ => true,
406+
};
407+
if !can_be_deleted {
408+
log::debug!(
409+
"Grace period expired, but one or more associated node contracts have held user funds. \
410+
Rent contract deletion will be delayed until funds are distributed to beneficiaries."
411+
);
412+
return Ok(().into());
413+
}
414+
389415
log::info!("Contract {:?} state changed to deleted at block {:?} due to an expired grace period.", contract.contract_id, current_block);
390416
Self::deposit_event(Event::ContractGracePeriodElapsed {
391417
contract_id: contract.contract_id,
@@ -734,6 +760,14 @@ impl<T: Config> Pallet<T> {
734760
for ctr_id in active_node_contracts {
735761
let mut ctr =
736762
Contracts::<T>::get(ctr_id).ok_or(Error::<T>::ContractNotExists)?;
763+
764+
// Defensive check to prevent a node contract on a rented node from transitioning to 'created' status if there's an overdraft (unsettled public IP rent).
765+
if ContractPaymentState::<T>::get(ctr_id).map_or_else(|| false, |ps| ps.has_overdraft())
766+
&& matches!(state, types::ContractState::Created)
767+
{
768+
continue;
769+
}
770+
737771
Self::update_contract_state(&mut ctr, &state)?;
738772

739773
match state {

substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,12 @@ impl<T: Config> Pallet<T> {
270270
// override values
271271
contract.contract_type = types::ContractData::NodeContract(node_contract);
272272

273-
let state = contract.state.clone();
274-
Self::update_contract_state(&mut contract, &state)?;
275-
273+
// Why we call update_contract_state if state can't be changed?
274+
// Obviously, we just depened on the update_contract_state for writing the contract to the storage, but we can do it here
275+
// let state = contract.state.clone();
276+
// Self::update_contract_state(&mut contract, &state)?;
277+
Contracts::<T>::insert(&contract.contract_id, contract.clone());
278+
276279
Self::deposit_event(Event::ContractUpdated(contract));
277280

278281
Ok(().into())
@@ -391,24 +394,35 @@ impl<T: Config> Pallet<T> {
391394
fn remove_active_node_contract(node_id: u32, contract_id: u64) {
392395
let mut contracts = ActiveNodeContracts::<T>::get(&node_id);
393396

394-
match contracts.iter().position(|id| id == &contract_id) {
395-
Some(index) => {
396-
contracts.remove(index);
397-
}
398-
None => (),
399-
};
397+
let initial_len = contracts.len();
398+
399+
contracts.retain(|&id| id != contract_id);
400400

401-
ActiveNodeContracts::<T>::insert(&node_id, &contracts);
401+
// Only update the storage if the list length has changed, meaning a contract was removed
402+
if contracts.len() != initial_len {
403+
ActiveNodeContracts::<T>::insert(&node_id, contracts);
404+
}
402405
}
403406

404407
// Helper function that updates the contract state and manages storage accordingly
405408
pub fn update_contract_state(
406409
contract: &mut types::Contract<T>,
407410
state: &types::ContractState,
408411
) -> DispatchResultWithPostInfo {
409-
// update the state and save the contract
410-
contract.state = state.clone();
411-
Contracts::<T>::insert(&contract.contract_id, contract.clone());
412+
// Update the state and save the contract only when necessary.
413+
if contract.state != *state {
414+
match (&contract.state, state) {
415+
// Transition from GracePeriod(x) to GracePeriod(y) is not allowed
416+
(types::ContractState::GracePeriod(_), types::ContractState::GracePeriod(_)) => {
417+
log::info!("Contract {:?} already in grace period, not changing to {:?} state", contract.contract_id, state);
418+
},
419+
// All other transitions are allowed
420+
_ => {
421+
contract.state = state.clone();
422+
Contracts::<T>::insert(&contract.contract_id, contract.clone());
423+
}
424+
}
425+
}
412426

413427
// if the contract is a name or rent contract, nothing to do left here
414428
match contract.contract_type {
@@ -423,6 +437,7 @@ impl<T: Config> Pallet<T> {
423437

424438
let mut contracts = ActiveNodeContracts::<T>::get(&node_contract.node_id);
425439

440+
let original_len = contracts.len();
426441
match contracts.iter().position(|id| id == &contract.contract_id) {
427442
Some(index) => {
428443
// if the new contract state is delete, remove the contract id from the map
@@ -437,8 +452,9 @@ impl<T: Config> Pallet<T> {
437452
}
438453
}
439454
};
440-
441-
ActiveNodeContracts::<T>::insert(&node_contract.node_id, &contracts);
455+
if contracts.len() != original_len {
456+
ActiveNodeContracts::<T>::insert(&node_contract.node_id, &contracts);
457+
};
442458

443459
Ok(().into())
444460
}

0 commit comments

Comments
 (0)