mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-10 20:27:21 +00:00
e6c83710e6
* Base features and traits.
* pallet and unsigned phase
* add signed phase.
* remove comments
* Undo bad formattings.
* some formatting cleanup.
* Small self-cleanup.
* Add todo
* Make it all build
* self-review
* Some doc tests.
* Some changes from other PR
* Fix session test
* Update bin/node/runtime/src/lib.rs
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
* Fix name.
* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs
* typos and verbiage
* no glob imports in signed.rs
* meaningful generic type parameters for SignedSubmission
* dedup feasibility check weight calculation
* simplify/optimize fn insert_submission
* tests: remove glob, cause to build without error
* use sp_std::vec::Vec
* maintain invariant within fn insert_submission
* fix accidentally ordering the list backward
* intentionally order the list in reverse
* get rid of unused import
* ensure signed submissions are cleared in early elect
* finalize the signed phase when appropriate
- ensure we don't leave storage lying around, even if elect called prematurely
- test that proposition
- disable the unsigned phase if a viable solution from the signed phase exists
- ensure signed phase finalization weight is accounted for
* resolve dispatch error todo
* update assumptions in submit benchmark
* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs
* line length
* make a few more things pub
* restore missing import
* update ui test output
* update tests from master branch
* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs
* remove duplicate definitions
* remove signed reward factor due to its attack potential
* Update frame/election-provider-multi-phase/src/signed.rs
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
* remove SignedRewardMax; no longer necessary
* compute the encoded size without actually encoding
* remove unused PostInfo
* pub use some stuff
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
* ensure `pub use` things are in fact `pub`
* add event information: was another solution ejected to make room
* unconditionally run the unsigned phase even if signed was successful
* remove dead test code
* meaningful witness data name
* use errors instead of defensive `unwrap_or_default`
* get rid of a log message redundant with an event
* saturating math
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* import Saturating
* mv `fn submit` to end of call
* add log line
* Use a better data structure for SignedSubmissions instead of Vec (#8933)
* Remove: (#8748)
* `NetworkStatusSinks`
* `sc_service::SpawnTasksParams::network_status_sinks`
Also:
* `sc_service::build_network()` does not return `network_status_sinks`
* CI: fix simnet trigger (#8927)
* CI: chore
* CI: pin simnet version
* More sc-service config reexports (#8887)
* Reexport ExecutionStrategies and ExecutionStrategy
* Reexport more of the network
* Reexport the ExecutionStrategy as it's used within ExecutionStrategies
* Fix check runtime CI (#8930)
* Fix check_runtime.sh script
* contracts: Remove confusing "Related Modules" doc
* Bump parity-wasm and pwasm-utils to the newest versions everywhere (#8928)
* BROKEN: convert SignedSubmissions to BoundedBTreeSet
Eventually, once it works, this change should improve overall performance.
However, in the meantime, the trait bounds aren't playing nicely, and
this is turning into too much of a pain to handle right now as part of
/#7910. We can take care of it later.
* Simple `MaxBoundedLen` Implementations (#8793)
* implement max_values + storages info
* some formatting + doc
* sudo sanity check
* timestamp
* assets (not working)
* fix assets
* impl for proxy
* update balances
* rename StoragesInfo -> PalletStorageInfo
* merge both StorageInfoTrait and PalletStorageInfo
I think it is more future proof. In the future some storage could make
use of multiple prefix. Like one to store how much value has been
inserted, etc...
* Update frame/support/procedural/src/storage/parse.rs
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
* Update frame/support/procedural/src/storage/storage_struct.rs
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
* Fix max_size using hasher information
hasher now expose `max_len` which allows to computes their maximum len.
For hasher without concatenation, it is the size of the hash part,
for hasher with concatenation, it is the size of the hash part + max
encoded len of the key.
* fix tests
* fix ui tests
* Move `MaxBoundedLen` into its own crate (#8814)
* move MaxEncodedLen into its own crate
* remove MaxEncodedLen impl from frame-support
* add to assets and balances
* try more fixes
* fix compile
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* nits
* fix compile
* line width
* fix max-values-macro merge
* Add some derive, needed for test and other purpose
* use weak bounded vec in some cases
* Update lib.rs
* move max-encoded-len crate
* fix
* remove app crypto for now
* width
* Revert "remove app crypto for now"
This reverts commit 73623e9933d50648e0e7fe90b6171a8e45d7f5a2.
* unused variable
* more unused variables
* more fixes
* Add #[max_encoded_len_crate(...)] helper attribute
The purpose of this attribute is to reduce the surface area of
max_encoded_len changes. Crates deriving `MaxEncodedLen` do not
need to add it to `Cargo.toml`; they can instead just do
```rust
\#[derive(Encode, MaxEncodedLen)]
\#[max_encoded_len_crate(frame_support::max_encoded_len)]
struct Example;
```
* fix a ui test
* use #[max_encoded_len_crate(...)] helper in app_crypto
* remove max_encoded_len import where not necessary
* update lockfile
* fix ui test
* ui
* newline
* fix merge
* try fix ui again
* Update max-encoded-len/derive/src/lib.rs
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
* extract generate_crate_access_2018
* Update lib.rs
* compiler isnt smart enough
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
Co-authored-by: Peter Goodspeed-Niklaus <peter.r.goodspeedniklaus@gmail.com>
* remove duplicate Issued/Burned events (#8935)
* weather -> whether (#8938)
* make remote ext use batch ws-client (#8916)
* make remote ext use batch ws-client
* Add debug log for key length
* better assertions
* new sanity_checl
* try and make it work with batch
* update test
* remove exctra uri
* add missing at
* remove unused rpc stuff
* improve
Co-authored-by: emostov <32168567+emostov@users.noreply.github.com>
* Make `Schedule` fields public to allow for customization (#8924)
* Make `Schedule` fields public for customization
* Fix doc typo
Co-authored-by: Andrew Jones <ascjones@gmail.com>
Co-authored-by: Andrew Jones <ascjones@gmail.com>
* Session key should be settable at genesis even for non-endowed accounts (#8942)
* Session key should be settable at genesis even for non-endowed accounts
* Docs
* Migrate pallet-scored-pool to pallet attribute macro (#8825)
* Migrate pallet-scored-pool to pallet attribute macro.
* Remove dummy event.
* Apply review suggestions.
* Bump retain_mut from 0.1.2 to 0.1.3 (#8951)
Bumps [retain_mut](https://github.com/upsuper/retain_mut) from 0.1.2 to 0.1.3.
- [Release notes](https://github.com/upsuper/retain_mut/releases)
- [Commits](https://github.com/upsuper/retain_mut/compare/v0.1.2...v0.1.3)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Use correct CreateInherentDataProviders impl for manual seal (#8852)
* use correct CreateInherentDataProviders impl for manual seal
* add babe inherent provider
* move client into factory fn
* Refactor code a little bit (#8932)
* Optimize `next_storage_key` (#8956)
* Optimize `next_storage_key`
- Do not rely on recursion
- Use an iterator over the overlay to not always call the same method
* Fix bug
* Add deserialize for TransactionValidityError in std. (#8961)
* Add deserialize for TransactionValidityError in std.
* Fix derives
* Bump getrandom from 0.2.2 to 0.2.3 (#8952)
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.2.2 to 0.2.3.
- [Release notes](https://github.com/rust-random/getrandom/releases)
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.2.2...v0.2.3)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Allow usage of path in construct_runtime! (#8801)
* Allow usage of path in construct_runtime!
* Fix whitespace
* Fix whitespace
* Make expand_runtime_metadata accept slice instead of Iterator
* Include Call and Event in construct_runtime for testing
* Migrate impl_outer_event to proc macro
* Fix integrity_test_works
* Update UI test expectations
* Factor in module path while generating enum variant or fn names
* Use ParseStream::lookahead for more helpful error messages
* Remove generating outer_event_metadata
* Ensure pallets with different paths but same last path segment can coexist
* Remove unnecessary generated function
* Migrate decl_outer_config to proc macro
* Add default_filter test for expand_outer_origin
* Allow crate, self and super keywords to appear in pallet path
* Add UI test for specifying empty pallet paths in construct_runtime
* Reduce cargo doc warnings (#8947)
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
* Update wasmtime to 0.27 (#8913)
* Update wasmtime to 0.27
A couple of notes:
- Now we are fair about unsafeness of runtime creation via an compiled artifact.
This change was prompted by the change in wasmtime which made
`deserialize` rightfully unsafe. Now `CodeSupplyMode` was hidden and
the `create_runtime` now takes the blob again and there is now a new
fn for creating a runtime with a compiled artifact.
- This is a big change for wasmtime. They switched to the modern backend
for code generation. While this can bring performance improvements, it
can also introduce some problems. In fact, 0.27 fixed a serious issue
that could lead to sandbox escape. Hence we need a proper burn in.
This would require a change to PVF validation host as well.
* Filter regalloc logging
* Spellling corrections (no code changes) (#8971)
* Spelling corrections
* As this might break let's do as a separate PR
* Dependabot use correct label (#8973)
* Inject hashed prefix for remote-ext (#8960)
* Inject for remote-ext
* Update utils/frame/remote-externalities/src/lib.rs
Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com>
* Update utils/frame/remote-externalities/src/lib.rs
Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com>
* Apply suggestions from code review
* Apply suggestions from code review
Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com>
* Use `SpawnTaskHandle`s for spawning tasks in the tx pool (#8958)
* Remove futures-diagnose
* Use `SpawnTaskHandle`s for spawning tasks in the tx pool
* Box the spawner
* Fix tests
* Use the testing task executor
* Do not spend time on verifying the signatures before calling Runtime (#8980)
* Revert "Use `SpawnTaskHandle`s for spawning tasks in the tx pool (#8958)" (#8983)
This reverts commit 84e402389b.
* Uniques: An economically-secure basic-featured NFT pallet (#8813)
* Uniques: An economically-secure basic-featured NFT pallet
* force_transfer
* freeze/thaw
* team management
* approvals
* Fixes
* force_asset_status
* class_metadata
* instance metadata
* Fixes
* use nmap
* Fixes
* class metadata has information field
* Intiial mock/tests and a fix
* Remove impl_non_fungibles
* Docs
* Update frame/uniques/src/lib.rs
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* Update frame/uniques/src/lib.rs
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* Update frame/uniques/src/lib.rs
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* Update frame/uniques/src/lib.rs
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* Reserve, don't transfer.
* Fixes
* Tests
* Tests
* refresh_deposit
* Tests and proper handling of metdata destruction
* test burn
* Tests
* Update impl_fungibles.rs
* Initial benchmarking
* benchmark
* Fixes
* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs
* Attributes
* Attribute metadata
* Fixes
* Update frame/uniques/README.md
* Docs
* Docs
* Docs
* Simple metadata
* Use BoundedVec
* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs
* Update frame/uniques/src/lib.rs
Co-authored-by: Lohann Paterno Coutinho Ferreira <developer@lohann.dev>
* Update frame/uniques/src/lib.rs
Co-authored-by: Lohann Paterno Coutinho Ferreira <developer@lohann.dev>
* Update frame/uniques/src/lib.rs
Co-authored-by: Lohann Paterno Coutinho Ferreira <developer@lohann.dev>
* Update frame/uniques/src/lib.rs
Co-authored-by: Lohann Paterno Coutinho Ferreira <developer@lohann.dev>
* Update frame/uniques/src/lib.rs
Co-authored-by: Lohann Paterno Coutinho Ferreira <developer@lohann.dev>
* Fixes
* Update frame/uniques/README.md
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
* Update frame/uniques/README.md
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
* Update frame/uniques/README.md
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
* Docs
* Bump
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Parity Bot <admin@parity.io>
Co-authored-by: Lohann Paterno Coutinho Ferreira <developer@lohann.dev>
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
* Update WeakBoundedVec's remove and swap_remove (#8985)
Co-authored-by: Boiethios <felix-dev@daudre-vignier.fr>
* Convert another instance of Into impl to From in the macros (#8986)
* Convert another instance of Into impl to From in the macros
* Convert another location
* also fix bounded vec (#8987)
* fix most compiler errors
Mostly the work so far has been in tracking down where precisely
to insert appropriate trait bounds, and updating `fn insert_submission`.
However, there's still a compiler error remaining:
```
error[E0275]: overflow evaluating the requirement `Compact<_>: Decode`
|
= help: consider adding a `#![recursion_limit="256"]` attribute to your crate (`pallet_election_provider_multi_phase`)
= note: required because of the requirements on the impl of `Decode` for `Compact<_>`
= note: 126 redundant requirements hidden
= note: required because of the requirements on the impl of `Decode` for `Compact<_>`
```
Next up: figure out how we ended up with that recursive bound, and fix it.
* extract type SignedSubmissionsOf<T>
Weirdly, we still encounter the recursive trait definition error here,
despite removing the trait bounds. Something weird is happening.
* impl Decode bounds on BoundedBTreeMap/Set on T, not predecessor
Otherwise, Rust gets confused and decides that the trait bound is
infinitely recursive.
For that matter, it _still_ gets confused somehow and decides
that the trait bound is infinitely recursive, but at least
this should somewhat simplify the matter.
* fix recursive trait bound problem
* minor fixes
* more little fixes
* correct semantics for try_insert
* more fixes
* derive Ord for SolutionType
* tests compile
* fix most tests, rm unnecessary one
* Transactionpool: Make `ready_at` return earlier (#8995)
`ready_at` returns when we have processed the requested block. However,
on startup we already have processed the best block and there
are no transactions in the pool on startup anyway. So, we can set `updated_at`
to the best block on startup.
Besides that `ready_at` now returns early when there are no ready nor
any future transactions in the pool.
* Discard notifications if we have failed to parse handshake (#8806)
* Migrate pallet-democracy to pallet attribute macro (#8824)
* Migrate pallet-democracy to pallet attribute macro.
* Metadata fix.
* Trigger CI.
* Add ecdsa::Pair::verify_prehashed() (#8996)
* Add ecdsa::Pair::verify_prehashed()
* turn verify_prehashed() into an associated function
* add Signature::recover_prehashed()
* Non-fungible token traits (#8993)
* Non-fungible token traits
* Docs
* Fixes
* Implement non-fungible trait for Uniques
* Update frame/uniques/src/impl_nonfungibles.rs
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* Update frame/uniques/src/impl_nonfungibles.rs
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* Removes unused import (#9007)
* Add Call Filter That Prevents Nested `batch_all` (#9009)
* add filter preventing nested `batch_all`
* more tests
* fix test
* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_utility --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/utility/src/weights.rs --template=./.maintain/frame-weight-template.hbs
Co-authored-by: Parity Bot <admin@parity.io>
* Transaction pool: Ensure that we prune transactions properly (#8963)
* Transaction pool: Ensure that we prune transactions properly
There was a bug in the transaction pool that we didn't pruned
transactions properly because we called `prune_known`, instead of `prune`.
This bug was introduced by:
https://github.com/paritytech/substrate/pull/4629
This is required to have stale extrinsics being removed properly, so
that they don't fill up the tx pool.
* Fix compilation
* Fix benches
* ...
* Storage chain: Runtime module (#8624)
* Transaction storage runtime module
* WIP: Tests
* Tests, benchmarks and docs
* Made check_proof mandatory
* Typo
* Renamed a crate
* Apply suggestions from code review
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
* Added weight for on_finalize
* Fixed counter mutations
* Reorganized tests
* Fixed build
* Update for the new inherent API
* Reworked for the new inherents API
* Apply suggestions from code review
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* Store transactions in a Vec
* Added FeeDestination
* Get rid of constants
* Fixed node runtime build
* Fixed benches
* Update frame/transaction-storage/src/lib.rs
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* more useful error message (#9014)
* Named reserve (#7778)
* add NamedReservableCurrency
* move currency related trait and types into a new file
* implement NamedReservableCurrency
* remove empty reserves
* Update frame/support/src/traits.rs
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* fix build
* bump year
* add MaxReserves
* repatriate_reserved_named should put reserved fund into named reserved
* add tests
* add some docs
* fix warning
* Update lib.rs
* fix test
* fix test
* fix
* fix
* triggier CI
* Move NamedReservableCurrency.
* Use strongly bounded vec for reserves.
* Fix test.
* remove duplicated file
* trigger CI
* Make `ReserveIdentifier` assosicated type
* add helpers
* make ReserveIdentifier assosicated type
* fix
* update
* trigger CI
* Apply suggestions from code review
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
* trigger CI
* Apply suggestions from code review
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Gavin Wood <i@gavwood.com>
Co-authored-by: Shaun Wang <spxwang@gmail.com>
* update ss58 type to u16 (#8955)
* Fixed build (#9021)
* Bump parity-db (#9024)
* consensus: handle justification sync for blocks authored locally (#8698)
* consensus: add trait to control justification sync process
* network: implement JustificationSyncLink for NetworkService
* slots: handle justification sync in slot worker
* babe: fix slot worker instantiation
* aura: fix slot worker instantiation
* pow: handle justification sync in miner
* babe: fix tests
* aura: fix tests
* node: fix compilation
* node-template: fix compilation
* consensus: rename justification sync link parameter
* aura: fix test compilation
* consensus: slots: move JustificationSyncLink out of on_slot
* arithmetic: fix PerThing pow (#9030)
* arithmetic: add failing test for pow
* arithmetic: fix PerThing::pow
* Revert back to previous optimisations
Co-authored-by: Gav Wood <gavin@parity.io>
* Compact proof utilities in sp_trie. (#8574)
* validation extension in sp_io
* need paths
* arc impl
* missing host function in executor
* io to pkdot
* decode function.
* encode primitive.
* trailing tab
* multiple patch
* fix child trie logic
* restore master versionning
* bench compact proof size
* trie-db 22.3 is needed
* line width
* split line
* fixes for bench (additional root may not be needed as original issue was
with empty proof).
* revert compact from block size calculation.
* New error type for compression.
* Adding test (incomplete (failing)).
Also lacking real proof checking (no good primitives in sp-trie crate).
* There is currently no proof recording utility in sp_trie, removing
test.
* small test of child root in proof without a child proof.
* remove empty test.
* remove non compact proof size
* Missing revert.
* proof method to encode decode.
* Don't inlucde nominaotrs that back no one in the snapshot. (#9017)
* fix all_in_one test which had a logic error
* use sp_std, not std
* Periodically call `Peerset::alloc_slots` on all sets (#9025)
* Periodically call alloc_slots on all slots
* Add test
* contracts: Add new `seal_call` that offers new features (#8909)
* Add new `seal_call` that offers new features
* Fix doc typo
Co-authored-by: Michael Müller <michi@parity.io>
* Fix doc typos
Co-authored-by: Michael Müller <michi@parity.io>
* Fix comment on assert
* Update CHANGELOG.md
Co-authored-by: Michael Müller <michi@parity.io>
* fix unreserve_all_named (#9042)
* Delete legacy runtime metadata macros (#9043)
* `rpc-http-threads` cli arg (#8890)
* Add optional `rpc-http-threads` cli arg
* Update `http::ServerBuilder`threads
* allow inserting equal items into bounded map/set
* refactor: only load one solution at a time
This increases the database read load, because we read one solution
at a time. On the other hand, it substantially decreases the overall
memory load, because we _only_ read one solution at a time instead
of reading all of them.
* Emit `Bonded` event when rebonding (#9040)
* Emit `Bonded` event when rebonding
* fix borrow checker
* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_staking --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/staking/src/weights.rs --template=./.maintain/frame-weight-template.hbs
Co-authored-by: Parity Bot <admin@parity.io>
* fix tests
* Revert "Merge remote-tracking branch 'origin/master' into prgn-election-provider-multi-phase-bounded-btree-set-signed-submissions"
This reverts commit de92b1e8e0e44a74c24e270d02b6e8e6a2c37032, reversing
changes made to dae31f2018593b60dbf1d96ec96cdc35c374bb9e.
* only derive debug when std
* write after check
* SignedSubmissions doesn't ever modify storage until .put()
This makes a true check-before-write pattern possible.
* REVERT ME: demo that Drop impl doesn't work
* Revert "REVERT ME: demo that Drop impl doesn't work"
This reverts commit 3317a4bb4de2e77d5a7fff2154552a81ec081763.
* doc note about decode_len
* rename get_submission, take_submission for clarity
* add test which fails for current incorrect behavior
* inline fn insert_submission
This fixes a tricky check-before-write error, ensuring that we
really only ever modify anything if we have in fact succeeded.
Co-authored-by: Roman Proskuryakov <humbug@deeptown.org>
Co-authored-by: Denis Pisarev <denis.pisarev@parity.io>
Co-authored-by: MOZGIII <mike-n@narod.ru>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: Sebastian Müller <sebastian@web3.foundation>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: emostov <32168567+emostov@users.noreply.github.com>
Co-authored-by: Andrew Jones <ascjones@gmail.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: Shaun Wang <spxwang@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Seun Lanlege <seun@parity.io>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Squirrel <gilescope@gmail.com>
Co-authored-by: Sergei Shulepov <sergei@parity.io>
Co-authored-by: Ashley <ashley.ruglys@gmail.com>
Co-authored-by: Parity Bot <admin@parity.io>
Co-authored-by: Lohann Paterno Coutinho Ferreira <developer@lohann.dev>
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: Boiethios <Boiethios@users.noreply.github.com>
Co-authored-by: Boiethios <felix-dev@daudre-vignier.fr>
Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>
Co-authored-by: Andreas Doerr <adoerr@users.noreply.github.com>
Co-authored-by: Dmitry Kashitsyn <korvin@deeptown.org>
Co-authored-by: Arkadiy Paronyan <arkady.paronyan@gmail.com>
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Andronik Ordian <write@reusable.software>
Co-authored-by: Xiliang Chen <xlchen1291@gmail.com>
Co-authored-by: Gavin Wood <i@gavwood.com>
Co-authored-by: Jakub Pánik <jakub.panik@gmail.com>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: Michael Müller <michi@parity.io>
Co-authored-by: tgmichel <telmo@purestake.com>
* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs
* remove duplicate weight definitions injected by benchmark bot
* check deletion overlay before getting
* clarify non-conflict between delete, insert overlays
* drain can be used wrong so is private
* update take_submission docs
* more drain improvements
* more take_submission docs
* debug assertion helps prove expectation is valid
* doc on changing SignedMaxSubmissions
* take_submission inner doc on system properties
* Apply suggestions from code review
Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
* get SolutionOrSnapshotSize out of the loop
Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com>
* doc which items comprise `SignedSubmissions`
* add doc about index as unique identifier
* Add debug assertions to prove drain worked properly
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
* replace take_submission with swap_out_submission
* use a match to demonstrate all cases from signed_submissions.insert
* refactor signed_submissions.insert return type
* prettify test assertion
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
* improve docs
Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
* add tests that finalize_signed_phase is idempotent
* add some debug assertions to guard against misuse of storage
* log internal logic errors instead of panicing
* don't store the reward with each signed submission
The signed reward base can be treated as a constant. It can in principle
change, but even if it's updated in the middle of an election, it's
appropriate to use the current value for the winner.
* emit Rewarded, Slashed events as appropriate
Makes it easier to see who won/lost with signed submissions.
* update docs
* use a custom enum to be explicit about the outcome of insertion
* remove outdated docs
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
Co-authored-by: Parity Benchmarking Bot <admin@parity.io>
Co-authored-by: Peter Goodspeed-Niklaus <peter.r.goodspeedniklaus@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Roman Proskuryakov <humbug@deeptown.org>
Co-authored-by: Denis Pisarev <denis.pisarev@parity.io>
Co-authored-by: MOZGIII <mike-n@narod.ru>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: Sebastian Müller <sebastian@web3.foundation>
Co-authored-by: emostov <32168567+emostov@users.noreply.github.com>
Co-authored-by: Andrew Jones <ascjones@gmail.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: Shaun Wang <spxwang@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Seun Lanlege <seun@parity.io>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Squirrel <gilescope@gmail.com>
Co-authored-by: Sergei Shulepov <sergei@parity.io>
Co-authored-by: Ashley <ashley.ruglys@gmail.com>
Co-authored-by: Lohann Paterno Coutinho Ferreira <developer@lohann.dev>
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: Boiethios <Boiethios@users.noreply.github.com>
Co-authored-by: Boiethios <felix-dev@daudre-vignier.fr>
Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>
Co-authored-by: Andreas Doerr <adoerr@users.noreply.github.com>
Co-authored-by: Dmitry Kashitsyn <korvin@deeptown.org>
Co-authored-by: Arkadiy Paronyan <arkady.paronyan@gmail.com>
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Andronik Ordian <write@reusable.software>
Co-authored-by: Xiliang Chen <xlchen1291@gmail.com>
Co-authored-by: Gavin Wood <i@gavwood.com>
Co-authored-by: Jakub Pánik <jakub.panik@gmail.com>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: Michael Müller <michi@parity.io>
Co-authored-by: tgmichel <telmo@purestake.com>
1546 lines
52 KiB
Rust
1546 lines
52 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! The unsigned phase, and its miner.
|
|
|
|
use crate::{
|
|
helpers, Call, CompactAccuracyOf, CompactOf, Config, ElectionCompute, Error, FeasibilityError,
|
|
Pallet, RawSolution, ReadySolution, RoundSnapshot, SolutionOrSnapshotSize, Weight, WeightInfo,
|
|
};
|
|
use codec::{Encode, Decode};
|
|
use frame_support::{dispatch::DispatchResult, ensure, traits::Get};
|
|
use frame_system::offchain::SubmitTransaction;
|
|
use sp_arithmetic::Perbill;
|
|
use sp_npos_elections::{
|
|
CompactSolution, ElectionResult, assignment_ratio_to_staked_normalized,
|
|
assignment_staked_to_ratio_normalized, is_score_better, seq_phragmen,
|
|
};
|
|
use sp_runtime::{
|
|
DispatchError,
|
|
SaturatedConversion,
|
|
offchain::storage::{MutateStorageError, StorageValueRef},
|
|
traits::TrailingZeroInput,
|
|
};
|
|
use sp_std::{cmp::Ordering, convert::TryFrom, vec::Vec};
|
|
|
|
/// Storage key used to store the last block number at which offchain worker ran.
|
|
pub(crate) const OFFCHAIN_LAST_BLOCK: &[u8] = b"parity/multi-phase-unsigned-election";
|
|
/// Storage key used to store the offchain worker running status.
|
|
pub(crate) const OFFCHAIN_LOCK: &[u8] = b"parity/multi-phase-unsigned-election/lock";
|
|
|
|
/// Storage key used to cache the solution `call`.
|
|
pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-election/call";
|
|
|
|
/// A voter's fundamental data: their ID, their stake, and the list of candidates for whom they
|
|
/// voted.
|
|
pub type Voter<T> = (
|
|
<T as frame_system::Config>::AccountId,
|
|
sp_npos_elections::VoteWeight,
|
|
Vec<<T as frame_system::Config>::AccountId>,
|
|
);
|
|
|
|
/// The relative distribution of a voter's stake among the winning targets.
|
|
pub type Assignment<T> = sp_npos_elections::Assignment<
|
|
<T as frame_system::Config>::AccountId,
|
|
CompactAccuracyOf<T>,
|
|
>;
|
|
|
|
/// The [`IndexAssignment`][sp_npos_elections::IndexAssignment] type specialized for a particular
|
|
/// runtime `T`.
|
|
pub type IndexAssignmentOf<T> = sp_npos_elections::IndexAssignmentOf<CompactOf<T>>;
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
pub enum MinerError {
|
|
/// An internal error in the NPoS elections crate.
|
|
NposElections(sp_npos_elections::Error),
|
|
/// Snapshot data was unavailable unexpectedly.
|
|
SnapshotUnAvailable,
|
|
/// Submitting a transaction to the pool failed.
|
|
PoolSubmissionFailed,
|
|
/// The pre-dispatch checks failed for the mined solution.
|
|
PreDispatchChecksFailed(DispatchError),
|
|
/// The solution generated from the miner is not feasible.
|
|
Feasibility(FeasibilityError),
|
|
/// Something went wrong fetching the lock.
|
|
Lock(&'static str),
|
|
/// Cannot restore a solution that was not stored.
|
|
NoStoredSolution,
|
|
/// Cached solution is not a `submit_unsigned` call.
|
|
SolutionCallInvalid,
|
|
/// Failed to store a solution.
|
|
FailedToStoreSolution,
|
|
/// There are no more voters to remove to trim the solution.
|
|
NoMoreVoters,
|
|
}
|
|
|
|
impl From<sp_npos_elections::Error> for MinerError {
|
|
fn from(e: sp_npos_elections::Error) -> Self {
|
|
MinerError::NposElections(e)
|
|
}
|
|
}
|
|
|
|
impl From<FeasibilityError> for MinerError {
|
|
fn from(e: FeasibilityError) -> Self {
|
|
MinerError::Feasibility(e)
|
|
}
|
|
}
|
|
|
|
/// Save a given call into OCW storage.
|
|
fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> {
|
|
log!(debug, "saving a call to the offchain storage.");
|
|
let storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL);
|
|
match storage.mutate::<_, (), _>(|_| Ok(call.clone())) {
|
|
Ok(_) => Ok(()),
|
|
Err(MutateStorageError::ConcurrentModification(_)) => Err(MinerError::FailedToStoreSolution),
|
|
Err(MutateStorageError::ValueFunctionFailed(_)) => {
|
|
// this branch should be unreachable according to the definition of
|
|
// `StorageValueRef::mutate`: that function should only ever `Err` if the closure we
|
|
// pass it returns an error. however, for safety in case the definition changes, we do
|
|
// not optimize the branch away or panic.
|
|
Err(MinerError::FailedToStoreSolution)
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Get a saved solution from OCW storage if it exists.
|
|
fn restore_solution<T: Config>() -> Result<Call<T>, MinerError> {
|
|
StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL)
|
|
.get()
|
|
.ok()
|
|
.flatten()
|
|
.ok_or(MinerError::NoStoredSolution)
|
|
}
|
|
|
|
/// Clear a saved solution from OCW storage.
|
|
pub(super) fn kill_ocw_solution<T: Config>() {
|
|
log!(debug, "clearing offchain call cache storage.");
|
|
let mut storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL);
|
|
storage.clear();
|
|
}
|
|
|
|
/// Clear the offchain repeat storage.
|
|
///
|
|
/// After calling this, the next offchain worker is guaranteed to work, with respect to the
|
|
/// frequency repeat.
|
|
fn clear_offchain_repeat_frequency() {
|
|
let mut last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
|
|
last_block.clear();
|
|
}
|
|
|
|
/// `true` when OCW storage contains a solution
|
|
#[cfg(test)]
|
|
fn ocw_solution_exists<T: Config>() -> bool {
|
|
matches!(StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL).get::<Call<T>>(), Ok(Some(_)))
|
|
}
|
|
|
|
impl<T: Config> Pallet<T> {
|
|
/// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit
|
|
/// if our call's score is greater than that of the cached solution.
|
|
pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> {
|
|
log!(debug,"miner attempting to restore or compute an unsigned solution.");
|
|
|
|
let call = restore_solution::<T>()
|
|
.and_then(|call| {
|
|
// ensure the cached call is still current before submitting
|
|
if let Call::submit_unsigned(solution, _) = &call {
|
|
// prevent errors arising from state changes in a forkful chain
|
|
Self::basic_checks(solution, "restored")?;
|
|
Ok(call)
|
|
} else {
|
|
Err(MinerError::SolutionCallInvalid)
|
|
}
|
|
}).or_else::<MinerError, _>(|error| {
|
|
log!(debug, "restoring solution failed due to {:?}", error);
|
|
match error {
|
|
MinerError::NoStoredSolution => {
|
|
log!(trace, "mining a new solution.");
|
|
// if not present or cache invalidated due to feasibility, regenerate.
|
|
// note that failing `Feasibility` can only mean that the solution was
|
|
// computed over a snapshot that has changed due to a fork.
|
|
let call = Self::mine_checked_call()?;
|
|
save_solution(&call)?;
|
|
Ok(call)
|
|
}
|
|
MinerError::Feasibility(_) => {
|
|
log!(trace, "wiping infeasible solution.");
|
|
// kill the infeasible solution, hopefully in the next runs (whenever they
|
|
// may be) we mine a new one.
|
|
kill_ocw_solution::<T>();
|
|
clear_offchain_repeat_frequency();
|
|
Err(error)
|
|
},
|
|
_ => {
|
|
// nothing to do. Return the error as-is.
|
|
Err(error)
|
|
}
|
|
}
|
|
})?;
|
|
|
|
Self::submit_call(call)
|
|
}
|
|
|
|
/// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction.
|
|
pub fn mine_check_save_submit() -> Result<(), MinerError> {
|
|
log!(debug, "miner attempting to compute an unsigned solution.");
|
|
|
|
let call = Self::mine_checked_call()?;
|
|
save_solution(&call)?;
|
|
Self::submit_call(call)
|
|
}
|
|
|
|
/// Mine a new solution as a call. Performs all checks.
|
|
pub fn mine_checked_call() -> Result<Call<T>, MinerError> {
|
|
let iters = Self::get_balancing_iters();
|
|
// get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID.
|
|
let (raw_solution, witness) = Self::mine_and_check(iters)?;
|
|
|
|
let score = raw_solution.score.clone();
|
|
let call: Call<T> = Call::submit_unsigned(raw_solution, witness).into();
|
|
|
|
log!(
|
|
debug,
|
|
"mined a solution with score {:?} and size {}",
|
|
score,
|
|
call.using_encoded(|b| b.len())
|
|
);
|
|
|
|
Ok(call)
|
|
}
|
|
|
|
fn submit_call(call: Call<T>) -> Result<(), MinerError> {
|
|
log!(debug, "miner submitting a solution as an unsigned transaction");
|
|
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| MinerError::PoolSubmissionFailed)
|
|
}
|
|
|
|
// perform basic checks of a solution's validity
|
|
//
|
|
// Performance: note that it internally clones the provided solution.
|
|
pub fn basic_checks(
|
|
raw_solution: &RawSolution<CompactOf<T>>,
|
|
solution_type: &str,
|
|
) -> Result<(), MinerError> {
|
|
Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| {
|
|
log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err);
|
|
MinerError::PreDispatchChecksFailed(err)
|
|
})?;
|
|
|
|
Self::feasibility_check(raw_solution.clone(), ElectionCompute::Unsigned).map_err(|err| {
|
|
log!(debug, "feasibility check failed for {} solution: {:?}", solution_type, err);
|
|
err
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Mine a new npos solution, with all the relevant checks to make sure that it will be accepted
|
|
/// to the chain.
|
|
///
|
|
/// If you want an unchecked solution, use [`Pallet::mine_solution`].
|
|
/// If you want a checked solution and submit it at the same time, use
|
|
/// [`Pallet::mine_check_save_submit`].
|
|
pub fn mine_and_check(
|
|
iters: usize,
|
|
) -> Result<(RawSolution<CompactOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
|
let (raw_solution, witness) = Self::mine_solution(iters)?;
|
|
Self::basic_checks(&raw_solution, "mined")?;
|
|
Ok((raw_solution, witness))
|
|
}
|
|
|
|
/// Mine a new npos solution.
|
|
pub fn mine_solution(
|
|
iters: usize,
|
|
) -> Result<(RawSolution<CompactOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
|
let RoundSnapshot { voters, targets } =
|
|
Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
|
|
let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
|
|
|
|
seq_phragmen::<_, CompactAccuracyOf<T>>(
|
|
desired_targets as usize,
|
|
targets,
|
|
voters,
|
|
Some((iters, 0)),
|
|
)
|
|
.map_err(Into::into)
|
|
.and_then(Self::prepare_election_result)
|
|
}
|
|
|
|
/// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which
|
|
/// is ready to be submitted to the chain.
|
|
///
|
|
/// Will always reduce the solution as well.
|
|
pub fn prepare_election_result(
|
|
election_result: ElectionResult<T::AccountId, CompactAccuracyOf<T>>,
|
|
) -> Result<(RawSolution<CompactOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
|
// NOTE: This code path is generally not optimized as it is run offchain. Could use some at
|
|
// some point though.
|
|
|
|
// storage items. Note: we have already read this from storage, they must be in cache.
|
|
let RoundSnapshot { voters, targets } =
|
|
Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
|
|
let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
|
|
|
|
// now make some helper closures.
|
|
let cache = helpers::generate_voter_cache::<T>(&voters);
|
|
let voter_index = helpers::voter_index_fn::<T>(&cache);
|
|
let target_index = helpers::target_index_fn::<T>(&targets);
|
|
let voter_at = helpers::voter_at_fn::<T>(&voters);
|
|
let target_at = helpers::target_at_fn::<T>(&targets);
|
|
let stake_of = helpers::stake_of_fn::<T>(&voters, &cache);
|
|
|
|
// Compute the size of a compact solution comprised of the selected arguments.
|
|
//
|
|
// This function completes in `O(edges)`; it's expensive, but linear.
|
|
let encoded_size_of = |assignments: &[IndexAssignmentOf<T>]| {
|
|
CompactOf::<T>::try_from(assignments).map(|compact| compact.encoded_size())
|
|
};
|
|
|
|
let ElectionResult { assignments, winners } = election_result;
|
|
|
|
// Reduce (requires round-trip to staked form)
|
|
let sorted_assignments = {
|
|
// convert to staked and reduce.
|
|
let mut staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?;
|
|
|
|
// we reduce before sorting in order to ensure that the reduction process doesn't
|
|
// accidentally change the sort order
|
|
sp_npos_elections::reduce(&mut staked);
|
|
|
|
// Sort the assignments by reversed voter stake. This ensures that we can efficiently
|
|
// truncate the list.
|
|
staked.sort_by_key(
|
|
|sp_npos_elections::StakedAssignment::<T::AccountId> { who, .. }| {
|
|
// though staked assignments are expressed in terms of absolute stake, we'd
|
|
// still need to iterate over all votes in order to actually compute the total
|
|
// stake. it should be faster to look it up from the cache.
|
|
let stake = cache
|
|
.get(who)
|
|
.map(|idx| {
|
|
let (_, stake, _) = voters[*idx];
|
|
stake
|
|
})
|
|
.unwrap_or_default();
|
|
sp_std::cmp::Reverse(stake)
|
|
},
|
|
);
|
|
|
|
// convert back.
|
|
assignment_staked_to_ratio_normalized(staked)?
|
|
};
|
|
|
|
// convert to `IndexAssignment`. This improves the runtime complexity of repeatedly
|
|
// converting to `Compact`.
|
|
let mut index_assignments = sorted_assignments
|
|
.into_iter()
|
|
.map(|assignment| IndexAssignmentOf::<T>::new(
|
|
&assignment,
|
|
&voter_index,
|
|
&target_index,
|
|
))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
// trim assignments list for weight and length.
|
|
let size =
|
|
SolutionOrSnapshotSize { voters: voters.len() as u32, targets: targets.len() as u32 };
|
|
Self::trim_assignments_weight(
|
|
desired_targets,
|
|
size,
|
|
T::MinerMaxWeight::get(),
|
|
&mut index_assignments,
|
|
);
|
|
Self::trim_assignments_length(
|
|
T::MinerMaxLength::get(),
|
|
&mut index_assignments,
|
|
&encoded_size_of,
|
|
)?;
|
|
|
|
// now make compact.
|
|
let compact = CompactOf::<T>::try_from(&index_assignments)?;
|
|
|
|
// re-calc score.
|
|
let winners = sp_npos_elections::to_without_backing(winners);
|
|
let score = compact.clone().score(&winners, stake_of, voter_at, target_at)?;
|
|
|
|
let round = Self::round();
|
|
Ok((RawSolution { compact, score, round }, size))
|
|
}
|
|
|
|
/// Get a random number of iterations to run the balancing in the OCW.
|
|
///
|
|
/// Uses the offchain seed to generate a random number, maxed with
|
|
/// [`Config::MinerMaxIterations`].
|
|
pub fn get_balancing_iters() -> usize {
|
|
match T::MinerMaxIterations::get() {
|
|
0 => 0,
|
|
max @ _ => {
|
|
let seed = sp_io::offchain::random_seed();
|
|
let random = <u32>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
|
|
.expect("input is padded with zeroes; qed")
|
|
% max.saturating_add(1);
|
|
random as usize
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Greedily reduce the size of the solution to fit into the block w.r.t. weight.
|
|
///
|
|
/// The weight of the solution is foremost a function of the number of voters (i.e.
|
|
/// `assignments.len()`). Aside from this, the other components of the weight are invariant. The
|
|
/// number of winners shall not be changed (otherwise the solution is invalid) and the
|
|
/// `ElectionSize` is merely a representation of the total number of stakers.
|
|
///
|
|
/// Thus, we reside to stripping away some voters from the `assignments`.
|
|
///
|
|
/// Note that the solution is already computed, and the winners are elected based on the merit
|
|
/// of the entire stake in the system. Nonetheless, some of the voters will be removed further
|
|
/// down the line.
|
|
///
|
|
/// Indeed, the score must be computed **after** this step. If this step reduces the score too
|
|
/// much or remove a winner, then the solution must be discarded **after** this step.
|
|
pub fn trim_assignments_weight(
|
|
desired_targets: u32,
|
|
size: SolutionOrSnapshotSize,
|
|
max_weight: Weight,
|
|
assignments: &mut Vec<IndexAssignmentOf<T>>,
|
|
) {
|
|
let maximum_allowed_voters = Self::maximum_voter_for_weight::<T::WeightInfo>(
|
|
desired_targets,
|
|
size,
|
|
max_weight,
|
|
);
|
|
let removing: usize = assignments.len().saturating_sub(
|
|
maximum_allowed_voters.saturated_into(),
|
|
);
|
|
log!(
|
|
debug,
|
|
"from {} assignments, truncating to {} for weight, removing {}",
|
|
assignments.len(), maximum_allowed_voters, removing,
|
|
);
|
|
assignments.truncate(maximum_allowed_voters as usize);
|
|
}
|
|
|
|
/// Greedily reduce the size of the solution to fit into the block w.r.t length.
|
|
///
|
|
/// The length of the solution is largely a function of the number of voters. The number of
|
|
/// winners cannot be changed. Thus, to reduce the solution size, we need to strip voters.
|
|
///
|
|
/// Note that this solution is already computed, and winners are elected based on the merit of
|
|
/// the total stake in the system. Nevertheless, some of the voters may be removed here.
|
|
///
|
|
/// Sometimes, removing a voter can cause a validator to also be implicitly removed, if
|
|
/// that voter was the only backer of that winner. In such cases, this solution is invalid,
|
|
/// which will be caught prior to submission.
|
|
///
|
|
/// The score must be computed **after** this step. If this step reduces the score too much,
|
|
/// then the solution must be discarded.
|
|
pub fn trim_assignments_length(
|
|
max_allowed_length: u32,
|
|
assignments: &mut Vec<IndexAssignmentOf<T>>,
|
|
encoded_size_of: impl Fn(&[IndexAssignmentOf<T>]) -> Result<usize, sp_npos_elections::Error>,
|
|
) -> Result<(), MinerError> {
|
|
// Perform a binary search for the max subset of which can fit into the allowed
|
|
// length. Having discovered that, we can truncate efficiently.
|
|
let max_allowed_length: usize = max_allowed_length.saturated_into();
|
|
let mut high = assignments.len();
|
|
let mut low = 0;
|
|
|
|
// not much we can do if assignments are already empty.
|
|
if high == low {
|
|
return Ok(());
|
|
}
|
|
|
|
while high - low > 1 {
|
|
let test = (high + low) / 2;
|
|
if encoded_size_of(&assignments[..test])? <= max_allowed_length {
|
|
low = test;
|
|
} else {
|
|
high = test;
|
|
}
|
|
}
|
|
let maximum_allowed_voters =
|
|
if low < assignments.len() &&
|
|
encoded_size_of(&assignments[..low + 1])? <= max_allowed_length
|
|
{
|
|
low + 1
|
|
} else {
|
|
low
|
|
};
|
|
|
|
// ensure our post-conditions are correct
|
|
debug_assert!(
|
|
encoded_size_of(&assignments[..maximum_allowed_voters]).unwrap() <= max_allowed_length
|
|
);
|
|
debug_assert!(if maximum_allowed_voters < assignments.len() {
|
|
encoded_size_of(&assignments[..maximum_allowed_voters + 1]).unwrap()
|
|
> max_allowed_length
|
|
} else {
|
|
true
|
|
});
|
|
|
|
// NOTE: before this point, every access was immutable.
|
|
// after this point, we never error.
|
|
// check before edit.
|
|
|
|
log!(
|
|
debug,
|
|
"from {} assignments, truncating to {} for length, removing {}",
|
|
assignments.len(),
|
|
maximum_allowed_voters,
|
|
assignments.len().saturating_sub(maximum_allowed_voters),
|
|
);
|
|
assignments.truncate(maximum_allowed_voters);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Find the maximum `len` that a compact can have in order to fit into the block weight.
|
|
///
|
|
/// This only returns a value between zero and `size.nominators`.
|
|
pub fn maximum_voter_for_weight<W: WeightInfo>(
|
|
desired_winners: u32,
|
|
size: SolutionOrSnapshotSize,
|
|
max_weight: Weight,
|
|
) -> u32 {
|
|
if size.voters < 1 {
|
|
return size.voters;
|
|
}
|
|
|
|
let max_voters = size.voters.max(1);
|
|
let mut voters = max_voters;
|
|
|
|
// helper closures.
|
|
let weight_with = |active_voters: u32| -> Weight {
|
|
W::submit_unsigned(size.voters, size.targets, active_voters, desired_winners)
|
|
};
|
|
|
|
let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result<u32, ()> {
|
|
match current_weight.cmp(&max_weight) {
|
|
Ordering::Less => {
|
|
let next_voters = voters.checked_add(step);
|
|
match next_voters {
|
|
Some(voters) if voters < max_voters => Ok(voters),
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
Ordering::Greater => voters.checked_sub(step).ok_or(()),
|
|
Ordering::Equal => Ok(voters),
|
|
}
|
|
};
|
|
|
|
// First binary-search the right amount of voters
|
|
let mut step = voters / 2;
|
|
let mut current_weight = weight_with(voters);
|
|
|
|
while step > 0 {
|
|
match next_voters(current_weight, voters, step) {
|
|
// proceed with the binary search
|
|
Ok(next) if next != voters => {
|
|
voters = next;
|
|
}
|
|
// we are out of bounds, break out of the loop.
|
|
Err(()) => {
|
|
break;
|
|
}
|
|
// we found the right value - early exit the function.
|
|
Ok(next) => return next,
|
|
}
|
|
step = step / 2;
|
|
current_weight = weight_with(voters);
|
|
}
|
|
|
|
// Time to finish. We might have reduced less than expected due to rounding error. Increase
|
|
// one last time if we have any room left, the reduce until we are sure we are below limit.
|
|
while voters + 1 <= max_voters && weight_with(voters + 1) < max_weight {
|
|
voters += 1;
|
|
}
|
|
while voters.checked_sub(1).is_some() && weight_with(voters) > max_weight {
|
|
voters -= 1;
|
|
}
|
|
|
|
let final_decision = voters.min(size.voters);
|
|
debug_assert!(
|
|
weight_with(final_decision) <= max_weight,
|
|
"weight_with({}) <= {}",
|
|
final_decision,
|
|
max_weight,
|
|
);
|
|
final_decision
|
|
}
|
|
|
|
/// Checks if an execution of the offchain worker is permitted at the given block number, or
|
|
/// not.
|
|
///
|
|
/// This makes sure that
|
|
/// 1. we don't run on previous blocks in case of a re-org
|
|
/// 2. we don't run twice within a window of length `T::OffchainRepeat`.
|
|
///
|
|
/// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()`
|
|
/// is returned, `now` is written in storage and will be used in further calls as the baseline.
|
|
pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> {
|
|
let threshold = T::OffchainRepeat::get();
|
|
let last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
|
|
|
|
let mutate_stat = last_block.mutate::<_, &'static str, _>(
|
|
|maybe_head: Result<Option<T::BlockNumber>, _>| {
|
|
match maybe_head {
|
|
Ok(Some(head)) if now < head => Err("fork."),
|
|
Ok(Some(head)) if now >= head && now <= head + threshold => {
|
|
Err("recently executed.")
|
|
}
|
|
Ok(Some(head)) if now > head + threshold => {
|
|
// we can run again now. Write the new head.
|
|
Ok(now)
|
|
}
|
|
_ => {
|
|
// value doesn't exists. Probably this node just booted up. Write, and run
|
|
Ok(now)
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
match mutate_stat {
|
|
// all good
|
|
Ok(_) => Ok(()),
|
|
// failed to write.
|
|
Err(MutateStorageError::ConcurrentModification(_)) =>
|
|
Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")),
|
|
// fork etc.
|
|
Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)),
|
|
}
|
|
}
|
|
|
|
/// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned
|
|
/// transaction.
|
|
///
|
|
/// Can optionally also be called during dispatch, if needed.
|
|
///
|
|
/// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's
|
|
/// code, so that we do less and less storage reads here.
|
|
pub fn unsigned_pre_dispatch_checks(
|
|
solution: &RawSolution<CompactOf<T>>,
|
|
) -> DispatchResult {
|
|
// ensure solution is timely. Don't panic yet. This is a cheap check.
|
|
ensure!(Self::current_phase().is_unsigned_open(), Error::<T>::PreDispatchEarlySubmission);
|
|
|
|
// ensure round is current
|
|
ensure!(Self::round() == solution.round, Error::<T>::OcwCallWrongEra);
|
|
|
|
// ensure correct number of winners.
|
|
ensure!(
|
|
Self::desired_targets().unwrap_or_default()
|
|
== solution.compact.unique_targets().len() as u32,
|
|
Error::<T>::PreDispatchWrongWinnerCount,
|
|
);
|
|
|
|
// ensure score is being improved. Panic henceforth.
|
|
ensure!(
|
|
Self::queued_solution().map_or(true, |q: ReadySolution<_>| is_score_better::<Perbill>(
|
|
solution.score,
|
|
q.score,
|
|
T::SolutionImprovementThreshold::get()
|
|
)),
|
|
Error::<T>::PreDispatchWeakSubmission,
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod max_weight {
|
|
#![allow(unused_variables)]
|
|
use super::*;
|
|
use crate::mock::MultiPhase;
|
|
|
|
struct TestWeight;
|
|
impl crate::weights::WeightInfo for TestWeight {
|
|
fn on_initialize_nothing() -> Weight {
|
|
unreachable!()
|
|
}
|
|
fn on_initialize_open_signed() -> Weight {
|
|
unreachable!()
|
|
}
|
|
fn on_initialize_open_unsigned_with_snapshot() -> Weight {
|
|
unreachable!()
|
|
}
|
|
fn elect_queued() -> Weight {
|
|
0
|
|
}
|
|
fn on_initialize_open_unsigned_without_snapshot() -> Weight {
|
|
unreachable!()
|
|
}
|
|
fn finalize_signed_phase_accept_solution() -> Weight {
|
|
unreachable!()
|
|
}
|
|
fn finalize_signed_phase_reject_solution() -> Weight {
|
|
unreachable!()
|
|
}
|
|
fn submit(c: u32) -> Weight {
|
|
unreachable!()
|
|
}
|
|
fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight {
|
|
(0 * v + 0 * t + 1000 * a + 0 * d) as Weight
|
|
}
|
|
fn feasibility_check(v: u32, _t: u32, a: u32, d: u32) -> Weight {
|
|
unreachable!()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn find_max_voter_binary_search_works() {
|
|
let w = SolutionOrSnapshotSize { voters: 10, targets: 0 };
|
|
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1990), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 2);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 2);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 2);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2990), 2);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2999), 2);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3000), 3);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 3);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 5500), 5);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 7777), 7);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 9999), 9);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 10_000), 10);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 10_999), 10);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 11_000), 10);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 22_000), 10);
|
|
|
|
let w = SolutionOrSnapshotSize { voters: 1, targets: 0 };
|
|
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1990), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 1);
|
|
|
|
let w = SolutionOrSnapshotSize { voters: 2, targets: 0 };
|
|
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 2);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 2);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 2);
|
|
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 2);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{
|
|
CurrentPhase, InvalidTransaction, Phase, QueuedSolution, TransactionSource,
|
|
TransactionValidityError,
|
|
mock::{
|
|
Call as OuterCall, ExtBuilder, Extrinsic, MinerMaxWeight, MultiPhase, Origin, Runtime,
|
|
TestCompact, TrimHelpers, roll_to, roll_to_with_ocw, trim_helpers, witness,
|
|
UnsignedPhase, BlockNumber, System,
|
|
},
|
|
};
|
|
use frame_benchmarking::Zero;
|
|
use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::OffchainWorker};
|
|
use sp_npos_elections::IndexAssignment;
|
|
use sp_runtime::offchain::storage_lock::{StorageLock, BlockAndTime};
|
|
use sp_runtime::{traits::ValidateUnsigned, PerU16};
|
|
|
|
type Assignment = crate::unsigned::Assignment<Runtime>;
|
|
|
|
#[test]
|
|
fn validate_unsigned_retracts_wrong_phase() {
|
|
ExtBuilder::default().desired_targets(0).build_and_execute(|| {
|
|
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
|
let call = Call::submit_unsigned(solution.clone(), witness());
|
|
|
|
// initial
|
|
assert_eq!(MultiPhase::current_phase(), Phase::Off);
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::validate_unsigned(TransactionSource::Local, &call)
|
|
.unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
|
));
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
|
));
|
|
|
|
// signed
|
|
roll_to(15);
|
|
assert_eq!(MultiPhase::current_phase(), Phase::Signed);
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::validate_unsigned(TransactionSource::Local, &call)
|
|
.unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
|
));
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
|
));
|
|
|
|
// unsigned
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
assert!(<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
|
TransactionSource::Local,
|
|
&call
|
|
)
|
|
.is_ok());
|
|
assert!(<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).is_ok());
|
|
|
|
// unsigned -- but not enabled.
|
|
<CurrentPhase<Runtime>>::put(Phase::Unsigned((false, 25)));
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::validate_unsigned(TransactionSource::Local, &call)
|
|
.unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
|
));
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(0))
|
|
));
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn validate_unsigned_retracts_low_score() {
|
|
ExtBuilder::default().desired_targets(0).build_and_execute(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
|
let call = Call::submit_unsigned(solution.clone(), witness());
|
|
|
|
// initial
|
|
assert!(<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
|
TransactionSource::Local,
|
|
&call
|
|
)
|
|
.is_ok());
|
|
assert!(<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).is_ok());
|
|
|
|
// set a better score
|
|
let ready = ReadySolution { score: [10, 0, 0], ..Default::default() };
|
|
<QueuedSolution<Runtime>>::put(ready);
|
|
|
|
// won't work anymore.
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
|
TransactionSource::Local,
|
|
&call
|
|
)
|
|
.unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(2))
|
|
));
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(2))
|
|
));
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn validate_unsigned_retracts_incorrect_winner_count() {
|
|
ExtBuilder::default().desired_targets(1).build_and_execute(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
|
let call = Call::submit_unsigned(solution.clone(), witness());
|
|
assert_eq!(solution.compact.unique_targets().len(), 0);
|
|
|
|
// won't work anymore.
|
|
assert!(matches!(
|
|
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
|
TransactionSource::Local,
|
|
&call
|
|
)
|
|
.unwrap_err(),
|
|
TransactionValidityError::Invalid(InvalidTransaction::Custom(1))
|
|
));
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn priority_is_set() {
|
|
ExtBuilder::default().miner_tx_priority(20).desired_targets(0).build_and_execute(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
|
let call = Call::submit_unsigned(solution.clone(), witness());
|
|
|
|
assert_eq!(
|
|
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
|
TransactionSource::Local,
|
|
&call
|
|
)
|
|
.unwrap()
|
|
.priority,
|
|
25
|
|
);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "Invalid unsigned submission must produce invalid block and \
|
|
deprive validator from their authoring reward.: \
|
|
Module { index: 2, error: 1, message: \
|
|
Some(\"PreDispatchWrongWinnerCount\") }")]
|
|
fn unfeasible_solution_panics() {
|
|
ExtBuilder::default().build_and_execute(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
// This is in itself an invalid BS solution.
|
|
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
|
let call = Call::submit_unsigned(solution.clone(), witness());
|
|
let outer_call: OuterCall = call.into();
|
|
let _ = outer_call.dispatch(Origin::none());
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "Invalid unsigned submission must produce invalid block and \
|
|
deprive validator from their authoring reward.")]
|
|
fn wrong_witness_panics() {
|
|
ExtBuilder::default().build_and_execute(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
// This solution is unfeasible as well, but we won't even get there.
|
|
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
|
|
|
let mut correct_witness = witness();
|
|
correct_witness.voters += 1;
|
|
correct_witness.targets -= 1;
|
|
let call = Call::submit_unsigned(solution.clone(), correct_witness);
|
|
let outer_call: OuterCall = call.into();
|
|
let _ = outer_call.dispatch(Origin::none());
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn miner_works() {
|
|
ExtBuilder::default().build_and_execute(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
// ensure we have snapshots in place.
|
|
assert!(MultiPhase::snapshot().is_some());
|
|
assert_eq!(MultiPhase::desired_targets().unwrap(), 2);
|
|
|
|
// mine seq_phragmen solution with 2 iters.
|
|
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
|
|
|
// ensure this solution is valid.
|
|
assert!(MultiPhase::queued_solution().is_none());
|
|
assert_ok!(MultiPhase::submit_unsigned(Origin::none(), solution, witness));
|
|
assert!(MultiPhase::queued_solution().is_some());
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn miner_trims_weight() {
|
|
ExtBuilder::default().miner_weight(100).mock_weight_info(true).build_and_execute(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
|
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
|
witness.voters,
|
|
witness.targets,
|
|
solution.compact.voter_count() as u32,
|
|
solution.compact.unique_targets().len() as u32,
|
|
);
|
|
// default solution will have 5 edges (5 * 5 + 10)
|
|
assert_eq!(solution_weight, 35);
|
|
assert_eq!(solution.compact.voter_count(), 5);
|
|
|
|
// now reduce the max weight
|
|
<MinerMaxWeight>::set(25);
|
|
|
|
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
|
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
|
witness.voters,
|
|
witness.targets,
|
|
solution.compact.voter_count() as u32,
|
|
solution.compact.unique_targets().len() as u32,
|
|
);
|
|
// default solution will have 5 edges (5 * 5 + 10)
|
|
assert_eq!(solution_weight, 25);
|
|
assert_eq!(solution.compact.voter_count(), 3);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn miner_will_not_submit_if_not_enough_winners() {
|
|
let (mut ext, _) = ExtBuilder::default().desired_targets(8).build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
assert_eq!(
|
|
MultiPhase::mine_check_save_submit().unwrap_err(),
|
|
MinerError::PreDispatchChecksFailed(DispatchError::Module{
|
|
index: 2,
|
|
error: 1,
|
|
message: Some("PreDispatchWrongWinnerCount"),
|
|
}),
|
|
);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn unsigned_per_dispatch_checks_can_only_submit_threshold_better() {
|
|
ExtBuilder::default()
|
|
.desired_targets(1)
|
|
.add_voter(7, 2, vec![10])
|
|
.add_voter(8, 5, vec![10])
|
|
.solution_improvement_threshold(Perbill::from_percent(50))
|
|
.build_and_execute(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
assert_eq!(MultiPhase::desired_targets().unwrap(), 1);
|
|
|
|
// an initial solution
|
|
let result = ElectionResult {
|
|
// note: This second element of backing stake is not important here.
|
|
winners: vec![(10, 10)],
|
|
assignments: vec![Assignment {
|
|
who: 10,
|
|
distribution: vec![(10, PerU16::one())],
|
|
}],
|
|
};
|
|
let (solution, witness) = MultiPhase::prepare_election_result(result).unwrap();
|
|
assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution));
|
|
assert_ok!(MultiPhase::submit_unsigned(Origin::none(), solution, witness));
|
|
assert_eq!(MultiPhase::queued_solution().unwrap().score[0], 10);
|
|
|
|
// trial 1: a solution who's score is only 2, i.e. 20% better in the first element.
|
|
let result = ElectionResult {
|
|
winners: vec![(10, 12)],
|
|
assignments: vec![
|
|
Assignment { who: 10, distribution: vec![(10, PerU16::one())] },
|
|
Assignment {
|
|
who: 7,
|
|
// note: this percent doesn't even matter, in compact it is 100%.
|
|
distribution: vec![(10, PerU16::one())],
|
|
},
|
|
],
|
|
};
|
|
let (solution, _) = MultiPhase::prepare_election_result(result).unwrap();
|
|
// 12 is not 50% more than 10
|
|
assert_eq!(solution.score[0], 12);
|
|
assert_noop!(
|
|
MultiPhase::unsigned_pre_dispatch_checks(&solution),
|
|
Error::<Runtime>::PreDispatchWeakSubmission,
|
|
);
|
|
// submitting this will actually panic.
|
|
|
|
// trial 2: a solution who's score is only 7, i.e. 70% better in the first element.
|
|
let result = ElectionResult {
|
|
winners: vec![(10, 12)],
|
|
assignments: vec![
|
|
Assignment { who: 10, distribution: vec![(10, PerU16::one())] },
|
|
Assignment { who: 7, distribution: vec![(10, PerU16::one())] },
|
|
Assignment {
|
|
who: 8,
|
|
// note: this percent doesn't even matter, in compact it is 100%.
|
|
distribution: vec![(10, PerU16::one())],
|
|
},
|
|
],
|
|
};
|
|
let (solution, witness) = MultiPhase::prepare_election_result(result).unwrap();
|
|
assert_eq!(solution.score[0], 17);
|
|
|
|
// and it is fine
|
|
assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution));
|
|
assert_ok!(MultiPhase::submit_unsigned(Origin::none(), solution, witness));
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_lock_prevents_frequent_execution() {
|
|
let (mut ext, _) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
let offchain_repeat = <Runtime as Config>::OffchainRepeat::get();
|
|
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
// first execution -- okay.
|
|
assert!(MultiPhase::ensure_offchain_repeat_frequency(25).is_ok());
|
|
|
|
// next block: rejected.
|
|
assert_noop!(
|
|
MultiPhase::ensure_offchain_repeat_frequency(26),
|
|
MinerError::Lock("recently executed.")
|
|
);
|
|
|
|
// allowed after `OFFCHAIN_REPEAT`
|
|
assert!(
|
|
MultiPhase::ensure_offchain_repeat_frequency((26 + offchain_repeat).into()).is_ok()
|
|
);
|
|
|
|
// a fork like situation: re-execute last 3.
|
|
assert!(MultiPhase::ensure_offchain_repeat_frequency(
|
|
(26 + offchain_repeat - 3).into()
|
|
)
|
|
.is_err());
|
|
assert!(MultiPhase::ensure_offchain_repeat_frequency(
|
|
(26 + offchain_repeat - 2).into()
|
|
)
|
|
.is_err());
|
|
assert!(MultiPhase::ensure_offchain_repeat_frequency(
|
|
(26 + offchain_repeat - 1).into()
|
|
)
|
|
.is_err());
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_lock_released_after_successful_execution() {
|
|
// first, ensure that a successful execution releases the lock
|
|
let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
let guard = StorageValueRef::persistent(&OFFCHAIN_LOCK);
|
|
let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK);
|
|
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
// initially, the lock is not set.
|
|
assert!(guard.get::<bool>().unwrap().is_none());
|
|
|
|
// a successful a-z execution.
|
|
MultiPhase::offchain_worker(25);
|
|
assert_eq!(pool.read().transactions.len(), 1);
|
|
|
|
// afterwards, the lock is not set either..
|
|
assert!(guard.get::<bool>().unwrap().is_none());
|
|
assert_eq!(last_block.get::<BlockNumber>().unwrap(), Some(25));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_lock_prevents_overlapping_execution() {
|
|
// ensure that if the guard is in hold, a new execution is not allowed.
|
|
let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
roll_to(25);
|
|
assert!(MultiPhase::current_phase().is_unsigned());
|
|
|
|
// artificially set the value, as if another thread is mid-way.
|
|
let mut lock = StorageLock::<BlockAndTime<System>>::with_block_deadline(
|
|
OFFCHAIN_LOCK,
|
|
UnsignedPhase::get().saturated_into(),
|
|
);
|
|
let guard = lock.lock();
|
|
|
|
// nothing submitted.
|
|
MultiPhase::offchain_worker(25);
|
|
assert_eq!(pool.read().transactions.len(), 0);
|
|
MultiPhase::offchain_worker(26);
|
|
assert_eq!(pool.read().transactions.len(), 0);
|
|
|
|
drop(guard);
|
|
|
|
// 🎉 !
|
|
MultiPhase::offchain_worker(25);
|
|
assert_eq!(pool.read().transactions.len(), 1);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_only_runs_when_unsigned_open_now() {
|
|
let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
roll_to(25);
|
|
assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25)));
|
|
|
|
// we must clear the offchain storage to ensure the offchain execution check doesn't get
|
|
// in the way.
|
|
let mut storage = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
|
|
|
|
MultiPhase::offchain_worker(24);
|
|
assert!(pool.read().transactions.len().is_zero());
|
|
storage.clear();
|
|
|
|
// creates, caches, submits without expecting previous cache value
|
|
MultiPhase::offchain_worker(25);
|
|
assert_eq!(pool.read().transactions.len(), 1);
|
|
// assume that the tx has been processed
|
|
pool.try_write().unwrap().transactions.clear();
|
|
|
|
// locked, but also, has previously cached.
|
|
MultiPhase::offchain_worker(26);
|
|
assert!(pool.read().transactions.len().is_zero());
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_clears_cache_after_election() {
|
|
let (mut ext, _pool) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
roll_to(25);
|
|
assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25)));
|
|
|
|
// we must clear the offchain storage to ensure the offchain execution check doesn't get
|
|
// in the way.
|
|
let mut storage = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
|
|
storage.clear();
|
|
|
|
assert!(
|
|
!ocw_solution_exists::<Runtime>(),
|
|
"no solution should be present before we mine one",
|
|
);
|
|
|
|
// creates and cache a solution
|
|
MultiPhase::offchain_worker(25);
|
|
assert!(
|
|
ocw_solution_exists::<Runtime>(),
|
|
"a solution must be cached after running the worker",
|
|
);
|
|
|
|
// after an election, the solution must be cleared
|
|
// we don't actually care about the result of the election
|
|
roll_to(26);
|
|
let _ = MultiPhase::do_elect();
|
|
MultiPhase::offchain_worker(26);
|
|
assert!(!ocw_solution_exists::<Runtime>(), "elections must clear the ocw cache");
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_resubmits_after_offchain_repeat() {
|
|
let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
const BLOCK: u64 = 25;
|
|
let block_plus = |delta: i32| ((BLOCK as i32) + delta) as u64;
|
|
let offchain_repeat = <Runtime as Config>::OffchainRepeat::get();
|
|
|
|
roll_to(BLOCK);
|
|
assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, BLOCK)));
|
|
|
|
// we must clear the offchain storage to ensure the offchain execution check doesn't get
|
|
// in the way.
|
|
let mut storage = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
|
|
|
|
MultiPhase::offchain_worker(block_plus(-1));
|
|
assert!(pool.read().transactions.len().is_zero());
|
|
storage.clear();
|
|
|
|
// creates, caches, submits without expecting previous cache value
|
|
MultiPhase::offchain_worker(BLOCK);
|
|
assert_eq!(pool.read().transactions.len(), 1);
|
|
let tx_cache = pool.read().transactions[0].clone();
|
|
// assume that the tx has been processed
|
|
pool.try_write().unwrap().transactions.clear();
|
|
|
|
// attempts to resubmit the tx after the threshold has expired
|
|
// note that we have to add 1: the semantics forbid resubmission at
|
|
// BLOCK + offchain_repeat
|
|
MultiPhase::offchain_worker(block_plus(1 + offchain_repeat as i32));
|
|
assert_eq!(pool.read().transactions.len(), 1);
|
|
|
|
// resubmitted tx is identical to first submission
|
|
let tx = &pool.read().transactions[0];
|
|
assert_eq!(&tx_cache, tx);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_regenerates_and_resubmits_after_offchain_repeat() {
|
|
let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
const BLOCK: u64 = 25;
|
|
let block_plus = |delta: i32| ((BLOCK as i32) + delta) as u64;
|
|
let offchain_repeat = <Runtime as Config>::OffchainRepeat::get();
|
|
|
|
roll_to(BLOCK);
|
|
assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, BLOCK)));
|
|
|
|
// we must clear the offchain storage to ensure the offchain execution check doesn't get
|
|
// in the way.
|
|
let mut storage = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
|
|
|
|
MultiPhase::offchain_worker(block_plus(-1));
|
|
assert!(pool.read().transactions.len().is_zero());
|
|
storage.clear();
|
|
|
|
// creates, caches, submits without expecting previous cache value
|
|
MultiPhase::offchain_worker(BLOCK);
|
|
assert_eq!(pool.read().transactions.len(), 1);
|
|
let tx_cache = pool.read().transactions[0].clone();
|
|
// assume that the tx has been processed
|
|
pool.try_write().unwrap().transactions.clear();
|
|
|
|
// remove the cached submitted tx
|
|
// this ensures that when the resubmit window rolls around, we're ready to regenerate
|
|
// from scratch if necessary
|
|
let mut call_cache = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL);
|
|
assert!(matches!(call_cache.get::<Call<Runtime>>(), Ok(Some(_call))));
|
|
call_cache.clear();
|
|
|
|
// attempts to resubmit the tx after the threshold has expired
|
|
// note that we have to add 1: the semantics forbid resubmission at
|
|
// BLOCK + offchain_repeat
|
|
MultiPhase::offchain_worker(block_plus(1 + offchain_repeat as i32));
|
|
assert_eq!(pool.read().transactions.len(), 1);
|
|
|
|
// resubmitted tx is identical to first submission
|
|
let tx = &pool.read().transactions[0];
|
|
assert_eq!(&tx_cache, tx);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_can_submit_to_pool() {
|
|
let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
roll_to_with_ocw(25);
|
|
assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25)));
|
|
// OCW must have submitted now
|
|
|
|
let encoded = pool.read().transactions[0].clone();
|
|
let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap();
|
|
let call = extrinsic.call;
|
|
assert!(matches!(call, OuterCall::MultiPhase(Call::submit_unsigned(..))));
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn ocw_solution_must_have_correct_round() {
|
|
let (mut ext, pool) = ExtBuilder::default().build_offchainify(0);
|
|
ext.execute_with(|| {
|
|
roll_to_with_ocw(25);
|
|
assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25)));
|
|
// OCW must have submitted now
|
|
// now, before we check the call, update the round
|
|
<crate::Round<Runtime>>::mutate(|round| *round += 1);
|
|
|
|
let encoded = pool.read().transactions[0].clone();
|
|
let extrinsic = Extrinsic::decode(&mut &*encoded).unwrap();
|
|
let call = match extrinsic.call {
|
|
OuterCall::MultiPhase(call @ Call::submit_unsigned(..)) => call,
|
|
_ => panic!("bad call: unexpected submission"),
|
|
};
|
|
|
|
// Custom(7) maps to PreDispatchChecksFailed
|
|
let pre_dispatch_check_error = TransactionValidityError::Invalid(
|
|
InvalidTransaction::Custom(7),
|
|
);
|
|
assert_eq!(
|
|
<MultiPhase as ValidateUnsigned>::validate_unsigned(
|
|
TransactionSource::Local,
|
|
&call,
|
|
)
|
|
.unwrap_err(),
|
|
pre_dispatch_check_error,
|
|
);
|
|
assert_eq!(
|
|
<MultiPhase as ValidateUnsigned>::pre_dispatch(&call).unwrap_err(),
|
|
pre_dispatch_check_error,
|
|
);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn trim_assignments_length_does_not_modify_when_short_enough() {
|
|
ExtBuilder::default().build_and_execute(|| {
|
|
roll_to(25);
|
|
|
|
// given
|
|
let TrimHelpers {
|
|
mut assignments,
|
|
encoded_size_of,
|
|
..
|
|
} = trim_helpers();
|
|
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
|
let encoded_len = compact.encoded_size() as u32;
|
|
let compact_clone = compact.clone();
|
|
|
|
// when
|
|
MultiPhase::trim_assignments_length(
|
|
encoded_len,
|
|
&mut assignments,
|
|
encoded_size_of,
|
|
).unwrap();
|
|
|
|
// then
|
|
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
|
assert_eq!(compact, compact_clone);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn trim_assignments_length_modifies_when_too_long() {
|
|
ExtBuilder::default().build().execute_with(|| {
|
|
roll_to(25);
|
|
|
|
// given
|
|
let TrimHelpers {
|
|
mut assignments,
|
|
encoded_size_of,
|
|
..
|
|
} = trim_helpers();
|
|
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
|
let encoded_len = compact.encoded_size();
|
|
let compact_clone = compact.clone();
|
|
|
|
// when
|
|
MultiPhase::trim_assignments_length(
|
|
encoded_len as u32 - 1,
|
|
&mut assignments,
|
|
encoded_size_of,
|
|
).unwrap();
|
|
|
|
// then
|
|
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
|
assert_ne!(compact, compact_clone);
|
|
assert!(compact.encoded_size() < encoded_len);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn trim_assignments_length_trims_lowest_stake() {
|
|
ExtBuilder::default().build().execute_with(|| {
|
|
roll_to(25);
|
|
|
|
// given
|
|
let TrimHelpers {
|
|
voters,
|
|
mut assignments,
|
|
encoded_size_of,
|
|
voter_index,
|
|
} = trim_helpers();
|
|
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
|
let encoded_len = compact.encoded_size() as u32;
|
|
let count = assignments.len();
|
|
let min_stake_voter = voters.iter()
|
|
.map(|(id, weight, _)| (weight, id))
|
|
.min()
|
|
.and_then(|(_, id)| voter_index(id))
|
|
.unwrap();
|
|
|
|
// when
|
|
MultiPhase::trim_assignments_length(
|
|
encoded_len - 1,
|
|
&mut assignments,
|
|
encoded_size_of,
|
|
).unwrap();
|
|
|
|
// then
|
|
assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment");
|
|
assert!(
|
|
assignments.iter()
|
|
.all(|IndexAssignment{ who, ..}| *who != min_stake_voter),
|
|
"min_stake_voter must no longer be in the set of voters",
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn trim_assignments_length_wont_panic() {
|
|
// we shan't panic if assignments are initially empty.
|
|
ExtBuilder::default().build_and_execute(|| {
|
|
let encoded_size_of = Box::new(|assignments: &[IndexAssignmentOf<Runtime>]| {
|
|
CompactOf::<Runtime>::try_from(assignments).map(|compact| compact.encoded_size())
|
|
});
|
|
|
|
let mut assignments = vec![];
|
|
|
|
// since we have 16 fields, we need to store the length fields of 16 vecs, thus 16 bytes
|
|
// minimum.
|
|
let min_compact_size = encoded_size_of(&assignments).unwrap();
|
|
assert_eq!(min_compact_size, CompactOf::<Runtime>::LIMIT);
|
|
|
|
// all of this should not panic.
|
|
MultiPhase::trim_assignments_length(0, &mut assignments, encoded_size_of.clone())
|
|
.unwrap();
|
|
MultiPhase::trim_assignments_length(1, &mut assignments, encoded_size_of.clone())
|
|
.unwrap();
|
|
MultiPhase::trim_assignments_length(
|
|
min_compact_size as u32,
|
|
&mut assignments,
|
|
encoded_size_of,
|
|
)
|
|
.unwrap();
|
|
});
|
|
|
|
// or when we trim it to zero.
|
|
ExtBuilder::default().build_and_execute(|| {
|
|
// we need snapshot for `trim_helpers` to work.
|
|
roll_to(25);
|
|
let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
|
|
assert!(assignments.len() > 0);
|
|
|
|
// trim to min compact size.
|
|
let min_compact_size = CompactOf::<Runtime>::LIMIT as u32;
|
|
MultiPhase::trim_assignments_length(
|
|
min_compact_size,
|
|
&mut assignments,
|
|
encoded_size_of,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(assignments.len(), 0);
|
|
});
|
|
}
|
|
|
|
// all the other solution-generation functions end up delegating to `mine_solution`, so if we
|
|
// demonstrate that `mine_solution` solutions are all trimmed to an acceptable length, then
|
|
// we know that higher-level functions will all also have short-enough solutions.
|
|
#[test]
|
|
fn mine_solution_solutions_always_within_acceptable_length() {
|
|
ExtBuilder::default().build_and_execute(|| {
|
|
roll_to(25);
|
|
|
|
// how long would the default solution be?
|
|
let solution = MultiPhase::mine_solution(0).unwrap();
|
|
let max_length = <Runtime as Config>::MinerMaxLength::get();
|
|
let solution_size = solution.0.compact.encoded_size();
|
|
assert!(solution_size <= max_length as usize);
|
|
|
|
// now set the max size to less than the actual size and regenerate
|
|
<Runtime as Config>::MinerMaxLength::set(solution_size as u32 - 1);
|
|
let solution = MultiPhase::mine_solution(0).unwrap();
|
|
let max_length = <Runtime as Config>::MinerMaxLength::get();
|
|
let solution_size = solution.0.compact.encoded_size();
|
|
assert!(solution_size <= max_length as usize);
|
|
});
|
|
}
|
|
}
|