In short
Solana is a single-shard, high-throughput public blockchain whose design goal is to scale with hardware (bandwidth, cores, SSDs) rather than by sharding or rollups, targeting 400 ms slots and tens of thousands of transactions per second with sub-rupee fees. Its distinguishing ideas are Proof of History (a verifiable clock), stake-weighted Tower BFT voting, and a strict separation of *code* (stateless programs) from *state* (accounts), which lets the Sealevel runtime execute non-conflicting transactions in parallel. On-chain programs are written mainly in Rust (compiled to SBF bytecode), usually through the Anchor framework, and power DEXs, stablecoin payments, NFTs, depins and consumer apps. In Indian university syllabi it appears in Blockchain Technology electives as the leading non-EVM / "high-performance chain" case study, contrasted with Ethereum and Solidity; in placements it is asked by Web3 and exchange employers (CoinDCX/Okto, CoinSwitch, Mudrex, ZebPay, Coinbase India, Polygon, and the many startups in the Superteam India network). Indian students meet it most often through Solana Foundation and Superteam hackathons, where Rust/Anchor is the expected skill.
Explained
1. Why a new architecture: the eight core components
- Proof of History (PoH): a *clock*, not a consensus rule. The leader runs a sequential SHA-256 chain (hash of previous hash, repeatedly), so the number of hashes between two events proves that real time passed. Sequential by construction, verifiable in parallel - a verifiable delay function.
- Ledger time is measured in ticks (currently 62,500 hashes per tick on mainnet, raised in stages from the original default of 12,500 as CPUs got faster at SHA-256; 64 ticks per slot), which fixes a slot at about 400 ms. One leader owns 4 consecutive slots (1.6 s).
- Tower BFT: a PoS variant of PBFT that uses PoH as a synchronous clock. Validators vote on forks; each vote doubles the lockout on that fork (2, 4, 8, 16 … slots), so a validator that keeps voting the same way cannot cheaply switch forks. Votes are weighted by stake; >2/3 of stake voting makes a block confirmed (optimistically confirmed); ~31+ confirmed descendants makes it rooted/finalized.
- Gulf Stream: there is no global mempool. Clients and RPCs forward signed transactions directly to the current and next few leaders (the leader schedule for an epoch is known in advance, stake-weighted). Reduces leader-switch latency and memory.
- Sealevel: the parallel execution runtime. Every instruction must declare in advance the accounts it reads and writes, so the scheduler can take read/write locks and run transactions that do not touch the same writable account simultaneously across CPU cores. This is the single biggest architectural difference from the EVM's sequential, single-threaded execution.
- Pipelining (TPU/TVU): the transaction processing unit is a four-stage pipeline - fetch → signature verification (GPU/SIMD friendly) → banking/execution → write to store - so stages overlap like a CPU pipeline.
- Turbine: block propagation. A block is split into shreds, protected with Reed-Solomon erasure coding, and relayed through a stake-weighted tree of fanout layers instead of being gossiped to everyone - bandwidth per node stays bounded.
- Cloudbreak: the accounts database, horizontally scaled across SSDs with memory-mapped files, designed for concurrent reads/writes rather than a single Merkle-Patricia trie. Archival: historical ledger is stored off the validator hot path (cloud warehouse stores, e.g. BigTable, and RPC providers); the original "Archivers/Proof-of-Replication" plan was dropped.
- Networking: transaction ingress uses QUIC with stake-weighted quality of service after the 2021-22 spam outages; local fee markets price congestion per writable account, so one hot NFT mint does not raise fees for everyone.
- Validator clients: Agave (maintained by Anza, the Rust client descended from solana-labs) and Firedancer / Frankendancer (Jump Crypto, written in C, on mainnet since 2025) - client diversity is now a stated resilience goal.
- Alpenglow is an approved consensus redesign (validator governance vote, SIMD-0326, 2025) replacing PoH-gated TowerBFT voting with Votor (fast 1-2 round finality) and Rotor (single-layer relay), targeting ~150 ms finality. Treat its exact rollout state as something to verify - it was still being staged at the time of writing.
2. The account model (the thing most students get wrong)
- Everything is an account: a flat key-value store keyed by a 32-byte Ed25519 public key (base58). Fields:
lamports(balance),owner(the program that may mutate it),data(byte array),executable(is it a program?),rent_epoch. - 1 SOL = 1,000,000,000 lamports (10⁹), i.e. 9 decimals. SOL has no hard cap; supply is roughly 600 million.
- Programs are stateless. A deployed program owns no variables; all state lives in separate data accounts it owns. Contrast: a Solidity contract's storage is inside the contract.
- Account types: system-owned wallets (owner = System Program), program (executable) accounts + their program-data account (upgradeable BPF loader), data accounts, sysvars (Clock, Rent, EpochSchedule…), native programs (System, Vote, Stake, BPF Loader, Compute Budget, Ed25519/Secp256k1 sig verify).
- Rent / rent-exemption: storage is paid for by locking lamports. Hold ≥ 2 years' rent and the account is rent-exempt (no ongoing charge); the deposit is fully refundable when the account is closed. Periodic rent collection has been deprecated - new accounts must be created rent-exempt. Max account data size 10 MiB;
reallocwithin an instruction is limited (≈10 KiB per instruction) and increases needed rent. - PDA (Program Derived Address): a deterministic address derived from
seeds + bump + program_idby hashing, that is forced off the Ed25519 curve - so no private key exists and only the owning program can sign for it viainvoke_signed. The canonical bump is the highest value (starting at 255, counting down) that yields an off-curve address. Up to 16 seeds, each ≤ 32 bytes. PDAs are how Solana does mappings:seeds = [b"vault", user.key()]is the equivalent ofmapping(address => Vault). - Transaction anatomy: signatures + message (header counts of signers/readonly, the account keys array, a recent blockhash, and instructions each pointing to a program index + account indices + opaque data). Hard limits: 1,232 bytes per transaction (IPv6 MTU 1,280 − 48), 64 account locks, blockhash valid for 150 slots (~1 minute). Versioned transactions (v0) + Address Lookup Tables let one transaction reference up to 256 addresses by index.
- Atomicity: all instructions in one transaction succeed or all revert - so a swap, a fee transfer and a log can be bundled. No partial state.
3. Rust programs: toolchain, limits, Anchor
- Programs compile from Rust to SBF/sBPF (Solana's eBPF derivative), loaded by the upgradeable BPF loader. Constraints:
no_std-style environment, no threads, no system calls, no files, essentially no floating point, 4 KB stack frame, default 32 KB heap, limited standard library; usesolana_programsyscalls for logging, hashing, clock and CPI. - Native entrypoint signature:
use solana_program::{account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, msg};
entrypoint!(process_instruction);
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!("counter program invoked");
Ok(())
}
- Anchor is the de facto framework: it generates deserialization, account validation, an 8-byte discriminator per account type and per instruction, an IDL (JSON interface, like an ABI) and TypeScript clients. Serialization is Borsh.
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.authority = ctx.accounts.user.key();
ctx.accounts.counter.count = 0;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let c = &mut ctx.accounts.counter;
c.count = c.count.checked_add(1).ok_or(ErrorCode::Overflow)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 32 + 8,
seeds = [b"counter", user.key().as_ref()], bump)]
pub counter: Account<'info, Counter>,
#[account(mut)] pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, seeds = [b"counter", authority.key().as_ref()], bump,
has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter { pub authority: Pubkey, pub count: u64 }
#[error_code] pub enum ErrorCode { #[msg("overflow")] Overflow }
- Read the
space = 8 + 32 + 8: 8 bytes discriminator + 32 bytes Pubkey + 8 bytes u64 = 48 bytes. - CPI (Cross-Program Invocation):
invoke(normal) andinvoke_signed(PDA signs); Anchor wraps it asCpiContext. Maximum CPI depth is 4, and the callee's accounts must be passed in by the caller's transaction - so Solana has no dynamic, unbounded call graph like Solidity's external calls (this is also why re-entrancy is far narrower: self-recursion is restricted and depth is capped). - Deployment / upgrades:
cargo build-sbfthensolana program deploy; the upgrade authority can replace bytecode (there is no proxy pattern needed), and setting the authority to none makes the program immutable. Development tools:solana-test-validator,anchor test, Bankrun/LiteSVM for fast tests, Devnet/Testnet/Mainnet-beta clusters. - Compute budget: work is metered in compute units (CU): default 200,000 CU per instruction, requestable up to 1,400,000 CU per transaction; a block has a cap in the tens of millions of CU (raised in stages by SIMDs - 48M, then higher), plus a per-writable-account write-lock cap. Exceeding the budget aborts the transaction.
4. Fees, staking and economics
- Base fee: 5,000 lamports per signature, charged by the fee payer (first signer) whether or not the transaction succeeds. 50% of the base fee is burned, 50% goes to the leader. Since SIMD-0096 (2025), 100% of priority fees go to the leader.
- Priority fee = compute-unit price (in micro-lamports per CU) × CU limit, set with ComputeBudget instructions. Because fee markets are local to each writable account, congestion is contained.
- Inflation schedule: started at 8% annual, disinflation 15% per year, terminal rate 1.5%. Rewards go to stakers and validators (commission); vote costs are a real validator expense. A proposal to make issuance market-based (SIMD-0228) was rejected by the 2025 validator vote.
- Staking: delegate from a stake account to a vote account; activation/deactivation take effect at epoch boundaries (warm-up/cool-down). Epoch = 432,000 slots ≈ 2 days. Liquid staking tokens (mSOL, jitoSOL, bnSOL-type products) and MEV-sharing clients are common; Jito-style tipping is the main MEV channel since there is no public mempool to front-run.
- Commitment levels for developers:
processed(leader has executed it),confirmed(optimistically confirmed by >2/3 stake - what most dApps use),finalized(rooted, what exchanges use). - Honest limitation: Solana's liveness record includes several full or partial mainnet-beta outages (2021-2024), caused by spam-driven resource exhaustion and a bug in a loader; QUIC, stake-weighted QoS, local fee markets and the second client (Firedancer) are the direct responses. Decentralisation critiques centre on hardware requirements (high-core CPU, 256-512 GB RAM class machines, NVMe) and stake concentration.
5. The SPL layer and application patterns
- SPL Token program: a mint account (82 bytes: supply, decimals, mint & freeze authority) plus one token account per (owner, mint) pair (165 bytes). The Associated Token Account (ATA) is the canonical PDA-derived token account for a wallet. Amounts are integers in the mint's smallest unit.
- Token-2022 adds extensions: transfer fees, confidential transfers, transfer hooks, interest-bearing tokens, non-transferable tokens, metadata pointer, permanent delegate. Choose it when you need those; classic SPL Token otherwise (wider support).
- NFTs: Metaplex Token Metadata (a mint with supply 1, decimals 0, plus a metadata PDA); compressed NFTs / state compression store the data in a concurrent Merkle tree with only the root on-chain, cutting mint cost by orders of magnitude - the standard answer to "how do you airdrop a million NFTs cheaply?"
- Common app patterns: PDA vault/escrow; order-book DEX (Serum/OpenBook lineage) vs AMM (Raydium/Orca style) vs aggregator (Jupiter style); perpetuals and lending with oracle price feeds (Pyth, Switchboard); payments and stablecoin rails (Solana Pay QR specification - directly relevant in India as a UPI-adjacent comparison); DePIN (Helium, Hivemapper) using low per-transaction cost for machine data.
- Comparison with Solidity/EVM (the other topic covers Solidity itself; what matters here is the contrast): no
msg.senderequivalent inside an account's storage - you checkSigner; no implicit storage mapping - you derive PDAs; noapprovepattern for SOL - you use delegates or PDAs; gas → compute units; ABI → IDL; proxy upgrades → native program upgrade authority.
6. Advantages and limitations (exam-style)
- Advantages: 400 ms slots and ~1-2 s practical confirmation; fees typically a small fraction of a rupee; parallel execution scales with cores; single global state (no cross-shard or cross-rollup bridging); atomic composability across all apps; state compression for cheap bulk data.
- Limitations: high validator hardware and bandwidth cost; historical outages; account-model learning curve and explicit account passing; Rust's difficulty versus Solidity; 1,232-byte transaction limit forces lookup tables and multi-transaction flows; declaring accounts in advance makes some dynamic logic awkward; rent deposits lock capital.
Key points
- Proof of History - a sequential SHA-256 hash chain used as a verifiable clock; it is *not* a consensus mechanism and does not replace stake-weighted voting.
- Tower BFT - PoS BFT with exponentially doubling fork lockouts, using PoH as the shared clock.
- Gulf Stream - mempool-less transaction forwarding to known future leaders.
- Sealevel - parallel execution enabled by declaring read/write accounts up front.
- Turbine - erasure-coded shred propagation through a stake-weighted fanout tree.
- Cloudbreak - SSD-scaled, memory-mapped accounts database.
- Slot = 400 ms target; epoch = 432,000 slots ≈ 2 days; leader holds 4 consecutive slots.
- Account - key-value record with lamports, owner program, data, executable flag; programs are stateless, state lives in accounts.
- Lamport - 10⁻⁹ SOL; all on-chain amounts are integers.
- Rent-exemption - two years' rent locked up front (refundable on close) makes an account permanent; 10 MiB max data.
- PDA - deterministic off-curve address signed by its owning program via
invoke_signed; the canonical bump is the highest valid one. - Discriminator - Anchor's 8-byte type tag that prevents one account type being passed where another is expected.
- CPI - program-to-program call, maximum depth 4, callee accounts supplied by the caller.
- Compute units - 200,000 default per instruction, 1.4 million maximum per transaction; priority fee = CU price × CU limit.
- Base fee - 5,000 lamports per signature, 50% burned, 50% to the leader; priority fees go fully to the leader.
- Inflation - 8% start, −15% per year, 1.5% terminal; staking rewards paid at epoch boundaries.
- Transaction limits - 1,232 bytes, 64 account locks, blockhash valid 150 slots; versioned transactions + ALTs raise the address count to 256.
- Clients - Agave (Rust) and Firedancer (C); Alpenglow (Votor + Rotor) is the approved consensus upgrade replacing PoH-gated voting.
Formulas and examples
Rent-exempt minimum (lamports) = (data_len + 128) × 3,480 × 2 = (data_len + 128) × 6,960
data_len = account data size in bytes; 128 = per-account storage overhead; 3,480 = lamports per byte-year; 2 = exemption threshold in years. Parameters are cluster constants and can be changed by governance.
Base fee (lamports) = 5,000 × (number of signatures)
Priority fee (lamports) = CU_limit × price(micro-lamports per CU) / 10⁶
Total fee = base fee + priority fee
Epoch duration = 432,000 slots × slot time; slot time = ticks_per_slot × hashes_per_tick / hash_rate (target 0.4 s)
Inflation in year n = 8% × (0.85)ⁿ, floored at 1.5%
Gross staking yield ≈ inflation rate × (total supply / staked supply); net yield ≈ gross × (1 − validator commission)
Token raw amount = display amount × 10decimals
Theoretical TPS = block CU limit / CU per transaction ÷ slot time (seconds)
Example 1 - rent for an Anchor account. A Counter account stores an 8-byte discriminator, a 32-byte Pubkey and a u64.
- data_len = 8 + 32 + 8 = 48 bytes.
- Rent-exempt minimum = (48 + 128) × 6,960 = 176 × 6,960 = 1,224,960 lamports = 0.00122496 SOL.
- At an illustrative ₹14,000 per SOL: 0.00122496 × 14,000 ≈ ₹17.15, refunded when the account is closed. (SOL price is volatile - treat the rupee figure as an illustration only.)
Example 2 - standard SPL accounts.
- Token account, 165 bytes → (165 + 128) × 6,960 = 293 × 6,960 = 2,039,280 lamports = 0.00203928 SOL.
- Mint account, 82 bytes → (82 + 128) × 6,960 = 210 × 6,960 = 1,461,600 lamports = 0.0014616 SOL.
- An empty system account, 0 bytes → 128 × 6,960 = 890,880 lamports = 0.00089088 SOL.
Example 3 - fee with priority. A swap transaction has 2 signatures, requests a 300,000 CU limit and sets a CU price of 20,000 micro-lamports.
- Base fee = 5,000 × 2 = 10,000 lamports.
- Priority fee = 300,000 × 20,000 / 10⁶ = 6,000,000,000 / 10⁶ = 6,000 lamports.
- Total = 16,000 lamports = 0.000016 SOL ≈ ₹0.22 at ₹14,000/SOL. Of the base fee, 5,000 lamports are burned.
Example 4 - epoch timing and throughput.
- At the 400 ms target: 432,000 × 0.4 s = 172,800 s = 48 hours.
- If the cluster actually averages 460 ms: 432,000 × 0.46 = 198,720 s = 55.2 h ≈ 2.3 days - which is why stake activation times drift.
- Throughput ceiling with a 48,000,000 CU block limit and a token transfer costing ~5,000 CU: 48,000,000 / 5,000 = 9,600 transactions per block; ÷ 0.4 s = 24,000 TPS theoretical. Real TPS is lower because of the 1,232-byte packet limit, signature verification and write-lock contention.
Example 5 - staking yield. Inflation 4.5%, 65% of supply staked, validator commission 7%.
- Gross staker yield = 4.5 / 0.65 = 6.92%.
- Net = 6.92 × (1 − 0.07) = 6.44% per annum in SOL terms (before any rupee price change and before Indian tax on the reward).
| Feature | Solana | Ethereum (EVM) |
|---|---|---|
| Consensus | PoH + Tower BFT (PoS), Alpenglow approved | Gasper: Casper FFG + LMD-GHOST |
| Block/slot time | ~0.4 s | ~12 s |
| Execution | Parallel (Sealevel), accounts declared | Sequential, single-threaded |
| State model | Separate accounts; stateless programs | Contract-internal storage |
| Language / toolchain | Rust (also C, Zig) → SBF; Anchor, IDL | Solidity/Vyper → EVM bytecode; Hardhat/Foundry, ABI |
| Metering | Compute units, 1.4 M per tx | Gas, block gas limit |
| Fees | 5,000 lamports/signature + local priority fee | EIP-1559 base fee + tip, global market |
| Mapping pattern | PDA derived from seeds | mapping in storage |
| Upgrades | Native program upgrade authority | Proxy patterns (UUPS, transparent) |
| Scaling route | Scale the single chain (hardware) | L2 rollups + danksharding |
| Re-entrancy | Narrow: CPI depth 4, restricted self-recursion | Classic risk; needs guards |
Where it is used
- Indian exchanges and wallets: SOL and SPL token listings, deposit/withdrawal integration and staking products at CoinDCX/Okto, CoinSwitch, ZebPay, Mudrex, Giottus; backend roles need RPC, ATA derivation and finality handling. Indian tax treatment of such gains (the VDA provisions of the Income-tax Act, 2025 and the 1% TDS) and FIU-IND registration under the PMLA are covered in this subject's crypto-assets-in-India note.
- DeFi and trading infrastructure: AMMs, order books, perpetuals, oracles and MEV/block-building - roles for Rust program engineers, quant/market-making engineers and RPC/infra (validator, geyser plugin, indexer) engineers.
- Payments: Solana Pay QR flows and stablecoin settlement for merchants and cross-border remittance pilots - frequently benchmarked in India against UPI for cost per transaction and settlement finality.
- Consumer and gaming apps, NFTs and ticketing: compressed NFTs for large-scale loyalty, certificates and ticket drops where per-item cost must be in paise, not rupees.
- DePIN and IoT: mapping, wireless and compute networks paying devices micro-rewards on-chain - the use case that depends most on cheap, fast finality.
- Hiring and training routes in India: Solana Foundation / Superteam India bounties, grants and hackathons; a working Anchor program with tests on Devnet is the portfolio artefact interviewers ask for, alongside a GitHub repo and a Devnet program ID.
Interview questions
- Q: Is Proof of History a consensus algorithm? A: No. It is a verifiable clock - a sequential hash chain proving elapsed time and ordering - while consensus and finality come from stake-weighted Tower BFT voting.
- Q: How does Solana execute transactions in parallel when the EVM cannot? A: Every Solana instruction declares the accounts it will read and write, so the Sealevel scheduler can lock accounts and run non-overlapping transactions on different cores; EVM transactions can touch arbitrary storage discovered at run time.
- Q: What is a PDA and why can a program sign with it? A: A Program Derived Address is derived from seeds, a bump and the program ID and is forced off the Ed25519 curve, so no private key exists; the runtime lets only the owning program authorise it, through
invoke_signed. - Q: What is the canonical bump and why does it matter? A: The highest bump (searching down from 255) that yields a valid off-curve PDA; accepting a user-supplied bump lets an attacker create multiple valid PDAs for the same logical seeds, so always validate the canonical one (
seeds/bumpin Anchor). - Q: Where does a Solana program store its state? A: In separate accounts that the program owns - typically PDAs - because programs themselves are stateless executable accounts.
- Q: What is rent, and is it a recurring charge? A: It is a storage deposit; holding at least two years' worth makes the account rent-exempt with no recurring deduction, and the deposit is returned when the account is closed. Periodic rent collection is deprecated.
- Q: How do compute units and priority fees interact? A: You set a CU limit and a CU price; the priority fee is limit × price ÷ 10⁶ lamports and buys ordering within the local fee market for the accounts you write, while exceeding the CU limit fails the transaction.
- Q: Is re-entrancy possible on Solana? A: Only in a very limited form - CPI depth is capped at 4 and general self-recursion is restricted - but you can still be attacked through untrusted CPI targets, stale account data and oracle manipulation, so ordering and validation still matter.
- Q: Why is the transaction size limited to 1,232 bytes, and how do you get around it? A: It must fit one UDP/QUIC packet within the IPv6 minimum MTU of 1,280 bytes minus headers; use versioned transactions with Address Lookup Tables, or split the work across transactions with intermediate on-chain state.
- Q: What does Anchor's 8-byte discriminator prevent? A: Type confusion ("account cosplay") - passing a correctly-sized account of a different type; the discriminator is checked on deserialization.
- Q: Which commitment level should a dApp wait for? A:
confirmedfor normal UX (optimistically confirmed by over two-thirds of stake),finalizedfor value-critical flows such as exchange credits. - Q: What are Agave, Firedancer and Alpenglow? A: Agave is the Rust validator client, Firedancer is Jump's independent C client (client diversity), and Alpenglow is the governance-approved consensus redesign - Votor for fast finality and Rotor for propagation - replacing PoH-gated voting.
- Q: How would you airdrop one million NFTs on Solana? A: Use state compression - a concurrent Merkle tree with only the root on-chain (Metaplex Bubblegum style) - rather than one mint plus one metadata account per item.
Common mistakes
- Calling PoH the consensus mechanism, or claiming Solana "has no consensus" - PoH only orders and timestamps; Tower BFT finalises.
- Thinking a program stores variables like a Solidity contract, and then being unable to explain where a counter or balance actually lives.
- Forgetting the 8-byte discriminator in
space, or forgetting the 128-byte overhead in the rent formula - both give a wrong rent figure and a failedinit. - Omitting a signer check or an owner check, or trusting a client-supplied bump - the three classic Solana authorisation bugs.
- Using
+/-onu64balances instead ofchecked_add/checked_sub(and not enablingoverflow-checksin the release profile), then wondering why a vault drains. - Confusing display amounts with raw amounts: an SPL transfer takes integers in the mint's smallest unit, so 1.5 tokens at 6 decimals is 1,500,000.
- Quoting the 65,000 TPS marketing figure as achieved throughput, or ignoring the per-writable-account write-lock limit when explaining parallelism.
- Expecting to fit everything in one transaction and hitting the 1,232-byte or 64-account-lock limit; or submitting after the recent blockhash has expired (150 slots) and reporting it as a network failure.
Related topics
Smart contracts and Solidity · Blockchain security and smart-contract audits · Consensus algorithms: PoW, PoS and BFT · Enterprise blockchains: Hyperledger Fabric and Corda · Rust systems programming and memory safety · Blockchain in finance and supply chain