From 1e5c1467561e666fae6d5186e7595729f45f3ff0 Mon Sep 17 00:00:00 2001 From: Sameh Abouelsaad Date: Wed, 19 Aug 2026 15:45:30 +0300 Subject: [PATCH 1/5] feat(smart-contract): migrate a node contract to another node in the same farm Adds `migrate_node_contract(contract_id, node_id, deployment_hash)` at call index 22, restricted to `RestrictedOrigin` (root or 3/5 council). It moves a live node contract to another node in the same farm without cancelling it, so a farmer can empty a machine and power it down without destroying the tenant's contract. The extrinsic bills the contract first, settling at the source node's cost basis, then re-reads it because billing can mutate or remove it. It moves the entry in `ContractIDByNodeIDAndHash` behind an equality guard, since `update_node_contract` never enforced hash uniqueness and a key may already point at a different live contract. `ContractPaymentState.last_updated_seconds` is stamped unconditionally. It moves the booking, not the workload. The source node deprovisions its copy on its next reconciliation and deletes the disks, so preserving data is the caller's responsibility before calling. See docs/architecture/0027 for the full reasoning and docs/misc/migrate_node_contract.md for the operational runbook. Also brings the two report handlers into line with each other. Both already skipped a report naming a contract that does not exist, silently and for free; a report naming a contract that exists but sits on another node was handled the opposite way, aborting the whole batch and charging for it. Both now skip and log. That asymmetry was unreachable before this change, since cancellation removes the contract and the missing-contract guards catch it. Weight is parameterised on `max(source, destination)` active-contract count, following the convention established in 526158f. The measured weights.rs still needs regenerating via workflow 060 before merge. Fixes a pre-existing bug in the integration harness where TMP_DIR was "\tmp", which Python reads as TAB + "mp". --- clients/tfchain-client-go/contract.go | 36 + .../0027-migrate-node-contract.md | 228 ++++++ docs/misc/migrate_node_contract.md | 109 +++ .../pallet-dao/creating_motion_council.md | 2 + .../pallets/pallet-smart-contract/readme.md | 2 + .../pallet-smart-contract/src/benchmarking.rs | 78 ++ .../src/grid_contract.rs | 221 +++++- .../pallets/pallet-smart-contract/src/lib.rs | 42 ++ .../pallet-smart-contract/src/tests.rs | 705 ++++++++++++++++++ .../pallet-smart-contract/src/weights.rs | 33 + substrate-node/tests/SubstrateNetwork.py | 5 +- substrate-node/tests/TfChainClient.py | 52 ++ substrate-node/tests/integration_tests.robot | 42 ++ 13 files changed, 1544 insertions(+), 11 deletions(-) create mode 100644 docs/architecture/0027-migrate-node-contract.md create mode 100644 docs/misc/migrate_node_contract.md diff --git a/clients/tfchain-client-go/contract.go b/clients/tfchain-client-go/contract.go index 1eec5815e..4a8992c26 100644 --- a/clients/tfchain-client-go/contract.go +++ b/clients/tfchain-client-go/contract.go @@ -444,6 +444,42 @@ func (s *Substrate) CancelContract(identity Identity, contract uint64) error { return nil } +// MigrateNodeContract moves a node contract to another node in the same farm, +// keeping the contract alive instead of cancelling it. It moves the booking, not +// the workload: the source node deprovisions its copy once the contract leaves its +// list, and the owner must redeploy on the destination. The extrinsic takes a +// configurable origin (root or council), so this must be submitted through a +// collective motion rather than signed by an ordinary twin. +// +// Pass an empty hash to keep the contract's current deployment hash. +func (s *Substrate) MigrateNodeContract(identity Identity, contract uint64, nodeID uint32, hash string) error { + cl, meta, err := s.GetClient() + if err != nil { + return err + } + + var deploymentHash types.Option[HexHash] + if hash == "" { + deploymentHash = types.NewEmptyOption[HexHash]() + } else { + deploymentHash = types.NewOption[HexHash](NewHexHash(hash)) + } + + c, err := types.NewCall(meta, "SmartContractModule.migrate_node_contract", + contract, nodeID, deploymentHash, + ) + if err != nil { + return errors.Wrap(err, "failed to create call") + } + + _, err = s.Call(cl, meta, identity, c) + if err != nil { + return errors.Wrap(err, "failed to migrate node contract") + } + + return nil +} + // BatchCancelContract cancels a batch of contracts func (s *Substrate) BatchCancelContract(identity Identity, contracts []uint64) error { cl, meta, err := s.GetClient() diff --git a/docs/architecture/0027-migrate-node-contract.md b/docs/architecture/0027-migrate-node-contract.md new file mode 100644 index 000000000..691e09ce6 --- /dev/null +++ b/docs/architecture/0027-migrate-node-contract.md @@ -0,0 +1,228 @@ +# 27. Migrate a Node Contract to Another Node in the Same Farm + +Date: 2026-08-18 + +## Status + +Accepted + +## Context + +A farmer cannot power a node down while it holds a single active contract: +`change_power_target(Down)` requires `node_has_no_active_contracts` +(`pallet-tfgrid/src/node.rs:373-378`). The only way to empty a node today is to +cancel its contracts, which is terminal — the contract is gone, and with it any +path for the tenant to return under the same identity. + +That blocks hardware consolidation. On mainnet at spec 157, emptying ten machines +in Freefarm means relocating 74 contracts across 29 twins, none of them the +operator; retiring the 2012 generation means 122. Asking those twins to +recreate contracts by hand is not available to a hosting provider. + +Two alternatives were rejected. **Cancel and recreate** is the problem, not the +fix. **Off-chain coordination** offload the responsibility to the tenant, which +is not feasible. + +### What the chain does and does not guarantee + +**Migration moves the booking, not the bytes.** The chain stores a contract ID, a +deployment hash and small metadata; the workload lives on the node. Once the +contract leaves `ActiveNodeContracts[source]`, that node's ZOS reconciles, finds a +local deployment the chain no longer lists for it, and deprovisions it — and +`zmount.Deprovision` calls `DiskDelete`. Nothing is copied between nodes, and no +on-chain mechanism could copy it. + +So **preserving data is the caller's responsibility, before calling.** The team's +intended sequence for the consolidation is: pause the deployment on the source, +copy it to S3, call `migrate_node_contract` — at which point tearing down the +source is safe and expected — then create the deployment on the destination. The +chain neither enforces nor observes any of that. + +What migration preserves that cancellation does not: the contract ID, billing +continuity, the reward payee, reserved public IPs, and a live contract to deploy +against. That is the whole case for the feature — it turns an irreversible +operator action into a recoverable one. + +## Decision + +### New extrinsic (pallet-smart-contract) + +| Extrinsic | Origin | Call index | +| --- | --- | --- | +| `migrate_node_contract(contract_id, node_id, deployment_hash)` | `RestrictedOrigin` (root or 3/5 council) | 22 | + +`deployment_hash` is `Option`; `None` keeps the current hash. The hash +covers the full deployment sent to the node and legitimately changes for network +workloads but not for VMs. On-chain `deployment_data` is small metadata, unaffected +by a relocation, and is therefore not a parameter. + +**Council-only for v1.** The consolidation is cross-tenant by construction, so +restricting v1 removes the whole authorization matrix (owner vs farmer, and who may +rewrite a deployment hash). **Owner and farmer paths must arrive as a new +`call_index(23)`, never by widening 22's origin check** — adding a dispatchable is +exempt from a `transaction_version` bump (`sp_version/src/lib.rs:209`), altering an +existing one's semantics is not (`:204-206`). + +That rule cuts against this changeset's own report-handler fix, so state it plainly +rather than let this PR become precedent: `transaction_version` stays at 2 because +the call index and parameter encoding are byte-identical (so no signed payload +decodes differently), the change is strictly in the submitter's favour, and both +calls are ZOS telemetry with no offline-signing consumer. A future semantics change +lacking those three properties needs the bump. + +### Preconditions enforced on-chain + +1. Contract exists and is a node contract. +2. State is `Created`. Every target contract, and all 2,372 in Freefarm, are + `Created`; the chain holds exactly one `GracePeriod` node contract, and + relocating a contract due to auto-delete in 14 days only moves where it dies. +3. Destination differs from the source. +4. Same **farm**. No same-country check — farm membership is the only relationship + the chain models, and it is what keeps public-IP reservations coherent, since + `reserve_ip`/`free_ip` resolve the farm *through* the node. +5. Neither node is in `NodeV3BillingOptOut`, and neither has an + `ActiveRentContractForNode`. Both would silently reprice the tenant: opting out + waives billing, and a rent contract zeroes CPU/RAM/disk cost (`cost.rs:78`). +6. Destination is not standby and not dedicated. The **source** node's power state + is deliberately unchecked — migrating off a machine you are about to shut down + is the point. +7. The destination's `(node_id, deployment_hash)` key is strictly free. Stricter + than `create_node_contract`, which permits overwriting a `Deleted` entry: there + is no restore semantic here, and that contract's eventual `remove_contract` + would unconditionally delete the key we just claimed. + +### Behaviour + +**Bills first**, settling at the source node's cost basis — billing reads the +source's certification and resolves the payee from the source farm — then re-reads +the contract, because billing can mutate or remove it. The two post-billing +branches answer deliberately differently: + +- **grace → `Err`** (the live path): an underfunded twin transitions to + `GracePeriod` inside this call. Nothing was removed, the contract still pins the + node, and `Ok` would be a silent no-op indistinguishable from success. The + rolled-back cycle is redone by the offchain worker. +- **gone → `Ok`** (currently unreachable, kept as a guard): `bill_contract` only + deletes once an *existing* grace period elapses, and precondition 2 already + refused anything but `Created`. + +**The `ContractIDByNodeIDAndHash` removal is guarded by an equality check.** +`update_node_contract` never enforced hash uniqueness where `create_node_contract` +does, so a key may already point at a *different* live contract; an unguarded +remove would destroy that contract's index entry. This is a real regression guard +with a test, not defensive padding. + +Then the contract moves between the two `ActiveNodeContracts` vectors, and +`ContractPaymentState.last_updated_seconds` is stamped unconditionally — +`bill_contract` skips this on its zero-amount early return, and the +`deployment_hash` parameter can change what the destination deploys. + +**`NodeContractResources` is deliberately NOT cleared,** and the tenant is not +over-billed by the move. The entry is contract-keyed, so it survives the migration +and billing continues at the same quantity on the same schedule — the inline +`bill_contract` settles the source period and stamps the clock (`billing.rs:199,369`), +so the next cycle bills from that instant forward. No overlap, no gap, no double +charge; the loop index is `contract_id % billing_frequency`, which the move does not +change. The one real deviation is the certification multiplier, covered below. + +Clearing the entry is what would break this. `calculate_resources_cost_units_usd` +(`cost.rs:71-89`) derives the whole node-contract cost from it, so zeroing it sends +`bill_contract` down its zero-amount early return (`billing.rs:276-286`) — no +overdraft, so no grace, so no 14-day auto-delete, leaving a free immortal contract +pinning the destination against `node_has_no_active_contracts`. + +### Ordering is forced by ZOS: migrate first, then deploy + +A deployment cannot be pre-staged. `validate()` +(`zosbase/pkg/provision/engine.go:616-626`) rejects any deployment whose contract +does not already name that node and whose `ChallengeHash()` does not match the +contract's on-chain hash; the only bypass is `boot()` reinstalling from local +storage. So the destination cannot accept the workload until the extrinsic has +landed — which is what the `deployment_hash` parameter exists for. Without it the +flow would need `update_node_contract`, which is owner-only, so a council-driven +migration could not fix the hash at all. + +### No new event + +`ContractUpdated(Contract)` already carries the whole contract, hence the new +`node_id` and hash, and the indexer already writes both +(`tfchain_graphql/src/mappings/contracts.ts:227,231`). So tfchain_graphql and grid +proxy need no changes, and the Go client needs only a call wrapper. Source-node +cleanup needs no chain change either: `ContractEventHandler.sync()` runs hourly, +compares local deployments against `ActiveNodeContracts[node]`, and deprovisions +what the chain no longer lists. + +**Prompt teardown was considered and is not worth building.** A ZOS node could act on +`ContractUpdated` directly rather than waiting for `sync()`, since every node +already decodes the complete `EventRecords` per block locally. But it accelerates +nothing the operator needs — `change_power_target(Down)` is gated on +`ActiveNodeContracts`, which is empty the instant the extrinsic lands — and the +measured benefit is the negligible stream saving above. If it is ever built: + +- whoever builds it must confirm the operating procedure still copies data off the +source *before* calling, because same-block teardown removes any margin for a +procedure that does not. +- A dedicated `NodeContractMigrated` would let ZOS filter before pushing to its local +stream, but `ContractUpdated` fires well under once per hour chain-wide, so the +saving is about one discarded stream entry every few hours. It remains purely +additive if audit ever wants it. + +### New errors + +Appended at the end of the enum — `Error` variants have no explicit index and are +SCALE-encoded by declaration order: `NodeNotInSameFarm`, `ContractAlreadyOnNode`, +`NodeIsOptedOutOfV3Billing`, `ContractNotInCreatedState`. + +`NodeNotAuthorizedToComputeReport` is left in place and marked reserved; the +companion fix below removed its last construction site, and deleting it would +renumber every variant below. + +### Companion fix: report handlers skip a stale entry, consistently + +Best read as a consistency sweep. Both handlers already tolerate a report naming a +contract that does not exist — `if let Some(contract)` in +`_report_contract_resources`, two `contains_key` guards in `_compute_reports` — +skipping it silently and for free, since both end in `Ok(Pays::No)`. A report +naming a contract that exists but sits on **another** node is the same class of +mistake, yet was handled the opposite way: `ensure!` rejecting the whole extrinsic, +and charging for it. That asymmetry was the anomaly. Both now `continue` and log. + +The abort path stops being reachable exactly when it would start to matter: it was +unreachable before, because cancellation removes the contract and the +missing-contract guards catch it. `migrate_node_contract` is the first operation +that leaves a *live* contract pointing elsewhere, and left as it was, one migrated +contract would have frozen resource and NRU reporting for every other contract on +the source node. + +Charging for a skipped entry was considered and rejected: it would re-introduce the +asymmetry in the other direction. If the anti-spam posture is revisited, both cases +should move together. + +## Consequences + +- No storage migration. `Contract` unchanged, `CONTRACT_VERSION` 4, `StorageVersion` + V12, `transaction_version` 2. No indexer, grid proxy or ZOS change required. +- **An emptied `ActiveNodeContracts` is not permission to power the machine off.** + It empties the instant the extrinsic lands and says nothing about where the + workload is. Nothing in the pallet can detect the difference. +- **Certification is per-node and legitimately mixed within a farm**, driving a + +25% Certified multiplier, so a council-approved move can reprice a tenant. Worse across the + reporting gap: subsequent cycles read the *destination's* certification + (`billing.rs:161-175`) against the *source's* still-stored footprint — new price × + old quantity, either direction, until the destination reports. Choosing + certification-matched destinations makes the multiplier 1.0 and removes it. If the + signed path is added it needs a directional guard. +- **A council motion has a weight ceiling.** `MaxProposalWeight` is 50% of max block + weight, bounding a motion at roughly 900–1,800 contracts. + +## Operational gate + +Conditions on the first mainnet migration, not on merging. + +1. **Data is copied off the source before the extrinsic is called.** The chain does + not check this and cannot; deprovisioning follows the move automatically. +2. **Destinations are certification-matched**, removing the repricing exposure + rather than documenting it. +3. **Batch per tenant group**, never all at once. +4. **Power the source down only after confirming the destination provisioned** — + never on `ActiveNodeContracts` being empty. diff --git a/docs/misc/migrate_node_contract.md b/docs/misc/migrate_node_contract.md new file mode 100644 index 000000000..ffe0fdfb4 --- /dev/null +++ b/docs/misc/migrate_node_contract.md @@ -0,0 +1,109 @@ +# Migrating a Node Contract + +See [ADR 0027](../architecture/0027-migrate-node-contract.md) for why this exists +and what was decided. + +## Overview + +`migrate_node_contract` moves a live node contract to another node **in the same +farm** without cancelling it. The contract keeps its id, billing state, reported +resources and reserved public IPs, so the tenant is left with something to deploy +against — which cancelling is not. + +It moves the **booking, not the workload**. Nothing is copied between nodes. Once +the contract leaves the source node's `ActiveNodeContracts`, that node deprovisions +its local copy on the next hourly reconciliation and deletes the disks. Getting the +data off the source beforehand is the caller's job. + +## Dispatchable + +``` +migrate_node_contract(contract_id: u64, node_id: u32, deployment_hash: Option) +``` + +Origin: root, or a 3/5 council motion. Pass `None` for `deployment_hash` to keep the +current one. + +## Preconditions checked on chain + +- the contract exists, is a node contract, and is in `Created` state +- destination is a different node, in the **same farm** +- neither node is opted out of v3 billing, nor under a rent contract +- destination is not in standby and not dedicated +- destination does not already hold a contract with the same deployment hash + +The **source** node's power state is not checked — migrating off a machine you are +about to shut down is the point. + +## Not checked on chain + +Read this section before planning a wave. + +- **Destination capacity.** Neither this call nor `create_node_contract` verifies + the destination can host the workload; ZOS decides that at deployment time. So an + oversubscribed destination is discovered *after* the contract has moved and the + source has been told to let go. Check real free memory on the destination, not + just reported usage. +- **That the data was preserved.** See Overview. +- **That anything is actually running.** `ActiveNodeContracts` for the source is + empty the instant the call lands. That is also what `change_power_target(Down)` + gates on, so the chain will report the machine safe to sleep while the workload + is still on it and not yet rebuilt anywhere else. + +## Before you call + +1. Get the data off the source. +2. Prefer a destination whose **node certification matches the source**. Certified + nodes bill 25% above Diy, and until the destination files its first resource + report the tenant is billed at the destination's rate against the source's + footprint. Matching certification makes that 1.0, and a farm whose nodes are all + one certification cannot hit this at all. +3. Confirm the contract still exists, still belongs to that twin and still sits on + the source — plans go stale, contracts get cancelled continuously. +4. Confirm the contract has a `ContractPaymentState`; without one `bill_contract` + fails and the migration cannot proceed. + +## Submitting through the council + +Propose → vote → close, as in [council.md](council.md). Notes specific to this call: + +- Use `utility.batch` **per tenant group**, not `batch_all` across everything. + `batch_all` is atomic, so one underfunded twin rolls back every other migration in + the batch along with its settlement. +- A motion is capped by `MaxProposalWeight` (50% of max block weight), which bounds + it at roughly 900–1,800 contracts. The weight scales with how many contracts the + source and destination nodes already hold. + +## Common errors + +| Error | Meaning | +| --- | --- | +| `ContractNotExists` | wrong id, or already cancelled | +| `InvalidContractType` | not a node contract (name or rent) | +| `ContractNotInCreatedState` | in grace or deleted — or it entered grace during this call's own billing, which means the twin is underfunded | +| `ContractAlreadyOnNode` | source and destination are the same | +| `NodeNotInSameFarm` | cross-farm move; not supported | +| `NodeIsOptedOutOfV3Billing` | either side is opted out | +| `NodeNotAvailableToDeploy` | destination is rented, standby, or dedicated | +| `ContractIsNotUnique` | destination already has a contract with this deployment hash — pass a fresh hash | +| `ContractPaymentStateNotExists` | contract is already unbillable; cannot migrate | +| `BadOrigin` | not submitted as root or through a council motion | + +## Verifying + +After the call: + +- `Contracts(contract_id).node_id` is the destination +- the contract is gone from `ActiveNodeContracts(source)` and present in + `ActiveNodeContracts(destination)` +- a `ContractUpdated` event carries the new node id and hash + +## After the call + +Deploy on the destination. The order is forced — a node rejects a deployment whose +contract does not already name it, so the destination cannot be prepared in advance. + +The source deprovisions its stale copy on its next reconciliation (hourly). + +**Power the source down only after confirming the destination is provisioned and +serving** — never on `ActiveNodeContracts` being empty. diff --git a/substrate-node/pallets/pallet-dao/creating_motion_council.md b/substrate-node/pallets/pallet-dao/creating_motion_council.md index 55a63c7d3..6099fc2af 100644 --- a/substrate-node/pallets/pallet-dao/creating_motion_council.md +++ b/substrate-node/pallets/pallet-dao/creating_motion_council.md @@ -76,6 +76,8 @@ Once the motion is closed it is removed from list and the `proposal` extrinsic i * `cancelContract()` (cancels a contract) * `approveSolutionProvider()` (approves a solution provider) * `changeBillingFrequency()` (changes the billing frequency) +* `cancelContractCollective()` (cancels a contract) +* `migrateNodeContract()` (moves a node contract to another node in the same farm) ### tftBridgeModule diff --git a/substrate-node/pallets/pallet-smart-contract/readme.md b/substrate-node/pallets/pallet-smart-contract/readme.md index cfefe6823..461b66b6b 100644 --- a/substrate-node/pallets/pallet-smart-contract/readme.md +++ b/substrate-node/pallets/pallet-smart-contract/readme.md @@ -53,3 +53,5 @@ Dispatchable functions of this pallet. * `change_billing_frequency`: Change the billing frequency of all contracts, the origin for this call is a configurable origin. * `attach_solution_provider_id`: Attach a solution provider id to a contract * `set_dedicated_node_extra_fee`: Set an extra fee for a dedicated node +* `cancel_contract_collective`: Cancel a contract, the origin for this call is a configurable origin +* `migrate_node_contract`: Move a node contract to another node in the same farm without cancelling it. This moves the booking, not the workload; the owner must redeploy on the destination. The origin for this call is a configurable origin diff --git a/substrate-node/pallets/pallet-smart-contract/src/benchmarking.rs b/substrate-node/pallets/pallet-smart-contract/src/benchmarking.rs index 60b67866c..ea1fe3d42 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/benchmarking.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/benchmarking.rs @@ -503,6 +503,84 @@ benchmarks! { }.into()); } + // migrate_node_contract() + // Setup mirrors bill_contract_for_block: the extrinsic calls bill_contract + // inline, and with a fresh contract seconds_elapsed == 0 so billing would take + // its zero-amount early return and the benchmark would miss the real cost. + migrate_node_contract { + // n = length of the SOURCE node's ActiveNodeContracts vector, which the + // extrinsic read-modify-writes. The weight closure passes max(source, dest) + // and the source is the crowded side by construction -- migrating OFF a full + // node is the point of the feature. Ranges here are inclusive on BOTH ends + // (frame/benchmarking v1), so this covers 1..=100. + let n in 1 .. 100; + + let farmer: T::AccountId = account("Alice", 0, 0); + _prepare_farm_with_node::(farmer.clone()); // farm 1, node 1 + + // A second node on the SAME farm needs its own twin: pallet-tfgrid enforces + // one node per twin (NodeIdByTwinID -> NodeWithTwinIdExists). _create_node + // only requires the farm to exist, not that the caller owns it. + let node_owner: T::AccountId = account("Charlie", 0, 2); + _create_twin::(node_owner.clone()); + _create_node::(node_owner); // node 2, farm 1 + + let user: T::AccountId = account("Bob", 0, 1); + let user_lookup = T::Lookup::unlookup(user.clone()); + let balance_init_amount = ::Balance::saturated_from(100000000 as u128); + Balances::::force_set_balance(RawOrigin::Root.into(), user_lookup, balance_init_amount).unwrap(); + _create_twin::(user.clone()); + _create_node_contract::(user.clone()); // contract 1 on node 1 + let contract_id = 1; // created FIRST, so id is stable + let destination_node_id = 2; + + // n - 1 siblings pinned to the source node, so the vector the extrinsic + // retains over is n long. Each needs its own deployment hash: the pair + // (node_id, hash) is unique in ContractIDByNodeIDAndHash, so reusing + // _create_node_contract's fixed hash would fail with ContractIsNotUnique + // on the second iteration. + for i in 1..n { + let mut sibling_hash = get_deployment_hash_input(b"00000000000000000000000000000000"); + sibling_hash[0] = (i % 256) as u8; + sibling_hash[1] = (i / 256) as u8; + assert_ok!(SmartContractModule::::create_node_contract( + RawOrigin::Signed(user.clone()).into(), + 1, + sibling_hash, + get_deployment_data_input::(b"some_data123"), + 0, + None, + )); + } + + // advance time and report usage so bill_contract does real work + let now = SmartContractModule::::get_current_timestamp_in_secs(); + let elapsed_seconds = 5; + let then: u64 = now + elapsed_seconds; + pallet_timestamp::Pallet::::set_timestamp((then * 1000).try_into().unwrap()); + + _push_contract_used_resources_report::(farmer.clone()); + _push_contract_nru_consumption_report::(farmer.clone(), then, elapsed_seconds); + + let new_deployment_hash = get_deployment_hash_input(b"858f8fb2184b15ecb8c0be8b95398c82"); + }: _(RawOrigin::Root, contract_id, destination_node_id, Some(new_deployment_hash)) + verify { + let contract = SmartContractModule::::contracts(contract_id).unwrap(); + assert_eq!(contract.get_node_id(), destination_node_id); + // the source keeps its n - 1 siblings; only the migrated contract leaves + let source_remaining = SmartContractModule::::active_node_contracts(1); + assert!(!source_remaining.contains(&contract_id)); + assert_eq!(source_remaining.len(), (n - 1) as usize); + assert_eq!(SmartContractModule::::active_node_contracts(destination_node_id), vec![contract_id]); + assert_eq!( + SmartContractModule::::node_contract_by_hash(destination_node_id, new_deployment_hash), + contract_id + ); + let old_hash = get_deployment_hash_input(b"858f8fb2184b15ecb8c0be8b95398c81"); + assert_eq!(SmartContractModule::::node_contract_by_hash(1, old_hash), 0); + assert_last_event::(Event::ContractUpdated(contract).into()); + } + // Calling the `impl_benchmark_test_suite` macro inside the `benchmarks` // block will generate one #[test] function per benchmark impl_benchmark_test_suite!(SmartContractModule, crate::mock::new_test_ext(), crate::mock::TestRuntime) diff --git a/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs b/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs index a85dbf82f..f634bf0fd 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs @@ -299,6 +299,167 @@ impl Pallet { Ok(().into()) } + /// Number of contracts the weight of a migration should be charged against: + /// the migration read-modify-writes BOTH the source and destination + /// `ActiveNodeContracts` vectors, which are unbounded. Uses the max of the + /// two lengths -- never the destination alone, which would systematically + /// under-count migrating off a crowded node. + pub fn migrate_weight_contracts(contract_id: u64, node_id: u32) -> u32 { + let source_len = Contracts::::get(contract_id) + .map(|c| ActiveNodeContracts::::get(c.get_node_id()).len()) + .unwrap_or(0); + let destination_len = ActiveNodeContracts::::get(node_id).len(); + source_len.max(destination_len).try_into().unwrap_or(u32::MAX) + } + + /// Move a node contract to another node in the same farm without cancelling it. + /// + /// Origin is checked by the caller (`RestrictedOrigin`); this fn performs no + /// authorization of its own. + pub fn _migrate_node_contract( + contract_id: u64, + node_id: u32, + deployment_hash: Option, + ) -> DispatchResultWithPostInfo { + // --- validation (reads only) --- + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + let node_contract = Self::get_node_contract(&contract)?; + // Named for the invariant, not for grace: this also rejects Deleted. + ensure!( + matches!(contract.state, types::ContractState::Created), + Error::::ContractNotInCreatedState + ); + + let source_node_id = node_contract.node_id; + ensure!( + source_node_id != node_id, + Error::::ContractAlreadyOnNode + ); + + let source_node = + pallet_tfgrid::Nodes::::get(source_node_id).ok_or(Error::::NodeNotExists)?; + let farm = pallet_tfgrid::Farms::::get(source_node.farm_id) + .ok_or(Error::::FarmNotExists)?; + + let destination_node = + pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + ensure!( + destination_node.farm_id == source_node.farm_id, + Error::::NodeNotInSameFarm + ); + + // Excluded on BOTH sides: opting out waives billing, and a rent contract + // flips the cost basis. Either would make the move reprice the tenant. + ensure!( + !pallet_tfgrid::NodeV3BillingOptOut::::contains_key(source_node_id) + && !pallet_tfgrid::NodeV3BillingOptOut::::contains_key(node_id), + Error::::NodeIsOptedOutOfV3Billing + ); + ensure!( + !ActiveRentContractForNode::::contains_key(source_node_id) + && !ActiveRentContractForNode::::contains_key(node_id), + Error::::NodeNotAvailableToDeploy + ); + + // Destination must be able to accept a deployment. The SOURCE node's power + // state is deliberately not checked: migrating off a node that is about to + // be shut down is the point of this extrinsic. + let node_power = pallet_tfgrid::NodePower::::get(node_id); + ensure!( + !node_power.is_standby_phase(), + Error::::NodeNotAvailableToDeploy + ); + ensure!( + DedicatedNodesExtraFee::::get(node_id) == 0 && !farm.dedicated_farm, + Error::::NodeNotAvailableToDeploy + ); + + let target_hash = deployment_hash.unwrap_or(node_contract.deployment_hash); + // Stricter than _create_node_contract, which allows overwriting a Deleted + // entry: there is no restore semantic here, and that contract's eventual + // remove_contract would unconditionally delete the key we just claimed. + ensure!( + !ContractIDByNodeIDAndHash::::contains_key(node_id, &target_hash), + Error::::ContractIsNotUnique + ); + + // --- settle at the SOURCE node's cost basis before anything moves --- + Self::bill_contract(contract_id)?; + + // Billing may have mutated or removed the contract, so re-read it. + // NOTE: the two branches below deliberately answer differently. + // Two outcomes of the inline bill_contract, and they get opposite answers. + // Both are right; do not "fix" one to match the other. + // + // Grace => Err (LIVE path, covered by + // test_migrate_node_contract_fails_when_inline_billing_pushes_to_grace_period): + // an underfunded twin sends manage_contract_state Created -> GracePeriod + // inside this very call. Nothing was removed from the source yet, the + // contract still pins the node, and Ok would be a silent no-op that a batch + // caller cannot tell from success -- no ContractUpdated is emitted either + // way. The rolled-back cycle is redone by the offchain worker within the hour. + // + // Gone => Ok (currently UNREACHABLE, kept as a guard): bill_contract only + // deletes a contract once an EXISTING grace period has elapsed, and step 3 + // above already refused anything not in Created. A Created contract can at + // worst enter grace here, never leave it. The arm stays because it is the + // correct answer if that ever changes -- remove_contract would already have + // cleared the source's ActiveNodeContracts entry, so the node is drained and + // the goal is met, while Err would roll back a terminal settlement and its + // reward distribution for no gain. + let Some(mut contract) = Contracts::::get(contract_id) else { + return Ok(().into()); + }; + ensure!( + matches!(contract.state, types::ContractState::Created), + Error::::ContractNotInCreatedState + ); + let mut node_contract = Self::get_node_contract(&contract)?; + + // --- mutation --- + // Guarded: _update_node_contract never enforced hash uniqueness, so this key + // may already point at a DIFFERENT live contract. An unguarded remove would + // destroy that contract's index entry. + if ContractIDByNodeIDAndHash::::get(source_node_id, &node_contract.deployment_hash) + == contract_id + { + ContractIDByNodeIDAndHash::::remove( + source_node_id, + &node_contract.deployment_hash, + ); + } + ContractIDByNodeIDAndHash::::insert(node_id, &target_hash, contract_id); + + Self::remove_active_node_contract(source_node_id, contract_id); + + // Push open-coded, the way _create_node_contract does it: validation + // already refused source == destination, and the remove above just cleared + // the source, so the destination cannot already hold this id. + let mut destination_contracts = ActiveNodeContracts::::get(&node_id); + destination_contracts.push(contract_id); + ActiveNodeContracts::::insert(&node_id, &destination_contracts); + + node_contract.node_id = node_id; + node_contract.deployment_hash = target_hash; + contract.contract_type = types::ContractData::NodeContract(node_contract); + Contracts::::insert(contract_id, &contract); + + // bill_contract skips this stamp on its zero-amount early return, leaving a + // stale clock. Harmless today (a zero-cost contract stays zero-cost), but the + // deployment_hash parameter above can change what the destination deploys, + // so stamp unconditionally. Nothing is forgiven: the window's cost was zero. + let mut contract_payment_state = ContractPaymentState::::get(contract_id) + .ok_or(Error::::ContractPaymentStateNotExists)?; + contract_payment_state.last_updated_seconds = Self::get_current_timestamp_in_secs(); + ContractPaymentState::::insert(contract_id, &contract_payment_state); + + // No dedicated migration event: ContractUpdated carries the new node id and + // deployment hash, which is exactly what the indexer writes. + Self::deposit_event(Event::ContractUpdated(contract)); + + Ok(().into()) + } + pub fn _cancel_contract( account_id: T::AccountId, contract_id: u64, @@ -621,11 +782,31 @@ impl Pallet { // we know contract exists, fetch it // if the node is trying to send garbage data we can throw an error here if let Some(contract) = Contracts::::get(contract_resource.contract_id) { - let node_contract = Self::get_node_contract(&contract)?; - ensure!( - node_contract.node_id == node_id, - Error::::NodeNotAuthorizedToComputeReport - ); + // Consistency, not a new policy: the branch above already skips a + // report naming a contract that does not exist -- silently, and for + // free, since this returns Ok(Pays::No). A report naming a contract + // that exists but sits on another node is the same class of mistake, + // so it gets the same treatment. Aborting the whole batch and + // charging for it was the odd case out. + // It also stops being unreachable with migrate_node_contract, which + // leaves a LIVE contract on another node until this one reconciles. + let Ok(node_contract) = Self::get_node_contract(&contract) else { + log::warn!( + "node {:?} reported resources for non-node contract {:?}, skipping", + node_id, + contract_resource.contract_id + ); + continue; + }; + if node_contract.node_id != node_id { + log::warn!( + "node {:?} reported resources for contract {:?} which now lives on node {:?}, skipping", + node_id, + contract_resource.contract_id, + node_contract.node_id + ); + continue; + } // Do insert NodeContractResources::::insert(contract.contract_id, &contract_resource); @@ -666,11 +847,31 @@ impl Pallet { // if the node is trying to send garbage data we can throw an error here let contract = Contracts::::get(report.contract_id).ok_or(Error::::ContractNotExists)?; - let node_contract = Self::get_node_contract(&contract)?; - ensure!( - node_contract.node_id == node_id, - Error::::NodeNotAuthorizedToComputeReport - ); + // Consistency, not a new policy: the two contains_key guards above + // already skip a report naming a contract that does not exist -- + // silently, and for free, since this returns Ok(Pays::No). A report + // naming a contract that exists but sits on another node is the same + // class of mistake, so it gets the same treatment. Aborting the whole + // batch and charging for it was the odd case out. + // It also stops being unreachable with migrate_node_contract, which + // leaves a LIVE contract on another node until this one reconciles. + let Ok(node_contract) = Self::get_node_contract(&contract) else { + log::warn!( + "node {:?} reported NRU for non-node contract {:?}, skipping", + node_id, + report.contract_id + ); + continue; + }; + if node_contract.node_id != node_id { + log::warn!( + "node {:?} reported NRU for contract {:?} which now lives on node {:?}, skipping", + node_id, + report.contract_id, + node_contract.node_id + ); + continue; + } report.calculate_report_cost_units_usd::(&pricing_policy); diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 14ba20f11..2b99bcfc8 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1,4 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +// The benchmarks! macro expands one test per benchmark via impl_bench_name_tests; +// the default limit of 128 is exceeded once the pallet has this many benchmarks. +#![recursion_limit = "256"] pub mod billing; pub mod cost; @@ -375,6 +378,11 @@ pub mod pallet { TwinNotAuthorizedToUpdateContract, TwinNotAuthorizedToCancelContract, NodeNotAuthorizedToDeployContract, + // RESERVED -- no longer constructed. The report handlers used to raise this + // and abort the whole batch; they now log::warn! and skip the item instead. + // Kept because indices are declaration order (see the note at the end of + // this enum): deleting it renumbers every variant below, so a client with a + // cached mapping would misname them. Do not reuse the slot either. NodeNotAuthorizedToComputeReport, PricingPolicyNotExists, ContractIsNotUnique, @@ -419,6 +427,12 @@ pub mod pallet { RewardDistributionError, ContractPaymentStateNotExists, OnlyTwinAdminCanDeployOnThisNode, + // Appended for migrate_node_contract. Error variants have no explicit + // index and are SCALE-encoded by declaration order: append only, never insert. + NodeNotInSameFarm, + ContractAlreadyOnNode, + NodeIsOptedOutOfV3Billing, + ContractNotInCreatedState, } #[pallet::genesis_config] @@ -703,6 +717,34 @@ pub mod pallet { ::RestrictedOrigin::ensure_origin(origin)?; Self::_cancel_contract_collective(contract_id, types::Cause::CanceledByCollective) } + + /// Move a node contract to another node within the same farm, keeping the + /// contract alive instead of cancelling it. + /// + /// This moves the booking, not the workload. The source node deprovisions its + /// copy once it sees the contract has left its list, deleting the disks; the + /// owner must redeploy on the destination. An emptied node is therefore NOT + /// safe to power off just because it now reports no active contracts. + /// + /// Council/root only for now; owner and farmer paths are deferred to a future + /// call index (never by widening this one's origin check). + /// + /// `deployment_hash` is `None` to keep the current hash. No dedicated event is + /// emitted: `ContractUpdated` already carries the new node id and hash, which is + /// what the indexer and grid proxy consume. + #[pallet::call_index(22)] + #[pallet::weight(::WeightInfo::migrate_node_contract( + Pallet::::migrate_weight_contracts(*contract_id, *node_id) + ))] + pub fn migrate_node_contract( + origin: OriginFor, + contract_id: u64, + node_id: u32, + deployment_hash: Option, + ) -> DispatchResultWithPostInfo { + ::RestrictedOrigin::ensure_origin(origin)?; + Self::_migrate_node_contract(contract_id, node_id, deployment_hash) + } } #[pallet::hooks] diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 1ca11c5bc..1f13cd739 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -362,6 +362,659 @@ fn test_cancel_contract_collective_by_dao_approval_works() { }); } +// --------------------------------------------------------------------------- +// migrate_node_contract +// --------------------------------------------------------------------------- + +#[test] +fn test_migrate_node_contract_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let hash = prepare_two_nodes_and_node_contract(); + let contract_id = 1; + + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + contract_id, + 2, + None + )); + + let contract = SmartContractModule::contracts(contract_id).unwrap(); + assert_eq!(contract.get_node_id(), 2); + assert_eq!(contract.state, types::ContractState::Created); + + // source drained, destination holds it + assert!(SmartContractModule::active_node_contracts(1).is_empty()); + assert_eq!( + SmartContractModule::active_node_contracts(2), + vec![contract_id] + ); + + // hash index re-keyed, source entry gone + assert_eq!(SmartContractModule::node_contract_by_hash(2, hash), contract_id); + assert_eq!(SmartContractModule::node_contract_by_hash(1, hash), 0); + }); +} + +#[test] +fn test_migrate_node_contract_by_council_approval_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + assert_ok!(SmartContractModule::migrate_node_contract( + pallet_collective::RawOrigin::Members(3, 5).into(), + 1, + 2, + None + )); + + assert_eq!(SmartContractModule::contracts(1).unwrap().get_node_id(), 2); + }); +} + +#[test] +fn test_migrate_node_contract_with_new_deployment_hash_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let old_hash = prepare_two_nodes_and_node_contract(); + let contract_id = 1; + let new_hash: types::HexHash = [9u8; 32]; + + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + contract_id, + 2, + Some(new_hash) + )); + + let contract = SmartContractModule::contracts(contract_id).unwrap(); + let node_contract = SmartContractModule::get_node_contract(&contract).unwrap(); + assert_eq!(node_contract.deployment_hash, new_hash); + + assert_eq!( + SmartContractModule::node_contract_by_hash(2, new_hash), + contract_id + ); + assert_eq!(SmartContractModule::node_contract_by_hash(2, old_hash), 0); + assert_eq!(SmartContractModule::node_contract_by_hash(1, old_hash), 0); + }); +} + +#[test] +fn test_migrate_node_contract_stamps_clock_when_nothing_billed_works() { + // Regression guard: bill_contract takes its zero-amount early return for a + // contract with no resources and no IPs, and that path does NOT stamp the + // clock. The extrinsic must stamp it anyway. + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + let contract_id = 1; + + let before = SmartContractModule::contract_payment_state(contract_id) + .unwrap() + .last_updated_seconds; + + run_to_block(100, None); + + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + contract_id, + 2, + None + )); + + let after = SmartContractModule::contract_payment_state(contract_id) + .unwrap() + .last_updated_seconds; + assert!( + after > before, + "billing clock must advance even when nothing was billed" + ); + }); +} + +#[test] +fn test_migrate_node_contract_off_standby_source_node_works() { + // The SOURCE node's power state is deliberately not checked -- migrating off a + // node you are about to shut down is the point of the extrinsic. + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + assert_ok!(TfgridModule::change_power_state( + RuntimeOrigin::signed(alice()), + tfchain_support::types::Power::Down + )); + + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + 1, + 2, + None + )); + + assert_eq!(SmartContractModule::contracts(1).unwrap().get_node_id(), 2); + }); +} + +#[test] +fn test_migrate_node_contract_preserves_other_contracts_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_two_nodes(); + + for who in [bob(), charlie(), dave()] { + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(who), + 1, + generate_deployment_hash(), + get_deployment_data(), + 0, + None + )); + } + // three contracts on node 1; move the middle one + assert_eq!(SmartContractModule::active_node_contracts(1).len(), 3); + + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + 2, + 2, + None + )); + + assert_eq!(SmartContractModule::active_node_contracts(1), vec![1, 3]); + assert_eq!(SmartContractModule::active_node_contracts(2), vec![2]); + }); +} + +#[test] +fn test_migrate_node_contract_then_cancel_cleans_up_destination_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let hash = prepare_two_nodes_and_node_contract(); + let contract_id = 1; + + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + contract_id, + 2, + None + )); + assert_ok!(SmartContractModule::cancel_contract( + RuntimeOrigin::signed(bob()), + contract_id + )); + + assert_eq!(SmartContractModule::contracts(contract_id), None); + assert!(SmartContractModule::active_node_contracts(2).is_empty()); + assert_eq!(SmartContractModule::node_contract_by_hash(2, hash), 0); + assert_eq!(SmartContractModule::contract_payment_state(contract_id), None); + }); +} + +#[test] +fn test_migrate_node_contract_not_exists_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_two_nodes(); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 2, None), + Error::::ContractNotExists + ); + }); +} + +#[test] +fn test_migrate_name_contract_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_two_nodes(); + assert_ok!(SmartContractModule::create_name_contract( + RuntimeOrigin::signed(bob()), + b"foobar".to_vec() + )); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 2, None), + Error::::InvalidContractType + ); + }); +} + +#[test] +fn test_migrate_node_contract_to_same_node_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 1, None), + Error::::ContractAlreadyOnNode + ); + }); +} + +#[test] +fn test_migrate_node_contract_to_unknown_node_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 99, None), + Error::::NodeNotExists + ); + }); +} + +#[test] +fn test_migrate_node_contract_to_standby_node_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + // charlie owns node 2 + assert_ok!(TfgridModule::change_power_state( + RuntimeOrigin::signed(charlie()), + tfchain_support::types::Power::Down + )); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 2, None), + Error::::NodeNotAvailableToDeploy + ); + }); +} + +#[test] +fn test_migrate_node_contract_from_opted_out_node_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + assert_ok!(TfgridModule::opt_out_of_v3_billing( + RuntimeOrigin::signed(alice()), + 1, + )); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 2, None), + Error::::NodeIsOptedOutOfV3Billing + ); + }); +} + +#[test] +fn test_migrate_node_contract_to_opted_out_node_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + assert_ok!(TfgridModule::opt_out_of_v3_billing( + RuntimeOrigin::signed(alice()), + 2, + )); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 2, None), + Error::::NodeIsOptedOutOfV3Billing + ); + }); +} + +#[test] +fn test_migrate_node_contract_to_dedicated_node_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + assert_ok!(SmartContractModule::set_dedicated_node_extra_fee( + RuntimeOrigin::signed(alice()), + 2, + 1000 + )); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 2, None), + Error::::NodeNotAvailableToDeploy + ); + }); +} + +#[test] +fn test_migrate_node_contract_with_duplicate_hash_on_destination_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let hash = prepare_two_nodes_and_node_contract(); + + // same deployment hash already live on node 2 + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(dave()), + 2, + hash, + get_deployment_data(), + 0, + None + )); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 2, None), + Error::::ContractIsNotUnique + ); + }); +} + +#[test] +fn test_migrate_node_contract_from_signed_origin_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let _ = prepare_two_nodes_and_node_contract(); + + assert_noop!( + SmartContractModule::migrate_node_contract( + RuntimeOrigin::signed(alice()), + 1, + 2, + None + ), + sp_runtime::traits::BadOrigin + ); + }); +} + +#[test] +fn test_migrate_node_contract_does_not_destroy_another_contracts_hash_index() { + // update_node_contract has no hash-uniqueness check (unlike create), so a second + // contract can claim a hash already indexed to a first one, orphaning the first. + // Migrating the orphaned contract must NOT remove the index entry that now + // belongs to the live second contract. + new_test_ext().execute_with(|| { + run_to_block(1, None); + let hash_a = prepare_two_nodes_and_node_contract(); // contract 1, bob, node 1 + + // contract 2 on the same node, owned by charlie + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(charlie()), + 1, + generate_deployment_hash(), + get_deployment_data(), + 0, + None + )); + + // charlie points contract 2 at contract 1's hash; the index now maps + // (node 1, hash_a) -> 2, and contract 1 is orphaned in the index + assert_ok!(SmartContractModule::update_node_contract( + RuntimeOrigin::signed(charlie()), + 2, + hash_a, + get_deployment_data() + )); + assert_eq!(SmartContractModule::node_contract_by_hash(1, hash_a), 2); + + // migrating the orphaned contract 1 must leave contract 2's entry alone + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + 1, + 2, + None + )); + + assert_eq!( + SmartContractModule::node_contract_by_hash(1, hash_a), + 2, + "the live contract's index entry must survive the migration" + ); + assert_eq!(SmartContractModule::contracts(1).unwrap().get_node_id(), 2); + }); +} + +#[test] +fn test_migrate_node_contract_leaves_storage_untouched_on_rejection() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + let hash = prepare_two_nodes_and_node_contract(); + + assert_noop!( + SmartContractModule::migrate_node_contract(RawOrigin::Root.into(), 1, 1, None), + Error::::ContractAlreadyOnNode + ); + + assert_eq!(SmartContractModule::active_node_contracts(1), vec![1]); + assert!(SmartContractModule::active_node_contracts(2).is_empty()); + assert_eq!(SmartContractModule::node_contract_by_hash(1, hash), 1); + assert_eq!(SmartContractModule::contracts(1).unwrap().get_node_id(), 1); + }); +} + +/// F6 -- the report handlers must skip a stale entry, not abort the batch. +/// +/// Before migration existed this mismatch was unreachable: cancelling removes the +/// contract, and both loops already skip missing ones. `migrate_node_contract` is +/// the first construct that leaves a LIVE contract pointing at a different node, so +/// until the source node reconciles it keeps submitting batches naming a contract +/// that has moved. Aborting on those would freeze reporting for every OTHER contract +/// still on that node. +#[test] +fn test_report_contract_resources_skips_migrated_contract_and_keeps_the_rest() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_two_nodes(); + + // two contracts on node 1 + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(bob()), + 1, + generate_deployment_hash(), + get_deployment_data(), + 0, + None + )); + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(bob()), + 1, + generate_deployment_hash(), + get_deployment_data(), + 0, + None + )); + let migrated = 1; + let stayed = 2; + + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + migrated, + 2, + None + )); + + // node 1 (alice) reports for BOTH -- it has not reconciled yet + let reports = vec![ + types::ContractResources { + contract_id: migrated, + used: Resources { + cru: 2, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE, + }, + }, + types::ContractResources { + contract_id: stayed, + used: Resources { + cru: 4, + hru: 0, + mru: 4 * GIGABYTE, + sru: 80 * GIGABYTE, + }, + }, + ]; + + // the batch succeeds rather than reverting + assert_ok!(SmartContractModule::report_contract_resources( + RuntimeOrigin::signed(alice()), + reports + )); + + // the stale entry was dropped ... + assert_eq!( + SmartContractModule::node_contract_resources(migrated).used, + Resources::empty() + ); + // ... and the contract still on node 1 was NOT collateral damage + assert_eq!( + SmartContractModule::node_contract_resources(stayed).used.cru, + 4 + ); + }); +} + +/// F6 -- same guarantee for the NRU batch (`add_nru_reports` -> `_compute_reports`). +/// This is the periodic report, so an abort here would freeze `amount_unbilled` for +/// every other contract on the node on every submission. +#[test] +fn test_add_nru_reports_skips_migrated_contract_and_keeps_the_rest() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_two_nodes(); + + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(bob()), + 1, + generate_deployment_hash(), + get_deployment_data(), + 0, + None + )); + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(bob()), + 1, + generate_deployment_hash(), + get_deployment_data(), + 0, + None + )); + let migrated = 1; + let stayed = 2; + + assert_ok!(SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + migrated, + 2, + None + )); + + let gigabyte = 1000 * 1000 * 1000; + let reports = vec![ + super::types::NruConsumption { + contract_id: migrated, + nru: 3 * gigabyte, + timestamp: get_timestamp_in_seconds_for_block(1), + window: 6, + }, + super::types::NruConsumption { + contract_id: stayed, + nru: 3 * gigabyte, + timestamp: get_timestamp_in_seconds_for_block(1), + window: 6, + }, + ]; + + assert_ok!(SmartContractModule::add_nru_reports( + RuntimeOrigin::signed(alice()), + reports + )); + + // stale entry contributed nothing + assert_eq!( + SmartContractModule::contract_billing_information_by_id(migrated).amount_unbilled, + 0 + ); + // the other contract on the node was still billed for its traffic + assert!( + SmartContractModule::contract_billing_information_by_id(stayed).amount_unbilled > 0 + ); + }); +} + +/// F5 -- the post-billing state re-check is reachable, and this is the branch. +/// +/// `migrate_node_contract` bills inline, between validation and mutation. If that +/// billing finds the twin short of funds, `manage_contract_state` drops the contract +/// to `GracePeriod` and the re-read then fails the `Created` check. Returning Err is +/// deliberate: nothing was removed from the source yet, the contract still pins the +/// node, and Ok would be a silent no-op a batch caller could not distinguish from +/// success. The rolled-back billing cycle is redone by the offchain worker. +#[test] +fn test_migrate_node_contract_fails_when_inline_billing_pushes_to_grace_period() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_two_nodes(); + + TFTPriceModule::set_prices(RuntimeOrigin::signed(alice()), 50, 101).unwrap(); + + // charlie is the underfunded twin the grace-period suite uses: it can afford + // one billing cycle, not two + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(charlie()), + 1, + generate_deployment_hash(), + get_deployment_data(), + 0, + None + )); + let contract_id = 1; + + // Report the whole node as used, so a single short cycle already costs more + // than charlie's 150_000. The standard fixture footprint is affordable for + // about one cycle, which is not enough to force the transition below. + assert_ok!(SmartContractModule::report_contract_resources( + RuntimeOrigin::signed(alice()), + vec![types::ContractResources { + contract_id, + used: Resources { + cru: 8, + hru: 1024 * GIGABYTE, + mru: 16 * GIGABYTE, + sru: 512 * GIGABYTE, + }, + }] + )); + + // Stop short of block 11. Contract 1 is billed on blocks where + // `block % BillingFrequency == 1`, and crossing one would make the offchain + // worker sign -- which panics without a keystore, and would also settle the + // contract before the extrinsic gets to it. Staying below 11 leaves the cost + // accrued but unbilled and the contract in `Created`, so validation step 3 + // passes and the state change happens inside the extrinsic's own + // bill_contract, which is the branch under test. + run_to_block(10, None); + assert_eq!( + SmartContractModule::contracts(contract_id).unwrap().state, + types::ContractState::Created + ); + + assert_noop!( + SmartContractModule::migrate_node_contract( + RawOrigin::Root.into(), + contract_id, + 2, + None + ), + Error::::ContractNotInCreatedState + ); + + // assert_noop already proves storage is untouched; assert the intent too -- + // the contract still pins the SOURCE node, which is what makes Err correct + assert_eq!(SmartContractModule::active_node_contracts(1), vec![contract_id]); + assert!(SmartContractModule::active_node_contracts(2).is_empty()); + }); +} + #[test] fn test_cancel_contract_collective_by_council_approval_works() { new_test_ext().execute_with(|| { @@ -5159,6 +5812,58 @@ pub fn prepare_farm_and_node() { .unwrap(); } +/// farm 1 with node 1 (alice's twin) AND node 2 (charlie's twin), same farm. +/// A separate helper on purpose: adding a node to `prepare_farm_and_node` would +/// emit an extra NodeStored event and break the exact event-count assertions that +/// most of this suite relies on. +pub fn prepare_farm_and_two_nodes() { + prepare_farm_and_node(); + + let resources = ResourcesInput { + hru: 1024 * GIGABYTE, + sru: 512 * GIGABYTE, + cru: 8, + mru: 16 * GIGABYTE, + }; + + let location = LocationInput { + city: get_city_name_input(b"Ghent"), + country: get_country_name_input(b"Belgium"), + latitude: get_latitude_input(b"12.233213231"), + longitude: get_longitude_input(b"32.323112123"), + }; + + // pallet-tfgrid enforces one node per twin, so node 2 needs its own twin. + TfgridModule::create_node( + RuntimeOrigin::signed(charlie()), + 1, + resources, + location, + bounded_vec![], + false, + false, + None, + ) + .unwrap(); +} + +/// farm 1, two nodes, and a node contract owned by bob on node 1. +/// Distinct from `prepare_farm_node_and_node_contract`, where alice is farmer, +/// node owner AND contract owner. +pub fn prepare_two_nodes_and_node_contract() -> types::HexHash { + prepare_farm_and_two_nodes(); + let hash = generate_deployment_hash(); + assert_ok!(SmartContractModule::create_node_contract( + RuntimeOrigin::signed(bob()), + 1, + hash, + get_deployment_data(), + 0, + None + )); + hash +} + pub fn prepare_farm_node_and_node_contract() { prepare_farm_and_node(); let node_id = 1; diff --git a/substrate-node/pallets/pallet-smart-contract/src/weights.rs b/substrate-node/pallets/pallet-smart-contract/src/weights.rs index 715755161..35940c1e4 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/weights.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/weights.rs @@ -56,6 +56,7 @@ pub trait WeightInfo { fn attach_solution_provider_id() -> Weight; fn set_dedicated_node_extra_fee() -> Weight; fn cancel_contract_collective() -> Weight; + fn migrate_node_contract(n: u32, ) -> Weight; } /// Weights for pallet_smart_contract using the Substrate node and recommended hardware. @@ -576,6 +577,22 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } + /// Placeholder until regenerated by `060_generate_benchmark_weights.yml`. + /// Base is `cancel_contract_collective` (closest read/write profile: contract + + /// billing + two node-keyed maps). `n` is the max length of the source and + /// destination `ActiveNodeContracts` vectors, both of which are read, mutated + /// and re-encoded; the per-element slope is modelled on `pallet-tfgrid`'s + /// `add_twin_admin`, scaled from its 32-byte elements to our 8-byte u64s. + fn migrate_node_contract(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1650 + n * (8 ±0)` + // Estimated: `7590 + n * (8 ±0)` + Weight::from_parts(233_363_000, 7590) + .saturating_add(Weight::from_parts(53_223, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(16_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(n.into())) + } } // For backwards compatibility and tests @@ -1095,4 +1112,20 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } + /// Placeholder until regenerated by `060_generate_benchmark_weights.yml`. + /// Base is `cancel_contract_collective` (closest read/write profile: contract + + /// billing + two node-keyed maps). `n` is the max length of the source and + /// destination `ActiveNodeContracts` vectors, both of which are read, mutated + /// and re-encoded; the per-element slope is modelled on `pallet-tfgrid`'s + /// `add_twin_admin`, scaled from its 32-byte elements to our 8-byte u64s. + fn migrate_node_contract(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1650 + n * (8 ±0)` + // Estimated: `7590 + n * (8 ±0)` + Weight::from_parts(233_363_000, 7590) + .saturating_add(Weight::from_parts(53_223, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(16_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(n.into())) + } } diff --git a/substrate-node/tests/SubstrateNetwork.py b/substrate-node/tests/SubstrateNetwork.py index d6b8794bc..bb50e0236 100644 --- a/substrate-node/tests/SubstrateNetwork.py +++ b/substrate-node/tests/SubstrateNetwork.py @@ -13,7 +13,10 @@ SUBSTRATE_NODE_DIR = dirname(os.getcwd()) -TMP_DIR = "\tmp" +# NOTE: this was previously "\tmp", which Python reads as TAB + "mp" -- so every +# local run created a directory named mp inside tests/ instead of using the +# system temp dir, leaving untracked chain data in the working tree. +TMP_DIR = tempfile.gettempdir() TFCHAIN_EXE = join(SUBSTRATE_NODE_DIR, "target", "release", "tfchain") RE_NODE_STARTED = re.compile("Running JSON-RPC server") diff --git a/substrate-node/tests/TfChainClient.py b/substrate-node/tests/TfChainClient.py index 1b205ae2c..040fad0bb 100644 --- a/substrate-node/tests/TfChainClient.py +++ b/substrate-node/tests/TfChainClient.py @@ -428,6 +428,35 @@ def get_node(self, id: int = 1, port: int = DEFAULT_PORT): q = substrate.query("TfgridModule", "Nodes", [id]) return q.value + def change_power_target(self, node_id: int = 1, power_target: str = "Down", port: int = DEFAULT_PORT, + who: str = DEFAULT_SIGNER): + """Ask a node to power up/down. Only the farm owner twin may call this, and + powering down is refused while the node still holds active contracts.""" + substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") + + call = substrate.compose_call("TfgridModule", "change_power_target", { + "node_id": node_id, + "power_target": power_target + }) + expected_events = [{ + "module_id": "TfgridModule", + "event_id": "PowerTargetChanged" + }] + self._sign_extrinsic_submit_check_response( + substrate, call, who, expected_events=expected_events) + + def get_contract(self, id: int = 1, port: int = DEFAULT_PORT): + substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") + + q = substrate.query("SmartContractModule", "Contracts", [id]) + return q.value + + def get_active_node_contracts(self, node_id: int = 1, port: int = DEFAULT_PORT): + substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") + + q = substrate.query("SmartContractModule", "ActiveNodeContracts", [node_id]) + return q.value + def create_node_contract(self, node_id: int = 1, deployment_data: bytes = randbytes(32), deployment_hash: bytes = randbytes(32), public_ips: int = 0, solution_provider_id: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): @@ -465,6 +494,29 @@ def update_node_contract(self, contract_id: int = 1, deployment_data: bytes = ra self._sign_extrinsic_submit_check_response( substrate, call, who, expected_events=expected_events) + def migrate_node_contract(self, contract_id: int = 1, node_id: int = 2, deployment_hash: bytes | None = None, + port: int = DEFAULT_PORT, who: str = "Council"): + """Move a node contract to another node in the same farm. + + Takes a configurable origin (root or council), so `who` defaults to + Council. `deployment_hash=None` keeps the contract's current hash. + No dedicated event is emitted: ContractUpdated already carries the new + node id and hash. + """ + substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") + + call = substrate.compose_call("SmartContractModule", "migrate_node_contract", { + "contract_id": contract_id, + "node_id": node_id, + "deployment_hash": deployment_hash + }) + expected_events = [{ + "module_id": "SmartContractModule", + "event_id": "ContractUpdated" + }] + self._sign_extrinsic_submit_check_response( + substrate, call, who, expected_events=expected_events) + def create_rent_contract(self, node_id: int = 1, solution_provider_id: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index 5ceb4c7ca..ad4aca341 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -309,6 +309,48 @@ Test Create Update Cancel Node Contract: Success Tear Down Multi Node Network +Test Migrate Node Contract: Success + [Documentation] Move a node contract to a second node in the same farm without cancelling it + Setup Multi Node Network log_name=test_migrate_node_contract amt=${2} + + Setup Predefined Account who=Alice + Setup Predefined Account who=Bob + Setup Predefined Account who=Charlie + + Create Farm name=alice_farm + + ${interface_ips} = Create List 10.2.3.3 + ${interface_1} = Create Interface name=zos mac=00:00:5e:00:53:af ips=${interface_ips} + ${interfaces} = Create List ${interface_1} + Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent interfaces=${interfaces} + + # a second node on the SAME farm needs its own twin + Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent interfaces=${interfaces} who=Charlie + + # Bob owns the contract; the migration is a council action + Create Node Contract node_id=${1} who=Bob port=9945 + + ${contract} = Get Contract ${1} + Should Be Equal ${contract}[contract_type][NodeContract][node_id] ${1} msg=The contract should start on node 1 + + Migrate Node Contract contract_id=${1} node_id=${2} who=Council + + ${contract} = Get Contract ${1} + Should Be Equal ${contract}[contract_type][NodeContract][node_id] ${2} msg=The contract should have moved to node 2 + + ${on_node_1} = Get Active Node Contracts ${1} + Should Be Empty ${on_node_1} msg=Node 1 should have no active contracts left + ${on_node_2} = Get Active Node Contracts ${2} + Should Contain ${on_node_2} ${1} msg=Node 2 should now hold the contract + + # the point of the whole feature: the drained node can now be powered down, + # which fails with NodeHasActiveContracts while it still holds a contract + Change Power Target node_id=${1} power_target=Down who=Alice + + Cancel Node Contract contract_id=${1} who=Bob port=${9945} + + Tear Down Multi Node Network + Test Create Node Contract: Failure Not Enough Public Ips [Documentation] Testing creating a node contract and requesting too much pub ips Setup Multi Node Network log_name=test_create_node_contract_failure_notenoughpubips From 1607fdad815c131ac22ebaf3634ae3aacaeeeded Mon Sep 17 00:00:00 2001 From: Sameh Abouelsaad Date: Wed, 19 Aug 2026 16:48:53 +0300 Subject: [PATCH 2/5] docs(0027): trim the billing note to the part that acts on the reader The NodeContractResources section opened by explaining that the tenant is not over-billed by a migration. That is true but it documents a non-event: billing continues at the same quantity on the same schedule, so describing it at length mostly plants the doubt it then answers. What survives is the warning, which three reviewers of this change independently tried to "fix" by clearing the entry. Rewritten to stand on its own so the dangling reference to the removed paragraph goes with it. Also drops "retiring the 2012 generation means 122" from the context. The number carried no unit, and the sentence already lands with 74 contracts across 29 twins. --- .../0027-migrate-node-contract.md | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/docs/architecture/0027-migrate-node-contract.md b/docs/architecture/0027-migrate-node-contract.md index 691e09ce6..3cdc93577 100644 --- a/docs/architecture/0027-migrate-node-contract.md +++ b/docs/architecture/0027-migrate-node-contract.md @@ -16,8 +16,8 @@ path for the tenant to return under the same identity. That blocks hardware consolidation. On mainnet at spec 157, emptying ten machines in Freefarm means relocating 74 contracts across 29 twins, none of them the -operator; retiring the 2012 generation means 122. Asking those twins to -recreate contracts by hand is not available to a hosting provider. +operator. Asking those twins to recreate contracts by hand is not available to a +hosting provider. Two alternatives were rejected. **Cancel and recreate** is the problem, not the fix. **Off-chain coordination** offload the responsibility to the tenant, which @@ -117,19 +117,13 @@ Then the contract moves between the two `ActiveNodeContracts` vectors, and `bill_contract` skips this on its zero-amount early return, and the `deployment_hash` parameter can change what the destination deploys. -**`NodeContractResources` is deliberately NOT cleared,** and the tenant is not -over-billed by the move. The entry is contract-keyed, so it survives the migration -and billing continues at the same quantity on the same schedule — the inline -`bill_contract` settles the source period and stamps the clock (`billing.rs:199,369`), -so the next cycle bills from that instant forward. No overlap, no gap, no double -charge; the loop index is `contract_id % billing_frequency`, which the move does not -change. The one real deviation is the certification multiplier, covered below. - -Clearing the entry is what would break this. `calculate_resources_cost_units_usd` -(`cost.rs:71-89`) derives the whole node-contract cost from it, so zeroing it sends -`bill_contract` down its zero-amount early return (`billing.rs:276-286`) — no -overdraft, so no grace, so no 14-day auto-delete, leaving a free immortal contract -pinning the destination against `node_has_no_active_contracts`. +**Do not clear `NodeContractResources` on migration.** It is contract-keyed and +survives the move, which is what keeps billing unchanged. +`calculate_resources_cost_units_usd` (`cost.rs:71-89`) derives the whole +node-contract cost from it, so zeroing it sends `bill_contract` down its +zero-amount early return (`billing.rs:276-286`): no overdraft, so no grace, so no +14-day auto-delete, leaving a free immortal contract pinning the destination +against `node_has_no_active_contracts`. ### Ordering is forced by ZOS: migrate first, then deploy From aa58a249d65a4453f6e8fbf5307b67ba895aa91e Mon Sep 17 00:00:00 2001 From: Sameh Abouelsaad Date: Wed, 19 Aug 2026 17:01:17 +0300 Subject: [PATCH 3/5] docs(0027): restructure as a decisions record, move the billing trap into the code The ADR read as an implementation walkthrough: it narrated what the extrinsic does in execution order, re-telling mechanics that already carry comments at their own sites. That duplicates the code and goes stale the moment either side moves. Reorganised around the eight decisions actually taken, each with its reasoning and what it costs. Preconditions move into their own section, since an operator wants them as a list rather than buried in rationale. Nothing is dropped: the call_index 23 forward constraint, the transaction_version reasoning, the ZOS-forced ordering, certification repricing and the weight ceiling are all still here. The "do not clear NodeContractResources" warning becomes a comment in _migrate_node_contract, next to the mutation where someone would be tempted. Three reviewers of this change proposed clearing it; the warning belongs where that edit would be written, not in a document they may not open. --- .../0027-migrate-node-contract.md | 285 ++++++++---------- .../src/grid_contract.rs | 8 + 2 files changed, 135 insertions(+), 158 deletions(-) diff --git a/docs/architecture/0027-migrate-node-contract.md b/docs/architecture/0027-migrate-node-contract.md index 3cdc93577..f3059a818 100644 --- a/docs/architecture/0027-migrate-node-contract.md +++ b/docs/architecture/0027-migrate-node-contract.md @@ -9,10 +9,9 @@ Accepted ## Context A farmer cannot power a node down while it holds a single active contract: -`change_power_target(Down)` requires `node_has_no_active_contracts` -(`pallet-tfgrid/src/node.rs:373-378`). The only way to empty a node today is to -cancel its contracts, which is terminal — the contract is gone, and with it any -path for the tenant to return under the same identity. +`change_power_target(Down)` requires `node_has_no_active_contracts`. The only way +to empty a node today is to cancel its contracts, which is terminal — the contract +is gone, and with it any path for the tenant to return under the same identity. That blocks hardware consolidation. On mainnet at spec 157, emptying ten machines in Freefarm means relocating 74 contracts across 29 twins, none of them the @@ -20,194 +19,164 @@ operator. Asking those twins to recreate contracts by hand is not available to a hosting provider. Two alternatives were rejected. **Cancel and recreate** is the problem, not the -fix. **Off-chain coordination** offload the responsibility to the tenant, which -is not feasible. +fix. **Off-chain coordination** cannot work: the contract ID is the identity ZOS, +the billing loop and the indexer all key on, and nothing off-chain can move it. -### What the chain does and does not guarantee +### What migration moves, and what it does not -**Migration moves the booking, not the bytes.** The chain stores a contract ID, a +**It moves the booking, not the bytes.** The chain stores a contract ID, a deployment hash and small metadata; the workload lives on the node. Once the -contract leaves `ActiveNodeContracts[source]`, that node's ZOS reconciles, finds a -local deployment the chain no longer lists for it, and deprovisions it — and -`zmount.Deprovision` calls `DiskDelete`. Nothing is copied between nodes, and no +contract leaves the source node's `ActiveNodeContracts`, that node's ZOS +reconciles, finds a local deployment the chain no longer lists for it, and +deprovisions it — deleting the disks. Nothing is copied between nodes, and no on-chain mechanism could copy it. So **preserving data is the caller's responsibility, before calling.** The team's -intended sequence for the consolidation is: pause the deployment on the source, -copy it to S3, call `migrate_node_contract` — at which point tearing down the -source is safe and expected — then create the deployment on the destination. The -chain neither enforces nor observes any of that. +intended sequence is: pause the deployment on the source, copy it to S3, call +`migrate_node_contract` — at which point tearing down the source is safe and +expected — then create the deployment on the destination. The chain neither +enforces nor observes any of that. What migration preserves that cancellation does not: the contract ID, billing continuity, the reward payee, reserved public IPs, and a live contract to deploy against. That is the whole case for the feature — it turns an irreversible operator action into a recoverable one. -## Decision +## Decisions -### New extrinsic (pallet-smart-contract) +### 1. A new extrinsic, restricted to council | Extrinsic | Origin | Call index | | --- | --- | --- | | `migrate_node_contract(contract_id, node_id, deployment_hash)` | `RestrictedOrigin` (root or 3/5 council) | 22 | -`deployment_hash` is `Option`; `None` keeps the current hash. The hash -covers the full deployment sent to the node and legitimately changes for network -workloads but not for VMs. On-chain `deployment_data` is small metadata, unaffected -by a relocation, and is therefore not a parameter. - -**Council-only for v1.** The consolidation is cross-tenant by construction, so -restricting v1 removes the whole authorization matrix (owner vs farmer, and who may -rewrite a deployment hash). **Owner and farmer paths must arrive as a new -`call_index(23)`, never by widening 22's origin check** — adding a dispatchable is -exempt from a `transaction_version` bump (`sp_version/src/lib.rs:209`), altering an -existing one's semantics is not (`:204-206`). - -That rule cuts against this changeset's own report-handler fix, so state it plainly -rather than let this PR become precedent: `transaction_version` stays at 2 because -the call index and parameter encoding are byte-identical (so no signed payload -decodes differently), the change is strictly in the submitter's favour, and both -calls are ZOS telemetry with no offline-signing consumer. A future semantics change -lacking those three properties needs the bump. - -### Preconditions enforced on-chain - -1. Contract exists and is a node contract. -2. State is `Created`. Every target contract, and all 2,372 in Freefarm, are - `Created`; the chain holds exactly one `GracePeriod` node contract, and - relocating a contract due to auto-delete in 14 days only moves where it dies. -3. Destination differs from the source. -4. Same **farm**. No same-country check — farm membership is the only relationship - the chain models, and it is what keeps public-IP reservations coherent, since - `reserve_ip`/`free_ip` resolve the farm *through* the node. -5. Neither node is in `NodeV3BillingOptOut`, and neither has an - `ActiveRentContractForNode`. Both would silently reprice the tenant: opting out - waives billing, and a rent contract zeroes CPU/RAM/disk cost (`cost.rs:78`). -6. Destination is not standby and not dedicated. The **source** node's power state - is deliberately unchecked — migrating off a machine you are about to shut down - is the point. -7. The destination's `(node_id, deployment_hash)` key is strictly free. Stricter - than `create_node_contract`, which permits overwriting a `Deleted` entry: there - is no restore semantic here, and that contract's eventual `remove_contract` - would unconditionally delete the key we just claimed. - -### Behaviour - -**Bills first**, settling at the source node's cost basis — billing reads the -source's certification and resolves the payee from the source farm — then re-reads -the contract, because billing can mutate or remove it. The two post-billing -branches answer deliberately differently: - -- **grace → `Err`** (the live path): an underfunded twin transitions to - `GracePeriod` inside this call. Nothing was removed, the contract still pins the - node, and `Ok` would be a silent no-op indistinguishable from success. The - rolled-back cycle is redone by the offchain worker. -- **gone → `Ok`** (currently unreachable, kept as a guard): `bill_contract` only - deletes once an *existing* grace period elapses, and precondition 2 already - refused anything but `Created`. - -**The `ContractIDByNodeIDAndHash` removal is guarded by an equality check.** -`update_node_contract` never enforced hash uniqueness where `create_node_contract` -does, so a key may already point at a *different* live contract; an unguarded -remove would destroy that contract's index entry. This is a real regression guard -with a test, not defensive padding. - -Then the contract moves between the two `ActiveNodeContracts` vectors, and -`ContractPaymentState.last_updated_seconds` is stamped unconditionally — -`bill_contract` skips this on its zero-amount early return, and the -`deployment_hash` parameter can change what the destination deploys. - -**Do not clear `NodeContractResources` on migration.** It is contract-keyed and -survives the move, which is what keeps billing unchanged. -`calculate_resources_cost_units_usd` (`cost.rs:71-89`) derives the whole -node-contract cost from it, so zeroing it sends `bill_contract` down its -zero-amount early return (`billing.rs:276-286`): no overdraft, so no grace, so no -14-day auto-delete, leaving a free immortal contract pinning the destination -against `node_has_no_active_contracts`. - -### Ordering is forced by ZOS: migrate first, then deploy - -A deployment cannot be pre-staged. `validate()` -(`zosbase/pkg/provision/engine.go:616-626`) rejects any deployment whose contract -does not already name that node and whose `ChallengeHash()` does not match the -contract's on-chain hash; the only bypass is `boot()` reinstalling from local -storage. So the destination cannot accept the workload until the extrinsic has -landed — which is what the `deployment_hash` parameter exists for. Without it the -flow would need `update_node_contract`, which is owner-only, so a council-driven -migration could not fix the hash at all. - -### No new event - -`ContractUpdated(Contract)` already carries the whole contract, hence the new -`node_id` and hash, and the indexer already writes both -(`tfchain_graphql/src/mappings/contracts.ts:227,231`). So tfchain_graphql and grid -proxy need no changes, and the Go client needs only a call wrapper. Source-node -cleanup needs no chain change either: `ContractEventHandler.sync()` runs hourly, -compares local deployments against `ActiveNodeContracts[node]`, and deprovisions -what the chain no longer lists. - -**Prompt teardown was considered and is not worth building.** A ZOS node could act on -`ContractUpdated` directly rather than waiting for `sync()`, since every node -already decodes the complete `EventRecords` per block locally. But it accelerates -nothing the operator needs — `change_power_target(Down)` is gated on -`ActiveNodeContracts`, which is empty the instant the extrinsic lands — and the -measured benefit is the negligible stream saving above. If it is ever built: - -- whoever builds it must confirm the operating procedure still copies data off the -source *before* calling, because same-block teardown removes any margin for a -procedure that does not. -- A dedicated `NodeContractMigrated` would let ZOS filter before pushing to its local +The consolidation is cross-tenant by construction, so restricting v1 removes the +whole authorization matrix — owner versus farmer, and who may rewrite a deployment +hash — rather than answering it. + +`deployment_hash` is `Option`; `None` keeps the current hash. It exists +because the hash covers the full deployment sent to the node and legitimately +changes for network workloads, though not for VMs. On-chain `deployment_data` is +small metadata unaffected by a relocation, so it is not a parameter. + +**Forward constraint: owner and farmer paths must arrive as a new `call_index(23)`, +never by widening 22's origin check.** Adding a dispatchable is exempt from a +`transaction_version` bump; altering an existing one's semantics is not. + +### 2. Same farm, and deliberately no same-country rule + +Farm membership is the only relationship the chain models, and it is what keeps +public-IP reservations coherent, since `reserve_ip`/`free_ip` resolve the farm +*through* the node. A same-country rule would encode a relationship the chain +cannot verify. + +### 3. `Created` contracts only + +Every target contract is `Created`, and the chain holds exactly one `GracePeriod` +node contract. Relocating a contract that will auto-delete in 14 days only moves +where it dies. + +### 4. Opted-out and rented nodes excluded, on both sides + +Both would silently reprice the tenant: opting out of v3 billing waives billing +entirely, and a rent contract zeroes CPU/RAM/disk cost. Excluding them costs +nothing in the farms this was built for and removes two repricing paths. + +### 5. Bill before moving + +Billing settles at the **source** node's cost basis — it reads the source's +certification and resolves the payee from the source farm — so the source period +is closed before the contract changes hands. The contract is then re-read, because +billing can mutate or remove it, and the two branches that follow answer +deliberately differently: a contract pushed into grace returns `Err`, a contract +already deleted returns `Ok`. The reasoning for that asymmetry, and for the +guarded `ContractIDByNodeIDAndHash` removal and the unconditional clock stamp, +lives in comments at each site in `_migrate_node_contract`, where the next person +to touch them will actually read it. + +### 6. Reuse `ContractUpdated`; add no event + +`ContractUpdated` already carries the whole contract, hence the new `node_id` and +hash, and the indexer already writes both. So tfchain_graphql and grid proxy need +no changes, and the Go client needs only a call wrapper. Source-node cleanup needs +no chain change either: `ContractEventHandler.sync()` runs hourly, compares local +deployments against `ActiveNodeContracts[node]`, and deprovisions what the chain no +longer lists. + +A dedicated `NodeContractMigrated` would let ZOS filter before pushing to its local stream, but `ContractUpdated` fires well under once per hour chain-wide, so the -saving is about one discarded stream entry every few hours. It remains purely +saving is roughly one discarded stream entry every few hours. It stays purely additive if audit ever wants it. -### New errors +### 7. Do not build prompt teardown -Appended at the end of the enum — `Error` variants have no explicit index and are -SCALE-encoded by declaration order: `NodeNotInSameFarm`, `ContractAlreadyOnNode`, -`NodeIsOptedOutOfV3Billing`, `ContractNotInCreatedState`. +A ZOS node could act on `ContractUpdated` directly rather than waiting for +`sync()`, since every node already decodes the complete `EventRecords` per block +locally. It is not worth building: it accelerates nothing the operator needs, since +`change_power_target(Down)` is gated on `ActiveNodeContracts`, which empties the +instant the extrinsic lands — and the benefit is the negligible stream saving +above. -`NodeNotAuthorizedToComputeReport` is left in place and marked reserved; the -companion fix below removed its last construction site, and deleting it would -renumber every variant below. +If it is ever built, whoever builds it must confirm the operating procedure still +copies data off the source *before* calling, because same-block teardown removes +any margin for a procedure that does not. -### Companion fix: report handlers skip a stale entry, consistently +### 8. Report handlers skip a stale entry instead of aborting -Best read as a consistency sweep. Both handlers already tolerate a report naming a -contract that does not exist — `if let Some(contract)` in -`_report_contract_resources`, two `contains_key` guards in `_compute_reports` — -skipping it silently and for free, since both end in `Ok(Pays::No)`. A report -naming a contract that exists but sits on **another** node is the same class of -mistake, yet was handled the opposite way: `ensure!` rejecting the whole extrinsic, -and charging for it. That asymmetry was the anomaly. Both now `continue` and log. +A consistency sweep more than a behaviour change. Both handlers already tolerated a +report naming a contract that does not exist, skipping it silently and for free. A +report naming a contract that exists but sits on **another** node is the same class +of mistake, yet was handled the opposite way — rejecting the whole extrinsic and +charging for it. That asymmetry was the anomaly. -The abort path stops being reachable exactly when it would start to matter: it was -unreachable before, because cancellation removes the contract and the -missing-contract guards catch it. `migrate_node_contract` is the first operation -that leaves a *live* contract pointing elsewhere, and left as it was, one migrated +It was also unreachable until now: cancellation removes the contract, so the +missing-contract guards caught it. `migrate_node_contract` is the first operation +that leaves a *live* contract pointing elsewhere, and left alone, one migrated contract would have frozen resource and NRU reporting for every other contract on the source node. -Charging for a skipped entry was considered and rejected: it would re-introduce the -asymmetry in the other direction. If the anti-spam posture is revisited, both cases -should move together. +Charging for a skipped entry was considered and rejected — it would re-introduce +the asymmetry in the other direction. If the anti-spam posture is revisited, both +cases should move together. + +## What the extrinsic refuses + +1. A contract that does not exist, or is not a node contract. +2. A contract not in `Created` state. +3. A destination equal to the source. +4. A destination in a different farm. +5. Either node opted out of v3 billing, or holding a rent contract. +6. A destination that is in standby or dedicated. The **source** node's power state + is deliberately unchecked — migrating off a machine you are about to shut down + is the point. +7. A destination whose `(node_id, deployment_hash)` key is taken. Stricter than + `create_node_contract`, which permits overwriting a `Deleted` entry: there is no + restore semantic here, and that contract's eventual `remove_contract` would + delete the key we just claimed. + +New errors are appended at the end of the enum, since `Error` variants are +SCALE-encoded by declaration order: `NodeNotInSameFarm`, `ContractAlreadyOnNode`, +`NodeIsOptedOutOfV3Billing`, `ContractNotInCreatedState`. +`NodeNotAuthorizedToComputeReport` is kept and marked reserved — decision 8 removed +its last construction site, and deleting it would renumber every variant below. -## Consequences +## Consequences we accept -- No storage migration. `Contract` unchanged, `CONTRACT_VERSION` 4, `StorageVersion` - V12, `transaction_version` 2. No indexer, grid proxy or ZOS change required. +- **No storage migration.** `Contract` unchanged, `CONTRACT_VERSION` 4, + `StorageVersion` V12, `transaction_version` 2. No indexer, grid proxy or ZOS + change required. - **An emptied `ActiveNodeContracts` is not permission to power the machine off.** It empties the instant the extrinsic lands and says nothing about where the workload is. Nothing in the pallet can detect the difference. -- **Certification is per-node and legitimately mixed within a farm**, driving a - +25% Certified multiplier, so a council-approved move can reprice a tenant. Worse across the - reporting gap: subsequent cycles read the *destination's* certification - (`billing.rs:161-175`) against the *source's* still-stored footprint — new price × - old quantity, either direction, until the destination reports. Choosing - certification-matched destinations makes the multiplier 1.0 and removes it. If the - signed path is added it needs a directional guard. -- **A council motion has a weight ceiling.** `MaxProposalWeight` is 50% of max block - weight, bounding a motion at roughly 900–1,800 contracts. +- **A council-approved move can reprice a tenant.** Certification is per-node and + legitimately mixed within a farm, and Certified nodes bill 25% above Diy. Worse + across the reporting gap: subsequent cycles read the *destination's* + certification against the *source's* still-stored footprint — new price × old + quantity, either direction, until the destination reports. Certification-matched + destinations make the multiplier 1.0 and remove it. If the signed path is added, + it needs a directional guard. +- **A council motion has a weight ceiling.** `MaxProposalWeight` is 50% of max + block weight, bounding a motion at roughly 900–1,800 contracts. ## Operational gate diff --git a/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs b/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs index f634bf0fd..01339ca8d 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs @@ -439,6 +439,14 @@ impl Pallet { destination_contracts.push(contract_id); ActiveNodeContracts::::insert(&node_id, &destination_contracts); + // NodeContractResources is deliberately left alone. It is contract-keyed, so it + // survives the move, and that is what keeps billing continuous. Clearing it + // looks tidy and is a trap: calculate_resources_cost_units_usd (cost.rs) derives + // the whole node-contract cost from this entry, so zeroing it sends every + // caller of bill_contract -- including a manual bill_contract_for_block -- + // down the zero-amount early return. No overdraft means no grace period, which + // means no 14-day auto-delete: a free, immortal contract pinning the + // destination against node_has_no_active_contracts forever. node_contract.node_id = node_id; node_contract.deployment_hash = target_hash; contract.contract_type = types::ContractData::NodeContract(node_contract); From e8f7ae3ffd3cbaf814a55adc5a3e9e72f4f263bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Aug 2026 11:14:43 +0000 Subject: [PATCH 4/5] chore: update benchmark `weights.rs` file for all pallets --- .../pallets/pallet-burning/src/weights.rs | 12 +- .../pallets/pallet-dao/src/weights.rs | 36 +- .../pallets/pallet-kvstore/src/weights.rs | 20 +- .../pallet-smart-contract/src/weights.rs | 314 ++++++++++------- .../pallets/pallet-tfgrid/src/weights.rs | 332 +++++++++--------- .../pallets/pallet-tft-bridge/src/weights.rs | 100 +++--- .../pallets/pallet-tft-price/src/weights.rs | 28 +- .../pallets/pallet-validator/src/weights.rs | 52 +-- .../substrate-validator-set/src/weights.rs | 28 +- 9 files changed, 500 insertions(+), 422 deletions(-) diff --git a/substrate-node/pallets/pallet-burning/src/weights.rs b/substrate-node/pallets/pallet-burning/src/weights.rs index 4ab7d2cf9..abf45ecaf 100644 --- a/substrate-node/pallets/pallet-burning/src/weights.rs +++ b/substrate-node/pallets/pallet-burning/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_burning //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -45,8 +45,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 87_028_000 picoseconds. - Weight::from_parts(90_638_000, 1594) + // Minimum execution time: 86_124_000 picoseconds. + Weight::from_parts(90_443_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -60,8 +60,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 87_028_000 picoseconds. - Weight::from_parts(90_638_000, 1594) + // Minimum execution time: 86_124_000 picoseconds. + Weight::from_parts(90_443_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } diff --git a/substrate-node/pallets/pallet-dao/src/weights.rs b/substrate-node/pallets/pallet-dao/src/weights.rs index dba784072..9ed1208a8 100644 --- a/substrate-node/pallets/pallet-dao/src/weights.rs +++ b/substrate-node/pallets/pallet-dao/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_dao //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -58,8 +58,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `208` // Estimated: `4687` - // Minimum execution time: 39_181_000 picoseconds. - Weight::from_parts(61_474_000, 4687) + // Minimum execution time: 38_887_000 picoseconds. + Weight::from_parts(65_234_000, 4687) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -77,8 +77,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `979` // Estimated: `4444` - // Minimum execution time: 50_866_000 picoseconds. - Weight::from_parts(79_104_000, 4444) + // Minimum execution time: 48_653_000 picoseconds. + Weight::from_parts(90_109_000, 4444) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -92,8 +92,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `487` // Estimated: `4687` - // Minimum execution time: 40_601_000 picoseconds. - Weight::from_parts(57_403_000, 4687) + // Minimum execution time: 49_894_000 picoseconds. + Weight::from_parts(55_437_000, 4687) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -111,8 +111,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `521` // Estimated: `4687` - // Minimum execution time: 48_263_000 picoseconds. - Weight::from_parts(76_166_000, 4687) + // Minimum execution time: 52_398_000 picoseconds. + Weight::from_parts(82_391_000, 4687) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -136,8 +136,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `208` // Estimated: `4687` - // Minimum execution time: 39_181_000 picoseconds. - Weight::from_parts(61_474_000, 4687) + // Minimum execution time: 38_887_000 picoseconds. + Weight::from_parts(65_234_000, 4687) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -155,8 +155,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `979` // Estimated: `4444` - // Minimum execution time: 50_866_000 picoseconds. - Weight::from_parts(79_104_000, 4444) + // Minimum execution time: 48_653_000 picoseconds. + Weight::from_parts(90_109_000, 4444) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -170,8 +170,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `487` // Estimated: `4687` - // Minimum execution time: 40_601_000 picoseconds. - Weight::from_parts(57_403_000, 4687) + // Minimum execution time: 49_894_000 picoseconds. + Weight::from_parts(55_437_000, 4687) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -189,8 +189,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `521` // Estimated: `4687` - // Minimum execution time: 48_263_000 picoseconds. - Weight::from_parts(76_166_000, 4687) + // Minimum execution time: 52_398_000 picoseconds. + Weight::from_parts(82_391_000, 4687) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } diff --git a/substrate-node/pallets/pallet-kvstore/src/weights.rs b/substrate-node/pallets/pallet-kvstore/src/weights.rs index 8ae6c0557..6a8ecac3d 100644 --- a/substrate-node/pallets/pallet-kvstore/src/weights.rs +++ b/substrate-node/pallets/pallet-kvstore/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_kvstore //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -46,8 +46,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 23_122_000 picoseconds. - Weight::from_parts(26_525_000, 0) + // Minimum execution time: 19_873_000 picoseconds. + Weight::from_parts(27_293_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `TFKVStore::TFKVStore` (r:1 w:1) @@ -56,8 +56,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `146` // Estimated: `3611` - // Minimum execution time: 23_369_000 picoseconds. - Weight::from_parts(24_237_000, 3611) + // Minimum execution time: 40_476_000 picoseconds. + Weight::from_parts(48_129_000, 3611) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -71,8 +71,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 23_122_000 picoseconds. - Weight::from_parts(26_525_000, 0) + // Minimum execution time: 19_873_000 picoseconds. + Weight::from_parts(27_293_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `TFKVStore::TFKVStore` (r:1 w:1) @@ -81,8 +81,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `146` // Estimated: `3611` - // Minimum execution time: 23_369_000 picoseconds. - Weight::from_parts(24_237_000, 3611) + // Minimum execution time: 40_476_000 picoseconds. + Weight::from_parts(48_129_000, 3611) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } diff --git a/substrate-node/pallets/pallet-smart-contract/src/weights.rs b/substrate-node/pallets/pallet-smart-contract/src/weights.rs index 35940c1e4..4fda720e8 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/weights.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_smart_contract //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -98,8 +98,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `868` // Estimated: `4333` - // Minimum execution time: 94_195_000 picoseconds. - Weight::from_parts(97_943_000, 4333) + // Minimum execution time: 153_191_000 picoseconds. + Weight::from_parts(175_883_000, 4333) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(8_u64)) } @@ -113,8 +113,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `869` // Estimated: `4334` - // Minimum execution time: 45_239_000 picoseconds. - Weight::from_parts(66_665_000, 4334) + // Minimum execution time: 76_922_000 picoseconds. + Weight::from_parts(83_803_000, 4334) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -154,8 +154,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `7590` - // Minimum execution time: 147_718_000 picoseconds. - Weight::from_parts(245_368_000, 7590) + // Minimum execution time: 202_502_000 picoseconds. + Weight::from_parts(290_227_000, 7590) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -179,8 +179,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `340` // Estimated: `3805` - // Minimum execution time: 47_262_000 picoseconds. - Weight::from_parts(48_046_000, 3805) + // Minimum execution time: 63_406_000 picoseconds. + Weight::from_parts(81_571_000, 3805) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -206,8 +206,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `949` // Estimated: `4414` - // Minimum execution time: 98_979_000 picoseconds. - Weight::from_parts(159_048_000, 4414) + // Minimum execution time: 164_903_000 picoseconds. + Weight::from_parts(191_094_000, 4414) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -229,8 +229,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1286` // Estimated: `4751` - // Minimum execution time: 69_958_000 picoseconds. - Weight::from_parts(98_651_000, 4751) + // Minimum execution time: 118_126_000 picoseconds. + Weight::from_parts(149_359_000, 4751) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -246,8 +246,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `765` // Estimated: `4230` - // Minimum execution time: 43_491_000 picoseconds. - Weight::from_parts(62_625_000, 4230) + // Minimum execution time: 71_420_000 picoseconds. + Weight::from_parts(90_818_000, 4230) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -279,8 +279,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `776` // Estimated: `4241` - // Minimum execution time: 75_727_000 picoseconds. - Weight::from_parts(113_461_000, 4241) + // Minimum execution time: 96_038_000 picoseconds. + Weight::from_parts(126_167_000, 4241) .saturating_add(T::DbWeight::get().reads(10_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -318,8 +318,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1607` // Estimated: `7547` - // Minimum execution time: 140_505_000 picoseconds. - Weight::from_parts(219_590_000, 7547) + // Minimum execution time: 234_876_000 picoseconds. + Weight::from_parts(278_583_000, 7547) .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -331,8 +331,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `37` // Estimated: `1522` - // Minimum execution time: 20_215_000 picoseconds. - Weight::from_parts(30_997_000, 1522) + // Minimum execution time: 33_676_000 picoseconds. + Weight::from_parts(38_408_000, 1522) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -342,8 +342,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `215` // Estimated: `3680` - // Minimum execution time: 38_327_000 picoseconds. - Weight::from_parts(50_416_000, 3680) + // Minimum execution time: 40_855_000 picoseconds. + Weight::from_parts(47_531_000, 3680) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -385,8 +385,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2077` // Estimated: `8017` - // Minimum execution time: 259_746_000 picoseconds. - Weight::from_parts(297_625_000, 8017) + // Minimum execution time: 290_205_000 picoseconds. + Weight::from_parts(318_180_000, 8017) .saturating_add(T::DbWeight::get().reads(18_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -400,8 +400,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `395` // Estimated: `6335` - // Minimum execution time: 34_359_000 picoseconds. - Weight::from_parts(36_044_000, 6335) + // Minimum execution time: 55_623_000 picoseconds. + Weight::from_parts(71_866_000, 6335) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -413,8 +413,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `489` // Estimated: `3954` - // Minimum execution time: 38_570_000 picoseconds. - Weight::from_parts(50_746_000, 3954) + // Minimum execution time: 42_779_000 picoseconds. + Weight::from_parts(52_030_000, 3954) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -426,8 +426,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `489` // Estimated: `3954` - // Minimum execution time: 30_886_000 picoseconds. - Weight::from_parts(31_463_000, 3954) + // Minimum execution time: 44_066_000 picoseconds. + Weight::from_parts(51_799_000, 3954) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -439,8 +439,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `502` // Estimated: `3967` - // Minimum execution time: 30_791_000 picoseconds. - Weight::from_parts(47_616_000, 3967) + // Minimum execution time: 48_441_000 picoseconds. + Weight::from_parts(63_189_000, 3967) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -452,8 +452,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `502` // Estimated: `3967` - // Minimum execution time: 47_084_000 picoseconds. - Weight::from_parts(49_834_000, 3967) + // Minimum execution time: 54_190_000 picoseconds. + Weight::from_parts(57_535_000, 3967) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -465,8 +465,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `502` // Estimated: `3967` - // Minimum execution time: 30_282_000 picoseconds. - Weight::from_parts(32_405_000, 3967) + // Minimum execution time: 47_053_000 picoseconds. + Weight::from_parts(58_284_000, 3967) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -484,8 +484,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `841` // Estimated: `6781` - // Minimum execution time: 55_583_000 picoseconds. - Weight::from_parts(58_894_000, 6781) + // Minimum execution time: 96_733_000 picoseconds. + Weight::from_parts(104_297_000, 6781) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -495,8 +495,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `37` // Estimated: `1522` - // Minimum execution time: 15_559_000 picoseconds. - Weight::from_parts(25_002_000, 1522) + // Minimum execution time: 23_071_000 picoseconds. + Weight::from_parts(32_634_000, 1522) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -510,8 +510,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `965` // Estimated: `4430` - // Minimum execution time: 41_314_000 picoseconds. - Weight::from_parts(51_323_000, 4430) + // Minimum execution time: 65_322_000 picoseconds. + Weight::from_parts(81_424_000, 4430) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -531,8 +531,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `733` // Estimated: `4198` - // Minimum execution time: 45_506_000 picoseconds. - Weight::from_parts(63_919_000, 4198) + // Minimum execution time: 64_562_000 picoseconds. + Weight::from_parts(73_705_000, 4198) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -572,26 +572,63 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `7590` - // Minimum execution time: 142_843_000 picoseconds. - Weight::from_parts(233_363_000, 7590) + // Minimum execution time: 230_804_000 picoseconds. + Weight::from_parts(293_910_000, 7590) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } - /// Placeholder until regenerated by `060_generate_benchmark_weights.yml`. - /// Base is `cancel_contract_collective` (closest read/write profile: contract + - /// billing + two node-keyed maps). `n` is the max length of the source and - /// destination `ActiveNodeContracts` vectors, both of which are read, mutated - /// and re-encoded; the per-element slope is modelled on `pallet-tfgrid`'s - /// `add_twin_admin`, scaled from its 32-byte elements to our 8-byte u64s. + /// Storage: `SmartContractModule::Contracts` (r:1 w:1) + /// Proof: `SmartContractModule::Contracts` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::Nodes` (r:2 w:0) + /// Proof: `TfgridModule::Nodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::Farms` (r:1 w:0) + /// Proof: `TfgridModule::Farms` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::NodeV3BillingOptOut` (r:2 w:0) + /// Proof: `TfgridModule::NodeV3BillingOptOut` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::ActiveRentContractForNode` (r:2 w:0) + /// Proof: `SmartContractModule::ActiveRentContractForNode` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::NodePower` (r:1 w:0) + /// Proof: `TfgridModule::NodePower` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::DedicatedNodesExtraFee` (r:1 w:0) + /// Proof: `SmartContractModule::DedicatedNodesExtraFee` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::ContractIDByNodeIDAndHash` (r:2 w:2) + /// Proof: `SmartContractModule::ContractIDByNodeIDAndHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::Twins` (r:2 w:0) + /// Proof: `TfgridModule::Twins` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::PricingPolicies` (r:1 w:0) + /// Proof: `TfgridModule::PricingPolicies` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::ContractPaymentState` (r:1 w:1) + /// Proof: `SmartContractModule::ContractPaymentState` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `TfgridModule::TwinBoundedAccountID` (r:1 w:0) + /// Proof: `TfgridModule::TwinBoundedAccountID` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `SmartContractModule::ContractBillingInformationByID` (r:1 w:1) + /// Proof: `SmartContractModule::ContractBillingInformationByID` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::NodeContractResources` (r:1 w:0) + /// Proof: `SmartContractModule::NodeContractResources` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TFTPriceModule::AverageTftPrice` (r:1 w:0) + /// Proof: `TFTPriceModule::AverageTftPrice` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `TFTPriceModule::MinTftPrice` (r:1 w:0) + /// Proof: `TFTPriceModule::MinTftPrice` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `TFTPriceModule::MaxTftPrice` (r:1 w:0) + /// Proof: `TFTPriceModule::MaxTftPrice` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::ActiveNodeContracts` (r:2 w:2) + /// Proof: `SmartContractModule::ActiveNodeContracts` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[1, 100]`. fn migrate_node_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1650 + n * (8 ±0)` - // Estimated: `7590 + n * (8 ±0)` - Weight::from_parts(233_363_000, 7590) - .saturating_add(Weight::from_parts(53_223, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(16_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) - .saturating_add(Weight::from_parts(0, 8).saturating_mul(n.into())) + // Measured: `3484 + n * (26 ±0)` + // Estimated: `9090 + n * (31 ±0)` + // Minimum execution time: 351_309_000 picoseconds. + Weight::from_parts(492_079_512, 9090) + // Standard Error: 54_763 + .saturating_add(Weight::from_parts(1_449_325, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(26_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + .saturating_add(Weight::from_parts(0, 31).saturating_mul(n.into())) } } @@ -633,8 +670,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `868` // Estimated: `4333` - // Minimum execution time: 94_195_000 picoseconds. - Weight::from_parts(97_943_000, 4333) + // Minimum execution time: 153_191_000 picoseconds. + Weight::from_parts(175_883_000, 4333) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(8_u64)) } @@ -648,8 +685,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `869` // Estimated: `4334` - // Minimum execution time: 45_239_000 picoseconds. - Weight::from_parts(66_665_000, 4334) + // Minimum execution time: 76_922_000 picoseconds. + Weight::from_parts(83_803_000, 4334) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -689,8 +726,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `7590` - // Minimum execution time: 147_718_000 picoseconds. - Weight::from_parts(245_368_000, 7590) + // Minimum execution time: 202_502_000 picoseconds. + Weight::from_parts(290_227_000, 7590) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -714,8 +751,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `340` // Estimated: `3805` - // Minimum execution time: 47_262_000 picoseconds. - Weight::from_parts(48_046_000, 3805) + // Minimum execution time: 63_406_000 picoseconds. + Weight::from_parts(81_571_000, 3805) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -741,8 +778,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `949` // Estimated: `4414` - // Minimum execution time: 98_979_000 picoseconds. - Weight::from_parts(159_048_000, 4414) + // Minimum execution time: 164_903_000 picoseconds. + Weight::from_parts(191_094_000, 4414) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -764,8 +801,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1286` // Estimated: `4751` - // Minimum execution time: 69_958_000 picoseconds. - Weight::from_parts(98_651_000, 4751) + // Minimum execution time: 118_126_000 picoseconds. + Weight::from_parts(149_359_000, 4751) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -781,8 +818,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `765` // Estimated: `4230` - // Minimum execution time: 43_491_000 picoseconds. - Weight::from_parts(62_625_000, 4230) + // Minimum execution time: 71_420_000 picoseconds. + Weight::from_parts(90_818_000, 4230) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -814,8 +851,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `776` // Estimated: `4241` - // Minimum execution time: 75_727_000 picoseconds. - Weight::from_parts(113_461_000, 4241) + // Minimum execution time: 96_038_000 picoseconds. + Weight::from_parts(126_167_000, 4241) .saturating_add(RocksDbWeight::get().reads(10_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -853,8 +890,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1607` // Estimated: `7547` - // Minimum execution time: 140_505_000 picoseconds. - Weight::from_parts(219_590_000, 7547) + // Minimum execution time: 234_876_000 picoseconds. + Weight::from_parts(278_583_000, 7547) .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -866,8 +903,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `37` // Estimated: `1522` - // Minimum execution time: 20_215_000 picoseconds. - Weight::from_parts(30_997_000, 1522) + // Minimum execution time: 33_676_000 picoseconds. + Weight::from_parts(38_408_000, 1522) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -877,8 +914,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `215` // Estimated: `3680` - // Minimum execution time: 38_327_000 picoseconds. - Weight::from_parts(50_416_000, 3680) + // Minimum execution time: 40_855_000 picoseconds. + Weight::from_parts(47_531_000, 3680) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -920,8 +957,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2077` // Estimated: `8017` - // Minimum execution time: 259_746_000 picoseconds. - Weight::from_parts(297_625_000, 8017) + // Minimum execution time: 290_205_000 picoseconds. + Weight::from_parts(318_180_000, 8017) .saturating_add(RocksDbWeight::get().reads(18_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -935,8 +972,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `395` // Estimated: `6335` - // Minimum execution time: 34_359_000 picoseconds. - Weight::from_parts(36_044_000, 6335) + // Minimum execution time: 55_623_000 picoseconds. + Weight::from_parts(71_866_000, 6335) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -948,8 +985,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `489` // Estimated: `3954` - // Minimum execution time: 38_570_000 picoseconds. - Weight::from_parts(50_746_000, 3954) + // Minimum execution time: 42_779_000 picoseconds. + Weight::from_parts(52_030_000, 3954) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -961,8 +998,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `489` // Estimated: `3954` - // Minimum execution time: 30_886_000 picoseconds. - Weight::from_parts(31_463_000, 3954) + // Minimum execution time: 44_066_000 picoseconds. + Weight::from_parts(51_799_000, 3954) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -974,8 +1011,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `502` // Estimated: `3967` - // Minimum execution time: 30_791_000 picoseconds. - Weight::from_parts(47_616_000, 3967) + // Minimum execution time: 48_441_000 picoseconds. + Weight::from_parts(63_189_000, 3967) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -987,8 +1024,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `502` // Estimated: `3967` - // Minimum execution time: 47_084_000 picoseconds. - Weight::from_parts(49_834_000, 3967) + // Minimum execution time: 54_190_000 picoseconds. + Weight::from_parts(57_535_000, 3967) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1000,8 +1037,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `502` // Estimated: `3967` - // Minimum execution time: 30_282_000 picoseconds. - Weight::from_parts(32_405_000, 3967) + // Minimum execution time: 47_053_000 picoseconds. + Weight::from_parts(58_284_000, 3967) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1019,8 +1056,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `841` // Estimated: `6781` - // Minimum execution time: 55_583_000 picoseconds. - Weight::from_parts(58_894_000, 6781) + // Minimum execution time: 96_733_000 picoseconds. + Weight::from_parts(104_297_000, 6781) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1030,8 +1067,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `37` // Estimated: `1522` - // Minimum execution time: 15_559_000 picoseconds. - Weight::from_parts(25_002_000, 1522) + // Minimum execution time: 23_071_000 picoseconds. + Weight::from_parts(32_634_000, 1522) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1045,8 +1082,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `965` // Estimated: `4430` - // Minimum execution time: 41_314_000 picoseconds. - Weight::from_parts(51_323_000, 4430) + // Minimum execution time: 65_322_000 picoseconds. + Weight::from_parts(81_424_000, 4430) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1066,8 +1103,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `733` // Estimated: `4198` - // Minimum execution time: 45_506_000 picoseconds. - Weight::from_parts(63_919_000, 4198) + // Minimum execution time: 64_562_000 picoseconds. + Weight::from_parts(73_705_000, 4198) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1107,25 +1144,62 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `7590` - // Minimum execution time: 142_843_000 picoseconds. - Weight::from_parts(233_363_000, 7590) + // Minimum execution time: 230_804_000 picoseconds. + Weight::from_parts(293_910_000, 7590) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } - /// Placeholder until regenerated by `060_generate_benchmark_weights.yml`. - /// Base is `cancel_contract_collective` (closest read/write profile: contract + - /// billing + two node-keyed maps). `n` is the max length of the source and - /// destination `ActiveNodeContracts` vectors, both of which are read, mutated - /// and re-encoded; the per-element slope is modelled on `pallet-tfgrid`'s - /// `add_twin_admin`, scaled from its 32-byte elements to our 8-byte u64s. + /// Storage: `SmartContractModule::Contracts` (r:1 w:1) + /// Proof: `SmartContractModule::Contracts` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::Nodes` (r:2 w:0) + /// Proof: `TfgridModule::Nodes` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::Farms` (r:1 w:0) + /// Proof: `TfgridModule::Farms` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::NodeV3BillingOptOut` (r:2 w:0) + /// Proof: `TfgridModule::NodeV3BillingOptOut` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::ActiveRentContractForNode` (r:2 w:0) + /// Proof: `SmartContractModule::ActiveRentContractForNode` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::NodePower` (r:1 w:0) + /// Proof: `TfgridModule::NodePower` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::DedicatedNodesExtraFee` (r:1 w:0) + /// Proof: `SmartContractModule::DedicatedNodesExtraFee` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::ContractIDByNodeIDAndHash` (r:2 w:2) + /// Proof: `SmartContractModule::ContractIDByNodeIDAndHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::Twins` (r:2 w:0) + /// Proof: `TfgridModule::Twins` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TfgridModule::PricingPolicies` (r:1 w:0) + /// Proof: `TfgridModule::PricingPolicies` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::ContractPaymentState` (r:1 w:1) + /// Proof: `SmartContractModule::ContractPaymentState` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `TfgridModule::TwinBoundedAccountID` (r:1 w:0) + /// Proof: `TfgridModule::TwinBoundedAccountID` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `SmartContractModule::ContractBillingInformationByID` (r:1 w:1) + /// Proof: `SmartContractModule::ContractBillingInformationByID` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::NodeContractResources` (r:1 w:0) + /// Proof: `SmartContractModule::NodeContractResources` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `TFTPriceModule::AverageTftPrice` (r:1 w:0) + /// Proof: `TFTPriceModule::AverageTftPrice` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `TFTPriceModule::MinTftPrice` (r:1 w:0) + /// Proof: `TFTPriceModule::MinTftPrice` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `TFTPriceModule::MaxTftPrice` (r:1 w:0) + /// Proof: `TFTPriceModule::MaxTftPrice` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SmartContractModule::ActiveNodeContracts` (r:2 w:2) + /// Proof: `SmartContractModule::ActiveNodeContracts` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[1, 100]`. fn migrate_node_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1650 + n * (8 ±0)` - // Estimated: `7590 + n * (8 ±0)` - Weight::from_parts(233_363_000, 7590) - .saturating_add(Weight::from_parts(53_223, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(16_u64)) - .saturating_add(RocksDbWeight::get().writes(6_u64)) - .saturating_add(Weight::from_parts(0, 8).saturating_mul(n.into())) + // Measured: `3484 + n * (26 ±0)` + // Estimated: `9090 + n * (31 ±0)` + // Minimum execution time: 351_309_000 picoseconds. + Weight::from_parts(492_079_512, 9090) + // Standard Error: 54_763 + .saturating_add(Weight::from_parts(1_449_325, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(26_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + .saturating_add(Weight::from_parts(0, 31).saturating_mul(n.into())) } } diff --git a/substrate-node/pallets/pallet-tfgrid/src/weights.rs b/substrate-node/pallets/pallet-tfgrid/src/weights.rs index 6f79d9f5c..4d6c08af4 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/weights.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_tfgrid //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -83,8 +83,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_037_000 picoseconds. - Weight::from_parts(8_853_000, 0) + // Minimum execution time: 8_622_000 picoseconds. + Weight::from_parts(10_885_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `TfgridModule::TwinIdByAccountID` (r:1 w:0) @@ -101,8 +101,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3961` - // Minimum execution time: 56_543_000 picoseconds. - Weight::from_parts(64_323_000, 3961) + // Minimum execution time: 57_664_000 picoseconds. + Weight::from_parts(68_736_000, 3961) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -116,8 +116,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `507` // Estimated: `3972` - // Minimum execution time: 40_766_000 picoseconds. - Weight::from_parts(43_995_000, 3972) + // Minimum execution time: 64_695_000 picoseconds. + Weight::from_parts(90_119_000, 3972) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -131,8 +131,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `453` // Estimated: `3918` - // Minimum execution time: 29_838_000 picoseconds. - Weight::from_parts(42_549_000, 3918) + // Minimum execution time: 46_483_000 picoseconds. + Weight::from_parts(56_644_000, 3918) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -142,8 +142,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `412` // Estimated: `3877` - // Minimum execution time: 24_842_000 picoseconds. - Weight::from_parts(37_699_000, 3877) + // Minimum execution time: 30_352_000 picoseconds. + Weight::from_parts(32_672_000, 3877) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -155,8 +155,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `569` // Estimated: `4034` - // Minimum execution time: 33_701_000 picoseconds. - Weight::from_parts(36_234_000, 4034) + // Minimum execution time: 52_653_000 picoseconds. + Weight::from_parts(64_534_000, 4034) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -168,8 +168,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `569` // Estimated: `4034` - // Minimum execution time: 33_533_000 picoseconds. - Weight::from_parts(48_821_000, 4034) + // Minimum execution time: 46_864_000 picoseconds. + Weight::from_parts(50_744_000, 4034) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -197,8 +197,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `905` // Estimated: `11795` - // Minimum execution time: 104_408_000 picoseconds. - Weight::from_parts(148_094_000, 11795) + // Minimum execution time: 141_578_000 picoseconds. + Weight::from_parts(172_488_000, 11795) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -214,8 +214,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `661` // Estimated: `4126` - // Minimum execution time: 72_750_000 picoseconds. - Weight::from_parts(82_373_000, 4126) + // Minimum execution time: 63_203_000 picoseconds. + Weight::from_parts(83_834_000, 4126) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -229,8 +229,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `956` // Estimated: `11846` - // Minimum execution time: 67_047_000 picoseconds. - Weight::from_parts(104_596_000, 11846) + // Minimum execution time: 74_754_000 picoseconds. + Weight::from_parts(124_676_000, 11846) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -246,8 +246,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `454` // Estimated: `3919` - // Minimum execution time: 34_426_000 picoseconds. - Weight::from_parts(51_946_000, 3919) + // Minimum execution time: 34_590_000 picoseconds. + Weight::from_parts(54_836_000, 3919) .saturating_add(T::DbWeight::get().reads(4_u64)) } /// Storage: `TfgridModule::Farms` (r:1 w:0) @@ -260,8 +260,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `779` // Estimated: `4244` - // Minimum execution time: 66_917_000 picoseconds. - Weight::from_parts(78_524_000, 4244) + // Minimum execution time: 74_676_000 picoseconds. + Weight::from_parts(89_718_000, 4244) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -283,8 +283,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `682` // Estimated: `4147` - // Minimum execution time: 76_379_000 picoseconds. - Weight::from_parts(80_683_000, 4147) + // Minimum execution time: 84_753_000 picoseconds. + Weight::from_parts(86_517_000, 4147) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -300,8 +300,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `235` // Estimated: `3700` - // Minimum execution time: 28_966_000 picoseconds. - Weight::from_parts(45_814_000, 3700) + // Minimum execution time: 43_809_000 picoseconds. + Weight::from_parts(55_195_000, 3700) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -313,8 +313,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `428` // Estimated: `3893` - // Minimum execution time: 41_237_000 picoseconds. - Weight::from_parts(44_071_000, 3893) + // Minimum execution time: 43_850_000 picoseconds. + Weight::from_parts(56_457_000, 3893) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -328,8 +328,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `134` // Estimated: `3599` - // Minimum execution time: 35_674_000 picoseconds. - Weight::from_parts(39_693_000, 3599) + // Minimum execution time: 39_719_000 picoseconds. + Weight::from_parts(48_301_000, 3599) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -343,8 +343,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `382` // Estimated: `3847` - // Minimum execution time: 47_788_000 picoseconds. - Weight::from_parts(54_375_000, 3847) + // Minimum execution time: 49_277_000 picoseconds. + Weight::from_parts(58_610_000, 3847) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -356,8 +356,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `134` // Estimated: `1619` - // Minimum execution time: 29_849_000 picoseconds. - Weight::from_parts(37_821_000, 1619) + // Minimum execution time: 32_763_000 picoseconds. + Weight::from_parts(43_841_000, 1619) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -369,8 +369,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `3605` - // Minimum execution time: 21_272_000 picoseconds. - Weight::from_parts(21_848_000, 3605) + // Minimum execution time: 22_449_000 picoseconds. + Weight::from_parts(29_820_000, 3605) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -390,8 +390,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `527` // Estimated: `6467` - // Minimum execution time: 65_335_000 picoseconds. - Weight::from_parts(72_204_000, 6467) + // Minimum execution time: 68_352_000 picoseconds. + Weight::from_parts(79_888_000, 6467) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -407,8 +407,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `605` // Estimated: `4070` - // Minimum execution time: 51_071_000 picoseconds. - Weight::from_parts(77_885_000, 4070) + // Minimum execution time: 73_330_000 picoseconds. + Weight::from_parts(81_796_000, 4070) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -420,8 +420,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 36_730_000 picoseconds. - Weight::from_parts(39_859_000, 3868) + // Minimum execution time: 38_526_000 picoseconds. + Weight::from_parts(43_979_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -445,8 +445,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `828` // Estimated: `4293` - // Minimum execution time: 59_940_000 picoseconds. - Weight::from_parts(64_555_000, 4293) + // Minimum execution time: 64_707_000 picoseconds. + Weight::from_parts(102_333_000, 4293) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -456,8 +456,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `412` // Estimated: `3877` - // Minimum execution time: 34_415_000 picoseconds. - Weight::from_parts(42_555_000, 3877) + // Minimum execution time: 33_621_000 picoseconds. + Weight::from_parts(48_442_000, 3877) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -467,8 +467,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `412` // Estimated: `3877` - // Minimum execution time: 25_866_000 picoseconds. - Weight::from_parts(39_327_000, 3877) + // Minimum execution time: 41_263_000 picoseconds. + Weight::from_parts(46_689_000, 3877) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -478,8 +478,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 18_400_000 picoseconds. - Weight::from_parts(20_841_000, 0) + // Minimum execution time: 18_951_000 picoseconds. + Weight::from_parts(22_346_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `TfgridModule::AllowedNodeCertifiers` (r:1 w:1) @@ -488,8 +488,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `355` // Estimated: `1840` - // Minimum execution time: 26_819_000 picoseconds. - Weight::from_parts(31_986_000, 1840) + // Minimum execution time: 29_396_000 picoseconds. + Weight::from_parts(40_416_000, 1840) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -499,8 +499,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `413` // Estimated: `1898` - // Minimum execution time: 32_667_000 picoseconds. - Weight::from_parts(36_118_000, 1898) + // Minimum execution time: 37_295_000 picoseconds. + Weight::from_parts(42_963_000, 1898) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -510,8 +510,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `294` // Estimated: `3759` - // Minimum execution time: 37_886_000 picoseconds. - Weight::from_parts(40_113_000, 3759) + // Minimum execution time: 41_049_000 picoseconds. + Weight::from_parts(49_667_000, 3759) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -525,8 +525,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `572` // Estimated: `4037` - // Minimum execution time: 44_149_000 picoseconds. - Weight::from_parts(68_043_000, 4037) + // Minimum execution time: 66_354_000 picoseconds. + Weight::from_parts(82_894_000, 4037) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -536,8 +536,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `134` // Estimated: `1619` - // Minimum execution time: 15_444_000 picoseconds. - Weight::from_parts(16_388_000, 1619) + // Minimum execution time: 26_178_000 picoseconds. + Weight::from_parts(30_372_000, 1619) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -553,8 +553,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `621` // Estimated: `4086` - // Minimum execution time: 42_023_000 picoseconds. - Weight::from_parts(61_865_000, 4086) + // Minimum execution time: 69_122_000 picoseconds. + Weight::from_parts(77_614_000, 4086) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -574,8 +574,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `792` // Estimated: `4257` - // Minimum execution time: 48_568_000 picoseconds. - Weight::from_parts(52_485_000, 4257) + // Minimum execution time: 74_176_000 picoseconds. + Weight::from_parts(87_230_000, 4257) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -587,8 +587,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `387` // Estimated: `3852` - // Minimum execution time: 23_170_000 picoseconds. - Weight::from_parts(35_217_000, 3852) + // Minimum execution time: 29_146_000 picoseconds. + Weight::from_parts(30_845_000, 3852) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -604,8 +604,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `454` // Estimated: `3919` - // Minimum execution time: 52_982_000 picoseconds. - Weight::from_parts(60_041_000, 3919) + // Minimum execution time: 41_495_000 picoseconds. + Weight::from_parts(42_582_000, 3919) .saturating_add(T::DbWeight::get().reads(4_u64)) } /// Storage: `TfgridModule::TwinIdByAccountID` (r:1 w:0) @@ -622,8 +622,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `669` // Estimated: `4134` - // Minimum execution time: 40_710_000 picoseconds. - Weight::from_parts(44_264_000, 4134) + // Minimum execution time: 66_632_000 picoseconds. + Weight::from_parts(82_801_000, 4134) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -641,8 +641,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `712` // Estimated: `4177` - // Minimum execution time: 41_384_000 picoseconds. - Weight::from_parts(42_486_000, 4177) + // Minimum execution time: 68_549_000 picoseconds. + Weight::from_parts(86_693_000, 4177) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -653,10 +653,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `158 + n * (32 ±0)` // Estimated: `1641 + n * (32 ±0)` - // Minimum execution time: 15_950_000 picoseconds. - Weight::from_parts(20_782_738, 1641) - // Standard Error: 9_817 - .saturating_add(Weight::from_parts(212_894, 0).saturating_mul(n.into())) + // Minimum execution time: 24_856_000 picoseconds. + Weight::from_parts(35_556_955, 1641) + // Standard Error: 6_339 + .saturating_add(Weight::from_parts(183_896, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(n.into())) @@ -668,10 +668,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `158 + n * (32 ±0)` // Estimated: `1643 + n * (32 ±0)` - // Minimum execution time: 19_152_000 picoseconds. - Weight::from_parts(27_969_719, 1643) - // Standard Error: 11_828 - .saturating_add(Weight::from_parts(95_628, 0).saturating_mul(n.into())) + // Minimum execution time: 19_847_000 picoseconds. + Weight::from_parts(33_853_519, 1643) + // Standard Error: 6_288 + .saturating_add(Weight::from_parts(166_315, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(n.into())) @@ -686,8 +686,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_037_000 picoseconds. - Weight::from_parts(8_853_000, 0) + // Minimum execution time: 8_622_000 picoseconds. + Weight::from_parts(10_885_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `TfgridModule::TwinIdByAccountID` (r:1 w:0) @@ -704,8 +704,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3961` - // Minimum execution time: 56_543_000 picoseconds. - Weight::from_parts(64_323_000, 3961) + // Minimum execution time: 57_664_000 picoseconds. + Weight::from_parts(68_736_000, 3961) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -719,8 +719,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `507` // Estimated: `3972` - // Minimum execution time: 40_766_000 picoseconds. - Weight::from_parts(43_995_000, 3972) + // Minimum execution time: 64_695_000 picoseconds. + Weight::from_parts(90_119_000, 3972) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -734,8 +734,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `453` // Estimated: `3918` - // Minimum execution time: 29_838_000 picoseconds. - Weight::from_parts(42_549_000, 3918) + // Minimum execution time: 46_483_000 picoseconds. + Weight::from_parts(56_644_000, 3918) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -745,8 +745,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `412` // Estimated: `3877` - // Minimum execution time: 24_842_000 picoseconds. - Weight::from_parts(37_699_000, 3877) + // Minimum execution time: 30_352_000 picoseconds. + Weight::from_parts(32_672_000, 3877) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -758,8 +758,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `569` // Estimated: `4034` - // Minimum execution time: 33_701_000 picoseconds. - Weight::from_parts(36_234_000, 4034) + // Minimum execution time: 52_653_000 picoseconds. + Weight::from_parts(64_534_000, 4034) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -771,8 +771,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `569` // Estimated: `4034` - // Minimum execution time: 33_533_000 picoseconds. - Weight::from_parts(48_821_000, 4034) + // Minimum execution time: 46_864_000 picoseconds. + Weight::from_parts(50_744_000, 4034) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -800,8 +800,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `905` // Estimated: `11795` - // Minimum execution time: 104_408_000 picoseconds. - Weight::from_parts(148_094_000, 11795) + // Minimum execution time: 141_578_000 picoseconds. + Weight::from_parts(172_488_000, 11795) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -817,8 +817,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `661` // Estimated: `4126` - // Minimum execution time: 72_750_000 picoseconds. - Weight::from_parts(82_373_000, 4126) + // Minimum execution time: 63_203_000 picoseconds. + Weight::from_parts(83_834_000, 4126) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -832,8 +832,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `956` // Estimated: `11846` - // Minimum execution time: 67_047_000 picoseconds. - Weight::from_parts(104_596_000, 11846) + // Minimum execution time: 74_754_000 picoseconds. + Weight::from_parts(124_676_000, 11846) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -849,8 +849,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `454` // Estimated: `3919` - // Minimum execution time: 34_426_000 picoseconds. - Weight::from_parts(51_946_000, 3919) + // Minimum execution time: 34_590_000 picoseconds. + Weight::from_parts(54_836_000, 3919) .saturating_add(RocksDbWeight::get().reads(4_u64)) } /// Storage: `TfgridModule::Farms` (r:1 w:0) @@ -863,8 +863,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `779` // Estimated: `4244` - // Minimum execution time: 66_917_000 picoseconds. - Weight::from_parts(78_524_000, 4244) + // Minimum execution time: 74_676_000 picoseconds. + Weight::from_parts(89_718_000, 4244) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -886,8 +886,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `682` // Estimated: `4147` - // Minimum execution time: 76_379_000 picoseconds. - Weight::from_parts(80_683_000, 4147) + // Minimum execution time: 84_753_000 picoseconds. + Weight::from_parts(86_517_000, 4147) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -903,8 +903,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `235` // Estimated: `3700` - // Minimum execution time: 28_966_000 picoseconds. - Weight::from_parts(45_814_000, 3700) + // Minimum execution time: 43_809_000 picoseconds. + Weight::from_parts(55_195_000, 3700) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -916,8 +916,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `428` // Estimated: `3893` - // Minimum execution time: 41_237_000 picoseconds. - Weight::from_parts(44_071_000, 3893) + // Minimum execution time: 43_850_000 picoseconds. + Weight::from_parts(56_457_000, 3893) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -931,8 +931,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `134` // Estimated: `3599` - // Minimum execution time: 35_674_000 picoseconds. - Weight::from_parts(39_693_000, 3599) + // Minimum execution time: 39_719_000 picoseconds. + Weight::from_parts(48_301_000, 3599) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -946,8 +946,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `382` // Estimated: `3847` - // Minimum execution time: 47_788_000 picoseconds. - Weight::from_parts(54_375_000, 3847) + // Minimum execution time: 49_277_000 picoseconds. + Weight::from_parts(58_610_000, 3847) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -959,8 +959,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `134` // Estimated: `1619` - // Minimum execution time: 29_849_000 picoseconds. - Weight::from_parts(37_821_000, 1619) + // Minimum execution time: 32_763_000 picoseconds. + Weight::from_parts(43_841_000, 1619) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -972,8 +972,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `140` // Estimated: `3605` - // Minimum execution time: 21_272_000 picoseconds. - Weight::from_parts(21_848_000, 3605) + // Minimum execution time: 22_449_000 picoseconds. + Weight::from_parts(29_820_000, 3605) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -993,8 +993,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `527` // Estimated: `6467` - // Minimum execution time: 65_335_000 picoseconds. - Weight::from_parts(72_204_000, 6467) + // Minimum execution time: 68_352_000 picoseconds. + Weight::from_parts(79_888_000, 6467) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1010,8 +1010,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `605` // Estimated: `4070` - // Minimum execution time: 51_071_000 picoseconds. - Weight::from_parts(77_885_000, 4070) + // Minimum execution time: 73_330_000 picoseconds. + Weight::from_parts(81_796_000, 4070) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1023,8 +1023,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 36_730_000 picoseconds. - Weight::from_parts(39_859_000, 3868) + // Minimum execution time: 38_526_000 picoseconds. + Weight::from_parts(43_979_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1048,8 +1048,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `828` // Estimated: `4293` - // Minimum execution time: 59_940_000 picoseconds. - Weight::from_parts(64_555_000, 4293) + // Minimum execution time: 64_707_000 picoseconds. + Weight::from_parts(102_333_000, 4293) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1059,8 +1059,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `412` // Estimated: `3877` - // Minimum execution time: 34_415_000 picoseconds. - Weight::from_parts(42_555_000, 3877) + // Minimum execution time: 33_621_000 picoseconds. + Weight::from_parts(48_442_000, 3877) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1070,8 +1070,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `412` // Estimated: `3877` - // Minimum execution time: 25_866_000 picoseconds. - Weight::from_parts(39_327_000, 3877) + // Minimum execution time: 41_263_000 picoseconds. + Weight::from_parts(46_689_000, 3877) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1081,8 +1081,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 18_400_000 picoseconds. - Weight::from_parts(20_841_000, 0) + // Minimum execution time: 18_951_000 picoseconds. + Weight::from_parts(22_346_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `TfgridModule::AllowedNodeCertifiers` (r:1 w:1) @@ -1091,8 +1091,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `355` // Estimated: `1840` - // Minimum execution time: 26_819_000 picoseconds. - Weight::from_parts(31_986_000, 1840) + // Minimum execution time: 29_396_000 picoseconds. + Weight::from_parts(40_416_000, 1840) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1102,8 +1102,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `413` // Estimated: `1898` - // Minimum execution time: 32_667_000 picoseconds. - Weight::from_parts(36_118_000, 1898) + // Minimum execution time: 37_295_000 picoseconds. + Weight::from_parts(42_963_000, 1898) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1113,8 +1113,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `294` // Estimated: `3759` - // Minimum execution time: 37_886_000 picoseconds. - Weight::from_parts(40_113_000, 3759) + // Minimum execution time: 41_049_000 picoseconds. + Weight::from_parts(49_667_000, 3759) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1128,8 +1128,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `572` // Estimated: `4037` - // Minimum execution time: 44_149_000 picoseconds. - Weight::from_parts(68_043_000, 4037) + // Minimum execution time: 66_354_000 picoseconds. + Weight::from_parts(82_894_000, 4037) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1139,8 +1139,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `134` // Estimated: `1619` - // Minimum execution time: 15_444_000 picoseconds. - Weight::from_parts(16_388_000, 1619) + // Minimum execution time: 26_178_000 picoseconds. + Weight::from_parts(30_372_000, 1619) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1156,8 +1156,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `621` // Estimated: `4086` - // Minimum execution time: 42_023_000 picoseconds. - Weight::from_parts(61_865_000, 4086) + // Minimum execution time: 69_122_000 picoseconds. + Weight::from_parts(77_614_000, 4086) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1177,8 +1177,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `792` // Estimated: `4257` - // Minimum execution time: 48_568_000 picoseconds. - Weight::from_parts(52_485_000, 4257) + // Minimum execution time: 74_176_000 picoseconds. + Weight::from_parts(87_230_000, 4257) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1190,8 +1190,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `387` // Estimated: `3852` - // Minimum execution time: 23_170_000 picoseconds. - Weight::from_parts(35_217_000, 3852) + // Minimum execution time: 29_146_000 picoseconds. + Weight::from_parts(30_845_000, 3852) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1207,8 +1207,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `454` // Estimated: `3919` - // Minimum execution time: 52_982_000 picoseconds. - Weight::from_parts(60_041_000, 3919) + // Minimum execution time: 41_495_000 picoseconds. + Weight::from_parts(42_582_000, 3919) .saturating_add(RocksDbWeight::get().reads(4_u64)) } /// Storage: `TfgridModule::TwinIdByAccountID` (r:1 w:0) @@ -1225,8 +1225,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `669` // Estimated: `4134` - // Minimum execution time: 40_710_000 picoseconds. - Weight::from_parts(44_264_000, 4134) + // Minimum execution time: 66_632_000 picoseconds. + Weight::from_parts(82_801_000, 4134) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1244,8 +1244,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `712` // Estimated: `4177` - // Minimum execution time: 41_384_000 picoseconds. - Weight::from_parts(42_486_000, 4177) + // Minimum execution time: 68_549_000 picoseconds. + Weight::from_parts(86_693_000, 4177) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1256,10 +1256,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `158 + n * (32 ±0)` // Estimated: `1641 + n * (32 ±0)` - // Minimum execution time: 15_950_000 picoseconds. - Weight::from_parts(20_782_738, 1641) - // Standard Error: 9_817 - .saturating_add(Weight::from_parts(212_894, 0).saturating_mul(n.into())) + // Minimum execution time: 24_856_000 picoseconds. + Weight::from_parts(35_556_955, 1641) + // Standard Error: 6_339 + .saturating_add(Weight::from_parts(183_896, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(n.into())) @@ -1271,10 +1271,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `158 + n * (32 ±0)` // Estimated: `1643 + n * (32 ±0)` - // Minimum execution time: 19_152_000 picoseconds. - Weight::from_parts(27_969_719, 1643) - // Standard Error: 11_828 - .saturating_add(Weight::from_parts(95_628, 0).saturating_mul(n.into())) + // Minimum execution time: 19_847_000 picoseconds. + Weight::from_parts(33_853_519, 1643) + // Standard Error: 6_288 + .saturating_add(Weight::from_parts(166_315, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(n.into())) diff --git a/substrate-node/pallets/pallet-tft-bridge/src/weights.rs b/substrate-node/pallets/pallet-tft-bridge/src/weights.rs index 42e980175..634460968 100644 --- a/substrate-node/pallets/pallet-tft-bridge/src/weights.rs +++ b/substrate-node/pallets/pallet-tft-bridge/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_tft_bridge //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -55,8 +55,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `256` // Estimated: `1741` - // Minimum execution time: 13_362_000 picoseconds. - Weight::from_parts(20_041_000, 1741) + // Minimum execution time: 20_283_000 picoseconds. + Weight::from_parts(23_172_000, 1741) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -66,8 +66,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `289` // Estimated: `1774` - // Minimum execution time: 12_793_000 picoseconds. - Weight::from_parts(19_337_000, 1774) + // Minimum execution time: 20_723_000 picoseconds. + Weight::from_parts(22_034_000, 1774) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -77,8 +77,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_354_000 picoseconds. - Weight::from_parts(8_247_000, 0) + // Minimum execution time: 9_114_000 picoseconds. + Weight::from_parts(13_691_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `TFTBridgeModule::WithdrawFee` (r:0 w:1) @@ -87,8 +87,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_267_000 picoseconds. - Weight::from_parts(8_445_000, 0) + // Minimum execution time: 7_833_000 picoseconds. + Weight::from_parts(10_677_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `TFTBridgeModule::DepositFee` (r:0 w:1) @@ -97,8 +97,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_535_000 picoseconds. - Weight::from_parts(4_880_000, 0) + // Minimum execution time: 8_303_000 picoseconds. + Weight::from_parts(10_761_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `TFTBridgeModule::WithdrawFee` (r:1 w:0) @@ -115,8 +115,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `253` // Estimated: `3593` - // Minimum execution time: 84_028_000 picoseconds. - Weight::from_parts(146_829_000, 3593) + // Minimum execution time: 143_749_000 picoseconds. + Weight::from_parts(160_539_000, 3593) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -136,8 +136,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `499` // Estimated: `3964` - // Minimum execution time: 114_895_000 picoseconds. - Weight::from_parts(170_544_000, 3964) + // Minimum execution time: 206_260_000 picoseconds. + Weight::from_parts(245_016_000, 3964) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -151,8 +151,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `631` // Estimated: `4096` - // Minimum execution time: 47_733_000 picoseconds. - Weight::from_parts(75_616_000, 4096) + // Minimum execution time: 74_904_000 picoseconds. + Weight::from_parts(90_304_000, 4096) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -166,22 +166,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `571` // Estimated: `4036` - // Minimum execution time: 35_470_000 picoseconds. - Weight::from_parts(38_994_000, 4036) + // Minimum execution time: 51_667_000 picoseconds. + Weight::from_parts(70_089_000, 4036) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `TFTBridgeModule::Validators` (r:1 w:0) /// Proof: `TFTBridgeModule::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `TFTBridgeModule::ExecutedRefundTransactions` (r:1 w:0) + /// Proof: `TFTBridgeModule::ExecutedRefundTransactions` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `TFTBridgeModule::RefundTransactions` (r:1 w:1) /// Proof: `TFTBridgeModule::RefundTransactions` (`max_values`: None, `max_size`: None, mode: `Measured`) fn create_refund_transaction_or_add_sig() -> Weight { // Proof Size summary in bytes: // Measured: `385` // Estimated: `3850` - // Minimum execution time: 44_425_000 picoseconds. - Weight::from_parts(68_629_000, 3850) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 54_449_000 picoseconds. + Weight::from_parts(81_549_000, 3850) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `TFTBridgeModule::Validators` (r:1 w:0) @@ -194,8 +196,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `560` // Estimated: `4025` - // Minimum execution time: 35_428_000 picoseconds. - Weight::from_parts(54_951_000, 4025) + // Minimum execution time: 54_326_000 picoseconds. + Weight::from_parts(63_873_000, 4025) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -209,8 +211,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `256` // Estimated: `1741` - // Minimum execution time: 13_362_000 picoseconds. - Weight::from_parts(20_041_000, 1741) + // Minimum execution time: 20_283_000 picoseconds. + Weight::from_parts(23_172_000, 1741) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -220,8 +222,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `289` // Estimated: `1774` - // Minimum execution time: 12_793_000 picoseconds. - Weight::from_parts(19_337_000, 1774) + // Minimum execution time: 20_723_000 picoseconds. + Weight::from_parts(22_034_000, 1774) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -231,8 +233,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_354_000 picoseconds. - Weight::from_parts(8_247_000, 0) + // Minimum execution time: 9_114_000 picoseconds. + Weight::from_parts(13_691_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `TFTBridgeModule::WithdrawFee` (r:0 w:1) @@ -241,8 +243,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_267_000 picoseconds. - Weight::from_parts(8_445_000, 0) + // Minimum execution time: 7_833_000 picoseconds. + Weight::from_parts(10_677_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `TFTBridgeModule::DepositFee` (r:0 w:1) @@ -251,8 +253,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_535_000 picoseconds. - Weight::from_parts(4_880_000, 0) + // Minimum execution time: 8_303_000 picoseconds. + Weight::from_parts(10_761_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `TFTBridgeModule::WithdrawFee` (r:1 w:0) @@ -269,8 +271,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `253` // Estimated: `3593` - // Minimum execution time: 84_028_000 picoseconds. - Weight::from_parts(146_829_000, 3593) + // Minimum execution time: 143_749_000 picoseconds. + Weight::from_parts(160_539_000, 3593) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -290,8 +292,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `499` // Estimated: `3964` - // Minimum execution time: 114_895_000 picoseconds. - Weight::from_parts(170_544_000, 3964) + // Minimum execution time: 206_260_000 picoseconds. + Weight::from_parts(245_016_000, 3964) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -305,8 +307,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `631` // Estimated: `4096` - // Minimum execution time: 47_733_000 picoseconds. - Weight::from_parts(75_616_000, 4096) + // Minimum execution time: 74_904_000 picoseconds. + Weight::from_parts(90_304_000, 4096) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -320,22 +322,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `571` // Estimated: `4036` - // Minimum execution time: 35_470_000 picoseconds. - Weight::from_parts(38_994_000, 4036) + // Minimum execution time: 51_667_000 picoseconds. + Weight::from_parts(70_089_000, 4036) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `TFTBridgeModule::Validators` (r:1 w:0) /// Proof: `TFTBridgeModule::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `TFTBridgeModule::ExecutedRefundTransactions` (r:1 w:0) + /// Proof: `TFTBridgeModule::ExecutedRefundTransactions` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `TFTBridgeModule::RefundTransactions` (r:1 w:1) /// Proof: `TFTBridgeModule::RefundTransactions` (`max_values`: None, `max_size`: None, mode: `Measured`) fn create_refund_transaction_or_add_sig() -> Weight { // Proof Size summary in bytes: // Measured: `385` // Estimated: `3850` - // Minimum execution time: 44_425_000 picoseconds. - Weight::from_parts(68_629_000, 3850) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Minimum execution time: 54_449_000 picoseconds. + Weight::from_parts(81_549_000, 3850) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `TFTBridgeModule::Validators` (r:1 w:0) @@ -348,8 +352,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `560` // Estimated: `4025` - // Minimum execution time: 35_428_000 picoseconds. - Weight::from_parts(54_951_000, 4025) + // Minimum execution time: 54_326_000 picoseconds. + Weight::from_parts(63_873_000, 4025) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } diff --git a/substrate-node/pallets/pallet-tft-price/src/weights.rs b/substrate-node/pallets/pallet-tft-price/src/weights.rs index 18b3ebe7f..2c79f5987 100644 --- a/substrate-node/pallets/pallet-tft-price/src/weights.rs +++ b/substrate-node/pallets/pallet-tft-price/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_tft_price //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -61,8 +61,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `235` // Estimated: `6175` - // Minimum execution time: 83_896_000 picoseconds. - Weight::from_parts(98_515_000, 6175) + // Minimum execution time: 91_464_000 picoseconds. + Weight::from_parts(110_359_000, 6175) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -74,8 +74,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `93` // Estimated: `1578` - // Minimum execution time: 10_248_000 picoseconds. - Weight::from_parts(14_157_000, 1578) + // Minimum execution time: 14_440_000 picoseconds. + Weight::from_parts(14_903_000, 1578) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -87,8 +87,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `93` // Estimated: `1578` - // Minimum execution time: 9_079_000 picoseconds. - Weight::from_parts(9_349_000, 1578) + // Minimum execution time: 14_827_000 picoseconds. + Weight::from_parts(16_694_000, 1578) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -116,8 +116,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `235` // Estimated: `6175` - // Minimum execution time: 83_896_000 picoseconds. - Weight::from_parts(98_515_000, 6175) + // Minimum execution time: 91_464_000 picoseconds. + Weight::from_parts(110_359_000, 6175) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -129,8 +129,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `93` // Estimated: `1578` - // Minimum execution time: 10_248_000 picoseconds. - Weight::from_parts(14_157_000, 1578) + // Minimum execution time: 14_440_000 picoseconds. + Weight::from_parts(14_903_000, 1578) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -142,8 +142,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `93` // Estimated: `1578` - // Minimum execution time: 9_079_000 picoseconds. - Weight::from_parts(9_349_000, 1578) + // Minimum execution time: 14_827_000 picoseconds. + Weight::from_parts(16_694_000, 1578) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } diff --git a/substrate-node/pallets/pallet-validator/src/weights.rs b/substrate-node/pallets/pallet-validator/src/weights.rs index affed8c5b..83ebdd680 100644 --- a/substrate-node/pallets/pallet-validator/src/weights.rs +++ b/substrate-node/pallets/pallet-validator/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_validator //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -50,8 +50,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3507` - // Minimum execution time: 30_422_000 picoseconds. - Weight::from_parts(33_328_000, 3507) + // Minimum execution time: 30_923_000 picoseconds. + Weight::from_parts(35_120_000, 3507) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -65,8 +65,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `365` // Estimated: `3830` - // Minimum execution time: 73_955_000 picoseconds. - Weight::from_parts(80_040_000, 3830) + // Minimum execution time: 77_325_000 picoseconds. + Weight::from_parts(87_716_000, 3830) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -80,8 +80,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `431` // Estimated: `3896` - // Minimum execution time: 67_710_000 picoseconds. - Weight::from_parts(110_017_000, 3896) + // Minimum execution time: 111_901_000 picoseconds. + Weight::from_parts(135_294_000, 3896) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -91,8 +91,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3507` - // Minimum execution time: 17_537_000 picoseconds. - Weight::from_parts(27_341_000, 3507) + // Minimum execution time: 24_694_000 picoseconds. + Weight::from_parts(27_765_000, 3507) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -110,8 +110,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `494` // Estimated: `4687` - // Minimum execution time: 49_255_000 picoseconds. - Weight::from_parts(50_552_000, 4687) + // Minimum execution time: 73_970_000 picoseconds. + Weight::from_parts(84_533_000, 4687) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -131,8 +131,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `558` // Estimated: `4687` - // Minimum execution time: 65_252_000 picoseconds. - Weight::from_parts(69_396_000, 4687) + // Minimum execution time: 66_678_000 picoseconds. + Weight::from_parts(80_158_000, 4687) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -146,8 +146,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3507` - // Minimum execution time: 30_422_000 picoseconds. - Weight::from_parts(33_328_000, 3507) + // Minimum execution time: 30_923_000 picoseconds. + Weight::from_parts(35_120_000, 3507) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -161,8 +161,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `365` // Estimated: `3830` - // Minimum execution time: 73_955_000 picoseconds. - Weight::from_parts(80_040_000, 3830) + // Minimum execution time: 77_325_000 picoseconds. + Weight::from_parts(87_716_000, 3830) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -176,8 +176,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `431` // Estimated: `3896` - // Minimum execution time: 67_710_000 picoseconds. - Weight::from_parts(110_017_000, 3896) + // Minimum execution time: 111_901_000 picoseconds. + Weight::from_parts(135_294_000, 3896) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -187,8 +187,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3507` - // Minimum execution time: 17_537_000 picoseconds. - Weight::from_parts(27_341_000, 3507) + // Minimum execution time: 24_694_000 picoseconds. + Weight::from_parts(27_765_000, 3507) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -206,8 +206,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `494` // Estimated: `4687` - // Minimum execution time: 49_255_000 picoseconds. - Weight::from_parts(50_552_000, 4687) + // Minimum execution time: 73_970_000 picoseconds. + Weight::from_parts(84_533_000, 4687) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -227,8 +227,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `558` // Estimated: `4687` - // Minimum execution time: 65_252_000 picoseconds. - Weight::from_parts(69_396_000, 4687) + // Minimum execution time: 66_678_000 picoseconds. + Weight::from_parts(80_158_000, 4687) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } diff --git a/substrate-node/pallets/substrate-validator-set/src/weights.rs b/substrate-node/pallets/substrate-validator-set/src/weights.rs index ccdc7b271..28d727689 100644 --- a/substrate-node/pallets/substrate-validator-set/src/weights.rs +++ b/substrate-node/pallets/substrate-validator-set/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for substrate_validator_set //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2026-03-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cc3955fca132`, CPU: `DO-Regular` +//! HOSTNAME: `0cd0ec2d76e9`, CPU: `DO-Regular` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -49,8 +49,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `139` // Estimated: `1624` - // Minimum execution time: 41_673_000 picoseconds. - Weight::from_parts(46_756_000, 1624) + // Minimum execution time: 50_354_000 picoseconds. + Weight::from_parts(54_149_000, 1624) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -62,8 +62,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `205` // Estimated: `1690` - // Minimum execution time: 32_364_000 picoseconds. - Weight::from_parts(33_414_000, 1690) + // Minimum execution time: 40_982_000 picoseconds. + Weight::from_parts(43_128_000, 1690) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -75,8 +75,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `172` // Estimated: `1657` - // Minimum execution time: 34_943_000 picoseconds. - Weight::from_parts(39_459_000, 1657) + // Minimum execution time: 41_280_000 picoseconds. + Weight::from_parts(45_255_000, 1657) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -92,8 +92,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `139` // Estimated: `1624` - // Minimum execution time: 41_673_000 picoseconds. - Weight::from_parts(46_756_000, 1624) + // Minimum execution time: 50_354_000 picoseconds. + Weight::from_parts(54_149_000, 1624) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -105,8 +105,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `205` // Estimated: `1690` - // Minimum execution time: 32_364_000 picoseconds. - Weight::from_parts(33_414_000, 1690) + // Minimum execution time: 40_982_000 picoseconds. + Weight::from_parts(43_128_000, 1690) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -118,8 +118,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `172` // Estimated: `1657` - // Minimum execution time: 34_943_000 picoseconds. - Weight::from_parts(39_459_000, 1657) + // Minimum execution time: 41_280_000 picoseconds. + Weight::from_parts(45_255_000, 1657) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } From ba41554be0c14feacce283eaa3204e5153e22613 Mon Sep 17 00:00:00 2001 From: Sameh Abouelsaad Date: Thu, 20 Aug 2026 14:17:55 +0300 Subject: [PATCH 5/5] docs(0027): correct the council-motion ceiling against the measured weights The estimate of 900-1,800 contracts per motion came from a placeholder weight whose per-element slope was 27x too low. The generated benchmark measures 492 us base plus 1.45 us for every contract in the vectors being rewritten, which bounds a motion at about 2,000 contracts between near-empty nodes but only ~340 when migrating off a node holding 1,660 -- the busiest nodes batch worst, which is the opposite of the intuition the old range gave. --- docs/architecture/0027-migrate-node-contract.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/architecture/0027-migrate-node-contract.md b/docs/architecture/0027-migrate-node-contract.md index f3059a818..42a33d069 100644 --- a/docs/architecture/0027-migrate-node-contract.md +++ b/docs/architecture/0027-migrate-node-contract.md @@ -176,7 +176,11 @@ its last construction site, and deleting it would renumber every variant below. destinations make the multiplier 1.0 and remove it. If the signed path is added, it needs a directional guard. - **A council motion has a weight ceiling.** `MaxProposalWeight` is 50% of max - block weight, bounding a motion at roughly 900–1,800 contracts. + block weight. Against the measured weight, that bounds a motion at about 2,000 + contracts when the nodes involved are near-empty, falling to roughly **340** when + migrating off a node holding ~1,660 — the cost scales with the length of the + contract vectors being rewritten, so the busiest nodes are the ones that batch + worst. Size batches per tenant group and this ceiling is never near. ## Operational gate