Skip to content

Commit

Permalink
Allow modifying jobs ttl after submission task (#537)
Browse files Browse the repository at this point in the history
* feat: add rpc to get role key assigned to restaker

* allow modify ttl

* fix clippy
  • Loading branch information
1xstj authored Mar 7, 2024
1 parent 2e7b6c1 commit 4f1f6e1
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 39 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 16 additions & 7 deletions pallets/dkg/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
//
// You should have received a copy of the GNU General Public License
// along with Tangle. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::types::BalanceOf;
use frame_support::{pallet_prelude::DispatchResult, sp_runtime::Saturating};
use frame_system::pallet_prelude::BlockNumberFor;
use sp_core::Get;
use tangle_primitives::jobs::*;

use self::signatures_schemes::{
bls12_381::verify_bls12_381_signature,
ecdsa::{verify_ecdsa_signature, verify_generated_dkg_key_ecdsa},
schnorr_frost::verify_dkg_signature_schnorr_frost,
schnorr_sr25519::verify_schnorr_sr25519_signature,
};
use super::*;
use crate::types::BalanceOf;
use frame_support::{pallet_prelude::DispatchResult, sp_runtime::Saturating};
use frame_system::pallet_prelude::BlockNumberFor;
use scale_info::prelude::vec::Vec;
use sp_core::Get;
use tangle_primitives::jobs::*;

impl<T: Config> Pallet<T> {
/// Calculates the fee for a given job submission based on the provided fee information.
Expand Down Expand Up @@ -66,6 +66,15 @@ impl<T: Config> Pallet<T> {
}
}

pub fn calculate_result_extension_fee(
result: Vec<u8>,
_extension_time: BlockNumberFor<T>,
) -> BalanceOf<T> {
let fee_info = FeeInfo::<T>::get();
let storage_fee = fee_info.storage_fee_per_byte * (result.len() as u32).into();
fee_info.storage_fee_per_block * storage_fee
}

/// Verifies a given job verification information and dispatches to specific verification logic
/// based on the job type.
///
Expand Down
1 change: 1 addition & 0 deletions pallets/dkg/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fn set_fees_works() {
sig_validator_fee: 5,
refresh_validator_fee: 5,
storage_fee_per_byte: 1,
storage_fee_per_block: 0,
};

// Dispatch a signed extrinsic.
Expand Down
3 changes: 3 additions & 0 deletions pallets/dkg/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ pub struct FeeInfo<Balance: MaxEncodedLen> {

/// The storage fee per byts
pub storage_fee_per_byte: Balance,

// The storage fee per block
pub storage_fee_per_block: Balance,
}

impl<Balance: MaxEncodedLen> FeeInfo<Balance> {
Expand Down
52 changes: 43 additions & 9 deletions pallets/jobs/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use sp_runtime::traits::{Bounded, Zero};
use sp_std::vec;
use tangle_primitives::{
jobs::{
DKGTSSKeySubmissionResult, DKGTSSPhaseOneJobType, DigitalSignatureScheme, JobId, JobResult,
JobType,
DKGTSSKeySubmissionResult, DKGTSSPhaseOneJobType, DigitalSignatureScheme, FallbackOptions,
JobId, JobResult, JobType,
},
roles::{RoleType, ThresholdSignatureRoleType},
};
Expand All @@ -22,7 +22,8 @@ benchmarks! {
let job = JobSubmissionOf::<T> {
expiry: 100u32.into(),
ttl: 100u32.into(),
job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType { participants: vec![caller.clone(), caller.clone()], threshold: 1, permitted_caller: None, role_type : Default::default() }),
fallback: FallbackOptions::Destroy,
job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType { participants: vec![caller.clone(), caller.clone()].try_into().unwrap(), threshold: 1, permitted_caller: None, role_type : Default::default() }),
};

}: _(RawOrigin::Signed(caller.clone()), job.clone())
Expand All @@ -35,16 +36,17 @@ benchmarks! {
let job = JobSubmissionOf::<T> {
expiry: 100u32.into(),
ttl: 100u32.into(),
job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType { participants: vec![caller.clone(), validator2], threshold: 1, permitted_caller: None, role_type : Default::default() }),
fallback: FallbackOptions::Destroy,
job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType { participants: vec![caller.clone(), validator2].try_into().unwrap(), threshold: 1, permitted_caller: None, role_type : Default::default() }),
};
let _ = Pallet::<T>::submit_job(RawOrigin::Signed(caller.clone()).into(), job);
let job_key: RoleType = RoleType::Tss(Default::default());
let job_id: JobId = 0;
let result = JobResult::DKGPhaseOne(DKGTSSKeySubmissionResult {
signatures: vec![],
signatures: vec![].try_into().unwrap(),
threshold: 3,
participants: vec![],
key: vec![],
participants: vec![].try_into().unwrap(),
key: vec![].try_into().unwrap(),
signature_scheme: DigitalSignatureScheme::Ecdsa
});
}: _(RawOrigin::Signed(caller.clone()), job_key.clone(), job_id.clone(), result)
Expand All @@ -67,13 +69,45 @@ benchmarks! {
let job = JobSubmissionOf::<T> {
expiry: 100u32.into(),
ttl: 100u32.into(),
job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType { participants: vec![caller.clone(), validator2, validator3], threshold: 2, permitted_caller: None, role_type : Default::default() }),
fallback: FallbackOptions::Destroy,
job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType { participants: vec![caller.clone(), validator2, validator3].try_into().unwrap(), threshold: 2, permitted_caller: None, role_type : Default::default() }),
};
let _ = Pallet::<T>::submit_job(RawOrigin::Signed(caller.clone()).into(), job);
let job_key: RoleType = RoleType::Tss(Default::default());
let job_id: JobId = 0;
}: _(RawOrigin::Signed(caller.clone()), job_key.clone(), job_id.clone(), caller.clone(), ValidatorOffenceType::Inactivity, vec![])

// Benchmark extend_job_result_ttl function
extend_job_result_ttl {
let caller: T::AccountId = account("caller", 0, 0);
let validator2: T::AccountId = account("caller", 0, 1);
let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
let job = JobSubmissionOf::<T> {
expiry: 100u32.into(),
ttl: 100u32.into(),
fallback: FallbackOptions::Destroy,
job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType { participants: vec![caller.clone(), validator2].try_into().unwrap(), threshold: 1, permitted_caller: None, role_type : Default::default() }),
};
let _ = Pallet::<T>::submit_job(RawOrigin::Signed(caller.clone()).into(), job);
let job_key: RoleType = RoleType::Tss(Default::default());
let job_id: JobId = 0;
let result = JobResult::DKGPhaseOne(DKGTSSKeySubmissionResult {
signatures: vec![].try_into().unwrap(),
threshold: 3,
participants: vec![].try_into().unwrap(),
key: vec![].try_into().unwrap(),
signature_scheme: DigitalSignatureScheme::Ecdsa
});
let _ = Pallet::<T>::submit_job_result(RawOrigin::Signed(caller.clone()).into(), job_key.clone(), job_id.clone(), result);
}: _(RawOrigin::Signed(caller.clone()), RoleType::Tss(Default::default()), job_id.clone(), 10u32.into())
}

// Define the module and associated types for the benchmarks
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Runtime,);
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(vec![
frame_benchmarking::account("caller", 0, 1),
frame_benchmarking::account("caller", 0, 2)
]),
crate::mock::Runtime,
);
57 changes: 57 additions & 0 deletions pallets/jobs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ pub mod module {
/// A job has been resubmitted, this is when a phase1 result has been discarded
/// and a new phase1 job is requested
JobReSubmitted { job_id: JobId, role_type: RoleType, details: JobInfoOf<T> },
/// A job result expiry time has been extended
JobResultExtended { job_id: JobId, role_type: RoleType, new_expiry: BlockNumberFor<T> },
}

#[pallet::storage]
Expand Down Expand Up @@ -661,5 +663,60 @@ pub mod module {
// TODO: emit events
Ok(())
}

/// Extend the time-to-live (TTL) of a job result.
///
/// This function allows extending the TTL of a job result for a specific role type and job
/// ID. The TTL represents the time until the result is considered expired.
///
/// - `origin`: The dispatch origin, usually a signed account.
/// - `role_type`: The type of role associated with the job result.
/// - `job_id`: The ID of the job whose result's TTL is to be extended.
/// - `extend_by`: The number of blocks by which to extend the TTL.
///
/// Weight: `T::WeightInfo::withdraw_rewards()` where `T` is the pallet configuration trait.
///
/// # Errors
///
/// - `JobNotFound`: The specified job ID does not exist.
/// - `ResultExpired`: The result is already expired.
/// - Transfer errors: Errors related to transferring fees.
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::withdraw_rewards())]
pub fn extend_job_result_ttl(
origin: OriginFor<T>,
role_type: RoleType,
job_id: JobId,
extend_by: BlockNumberFor<T>,
) -> DispatchResult {
let caller = ensure_signed(origin)?;

KnownResults::<T>::try_mutate(role_type, job_id, |result| -> DispatchResult {
let result = result.as_mut().ok_or(Error::<T>::JobNotFound)?;

let now = <frame_system::Pallet<T>>::block_number();
ensure!(result.ttl > now, Error::<T>::ResultExpired);

let extension_fee =
T::JobToFee::calculate_result_extension_fee(result.encode(), extend_by);

T::Currency::transfer(
&caller,
&Self::rewards_account_id(),
extension_fee,
ExistenceRequirement::KeepAlive,
)?;

result.ttl += extend_by;

Self::deposit_event(Event::JobResultExtended {
job_id,
role_type,
new_expiry: result.ttl,
});

Ok(())
})
}
}
}
8 changes: 8 additions & 0 deletions pallets/jobs/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ impl MockDKGPallet {
20
}
}

fn calculate_result_extension_fee(_result: Vec<u8>, _extension_time: BlockNumber) -> Balance {
20
}
}

pub struct MockZkSaasPallet;
Expand Down Expand Up @@ -133,6 +137,10 @@ impl JobToFee<AccountId, BlockNumber, MaxParticipants, MaxSubmissionLen> for Moc
},
}
}

fn calculate_result_extension_fee(result: Vec<u8>, extension_time: BlockNumber) -> Balance {
MockDKGPallet::calculate_result_extension_fee(result, extension_time)
}
}

pub struct MockMPCHandler;
Expand Down
80 changes: 80 additions & 0 deletions pallets/jobs/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1784,3 +1784,83 @@ fn test_validator_limit_is_counted_for_jobs_submission() {
);
});
}

#[test]
fn jobs_extend_result_works() {
new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| {
System::set_block_number(1);

let threshold_signature_role_type = ThresholdSignatureRoleType::ZengoGG20Secp256k1;

// all validators sign up in roles pallet
let profile = shared_profile();
for validator in [ALICE, BOB, CHARLIE, DAVE, EVE] {
assert_ok!(Roles::create_profile(
RuntimeOrigin::signed(mock_pub_key(validator)),
profile.clone(),
None
));
}

Balances::make_free_balance_be(&mock_pub_key(TEN), 100);

// should work when n = t
let submission = JobSubmission {
expiry: 10,
ttl: 200,
job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType {
participants: [ALICE, BOB, CHARLIE, DAVE, EVE]
.iter()
.map(|x| mock_pub_key(*x))
.collect::<Vec<_>>()
.try_into()
.unwrap(),
threshold: 5,
permitted_caller: Some(mock_pub_key(TEN)),
role_type: threshold_signature_role_type,
}),
fallback: FallbackOptions::Destroy,
};
assert_ok!(Jobs::submit_job(RuntimeOrigin::signed(mock_pub_key(TEN)), submission));

// submit a solution for this job
assert_ok!(Jobs::submit_job_result(
RuntimeOrigin::signed(mock_pub_key(TEN)),
RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1),
0,
JobResult::DKGPhaseOne(DKGTSSKeySubmissionResult {
signatures: vec![].try_into().unwrap(),
threshold: 3,
participants: vec![].try_into().unwrap(),
key: vec![].try_into().unwrap(),
signature_scheme: DigitalSignatureScheme::Ecdsa
})
));

// ensure storage is correctly setup
assert!(KnownResults::<Runtime>::get(
RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1),
0
)
.is_some());

// extend result by paying fee
assert_ok!(Jobs::extend_job_result_ttl(
RuntimeOrigin::signed(mock_pub_key(TEN)),
RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1),
0,
100
));

// should be charged 5 for creation and 20 for extension
assert_eq!(Balances::free_balance(mock_pub_key(TEN)), 100 - 25);

let known_result = KnownResults::<Runtime>::get(
RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1),
0,
)
.unwrap();

assert_eq!(known_result.ttl, 300);
});
}
4 changes: 4 additions & 0 deletions pallets/roles/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ impl JobToFee<AccountId, BlockNumber, MaxParticipants, MaxSubmissionLen> for Moc
) -> Balance {
Default::default()
}

fn calculate_result_extension_fee(_result: Vec<u8>, _extension_time: BlockNumber) -> Balance {
Default::default()
}
}

pub struct MockMPCHandler;
Expand Down
4 changes: 4 additions & 0 deletions precompiles/jobs/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ impl JobToFee<AccountId, BlockNumber, MaxParticipants, MaxSubmissionLen> for Moc
},
}
}

fn calculate_result_extension_fee(_result: Vec<u8>, _extension_time: BlockNumber) -> Balance {
Default::default()
}
}

pub struct MockRolesHandler;
Expand Down
15 changes: 15 additions & 0 deletions primitives/src/jobs/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ pub trait JobToFee<
fn job_to_fee(
job: &JobSubmission<AccountId, BlockNumber, MaxParticipants, MaxSubmissionLen>,
) -> Self::Balance;

/// Calculates the fee to extend an already existing result.
///
/// # Parameters
///
/// - `result`: A reference to the result stored onchain
/// - `extension_time` : The number of blocks to extend the job ttl by
///
/// # Returns
///
/// Returns the calculated fee as `Self::Balance`.
fn calculate_result_extension_fee(
result: Vec<u8>,
extension_time: BlockNumber,
) -> Self::Balance;
}

/// A trait that describes the job result verification.
Expand Down
Loading

0 comments on commit 4f1f6e1

Please sign in to comment.