From 1a3e67f41b5877e265558ed192be7bf10a3a40aa Mon Sep 17 00:00:00 2001
From: Shawn Tabrizi <shawntabrizi@gmail.com>
Date: Wed, 24 Mar 2021 12:22:20 +0100
Subject: [PATCH] Sample Auction Winners Every `SampleLength` Blocks (#2670)

* initial

* add test

* Update lib.rs

* Update lib.rs
---
 polkadot/runtime/common/src/auctions.rs       | 149 ++++++++++++++++--
 .../runtime/common/src/integration_tests.rs   |   2 +
 polkadot/runtime/rococo/src/lib.rs            |  10 +-
 polkadot/runtime/westend/src/lib.rs           |   2 +
 4 files changed, 149 insertions(+), 14 deletions(-)

diff --git a/polkadot/runtime/common/src/auctions.rs b/polkadot/runtime/common/src/auctions.rs
index 2619fa519ce..826ddc8e814 100644
--- a/polkadot/runtime/common/src/auctions.rs
+++ b/polkadot/runtime/common/src/auctions.rs
@@ -60,6 +60,11 @@ pub trait Config: frame_system::Config {
 	/// The number of blocks over which an auction may be retroactively ended.
 	type EndingPeriod: Get<Self::BlockNumber>;
 
+	/// The length of each sample to take during the ending period.
+	///
+	/// EndingPeriod / SampleLength = Total # of Samples
+	type SampleLength: Get<Self::BlockNumber>;
+
 	/// Something that provides randomness in the runtime.
 	type Randomness: Randomness<Self::Hash, Self::BlockNumber>;
 
@@ -99,9 +104,9 @@ decl_storage! {
 		pub ReservedAmounts get(fn reserved_amounts):
 			map hasher(twox_64_concat) (T::AccountId, ParaId) => Option<BalanceOf<T>>;
 
-		/// The winning bids for each of the 10 ranges at each block in the final Ending Period of
-		/// the current auction. The map's key is the 0-based index into the Ending Period. The
-		/// first block of the ending period is 0; the last is `EndingPeriod - 1`.
+		/// The winning bids for each of the 10 ranges at each sample in the final Ending Period of
+		/// the current auction. The map's key is the 0-based index into the Sample Size. The
+		/// first sample of the ending period is 0; the last is `Sample Size - 1`.
 		pub Winning get(fn winning): map hasher(twox_64_concat) T::BlockNumber => Option<WinningData<T>>;
 	}
 }
@@ -296,11 +301,12 @@ impl<T: Config> Auctioneer for Module<T> {
 		Self::do_new_auction(duration, lease_period_index)
 	}
 
+	// Returns whether the auction is ending, and which sample number we are on.
 	fn is_ending(now: Self::BlockNumber) -> Option<Self::BlockNumber> {
 		if let Some((_, early_end)) = AuctionInfo::<T>::get() {
 			if let Some(after_early_end) = now.checked_sub(&early_end) {
 				if after_early_end < T::EndingPeriod::get() {
-					return Some(after_early_end)
+					return Some(after_early_end / T::SampleLength::get())
 				}
 			}
 		}
@@ -388,7 +394,7 @@ impl<T: Config> Module<T> {
 		let range = SlotRange::new_bounded(first_lease_period, first_slot, last_slot)?;
 		// Range as an array index.
 		let range_index = range as u8 as usize;
-		// The offset into the auction ending set.
+		// The offset into the ending samples of the auction.
 		let offset = Self::is_ending(frame_system::Pallet::<T>::block_number()).unwrap_or_default();
 		// The current winning ranges.
 		let mut current_winning = Winning::<T>::get(offset)
@@ -481,7 +487,8 @@ impl<T: Config> Module<T> {
 					// Our random seed was known only after the auction ended. Good to use.
 					let raw_offset_block_number = <T::BlockNumber>::decode(&mut raw_offset.as_ref())
 						.expect("secure hashes should always be bigger than the block number; qed");
-					let offset = raw_offset_block_number % ending_period;
+					let offset = (raw_offset_block_number % ending_period) / T::SampleLength::get();
+
 					let res = Winning::<T>::get(offset).unwrap_or_default();
 					let mut i = T::BlockNumber::zero();
 					while i < ending_period {
@@ -725,10 +732,6 @@ mod tests {
 		}
 	}
 
-	parameter_types!{
-		pub const EndingPeriod: BlockNumber = 3;
-	}
-
 	ord_parameter_types!{
 		pub const Six: u64 = 6;
 	}
@@ -758,10 +761,16 @@ mod tests {
 		}
 	}
 
+	parameter_types!{
+		pub static EndingPeriod: BlockNumber = 3;
+		pub static SampleLength: BlockNumber = 1;
+	}
+
 	impl Config for Test {
 		type Event = Event;
 		type Leaser = TestLeaser;
 		type EndingPeriod = EndingPeriod;
+		type SampleLength = SampleLength;
 		type Randomness = TestPastRandomness;
 		type InitiateOrigin = RootOrSix;
 		type WeightInfo = crate::auctions::TestWeightInfo;
@@ -1327,6 +1336,126 @@ mod tests {
 		});
 	}
 
+	// Here we will test that taking only 10 samples during the ending period works as expected.
+	#[test]
+	fn less_winning_samples_work() {
+		new_test_ext().execute_with(|| {
+			EndingPeriod::set(30);
+			SampleLength::set(10);
+
+			run_to_block(1);
+			assert_ok!(Auctions::new_auction(Origin::signed(6), 9, 11));
+			let para_1 = ParaId::from(1);
+			let para_2 = ParaId::from(2);
+			let para_3 = ParaId::from(3);
+
+			// Make bids
+			assert_ok!(Auctions::bid(Origin::signed(1), para_1, 1, 11, 14, 10));
+			assert_ok!(Auctions::bid(Origin::signed(2), para_2, 1, 13, 14, 20));
+
+			assert_eq!(Auctions::is_ending(System::block_number()), None);
+			assert_eq!(Auctions::winning(0), Some([
+				None,
+				None,
+				None,
+				Some((1, para_1, 10)),
+				None,
+				None,
+				None,
+				None,
+				Some((2, para_2, 20)),
+				None,
+			]));
+
+			run_to_block(9);
+			assert_eq!(Auctions::is_ending(System::block_number()), None);
+
+			run_to_block(10);
+			assert_eq!(Auctions::is_ending(System::block_number()), Some(0));
+			assert_eq!(Auctions::winning(0), Some([
+				None,
+				None,
+				None,
+				Some((1, para_1, 10)),
+				None,
+				None,
+				None,
+				None,
+				Some((2, para_2, 20)),
+				None,
+			]));
+
+			// New bids update the current winning
+			assert_ok!(Auctions::bid(Origin::signed(3), para_3, 1, 14, 14, 30));
+			assert_eq!(Auctions::winning(0), Some([
+				None,
+				None,
+				None,
+				Some((1, para_1, 10)),
+				None,
+				None,
+				None,
+				None,
+				Some((2, para_2, 20)),
+				Some((3, para_3, 30)),
+			]));
+
+			run_to_block(20);
+			assert_eq!(Auctions::is_ending(System::block_number()), Some(1));
+			assert_eq!(Auctions::winning(1), Some([
+				None,
+				None,
+				None,
+				Some((1, para_1, 10)),
+				None,
+				None,
+				None,
+				None,
+				Some((2, para_2, 20)),
+				Some((3, para_3, 30)),
+			]));
+			run_to_block(25);
+			// Overbid mid sample
+			assert_ok!(Auctions::bid(Origin::signed(3), para_3, 1, 13, 14, 30));
+			assert_eq!(Auctions::winning(1), Some([
+				None,
+				None,
+				None,
+				Some((1, para_1, 10)),
+				None,
+				None,
+				None,
+				None,
+				Some((3, para_3, 30)),
+				Some((3, para_3, 30)),
+			]));
+
+			run_to_block(30);
+			assert_eq!(Auctions::is_ending(System::block_number()), Some(2));
+			assert_eq!(Auctions::winning(2), Some([
+				None,
+				None,
+				None,
+				Some((1, para_1, 10)),
+				None,
+				None,
+				None,
+				None,
+				Some((3, para_3, 30)),
+				Some((3, para_3, 30)),
+			]));
+
+			set_last_random(H256::from([254; 32]), 40);
+			run_to_block(40);
+			// Auction ended and winner selected
+			assert!(!Auctions::is_in_progress());
+			assert_eq!(leases(), vec![
+				((3.into(), 13), LeaseData { leaser: 3, amount: 30 }),
+				((3.into(), 14), LeaseData { leaser: 3, amount: 30 }),
+			]);
+		});
+	}
+
 	#[test]
 	fn can_cancel_auction() {
 		new_test_ext().execute_with(|| {
diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs
index 82c35b85a67..4cc5b5154a7 100644
--- a/polkadot/runtime/common/src/integration_tests.rs
+++ b/polkadot/runtime/common/src/integration_tests.rs
@@ -183,12 +183,14 @@ impl paras_registrar::Config for Test {
 
 parameter_types! {
 	pub const EndingPeriod: BlockNumber = 10;
+	pub const SampleLength: BlockNumber = 1;
 }
 
 impl auctions::Config for Test {
 	type Event = Event;
 	type Leaser = Slots;
 	type EndingPeriod = EndingPeriod;
+	type SampleLength = SampleLength;
 	type Randomness = TestRandomness<Self>;
 	type InitiateOrigin = EnsureRoot<AccountId>;
 	type WeightInfo = crate::auctions::TestWeightInfo;
diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs
index 3b78dc522c2..5e7daa346dc 100644
--- a/polkadot/runtime/rococo/src/lib.rs
+++ b/polkadot/runtime/rococo/src/lib.rs
@@ -612,10 +612,6 @@ impl paras_registrar::Config for Runtime {
 	type WeightInfo = paras_registrar::TestWeightInfo;
 }
 
-parameter_types! {
-	pub const EndingPeriod: BlockNumber = 15 * MINUTES;
-}
-
 // A wrapper around `babe::CurrentBlockRandomness` that does not return `Option<Random>`.
 pub struct CurrentBlockRandomness;
 
@@ -634,10 +630,16 @@ impl Randomness<Hash, BlockNumber> for CurrentBlockRandomness {
 	}
 }
 
+parameter_types! {
+	pub const EndingPeriod: BlockNumber = 15 * MINUTES;
+	pub const SampleLength: BlockNumber = 1;
+}
+
 impl auctions::Config for Runtime {
 	type Event = Event;
 	type Leaser = Slots;
 	type EndingPeriod = EndingPeriod;
+	type SampleLength = SampleLength;
 	type Randomness = CurrentBlockRandomness;
 	type InitiateOrigin = EnsureRoot<AccountId>;
 	type WeightInfo = auctions::TestWeightInfo;
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 6037df12276..0d187d0be23 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -755,12 +755,14 @@ impl paras_registrar::Config for Runtime {
 
 parameter_types! {
 	pub const EndingPeriod: BlockNumber = 1 * HOURS;
+	pub const SampleLength: BlockNumber = 1 * MINUTES;
 }
 
 impl auctions::Config for Runtime {
 	type Event = Event;
 	type Leaser = Slots;
 	type EndingPeriod = EndingPeriod;
+	type SampleLength = SampleLength;
 	type Randomness = pallet_babe::RandomnessFromOneEpochAgo<Runtime>;
 	type InitiateOrigin = EnsureRoot<AccountId>;
 	type WeightInfo = auctions::TestWeightInfo;
-- 
GitLab