Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feat-13-storage-pr…
Browse files Browse the repository at this point in the history
…ovider-pallet-implementation
  • Loading branch information
aidan46 committed Jun 27, 2024
2 parents 64d86c2 + 37753a5 commit 2260627
Show file tree
Hide file tree
Showing 10 changed files with 1,319 additions and 7 deletions.
50 changes: 49 additions & 1 deletion Cargo.lock

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

18 changes: 16 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ license-file = "LICENSE"
repository = "https://github.com/eigerco/polka-storage"

[workspace]
members = ["cli/polka-storage-provider", "node", "pallets/storage-provider", "primitives/cli", "runtime", "storage/mater", "storage/polka-index"]
members = [
"cli/polka-storage-provider",
"node",
"pallets/market",
"pallets/storage-provider",
"primitives/cli",
"runtime",
"storage/mater",
"storage/polka-index",
]
resolver = "2"

[profile.ci]
Expand All @@ -28,11 +37,12 @@ async-channel = "2.3.1"
async-stream = "0.3.5"
base64 = "0.22.1"
bitflags = "2.5.0"
blake2b_simd = { version = "1.0.2" }
byteorder = "1.5.0"
bytes = "1.6.0"
chrono = "0.4.38"
ciborium = "0.2.2"
cid = { version = "0.11.1" }
cid = { version = "0.11.1", default-features = false }
clap = { version = "4.5.3" }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
color-print = "0.3.4"
Expand All @@ -48,6 +58,7 @@ ipld-dagpb = "0.2.1"
itertools = "0.13.0"
jsonrpsee = { version = "0.22.5" }
log = { version = "0.4.21", default-features = false }
multihash-codetable = { version = "0.1.1", default-features = false }
polkavm = "0.9.3"
polkavm-derive = "0.9.1"
polkavm-linker = "0.9.2"
Expand Down Expand Up @@ -78,7 +89,9 @@ url = "2.5.0"
uuid = "1.8.0"

# Local

cli-primitives = { path = "primitives/cli" }
pallet-market = { path = "pallets/market", default-features = false }
pallet-storage-provider = { path = "pallets/storage-provider", default-features = false }
polka-storage-runtime = { path = "runtime" }

Expand All @@ -101,6 +114,7 @@ sc-tracing = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkad
sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.13.0" }
sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.13.0" }
sp-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.13.0", default-features = false }
sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.13.0", default-features = false }
sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.13.0", default-features = false }
sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.13.0" }
sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.13.0", default-features = false }
Expand Down
60 changes: 60 additions & 0 deletions pallets/market/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[package]
authors.workspace = true
description = "handles deal interactions between Storage Clients and Storage Providers"
edition.workspace = true
homepage.workspace = true
license-file.workspace = true
name = "pallet-market"
publish = false
repository.workspace = true
version = "0.0.0"

[lints]
workspace = true

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
cid = { workspace = true, default-features = false, features = ["scale-codec"] }
codec = { workspace = true, default-features = false, features = ["derive"] }
log = { workspace = true }
scale-info = { workspace = true, default-features = false, features = ["derive"] }

# frame deps
frame-benchmarking = { workspace = true, default-features = false, optional = true }
frame-support = { workspace = true, default-features = false }
frame-system = { workspace = true, default-features = false }
sp-arithmetic = { workspace = true, default-features = false }
sp-std = { workspace = true, default-features = false }

[dev-dependencies]
blake2b_simd = { workspace = true }
env_logger = { workspace = true }
multihash-codetable = { workspace = true, features = ["blake2b"] }
pallet-balances = { workspace = true, default-features = false }
sp-core = { workspace = true, default-features = false }
sp-io = { workspace = true }
sp-runtime = { workspace = true, default-features = false }

[features]
default = ["std"]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [
"cid/std",
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"pallet-balances/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
]
try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime"]
135 changes: 135 additions & 0 deletions pallets/market/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Market Pallet

## Overview

A [Storage Client][1] finds a [Storage Provider][2] and they negotiate off-chain.
Storage Provider Discovery is handled by [`Storage Provider Pallet`][4], it exposes a function to list all of the Storage Providers withing the system.
Price Negotiation is handled by **libp2p protocol** `Storage Query Protocol` and verified by `Storage Deal Protocol`, it happens off-chain.
The data is transferred between the parties and the Storage Provider adds [collateral][3], locks the fund and _publishes_ the deal on-chain.
The Storage Provider seals the data and _activates_ the deal.

[`Storage Query Protocol` and `Storage Deal Protocol`][5] are implemented by Storage Provider Node and are not the part of Market Pallet implementation.

## Data Structures

```rust
enum DealState<BlockNumber> {
/// Deal has been negotiated off-chain and is being proposed via `publish_storage_deals`.
Unpublished,
/// Deal has been accepted on-chain by both Storage Provider and Storage Client, it's waiting for activation.
Published,
/// Deal has been activated
Active(ActiveDealState<BlockNumber>)
}


/// State only related to the activated deal
/// Reference: <https://github.com/filecoin-project/builtin-actors/blob/17ede2b256bc819dc309edf38e031e246a516486/actors/market/src/deal.rs#L138>
struct ActiveDealState<BlockNumber> {
/// Sector in which given piece has been included
sector_number: SectorNumber,

/// At which block (time) the deal's sector has been activated.
sector_start_block: BlockNumber,
last_updated_block: Option<BlockNumber>,

/// When the deal was last slashed, can be never.
slash_block: Option<BlockNumber>
}

/// Reference: <https://github.com/filecoin-project/builtin-actors/blob/17ede2b256bc819dc309edf38e031e246a516486/actors/market/src/deal.rs#L93>
struct DealProposal<Address, Balance, BlockNumber> {
piece_cid: Cid,
piece_size: u64,
/// Storage Client's Account Id
client: Address,
/// Storage Provider's Account Id
provider: Address,

/// Arbitrary client chosen label to apply to the deal
label: String,

/// Nominal start block. Deal payment is linear between StartBlock and EndBlock,
/// with total amount StoragePricePerBlock * (EndBlock - StartBlock).
/// Storage deal must appear in a sealed (proven) sector no later than StartBlock,
/// otherwise it is invalid.
start_block: BlockNumber,
/// When the Deal is supposed to end.
end_block: BlockNumber,
/// `Deal` can be terminated early, by `on_sectors_terminate`.
/// Before that, a Storage Provider can payout it's earned fees by calling `on_settle_deal_payments`.
/// `on_settle_deal_payments` must know how much money it can payout, so it's related to the number of blocks (time) it was stored.
/// Reference <https://spec.filecoin.io/#section-systems.filecoin_markets.onchain_storage_market.storage_deal_states>
storage_price_per_block: Balance,

/// Amount of Balance (DOTs) Storage Provider stakes as Collateral for storing given `piece_cid`
/// There should be enough Balance added by `add_balance` by Storage Provider to cover it.
/// When the Deal fails/is terminated to early, this is the amount which get slashed.
provider_collateral: Balance,
/// Current [`DealState`].
/// It goes: `Unpublished` -> `Published` -> `Active`
state: DealState<BlockNumber>,
}

struct DealId(u64);

/// Proposals are deals that have been proposed and not yet cleaned up after expiry or termination.
/// They are either 'Published' or 'Active'.
type Proposals = StorageMap<DealId, DealProposal>;

/// Bookkeeping of funds deposited by Market Participants
type BalanceTable<T::Config> = StorageMap<T::AccountId, BalanceEntry<T::Currency>>

struct BalanceEntry<Currency> {
/// Funds available to be used in the market as Collateral or as Payment for Storage
/// They can be withdrawn at any time
deposit: Currency,
/// Funds locked as Collateral or as Payment for Storage
/// They cannot be withdrawn unless a sector is terminated
/// Subject to slashing when a Storage Provider misbehaves
locked: Currency,
}

/// After Storage Client has successfully negotiated with the Storage Provider, they prepare a DealProposal,
/// sign it with their signature and send to the Storage Provider.
/// Storage Provider only after successful file transfer and verification of the data, calls an extrinsic `market.publish_storage_deals`.
/// The extrinsic call is signed by the Storage Provider and Storage Client's signature is in the message.
/// Based on that, Market Pallet can verify the signature and lock appropriate funds.
struct ClientDealProposal<Address, Currency, BlockNumber, OffchainSignature> {
pub proposal: DealProposal,
pub client_signature: OffchainSignature,
}

/// Used for activation of the deals for a given sector
struct SectorDeal<BlockNumber> {
sector_number: SectorNumber,
sector_expiry: BlockNumber,
deal_ids: Vec<DealId>
}
```

## Market Flow

1. Storage Client and Storage Provider negotiate a deal off-chain.
2. Storage Client calls `market.add_balance(amount: BalanceOf<T>)` to make sure it has enough funds in the market to cover the deal.
- amount is added to the `Market Pallet Account Id`, the AccountId is derived from PalletId.
3. Storage Provider calls `market.add_balance(amount: BalanceOf<T>)` to make sure it has enough funds to cover the deal collateral.
4. In between now and a call by Storage Provider to `market.publish_storage_deals(deals: Vec<DealProposal>)`, any party can call `market.withdraw_balance(amount: BalanceOf<T>)`.
5. Storage Provider calls `market.publish_storage_deals(deals: Vec<DealProposal>)`
- funds are locked in BalanceTable
- deals are now Published, if the Storage Provider does activate them within a timeframe, they're slashed.
6. Storage Provider seals the sector.
7. Storage Provider calls `market.activate_deals(sectors: Vec<SectorDeals>)`.
8. Storage Provider can call `market.settle_deal_payments(deals: Vec<DealId>)` to receive funds periodically, for the storage per blocks elapsed.
- the gas processing fees are on SP, so they call it as frequently as they want
- anyone can call this method, the caller is paying for the gas, so usually it's only in Storage Provider interest to do that
9. Storage Provider calls `market.on_sector_terminate(block: BlockNumber, sectors: Vec<SectorNumber>)` to notify market that the sectors no longer exist.
- if storage was terminated to early, slash the SP, return the funds to the client
- else, just clean-up data structures used for deals
10. In the meantime, on each block authored, a Hook is executed that checks whether the `Published` deal have been activated. If they were supposed to be activated, but were not, Storage Provider is slashed and client refunded.

[1]: ../../docs/glossary.md#storage-user
[2]: ../../docs/glossary.md#storage-provider
[3]: ../../docs/glossary.md#collateral
[4]: ../storage-provider/DESIGN.md
[5]: https://spec.filecoin.io/#section-systems.filecoin_markets.storage_market.protocols
Loading

0 comments on commit 2260627

Please sign in to comment.