Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions substrate/frame/broker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod benchmarking;
mod core_mask;
mod coretime_interface;
mod dispatchable_impls;
mod market;
#[cfg(test)]
mod mock;
mod nonfungible_impl;
Expand All @@ -45,6 +46,7 @@ pub use weights::WeightInfo;
pub use adapt_price::*;
pub use core_mask::*;
pub use coretime_interface::*;
pub use market::*;
pub use types::*;

extern crate alloc;
Expand Down Expand Up @@ -97,6 +99,9 @@ pub mod pallet {
/// The algorithm to determine the next price on the basis of market performance.
type PriceAdapter: AdaptPrice<BalanceOf<Self>>;

/// The bulk coretime market algorithm implementation.
type Market: Market<BalanceOf<Self>, RelayBlockNumberOf<Self>, Self::AccountId>;

/// Reversible conversion from local balance to Relay-chain balance. This will typically be
/// the `Identity`, but provided just in case the chains use different representations.
type ConvertBalance: Convert<BalanceOf<Self>, RelayBalanceOf<Self>>
Expand Down
96 changes: 96 additions & 0 deletions substrate/frame/broker/src/market.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::PotentialRenewalId;

/// Trait representig generic market logic.
///
/// The assumptions for this generic market are:
/// - Every order will either create a bid or will be resolved immediately.
/// - There're two types of orders: bulk coretime purchase and bulk coretime renewal.
/// - Coretime regions are fungible.
pub trait Market<Balance, BlockNumber, AccountId> {
type Error;
/// Internal market state that must be preserved between the method calls. If the market logic
/// allows creating bids they should be stored there as well as the bid structure depends on the
/// market implementation.
type State;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you guess would be in this state? Just a vector or some more complicated struct?
I am asking to figure out the storage layout that you could use. Normally we use the FRAME primitives like StorageMap and StorageValue, but these are imperative - not functional - like here.

So if we pass around a big struct and then write it to a StorageValue it could be suboptimal since it has to read and write that big struct every time even just for small changes.
Alternatively you could use a State interface with functions like insert_order or get_order, or make the interface imperative without State being passed around. But I get that having it functional is cleaner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed in DM, the conclusion is that it can be removed and the pallet_broker may implement Market trait instead of exposing it in the config. In this setup the market functions can directly read/write the pallet storage

/// Unique ID assigned to every bid.
type BidId;

/// Place an order for bulk coretime purchase.
///
/// This method may or may not create a bid, according to the market rules.
///
/// - `since_timeslice_start` - amount of blocks passed since the current timeslice start
/// - `amount` - maximum price which the buyer is willing to pay (or None if it's defined by the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DQ but how much coretime do they buy for this amount? Like 1 region, or 2 or 10?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to specify that only one region is bought explicitly in the function name or to add a parameter specifying how much regions we want to buy?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just in the doc is enough.

/// market itself)
/// - `state` - market state, the caller is responsible for storing it
fn place_order(
since_timeslice_start: BlockNumber,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What block number is this? Relay chain or local coretime chain?
Maybe its overkill, but one way to do this would be to create some MarketTime (or whatever name) that uses the same RelayChainBlockNumberProvider as the CoretimeInterface and the TimeslicePeriod and calculates this instead of forcing the caller to do it correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a relay chain block as both old and new implementations depend on it, so I guess if that would need abstraction in the future the appropriate adjustments can be made

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then maybe change the typename to RelayBlockNumber, its quite easy to confuse 😆

who: AccountId,
amount: Option<Balance>,
state: &mut Self::State,
) -> Result<PlaceOrderOutcome<Balance, Self::BidId>, Self::Error>;

/// Place an order for bulk coretime renewal.
///
/// This method may or may not create a bid, according to the market rules.
///
/// - `since_timeslice_start` - amount of blocks passed since the current timeslice start
/// - `buying_price` - price which was paid for this region the last time it was sold
/// - `state` - market state, the caller is responsible for storing it
fn place_renewal_order(
since_timeslice_start: BlockNumber,
who: AccountId,
renewal: PotentialRenewalId,
buying_price: Balance,
state: &mut Self::State,
) -> Result<PlaceRenewalOrderOutcome<Balance, Self::BidId>, Self::Error>;

/// Close the bid given its `BidId`.
///
/// If the market logic allows creating the bids this method allows to close any bids (either
/// forcefully if `maybe_check_owner` is `None` or checking the bid owner if it's `Some`).
fn close_bid(
id: Self::BidId,
maybe_check_owner: Option<AccountId>,
state: &mut Self::State,
) -> Result<(), Self::Error>;

/// Logic that gets called in `on_initialize` hook.
Copy link
Member

@ggwpez ggwpez Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or the coretime chain? Please describe how often it must be called (otherwise Polkadot will stop working) and how often it should be called (Polkadot keeps working but the market may be a bit slow).

This is important to distinguish since on_initialize should only be used for protocol level security relevant execution. Everything else can go into on_idle or on_poll to reduce risk of stalling the chain when there is an issue with the code that is being run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it can be rephrased to Logic that gets called periodically because any assumptions about the consequences of calling it not in the on_initialize cannot be made without looking into the implementation

fn tick(
since_timeslice_start: BlockNumber,
state: &mut Self::State,
) -> Result<Vec<TickAction<AccountId, Balance, Self::BidId>>, Self::Error>;
}

enum PlaceOrderOutcome<Balance, BidId> {
BidPlaced { id: BidId, bid_amount: Balance },
Sold { price: Balance },
}

enum PlaceRenewalOrderOutcome<Balance, BidId> {
BidPlaced { id: BidId, bid_amount: Balance },
Sold { price: Balance },
}

enum TickAction<AccountId, Balance, BidId> {
SellRegion { who: AccountId, refund: Balance },
RenewRegion { who: AccountId, renewal_id: PotentialRenewalId, refund: Balance },
CloseBid { id: BidId, amount: Balance },
}
Loading