fix: Complete snowbridge pezpallet rebrand and critical bug fixes
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers
|
||||
pledge to making participation in our project and our community a harassment-free experience for
|
||||
everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity
|
||||
and expression, level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit
|
||||
permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
### Facilitation, Not Strongarming
|
||||
|
||||
We recognise that this software is merely a tool for users to create and maintain their blockchain
|
||||
of preference. We see that blockchains are naturally community platforms with users being the
|
||||
ultimate decision makers. We assert that good software will maximise user agency by facilitate
|
||||
user-expression on the network. As such:
|
||||
|
||||
* This project will strive to give users as much choice as is both reasonable and possible over what
|
||||
protocol they adhere to; but
|
||||
* use of the project's technical forums, commenting systems, pull requests and issue trackers as a
|
||||
means to express individual protocol preferences is forbidden.
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are
|
||||
expected to take appropriate and fair corrective action in response to any instances of unacceptable
|
||||
behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits,
|
||||
code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or
|
||||
to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is
|
||||
representing the project or its community. Examples of representing a project or community include
|
||||
using an official project e-mail address, posting via an official social media account, or acting as
|
||||
an appointed representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting
|
||||
the project team at admin@parity.io. All complaints will be reviewed and investigated and will
|
||||
result in a response that is deemed necessary and appropriate to the circumstances. The project team
|
||||
is obligated to maintain confidentiality with regard to the reporter of an incident. Further
|
||||
details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face
|
||||
temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at
|
||||
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -0,0 +1,675 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# Parity Bridges Common
|
||||
|
||||
This is a collection of components for building bridges.
|
||||
|
||||
These components include Bizinikiwi pallets for syncing headers, passing arbitrary messages, as well as libraries for
|
||||
building relayers to provide cross-chain communication capabilities.
|
||||
|
||||
Three bridge nodes are also available. The nodes can be used to run test networks which bridge other Bizinikiwi chains.
|
||||
|
||||
🚧 The bridges are currently under construction - a hardhat is recommended beyond this point 🚧
|
||||
|
||||
## Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [High-Level Architecture](#high-level-architecture)
|
||||
- [Project Layout](#project-layout)
|
||||
- [Running the Bridge](#running-the-bridge)
|
||||
- [How to send a message](#how-to-send-a-message)
|
||||
- [Community](#community)
|
||||
|
||||
## Installation
|
||||
|
||||
To get up and running you need both stable and nightly Rust. Rust nightly is used to build the Web Assembly (WASM)
|
||||
runtime for the node. You can configure the WASM support as so:
|
||||
|
||||
```bash
|
||||
rustup install nightly
|
||||
rustup target add wasm32-unknown-unknown --toolchain nightly
|
||||
```
|
||||
|
||||
Once this is configured you can build and test the repo as follows:
|
||||
|
||||
```
|
||||
git clone https://github.com/pezkuwichain/kurdistan-sdk/tree/main/bridges
|
||||
cd parity-bridges-common
|
||||
cargo build --all
|
||||
cargo test --all
|
||||
```
|
||||
|
||||
Also you can build the repo with [Parity CI Docker
|
||||
image](https://github.com/pezkuwichain/scripts/tree/master/dockerfiles/ci-unified):
|
||||
|
||||
```bash
|
||||
docker pull paritytech/ci-unified:latest
|
||||
mkdir ~/cache
|
||||
chown 1000:1000 ~/cache #processes in the container runs as "nonroot" user with UID 1000
|
||||
docker run --rm -it -w /shellhere/parity-bridges-common \
|
||||
-v /home/$(whoami)/cache/:/cache/ \
|
||||
-v "$(pwd)":/shellhere/parity-bridges-common \
|
||||
-e CARGO_HOME=/cache/cargo/ \
|
||||
-e SCCACHE_DIR=/cache/sccache/ \
|
||||
-e CARGO_TARGET_DIR=/cache/target/ paritytech/ci-unified:latest cargo build --all
|
||||
#artifacts can be found in ~/cache/target
|
||||
```
|
||||
|
||||
If you want to reproduce other steps of CI process you can use the following
|
||||
[guide](https://github.com/pezkuwichain/scripts#reproduce-ci-locally).
|
||||
|
||||
If you need more information about setting up your development environment [Bizinikiwi's Installation
|
||||
page](https://docs.pezkuwichain.io/main-docs/install/) is a good resource.
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
This repo has support for bridging foreign chains together using a combination of Bizinikiwi pallets and external
|
||||
processes called relayers. A bridge chain is one that is able to follow the consensus of a foreign chain independently.
|
||||
For example, consider the case below where we want to bridge two Bizinikiwi based chains.
|
||||
|
||||
```
|
||||
+---------------+ +---------------+
|
||||
| | | |
|
||||
| pezkuwichain | | zagros |
|
||||
| | | |
|
||||
+-------+-------+ +-------+-------+
|
||||
^ ^
|
||||
| +---------------+ |
|
||||
| | | |
|
||||
+-----> | Bridge Relay | <-------+
|
||||
| |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
The pezkuwichain chain must be able to accept zagros headers and verify their integrity. It does this by using a runtime
|
||||
module designed to track GRANDPA finality. Since two blockchains can't interact directly they need an external service,
|
||||
called a relayer, to communicate. The relayer will subscribe to new pezkuwichain headers via RPC and submit them to the zagros
|
||||
chain for verification.
|
||||
|
||||
Take a look at [Bridge High Level Documentation](./docs/high-level-overview.md) for more in-depth description of the
|
||||
bridge interaction.
|
||||
|
||||
## Project Layout
|
||||
|
||||
Here's an overview of how the project is laid out. The main bits are the `bin`, which is the actual "blockchain", the
|
||||
`modules` which are used to build the blockchain's logic (a.k.a the runtime) and the `relays` which are used to pass
|
||||
messages between chains.
|
||||
|
||||
```
|
||||
├── modules // Bizinikiwi Runtime Modules (a.k.a Pallets)
|
||||
│ ├── beefy // On-Chain BEEFY Light Client (in progress)
|
||||
│ ├── grandpa // On-Chain GRANDPA Light Client
|
||||
│ ├── messages // Cross Chain Message Passing
|
||||
│ ├── teyrchains // On-Chain Teyrchains Light Client
|
||||
│ ├── relayers // Relayer Rewards Registry
|
||||
│ ├── xcm-bridge-hub // Multiple Dynamic Bridges Support
|
||||
│ ├── xcm-bridge-hub-router // XCM Router that may be used to Connect to XCM Bridge Hub
|
||||
├── primitives // Code shared between modules, runtimes, and relays
|
||||
│ └── ...
|
||||
├── relays // Application for sending finality proofs and messages between chains
|
||||
│ └── ...
|
||||
└── scripts // Useful development and maintenance scripts
|
||||
```
|
||||
|
||||
## Running the Bridge
|
||||
|
||||
Apart from live pezkuwichain <> zagros bridge, you may spin up local networks and test see how it works locally. More
|
||||
details may be found in
|
||||
[this document](https://github.com/pezkuwichain/pezkuwi-sdk/tree/master//pezcumulus/parachains/runtimes/bridge-hubs/README.md).
|
||||
@@ -0,0 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
Thanks for helping make the Parity ecosystem more secure. Security is one of our first priorities.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
If you find something that can be treated as a security vulnerability, please do not use the issue tracker or discuss it
|
||||
in the public forum as it can cause more damage, rather than giving real help to the ecosystem.
|
||||
|
||||
Security vulnerabilities should be reported by the [contact form](https://security-submission.parity.io/).
|
||||
|
||||
If you think that your report might be eligible for the Bug Bounty Program, please mark this during the submission.
|
||||
Please check up-to-date [Parity Bug Bounty Program rules](https://www.parity.io/bug-bounty) to find out the information
|
||||
about our Bug Bounty Program.
|
||||
|
||||
**Warning**: This is a unified SECURITY.md file for Paritytech GitHub Organization. The presence of this file does not
|
||||
mean that this repository is covered by the Bug Bounty program. Please always check the Bug Bounty Program scope for
|
||||
information.
|
||||
@@ -0,0 +1,112 @@
|
||||
[package]
|
||||
name = "pezbridge-runtime-common"
|
||||
version = "0.7.0"
|
||||
description = "Common types and functions that may be used by bizinikiwi-based runtimes of all bridged chains"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
documentation = "https://docs.rs/pezbridge-runtime-common"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
static_assertions = { optional = true, workspace = true, default-features = true }
|
||||
tracing = { workspace = true }
|
||||
tuplex = { workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-messages = { workspace = true }
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
bp-relayers = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
bp-teyrchains = { workspace = true }
|
||||
pezpallet-bridge-grandpa = { workspace = true }
|
||||
pezpallet-bridge-messages = { workspace = true }
|
||||
pezpallet-bridge-relayers = { workspace = true }
|
||||
pezpallet-bridge-teyrchains = { workspace = true }
|
||||
|
||||
# Bizinikiwi dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-transaction-payment = { workspace = true }
|
||||
pezpallet-utility = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
pezsp-trie = { optional = true, workspace = true }
|
||||
pezsp-weights = { workspace = true }
|
||||
|
||||
# Pezkuwi dependencies
|
||||
xcm = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-test-utils = { workspace = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-bridge-messages = { features = [
|
||||
"std",
|
||||
"test-helpers",
|
||||
], workspace = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"bp-relayers/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-test-utils/std",
|
||||
"bp-teyrchains/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-bridge-grandpa/std",
|
||||
"pezpallet-bridge-messages/std",
|
||||
"pezpallet-bridge-relayers/std",
|
||||
"pezpallet-bridge-teyrchains/std",
|
||||
"pezpallet-transaction-payment/std",
|
||||
"pezpallet-utility/std",
|
||||
"scale-info/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"pezsp-trie/std",
|
||||
"pezsp-weights/std",
|
||||
"tracing/std",
|
||||
"tuplex/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"bp-relayers/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezbp-runtime/test-helpers",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"bp-teyrchains/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-bridge-grandpa/runtime-benchmarks",
|
||||
"pezpallet-bridge-messages/runtime-benchmarks",
|
||||
"pezpallet-bridge-messages/test-helpers",
|
||||
"pezpallet-bridge-relayers/runtime-benchmarks",
|
||||
"pezpallet-bridge-teyrchains/runtime-benchmarks",
|
||||
"pezpallet-transaction-payment/runtime-benchmarks",
|
||||
"pezpallet-utility/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-trie",
|
||||
"pezsp-trie?/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
integrity-test = ["static_assertions"]
|
||||
test-helpers = ["pezbp-runtime/test-helpers", "pezsp-trie"]
|
||||
@@ -0,0 +1,849 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Transaction extension that rejects bridge-related transactions, that include
|
||||
//! obsolete (duplicated) data or do not pass some additional pezpallet-specific
|
||||
//! checks.
|
||||
|
||||
use bp_relayers::ExplicitOrAccountParams;
|
||||
use pezbp_runtime::Teyrchain;
|
||||
use bp_teyrchains::SubmitTeyrchainHeadsInfo;
|
||||
use pezpallet_bridge_grandpa::{
|
||||
BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper,
|
||||
};
|
||||
use pezpallet_bridge_messages::CallSubType as MessagesCallSubType;
|
||||
use pezpallet_bridge_relayers::Pezpallet as RelayersPallet;
|
||||
use pezpallet_bridge_teyrchains::{CallSubType as TeyrchainsCallSubtype, SubmitTeyrchainHeadsHelper};
|
||||
use pezsp_runtime::{
|
||||
traits::{Get, UniqueSaturatedInto},
|
||||
transaction_validity::{TransactionPriority, TransactionValidity, ValidTransactionBuilder},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
// Re-export to avoid include tuplex dependency everywhere.
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
pub use tuplex;
|
||||
}
|
||||
|
||||
/// A duplication of the `FilterCall` trait.
|
||||
///
|
||||
/// We need this trait in order to be able to implement it for the messages pezpallet,
|
||||
/// since the implementation is done outside of the pezpallet crate.
|
||||
pub trait BridgeRuntimeFilterCall<AccountId, Call> {
|
||||
/// Data that may be passed from the validate to `post_dispatch`.
|
||||
type ToPostDispatch;
|
||||
/// Called during validation. Needs to checks whether a runtime call, submitted
|
||||
/// by the `who` is valid. Transactions not signed are not validated.
|
||||
fn validate(who: &AccountId, call: &Call) -> (Self::ToPostDispatch, TransactionValidity);
|
||||
/// Called after transaction is dispatched.
|
||||
fn post_dispatch(_who: &AccountId, _has_failed: bool, _to_post_dispatch: Self::ToPostDispatch) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the bridge GRANDPA pezpallet that checks calls for obsolete submissions
|
||||
/// and also boosts transaction priority if it has submitted by registered relayer.
|
||||
/// The boost is computed as
|
||||
/// `(BundledHeaderNumber - 1 - BestFinalizedHeaderNumber) * Priority::get()`.
|
||||
/// The boost is only applied if submitter has active registration in the relayers
|
||||
/// pezpallet.
|
||||
pub struct CheckAndBoostBridgeGrandpaTransactions<T, I, Priority, SlashAccount>(
|
||||
PhantomData<(T, I, Priority, SlashAccount)>,
|
||||
);
|
||||
|
||||
impl<T, I: 'static, Priority: Get<TransactionPriority>, SlashAccount: Get<T::AccountId>>
|
||||
BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
|
||||
for CheckAndBoostBridgeGrandpaTransactions<T, I, Priority, SlashAccount>
|
||||
where
|
||||
T: pezpallet_bridge_relayers::Config + pezpallet_bridge_grandpa::Config<I>,
|
||||
T::RuntimeCall: GrandpaCallSubType<T, I>,
|
||||
{
|
||||
// bridged header number, bundled in transaction
|
||||
type ToPostDispatch = Option<BridgedBlockNumber<T, I>>;
|
||||
|
||||
fn validate(
|
||||
who: &T::AccountId,
|
||||
call: &T::RuntimeCall,
|
||||
) -> (Self::ToPostDispatch, TransactionValidity) {
|
||||
match GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call) {
|
||||
Ok(Some(our_tx)) => {
|
||||
let to_post_dispatch = Some(our_tx.base.block_number);
|
||||
let total_priority_boost =
|
||||
compute_priority_boost::<T, _, Priority>(who, our_tx.improved_by);
|
||||
(
|
||||
to_post_dispatch,
|
||||
ValidTransactionBuilder::default().priority(total_priority_boost).build(),
|
||||
)
|
||||
},
|
||||
Ok(None) => (None, ValidTransactionBuilder::default().build()),
|
||||
Err(e) => (None, Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn post_dispatch(
|
||||
relayer: &T::AccountId,
|
||||
has_failed: bool,
|
||||
bundled_block_number: Self::ToPostDispatch,
|
||||
) {
|
||||
// we are only interested in associated pezpallet submissions
|
||||
let Some(bundled_block_number) = bundled_block_number else { return };
|
||||
// we are only interested in failed or unneeded transactions
|
||||
let has_failed =
|
||||
has_failed || !SubmitFinalityProofHelper::<T, I>::was_successful(bundled_block_number);
|
||||
|
||||
if !has_failed {
|
||||
return;
|
||||
}
|
||||
|
||||
// let's slash registered relayer
|
||||
RelayersPallet::<T>::slash_and_deregister(
|
||||
relayer,
|
||||
ExplicitOrAccountParams::Explicit::<_, ()>(SlashAccount::get()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the bridge teyrchains pezpallet that checks calls for obsolete submissions
|
||||
/// and also boosts transaction priority if it has submitted by registered relayer.
|
||||
/// The boost is computed as
|
||||
/// `(BundledHeaderNumber - 1 - BestKnownHeaderNumber) * Priority::get()`.
|
||||
/// The boost is only applied if submitter has active registration in the relayers
|
||||
/// pezpallet.
|
||||
pub struct CheckAndBoostBridgeTeyrchainsTransactions<
|
||||
T,
|
||||
TeyrchainsInstance,
|
||||
Para,
|
||||
Priority,
|
||||
SlashAccount,
|
||||
>(PhantomData<(T, TeyrchainsInstance, Para, Priority, SlashAccount)>);
|
||||
|
||||
impl<
|
||||
T,
|
||||
TeyrchainsInstance,
|
||||
Para,
|
||||
Priority: Get<TransactionPriority>,
|
||||
SlashAccount: Get<T::AccountId>,
|
||||
> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
|
||||
for CheckAndBoostBridgeTeyrchainsTransactions<T, TeyrchainsInstance, Para, Priority, SlashAccount>
|
||||
where
|
||||
T: pezpallet_bridge_relayers::Config + pezpallet_bridge_teyrchains::Config<TeyrchainsInstance>,
|
||||
TeyrchainsInstance: 'static,
|
||||
Para: Teyrchain,
|
||||
T::RuntimeCall: TeyrchainsCallSubtype<T, TeyrchainsInstance>,
|
||||
{
|
||||
// bridged header number, bundled in transaction
|
||||
type ToPostDispatch = Option<SubmitTeyrchainHeadsInfo>;
|
||||
|
||||
fn validate(
|
||||
who: &T::AccountId,
|
||||
call: &T::RuntimeCall,
|
||||
) -> (Self::ToPostDispatch, TransactionValidity) {
|
||||
match TeyrchainsCallSubtype::<T, TeyrchainsInstance>::check_obsolete_submit_teyrchain_heads(
|
||||
call,
|
||||
) {
|
||||
Ok(Some(our_tx)) if our_tx.base.para_id.0 == Para::TEYRCHAIN_ID => {
|
||||
let to_post_dispatch = Some(our_tx.base);
|
||||
let total_priority_boost =
|
||||
compute_priority_boost::<T, _, Priority>(&who, our_tx.improved_by);
|
||||
(
|
||||
to_post_dispatch,
|
||||
ValidTransactionBuilder::default().priority(total_priority_boost).build(),
|
||||
)
|
||||
},
|
||||
Ok(_) => (None, ValidTransactionBuilder::default().build()),
|
||||
Err(e) => (None, Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn post_dispatch(relayer: &T::AccountId, has_failed: bool, maybe_update: Self::ToPostDispatch) {
|
||||
// we are only interested in associated pezpallet submissions
|
||||
let Some(update) = maybe_update else { return };
|
||||
// we are only interested in failed or unneeded transactions
|
||||
let has_failed = has_failed ||
|
||||
!SubmitTeyrchainHeadsHelper::<T, TeyrchainsInstance>::was_successful(&update);
|
||||
|
||||
if !has_failed {
|
||||
return;
|
||||
}
|
||||
|
||||
// let's slash registered relayer
|
||||
RelayersPallet::<T>::slash_and_deregister(
|
||||
relayer,
|
||||
ExplicitOrAccountParams::Explicit::<_, ()>(SlashAccount::get()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: 'static> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
|
||||
for pezpallet_bridge_grandpa::Pezpallet<T, I>
|
||||
where
|
||||
T: pezpallet_bridge_grandpa::Config<I>,
|
||||
T::RuntimeCall: GrandpaCallSubType<T, I>,
|
||||
{
|
||||
type ToPostDispatch = ();
|
||||
fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
|
||||
(
|
||||
(),
|
||||
GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
|
||||
.and_then(|_| ValidTransactionBuilder::default().build()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: 'static> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
|
||||
for pezpallet_bridge_teyrchains::Pezpallet<T, I>
|
||||
where
|
||||
T: pezpallet_bridge_teyrchains::Config<I>,
|
||||
T::RuntimeCall: TeyrchainsCallSubtype<T, I>,
|
||||
{
|
||||
type ToPostDispatch = ();
|
||||
fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
|
||||
(
|
||||
(),
|
||||
TeyrchainsCallSubtype::<T, I>::check_obsolete_submit_teyrchain_heads(call)
|
||||
.and_then(|_| ValidTransactionBuilder::default().build()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: pezpallet_bridge_messages::Config<I>, I: 'static>
|
||||
BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall> for pezpallet_bridge_messages::Pezpallet<T, I>
|
||||
where
|
||||
T::RuntimeCall: MessagesCallSubType<T, I>,
|
||||
{
|
||||
type ToPostDispatch = ();
|
||||
/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
|
||||
/// transactions, that are delivering outdated messages/confirmations. Without this validation,
|
||||
/// even honest relayers may lose their funds if there are multiple relays running and
|
||||
/// submitting the same messages/confirmations.
|
||||
fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
|
||||
((), call.check_obsolete_call())
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes priority boost that improved known header by `improved_by`
|
||||
fn compute_priority_boost<T, N, Priority>(
|
||||
relayer: &T::AccountId,
|
||||
improved_by: N,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
T: pezpallet_bridge_relayers::Config,
|
||||
N: UniqueSaturatedInto<TransactionPriority>,
|
||||
Priority: Get<TransactionPriority>,
|
||||
{
|
||||
// we only boost priority if relayer has staked required balance
|
||||
let is_relayer_registration_active = RelayersPallet::<T>::is_registration_active(relayer);
|
||||
// if tx improves by just one, there's no need to bump its priority
|
||||
let improved_by: TransactionPriority = improved_by.unique_saturated_into().saturating_sub(1);
|
||||
// if relayer is registered, for every skipped header we improve by `Priority`
|
||||
let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 };
|
||||
improved_by.saturating_mul(boost_per_header)
|
||||
}
|
||||
|
||||
/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```nocompile
|
||||
/// generate_bridge_reject_obsolete_headers_and_messages!{
|
||||
/// Call, AccountId
|
||||
/// BridgePezkuwichainGrandpa, BridgePezkuwichainMessages,
|
||||
/// BridgePezkuwichainTeyrchains
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged
|
||||
/// headers and messages. Without that extension, even honest relayers may lose their funds if
|
||||
/// there are multiple relays running and submitting the same information.
|
||||
#[macro_export]
|
||||
macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
|
||||
($call:ty, $account_id:ty, $($filter_call:ty),*) => {
|
||||
#[derive(Clone, codec::Decode, codec::DecodeWithMemTracking, Default, codec::Encode, Eq, PartialEq, pezsp_runtime::RuntimeDebug, scale_info::TypeInfo)]
|
||||
pub struct BridgeRejectObsoleteHeadersAndMessages;
|
||||
impl pezsp_runtime::traits::TransactionExtension<$call> for BridgeRejectObsoleteHeadersAndMessages {
|
||||
const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
|
||||
type Implicit = ();
|
||||
type Val = Option<(
|
||||
$account_id,
|
||||
( $(
|
||||
<$filter_call as $crate::extensions::BridgeRuntimeFilterCall<
|
||||
$account_id,
|
||||
$call,
|
||||
>>::ToPostDispatch,
|
||||
)* ),
|
||||
)>;
|
||||
type Pre = Self::Val;
|
||||
|
||||
fn weight(&self, _: &$call) -> pezframe_support::pezpallet_prelude::Weight {
|
||||
pezframe_support::pezpallet_prelude::Weight::zero()
|
||||
}
|
||||
|
||||
fn validate(
|
||||
&self,
|
||||
origin: <$call as pezsp_runtime::traits::Dispatchable>::RuntimeOrigin,
|
||||
call: &$call,
|
||||
_info: &pezsp_runtime::traits::DispatchInfoOf<$call>,
|
||||
_len: usize,
|
||||
_self_implicit: Self::Implicit,
|
||||
_inherited_implication: &impl codec::Encode,
|
||||
_source: pezsp_runtime::transaction_validity::TransactionSource,
|
||||
) -> Result<
|
||||
(
|
||||
pezsp_runtime::transaction_validity::ValidTransaction,
|
||||
Self::Val,
|
||||
<$call as pezsp_runtime::traits::Dispatchable>::RuntimeOrigin,
|
||||
), pezsp_runtime::transaction_validity::TransactionValidityError
|
||||
> {
|
||||
use $crate::extensions::__private::tuplex::PushBack;
|
||||
use pezsp_runtime::traits::AsSystemOriginSigner;
|
||||
|
||||
let Some(who) = origin.as_system_origin_signer() else {
|
||||
return Ok((Default::default(), None, origin));
|
||||
};
|
||||
|
||||
let to_post_dispatch = ();
|
||||
let tx_validity = pezsp_runtime::transaction_validity::ValidTransaction::default();
|
||||
$(
|
||||
let (from_validate, call_filter_validity) = <
|
||||
$filter_call as
|
||||
$crate::extensions::BridgeRuntimeFilterCall<
|
||||
$account_id,
|
||||
$call,
|
||||
>>::validate(who, call);
|
||||
let to_post_dispatch = to_post_dispatch.push_back(from_validate);
|
||||
let tx_validity = tx_validity.combine_with(call_filter_validity?);
|
||||
)*
|
||||
Ok((tx_validity, Some((who.clone(), to_post_dispatch)), origin))
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
self,
|
||||
val: Self::Val,
|
||||
_origin: &<$call as pezsp_runtime::traits::Dispatchable>::RuntimeOrigin,
|
||||
_call: &$call,
|
||||
_info: &pezsp_runtime::traits::DispatchInfoOf<$call>,
|
||||
_len: usize,
|
||||
) -> Result<Self::Pre, pezsp_runtime::transaction_validity::TransactionValidityError> {
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn post_dispatch_details(
|
||||
to_post_dispatch: Self::Pre,
|
||||
info: &pezsp_runtime::traits::DispatchInfoOf<$call>,
|
||||
post_info: &pezsp_runtime::traits::PostDispatchInfoOf<$call>,
|
||||
len: usize,
|
||||
result: &pezsp_runtime::DispatchResult,
|
||||
) -> Result<pezframe_support::pezpallet_prelude::Weight, pezsp_runtime::transaction_validity::TransactionValidityError> {
|
||||
use $crate::extensions::__private::tuplex::PopFront;
|
||||
|
||||
let Some((relayer, to_post_dispatch)) = to_post_dispatch else {
|
||||
return Ok(pezframe_support::pezpallet_prelude::Weight::zero())
|
||||
};
|
||||
|
||||
let has_failed = result.is_err();
|
||||
$(
|
||||
let (item, to_post_dispatch) = to_post_dispatch.pop_front();
|
||||
<
|
||||
$filter_call as
|
||||
$crate::extensions::BridgeRuntimeFilterCall<
|
||||
$account_id,
|
||||
$call,
|
||||
>>::post_dispatch(&relayer, has_failed, item);
|
||||
)*
|
||||
Ok(pezframe_support::pezpallet_prelude::Weight::zero())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
use bp_header_pez_chain::StoredHeaderDataBuilder;
|
||||
use bp_messages::{InboundLaneData, MessageNonce, OutboundLaneData};
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHeadsProof, ParaId};
|
||||
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
|
||||
use pezbp_runtime::HeaderId;
|
||||
use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID};
|
||||
use bp_teyrchains::{BestParaHeadHash, ParaInfo};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{assert_err, assert_ok, traits::fungible::Mutate};
|
||||
use pezpallet_bridge_grandpa::{Call as GrandpaCall, StoredAuthoritySet};
|
||||
use pezpallet_bridge_teyrchains::Call as TeyrchainsCall;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
traits::{
|
||||
parameter_types, AsSystemOriginSigner, AsTransactionAuthorizedOrigin, ConstU64,
|
||||
DispatchTransaction, Header as _, TransactionExtension,
|
||||
},
|
||||
transaction_validity::{
|
||||
InvalidTransaction, TransactionSource::External, TransactionValidity, ValidTransaction,
|
||||
},
|
||||
DispatchError,
|
||||
};
|
||||
|
||||
parameter_types! {
|
||||
pub MsgProofsRewardsAccount: RewardsAccountParams<TestLaneIdType> = RewardsAccountParams::new(
|
||||
test_lane_id(),
|
||||
TEST_BRIDGED_CHAIN_ID,
|
||||
RewardsAccountOwner::ThisChain,
|
||||
);
|
||||
pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams<TestLaneIdType> = RewardsAccountParams::new(
|
||||
test_lane_id(),
|
||||
TEST_BRIDGED_CHAIN_ID,
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub struct MockCall {
|
||||
data: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub struct MockOrigin(pub u64);
|
||||
|
||||
impl AsSystemOriginSigner<u64> for MockOrigin {
|
||||
fn as_system_origin_signer(&self) -> Option<&u64> {
|
||||
Some(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsTransactionAuthorizedOrigin for MockOrigin {
|
||||
fn is_transaction_authorized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for MockOrigin {
|
||||
fn from(o: u64) -> Self {
|
||||
Self(o)
|
||||
}
|
||||
}
|
||||
|
||||
impl pezsp_runtime::traits::Dispatchable for MockCall {
|
||||
type RuntimeOrigin = MockOrigin;
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = ();
|
||||
|
||||
fn dispatch(
|
||||
self,
|
||||
_origin: Self::RuntimeOrigin,
|
||||
) -> pezsp_runtime::DispatchResultWithInfo<Self::PostInfo> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FirstFilterCall;
|
||||
impl FirstFilterCall {
|
||||
fn post_dispatch_called_with(success: bool) {
|
||||
pezframe_support::storage::unhashed::put(&[1], &success);
|
||||
}
|
||||
|
||||
fn verify_post_dispatch_called_with(success: bool) {
|
||||
assert_eq!(pezframe_support::storage::unhashed::get::<bool>(&[1]), Some(success));
|
||||
}
|
||||
}
|
||||
|
||||
impl BridgeRuntimeFilterCall<u64, MockCall> for FirstFilterCall {
|
||||
type ToPostDispatch = u64;
|
||||
fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
|
||||
if call.data <= 1 {
|
||||
return (1, InvalidTransaction::Custom(1).into());
|
||||
}
|
||||
|
||||
(1, Ok(ValidTransaction { priority: 1, ..Default::default() }))
|
||||
}
|
||||
|
||||
fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) {
|
||||
Self::post_dispatch_called_with(!has_failed);
|
||||
assert_eq!(to_post_dispatch, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SecondFilterCall;
|
||||
|
||||
impl SecondFilterCall {
|
||||
fn post_dispatch_called_with(success: bool) {
|
||||
pezframe_support::storage::unhashed::put(&[2], &success);
|
||||
}
|
||||
|
||||
fn verify_post_dispatch_called_with(success: bool) {
|
||||
assert_eq!(pezframe_support::storage::unhashed::get::<bool>(&[2]), Some(success));
|
||||
}
|
||||
}
|
||||
|
||||
impl BridgeRuntimeFilterCall<u64, MockCall> for SecondFilterCall {
|
||||
type ToPostDispatch = u64;
|
||||
fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
|
||||
if call.data <= 2 {
|
||||
return (2, InvalidTransaction::Custom(2).into());
|
||||
}
|
||||
|
||||
(2, Ok(ValidTransaction { priority: 2, ..Default::default() }))
|
||||
}
|
||||
|
||||
fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) {
|
||||
Self::post_dispatch_called_with(!has_failed);
|
||||
assert_eq!(to_post_dispatch, 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
|
||||
let test_stake: ThisChainBalance = TestStake::get();
|
||||
ExistentialDeposit::get().saturating_add(test_stake * 100)
|
||||
}
|
||||
|
||||
// in tests, the following accounts are equal (because of how `into_sub_account_truncating`
|
||||
// works)
|
||||
|
||||
fn delivery_rewards_account() -> ThisChainAccountId {
|
||||
TestPaymentProcedure::rewards_account(MsgProofsRewardsAccount::get())
|
||||
}
|
||||
|
||||
fn confirmation_rewards_account() -> ThisChainAccountId {
|
||||
TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
|
||||
}
|
||||
|
||||
fn relayer_account_at_this_chain() -> ThisChainAccountId {
|
||||
0
|
||||
}
|
||||
|
||||
fn initialize_environment(
|
||||
best_relay_header_number: BridgedChainBlockNumber,
|
||||
teyrchain_head_at_relay_header_number: BridgedChainBlockNumber,
|
||||
best_message: MessageNonce,
|
||||
) {
|
||||
let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
|
||||
let best_relay_header = HeaderId(best_relay_header_number, BridgedChainHash::default());
|
||||
pezpallet_bridge_grandpa::CurrentAuthoritySet::<TestRuntime>::put(
|
||||
StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
|
||||
);
|
||||
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(best_relay_header);
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
best_relay_header.hash(),
|
||||
bp_test_utils::test_header::<BridgedChainHeader>(0).build(),
|
||||
);
|
||||
|
||||
let para_id = ParaId(BridgedUnderlyingTeyrchain::TEYRCHAIN_ID);
|
||||
let para_info = ParaInfo {
|
||||
best_head_hash: BestParaHeadHash {
|
||||
at_relay_block_number: teyrchain_head_at_relay_header_number,
|
||||
head_hash: [teyrchain_head_at_relay_header_number as u8; 32].into(),
|
||||
},
|
||||
next_imported_hash_position: 0,
|
||||
};
|
||||
pezpallet_bridge_teyrchains::ParasInfo::<TestRuntime>::insert(para_id, para_info);
|
||||
|
||||
let lane_id = test_lane_id();
|
||||
let in_lane_data =
|
||||
InboundLaneData { last_confirmed_nonce: best_message, ..Default::default() };
|
||||
pezpallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
|
||||
|
||||
let out_lane_data =
|
||||
OutboundLaneData { latest_received_nonce: best_message, ..Default::default() };
|
||||
pezpallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(lane_id, out_lane_data);
|
||||
|
||||
Balances::mint_into(&delivery_rewards_account(), ExistentialDeposit::get()).unwrap();
|
||||
Balances::mint_into(&confirmation_rewards_account(), ExistentialDeposit::get()).unwrap();
|
||||
Balances::mint_into(
|
||||
&relayer_account_at_this_chain(),
|
||||
initial_balance_of_relayer_account_at_this_chain(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn submit_relay_header_call(relay_header_number: BridgedChainBlockNumber) -> RuntimeCall {
|
||||
let relay_header = BridgedChainHeader::new(
|
||||
relay_header_number,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let relay_justification = make_default_justification(&relay_header);
|
||||
|
||||
RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof {
|
||||
finality_target: Box::new(relay_header),
|
||||
justification: relay_justification,
|
||||
})
|
||||
}
|
||||
|
||||
fn submit_teyrchain_head_call(
|
||||
teyrchain_head_at_relay_header_number: BridgedChainBlockNumber,
|
||||
) -> RuntimeCall {
|
||||
RuntimeCall::BridgeTeyrchains(TeyrchainsCall::submit_teyrchain_heads {
|
||||
at_relay_block: (teyrchain_head_at_relay_header_number, BridgedChainHash::default()),
|
||||
teyrchains: vec![(
|
||||
ParaId(BridgedUnderlyingTeyrchain::TEYRCHAIN_ID),
|
||||
[teyrchain_head_at_relay_header_number as u8; 32].into(),
|
||||
)],
|
||||
teyrchain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generated_obsolete_extension() {
|
||||
generate_bridge_reject_obsolete_headers_and_messages!(
|
||||
MockCall,
|
||||
u64,
|
||||
FirstFilterCall,
|
||||
SecondFilterCall
|
||||
);
|
||||
|
||||
run_test(|| {
|
||||
assert_err!(
|
||||
BridgeRejectObsoleteHeadersAndMessages.validate_only(
|
||||
42u64.into(),
|
||||
&MockCall { data: 1 },
|
||||
&(),
|
||||
0,
|
||||
External,
|
||||
0,
|
||||
),
|
||||
InvalidTransaction::Custom(1)
|
||||
);
|
||||
assert_err!(
|
||||
BridgeRejectObsoleteHeadersAndMessages.validate_and_prepare(
|
||||
42u64.into(),
|
||||
&MockCall { data: 1 },
|
||||
&(),
|
||||
0,
|
||||
0,
|
||||
),
|
||||
InvalidTransaction::Custom(1)
|
||||
);
|
||||
|
||||
assert_err!(
|
||||
BridgeRejectObsoleteHeadersAndMessages.validate_only(
|
||||
42u64.into(),
|
||||
&MockCall { data: 2 },
|
||||
&(),
|
||||
0,
|
||||
External,
|
||||
0,
|
||||
),
|
||||
InvalidTransaction::Custom(2)
|
||||
);
|
||||
assert_err!(
|
||||
BridgeRejectObsoleteHeadersAndMessages.validate_and_prepare(
|
||||
42u64.into(),
|
||||
&MockCall { data: 2 },
|
||||
&(),
|
||||
0,
|
||||
0,
|
||||
),
|
||||
InvalidTransaction::Custom(2)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
BridgeRejectObsoleteHeadersAndMessages
|
||||
.validate_only(42u64.into(), &MockCall { data: 3 }, &(), 0, External, 0)
|
||||
.unwrap()
|
||||
.0,
|
||||
ValidTransaction { priority: 3, ..Default::default() },
|
||||
);
|
||||
assert_eq!(
|
||||
BridgeRejectObsoleteHeadersAndMessages
|
||||
.validate_and_prepare(42u64.into(), &MockCall { data: 3 }, &(), 0, 0)
|
||||
.unwrap()
|
||||
.0
|
||||
.unwrap(),
|
||||
(42, (1, 2)),
|
||||
);
|
||||
|
||||
// when post_dispatch is called with `Ok(())`, it is propagated to all "nested"
|
||||
// extensions
|
||||
assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch_details(
|
||||
Some((0, (1, 2))),
|
||||
&(),
|
||||
&(),
|
||||
0,
|
||||
&Ok(()),
|
||||
));
|
||||
FirstFilterCall::verify_post_dispatch_called_with(true);
|
||||
SecondFilterCall::verify_post_dispatch_called_with(true);
|
||||
|
||||
// when post_dispatch is called with `Err(())`, it is propagated to all "nested"
|
||||
// extensions
|
||||
assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch_details(
|
||||
Some((0, (1, 2))),
|
||||
&(),
|
||||
&(),
|
||||
0,
|
||||
&Err(DispatchError::BadOrigin),
|
||||
));
|
||||
FirstFilterCall::verify_post_dispatch_called_with(false);
|
||||
SecondFilterCall::verify_post_dispatch_called_with(false);
|
||||
});
|
||||
}
|
||||
|
||||
pezframe_support::parameter_types! {
|
||||
pub SlashDestination: ThisChainAccountId = 42;
|
||||
}
|
||||
|
||||
type BridgeGrandpaWrapper =
|
||||
CheckAndBoostBridgeGrandpaTransactions<TestRuntime, (), ConstU64<1_000>, SlashDestination>;
|
||||
|
||||
#[test]
|
||||
fn grandpa_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
|
||||
run_test(|| {
|
||||
initialize_environment(100, 100, 100);
|
||||
|
||||
let priority_boost = BridgeGrandpaWrapper::validate(
|
||||
&relayer_account_at_this_chain(),
|
||||
&submit_relay_header_call(200),
|
||||
)
|
||||
.1
|
||||
.unwrap()
|
||||
.priority;
|
||||
assert_eq!(priority_boost, 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grandpa_wrapper_boosts_extensions_for_registered_relayer() {
|
||||
run_test(|| {
|
||||
initialize_environment(100, 100, 100);
|
||||
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||
.unwrap();
|
||||
|
||||
let priority_boost = BridgeGrandpaWrapper::validate(
|
||||
&relayer_account_at_this_chain(),
|
||||
&submit_relay_header_call(200),
|
||||
)
|
||||
.1
|
||||
.unwrap()
|
||||
.priority;
|
||||
assert_eq!(priority_boost, 99_000);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grandpa_wrapper_slashes_registered_relayer_if_transaction_fails() {
|
||||
run_test(|| {
|
||||
initialize_environment(100, 100, 100);
|
||||
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||
.unwrap();
|
||||
|
||||
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
|
||||
BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), true, Some(150));
|
||||
assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grandpa_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
|
||||
run_test(|| {
|
||||
initialize_environment(100, 100, 100);
|
||||
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||
.unwrap();
|
||||
|
||||
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
|
||||
BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), false, Some(100));
|
||||
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
|
||||
})
|
||||
}
|
||||
|
||||
type BridgeTeyrchainsWrapper = CheckAndBoostBridgeTeyrchainsTransactions<
|
||||
TestRuntime,
|
||||
(),
|
||||
BridgedUnderlyingTeyrchain,
|
||||
ConstU64<1_000>,
|
||||
SlashDestination,
|
||||
>;
|
||||
|
||||
#[test]
|
||||
fn teyrchains_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
|
||||
run_test(|| {
|
||||
initialize_environment(100, 100, 100);
|
||||
|
||||
let priority_boost = BridgeTeyrchainsWrapper::validate(
|
||||
&relayer_account_at_this_chain(),
|
||||
&submit_teyrchain_head_call(200),
|
||||
)
|
||||
.1
|
||||
.unwrap()
|
||||
.priority;
|
||||
assert_eq!(priority_boost, 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn teyrchains_wrapper_boosts_extensions_for_registered_relayer() {
|
||||
run_test(|| {
|
||||
initialize_environment(100, 100, 100);
|
||||
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||
.unwrap();
|
||||
|
||||
let priority_boost = BridgeTeyrchainsWrapper::validate(
|
||||
&relayer_account_at_this_chain(),
|
||||
&submit_teyrchain_head_call(200),
|
||||
)
|
||||
.1
|
||||
.unwrap()
|
||||
.priority;
|
||||
assert_eq!(priority_boost, 99_000);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn teyrchains_wrapper_slashes_registered_relayer_if_transaction_fails() {
|
||||
run_test(|| {
|
||||
initialize_environment(100, 100, 100);
|
||||
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||
.unwrap();
|
||||
|
||||
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
|
||||
BridgeTeyrchainsWrapper::post_dispatch(
|
||||
&relayer_account_at_this_chain(),
|
||||
true,
|
||||
Some(SubmitTeyrchainHeadsInfo {
|
||||
at_relay_block: HeaderId(150, Default::default()),
|
||||
para_id: ParaId(BridgedUnderlyingTeyrchain::TEYRCHAIN_ID),
|
||||
para_head_hash: [150u8; 32].into(),
|
||||
is_free_execution_expected: false,
|
||||
}),
|
||||
);
|
||||
assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn teyrchains_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
|
||||
run_test(|| {
|
||||
initialize_environment(100, 100, 100);
|
||||
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||
.unwrap();
|
||||
|
||||
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
|
||||
BridgeTeyrchainsWrapper::post_dispatch(
|
||||
&relayer_account_at_this_chain(),
|
||||
false,
|
||||
Some(SubmitTeyrchainHeadsInfo {
|
||||
at_relay_block: HeaderId(100, Default::default()),
|
||||
para_id: ParaId(BridgedUnderlyingTeyrchain::TEYRCHAIN_ID),
|
||||
para_head_hash: [100u8; 32].into(),
|
||||
is_free_execution_expected: false,
|
||||
}),
|
||||
);
|
||||
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Integrity tests for chain constants and pallets configuration.
|
||||
//!
|
||||
//! Most of the tests in this module assume that the bridge is using standard (see `crate::messages`
|
||||
//! module for details) configuration.
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use bp_messages::{ChainWithMessages, InboundLaneData, MessageNonce};
|
||||
use pezbp_runtime::{AccountIdOf, Chain};
|
||||
use codec::Encode;
|
||||
use pezframe_support::{storage::generator::StorageValue, traits::Get, weights::Weight};
|
||||
use pezframe_system::limits;
|
||||
use pezpallet_bridge_messages::{ThisChainOf, WeightInfoExt as _};
|
||||
|
||||
// Re-export to avoid include all dependencies everywhere.
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
pub use static_assertions;
|
||||
}
|
||||
|
||||
/// Macro that ensures that the runtime configuration and chain primitives crate are sharing
|
||||
/// the same types (nonce, block number, hash, hasher, account id and header).
|
||||
#[macro_export]
|
||||
macro_rules! assert_chain_types(
|
||||
( runtime: $r:path, this_chain: $this:path ) => {
|
||||
{
|
||||
use pezframe_system::{Config as SystemConfig, pezpallet_prelude::{BlockNumberFor, HeaderFor}};
|
||||
use $crate::integrity::__private::static_assertions::assert_type_eq_all;
|
||||
|
||||
// if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard
|
||||
// configuration is used), or something has broke existing configuration (meaning that all bridged chains
|
||||
// and relays will stop functioning)
|
||||
|
||||
assert_type_eq_all!(<$r as SystemConfig>::Nonce, pezbp_runtime::NonceOf<$this>);
|
||||
assert_type_eq_all!(BlockNumberFor<$r>, pezbp_runtime::BlockNumberOf<$this>);
|
||||
assert_type_eq_all!(<$r as SystemConfig>::Hash, pezbp_runtime::HashOf<$this>);
|
||||
assert_type_eq_all!(<$r as SystemConfig>::Hashing, pezbp_runtime::HasherOf<$this>);
|
||||
assert_type_eq_all!(<$r as SystemConfig>::AccountId, pezbp_runtime::AccountIdOf<$this>);
|
||||
assert_type_eq_all!(HeaderFor<$r>, pezbp_runtime::HeaderOf<$this>);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/// Macro that ensures that the bridge messages pezpallet is configured properly to bridge using given
|
||||
/// configuration.
|
||||
#[macro_export]
|
||||
macro_rules! assert_bridge_messages_pallet_types(
|
||||
(
|
||||
runtime: $r:path,
|
||||
with_bridged_chain_messages_instance: $i:path,
|
||||
this_chain: $this:path,
|
||||
bridged_chain: $bridged:path,
|
||||
expected_payload_type: $payload:path,
|
||||
) => {
|
||||
{
|
||||
use $crate::integrity::__private::static_assertions::assert_type_eq_all;
|
||||
use bp_messages::ChainWithMessages;
|
||||
use pezbp_runtime::Chain;
|
||||
use pezpallet_bridge_messages::Config as BridgeMessagesConfig;
|
||||
|
||||
// if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard
|
||||
// configuration is used), or something has broke existing configuration (meaning that all bridged chains
|
||||
// and relays will stop functioning)
|
||||
|
||||
assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::ThisChain, $this);
|
||||
assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::BridgedChain, $bridged);
|
||||
|
||||
assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::OutboundPayload, $payload);
|
||||
assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::InboundPayload, $payload);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/// Macro that combines four other macro calls - `assert_chain_types`, `assert_bridge_types`,
|
||||
/// and `assert_bridge_messages_pallet_types`. It may be used
|
||||
/// at the chain that is implementing standard messages bridge with messages pallets deployed.
|
||||
#[macro_export]
|
||||
macro_rules! assert_complete_bridge_types(
|
||||
(
|
||||
runtime: $r:path,
|
||||
with_bridged_chain_messages_instance: $mi:path,
|
||||
this_chain: $this:path,
|
||||
bridged_chain: $bridged:path,
|
||||
expected_payload_type: $payload:path,
|
||||
) => {
|
||||
$crate::assert_chain_types!(runtime: $r, this_chain: $this);
|
||||
$crate::assert_bridge_messages_pallet_types!(
|
||||
runtime: $r,
|
||||
with_bridged_chain_messages_instance: $mi,
|
||||
this_chain: $this,
|
||||
bridged_chain: $bridged,
|
||||
expected_payload_type: $payload,
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/// Parameters for asserting chain-related constants.
|
||||
#[derive(Debug)]
|
||||
pub struct AssertChainConstants {
|
||||
/// Block length limits of the chain.
|
||||
pub block_length: limits::BlockLength,
|
||||
/// Block weight limits of the chain.
|
||||
pub block_weights: limits::BlockWeights,
|
||||
}
|
||||
|
||||
/// Test that our hardcoded, chain-related constants, are matching chain runtime configuration.
|
||||
///
|
||||
/// In particular, this test ensures that:
|
||||
///
|
||||
/// 1) block weight limits are matching;
|
||||
/// 2) block size limits are matching.
|
||||
pub fn assert_chain_constants<R>(params: AssertChainConstants)
|
||||
where
|
||||
R: pezframe_system::Config,
|
||||
{
|
||||
// we don't check runtime version here, because in our case we'll be building relay from one
|
||||
// repo and runtime will live in another repo, along with outdated relay version. To avoid
|
||||
// unneeded commits, let's not raise an error in case of version mismatch.
|
||||
|
||||
// if one of following assert fails, it means that we may need to upgrade bridged chain and
|
||||
// relay to use updated constants. If constants are now smaller than before, it may lead to
|
||||
// undeliverable messages.
|
||||
|
||||
// `BlockLength` struct is not implementing `PartialEq`, so we compare encoded values here.
|
||||
assert_eq!(
|
||||
R::BlockLength::get().encode(),
|
||||
params.block_length.encode(),
|
||||
"BlockLength from runtime ({:?}) differ from hardcoded: {:?}",
|
||||
R::BlockLength::get(),
|
||||
params.block_length,
|
||||
);
|
||||
// `BlockWeights` struct is not implementing `PartialEq`, so we compare encoded values here
|
||||
assert_eq!(
|
||||
R::BlockWeights::get().encode(),
|
||||
params.block_weights.encode(),
|
||||
"BlockWeights from runtime ({:?}) differ from hardcoded: {:?}",
|
||||
R::BlockWeights::get(),
|
||||
params.block_weights,
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that the constants, used in GRANDPA pezpallet configuration are valid.
|
||||
pub fn assert_bridge_grandpa_pallet_constants<R, GI>()
|
||||
where
|
||||
R: pezpallet_bridge_grandpa::Config<GI>,
|
||||
GI: 'static,
|
||||
{
|
||||
assert!(
|
||||
R::HeadersToKeep::get() > 0,
|
||||
"HeadersToKeep ({}) must be larger than zero",
|
||||
R::HeadersToKeep::get(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that the constants, used in messages pezpallet configuration are valid.
|
||||
pub fn assert_bridge_messages_pallet_constants<R, MI>()
|
||||
where
|
||||
R: pezpallet_bridge_messages::Config<MI>,
|
||||
MI: 'static,
|
||||
{
|
||||
assert!(
|
||||
pezpallet_bridge_messages::BridgedChainOf::<R, MI>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX
|
||||
<= pezpallet_bridge_messages::BridgedChainOf::<R, MI>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
"MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX ({}) of {:?} is larger than \
|
||||
its MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX ({}). This makes \
|
||||
no sense",
|
||||
pezpallet_bridge_messages::BridgedChainOf::<R, MI>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
|
||||
pezpallet_bridge_messages::BridgedChainOf::<R, MI>::ID,
|
||||
pezpallet_bridge_messages::BridgedChainOf::<R, MI>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters for asserting bridge GRANDPA pezpallet names.
|
||||
#[derive(Debug)]
|
||||
struct AssertBridgeGrandpaPalletNames<'a> {
|
||||
/// Name of the GRANDPA pezpallet, deployed at this chain and used to bridge with the bridged
|
||||
/// chain.
|
||||
pub with_bridged_chain_grandpa_pallet_name: &'a str,
|
||||
}
|
||||
|
||||
/// Tests that bridge pezpallet names used in `construct_runtime!()` macro call are matching constants
|
||||
/// from chain primitives crates.
|
||||
fn assert_bridge_grandpa_pallet_names<R, GI>(params: AssertBridgeGrandpaPalletNames)
|
||||
where
|
||||
R: pezpallet_bridge_grandpa::Config<GI>,
|
||||
GI: 'static,
|
||||
{
|
||||
// check that the bridge GRANDPA pezpallet has required name
|
||||
assert_eq!(
|
||||
pezpallet_bridge_grandpa::PalletOwner::<R, GI>::storage_value_final_key().to_vec(),
|
||||
pezbp_runtime::storage_value_key(
|
||||
params.with_bridged_chain_grandpa_pallet_name,
|
||||
"PalletOwner",
|
||||
)
|
||||
.0,
|
||||
);
|
||||
assert_eq!(
|
||||
pezpallet_bridge_grandpa::PalletOperatingMode::<R, GI>::storage_value_final_key().to_vec(),
|
||||
pezbp_runtime::storage_value_key(
|
||||
params.with_bridged_chain_grandpa_pallet_name,
|
||||
"PalletOperatingMode",
|
||||
)
|
||||
.0,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters for asserting bridge messages pezpallet names.
|
||||
#[derive(Debug)]
|
||||
struct AssertBridgeMessagesPalletNames<'a> {
|
||||
/// Name of the messages pezpallet, deployed at this chain and used to bridge with the bridged
|
||||
/// chain.
|
||||
pub with_bridged_chain_messages_pallet_name: &'a str,
|
||||
}
|
||||
|
||||
/// Tests that bridge pezpallet names used in `construct_runtime!()` macro call are matching constants
|
||||
/// from chain primitives crates.
|
||||
fn assert_bridge_messages_pallet_names<R, MI>(params: AssertBridgeMessagesPalletNames)
|
||||
where
|
||||
R: pezpallet_bridge_messages::Config<MI>,
|
||||
MI: 'static,
|
||||
{
|
||||
// check that the bridge messages pezpallet has required name
|
||||
assert_eq!(
|
||||
pezpallet_bridge_messages::PalletOwner::<R, MI>::storage_value_final_key().to_vec(),
|
||||
pezbp_runtime::storage_value_key(
|
||||
params.with_bridged_chain_messages_pallet_name,
|
||||
"PalletOwner",
|
||||
)
|
||||
.0,
|
||||
);
|
||||
assert_eq!(
|
||||
pezpallet_bridge_messages::PalletOperatingMode::<R, MI>::storage_value_final_key().to_vec(),
|
||||
pezbp_runtime::storage_value_key(
|
||||
params.with_bridged_chain_messages_pallet_name,
|
||||
"PalletOperatingMode",
|
||||
)
|
||||
.0,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters for asserting complete standard messages bridge.
|
||||
#[derive(Debug)]
|
||||
pub struct AssertCompleteBridgeConstants {
|
||||
/// Parameters to assert this chain constants.
|
||||
pub this_chain_constants: AssertChainConstants,
|
||||
}
|
||||
|
||||
/// All bridge-related constants tests for the complete standard relay-chain messages bridge
|
||||
/// (i.e. with bridge GRANDPA and messages pallets deployed).
|
||||
pub fn assert_complete_with_relay_chain_bridge_constants<R, GI, MI>(
|
||||
params: AssertCompleteBridgeConstants,
|
||||
) where
|
||||
R: pezframe_system::Config
|
||||
+ pezpallet_bridge_grandpa::Config<GI>
|
||||
+ pezpallet_bridge_messages::Config<MI>,
|
||||
GI: 'static,
|
||||
MI: 'static,
|
||||
{
|
||||
assert_chain_constants::<R>(params.this_chain_constants);
|
||||
assert_bridge_grandpa_pallet_constants::<R, GI>();
|
||||
assert_bridge_messages_pallet_constants::<R, MI>();
|
||||
assert_bridge_grandpa_pallet_names::<R, GI>(AssertBridgeGrandpaPalletNames {
|
||||
with_bridged_chain_grandpa_pallet_name:
|
||||
<R as pezpallet_bridge_grandpa::Config<GI>>::BridgedChain::WITH_CHAIN_GRANDPA_PALLET_NAME,
|
||||
});
|
||||
assert_bridge_messages_pallet_names::<R, MI>(AssertBridgeMessagesPalletNames {
|
||||
with_bridged_chain_messages_pallet_name:
|
||||
<R as pezpallet_bridge_messages::Config<MI>>::BridgedChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
});
|
||||
}
|
||||
|
||||
/// All bridge-related constants tests for the complete standard teyrchain messages bridge
|
||||
/// (i.e. with bridge GRANDPA, teyrchains and messages pallets deployed).
|
||||
pub fn assert_complete_with_teyrchain_bridge_constants<R, PI, MI>(
|
||||
params: AssertCompleteBridgeConstants,
|
||||
) where
|
||||
R: pezframe_system::Config
|
||||
+ pezpallet_bridge_teyrchains::Config<PI>
|
||||
+ pezpallet_bridge_messages::Config<MI>,
|
||||
<R as pezpallet_bridge_teyrchains::BoundedBridgeGrandpaConfig<R::BridgesGrandpaPalletInstance>>::BridgedRelayChain: ChainWithGrandpa,
|
||||
PI: 'static,
|
||||
MI: 'static,
|
||||
{
|
||||
assert_chain_constants::<R>(params.this_chain_constants);
|
||||
assert_bridge_grandpa_pallet_constants::<R, R::BridgesGrandpaPalletInstance>();
|
||||
assert_bridge_messages_pallet_constants::<R, MI>();
|
||||
assert_bridge_grandpa_pallet_names::<R, R::BridgesGrandpaPalletInstance>(
|
||||
AssertBridgeGrandpaPalletNames {
|
||||
with_bridged_chain_grandpa_pallet_name:
|
||||
<<R as pezpallet_bridge_teyrchains::BoundedBridgeGrandpaConfig<
|
||||
R::BridgesGrandpaPalletInstance,
|
||||
>>::BridgedRelayChain>::WITH_CHAIN_GRANDPA_PALLET_NAME,
|
||||
},
|
||||
);
|
||||
assert_bridge_messages_pallet_names::<R, MI>(AssertBridgeMessagesPalletNames {
|
||||
with_bridged_chain_messages_pallet_name:
|
||||
<R as pezpallet_bridge_messages::Config<MI>>::BridgedChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
});
|
||||
}
|
||||
|
||||
/// All bridge-related constants tests for the standalone messages bridge deployment (only with
|
||||
/// messages pallets deployed).
|
||||
pub fn assert_standalone_messages_bridge_constants<R, MI>(params: AssertCompleteBridgeConstants)
|
||||
where
|
||||
R: pezframe_system::Config + pezpallet_bridge_messages::Config<MI>,
|
||||
MI: 'static,
|
||||
{
|
||||
assert_chain_constants::<R>(params.this_chain_constants);
|
||||
assert_bridge_messages_pallet_constants::<R, MI>();
|
||||
assert_bridge_messages_pallet_names::<R, MI>(AssertBridgeMessagesPalletNames {
|
||||
with_bridged_chain_messages_pallet_name:
|
||||
<R as pezpallet_bridge_messages::Config<MI>>::BridgedChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
});
|
||||
}
|
||||
|
||||
/// Check that the message lane weights are correct.
|
||||
pub fn check_message_lane_weights<
|
||||
C: ChainWithMessages,
|
||||
T: pezframe_system::Config + pezpallet_bridge_messages::Config<MessagesPalletInstance>,
|
||||
MessagesPalletInstance: 'static,
|
||||
>(
|
||||
bridged_chain_extra_storage_proof_size: u32,
|
||||
this_chain_max_unrewarded_relayers: MessageNonce,
|
||||
this_chain_max_unconfirmed_messages: MessageNonce,
|
||||
// whether `RefundBridgedTeyrchainMessages` extension is deployed at runtime and is used for
|
||||
// refunding this bridge transactions?
|
||||
//
|
||||
// in other words: pass true for all known production chains
|
||||
runtime_includes_refund_extension: bool,
|
||||
) {
|
||||
type Weights<T, MI> = <T as pezpallet_bridge_messages::Config<MI>>::WeightInfo;
|
||||
|
||||
// check basic weight assumptions
|
||||
pezpallet_bridge_messages::ensure_weights_are_correct::<Weights<T, MessagesPalletInstance>>();
|
||||
|
||||
// check that the maximal message dispatch weight is below hardcoded limit
|
||||
pezpallet_bridge_messages::ensure_maximal_message_dispatch::<Weights<T, MessagesPalletInstance>>(
|
||||
C::maximal_incoming_message_size(),
|
||||
C::maximal_incoming_message_dispatch_weight(),
|
||||
);
|
||||
|
||||
// check that weights allow us to receive messages
|
||||
let max_incoming_message_proof_size =
|
||||
bridged_chain_extra_storage_proof_size.saturating_add(C::maximal_incoming_message_size());
|
||||
pezpallet_bridge_messages::ensure_able_to_receive_message::<Weights<T, MessagesPalletInstance>>(
|
||||
C::max_extrinsic_size(),
|
||||
C::max_extrinsic_weight(),
|
||||
max_incoming_message_proof_size,
|
||||
C::maximal_incoming_message_dispatch_weight(),
|
||||
);
|
||||
|
||||
// check that weights allow us to receive delivery confirmations
|
||||
let max_incoming_inbound_lane_data_proof_size = InboundLaneData::<
|
||||
AccountIdOf<ThisChainOf<T, MessagesPalletInstance>>,
|
||||
>::encoded_size_hint_u32(
|
||||
this_chain_max_unrewarded_relayers as _
|
||||
);
|
||||
pezpallet_bridge_messages::ensure_able_to_receive_confirmation::<Weights<T, MessagesPalletInstance>>(
|
||||
C::max_extrinsic_size(),
|
||||
C::max_extrinsic_weight(),
|
||||
max_incoming_inbound_lane_data_proof_size,
|
||||
this_chain_max_unrewarded_relayers,
|
||||
this_chain_max_unconfirmed_messages,
|
||||
);
|
||||
|
||||
// check that extra weights of delivery/confirmation transactions include the weight
|
||||
// of `RefundBridgedTeyrchainMessages` operations. This signed extension assumes the worst case
|
||||
// (i.e. slashing if delivery transaction was invalid) and refunds some weight if
|
||||
// assumption was wrong (i.e. if we did refund instead of slashing). This check
|
||||
// ensures the extension will not refund weight when it doesn't need to (i.e. if pezpallet
|
||||
// weights do not account weights of refund extension).
|
||||
if runtime_includes_refund_extension {
|
||||
assert_ne!(
|
||||
Weights::<T, MessagesPalletInstance>::receive_messages_proof_overhead_from_runtime(),
|
||||
Weight::zero()
|
||||
);
|
||||
assert_ne!(
|
||||
Weights::<T, MessagesPalletInstance>::receive_messages_delivery_proof_overhead_from_runtime(),
|
||||
Weight::zero()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Common types/functions that may be used by runtimes of all bridged chains.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod extensions;
|
||||
pub mod messages_api;
|
||||
pub mod messages_benchmarking;
|
||||
pub mod teyrchains_benchmarking;
|
||||
|
||||
mod mock;
|
||||
|
||||
#[cfg(feature = "integrity-test")]
|
||||
pub mod integrity;
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helpers for implementing various message-related runtime API methods.
|
||||
|
||||
use bp_messages::{InboundMessageDetails, MessageNonce, MessagePayload, OutboundMessageDetails};
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
/// Implementation of the `To*OutboundLaneApi::message_details`.
|
||||
pub fn outbound_message_details<Runtime, MessagesPalletInstance>(
|
||||
lane: Runtime::LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<OutboundMessageDetails>
|
||||
where
|
||||
Runtime: pezpallet_bridge_messages::Config<MessagesPalletInstance>,
|
||||
MessagesPalletInstance: 'static,
|
||||
{
|
||||
(begin..=end)
|
||||
.filter_map(|nonce| {
|
||||
let message_data =
|
||||
pezpallet_bridge_messages::Pezpallet::<Runtime, MessagesPalletInstance>::outbound_message_data(lane, nonce)?;
|
||||
Some(OutboundMessageDetails {
|
||||
nonce,
|
||||
// dispatch message weight is always zero at the source chain, since we're paying for
|
||||
// dispatch at the target chain
|
||||
dispatch_weight: pezframe_support::weights::Weight::zero(),
|
||||
size: message_data.len() as _,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Implementation of the `To*InboundLaneApi::message_details`.
|
||||
pub fn inbound_message_details<Runtime, MessagesPalletInstance>(
|
||||
lane: Runtime::LaneId,
|
||||
messages: Vec<(MessagePayload, OutboundMessageDetails)>,
|
||||
) -> Vec<InboundMessageDetails>
|
||||
where
|
||||
Runtime: pezpallet_bridge_messages::Config<MessagesPalletInstance>,
|
||||
MessagesPalletInstance: 'static,
|
||||
{
|
||||
messages
|
||||
.into_iter()
|
||||
.map(|(payload, details)| {
|
||||
pezpallet_bridge_messages::Pezpallet::<Runtime, MessagesPalletInstance>::inbound_message_data(
|
||||
lane, payload, details,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Everything required to run benchmarks of messages module, based on
|
||||
//! `pezbridge_runtime_common::messages` implementation.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use bp_messages::{
|
||||
source_chain::FromBridgedChainMessagesDeliveryProof,
|
||||
target_chain::FromBridgedChainMessagesProof, MessagePayload,
|
||||
};
|
||||
use bp_pezkuwi_core::teyrchains::ParaHash;
|
||||
use pezbp_runtime::{AccountIdOf, Chain, HashOf, Teyrchain};
|
||||
use codec::Encode;
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezpallet_bridge_messages::{
|
||||
benchmarking::{MessageDeliveryProofParams, MessageProofParams},
|
||||
messages_generation::{
|
||||
encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof,
|
||||
prepare_messages_storage_proof,
|
||||
},
|
||||
BridgedChainOf, LaneIdOf, ThisChainOf,
|
||||
};
|
||||
use pezsp_runtime::traits::{Header, Zero};
|
||||
use pezsp_std::prelude::*;
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
/// Prepare inbound bridge message according to given message proof parameters.
|
||||
fn prepare_inbound_message<LaneId>(
|
||||
params: &MessageProofParams<LaneId>,
|
||||
successful_dispatch_message_generator: impl Fn(usize) -> MessagePayload,
|
||||
) -> MessagePayload {
|
||||
let expected_size = params.proof_params.db_size.unwrap_or(0) as usize;
|
||||
|
||||
// if we don't need a correct message, then we may just return some random blob
|
||||
if !params.is_successful_dispatch_expected {
|
||||
return vec![0u8; expected_size];
|
||||
}
|
||||
|
||||
// else let's prepare successful message.
|
||||
let msg = successful_dispatch_message_generator(expected_size);
|
||||
assert!(
|
||||
msg.len() >= expected_size,
|
||||
"msg.len(): {} does not match expected_size: {}",
|
||||
expected_size,
|
||||
msg.len()
|
||||
);
|
||||
msg
|
||||
}
|
||||
|
||||
/// Prepare proof of messages for the `receive_messages_proof` call.
|
||||
///
|
||||
/// In addition to returning valid messages proof, environment is prepared to verify this message
|
||||
/// proof.
|
||||
///
|
||||
/// This method is intended to be used when benchmarking pezpallet, linked to the chain that
|
||||
/// uses GRANDPA finality. For teyrchains, please use the `prepare_message_proof_from_teyrchain`
|
||||
/// function.
|
||||
pub fn prepare_message_proof_from_grandpa_chain<R, FI, MI>(
|
||||
params: MessageProofParams<LaneIdOf<R, MI>>,
|
||||
message_generator: impl Fn(usize) -> MessagePayload,
|
||||
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChainOf<R, MI>>, LaneIdOf<R, MI>>, Weight)
|
||||
where
|
||||
R: pezpallet_bridge_grandpa::Config<FI, BridgedChain = BridgedChainOf<R, MI>>
|
||||
+ pezpallet_bridge_messages::Config<
|
||||
MI,
|
||||
BridgedHeaderChain = pezpallet_bridge_grandpa::Pezpallet<R, FI>,
|
||||
>,
|
||||
FI: 'static,
|
||||
MI: 'static,
|
||||
{
|
||||
// prepare storage proof
|
||||
let (state_root, storage_proof) = prepare_messages_storage_proof::<
|
||||
BridgedChainOf<R, MI>,
|
||||
ThisChainOf<R, MI>,
|
||||
LaneIdOf<R, MI>,
|
||||
>(
|
||||
params.lane,
|
||||
params.message_nonces.clone(),
|
||||
params.outbound_lane_data.clone(),
|
||||
params.proof_params,
|
||||
|_| prepare_inbound_message(¶ms, &message_generator),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
// update runtime storage
|
||||
let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(state_root);
|
||||
|
||||
(
|
||||
FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane: params.lane,
|
||||
nonces_start: *params.message_nonces.start(),
|
||||
nonces_end: *params.message_nonces.end(),
|
||||
},
|
||||
Weight::MAX / 1000,
|
||||
)
|
||||
}
|
||||
|
||||
/// Prepare proof of messages for the `receive_messages_proof` call.
|
||||
///
|
||||
/// In addition to returning valid messages proof, environment is prepared to verify this message
|
||||
/// proof.
|
||||
///
|
||||
/// This method is intended to be used when benchmarking pezpallet, linked to the chain that
|
||||
/// uses teyrchain finality. For GRANDPA chains, please use the
|
||||
/// `prepare_message_proof_from_grandpa_chain` function.
|
||||
pub fn prepare_message_proof_from_teyrchain<R, PI, MI>(
|
||||
params: MessageProofParams<LaneIdOf<R, MI>>,
|
||||
message_generator: impl Fn(usize) -> MessagePayload,
|
||||
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChainOf<R, MI>>, LaneIdOf<R, MI>>, Weight)
|
||||
where
|
||||
R: pezpallet_bridge_teyrchains::Config<PI> + pezpallet_bridge_messages::Config<MI>,
|
||||
PI: 'static,
|
||||
MI: 'static,
|
||||
BridgedChainOf<R, MI>: Chain<Hash = ParaHash> + Teyrchain,
|
||||
{
|
||||
// prepare storage proof
|
||||
let (state_root, storage_proof) = prepare_messages_storage_proof::<
|
||||
BridgedChainOf<R, MI>,
|
||||
ThisChainOf<R, MI>,
|
||||
LaneIdOf<R, MI>,
|
||||
>(
|
||||
params.lane,
|
||||
params.message_nonces.clone(),
|
||||
params.outbound_lane_data.clone(),
|
||||
params.proof_params,
|
||||
|_| prepare_inbound_message(¶ms, &message_generator),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
// update runtime storage
|
||||
let (_, bridged_header_hash) =
|
||||
insert_header_to_teyrchains_pallet::<R, PI, BridgedChainOf<R, MI>>(state_root);
|
||||
|
||||
(
|
||||
FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane: params.lane,
|
||||
nonces_start: *params.message_nonces.start(),
|
||||
nonces_end: *params.message_nonces.end(),
|
||||
},
|
||||
Weight::MAX / 1000,
|
||||
)
|
||||
}
|
||||
|
||||
/// Prepare proof of messages delivery for the `receive_messages_delivery_proof` call.
|
||||
///
|
||||
/// This method is intended to be used when benchmarking pezpallet, linked to the chain that
|
||||
/// uses GRANDPA finality. For teyrchains, please use the
|
||||
/// `prepare_message_delivery_proof_from_teyrchain` function.
|
||||
pub fn prepare_message_delivery_proof_from_grandpa_chain<R, FI, MI>(
|
||||
params: MessageDeliveryProofParams<AccountIdOf<ThisChainOf<R, MI>>, LaneIdOf<R, MI>>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<R, MI>>, LaneIdOf<R, MI>>
|
||||
where
|
||||
R: pezpallet_bridge_grandpa::Config<FI, BridgedChain = BridgedChainOf<R, MI>>
|
||||
+ pezpallet_bridge_messages::Config<
|
||||
MI,
|
||||
BridgedHeaderChain = pezpallet_bridge_grandpa::Pezpallet<R, FI>,
|
||||
>,
|
||||
FI: 'static,
|
||||
MI: 'static,
|
||||
{
|
||||
// prepare storage proof
|
||||
let lane = params.lane;
|
||||
let (state_root, storage_proof) = prepare_message_delivery_storage_proof::<
|
||||
BridgedChainOf<R, MI>,
|
||||
ThisChainOf<R, MI>,
|
||||
LaneIdOf<R, MI>,
|
||||
>(params.lane, params.inbound_lane_data, params.proof_params);
|
||||
|
||||
// update runtime storage
|
||||
let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(state_root);
|
||||
|
||||
FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: bridged_header_hash.into(),
|
||||
storage_proof,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare proof of messages delivery for the `receive_messages_delivery_proof` call.
|
||||
///
|
||||
/// This method is intended to be used when benchmarking pezpallet, linked to the chain that
|
||||
/// uses teyrchain finality. For GRANDPA chains, please use the
|
||||
/// `prepare_message_delivery_proof_from_grandpa_chain` function.
|
||||
pub fn prepare_message_delivery_proof_from_teyrchain<R, PI, MI>(
|
||||
params: MessageDeliveryProofParams<AccountIdOf<ThisChainOf<R, MI>>, LaneIdOf<R, MI>>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<R, MI>>, LaneIdOf<R, MI>>
|
||||
where
|
||||
R: pezpallet_bridge_teyrchains::Config<PI> + pezpallet_bridge_messages::Config<MI>,
|
||||
PI: 'static,
|
||||
MI: 'static,
|
||||
BridgedChainOf<R, MI>: Chain<Hash = ParaHash> + Teyrchain,
|
||||
{
|
||||
// prepare storage proof
|
||||
let lane = params.lane;
|
||||
let (state_root, storage_proof) = prepare_message_delivery_storage_proof::<
|
||||
BridgedChainOf<R, MI>,
|
||||
ThisChainOf<R, MI>,
|
||||
LaneIdOf<R, MI>,
|
||||
>(params.lane, params.inbound_lane_data, params.proof_params);
|
||||
|
||||
// update runtime storage
|
||||
let (_, bridged_header_hash) =
|
||||
insert_header_to_teyrchains_pallet::<R, PI, BridgedChainOf<R, MI>>(state_root);
|
||||
|
||||
FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: bridged_header_hash.into(),
|
||||
storage_proof,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert header to the bridge GRANDPA pezpallet.
|
||||
pub(crate) fn insert_header_to_grandpa_pallet<R, GI>(
|
||||
state_root: pezbp_runtime::HashOf<R::BridgedChain>,
|
||||
) -> (pezbp_runtime::BlockNumberOf<R::BridgedChain>, pezbp_runtime::HashOf<R::BridgedChain>)
|
||||
where
|
||||
R: pezpallet_bridge_grandpa::Config<GI>,
|
||||
GI: 'static,
|
||||
R::BridgedChain: pezbp_runtime::Chain,
|
||||
{
|
||||
let bridged_block_number = Zero::zero();
|
||||
let bridged_header = pezbp_runtime::HeaderOf::<R::BridgedChain>::new(
|
||||
bridged_block_number,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let bridged_header_hash = bridged_header.hash();
|
||||
pezpallet_bridge_grandpa::initialize_for_benchmarks::<R, GI>(bridged_header);
|
||||
(bridged_block_number, bridged_header_hash)
|
||||
}
|
||||
|
||||
/// Insert header to the bridge teyrchains pezpallet.
|
||||
pub(crate) fn insert_header_to_teyrchains_pallet<R, PI, PC>(
|
||||
state_root: pezbp_runtime::HashOf<PC>,
|
||||
) -> (pezbp_runtime::BlockNumberOf<PC>, pezbp_runtime::HashOf<PC>)
|
||||
where
|
||||
R: pezpallet_bridge_teyrchains::Config<PI>,
|
||||
PI: 'static,
|
||||
PC: Chain<Hash = ParaHash> + Teyrchain,
|
||||
{
|
||||
let bridged_block_number = Zero::zero();
|
||||
let bridged_header = pezbp_runtime::HeaderOf::<PC>::new(
|
||||
bridged_block_number,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let bridged_header_hash = bridged_header.hash();
|
||||
pezpallet_bridge_teyrchains::initialize_for_benchmarks::<R, PI, PC>(bridged_header);
|
||||
(bridged_block_number, bridged_header_hash)
|
||||
}
|
||||
|
||||
/// Returns callback which generates `BridgeMessage` from Pezkuwi XCM builder based on
|
||||
/// `expected_message_size` for benchmark.
|
||||
pub fn generate_xcm_builder_bridge_message_sample(
|
||||
destination: InteriorLocation,
|
||||
) -> impl Fn(usize) -> MessagePayload {
|
||||
move |expected_message_size| -> MessagePayload {
|
||||
// For XCM bridge hubs, it is the message that
|
||||
// will be pushed further to some XCM queue (XCMP/UMP)
|
||||
let location = xcm::VersionedInteriorLocation::from(destination.clone());
|
||||
let location_encoded_size = location.encoded_size();
|
||||
|
||||
// we don't need to be super-precise with `expected_size` here
|
||||
let xcm_size = expected_message_size.saturating_sub(location_encoded_size);
|
||||
let xcm_data_size = xcm_size.saturating_sub(
|
||||
// minus empty instruction size
|
||||
Instruction::<()>::ExpectPallet {
|
||||
index: 0,
|
||||
name: vec![],
|
||||
module_name: vec![],
|
||||
crate_major: 0,
|
||||
min_crate_minor: 0,
|
||||
}
|
||||
.encoded_size(),
|
||||
);
|
||||
|
||||
tracing::trace!(
|
||||
target: "runtime::bridge-benchmarks",
|
||||
%expected_message_size, %location_encoded_size, %xcm_size, %xcm_data_size,
|
||||
"generate_xcm_builder_bridge_message_sample"
|
||||
);
|
||||
|
||||
let xcm = xcm::VersionedXcm::<()>::from(Xcm(vec![Instruction::<()>::ExpectPallet {
|
||||
index: 0,
|
||||
name: vec![42; xcm_data_size],
|
||||
module_name: vec![],
|
||||
crate_major: 0,
|
||||
min_crate_minor: 0,
|
||||
}]));
|
||||
|
||||
// this is the `BridgeMessage` from pezkuwi xcm builder, but it has no constructor
|
||||
// or public fields, so just tuple
|
||||
// (double encoding, because `.encode()` is called on original Xcm BLOB when it is pushed
|
||||
// to the storage)
|
||||
(location, xcm).encode().encode()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A mock runtime for testing different stuff in the crate.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, MessageDispatch},
|
||||
ChainWithMessages, HashedLaneId, LaneIdType, MessageNonce,
|
||||
};
|
||||
use bp_relayers::{PayRewardFromAccount, RewardsAccountParams};
|
||||
use pezbp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Teyrchain};
|
||||
use bp_teyrchains::SingleParaStoredHeaderDataBuilder;
|
||||
use codec::Encode;
|
||||
use pezframe_support::{
|
||||
derive_impl, parameter_types,
|
||||
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight},
|
||||
};
|
||||
use pezpallet_transaction_payment::Multiplier;
|
||||
use pezsp_runtime::{
|
||||
testing::H256,
|
||||
traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8},
|
||||
FixedPointNumber, Perquintill, StateVersion,
|
||||
};
|
||||
|
||||
/// Account identifier at `ThisChain`.
|
||||
pub type ThisChainAccountId = u64;
|
||||
/// Balance at `ThisChain`.
|
||||
pub type ThisChainBalance = u64;
|
||||
/// Block number at `ThisChain`.
|
||||
pub type ThisChainBlockNumber = u32;
|
||||
/// Hash at `ThisChain`.
|
||||
pub type ThisChainHash = H256;
|
||||
/// Hasher at `ThisChain`.
|
||||
pub type ThisChainHasher = BlakeTwo256;
|
||||
/// Runtime call at `ThisChain`.
|
||||
pub type ThisChainRuntimeCall = RuntimeCall;
|
||||
/// Header of `ThisChain`.
|
||||
pub type ThisChainHeader = pezsp_runtime::generic::Header<ThisChainBlockNumber, ThisChainHasher>;
|
||||
/// Block of `ThisChain`.
|
||||
pub type ThisChainBlock = pezframe_system::mocking::MockBlockU32<TestRuntime>;
|
||||
|
||||
/// Account identifier at the `BridgedChain`.
|
||||
pub type BridgedChainAccountId = u128;
|
||||
/// Balance at the `BridgedChain`.
|
||||
pub type BridgedChainBalance = u128;
|
||||
/// Block number at the `BridgedChain`.
|
||||
pub type BridgedChainBlockNumber = u32;
|
||||
/// Hash at the `BridgedChain`.
|
||||
pub type BridgedChainHash = H256;
|
||||
/// Hasher at the `BridgedChain`.
|
||||
pub type BridgedChainHasher = BlakeTwo256;
|
||||
/// Header of the `BridgedChain`.
|
||||
pub type BridgedChainHeader =
|
||||
pezsp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
|
||||
|
||||
/// Rewards payment procedure.
|
||||
pub type TestPaymentProcedure =
|
||||
PayRewardFromAccount<Balances, ThisChainAccountId, TestLaneIdType, RewardBalance>;
|
||||
/// Stake that we are using in tests.
|
||||
pub type TestStake = ConstU64<5_000>;
|
||||
/// Stake and slash mechanism to use in tests.
|
||||
pub type TestStakeAndSlash = pezpallet_bridge_relayers::StakeAndSlashNamed<
|
||||
ThisChainAccountId,
|
||||
ThisChainBlockNumber,
|
||||
Balances,
|
||||
ReserveId,
|
||||
TestStake,
|
||||
ConstU32<8>,
|
||||
>;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
pub type TestLaneIdType = HashedLaneId;
|
||||
/// Lane that we're using in tests.
|
||||
pub fn test_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 2).unwrap()
|
||||
}
|
||||
/// Reward measurement type.
|
||||
pub type RewardBalance = u32;
|
||||
|
||||
/// Bridged chain id used in tests.
|
||||
pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg";
|
||||
/// Maximal extrinsic size at the `BridgedChain`.
|
||||
pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
|
||||
|
||||
pezframe_support::construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Utility: pezpallet_utility,
|
||||
Balances: pezpallet_balances::{Pezpallet, Call, Storage, Config<T>, Event<T>},
|
||||
TransactionPayment: pezpallet_transaction_payment::{Pezpallet, Storage, Event<T>},
|
||||
BridgeRelayers: pezpallet_bridge_relayers::{Pezpallet, Call, Storage, Event<T>},
|
||||
BridgeGrandpa: pezpallet_bridge_grandpa::{Pezpallet, Call, Storage, Event<T>},
|
||||
BridgeTeyrchains: pezpallet_bridge_teyrchains::{Pezpallet, Call, Storage, Event<T>},
|
||||
BridgeMessages: pezpallet_bridge_messages::{Pezpallet, Call, Storage, Event<T>, Config<T>},
|
||||
}
|
||||
}
|
||||
|
||||
crate::generate_bridge_reject_obsolete_headers_and_messages! {
|
||||
ThisChainRuntimeCall, ThisChainAccountId,
|
||||
BridgeGrandpa, BridgeTeyrchains, BridgeMessages
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BridgedParasPalletName: &'static str = "Paras";
|
||||
pub const ExistentialDeposit: ThisChainBalance = 500;
|
||||
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
||||
pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
|
||||
pub const TransactionBaseFee: ThisChainBalance = 0;
|
||||
pub const TransactionByteFee: ThisChainBalance = 1;
|
||||
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000);
|
||||
pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128);
|
||||
pub MaximumMultiplier: Multiplier = pezsp_runtime::traits::Bounded::max_value();
|
||||
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Hash = ThisChainHash;
|
||||
type Hashing = ThisChainHasher;
|
||||
type AccountId = ThisChainAccountId;
|
||||
type Block = ThisChainBlock;
|
||||
type AccountData = pezpallet_balances::AccountData<ThisChainBalance>;
|
||||
}
|
||||
|
||||
impl pezpallet_utility::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type PalletsOrigin = OriginCaller;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_transaction_payment::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_transaction_payment::Config for TestRuntime {
|
||||
type OnChargeTransaction = pezpallet_transaction_payment::FungibleAdapter<Balances, ()>;
|
||||
type OperationalFeeMultiplier = ConstU8<5>;
|
||||
type WeightToFee = IdentityFee<ThisChainBalance>;
|
||||
type LengthToFee = ConstantMultiplier<ThisChainBalance, TransactionByteFee>;
|
||||
type FeeMultiplierUpdate = pezpallet_transaction_payment::TargetedFeeAdjustment<
|
||||
TestRuntime,
|
||||
TargetBlockFullness,
|
||||
AdjustmentVariable,
|
||||
MinimumMultiplier,
|
||||
MaximumMultiplier,
|
||||
>;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = BridgedUnderlyingChain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<4>;
|
||||
type FreeHeadersInterval = ConstU32<1_024>;
|
||||
type HeadersToKeep = ConstU32<8>;
|
||||
type WeightInfo = pezpallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_teyrchains::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgesGrandpaPalletInstance = ();
|
||||
type ParasPalletName = BridgedParasPalletName;
|
||||
type ParaStoredHeaderDataBuilder =
|
||||
SingleParaStoredHeaderDataBuilder<BridgedUnderlyingTeyrchain>;
|
||||
type HeadsToKeep = ConstU32<8>;
|
||||
type MaxParaHeadDataSize = ConstU32<1024>;
|
||||
type WeightInfo = pezpallet_bridge_teyrchains::weights::BridgeWeight<TestRuntime>;
|
||||
type OnNewHead = ();
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = pezpallet_bridge_messages::weights::BridgeWeight<TestRuntime>;
|
||||
|
||||
type OutboundPayload = Vec<u8>;
|
||||
type InboundPayload = Vec<u8>;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
type DeliveryPayments = ();
|
||||
type DeliveryConfirmationPayments = pezpallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter<
|
||||
TestRuntime,
|
||||
(),
|
||||
(),
|
||||
ConstU32<100_000>,
|
||||
>;
|
||||
type OnMessagesDelivered = ();
|
||||
|
||||
type MessageDispatch = DummyMessageDispatch;
|
||||
|
||||
type ThisChain = ThisUnderlyingChain;
|
||||
type BridgedChain = BridgedUnderlyingChain;
|
||||
type BridgedHeaderChain = BridgeGrandpa;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_relayers::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RewardBalance = RewardBalance;
|
||||
type Reward = RewardsAccountParams<pezpallet_bridge_messages::LaneIdOf<TestRuntime, ()>>;
|
||||
type PaymentProcedure = TestPaymentProcedure;
|
||||
type StakeAndSlash = TestStakeAndSlash;
|
||||
type Balance = ThisChainBalance;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
/// Dummy message dispatcher.
|
||||
pub struct DummyMessageDispatch;
|
||||
|
||||
impl DummyMessageDispatch {
|
||||
pub fn deactivate(lane: TestLaneIdType) {
|
||||
pezframe_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for DummyMessageDispatch {
|
||||
type DispatchPayload = Vec<u8>;
|
||||
type DispatchLevelResult = ();
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
pezframe_support::storage::unhashed::take::<bool>(&(b"inactive", lane).encode()[..]) !=
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
_message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
_: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
||||
}
|
||||
}
|
||||
|
||||
/// Underlying chain of `ThisChain`.
|
||||
pub struct ThisUnderlyingChain;
|
||||
|
||||
impl Chain for ThisUnderlyingChain {
|
||||
const ID: ChainId = *b"tuch";
|
||||
|
||||
type BlockNumber = ThisChainBlockNumber;
|
||||
type Hash = ThisChainHash;
|
||||
type Hasher = ThisChainHasher;
|
||||
type Header = ThisChainHeader;
|
||||
type AccountId = ThisChainAccountId;
|
||||
type Balance = ThisChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for ThisUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
|
||||
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
|
||||
}
|
||||
|
||||
/// Underlying chain of `BridgedChain`.
|
||||
pub struct BridgedUnderlyingChain;
|
||||
/// Some teyrchain under `BridgedChain` consensus.
|
||||
pub struct BridgedUnderlyingTeyrchain;
|
||||
|
||||
impl Chain for BridgedUnderlyingChain {
|
||||
const ID: ChainId = TEST_BRIDGED_CHAIN_ID;
|
||||
|
||||
type BlockNumber = BridgedChainBlockNumber;
|
||||
type Hash = BridgedChainHash;
|
||||
type Hasher = BridgedChainHasher;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = BridgedChainAccountId;
|
||||
type Balance = BridgedChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for BridgedUnderlyingChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 16;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 64;
|
||||
}
|
||||
|
||||
impl ChainWithMessages for BridgedUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
|
||||
}
|
||||
|
||||
impl Chain for BridgedUnderlyingTeyrchain {
|
||||
const ID: ChainId = *b"bupc";
|
||||
|
||||
type BlockNumber = BridgedChainBlockNumber;
|
||||
type Hash = BridgedChainHash;
|
||||
type Hasher = BridgedChainHasher;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = BridgedChainAccountId;
|
||||
type Balance = BridgedChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for BridgedUnderlyingTeyrchain {
|
||||
const TEYRCHAIN_ID: u32 = 42;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
/// Run test within test externalities.
|
||||
pub fn run_test(test: impl FnOnce()) {
|
||||
pezsp_io::TestExternalities::new(Default::default()).execute_with(test)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Everything required to run benchmarks of teyrchains finality module.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::messages_benchmarking::insert_header_to_grandpa_pallet;
|
||||
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
|
||||
use pezbp_runtime::{grow_storage_value, record_all_trie_keys, Chain, UnverifiedStorageProofParams};
|
||||
use bp_teyrchains::{
|
||||
teyrchain_head_storage_key_at_source, RelayBlockHash, RelayBlockHasher, RelayBlockNumber,
|
||||
};
|
||||
use codec::Encode;
|
||||
use pezframe_support::traits::Get;
|
||||
use pezsp_std::prelude::*;
|
||||
use pezsp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
|
||||
|
||||
/// Prepare proof of messages for the `receive_messages_proof` call.
|
||||
///
|
||||
/// In addition to returning valid messages proof, environment is prepared to verify this message
|
||||
/// proof.
|
||||
pub fn prepare_teyrchain_heads_proof<R, PI>(
|
||||
teyrchains: &[ParaId],
|
||||
teyrchain_head_size: u32,
|
||||
proof_params: UnverifiedStorageProofParams,
|
||||
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>)
|
||||
where
|
||||
R: pezpallet_bridge_teyrchains::Config<PI>
|
||||
+ pezpallet_bridge_grandpa::Config<R::BridgesGrandpaPalletInstance>,
|
||||
PI: 'static,
|
||||
<R as pezpallet_bridge_grandpa::Config<R::BridgesGrandpaPalletInstance>>::BridgedChain:
|
||||
Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash>,
|
||||
{
|
||||
let teyrchain_head = ParaHead(vec![0u8; teyrchain_head_size as usize]);
|
||||
|
||||
// insert all heads to the trie
|
||||
let mut teyrchain_heads = Vec::with_capacity(teyrchains.len());
|
||||
let mut storage_keys = Vec::with_capacity(teyrchains.len());
|
||||
let mut state_root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
{
|
||||
let mut trie =
|
||||
TrieDBMutBuilderV1::<RelayBlockHasher>::new(&mut mdb, &mut state_root).build();
|
||||
|
||||
// insert teyrchain heads
|
||||
for (i, teyrchain) in teyrchains.into_iter().enumerate() {
|
||||
let storage_key =
|
||||
teyrchain_head_storage_key_at_source(R::ParasPalletName::get(), *teyrchain);
|
||||
let leaf_data = if i == 0 {
|
||||
grow_storage_value(teyrchain_head.encode(), &proof_params)
|
||||
} else {
|
||||
teyrchain_head.encode()
|
||||
};
|
||||
trie.insert(&storage_key.0, &leaf_data)
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
storage_keys.push(storage_key);
|
||||
teyrchain_heads.push((*teyrchain, teyrchain_head.hash()))
|
||||
}
|
||||
}
|
||||
|
||||
// generate heads storage proof
|
||||
let proof = record_all_trie_keys::<LayoutV1<RelayBlockHasher>, _>(&mdb, &state_root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
|
||||
let (relay_block_number, relay_block_hash) =
|
||||
insert_header_to_grandpa_pallet::<R, R::BridgesGrandpaPalletInstance>(state_root);
|
||||
|
||||
(relay_block_number, relay_block_hash, ParaHeadsProof { storage_proof: proof }, teyrchain_heads)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
[package]
|
||||
name = "bp-bridge-hub-pezcumulus"
|
||||
description = "Primitives for BridgeHub teyrchain runtimes."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-bridge-hub-pezcumulus"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[package.metadata.pezkuwi-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Bridge Dependencies
|
||||
bp-messages = { workspace = true }
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Based Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
teyrchains-common = { workspace = true }
|
||||
|
||||
# Pezkuwi Dependencies
|
||||
pezkuwi-primitives = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-messages/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"pezbp-runtime/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezkuwi-primitives/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-std/std",
|
||||
"teyrchains-common/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezkuwi-primitives/runtime-benchmarks",
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"teyrchains-common/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of all Pezcumulus-based bridge hubs.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use bp_pezkuwi_core::{
|
||||
AccountId, AccountInfoStorageMapKeyProvider, AccountPublic, Balance, BlockNumber, Hash, Hasher,
|
||||
Hashing, Header, Nonce, Perbill, Signature, SignedBlock, UncheckedExtrinsic,
|
||||
EXTRA_STORAGE_PROOF_SIZE, TX_EXTRA_BYTES,
|
||||
};
|
||||
|
||||
pub use teyrchains_common::{
|
||||
AVERAGE_ON_INITIALIZE_RATIO, MAXIMUM_BLOCK_WEIGHT, MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING,
|
||||
NORMAL_DISPATCH_RATIO, SLOT_DURATION,
|
||||
};
|
||||
|
||||
use bp_messages::*;
|
||||
use bp_pezkuwi_core::SuffixedCommonTransactionExtension;
|
||||
use pezbp_runtime::extensions::{
|
||||
BridgeRejectObsoleteHeadersAndMessages, RefundBridgedTeyrchainMessagesSchema,
|
||||
};
|
||||
use pezframe_support::{
|
||||
dispatch::DispatchClass,
|
||||
parameter_types,
|
||||
pezsp_runtime::{MultiAddress, MultiSigner},
|
||||
weights::constants,
|
||||
};
|
||||
use pezframe_system::limits;
|
||||
use pezsp_std::time::Duration;
|
||||
|
||||
/// Average block time for Pezcumulus-based teyrchains
|
||||
pub const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(SLOT_DURATION);
|
||||
|
||||
/// Maximal asset hub header size.
|
||||
pub const MAX_ASSET_HUB_HEADER_SIZE: u32 = 4_096;
|
||||
|
||||
/// Maximal bridge hub header size.
|
||||
pub const MAX_BRIDGE_HUB_HEADER_SIZE: u32 = 4_096;
|
||||
|
||||
parameter_types! {
|
||||
/// Size limit of the Pezcumulus-based bridge hub blocks.
|
||||
pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio(
|
||||
5 * 1024 * 1024,
|
||||
NORMAL_DISPATCH_RATIO,
|
||||
);
|
||||
|
||||
/// Importing a block with 0 Extrinsics.
|
||||
pub const BlockExecutionWeight: Weight = Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS, 0)
|
||||
.saturating_mul(5_000_000);
|
||||
/// Executing a NO-OP `System::remarks` Extrinsic.
|
||||
pub const ExtrinsicBaseWeight: Weight = Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS, 0)
|
||||
.saturating_mul(125_000);
|
||||
|
||||
/// Weight limit of the Pezcumulus-based bridge hub blocks.
|
||||
pub BlockWeights: limits::BlockWeights = limits::BlockWeights::builder()
|
||||
.base_block(BlockExecutionWeight::get())
|
||||
.for_class(DispatchClass::all(), |weights| {
|
||||
weights.base_extrinsic = ExtrinsicBaseWeight::get();
|
||||
})
|
||||
.for_class(DispatchClass::Normal, |weights| {
|
||||
weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT);
|
||||
})
|
||||
.for_class(DispatchClass::Operational, |weights| {
|
||||
weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT);
|
||||
// Operational transactions have an extra reserved space, so that they
|
||||
// are included even if block reached `MAXIMUM_BLOCK_WEIGHT`.
|
||||
weights.reserved = Some(
|
||||
MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT,
|
||||
);
|
||||
})
|
||||
.avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO)
|
||||
.build_or_panic();
|
||||
|
||||
/// Weight limit of the Pezcumulus-based bridge hub blocks when async backing is enabled.
|
||||
pub BlockWeightsForAsyncBacking: limits::BlockWeights = limits::BlockWeights::builder()
|
||||
.base_block(BlockExecutionWeight::get())
|
||||
.for_class(DispatchClass::all(), |weights| {
|
||||
weights.base_extrinsic = ExtrinsicBaseWeight::get();
|
||||
})
|
||||
.for_class(DispatchClass::Normal, |weights| {
|
||||
weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING);
|
||||
})
|
||||
.for_class(DispatchClass::Operational, |weights| {
|
||||
weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING);
|
||||
// Operational transactions have an extra reserved space, so that they
|
||||
// are included even if block reached `MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING`.
|
||||
weights.reserved = Some(
|
||||
MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING,
|
||||
);
|
||||
})
|
||||
.avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO)
|
||||
.build_or_panic();
|
||||
}
|
||||
|
||||
/// Public key of the chain account that may be used to verify signatures.
|
||||
pub type AccountSigner = MultiSigner;
|
||||
|
||||
/// The address format for describing accounts.
|
||||
pub type Address = MultiAddress<AccountId, ()>;
|
||||
|
||||
// Note about selecting values of two following constants:
|
||||
//
|
||||
// Normal transactions have limit of 75% of 1/2 second weight for Pezcumulus teyrchains. Let's keep
|
||||
// some reserve for the rest of stuff there => let's select values that fit in 50% of maximal limit.
|
||||
//
|
||||
// Using current constants, the limit would be:
|
||||
//
|
||||
// `75% * WEIGHT_REF_TIME_PER_SECOND * 1 / 2 * 50% = 0.75 * 1_000_000_000_000 / 2 * 0.5 =
|
||||
// 187_500_000_000`
|
||||
//
|
||||
// According to (preliminary) weights of messages pezpallet, cost of additional message is zero and the
|
||||
// cost of additional relayer is `8_000_000 + db read + db write`. Let's say we want no more than
|
||||
// 4096 unconfirmed messages (no any scientific justification for that - it just looks large
|
||||
// enough). And then we can't have more than 4096 relayers. E.g. for 1024 relayers is (using
|
||||
// `RocksDbWeight`):
|
||||
//
|
||||
// `1024 * (8_000_000 + db read + db write) = 1024 * (8_000_000 + 25_000_000 + 100_000_000) =
|
||||
// 136_192_000_000`
|
||||
//
|
||||
// So 1024 looks like good approximation for the number of relayers. If something is wrong in those
|
||||
// assumptions, or something will change, it shall be caught by the
|
||||
// `ensure_able_to_receive_confirmation` test.
|
||||
|
||||
/// Maximal number of unrewarded relayer entries at inbound lane for Pezcumulus-based teyrchains.
|
||||
/// Note: this value is security-relevant, decreasing it should not be done without careful
|
||||
/// analysis (like the one above).
|
||||
pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024;
|
||||
|
||||
/// Maximal number of unconfirmed messages at inbound lane for Pezcumulus-based teyrchains.
|
||||
/// Note: this value is security-relevant, decreasing it should not be done without careful
|
||||
/// analysis (like the one above).
|
||||
pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 4096;
|
||||
|
||||
/// Signed extension that is used by all bridge hubs.
|
||||
pub type TransactionExtension = SuffixedCommonTransactionExtension<(
|
||||
BridgeRejectObsoleteHeadersAndMessages,
|
||||
RefundBridgedTeyrchainMessagesSchema,
|
||||
)>;
|
||||
@@ -0,0 +1,59 @@
|
||||
[package]
|
||||
name = "bp-pezkuwi-bulletin"
|
||||
description = "Primitives of Pezkuwi Bulletin chain runtime."
|
||||
version = "0.4.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-pezkuwi-bulletin"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[package.metadata.pezkuwi-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-messages = { workspace = true }
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Based Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"scale-info/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,228 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Pezkuwi Bulletin Chain primitives.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use bp_messages::{ChainWithMessages, MessageNonce};
|
||||
use pezbp_runtime::{
|
||||
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis,
|
||||
extensions::{
|
||||
CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion,
|
||||
CheckWeight, GenericTransactionExtension, GenericTransactionExtensionSchema,
|
||||
},
|
||||
Chain, ChainId, TransactionEra,
|
||||
};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::{
|
||||
dispatch::DispatchClass,
|
||||
parameter_types,
|
||||
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
|
||||
};
|
||||
use pezframe_system::limits;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
impl_tx_ext_default, traits::Dispatchable, transaction_validity::TransactionValidityError,
|
||||
Perbill, StateVersion,
|
||||
};
|
||||
|
||||
// This chain reuses most of Pezkuwi primitives.
|
||||
pub use bp_pezkuwi_core::{
|
||||
AccountAddress, AccountId, Balance, Block, BlockNumber, Hash, Hasher, Header, Nonce, Signature,
|
||||
SignedBlock, UncheckedExtrinsic, AVERAGE_HEADER_SIZE, EXTRA_STORAGE_PROOF_SIZE,
|
||||
MAX_MANDATORY_HEADER_SIZE, REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
|
||||
};
|
||||
|
||||
/// Maximal number of GRANDPA authorities at Pezkuwi Bulletin chain.
|
||||
pub const MAX_AUTHORITIES_COUNT: u32 = 100;
|
||||
|
||||
/// Name of the With-Pezkuwi Bulletin chain GRANDPA pezpallet instance that is deployed at bridged
|
||||
/// chains.
|
||||
pub const WITH_PEZKUWI_BULLETIN_GRANDPA_PALLET_NAME: &str = "BridgePezkuwiBulletinGrandpa";
|
||||
/// Name of the With-Pezkuwi Bulletin chain messages pezpallet instance that is deployed at bridged
|
||||
/// chains.
|
||||
pub const WITH_PEZKUWI_BULLETIN_MESSAGES_PALLET_NAME: &str = "BridgePezkuwiBulletinMessages";
|
||||
|
||||
// There are fewer system operations on this chain (e.g. staking, governance, etc.). Use a higher
|
||||
// percentage of the block for data storage.
|
||||
const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(90);
|
||||
|
||||
// Re following constants - we are using the same values at Pezcumulus teyrchains. They are limited
|
||||
// by the maximal transaction weight/size. Since block limits at Bulletin Chain are larger than
|
||||
// at the Pezcumulus Bridge Hubs, we could reuse the same values.
|
||||
|
||||
/// Maximal number of unrewarded relayer entries at inbound lane for Pezcumulus-based teyrchains.
|
||||
pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024;
|
||||
|
||||
/// Maximal number of unconfirmed messages at inbound lane for Pezcumulus-based teyrchains.
|
||||
pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 4096;
|
||||
|
||||
/// This signed extension is used to ensure that the chain transactions are signed by proper
|
||||
pub type ValidateSigned = GenericTransactionExtensionSchema<(), ()>;
|
||||
|
||||
/// Signed extension schema, used by Pezkuwi Bulletin.
|
||||
pub type TransactionExtensionSchema = GenericTransactionExtension<(
|
||||
(
|
||||
CheckNonZeroSender,
|
||||
CheckSpecVersion,
|
||||
CheckTxVersion,
|
||||
CheckGenesis<Hash>,
|
||||
CheckEra<Hash>,
|
||||
CheckNonce<Nonce>,
|
||||
CheckWeight,
|
||||
),
|
||||
ValidateSigned,
|
||||
)>;
|
||||
|
||||
/// Transaction extension, used by Pezkuwi Bulletin.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub struct TransactionExtension(TransactionExtensionSchema);
|
||||
|
||||
impl<C> pezsp_runtime::traits::TransactionExtension<C> for TransactionExtension
|
||||
where
|
||||
C: Dispatchable,
|
||||
{
|
||||
const IDENTIFIER: &'static str = "Not needed.";
|
||||
type Implicit =
|
||||
<TransactionExtensionSchema as pezsp_runtime::traits::TransactionExtension<C>>::Implicit;
|
||||
|
||||
fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
|
||||
<TransactionExtensionSchema as pezsp_runtime::traits::TransactionExtension<C>>::implicit(
|
||||
&self.0,
|
||||
)
|
||||
}
|
||||
type Pre = ();
|
||||
type Val = ();
|
||||
|
||||
impl_tx_ext_default!(C; weight validate prepare);
|
||||
}
|
||||
|
||||
impl TransactionExtension {
|
||||
/// Create signed extension from its components.
|
||||
pub fn from_params(
|
||||
spec_version: u32,
|
||||
transaction_version: u32,
|
||||
era: TransactionEra<BlockNumber, Hash>,
|
||||
genesis_hash: Hash,
|
||||
nonce: Nonce,
|
||||
) -> Self {
|
||||
Self(GenericTransactionExtension::new(
|
||||
(
|
||||
(
|
||||
(), // non-zero sender
|
||||
(), // spec version
|
||||
(), // tx version
|
||||
(), // genesis
|
||||
era.frame_era(), // era
|
||||
nonce.into(), // nonce (compact encoding)
|
||||
(), // Check weight
|
||||
),
|
||||
(),
|
||||
),
|
||||
Some((
|
||||
(
|
||||
(),
|
||||
spec_version,
|
||||
transaction_version,
|
||||
genesis_hash,
|
||||
era.signed_payload(genesis_hash),
|
||||
(),
|
||||
(),
|
||||
),
|
||||
(),
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
/// Return transaction nonce.
|
||||
pub fn nonce(&self) -> Nonce {
|
||||
let common_payload = self.0.payload.0;
|
||||
common_payload.5 .0
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
/// We allow for 2 seconds of compute with a 6 second average block time.
|
||||
pub BlockWeights: limits::BlockWeights = limits::BlockWeights::with_sensible_defaults(
|
||||
Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX),
|
||||
NORMAL_DISPATCH_RATIO,
|
||||
);
|
||||
// Note: Max transaction size is 8 MB. Set max block size to 10 MB to facilitate data storage.
|
||||
// This is double the "normal" Relay Chain block length limit.
|
||||
/// Maximal block length at Pezkuwi Bulletin chain.
|
||||
pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio(
|
||||
10 * 1024 * 1024,
|
||||
NORMAL_DISPATCH_RATIO,
|
||||
);
|
||||
}
|
||||
|
||||
/// Pezkuwi Bulletin Chain declaration.
|
||||
pub struct PezkuwiBulletin;
|
||||
|
||||
impl Chain for PezkuwiBulletin {
|
||||
const ID: ChainId = *b"pdbc";
|
||||
|
||||
type BlockNumber = BlockNumber;
|
||||
type Hash = Hash;
|
||||
type Hasher = Hasher;
|
||||
type Header = Header;
|
||||
|
||||
type AccountId = AccountId;
|
||||
// The Bulletin Chain is a permissioned blockchain without any balances. Our `Chain` trait
|
||||
// requires balance type, which is then used by various bridge infrastructure code. However
|
||||
// this code is optional and we are not planning to use it in our bridge.
|
||||
type Balance = Balance;
|
||||
type Nonce = Nonce;
|
||||
type Signature = Signature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
*BlockLength::get().max.get(DispatchClass::Normal)
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
BlockWeights::get()
|
||||
.get(DispatchClass::Normal)
|
||||
.max_extrinsic
|
||||
.unwrap_or(Weight::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for PezkuwiBulletin {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_PEZKUWI_BULLETIN_GRANDPA_PALLET_NAME;
|
||||
const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 =
|
||||
REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE;
|
||||
const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
|
||||
}
|
||||
|
||||
impl ChainWithMessages for PezkuwiBulletin {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
|
||||
WITH_PEZKUWI_BULLETIN_MESSAGES_PALLET_NAME;
|
||||
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce =
|
||||
MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce =
|
||||
MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
|
||||
}
|
||||
|
||||
decl_bridge_finality_runtime_apis!(pezkuwi_bulletin, grandpa);
|
||||
decl_bridge_messages_runtime_apis!(pezkuwi_bulletin, bp_messages::LegacyLaneId);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Complex Relay</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Complex Relay</h1>
|
||||
<p>
|
||||
Both Source Chain and Target Chains have Bridge Messages pallets deployed. They also have required
|
||||
finality pallets deployed - we don't care about finality type here - they can be either Bridge GRANDPA,
|
||||
or Bridge Parachains finality pallets, or any combination of those.<br/>
|
||||
</p>
|
||||
<p>
|
||||
There are 4-6 relayer subprocesses inside the Complex Relayer. They include two message relayers,
|
||||
serving the lane in both directions and 2-4 Complex Relayers (depending on the finality type of Source
|
||||
and Target Chains).<br/>
|
||||
</p>
|
||||
<p>
|
||||
The following diagram shows the way the complex relayer serves the lane in single direction. Everything
|
||||
below may be applied to the opposite direction if you'll swap the Source and Target Chains.
|
||||
</p>
|
||||
<div class="mermaid">
|
||||
sequenceDiagram
|
||||
participant Source Chain
|
||||
participant Complex Relayer
|
||||
participant Target Chain
|
||||
|
||||
Note right of Source Chain: Finalized: 480, Target Finalized: 50, Sent Messages: 42, Confirmed Messages: 42
|
||||
Note left of Target Chain: Finalized: 60, Source Finalized: 420, Received Messages: 42
|
||||
|
||||
Source Chain ->> Source Chain: someone Sends Message 43
|
||||
Source Chain ->> Source Chain: Import and Finalize Block 481
|
||||
|
||||
Source Chain ->> Complex Relayer: notes new outbound message 43 at Source Chain Block 481
|
||||
Note right of Complex Relayer: can't deliver message 43, Source Chain Block 481 is not relayed
|
||||
Complex Relayer ->> Complex Relayer: asks on-demand Finality Relayer to relay Source Chain Block 481
|
||||
|
||||
Source Chain ->> Complex Relayer: Read Finality Proof of Block 481
|
||||
Complex Relayer ->> Target Chain: Submit Finality Proof of Block 481
|
||||
Target Chain ->> Target Chain: Import and Finalize Block 61
|
||||
Note left of Target Chain: Finalized: 61, Source Finalized: 481, Received Messages: 42
|
||||
|
||||
Source Chain ->> Complex Relayer: Read Proof of Message 43 at Block 481
|
||||
Complex Relayer ->> Target Chain: Submit Proof of Message 43 at Block 481
|
||||
Target Chain ->> Target Chain: Import and Finalize Block 62
|
||||
Note left of Target Chain: Finalized: 62, Source Finalized: 481, Received Messages: { rewarded: 42, messages-relayer-account: [43] }
|
||||
|
||||
Target Chain ->> Complex Relayer: notes new unrewarded relayer at Target Chain Block 62
|
||||
Note right of Complex Relayer: can't relay delivery confirmations because Target Chain Block 62 is not relayed
|
||||
Complex Relayer ->> Complex Relayer: asks on-demand Finality Relayer to relay Target Chain Block 62
|
||||
|
||||
Target Chain ->> Complex Relayer: Read Finality Proof of Block 62
|
||||
Complex Relayer ->> Source Chain: Submit Finality Proof of Block 62
|
||||
Source Chain ->> Source Chain: Import and Finalize Block 482
|
||||
Note right of Source Chain: Finalized: 482, Target Finalized: 62, Confirmed Messages: 42
|
||||
|
||||
Target Chain ->> Complex Relayer: Read Proof of Message 43 Delivery at Block 62
|
||||
Complex Relayer ->> Source Chain: Submit Proof of Message 43 Delivery at Block 612
|
||||
Source Chain ->> Source Chain: rewards messages-relayer-account for delivering message [43]
|
||||
Source Chain ->> Source Chain: prune delivered message 43 from runtime storage
|
||||
Note right of Source Chain: Finalized: 482, Target Finalized: 61, Confirmed Messages: 43
|
||||
|
||||
Source Chain ->> Source Chain: someone Sends Message 44
|
||||
Source Chain ->> Source Chain: Import and Finalize Block 483
|
||||
|
||||
Source Chain ->> Complex Relayer: notes new outbound message 44 at Source Chain Block 483 and new confirmed message 43
|
||||
Note right of Complex Relayer: can't deliver message 44, Source Chain Block 483 is not relayed
|
||||
Complex Relayer ->> Complex Relayer: asks on-demand Finality Relayer to relay Source Chain Block 483
|
||||
|
||||
Source Chain ->> Complex Relayer: Read Finality Proof of Block 483
|
||||
Complex Relayer ->> Target Chain: Submit Finality Proof of Block 483
|
||||
Target Chain ->> Target Chain: Import and Finalize Block 63
|
||||
Note left of Target Chain: Finalized: 63, Source Finalized: 483, Received Messages: { rewarded: 42, messages-relayer-account: [43] }
|
||||
|
||||
Source Chain ->> Complex Relayer: Read Proof of Message 44 and Proof of Message 43 reward at Block 483
|
||||
Complex Relayer ->> Target Chain: Submit Proof of Message 44 and Proof of Message 43 reward at Block 483
|
||||
Target Chain ->> Target Chain: Import and Finalize Block 64
|
||||
Note left of Target Chain: Finalized: 64, Source Finalized: 483, Received Messages: { rewarded: 43, messages-relayer-account: [44] }-->
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@8.8.4/dist/mermaid.min.js"></script>
|
||||
<script>mermaid.initialize({startOnLoad: true})</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>GRANDPA Finality Relay</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>GRANDPA Finality Relay</h1>
|
||||
<p>
|
||||
Source Chain is running GRANDPA Finality Gadget. Bridge GRANDPA finality pallet is deployed at
|
||||
Target Chain runtime. Relayer is configured to relay Source Chain finality to Target Chain.
|
||||
</p>
|
||||
<div class="mermaid">
|
||||
sequenceDiagram
|
||||
participant Source Chain
|
||||
participant Relayer
|
||||
participant Target Chain
|
||||
Note left of Source Chain: Best: 500, Finalized: 480, Authorities Set Index: 42
|
||||
Note right of Target Chain: Uninitialized
|
||||
|
||||
Source Chain ->> Relayer: Read Initialization Data
|
||||
Relayer ->> Target Chain: Initialize Bridge GRANDPA Finality Pallet
|
||||
Note right of Target Chain: Finalized: 480, Authorities Set Index: 42
|
||||
|
||||
Source Chain ->> Source Chain: Import Block 501
|
||||
Source Chain ->> Source Chain: Import Block 502
|
||||
Source Chain ->> Source Chain: Finalize Block 495
|
||||
Source Chain ->> Relayer: Read Finality Proof of Block 495
|
||||
Relayer ->> Target Chain: Finality Proof of Block 495
|
||||
Note right of Target Chain: Finalized: 495, Authorities Set Index: 42
|
||||
|
||||
Source Chain ->> Source Chain: Import Block 503 that changes Authorities Set to 43
|
||||
Source Chain ->> Source Chain: Finalize Block 500
|
||||
Note left of Relayer: Relayer Misses Finality Notification for Block 500
|
||||
|
||||
Source Chain ->> Source Chain: Import Block 504
|
||||
Source Chain ->> Source Chain: Finalize Mandatory Block 503
|
||||
Source Chain ->> Source Chain: Finalize Block 504
|
||||
Source Chain ->> Relayer: Read Finality Proof of Mandatory Block 503
|
||||
Relayer ->> Target Chain: Finality Proof of Block 503
|
||||
Note right of Target Chain: Finalized: 503, Authorities Set Index: 43
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@8.8.4/dist/mermaid.min.js"></script>
|
||||
<script>mermaid.initialize({startOnLoad: true})</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,184 @@
|
||||
# High-Level Bridge Documentation
|
||||
|
||||
This document gives a brief, abstract description of main components that may be found in this repository. If you want
|
||||
to see how we're using them to build Pezkuwichain <> Zagros (Kusama <> Pezkuwi) bridge, please refer to the [Pezkuwi <>
|
||||
Kusama Bridge](./pezkuwi-kusama-bridge-overview.md).
|
||||
|
||||
## Purpose
|
||||
|
||||
This repo contains all components required to build a trustless connection between standalone Bizinikiwi chains, that are
|
||||
using GRANDPA finality, their teyrchains or any combination of those. On top of this connection, we offer a messaging
|
||||
pezpallet that provides means to organize messages exchange.
|
||||
|
||||
On top of that layered infrastructure, anyone may build their own bridge applications - e.g. [XCM
|
||||
messaging](./pezkuwi-kusama-bridge-overview.md), [encoded calls
|
||||
messaging](https://github.com/paritytech/parity-bridges-common/releases/tag/encoded-calls-messaging) and so on.
|
||||
|
||||
## Terminology
|
||||
|
||||
Even though we support (and require) two-way bridging, the documentation will generally talk about a one-sided
|
||||
interaction. That's to say, we will only talk about syncing finality proofs and messages from a _source_ chain to a
|
||||
_target_ chain. This is because the two-sided interaction is really just the one-sided interaction with the source and
|
||||
target chains switched.
|
||||
|
||||
The bridge has both on-chain (pallets) and offchain (relayers) components.
|
||||
|
||||
## On-chain components
|
||||
|
||||
On-chain bridge components are pallets that are deployed at the chain runtime. Finality pallets require deployment at
|
||||
the target chain, while messages pezpallet needs to be deployed at both, source and target chains.
|
||||
|
||||
### Bridge GRANDPA Finality Pezpallet
|
||||
|
||||
A GRANDPA light client of the source chain built into the target chain's runtime. It provides a "source of truth" about
|
||||
the source chain headers which have been finalized. This is useful for higher level applications.
|
||||
|
||||
The pezpallet tracks current GRANDPA authorities set and only accepts finality proofs (GRANDPA justifications), generated
|
||||
by the current authorities set. The GRANDPA protocol itself requires current authorities set to generate explicit
|
||||
justification for the header that enacts next authorities set. Such headers and their finality proofs are called
|
||||
mandatory in the pezpallet and relayer pays no fee for such headers submission.
|
||||
|
||||
The pezpallet does not require all headers to be imported or provided. The relayer itself chooses which headers he wants to
|
||||
submit (with the exception of mandatory headers).
|
||||
|
||||
More: [pezpallet level documentation and code](../modules/grandpa/).
|
||||
|
||||
### Bridge Teyrchains Finality Pezpallet
|
||||
|
||||
Teyrchains are not supposed to have their own finality, so we can't use bridge GRANDPA pezpallet to verify their finality
|
||||
proofs. Instead, they rely on their relay chain finality. The teyrchain header is considered final, when it is accepted
|
||||
by the [`paras`
|
||||
pezpallet](https://github.com/paritytech/polkadot/tree/1a034bd6de0e76721d19aed02a538bcef0787260/runtime/parachains/src/paras)
|
||||
at its relay chain. Obviously, the relay chain block, where it is accepted, must also be finalized by the relay chain
|
||||
GRANDPA gadget.
|
||||
|
||||
That said, the bridge teyrchains pezpallet accepts storage proof of one or several teyrchain heads, inserted to the
|
||||
[`Heads`](https://github.com/paritytech/polkadot/blob/1a034bd6de0e76721d19aed02a538bcef0787260/runtime/parachains/src/paras/mod.rs#L642)
|
||||
map of the [`paras`
|
||||
pezpallet](https://github.com/paritytech/polkadot/tree/1a034bd6de0e76721d19aed02a538bcef0787260/runtime/parachains/src/paras).
|
||||
To verify this storage proof, the pezpallet uses relay chain header, imported earlier by the bridge GRANDPA pezpallet.
|
||||
|
||||
The pezpallet may track multiple teyrchains at once and those teyrchains may use different primitives. So the teyrchain
|
||||
header decoding never happens at the pezpallet level. For maintaining the headers order, the pezpallet uses relay chain header
|
||||
number.
|
||||
|
||||
More: [pezpallet level documentation and code](../modules/teyrchains/).
|
||||
|
||||
### Bridge Messages Pezpallet
|
||||
|
||||
The pezpallet is responsible for queuing messages at the source chain and receiving the messages proofs at the target
|
||||
chain. The messages are sent to the particular _lane_, where they are guaranteed to be received in the same order they
|
||||
are sent. The pezpallet supports many lanes.
|
||||
|
||||
The lane has two ends. Outbound lane end is storing number of messages that have been sent and the number of messages
|
||||
that have been received. Inbound lane end stores the number of messages that have been received and also a map that maps
|
||||
messages to relayers that have delivered those messages to the target chain.
|
||||
|
||||
The pezpallet has three main entrypoints:
|
||||
- the `send_message` may be used by the other runtime pallets to send the messages;
|
||||
- the `receive_messages_proof` is responsible for parsing the messages proof and handing messages over to the dispatch
|
||||
code;
|
||||
- the `receive_messages_delivery_proof` is responsible for parsing the messages delivery proof and rewarding relayers
|
||||
that have delivered the message.
|
||||
|
||||
Many things are abstracted by the pezpallet:
|
||||
- the message itself may mean anything, the pezpallet doesn't care about its content;
|
||||
- the message dispatch happens during delivery, but it is decoupled from the pezpallet code;
|
||||
- the messages proof and messages delivery proof are verified outside of the pezpallet;
|
||||
- the relayers incentivization scheme is defined outside of the pezpallet.
|
||||
|
||||
Outside of the messaging pezpallet, we have a set of adapters, where messages and delivery proofs are regular storage
|
||||
proofs. The proofs are generated at the bridged chain and require bridged chain finality. So messages pezpallet, in this
|
||||
case, depends on one of the finality pallets. The messages are XCM messages and we are using XCM executor to dispatch
|
||||
them on receival. You may find more info in [Pezkuwi <> Kusama Bridge](./pezkuwi-kusama-bridge-overview.md) document.
|
||||
|
||||
More: [pezpallet level documentation and code](../modules/messages/).
|
||||
|
||||
### Bridge Relayers Pezpallet
|
||||
|
||||
The pezpallet is quite simple. It just registers relayer rewards and has an entrypoint to collect them. When the rewards
|
||||
are registered and the reward amount is configured outside of the pezpallet.
|
||||
|
||||
More: [pezpallet level documentation and code](../modules/relayers/).
|
||||
|
||||
## Offchain Components
|
||||
|
||||
Offchain bridge components are separate processes, called relayers. Relayers are connected both to the source chain and
|
||||
target chain nodes. Relayers are reading state of the source chain, compare it to the state of the target chain and, if
|
||||
state at target chain needs to be updated, submits target chain transaction.
|
||||
|
||||
### GRANDPA Finality Relay
|
||||
|
||||
The task of relay is to submit source chain GRANDPA justifications and their corresponding headers to the Bridge GRANDPA
|
||||
Finality Pezpallet, deployed at the target chain. For that, the relay subscribes to the source chain GRANDPA justifications
|
||||
stream and submits every new justification it sees to the target chain GRANDPA light client. In addition, relay is
|
||||
searching for mandatory headers and submits their justifications - without that the pezpallet will be unable to move
|
||||
forward.
|
||||
|
||||
More: [GRANDPA Finality Relay Sequence Diagram](./grandpa-pez-finality-relay.html), [pezpallet level documentation and
|
||||
code](../relays/finality/).
|
||||
|
||||
### Teyrchains Finality Relay
|
||||
|
||||
The relay connects to the source _relay_ chain and the target chain nodes. It doesn't need to connect to the tracked
|
||||
teyrchain nodes. The relay looks at the
|
||||
[`Heads`](https://github.com/paritytech/polkadot/blob/1a034bd6de0e76721d19aed02a538bcef0787260/runtime/parachains/src/paras/mod.rs#L642)
|
||||
map of the [`paras`
|
||||
pezpallet](https://github.com/paritytech/polkadot/tree/1a034bd6de0e76721d19aed02a538bcef0787260/runtime/parachains/src/paras)
|
||||
in source chain, and compares the value with the best teyrchain head, stored in the bridge teyrchains pezpallet at the
|
||||
target chain. If new teyrchain head appears at the relay chain block `B`, the relay process **waits** until header `B`
|
||||
or one of its ancestors appears at the target chain. Once it is available, the storage proof of the map entry is
|
||||
generated and is submitted to the target chain.
|
||||
|
||||
As its on-chain component (which requires bridge GRANDPA pezpallet to be deployed nearby), the teyrchains finality relay
|
||||
requires GRANDPA finality relay to be running in parallel. Without it, the header `B` or any of its children's finality
|
||||
at source won't be relayed at target, and target chain won't be able to verify generated storage proof.
|
||||
|
||||
More: [Teyrchains Finality Relay Sequence Diagram](./teyrchains-pez-finality-relay.html), [code](../relays/teyrchains/).
|
||||
|
||||
### Messages Relay
|
||||
|
||||
Messages relay is actually two relays that are running in a single process: messages delivery relay and delivery
|
||||
confirmation relay. Even though they are more complex and have many caveats, the overall algorithm is the same as in
|
||||
other relays.
|
||||
|
||||
Message delivery relay connects to the source chain and looks at the outbound lane end, waiting until new messages are
|
||||
queued there. Once they appear at the source block `B`, the relay start waiting for the block `B` or its descendant
|
||||
appear at the target chain. Then the messages storage proof is generated and submitted to the bridge messages pezpallet at
|
||||
the target chain. In addition, the transaction may include the storage proof of the outbound lane state - that proves
|
||||
that relayer rewards have been paid and this data (map of relay accounts to the delivered messages) may be pruned from
|
||||
the inbound lane state at the target chain.
|
||||
|
||||
Delivery confirmation relay connects to the target chain and starts watching the inbound lane end. When new messages are
|
||||
delivered to the target chain, the corresponding _source chain account_ is inserted to the map in the inbound lane data.
|
||||
Relay detects that, say, at the target chain block `B` and waits until that block or its descendant appears at the
|
||||
source chain. Once that happens, the relay crafts a storage proof of that data and sends it to the messages pezpallet,
|
||||
deployed at the source chain.
|
||||
|
||||
As you can see, the messages relay also requires finality relay to be operating in parallel. Since messages relay
|
||||
submits transactions to both source and target chains, it requires both _source-to-target_ and _target-to-source_
|
||||
finality relays. They can be GRANDPA finality relays or GRANDPA+teyrchains finality relays, depending on the type of
|
||||
connected chain.
|
||||
|
||||
More: [Messages Relay Sequence Diagram](./pez-messages-relay.html), [pezpallet level documentation and
|
||||
code](../relays/messages/).
|
||||
|
||||
### Complex Relay
|
||||
|
||||
Every relay transaction has its cost. The only transaction, that is "free" to relayer is when the mandatory GRANDPA
|
||||
header is submitted. The relay that feeds the bridge with every relay chain and/or teyrchain head it sees, will have to
|
||||
pay a (quite large) cost. And if no messages are sent through the bridge, that is just waste of money.
|
||||
|
||||
We have a special relay mode, called _complex relay_, where relay mostly sleeps and only submits transactions that are
|
||||
required for the messages/confirmations delivery. This mode starts two message relays (in both directions). All required
|
||||
finality relays are also started in a special _on-demand_ mode. In this mode they do not submit any headers without
|
||||
special request. As always, the only exception is when GRANDPA finality relay sees the mandatory header - it is
|
||||
submitted without such request.
|
||||
|
||||
The message relays are watching their lanes and when, at some block `B`, they see new messages/confirmations to be
|
||||
delivered, they are asking on-demand relays to relay this block `B`. On-demand relays does that and then message relay
|
||||
may perform its job. If on-demand relay is a teyrchain finality relay, it also runs its own on-demand GRANDPA relay,
|
||||
which is used to relay required relay chain headers.
|
||||
|
||||
More: [Complex Relay Sequence Diagram](./complex-relay.html),
|
||||
[code](../relays/bin-bizinikiwi/src/cli/relay_headers_and_messages/).
|
||||
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Messages Relay</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Messages Relay</h1>
|
||||
<p>
|
||||
Both Source Chain and Target Chains have Bridge Messages pallets deployed. They also have required
|
||||
finality pallets deployed - we don't care about finality type here - they can be either Bridge GRANDPA,
|
||||
or Bridge Parachains finality pallets, or any combination of those.
|
||||
</p>
|
||||
<p>
|
||||
Finality Relayer represents two actual relayers - one relays Source Chain Finality to Target Chain.
|
||||
And another one relays Target Chain Finality to Source Chain.
|
||||
</p>
|
||||
<div class="mermaid">
|
||||
sequenceDiagram
|
||||
participant Source Chain
|
||||
participant Finality Relayer
|
||||
participant Messages Relayer
|
||||
participant Target Chain
|
||||
|
||||
Note right of Source Chain: Finalized: 480, Target Finalized: 50, Sent Messages: 42, Confirmed Messages: 42
|
||||
Note left of Target Chain: Finalized: 60, Source Finalized: 420, Received Messages: 42
|
||||
|
||||
Source Chain ->> Source Chain: someone Sends Message 43
|
||||
Source Chain ->> Source Chain: Import and Finalize Block 481
|
||||
|
||||
Source Chain ->> Messages Relayer: notes new outbound message 43 at Source Chain Block 481
|
||||
Note right of Messages Relayer: can't deliver message 43, Source Chain Block 481 is not relayed
|
||||
|
||||
Source Chain ->> Finality Relayer: Read Finality Proof of Block 481
|
||||
Finality Relayer ->> Target Chain: Submit Finality Proof of Block 481
|
||||
Target Chain ->> Target Chain: Import and Finalize Block 61
|
||||
Note left of Target Chain: Finalized: 61, Source Finalized: 481, Received Messages: 42
|
||||
|
||||
Source Chain ->> Messages Relayer: Read Proof of Message 43 at Block 481
|
||||
Messages Relayer ->> Target Chain: Submit Proof of Message 43 at Block 481
|
||||
Target Chain ->> Target Chain: Import and Finalize Block 62
|
||||
Note left of Target Chain: Finalized: 62, Source Finalized: 481, Received Messages: { rewarded: 42, messages-relayer-account: [43] }
|
||||
|
||||
Target Chain ->> Messages Relayer: notes new unrewarded relayer at Target Chain Block 62
|
||||
Note right of Messages Relayer: can't relay delivery confirmations because Target Chain Block 62 is not relayed
|
||||
|
||||
Target Chain ->> Finality Relayer: Read Finality Proof of Block 62
|
||||
Finality Relayer ->> Source Chain: Submit Finality Proof of Block 62
|
||||
Source Chain ->> Source Chain: Import and Finalize Block 482
|
||||
Note right of Source Chain: Finalized: 482, Target Finalized: 62, Confirmed Messages: 42
|
||||
|
||||
Target Chain ->> Messages Relayer: Read Proof of Message 43 Delivery at Block 62
|
||||
Messages Relayer ->> Source Chain: Submit Proof of Message 43 Delivery at Block 612
|
||||
Source Chain ->> Source Chain: rewards messages-relayer-account for delivering message [43]
|
||||
Source Chain ->> Source Chain: prune delivered message 43 from runtime storage
|
||||
Note right of Source Chain: Finalized: 482, Target Finalized: 61, Confirmed Messages: 43
|
||||
|
||||
Source Chain ->> Source Chain: someone Sends Message 44
|
||||
Source Chain ->> Source Chain: Import and Finalize Block 483
|
||||
|
||||
Source Chain ->> Messages Relayer: notes new outbound message 44 at Source Chain Block 483 and new confirmed message 43
|
||||
Note right of Messages Relayer: can't deliver message 44, Source Chain Block 483 is not relayed
|
||||
|
||||
Source Chain ->> Finality Relayer: Read Finality Proof of Block 483
|
||||
Finality Relayer ->> Target Chain: Submit Finality Proof of Block 483
|
||||
Target Chain ->> Target Chain: Import and Finalize Block 63
|
||||
Note left of Target Chain: Finalized: 63, Source Finalized: 483, Received Messages: { rewarded: 42, messages-relayer-account: [43] }
|
||||
|
||||
Source Chain ->> Messages Relayer: Read Proof of Message 44 and Proof of Message 43 reward at Block 483
|
||||
Messages Relayer ->> Target Chain: Submit Proof of Message 44 and Proof of Message 43 reward at Block 483
|
||||
Target Chain ->> Target Chain: Import and Finalize Block 64
|
||||
Note left of Target Chain: Finalized: 64, Source Finalized: 483, Received Messages: { rewarded: 43, messages-relayer-account: [44] }
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@8.8.4/dist/mermaid.min.js"></script>
|
||||
<script>mermaid.initialize({startOnLoad: true})</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,131 @@
|
||||
# Pezkuwi <> Kusama Bridge Overview
|
||||
|
||||
This document describes how we use all components, described in the [High-Level Bridge
|
||||
Documentation](./high-level-overview.md), to build the XCM bridge between Kusama and Pezkuwi. In this case, our
|
||||
components merely work as a XCM transport (like XCMP/UMP/HRMP), between chains that are not a part of the same consensus
|
||||
system.
|
||||
|
||||
The overall architecture may be seen in [this diagram](./pezkuwi-kusama-bridge.html).
|
||||
|
||||
## Bridge Hubs
|
||||
|
||||
All operations at relay chain are expensive. Ideally all non-mandatory transactions must happen on teyrchains. That's
|
||||
why we are planning to have two teyrchains - Pezkuwi Bridge Hub under Pezkuwi consensus and Kusama Bridge Hub under
|
||||
Kusama consensus.
|
||||
|
||||
The Bridge Hub will have all required bridge pallets in its runtime. We hope that later, other teams will be able to use
|
||||
our bridge hubs too and have their pallets there.
|
||||
|
||||
The Bridge Hub will use the base token of the ecosystem - KSM at Kusama Bridge Hub and HEZ at Pezkuwi Bridge Hub. The
|
||||
runtime will have minimal set of non-bridge pallets, so there's not much you can do directly on bridge hubs.
|
||||
|
||||
## Connecting Teyrchains
|
||||
|
||||
You won't be able to directly use bridge hub transactions to send XCM messages over the bridge. Instead, you'll need to
|
||||
use other teyrchains transactions, which will use HRMP to deliver messages to the Bridge Hub. The Bridge Hub will just
|
||||
queue these messages in its outbound lane, which is dedicated to deliver messages between two teyrchains.
|
||||
|
||||
Our first planned bridge will connect the Pezkuwi and Kusama Asset Hubs. A bridge between those two
|
||||
teyrchains would allow Asset Hub Pezkuwi accounts to hold wrapped KSM tokens and Asset Hub Kusama
|
||||
accounts to hold wrapped HEZ tokens.
|
||||
|
||||
For that bridge (pair of teyrchains under different consensus systems) we'll be using the lane 00000000. Later, when
|
||||
other teyrchains will join the bridge, they will be using other lanes for their messages.
|
||||
|
||||
## Running Relayers
|
||||
|
||||
We are planning to run our own complex relayer for the lane 00000000. The relayer will relay Kusama/Pezkuwi GRANDPA
|
||||
justifications to the bridge hubs at the other side. It'll also relay finalized Kusama Bridge Hub and Pezkuwi Bridge
|
||||
Hub heads. This will only happen when messages will be queued at hubs. So most of time relayer will be idle.
|
||||
|
||||
There's no any active relayer sets, or something like that. Anyone may start its own relayer and relay queued messages.
|
||||
We are not against that and, as always, appreciate any community efforts. Of course, running relayer has the cost. Apart
|
||||
from paying for the CPU and network, the relayer pays for transactions at both sides of the bridge. We have a mechanism
|
||||
for rewarding relayers.
|
||||
|
||||
### Compensating the Cost of Message Delivery Transactions
|
||||
|
||||
One part of our rewarding scheme is that the cost of message delivery, for honest relayer, is zero. The honest relayer
|
||||
is the relayer, which is following our rules:
|
||||
|
||||
- we do not reward relayers for submitting GRANDPA finality transactions. The only exception is submitting mandatory
|
||||
headers (headers which are changing the GRANDPA authorities set) - the cost of such transaction is zero. The relayer
|
||||
will pay the full cost for submitting all other headers;
|
||||
|
||||
- we do not reward relayers for submitting teyrchain finality transactions. The relayer will pay the full cost for
|
||||
submitting teyrchain finality transactions;
|
||||
|
||||
- we compensate the cost of message delivery transactions that have actually delivered the messages. So if your
|
||||
transaction has claimed to deliver messages `[42, 43, 44]`, but, because of some reasons, has actually delivered
|
||||
messages `[42, 43]`, the transaction will be free for relayer. If it has not delivered any messages, then the relayer
|
||||
pays the full cost of the transaction;
|
||||
|
||||
- we compensate the cost of message delivery and all required finality calls, if they are part of the same
|
||||
[`frame_utility::batch_all`](https://github.com/paritytech/bizinikiwi/blob/891d6a5c870ab88521183facafc811a203bb6541/frame/utility/src/lib.rs#L326)
|
||||
transaction. Of course, the calls inside the batch must be linked - e.g. the submitted teyrchain head must be used to
|
||||
prove messages. Relay header must be used to prove teyrchain head finality. If one of calls fails, or if they are not
|
||||
linked together, the relayer pays the full transaction cost.
|
||||
|
||||
Please keep in mind that the fee of "zero-cost" transactions is still withdrawn from the relayer account. But the
|
||||
compensation is registered in the `pallet_bridge_relayers::RelayerRewards` map at the target bridge hub. The relayer may
|
||||
later claim all its rewards later, using the `pallet_bridge_relayers::claim_rewards` call.
|
||||
|
||||
*A side note*: why we don't simply set the cost of useful transactions to zero? That's because the bridge has its cost.
|
||||
If we won't take any fees, it would mean that the sender is not obliged to pay for its messages. And Bridge Hub
|
||||
collators (and, maybe, "treasury") are not receiving any payment for including transactions. More about this later, in
|
||||
the [Who is Rewarding Relayers](#who-is-rewarding-relayers) section.
|
||||
|
||||
### Message Delivery Confirmation Rewards
|
||||
|
||||
In addition to the "zero-cost" message delivery transactions, the relayer is also rewarded for:
|
||||
|
||||
- delivering every message. The reward is registered during delivery confirmation transaction at the Source Bridge Hub.;
|
||||
|
||||
- submitting delivery confirmation transaction. The relayer may submit delivery confirmation that e.g. confirms delivery
|
||||
of four messages, of which the only one (or zero) messages is actually delivered by this relayer. It receives some fee
|
||||
for confirming messages, delivered by other relayers.
|
||||
|
||||
Both rewards may be claimed using the `pallet_bridge_relayers::claim_rewards` call at the Source Bridge Hub.
|
||||
|
||||
### Who is Rewarding Relayers
|
||||
|
||||
Obviously, there should be someone who is paying relayer rewards. We want bridge transactions to have a cost, so we
|
||||
can't use fees for rewards. Instead, the teyrchains using the bridge, use sovereign accounts on both sides of the bridge
|
||||
to cover relayer rewards.
|
||||
|
||||
Bridged Teyrchains will have sovereign accounts at bridge hubs. For example, the Kusama Asset Hub will
|
||||
have an account at the Pezkuwi Bridge Hub. The Pezkuwi Asset Hub will have an account at the Kusama
|
||||
Bridge Hub. The sovereign accounts are used as a source of funds when the relayer is calling the
|
||||
`pallet_bridge_relayers::claim_rewards`.
|
||||
|
||||
Since messages lane is only used by the pair of teyrchains, there's no collision between different bridges. E.g.
|
||||
Kusama Asset Hub will only reward relayers that are delivering messages from Kusama Asset Hub.
|
||||
The Kusama Asset Hub sovereign account is not used to cover rewards of bridging with some other Pezkuwi Teyrchain.
|
||||
|
||||
### Multiple Relayers and Rewards
|
||||
|
||||
Our goal is to incentivize running honest relayers. But we have no relayers sets, so at any time anyone may submit
|
||||
message delivery transaction, hoping that the cost of this transaction will be compensated. So what if some message is
|
||||
currently queued and two relayers are submitting two identical message delivery transactions at once? Without any
|
||||
special means, the cost of first included transaction will be compensated and the cost of the other one won't. A honest,
|
||||
but unlucky relayer will lose some money. In addition, we'll waste some portion of block size and weight, which may be
|
||||
used by other useful transactions.
|
||||
|
||||
To solve the problem, we have two signed extensions ([generate_bridge_reject_obsolete_headers_and_messages!
|
||||
{}](../bin/runtime-common/src/lib.rs) and
|
||||
[RefundRelayerForMessagesFromTeyrchain](../bin/runtime-common/src/refund_relayer_extension.rs)), that are preventing
|
||||
bridge transactions with obsolete data from including into the block. We are rejecting following transactions:
|
||||
|
||||
- transactions, that are submitting the GRANDPA justification for the best finalized header, or one of its ancestors;
|
||||
|
||||
- transactions, that are submitting the proof of the current best teyrchain head, or one of its ancestors;
|
||||
|
||||
- transactions, that are delivering already delivered messages. If at least one of messages is not yet delivered, the
|
||||
transaction is not rejected;
|
||||
|
||||
- transactions, that are confirming delivery of already confirmed messages. If at least one of confirmations is new, the
|
||||
transaction is not rejected;
|
||||
|
||||
- [`frame_utility::batch_all`](https://github.com/paritytech/bizinikiwi/blob/891d6a5c870ab88521183facafc811a203bb6541/frame/utility/src/lib.rs#L326)
|
||||
transactions, that have both finality and message delivery calls. All restrictions from the [Compensating the Cost of
|
||||
Message Delivery Transactions](#compensating-the-cost-of-message-delivery-transactions) are applied.
|
||||
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Polkadot <> Kusama Bridge</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Polkadot <> Kusama Bridge</h1>
|
||||
<p>
|
||||
Our bridge connects two parachains - Kusama Bridge Hub and Polkadot Bridge Hub. Messages that
|
||||
are sent over bridge have XCM format and we are using existing architecture to dispatch them.
|
||||
Since both Polkadot, Kusama and their parachains already have means to exchange XCM messages
|
||||
within the same consensus system (HRMP, VMP, ...), it means that we are able to connect all those
|
||||
chains with our bridge.
|
||||
</p>
|
||||
<p>
|
||||
In our architecture, the lane that is used to relay messages over the bridge is determined by
|
||||
the XCM source and destinations. So e.g. bridge between Asset Hubs Polkadot and Kusama (and opposite direction)
|
||||
will use the lane 00000000, bridge between some other Polkadot Parachain and some other Kusama Parachain
|
||||
will use the lane 00000001 and so on.
|
||||
</p>
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
subgraph Polkadot Consensus
|
||||
polkadot(((Polkadot)))
|
||||
asset_hub_polkadot(((Polkadot Asset Hub)))
|
||||
polkadot_bh(((Polkadot Bridge Hub)))
|
||||
|
||||
polkadot---asset_hub_polkadot
|
||||
polkadot---polkadot_bh
|
||||
|
||||
asset_hub_polkadot-->|Send Message Using HRMP|polkadot_bh
|
||||
|
||||
polkadot_bh-->|Send Message Using HRMP|asset_hub_polkadot
|
||||
asset_hub_polkadot-->|Dispatch the Message|asset_hub_polkadot
|
||||
end
|
||||
subgraph Kusama Consensus
|
||||
kusama_bh(((Kusama Bridge Hub)))
|
||||
asset_hub_kusama(((Kusama Asset Hub)))
|
||||
kusama(((Kusama)))
|
||||
|
||||
kusama---asset_hub_kusama
|
||||
kusama---kusama_bh
|
||||
|
||||
kusama_bh-->|Send Message Using HRMP|asset_hub_kusama
|
||||
asset_hub_kusama-->|Dispatch the Message|asset_hub_kusama
|
||||
|
||||
asset_hub_kusama-->|Send Message Using HRMP|kusama_bh
|
||||
end
|
||||
|
||||
polkadot_bh<===>|Message is relayed to the Bridged Chain using lane 00000000|kusama_bh
|
||||
|
||||
linkStyle 2 stroke:red
|
||||
linkStyle 7 stroke:red
|
||||
linkStyle 8 stroke:red
|
||||
|
||||
linkStyle 3 stroke:green
|
||||
linkStyle 4 stroke:green
|
||||
linkStyle 9 stroke:green
|
||||
</div>
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,343 @@
|
||||
# Running your own bridge relayer
|
||||
|
||||
:warning: :construction: Please read the [Disclaimer](#disclaimer) section first :construction: :warning:
|
||||
|
||||
## Disclaimer
|
||||
|
||||
There are several things you should know before running your own relayer:
|
||||
|
||||
- initial bridge version (we call it bridges v1) supports any number of relayers, but **there's no guaranteed
|
||||
compensation** for running a relayer and/or submitting valid bridge transactions. Most probably you'll end up
|
||||
spending more funds than getting from rewards - please accept this fact;
|
||||
|
||||
- even if your relayer has managed to submit a valid bridge transaction that has been included into the bridge
|
||||
hub block, there's no guarantee that you will be able to claim your compensation for that transaction. That's
|
||||
because compensations are paid from the account, controlled by relay chain governance and it could have no funds
|
||||
to compensate your useful actions. We'll be working on a proper process to resupply it on-time, but we can't
|
||||
provide any guarantee until that process is well established.
|
||||
|
||||
## A Brief Introduction into Relayers and our Compensations Scheme
|
||||
|
||||
Omitting details, relayer is an offchain process that is connected to both bridged chains. It looks at the
|
||||
outbound bridge messages queue and submits message delivery transactions to the target chain. There's a lot
|
||||
of details behind that simple phrase - you could find more info in the
|
||||
[High-Level Bridge Overview](./high-level-overview.md) document.
|
||||
|
||||
Reward that is paid to relayer has two parts. The first part static and is controlled by the governance.
|
||||
It is rather small initially - e.g. you need to deliver `10_000` Kusama -> Pezkuwi messages to gain single
|
||||
KSM token.
|
||||
|
||||
The other reward part is dynamic. So to deliver an XCM message from one BridgeHub to another, we'll need to
|
||||
submit two transactions on different chains. Every transaction has its cost, which is:
|
||||
|
||||
- dynamic, because e.g. message size can change and/or fee factor of the target chain may change;
|
||||
|
||||
- quite large, because those transactions are quite heavy (mostly in terms of size, not weight).
|
||||
|
||||
We are compensating the cost of **valid**, **minimal** and **useful** bridge-related transactions to
|
||||
relayer, that has submitted such transaction. Valid here means that the transaction doesn't fail. Minimal
|
||||
means that all data within transaction call is actually required for the transaction to succeed. Useful
|
||||
means that all supplied data in transaction is new and yet unknown to the target chain.
|
||||
|
||||
We have implemented a relayer that is able to craft such transactions. The rest of document contains a detailed
|
||||
information on how to deploy this software on your own node.
|
||||
|
||||
## Relayers Concurrency
|
||||
|
||||
As it has been said above, we are not compensating cost of transactions that are not **useful**. For
|
||||
example, if message `100` has already been delivered from Kusama Bridge Hub to Pezkuwi Bridge Hub, then another
|
||||
transaction that delivers the same message `100` won't be **useful**. Hence, no compensation to relayer that
|
||||
has submitted that second transaction.
|
||||
|
||||
But what if there are several relayers running? They are noticing the same queued message `100` and
|
||||
simultaneously submit identical message delivery transactions. You may expect that there'll be one lucky
|
||||
relayer, whose transaction would win the "race" and which will receive the compensation and reward. And
|
||||
there'll be several other relayers, losing some funds on their unuseful transactions.
|
||||
|
||||
But actually, we have a solution that invalidates transactions of "unlucky" relayers before they are
|
||||
included into the block. So at least you may be sure that you won't waste your funds on duplicate transactions.
|
||||
|
||||
<details>
|
||||
<summary>Some details?</summary>
|
||||
|
||||
All **unuseful** transactions are rejected by our
|
||||
[transaction extension](https://github.com/pezkuwichain/pezkuwi-sdk/blob/master/bridges/bin/runtime-common/src/refund_relayer_extension.rs),
|
||||
which also handles transaction fee compensations. You may find more info on unuseful (aka obsolete) transactions
|
||||
by lurking in the code.
|
||||
|
||||
We also have the WiP prototype of relayers coordination protocol, where relayers will get some guarantee
|
||||
that their transactions will be prioritized over other relayers transactions at their assigned slots.
|
||||
That is planned for the future version of bridge and the progress is
|
||||
[tracked here](https://github.com/pezkuwichain/kurdistan-sdk/issues/85).
|
||||
|
||||
</details>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Let's focus on the bridge between Pezkuwi and Kusama Bridge Hubs. Let's also assume that we want to start
|
||||
a relayer that "serves" an initial lane [`0x00000001`](https://github.com/polkadot-fellows/runtimes/blob/9ce1bbbbcd7843b3c76ba4d43c036bc311959e9f/system-parachains/bridge-hubs/bridge-hub-kusama/src/bridge_to_polkadot_config.rs#L54).
|
||||
|
||||
<details>
|
||||
<summary>Lane?</summary>
|
||||
|
||||
Think of lane as a queue of messages that need to be delivered to the other/bridged chain. The lane is
|
||||
bidirectional, meaning that there are four "endpoints". Two "outbound" endpoints (one at every chain), contain
|
||||
messages that need to be delivered to the bridged chain. Two "inbound" are accepting messages from the bridged
|
||||
chain and also remember the relayer, who has delivered message(s) to reward it later.
|
||||
|
||||
</details>
|
||||
|
||||
The same steps may be performed for other lanes and bridges as well - you'll just need to change several parameters.
|
||||
|
||||
So to start your relayer instance, you'll need to prepare:
|
||||
|
||||
- an address of ws/wss RPC endpoint of the Kusama relay chain;
|
||||
|
||||
- an address of ws/wss RPC endpoint of the Pezkuwi relay chain;
|
||||
|
||||
- an address of ws/wss RPC endpoint of the Kusama Bridge Hub chain;
|
||||
|
||||
- an address of ws/wss RPC endpoint of the Pezkuwi Bridge Hub chain;
|
||||
|
||||
- an account on Kusama Bridge Hub;
|
||||
|
||||
- an account on Pezkuwi Bridge Hub.
|
||||
|
||||
For RPC endpoints, you could start your own nodes, or use some public community nodes. Nodes are not meant to be
|
||||
archive or provide access to insecure RPC calls.
|
||||
|
||||
To create an account on Bridge Hubs, you could use XCM teleport functionality. E.g. if you have an account on
|
||||
the relay chain, you could use the `teleportAssets` call of `xcmPallet` and send asset
|
||||
`V3 { id: Concrete(0, Here), Fungible: <your-amount> }` to beneficiary `V3(0, X1(AccountId32(<your-account>)))`
|
||||
on destination `V3(0, X1(Teyrchain(1002)))`. To estimate amounts you need, please refer to the [Costs](#costs)
|
||||
section of the document.
|
||||
|
||||
## Registering your Relayer Account (Optional, But Please Read)
|
||||
|
||||
Bridge transactions are quite heavy and expensive. We want to minimize block space that can be occupied by
|
||||
invalid bridge transactions and prioritize valid transactions over invalid. That is achieved by **optional**
|
||||
relayer registration. Transactions, signed by relayers with active registration, gain huge priority boost.
|
||||
In exchange, such relayers may be slashed if they submit **invalid** or **non-minimal** transaction.
|
||||
|
||||
Transactions, signed by relayers **without** active registration, on the other hand, receive no priority
|
||||
boost. It means that if there is active registered relayer, most likely all transactions from unregistered
|
||||
will be counted as **unuseful**, not included into the block and unregistered relayer won't get any reward
|
||||
for his operations.
|
||||
|
||||
Before registering, you should know several things about your funds:
|
||||
|
||||
- to register, you need to hold significant amount of funds on your relayer account. As of now, it is
|
||||
[100 KSM](https://github.com/polkadot-fellows/runtimes/blob/9ce1bbbbcd7843b3c76ba4d43c036bc311959e9f/system-parachains/bridge-hubs/bridge-hub-kusama/src/bridge_to_polkadot_config.rs#L71C14-L71C43)
|
||||
for registration on Kusama Bridge Hub and
|
||||
[500 HEZ](https://github.com/polkadot-fellows/runtimes/blob/9ce1bbbbcd7843b3c76ba4d43c036bc311959e9f/system-parachains/bridge-hubs/bridge-hub-polkadot/src/bridge_to_kusama_config.rs#L71C14-L71C43)
|
||||
for registration on Pezkuwi Bridge Hub;
|
||||
|
||||
- when you are registered, those funds are reserved on relayer account and you can't transfer them.
|
||||
|
||||
The registration itself, has three states: active, inactive or expired. Initially, it is active, meaning that all
|
||||
your transactions that are **validated** on top of block, where it is active get priority boost. Registration
|
||||
becomes expired when the block with the number you have specified during registration is "mined". It is the
|
||||
`validTill` parameter of the `register` call (see below). After that `validTill` block, you may unregister and get
|
||||
your reserved funds back. There's also an intermediate point between those blocks - it is the `validTill - LEASE`,
|
||||
where `LEASE` is the chain constant, controlled by the governance. Initially it is set to `300` blocks.
|
||||
All your transactions, **validated** between the `validTill - LEASE` and `validTill` blocks do not get the
|
||||
priority boost. Also, it is forbidden to specify `validTill` such that the `validTill - currentBlock` is less
|
||||
than the `LEASE`.
|
||||
|
||||
<details>
|
||||
<summary>Example?</summary>
|
||||
|
||||
| Bridge Hub Block | Registration State | Comment |
|
||||
| ----------------- | ------------------ | ------------------------------------------------------ |
|
||||
| 100 | Active | You have submitted a tx with the `register(1000)` call |
|
||||
| 101 | Active | Your message delivery transactions are boosted |
|
||||
| 102 | Active | Your message delivery transactions are boosted |
|
||||
| ... | Active | Your message delivery transactions are boosted |
|
||||
| 700 | Inactive | Your message delivery transactions are not boosted |
|
||||
| 701 | Inactive | Your message delivery transactions are not boosted |
|
||||
| ... | Inactive | Your message delivery transactions are not boosted |
|
||||
| 1000 | Expired | You may submit a tx with the `deregister` call |
|
||||
|
||||
</details>
|
||||
|
||||
So once you have enough funds on your account and have selected the `validTill` parameter value, you
|
||||
could use the Pezkuwi JS apps to submit an extrinsic. If you want priority boost for your transactions
|
||||
on the Kusama Bridge Hub, open the
|
||||
[Pezkuwi JS Apps](https://pezkuwichain.io/?rpc=wss%3A%2F%2Fkusama-bridge-hub-rpc.polkadot.io#/extrinsics)
|
||||
and submit the `register` extrinsic from the `bridgeRelayers` pezpallet:
|
||||
|
||||

|
||||
|
||||
To deregister, submit the simple `deregister` extrinsic when registration is expired:
|
||||
|
||||

|
||||
|
||||
At any time, you can prolong your registration by calling the `register` with the larger `validTill`.
|
||||
|
||||
## Costs
|
||||
|
||||
Your relayer account (on both Bridge Hubs) must hold enough funds to be able to pay costs of bridge
|
||||
transactions. If your relayer behaves correctly, those costs will be compensated and you will be
|
||||
able to claim it later.
|
||||
|
||||
**IMPORTANT**: you may add tip to your bridge transactions to boost their priority. But our
|
||||
compensation mechanism never refunds transaction tip, so all tip tokens will be lost.
|
||||
|
||||
<details>
|
||||
<summary>Types of bridge transactions</summary>
|
||||
|
||||
There are two types of bridge transactions:
|
||||
|
||||
- message delivery transaction brings queued message(s) from one Bridge Hub to another. We record
|
||||
the fact that this specific (your) relayer has delivered those messages;
|
||||
|
||||
- message confirmation transaction confirms that some message have been delivered and also brings
|
||||
back information on how many messages (your) relayer has delivered. We use this information later
|
||||
to register delivery rewards on the source chain.
|
||||
|
||||
Several messages/confirmations may be included in a single bridge transaction. Apart from this
|
||||
data, bridge transaction may include finality and storage proofs, required to prove authenticity of
|
||||
this data.
|
||||
|
||||
</details>
|
||||
|
||||
To deliver and get reward for a single message, the relayer needs to submit two transactions. One
|
||||
at the source Bridge Hub and one at the target Bridge Hub. Below are costs for Pezkuwi <> Kusama
|
||||
messages (as of today):
|
||||
|
||||
- to deliver a single Pezkuwi -> Kusama message, you would need to pay around `0.06 KSM` at Kusama
|
||||
Bridge Hub and around `1.62 HEZ` at Pezkuwi Bridge Hub;
|
||||
|
||||
- to deliver a single Kusama -> Pezkuwi message, you would need to pay around `1.70 HEZ` at Pezkuwi
|
||||
Bridge Hub and around `0.05 KSM` at Kusama Bridge Hub.
|
||||
|
||||
Those values are not constants - they depend on call weights (that may change from release to release),
|
||||
on transaction sizes (that depends on message size and chain state) and congestion factor. In any
|
||||
case - it is your duty to make sure that the relayer has enough funds to pay transaction fees.
|
||||
|
||||
## Claiming your Compensations and Rewards
|
||||
|
||||
Hopefully you have successfully delivered some messages and now can claim your compensation and reward.
|
||||
This requires submitting several transactions. But first, let's check that you actually have something to
|
||||
claim. For that, let's check the state of the pezpallet that tracks all rewards.
|
||||
|
||||
To check your rewards at the Kusama Bridge Hub, go to the
|
||||
[Pezkuwi JS Apps](https://pezkuwichain.io/?rpc=wss%3A%2F%2Fkusama-bridge-hub-rpc.polkadot.io#/chainstate)
|
||||
targeting Kusama Bridge Hub, select the `bridgeRelayers` pezpallet, choose `relayerRewards` map and
|
||||
your relayer account. Then:
|
||||
|
||||
- set the `laneId` to `0x00000001`
|
||||
|
||||
- set the `bridgedChainId` to `bhpd`;
|
||||
|
||||
- check both variants of the `owner` field: `ThisChain` is used to pay for message delivery transactions
|
||||
and `BridgedChain` is used to pay for message confirmation transactions.
|
||||
|
||||
If check shows that you have some rewards, you can craft the claim transaction, with similar parameters.
|
||||
For that, go to `Extrinsics` tab of the
|
||||
[Pezkuwi JS Apps](https://pezkuwichain.io/?rpc=wss%3A%2F%2Fkusama-bridge-hub-rpc.polkadot.io#/extrinsics)
|
||||
and submit the following transaction (make sure to change `owner` before):
|
||||
|
||||

|
||||
|
||||
To claim rewards on Pezkuwi Bridge Hub you can follow the same process. The only difference is that you
|
||||
need to set value of the `bridgedChainId` to `bhks`.
|
||||
|
||||
## Starting your Relayer
|
||||
|
||||
### Starting your Pezkuwichain <> Zagros Relayer
|
||||
|
||||
You may find the relayer image reference in the
|
||||
[Releases](https://github.com/paritytech/parity-bridges-common/releases)
|
||||
of this repository. Make sure to check supported (bundled) versions
|
||||
of release there. For Pezkuwichain <> Zagros bridge, normally you may use the
|
||||
latest published release. The release notes always contain the docker
|
||||
image reference and source files, required to build relayer manually.
|
||||
|
||||
Once you have the docker image, update variables and run the following script:
|
||||
```sh
|
||||
export DOCKER_IMAGE=<image-of-bizinikiwi-relay>
|
||||
|
||||
export PEZKUWICHAIN_HOST=<pezkuwichain-ws-rpc-host-here>
|
||||
export PEZKUWICHAIN_PORT=<pezkuwichain-ws-rpc-port-here>
|
||||
# or set it to '--pezkuwichain-secure' if wss is used above
|
||||
export PEZKUWICHAIN_IS_SECURE=
|
||||
export BRIDGE_HUB_PEZKUWICHAIN_HOST=<bridge-hub-pezkuwichain-ws-rpc-host-here>
|
||||
export BRIDGE_HUB_PEZKUWICHAIN_PORT=<bridge-hub-pezkuwichain-ws-rpc-port-here>
|
||||
# or set it to '--bridge-hub-pezkuwichain-secure' if wss is used above
|
||||
export BRIDGE_HUB_PEZKUWICHAIN_IS_SECURE=
|
||||
export BRIDGE_HUB_PEZKUWICHAIN_KEY_FILE=<absolute-path-to-file-with-account-key-at-bridge-hub-pezkuwichain>
|
||||
|
||||
export ZAGROS_HOST=<zagros-wss-rpc-host-here>
|
||||
export ZAGROS_PORT=<zagros-wss-rpc-port-here>
|
||||
# or set it to '--zagros-secure' if wss is used above
|
||||
export ZAGROS_IS_SECURE=
|
||||
export BRIDGE_HUB_ZAGROS_HOST=<bridge-hub-zagros-ws-rpc-host-here>
|
||||
export BRIDGE_HUB_ZAGROS_PORT=<bridge-hub-zagros-ws-rpc-port-here>
|
||||
# or set it to '--bridge-hub-zagros-secure ' if wss is used above
|
||||
export BRIDGE_HUB_ZAGROS_IS_SECURE=
|
||||
export BRIDGE_HUB_ZAGROS_KEY_FILE=<absolute-path-to-file-with-account-key-at-bridge-hub-zagros>
|
||||
|
||||
# you can get extended relay logs (e.g. for debugging issues) by passing `-e RUST_LOG=bridge=trace`
|
||||
# argument to the `docker` binary
|
||||
docker run \
|
||||
-v $BRIDGE_HUB_PEZKUWICHAIN_KEY_FILE:/bhr.key \
|
||||
-v $BRIDGE_HUB_ZAGROS_KEY_FILE:/bhw.key \
|
||||
$DOCKER_IMAGE \
|
||||
relay-headers-and-messages bridge-hub-pezkuwichain-bridge-hub-zagros \
|
||||
--pezkuwichain-host $PEZKUWICHAIN_HOST \
|
||||
--pezkuwichain-port $PEZKUWICHAIN_PORT \
|
||||
$PEZKUWICHAIN_IS_SECURE \
|
||||
--pezkuwichain-version-mode Auto \
|
||||
--bridge-hub-pezkuwichain-host $BRIDGE_HUB_PEZKUWICHAIN_HOST \
|
||||
--bridge-hub-pezkuwichain-port $BRIDGE_HUB_PEZKUWICHAIN_PORT \
|
||||
$BRIDGE_HUB_PEZKUWICHAIN_IS_SECURE \
|
||||
--bridge-hub-pezkuwichain-version-mode Auto \
|
||||
--bridge-hub-pezkuwichain-signer-file /bhr.key \
|
||||
--bridge-hub-pezkuwichain-transactions-mortality 16 \
|
||||
--zagros-host $ZAGROS_HOST \
|
||||
--zagros-port $ZAGROS_PORT \
|
||||
$ZAGROS_IS_SECURE \
|
||||
--zagros-version-mode Auto \
|
||||
--bridge-hub-zagros-host $BRIDGE_HUB_ZAGROS_HOST \
|
||||
--bridge-hub-zagros-port $BRIDGE_HUB_ZAGROS_PORT \
|
||||
$BRIDGE_HUB_ZAGROS_IS_SECURE \
|
||||
--bridge-hub-zagros-version-mode Auto \
|
||||
--bridge-hub-zagros-signer-file /bhw.key \
|
||||
--bridge-hub-zagros-transactions-mortality 16 \
|
||||
--lane 00000002
|
||||
```
|
||||
|
||||
### Starting your Pezkuwi <> Kusama Relayer
|
||||
|
||||
*Work in progress, coming soon*
|
||||
|
||||
### Watching your relayer state
|
||||
|
||||
Our relayer provides some Prometheus metrics that you may convert into some fancy Grafana dashboards
|
||||
and alerts. By default, metrics are exposed at port `9616`. To expose endpoint to the localhost, change
|
||||
the docker command by adding following two lines:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
..
|
||||
-p 127.0.0.1:9616:9616 \ # tell Docker to bind container port 9616 to host port 9616
|
||||
# and listen for connections on the host' localhost interface
|
||||
..
|
||||
$DOCKER_IMAGE \
|
||||
relay-headers-and-messages bridge-hub-pezkuwichain-bridge-hub-zagros \
|
||||
--prometheus-host 0.0.0.0 \ # tell `bizinikiwi-relay` binary to accept Prometheus endpoint
|
||||
# connections from everywhere
|
||||
..
|
||||
```
|
||||
|
||||
You can find more info on configuring Prometheus and Grafana in the
|
||||
[Monitor your node](https://docs.pezkuwichain.io/infrastructure/running-a-validator/operational-tasks/general-management/#monitor-your-node)
|
||||
guide from Pezkuwi wiki.
|
||||
|
||||
We have our own set of Grafana dashboards and alerts. You may use them for inspiration.
|
||||
Please find them in this folder:
|
||||
|
||||
- for Pezkuwichain <> Zagros bridge: [pezkuwichain-zagros](https://github.com/paritytech/parity-bridges-common/tree/master/deployments/bridges/rococo-westend).
|
||||
|
||||
- for Pezkuwi <> Kusama bridge: *work in progress, coming soon*
|
||||
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Parachains Finality Relay</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Parachains Finality Relay</h1>
|
||||
<p>
|
||||
Source Relay Chain is running GRANDPA Finality Gadget. Source Parachain is a parachain of the Source
|
||||
Relay Chain. Bridge GRANDPA finality pallet is deployed at Target Chain runtime and is "connected"
|
||||
to the Source Relay Chain. Bridge Parachains finality pallet is deployed at Target Chain and is
|
||||
configured to track the Source Parachain. GRANDPA Relayer is configured to relay Source Relay Chain
|
||||
finality to Target Chain. Parachains Relayer is configured to relay Source Parachain headers finality
|
||||
to Target Chain.
|
||||
</p>
|
||||
<div class="mermaid">
|
||||
sequenceDiagram
|
||||
participant Source Parachain
|
||||
participant Source Relay Chain
|
||||
participant GRANDPA Relayer
|
||||
participant Parachains Relayer
|
||||
participant Target Chain
|
||||
|
||||
Note left of Source Parachain: Best: 125
|
||||
Note left of Source Relay Chain: Finalized: 500, Best Parachain at Finalized: 120
|
||||
Note right of Target Chain: Best Relay: 480, Best Parachain: 110
|
||||
|
||||
Source Parachain ->> Source Parachain: Import Block 126
|
||||
Source Parachain ->> Source Relay Chain: Receives the Parachain block 126
|
||||
|
||||
Source Relay Chain ->> Source Relay Chain: Import block 501
|
||||
Source Relay Chain ->> Source Relay Chain: Finalize block 501
|
||||
Note left of Source Relay Chain: Finalized: 501, Best Parachain at Finalized: 126
|
||||
|
||||
Source Relay Chain ->> Parachains Relayer: notes new Source Parachain Block 126
|
||||
Note left of Parachains Relayer: can't relay Source Parachain Block 126, because it requires at least Source Relay Block 501 at Target Chain
|
||||
|
||||
Source Relay Chain ->> Source Relay Chain: Import block 502
|
||||
Source Relay Chain ->> Source Relay Chain: Finalize block 502
|
||||
|
||||
Source Relay Chain ->> GRANDPA Relayer: read GRANDPA Finality Proof of Block 502
|
||||
GRANDPA Relayer ->> Target Chain: submit GRANDPA Finality Proof of Block 502
|
||||
Note right of Target Chain: Best Relay: 502, Best Parachain: 110
|
||||
|
||||
Target Chain ->> Parachains Relayer: notes finalized Source Relay Block 502 at Target Chain
|
||||
Source Relay Chain ->> Parachains Relayer: read Parachain Finality Proof at Relay Block 502
|
||||
Parachains Relayer ->> Target Chain: submit Parachain Finality Proof at Relay Block 502
|
||||
Note right of Target Chain: Best Relay: 502, Best Parachain: 126
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@8.8.4/dist/mermaid.min.js"></script>
|
||||
<script>mermaid.initialize({startOnLoad: true})</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,75 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-beefy"
|
||||
version = "0.1.0"
|
||||
description = "Module implementing BEEFY on-chain light client used for bridging consensus of bizinikiwi-based chains."
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
publish = false
|
||||
documentation = "https://docs.rs/pezpallet-bridge-beefy"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { optional = true, workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-beefy = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-test-utils = { workspace = true, default-features = true }
|
||||
mmr-lib = { workspace = true, default-features = true }
|
||||
pezpallet-beefy-mmr = { workspace = true, default-features = true }
|
||||
pezpallet-mmr = { workspace = true, default-features = true }
|
||||
rand = { workspace = true, default-features = true }
|
||||
pezsp-consensus-beefy = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-beefy/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-beefy-mmr/try-runtime",
|
||||
"pezpallet-mmr/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-beefy/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-beefy-mmr/runtime-benchmarks",
|
||||
"pezpallet-mmr/runtime-benchmarks",
|
||||
"pezsp-consensus-beefy/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,657 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! BEEFY bridge pezpallet.
|
||||
//!
|
||||
//! This pezpallet is an on-chain BEEFY light client for Bizinikiwi-based chains that are using the
|
||||
//! following pallets bundle: `pezpallet-mmr`, `pezpallet-beefy` and `pezpallet-beefy-mmr`.
|
||||
//!
|
||||
//! The pezpallet is able to verify MMR leaf proofs and BEEFY commitments, so it has access
|
||||
//! to the following data of the bridged chain:
|
||||
//!
|
||||
//! - header hashes
|
||||
//! - changes of BEEFY authorities
|
||||
//! - extra data of MMR leafs
|
||||
//!
|
||||
//! Given the header hash, other pallets are able to verify header-based proofs
|
||||
//! (e.g. storage proofs, transaction inclusion proofs, etc.).
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_beefy::{ChainWithBeefy, InitializationData};
|
||||
use pezsp_std::{boxed::Box, prelude::*};
|
||||
|
||||
// Re-export in crate namespace for `construct_runtime!`
|
||||
pub use pezpallet::*;
|
||||
|
||||
mod utils;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod mock_chain;
|
||||
|
||||
/// The target that will be used when publishing logs related to this pezpallet.
|
||||
pub const LOG_TARGET: &str = "runtime::bridge-beefy";
|
||||
|
||||
/// Configured bridged chain.
|
||||
pub type BridgedChain<T, I> = <T as Config<I>>::BridgedChain;
|
||||
/// Block number, used by configured bridged chain.
|
||||
pub type BridgedBlockNumber<T, I> = pezbp_runtime::BlockNumberOf<BridgedChain<T, I>>;
|
||||
/// Block hash, used by configured bridged chain.
|
||||
pub type BridgedBlockHash<T, I> = pezbp_runtime::HashOf<BridgedChain<T, I>>;
|
||||
|
||||
/// Pezpallet initialization data.
|
||||
pub type InitializationDataOf<T, I> =
|
||||
InitializationData<BridgedBlockNumber<T, I>, bp_beefy::MmrHashOf<BridgedChain<T, I>>>;
|
||||
/// BEEFY commitment hasher, used by configured bridged chain.
|
||||
pub type BridgedBeefyCommitmentHasher<T, I> = bp_beefy::BeefyCommitmentHasher<BridgedChain<T, I>>;
|
||||
/// BEEFY validator id, used by configured bridged chain.
|
||||
pub type BridgedBeefyAuthorityId<T, I> = bp_beefy::BeefyAuthorityIdOf<BridgedChain<T, I>>;
|
||||
/// BEEFY validator set, used by configured bridged chain.
|
||||
pub type BridgedBeefyAuthoritySet<T, I> = bp_beefy::BeefyAuthoritySetOf<BridgedChain<T, I>>;
|
||||
/// BEEFY authority set, used by configured bridged chain.
|
||||
pub type BridgedBeefyAuthoritySetInfo<T, I> = bp_beefy::BeefyAuthoritySetInfoOf<BridgedChain<T, I>>;
|
||||
/// BEEFY signed commitment, used by configured bridged chain.
|
||||
pub type BridgedBeefySignedCommitment<T, I> = bp_beefy::BeefySignedCommitmentOf<BridgedChain<T, I>>;
|
||||
/// MMR hashing algorithm, used by configured bridged chain.
|
||||
pub type BridgedMmrHashing<T, I> = bp_beefy::MmrHashingOf<BridgedChain<T, I>>;
|
||||
/// MMR hashing output type of `BridgedMmrHashing<T, I>`.
|
||||
pub type BridgedMmrHash<T, I> = bp_beefy::MmrHashOf<BridgedChain<T, I>>;
|
||||
/// The type of the MMR leaf extra data used by the configured bridged chain.
|
||||
pub type BridgedBeefyMmrLeafExtra<T, I> = bp_beefy::BeefyMmrLeafExtraOf<BridgedChain<T, I>>;
|
||||
/// BEEFY MMR proof type used by the pezpallet
|
||||
pub type BridgedMmrProof<T, I> = bp_beefy::MmrProofOf<BridgedChain<T, I>>;
|
||||
/// MMR leaf type, used by configured bridged chain.
|
||||
pub type BridgedBeefyMmrLeaf<T, I> = bp_beefy::BeefyMmrLeafOf<BridgedChain<T, I>>;
|
||||
/// Imported commitment data, stored by the pezpallet.
|
||||
pub type ImportedCommitment<T, I> = bp_beefy::ImportedCommitment<
|
||||
BridgedBlockNumber<T, I>,
|
||||
BridgedBlockHash<T, I>,
|
||||
BridgedMmrHash<T, I>,
|
||||
>;
|
||||
|
||||
/// Some high level info about the imported commitments.
|
||||
#[derive(codec::Encode, codec::Decode, scale_info::TypeInfo)]
|
||||
pub struct ImportedCommitmentsInfoData<BlockNumber> {
|
||||
/// Best known block number, provided in a BEEFY commitment. However this is not
|
||||
/// the best proven block. The best proven block is this block's parent.
|
||||
best_block_number: BlockNumber,
|
||||
/// The head of the `ImportedBlockNumbers` ring buffer.
|
||||
next_block_number_index: u32,
|
||||
}
|
||||
|
||||
#[pezframe_support::pezpallet(dev_mode)]
|
||||
pub mod pezpallet {
|
||||
use super::*;
|
||||
use pezbp_runtime::{BasicOperatingMode, OwnedBridgeModule};
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pezpallet::config]
|
||||
pub trait Config<I: 'static = ()>: pezframe_system::Config {
|
||||
/// The upper bound on the number of requests allowed by the pezpallet.
|
||||
///
|
||||
/// A request refers to an action which writes a header to storage.
|
||||
///
|
||||
/// Once this bound is reached the pezpallet will reject all commitments
|
||||
/// until the request count has decreased.
|
||||
#[pezpallet::constant]
|
||||
type MaxRequests: Get<u32>;
|
||||
|
||||
/// Maximal number of imported commitments to keep in the storage.
|
||||
///
|
||||
/// The setting is there to prevent growing the on-chain state indefinitely. Note
|
||||
/// the setting does not relate to block numbers - we will simply keep as much items
|
||||
/// in the storage, so it doesn't guarantee any fixed timeframe for imported commitments.
|
||||
#[pezpallet::constant]
|
||||
type CommitmentsToKeep: Get<u32>;
|
||||
|
||||
/// The chain we are bridging to here.
|
||||
type BridgedChain: ChainWithBeefy;
|
||||
}
|
||||
|
||||
#[pezpallet::pezpallet]
|
||||
#[pezpallet::without_storage_info]
|
||||
pub struct Pezpallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
#[pezpallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pezpallet<T, I> {
|
||||
fn on_initialize(_n: BlockNumberFor<T>) -> pezframe_support::weights::Weight {
|
||||
<RequestCount<T, I>>::mutate(|count| *count = count.saturating_sub(1));
|
||||
|
||||
Weight::from_parts(0, 0)
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pezpallet<T, I> {
|
||||
const LOG_TARGET: &'static str = LOG_TARGET;
|
||||
type OwnerStorage = PalletOwner<T, I>;
|
||||
type OperatingMode = BasicOperatingMode;
|
||||
type OperatingModeStorage = PalletOperatingMode<T, I>;
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I>
|
||||
where
|
||||
BridgedMmrHashing<T, I>: 'static + Send + Sync,
|
||||
{
|
||||
/// Initialize pezpallet with BEEFY authority set and best known finalized block number.
|
||||
#[pezpallet::call_index(0)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(2, 3), DispatchClass::Operational))]
|
||||
pub fn initialize(
|
||||
origin: OriginFor<T>,
|
||||
init_data: InitializationDataOf<T, I>,
|
||||
) -> DispatchResult {
|
||||
Self::ensure_owner_or_root(origin)?;
|
||||
|
||||
let is_initialized = <ImportedCommitmentsInfo<T, I>>::exists();
|
||||
ensure!(!is_initialized, <Error<T, I>>::AlreadyInitialized);
|
||||
|
||||
tracing::info!(target: LOG_TARGET, ?init_data, "Initializing bridge BEEFY pezpallet");
|
||||
Ok(initialize::<T, I>(init_data)?)
|
||||
}
|
||||
|
||||
/// Change `PalletOwner`.
|
||||
///
|
||||
/// May only be called either by root, or by `PalletOwner`.
|
||||
#[pezpallet::call_index(1)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
|
||||
<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
|
||||
}
|
||||
|
||||
/// Halt or resume all pezpallet operations.
|
||||
///
|
||||
/// May only be called either by root, or by `PalletOwner`.
|
||||
#[pezpallet::call_index(2)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_operating_mode(
|
||||
origin: OriginFor<T>,
|
||||
operating_mode: BasicOperatingMode,
|
||||
) -> DispatchResult {
|
||||
<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
|
||||
}
|
||||
|
||||
/// Submit a commitment generated by BEEFY authority set.
|
||||
///
|
||||
/// It will use the underlying storage pezpallet to fetch information about the current
|
||||
/// authority set and best finalized block number in order to verify that the commitment
|
||||
/// is valid.
|
||||
///
|
||||
/// If successful in verification, it will update the underlying storage with the data
|
||||
/// provided in the newly submitted commitment.
|
||||
#[pezpallet::call_index(3)]
|
||||
#[pezpallet::weight(0)]
|
||||
pub fn submit_commitment(
|
||||
origin: OriginFor<T>,
|
||||
commitment: BridgedBeefySignedCommitment<T, I>,
|
||||
validator_set: BridgedBeefyAuthoritySet<T, I>,
|
||||
mmr_leaf: Box<BridgedBeefyMmrLeaf<T, I>>,
|
||||
mmr_proof: BridgedMmrProof<T, I>,
|
||||
) -> DispatchResult
|
||||
where
|
||||
BridgedBeefySignedCommitment<T, I>: Clone,
|
||||
{
|
||||
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
|
||||
ensure_signed(origin)?;
|
||||
|
||||
ensure!(Self::request_count() < T::MaxRequests::get(), <Error<T, I>>::TooManyRequests);
|
||||
|
||||
// Ensure that the commitment is for a better block.
|
||||
let commitments_info =
|
||||
ImportedCommitmentsInfo::<T, I>::get().ok_or(Error::<T, I>::NotInitialized)?;
|
||||
ensure!(
|
||||
commitment.commitment.block_number > commitments_info.best_block_number,
|
||||
Error::<T, I>::OldCommitment
|
||||
);
|
||||
|
||||
// Verify commitment and mmr leaf.
|
||||
let current_authority_set_info = CurrentAuthoritySetInfo::<T, I>::get();
|
||||
let mmr_root = utils::verify_commitment::<T, I>(
|
||||
&commitment,
|
||||
¤t_authority_set_info,
|
||||
&validator_set,
|
||||
)?;
|
||||
utils::verify_beefy_mmr_leaf::<T, I>(&mmr_leaf, mmr_proof, mmr_root)?;
|
||||
|
||||
// Update request count.
|
||||
RequestCount::<T, I>::mutate(|count| *count += 1);
|
||||
// Update authority set if needed.
|
||||
if mmr_leaf.beefy_next_authority_set.id > current_authority_set_info.id {
|
||||
CurrentAuthoritySetInfo::<T, I>::put(mmr_leaf.beefy_next_authority_set);
|
||||
}
|
||||
|
||||
// Import commitment.
|
||||
let block_number_index = commitments_info.next_block_number_index;
|
||||
let to_prune = ImportedBlockNumbers::<T, I>::try_get(block_number_index);
|
||||
ImportedCommitments::<T, I>::insert(
|
||||
commitment.commitment.block_number,
|
||||
ImportedCommitment::<T, I> {
|
||||
parent_number_and_hash: mmr_leaf.parent_number_and_hash,
|
||||
mmr_root,
|
||||
},
|
||||
);
|
||||
ImportedBlockNumbers::<T, I>::insert(
|
||||
block_number_index,
|
||||
commitment.commitment.block_number,
|
||||
);
|
||||
ImportedCommitmentsInfo::<T, I>::put(ImportedCommitmentsInfoData {
|
||||
best_block_number: commitment.commitment.block_number,
|
||||
next_block_number_index: (block_number_index + 1) % T::CommitmentsToKeep::get(),
|
||||
});
|
||||
if let Ok(old_block_number) = to_prune {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?old_block_number,
|
||||
"Pruning commitment for old block."
|
||||
);
|
||||
ImportedCommitments::<T, I>::remove(old_block_number);
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
block=?commitment.commitment.block_number,
|
||||
"Successfully imported commitment for block",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The current number of requests which have written to storage.
|
||||
///
|
||||
/// If the `RequestCount` hits `MaxRequests`, no more calls will be allowed to the pezpallet until
|
||||
/// the request capacity is increased.
|
||||
///
|
||||
/// The `RequestCount` is decreased by one at the beginning of every block. This is to ensure
|
||||
/// that the pezpallet can always make progress.
|
||||
#[pezpallet::storage]
|
||||
pub type RequestCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// High level info about the imported commitments.
|
||||
///
|
||||
/// Contains the following info:
|
||||
/// - best known block number of the bridged chain, finalized by BEEFY
|
||||
/// - the head of the `ImportedBlockNumbers` ring buffer
|
||||
#[pezpallet::storage]
|
||||
pub type ImportedCommitmentsInfo<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, ImportedCommitmentsInfoData<BridgedBlockNumber<T, I>>>;
|
||||
|
||||
/// A ring buffer containing the block numbers of the commitments that we have imported,
|
||||
/// ordered by the insertion time.
|
||||
#[pezpallet::storage]
|
||||
pub(super) type ImportedBlockNumbers<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Identity, u32, BridgedBlockNumber<T, I>>;
|
||||
|
||||
/// All the commitments that we have imported and haven't been pruned yet.
|
||||
#[pezpallet::storage]
|
||||
pub type ImportedCommitments<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Blake2_128Concat, BridgedBlockNumber<T, I>, ImportedCommitment<T, I>>;
|
||||
|
||||
/// The current BEEFY authority set at the bridged chain.
|
||||
#[pezpallet::storage]
|
||||
pub type CurrentAuthoritySetInfo<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, BridgedBeefyAuthoritySetInfo<T, I>, ValueQuery>;
|
||||
|
||||
/// Optional pezpallet owner.
|
||||
///
|
||||
/// Pezpallet owner has the right to halt all pezpallet operations and then resume it. If it is
|
||||
/// `None`, then there are no direct ways to halt/resume pezpallet operations, but other
|
||||
/// runtime methods may still be used to do that (i.e. `democracy::referendum` to update halt
|
||||
/// flag directly or calling `set_operating_mode`).
|
||||
#[pezpallet::storage]
|
||||
pub type PalletOwner<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, T::AccountId, OptionQuery>;
|
||||
|
||||
/// The current operating mode of the pezpallet.
|
||||
///
|
||||
/// Depending on the mode either all, or no transactions will be allowed.
|
||||
#[pezpallet::storage]
|
||||
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, BasicOperatingMode, ValueQuery>;
|
||||
|
||||
#[pezpallet::genesis_config]
|
||||
#[derive(pezframe_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
||||
/// Optional module owner account.
|
||||
pub owner: Option<T::AccountId>,
|
||||
/// Optional module initialization data.
|
||||
pub init_data: Option<InitializationDataOf<T, I>>,
|
||||
}
|
||||
|
||||
#[pezpallet::genesis_build]
|
||||
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
|
||||
fn build(&self) {
|
||||
if let Some(ref owner) = self.owner {
|
||||
<PalletOwner<T, I>>::put(owner);
|
||||
}
|
||||
|
||||
if let Some(init_data) = self.init_data.clone() {
|
||||
initialize::<T, I>(init_data)
|
||||
.expect("invalid initialization data of BEEFY bridge pezpallet");
|
||||
} else {
|
||||
// Since the bridge hasn't been initialized we shouldn't allow anyone to perform
|
||||
// transactions.
|
||||
<PalletOperatingMode<T, I>>::put(BasicOperatingMode::Halted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// The pezpallet has not been initialized yet.
|
||||
NotInitialized,
|
||||
/// The pezpallet has already been initialized.
|
||||
AlreadyInitialized,
|
||||
/// Invalid initial authority set.
|
||||
InvalidInitialAuthoritySet,
|
||||
/// There are too many requests for the current window to handle.
|
||||
TooManyRequests,
|
||||
/// The imported commitment is older than the best commitment known to the pezpallet.
|
||||
OldCommitment,
|
||||
/// The commitment is signed by unknown validator set.
|
||||
InvalidCommitmentValidatorSetId,
|
||||
/// The id of the provided validator set is invalid.
|
||||
InvalidValidatorSetId,
|
||||
/// The number of signatures in the commitment is invalid.
|
||||
InvalidCommitmentSignaturesLen,
|
||||
/// The number of validator ids provided is invalid.
|
||||
InvalidValidatorSetLen,
|
||||
/// There aren't enough correct signatures in the commitment to finalize the block.
|
||||
NotEnoughCorrectSignatures,
|
||||
/// MMR root is missing from the commitment.
|
||||
MmrRootMissingFromCommitment,
|
||||
/// MMR proof verification has failed.
|
||||
MmrProofVerificationFailed,
|
||||
/// The validators are not matching the merkle tree root of the authority set.
|
||||
InvalidValidatorSetRoot,
|
||||
/// Error generated by the `OwnedBridgeModule` trait.
|
||||
BridgeModule(pezbp_runtime::OwnedBridgeModuleError),
|
||||
}
|
||||
|
||||
/// Initialize pezpallet with given parameters.
|
||||
pub(super) fn initialize<T: Config<I>, I: 'static>(
|
||||
init_data: InitializationDataOf<T, I>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
if init_data.authority_set.len == 0 {
|
||||
return Err(Error::<T, I>::InvalidInitialAuthoritySet);
|
||||
}
|
||||
CurrentAuthoritySetInfo::<T, I>::put(init_data.authority_set);
|
||||
|
||||
<PalletOperatingMode<T, I>>::put(init_data.operating_mode);
|
||||
ImportedCommitmentsInfo::<T, I>::put(ImportedCommitmentsInfoData {
|
||||
best_block_number: init_data.best_block_number,
|
||||
next_block_number_index: 0,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// The current number of requests which have written to storage.
|
||||
pub fn request_count() -> u32 {
|
||||
RequestCount::<T, I>::get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezbp_runtime::{BasicOperatingMode, OwnedBridgeModuleError};
|
||||
use bp_test_utils::generate_owned_bridge_module_tests;
|
||||
use pezframe_support::{assert_noop, assert_ok, traits::Get};
|
||||
use mock::*;
|
||||
use mock_chain::*;
|
||||
use pezsp_consensus_beefy::mmr::BeefyAuthoritySet;
|
||||
use pezsp_runtime::DispatchError;
|
||||
|
||||
fn next_block() {
|
||||
use pezframe_support::traits::OnInitialize;
|
||||
|
||||
let current_number = pezframe_system::Pezpallet::<TestRuntime>::block_number();
|
||||
pezframe_system::Pezpallet::<TestRuntime>::set_block_number(current_number + 1);
|
||||
let _ = Pezpallet::<TestRuntime>::on_initialize(current_number);
|
||||
}
|
||||
|
||||
fn import_header_chain(headers: Vec<HeaderAndCommitment>) {
|
||||
for header in headers {
|
||||
if header.commitment.is_some() {
|
||||
assert_ok!(import_commitment(header));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_initialize_if_already_initialized() {
|
||||
run_test_with_initialize(32, || {
|
||||
assert_noop!(
|
||||
Pezpallet::<TestRuntime>::initialize(
|
||||
RuntimeOrigin::root(),
|
||||
InitializationData {
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
best_block_number: 0,
|
||||
authority_set: BeefyAuthoritySet {
|
||||
id: 0,
|
||||
len: 1,
|
||||
keyset_commitment: [0u8; 32].into()
|
||||
}
|
||||
}
|
||||
),
|
||||
Error::<TestRuntime, ()>::AlreadyInitialized,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_initialize_if_authority_set_is_empty() {
|
||||
run_test(|| {
|
||||
assert_noop!(
|
||||
Pezpallet::<TestRuntime>::initialize(
|
||||
RuntimeOrigin::root(),
|
||||
InitializationData {
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
best_block_number: 0,
|
||||
authority_set: BeefyAuthoritySet {
|
||||
id: 0,
|
||||
len: 0,
|
||||
keyset_commitment: [0u8; 32].into()
|
||||
}
|
||||
}
|
||||
),
|
||||
Error::<TestRuntime, ()>::InvalidInitialAuthoritySet,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_commitment_if_halted() {
|
||||
run_test_with_initialize(1, || {
|
||||
assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
|
||||
RuntimeOrigin::root(),
|
||||
BasicOperatingMode::Halted
|
||||
));
|
||||
assert_noop!(
|
||||
import_commitment(ChainBuilder::new(1).append_finalized_header().to_header()),
|
||||
Error::<TestRuntime, ()>::BridgeModule(OwnedBridgeModuleError::Halted),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_commitment_if_too_many_requests() {
|
||||
run_test_with_initialize(1, || {
|
||||
let max_requests = <<TestRuntime as Config>::MaxRequests as Get<u32>>::get() as u64;
|
||||
let mut chain = ChainBuilder::new(1);
|
||||
for _ in 0..max_requests + 2 {
|
||||
chain = chain.append_finalized_header();
|
||||
}
|
||||
|
||||
// import `max_request` headers
|
||||
for i in 0..max_requests {
|
||||
assert_ok!(import_commitment(chain.header(i + 1)));
|
||||
}
|
||||
|
||||
// try to import next header: it fails because we are no longer accepting commitments
|
||||
assert_noop!(
|
||||
import_commitment(chain.header(max_requests + 1)),
|
||||
Error::<TestRuntime, ()>::TooManyRequests,
|
||||
);
|
||||
|
||||
// when next block is "started", we allow import of next header
|
||||
next_block();
|
||||
assert_ok!(import_commitment(chain.header(max_requests + 1)));
|
||||
|
||||
// but we can't import two headers until next block and so on
|
||||
assert_noop!(
|
||||
import_commitment(chain.header(max_requests + 2)),
|
||||
Error::<TestRuntime, ()>::TooManyRequests,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_commitment_if_not_initialized() {
|
||||
run_test(|| {
|
||||
assert_noop!(
|
||||
import_commitment(ChainBuilder::new(1).append_finalized_header().to_header()),
|
||||
Error::<TestRuntime, ()>::NotInitialized,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_works_with_long_chain_with_handoffs() {
|
||||
run_test_with_initialize(3, || {
|
||||
let chain = ChainBuilder::new(3)
|
||||
.append_finalized_header()
|
||||
.append_default_headers(16) // 2..17
|
||||
.append_finalized_header() // 18
|
||||
.append_default_headers(16) // 19..34
|
||||
.append_handoff_header(9) // 35
|
||||
.append_default_headers(8) // 36..43
|
||||
.append_finalized_header() // 44
|
||||
.append_default_headers(8) // 45..52
|
||||
.append_handoff_header(17) // 53
|
||||
.append_default_headers(4) // 54..57
|
||||
.append_finalized_header() // 58
|
||||
.append_default_headers(4); // 59..63
|
||||
import_header_chain(chain.to_chain());
|
||||
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().best_block_number,
|
||||
58
|
||||
);
|
||||
assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().id, 2);
|
||||
assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().len, 17);
|
||||
|
||||
let imported_commitment = ImportedCommitments::<TestRuntime>::get(58).unwrap();
|
||||
assert_eq!(
|
||||
imported_commitment,
|
||||
bp_beefy::ImportedCommitment {
|
||||
parent_number_and_hash: (57, chain.header(57).header.hash()),
|
||||
mmr_root: chain.header(58).mmr_root,
|
||||
},
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commitment_pruning_works() {
|
||||
run_test_with_initialize(3, || {
|
||||
let commitments_to_keep = <TestRuntime as Config<()>>::CommitmentsToKeep::get();
|
||||
let commitments_to_import: Vec<HeaderAndCommitment> = ChainBuilder::new(3)
|
||||
.append_finalized_headers(commitments_to_keep as usize + 2)
|
||||
.to_chain();
|
||||
|
||||
// import exactly `CommitmentsToKeep` commitments
|
||||
for index in 0..commitments_to_keep {
|
||||
next_block();
|
||||
import_commitment(commitments_to_import[index as usize].clone())
|
||||
.expect("must succeed");
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().next_block_number_index,
|
||||
(index + 1) % commitments_to_keep
|
||||
);
|
||||
}
|
||||
|
||||
// ensure that all commitments are in the storage
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().best_block_number,
|
||||
commitments_to_keep as TestBridgedBlockNumber
|
||||
);
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().next_block_number_index,
|
||||
0
|
||||
);
|
||||
for index in 0..commitments_to_keep {
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(
|
||||
index as TestBridgedBlockNumber + 1
|
||||
)
|
||||
.is_some());
|
||||
assert_eq!(
|
||||
ImportedBlockNumbers::<TestRuntime>::get(index),
|
||||
Some(Into::into(index + 1)),
|
||||
);
|
||||
}
|
||||
|
||||
// import next commitment
|
||||
next_block();
|
||||
import_commitment(commitments_to_import[commitments_to_keep as usize].clone())
|
||||
.expect("must succeed");
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().next_block_number_index,
|
||||
1
|
||||
);
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(
|
||||
commitments_to_keep as TestBridgedBlockNumber + 1
|
||||
)
|
||||
.is_some());
|
||||
assert_eq!(
|
||||
ImportedBlockNumbers::<TestRuntime>::get(0),
|
||||
Some(Into::into(commitments_to_keep + 1)),
|
||||
);
|
||||
// the side effect of the import is that the commitment#1 is pruned
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(1).is_none());
|
||||
|
||||
// import next commitment
|
||||
next_block();
|
||||
import_commitment(commitments_to_import[commitments_to_keep as usize + 1].clone())
|
||||
.expect("must succeed");
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().next_block_number_index,
|
||||
2
|
||||
);
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(
|
||||
commitments_to_keep as TestBridgedBlockNumber + 2
|
||||
)
|
||||
.is_some());
|
||||
assert_eq!(
|
||||
ImportedBlockNumbers::<TestRuntime>::get(1),
|
||||
Some(Into::into(commitments_to_keep + 2)),
|
||||
);
|
||||
// the side effect of the import is that the commitment#2 is pruned
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(1).is_none());
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(2).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate as beefy;
|
||||
use crate::{
|
||||
utils::get_authorities_mmr_root, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo,
|
||||
BridgedBeefyCommitmentHasher, BridgedBeefyMmrLeafExtra, BridgedBeefySignedCommitment,
|
||||
BridgedMmrHash, BridgedMmrHashing, BridgedMmrProof,
|
||||
};
|
||||
|
||||
use bp_beefy::{BeefyValidatorSignatureOf, ChainWithBeefy, Commitment, MmrDataOrHash};
|
||||
use pezbp_runtime::{BasicOperatingMode, Chain, ChainId};
|
||||
use codec::Encode;
|
||||
use pezframe_support::{construct_runtime, derive_impl, weights::Weight};
|
||||
use pezsp_core::{sr25519::Signature, Pair};
|
||||
use pezsp_runtime::{
|
||||
testing::{Header, H256},
|
||||
traits::{BlakeTwo256, Hash},
|
||||
StateVersion,
|
||||
};
|
||||
|
||||
pub use pezsp_consensus_beefy::ecdsa_crypto::{AuthorityId as BeefyId, Pair as BeefyPair};
|
||||
use pezsp_core::crypto::Wraps;
|
||||
use pezsp_runtime::traits::Keccak256;
|
||||
|
||||
pub type TestAccountId = u64;
|
||||
pub type TestBridgedBlockNumber = u64;
|
||||
pub type TestBridgedBlockHash = H256;
|
||||
pub type TestBridgedHeader = Header;
|
||||
pub type TestBridgedAuthoritySetInfo = BridgedBeefyAuthoritySetInfo<TestRuntime, ()>;
|
||||
pub type TestBridgedValidatorSet = BridgedBeefyAuthoritySet<TestRuntime, ()>;
|
||||
pub type TestBridgedCommitment = BridgedBeefySignedCommitment<TestRuntime, ()>;
|
||||
pub type TestBridgedValidatorSignature = BeefyValidatorSignatureOf<TestBridgedChain>;
|
||||
pub type TestBridgedCommitmentHasher = BridgedBeefyCommitmentHasher<TestRuntime, ()>;
|
||||
pub type TestBridgedMmrHashing = BridgedMmrHashing<TestRuntime, ()>;
|
||||
pub type TestBridgedMmrHash = BridgedMmrHash<TestRuntime, ()>;
|
||||
pub type TestBridgedBeefyMmrLeafExtra = BridgedBeefyMmrLeafExtra<TestRuntime, ()>;
|
||||
pub type TestBridgedMmrProof = BridgedMmrProof<TestRuntime, ()>;
|
||||
pub type TestBridgedRawMmrLeaf = pezsp_consensus_beefy::mmr::MmrLeaf<
|
||||
TestBridgedBlockNumber,
|
||||
TestBridgedBlockHash,
|
||||
TestBridgedMmrHash,
|
||||
TestBridgedBeefyMmrLeafExtra,
|
||||
>;
|
||||
pub type TestBridgedMmrNode = MmrDataOrHash<Keccak256, TestBridgedRawMmrLeaf>;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Beefy: beefy::{Pezpallet},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
impl beefy::Config for TestRuntime {
|
||||
type MaxRequests = pezframe_support::traits::ConstU32<16>;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type CommitmentsToKeep = pezframe_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
const ID: ChainId = *b"tbch";
|
||||
|
||||
type BlockNumber = TestBridgedBlockNumber;
|
||||
type Hash = H256;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = pezsp_runtime::testing::Header;
|
||||
|
||||
type AccountId = TestAccountId;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = Signature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
unreachable!()
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithBeefy for TestBridgedChain {
|
||||
type CommitmentHasher = Keccak256;
|
||||
type MmrHashing = Keccak256;
|
||||
type MmrHash = <Keccak256 as Hash>::Output;
|
||||
type BeefyMmrLeafExtra = ();
|
||||
type AuthorityId = BeefyId;
|
||||
type AuthorityIdToMerkleLeaf = pezpallet_beefy_mmr::BeefyEcdsaToEthereum;
|
||||
}
|
||||
|
||||
/// Run test within test runtime.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
pezsp_io::TestExternalities::new(Default::default()).execute_with(test)
|
||||
}
|
||||
|
||||
/// Initialize pezpallet and run test.
|
||||
pub fn run_test_with_initialize<T>(initial_validators_count: u32, test: impl FnOnce() -> T) -> T {
|
||||
run_test(|| {
|
||||
let validators = validator_ids(0, initial_validators_count);
|
||||
let authority_set = authority_set_info(0, &validators);
|
||||
|
||||
crate::Pezpallet::<TestRuntime>::initialize(
|
||||
RuntimeOrigin::root(),
|
||||
bp_beefy::InitializationData {
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
best_block_number: 0,
|
||||
authority_set,
|
||||
},
|
||||
)
|
||||
.expect("initialization data is correct");
|
||||
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
/// Import given commitment.
|
||||
pub fn import_commitment(
|
||||
header: crate::mock_chain::HeaderAndCommitment,
|
||||
) -> pezsp_runtime::DispatchResult {
|
||||
crate::Pezpallet::<TestRuntime>::submit_commitment(
|
||||
RuntimeOrigin::signed(1),
|
||||
header
|
||||
.commitment
|
||||
.expect("thou shall not call import_commitment on header without commitment"),
|
||||
header.validator_set,
|
||||
Box::new(header.leaf),
|
||||
header.leaf_proof,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn validator_pairs(index: u32, count: u32) -> Vec<BeefyPair> {
|
||||
(index..index + count)
|
||||
.map(|index| {
|
||||
let mut seed = [1u8; 32];
|
||||
seed[0..8].copy_from_slice(&(index as u64).encode());
|
||||
BeefyPair::from_seed(&seed)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return identifiers of validators, starting at given index.
|
||||
pub fn validator_ids(index: u32, count: u32) -> Vec<BeefyId> {
|
||||
validator_pairs(index, count).into_iter().map(|pair| pair.public()).collect()
|
||||
}
|
||||
|
||||
pub fn authority_set_info(id: u64, validators: &[BeefyId]) -> TestBridgedAuthoritySetInfo {
|
||||
let merkle_root = get_authorities_mmr_root::<TestRuntime, (), _>(validators.iter());
|
||||
|
||||
TestBridgedAuthoritySetInfo { id, len: validators.len() as u32, keyset_commitment: merkle_root }
|
||||
}
|
||||
|
||||
/// Sign BEEFY commitment.
|
||||
pub fn sign_commitment(
|
||||
commitment: Commitment<TestBridgedBlockNumber>,
|
||||
validator_pairs: &[BeefyPair],
|
||||
signature_count: usize,
|
||||
) -> TestBridgedCommitment {
|
||||
let total_validators = validator_pairs.len();
|
||||
let random_validators =
|
||||
rand::seq::index::sample(&mut rand::thread_rng(), total_validators, signature_count);
|
||||
|
||||
let commitment_hash = TestBridgedCommitmentHasher::hash(&commitment.encode());
|
||||
let mut signatures = vec![None; total_validators];
|
||||
for validator_idx in random_validators.iter() {
|
||||
let validator = &validator_pairs[validator_idx];
|
||||
signatures[validator_idx] =
|
||||
Some(validator.as_inner_ref().sign_prehashed(commitment_hash.as_fixed_bytes()).into());
|
||||
}
|
||||
|
||||
TestBridgedCommitment { commitment, signatures }
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities to build bridged chain and BEEFY+MMR structures.
|
||||
|
||||
use crate::{
|
||||
mock::{
|
||||
sign_commitment, validator_pairs, BeefyPair, TestBridgedBlockNumber, TestBridgedCommitment,
|
||||
TestBridgedHeader, TestBridgedMmrHash, TestBridgedMmrHashing, TestBridgedMmrNode,
|
||||
TestBridgedMmrProof, TestBridgedRawMmrLeaf, TestBridgedValidatorSet,
|
||||
TestBridgedValidatorSignature, TestRuntime,
|
||||
},
|
||||
utils::get_authorities_mmr_root,
|
||||
};
|
||||
|
||||
use bp_beefy::{BeefyPayload, Commitment, ValidatorSetId, MMR_ROOT_PAYLOAD_ID};
|
||||
use codec::Encode;
|
||||
use pezpallet_mmr::NodeIndex;
|
||||
use rand::Rng;
|
||||
use pezsp_consensus_beefy::mmr::{BeefyNextAuthoritySet, MmrLeafVersion};
|
||||
use pezsp_core::Pair;
|
||||
use pezsp_runtime::traits::{Hash, Header as HeaderT};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HeaderAndCommitment {
|
||||
pub header: TestBridgedHeader,
|
||||
pub commitment: Option<TestBridgedCommitment>,
|
||||
pub validator_set: TestBridgedValidatorSet,
|
||||
pub leaf: TestBridgedRawMmrLeaf,
|
||||
pub leaf_proof: TestBridgedMmrProof,
|
||||
pub mmr_root: TestBridgedMmrHash,
|
||||
}
|
||||
|
||||
impl HeaderAndCommitment {
|
||||
pub fn customize_signatures(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Vec<Option<TestBridgedValidatorSignature>>),
|
||||
) {
|
||||
if let Some(commitment) = &mut self.commitment {
|
||||
f(&mut commitment.signatures);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn customize_commitment(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Commitment<TestBridgedBlockNumber>),
|
||||
validator_pairs: &[BeefyPair],
|
||||
signature_count: usize,
|
||||
) {
|
||||
if let Some(mut commitment) = self.commitment.take() {
|
||||
f(&mut commitment.commitment);
|
||||
self.commitment =
|
||||
Some(sign_commitment(commitment.commitment, validator_pairs, signature_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChainBuilder {
|
||||
headers: Vec<HeaderAndCommitment>,
|
||||
validator_set_id: ValidatorSetId,
|
||||
validator_keys: Vec<BeefyPair>,
|
||||
mmr: mmr_lib::MMR<TestBridgedMmrNode, BridgedMmrHashMerge, BridgedMmrStorage>,
|
||||
}
|
||||
|
||||
struct BridgedMmrStorage {
|
||||
nodes: HashMap<NodeIndex, TestBridgedMmrNode>,
|
||||
}
|
||||
|
||||
impl mmr_lib::MMRStoreReadOps<TestBridgedMmrNode> for BridgedMmrStorage {
|
||||
fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result<Option<TestBridgedMmrNode>> {
|
||||
Ok(self.nodes.get(&pos).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl mmr_lib::MMRStoreWriteOps<TestBridgedMmrNode> for BridgedMmrStorage {
|
||||
fn append(&mut self, pos: NodeIndex, elems: Vec<TestBridgedMmrNode>) -> mmr_lib::Result<()> {
|
||||
for (i, elem) in elems.into_iter().enumerate() {
|
||||
self.nodes.insert(pos + i as NodeIndex, elem);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainBuilder {
|
||||
/// Creates new chain builder with given validator set size.
|
||||
pub fn new(initial_validators_count: u32) -> Self {
|
||||
ChainBuilder {
|
||||
headers: Vec::new(),
|
||||
validator_set_id: 0,
|
||||
validator_keys: validator_pairs(0, initial_validators_count),
|
||||
mmr: mmr_lib::MMR::new(0, BridgedMmrStorage { nodes: HashMap::new() }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get header with given number.
|
||||
pub fn header(&self, number: TestBridgedBlockNumber) -> HeaderAndCommitment {
|
||||
self.headers[number as usize - 1].clone()
|
||||
}
|
||||
|
||||
/// Returns single built header.
|
||||
pub fn to_header(&self) -> HeaderAndCommitment {
|
||||
assert_eq!(self.headers.len(), 1);
|
||||
self.headers[0].clone()
|
||||
}
|
||||
|
||||
/// Returns built chain.
|
||||
pub fn to_chain(&self) -> Vec<HeaderAndCommitment> {
|
||||
self.headers.clone()
|
||||
}
|
||||
|
||||
/// Appends header, that has been finalized by BEEFY (so it has a linked signed commitment).
|
||||
pub fn append_finalized_header(self) -> Self {
|
||||
let next_validator_set_id = self.validator_set_id;
|
||||
let next_validator_keys = self.validator_keys.clone();
|
||||
HeaderBuilder::with_chain(self, next_validator_set_id, next_validator_keys).finalize()
|
||||
}
|
||||
|
||||
/// Append multiple finalized headers at once.
|
||||
pub fn append_finalized_headers(mut self, count: usize) -> Self {
|
||||
for _ in 0..count {
|
||||
self = self.append_finalized_header();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Appends header, that enacts new validator set.
|
||||
///
|
||||
/// Such headers are explicitly finalized by BEEFY.
|
||||
pub fn append_handoff_header(self, next_validators_len: u32) -> Self {
|
||||
let new_validator_set_id = self.validator_set_id + 1;
|
||||
let new_validator_pairs =
|
||||
validator_pairs(rand::thread_rng().gen::<u32>() % (u32::MAX / 2), next_validators_len);
|
||||
|
||||
HeaderBuilder::with_chain(self, new_validator_set_id, new_validator_pairs).finalize()
|
||||
}
|
||||
|
||||
/// Append several default header without commitment.
|
||||
pub fn append_default_headers(mut self, count: usize) -> Self {
|
||||
for _ in 0..count {
|
||||
let next_validator_set_id = self.validator_set_id;
|
||||
let next_validator_keys = self.validator_keys.clone();
|
||||
self =
|
||||
HeaderBuilder::with_chain(self, next_validator_set_id, next_validator_keys).build()
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom header builder.
|
||||
pub struct HeaderBuilder {
|
||||
chain: ChainBuilder,
|
||||
header: TestBridgedHeader,
|
||||
leaf: TestBridgedRawMmrLeaf,
|
||||
leaf_proof: Option<TestBridgedMmrProof>,
|
||||
next_validator_set_id: ValidatorSetId,
|
||||
next_validator_keys: Vec<BeefyPair>,
|
||||
}
|
||||
|
||||
impl HeaderBuilder {
|
||||
fn with_chain(
|
||||
chain: ChainBuilder,
|
||||
next_validator_set_id: ValidatorSetId,
|
||||
next_validator_keys: Vec<BeefyPair>,
|
||||
) -> Self {
|
||||
// we're starting with header#1, since header#0 is always finalized
|
||||
let header_number = chain.headers.len() as TestBridgedBlockNumber + 1;
|
||||
let header = TestBridgedHeader::new(
|
||||
header_number,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
chain.headers.last().map(|h| h.header.hash()).unwrap_or_default(),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let next_validators =
|
||||
next_validator_keys.iter().map(|pair| pair.public()).collect::<Vec<_>>();
|
||||
let next_validators_mmr_root =
|
||||
get_authorities_mmr_root::<TestRuntime, (), _>(next_validators.iter());
|
||||
let leaf = pezsp_consensus_beefy::mmr::MmrLeaf {
|
||||
version: MmrLeafVersion::new(1, 0),
|
||||
parent_number_and_hash: (header.number().saturating_sub(1), *header.parent_hash()),
|
||||
beefy_next_authority_set: BeefyNextAuthoritySet {
|
||||
id: next_validator_set_id,
|
||||
len: next_validators.len() as u32,
|
||||
keyset_commitment: next_validators_mmr_root,
|
||||
},
|
||||
leaf_extra: (),
|
||||
};
|
||||
|
||||
HeaderBuilder {
|
||||
chain,
|
||||
header,
|
||||
leaf,
|
||||
leaf_proof: None,
|
||||
next_validator_keys,
|
||||
next_validator_set_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Customize generated proof of header MMR leaf.
|
||||
///
|
||||
/// Can only be called once.
|
||||
pub fn customize_proof(
|
||||
mut self,
|
||||
f: impl FnOnce(TestBridgedMmrProof) -> TestBridgedMmrProof,
|
||||
) -> Self {
|
||||
assert!(self.leaf_proof.is_none());
|
||||
|
||||
let leaf_hash = TestBridgedMmrHashing::hash(&self.leaf.encode());
|
||||
let node = TestBridgedMmrNode::Hash(leaf_hash);
|
||||
let leaf_position = self.chain.mmr.push(node).unwrap();
|
||||
|
||||
let proof = self.chain.mmr.gen_proof(vec![leaf_position]).unwrap();
|
||||
// genesis has no leaf => leaf index is header number minus 1
|
||||
let leaf_index = *self.header.number() - 1;
|
||||
let leaf_count = *self.header.number();
|
||||
self.leaf_proof = Some(f(TestBridgedMmrProof {
|
||||
leaf_indices: vec![leaf_index],
|
||||
leaf_count,
|
||||
items: proof.proof_items().iter().map(|i| i.hash()).collect(),
|
||||
}));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Build header without commitment.
|
||||
pub fn build(mut self) -> ChainBuilder {
|
||||
if self.leaf_proof.is_none() {
|
||||
self = self.customize_proof(|proof| proof);
|
||||
}
|
||||
|
||||
let validators =
|
||||
self.chain.validator_keys.iter().map(|pair| pair.public()).collect::<Vec<_>>();
|
||||
self.chain.headers.push(HeaderAndCommitment {
|
||||
header: self.header,
|
||||
commitment: None,
|
||||
validator_set: TestBridgedValidatorSet::new(validators, self.chain.validator_set_id)
|
||||
.unwrap(),
|
||||
leaf: self.leaf,
|
||||
leaf_proof: self.leaf_proof.expect("guaranteed by the customize_proof call above; qed"),
|
||||
mmr_root: self.chain.mmr.get_root().unwrap().hash(),
|
||||
});
|
||||
|
||||
self.chain.validator_set_id = self.next_validator_set_id;
|
||||
self.chain.validator_keys = self.next_validator_keys;
|
||||
|
||||
self.chain
|
||||
}
|
||||
|
||||
/// Build header with commitment.
|
||||
pub fn finalize(self) -> ChainBuilder {
|
||||
let validator_count = self.chain.validator_keys.len();
|
||||
let current_validator_set_id = self.chain.validator_set_id;
|
||||
let current_validator_set_keys = self.chain.validator_keys.clone();
|
||||
let mut chain = self.build();
|
||||
|
||||
let last_header = chain.headers.last_mut().expect("added by append_header; qed");
|
||||
last_header.commitment = Some(sign_commitment(
|
||||
Commitment {
|
||||
payload: BeefyPayload::from_single_entry(
|
||||
MMR_ROOT_PAYLOAD_ID,
|
||||
chain.mmr.get_root().unwrap().hash().encode(),
|
||||
),
|
||||
block_number: *last_header.header.number(),
|
||||
validator_set_id: current_validator_set_id,
|
||||
},
|
||||
¤t_validator_set_keys,
|
||||
validator_count * 2 / 3 + 1,
|
||||
));
|
||||
|
||||
chain
|
||||
}
|
||||
}
|
||||
|
||||
/// Default Merging & Hashing behavior for MMR.
|
||||
pub struct BridgedMmrHashMerge;
|
||||
|
||||
impl mmr_lib::Merge for BridgedMmrHashMerge {
|
||||
type Item = TestBridgedMmrNode;
|
||||
|
||||
fn merge(left: &Self::Item, right: &Self::Item) -> mmr_lib::Result<Self::Item> {
|
||||
let mut concat = left.hash().as_ref().to_vec();
|
||||
concat.extend_from_slice(right.hash().as_ref());
|
||||
|
||||
Ok(TestBridgedMmrNode::Hash(TestBridgedMmrHashing::hash(&concat)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
use crate::{
|
||||
BridgedBeefyAuthorityId, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo,
|
||||
BridgedBeefyMmrLeaf, BridgedBeefySignedCommitment, BridgedChain, BridgedMmrHash,
|
||||
BridgedMmrHashing, BridgedMmrProof, Config, Error, LOG_TARGET,
|
||||
};
|
||||
use bp_beefy::{merkle_root, verify_mmr_leaves_proof, BeefyAuthorityId, MmrDataOrHash};
|
||||
use codec::Encode;
|
||||
use pezframe_support::ensure;
|
||||
use pezsp_runtime::traits::{Convert, Hash};
|
||||
use pezsp_std::{vec, vec::Vec};
|
||||
|
||||
type BridgedMmrDataOrHash<T, I> = MmrDataOrHash<BridgedMmrHashing<T, I>, BridgedBeefyMmrLeaf<T, I>>;
|
||||
/// A way to encode validator id to the BEEFY merkle tree leaf.
|
||||
type BridgedBeefyAuthorityIdToMerkleLeaf<T, I> =
|
||||
bp_beefy::BeefyAuthorityIdToMerkleLeafOf<BridgedChain<T, I>>;
|
||||
|
||||
/// Get the MMR root for a collection of validators.
|
||||
pub(crate) fn get_authorities_mmr_root<
|
||||
'a,
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
V: Iterator<Item = &'a BridgedBeefyAuthorityId<T, I>>,
|
||||
>(
|
||||
authorities: V,
|
||||
) -> BridgedMmrHash<T, I> {
|
||||
let merkle_leafs = authorities
|
||||
.cloned()
|
||||
.map(BridgedBeefyAuthorityIdToMerkleLeaf::<T, I>::convert)
|
||||
.collect::<Vec<_>>();
|
||||
merkle_root::<BridgedMmrHashing<T, I>, _>(merkle_leafs)
|
||||
}
|
||||
|
||||
fn verify_authority_set<T: Config<I>, I: 'static>(
|
||||
authority_set_info: &BridgedBeefyAuthoritySetInfo<T, I>,
|
||||
authority_set: &BridgedBeefyAuthoritySet<T, I>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
ensure!(authority_set.id() == authority_set_info.id, Error::<T, I>::InvalidValidatorSetId);
|
||||
ensure!(
|
||||
authority_set.len() == authority_set_info.len as usize,
|
||||
Error::<T, I>::InvalidValidatorSetLen
|
||||
);
|
||||
|
||||
// Ensure that the authority set that signed the commitment is the expected one.
|
||||
let root = get_authorities_mmr_root::<T, I, _>(authority_set.validators().iter());
|
||||
ensure!(root == authority_set_info.keyset_commitment, Error::<T, I>::InvalidValidatorSetRoot);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Number of correct signatures, required from given validators set to accept signed
|
||||
/// commitment.
|
||||
///
|
||||
/// We're using 'conservative' approach here, where signatures of `2/3+1` validators are
|
||||
/// required..
|
||||
pub(crate) fn signatures_required(validators_len: usize) -> usize {
|
||||
validators_len - validators_len.saturating_sub(1) / 3
|
||||
}
|
||||
|
||||
fn verify_signatures<T: Config<I>, I: 'static>(
|
||||
commitment: &BridgedBeefySignedCommitment<T, I>,
|
||||
authority_set: &BridgedBeefyAuthoritySet<T, I>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
ensure!(
|
||||
commitment.signatures.len() == authority_set.len(),
|
||||
Error::<T, I>::InvalidCommitmentSignaturesLen
|
||||
);
|
||||
|
||||
// Ensure that the commitment was signed by enough authorities.
|
||||
let msg = commitment.commitment.encode();
|
||||
let mut missing_signatures = signatures_required(authority_set.len());
|
||||
for (idx, (authority, maybe_sig)) in
|
||||
authority_set.validators().iter().zip(commitment.signatures.iter()).enumerate()
|
||||
{
|
||||
if let Some(sig) = maybe_sig {
|
||||
if authority.verify(sig, &msg) {
|
||||
missing_signatures = missing_signatures.saturating_sub(1);
|
||||
if missing_signatures == 0 {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
%idx,
|
||||
?authority,
|
||||
?sig,
|
||||
"Signed commitment contains incorrect signature of validator"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ensure!(missing_signatures == 0, Error::<T, I>::NotEnoughCorrectSignatures);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extract MMR root from commitment payload.
|
||||
fn extract_mmr_root<T: Config<I>, I: 'static>(
|
||||
commitment: &BridgedBeefySignedCommitment<T, I>,
|
||||
) -> Result<BridgedMmrHash<T, I>, Error<T, I>> {
|
||||
commitment
|
||||
.commitment
|
||||
.payload
|
||||
.get_decoded(&bp_beefy::MMR_ROOT_PAYLOAD_ID)
|
||||
.ok_or(Error::MmrRootMissingFromCommitment)
|
||||
}
|
||||
|
||||
pub(crate) fn verify_commitment<T: Config<I>, I: 'static>(
|
||||
commitment: &BridgedBeefySignedCommitment<T, I>,
|
||||
authority_set_info: &BridgedBeefyAuthoritySetInfo<T, I>,
|
||||
authority_set: &BridgedBeefyAuthoritySet<T, I>,
|
||||
) -> Result<BridgedMmrHash<T, I>, Error<T, I>> {
|
||||
// Ensure that the commitment is signed by the best known BEEFY validator set.
|
||||
ensure!(
|
||||
commitment.commitment.validator_set_id == authority_set_info.id,
|
||||
Error::<T, I>::InvalidCommitmentValidatorSetId
|
||||
);
|
||||
ensure!(
|
||||
commitment.signatures.len() == authority_set_info.len as usize,
|
||||
Error::<T, I>::InvalidCommitmentSignaturesLen
|
||||
);
|
||||
|
||||
verify_authority_set(authority_set_info, authority_set)?;
|
||||
verify_signatures(commitment, authority_set)?;
|
||||
|
||||
extract_mmr_root(commitment)
|
||||
}
|
||||
|
||||
/// Verify MMR proof of given leaf.
|
||||
pub(crate) fn verify_beefy_mmr_leaf<T: Config<I>, I: 'static>(
|
||||
mmr_leaf: &BridgedBeefyMmrLeaf<T, I>,
|
||||
mmr_proof: BridgedMmrProof<T, I>,
|
||||
mmr_root: BridgedMmrHash<T, I>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
let mmr_proof_leaf_count = mmr_proof.leaf_count;
|
||||
let mmr_proof_length = mmr_proof.items.len();
|
||||
|
||||
// Verify the mmr proof for the provided leaf.
|
||||
let mmr_leaf_hash = BridgedMmrHashing::<T, I>::hash(&mmr_leaf.encode());
|
||||
verify_mmr_leaves_proof(
|
||||
mmr_root,
|
||||
vec![BridgedMmrDataOrHash::<T, I>::Hash(mmr_leaf_hash)],
|
||||
mmr_proof,
|
||||
)
|
||||
.map_err(|e| {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
?mmr_leaf_hash,
|
||||
root=?mmr_root,
|
||||
leaf_count=%mmr_proof_leaf_count,
|
||||
len=%mmr_proof_length,
|
||||
"MMR proof of leaf verification has failed"
|
||||
);
|
||||
|
||||
Error::<T, I>::MmrProofVerificationFailed
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, mock_chain::*, *};
|
||||
use bp_beefy::{BeefyPayload, MMR_ROOT_PAYLOAD_ID};
|
||||
use pezframe_support::{assert_noop, assert_ok};
|
||||
use pezsp_consensus_beefy::ValidatorSet;
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_checks_metadata() {
|
||||
run_test_with_initialize(8, || {
|
||||
// Fails if `commitment.commitment.validator_set_id` differs.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.customize_commitment(
|
||||
|commitment| {
|
||||
commitment.validator_set_id += 1;
|
||||
},
|
||||
&validator_pairs(0, 8),
|
||||
6,
|
||||
);
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidCommitmentValidatorSetId,
|
||||
);
|
||||
|
||||
// Fails if `commitment.signatures.len()` differs.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.customize_signatures(|signatures| {
|
||||
signatures.pop();
|
||||
});
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidCommitmentSignaturesLen,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_checks_validator_set() {
|
||||
run_test_with_initialize(8, || {
|
||||
// Fails if `ValidatorSet::id` differs.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.validator_set = ValidatorSet::new(validator_ids(0, 8), 1).unwrap();
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidValidatorSetId,
|
||||
);
|
||||
|
||||
// Fails if `ValidatorSet::len()` differs.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.validator_set = ValidatorSet::new(validator_ids(0, 5), 0).unwrap();
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidValidatorSetLen,
|
||||
);
|
||||
|
||||
// Fails if the validators differ.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.validator_set = ValidatorSet::new(validator_ids(3, 8), 0).unwrap();
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidValidatorSetRoot,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_checks_signatures() {
|
||||
run_test_with_initialize(20, || {
|
||||
// Fails when there aren't enough signatures.
|
||||
let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
|
||||
header.customize_signatures(|signatures| {
|
||||
let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
|
||||
signatures[first_signature_idx] = None;
|
||||
});
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::NotEnoughCorrectSignatures,
|
||||
);
|
||||
|
||||
// Fails when there aren't enough correct signatures.
|
||||
let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
|
||||
header.customize_signatures(|signatures| {
|
||||
let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
|
||||
let last_signature_idx = signatures.len() -
|
||||
signatures.iter().rev().position(Option::is_some).unwrap() -
|
||||
1;
|
||||
signatures[first_signature_idx] = signatures[last_signature_idx].clone();
|
||||
});
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::NotEnoughCorrectSignatures,
|
||||
);
|
||||
|
||||
// Returns Ok(()) when there are enough signatures, even if some are incorrect.
|
||||
let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
|
||||
header.customize_signatures(|signatures| {
|
||||
let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
|
||||
let first_missing_signature_idx =
|
||||
signatures.iter().position(Option::is_none).unwrap();
|
||||
signatures[first_missing_signature_idx] = signatures[first_signature_idx].clone();
|
||||
});
|
||||
assert_ok!(import_commitment(header));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_checks_mmr_proof() {
|
||||
run_test_with_initialize(1, || {
|
||||
let validators = validator_pairs(0, 1);
|
||||
|
||||
// Fails if leaf is not for parent.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
header.leaf.parent_number_and_hash.0 += 1;
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrProofVerificationFailed,
|
||||
);
|
||||
|
||||
// Fails if mmr proof is incorrect.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
header.leaf_proof.leaf_indices[0] += 1;
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrProofVerificationFailed,
|
||||
);
|
||||
|
||||
// Fails if mmr root is incorrect.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
// Replace MMR root with zeroes.
|
||||
header.customize_commitment(
|
||||
|commitment| {
|
||||
commitment.payload =
|
||||
BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, [0u8; 32].encode());
|
||||
},
|
||||
&validators,
|
||||
1,
|
||||
);
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrProofVerificationFailed,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_extracts_mmr_root() {
|
||||
run_test_with_initialize(1, || {
|
||||
let validators = validator_pairs(0, 1);
|
||||
|
||||
// Fails if there is no mmr root in the payload.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
// Remove MMR root from the payload.
|
||||
header.customize_commitment(
|
||||
|commitment| {
|
||||
commitment.payload = BeefyPayload::from_single_entry(*b"xy", vec![]);
|
||||
},
|
||||
&validators,
|
||||
1,
|
||||
);
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrRootMissingFromCommitment,
|
||||
);
|
||||
|
||||
// Fails if mmr root can't be decoded.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
// MMR root is a 32-byte array and we have replaced it with single byte
|
||||
header.customize_commitment(
|
||||
|commitment| {
|
||||
commitment.payload =
|
||||
BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, vec![42]);
|
||||
},
|
||||
&validators,
|
||||
1,
|
||||
);
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrRootMissingFromCommitment,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_stores_valid_data() {
|
||||
run_test_with_initialize(20, || {
|
||||
let header = ChainBuilder::new(20).append_handoff_header(30).to_header();
|
||||
assert_ok!(import_commitment(header.clone()));
|
||||
|
||||
assert_eq!(ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().best_block_number, 1);
|
||||
assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().id, 1);
|
||||
assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().len, 30);
|
||||
assert_eq!(
|
||||
ImportedCommitments::<TestRuntime>::get(1).unwrap(),
|
||||
bp_beefy::ImportedCommitment {
|
||||
parent_number_and_hash: (0, [0; 32].into()),
|
||||
mmr_root: header.mmr_root,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-grandpa"
|
||||
version = "0.7.0"
|
||||
description = "Module implementing GRANDPA on-chain light client used for bridging consensus of bizinikiwi-based chains."
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-bridge-grandpa"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-consensus-grandpa = { features = ["serde"], workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
# Optional Benchmarking Dependencies
|
||||
bp-test-utils = { optional = true, workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezbp-runtime = { features = ["test-helpers"], workspace = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-test-utils/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"scale-info/std",
|
||||
"pezsp-consensus-grandpa/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils",
|
||||
"bp-test-utils?/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezsp-consensus-grandpa/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,101 @@
|
||||
# Bridge GRANDPA Pezpallet
|
||||
|
||||
The bridge GRANDPA pezpallet is a light client for the GRANDPA finality gadget, running at the bridged chain.
|
||||
It may import headers and their GRANDPA finality proofs (justifications) of the bridged chain. Imported
|
||||
headers then may be used to verify storage proofs by other pallets. This makes the bridge GRANDPA pezpallet
|
||||
a basic pezpallet of all bridges with Bizinikiwi-based chains. It is used by all bridge types (bridge between
|
||||
standalone chains, between teyrchains and any combination of those) and is used by other bridge pallets.
|
||||
It is used by the teyrchains light client (bridge teyrchains pezpallet) and by messages pezpallet.
|
||||
|
||||
## A Brief Introduction into GRANDPA Finality
|
||||
|
||||
You can find detailed information on GRANDPA, by exploring its [repository](https://github.com/pezkuwichain/finality-grandpa).
|
||||
Here is the minimal required GRANDPA information to understand how pezpallet works.
|
||||
|
||||
Any Bizinikiwi chain may use different block authorship algorithms (like BABE or Aura) to determine block producers and
|
||||
generate blocks. This has nothing common with finality, though - the task of block authorship is to coordinate
|
||||
blocks generation. Any block may be reverted (if there's a fork) if it is not finalized. The finality solution
|
||||
for (standalone) Bizinikiwi-based chains is the GRANDPA finality gadget. If some block is finalized by the gadget, it
|
||||
can't be reverted.
|
||||
|
||||
In GRANDPA, there are validators, identified by their public keys. They select some generated block and produce
|
||||
signatures on this block hash. If there are enough (more than `2 / 3 * N`, where `N` is number of validators)
|
||||
signatures, then the block is considered finalized. The set of signatures for the block is called justification.
|
||||
Anyone who knows the public keys of validators is able to verify GRANDPA justification and that it is generated
|
||||
for provided header.
|
||||
|
||||
There are two main things in GRANDPA that help building light clients:
|
||||
|
||||
- there's no need to import all headers of the bridged chain. Light client may import finalized headers or just
|
||||
some of finalized headers that it consider useful. While the validators set stays the same, the client may
|
||||
import any header that is finalized by this set;
|
||||
|
||||
- when validators set changes, the GRANDPA gadget adds next set to the header. So light client doesn't need to
|
||||
verify storage proofs when this happens - it only needs to look at the header and see if it changes the set.
|
||||
Once set is changed, all following justifications are generated by the new set. Header that is changing the
|
||||
set is called "mandatory" in the pezpallet. As the name says, the light client need to import all such headers
|
||||
to be able to operate properly.
|
||||
|
||||
## Pezpallet Operations
|
||||
|
||||
The main entrypoint of the pezpallet is the `submit_finality_proof_ex` call. It has three arguments - the finalized
|
||||
headers, associated GRANDPA justification and ID of the authority set that has generated this justification. The
|
||||
call simply verifies the justification using current validators set and checks if header is better than the
|
||||
previous best header. If both checks are passed, the header (only its useful fields) is inserted into the runtime
|
||||
storage and may be used by other pallets to verify storage proofs.
|
||||
|
||||
The submitter pays regular fee for submitting all headers, except for the mandatory header. Since it is
|
||||
required for the pezpallet operations, submitting such header is free. So if you're ok with session-length
|
||||
lags (meaning that there's exactly 1 mandatory header per session), the cost of pezpallet calls is zero.
|
||||
|
||||
When the pezpallet sees mandatory header, it updates the validators set with the set from the header. All
|
||||
following justifications (until next mandatory header) must be generated by this new set.
|
||||
|
||||
## Pezpallet Initialization
|
||||
|
||||
As the previous section states, there are two things that are mandatory for pezpallet operations: best finalized
|
||||
header and the current validators set. Without it the pezpallet can't import any headers. But how to provide
|
||||
initial values for these fields? There are two options.
|
||||
|
||||
First option, while it is easier, doesn't work in all cases. It is to start chain with initial header and
|
||||
validators set specified in the chain specification. This won't work, however, if we want to add bridge
|
||||
to already started chain.
|
||||
|
||||
For the latter case we have the `initialize` call. It accepts the initial header and initial validators set.
|
||||
The call may be called by the governance, root or by the pezpallet owner (if it is set).
|
||||
|
||||
## Non-Essential Functionality
|
||||
|
||||
There may be a special account in every runtime where the bridge GRANDPA module is deployed. This
|
||||
account, named 'module owner', is like a module-level sudo account - he's able to halt and
|
||||
resume all module operations without requiring runtime upgrade. Calls that are related to this
|
||||
account are:
|
||||
|
||||
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
|
||||
|
||||
- `fn set_operating_mode()`: the module owner (or sudo account) may call this function to stop all
|
||||
module operations. After this call, all finality proofs will be rejected until further `set_operating_mode` call'.
|
||||
This call may be used when something extraordinary happens with the bridge;
|
||||
|
||||
- `fn initialize()`: module owner may call this function to initialize the bridge.
|
||||
|
||||
If pezpallet owner is not defined, the governance may be used to make those calls.
|
||||
|
||||
## Signed Extension to Reject Obsolete Headers
|
||||
|
||||
It'd be better for anyone (for chain and for submitters) to reject all transactions that are submitting
|
||||
already known headers to the pezpallet. This way, we leave block space to other useful transactions and
|
||||
we don't charge concurrent submitters for their honest actions.
|
||||
|
||||
To deal with that, we have a [signed extension](./src/call_ext.rs) that may be added to the runtime.
|
||||
It does exactly what is required - rejects all transactions with already known headers. The submitter
|
||||
pays nothing for such transactions - they're simply removed from the transaction pool, when the block
|
||||
is built.
|
||||
|
||||
You may also take a look at the [`generate_bridge_reject_obsolete_headers_and_messages`](../../bin/runtime-common/src/lib.rs)
|
||||
macro that bundles several similar signed extensions in a single one.
|
||||
|
||||
## GRANDPA Finality Relay
|
||||
|
||||
We have an offchain actor, who is watching for GRANDPA justifications and submits them to the bridged chain.
|
||||
It is the finality relay - you may look at the [crate level documentation and the code](../../relays/finality/).
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Benchmarks for the GRANDPA Pezpallet.
|
||||
//!
|
||||
//! The main dispatchable for the GRANDPA pezpallet is `submit_finality_proof_ex`. Our benchmarks
|
||||
//! are based around `submit_finality_proof`, though - from weight PoV they are the same calls.
|
||||
//! There are to main factors which affect finality proof verification:
|
||||
//!
|
||||
//! 1. The number of `votes-ancestries` in the justification
|
||||
//! 2. The number of `pre-commits` in the justification
|
||||
//!
|
||||
//! Vote ancestries are the headers between (`finality_target`, `head_of_chain`], where
|
||||
//! `header_of_chain` is a descendant of `finality_target`.
|
||||
//!
|
||||
//! Pre-commits are messages which are signed by validators at the head of the chain they think is
|
||||
//! the best.
|
||||
//!
|
||||
//! Consider the following:
|
||||
//!
|
||||
//! / B <- C'
|
||||
//! A <- B <- C
|
||||
//!
|
||||
//! The common ancestor of both forks is block A, so this is what GRANDPA will finalize. In order to
|
||||
//! verify this we will have vote ancestries of `[B, C, B', C']` and pre-commits `[C, C']`.
|
||||
//!
|
||||
//! Note that the worst case scenario here would be a justification where each validator has it's
|
||||
//! own fork which is `SESSION_LENGTH` blocks long.
|
||||
|
||||
use crate::*;
|
||||
|
||||
use bp_header_pez_chain::justification::required_justification_precommits;
|
||||
use pezbp_runtime::BasicOperatingMode;
|
||||
use bp_test_utils::{
|
||||
accounts, make_justification_for_header, JustificationGeneratorParams, TEST_GRANDPA_ROUND,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
};
|
||||
use pezframe_benchmarking::{benchmarks_instance_pallet, whitelisted_caller};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_consensus_grandpa::AuthorityId;
|
||||
use pezsp_runtime::traits::{One, Zero};
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
/// The maximum number of vote ancestries to include in a justification.
|
||||
///
|
||||
/// In practice this would be limited by the session length (number of blocks a single authority set
|
||||
/// can produce) of a given chain.
|
||||
const MAX_VOTE_ANCESTRIES: u32 = 1000;
|
||||
|
||||
// `1..MAX_VOTE_ANCESTRIES` is too large && benchmarks are running for almost 40m (steps=50,
|
||||
// repeat=20) on a decent laptop, which is too much. Since we're building linear function here,
|
||||
// let's just select some limited subrange for benchmarking.
|
||||
const MAX_VOTE_ANCESTRIES_RANGE_BEGIN: u32 = MAX_VOTE_ANCESTRIES / 20;
|
||||
const MAX_VOTE_ANCESTRIES_RANGE_END: u32 =
|
||||
MAX_VOTE_ANCESTRIES_RANGE_BEGIN + MAX_VOTE_ANCESTRIES_RANGE_BEGIN;
|
||||
|
||||
// the same with validators - if there are too much validators, let's run benchmarks on subrange
|
||||
fn precommits_range_end<T: Config<I>, I: 'static>() -> u32 {
|
||||
let max_bridged_authorities = T::BridgedChain::MAX_AUTHORITIES_COUNT;
|
||||
let max_bridged_authorities = if max_bridged_authorities > 128 {
|
||||
pezsp_std::cmp::max(128, max_bridged_authorities / 5)
|
||||
} else {
|
||||
max_bridged_authorities
|
||||
};
|
||||
|
||||
required_justification_precommits(max_bridged_authorities)
|
||||
}
|
||||
|
||||
/// Prepare header and its justification to submit using `submit_finality_proof`.
|
||||
fn prepare_benchmark_data<T: Config<I>, I: 'static>(
|
||||
precommits: u32,
|
||||
ancestors: u32,
|
||||
) -> (BridgedHeader<T, I>, GrandpaJustification<BridgedHeader<T, I>>) {
|
||||
// going from precommits to total authorities count
|
||||
let total_authorities_count = (3 * precommits - 1) / 2;
|
||||
|
||||
let authority_list = accounts(total_authorities_count as u16)
|
||||
.iter()
|
||||
.map(|id| (AuthorityId::from(*id), 1))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let genesis_header: BridgedHeader<T, I> = bp_test_utils::test_header(Zero::zero());
|
||||
let genesis_hash = genesis_header.hash();
|
||||
let init_data = InitializationData {
|
||||
header: Box::new(genesis_header),
|
||||
authority_list,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
};
|
||||
|
||||
bootstrap_bridge::<T, I>(init_data);
|
||||
assert!(<ImportedHeaders<T, I>>::contains_key(genesis_hash));
|
||||
|
||||
let header: BridgedHeader<T, I> = bp_test_utils::test_header(One::one());
|
||||
let params = JustificationGeneratorParams {
|
||||
header: header.clone(),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: accounts(precommits as u16).iter().map(|k| (*k, 1)).collect::<Vec<_>>(),
|
||||
ancestors,
|
||||
forks: 1,
|
||||
};
|
||||
let justification = make_justification_for_header(params);
|
||||
(header, justification)
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
// This is the "gold standard" benchmark for this extrinsic, and it's what should be used to
|
||||
// annotate the weight in the pezpallet.
|
||||
submit_finality_proof {
|
||||
let p in 1 .. precommits_range_end::<T, I>();
|
||||
let v in MAX_VOTE_ANCESTRIES_RANGE_BEGIN..MAX_VOTE_ANCESTRIES_RANGE_END;
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let (header, justification) = prepare_benchmark_data::<T, I>(p, v);
|
||||
}: submit_finality_proof(RawOrigin::Signed(caller), Box::new(header), justification)
|
||||
verify {
|
||||
let genesis_header: BridgedHeader<T, I> = bp_test_utils::test_header(Zero::zero());
|
||||
let header: BridgedHeader<T, I> = bp_test_utils::test_header(One::one());
|
||||
let expected_hash = header.hash();
|
||||
|
||||
// check that the header#1 has been inserted
|
||||
assert_eq!(<BestFinalized<T, I>>::get().unwrap().1, expected_hash);
|
||||
assert!(<ImportedHeaders<T, I>>::contains_key(expected_hash));
|
||||
|
||||
// check that the header#0 has been pruned
|
||||
assert!(!<ImportedHeaders<T, I>>::contains_key(genesis_header.hash()));
|
||||
}
|
||||
|
||||
force_set_pallet_state {
|
||||
let set_id = 100;
|
||||
let authorities = accounts(T::BridgedChain::MAX_AUTHORITIES_COUNT as u16)
|
||||
.iter()
|
||||
.map(|id| (AuthorityId::from(*id), 1))
|
||||
.collect::<Vec<_>>();
|
||||
let (header, _) = prepare_benchmark_data::<T, I>(1, 1);
|
||||
let expected_hash = header.hash();
|
||||
}: force_set_pallet_state(RawOrigin::Root, set_id, authorities, Box::new(header))
|
||||
verify {
|
||||
assert_eq!(<BestFinalized<T, I>>::get().unwrap().1, expected_hash);
|
||||
assert_eq!(<CurrentAuthoritySet<T, I>>::get().set_id, set_id);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pezpallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
weights::WeightInfo, BestFinalized, BridgedBlockNumber, BridgedHeader, Config,
|
||||
CurrentAuthoritySet, Error, FreeHeadersRemaining, Pezpallet,
|
||||
};
|
||||
use bp_header_pez_chain::{
|
||||
justification::GrandpaJustification, submit_finality_proof_limits_extras,
|
||||
SubmitFinalityProofInfo,
|
||||
};
|
||||
use pezbp_runtime::{BlockNumberOf, Chain, OwnedBridgeModule};
|
||||
use pezframe_support::{
|
||||
dispatch::CallableCallFor,
|
||||
traits::{Get, IsSubType},
|
||||
weights::Weight,
|
||||
};
|
||||
use pezsp_consensus_grandpa::SetId;
|
||||
use pezsp_runtime::{
|
||||
traits::{CheckedSub, Header, Zero},
|
||||
transaction_validity::{InvalidTransaction, TransactionValidityError},
|
||||
RuntimeDebug, SaturatedConversion,
|
||||
};
|
||||
use pezsp_std::fmt::Debug;
|
||||
|
||||
/// Verified `SubmitFinalityProofInfo<N>`.
|
||||
#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
|
||||
pub struct VerifiedSubmitFinalityProofInfo<N: Debug> {
|
||||
/// Base call information.
|
||||
pub base: SubmitFinalityProofInfo<N>,
|
||||
/// A difference between bundled bridged header and best bridged header known to us
|
||||
/// before the call.
|
||||
pub improved_by: N,
|
||||
}
|
||||
|
||||
/// Helper struct that provides methods for working with the `SubmitFinalityProof` call.
|
||||
pub struct SubmitFinalityProofHelper<T: Config<I>, I: 'static> {
|
||||
_phantom_data: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
|
||||
/// Returns `true` if we may fit more free headers into the current block. If `false` is
|
||||
/// returned, the call will be paid even if `is_free_execution_expected` has been set
|
||||
/// to `true`.
|
||||
pub fn has_free_header_slots() -> bool {
|
||||
// `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept
|
||||
// this header for free. That is a small cheat - it is `None` if executed outside of
|
||||
// transaction (e.g. during block initialization). Normal relayer would never submit
|
||||
// such calls, but if he did, that is not our problem. During normal transactions,
|
||||
// the `FreeHeadersRemaining` is always `Some(_)`.
|
||||
let free_headers_remaining = FreeHeadersRemaining::<T, I>::get().unwrap_or(u32::MAX);
|
||||
free_headers_remaining > 0
|
||||
}
|
||||
|
||||
/// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the
|
||||
/// best one we know (2) if `current_set_id` matches the current authority set id, if specified
|
||||
/// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected`
|
||||
/// is `true`.
|
||||
///
|
||||
/// Returns number of headers between the current best finalized header, known to the pezpallet
|
||||
/// and the bundled header.
|
||||
pub fn check_obsolete_from_extension(
|
||||
call_info: &SubmitFinalityProofInfo<BlockNumberOf<T::BridgedChain>>,
|
||||
) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
|
||||
// do basic checks first
|
||||
let improved_by = Self::check_obsolete(call_info.block_number, call_info.current_set_id)?;
|
||||
|
||||
// if submitter has NOT specified that it wants free execution, then we are done
|
||||
if !call_info.is_free_execution_expected {
|
||||
return Ok(improved_by);
|
||||
}
|
||||
|
||||
// else - if we can not accept more free headers, "reject" the transaction
|
||||
if !Self::has_free_header_slots() {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
chain_id=?T::BridgedChain::ID,
|
||||
block_number=?call_info.block_number,
|
||||
"Cannot accept free header. No more free slots remaining"
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::FreeHeadersLimitExceded);
|
||||
}
|
||||
|
||||
// ensure that the `improved_by` is larger than the configured free interval
|
||||
if !call_info.is_mandatory {
|
||||
if let Some(free_headers_interval) = T::FreeHeadersInterval::get() {
|
||||
if improved_by < free_headers_interval.into() {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
chain_id=?T::BridgedChain::ID,
|
||||
block_number=?call_info.block_number,
|
||||
?improved_by,
|
||||
%free_headers_interval,
|
||||
"Cannot accept free header. Too small difference between submitted headers"
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::BelowFreeHeaderInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let's also check whether the header submission fits the hardcoded limits. A normal
|
||||
// relayer would check that before submitting a transaction (since limits are constants
|
||||
// and do not depend on a volatile runtime state), but the ckeck itself is cheap, so
|
||||
// let's do it here too
|
||||
if !call_info.fits_limits() {
|
||||
return Err(Error::<T, I>::HeaderOverflowLimits);
|
||||
}
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best
|
||||
/// one we know. Additionally, checks if `current_set_id` matches the current authority set
|
||||
/// id, if specified. This method is called by the call code and the transaction extension,
|
||||
/// so it does not check the free execution.
|
||||
///
|
||||
/// Returns number of headers between the current best finalized header, known to the pezpallet
|
||||
/// and the bundled header.
|
||||
pub fn check_obsolete(
|
||||
finality_target: BlockNumberOf<T::BridgedChain>,
|
||||
current_set_id: Option<SetId>,
|
||||
) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
|
||||
let best_finalized = BestFinalized::<T, I>::get().ok_or_else(|| {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
header=?finality_target,
|
||||
"Cannot finalize header because pezpallet is not yet initialized"
|
||||
);
|
||||
<Error<T, I>>::NotInitialized
|
||||
})?;
|
||||
|
||||
let improved_by = match finality_target.checked_sub(&best_finalized.number()) {
|
||||
Some(improved_by) if improved_by > Zero::zero() => improved_by,
|
||||
_ => {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
bundled=?finality_target,
|
||||
best=?best_finalized,
|
||||
"Cannot finalize obsolete header"
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::OldHeader);
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(current_set_id) = current_set_id {
|
||||
let actual_set_id = <CurrentAuthoritySet<T, I>>::get().set_id;
|
||||
if current_set_id != actual_set_id {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
bundled=?current_set_id,
|
||||
best=?actual_set_id,
|
||||
"Cannot finalize header signed by unknown authority set"
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::InvalidAuthoritySetId);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check if the `SubmitFinalityProof` was successfully executed.
|
||||
pub fn was_successful(finality_target: BlockNumberOf<T::BridgedChain>) -> bool {
|
||||
match BestFinalized::<T, I>::get() {
|
||||
Some(best_finalized) => best_finalized.number() == finality_target,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing a call that is a sub type of this pezpallet's call.
|
||||
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
|
||||
{
|
||||
/// Extract finality proof info from a runtime call.
|
||||
fn submit_finality_proof_info(
|
||||
&self,
|
||||
) -> Option<SubmitFinalityProofInfo<BridgedBlockNumber<T, I>>> {
|
||||
if let Some(crate::Call::<T, I>::submit_finality_proof { finality_target, justification }) =
|
||||
self.is_sub_type()
|
||||
{
|
||||
return Some(submit_finality_proof_info_from_args::<T, I>(
|
||||
finality_target,
|
||||
justification,
|
||||
None,
|
||||
false,
|
||||
));
|
||||
} else if let Some(crate::Call::<T, I>::submit_finality_proof_ex {
|
||||
finality_target,
|
||||
justification,
|
||||
current_set_id,
|
||||
is_free_execution_expected,
|
||||
}) = self.is_sub_type()
|
||||
{
|
||||
return Some(submit_finality_proof_info_from_args::<T, I>(
|
||||
finality_target,
|
||||
justification,
|
||||
Some(*current_set_id),
|
||||
*is_free_execution_expected,
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Validate Grandpa headers in order to avoid "mining" transactions that provide outdated
|
||||
/// bridged chain headers. Without this validation, even honest relayers may lose their funds
|
||||
/// if there are multiple relays running and submitting the same information.
|
||||
///
|
||||
/// Returns `Ok(None)` if the call is not the `submit_finality_proof` call of our pezpallet.
|
||||
/// Returns `Ok(Some(_))` if the call is the `submit_finality_proof` call of our pezpallet and
|
||||
/// we believe the call brings header that improves the pezpallet state.
|
||||
/// Returns `Err(_)` if the call is the `submit_finality_proof` call of our pezpallet and we
|
||||
/// believe that the call will fail.
|
||||
fn check_obsolete_submit_finality_proof(
|
||||
&self,
|
||||
) -> Result<
|
||||
Option<VerifiedSubmitFinalityProofInfo<BridgedBlockNumber<T, I>>>,
|
||||
TransactionValidityError,
|
||||
>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let call_info = match self.submit_finality_proof_info() {
|
||||
Some(finality_proof) => finality_proof,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
if Pezpallet::<T, I>::ensure_not_halted().is_err() {
|
||||
return Err(InvalidTransaction::Call.into());
|
||||
}
|
||||
|
||||
let result = SubmitFinalityProofHelper::<T, I>::check_obsolete_from_extension(&call_info);
|
||||
match result {
|
||||
Ok(improved_by) =>
|
||||
Ok(Some(VerifiedSubmitFinalityProofInfo { base: call_info, improved_by })),
|
||||
Err(Error::<T, I>::OldHeader) => Err(InvalidTransaction::Stale.into()),
|
||||
Err(_) => Err(InvalidTransaction::Call.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> CallSubType<T, I> for T::RuntimeCall where
|
||||
T::RuntimeCall: IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
|
||||
{
|
||||
}
|
||||
|
||||
/// Extract finality proof info from the submitted header and justification.
|
||||
pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
|
||||
finality_target: &BridgedHeader<T, I>,
|
||||
justification: &GrandpaJustification<BridgedHeader<T, I>>,
|
||||
current_set_id: Option<SetId>,
|
||||
is_free_execution_expected: bool,
|
||||
) -> SubmitFinalityProofInfo<BridgedBlockNumber<T, I>> {
|
||||
// check if call exceeds limits. In other words - whether some size or weight is included
|
||||
// in the call
|
||||
let extras =
|
||||
submit_finality_proof_limits_extras::<T::BridgedChain>(finality_target, justification);
|
||||
|
||||
// We do care about extra weight because of more-than-expected headers in the votes
|
||||
// ancestries. But we have problems computing extra weight for additional headers (weight of
|
||||
// additional header is too small, so that our benchmarks aren't detecting that). So if there
|
||||
// are more than expected headers in votes ancestries, we will treat the whole call weight
|
||||
// as an extra weight.
|
||||
let extra_weight = if extras.is_weight_limit_exceeded {
|
||||
let precommits_len = justification.commit.precommits.len().saturated_into();
|
||||
let votes_ancestries_len = justification.votes_ancestries.len().saturated_into();
|
||||
T::WeightInfo::submit_finality_proof(precommits_len, votes_ancestries_len)
|
||||
} else {
|
||||
Weight::zero()
|
||||
};
|
||||
|
||||
SubmitFinalityProofInfo {
|
||||
block_number: *finality_target.number(),
|
||||
current_set_id,
|
||||
is_mandatory: extras.is_mandatory_finality_target,
|
||||
is_free_execution_expected,
|
||||
extra_weight,
|
||||
extra_size: extras.extra_size,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
call_ext::CallSubType,
|
||||
mock::{
|
||||
run_test, test_header, FreeHeadersInterval, RuntimeCall, TestBridgedChain, TestNumber,
|
||||
TestRuntime,
|
||||
},
|
||||
BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode,
|
||||
StoredAuthoritySet, WeightInfo,
|
||||
};
|
||||
use bp_header_pez_chain::{ChainWithGrandpa, SubmitFinalityProofInfo};
|
||||
use pezbp_runtime::{BasicOperatingMode, HeaderId};
|
||||
use bp_test_utils::{
|
||||
make_default_justification, make_justification_for_header, JustificationGeneratorParams,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
};
|
||||
use codec::Encode;
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezsp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion};
|
||||
|
||||
fn validate_block_submit(num: TestNumber) -> bool {
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(num)),
|
||||
justification: make_default_justification(&test_header(num)),
|
||||
// not initialized => zero
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: false,
|
||||
};
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call,
|
||||
))
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn sync_to_header_10() {
|
||||
let header10_hash = pezsp_core::H256::default();
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(10, header10_hash));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_obsolete_header() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#5 => tx is
|
||||
// rejected
|
||||
sync_to_header_10();
|
||||
assert!(!validate_block_submit(5));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_same_header() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
||||
// rejected
|
||||
sync_to_header_10();
|
||||
assert!(!validate_block_submit(10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_pallet_is_halted() {
|
||||
run_test(|| {
|
||||
// when pezpallet is halted => tx is rejected
|
||||
sync_to_header_10();
|
||||
PalletOperatingMode::<TestRuntime, ()>::put(BasicOperatingMode::Halted);
|
||||
|
||||
assert!(!validate_block_submit(15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_set_id_is_invalid() {
|
||||
run_test(|| {
|
||||
// when set id is different from the passed one => tx is rejected
|
||||
sync_to_header_10();
|
||||
let next_set = StoredAuthoritySet::<TestRuntime, ()>::try_new(vec![], 0x42).unwrap();
|
||||
CurrentAuthoritySet::<TestRuntime, ()>::put(next_set);
|
||||
|
||||
assert!(!validate_block_submit(15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted(
|
||||
) {
|
||||
run_test(|| {
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(10 + FreeHeadersInterval::get() as u64)),
|
||||
justification: make_default_justification(&test_header(
|
||||
10 + FreeHeadersInterval::get() as u64,
|
||||
)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// when we can accept free headers => Ok
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(2);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when we can NOT accept free headers => Err
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(0);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
|
||||
// when called outside of transaction => Ok
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::kill();
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call,
|
||||
),)
|
||||
.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_it_overflow_size_limits() {
|
||||
run_test(|| {
|
||||
let mut large_finality_target = test_header(10 + FreeHeadersInterval::get() as u64);
|
||||
large_finality_target
|
||||
.digest_mut()
|
||||
.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
|
||||
let justification_params = JustificationGeneratorParams {
|
||||
header: large_finality_target.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let large_justification = make_justification_for_header(justification_params);
|
||||
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(large_finality_target),
|
||||
justification: large_justification,
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// if overflow size limits => Err
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(2);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_it_overflow_weight_limits() {
|
||||
run_test(|| {
|
||||
let finality_target = test_header(10 + FreeHeadersInterval::get() as u64);
|
||||
let justification_params = JustificationGeneratorParams {
|
||||
header: finality_target.clone(),
|
||||
ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
|
||||
..Default::default()
|
||||
};
|
||||
let justification = make_justification_for_header(justification_params);
|
||||
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(finality_target),
|
||||
justification,
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// if overflow weight limits => Err
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(2);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected(
|
||||
) {
|
||||
run_test(|| {
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(100)),
|
||||
justification: make_default_justification(&test_header(100)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// when `improved_by` is less than the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 + 1,
|
||||
pezsp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
|
||||
// when `improved_by` is equal to the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64,
|
||||
pezsp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when `improved_by` is larger than the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 - 1,
|
||||
pezsp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when `improved_by` is less than the free interval BUT it is a mandatory header
|
||||
let mut mandatory_header = test_header(100);
|
||||
let consensus_log = pezsp_consensus_grandpa::ConsensusLog::<TestNumber>::ScheduledChange(
|
||||
pezsp_consensus_grandpa::ScheduledChange {
|
||||
next_authorities: bp_test_utils::authority_list(),
|
||||
delay: 0,
|
||||
},
|
||||
);
|
||||
mandatory_header.digest = pezsp_runtime::Digest {
|
||||
logs: vec![DigestItem::Consensus(
|
||||
pezsp_consensus_grandpa::GRANDPA_ENGINE_ID,
|
||||
consensus_log.encode(),
|
||||
)],
|
||||
};
|
||||
let justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: mandatory_header.clone(),
|
||||
set_id: 1,
|
||||
..Default::default()
|
||||
});
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(mandatory_header),
|
||||
justification,
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 + 1,
|
||||
pezsp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_header() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#15 => tx is
|
||||
// accepted
|
||||
sync_to_header_10();
|
||||
assert!(validate_block_submit(15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_finality_proof_info_is_parsed() {
|
||||
// when `submit_finality_proof` is used, `current_set_id` is set to `None`
|
||||
let deprecated_call =
|
||||
RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof {
|
||||
finality_target: Box::new(test_header(42)),
|
||||
justification: make_default_justification(&test_header(42)),
|
||||
});
|
||||
assert_eq!(
|
||||
deprecated_call.submit_finality_proof_info(),
|
||||
Some(SubmitFinalityProofInfo {
|
||||
block_number: 42,
|
||||
current_set_id: None,
|
||||
extra_weight: Weight::zero(),
|
||||
extra_size: 0,
|
||||
is_mandatory: false,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
);
|
||||
|
||||
// when `submit_finality_proof_ex` is used, `current_set_id` is set to `Some`
|
||||
let deprecated_call =
|
||||
RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(42)),
|
||||
justification: make_default_justification(&test_header(42)),
|
||||
current_set_id: 777,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(
|
||||
deprecated_call.submit_finality_proof_info(),
|
||||
Some(SubmitFinalityProofInfo {
|
||||
block_number: 42,
|
||||
current_set_id: Some(777),
|
||||
extra_weight: Weight::zero(),
|
||||
extra_size: 0,
|
||||
is_mandatory: false,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_returns_correct_extra_size_if_call_arguments_are_too_large() {
|
||||
// when call arguments are below our limit => no refund
|
||||
let small_finality_target = test_header(1);
|
||||
let justification_params = JustificationGeneratorParams {
|
||||
header: small_finality_target.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let small_justification = make_justification_for_header(justification_params);
|
||||
let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
|
||||
finality_target: Box::new(small_finality_target),
|
||||
justification: small_justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0);
|
||||
|
||||
// when call arguments are too large => partial refund
|
||||
let mut large_finality_target = test_header(1);
|
||||
large_finality_target
|
||||
.digest_mut()
|
||||
.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
|
||||
let justification_params = JustificationGeneratorParams {
|
||||
header: large_finality_target.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let large_justification = make_justification_for_header(justification_params);
|
||||
let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
|
||||
finality_target: Box::new(large_finality_target),
|
||||
justification: large_justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_returns_correct_extra_weight_if_there_are_too_many_headers_in_votes_ancestry() {
|
||||
let finality_target = test_header(1);
|
||||
let mut justification_params = JustificationGeneratorParams {
|
||||
header: finality_target.clone(),
|
||||
ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// when there are `REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY` headers => no refund
|
||||
let justification = make_justification_for_header(justification_params.clone());
|
||||
let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
|
||||
finality_target: Box::new(finality_target.clone()),
|
||||
justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero());
|
||||
|
||||
// when there are `REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY + 1` headers => full refund
|
||||
justification_params.ancestors += 1;
|
||||
let justification = make_justification_for_header(justification_params);
|
||||
let call_weight = <TestRuntime as Config>::WeightInfo::submit_finality_proof(
|
||||
justification.commit.precommits.len().saturated_into(),
|
||||
justification.votes_ancestries.len().saturated_into(),
|
||||
);
|
||||
let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
|
||||
finality_target: Box::new(finality_target),
|
||||
justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_obsolete_submit_finality_proof_returns_correct_improved_by() {
|
||||
run_test(|| {
|
||||
fn make_call(number: u64) -> RuntimeCall {
|
||||
RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(number)),
|
||||
justification: make_default_justification(&test_header(number)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
}
|
||||
|
||||
sync_to_header_10();
|
||||
|
||||
// when the difference between headers is 1
|
||||
assert_eq!(
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.improved_by,
|
||||
1,
|
||||
);
|
||||
|
||||
// when the difference between headers is 2
|
||||
assert_eq!(
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.improved_by,
|
||||
2,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_obsolete_submit_finality_proof_ignores_other_calls() {
|
||||
run_test(|| {
|
||||
let call =
|
||||
RuntimeCall::System(pezframe_system::Call::<TestRuntime>::remark { remark: vec![42] });
|
||||
|
||||
assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call), Ok(None));
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// From construct_runtime macro
|
||||
#![allow(clippy::from_over_into)]
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use pezbp_runtime::{Chain, ChainId};
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types, pezsp_runtime::StateVersion, traits::Hooks,
|
||||
weights::Weight,
|
||||
};
|
||||
use pezsp_core::sr25519::Signature;
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type TestHeader = pezsp_runtime::testing::Header;
|
||||
pub type TestNumber = u64;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
pub const MAX_BRIDGED_AUTHORITIES: u32 = 5;
|
||||
|
||||
use crate as grandpa;
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Grandpa: grandpa::{Pezpallet, Call, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxFreeHeadersPerBlock: u32 = 2;
|
||||
pub const FreeHeadersInterval: u32 = 32;
|
||||
pub const HeadersToKeep: u32 = 5;
|
||||
}
|
||||
|
||||
impl grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxFreeHeadersPerBlock = MaxFreeHeadersPerBlock;
|
||||
type FreeHeadersInterval = FreeHeadersInterval;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
const ID: ChainId = *b"tbch";
|
||||
|
||||
type BlockNumber = pezframe_system::pezpallet_prelude::BlockNumberFor<TestRuntime>;
|
||||
type Hash = <TestRuntime as pezframe_system::Config>::Hash;
|
||||
type Hasher = <TestRuntime as pezframe_system::Config>::Hashing;
|
||||
type Header = TestHeader;
|
||||
|
||||
type AccountId = AccountId;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = Signature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
unreachable!()
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for TestBridgedChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = MAX_BRIDGED_AUTHORITIES;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 64;
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
pezsp_io::TestExternalities::new(Default::default())
|
||||
}
|
||||
|
||||
/// Return test within default test externalities context.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
let _ = Grandpa::on_initialize(0);
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
/// Return test header with given number.
|
||||
pub fn test_header(num: TestNumber) -> TestHeader {
|
||||
// We wrap the call to avoid explicit type annotations in our tests
|
||||
bp_test_utils::test_header(num)
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Wrappers for public types that are implementing `MaxEncodedLen`
|
||||
|
||||
use crate::{Config, Error};
|
||||
|
||||
use bp_header_pez_chain::{AuthoritySet, ChainWithGrandpa};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{traits::Get, BoundedVec, CloneNoBound, RuntimeDebugNoBound};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// A bounded list of Grandpa authorities with associated weights.
|
||||
pub type StoredAuthorityList<MaxBridgedAuthorities> =
|
||||
BoundedVec<(AuthorityId, AuthorityWeight), MaxBridgedAuthorities>;
|
||||
|
||||
/// Adapter for using `T::BridgedChain::MAX_BRIDGED_AUTHORITIES` in `BoundedVec`.
|
||||
pub struct StoredAuthorityListLimit<T, I>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> Get<u32> for StoredAuthorityListLimit<T, I> {
|
||||
fn get() -> u32 {
|
||||
T::BridgedChain::MAX_AUTHORITIES_COUNT
|
||||
}
|
||||
}
|
||||
|
||||
/// A bounded GRANDPA Authority List and ID.
|
||||
#[derive(CloneNoBound, Decode, Encode, Eq, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)]
|
||||
#[scale_info(skip_type_params(T, I))]
|
||||
pub struct StoredAuthoritySet<T: Config<I>, I: 'static> {
|
||||
/// List of GRANDPA authorities for the current round.
|
||||
pub authorities: StoredAuthorityList<StoredAuthorityListLimit<T, I>>,
|
||||
/// Monotonic identifier of the current GRANDPA authority set.
|
||||
pub set_id: SetId,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> StoredAuthoritySet<T, I> {
|
||||
/// Try to create a new bounded GRANDPA Authority Set from unbounded list.
|
||||
///
|
||||
/// Returns error if number of authorities in the provided list is too large.
|
||||
pub fn try_new(authorities: AuthorityList, set_id: SetId) -> Result<Self, Error<T, I>> {
|
||||
Ok(Self {
|
||||
authorities: TryFrom::try_from(authorities)
|
||||
.map_err(|_| Error::TooManyAuthoritiesInSet)?,
|
||||
set_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns number of bytes that may be subtracted from the PoV component of
|
||||
/// `submit_finality_proof` call, because the actual authorities set is smaller than the maximal
|
||||
/// configured.
|
||||
///
|
||||
/// Maximal authorities set size is configured by the `MaxBridgedAuthorities` constant from
|
||||
/// the pezpallet configuration. The PoV of the call includes the size of maximal authorities
|
||||
/// count. If the actual size is smaller, we may subtract extra bytes from this component.
|
||||
pub fn unused_proof_size(&self) -> u64 {
|
||||
// we can only safely estimate bytes that are occupied by the authority data itself. We have
|
||||
// no means here to compute PoV bytes, occupied by extra trie nodes or extra bytes in the
|
||||
// whole set encoding
|
||||
let single_authority_max_encoded_len =
|
||||
<(AuthorityId, AuthorityWeight)>::max_encoded_len() as u64;
|
||||
let extra_authorities =
|
||||
T::BridgedChain::MAX_AUTHORITIES_COUNT.saturating_sub(self.authorities.len() as _);
|
||||
single_authority_max_encoded_len.saturating_mul(extra_authorities as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> PartialEq for StoredAuthoritySet<T, I> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.set_id == other.set_id && self.authorities == other.authorities
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for StoredAuthoritySet<T, I> {
|
||||
fn default() -> Self {
|
||||
StoredAuthoritySet { authorities: BoundedVec::default(), set_id: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> From<StoredAuthoritySet<T, I>> for AuthoritySet {
|
||||
fn from(t: StoredAuthoritySet<T, I>) -> Self {
|
||||
AuthoritySet { authorities: t.authorities.into(), set_id: t.set_id }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::mock::{TestRuntime, MAX_BRIDGED_AUTHORITIES};
|
||||
use bp_test_utils::authority_list;
|
||||
|
||||
type StoredAuthoritySet = super::StoredAuthoritySet<TestRuntime, ()>;
|
||||
|
||||
#[test]
|
||||
fn unused_proof_size_works() {
|
||||
let authority_entry = authority_list().pop().unwrap();
|
||||
|
||||
// when we have exactly `MaxBridgedAuthorities` authorities
|
||||
assert_eq!(
|
||||
StoredAuthoritySet::try_new(
|
||||
vec![authority_entry.clone(); MAX_BRIDGED_AUTHORITIES as usize],
|
||||
0,
|
||||
)
|
||||
.unwrap()
|
||||
.unused_proof_size(),
|
||||
0,
|
||||
);
|
||||
|
||||
// when we have less than `MaxBridgedAuthorities` authorities
|
||||
assert_eq!(
|
||||
StoredAuthoritySet::try_new(
|
||||
vec![authority_entry; MAX_BRIDGED_AUTHORITIES as usize - 1],
|
||||
0,
|
||||
)
|
||||
.unwrap()
|
||||
.unused_proof_size(),
|
||||
40,
|
||||
);
|
||||
|
||||
// and we can't have more than `MaxBridgedAuthorities` authorities in the bounded vec, so
|
||||
// no test for this case
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_bridge_grandpa
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-03-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/unknown-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_bridge_grandpa
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/grandpa/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pezpallet_bridge_grandpa.
|
||||
pub trait WeightInfo {
|
||||
fn submit_finality_proof(p: u32, v: u32) -> Weight;
|
||||
fn force_set_pallet_state() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_bridge_grandpa` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
/// Storage: BridgeUnknownGrandpa PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa RequestCount (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa RequestCount (max_values: Some(1), max_size: Some(4), added:
|
||||
/// 499, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa BestFinalized (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added:
|
||||
/// 531, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa CurrentAuthoritySet (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(209),
|
||||
/// added: 704, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHashesPointer (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4),
|
||||
/// added: 499, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHashes (max_values: Some(14400), max_size: Some(36),
|
||||
/// added: 2016, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `p` is `[1, 4]`.
|
||||
///
|
||||
/// The range of component `v` is `[50, 100]`.
|
||||
fn submit_finality_proof(p: u32, v: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `394 + p * (60 ±0)`
|
||||
// Estimated: `4745`
|
||||
// Minimum execution time: 228_072 nanoseconds.
|
||||
Weight::from_parts(57_853_228, 4745)
|
||||
// Standard Error: 149_421
|
||||
.saturating_add(Weight::from_parts(36_708_702, 0).saturating_mul(p.into()))
|
||||
// Standard Error: 10_625
|
||||
.saturating_add(Weight::from_parts(1_469_032, 0).saturating_mul(v.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(6_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(6_u64))
|
||||
}
|
||||
|
||||
/// Storage: `BridgeZagrosGrandpa::CurrentAuthoritySet` (r:1 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::CurrentAuthoritySet` (`max_values`: Some(1), `max_size`:
|
||||
/// Some(50250), added: 50745, mode: `MaxEncodedLen`)
|
||||
/// Storage: `BridgeZagrosGrandpa::ImportedHashesPointer` (r:1 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::ImportedHashesPointer` (`max_values`: Some(1), `max_size`:
|
||||
/// Some(4), added: 499, mode: `MaxEncodedLen`) Storage: `BridgeZagrosGrandpa::ImportedHashes`
|
||||
/// (r:1 w:1) Proof: `BridgeZagrosGrandpa::ImportedHashes` (`max_values`: Some(1024),
|
||||
/// `max_size`: Some(36), added: 1521, mode: `MaxEncodedLen`)
|
||||
/// Storage: `BridgeZagrosGrandpa::BestFinalized` (r:0 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::BestFinalized` (`max_values`: Some(1), `max_size`: Some(36),
|
||||
/// added: 531, mode: `MaxEncodedLen`) Storage: `BridgeZagrosGrandpa::ImportedHeaders` (r:0
|
||||
/// w:2) Proof: `BridgeZagrosGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`:
|
||||
/// Some(68), added: 1553, mode: `MaxEncodedLen`)
|
||||
fn force_set_pallet_state() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `452`
|
||||
// Estimated: `51735`
|
||||
// Minimum execution time: 62_232_000 picoseconds.
|
||||
Weight::from_parts(78_755_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 51735))
|
||||
.saturating_add(RocksDbWeight::get().reads(3))
|
||||
.saturating_add(RocksDbWeight::get().writes(6))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: BridgeUnknownGrandpa PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa RequestCount (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa RequestCount (max_values: Some(1), max_size: Some(4), added:
|
||||
/// 499, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa BestFinalized (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added:
|
||||
/// 531, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa CurrentAuthoritySet (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(209),
|
||||
/// added: 704, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHashesPointer (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4),
|
||||
/// added: 499, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHashes (max_values: Some(14400), max_size: Some(36),
|
||||
/// added: 2016, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `p` is `[1, 4]`.
|
||||
///
|
||||
/// The range of component `v` is `[50, 100]`.
|
||||
fn submit_finality_proof(p: u32, v: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `394 + p * (60 ±0)`
|
||||
// Estimated: `4745`
|
||||
// Minimum execution time: 228_072 nanoseconds.
|
||||
Weight::from_parts(57_853_228, 4745)
|
||||
// Standard Error: 149_421
|
||||
.saturating_add(Weight::from_parts(36_708_702, 0).saturating_mul(p.into()))
|
||||
// Standard Error: 10_625
|
||||
.saturating_add(Weight::from_parts(1_469_032, 0).saturating_mul(v.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(6_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(6_u64))
|
||||
}
|
||||
|
||||
/// Storage: `BridgeZagrosGrandpa::CurrentAuthoritySet` (r:1 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::CurrentAuthoritySet` (`max_values`: Some(1), `max_size`:
|
||||
/// Some(50250), added: 50745, mode: `MaxEncodedLen`)
|
||||
/// Storage: `BridgeZagrosGrandpa::ImportedHashesPointer` (r:1 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::ImportedHashesPointer` (`max_values`: Some(1), `max_size`:
|
||||
/// Some(4), added: 499, mode: `MaxEncodedLen`) Storage: `BridgeZagrosGrandpa::ImportedHashes`
|
||||
/// (r:1 w:1) Proof: `BridgeZagrosGrandpa::ImportedHashes` (`max_values`: Some(1024),
|
||||
/// `max_size`: Some(36), added: 1521, mode: `MaxEncodedLen`)
|
||||
/// Storage: `BridgeZagrosGrandpa::BestFinalized` (r:0 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::BestFinalized` (`max_values`: Some(1), `max_size`: Some(36),
|
||||
/// added: 531, mode: `MaxEncodedLen`) Storage: `BridgeZagrosGrandpa::ImportedHeaders` (r:0
|
||||
/// w:2) Proof: `BridgeZagrosGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`:
|
||||
/// Some(68), added: 1553, mode: `MaxEncodedLen`)
|
||||
fn force_set_pallet_state() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `452`
|
||||
// Estimated: `51735`
|
||||
// Minimum execution time: 62_232_000 picoseconds.
|
||||
Weight::from_parts(78_755_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 51735))
|
||||
.saturating_add(RocksDbWeight::get().reads(3))
|
||||
.saturating_add(RocksDbWeight::get().writes(6))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::{BridgeWeight, WeightInfo};
|
||||
|
||||
use pezframe_support::weights::Weight;
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
// Our configuration assumes that the runtime has special signed extensions used to:
|
||||
//
|
||||
// 1) boost priority of `submit_finality_proof` transactions;
|
||||
//
|
||||
// 2) slash relayer if he submits an invalid transaction.
|
||||
//
|
||||
// We read and update storage values of other pallets (`pezpallet-bridge-relayers` and
|
||||
// balances/assets pezpallet). So we need to add this weight to the weight of our call.
|
||||
// Hence two following methods.
|
||||
|
||||
/// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions
|
||||
/// that are declared at runtime level.
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight;
|
||||
|
||||
// Functions that are directly mapped to extrinsics weights.
|
||||
|
||||
/// Weight of message delivery extrinsic.
|
||||
fn submit_finality_proof_weight(precommits_len: u32, votes_ancestries_len: u32) -> Weight {
|
||||
let base_weight = Self::submit_finality_proof(precommits_len, votes_ancestries_len);
|
||||
base_weight.saturating_add(Self::submit_finality_proof_overhead_from_runtime())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: pezframe_system::Config> WeightInfoExt for BridgeWeight<T> {
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightInfoExt for () {
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-messages"
|
||||
description = "Module that allows bridged chains to exchange messages using lane concept."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-bridge-messages"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-messages = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
pezsp-trie = { optional = true, workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezbp-runtime = { features = ["test-helpers"], workspace = true }
|
||||
bp-test-utils = { workspace = true }
|
||||
pezpallet-balances = { workspace = true }
|
||||
pezpallet-bridge-grandpa = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-test-utils/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-bridge-grandpa/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"pezsp-trie/std",
|
||||
"tracing/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezbp-runtime/test-helpers",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-bridge-grandpa/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-trie?/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-bridge-grandpa/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
test-helpers = ["pezbp-runtime/test-helpers", "pezsp-trie"]
|
||||
@@ -0,0 +1,202 @@
|
||||
# Bridge Messages Pezpallet
|
||||
|
||||
The messages pezpallet is used to deliver messages from source chain to target chain. Message is (almost) opaque to the
|
||||
module and the final goal is to hand message to the message dispatch mechanism.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Message Workflow](#message-workflow)
|
||||
- [Integrating Message Lane Module into Runtime](#integrating-messages-module-into-runtime)
|
||||
- [Non-Essential Functionality](#non-essential-functionality)
|
||||
- [Weights of Module Extrinsics](#weights-of-module-extrinsics)
|
||||
|
||||
## Overview
|
||||
|
||||
Message lane is a unidirectional channel, where messages are sent from source chain to the target chain. At the same
|
||||
time, a single instance of messages module supports both outbound lanes and inbound lanes. So the chain where the module
|
||||
is deployed (this chain), may act as a source chain for outbound messages (heading to a bridged chain) and as a target
|
||||
chain for inbound messages (coming from a bridged chain).
|
||||
|
||||
Messages module supports multiple message lanes. Every message lane is identified with a 4-byte identifier. Messages
|
||||
sent through the lane are assigned unique (for this lane) increasing integer value that is known as nonce ("number that
|
||||
can only be used once"). Messages that are sent over the same lane are guaranteed to be delivered to the target chain in
|
||||
the same order they're sent from the source chain. In other words, message with nonce `N` will be delivered right before
|
||||
delivering a message with nonce `N+1`.
|
||||
|
||||
Single message lane may be seen as a transport channel for single application (onchain, offchain or mixed). At the same
|
||||
time the module itself never dictates any lane or message rules. In the end, it is the runtime developer who defines
|
||||
what message lane and message mean for this runtime.
|
||||
|
||||
In our [Kusama<>PezkuwiChain bridge](../../docs/pezkuwi-kusama-bridge-overview.md) we are using lane
|
||||
as a channel of communication between two teyrchains of different relay chains. For example, lane
|
||||
`[0, 0, 0, 0]` is used for PezkuwiChain <> Kusama Asset Hub communications. Other lanes may be used to
|
||||
bridge other teyrchains.
|
||||
|
||||
## Message Workflow
|
||||
|
||||
The pezpallet is not intended to be used by end users and provides no public calls to send the message. Instead, it
|
||||
provides runtime-internal method that allows other pallets (or other runtime code) to queue outbound messages.
|
||||
|
||||
The message "appears" when some runtime code calls the `send_message()` method of the pezpallet. The submitter specifies
|
||||
the lane that they're willing to use and the message itself. If some fee must be paid for sending the message, it must
|
||||
be paid outside of the pezpallet. If a message passes all checks (that include, for example, message size check, disabled
|
||||
lane check, ...), the nonce is assigned and the message is stored in the module storage. The message is in an
|
||||
"undelivered" state now.
|
||||
|
||||
We assume that there are external, offchain actors, called relayers, that are submitting module related transactions to
|
||||
both target and source chains. The pezpallet itself has no assumptions about relayers incentivization scheme, but it has
|
||||
some callbacks for paying rewards. See [Integrating Messages Module into
|
||||
runtime](#Integrating-Messages-Module-into-runtime) for details.
|
||||
|
||||
Eventually, some relayer would notice this message in the "undelivered" state and it would decide to deliver this
|
||||
message. Relayer then crafts `receive_messages_proof()` transaction (aka delivery transaction) for the messages module
|
||||
instance, deployed at the target chain. Relayer provides its account id at the source chain, the proof of message (or
|
||||
several messages), the number of messages in the transaction and their cumulative dispatch weight. Once a transaction is
|
||||
mined, the message is considered "delivered".
|
||||
|
||||
Once a message is delivered, the relayer may want to confirm delivery back to the source chain. There are two reasons
|
||||
why it would want to do that. The first is that we intentionally limit number of "delivered", but not yet "confirmed"
|
||||
messages at inbound lanes (see [What about other Constants in the Messages Module Configuration
|
||||
Trait](#What-about-other-Constants-in-the-Messages-Module-Configuration-Trait) for explanation). So at some point, the
|
||||
target chain may stop accepting new messages until relayers confirm some of these. The second is that if the relayer
|
||||
wants to be rewarded for delivery, it must prove the fact that it has actually delivered the message. And this proof may
|
||||
only be generated after the delivery transaction is mined. So relayer crafts the `receive_messages_delivery_proof()`
|
||||
transaction (aka confirmation transaction) for the messages module instance, deployed at the source chain. Once this
|
||||
transaction is mined, the message is considered "confirmed".
|
||||
|
||||
The "confirmed" state is the final state of the message. But there's one last thing related to the message - the fact
|
||||
that it is now "confirmed" and reward has been paid to the relayer (or at least callback for this has been called), must
|
||||
be confirmed to the target chain. Otherwise, we may reach the limit of "unconfirmed" messages at the target chain and it
|
||||
will stop accepting new messages. So relayer sometimes includes a nonce of the latest "confirmed" message in the next
|
||||
`receive_messages_proof()` transaction, proving that some messages have been confirmed.
|
||||
|
||||
## Integrating Messages Module into Runtime
|
||||
|
||||
As it has been said above, the messages module supports both outbound and inbound message lanes. So if we will integrate
|
||||
a module in some runtime, it may act as the source chain runtime for outbound messages and as the target chain runtime
|
||||
for inbound messages. In this section, we'll sometimes refer to the chain we're currently integrating with, as "this
|
||||
chain" and the other chain as "bridged chain".
|
||||
|
||||
Messages module doesn't simply accept transactions that are claiming that the bridged chain has some updated data for
|
||||
us. Instead of this, the module assumes that the bridged chain is able to prove that updated data in some way. The proof
|
||||
is abstracted from the module and may be of any kind. In our Bizinikiwi-to-Bizinikiwi bridge we're using runtime storage
|
||||
proofs. Other bridges may use transaction proofs, Bizinikiwi header digests or anything else that may be proved.
|
||||
|
||||
**IMPORTANT NOTE**: everything below in this chapter describes details of the messages module configuration. But if
|
||||
you're interested in well-probed and relatively easy integration of two Bizinikiwi-based chains, you may want to look at
|
||||
the [pezbridge-runtime-common](../../bin/runtime-common/) crate. This crate is providing a lot of helpers for integration,
|
||||
which may be directly used from within your runtime. Then if you'll decide to change something in this scheme, get back
|
||||
here for detailed information.
|
||||
|
||||
### General Information
|
||||
|
||||
The messages module supports instances. Every module instance is supposed to bridge this chain and some bridged chain.
|
||||
To bridge with another chain, using another instance is suggested (this isn't forced anywhere in the code, though). Keep
|
||||
in mind, that the pezpallet may be used to build virtual channels between multiple chains, as we do in our [PezkuwiChain <>
|
||||
Kusama bridge](../../docs/pezkuwi-kusama-bridge-overview.md). There, the pezpallet actually bridges only two teyrchains -
|
||||
Kusama Bridge Hub and PezkuwiChain Bridge Hub. However, other Kusama and PezkuwiChain teyrchains are able to send (XCM) messages
|
||||
to their Bridge Hubs. The messages will be delivered to the other side of the bridge and routed to the proper
|
||||
destination teyrchain within the bridged chain consensus.
|
||||
|
||||
Message submitters may track message progress by inspecting module events. When Message is accepted, the
|
||||
`MessageAccepted` event is emitted. The event contains both message lane identifier and nonce that has been assigned to
|
||||
the message. When a message is delivered to the target chain, the `MessagesDelivered` event is emitted from the
|
||||
`receive_messages_delivery_proof()` transaction. The `MessagesDelivered` contains the message lane identifier and
|
||||
inclusive range of delivered message nonces.
|
||||
|
||||
The pezpallet provides no means to get the result of message dispatch at the target chain. If that is
|
||||
required, it must be done outside of the pezpallet. For example, XCM messages, when dispatched, have
|
||||
special instructions to send some data back to the sender. Other dispatchers may use similar
|
||||
mechanism for that.
|
||||
|
||||
### How to plug-in Messages Module to Send and Receive Messages from the Bridged Chain?
|
||||
|
||||
The `pallet_bridge_messages::Config` trait has 2 main associated types that are used to work with
|
||||
inbound messages. The `pallet_bridge_messages::BridgedChain` defines basic primitives of the bridged
|
||||
chain. The `pallet_bridge_messages::BridgedHeaderChain` defines the way we access the bridged chain
|
||||
headers in our runtime. You may use `pallet_bridge_grandpa` if you're bridging with chain that uses
|
||||
GRANDPA finality or `pallet_bridge_teyrchains::TeyrchainHeaders` if you're bridging with teyrchain.
|
||||
|
||||
The `pallet_bridge_messages::Config::MessageDispatch` defines a way on how to dispatch delivered
|
||||
messages. Apart from actually dispatching the message, the implementation must return the correct
|
||||
dispatch weight of the message before dispatch is called.
|
||||
|
||||
The last type is the `pallet_bridge_messages::Config::DeliveryConfirmationPayments`. When confirmation
|
||||
transaction is received, we call the `pay_reward()` method, passing the range of delivered messages.
|
||||
You may use the [`pezpallet-bridge-relayers`](../relayers/) pezpallet and its
|
||||
[`DeliveryConfirmationPaymentsAdapter`](../relayers/src/payment_adapter.rs) adapter as a possible
|
||||
implementation. It allows you to pay fixed reward for relaying the message and some of its portion
|
||||
for confirming delivery.
|
||||
|
||||
### I have a Messages Module in my Runtime, but I Want to Reject all Outbound Messages. What shall I do?
|
||||
|
||||
You should be looking at the `bp_messages::source_chain::ForbidOutboundMessages` structure
|
||||
[`bp_messages::source_chain`](../../primitives/messages/src/source_chain.rs). It implements all required traits and will
|
||||
simply reject all transactions, related to outbound messages.
|
||||
|
||||
### I have a Messages Module in my Runtime, but I Want to Reject all Inbound Messages. What shall I do?
|
||||
|
||||
You should be looking at the `bp_messages::target_chain::ForbidInboundMessages` structure from the
|
||||
[`bp_messages::target_chain`](../../primitives/messages/src/target_chain.rs) module. It implements all required traits
|
||||
and will simply reject all transactions, related to inbound messages.
|
||||
|
||||
### What about other Constants in the Messages Module Configuration Trait?
|
||||
|
||||
`pallet_bridge_messages::Config::MaximalOutboundPayloadSize` constant defines the maximal size
|
||||
of outbound message that may be sent. If the message size is above this limit, the message is
|
||||
rejected.
|
||||
|
||||
To be able to reward the relayer for delivering messages, we store a map of message nonces range =>
|
||||
identifier of the relayer that has delivered this range at the target chain runtime storage. If a
|
||||
relayer delivers multiple consequent ranges, they're merged into single entry. So there may be more
|
||||
than one entry for the same relayer. Eventually, this whole map must be delivered back to the source
|
||||
chain to confirm delivery and pay rewards. So to make sure we are able to craft this confirmation
|
||||
transaction, we need to: (1) keep the size of this map below a certain limit and (2) make sure that
|
||||
the weight of processing this map is below a certain limit. Both size and processing weight mostly
|
||||
depend on the number of entries. The number of entries is limited with the
|
||||
`pallet_bridge_messages::Config::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` parameter.
|
||||
Processing weight also depends on the total number of messages that are being confirmed, because every
|
||||
confirmed message needs to be read. So there's another
|
||||
`pallet_bridge_messages::Config::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` parameter
|
||||
for that.
|
||||
|
||||
When choosing values for these parameters, you must also keep in mind that if proof in your scheme
|
||||
is based on finality of headers (and it is the most obvious option for Bizinikiwi-based chains with
|
||||
finality notion), then choosing too small values for these parameters may cause significant delays
|
||||
in message delivery. That's because there are too many actors involved in this scheme: 1) authorities
|
||||
that are finalizing headers of the target chain need to finalize header with non-empty map; 2) the
|
||||
headers relayer then needs to submit this header and its finality proof to the source chain; 3) the
|
||||
messages relayer must then send confirmation transaction (storage proof of this map) to the source
|
||||
chain; 4) when the confirmation transaction will be mined at some header, source chain authorities
|
||||
must finalize this header; 5) the headers relay then needs to submit this header and its finality
|
||||
proof to the target chain; 6) only now the messages relayer may submit new messages from the source
|
||||
to target chain and prune the entry from the map.
|
||||
|
||||
Delivery transaction requires the relayer to provide both number of entries and total number of
|
||||
messages in the map. This means that the module never charges an extra cost for delivering a map -
|
||||
the relayer would need to pay exactly for the number of entries+messages it has delivered. So the
|
||||
best guess for values of these parameters would be the pair that would occupy `N` percent of the
|
||||
maximal transaction size and weight of the source chain. The `N` should be large enough to process
|
||||
large maps, at the same time keeping reserve for future source chain upgrades.
|
||||
|
||||
## Non-Essential Functionality
|
||||
|
||||
There may be a special account in every runtime where the messages module is deployed. This account, named 'module
|
||||
owner', is like a module-level sudo account - he's able to halt and resume all module operations without requiring
|
||||
runtime upgrade. Calls that are related to this account are:
|
||||
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
|
||||
- `fn set_operating_mode()`: the module owner (or sudo account) may call this function to pause/resume
|
||||
pezpallet operations. Owner may halt the pezpallet by calling this method with
|
||||
`MessagesOperatingMode::Basic(BasicOperatingMode::Halted)` argument - all message-related
|
||||
transactions will be rejected. Owner may then resume pezpallet operations by passing the
|
||||
`MessagesOperatingMode::Basic(BasicOperatingMode::Normal)` argument. There's also
|
||||
`MessagesOperatingMode::RejectingOutboundMessages` pezpallet mode, where it still accepts all incoming
|
||||
messages, but all outbound messages are rejected.
|
||||
|
||||
If pezpallet owner is not defined, the governance may be used to make those calls.
|
||||
|
||||
## Messages Relay
|
||||
|
||||
We have an offchain actor, who is watching for new messages and submits them to the bridged chain. It is the messages
|
||||
relay - you may look at the [crate level documentation and the code](../../relays/messages/).
|
||||
@@ -0,0 +1,552 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Messages pezpallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::{
|
||||
active_outbound_lane, weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH, BridgedChainOf, Call,
|
||||
InboundLanes, OutboundLanes,
|
||||
};
|
||||
|
||||
use bp_messages::{
|
||||
source_chain::FromBridgedChainMessagesDeliveryProof,
|
||||
target_chain::FromBridgedChainMessagesProof, ChainWithMessages, DeliveredMessages,
|
||||
InboundLaneData, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer,
|
||||
UnrewardedRelayersState,
|
||||
};
|
||||
use pezbp_runtime::{AccountIdOf, HashOf, UnverifiedStorageProofParams};
|
||||
use codec::Decode;
|
||||
use pezframe_benchmarking::{account, v2::*};
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_runtime::{traits::TrailingZeroInput, BoundedVec};
|
||||
use pezsp_std::{ops::RangeInclusive, prelude::*};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
/// Pezpallet we're benchmarking here.
|
||||
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
|
||||
|
||||
/// Benchmark-specific message proof parameters.
|
||||
#[derive(Debug)]
|
||||
pub struct MessageProofParams<LaneId> {
|
||||
/// Id of the lane.
|
||||
pub lane: LaneId,
|
||||
/// Range of messages to include in the proof.
|
||||
pub message_nonces: RangeInclusive<MessageNonce>,
|
||||
/// If `Some`, the proof needs to include this outbound lane data.
|
||||
pub outbound_lane_data: Option<OutboundLaneData>,
|
||||
/// If `true`, the caller expects that the proof will contain correct messages that will
|
||||
/// be successfully dispatched. This is only called from the "optional"
|
||||
/// `receive_single_message_proof_with_dispatch` benchmark. If you don't need it, just
|
||||
/// return `true` from the `is_message_successfully_dispatched`.
|
||||
pub is_successful_dispatch_expected: bool,
|
||||
/// Proof size requirements.
|
||||
pub proof_params: UnverifiedStorageProofParams,
|
||||
}
|
||||
|
||||
/// Benchmark-specific message delivery proof parameters.
|
||||
#[derive(Debug)]
|
||||
pub struct MessageDeliveryProofParams<ThisChainAccountId, LaneId> {
|
||||
/// Id of the lane.
|
||||
pub lane: LaneId,
|
||||
/// The proof needs to include this inbound lane data.
|
||||
pub inbound_lane_data: InboundLaneData<ThisChainAccountId>,
|
||||
/// Proof size requirements.
|
||||
pub proof_params: UnverifiedStorageProofParams,
|
||||
}
|
||||
|
||||
/// Trait that must be implemented by runtime.
|
||||
pub trait Config<I: 'static>: crate::Config<I> {
|
||||
/// Lane id to use in benchmarks.
|
||||
fn bench_lane_id() -> Self::LaneId {
|
||||
Self::LaneId::default()
|
||||
}
|
||||
|
||||
/// Return id of relayer account at the bridged chain.
|
||||
///
|
||||
/// By default, zero account is returned.
|
||||
fn bridged_relayer_id() -> AccountIdOf<BridgedChainOf<Self, I>> {
|
||||
Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap()
|
||||
}
|
||||
|
||||
/// Create given account and give it enough balance for test purposes. Used to create
|
||||
/// relayer account at the target chain. Is strictly necessary when your rewards scheme
|
||||
/// assumes that the relayer account must exist.
|
||||
///
|
||||
/// Does nothing by default.
|
||||
fn endow_account(_account: &Self::AccountId) {}
|
||||
|
||||
/// Prepare messages proof to receive by the module.
|
||||
fn prepare_message_proof(
|
||||
params: MessageProofParams<Self::LaneId>,
|
||||
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChainOf<Self, I>>, Self::LaneId>, Weight);
|
||||
/// Prepare messages delivery proof to receive by the module.
|
||||
fn prepare_message_delivery_proof(
|
||||
params: MessageDeliveryProofParams<Self::AccountId, Self::LaneId>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<Self, I>>, Self::LaneId>;
|
||||
|
||||
/// Returns true if message has been successfully dispatched or not.
|
||||
fn is_message_successfully_dispatched(_nonce: MessageNonce) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if given relayer has been rewarded for some of its actions.
|
||||
fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool;
|
||||
}
|
||||
|
||||
fn send_regular_message<T: Config<I>, I: 'static>() {
|
||||
OutboundLanes::<T, I>::insert(
|
||||
T::bench_lane_id(),
|
||||
OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
latest_generated_nonce: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let mut outbound_lane = active_outbound_lane::<T, I>(T::bench_lane_id()).unwrap();
|
||||
outbound_lane.send_message(BoundedVec::try_from(vec![]).expect("We craft valid messages"));
|
||||
}
|
||||
|
||||
fn receive_messages<T: Config<I>, I: 'static>(nonce: MessageNonce) {
|
||||
InboundLanes::<T, I>::insert(
|
||||
T::bench_lane_id(),
|
||||
InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![UnrewardedRelayer {
|
||||
relayer: T::bridged_relayer_id(),
|
||||
messages: DeliveredMessages::new(nonce),
|
||||
}]
|
||||
.into(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
struct ReceiveMessagesProofSetup<T: Config<I>, I: 'static> {
|
||||
relayer_id_on_src: AccountIdOf<BridgedChainOf<T, I>>,
|
||||
relayer_id_on_tgt: T::AccountId,
|
||||
msgs_count: u32,
|
||||
_phantom_data: pezsp_std::marker::PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> ReceiveMessagesProofSetup<T, I> {
|
||||
const LATEST_RECEIVED_NONCE: MessageNonce = 20;
|
||||
|
||||
fn new(msgs_count: u32) -> Self {
|
||||
let setup = Self {
|
||||
relayer_id_on_src: T::bridged_relayer_id(),
|
||||
relayer_id_on_tgt: account("relayer", 0, SEED),
|
||||
msgs_count,
|
||||
_phantom_data: Default::default(),
|
||||
};
|
||||
T::endow_account(&setup.relayer_id_on_tgt);
|
||||
// mark messages 1..=latest_recvd_nonce as delivered
|
||||
receive_messages::<T, I>(Self::LATEST_RECEIVED_NONCE);
|
||||
|
||||
setup
|
||||
}
|
||||
|
||||
fn relayer_id_on_src(&self) -> AccountIdOf<BridgedChainOf<T, I>> {
|
||||
self.relayer_id_on_src.clone()
|
||||
}
|
||||
|
||||
fn relayer_id_on_tgt(&self) -> T::AccountId {
|
||||
self.relayer_id_on_tgt.clone()
|
||||
}
|
||||
|
||||
fn last_nonce(&self) -> MessageNonce {
|
||||
Self::LATEST_RECEIVED_NONCE + self.msgs_count as u64
|
||||
}
|
||||
|
||||
fn nonces(&self) -> RangeInclusive<MessageNonce> {
|
||||
(Self::LATEST_RECEIVED_NONCE + 1)..=self.last_nonce()
|
||||
}
|
||||
|
||||
fn check_last_nonce(&self) {
|
||||
assert_eq!(
|
||||
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).map(|d| d.last_delivered_nonce()),
|
||||
Some(self.last_nonce()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[instance_benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
//
|
||||
// Benchmarks that are used directly by the runtime calls weight formulae.
|
||||
//
|
||||
|
||||
fn max_msgs<T: Config<I>, I: 'static>() -> u32 {
|
||||
T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX as u32 -
|
||||
ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE as u32
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
|
||||
// conditions:
|
||||
// * proof does not include outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is dispatched (reminder: dispatch weight should be minimal);
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
#[benchmark]
|
||||
fn receive_single_message_proof() {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: None,
|
||||
is_successful_dispatch_expected: false,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(
|
||||
EXPECTED_DEFAULT_MESSAGE_LENGTH,
|
||||
),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with `n` minimal-weight messages and following
|
||||
// conditions:
|
||||
// * proof does not include outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is dispatched (reminder: dispatch weight should be minimal);
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
#[benchmark]
|
||||
fn receive_n_messages_proof(n: Linear<1, { max_msgs::<T, I>() }>) {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(n);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: None,
|
||||
is_successful_dispatch_expected: false,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(
|
||||
EXPECTED_DEFAULT_MESSAGE_LENGTH,
|
||||
),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
|
||||
// conditions:
|
||||
// * proof includes outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is successfully dispatched (reminder: dispatch weight should be minimal);
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
//
|
||||
// The weight of outbound lane state delivery would be
|
||||
// `weight(receive_single_message_proof_with_outbound_lane_state) -
|
||||
// weight(receive_single_message_proof)`. This won't be super-accurate if message has non-zero
|
||||
// dispatch weight, but estimation should be close enough to real weight.
|
||||
#[benchmark]
|
||||
fn receive_single_message_proof_with_outbound_lane_state() {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: setup.last_nonce(),
|
||||
latest_received_nonce: ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE,
|
||||
latest_generated_nonce: setup.last_nonce(),
|
||||
}),
|
||||
is_successful_dispatch_expected: false,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(
|
||||
EXPECTED_DEFAULT_MESSAGE_LENGTH,
|
||||
),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
|
||||
// conditions:
|
||||
// * the proof has large leaf with total size ranging between 1KB and 16KB;
|
||||
// * proof does not include outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is dispatched (reminder: dispatch weight should be minimal);
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
#[benchmark]
|
||||
fn receive_single_n_bytes_message_proof(
|
||||
/// Proof size in KB
|
||||
n: Linear<1, { 16 * 1024 }>,
|
||||
) {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: None,
|
||||
is_successful_dispatch_expected: false,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(n),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
|
||||
// * single relayer is rewarded for relaying single message;
|
||||
// * relayer account does not exist (in practice it needs to exist in production environment).
|
||||
//
|
||||
// This is base benchmark for all other confirmations delivery benchmarks.
|
||||
#[benchmark]
|
||||
fn receive_delivery_proof_for_single_message() {
|
||||
let relayer_id: T::AccountId = account("relayer", 0, SEED);
|
||||
|
||||
// send message that we're going to confirm
|
||||
send_regular_message::<T, I>();
|
||||
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 1,
|
||||
last_delivered_nonce: 1,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![UnrewardedRelayer {
|
||||
relayer: relayer_id.clone(),
|
||||
messages: DeliveredMessages::new(1),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
proof_params: UnverifiedStorageProofParams::default(),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_delivery_proof(
|
||||
RawOrigin::Signed(relayer_id.clone()),
|
||||
proof,
|
||||
relayers_state,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
|
||||
Some(1)
|
||||
);
|
||||
assert!(T::is_relayer_rewarded(&relayer_id));
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
|
||||
// * single relayer is rewarded for relaying two messages;
|
||||
// * relayer account does not exist (in practice it needs to exist in production environment).
|
||||
//
|
||||
// Additional weight for paying single-message reward to the same relayer could be computed
|
||||
// as `weight(receive_delivery_proof_for_two_messages_by_single_relayer)
|
||||
// - weight(receive_delivery_proof_for_single_message)`.
|
||||
#[benchmark]
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() {
|
||||
let relayer_id: T::AccountId = account("relayer", 0, SEED);
|
||||
|
||||
// send message that we're going to confirm
|
||||
send_regular_message::<T, I>();
|
||||
send_regular_message::<T, I>();
|
||||
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 2,
|
||||
total_messages: 2,
|
||||
last_delivered_nonce: 2,
|
||||
};
|
||||
let mut delivered_messages = DeliveredMessages::new(1);
|
||||
delivered_messages.note_dispatched_message();
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![UnrewardedRelayer {
|
||||
relayer: relayer_id.clone(),
|
||||
messages: delivered_messages,
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
proof_params: UnverifiedStorageProofParams::default(),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_delivery_proof(
|
||||
RawOrigin::Signed(relayer_id.clone()),
|
||||
proof,
|
||||
relayers_state,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
|
||||
Some(2)
|
||||
);
|
||||
assert!(T::is_relayer_rewarded(&relayer_id));
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
|
||||
// * two relayers are rewarded for relaying single message each;
|
||||
// * relayer account does not exist (in practice it needs to exist in production environment).
|
||||
//
|
||||
// Additional weight for paying reward to the next relayer could be computed
|
||||
// as `weight(receive_delivery_proof_for_two_messages_by_two_relayers)
|
||||
// - weight(receive_delivery_proof_for_two_messages_by_single_relayer)`.
|
||||
#[benchmark]
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() {
|
||||
let relayer1_id: T::AccountId = account("relayer1", 1, SEED);
|
||||
let relayer2_id: T::AccountId = account("relayer2", 2, SEED);
|
||||
|
||||
// send message that we're going to confirm
|
||||
send_regular_message::<T, I>();
|
||||
send_regular_message::<T, I>();
|
||||
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 2,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 2,
|
||||
last_delivered_nonce: 2,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![
|
||||
UnrewardedRelayer {
|
||||
relayer: relayer1_id.clone(),
|
||||
messages: DeliveredMessages::new(1),
|
||||
},
|
||||
UnrewardedRelayer {
|
||||
relayer: relayer2_id.clone(),
|
||||
messages: DeliveredMessages::new(2),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
proof_params: UnverifiedStorageProofParams::default(),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_delivery_proof(
|
||||
RawOrigin::Signed(relayer1_id.clone()),
|
||||
proof,
|
||||
relayers_state,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
|
||||
Some(2)
|
||||
);
|
||||
assert!(T::is_relayer_rewarded(&relayer1_id));
|
||||
assert!(T::is_relayer_rewarded(&relayer2_id));
|
||||
}
|
||||
|
||||
//
|
||||
// Benchmarks that the runtime developers may use for proper pezpallet configuration.
|
||||
//
|
||||
|
||||
// This benchmark is optional and may be used when runtime developer need a way to compute
|
||||
// message dispatch weight. In this case, he needs to provide messages that can go the whole
|
||||
// dispatch
|
||||
//
|
||||
// Benchmark `receive_messages_proof` extrinsic with single message and following conditions:
|
||||
//
|
||||
// * proof does not include outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is **SUCCESSFULLY** dispatched;
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
#[benchmark]
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(
|
||||
/// Proof size in KB
|
||||
n: Linear<1, { 16 * 1024 }>,
|
||||
) {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: None,
|
||||
is_successful_dispatch_expected: true,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(n),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
assert!(T::is_message_successfully_dispatched(setup.last_nonce()));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pezpallet,
|
||||
crate::tests::mock::new_test_ext(),
|
||||
crate::tests::mock::TestRuntime
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helpers for easier manipulation of call processing with signed extensions.
|
||||
|
||||
use crate::{BridgedChainOf, Config, InboundLanes, OutboundLanes, Pezpallet, LOG_TARGET};
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::MessageDispatch, BaseMessagesProofInfo, ChainWithMessages, InboundLaneData,
|
||||
MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo,
|
||||
UnrewardedRelayerOccupation,
|
||||
};
|
||||
use pezbp_runtime::{AccountIdOf, OwnedBridgeModule};
|
||||
use pezframe_support::{dispatch::CallableCallFor, traits::IsSubType};
|
||||
use pezsp_runtime::transaction_validity::TransactionValidity;
|
||||
|
||||
/// Helper struct that provides methods for working with a call supported by `MessagesCallInfo`.
|
||||
pub struct CallHelper<T: Config<I>, I: 'static> {
|
||||
_phantom_data: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> CallHelper<T, I> {
|
||||
/// Returns true if:
|
||||
///
|
||||
/// - call is `receive_messages_proof` and all messages have been delivered;
|
||||
///
|
||||
/// - call is `receive_messages_delivery_proof` and all messages confirmations have been
|
||||
/// received.
|
||||
pub fn was_successful(info: &MessagesCallInfo<T::LaneId>) -> bool {
|
||||
match info {
|
||||
MessagesCallInfo::ReceiveMessagesProof(info) => {
|
||||
let inbound_lane_data = match InboundLanes::<T, I>::get(info.base.lane_id) {
|
||||
Some(inbound_lane_data) => inbound_lane_data,
|
||||
None => return false,
|
||||
};
|
||||
if info.base.bundled_range.is_empty() {
|
||||
let post_occupation =
|
||||
unrewarded_relayers_occupation::<T, I>(&inbound_lane_data);
|
||||
// we don't care about `free_relayer_slots` here - it is checked in
|
||||
// `is_obsolete` and every relayer has delivered at least one message,
|
||||
// so if relayer slots are released, then message slots are also
|
||||
// released
|
||||
return post_occupation.free_message_slots >
|
||||
info.unrewarded_relayers.free_message_slots;
|
||||
}
|
||||
|
||||
inbound_lane_data.last_delivered_nonce() == *info.base.bundled_range.end()
|
||||
},
|
||||
MessagesCallInfo::ReceiveMessagesDeliveryProof(info) => {
|
||||
let outbound_lane_data = match OutboundLanes::<T, I>::get(info.0.lane_id) {
|
||||
Some(outbound_lane_data) => outbound_lane_data,
|
||||
None => return false,
|
||||
};
|
||||
outbound_lane_data.latest_received_nonce == *info.0.bundled_range.end()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing a call that is a sub type of `pezpallet_bridge_messages::Call`.
|
||||
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
|
||||
{
|
||||
/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call.
|
||||
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>>;
|
||||
|
||||
/// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from
|
||||
/// a `ReceiveMessagesDeliveryProof` call.
|
||||
fn receive_messages_delivery_proof_info(
|
||||
&self,
|
||||
) -> Option<ReceiveMessagesDeliveryProofInfo<T::LaneId>>;
|
||||
|
||||
/// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof`
|
||||
/// or a `ReceiveMessagesDeliveryProof` call.
|
||||
fn call_info(&self) -> Option<MessagesCallInfo<T::LaneId>>;
|
||||
|
||||
/// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof`
|
||||
/// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane.
|
||||
fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>>;
|
||||
|
||||
/// Ensures that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call:
|
||||
///
|
||||
/// - does not deliver already delivered messages. We require all messages in the
|
||||
/// `ReceiveMessagesProof` call to be undelivered;
|
||||
///
|
||||
/// - does not submit empty `ReceiveMessagesProof` call with zero messages, unless the lane
|
||||
/// needs to be unblocked by providing relayer rewards proof;
|
||||
///
|
||||
/// - brings no new delivery confirmations in a `ReceiveMessagesDeliveryProof` call. We require
|
||||
/// at least one new delivery confirmation in the unrewarded relayers set;
|
||||
///
|
||||
/// - does not violate some basic (easy verifiable) messages pezpallet rules obsolete (like
|
||||
/// submitting a call when a pezpallet is halted or delivering messages when a dispatcher is
|
||||
/// inactive).
|
||||
///
|
||||
/// If one of above rules is violated, the transaction is treated as invalid.
|
||||
fn check_obsolete_call(&self) -> TransactionValidity;
|
||||
}
|
||||
|
||||
impl<
|
||||
Call: IsSubType<CallableCallFor<Pezpallet<T, I>, T>>,
|
||||
T: pezframe_system::Config<RuntimeCall = Call> + Config<I>,
|
||||
I: 'static,
|
||||
> CallSubType<T, I> for T::RuntimeCall
|
||||
{
|
||||
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>> {
|
||||
if let Some(crate::Call::<T, I>::receive_messages_proof { ref proof, .. }) =
|
||||
self.is_sub_type()
|
||||
{
|
||||
let inbound_lane_data = InboundLanes::<T, I>::get(proof.lane)?;
|
||||
|
||||
return Some(ReceiveMessagesProofInfo {
|
||||
base: BaseMessagesProofInfo {
|
||||
lane_id: proof.lane,
|
||||
// we want all messages in this range to be new for us. Otherwise transaction
|
||||
// will be considered obsolete.
|
||||
bundled_range: proof.nonces_start..=proof.nonces_end,
|
||||
best_stored_nonce: inbound_lane_data.last_delivered_nonce(),
|
||||
},
|
||||
unrewarded_relayers: unrewarded_relayers_occupation::<T, I>(&inbound_lane_data),
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_info(
|
||||
&self,
|
||||
) -> Option<ReceiveMessagesDeliveryProofInfo<T::LaneId>> {
|
||||
if let Some(crate::Call::<T, I>::receive_messages_delivery_proof {
|
||||
ref proof,
|
||||
ref relayers_state,
|
||||
..
|
||||
}) = self.is_sub_type()
|
||||
{
|
||||
let outbound_lane_data = OutboundLanes::<T, I>::get(proof.lane)?;
|
||||
|
||||
return Some(ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
|
||||
lane_id: proof.lane,
|
||||
// there's a time frame between message delivery, message confirmation and reward
|
||||
// confirmation. Because of that, we can't assume that our state has been confirmed
|
||||
// to the bridged chain. So we are accepting any proof that brings new
|
||||
// confirmations.
|
||||
bundled_range: outbound_lane_data.latest_received_nonce + 1..=
|
||||
relayers_state.last_delivered_nonce,
|
||||
best_stored_nonce: outbound_lane_data.latest_received_nonce,
|
||||
}));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn call_info(&self) -> Option<MessagesCallInfo<T::LaneId>> {
|
||||
if let Some(info) = self.receive_messages_proof_info() {
|
||||
return Some(MessagesCallInfo::ReceiveMessagesProof(info));
|
||||
}
|
||||
|
||||
if let Some(info) = self.receive_messages_delivery_proof_info() {
|
||||
return Some(MessagesCallInfo::ReceiveMessagesDeliveryProof(info));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>> {
|
||||
self.call_info().filter(|info| {
|
||||
let actual_lane_id = match info {
|
||||
MessagesCallInfo::ReceiveMessagesProof(info) => info.base.lane_id,
|
||||
MessagesCallInfo::ReceiveMessagesDeliveryProof(info) => info.0.lane_id,
|
||||
};
|
||||
actual_lane_id == lane_id
|
||||
})
|
||||
}
|
||||
|
||||
fn check_obsolete_call(&self) -> TransactionValidity {
|
||||
let is_pallet_halted = Pezpallet::<T, I>::ensure_not_halted().is_err();
|
||||
match self.call_info() {
|
||||
Some(proof_info) if is_pallet_halted => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?proof_info,
|
||||
"Rejecting messages transaction on halted pezpallet"
|
||||
);
|
||||
|
||||
return pezsp_runtime::transaction_validity::InvalidTransaction::Call.into();
|
||||
},
|
||||
Some(MessagesCallInfo::ReceiveMessagesProof(proof_info))
|
||||
if proof_info
|
||||
.is_obsolete(T::MessageDispatch::is_active(proof_info.base.lane_id)) =>
|
||||
{
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?proof_info,
|
||||
"Rejecting obsolete messages delivery transaction"
|
||||
);
|
||||
|
||||
return pezsp_runtime::transaction_validity::InvalidTransaction::Stale.into();
|
||||
},
|
||||
Some(MessagesCallInfo::ReceiveMessagesDeliveryProof(proof_info))
|
||||
if proof_info.is_obsolete() =>
|
||||
{
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?proof_info,
|
||||
"Rejecting obsolete messages confirmation transaction"
|
||||
);
|
||||
|
||||
return pezsp_runtime::transaction_validity::InvalidTransaction::Stale.into();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(pezsp_runtime::transaction_validity::ValidTransaction::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns occupation state of unrewarded relayers vector.
|
||||
fn unrewarded_relayers_occupation<T: Config<I>, I: 'static>(
|
||||
inbound_lane_data: &InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
|
||||
) -> UnrewardedRelayerOccupation {
|
||||
UnrewardedRelayerOccupation {
|
||||
free_relayer_slots: T::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX
|
||||
.saturating_sub(inbound_lane_data.relayers.len() as MessageNonce),
|
||||
free_message_slots: {
|
||||
let unconfirmed_messages = inbound_lane_data
|
||||
.last_delivered_nonce()
|
||||
.saturating_sub(inbound_lane_data.last_confirmed_nonce);
|
||||
T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
|
||||
.saturating_sub(unconfirmed_messages)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::mock::*;
|
||||
use bp_messages::{
|
||||
source_chain::FromBridgedChainMessagesDeliveryProof,
|
||||
target_chain::FromBridgedChainMessagesProof, DeliveredMessages, InboundLaneData, LaneState,
|
||||
OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState,
|
||||
};
|
||||
use pezsp_std::ops::RangeInclusive;
|
||||
|
||||
fn fill_unrewarded_relayers() {
|
||||
let mut inbound_lane_state = InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap();
|
||||
for n in 0..BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX {
|
||||
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
|
||||
relayer: Default::default(),
|
||||
messages: DeliveredMessages { begin: n + 1, end: n + 1 },
|
||||
});
|
||||
}
|
||||
InboundLanes::<TestRuntime>::insert(test_lane_id(), inbound_lane_state);
|
||||
}
|
||||
|
||||
fn fill_unrewarded_messages() {
|
||||
let mut inbound_lane_state = InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap();
|
||||
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
|
||||
relayer: Default::default(),
|
||||
messages: DeliveredMessages {
|
||||
begin: 1,
|
||||
end: BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
},
|
||||
});
|
||||
InboundLanes::<TestRuntime>::insert(test_lane_id(), inbound_lane_state);
|
||||
}
|
||||
|
||||
fn deliver_message_10() {
|
||||
InboundLanes::<TestRuntime>::insert(
|
||||
test_lane_id(),
|
||||
bp_messages::InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: Default::default(),
|
||||
last_confirmed_nonce: 10,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn validate_message_delivery(
|
||||
nonces_start: bp_messages::MessageNonce,
|
||||
nonces_end: bp_messages::MessageNonce,
|
||||
) -> bool {
|
||||
RuntimeCall::Messages(crate::Call::<TestRuntime, ()>::receive_messages_proof {
|
||||
relayer_id_at_bridged_chain: 42,
|
||||
messages_count: nonces_end.checked_sub(nonces_start).map(|x| x + 1).unwrap_or(0) as u32,
|
||||
dispatch_weight: pezframe_support::weights::Weight::zero(),
|
||||
proof: Box::new(FromBridgedChainMessagesProof {
|
||||
bridged_header_hash: Default::default(),
|
||||
storage_proof: Default::default(),
|
||||
lane: test_lane_id(),
|
||||
nonces_start,
|
||||
nonces_end,
|
||||
}),
|
||||
})
|
||||
.check_obsolete_call()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn run_test<T>(test: impl Fn() -> T) -> T {
|
||||
pezsp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
InboundLanes::<TestRuntime>::insert(test_lane_id(), InboundLaneData::opened());
|
||||
OutboundLanes::<TestRuntime>::insert(test_lane_id(), OutboundLaneData::opened());
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_obsolete_messages() {
|
||||
run_test(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver messages 8..=9
|
||||
// => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(8, 9));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_same_message() {
|
||||
run_test(|| {
|
||||
// when current best delivered is message#10 and we're trying to import messages 10..=10
|
||||
// => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(8, 10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_call_with_some_obsolete_messages() {
|
||||
run_test(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver messages
|
||||
// 10..=15 => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(10, 15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_call_with_future_messages() {
|
||||
run_test(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver messages
|
||||
// 13..=15 => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(13, 15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_reject_call_when_dispatcher_is_inactive() {
|
||||
run_test(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver message 11..=15
|
||||
// => tx is accepted, but we have inactive dispatcher, so...
|
||||
deliver_message_10();
|
||||
|
||||
TestMessageDispatch::deactivate(test_lane_id());
|
||||
assert!(!validate_message_delivery(11, 15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots(
|
||||
) {
|
||||
run_test(|| {
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(10, 9));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_relayer_slots(
|
||||
) {
|
||||
run_test(|| {
|
||||
deliver_message_10();
|
||||
fill_unrewarded_relayers();
|
||||
assert!(validate_message_delivery(10, 9));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_message_slots(
|
||||
) {
|
||||
run_test(|| {
|
||||
fill_unrewarded_messages();
|
||||
assert!(validate_message_delivery(
|
||||
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX - 1
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_messages() {
|
||||
run_test(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver message 11..=15
|
||||
// => tx is accepted
|
||||
deliver_message_10();
|
||||
assert!(validate_message_delivery(11, 15));
|
||||
});
|
||||
}
|
||||
|
||||
fn confirm_message_10() {
|
||||
OutboundLanes::<TestRuntime>::insert(
|
||||
test_lane_id(),
|
||||
bp_messages::OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 0,
|
||||
latest_received_nonce: 10,
|
||||
latest_generated_nonce: 10,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool {
|
||||
RuntimeCall::Messages(crate::Call::<TestRuntime>::receive_messages_delivery_proof {
|
||||
proof: FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: Default::default(),
|
||||
storage_proof: Default::default(),
|
||||
lane: test_lane_id(),
|
||||
},
|
||||
relayers_state: UnrewardedRelayersState { last_delivered_nonce, ..Default::default() },
|
||||
})
|
||||
.check_obsolete_call()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_obsolete_confirmations() {
|
||||
run_test(|| {
|
||||
// when current best confirmed is message#10 and we're trying to confirm message#5 => tx
|
||||
// is rejected
|
||||
confirm_message_10();
|
||||
assert!(!validate_message_confirmation(5));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_same_confirmation() {
|
||||
run_test(|| {
|
||||
// when current best confirmed is message#10 and we're trying to confirm message#10 =>
|
||||
// tx is rejected
|
||||
confirm_message_10();
|
||||
assert!(!validate_message_confirmation(10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_empty_confirmation_even_if_there_are_no_free_unrewarded_entries() {
|
||||
run_test(|| {
|
||||
confirm_message_10();
|
||||
fill_unrewarded_relayers();
|
||||
assert!(!validate_message_confirmation(10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_confirmation() {
|
||||
run_test(|| {
|
||||
// when current best confirmed is message#10 and we're trying to confirm message#15 =>
|
||||
// tx is accepted
|
||||
confirm_message_10();
|
||||
assert!(validate_message_confirmation(15));
|
||||
});
|
||||
}
|
||||
|
||||
fn was_message_delivery_successful(
|
||||
bundled_range: RangeInclusive<MessageNonce>,
|
||||
is_empty: bool,
|
||||
) -> bool {
|
||||
CallHelper::<TestRuntime, ()>::was_successful(&MessagesCallInfo::ReceiveMessagesProof(
|
||||
ReceiveMessagesProofInfo {
|
||||
base: BaseMessagesProofInfo {
|
||||
lane_id: test_lane_id(),
|
||||
bundled_range,
|
||||
best_stored_nonce: 0, // doesn't matter for `was_successful`
|
||||
},
|
||||
unrewarded_relayers: UnrewardedRelayerOccupation {
|
||||
free_relayer_slots: 0, // doesn't matter for `was_successful`
|
||||
free_message_slots: if is_empty {
|
||||
0
|
||||
} else {
|
||||
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
|
||||
},
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn was_successful_returns_false_for_failed_reward_confirmation_transaction() {
|
||||
run_test(|| {
|
||||
fill_unrewarded_messages();
|
||||
assert!(!was_message_delivery_successful(10..=9, true));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn was_successful_returns_true_for_successful_reward_confirmation_transaction() {
|
||||
run_test(|| {
|
||||
assert!(was_message_delivery_successful(10..=9, true));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_failed_delivery() {
|
||||
run_test(|| {
|
||||
deliver_message_10();
|
||||
assert!(!was_message_delivery_successful(10..=12, false));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_partially_successful_delivery() {
|
||||
run_test(|| {
|
||||
deliver_message_10();
|
||||
assert!(!was_message_delivery_successful(9..=12, false));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_true_for_successful_delivery() {
|
||||
run_test(|| {
|
||||
deliver_message_10();
|
||||
assert!(was_message_delivery_successful(9..=10, false));
|
||||
});
|
||||
}
|
||||
|
||||
fn was_message_confirmation_successful(bundled_range: RangeInclusive<MessageNonce>) -> bool {
|
||||
CallHelper::<TestRuntime, ()>::was_successful(
|
||||
&MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
|
||||
BaseMessagesProofInfo {
|
||||
lane_id: test_lane_id(),
|
||||
bundled_range,
|
||||
best_stored_nonce: 0, // doesn't matter for `was_successful`
|
||||
},
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_failed_confirmation() {
|
||||
run_test(|| {
|
||||
confirm_message_10();
|
||||
assert!(!was_message_confirmation_successful(10..=12));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_partially_successful_confirmation() {
|
||||
run_test(|| {
|
||||
confirm_message_10();
|
||||
assert!(!was_message_confirmation_successful(9..=12));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_true_for_successful_confirmation() {
|
||||
run_test(|| {
|
||||
confirm_message_10();
|
||||
assert!(was_message_confirmation_successful(9..=10));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Everything about incoming messages receival.
|
||||
|
||||
use crate::{BridgedChainOf, Config};
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
|
||||
ChainWithMessages, DeliveredMessages, InboundLaneData, LaneState, MessageKey, MessageNonce,
|
||||
OutboundLaneData, ReceptionResult, UnrewardedRelayer,
|
||||
};
|
||||
use pezbp_runtime::AccountIdOf;
|
||||
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
|
||||
use scale_info::{Type, TypeInfo};
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
use pezsp_std::prelude::PartialEq;
|
||||
|
||||
/// Inbound lane storage.
|
||||
pub trait InboundLaneStorage {
|
||||
/// Id of relayer on source chain.
|
||||
type Relayer: Clone + PartialEq;
|
||||
/// Lane identifier type.
|
||||
type LaneId: Encode;
|
||||
|
||||
/// Lane id.
|
||||
fn id(&self) -> Self::LaneId;
|
||||
/// Return maximal number of unrewarded relayer entries in inbound lane.
|
||||
fn max_unrewarded_relayer_entries(&self) -> MessageNonce;
|
||||
/// Return maximal number of unconfirmed messages in inbound lane.
|
||||
fn max_unconfirmed_messages(&self) -> MessageNonce;
|
||||
/// Get lane data from the storage.
|
||||
fn data(&self) -> InboundLaneData<Self::Relayer>;
|
||||
/// Update lane data in the storage.
|
||||
fn set_data(&mut self, data: InboundLaneData<Self::Relayer>);
|
||||
/// Purge lane data from the storage.
|
||||
fn purge(self);
|
||||
}
|
||||
|
||||
/// Inbound lane data wrapper that implements `MaxEncodedLen`.
|
||||
///
|
||||
/// We have already had `MaxEncodedLen`-like functionality before, but its usage has
|
||||
/// been localized and we haven't been passing bounds (maximal count of unrewarded relayer entries,
|
||||
/// maximal count of unconfirmed messages) everywhere. This wrapper allows us to avoid passing
|
||||
/// these generic bounds all over the code.
|
||||
///
|
||||
/// The encoding of this type matches encoding of the corresponding `MessageData`.
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct StoredInboundLaneData<T: Config<I>, I: 'static>(
|
||||
pub InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
|
||||
);
|
||||
|
||||
impl<T: Config<I>, I: 'static> pezsp_std::ops::Deref for StoredInboundLaneData<T, I> {
|
||||
type Target = InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> pezsp_std::ops::DerefMut for StoredInboundLaneData<T, I> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for StoredInboundLaneData<T, I> {
|
||||
fn default() -> Self {
|
||||
StoredInboundLaneData(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> From<StoredInboundLaneData<T, I>>
|
||||
for InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>
|
||||
{
|
||||
fn from(data: StoredInboundLaneData<T, I>) -> Self {
|
||||
data.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> EncodeLike<StoredInboundLaneData<T, I>>
|
||||
for InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> TypeInfo for StoredInboundLaneData<T, I> {
|
||||
type Identity = Self;
|
||||
|
||||
fn type_info() -> Type {
|
||||
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> MaxEncodedLen for StoredInboundLaneData<T, I> {
|
||||
fn max_encoded_len() -> usize {
|
||||
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::encoded_size_hint(
|
||||
BridgedChainOf::<T, I>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as usize,
|
||||
)
|
||||
.unwrap_or(usize::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
/// Inbound messages lane.
|
||||
pub struct InboundLane<S> {
|
||||
storage: S,
|
||||
}
|
||||
|
||||
impl<S: InboundLaneStorage> InboundLane<S> {
|
||||
/// Create new inbound lane backed by given storage.
|
||||
pub fn new(storage: S) -> Self {
|
||||
InboundLane { storage }
|
||||
}
|
||||
|
||||
/// Get lane state.
|
||||
pub fn state(&self) -> LaneState {
|
||||
self.storage.data().state
|
||||
}
|
||||
|
||||
/// Returns storage reference.
|
||||
pub fn storage(&self) -> &S {
|
||||
&self.storage
|
||||
}
|
||||
|
||||
/// Set lane state.
|
||||
pub fn set_state(&mut self, state: LaneState) {
|
||||
let mut data = self.storage.data();
|
||||
data.state = state;
|
||||
self.storage.set_data(data);
|
||||
}
|
||||
|
||||
/// Receive state of the corresponding outbound lane.
|
||||
pub fn receive_state_update(
|
||||
&mut self,
|
||||
outbound_lane_data: OutboundLaneData,
|
||||
) -> Option<MessageNonce> {
|
||||
let mut data = self.storage.data();
|
||||
let last_delivered_nonce = data.last_delivered_nonce();
|
||||
|
||||
if outbound_lane_data.latest_received_nonce > last_delivered_nonce {
|
||||
// this is something that should never happen if proofs are correct
|
||||
return None;
|
||||
}
|
||||
if outbound_lane_data.latest_received_nonce <= data.last_confirmed_nonce {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_confirmed_nonce = outbound_lane_data.latest_received_nonce;
|
||||
data.last_confirmed_nonce = new_confirmed_nonce;
|
||||
// Firstly, remove all of the records where higher nonce <= new confirmed nonce
|
||||
while data
|
||||
.relayers
|
||||
.front()
|
||||
.map(|entry| entry.messages.end <= new_confirmed_nonce)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
data.relayers.pop_front();
|
||||
}
|
||||
// Secondly, update the next record with lower nonce equal to new confirmed nonce if needed.
|
||||
// Note: There will be max. 1 record to update as we don't allow messages from relayers to
|
||||
// overlap.
|
||||
match data.relayers.front_mut() {
|
||||
Some(entry) if entry.messages.begin <= new_confirmed_nonce => {
|
||||
entry.messages.begin = new_confirmed_nonce + 1;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
self.storage.set_data(data);
|
||||
Some(outbound_lane_data.latest_received_nonce)
|
||||
}
|
||||
|
||||
/// Receive new message.
|
||||
pub fn receive_message<Dispatch: MessageDispatch<LaneId = S::LaneId>>(
|
||||
&mut self,
|
||||
relayer_at_bridged_chain: &S::Relayer,
|
||||
nonce: MessageNonce,
|
||||
message_data: DispatchMessageData<Dispatch::DispatchPayload>,
|
||||
) -> ReceptionResult<Dispatch::DispatchLevelResult> {
|
||||
let mut data = self.storage.data();
|
||||
if Some(nonce) != data.last_delivered_nonce().checked_add(1) {
|
||||
return ReceptionResult::InvalidNonce;
|
||||
}
|
||||
|
||||
// if there are more unrewarded relayer entries than we may accept, reject this message
|
||||
if data.relayers.len() as MessageNonce >= self.storage.max_unrewarded_relayer_entries() {
|
||||
return ReceptionResult::TooManyUnrewardedRelayers;
|
||||
}
|
||||
|
||||
// if there are more unconfirmed messages than we may accept, reject this message
|
||||
let unconfirmed_messages_count = nonce.saturating_sub(data.last_confirmed_nonce);
|
||||
if unconfirmed_messages_count > self.storage.max_unconfirmed_messages() {
|
||||
return ReceptionResult::TooManyUnconfirmedMessages;
|
||||
}
|
||||
|
||||
// then, dispatch message
|
||||
let dispatch_result = Dispatch::dispatch(DispatchMessage {
|
||||
key: MessageKey { lane_id: self.storage.id(), nonce },
|
||||
data: message_data,
|
||||
});
|
||||
|
||||
// now let's update inbound lane storage
|
||||
match data.relayers.back_mut() {
|
||||
Some(entry) if entry.relayer == *relayer_at_bridged_chain => {
|
||||
entry.messages.note_dispatched_message();
|
||||
},
|
||||
_ => {
|
||||
data.relayers.push_back(UnrewardedRelayer {
|
||||
relayer: relayer_at_bridged_chain.clone(),
|
||||
messages: DeliveredMessages::new(nonce),
|
||||
});
|
||||
},
|
||||
};
|
||||
self.storage.set_data(data);
|
||||
|
||||
ReceptionResult::Dispatched(dispatch_result)
|
||||
}
|
||||
|
||||
/// Purge lane state from the storage.
|
||||
pub fn purge(self) {
|
||||
self.storage.purge()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{active_inbound_lane, lanes_manager::RuntimeInboundLaneStorage, tests::mock::*};
|
||||
use bp_messages::UnrewardedRelayersState;
|
||||
|
||||
fn receive_regular_message(
|
||||
lane: &mut InboundLane<RuntimeInboundLaneStorage<TestRuntime, ()>>,
|
||||
nonce: MessageNonce,
|
||||
) {
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
nonce,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_ignores_status_from_the_future() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 10,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_ignores_obsolete_status() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
receive_regular_message(&mut lane, 2);
|
||||
receive_regular_message(&mut lane, 3);
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(3),
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
|
||||
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_works() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
receive_regular_message(&mut lane, 2);
|
||||
receive_regular_message(&mut lane, 3);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 0);
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![unrewarded_relayer(1, 3, TEST_RELAYER_A)]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 2,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(2),
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 2);
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![unrewarded_relayer(3, 3, TEST_RELAYER_A)]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(3),
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
|
||||
assert_eq!(lane.storage.data().relayers, vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_works_with_batches_from_relayers() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
let mut seed_storage_data = lane.storage.data();
|
||||
// Prepare data
|
||||
seed_storage_data.last_confirmed_nonce = 0;
|
||||
seed_storage_data.relayers.push_back(unrewarded_relayer(1, 1, TEST_RELAYER_A));
|
||||
// Simulate messages batch (2, 3, 4) from relayer #2
|
||||
seed_storage_data.relayers.push_back(unrewarded_relayer(2, 4, TEST_RELAYER_B));
|
||||
seed_storage_data.relayers.push_back(unrewarded_relayer(5, 5, TEST_RELAYER_C));
|
||||
lane.storage.set_data(seed_storage_data);
|
||||
// Check
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(3),
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![
|
||||
unrewarded_relayer(4, 4, TEST_RELAYER_B),
|
||||
unrewarded_relayer(5, 5, TEST_RELAYER_C)
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_receive_message_with_incorrect_nonce() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
10,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::InvalidNonce
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_delivered_nonce(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_receive_messages_above_unrewarded_relayer_entries_limit_per_lane() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
let max_nonce = BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
|
||||
for current_nonce in 1..max_nonce + 1 {
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&(TEST_RELAYER_A + current_nonce),
|
||||
current_nonce,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
}
|
||||
// Fails to dispatch new message from different than latest relayer.
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&(TEST_RELAYER_A + max_nonce + 1),
|
||||
max_nonce + 1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::TooManyUnrewardedRelayers,
|
||||
);
|
||||
// Fails to dispatch new messages from latest relayer. Prevents griefing attacks.
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&(TEST_RELAYER_A + max_nonce),
|
||||
max_nonce + 1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::TooManyUnrewardedRelayers,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_receive_messages_above_unconfirmed_messages_limit_per_lane() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
let max_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
|
||||
for current_nonce in 1..=max_nonce {
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
current_nonce,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
}
|
||||
// Fails to dispatch new message from different than latest relayer.
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_B,
|
||||
max_nonce + 1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::TooManyUnconfirmedMessages,
|
||||
);
|
||||
// Fails to dispatch new messages from latest relayer.
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
max_nonce + 1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::TooManyUnconfirmedMessages,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_receives_following_messages_from_two_relayers_alternately() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_B,
|
||||
2,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
3,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![
|
||||
unrewarded_relayer(1, 1, TEST_RELAYER_A),
|
||||
unrewarded_relayer(2, 2, TEST_RELAYER_B),
|
||||
unrewarded_relayer(3, 3, TEST_RELAYER_A)
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_same_message_from_two_different_relayers() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_B,
|
||||
1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::InvalidNonce,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correct_message_is_processed_instantly() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
assert_eq!(lane.storage.data().last_delivered_nonce(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unspent_weight_is_returned_by_receive_message() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
let mut payload = REGULAR_PAYLOAD;
|
||||
*payload.dispatch_result.unspent_weight.ref_time_mut() = 1;
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
1,
|
||||
inbound_message_data(payload)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(1))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_message_is_confirmed_correctly() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
receive_regular_message(&mut lane, 2);
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 1,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(1),
|
||||
);
|
||||
assert_eq!(
|
||||
inbound_unrewarded_relayers_state(test_lane_id()),
|
||||
UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 1,
|
||||
last_delivered_nonce: 2,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
BridgedChainOf, Config, InboundLane, InboundLaneStorage, InboundLanes, OutboundLane,
|
||||
OutboundLaneStorage, OutboundLanes, OutboundMessages, StoredInboundLaneData,
|
||||
StoredMessagePayload,
|
||||
};
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneState, MessageKey,
|
||||
MessageNonce, OutboundLaneData,
|
||||
};
|
||||
use pezbp_runtime::AccountIdOf;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{ensure, pezsp_runtime::RuntimeDebug, PalletError};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Lanes manager errors.
|
||||
#[derive(
|
||||
Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
|
||||
)]
|
||||
pub enum LanesManagerError {
|
||||
/// Inbound lane already exists.
|
||||
InboundLaneAlreadyExists,
|
||||
/// Outbound lane already exists.
|
||||
OutboundLaneAlreadyExists,
|
||||
/// No inbound lane with given id.
|
||||
UnknownInboundLane,
|
||||
/// No outbound lane with given id.
|
||||
UnknownOutboundLane,
|
||||
/// Inbound lane with given id is closed.
|
||||
ClosedInboundLane,
|
||||
/// Outbound lane with given id is closed.
|
||||
ClosedOutboundLane,
|
||||
/// Message dispatcher is inactive at given inbound lane. This is logical equivalent
|
||||
/// of the [`Self::ClosedInboundLane`] variant.
|
||||
LaneDispatcherInactive,
|
||||
}
|
||||
|
||||
/// Message lanes manager.
|
||||
pub struct LanesManager<T, I>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for LanesManager<T, I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> LanesManager<T, I> {
|
||||
/// Create new lanes manager.
|
||||
pub fn new() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
|
||||
/// Create new inbound lane in `Opened` state.
|
||||
pub fn create_inbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
InboundLanes::<T, I>::try_mutate(lane_id, |lane| match lane {
|
||||
Some(_) => Err(LanesManagerError::InboundLaneAlreadyExists),
|
||||
None => {
|
||||
*lane = Some(StoredInboundLaneData(InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
..Default::default()
|
||||
}));
|
||||
Ok(())
|
||||
},
|
||||
})?;
|
||||
|
||||
self.active_inbound_lane(lane_id)
|
||||
}
|
||||
|
||||
/// Create new outbound lane in `Opened` state.
|
||||
pub fn create_outbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
OutboundLanes::<T, I>::try_mutate(lane_id, |lane| match lane {
|
||||
Some(_) => Err(LanesManagerError::OutboundLaneAlreadyExists),
|
||||
None => {
|
||||
*lane = Some(OutboundLaneData { state: LaneState::Opened, ..Default::default() });
|
||||
Ok(())
|
||||
},
|
||||
})?;
|
||||
|
||||
self.active_outbound_lane(lane_id)
|
||||
}
|
||||
|
||||
/// Get existing inbound lane, checking that it is in usable state.
|
||||
pub fn active_inbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, true)?))
|
||||
}
|
||||
|
||||
/// Get existing outbound lane, checking that it is in usable state.
|
||||
pub fn active_outbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, true)?))
|
||||
}
|
||||
|
||||
/// Get existing inbound lane without any additional state checks.
|
||||
pub fn any_state_inbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, false)?))
|
||||
}
|
||||
|
||||
/// Get existing outbound lane without any additional state checks.
|
||||
pub fn any_state_outbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, false)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime inbound lane storage.
|
||||
pub struct RuntimeInboundLaneStorage<T: Config<I>, I: 'static = ()> {
|
||||
pub(crate) lane_id: T::LaneId,
|
||||
pub(crate) cached_data: InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> RuntimeInboundLaneStorage<T, I> {
|
||||
/// Creates new runtime inbound lane storage for given **existing** lane.
|
||||
fn from_lane_id(
|
||||
lane_id: T::LaneId,
|
||||
check_active: bool,
|
||||
) -> Result<RuntimeInboundLaneStorage<T, I>, LanesManagerError> {
|
||||
let cached_data =
|
||||
InboundLanes::<T, I>::get(lane_id).ok_or(LanesManagerError::UnknownInboundLane)?;
|
||||
|
||||
if check_active {
|
||||
// check that the lane is not explicitly closed
|
||||
ensure!(cached_data.state.is_active(), LanesManagerError::ClosedInboundLane);
|
||||
// apart from the explicit closure, the lane may be unable to receive any messages.
|
||||
// Right now we do an additional check here, but it may be done later (e.g. by
|
||||
// explicitly closing the lane and reopening it from
|
||||
// `pezpallet-xcm-bridge-hub::on-initialize`)
|
||||
//
|
||||
// The fact that we only check it here, means that the `MessageDispatch` may switch
|
||||
// to inactive state during some message dispatch in the middle of message delivery
|
||||
// transaction. But we treat result of `MessageDispatch::is_active()` as a hint, so
|
||||
// we know that it won't drop messages - just it experiences problems with processing.
|
||||
// This would allow us to check that in our signed extensions, and invalidate
|
||||
// transaction early, thus avoiding losing honest relayers funds. This problem should
|
||||
// gone with relayers coordination protocol.
|
||||
//
|
||||
// There's a limit on number of messages in the message delivery transaction, so even
|
||||
// if we dispatch (enqueue) some additional messages, we'll know the maximal queue
|
||||
// length;
|
||||
ensure!(
|
||||
T::MessageDispatch::is_active(lane_id),
|
||||
LanesManagerError::LaneDispatcherInactive
|
||||
);
|
||||
}
|
||||
|
||||
Ok(RuntimeInboundLaneStorage { lane_id, cached_data: cached_data.into() })
|
||||
}
|
||||
|
||||
/// Returns number of bytes that may be subtracted from the PoV component of
|
||||
/// `receive_messages_proof` call, because the actual inbound lane state is smaller than the
|
||||
/// maximal configured.
|
||||
///
|
||||
/// Maximal inbound lane state set size is configured by the
|
||||
/// `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` constant from the pezpallet configuration. The PoV
|
||||
/// of the call includes the maximal size of inbound lane state. If the actual size is smaller,
|
||||
/// we may subtract extra bytes from this component.
|
||||
pub fn extra_proof_size_bytes(&self) -> u64 {
|
||||
let max_encoded_len = StoredInboundLaneData::<T, I>::max_encoded_len();
|
||||
let relayers_count = self.data().relayers.len();
|
||||
let actual_encoded_len =
|
||||
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::encoded_size_hint(relayers_count)
|
||||
.unwrap_or(usize::MAX);
|
||||
max_encoded_len.saturating_sub(actual_encoded_len) as _
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage<T, I> {
|
||||
type Relayer = AccountIdOf<BridgedChainOf<T, I>>;
|
||||
type LaneId = T::LaneId;
|
||||
|
||||
fn id(&self) -> Self::LaneId {
|
||||
self.lane_id
|
||||
}
|
||||
|
||||
fn max_unrewarded_relayer_entries(&self) -> MessageNonce {
|
||||
BridgedChainOf::<T, I>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX
|
||||
}
|
||||
|
||||
fn max_unconfirmed_messages(&self) -> MessageNonce {
|
||||
BridgedChainOf::<T, I>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
|
||||
}
|
||||
|
||||
fn data(&self) -> InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>> {
|
||||
self.cached_data.clone()
|
||||
}
|
||||
|
||||
fn set_data(&mut self, data: InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>) {
|
||||
self.cached_data = data.clone();
|
||||
InboundLanes::<T, I>::insert(self.lane_id, StoredInboundLaneData::<T, I>(data))
|
||||
}
|
||||
|
||||
fn purge(self) {
|
||||
InboundLanes::<T, I>::remove(self.lane_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime outbound lane storage.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RuntimeOutboundLaneStorage<T: Config<I>, I: 'static> {
|
||||
pub(crate) lane_id: T::LaneId,
|
||||
pub(crate) cached_data: OutboundLaneData,
|
||||
pub(crate) _phantom: PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> RuntimeOutboundLaneStorage<T, I> {
|
||||
/// Creates new runtime outbound lane storage for given **existing** lane.
|
||||
fn from_lane_id(lane_id: T::LaneId, check_active: bool) -> Result<Self, LanesManagerError> {
|
||||
let cached_data =
|
||||
OutboundLanes::<T, I>::get(lane_id).ok_or(LanesManagerError::UnknownOutboundLane)?;
|
||||
ensure!(
|
||||
!check_active || cached_data.state.is_active(),
|
||||
LanesManagerError::ClosedOutboundLane
|
||||
);
|
||||
Ok(Self { lane_id, cached_data, _phantom: PhantomData })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage<T, I> {
|
||||
type StoredMessagePayload = StoredMessagePayload<T, I>;
|
||||
type LaneId = T::LaneId;
|
||||
|
||||
fn id(&self) -> Self::LaneId {
|
||||
self.lane_id
|
||||
}
|
||||
|
||||
fn data(&self) -> OutboundLaneData {
|
||||
self.cached_data.clone()
|
||||
}
|
||||
|
||||
fn set_data(&mut self, data: OutboundLaneData) {
|
||||
self.cached_data = data.clone();
|
||||
OutboundLanes::<T, I>::insert(self.lane_id, data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn message(&self, nonce: &MessageNonce) -> Option<Self::StoredMessagePayload> {
|
||||
OutboundMessages::<T, I>::get(MessageKey { lane_id: self.lane_id, nonce: *nonce })
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn save_message(&mut self, nonce: MessageNonce, message_payload: Self::StoredMessagePayload) {
|
||||
OutboundMessages::<T, I>::insert(
|
||||
MessageKey { lane_id: self.lane_id, nonce },
|
||||
message_payload,
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_message(&mut self, nonce: &MessageNonce) {
|
||||
OutboundMessages::<T, I>::remove(MessageKey { lane_id: self.lane_id, nonce: *nonce });
|
||||
}
|
||||
|
||||
fn purge(self) {
|
||||
OutboundLanes::<T, I>::remove(self.lane_id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,791 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Runtime module that allows sending and receiving messages using lane concept:
|
||||
//!
|
||||
//! 1) the message is sent using `send_message()` call;
|
||||
//! 2) every outbound message is assigned nonce;
|
||||
//! 3) the messages are stored in the storage;
|
||||
//! 4) external component (relay) delivers messages to bridged chain;
|
||||
//! 5) messages are processed in order (ordered by assigned nonce);
|
||||
//! 6) relay may send proof-of-delivery back to this chain.
|
||||
//!
|
||||
//! Once message is sent, its progress can be tracked by looking at module events.
|
||||
//! The assigned nonce is reported using `MessageAccepted` event. When message is
|
||||
//! delivered to the the bridged chain, it is reported using `MessagesDelivered` event.
|
||||
//!
|
||||
//! **IMPORTANT NOTE**: after generating weights (custom `WeighInfo` implementation) for
|
||||
//! your runtime (where this module is plugged to), please add test for these weights.
|
||||
//! The test should call the `ensure_weights_are_correct` function from this module.
|
||||
//! If this test fails with your weights, then either weights are computed incorrectly,
|
||||
//! or some benchmarks assumptions are broken for your runtime.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use inbound_lane::{InboundLane, InboundLaneStorage, StoredInboundLaneData};
|
||||
pub use lanes_manager::{
|
||||
LanesManager, LanesManagerError, RuntimeInboundLaneStorage, RuntimeOutboundLaneStorage,
|
||||
};
|
||||
pub use outbound_lane::{
|
||||
OutboundLane, OutboundLaneStorage, ReceptionConfirmationError, StoredMessagePayload,
|
||||
};
|
||||
pub use weights::WeightInfo;
|
||||
pub use weights_ext::{
|
||||
ensure_able_to_receive_confirmation, ensure_able_to_receive_message,
|
||||
ensure_maximal_message_dispatch, ensure_weights_are_correct, WeightInfoExt,
|
||||
EXPECTED_DEFAULT_MESSAGE_LENGTH, EXTRA_STORAGE_PROOF_SIZE,
|
||||
};
|
||||
|
||||
use bp_header_pez_chain::HeaderChain;
|
||||
use bp_messages::{
|
||||
source_chain::{
|
||||
DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered,
|
||||
SendMessageArtifacts,
|
||||
},
|
||||
target_chain::{
|
||||
DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch,
|
||||
ProvedLaneMessages, ProvedMessages,
|
||||
},
|
||||
ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, MessageKey,
|
||||
MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails,
|
||||
UnrewardedRelayersState, VerificationError,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt,
|
||||
Size,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound};
|
||||
use pezsp_std::{marker::PhantomData, prelude::*};
|
||||
|
||||
mod call_ext;
|
||||
mod inbound_lane;
|
||||
mod lanes_manager;
|
||||
mod outbound_lane;
|
||||
mod proofs;
|
||||
mod tests;
|
||||
mod weights_ext;
|
||||
|
||||
pub mod weights;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarking;
|
||||
pub mod migration;
|
||||
|
||||
pub use call_ext::*;
|
||||
pub use pezpallet::*;
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub use tests::*;
|
||||
|
||||
/// The target that will be used when publishing logs related to this pezpallet.
|
||||
pub const LOG_TARGET: &str = "runtime::bridge-messages";
|
||||
|
||||
#[pezframe_support::pezpallet]
|
||||
pub mod pezpallet {
|
||||
use super::*;
|
||||
use bp_messages::{LaneIdType, ReceivedMessages, ReceptionResult};
|
||||
use pezbp_runtime::RangeInclusiveExt;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pezpallet::config]
|
||||
pub trait Config<I: 'static = ()>: pezframe_system::Config {
|
||||
// General types
|
||||
|
||||
/// The overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
/// Benchmarks results from runtime we're plugged into.
|
||||
type WeightInfo: WeightInfoExt;
|
||||
|
||||
/// This chain type.
|
||||
type ThisChain: ChainWithMessages;
|
||||
/// Bridged chain type.
|
||||
type BridgedChain: ChainWithMessages;
|
||||
/// Bridged chain headers provider.
|
||||
type BridgedHeaderChain: HeaderChain<Self::BridgedChain>;
|
||||
|
||||
/// Payload type of outbound messages. This payload is dispatched on the bridged chain.
|
||||
type OutboundPayload: Parameter + Size;
|
||||
/// Payload type of inbound messages. This payload is dispatched on this chain.
|
||||
type InboundPayload: Decode;
|
||||
/// Lane identifier type.
|
||||
type LaneId: LaneIdType;
|
||||
|
||||
/// Handler for relayer payments that happen during message delivery transaction.
|
||||
type DeliveryPayments: DeliveryPayments<Self::AccountId>;
|
||||
/// Handler for relayer payments that happen during message delivery confirmation
|
||||
/// transaction.
|
||||
type DeliveryConfirmationPayments: DeliveryConfirmationPayments<
|
||||
Self::AccountId,
|
||||
Self::LaneId,
|
||||
>;
|
||||
/// Delivery confirmation callback.
|
||||
type OnMessagesDelivered: OnMessagesDelivered<Self::LaneId>;
|
||||
|
||||
/// Message dispatch handler.
|
||||
type MessageDispatch: MessageDispatch<
|
||||
DispatchPayload = Self::InboundPayload,
|
||||
LaneId = Self::LaneId,
|
||||
>;
|
||||
}
|
||||
|
||||
/// Shortcut to this chain type for Config.
|
||||
pub type ThisChainOf<T, I> = <T as Config<I>>::ThisChain;
|
||||
/// Shortcut to bridged chain type for Config.
|
||||
pub type BridgedChainOf<T, I> = <T as Config<I>>::BridgedChain;
|
||||
/// Shortcut to bridged header chain type for Config.
|
||||
pub type BridgedHeaderChainOf<T, I> = <T as Config<I>>::BridgedHeaderChain;
|
||||
/// Shortcut to lane identifier type for Config.
|
||||
pub type LaneIdOf<T, I> = <T as Config<I>>::LaneId;
|
||||
|
||||
#[pezpallet::pezpallet]
|
||||
#[pezpallet::storage_version(migration::STORAGE_VERSION)]
|
||||
pub struct Pezpallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pezpallet<T, I> {
|
||||
const LOG_TARGET: &'static str = LOG_TARGET;
|
||||
type OwnerStorage = PalletOwner<T, I>;
|
||||
type OperatingMode = MessagesOperatingMode;
|
||||
type OperatingModeStorage = PalletOperatingMode<T, I>;
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Change `PalletOwner`.
|
||||
///
|
||||
/// May only be called either by root, or by `PalletOwner`.
|
||||
#[pezpallet::call_index(0)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
|
||||
<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
|
||||
}
|
||||
|
||||
/// Halt or resume all/some pezpallet operations.
|
||||
///
|
||||
/// May only be called either by root, or by `PalletOwner`.
|
||||
#[pezpallet::call_index(1)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_operating_mode(
|
||||
origin: OriginFor<T>,
|
||||
operating_mode: MessagesOperatingMode,
|
||||
) -> DispatchResult {
|
||||
<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
|
||||
}
|
||||
|
||||
/// Receive messages proof from bridged chain.
|
||||
///
|
||||
/// The weight of the call assumes that the transaction always brings outbound lane
|
||||
/// state update. Because of that, the submitter (relayer) has no benefit of not including
|
||||
/// this data in the transaction, so reward confirmations lags should be minimal.
|
||||
///
|
||||
/// The call fails if:
|
||||
///
|
||||
/// - the pezpallet is halted;
|
||||
///
|
||||
/// - the call origin is not `Signed(_)`;
|
||||
///
|
||||
/// - there are too many messages in the proof;
|
||||
///
|
||||
/// - the proof verification procedure returns an error - e.g. because header used to craft
|
||||
/// proof is not imported by the associated finality pezpallet;
|
||||
///
|
||||
/// - the `dispatch_weight` argument is not sufficient to dispatch all bundled messages.
|
||||
///
|
||||
/// The call may succeed, but some messages may not be delivered e.g. if they are not fit
|
||||
/// into the unrewarded relayers vector.
|
||||
#[pezpallet::call_index(2)]
|
||||
#[pezpallet::weight(T::WeightInfo::receive_messages_proof_weight(&**proof, *messages_count, *dispatch_weight))]
|
||||
pub fn receive_messages_proof(
|
||||
origin: OriginFor<T>,
|
||||
relayer_id_at_bridged_chain: AccountIdOf<BridgedChainOf<T, I>>,
|
||||
proof: Box<FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>>,
|
||||
messages_count: u32,
|
||||
dispatch_weight: Weight,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
|
||||
let relayer_id_at_this_chain = ensure_signed(origin)?;
|
||||
|
||||
// reject transactions that are declaring too many messages
|
||||
ensure!(
|
||||
MessageNonce::from(messages_count) <=
|
||||
BridgedChainOf::<T, I>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
Error::<T, I>::TooManyMessagesInTheProof
|
||||
);
|
||||
|
||||
// why do we need to know the weight of this (`receive_messages_proof`) call? Because
|
||||
// we may want to return some funds for not-dispatching (or partially dispatching) some
|
||||
// messages to the call origin (relayer). And this is done by returning actual weight
|
||||
// from the call. But we only know dispatch weight of every message. So to refund
|
||||
// relayer because we have not dispatched message, we need to:
|
||||
//
|
||||
// ActualWeight = DeclaredWeight - Message.DispatchWeight
|
||||
//
|
||||
// The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible
|
||||
// to get pre-computed value (and it has been already computed by the executive).
|
||||
let declared_weight = T::WeightInfo::receive_messages_proof_weight(
|
||||
&*proof,
|
||||
messages_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
let mut actual_weight = declared_weight;
|
||||
|
||||
// verify messages proof && convert proof into messages
|
||||
let (lane_id, lane_data) =
|
||||
verify_and_decode_messages_proof::<T, I>(*proof, messages_count).map_err(
|
||||
|err| {
|
||||
tracing::trace!(target: LOG_TARGET, error=?err, "Rejecting invalid messages proof");
|
||||
|
||||
Error::<T, I>::InvalidMessagesProof
|
||||
},
|
||||
)?;
|
||||
|
||||
// dispatch messages and (optionally) update lane(s) state(s)
|
||||
let mut total_messages = 0;
|
||||
let mut valid_messages = 0;
|
||||
let mut dispatch_weight_left = dispatch_weight;
|
||||
let mut lane = active_inbound_lane::<T, I>(lane_id)?;
|
||||
|
||||
// subtract extra storage proof bytes from the actual PoV size - there may be
|
||||
// less unrewarded relayers than the maximal configured value
|
||||
let lane_extra_proof_size_bytes = lane.storage().extra_proof_size_bytes();
|
||||
actual_weight = actual_weight.set_proof_size(
|
||||
actual_weight.proof_size().saturating_sub(lane_extra_proof_size_bytes),
|
||||
);
|
||||
|
||||
if let Some(lane_state) = lane_data.lane_state {
|
||||
let updated_latest_confirmed_nonce = lane.receive_state_update(lane_state);
|
||||
if let Some(updated_latest_confirmed_nonce) = updated_latest_confirmed_nonce {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
latest_confirmed_nonce=%updated_latest_confirmed_nonce,
|
||||
unrewarded_relayers=?UnrewardedRelayersState::from(&lane.storage().data()),
|
||||
"Received state update"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut messages_received_status =
|
||||
ReceivedMessages::new(lane_id, Vec::with_capacity(lane_data.messages.len()));
|
||||
for mut message in lane_data.messages {
|
||||
debug_assert_eq!(message.key.lane_id, lane_id);
|
||||
total_messages += 1;
|
||||
|
||||
// ensure that relayer has declared enough weight for dispatching next message
|
||||
// on this lane. We can't dispatch lane messages out-of-order, so if declared
|
||||
// weight is not enough, let's move to next lane
|
||||
let message_dispatch_weight = T::MessageDispatch::dispatch_weight(&mut message);
|
||||
if message_dispatch_weight.any_gt(dispatch_weight_left) {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
declared=%message_dispatch_weight,
|
||||
left=%dispatch_weight_left,
|
||||
"Cannot dispatch any more messages"
|
||||
);
|
||||
|
||||
fail!(Error::<T, I>::InsufficientDispatchWeight);
|
||||
}
|
||||
|
||||
let receival_result = lane.receive_message::<T::MessageDispatch>(
|
||||
&relayer_id_at_bridged_chain,
|
||||
message.key.nonce,
|
||||
message.data,
|
||||
);
|
||||
|
||||
// note that we're returning unspent weight to relayer even if message has been
|
||||
// rejected by the lane. This allows relayers to submit spam transactions with
|
||||
// e.g. the same set of already delivered messages over and over again, without
|
||||
// losing funds for messages dispatch. But keep in mind that relayer pays base
|
||||
// delivery transaction cost anyway. And base cost covers everything except
|
||||
// dispatch, so we have a balance here.
|
||||
let unspent_weight = match &receival_result {
|
||||
ReceptionResult::Dispatched(dispatch_result) => {
|
||||
valid_messages += 1;
|
||||
dispatch_result.unspent_weight
|
||||
},
|
||||
ReceptionResult::InvalidNonce |
|
||||
ReceptionResult::TooManyUnrewardedRelayers |
|
||||
ReceptionResult::TooManyUnconfirmedMessages => message_dispatch_weight,
|
||||
};
|
||||
messages_received_status.push(message.key.nonce, receival_result);
|
||||
|
||||
let unspent_weight = unspent_weight.min(message_dispatch_weight);
|
||||
dispatch_weight_left -= message_dispatch_weight - unspent_weight;
|
||||
actual_weight = actual_weight.saturating_sub(unspent_weight);
|
||||
}
|
||||
|
||||
// let's now deal with relayer payments
|
||||
T::DeliveryPayments::pay_reward(
|
||||
relayer_id_at_this_chain,
|
||||
total_messages,
|
||||
valid_messages,
|
||||
actual_weight,
|
||||
);
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
total=%total_messages,
|
||||
valid=%valid_messages,
|
||||
%actual_weight,
|
||||
%declared_weight,
|
||||
"Received messages."
|
||||
);
|
||||
|
||||
Self::deposit_event(Event::MessagesReceived(messages_received_status));
|
||||
|
||||
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
|
||||
}
|
||||
|
||||
/// Receive messages delivery proof from bridged chain.
|
||||
#[pezpallet::call_index(3)]
|
||||
#[pezpallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight(
|
||||
proof,
|
||||
relayers_state,
|
||||
))]
|
||||
pub fn receive_messages_delivery_proof(
|
||||
origin: OriginFor<T>,
|
||||
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
|
||||
mut relayers_state: UnrewardedRelayersState,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
|
||||
|
||||
let proof_size = proof.size();
|
||||
let confirmation_relayer = ensure_signed(origin)?;
|
||||
let (lane_id, lane_data) = proofs::verify_messages_delivery_proof::<T, I>(proof)
|
||||
.map_err(|err| {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
error=?err,
|
||||
"Rejecting invalid messages delivery proof"
|
||||
);
|
||||
|
||||
Error::<T, I>::InvalidMessagesDeliveryProof
|
||||
})?;
|
||||
ensure!(
|
||||
relayers_state.is_valid(&lane_data),
|
||||
Error::<T, I>::InvalidUnrewardedRelayersState
|
||||
);
|
||||
|
||||
// mark messages as delivered
|
||||
let mut lane = any_state_outbound_lane::<T, I>(lane_id)?;
|
||||
let last_delivered_nonce = lane_data.last_delivered_nonce();
|
||||
let confirmed_messages = lane
|
||||
.confirm_delivery(
|
||||
relayers_state.total_messages,
|
||||
last_delivered_nonce,
|
||||
&lane_data.relayers,
|
||||
)
|
||||
.map_err(Error::<T, I>::ReceptionConfirmation)?;
|
||||
|
||||
if let Some(confirmed_messages) = confirmed_messages {
|
||||
// emit 'delivered' event
|
||||
let received_range = confirmed_messages.begin..=confirmed_messages.end;
|
||||
Self::deposit_event(Event::MessagesDelivered {
|
||||
lane_id: lane_id.into(),
|
||||
messages: confirmed_messages,
|
||||
});
|
||||
|
||||
// if some new messages have been confirmed, reward relayers
|
||||
let actually_rewarded_relayers = T::DeliveryConfirmationPayments::pay_reward(
|
||||
lane_id,
|
||||
lane_data.relayers,
|
||||
&confirmation_relayer,
|
||||
&received_range,
|
||||
);
|
||||
|
||||
// update relayers state with actual numbers to compute actual weight below
|
||||
relayers_state.unrewarded_relayer_entries = pezsp_std::cmp::min(
|
||||
relayers_state.unrewarded_relayer_entries,
|
||||
actually_rewarded_relayers,
|
||||
);
|
||||
relayers_state.total_messages = pezsp_std::cmp::min(
|
||||
relayers_state.total_messages,
|
||||
received_range.checked_len().unwrap_or(MessageNonce::MAX),
|
||||
);
|
||||
};
|
||||
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
%last_delivered_nonce,
|
||||
"Received messages delivery proof up to (and including)"
|
||||
);
|
||||
|
||||
// notify others about messages delivery
|
||||
T::OnMessagesDelivered::on_messages_delivered(
|
||||
lane_id,
|
||||
lane.data().queued_messages().saturating_len(),
|
||||
);
|
||||
|
||||
// because of lags, the inbound lane state (`lane_data`) may have entries for
|
||||
// already rewarded relayers and messages (if all entries are duplicated, then
|
||||
// this transaction must be filtered out by our signed extension)
|
||||
let actual_weight = T::WeightInfo::receive_messages_delivery_proof_weight(
|
||||
&PreComputedSize(proof_size as usize),
|
||||
&relayers_state,
|
||||
);
|
||||
|
||||
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::event]
|
||||
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// Message has been accepted and is waiting to be delivered.
|
||||
MessageAccepted {
|
||||
/// Lane, which has accepted the message.
|
||||
lane_id: T::LaneId,
|
||||
/// Nonce of accepted message.
|
||||
nonce: MessageNonce,
|
||||
},
|
||||
/// Messages have been received from the bridged chain.
|
||||
MessagesReceived(
|
||||
/// Result of received messages dispatch.
|
||||
ReceivedMessages<
|
||||
<T::MessageDispatch as MessageDispatch>::DispatchLevelResult,
|
||||
T::LaneId,
|
||||
>,
|
||||
),
|
||||
/// Messages in the inclusive range have been delivered to the bridged chain.
|
||||
MessagesDelivered {
|
||||
/// Lane for which the delivery has been confirmed.
|
||||
lane_id: T::LaneId,
|
||||
/// Delivered messages.
|
||||
messages: DeliveredMessages,
|
||||
},
|
||||
}
|
||||
|
||||
#[pezpallet::error]
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// Pezpallet is not in Normal operating mode.
|
||||
NotOperatingNormally,
|
||||
/// Error that is reported by the lanes manager.
|
||||
LanesManager(LanesManagerError),
|
||||
/// Message has been treated as invalid by the pezpallet logic.
|
||||
MessageRejectedByPallet(VerificationError),
|
||||
/// The transaction brings too many messages.
|
||||
TooManyMessagesInTheProof,
|
||||
/// Invalid messages has been submitted.
|
||||
InvalidMessagesProof,
|
||||
/// Invalid messages delivery proof has been submitted.
|
||||
InvalidMessagesDeliveryProof,
|
||||
/// The relayer has declared invalid unrewarded relayers state in the
|
||||
/// `receive_messages_delivery_proof` call.
|
||||
InvalidUnrewardedRelayersState,
|
||||
/// The cumulative dispatch weight, passed by relayer is not enough to cover dispatch
|
||||
/// of all bundled messages.
|
||||
InsufficientDispatchWeight,
|
||||
/// Error confirming messages receival.
|
||||
ReceptionConfirmation(ReceptionConfirmationError),
|
||||
/// Error generated by the `OwnedBridgeModule` trait.
|
||||
BridgeModule(pezbp_runtime::OwnedBridgeModuleError),
|
||||
}
|
||||
|
||||
/// Optional pezpallet owner.
|
||||
///
|
||||
/// Pezpallet owner has a right to halt all pezpallet operations and then resume it. If it is
|
||||
/// `None`, then there are no direct ways to halt/resume pezpallet operations, but other
|
||||
/// runtime methods may still be used to do that (i.e. democracy::referendum to update halt
|
||||
/// flag directly or call the `set_operating_mode`).
|
||||
#[pezpallet::storage]
|
||||
pub type PalletOwner<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
|
||||
|
||||
/// The current operating mode of the pezpallet.
|
||||
///
|
||||
/// Depending on the mode either all, some, or no transactions will be allowed.
|
||||
#[pezpallet::storage]
|
||||
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, MessagesOperatingMode, ValueQuery>;
|
||||
|
||||
// TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/89: let's limit number of
|
||||
// possible opened lanes && use it to constraint maps below
|
||||
|
||||
/// Map of lane id => inbound lane data.
|
||||
#[pezpallet::storage]
|
||||
pub type InboundLanes<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Blake2_128Concat, T::LaneId, StoredInboundLaneData<T, I>, OptionQuery>;
|
||||
|
||||
/// Map of lane id => outbound lane data.
|
||||
#[pezpallet::storage]
|
||||
pub type OutboundLanes<T: Config<I>, I: 'static = ()> = StorageMap<
|
||||
Hasher = Blake2_128Concat,
|
||||
Key = T::LaneId,
|
||||
Value = OutboundLaneData,
|
||||
QueryKind = OptionQuery,
|
||||
>;
|
||||
|
||||
/// All queued outbound messages.
|
||||
#[pezpallet::storage]
|
||||
pub type OutboundMessages<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Blake2_128Concat, MessageKey<T::LaneId>, StoredMessagePayload<T, I>>;
|
||||
|
||||
#[pezpallet::genesis_config]
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
||||
/// Initial pezpallet operating mode.
|
||||
pub operating_mode: MessagesOperatingMode,
|
||||
/// Initial pezpallet owner.
|
||||
pub owner: Option<T::AccountId>,
|
||||
/// Opened lanes.
|
||||
pub opened_lanes: Vec<T::LaneId>,
|
||||
/// Dummy marker.
|
||||
#[serde(skip)]
|
||||
pub _phantom: pezsp_std::marker::PhantomData<I>,
|
||||
}
|
||||
|
||||
#[pezpallet::genesis_build]
|
||||
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
|
||||
fn build(&self) {
|
||||
PalletOperatingMode::<T, I>::put(self.operating_mode);
|
||||
if let Some(ref owner) = self.owner {
|
||||
PalletOwner::<T, I>::put(owner);
|
||||
}
|
||||
|
||||
for lane_id in &self.opened_lanes {
|
||||
InboundLanes::<T, I>::insert(lane_id, InboundLaneData::opened());
|
||||
OutboundLanes::<T, I>::insert(lane_id, OutboundLaneData::opened());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pezpallet<T, I> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_n: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::do_try_state()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Get stored data of the outbound message with given nonce.
|
||||
pub fn outbound_message_data(
|
||||
lane: T::LaneId,
|
||||
nonce: MessageNonce,
|
||||
) -> Option<MessagePayload> {
|
||||
OutboundMessages::<T, I>::get(MessageKey { lane_id: lane, nonce }).map(Into::into)
|
||||
}
|
||||
|
||||
/// Prepare data, related to given inbound message.
|
||||
pub fn inbound_message_data(
|
||||
lane: T::LaneId,
|
||||
payload: MessagePayload,
|
||||
outbound_details: OutboundMessageDetails,
|
||||
) -> InboundMessageDetails {
|
||||
let mut dispatch_message = DispatchMessage {
|
||||
key: MessageKey { lane_id: lane, nonce: outbound_details.nonce },
|
||||
data: payload.into(),
|
||||
};
|
||||
InboundMessageDetails {
|
||||
dispatch_weight: T::MessageDispatch::dispatch_weight(&mut dispatch_message),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return outbound lane data.
|
||||
pub fn outbound_lane_data(lane: T::LaneId) -> Option<OutboundLaneData> {
|
||||
OutboundLanes::<T, I>::get(lane)
|
||||
}
|
||||
|
||||
/// Return inbound lane data.
|
||||
pub fn inbound_lane_data(
|
||||
lane: T::LaneId,
|
||||
) -> Option<InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>> {
|
||||
InboundLanes::<T, I>::get(lane).map(|lane| lane.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "try-runtime", test))]
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Ensure the correctness of the state of this pezpallet.
|
||||
pub fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::do_try_state_for_outbound_lanes()
|
||||
}
|
||||
|
||||
/// Ensure the correctness of the state of outbound lanes.
|
||||
pub fn do_try_state_for_outbound_lanes() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
use pezsp_runtime::traits::One;
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
// collect unpruned lanes
|
||||
let mut unpruned_lanes = Vec::new();
|
||||
for (lane_id, lane_data) in OutboundLanes::<T, I>::iter() {
|
||||
let Some(expected_last_prunned_nonce) =
|
||||
lane_data.oldest_unpruned_nonce.checked_sub(One::one())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// collect message_nonces that were supposed to be pruned
|
||||
let mut unpruned_message_nonces = Vec::new();
|
||||
const MAX_MESSAGES_ITERATION: u64 = 16;
|
||||
let start_nonce =
|
||||
expected_last_prunned_nonce.checked_sub(MAX_MESSAGES_ITERATION).unwrap_or(0);
|
||||
for current_nonce in start_nonce..=expected_last_prunned_nonce {
|
||||
// check a message for current_nonce
|
||||
if OutboundMessages::<T, I>::contains_key(MessageKey {
|
||||
lane_id,
|
||||
nonce: current_nonce,
|
||||
}) {
|
||||
unpruned_message_nonces.push(current_nonce);
|
||||
}
|
||||
}
|
||||
|
||||
if !unpruned_message_nonces.is_empty() {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
?lane_data,
|
||||
?unpruned_message_nonces,
|
||||
"do_try_state_for_outbound_lanes found",
|
||||
);
|
||||
unpruned_lanes.push((lane_id, lane_data, unpruned_message_nonces));
|
||||
}
|
||||
}
|
||||
|
||||
// ensure messages before `oldest_unpruned_nonce` are really pruned.
|
||||
ensure!(unpruned_lanes.is_empty(), "Found unpruned lanes!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure, containing a validated message payload and all the info required
|
||||
/// to send it on the bridge.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SendMessageArgs<T: Config<I>, I: 'static> {
|
||||
lane_id: T::LaneId,
|
||||
lane: OutboundLane<RuntimeOutboundLaneStorage<T, I>>,
|
||||
payload: StoredMessagePayload<T, I>,
|
||||
}
|
||||
|
||||
impl<T, I> bp_messages::source_chain::MessagesBridge<T::OutboundPayload, T::LaneId> for Pezpallet<T, I>
|
||||
where
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
{
|
||||
type Error = Error<T, I>;
|
||||
type SendMessageArgs = SendMessageArgs<T, I>;
|
||||
|
||||
fn validate_message(
|
||||
lane_id: T::LaneId,
|
||||
message: &T::OutboundPayload,
|
||||
) -> Result<SendMessageArgs<T, I>, Self::Error> {
|
||||
// we can't accept any messages if the pezpallet is halted
|
||||
ensure_normal_operating_mode::<T, I>()?;
|
||||
|
||||
// check lane
|
||||
let lane = active_outbound_lane::<T, I>(lane_id)?;
|
||||
|
||||
Ok(SendMessageArgs {
|
||||
lane_id,
|
||||
lane,
|
||||
payload: StoredMessagePayload::<T, I>::try_from(message.encode()).map_err(|_| {
|
||||
Error::<T, I>::MessageRejectedByPallet(VerificationError::MessageTooLarge)
|
||||
})?,
|
||||
})
|
||||
}
|
||||
|
||||
fn send_message(args: SendMessageArgs<T, I>) -> SendMessageArtifacts {
|
||||
// save message in outbound storage and emit event
|
||||
let mut lane = args.lane;
|
||||
let message_len = args.payload.len();
|
||||
let nonce = lane.send_message(args.payload);
|
||||
|
||||
// return number of messages in the queue to let sender know about its state
|
||||
let enqueued_messages = lane.data().queued_messages().saturating_len();
|
||||
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
lane_id=?args.lane_id,
|
||||
%nonce,
|
||||
message_size=?message_len,
|
||||
"Accepted message"
|
||||
);
|
||||
|
||||
Pezpallet::<T, I>::deposit_event(Event::MessageAccepted {
|
||||
lane_id: args.lane_id.into(),
|
||||
nonce,
|
||||
});
|
||||
|
||||
SendMessageArtifacts { nonce, enqueued_messages }
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that the pezpallet is in normal operational mode.
|
||||
fn ensure_normal_operating_mode<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
|
||||
if PalletOperatingMode::<T, I>::get() ==
|
||||
MessagesOperatingMode::Basic(BasicOperatingMode::Normal)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Error::<T, I>::NotOperatingNormally)
|
||||
}
|
||||
|
||||
/// Creates new inbound lane object, backed by runtime storage. Lane must be active.
|
||||
fn active_inbound_lane<T: Config<I>, I: 'static>(
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, Error<T, I>> {
|
||||
LanesManager::<T, I>::new()
|
||||
.active_inbound_lane(lane_id)
|
||||
.map_err(Error::LanesManager)
|
||||
}
|
||||
|
||||
/// Creates new outbound lane object, backed by runtime storage. Lane must be active.
|
||||
fn active_outbound_lane<T: Config<I>, I: 'static>(
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, Error<T, I>> {
|
||||
LanesManager::<T, I>::new()
|
||||
.active_outbound_lane(lane_id)
|
||||
.map_err(Error::LanesManager)
|
||||
}
|
||||
|
||||
/// Creates new outbound lane object, backed by runtime storage.
|
||||
fn any_state_outbound_lane<T: Config<I>, I: 'static>(
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, Error<T, I>> {
|
||||
LanesManager::<T, I>::new()
|
||||
.any_state_outbound_lane(lane_id)
|
||||
.map_err(Error::LanesManager)
|
||||
}
|
||||
|
||||
/// Verify messages proof and return proved messages with decoded payload.
|
||||
fn verify_and_decode_messages_proof<T: Config<I>, I: 'static>(
|
||||
proof: FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
|
||||
messages_count: u32,
|
||||
) -> Result<
|
||||
ProvedMessages<T::LaneId, DispatchMessage<T::InboundPayload, T::LaneId>>,
|
||||
VerificationError,
|
||||
> {
|
||||
// `receive_messages_proof` weight formula and `MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX`
|
||||
// check guarantees that the `message_count` is sane and Vec<Message> may be allocated.
|
||||
// (tx with too many messages will either be rejected from the pool, or will fail earlier)
|
||||
proofs::verify_messages_proof::<T, I>(proof, messages_count).map(|(lane, lane_data)| {
|
||||
(
|
||||
lane,
|
||||
ProvedLaneMessages {
|
||||
lane_state: lane_data.lane_state,
|
||||
messages: lane_data.messages.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A module that is responsible for migration of storage.
|
||||
|
||||
use crate::{Config, Pezpallet};
|
||||
use pezframe_support::{
|
||||
traits::{Get, StorageVersion},
|
||||
weights::Weight,
|
||||
};
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
/// This module contains data structures that are valid for the initial state of `0`.
|
||||
/// (used with v1 migration).
|
||||
pub mod v0 {
|
||||
use super::Config;
|
||||
use crate::BridgedChainOf;
|
||||
use bp_messages::{MessageNonce, UnrewardedRelayer};
|
||||
use pezbp_runtime::AccountIdOf;
|
||||
use codec::{Decode, Encode};
|
||||
use pezsp_std::collections::vec_deque::VecDeque;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct StoredInboundLaneData<T: Config<I>, I: 'static>(
|
||||
pub(crate) InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
|
||||
);
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct InboundLaneData<RelayerId> {
|
||||
pub(crate) relayers: VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
pub(crate) last_confirmed_nonce: MessageNonce,
|
||||
}
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct OutboundLaneData {
|
||||
pub(crate) oldest_unpruned_nonce: MessageNonce,
|
||||
pub(crate) latest_received_nonce: MessageNonce,
|
||||
pub(crate) latest_generated_nonce: MessageNonce,
|
||||
}
|
||||
}
|
||||
|
||||
/// This migration to `1` updates the metadata of `InboundLanes` and `OutboundLanes` to the new
|
||||
/// structures.
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
use crate::{
|
||||
InboundLaneData, InboundLanes, OutboundLaneData, OutboundLanes, StoredInboundLaneData,
|
||||
};
|
||||
use bp_messages::LaneState;
|
||||
use pezframe_support::traits::UncheckedOnRuntimeUpgrade;
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Migrates the pezpallet storage to v1.
|
||||
pub struct UncheckedMigrationV0ToV1<T, I>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> UncheckedOnRuntimeUpgrade for UncheckedMigrationV0ToV1<T, I> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
|
||||
// `InboundLanes` - add state to the old structs
|
||||
let translate_inbound =
|
||||
|pre: v0::StoredInboundLaneData<T, I>| -> Option<v1::StoredInboundLaneData<T, I>> {
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
Some(v1::StoredInboundLaneData(v1::InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: pre.0.relayers,
|
||||
last_confirmed_nonce: pre.0.last_confirmed_nonce,
|
||||
}))
|
||||
};
|
||||
InboundLanes::<T, I>::translate_values(translate_inbound);
|
||||
|
||||
// `OutboundLanes` - add state to the old structs
|
||||
let translate_outbound = |pre: v0::OutboundLaneData| -> Option<v1::OutboundLaneData> {
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
Some(v1::OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: pre.oldest_unpruned_nonce,
|
||||
latest_received_nonce: pre.latest_received_nonce,
|
||||
latest_generated_nonce: pre.latest_generated_nonce,
|
||||
})
|
||||
};
|
||||
OutboundLanes::<T, I>::translate_values(translate_outbound);
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<pezsp_std::vec::Vec<u8>, pezsp_runtime::DispatchError> {
|
||||
use codec::Encode;
|
||||
|
||||
let number_of_inbound_to_migrate = InboundLanes::<T, I>::iter_keys().count();
|
||||
let number_of_outbound_to_migrate = OutboundLanes::<T, I>::iter_keys().count();
|
||||
Ok((number_of_inbound_to_migrate as u32, number_of_outbound_to_migrate as u32).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: pezsp_std::vec::Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
use codec::Decode;
|
||||
const LOG_TARGET: &str = "runtime::bridge-messages-migration";
|
||||
|
||||
let (number_of_inbound_to_migrate, number_of_outbound_to_migrate): (u32, u32) =
|
||||
Decode::decode(&mut &state[..]).unwrap();
|
||||
let number_of_inbound = InboundLanes::<T, I>::iter_keys().count();
|
||||
let number_of_outbound = OutboundLanes::<T, I>::iter_keys().count();
|
||||
|
||||
tracing::info!(target: LOG_TARGET, %number_of_inbound_to_migrate, "post-upgrade expects inbound lanes to have been migrated.");
|
||||
tracing::info!(target: LOG_TARGET, %number_of_outbound_to_migrate, "post-upgrade expects outbound lanes to have been migrated.");
|
||||
|
||||
pezframe_support::ensure!(
|
||||
number_of_inbound_to_migrate as usize == number_of_inbound,
|
||||
"must migrate all `InboundLanes`."
|
||||
);
|
||||
pezframe_support::ensure!(
|
||||
number_of_outbound_to_migrate as usize == number_of_outbound,
|
||||
"must migrate all `OutboundLanes`."
|
||||
);
|
||||
|
||||
tracing::info!(target: LOG_TARGET, "migrated all.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`UncheckedMigrationV0ToV1`] wrapped in a
|
||||
/// [`VersionedMigration`](pezframe_support::migrations::VersionedMigration), ensuring the
|
||||
/// migration is only performed when on-chain version is 0.
|
||||
pub type MigrationToV1<T, I> = pezframe_support::migrations::VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
UncheckedMigrationV0ToV1<T, I>,
|
||||
Pezpallet<T, I>,
|
||||
<T as pezframe_system::Config>::DbWeight,
|
||||
>;
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Everything about outgoing messages sending.
|
||||
|
||||
use crate::{Config, LOG_TARGET};
|
||||
|
||||
use bp_messages::{
|
||||
ChainWithMessages, DeliveredMessages, LaneState, MessageNonce, OutboundLaneData,
|
||||
UnrewardedRelayer,
|
||||
};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::{traits::Get, BoundedVec, PalletError};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
use pezsp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive};
|
||||
|
||||
/// Outbound lane storage.
|
||||
pub trait OutboundLaneStorage {
|
||||
/// Stored message payload type.
|
||||
type StoredMessagePayload;
|
||||
/// Lane identifier type.
|
||||
type LaneId: Encode;
|
||||
|
||||
/// Lane id.
|
||||
fn id(&self) -> Self::LaneId;
|
||||
/// Get lane data from the storage.
|
||||
fn data(&self) -> OutboundLaneData;
|
||||
/// Update lane data in the storage.
|
||||
fn set_data(&mut self, data: OutboundLaneData);
|
||||
/// Returns saved outbound message payload.
|
||||
#[cfg(test)]
|
||||
fn message(&self, nonce: &MessageNonce) -> Option<Self::StoredMessagePayload>;
|
||||
/// Save outbound message in the storage.
|
||||
fn save_message(&mut self, nonce: MessageNonce, message_payload: Self::StoredMessagePayload);
|
||||
/// Remove outbound message from the storage.
|
||||
fn remove_message(&mut self, nonce: &MessageNonce);
|
||||
/// Purge lane data from the storage.
|
||||
fn purge(self);
|
||||
}
|
||||
|
||||
/// Limit for the `StoredMessagePayload` vector.
|
||||
pub struct StoredMessagePayloadLimit<T, I>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> Get<u32> for StoredMessagePayloadLimit<T, I> {
|
||||
fn get() -> u32 {
|
||||
T::BridgedChain::maximal_incoming_message_size()
|
||||
}
|
||||
}
|
||||
|
||||
/// Outbound message data wrapper that implements `MaxEncodedLen`.
|
||||
pub type StoredMessagePayload<T, I> = BoundedVec<u8, StoredMessagePayloadLimit<T, I>>;
|
||||
|
||||
/// Result of messages receival confirmation.
|
||||
#[derive(
|
||||
Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
|
||||
)]
|
||||
pub enum ReceptionConfirmationError {
|
||||
/// Bridged chain is trying to confirm more messages than we have generated. May be a result
|
||||
/// of invalid bridged chain storage.
|
||||
FailedToConfirmFutureMessages,
|
||||
/// The unrewarded relayers vec contains an empty entry. May be a result of invalid bridged
|
||||
/// chain storage.
|
||||
EmptyUnrewardedRelayerEntry,
|
||||
/// The unrewarded relayers vec contains non-consecutive entries. May be a result of invalid
|
||||
/// bridged chain storage.
|
||||
NonConsecutiveUnrewardedRelayerEntries,
|
||||
/// The chain has more messages that need to be confirmed than there is in the proof.
|
||||
TryingToConfirmMoreMessagesThanExpected,
|
||||
}
|
||||
|
||||
/// Outbound messages lane.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct OutboundLane<S> {
|
||||
storage: S,
|
||||
}
|
||||
|
||||
impl<S: OutboundLaneStorage> OutboundLane<S> {
|
||||
/// Create new outbound lane backed by given storage.
|
||||
pub fn new(storage: S) -> Self {
|
||||
OutboundLane { storage }
|
||||
}
|
||||
|
||||
/// Get this lane data.
|
||||
pub fn data(&self) -> OutboundLaneData {
|
||||
self.storage.data()
|
||||
}
|
||||
|
||||
/// Get lane state.
|
||||
pub fn state(&self) -> LaneState {
|
||||
self.storage.data().state
|
||||
}
|
||||
|
||||
/// Set lane state.
|
||||
pub fn set_state(&mut self, state: LaneState) {
|
||||
let mut data = self.storage.data();
|
||||
data.state = state;
|
||||
self.storage.set_data(data);
|
||||
}
|
||||
|
||||
/// Return nonces of all currently queued messages.
|
||||
pub fn queued_messages(&self) -> RangeInclusive<MessageNonce> {
|
||||
let data = self.storage.data();
|
||||
data.oldest_unpruned_nonce..=data.latest_generated_nonce
|
||||
}
|
||||
|
||||
/// Send message over lane.
|
||||
///
|
||||
/// Returns new message nonce.
|
||||
pub fn send_message(&mut self, message_payload: S::StoredMessagePayload) -> MessageNonce {
|
||||
let mut data = self.storage.data();
|
||||
let nonce = data.latest_generated_nonce + 1;
|
||||
data.latest_generated_nonce = nonce;
|
||||
|
||||
self.storage.save_message(nonce, message_payload);
|
||||
self.storage.set_data(data);
|
||||
|
||||
nonce
|
||||
}
|
||||
|
||||
/// Confirm messages delivery.
|
||||
pub fn confirm_delivery<RelayerId>(
|
||||
&mut self,
|
||||
max_allowed_messages: MessageNonce,
|
||||
latest_delivered_nonce: MessageNonce,
|
||||
relayers: &VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
) -> Result<Option<DeliveredMessages>, ReceptionConfirmationError> {
|
||||
let mut data = self.storage.data();
|
||||
let confirmed_messages = DeliveredMessages {
|
||||
begin: data.latest_received_nonce.saturating_add(1),
|
||||
end: latest_delivered_nonce,
|
||||
};
|
||||
if confirmed_messages.total_messages() == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
if confirmed_messages.end > data.latest_generated_nonce {
|
||||
return Err(ReceptionConfirmationError::FailedToConfirmFutureMessages);
|
||||
}
|
||||
if confirmed_messages.total_messages() > max_allowed_messages {
|
||||
// that the relayer has declared correct number of messages that the proof contains (it
|
||||
// is checked outside of the function). But it may happen (but only if this/bridged
|
||||
// chain storage is corrupted, though) that the actual number of confirmed messages if
|
||||
// larger than declared. This would mean that 'reward loop' will take more time than the
|
||||
// weight formula accounts, so we can't allow that.
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
confirmed=%confirmed_messages.total_messages(),
|
||||
max_allowed=%max_allowed_messages,
|
||||
"Messages delivery proof contains too many messages to confirm"
|
||||
);
|
||||
return Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected);
|
||||
}
|
||||
|
||||
ensure_unrewarded_relayers_are_correct(confirmed_messages.end, relayers)?;
|
||||
|
||||
// prune all confirmed messages
|
||||
for nonce in confirmed_messages.begin..=confirmed_messages.end {
|
||||
self.storage.remove_message(&nonce);
|
||||
}
|
||||
|
||||
data.latest_received_nonce = confirmed_messages.end;
|
||||
data.oldest_unpruned_nonce = data.latest_received_nonce.saturating_add(1);
|
||||
self.storage.set_data(data);
|
||||
|
||||
Ok(Some(confirmed_messages))
|
||||
}
|
||||
|
||||
/// Remove message from the storage. Doesn't perform any checks.
|
||||
pub fn remove_oldest_unpruned_message(&mut self) {
|
||||
let mut data = self.storage.data();
|
||||
self.storage.remove_message(&data.oldest_unpruned_nonce);
|
||||
data.oldest_unpruned_nonce += 1;
|
||||
self.storage.set_data(data);
|
||||
}
|
||||
|
||||
/// Purge lane state from the storage.
|
||||
pub fn purge(self) {
|
||||
self.storage.purge()
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies unrewarded relayers vec.
|
||||
///
|
||||
/// Returns `Err(_)` if unrewarded relayers vec contains invalid data, meaning that the bridged
|
||||
/// chain has invalid runtime storage.
|
||||
fn ensure_unrewarded_relayers_are_correct<RelayerId>(
|
||||
latest_received_nonce: MessageNonce,
|
||||
relayers: &VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
) -> Result<(), ReceptionConfirmationError> {
|
||||
let mut expected_entry_begin = relayers.front().map(|entry| entry.messages.begin);
|
||||
for entry in relayers {
|
||||
// unrewarded relayer entry must have at least 1 unconfirmed message
|
||||
// (guaranteed by the `InboundLane::receive_message()`)
|
||||
if entry.messages.end < entry.messages.begin {
|
||||
return Err(ReceptionConfirmationError::EmptyUnrewardedRelayerEntry);
|
||||
}
|
||||
// every entry must confirm range of messages that follows previous entry range
|
||||
// (guaranteed by the `InboundLane::receive_message()`)
|
||||
if expected_entry_begin != Some(entry.messages.begin) {
|
||||
return Err(ReceptionConfirmationError::NonConsecutiveUnrewardedRelayerEntries);
|
||||
}
|
||||
expected_entry_begin = entry.messages.end.checked_add(1);
|
||||
// entry can't confirm messages larger than `inbound_lane_data.latest_received_nonce()`
|
||||
// (guaranteed by the `InboundLane::receive_message()`)
|
||||
if entry.messages.end > latest_received_nonce {
|
||||
return Err(ReceptionConfirmationError::FailedToConfirmFutureMessages);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
active_outbound_lane,
|
||||
tests::mock::{
|
||||
outbound_message_data, run_test, test_lane_id, unrewarded_relayer, TestRelayer,
|
||||
TestRuntime, REGULAR_PAYLOAD,
|
||||
},
|
||||
};
|
||||
use pezsp_std::ops::RangeInclusive;
|
||||
|
||||
fn unrewarded_relayers(
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> VecDeque<UnrewardedRelayer<TestRelayer>> {
|
||||
vec![unrewarded_relayer(*nonces.start(), *nonces.end(), 0)]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn delivered_messages(nonces: RangeInclusive<MessageNonce>) -> DeliveredMessages {
|
||||
DeliveredMessages { begin: *nonces.start(), end: *nonces.end() }
|
||||
}
|
||||
|
||||
fn assert_3_messages_confirmation_fails(
|
||||
latest_received_nonce: MessageNonce,
|
||||
relayers: &VecDeque<UnrewardedRelayer<TestRelayer>>,
|
||||
) -> Result<Option<DeliveredMessages>, ReceptionConfirmationError> {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
let result = lane.confirm_delivery(3, latest_received_nonce, relayers);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_message_works() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 0);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
|
||||
assert!(lane.storage.message(&1).is_some());
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_works() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 2);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 3);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
|
||||
Ok(Some(delivered_messages(1..=3))),
|
||||
);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_partial_delivery_works() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 2);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 3);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
|
||||
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 2, &unrewarded_relayers(1..=2)),
|
||||
Ok(Some(delivered_messages(1..=2))),
|
||||
);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 2);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3);
|
||||
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 3, &unrewarded_relayers(3..=3)),
|
||||
Ok(Some(delivered_messages(3..=3))),
|
||||
);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_rejects_nonce_lesser_than_latest_received() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
|
||||
Ok(Some(delivered_messages(1..=3))),
|
||||
);
|
||||
assert_eq!(lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), Ok(None),);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
|
||||
assert_eq!(lane.confirm_delivery(1, 2, &unrewarded_relayers(1..=1)), Ok(None),);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_rejects_nonce_larger_than_last_generated() {
|
||||
assert_eq!(
|
||||
assert_3_messages_confirmation_fails(10, &unrewarded_relayers(1..=10),),
|
||||
Err(ReceptionConfirmationError::FailedToConfirmFutureMessages),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_fails_if_entry_confirms_future_messages() {
|
||||
assert_eq!(
|
||||
assert_3_messages_confirmation_fails(
|
||||
3,
|
||||
&unrewarded_relayers(1..=1)
|
||||
.into_iter()
|
||||
.chain(unrewarded_relayers(2..=30))
|
||||
.chain(unrewarded_relayers(3..=3))
|
||||
.collect(),
|
||||
),
|
||||
Err(ReceptionConfirmationError::FailedToConfirmFutureMessages),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn confirm_delivery_fails_if_entry_is_empty() {
|
||||
assert_eq!(
|
||||
assert_3_messages_confirmation_fails(
|
||||
3,
|
||||
&unrewarded_relayers(1..=1)
|
||||
.into_iter()
|
||||
.chain(unrewarded_relayers(2..=1))
|
||||
.chain(unrewarded_relayers(2..=3))
|
||||
.collect(),
|
||||
),
|
||||
Err(ReceptionConfirmationError::EmptyUnrewardedRelayerEntry),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_fails_if_entries_are_non_consecutive() {
|
||||
assert_eq!(
|
||||
assert_3_messages_confirmation_fails(
|
||||
3,
|
||||
&unrewarded_relayers(1..=1)
|
||||
.into_iter()
|
||||
.chain(unrewarded_relayers(3..=3))
|
||||
.chain(unrewarded_relayers(2..=2))
|
||||
.collect(),
|
||||
),
|
||||
Err(ReceptionConfirmationError::NonConsecutiveUnrewardedRelayerEntries),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(0, 3, &unrewarded_relayers(1..=3)),
|
||||
Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected),
|
||||
);
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(2, 3, &unrewarded_relayers(1..=3)),
|
||||
Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected),
|
||||
);
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
|
||||
Ok(Some(delivered_messages(1..=3))),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tools for messages and delivery proof verification.
|
||||
|
||||
use crate::{BridgedChainOf, BridgedHeaderChainOf, Config};
|
||||
|
||||
use bp_header_pez_chain::{HeaderChain, HeaderChainError};
|
||||
use bp_messages::{
|
||||
source_chain::FromBridgedChainMessagesDeliveryProof,
|
||||
target_chain::{FromBridgedChainMessagesProof, ProvedLaneMessages, ProvedMessages},
|
||||
ChainWithMessages, InboundLaneData, Message, MessageKey, MessageNonce, MessagePayload,
|
||||
OutboundLaneData, VerificationError,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
HashOf, HasherOf, RangeInclusiveExt, RawStorageProof, StorageProofChecker, StorageProofError,
|
||||
};
|
||||
use codec::Decode;
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
/// 'Parsed' message delivery proof - inbound lane id and its state.
|
||||
pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain<T, I> =
|
||||
(<T as Config<I>>::LaneId, InboundLaneData<<T as pezframe_system::Config>::AccountId>);
|
||||
|
||||
/// Verify proof of Bridged -> This chain messages.
|
||||
///
|
||||
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
|
||||
/// teyrchains, please use the `verify_messages_proof_from_teyrchain`.
|
||||
///
|
||||
/// The `messages_count` argument verification (sane limits) is supposed to be made
|
||||
/// outside of this function. This function only verifies that the proof declares exactly
|
||||
/// `messages_count` messages.
|
||||
pub fn verify_messages_proof<T: Config<I>, I: 'static>(
|
||||
proof: FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
|
||||
messages_count: u32,
|
||||
) -> Result<ProvedMessages<T::LaneId, Message<T::LaneId>>, VerificationError> {
|
||||
let FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane,
|
||||
nonces_start,
|
||||
nonces_end,
|
||||
} = proof;
|
||||
let mut parser: MessagesStorageProofAdapter<T, I> =
|
||||
MessagesStorageProofAdapter::try_new_with_verified_storage_proof(
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
)
|
||||
.map_err(VerificationError::HeaderChain)?;
|
||||
let nonces_range = nonces_start..=nonces_end;
|
||||
|
||||
// receiving proofs where end < begin is ok (if proof includes outbound lane state)
|
||||
let messages_in_the_proof = nonces_range.saturating_len();
|
||||
if messages_in_the_proof != MessageNonce::from(messages_count) {
|
||||
return Err(VerificationError::MessagesCountMismatch);
|
||||
}
|
||||
|
||||
// Read messages first. All messages that are claimed to be in the proof must
|
||||
// be in the proof. So any error in `read_value`, or even missing value is fatal.
|
||||
//
|
||||
// Mind that we allow proofs with no messages if outbound lane state is proved.
|
||||
let mut messages = Vec::with_capacity(messages_in_the_proof as _);
|
||||
for nonce in nonces_range {
|
||||
let message_key = MessageKey { lane_id: lane, nonce };
|
||||
let message_payload = parser
|
||||
.read_and_decode_message_payload(&message_key)
|
||||
.map_err(VerificationError::MessageStorage)?;
|
||||
messages.push(Message { key: message_key, payload: message_payload });
|
||||
}
|
||||
|
||||
// Now let's check if proof contains outbound lane state proof. It is optional, so
|
||||
// we simply ignore `read_value` errors and missing value.
|
||||
let proved_lane_messages = ProvedLaneMessages {
|
||||
lane_state: parser
|
||||
.read_and_decode_outbound_lane_data(&lane)
|
||||
.map_err(VerificationError::OutboundLaneStorage)?,
|
||||
messages,
|
||||
};
|
||||
|
||||
// Now we may actually check if the proof is empty or not.
|
||||
if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() {
|
||||
return Err(VerificationError::EmptyMessageProof);
|
||||
}
|
||||
|
||||
// Check that the storage proof doesn't have any untouched keys.
|
||||
parser.ensure_no_unused_keys().map_err(VerificationError::StorageProof)?;
|
||||
|
||||
Ok((lane, proved_lane_messages))
|
||||
}
|
||||
|
||||
/// Verify proof of This -> Bridged chain messages delivery.
|
||||
pub fn verify_messages_delivery_proof<T: Config<I>, I: 'static>(
|
||||
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
|
||||
) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<T, I>, VerificationError> {
|
||||
let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof;
|
||||
let mut parser: MessagesStorageProofAdapter<T, I> =
|
||||
MessagesStorageProofAdapter::try_new_with_verified_storage_proof(
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
)
|
||||
.map_err(VerificationError::HeaderChain)?;
|
||||
// Messages delivery proof is just proof of single storage key read => any error
|
||||
// is fatal.
|
||||
let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key(
|
||||
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&lane,
|
||||
);
|
||||
let inbound_lane_data = parser
|
||||
.read_and_decode_mandatory_value(&storage_inbound_lane_data_key)
|
||||
.map_err(VerificationError::InboundLaneStorage)?;
|
||||
|
||||
// check that the storage proof doesn't have any untouched trie nodes
|
||||
parser.ensure_no_unused_keys().map_err(VerificationError::StorageProof)?;
|
||||
|
||||
Ok((lane, inbound_lane_data))
|
||||
}
|
||||
|
||||
/// Abstraction over storage proof manipulation, hiding implementation details of actual storage
|
||||
/// proofs.
|
||||
trait StorageProofAdapter<T: Config<I>, I: 'static> {
|
||||
fn read_and_decode_mandatory_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<D, StorageProofError>;
|
||||
fn read_and_decode_optional_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<Option<D>, StorageProofError>;
|
||||
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError>;
|
||||
|
||||
fn read_and_decode_outbound_lane_data(
|
||||
&mut self,
|
||||
lane_id: &T::LaneId,
|
||||
) -> Result<Option<OutboundLaneData>, StorageProofError> {
|
||||
let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key(
|
||||
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
lane_id,
|
||||
);
|
||||
self.read_and_decode_optional_value(&storage_outbound_lane_data_key)
|
||||
}
|
||||
|
||||
fn read_and_decode_message_payload(
|
||||
&mut self,
|
||||
message_key: &MessageKey<T::LaneId>,
|
||||
) -> Result<MessagePayload, StorageProofError> {
|
||||
let storage_message_key = bp_messages::storage_keys::message_key(
|
||||
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&message_key.lane_id,
|
||||
message_key.nonce,
|
||||
);
|
||||
self.read_and_decode_mandatory_value(&storage_message_key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Actual storage proof adapter for messages proofs.
|
||||
type MessagesStorageProofAdapter<T, I> = StorageProofCheckerAdapter<T, I>;
|
||||
|
||||
/// A `StorageProofAdapter` implementation for raw storage proofs.
|
||||
struct StorageProofCheckerAdapter<T: Config<I>, I: 'static> {
|
||||
storage: StorageProofChecker<HasherOf<BridgedChainOf<T, I>>>,
|
||||
_dummy: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> StorageProofCheckerAdapter<T, I> {
|
||||
fn try_new_with_verified_storage_proof(
|
||||
bridged_header_hash: HashOf<BridgedChainOf<T, I>>,
|
||||
storage_proof: RawStorageProof,
|
||||
) -> Result<Self, HeaderChainError> {
|
||||
BridgedHeaderChainOf::<T, I>::verify_storage_proof(bridged_header_hash, storage_proof).map(
|
||||
|storage| StorageProofCheckerAdapter::<T, I> { storage, _dummy: Default::default() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> StorageProofAdapter<T, I> for StorageProofCheckerAdapter<T, I> {
|
||||
fn read_and_decode_optional_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<Option<D>, StorageProofError> {
|
||||
self.storage.read_and_decode_opt_value(key.as_ref())
|
||||
}
|
||||
|
||||
fn read_and_decode_mandatory_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<D, StorageProofError> {
|
||||
self.storage.read_and_decode_mandatory_value(key.as_ref())
|
||||
}
|
||||
|
||||
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError> {
|
||||
self.storage.ensure_no_unused_nodes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{
|
||||
messages_generation::{
|
||||
encode_all_messages, encode_lane_data, generate_dummy_message,
|
||||
prepare_messages_storage_proof,
|
||||
},
|
||||
mock::*,
|
||||
};
|
||||
|
||||
use bp_header_pez_chain::{HeaderChainError, StoredHeaderDataBuilder};
|
||||
use bp_messages::LaneState;
|
||||
use pezbp_runtime::{HeaderId, StorageProofError};
|
||||
use codec::Encode;
|
||||
use pezsp_runtime::traits::Header;
|
||||
|
||||
fn using_messages_proof<R>(
|
||||
nonces_end: MessageNonce,
|
||||
outbound_lane_data: Option<OutboundLaneData>,
|
||||
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
|
||||
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
|
||||
add_duplicate_key: bool,
|
||||
add_unused_key: bool,
|
||||
test: impl Fn(FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>) -> R,
|
||||
) -> R {
|
||||
let (state_root, storage_proof) =
|
||||
prepare_messages_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
|
||||
test_lane_id(),
|
||||
1..=nonces_end,
|
||||
outbound_lane_data,
|
||||
pezbp_runtime::UnverifiedStorageProofParams::default(),
|
||||
generate_dummy_message,
|
||||
encode_message,
|
||||
encode_outbound_lane_data,
|
||||
add_duplicate_key,
|
||||
add_unused_key,
|
||||
);
|
||||
|
||||
pezsp_io::TestExternalities::new(Default::default()).execute_with(move || {
|
||||
let bridged_header = BridgedChainHeader::new(
|
||||
0,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let bridged_header_hash = bridged_header.hash();
|
||||
|
||||
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(HeaderId(
|
||||
0,
|
||||
bridged_header_hash,
|
||||
));
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
bridged_header.build(),
|
||||
);
|
||||
test(FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane: test_lane_id(),
|
||||
nonces_start: 1,
|
||||
nonces_end,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 5) }
|
||||
),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 15) }
|
||||
),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_header_is_missing_from_the_chain() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| {
|
||||
let bridged_header_hash =
|
||||
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::remove(
|
||||
bridged_header_hash,
|
||||
);
|
||||
verify_messages_proof::<TestRuntime, ()>(proof, 10)
|
||||
}
|
||||
),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::UnknownHeader)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_header_state_root_mismatches() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| {
|
||||
let bridged_header_hash =
|
||||
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
BridgedChainHeader::new(
|
||||
0,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
.build(),
|
||||
);
|
||||
verify_messages_proof::<TestRuntime, ()>(proof, 10)
|
||||
}
|
||||
),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
|
||||
StorageProofError::StorageRootMismatch
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_it_has_duplicate_trie_nodes() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
true,
|
||||
false,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 10) },
|
||||
),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
|
||||
StorageProofError::DuplicateNodes
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_it_has_unused_trie_nodes() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
true,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 10) },
|
||||
),
|
||||
Err(VerificationError::StorageProof(StorageProofError::UnusedKey)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_required_message_is_missing() {
|
||||
matches!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
|n, m| if n != 5 { Some(m.encode()) } else { None },
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10)
|
||||
),
|
||||
Err(VerificationError::MessageStorage(StorageProofError::EmptyVal)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_message_decode_fails() {
|
||||
matches!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
|n, m| {
|
||||
let mut m = m.encode();
|
||||
if n == 5 {
|
||||
m = vec![42]
|
||||
}
|
||||
Some(m)
|
||||
},
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10),
|
||||
),
|
||||
Err(VerificationError::MessageStorage(StorageProofError::DecodeError)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() {
|
||||
matches!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
encode_all_messages,
|
||||
|d| {
|
||||
let mut d = d.encode();
|
||||
d.truncate(1);
|
||||
d
|
||||
},
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10),
|
||||
),
|
||||
Err(VerificationError::OutboundLaneStorage(StorageProofError::DecodeError)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_it_is_empty() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
0,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 0) },
|
||||
),
|
||||
Err(VerificationError::EmptyMessageProof),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_empty_message_proof_without_messages_is_accepted() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
0,
|
||||
Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 0),
|
||||
),
|
||||
Ok((
|
||||
test_lane_id(),
|
||||
ProvedLaneMessages {
|
||||
lane_state: Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
messages: Vec::new(),
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_empty_message_proof_is_accepted() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
1,
|
||||
Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 1),
|
||||
),
|
||||
Ok((
|
||||
test_lane_id(),
|
||||
ProvedLaneMessages {
|
||||
lane_state: Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
messages: vec![Message {
|
||||
key: MessageKey { lane_id: test_lane_id(), nonce: 1 },
|
||||
payload: vec![42],
|
||||
}],
|
||||
},
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
1,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|mut proof| {
|
||||
proof.nonces_end = u64::MAX;
|
||||
verify_messages_proof::<TestRuntime, ()>(proof, u32::MAX)
|
||||
},
|
||||
),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helpers for generating message storage proofs, that are used by tests and by benchmarks.
|
||||
|
||||
use bp_messages::{
|
||||
storage_keys, ChainWithMessages, InboundLaneData, MessageKey, MessageNonce, MessagePayload,
|
||||
OutboundLaneData,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
grow_storage_value, record_all_trie_keys, AccountIdOf, Chain, HashOf, HasherOf,
|
||||
RawStorageProof, UnverifiedStorageProofParams,
|
||||
};
|
||||
use codec::Encode;
|
||||
use pezsp_std::{ops::RangeInclusive, prelude::*};
|
||||
use pezsp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
|
||||
|
||||
/// Dummy message generation function.
|
||||
pub fn generate_dummy_message(_: MessageNonce) -> MessagePayload {
|
||||
vec![42]
|
||||
}
|
||||
|
||||
/// Simple and correct message data encode function.
|
||||
pub fn encode_all_messages(_: MessageNonce, m: &MessagePayload) -> Option<Vec<u8>> {
|
||||
Some(m.encode())
|
||||
}
|
||||
|
||||
/// Simple and correct outbound lane data encode function.
|
||||
pub fn encode_lane_data(d: &OutboundLaneData) -> Vec<u8> {
|
||||
d.encode()
|
||||
}
|
||||
|
||||
/// Prepare storage proof of given messages.
|
||||
///
|
||||
/// Returns state trie root and nodes with prepared messages.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn prepare_messages_storage_proof<
|
||||
BridgedChain: Chain,
|
||||
ThisChain: ChainWithMessages,
|
||||
LaneId: Encode + Copy,
|
||||
>(
|
||||
lane: LaneId,
|
||||
message_nonces: RangeInclusive<MessageNonce>,
|
||||
outbound_lane_data: Option<OutboundLaneData>,
|
||||
proof_params: UnverifiedStorageProofParams,
|
||||
generate_message: impl Fn(MessageNonce) -> MessagePayload,
|
||||
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
|
||||
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
|
||||
add_duplicate_key: bool,
|
||||
add_unused_key: bool,
|
||||
) -> (HashOf<BridgedChain>, RawStorageProof)
|
||||
where
|
||||
HashOf<BridgedChain>: Copy + Default,
|
||||
{
|
||||
// prepare Bridged chain storage with messages and (optionally) outbound lane state
|
||||
let message_count = message_nonces.end().saturating_sub(*message_nonces.start()) + 1;
|
||||
let mut storage_keys = Vec::with_capacity(message_count as usize + 1);
|
||||
let mut root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
{
|
||||
let mut trie =
|
||||
TrieDBMutBuilderV1::<HasherOf<BridgedChain>>::new(&mut mdb, &mut root).build();
|
||||
|
||||
// insert messages
|
||||
for (i, nonce) in message_nonces.into_iter().enumerate() {
|
||||
let message_key = MessageKey { lane_id: lane, nonce };
|
||||
let message_payload = match encode_message(nonce, &generate_message(nonce)) {
|
||||
Some(message_payload) =>
|
||||
if i == 0 {
|
||||
grow_storage_value(message_payload, &proof_params)
|
||||
} else {
|
||||
message_payload
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
let storage_key = storage_keys::message_key(
|
||||
ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&message_key.lane_id,
|
||||
message_key.nonce,
|
||||
)
|
||||
.0;
|
||||
trie.insert(&storage_key, &message_payload)
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
storage_keys.push(storage_key);
|
||||
}
|
||||
|
||||
// insert outbound lane state
|
||||
if let Some(outbound_lane_data) = outbound_lane_data.as_ref().map(encode_outbound_lane_data)
|
||||
{
|
||||
let storage_key = storage_keys::outbound_lane_data_key(
|
||||
ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&lane,
|
||||
)
|
||||
.0;
|
||||
trie.insert(&storage_key, &outbound_lane_data)
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
storage_keys.push(storage_key);
|
||||
}
|
||||
}
|
||||
|
||||
// generate storage proof to be delivered to This chain
|
||||
let mut storage_proof =
|
||||
record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain>>, _>(&mdb, &root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
|
||||
if add_duplicate_key {
|
||||
assert!(!storage_proof.is_empty());
|
||||
let node = storage_proof.pop().unwrap();
|
||||
storage_proof.push(node.clone());
|
||||
storage_proof.push(node);
|
||||
}
|
||||
|
||||
if add_unused_key {
|
||||
storage_proof.push(b"unused_value".to_vec());
|
||||
}
|
||||
|
||||
(root, storage_proof)
|
||||
}
|
||||
|
||||
/// Prepare storage proof of given messages delivery.
|
||||
///
|
||||
/// Returns state trie root and nodes with prepared messages.
|
||||
pub fn prepare_message_delivery_storage_proof<
|
||||
BridgedChain: Chain,
|
||||
ThisChain: ChainWithMessages,
|
||||
LaneId: Encode,
|
||||
>(
|
||||
lane: LaneId,
|
||||
inbound_lane_data: InboundLaneData<AccountIdOf<ThisChain>>,
|
||||
proof_params: UnverifiedStorageProofParams,
|
||||
) -> (HashOf<BridgedChain>, RawStorageProof)
|
||||
where
|
||||
HashOf<BridgedChain>: Copy + Default,
|
||||
{
|
||||
// prepare Bridged chain storage with inbound lane state
|
||||
let storage_key =
|
||||
storage_keys::inbound_lane_data_key(ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, &lane).0;
|
||||
let mut root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
{
|
||||
let mut trie =
|
||||
TrieDBMutBuilderV1::<HasherOf<BridgedChain>>::new(&mut mdb, &mut root).build();
|
||||
let inbound_lane_data = grow_storage_value(inbound_lane_data.encode(), &proof_params);
|
||||
trie.insert(&storage_key, &inbound_lane_data)
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
}
|
||||
|
||||
// generate storage proof to be delivered to This chain
|
||||
let storage_proof = record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain>>, _>(&mdb, &root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
|
||||
(root, storage_proof)
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// From construct_runtime macro
|
||||
#![allow(clippy::from_over_into)]
|
||||
|
||||
use crate::{
|
||||
tests::messages_generation::{
|
||||
encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof,
|
||||
prepare_messages_storage_proof,
|
||||
},
|
||||
Config, StoredMessagePayload,
|
||||
};
|
||||
|
||||
use bp_header_pez_chain::{ChainWithGrandpa, StoredHeaderData};
|
||||
use bp_messages::{
|
||||
calc_relayers_rewards,
|
||||
source_chain::{
|
||||
DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered,
|
||||
},
|
||||
target_chain::{
|
||||
DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof,
|
||||
MessageDispatch,
|
||||
},
|
||||
ChainWithMessages, DeliveredMessages, HashedLaneId, InboundLaneData, LaneIdType, LaneState,
|
||||
Message, MessageKey, MessageNonce, OutboundLaneData, UnrewardedRelayer,
|
||||
UnrewardedRelayersState,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
messages::MessageDispatchResult, Chain, ChainId, Size, UnverifiedStorageProofParams,
|
||||
};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::{
|
||||
derive_impl,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
testing::Header as BizinikiwiHeader,
|
||||
traits::{BlakeTwo256, ConstU32},
|
||||
BuildStorage, StateVersion,
|
||||
};
|
||||
use std::{collections::VecDeque, ops::RangeInclusive};
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type Balance = u64;
|
||||
#[derive(Decode, DecodeWithMemTracking, Encode, Clone, Debug, PartialEq, Eq, TypeInfo)]
|
||||
pub struct TestPayload {
|
||||
/// Field that may be used to identify messages.
|
||||
pub id: u64,
|
||||
/// Dispatch weight that is declared by the message sender.
|
||||
pub declared_weight: Weight,
|
||||
/// Message dispatch result.
|
||||
///
|
||||
/// Note: in correct code `dispatch_result.unspent_weight` will always be <= `declared_weight`,
|
||||
/// but for test purposes we'll be making it larger than `declared_weight` sometimes.
|
||||
pub dispatch_result: MessageDispatchResult<TestDispatchLevelResult>,
|
||||
/// Extra bytes that affect payload size.
|
||||
pub extra: Vec<u8>,
|
||||
}
|
||||
pub type TestMessageFee = u64;
|
||||
pub type TestRelayer = u64;
|
||||
pub type TestDispatchLevelResult = ();
|
||||
|
||||
pub struct ThisChain;
|
||||
|
||||
impl Chain for ThisChain {
|
||||
const ID: ChainId = *b"ttch";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BizinikiwiHeader;
|
||||
type AccountId = AccountId;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
u32::MAX
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for ThisChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithThisChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
pub struct BridgedChain;
|
||||
|
||||
pub type BridgedHeaderHash = H256;
|
||||
pub type BridgedChainHeader = BizinikiwiHeader;
|
||||
|
||||
impl Chain for BridgedChain {
|
||||
const ID: ChainId = *b"tbch";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = BridgedHeaderHash;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = TestRelayer;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
4096
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for BridgedChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "WithBridgedChainBridgeGrandpa";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 16;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 4;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 4096;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 4096;
|
||||
}
|
||||
|
||||
impl ChainWithMessages for BridgedChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithBridgedChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
use crate as pezpallet_bridge_messages;
|
||||
|
||||
pezframe_support::construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Balances: pezpallet_balances::{Pezpallet, Call, Event<T>},
|
||||
BridgedChainGrandpa: pezpallet_bridge_grandpa::{Pezpallet, Call, Event<T>},
|
||||
Messages: pezpallet_bridge_messages::{Pezpallet, Call, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
pub type DbWeight = RocksDbWeight;
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
type DbWeight = DbWeight;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = BridgedChain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<4>;
|
||||
type FreeHeadersInterval = ConstU32<1_024>;
|
||||
type HeadersToKeep = ConstU32<8>;
|
||||
type WeightInfo = pezpallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
|
||||
}
|
||||
|
||||
/// weights of messages pezpallet calls we use in tests.
|
||||
pub type TestWeightInfo = ();
|
||||
|
||||
impl Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = TestWeightInfo;
|
||||
|
||||
type ThisChain = ThisChain;
|
||||
type BridgedChain = BridgedChain;
|
||||
type BridgedHeaderChain = BridgedChainGrandpa;
|
||||
|
||||
type OutboundPayload = TestPayload;
|
||||
type InboundPayload = TestPayload;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
type DeliveryPayments = TestDeliveryPayments;
|
||||
type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments;
|
||||
type OnMessagesDelivered = TestOnMessagesDelivered;
|
||||
|
||||
type MessageDispatch = TestMessageDispatch;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl crate::benchmarking::Config<()> for TestRuntime {
|
||||
fn bench_lane_id() -> Self::LaneId {
|
||||
test_lane_id()
|
||||
}
|
||||
|
||||
fn prepare_message_proof(
|
||||
params: crate::benchmarking::MessageProofParams<Self::LaneId>,
|
||||
) -> (FromBridgedChainMessagesProof<BridgedHeaderHash, Self::LaneId>, Weight) {
|
||||
use pezbp_runtime::RangeInclusiveExt;
|
||||
|
||||
let dispatch_weight =
|
||||
REGULAR_PAYLOAD.declared_weight * params.message_nonces.saturating_len();
|
||||
(
|
||||
*prepare_messages_proof(
|
||||
params.message_nonces.into_iter().map(|n| message(n, REGULAR_PAYLOAD)).collect(),
|
||||
params.outbound_lane_data,
|
||||
),
|
||||
dispatch_weight,
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare_message_delivery_proof(
|
||||
params: crate::benchmarking::MessageDeliveryProofParams<AccountId, Self::LaneId>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, Self::LaneId> {
|
||||
// in mock run we only care about benchmarks correctness, not the benchmark results
|
||||
// => ignore size related arguments
|
||||
prepare_messages_delivery_proof(params.lane, params.inbound_lane_data)
|
||||
}
|
||||
|
||||
fn is_relayer_rewarded(_relayer: &AccountId) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Size for TestPayload {
|
||||
fn size(&self) -> u32 {
|
||||
16 + self.extra.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Account that has balance to use in tests.
|
||||
pub const ENDOWED_ACCOUNT: AccountId = 0xDEAD;
|
||||
|
||||
/// Account id of test relayer.
|
||||
pub const TEST_RELAYER_A: AccountId = 100;
|
||||
|
||||
/// Account id of additional test relayer - B.
|
||||
pub const TEST_RELAYER_B: AccountId = 101;
|
||||
|
||||
/// Account id of additional test relayer - C.
|
||||
pub const TEST_RELAYER_C: AccountId = 102;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
pub type TestLaneIdType = HashedLaneId;
|
||||
/// Lane that we're using in tests.
|
||||
pub fn test_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 2).unwrap()
|
||||
}
|
||||
|
||||
/// Lane that is completely unknown to our runtime.
|
||||
pub fn unknown_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 3).unwrap()
|
||||
}
|
||||
|
||||
/// Lane that is registered, but it is closed.
|
||||
pub fn closed_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 4).unwrap()
|
||||
}
|
||||
|
||||
/// Regular message payload.
|
||||
pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50);
|
||||
|
||||
/// Reward payments at the target chain during delivery transaction.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TestDeliveryPayments;
|
||||
|
||||
impl TestDeliveryPayments {
|
||||
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
|
||||
/// cleared after the call.
|
||||
pub fn is_reward_paid(relayer: AccountId) -> bool {
|
||||
let key = (b":delivery-relayer-reward:", relayer).encode();
|
||||
pezframe_support::storage::unhashed::take::<bool>(&key).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeliveryPayments<AccountId> for TestDeliveryPayments {
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
relayer: AccountId,
|
||||
_total_messages: MessageNonce,
|
||||
_valid_messages: MessageNonce,
|
||||
_actual_weight: Weight,
|
||||
) {
|
||||
let key = (b":delivery-relayer-reward:", relayer).encode();
|
||||
pezframe_support::storage::unhashed::put(&key, &true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reward payments at the source chain during delivery confirmation transaction.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TestDeliveryConfirmationPayments;
|
||||
|
||||
impl TestDeliveryConfirmationPayments {
|
||||
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
|
||||
/// cleared after the call.
|
||||
pub fn is_reward_paid(relayer: AccountId, fee: TestMessageFee) -> bool {
|
||||
let key = (b":relayer-reward:", relayer, fee).encode();
|
||||
pezframe_support::storage::unhashed::take::<bool>(&key).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeliveryConfirmationPayments<AccountId, TestLaneIdType> for TestDeliveryConfirmationPayments {
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
_lane_id: TestLaneIdType,
|
||||
pez_messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
|
||||
_confirmation_relayer: &AccountId,
|
||||
received_range: &RangeInclusive<MessageNonce>,
|
||||
) -> MessageNonce {
|
||||
let relayers_rewards = calc_relayers_rewards(pez_messages_relayers, received_range);
|
||||
let rewarded_relayers = relayers_rewards.len();
|
||||
for (relayer, reward) in &relayers_rewards {
|
||||
let key = (b":relayer-reward:", relayer, reward).encode();
|
||||
pezframe_support::storage::unhashed::put(&key, &true);
|
||||
}
|
||||
|
||||
rewarded_relayers as _
|
||||
}
|
||||
}
|
||||
|
||||
/// Test message dispatcher.
|
||||
#[derive(Debug)]
|
||||
pub struct TestMessageDispatch;
|
||||
|
||||
impl TestMessageDispatch {
|
||||
pub fn deactivate(lane: TestLaneIdType) {
|
||||
// "enqueue" enough (to deactivate dispatcher) messages at dispatcher
|
||||
let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1;
|
||||
for _ in 1..=latest_received_nonce {
|
||||
Self::emulate_enqueued_message(lane);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emulate_enqueued_message(lane: TestLaneIdType) {
|
||||
let key = (b"dispatched", lane).encode();
|
||||
let dispatched = pezframe_support::storage::unhashed::get_or_default::<MessageNonce>(&key[..]);
|
||||
pezframe_support::storage::unhashed::put(&key[..], &(dispatched + 1));
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for TestMessageDispatch {
|
||||
type DispatchPayload = TestPayload;
|
||||
type DispatchLevelResult = TestDispatchLevelResult;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default::<MessageNonce>(
|
||||
&(b"dispatched", lane).encode()[..],
|
||||
) <= BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
|
||||
}
|
||||
|
||||
fn dispatch_weight(message: &mut DispatchMessage<TestPayload, Self::LaneId>) -> Weight {
|
||||
match message.data.payload.as_ref() {
|
||||
Ok(payload) => payload.declared_weight,
|
||||
Err(_) => Weight::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
message: DispatchMessage<TestPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<TestDispatchLevelResult> {
|
||||
match message.data.payload.as_ref() {
|
||||
Ok(payload) => {
|
||||
Self::emulate_enqueued_message(message.key.lane_id);
|
||||
payload.dispatch_result.clone()
|
||||
},
|
||||
Err(_) => dispatch_result(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test callback, called during message delivery confirmation transaction.
|
||||
pub struct TestOnMessagesDelivered;
|
||||
|
||||
impl TestOnMessagesDelivered {
|
||||
pub fn call_arguments() -> Option<(TestLaneIdType, MessageNonce)> {
|
||||
pezframe_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered")
|
||||
}
|
||||
}
|
||||
|
||||
impl OnMessagesDelivered<TestLaneIdType> for TestOnMessagesDelivered {
|
||||
fn on_messages_delivered(lane: TestLaneIdType, enqueued_messages: MessageNonce) {
|
||||
pezframe_support::storage::unhashed::put(
|
||||
b"TestOnMessagesDelivered.OnMessagesDelivered",
|
||||
&(lane, enqueued_messages),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return test lane message with given nonce and payload.
|
||||
pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message<TestLaneIdType> {
|
||||
Message { key: MessageKey { lane_id: test_lane_id(), nonce }, payload: payload.encode() }
|
||||
}
|
||||
|
||||
/// Return valid outbound message data, constructed from given payload.
|
||||
pub fn outbound_message_data(payload: TestPayload) -> StoredMessagePayload<TestRuntime, ()> {
|
||||
StoredMessagePayload::<TestRuntime, ()>::try_from(payload.encode()).expect("payload too large")
|
||||
}
|
||||
|
||||
/// Return valid inbound (dispatch) message data, constructed from given payload.
|
||||
pub fn inbound_message_data(payload: TestPayload) -> DispatchMessageData<TestPayload> {
|
||||
DispatchMessageData { payload: Ok(payload) }
|
||||
}
|
||||
|
||||
/// Constructs message payload using given arguments and zero unspent weight.
|
||||
pub const fn message_payload(id: u64, declared_weight: u64) -> TestPayload {
|
||||
TestPayload {
|
||||
id,
|
||||
declared_weight: Weight::from_parts(declared_weight, 0),
|
||||
dispatch_result: dispatch_result(0),
|
||||
extra: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns message dispatch result with given unspent weight.
|
||||
pub const fn dispatch_result(
|
||||
unspent_weight: u64,
|
||||
) -> MessageDispatchResult<TestDispatchLevelResult> {
|
||||
MessageDispatchResult {
|
||||
unspent_weight: Weight::from_parts(unspent_weight, 0),
|
||||
dispatch_level_result: (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs unrewarded relayer entry from nonces range and relayer id.
|
||||
pub fn unrewarded_relayer(
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
relayer: TestRelayer,
|
||||
) -> UnrewardedRelayer<TestRelayer> {
|
||||
UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end } }
|
||||
}
|
||||
|
||||
/// Returns unrewarded relayers state at given lane.
|
||||
pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRelayersState {
|
||||
let inbound_lane_data = crate::InboundLanes::<TestRuntime, ()>::get(lane).unwrap().0;
|
||||
UnrewardedRelayersState::from(&inbound_lane_data)
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
|
||||
pezpallet_balances::GenesisConfig::<TestRuntime> {
|
||||
balances: vec![(ENDOWED_ACCOUNT, 1_000_000)],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
pezsp_io::TestExternalities::new(t)
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
crate::InboundLanes::<TestRuntime, ()>::insert(test_lane_id(), InboundLaneData::opened());
|
||||
crate::OutboundLanes::<TestRuntime, ()>::insert(test_lane_id(), OutboundLaneData::opened());
|
||||
crate::InboundLanes::<TestRuntime, ()>::insert(
|
||||
closed_lane_id(),
|
||||
InboundLaneData { state: LaneState::Closed, ..Default::default() },
|
||||
);
|
||||
crate::OutboundLanes::<TestRuntime, ()>::insert(
|
||||
closed_lane_id(),
|
||||
OutboundLaneData { state: LaneState::Closed, ..Default::default() },
|
||||
);
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare valid storage proof for given messages and insert appropriate header to the
|
||||
/// bridged header chain.
|
||||
///
|
||||
/// Since this function changes the runtime storage, you can't "inline" it in the
|
||||
/// `asset_noop` macro calls.
|
||||
pub fn prepare_messages_proof(
|
||||
messages: Vec<Message<TestLaneIdType>>,
|
||||
outbound_lane_data: Option<OutboundLaneData>,
|
||||
) -> Box<FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>> {
|
||||
// first - let's generate storage proof
|
||||
let lane = messages.first().unwrap().key.lane_id;
|
||||
let nonces_start = messages.first().unwrap().key.nonce;
|
||||
let nonces_end = messages.last().unwrap().key.nonce;
|
||||
let (storage_root, storage_proof) =
|
||||
prepare_messages_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
|
||||
lane,
|
||||
nonces_start..=nonces_end,
|
||||
outbound_lane_data,
|
||||
UnverifiedStorageProofParams::default(),
|
||||
|nonce| messages[(nonce - nonces_start) as usize].payload.clone(),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
// let's now insert bridged chain header into the storage
|
||||
let bridged_header_hash = Default::default();
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
StoredHeaderData { number: 0, state_root: storage_root },
|
||||
);
|
||||
|
||||
Box::new(FromBridgedChainMessagesProof::<BridgedHeaderHash, TestLaneIdType> {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane,
|
||||
nonces_start,
|
||||
nonces_end,
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare valid storage proof for given messages and insert appropriate header to the
|
||||
/// bridged header chain.
|
||||
///
|
||||
/// Since this function changes the runtime storage, you can't "inline" it in the
|
||||
/// `asset_noop` macro calls.
|
||||
pub fn prepare_messages_delivery_proof(
|
||||
lane: TestLaneIdType,
|
||||
inbound_lane_data: InboundLaneData<AccountId>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, TestLaneIdType> {
|
||||
// first - let's generate storage proof
|
||||
let (storage_root, storage_proof) =
|
||||
prepare_message_delivery_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
|
||||
lane,
|
||||
inbound_lane_data,
|
||||
UnverifiedStorageProofParams::default(),
|
||||
);
|
||||
|
||||
// let's now insert bridged chain header into the storage
|
||||
let bridged_header_hash = Default::default();
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
StoredHeaderData { number: 0, state_root: storage_root },
|
||||
);
|
||||
|
||||
FromBridgedChainMessagesDeliveryProof::<BridgedHeaderHash, TestLaneIdType> {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests and test helpers for messages pezpallet.
|
||||
|
||||
#![cfg(any(feature = "test-helpers", test))]
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod mock;
|
||||
#[cfg(test)]
|
||||
mod pezpallet_tests;
|
||||
|
||||
pub mod messages_generation;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,530 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_bridge_messages
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-06-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `serban-ROG-Zephyrus`, CPU: `12th Gen Intel(R) Core(TM) i7-12700H`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/unknown-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_bridge_messages
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/messages/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pezpallet_bridge_messages.
|
||||
pub trait WeightInfo {
|
||||
fn receive_single_message_proof() -> Weight;
|
||||
fn receive_n_messages_proof(n: u32) -> Weight;
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight;
|
||||
fn receive_single_n_bytes_message_proof(n: u32) -> Weight;
|
||||
fn receive_delivery_proof_for_single_message() -> Weight;
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight;
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight;
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_bridge_messages` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_724 nanoseconds.
|
||||
Weight::from_parts(40_650_000, 52673)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
|
||||
/// 51683, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 1004]`.
|
||||
///
|
||||
/// The range of component `n` is `[1, 1004]`.
|
||||
fn receive_n_messages_proof(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 39_354 nanoseconds.
|
||||
Weight::from_parts(29_708_543, 52673)
|
||||
// Standard Error: 1_185
|
||||
.saturating_add(Weight::from_parts(7_648_787, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 45_578 nanoseconds.
|
||||
Weight::from_parts(47_161_000, 52673)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
fn receive_single_n_bytes_message_proof(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_702 nanoseconds.
|
||||
Weight::from_parts(41_040_143, 52673)
|
||||
// Standard Error: 5
|
||||
.saturating_add(Weight::from_parts(1_174, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `3558`
|
||||
// Minimum execution time: 37_197 nanoseconds.
|
||||
Weight::from_parts(38_371_000, 3558)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `3558`
|
||||
// Minimum execution time: 38_684 nanoseconds.
|
||||
Weight::from_parts(39_929_000, 3558)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:2 w:2)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `6126`
|
||||
// Minimum execution time: 41_363 nanoseconds.
|
||||
Weight::from_parts(42_621_000, 6126)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_925 nanoseconds.
|
||||
Weight::from_parts(39_617_000, 52673)
|
||||
// Standard Error: 612
|
||||
.saturating_add(Weight::from_parts(372_813, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_724 nanoseconds.
|
||||
Weight::from_parts(40_650_000, 52673)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
|
||||
/// 51683, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 1004]`.
|
||||
///
|
||||
/// The range of component `n` is `[1, 1004]`.
|
||||
fn receive_n_messages_proof(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 39_354 nanoseconds.
|
||||
Weight::from_parts(29_708_543, 52673)
|
||||
// Standard Error: 1_185
|
||||
.saturating_add(Weight::from_parts(7_648_787, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 45_578 nanoseconds.
|
||||
Weight::from_parts(47_161_000, 52673)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
|
||||
/// 51683, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
fn receive_single_n_bytes_message_proof(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_702 nanoseconds.
|
||||
Weight::from_parts(41_040_143, 52673)
|
||||
// Standard Error: 5
|
||||
.saturating_add(Weight::from_parts(1_174, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `3558`
|
||||
// Minimum execution time: 37_197 nanoseconds.
|
||||
Weight::from_parts(38_371_000, 3558)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `3558`
|
||||
// Minimum execution time: 38_684 nanoseconds.
|
||||
Weight::from_parts(39_929_000, 3558)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:2 w:2)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `6126`
|
||||
// Minimum execution time: 41_363 nanoseconds.
|
||||
Weight::from_parts(42_621_000, 6126)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_925 nanoseconds.
|
||||
Weight::from_parts(39_617_000, 52673)
|
||||
// Standard Error: 612
|
||||
.saturating_add(Weight::from_parts(372_813, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::WeightInfo;
|
||||
|
||||
use bp_messages::{MessageNonce, UnrewardedRelayersState};
|
||||
use pezbp_runtime::{PreComputedSize, Size};
|
||||
use pezframe_support::weights::Weight;
|
||||
|
||||
/// Size of the message being delivered in benchmarks.
|
||||
pub const EXPECTED_DEFAULT_MESSAGE_LENGTH: u32 = 128;
|
||||
|
||||
/// We assume that size of signed extensions on all our chains and size of all 'small' arguments of
|
||||
/// calls we're checking here would fit 1KB.
|
||||
const SIGNED_EXTENSIONS_SIZE: u32 = 1024;
|
||||
|
||||
/// Number of extra bytes (excluding size of storage value itself) of storage proof.
|
||||
/// This mostly depends on number of entries (and their density) in the storage trie.
|
||||
/// Some reserve is reserved to account future chain growth.
|
||||
pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
|
||||
|
||||
/// Ensure that weights from `WeightInfoExt` implementation are looking correct.
|
||||
pub fn ensure_weights_are_correct<W: WeightInfoExt>() {
|
||||
// all components of weight formulae must have zero `proof_size`, because the `proof_size` is
|
||||
// benchmarked using `MaxEncodedLen` approach and there are no components that cause additional
|
||||
// db reads
|
||||
|
||||
// W::receive_messages_proof_outbound_lane_state_overhead().ref_time() may be zero because:
|
||||
// the outbound lane state processing code (`InboundLane::receive_state_update`) is minimal and
|
||||
// may not be accounted by our benchmarks
|
||||
assert_eq!(W::receive_messages_proof_outbound_lane_state_overhead().proof_size(), 0);
|
||||
assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
|
||||
assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);
|
||||
|
||||
// verify `receive_messages_delivery_proof` weight components
|
||||
assert_ne!(W::receive_messages_delivery_proof_overhead().ref_time(), 0);
|
||||
assert_ne!(W::receive_messages_delivery_proof_overhead().proof_size(), 0);
|
||||
// W::receive_messages_delivery_proof_messages_overhead(1).ref_time() may be zero because:
|
||||
// there's no code that iterates over confirmed messages in confirmation transaction
|
||||
assert_eq!(W::receive_messages_delivery_proof_messages_overhead(1).proof_size(), 0);
|
||||
// W::receive_messages_delivery_proof_relayers_overhead(1).ref_time() may be zero because:
|
||||
// runtime **can** choose not to pay any rewards to relayers
|
||||
// W::receive_messages_delivery_proof_relayers_overhead(1).proof_size() is an exception
|
||||
// it may or may not cause additional db reads, so proof size may vary
|
||||
assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
|
||||
assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);
|
||||
|
||||
// verify `receive_message_proof` weight
|
||||
let receive_messages_proof_weight =
|
||||
W::receive_messages_proof_weight(&PreComputedSize(1), 10, Weight::zero());
|
||||
assert_ne!(receive_messages_proof_weight.ref_time(), 0);
|
||||
assert_ne!(receive_messages_proof_weight.proof_size(), 0);
|
||||
messages_proof_size_does_not_affect_proof_size::<W>();
|
||||
messages_count_does_not_affect_proof_size::<W>();
|
||||
|
||||
// verify `receive_message_proof` weight
|
||||
let receive_messages_delivery_proof_weight = W::receive_messages_delivery_proof_weight(
|
||||
&PreComputedSize(1),
|
||||
&UnrewardedRelayersState::default(),
|
||||
);
|
||||
assert_ne!(receive_messages_delivery_proof_weight.ref_time(), 0);
|
||||
assert_ne!(receive_messages_delivery_proof_weight.proof_size(), 0);
|
||||
messages_delivery_proof_size_does_not_affect_proof_size::<W>();
|
||||
total_messages_in_delivery_proof_does_not_affect_proof_size::<W>();
|
||||
}
|
||||
|
||||
/// Ensure that we are able to dispatch maximal size messages.
|
||||
pub fn ensure_maximal_message_dispatch<W: WeightInfoExt>(
|
||||
max_incoming_message_size: u32,
|
||||
max_incoming_message_dispatch_weight: Weight,
|
||||
) {
|
||||
let message_dispatch_weight = W::message_dispatch_weight(max_incoming_message_size);
|
||||
assert!(
|
||||
message_dispatch_weight.all_lte(max_incoming_message_dispatch_weight),
|
||||
"Dispatch weight of maximal message {message_dispatch_weight:?} must be lower \
|
||||
than the hardcoded {max_incoming_message_dispatch_weight:?}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure that we're able to receive maximal (by-size and by-weight) message from other chain.
|
||||
pub fn ensure_able_to_receive_message<W: WeightInfoExt>(
|
||||
max_extrinsic_size: u32,
|
||||
max_extrinsic_weight: Weight,
|
||||
max_incoming_message_proof_size: u32,
|
||||
max_incoming_message_dispatch_weight: Weight,
|
||||
) {
|
||||
// verify that we're able to receive proof of maximal-size message
|
||||
let max_delivery_transaction_size =
|
||||
max_incoming_message_proof_size.saturating_add(SIGNED_EXTENSIONS_SIZE);
|
||||
assert!(
|
||||
max_delivery_transaction_size <= max_extrinsic_size,
|
||||
"Size of maximal message delivery transaction {max_incoming_message_proof_size} + \
|
||||
{SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
|
||||
);
|
||||
|
||||
// verify that we're able to receive proof of maximal-size message with maximal dispatch weight
|
||||
let max_delivery_transaction_dispatch_weight = W::receive_messages_proof_weight(
|
||||
&PreComputedSize(
|
||||
(max_incoming_message_proof_size + W::expected_extra_storage_proof_size()) as usize,
|
||||
),
|
||||
1,
|
||||
max_incoming_message_dispatch_weight,
|
||||
);
|
||||
assert!(
|
||||
max_delivery_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
|
||||
"Weight of maximal message delivery transaction + {max_delivery_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure that we're able to receive maximal confirmation from other chain.
|
||||
pub fn ensure_able_to_receive_confirmation<W: WeightInfoExt>(
|
||||
max_extrinsic_size: u32,
|
||||
max_extrinsic_weight: Weight,
|
||||
max_inbound_lane_data_proof_size_from_peer_chain: u32,
|
||||
max_unrewarded_relayer_entries_at_peer_inbound_lane: MessageNonce,
|
||||
max_unconfirmed_messages_at_inbound_lane: MessageNonce,
|
||||
) {
|
||||
// verify that we're able to receive confirmation of maximal-size
|
||||
let max_confirmation_transaction_size =
|
||||
max_inbound_lane_data_proof_size_from_peer_chain.saturating_add(SIGNED_EXTENSIONS_SIZE);
|
||||
assert!(
|
||||
max_confirmation_transaction_size <= max_extrinsic_size,
|
||||
"Size of maximal message delivery confirmation transaction {max_inbound_lane_data_proof_size_from_peer_chain} + {SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
|
||||
);
|
||||
|
||||
// verify that we're able to reward maximal number of relayers that have delivered maximal
|
||||
// number of messages
|
||||
let max_confirmation_transaction_dispatch_weight = W::receive_messages_delivery_proof_weight(
|
||||
&PreComputedSize(max_inbound_lane_data_proof_size_from_peer_chain as usize),
|
||||
&UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: max_unrewarded_relayer_entries_at_peer_inbound_lane,
|
||||
total_messages: max_unconfirmed_messages_at_inbound_lane,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
max_confirmation_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
|
||||
"Weight of maximal confirmation transaction {max_confirmation_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of message delivery call depends on the message proof size.
|
||||
fn messages_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
|
||||
let dispatch_weight = Weight::zero();
|
||||
let weight_when_proof_size_is_8k =
|
||||
W::receive_messages_proof_weight(&PreComputedSize(8 * 1024), 1, dispatch_weight);
|
||||
let weight_when_proof_size_is_16k =
|
||||
W::receive_messages_proof_weight(&PreComputedSize(16 * 1024), 1, dispatch_weight);
|
||||
|
||||
ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
|
||||
ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
|
||||
ensure_proof_size_is_the_same(
|
||||
weight_when_proof_size_is_8k,
|
||||
weight_when_proof_size_is_16k,
|
||||
"Messages proof size does not affect values that we read from our storage",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of message delivery call depends on the messages count.
|
||||
///
|
||||
/// In practice, it will depend on the messages count, because most probably every
|
||||
/// message will read something from db during dispatch. But this must be accounted
|
||||
/// by the `dispatch_weight`.
|
||||
fn messages_count_does_not_affect_proof_size<W: WeightInfoExt>() {
|
||||
let messages_proof_size = PreComputedSize(8 * 1024);
|
||||
let dispatch_weight = Weight::zero();
|
||||
let weight_of_one_incoming_message =
|
||||
W::receive_messages_proof_weight(&messages_proof_size, 1, dispatch_weight);
|
||||
let weight_of_two_incoming_messages =
|
||||
W::receive_messages_proof_weight(&messages_proof_size, 2, dispatch_weight);
|
||||
|
||||
ensure_weight_components_are_not_zero(weight_of_one_incoming_message);
|
||||
ensure_weight_components_are_not_zero(weight_of_two_incoming_messages);
|
||||
ensure_proof_size_is_the_same(
|
||||
weight_of_one_incoming_message,
|
||||
weight_of_two_incoming_messages,
|
||||
"Number of same-lane incoming messages does not affect values that we read from our storage",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of delivery confirmation call depends on the delivery proof size.
|
||||
fn messages_delivery_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 1,
|
||||
last_delivered_nonce: 1,
|
||||
};
|
||||
let weight_when_proof_size_is_8k =
|
||||
W::receive_messages_delivery_proof_weight(&PreComputedSize(8 * 1024), &relayers_state);
|
||||
let weight_when_proof_size_is_16k =
|
||||
W::receive_messages_delivery_proof_weight(&PreComputedSize(16 * 1024), &relayers_state);
|
||||
|
||||
ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
|
||||
ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
|
||||
ensure_proof_size_is_the_same(
|
||||
weight_when_proof_size_is_8k,
|
||||
weight_when_proof_size_is_16k,
|
||||
"Messages delivery proof size does not affect values that we read from our storage",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of delivery confirmation call depends on the number of confirmed
|
||||
/// messages.
|
||||
fn total_messages_in_delivery_proof_does_not_affect_proof_size<W: WeightInfoExt>() {
|
||||
let proof_size = PreComputedSize(8 * 1024);
|
||||
let weight_when_1k_messages_confirmed = W::receive_messages_delivery_proof_weight(
|
||||
&proof_size,
|
||||
&UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 1024,
|
||||
last_delivered_nonce: 1,
|
||||
},
|
||||
);
|
||||
let weight_when_2k_messages_confirmed = W::receive_messages_delivery_proof_weight(
|
||||
&proof_size,
|
||||
&UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 2048,
|
||||
last_delivered_nonce: 1,
|
||||
},
|
||||
);
|
||||
|
||||
ensure_weight_components_are_not_zero(weight_when_1k_messages_confirmed);
|
||||
ensure_weight_components_are_not_zero(weight_when_2k_messages_confirmed);
|
||||
ensure_proof_size_is_the_same(
|
||||
weight_when_1k_messages_confirmed,
|
||||
weight_when_2k_messages_confirmed,
|
||||
"More messages in delivery proof does not affect values that we read from our storage",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if either Weight' `proof_size` or `ref_time` are zero.
|
||||
fn ensure_weight_components_are_not_zero(weight: Weight) {
|
||||
assert_ne!(weight.ref_time(), 0);
|
||||
assert_ne!(weight.proof_size(), 0);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of `weight1` is not equal to `proof_size` of `weight2`.
|
||||
fn ensure_proof_size_is_the_same(weight1: Weight, weight2: Weight, msg: &str) {
|
||||
assert_eq!(
|
||||
weight1.proof_size(),
|
||||
weight2.proof_size(),
|
||||
"{msg}: {} must be equal to {}",
|
||||
weight1.proof_size(),
|
||||
weight2.proof_size(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
/// Size of proof that is already included in the single message delivery weight.
|
||||
///
|
||||
/// The message submitter (at source chain) has already covered this cost. But there are two
|
||||
/// factors that may increase proof size: (1) the message size may be larger than predefined
|
||||
/// and (2) relayer may add extra trie nodes to the proof. So if proof size is larger than
|
||||
/// this value, we're going to charge relayer for that.
|
||||
fn expected_extra_storage_proof_size() -> u32;
|
||||
|
||||
// Our configuration assumes that the runtime has special signed extensions used to:
|
||||
//
|
||||
// 1) reject obsolete delivery and confirmation transactions;
|
||||
//
|
||||
// 2) refund transaction cost to relayer and register his rewards.
|
||||
//
|
||||
// The checks in (1) are trivial, so its computation weight may be ignored. And we only touch
|
||||
// storage values that are read during the call. So we may ignore the weight of this check.
|
||||
//
|
||||
// However, during (2) we read and update storage values of other pallets
|
||||
// (`pezpallet-bridge-relayers` and balances/assets pezpallet). So we need to add this weight to the
|
||||
// weight of our call. Hence two following methods.
|
||||
|
||||
/// Extra weight that is added to the `receive_messages_proof` call weight by signed extensions
|
||||
/// that are declared at runtime level.
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight;
|
||||
|
||||
/// Extra weight that is added to the `receive_messages_delivery_proof` call weight by signed
|
||||
/// extensions that are declared at runtime level.
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight;
|
||||
|
||||
// Functions that are directly mapped to extrinsics weights.
|
||||
|
||||
/// Weight of message delivery extrinsic.
|
||||
fn receive_messages_proof_weight(
|
||||
proof: &impl Size,
|
||||
messages_count: u32,
|
||||
dispatch_weight: Weight,
|
||||
) -> Weight {
|
||||
// basic components of extrinsic weight
|
||||
let base_weight = Self::receive_n_messages_proof(messages_count);
|
||||
let transaction_overhead_from_runtime =
|
||||
Self::receive_messages_proof_overhead_from_runtime();
|
||||
let outbound_state_delivery_weight =
|
||||
Self::receive_messages_proof_outbound_lane_state_overhead();
|
||||
let messages_dispatch_weight = dispatch_weight;
|
||||
|
||||
// proof size overhead weight
|
||||
let expected_proof_size = EXPECTED_DEFAULT_MESSAGE_LENGTH
|
||||
.saturating_mul(messages_count.saturating_sub(1))
|
||||
.saturating_add(Self::expected_extra_storage_proof_size());
|
||||
let actual_proof_size = proof.size();
|
||||
let proof_size_overhead = Self::storage_proof_size_overhead(
|
||||
actual_proof_size.saturating_sub(expected_proof_size),
|
||||
);
|
||||
|
||||
base_weight
|
||||
.saturating_add(transaction_overhead_from_runtime)
|
||||
.saturating_add(outbound_state_delivery_weight)
|
||||
.saturating_add(messages_dispatch_weight)
|
||||
.saturating_add(proof_size_overhead)
|
||||
}
|
||||
|
||||
/// Weight of confirmation delivery extrinsic.
|
||||
fn receive_messages_delivery_proof_weight(
|
||||
proof: &impl Size,
|
||||
relayers_state: &UnrewardedRelayersState,
|
||||
) -> Weight {
|
||||
// basic components of extrinsic weight
|
||||
let transaction_overhead = Self::receive_messages_delivery_proof_overhead();
|
||||
let transaction_overhead_from_runtime =
|
||||
Self::receive_messages_delivery_proof_overhead_from_runtime();
|
||||
let messages_overhead =
|
||||
Self::receive_messages_delivery_proof_messages_overhead(relayers_state.total_messages);
|
||||
let relayers_overhead = Self::receive_messages_delivery_proof_relayers_overhead(
|
||||
relayers_state.unrewarded_relayer_entries,
|
||||
);
|
||||
|
||||
// proof size overhead weight
|
||||
let expected_proof_size = Self::expected_extra_storage_proof_size();
|
||||
let actual_proof_size = proof.size();
|
||||
let proof_size_overhead = Self::storage_proof_size_overhead(
|
||||
actual_proof_size.saturating_sub(expected_proof_size),
|
||||
);
|
||||
|
||||
transaction_overhead
|
||||
.saturating_add(transaction_overhead_from_runtime)
|
||||
.saturating_add(messages_overhead)
|
||||
.saturating_add(relayers_overhead)
|
||||
.saturating_add(proof_size_overhead)
|
||||
}
|
||||
|
||||
// Functions that are used by extrinsics weights formulas.
|
||||
|
||||
/// Returns weight that needs to be accounted when message delivery transaction
|
||||
/// (`receive_messages_proof`) is carrying outbound lane state proof.
|
||||
fn receive_messages_proof_outbound_lane_state_overhead() -> Weight {
|
||||
let weight_of_single_message_and_lane_state =
|
||||
Self::receive_single_message_proof_with_outbound_lane_state();
|
||||
let weight_of_single_message = Self::receive_single_message_proof();
|
||||
weight_of_single_message_and_lane_state.saturating_sub(weight_of_single_message)
|
||||
}
|
||||
|
||||
/// Returns weight overhead of delivery confirmation transaction
|
||||
/// (`receive_messages_delivery_proof`).
|
||||
fn receive_messages_delivery_proof_overhead() -> Weight {
|
||||
let weight_of_two_messages_and_two_tx_overheads =
|
||||
Self::receive_delivery_proof_for_single_message().saturating_mul(2);
|
||||
let weight_of_two_messages_and_single_tx_overhead =
|
||||
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
|
||||
weight_of_two_messages_and_two_tx_overheads
|
||||
.saturating_sub(weight_of_two_messages_and_single_tx_overhead)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when receiving confirmations for given a number of
|
||||
/// messages with delivery confirmation transaction (`receive_messages_delivery_proof`).
|
||||
fn receive_messages_delivery_proof_messages_overhead(messages: MessageNonce) -> Weight {
|
||||
let weight_of_two_messages =
|
||||
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
|
||||
let weight_of_single_message = Self::receive_delivery_proof_for_single_message();
|
||||
weight_of_two_messages
|
||||
.saturating_sub(weight_of_single_message)
|
||||
.saturating_mul(messages as _)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when receiving confirmations for given a number of
|
||||
/// relayers entries with delivery confirmation transaction (`receive_messages_delivery_proof`).
|
||||
fn receive_messages_delivery_proof_relayers_overhead(relayers: MessageNonce) -> Weight {
|
||||
let weight_of_two_messages_by_two_relayers =
|
||||
Self::receive_delivery_proof_for_two_messages_by_two_relayers();
|
||||
let weight_of_two_messages_by_single_relayer =
|
||||
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
|
||||
weight_of_two_messages_by_two_relayers
|
||||
.saturating_sub(weight_of_two_messages_by_single_relayer)
|
||||
.saturating_mul(relayers as _)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when storage proof of given size is received
|
||||
/// (either in `receive_messages_proof` or `receive_messages_delivery_proof`).
|
||||
///
|
||||
/// **IMPORTANT**: this overhead is already included in the 'base' transaction cost - e.g. proof
|
||||
/// size depends on messages count or number of entries in the unrewarded relayers set. So this
|
||||
/// shouldn't be added to cost of transaction, but instead should act as a minimal cost that the
|
||||
/// relayer must pay when it relays proof of given size (even if cost based on other parameters
|
||||
/// is less than that cost).
|
||||
fn storage_proof_size_overhead(proof_size: u32) -> Weight {
|
||||
let proof_size_in_bytes = proof_size;
|
||||
let byte_weight = Self::receive_single_n_bytes_message_proof(2) -
|
||||
Self::receive_single_n_bytes_message_proof(1);
|
||||
proof_size_in_bytes * byte_weight
|
||||
}
|
||||
|
||||
// Functions that may be used by runtime developers.
|
||||
|
||||
/// Returns dispatch weight of message of given size.
|
||||
///
|
||||
/// This function would return correct value only if your runtime is configured to run
|
||||
/// `receive_single_message_proof_with_dispatch` benchmark. See its requirements for
|
||||
/// details.
|
||||
fn message_dispatch_weight(message_size: u32) -> Weight {
|
||||
let message_size_in_bytes = message_size;
|
||||
Self::receive_single_n_bytes_message_proof_with_dispatch(message_size_in_bytes)
|
||||
.saturating_sub(Self::receive_single_n_bytes_message_proof(message_size_in_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightInfoExt for () {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: pezframe_system::Config> WeightInfoExt for crate::weights::BridgeWeight<T> {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{tests::mock::TestRuntime, weights::BridgeWeight};
|
||||
|
||||
#[test]
|
||||
fn ensure_default_weights_are_correct() {
|
||||
ensure_weights_are_correct::<BridgeWeight<TestRuntime>>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-relayers"
|
||||
description = "Module used to store relayer rewards and coordinate relayers set."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-bridge-relayers"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-messages = { workspace = true }
|
||||
bp-relayers = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
pezpallet-bridge-grandpa = { workspace = true }
|
||||
pezpallet-bridge-messages = { workspace = true }
|
||||
pezpallet-bridge-teyrchains = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-transaction-payment = { workspace = true }
|
||||
pezsp-arithmetic = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
bp-test-utils = { workspace = true }
|
||||
bp-teyrchains = { workspace = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-utility = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"bp-relayers/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-test-utils/std",
|
||||
"bp-teyrchains/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-bridge-grandpa/std",
|
||||
"pezpallet-bridge-messages/std",
|
||||
"pezpallet-bridge-teyrchains/std",
|
||||
"pezpallet-transaction-payment/std",
|
||||
"pezpallet-utility/std",
|
||||
"scale-info/std",
|
||||
"pezsp-arithmetic/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"tracing/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"bp-relayers/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"bp-teyrchains/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-bridge-grandpa/runtime-benchmarks",
|
||||
"pezpallet-bridge-messages/runtime-benchmarks",
|
||||
"pezpallet-bridge-teyrchains/runtime-benchmarks",
|
||||
"pezpallet-transaction-payment/runtime-benchmarks",
|
||||
"pezpallet-utility/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-bridge-grandpa/try-runtime",
|
||||
"pezpallet-bridge-messages/try-runtime",
|
||||
"pezpallet-bridge-teyrchains/try-runtime",
|
||||
"pezpallet-transaction-payment/try-runtime",
|
||||
"pezpallet-utility/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
integrity-test = []
|
||||
@@ -0,0 +1,14 @@
|
||||
# Bridge Relayers Pezpallet
|
||||
|
||||
The pezpallet serves as a storage for pending bridge relayer rewards. Any runtime component may register reward
|
||||
to some relayer for doing some useful job at some messages lane. Later, the relayer may claim its rewards
|
||||
using the `claim_rewards` call.
|
||||
|
||||
The reward payment procedure is abstracted from the pezpallet code. One of possible implementations, is the
|
||||
[`PayLaneRewardFromAccount`](../../primitives/relayers/src/lib.rs), which just does a `Currency::transfer`
|
||||
call to relayer account from the relayer-rewards account, determined by the message lane id.
|
||||
|
||||
We have two examples of how this pezpallet is used in production. Rewards are registered at the target chain to
|
||||
compensate fees of message delivery transactions (and linked finality delivery calls). At the source chain, rewards
|
||||
are registered during delivery confirmation transactions. You may find more information about that in the
|
||||
[Kusama <> PezkuwiChain bridge](../../docs/pezkuwi-kusama-bridge-overview.md) documentation.
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Benchmarks for the relayers Pezpallet.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::*;
|
||||
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::{assert_ok, weights::Weight};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_runtime::traits::One;
|
||||
|
||||
/// Reward amount that is (hopefully) is larger than existential deposit across all chains.
|
||||
const REWARD_AMOUNT: u32 = u32::MAX;
|
||||
|
||||
/// Pezpallet we're benchmarking here.
|
||||
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
|
||||
|
||||
/// Trait that must be implemented by runtime.
|
||||
pub trait Config<I: 'static = ()>: crate::Config<I> {
|
||||
/// `T::Reward` to use in benchmarks.
|
||||
fn bench_reward() -> Self::Reward;
|
||||
/// Prepare environment for paying given reward for serving given lane.
|
||||
fn prepare_rewards_account(
|
||||
reward_kind: Self::Reward,
|
||||
reward: Self::RewardBalance,
|
||||
) -> Option<BeneficiaryOf<Self, I>>;
|
||||
/// Give enough balance to given account.
|
||||
fn deposit_account(account: Self::AccountId, balance: Self::Balance);
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config<I>, I: 'static>(
|
||||
generic_event: <T as pezpallet::Config<I>>::RuntimeEvent,
|
||||
) {
|
||||
pezframe_system::Pezpallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
#[instance_benchmarks(
|
||||
where
|
||||
BeneficiaryOf<T, I>: From<<T as pezframe_system::Config>::AccountId>,
|
||||
)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn claim_rewards() {
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let reward_kind = T::bench_reward();
|
||||
let reward_balance = T::RewardBalance::from(REWARD_AMOUNT);
|
||||
let _ = T::prepare_rewards_account(reward_kind, reward_balance);
|
||||
RelayerRewards::<T, I>::insert(&relayer, reward_kind, reward_balance);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(relayer.clone()), reward_kind);
|
||||
|
||||
// we can't check anything here, because `PaymentProcedure` is responsible for
|
||||
// payment logic, so we assume that if call has succeeded, the procedure has
|
||||
// also completed successfully
|
||||
assert_last_event::<T, I>(
|
||||
Event::RewardPaid {
|
||||
relayer: relayer.clone(),
|
||||
reward_kind,
|
||||
reward_balance,
|
||||
beneficiary: relayer.into(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn claim_rewards_to() -> Result<(), BenchmarkError> {
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let reward_kind = T::bench_reward();
|
||||
let reward_balance = T::RewardBalance::from(REWARD_AMOUNT);
|
||||
|
||||
let Some(alternative_beneficiary) = T::prepare_rewards_account(reward_kind, reward_balance)
|
||||
else {
|
||||
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
|
||||
};
|
||||
RelayerRewards::<T, I>::insert(&relayer, reward_kind, reward_balance);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(relayer.clone()), reward_kind, alternative_beneficiary.clone());
|
||||
|
||||
// we can't check anything here, because `PaymentProcedure` is responsible for
|
||||
// payment logic, so we assume that if call has succeeded, the procedure has
|
||||
// also completed successfully
|
||||
assert_last_event::<T, I>(
|
||||
Event::RewardPaid {
|
||||
relayer: relayer.clone(),
|
||||
reward_kind,
|
||||
reward_balance,
|
||||
beneficiary: alternative_beneficiary,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register() {
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let valid_till = pezframe_system::Pezpallet::<T>::block_number()
|
||||
.saturating_add(crate::Pezpallet::<T, I>::required_registration_lease())
|
||||
.saturating_add(One::one())
|
||||
.saturating_add(One::one());
|
||||
T::deposit_account(relayer.clone(), crate::Pezpallet::<T, I>::required_stake());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(relayer.clone()), valid_till);
|
||||
|
||||
assert!(crate::Pezpallet::<T, I>::is_registration_active(&relayer));
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn deregister() {
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let valid_till = pezframe_system::Pezpallet::<T>::block_number()
|
||||
.saturating_add(crate::Pezpallet::<T, I>::required_registration_lease())
|
||||
.saturating_add(One::one())
|
||||
.saturating_add(One::one());
|
||||
T::deposit_account(relayer.clone(), crate::Pezpallet::<T, I>::required_stake());
|
||||
crate::Pezpallet::<T, I>::register(RawOrigin::Signed(relayer.clone()).into(), valid_till)
|
||||
.unwrap();
|
||||
pezframe_system::Pezpallet::<T>::set_block_number(valid_till.saturating_add(One::one()));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(relayer.clone()));
|
||||
|
||||
assert!(!crate::Pezpallet::<T, I>::is_registration_active(&relayer));
|
||||
}
|
||||
|
||||
// Benchmark `slash_and_deregister` method of the pezpallet. We are adding this weight to
|
||||
// the weight of message delivery call if `BridgeRelayersTransactionExtension` signed extension
|
||||
// is deployed at runtime level.
|
||||
#[benchmark]
|
||||
fn slash_and_deregister() {
|
||||
// prepare and register relayer account
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let valid_till = pezframe_system::Pezpallet::<T>::block_number()
|
||||
.saturating_add(crate::Pezpallet::<T, I>::required_registration_lease())
|
||||
.saturating_add(One::one())
|
||||
.saturating_add(One::one());
|
||||
T::deposit_account(relayer.clone(), crate::Pezpallet::<T, I>::required_stake());
|
||||
assert_ok!(crate::Pezpallet::<T, I>::register(
|
||||
RawOrigin::Signed(relayer.clone()).into(),
|
||||
valid_till
|
||||
));
|
||||
|
||||
// create slash destination account
|
||||
let slash_destination: T::AccountId = whitelisted_caller();
|
||||
T::deposit_account(slash_destination.clone(), Zero::zero());
|
||||
|
||||
#[block]
|
||||
{
|
||||
crate::Pezpallet::<T, I>::slash_and_deregister(
|
||||
&relayer,
|
||||
bp_relayers::ExplicitOrAccountParams::Explicit::<_, ()>(slash_destination),
|
||||
);
|
||||
}
|
||||
|
||||
assert!(!crate::Pezpallet::<T, I>::is_registration_active(&relayer));
|
||||
}
|
||||
|
||||
// Benchmark `register_relayer_reward` method of the pezpallet. We are adding this weight to
|
||||
// the weight of message delivery call if `BridgeRelayersTransactionExtension` signed extension
|
||||
// is deployed at runtime level.
|
||||
#[benchmark]
|
||||
fn register_relayer_reward() {
|
||||
let reward_kind = T::bench_reward();
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
|
||||
#[block]
|
||||
{
|
||||
crate::Pezpallet::<T, I>::register_relayer_reward(reward_kind, &relayer, One::one());
|
||||
}
|
||||
|
||||
assert_eq!(RelayerRewards::<T, I>::get(relayer, &reward_kind), Some(One::one()));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pezpallet, crate::mock::new_test_ext(), crate::mock::TestRuntime);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Adapter that allows using `pezpallet-bridge-relayers` as a signed extension in the
|
||||
//! bridge with remote GRANDPA chain.
|
||||
|
||||
use crate::{
|
||||
extension::verify_messages_call_succeeded, Config as BridgeRelayersConfig, LOG_TARGET,
|
||||
};
|
||||
|
||||
use bp_relayers::{BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
|
||||
use pezbp_runtime::{Chain, StaticStrProvider};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::dispatch::{DispatchInfo, PostDispatchInfo};
|
||||
use pezframe_system::Config as SystemConfig;
|
||||
use pezpallet_bridge_grandpa::{
|
||||
CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig,
|
||||
SubmitFinalityProofHelper,
|
||||
};
|
||||
use pezpallet_bridge_messages::{
|
||||
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{Dispatchable, Get},
|
||||
transaction_validity::{TransactionPriority, TransactionValidityError},
|
||||
Saturating,
|
||||
};
|
||||
|
||||
/// Adapter to be used in signed extension configuration, when bridging with remote
|
||||
/// chains that are using GRANDPA finality.
|
||||
pub struct WithGrandpaChainExtensionConfig<
|
||||
// signed extension identifier
|
||||
IdProvider,
|
||||
// runtime that implements `BridgeMessagesConfig<BridgeMessagesPalletInstance>`, which
|
||||
// uses `BridgeGrandpaConfig<BridgeGrandpaPalletInstance>` to receive messages and
|
||||
// confirmations from the remote chain.
|
||||
Runtime,
|
||||
// batch call unpacker
|
||||
BatchCallUnpacker,
|
||||
// instance of the `pezpallet-bridge-grandpa`, tracked by this extension
|
||||
BridgeGrandpaPalletInstance,
|
||||
// instance of BridgedChain `pezpallet-bridge-messages`, tracked by this extension
|
||||
BridgeMessagesPalletInstance,
|
||||
// instance of `pezpallet-bridge-relayers`, tracked by this extension
|
||||
BridgeRelayersPalletInstance,
|
||||
// message delivery transaction priority boost for every additional message
|
||||
PriorityBoostPerMessage,
|
||||
>(
|
||||
PhantomData<(
|
||||
IdProvider,
|
||||
Runtime,
|
||||
BatchCallUnpacker,
|
||||
BridgeGrandpaPalletInstance,
|
||||
BridgeMessagesPalletInstance,
|
||||
BridgeRelayersPalletInstance,
|
||||
PriorityBoostPerMessage,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<ID, R, BCU, GI, MI, RI, P> ExtensionConfig
|
||||
for WithGrandpaChainExtensionConfig<ID, R, BCU, GI, MI, RI, P>
|
||||
where
|
||||
ID: StaticStrProvider,
|
||||
R: BridgeRelayersConfig<RI>
|
||||
+ BridgeMessagesConfig<MI, BridgedChain = pezpallet_bridge_grandpa::BridgedChain<R, GI>>
|
||||
+ BridgeGrandpaConfig<GI>,
|
||||
BCU: BatchCallUnpacker<R>,
|
||||
GI: 'static,
|
||||
MI: 'static,
|
||||
RI: 'static,
|
||||
P: Get<TransactionPriority>,
|
||||
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
||||
+ BridgeGrandpaCallSubtype<R, GI>
|
||||
+ BridgeMessagesCallSubType<R, MI>,
|
||||
{
|
||||
type IdProvider = ID;
|
||||
type Runtime = R;
|
||||
type BridgeMessagesPalletInstance = MI;
|
||||
type BridgeRelayersPalletInstance = RI;
|
||||
type PriorityBoostPerMessage = P;
|
||||
type RemoteGrandpaChainBlockNumber = pezpallet_bridge_grandpa::BridgedBlockNumber<R, GI>;
|
||||
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
|
||||
|
||||
fn parse_and_check_for_obsolete_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<
|
||||
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
|
||||
TransactionValidityError,
|
||||
> {
|
||||
let calls = BCU::unpack(call, 2);
|
||||
let total_calls = calls.len();
|
||||
let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
|
||||
|
||||
let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info());
|
||||
let relay_finality_call =
|
||||
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
|
||||
|
||||
Ok(match (total_calls, relay_finality_call, msgs_call) {
|
||||
(2, Some(relay_finality_call), Some(msgs_call)) =>
|
||||
Some(ExtensionCallInfo::RelayFinalityAndMsgs(relay_finality_call, msgs_call)),
|
||||
(1, None, Some(msgs_call)) => Some(ExtensionCallInfo::Msgs(msgs_call)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_obsolete_parsed_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<&R::RuntimeCall, TransactionValidityError> {
|
||||
call.check_obsolete_submit_finality_proof()?;
|
||||
call.check_obsolete_call()?;
|
||||
Ok(call)
|
||||
}
|
||||
|
||||
fn check_call_result(
|
||||
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &R::AccountId,
|
||||
) -> bool {
|
||||
verify_submit_finality_proof_succeeded::<Self, GI>(call_info, call_data, relayer) &&
|
||||
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the batch call contains the GRANDPA chain state update call, verify that it
|
||||
/// has been successful.
|
||||
///
|
||||
/// Only returns false when GRANDPA chain state update call has failed.
|
||||
pub(crate) fn verify_submit_finality_proof_succeeded<C, GI>(
|
||||
call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber, C::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &<C::Runtime as SystemConfig>::AccountId,
|
||||
) -> bool
|
||||
where
|
||||
C: ExtensionConfig,
|
||||
GI: 'static,
|
||||
C::Runtime: BridgeGrandpaConfig<GI>,
|
||||
<C::Runtime as BridgeGrandpaConfig<GI>>::BridgedChain:
|
||||
Chain<BlockNumber = C::RemoteGrandpaChainBlockNumber>,
|
||||
{
|
||||
let Some(finality_proof_info) = call_info.submit_finality_proof_info() else { return true };
|
||||
|
||||
if !SubmitFinalityProofHelper::<C::Runtime, GI>::was_successful(
|
||||
finality_proof_info.block_number,
|
||||
) {
|
||||
// we only refund relayer if all calls have updated chain state
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
id_provider=%C::IdProvider::STR,
|
||||
lane_id=?call_info.messages_call_info().lane_id(),
|
||||
?relayer,
|
||||
"Relayer has submitted invalid GRANDPA chain finality proof"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// there's a conflict between how bridge GRANDPA pezpallet works and a `utility.batchAll`
|
||||
// transaction. If relay chain header is mandatory, the GRANDPA pezpallet returns
|
||||
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
|
||||
// `utility.batchAll` transaction always requires payment. But in both cases we'll
|
||||
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
|
||||
// to submit dedicated transaction.
|
||||
|
||||
// submitter has means to include extra weight/bytes in the `submit_finality_proof`
|
||||
// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
|
||||
call_data.extra_weight.saturating_accrue(finality_proof_info.extra_weight);
|
||||
call_data.extra_size.saturating_accrue(finality_proof_info.extra_size);
|
||||
|
||||
true
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Adapter that allows using `pezpallet-bridge-relayers` as a signed extension in the
|
||||
//! bridge with any remote chain. This adapter does not refund any finality transactions.
|
||||
|
||||
use crate::{extension::verify_messages_call_succeeded, Config as BridgeRelayersConfig};
|
||||
|
||||
use bp_relayers::{ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
|
||||
use pezbp_runtime::StaticStrProvider;
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::dispatch::{DispatchInfo, PostDispatchInfo};
|
||||
use pezpallet_bridge_messages::{
|
||||
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{Dispatchable, Get},
|
||||
transaction_validity::{TransactionPriority, TransactionValidityError},
|
||||
};
|
||||
|
||||
/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation
|
||||
/// transactions. Finality transactions are not refunded.
|
||||
pub struct WithMessagesExtensionConfig<
|
||||
IdProvider,
|
||||
Runtime,
|
||||
BridgeMessagesPalletInstance,
|
||||
BridgeRelayersPalletInstance,
|
||||
PriorityBoostPerMessage,
|
||||
>(
|
||||
PhantomData<(
|
||||
// signed extension identifier
|
||||
IdProvider,
|
||||
// runtime with `pezpallet-bridge-messages` pezpallet deployed
|
||||
Runtime,
|
||||
// instance of BridgedChain `pezpallet-bridge-messages`, tracked by this extension
|
||||
BridgeMessagesPalletInstance,
|
||||
// instance of `pezpallet-bridge-relayers`, tracked by this extension
|
||||
BridgeRelayersPalletInstance,
|
||||
// message delivery transaction priority boost for every additional message
|
||||
PriorityBoostPerMessage,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<ID, R, MI, RI, P> ExtensionConfig for WithMessagesExtensionConfig<ID, R, MI, RI, P>
|
||||
where
|
||||
ID: StaticStrProvider,
|
||||
R: BridgeRelayersConfig<RI> + BridgeMessagesConfig<MI>,
|
||||
MI: 'static,
|
||||
RI: 'static,
|
||||
P: Get<TransactionPriority>,
|
||||
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
||||
+ BridgeMessagesCallSubType<R, MI>,
|
||||
{
|
||||
type IdProvider = ID;
|
||||
type Runtime = R;
|
||||
type BridgeMessagesPalletInstance = MI;
|
||||
type BridgeRelayersPalletInstance = RI;
|
||||
type PriorityBoostPerMessage = P;
|
||||
type RemoteGrandpaChainBlockNumber = ();
|
||||
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
|
||||
|
||||
fn parse_and_check_for_obsolete_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<
|
||||
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
|
||||
TransactionValidityError,
|
||||
> {
|
||||
let call = Self::check_obsolete_parsed_call(call)?;
|
||||
Ok(call.call_info().map(ExtensionCallInfo::Msgs))
|
||||
}
|
||||
|
||||
fn check_obsolete_parsed_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<&R::RuntimeCall, TransactionValidityError> {
|
||||
call.check_obsolete_call()?;
|
||||
Ok(call)
|
||||
}
|
||||
|
||||
fn check_call_result(
|
||||
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &R::AccountId,
|
||||
) -> bool {
|
||||
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,431 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Bridge transaction priority calculator.
|
||||
//!
|
||||
//! We want to prioritize message delivery transactions with more messages over
|
||||
//! transactions with less messages. That's because we reject delivery transactions
|
||||
//! if it contains already delivered message. And if some transaction delivers
|
||||
//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will
|
||||
//! be rejected. This can lower bridge throughput down to one message per block.
|
||||
|
||||
use pezframe_support::traits::Get;
|
||||
use pezsp_runtime::transaction_validity::TransactionPriority;
|
||||
|
||||
// reexport everything from `integrity_tests` module
|
||||
#[allow(unused_imports)]
|
||||
pub use integrity_tests::*;
|
||||
|
||||
/// We'll deal with different bridge items here - messages, headers, ...
|
||||
/// To avoid being too verbose with generic code, let's just define a separate alias.
|
||||
pub type ItemCount = u64;
|
||||
|
||||
/// Compute priority boost for transaction that brings given number of bridge
|
||||
/// items (messages, headers, ...), when every additional item adds `PriorityBoostPerItem`
|
||||
/// to transaction priority.
|
||||
pub fn compute_priority_boost<PriorityBoostPerItem>(n_items: ItemCount) -> TransactionPriority
|
||||
where
|
||||
PriorityBoostPerItem: Get<TransactionPriority>,
|
||||
{
|
||||
// we don't want any boost for transaction with single (additional) item => minus one
|
||||
PriorityBoostPerItem::get().saturating_mul(n_items.saturating_sub(1))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "integrity-test"))]
|
||||
mod integrity_tests {}
|
||||
|
||||
#[cfg(feature = "integrity-test")]
|
||||
mod integrity_tests {
|
||||
use super::{compute_priority_boost, ItemCount};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use pezbp_runtime::PreComputedSize;
|
||||
use pezframe_support::{
|
||||
dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo},
|
||||
traits::Get,
|
||||
};
|
||||
use pezpallet_transaction_payment::OnChargeTransaction;
|
||||
use pezsp_runtime::{
|
||||
traits::{Dispatchable, UniqueSaturatedInto, Zero},
|
||||
transaction_validity::TransactionPriority,
|
||||
FixedPointOperand, SaturatedConversion, Saturating,
|
||||
};
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as pezpallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<
|
||||
T,
|
||||
>>::Balance;
|
||||
|
||||
/// Ensures that the value of `PriorityBoostPerItem` matches the value of
|
||||
/// `tip_boost_per_item`.
|
||||
///
|
||||
/// We want two transactions, `TX1` with `N` items and `TX2` with `N+1` items, have almost
|
||||
/// the same priority if we'll add `tip_boost_per_item` tip to the `TX1`. We want to be sure
|
||||
/// that if we add plain `PriorityBoostPerItem` priority to `TX1`, the priority will be close
|
||||
/// to `TX2` as well.
|
||||
fn ensure_priority_boost_is_sane<PriorityBoostPerItem, Balance>(
|
||||
param_name: &str,
|
||||
max_items: ItemCount,
|
||||
tip_boost_per_item: Balance,
|
||||
estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority,
|
||||
) where
|
||||
PriorityBoostPerItem: Get<TransactionPriority>,
|
||||
ItemCount: UniqueSaturatedInto<Balance>,
|
||||
Balance: FixedPointOperand + Zero,
|
||||
{
|
||||
let priority_boost_per_item = PriorityBoostPerItem::get();
|
||||
for n_items in 1..=max_items {
|
||||
let base_priority = estimate_priority(n_items, Zero::zero());
|
||||
let priority_boost = compute_priority_boost::<PriorityBoostPerItem>(n_items);
|
||||
let priority_with_boost = base_priority
|
||||
.checked_add(priority_boost)
|
||||
.expect("priority overflow: try lowering `max_items` or `tip_boost_per_item`?");
|
||||
|
||||
let tip = tip_boost_per_item.saturating_mul((n_items - 1).unique_saturated_into());
|
||||
let priority_with_tip = estimate_priority(1, tip);
|
||||
|
||||
const ERROR_MARGIN: TransactionPriority = 5; // 5%
|
||||
if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) /
|
||||
priority_with_tip >
|
||||
ERROR_MARGIN
|
||||
{
|
||||
panic!(
|
||||
"The {param_name} value ({}) must be fixed to: {}",
|
||||
priority_boost_per_item,
|
||||
compute_priority_boost_per_item(
|
||||
max_items,
|
||||
tip_boost_per_item,
|
||||
estimate_priority
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute priority boost that we give to bridge transaction for every
|
||||
/// additional bridge item.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn compute_priority_boost_per_item<Balance>(
|
||||
max_items: ItemCount,
|
||||
tip_boost_per_item: Balance,
|
||||
estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
ItemCount: UniqueSaturatedInto<Balance>,
|
||||
Balance: FixedPointOperand + Zero,
|
||||
{
|
||||
// estimate priority of transaction that delivers one item and has large tip
|
||||
let small_with_tip_priority =
|
||||
estimate_priority(1, tip_boost_per_item.saturating_mul(max_items.saturated_into()));
|
||||
// estimate priority of transaction that delivers maximal number of items, but has no tip
|
||||
let large_without_tip_priority = estimate_priority(max_items, Zero::zero());
|
||||
|
||||
small_with_tip_priority
|
||||
.saturating_sub(large_without_tip_priority)
|
||||
.saturating_div(max_items - 1)
|
||||
}
|
||||
|
||||
/// Computations, specific to bridge relay chains transactions.
|
||||
pub mod per_relay_header {
|
||||
use super::*;
|
||||
|
||||
use bp_header_pez_chain::{
|
||||
max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa,
|
||||
};
|
||||
use pezpallet_bridge_grandpa::WeightInfoExt;
|
||||
|
||||
/// Ensures that the value of `PriorityBoostPerHeader` matches the value of
|
||||
/// `tip_boost_per_header`.
|
||||
///
|
||||
/// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have
|
||||
/// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want
|
||||
/// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority
|
||||
/// will be close to `TX2` as well.
|
||||
pub fn ensure_priority_boost_is_sane<Runtime, GrandpaInstance, PriorityBoostPerHeader>(
|
||||
tip_boost_per_header: BalanceOf<Runtime>,
|
||||
) where
|
||||
Runtime:
|
||||
pezpallet_transaction_payment::Config + pezpallet_bridge_grandpa::Config<GrandpaInstance>,
|
||||
GrandpaInstance: 'static,
|
||||
PriorityBoostPerHeader: Get<TransactionPriority>,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// the meaning of `max_items` here is different when comparing with message
|
||||
// transactions - with messages we have a strict limit on maximal number of
|
||||
// messages we can fit into a single transaction. With headers, current best
|
||||
// header may be improved by any "number of items". But this number is only
|
||||
// used to verify priority boost, so it should be fine to select this arbitrary
|
||||
// value - it SHALL NOT affect any value, it just adds more tests for the value.
|
||||
let maximal_improved_by = 4_096;
|
||||
super::ensure_priority_boost_is_sane::<PriorityBoostPerHeader, BalanceOf<Runtime>>(
|
||||
"PriorityBoostPerRelayHeader",
|
||||
maximal_improved_by,
|
||||
tip_boost_per_header,
|
||||
|_n_headers, tip| {
|
||||
estimate_relay_header_submit_transaction_priority::<Runtime, GrandpaInstance>(
|
||||
tip,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Estimate relay header delivery transaction priority.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn estimate_relay_header_submit_transaction_priority<Runtime, GrandpaInstance>(
|
||||
tip: BalanceOf<Runtime>,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
Runtime:
|
||||
pezpallet_transaction_payment::Config + pezpallet_bridge_grandpa::Config<GrandpaInstance>,
|
||||
GrandpaInstance: 'static,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// just an estimation of extra transaction bytes that are added to every transaction
|
||||
// (including signature, signed extensions extra and etc + in our case it includes
|
||||
// all call arguments except the proof itself)
|
||||
let base_tx_size = 512;
|
||||
// let's say we are relaying largest relay chain headers
|
||||
let tx_call_size = max_expected_submit_finality_proof_arguments_size::<
|
||||
Runtime::BridgedChain,
|
||||
>(true, Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1);
|
||||
|
||||
// finally we are able to estimate transaction size and weight
|
||||
let transaction_size = base_tx_size.saturating_add(tx_call_size);
|
||||
let transaction_weight = <Runtime as ::pezpallet_bridge_grandpa::Config<
|
||||
GrandpaInstance,
|
||||
>>::WeightInfo::submit_finality_proof_weight(
|
||||
Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1,
|
||||
Runtime::BridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
|
||||
);
|
||||
|
||||
pezpallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
|
||||
&DispatchInfo {
|
||||
call_weight: transaction_weight,
|
||||
extension_weight: Default::default(),
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
},
|
||||
transaction_size as _,
|
||||
tip,
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Computations, specific to bridge teyrchains transactions.
|
||||
pub mod per_teyrchain_header {
|
||||
use super::*;
|
||||
|
||||
use pezbp_runtime::Teyrchain;
|
||||
use pezpallet_bridge_teyrchains::WeightInfoExt;
|
||||
|
||||
/// Ensures that the value of `PriorityBoostPerHeader` matches the value of
|
||||
/// `tip_boost_per_header`.
|
||||
///
|
||||
/// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have
|
||||
/// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want
|
||||
/// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority
|
||||
/// will be close to `TX2` as well.
|
||||
pub fn ensure_priority_boost_is_sane<
|
||||
Runtime,
|
||||
TeyrchainsInstance,
|
||||
Para,
|
||||
PriorityBoostPerHeader,
|
||||
>(
|
||||
tip_boost_per_header: BalanceOf<Runtime>,
|
||||
) where
|
||||
Runtime: pezpallet_transaction_payment::Config
|
||||
+ pezpallet_bridge_teyrchains::Config<TeyrchainsInstance>,
|
||||
TeyrchainsInstance: 'static,
|
||||
Para: Teyrchain,
|
||||
PriorityBoostPerHeader: Get<TransactionPriority>,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// the meaning of `max_items` here is different when comparing with message
|
||||
// transactions - with messages we have a strict limit on maximal number of
|
||||
// messages we can fit into a single transaction. With headers, current best
|
||||
// header may be improved by any "number of items". But this number is only
|
||||
// used to verify priority boost, so it should be fine to select this arbitrary
|
||||
// value - it SHALL NOT affect any value, it just adds more tests for the value.
|
||||
let maximal_improved_by = 4_096;
|
||||
super::ensure_priority_boost_is_sane::<PriorityBoostPerHeader, BalanceOf<Runtime>>(
|
||||
"PriorityBoostPerTeyrchainHeader",
|
||||
maximal_improved_by,
|
||||
tip_boost_per_header,
|
||||
|_n_headers, tip| {
|
||||
estimate_teyrchain_header_submit_transaction_priority::<
|
||||
Runtime,
|
||||
TeyrchainsInstance,
|
||||
Para,
|
||||
>(tip)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Estimate teyrchain header delivery transaction priority.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn estimate_teyrchain_header_submit_transaction_priority<
|
||||
Runtime,
|
||||
TeyrchainsInstance,
|
||||
Para,
|
||||
>(
|
||||
tip: BalanceOf<Runtime>,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
Runtime: pezpallet_transaction_payment::Config
|
||||
+ pezpallet_bridge_teyrchains::Config<TeyrchainsInstance>,
|
||||
TeyrchainsInstance: 'static,
|
||||
Para: Teyrchain,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// just an estimation of extra transaction bytes that are added to every transaction
|
||||
// (including signature, signed extensions extra and etc + in our case it includes
|
||||
// all call arguments except the proof itself)
|
||||
let base_tx_size = 512;
|
||||
// let's say we are relaying largest teyrchain headers and proof takes some more bytes
|
||||
let tx_call_size = <Runtime as pezpallet_bridge_teyrchains::Config<
|
||||
TeyrchainsInstance,
|
||||
>>::WeightInfo::expected_extra_storage_proof_size()
|
||||
.saturating_add(Para::MAX_HEADER_SIZE);
|
||||
|
||||
// finally we are able to estimate transaction size and weight
|
||||
let transaction_size = base_tx_size.saturating_add(tx_call_size);
|
||||
let transaction_weight = <Runtime as pezpallet_bridge_teyrchains::Config<
|
||||
TeyrchainsInstance,
|
||||
>>::WeightInfo::submit_teyrchain_heads_weight(
|
||||
Runtime::DbWeight::get(),
|
||||
&PreComputedSize(transaction_size as _),
|
||||
// just one teyrchain - all other submissions won't receive any boost
|
||||
1,
|
||||
);
|
||||
|
||||
pezpallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
|
||||
&DispatchInfo {
|
||||
call_weight: transaction_weight,
|
||||
extension_weight: Default::default(),
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
},
|
||||
transaction_size as _,
|
||||
tip,
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Computations, specific to bridge messages transactions.
|
||||
pub mod per_message {
|
||||
use super::*;
|
||||
|
||||
use bp_messages::ChainWithMessages;
|
||||
use pezpallet_bridge_messages::WeightInfoExt;
|
||||
|
||||
/// Ensures that the value of `PriorityBoostPerMessage` matches the value of
|
||||
/// `tip_boost_per_message`.
|
||||
///
|
||||
/// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have
|
||||
/// almost the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want
|
||||
/// to be sure that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the
|
||||
/// priority will be close to `TX2` as well.
|
||||
pub fn ensure_priority_boost_is_sane<Runtime, MessagesInstance, PriorityBoostPerMessage>(
|
||||
tip_boost_per_message: BalanceOf<Runtime>,
|
||||
) where
|
||||
Runtime: pezpallet_transaction_payment::Config
|
||||
+ pezpallet_bridge_messages::Config<MessagesInstance>,
|
||||
MessagesInstance: 'static,
|
||||
PriorityBoostPerMessage: Get<TransactionPriority>,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
let maximal_messages_in_delivery_transaction =
|
||||
Runtime::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
|
||||
super::ensure_priority_boost_is_sane::<PriorityBoostPerMessage, BalanceOf<Runtime>>(
|
||||
"PriorityBoostPerMessage",
|
||||
maximal_messages_in_delivery_transaction,
|
||||
tip_boost_per_message,
|
||||
|n_messages, tip| {
|
||||
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(
|
||||
n_messages, tip,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Estimate message delivery transaction priority.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn estimate_message_delivery_transaction_priority<Runtime, MessagesInstance>(
|
||||
messages: MessageNonce,
|
||||
tip: BalanceOf<Runtime>,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
Runtime: pezpallet_transaction_payment::Config
|
||||
+ pezpallet_bridge_messages::Config<MessagesInstance>,
|
||||
MessagesInstance: 'static,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// just an estimation of extra transaction bytes that are added to every transaction
|
||||
// (including signature, signed extensions extra and etc + in our case it includes
|
||||
// all call arguments except the proof itself)
|
||||
let base_tx_size = 512;
|
||||
// let's say we are relaying similar small messages and for every message we add more
|
||||
// trie nodes to the proof (x0.5 because we expect some nodes to be reused)
|
||||
let estimated_message_size = 512;
|
||||
// let's say all our messages have the same dispatch weight
|
||||
let estimated_message_dispatch_weight = <Runtime as pezpallet_bridge_messages::Config<
|
||||
MessagesInstance,
|
||||
>>::WeightInfo::message_dispatch_weight(
|
||||
estimated_message_size
|
||||
);
|
||||
// messages proof argument size is (for every message) messages size + some additional
|
||||
// trie nodes. Some of them are reused by different messages, so let's take 2/3 of
|
||||
// default "overhead" constant
|
||||
let messages_proof_size = <Runtime as pezpallet_bridge_messages::Config<
|
||||
MessagesInstance,
|
||||
>>::WeightInfo::expected_extra_storage_proof_size()
|
||||
.saturating_mul(2)
|
||||
.saturating_div(3)
|
||||
.saturating_add(estimated_message_size)
|
||||
.saturating_mul(messages as _);
|
||||
|
||||
// finally we are able to estimate transaction size and weight
|
||||
let transaction_size = base_tx_size.saturating_add(messages_proof_size);
|
||||
let transaction_weight = <Runtime as pezpallet_bridge_messages::Config<
|
||||
MessagesInstance,
|
||||
>>::WeightInfo::receive_messages_proof_weight(
|
||||
&PreComputedSize(transaction_size as _),
|
||||
messages as _,
|
||||
estimated_message_dispatch_weight.saturating_mul(messages),
|
||||
);
|
||||
|
||||
pezpallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
|
||||
&DispatchInfo {
|
||||
call_weight: transaction_weight,
|
||||
extension_weight: Default::default(),
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
},
|
||||
transaction_size as _,
|
||||
tip,
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Adapter that allows using `pezpallet-bridge-relayers` as a signed extension in the
|
||||
//! bridge with remote teyrchain.
|
||||
|
||||
use crate::{
|
||||
extension::{
|
||||
grandpa_adapter::verify_submit_finality_proof_succeeded, verify_messages_call_succeeded,
|
||||
},
|
||||
Config as BridgeRelayersConfig, LOG_TARGET,
|
||||
};
|
||||
|
||||
use bp_relayers::{BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
|
||||
use pezbp_runtime::{StaticStrProvider, Teyrchain};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::dispatch::{DispatchInfo, PostDispatchInfo};
|
||||
use pezframe_system::Config as SystemConfig;
|
||||
use pezpallet_bridge_grandpa::{
|
||||
CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig,
|
||||
};
|
||||
use pezpallet_bridge_messages::{
|
||||
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
|
||||
};
|
||||
use pezpallet_bridge_teyrchains::{
|
||||
CallSubType as BridgeTeyrchainsCallSubtype, Config as BridgeTeyrchainsConfig,
|
||||
SubmitTeyrchainHeadsHelper,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{Dispatchable, Get},
|
||||
transaction_validity::{TransactionPriority, TransactionValidityError},
|
||||
};
|
||||
|
||||
/// Adapter to be used in signed extension configuration, when bridging with remote teyrchains.
|
||||
pub struct WithTeyrchainExtensionConfig<
|
||||
// signed extension identifier
|
||||
IdProvider,
|
||||
// runtime that implements `BridgeMessagesConfig<BridgeMessagesPalletInstance>`, which
|
||||
// uses `BridgeTeyrchainsConfig<BridgeTeyrchainsPalletInstance>` to receive messages and
|
||||
// confirmations from the remote chain.
|
||||
Runtime,
|
||||
// batch call unpacker
|
||||
BatchCallUnpacker,
|
||||
// instance of the `pezpallet-bridge-teyrchains`, tracked by this extension
|
||||
BridgeTeyrchainsPalletInstance,
|
||||
// instance of BridgedChain `pezpallet-bridge-messages`, tracked by this extension
|
||||
BridgeMessagesPalletInstance,
|
||||
// instance of `pezpallet-bridge-relayers`, tracked by this extension
|
||||
BridgeRelayersPalletInstance,
|
||||
// message delivery transaction priority boost for every additional message
|
||||
PriorityBoostPerMessage,
|
||||
>(
|
||||
PhantomData<(
|
||||
IdProvider,
|
||||
Runtime,
|
||||
BatchCallUnpacker,
|
||||
BridgeTeyrchainsPalletInstance,
|
||||
BridgeMessagesPalletInstance,
|
||||
BridgeRelayersPalletInstance,
|
||||
PriorityBoostPerMessage,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<ID, R, BCU, PI, MI, RI, P> ExtensionConfig
|
||||
for WithTeyrchainExtensionConfig<ID, R, BCU, PI, MI, RI, P>
|
||||
where
|
||||
ID: StaticStrProvider,
|
||||
R: BridgeRelayersConfig<RI>
|
||||
+ BridgeMessagesConfig<MI>
|
||||
+ BridgeTeyrchainsConfig<PI>
|
||||
+ BridgeGrandpaConfig<R::BridgesGrandpaPalletInstance>,
|
||||
BCU: BatchCallUnpacker<R>,
|
||||
PI: 'static,
|
||||
MI: 'static,
|
||||
RI: 'static,
|
||||
P: Get<TransactionPriority>,
|
||||
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
||||
+ BridgeGrandpaCallSubtype<R, R::BridgesGrandpaPalletInstance>
|
||||
+ BridgeTeyrchainsCallSubtype<R, PI>
|
||||
+ BridgeMessagesCallSubType<R, MI>,
|
||||
<R as BridgeMessagesConfig<MI>>::BridgedChain: Teyrchain,
|
||||
{
|
||||
type IdProvider = ID;
|
||||
type Runtime = R;
|
||||
type BridgeMessagesPalletInstance = MI;
|
||||
type BridgeRelayersPalletInstance = RI;
|
||||
type PriorityBoostPerMessage = P;
|
||||
type RemoteGrandpaChainBlockNumber =
|
||||
pezpallet_bridge_grandpa::BridgedBlockNumber<R, R::BridgesGrandpaPalletInstance>;
|
||||
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
|
||||
|
||||
fn parse_and_check_for_obsolete_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<
|
||||
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
|
||||
TransactionValidityError,
|
||||
> {
|
||||
let calls = BCU::unpack(call, 3);
|
||||
let total_calls = calls.len();
|
||||
let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
|
||||
|
||||
let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info());
|
||||
let para_finality_call = calls.next().transpose()?.and_then(|c| {
|
||||
let r = c.submit_teyrchain_heads_info_for(
|
||||
<R as BridgeMessagesConfig<Self::BridgeMessagesPalletInstance>>::BridgedChain::TEYRCHAIN_ID,
|
||||
);
|
||||
r
|
||||
});
|
||||
let relay_finality_call =
|
||||
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
|
||||
Ok(match (total_calls, relay_finality_call, para_finality_call, msgs_call) {
|
||||
(3, Some(relay_finality_call), Some(para_finality_call), Some(msgs_call)) =>
|
||||
Some(ExtensionCallInfo::AllFinalityAndMsgs(
|
||||
relay_finality_call,
|
||||
para_finality_call,
|
||||
msgs_call,
|
||||
)),
|
||||
(2, None, Some(para_finality_call), Some(msgs_call)) =>
|
||||
Some(ExtensionCallInfo::TeyrchainFinalityAndMsgs(para_finality_call, msgs_call)),
|
||||
(1, None, None, Some(msgs_call)) => Some(ExtensionCallInfo::Msgs(msgs_call)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_obsolete_parsed_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<&R::RuntimeCall, TransactionValidityError> {
|
||||
call.check_obsolete_submit_finality_proof()?;
|
||||
call.check_obsolete_submit_teyrchain_heads()?;
|
||||
call.check_obsolete_call()?;
|
||||
Ok(call)
|
||||
}
|
||||
|
||||
fn check_call_result(
|
||||
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &R::AccountId,
|
||||
) -> bool {
|
||||
verify_submit_finality_proof_succeeded::<Self, R::BridgesGrandpaPalletInstance>(
|
||||
call_info, call_data, relayer,
|
||||
) && verify_submit_teyrchain_head_succeeded::<Self, PI>(call_info, call_data, relayer) &&
|
||||
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the batch call contains the teyrchain state update call, verify that it
|
||||
/// has been successful.
|
||||
///
|
||||
/// Only returns false when teyrchain state update call has failed.
|
||||
pub(crate) fn verify_submit_teyrchain_head_succeeded<C, PI>(
|
||||
call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber, C::LaneId>,
|
||||
_call_data: &mut ExtensionCallData,
|
||||
relayer: &<C::Runtime as SystemConfig>::AccountId,
|
||||
) -> bool
|
||||
where
|
||||
C: ExtensionConfig,
|
||||
PI: 'static,
|
||||
C::Runtime: BridgeTeyrchainsConfig<PI>,
|
||||
{
|
||||
let Some(para_proof_info) = call_info.submit_teyrchain_heads_info() else { return true };
|
||||
|
||||
if !SubmitTeyrchainHeadsHelper::<C::Runtime, PI>::was_successful(para_proof_info) {
|
||||
// we only refund relayer if all calls have updated chain state
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
id_provider=%C::IdProvider::STR,
|
||||
lane_id=?call_info.messages_call_info().lane_id(),
|
||||
?relayer,
|
||||
"Relayer has submitted invalid teyrchain finality proof"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,451 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A module that is responsible for migration of storage.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use pezframe_support::{
|
||||
traits::{Get, StorageVersion},
|
||||
weights::Weight,
|
||||
};
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
|
||||
|
||||
/// This module contains data structures that are valid for the initial state of `0`.
|
||||
/// (used with v1 migration).
|
||||
pub mod v0 {
|
||||
use crate::{Config, Pezpallet};
|
||||
use bp_relayers::RewardsAccountOwner;
|
||||
use pezbp_runtime::{ChainId, StorageDoubleMapKeyProvider};
|
||||
use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{pezpallet_prelude::OptionQuery, Blake2_128Concat, Identity};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::traits::AccountIdConversion;
|
||||
|
||||
/// Structure used to identify the account that pays a reward to the relayer.
|
||||
#[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)]
|
||||
pub struct RewardsAccountParams<LaneId> {
|
||||
/// lane_id
|
||||
pub lane_id: LaneId,
|
||||
/// bridged_chain_id
|
||||
pub bridged_chain_id: ChainId,
|
||||
/// owner
|
||||
pub owner: RewardsAccountOwner,
|
||||
}
|
||||
|
||||
impl<LaneId: Decode + Encode> RewardsAccountParams<LaneId> {
|
||||
/// Create a new instance of `RewardsAccountParams`.
|
||||
pub const fn new(
|
||||
lane_id: LaneId,
|
||||
bridged_chain_id: ChainId,
|
||||
owner: RewardsAccountOwner,
|
||||
) -> Self {
|
||||
Self { lane_id, bridged_chain_id, owner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<LaneId> pezsp_runtime::TypeId for RewardsAccountParams<LaneId> {
|
||||
const TYPE_ID: [u8; 4] = *b"brap";
|
||||
}
|
||||
|
||||
pub(crate) struct RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>(
|
||||
PhantomData<(AccountId, RewardBalance, LaneId)>,
|
||||
);
|
||||
|
||||
impl<AccountId, RewardBalance, LaneId> StorageDoubleMapKeyProvider
|
||||
for RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>
|
||||
where
|
||||
AccountId: 'static + Codec + EncodeLike + Send + Sync,
|
||||
RewardBalance: 'static + Codec + EncodeLike + Send + Sync,
|
||||
LaneId: Codec + EncodeLike + Send + Sync,
|
||||
{
|
||||
const MAP_NAME: &'static str = "RelayerRewards";
|
||||
|
||||
type Hasher1 = Blake2_128Concat;
|
||||
type Key1 = AccountId;
|
||||
type Hasher2 = Identity;
|
||||
type Key2 = RewardsAccountParams<LaneId>;
|
||||
type Value = RewardBalance;
|
||||
}
|
||||
|
||||
pub(crate) type RelayerRewardsKeyProviderOf<T, I, LaneId> = RelayerRewardsKeyProvider<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
<T as Config<I>>::RewardBalance,
|
||||
LaneId,
|
||||
>;
|
||||
|
||||
#[pezframe_support::storage_alias]
|
||||
pub(crate) type RelayerRewards<T: Config<I>, I: 'static, LaneId> = StorageDoubleMap<
|
||||
Pezpallet<T, I>,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher1,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key1,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher2,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key2,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Value,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// Reward account generator for `v0`.
|
||||
pub struct PayRewardFromAccount<Account, LaneId>(PhantomData<(Account, LaneId)>);
|
||||
impl<Account, LaneId> PayRewardFromAccount<Account, LaneId>
|
||||
where
|
||||
Account: Decode + Encode,
|
||||
LaneId: Decode + Encode,
|
||||
{
|
||||
/// Return account that pays rewards based on the provided parameters.
|
||||
pub fn rewards_account(params: RewardsAccountParams<LaneId>) -> Account {
|
||||
params.into_sub_account_truncating(b"rewards-account")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This migration updates `RelayerRewards` where `RewardsAccountParams` was used as the key with
|
||||
/// `lane_id` as the first attribute, which affects `into_sub_account_truncating`. We are migrating
|
||||
/// this key to use the new `RewardsAccountParams` where `lane_id` is the last attribute.
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
use crate::{Config, Pezpallet};
|
||||
use bp_messages::LaneIdType;
|
||||
use bp_relayers::RewardsAccountParams;
|
||||
use pezbp_runtime::StorageDoubleMapKeyProvider;
|
||||
use codec::{Codec, EncodeLike};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::OptionQuery, traits::UncheckedOnRuntimeUpgrade, Blake2_128Concat, Identity,
|
||||
};
|
||||
use pezsp_arithmetic::traits::Zero;
|
||||
|
||||
pub(crate) struct RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>(
|
||||
PhantomData<(AccountId, RewardBalance, LaneId)>,
|
||||
);
|
||||
|
||||
impl<AccountId, RewardBalance, LaneId> StorageDoubleMapKeyProvider
|
||||
for RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>
|
||||
where
|
||||
AccountId: 'static + Codec + EncodeLike + Send + Sync,
|
||||
RewardBalance: 'static + Codec + EncodeLike + Send + Sync,
|
||||
LaneId: Codec + EncodeLike + Send + Sync,
|
||||
{
|
||||
const MAP_NAME: &'static str = "RelayerRewards";
|
||||
|
||||
type Hasher1 = Blake2_128Concat;
|
||||
type Key1 = AccountId;
|
||||
type Hasher2 = Identity;
|
||||
type Key2 = v1::RewardsAccountParams<LaneId>;
|
||||
type Value = RewardBalance;
|
||||
}
|
||||
|
||||
pub(crate) type RelayerRewardsKeyProviderOf<T, I, LaneId> = RelayerRewardsKeyProvider<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
<T as Config<I>>::RewardBalance,
|
||||
LaneId,
|
||||
>;
|
||||
|
||||
#[pezframe_support::storage_alias]
|
||||
pub(crate) type RelayerRewards<T: Config<I>, I: 'static, LaneId> = StorageDoubleMap<
|
||||
Pezpallet<T, I>,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher1,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key1,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher2,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key2,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Value,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
// Copy of `Pezpallet::<T, I>::register_relayer_reward` compatible with v1.
|
||||
fn register_relayer_reward_for_v1<
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
LaneId: LaneIdType + Send + Sync,
|
||||
>(
|
||||
rewards_account_params: v1::RewardsAccountParams<LaneId>,
|
||||
relayer: &T::AccountId,
|
||||
reward_balance: T::RewardBalance,
|
||||
) {
|
||||
use pezsp_runtime::Saturating;
|
||||
|
||||
if reward_balance.is_zero() {
|
||||
return;
|
||||
}
|
||||
|
||||
v1::RelayerRewards::<T, I, LaneId>::mutate(
|
||||
relayer,
|
||||
rewards_account_params,
|
||||
|old_reward: &mut Option<T::RewardBalance>| {
|
||||
let new_reward =
|
||||
old_reward.unwrap_or_else(Zero::zero).saturating_add(reward_balance);
|
||||
*old_reward = Some(new_reward);
|
||||
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
?relayer,
|
||||
?rewards_account_params,
|
||||
?new_reward,
|
||||
"Relayer can now claim reward"
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Migrates the pezpallet storage to v1.
|
||||
pub struct UncheckedMigrationV0ToV1<T, I, LaneId>(PhantomData<(T, I, LaneId)>);
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
const LOG_TARGET: &str = "runtime::bridge-relayers-migration";
|
||||
|
||||
impl<T: Config<I>, I: 'static, LaneId: LaneIdType + Send + Sync> UncheckedOnRuntimeUpgrade
|
||||
for UncheckedMigrationV0ToV1<T, I, LaneId>
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
|
||||
// list all rewards (we cannot do this as one step because of `drain` limitation)
|
||||
let mut rewards_to_migrate =
|
||||
Vec::with_capacity(v0::RelayerRewards::<T, I, LaneId>::iter().count());
|
||||
for (key1, key2, reward) in v0::RelayerRewards::<T, I, LaneId>::drain() {
|
||||
rewards_to_migrate.push((key1, key2, reward));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
}
|
||||
|
||||
// re-register rewards with new format of `RewardsAccountParams`.
|
||||
for (key1, key2, reward) in rewards_to_migrate {
|
||||
// expand old key
|
||||
let v0::RewardsAccountParams { owner, lane_id, bridged_chain_id } = key2;
|
||||
|
||||
// re-register reward
|
||||
register_relayer_reward_for_v1::<T, I, LaneId>(
|
||||
v1::RewardsAccountParams::new(lane_id, bridged_chain_id, owner),
|
||||
&key1,
|
||||
reward,
|
||||
);
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
}
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::DispatchError> {
|
||||
use codec::Encode;
|
||||
use pezframe_support::BoundedBTreeMap;
|
||||
use pezsp_runtime::traits::ConstU32;
|
||||
|
||||
// collect actual rewards
|
||||
let mut rewards: BoundedBTreeMap<
|
||||
(T::AccountId, LaneId),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = BoundedBTreeMap::new();
|
||||
for (key1, key2, reward) in v0::RelayerRewards::<T, I, LaneId>::iter() {
|
||||
tracing::info!(target: LOG_TARGET, ?key1, ?key2, ?reward, "Reward to migrate");
|
||||
rewards = rewards
|
||||
.try_mutate(|inner| {
|
||||
inner
|
||||
.entry((key1.clone(), key2.lane_id))
|
||||
.and_modify(|value| *value += reward)
|
||||
.or_insert(reward);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
tracing::info!(target: LOG_TARGET, ?rewards, "Found total rewards to migrate");
|
||||
|
||||
Ok(rewards.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
use codec::Decode;
|
||||
use pezframe_support::BoundedBTreeMap;
|
||||
use pezsp_runtime::traits::ConstU32;
|
||||
|
||||
let rewards_before: BoundedBTreeMap<
|
||||
(T::AccountId, LaneId),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = Decode::decode(&mut &state[..]).unwrap();
|
||||
|
||||
// collect migrated rewards
|
||||
let mut rewards_after: BoundedBTreeMap<
|
||||
(T::AccountId, LaneId),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = BoundedBTreeMap::new();
|
||||
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::iter() {
|
||||
tracing::info!(target: LOG_TARGET, ?key1, ?key2, ?reward, "Migrated rewards");
|
||||
rewards_after = rewards_after
|
||||
.try_mutate(|inner| {
|
||||
inner
|
||||
.entry((key1.clone(), *key2.lane_id()))
|
||||
.and_modify(|value| *value += reward)
|
||||
.or_insert(reward);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
tracing::info!(target: LOG_TARGET, ?rewards_after, "Found total migrated rewards");
|
||||
|
||||
pezframe_support::ensure!(
|
||||
rewards_before == rewards_after,
|
||||
"The rewards were not migrated correctly!."
|
||||
);
|
||||
|
||||
tracing::info!(target: LOG_TARGET, "migrated all.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`UncheckedMigrationV0ToV1`] wrapped in a
|
||||
/// [`VersionedMigration`](pezframe_support::migrations::VersionedMigration), ensuring the
|
||||
/// migration is only performed when on-chain version is 0.
|
||||
pub type MigrationToV1<T, I, LaneId> = pezframe_support::migrations::VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
UncheckedMigrationV0ToV1<T, I, LaneId>,
|
||||
Pezpallet<T, I>,
|
||||
<T as pezframe_system::Config>::DbWeight,
|
||||
>;
|
||||
}
|
||||
|
||||
/// The pezpallet in version 1 only supported rewards collected under the key of
|
||||
/// `RewardsAccountParams`. This migration essentially converts existing `RewardsAccountParams` keys
|
||||
/// to the generic type `T::Reward`.
|
||||
pub mod v2 {
|
||||
use super::*;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use crate::RelayerRewards;
|
||||
use crate::{Config, Pezpallet};
|
||||
use bp_messages::LaneIdType;
|
||||
use bp_relayers::RewardsAccountParams;
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::traits::UncheckedOnRuntimeUpgrade;
|
||||
|
||||
/// Migrates the pezpallet storage to v2.
|
||||
pub struct UncheckedMigrationV1ToV2<T, I, LaneId>(PhantomData<(T, I, LaneId)>);
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
const LOG_TARGET: &str = "runtime::bridge-relayers-migration";
|
||||
|
||||
impl<T: Config<I>, I: 'static, LaneId: LaneIdType + Send + Sync> UncheckedOnRuntimeUpgrade
|
||||
for UncheckedMigrationV1ToV2<T, I, LaneId>
|
||||
where
|
||||
<T as Config<I>>::Reward: From<RewardsAccountParams<LaneId>>,
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
|
||||
// list all rewards (we cannot do this as one step because of `drain` limitation)
|
||||
let mut rewards_to_migrate =
|
||||
Vec::with_capacity(v1::RelayerRewards::<T, I, LaneId>::iter().count());
|
||||
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::drain() {
|
||||
rewards_to_migrate.push((key1, key2, reward));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
}
|
||||
|
||||
// re-register rewards with new format.
|
||||
for (key1, key2, reward) in rewards_to_migrate {
|
||||
// convert old key to the new
|
||||
let new_key2: T::Reward = key2.into();
|
||||
|
||||
// re-register reward (drained above)
|
||||
Pezpallet::<T, I>::register_relayer_reward(new_key2, &key1, reward);
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
}
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::DispatchError> {
|
||||
use codec::Encode;
|
||||
use pezframe_support::BoundedBTreeMap;
|
||||
use pezsp_runtime::traits::ConstU32;
|
||||
|
||||
// collect actual rewards
|
||||
let mut rewards: BoundedBTreeMap<
|
||||
(T::AccountId, Vec<u8>),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = BoundedBTreeMap::new();
|
||||
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::iter() {
|
||||
let new_key2: T::Reward = key2.into();
|
||||
tracing::info!(target: LOG_TARGET, ?key1, ?key2, ?new_key2, ?reward, "Reward to migrate");
|
||||
rewards = rewards
|
||||
.try_mutate(|inner| {
|
||||
inner
|
||||
.entry((key1.clone(), new_key2.encode()))
|
||||
.and_modify(|value| *value += reward)
|
||||
.or_insert(reward);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
tracing::info!(target: LOG_TARGET, ?rewards, "Found total rewards to migrate");
|
||||
|
||||
Ok(rewards.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::BoundedBTreeMap;
|
||||
use pezsp_runtime::traits::ConstU32;
|
||||
|
||||
let rewards_before: BoundedBTreeMap<
|
||||
(T::AccountId, Vec<u8>),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = Decode::decode(&mut &state[..]).unwrap();
|
||||
|
||||
// collect migrated rewards
|
||||
let mut rewards_after: BoundedBTreeMap<
|
||||
(T::AccountId, Vec<u8>),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = BoundedBTreeMap::new();
|
||||
for (key1, key2, reward) in v2::RelayerRewards::<T, I>::iter() {
|
||||
tracing::info!(target: LOG_TARGET, ?key1, ?key2, ?reward, "Migrated rewards");
|
||||
rewards_after = rewards_after
|
||||
.try_mutate(|inner| {
|
||||
inner
|
||||
.entry((key1.clone(), key2.encode()))
|
||||
.and_modify(|value| *value += reward)
|
||||
.or_insert(reward);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
tracing::info!(target: LOG_TARGET, ?rewards_after, "Found total migrated rewards");
|
||||
|
||||
pezframe_support::ensure!(
|
||||
rewards_before == rewards_after,
|
||||
"The rewards were not migrated correctly!."
|
||||
);
|
||||
|
||||
tracing::info!(target: LOG_TARGET, "migrated all.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`UncheckedMigrationV1ToV2`] wrapped in a
|
||||
/// [`VersionedMigration`](pezframe_support::migrations::VersionedMigration), ensuring the
|
||||
/// migration is only performed when on-chain version is 1.
|
||||
pub type MigrationToV2<T, I, LaneId> = pezframe_support::migrations::VersionedMigration<
|
||||
1,
|
||||
2,
|
||||
UncheckedMigrationV1ToV2<T, I, LaneId>,
|
||||
Pezpallet<T, I>,
|
||||
<T as pezframe_system::Config>::DbWeight,
|
||||
>;
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pezpallet_bridge_relayers;
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, MessageDispatch},
|
||||
ChainWithMessages, HashedLaneId, LaneIdType, MessageNonce,
|
||||
};
|
||||
use bp_relayers::{
|
||||
PayRewardFromAccount, PaymentProcedure, RewardsAccountOwner, RewardsAccountParams,
|
||||
};
|
||||
use pezbp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Teyrchain};
|
||||
use bp_teyrchains::SingleParaStoredHeaderDataBuilder;
|
||||
use codec::Encode;
|
||||
use pezframe_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::fungible::Mutate,
|
||||
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight},
|
||||
};
|
||||
use pezpallet_transaction_payment::Multiplier;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8},
|
||||
BuildStorage, FixedPointNumber, Perquintill, StateVersion,
|
||||
};
|
||||
|
||||
/// Account identifier at `ThisChain`.
|
||||
pub type ThisChainAccountId = u64;
|
||||
/// Balance at `ThisChain`.
|
||||
pub type ThisChainBalance = u64;
|
||||
/// Block number at `ThisChain`.
|
||||
pub type ThisChainBlockNumber = u32;
|
||||
/// Hash at `ThisChain`.
|
||||
pub type ThisChainHash = H256;
|
||||
/// Hasher at `ThisChain`.
|
||||
pub type ThisChainHasher = BlakeTwo256;
|
||||
/// Header of `ThisChain`.
|
||||
pub type ThisChainHeader = pezsp_runtime::generic::Header<ThisChainBlockNumber, ThisChainHasher>;
|
||||
/// Block of `ThisChain`.
|
||||
pub type ThisChainBlock = pezframe_system::mocking::MockBlockU32<TestRuntime>;
|
||||
|
||||
/// Account identifier at the `BridgedChain`.
|
||||
pub type BridgedChainAccountId = u128;
|
||||
/// Balance at the `BridgedChain`.
|
||||
pub type BridgedChainBalance = u128;
|
||||
/// Block number at the `BridgedChain`.
|
||||
pub type BridgedChainBlockNumber = u32;
|
||||
/// Hash at the `BridgedChain`.
|
||||
pub type BridgedChainHash = H256;
|
||||
/// Hasher at the `BridgedChain`.
|
||||
pub type BridgedChainHasher = BlakeTwo256;
|
||||
/// Header of the `BridgedChain`.
|
||||
pub type BridgedChainHeader =
|
||||
pezsp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
|
||||
|
||||
/// Bridged chain id used in tests.
|
||||
pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg";
|
||||
/// Maximal extrinsic size at the `BridgedChain`.
|
||||
pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
pub type TestLaneIdType = HashedLaneId;
|
||||
/// Lane that we're using in tests.
|
||||
pub fn test_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 2).unwrap()
|
||||
}
|
||||
/// Reward measurement type.
|
||||
pub type RewardBalance = u64;
|
||||
|
||||
/// Underlying chain of `ThisChain`.
|
||||
pub struct ThisUnderlyingChain;
|
||||
|
||||
impl Chain for ThisUnderlyingChain {
|
||||
const ID: ChainId = *b"tuch";
|
||||
|
||||
type BlockNumber = ThisChainBlockNumber;
|
||||
type Hash = ThisChainHash;
|
||||
type Hasher = ThisChainHasher;
|
||||
type Header = ThisChainHeader;
|
||||
type AccountId = ThisChainAccountId;
|
||||
type Balance = ThisChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for ThisUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
|
||||
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
|
||||
}
|
||||
|
||||
/// Underlying chain of `BridgedChain`.
|
||||
pub struct BridgedUnderlyingTeyrchain;
|
||||
|
||||
impl Chain for BridgedUnderlyingTeyrchain {
|
||||
const ID: ChainId = TEST_BRIDGED_CHAIN_ID;
|
||||
|
||||
type BlockNumber = BridgedChainBlockNumber;
|
||||
type Hash = BridgedChainHash;
|
||||
type Hasher = BridgedChainHasher;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = BridgedChainAccountId;
|
||||
type Balance = BridgedChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for BridgedUnderlyingTeyrchain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 16;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 64;
|
||||
}
|
||||
|
||||
impl ChainWithMessages for BridgedUnderlyingTeyrchain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
|
||||
}
|
||||
|
||||
impl Teyrchain for BridgedUnderlyingTeyrchain {
|
||||
const TEYRCHAIN_ID: u32 = 42;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
pub type TestStakeAndSlash = pezpallet_bridge_relayers::StakeAndSlashNamed<
|
||||
ThisChainAccountId,
|
||||
ThisChainBlockNumber,
|
||||
Balances,
|
||||
ReserveId,
|
||||
Stake,
|
||||
Lease,
|
||||
>;
|
||||
|
||||
pezframe_support::construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system,
|
||||
Utility: pezpallet_utility,
|
||||
Balances: pezpallet_balances,
|
||||
TransactionPayment: pezpallet_transaction_payment,
|
||||
BridgeRelayers: pezpallet_bridge_relayers,
|
||||
BridgeGrandpa: pezpallet_bridge_grandpa,
|
||||
BridgeTeyrchains: pezpallet_bridge_teyrchains,
|
||||
BridgeMessages: pezpallet_bridge_messages,
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BridgedParasPalletName: &'static str = "Paras";
|
||||
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
||||
pub const ExistentialDeposit: ThisChainBalance = 1;
|
||||
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
|
||||
pub const Stake: ThisChainBalance = 1_000;
|
||||
pub const Lease: ThisChainBlockNumber = 8;
|
||||
pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
|
||||
pub const TransactionBaseFee: ThisChainBalance = 0;
|
||||
pub const TransactionByteFee: ThisChainBalance = 1;
|
||||
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000);
|
||||
pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128);
|
||||
pub MaximumMultiplier: Multiplier = pezsp_runtime::traits::Bounded::max_value();
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = ThisChainBlock;
|
||||
// TODO: remove when https://github.com/pezkuwichain/kurdistan-sdk/issues/120 merged
|
||||
type BlockHashCount = ConstU32<10>;
|
||||
type AccountData = pezpallet_balances::AccountData<ThisChainBalance>;
|
||||
type DbWeight = DbWeight;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_utility::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type PalletsOrigin = OriginCaller;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_transaction_payment::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_transaction_payment::Config for TestRuntime {
|
||||
type OnChargeTransaction = pezpallet_transaction_payment::FungibleAdapter<Balances, ()>;
|
||||
type OperationalFeeMultiplier = ConstU8<5>;
|
||||
type WeightToFee = IdentityFee<ThisChainBalance>;
|
||||
type LengthToFee = ConstantMultiplier<ThisChainBalance, TransactionByteFee>;
|
||||
type FeeMultiplierUpdate = pezpallet_transaction_payment::TargetedFeeAdjustment<
|
||||
TestRuntime,
|
||||
TargetBlockFullness,
|
||||
AdjustmentVariable,
|
||||
MinimumMultiplier,
|
||||
MaximumMultiplier,
|
||||
>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = BridgedUnderlyingTeyrchain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<4>;
|
||||
type FreeHeadersInterval = ConstU32<1_024>;
|
||||
type HeadersToKeep = ConstU32<8>;
|
||||
type WeightInfo = pezpallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_teyrchains::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgesGrandpaPalletInstance = ();
|
||||
type ParasPalletName = BridgedParasPalletName;
|
||||
type ParaStoredHeaderDataBuilder =
|
||||
SingleParaStoredHeaderDataBuilder<BridgedUnderlyingTeyrchain>;
|
||||
type HeadsToKeep = ConstU32<8>;
|
||||
type MaxParaHeadDataSize = ConstU32<1024>;
|
||||
type WeightInfo = pezpallet_bridge_teyrchains::weights::BridgeWeight<TestRuntime>;
|
||||
type OnNewHead = ();
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = pezpallet_bridge_messages::weights::BridgeWeight<TestRuntime>;
|
||||
|
||||
type OutboundPayload = Vec<u8>;
|
||||
type InboundPayload = Vec<u8>;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
type DeliveryPayments = ();
|
||||
type DeliveryConfirmationPayments = pezpallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter<
|
||||
TestRuntime,
|
||||
(),
|
||||
(),
|
||||
ConstU64<100_000>,
|
||||
>;
|
||||
type OnMessagesDelivered = ();
|
||||
|
||||
type MessageDispatch = DummyMessageDispatch;
|
||||
type ThisChain = ThisUnderlyingChain;
|
||||
type BridgedChain = BridgedUnderlyingTeyrchain;
|
||||
type BridgedHeaderChain = BridgeGrandpa;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_relayers::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RewardBalance = RewardBalance;
|
||||
type Reward = RewardsAccountParams<pezpallet_bridge_messages::LaneIdOf<TestRuntime, ()>>;
|
||||
type PaymentProcedure = TestPaymentProcedure;
|
||||
type StakeAndSlash = TestStakeAndSlash;
|
||||
type Balance = ThisChainBalance;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl pezpallet_bridge_relayers::benchmarking::Config for TestRuntime {
|
||||
fn bench_reward() -> Self::Reward {
|
||||
RewardsAccountParams::new(
|
||||
TestLaneIdType::default(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare_rewards_account(
|
||||
account_params: RewardsAccountParams<TestLaneIdType>,
|
||||
reward: Self::RewardBalance,
|
||||
) -> Option<ThisChainAccountId> {
|
||||
let rewards_account = PayRewardFromAccount::<
|
||||
Balances,
|
||||
ThisChainAccountId,
|
||||
TestLaneIdType,
|
||||
RewardBalance,
|
||||
>::rewards_account(account_params);
|
||||
Self::deposit_account(rewards_account, reward.into());
|
||||
|
||||
Some(REGULAR_RELAYER2)
|
||||
}
|
||||
|
||||
fn deposit_account(account: Self::AccountId, balance: Self::Balance) {
|
||||
pezframe_support::assert_ok!(Balances::mint_into(
|
||||
&account,
|
||||
balance.saturating_add(ExistentialDeposit::get())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Regular relayer that may receive rewards.
|
||||
pub const REGULAR_RELAYER: ThisChainAccountId = 1;
|
||||
/// Regular relayer that may receive rewards.
|
||||
pub const REGULAR_RELAYER2: ThisChainAccountId = 3;
|
||||
|
||||
/// Relayer that can't receive rewards.
|
||||
pub const FAILING_RELAYER: ThisChainAccountId = 2;
|
||||
|
||||
/// Relayer that is able to register.
|
||||
pub const REGISTER_RELAYER: ThisChainAccountId = 42;
|
||||
|
||||
/// Payment procedure that rejects payments to the `FAILING_RELAYER`.
|
||||
pub struct TestPaymentProcedure;
|
||||
|
||||
impl TestPaymentProcedure {
|
||||
pub fn rewards_account(params: RewardsAccountParams<TestLaneIdType>) -> ThisChainAccountId {
|
||||
PayRewardFromAccount::<(), ThisChainAccountId, TestLaneIdType, RewardBalance>::rewards_account(
|
||||
params,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PaymentProcedure<ThisChainAccountId, RewardsAccountParams<TestLaneIdType>, RewardBalance>
|
||||
for TestPaymentProcedure
|
||||
{
|
||||
type Error = ();
|
||||
type Beneficiary = ThisChainAccountId;
|
||||
|
||||
fn pay_reward(
|
||||
relayer: &ThisChainAccountId,
|
||||
_reward_kind: RewardsAccountParams<TestLaneIdType>,
|
||||
_reward: RewardBalance,
|
||||
_beneficiary: Self::Beneficiary,
|
||||
) -> Result<(), Self::Error> {
|
||||
match *relayer {
|
||||
FAILING_RELAYER => Err(()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy message dispatcher.
|
||||
pub struct DummyMessageDispatch;
|
||||
|
||||
impl DummyMessageDispatch {
|
||||
pub fn deactivate(lane: TestLaneIdType) {
|
||||
pezframe_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for DummyMessageDispatch {
|
||||
type DispatchPayload = Vec<u8>;
|
||||
type DispatchLevelResult = ();
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
pezframe_support::storage::unhashed::take::<bool>(&(b"inactive", lane).encode()[..]) !=
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
_message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
_: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
||||
}
|
||||
}
|
||||
|
||||
/// Reward account params that we are using in tests.
|
||||
pub fn test_reward_account_param() -> RewardsAccountParams<TestLaneIdType> {
|
||||
RewardsAccountParams::new(
|
||||
TestLaneIdType::try_new(1, 2).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
|
||||
pezsp_io::TestExternalities::new(t)
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
Balances::mint_into(®ISTER_RELAYER, ExistentialDeposit::get() + 10 * Stake::get())
|
||||
.unwrap();
|
||||
|
||||
test()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Code that allows relayers pezpallet to be used as a payment mechanism for
|
||||
//! the `pezpallet-bridge-messages` pezpallet using `RewardsAccountParams`.
|
||||
|
||||
use crate::{Config, Pezpallet};
|
||||
|
||||
use alloc::collections::vec_deque::VecDeque;
|
||||
use bp_messages::{
|
||||
source_chain::{DeliveryConfirmationPayments, RelayersRewards},
|
||||
MessageNonce,
|
||||
};
|
||||
pub use bp_relayers::PayRewardFromAccount;
|
||||
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
|
||||
use pezbp_runtime::Chain;
|
||||
use core::{marker::PhantomData, ops::RangeInclusive};
|
||||
use pezframe_support::{pezsp_runtime::SaturatedConversion, traits::Get};
|
||||
use pezpallet_bridge_messages::LaneIdOf;
|
||||
use pezsp_arithmetic::traits::{Saturating, Zero};
|
||||
|
||||
/// Adapter that allows relayers pezpallet to be used as a delivery+dispatch payment mechanism
|
||||
/// for the `pezpallet-bridge-messages` pezpallet and using `RewardsAccountParams`.
|
||||
pub struct DeliveryConfirmationPaymentsAdapter<T, MI, RI, DeliveryReward>(
|
||||
PhantomData<(T, MI, RI, DeliveryReward)>,
|
||||
);
|
||||
|
||||
impl<T, MI, RI, DeliveryReward> DeliveryConfirmationPayments<T::AccountId, LaneIdOf<T, MI>>
|
||||
for DeliveryConfirmationPaymentsAdapter<T, MI, RI, DeliveryReward>
|
||||
where
|
||||
T: Config<RI> + pezpallet_bridge_messages::Config<MI>,
|
||||
MI: 'static,
|
||||
RI: 'static,
|
||||
DeliveryReward: Get<T::RewardBalance>,
|
||||
<T as Config<RI>>::Reward: From<RewardsAccountParams<LaneIdOf<T, MI>>>,
|
||||
{
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
lane_id: LaneIdOf<T, MI>,
|
||||
pez_messages_relayers: VecDeque<bp_messages::UnrewardedRelayer<T::AccountId>>,
|
||||
confirmation_relayer: &T::AccountId,
|
||||
received_range: &RangeInclusive<bp_messages::MessageNonce>,
|
||||
) -> MessageNonce {
|
||||
let relayers_rewards =
|
||||
bp_messages::calc_relayers_rewards::<T::AccountId>(pez_messages_relayers, received_range);
|
||||
let rewarded_relayers = relayers_rewards.len();
|
||||
|
||||
register_relayers_rewards::<T, RI, MI>(
|
||||
confirmation_relayer,
|
||||
relayers_rewards,
|
||||
RewardsAccountParams::new(
|
||||
lane_id,
|
||||
T::BridgedChain::ID,
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
),
|
||||
DeliveryReward::get(),
|
||||
);
|
||||
|
||||
rewarded_relayers as _
|
||||
}
|
||||
}
|
||||
|
||||
// Update rewards to given relayers, optionally rewarding confirmation relayer.
|
||||
fn register_relayers_rewards<
|
||||
T: Config<RI> + pezpallet_bridge_messages::Config<MI>,
|
||||
RI: 'static,
|
||||
MI: 'static,
|
||||
>(
|
||||
confirmation_relayer: &T::AccountId,
|
||||
relayers_rewards: RelayersRewards<T::AccountId>,
|
||||
lane_id: RewardsAccountParams<LaneIdOf<T, MI>>,
|
||||
delivery_fee: T::RewardBalance,
|
||||
) where
|
||||
<T as Config<RI>>::Reward: From<RewardsAccountParams<LaneIdOf<T, MI>>>,
|
||||
{
|
||||
// reward every relayer except `confirmation_relayer`
|
||||
let mut confirmation_relayer_reward = T::RewardBalance::zero();
|
||||
for (relayer, messages) in relayers_rewards {
|
||||
// sane runtime configurations guarantee that the number of messages will be below
|
||||
// `u32::MAX`
|
||||
let relayer_reward =
|
||||
T::RewardBalance::saturated_from(messages).saturating_mul(delivery_fee);
|
||||
|
||||
if relayer != *confirmation_relayer {
|
||||
Pezpallet::<T, RI>::register_relayer_reward(lane_id.into(), &relayer, relayer_reward);
|
||||
} else {
|
||||
confirmation_relayer_reward =
|
||||
confirmation_relayer_reward.saturating_add(relayer_reward);
|
||||
}
|
||||
}
|
||||
|
||||
// finally - pay reward to confirmation relayer
|
||||
Pezpallet::<T, RI>::register_relayer_reward(
|
||||
lane_id.into(),
|
||||
confirmation_relayer,
|
||||
confirmation_relayer_reward,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, RelayerRewards};
|
||||
use bp_messages::LaneIdType;
|
||||
use bp_relayers::PaymentProcedure;
|
||||
use pezframe_support::{
|
||||
assert_ok,
|
||||
traits::fungible::{Inspect, Mutate},
|
||||
};
|
||||
|
||||
const RELAYER_1: ThisChainAccountId = 1;
|
||||
const RELAYER_2: ThisChainAccountId = 2;
|
||||
const RELAYER_3: ThisChainAccountId = 3;
|
||||
|
||||
fn relayers_rewards() -> RelayersRewards<ThisChainAccountId> {
|
||||
vec![(RELAYER_1, 2), (RELAYER_2, 3)].into_iter().collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() {
|
||||
run_test(|| {
|
||||
register_relayers_rewards::<TestRuntime, (), ()>(
|
||||
&RELAYER_2,
|
||||
relayers_rewards(),
|
||||
test_reward_account_param(),
|
||||
50,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_1, test_reward_account_param()),
|
||||
Some(100)
|
||||
);
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_2, test_reward_account_param()),
|
||||
Some(150)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmation_relayer_is_not_rewarded_if_it_has_not_delivered_any_messages() {
|
||||
run_test(|| {
|
||||
register_relayers_rewards::<TestRuntime, (), ()>(
|
||||
&RELAYER_3,
|
||||
relayers_rewards(),
|
||||
test_reward_account_param(),
|
||||
50,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_1, test_reward_account_param()),
|
||||
Some(100)
|
||||
);
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_2, test_reward_account_param()),
|
||||
Some(150)
|
||||
);
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_3, test_reward_account_param()),
|
||||
None
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pay_reward_from_account_actually_pays_reward() {
|
||||
type Balances = pezpallet_balances::Pezpallet<TestRuntime>;
|
||||
type PayLaneRewardFromAccount =
|
||||
PayRewardFromAccount<Balances, ThisChainAccountId, TestLaneIdType, RewardBalance>;
|
||||
|
||||
run_test(|| {
|
||||
let in_lane_0 = RewardsAccountParams::new(
|
||||
TestLaneIdType::try_new(1, 2).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
);
|
||||
let out_lane_1 = RewardsAccountParams::new(
|
||||
TestLaneIdType::try_new(1, 3).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
);
|
||||
|
||||
let in_lane0_rewards_account = PayLaneRewardFromAccount::rewards_account(in_lane_0);
|
||||
let out_lane1_rewards_account = PayLaneRewardFromAccount::rewards_account(out_lane_1);
|
||||
|
||||
assert_ok!(Balances::mint_into(&in_lane0_rewards_account, 200));
|
||||
assert_ok!(Balances::mint_into(&out_lane1_rewards_account, 100));
|
||||
assert_eq!(Balances::balance(&in_lane0_rewards_account), 200);
|
||||
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
|
||||
assert_eq!(Balances::balance(&1), 0);
|
||||
assert_eq!(Balances::balance(&2), 0);
|
||||
|
||||
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, in_lane_0, 100, 1_u64));
|
||||
assert_eq!(Balances::balance(&in_lane0_rewards_account), 100);
|
||||
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
|
||||
assert_eq!(Balances::balance(&1), 100);
|
||||
assert_eq!(Balances::balance(&2), 0);
|
||||
|
||||
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, out_lane_1, 100, 1_u64));
|
||||
assert_eq!(Balances::balance(&in_lane0_rewards_account), 100);
|
||||
assert_eq!(Balances::balance(&out_lane1_rewards_account), 0);
|
||||
assert_eq!(Balances::balance(&1), 200);
|
||||
assert_eq!(Balances::balance(&2), 0);
|
||||
|
||||
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, in_lane_0, 100, 2_u64));
|
||||
assert_eq!(Balances::balance(&in_lane0_rewards_account), 0);
|
||||
assert_eq!(Balances::balance(&out_lane1_rewards_account), 0);
|
||||
assert_eq!(Balances::balance(&1), 200);
|
||||
assert_eq!(Balances::balance(&2), 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Code that allows `NamedReservableCurrency` to be used as a `StakeAndSlash`
|
||||
//! mechanism of the relayers pezpallet.
|
||||
|
||||
use bp_relayers::StakeAndSlash;
|
||||
use codec::Codec;
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use pezframe_support::traits::{tokens::BalanceStatus, NamedReservableCurrency};
|
||||
use pezsp_runtime::{traits::Get, DispatchError, DispatchResult};
|
||||
|
||||
/// `StakeAndSlash` that works with `NamedReservableCurrency` and uses named
|
||||
/// reservations.
|
||||
///
|
||||
/// **WARNING**: this implementation assumes that the relayers pezpallet is configured to
|
||||
/// use the [`bp_relayers::PayRewardFromAccount`] as its relayers payment scheme.
|
||||
pub struct StakeAndSlashNamed<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>(
|
||||
PhantomData<(AccountId, BlockNumber, Currency, ReserveId, Stake, Lease)>,
|
||||
);
|
||||
|
||||
impl<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>
|
||||
StakeAndSlash<AccountId, BlockNumber, Currency::Balance>
|
||||
for StakeAndSlashNamed<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>
|
||||
where
|
||||
AccountId: Codec + Debug,
|
||||
Currency: NamedReservableCurrency<AccountId>,
|
||||
ReserveId: Get<Currency::ReserveIdentifier>,
|
||||
Stake: Get<Currency::Balance>,
|
||||
Lease: Get<BlockNumber>,
|
||||
{
|
||||
type RequiredStake = Stake;
|
||||
type RequiredRegistrationLease = Lease;
|
||||
|
||||
fn reserve(relayer: &AccountId, amount: Currency::Balance) -> DispatchResult {
|
||||
Currency::reserve_named(&ReserveId::get(), relayer, amount)
|
||||
}
|
||||
|
||||
fn unreserve(relayer: &AccountId, amount: Currency::Balance) -> Currency::Balance {
|
||||
Currency::unreserve_named(&ReserveId::get(), relayer, amount)
|
||||
}
|
||||
|
||||
fn repatriate_reserved(
|
||||
relayer: &AccountId,
|
||||
beneficiary: &AccountId,
|
||||
amount: Currency::Balance,
|
||||
) -> Result<Currency::Balance, DispatchError> {
|
||||
Currency::repatriate_reserved_named(
|
||||
&ReserveId::get(),
|
||||
relayer,
|
||||
&beneficiary,
|
||||
amount,
|
||||
BalanceStatus::Free,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
use bp_relayers::ExplicitOrAccountParams;
|
||||
|
||||
use pezframe_support::traits::fungible::Mutate;
|
||||
use pezsp_runtime::traits::IdentifyAccount;
|
||||
|
||||
fn test_stake() -> ThisChainBalance {
|
||||
Stake::get()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserve_works() {
|
||||
run_test(|| {
|
||||
assert!(TestStakeAndSlash::reserve(&1, test_stake()).is_err());
|
||||
assert_eq!(Balances::free_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
|
||||
Balances::mint_into(&2, test_stake() - 1).unwrap();
|
||||
assert!(TestStakeAndSlash::reserve(&2, test_stake()).is_err());
|
||||
assert_eq!(Balances::free_balance(2), test_stake() - 1);
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
|
||||
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||
assert_eq!(TestStakeAndSlash::reserve(&3, test_stake()), Ok(()));
|
||||
assert_eq!(Balances::free_balance(3), test_stake());
|
||||
assert_eq!(Balances::reserved_balance(3), test_stake());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unreserve_works() {
|
||||
run_test(|| {
|
||||
assert_eq!(TestStakeAndSlash::unreserve(&1, test_stake()), test_stake());
|
||||
assert_eq!(Balances::free_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
|
||||
Balances::mint_into(&2, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap();
|
||||
assert_eq!(
|
||||
TestStakeAndSlash::unreserve(&2, test_stake()),
|
||||
test_stake() - test_stake() / 3
|
||||
);
|
||||
assert_eq!(Balances::free_balance(2), test_stake() * 2);
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
|
||||
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||
assert_eq!(TestStakeAndSlash::unreserve(&3, test_stake()), 0);
|
||||
assert_eq!(Balances::free_balance(3), test_stake() * 2);
|
||||
assert_eq!(Balances::reserved_balance(3), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repatriate_reserved_works() {
|
||||
run_test(|| {
|
||||
let beneficiary = test_reward_account_param();
|
||||
let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
|
||||
|
||||
let mut expected_balance = ExistentialDeposit::get();
|
||||
Balances::mint_into(&beneficiary_account, expected_balance).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
TestStakeAndSlash::repatriate_reserved(
|
||||
&1,
|
||||
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
|
||||
test_stake()
|
||||
),
|
||||
Ok(test_stake())
|
||||
);
|
||||
assert_eq!(Balances::free_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||
|
||||
expected_balance += test_stake() / 3;
|
||||
Balances::mint_into(&2, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap();
|
||||
assert_eq!(
|
||||
TestStakeAndSlash::repatriate_reserved(
|
||||
&2,
|
||||
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
|
||||
test_stake()
|
||||
),
|
||||
Ok(test_stake() - test_stake() / 3)
|
||||
);
|
||||
assert_eq!(Balances::free_balance(2), test_stake() * 2 - test_stake() / 3);
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||
|
||||
expected_balance += test_stake();
|
||||
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||
assert_eq!(
|
||||
TestStakeAndSlash::repatriate_reserved(
|
||||
&3,
|
||||
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
|
||||
test_stake()
|
||||
),
|
||||
Ok(0)
|
||||
);
|
||||
assert_eq!(Balances::free_balance(3), test_stake());
|
||||
assert_eq!(Balances::reserved_balance(3), 0);
|
||||
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repatriate_reserved_doesnt_work_when_beneficiary_account_is_missing() {
|
||||
run_test(|| {
|
||||
let beneficiary = test_reward_account_param();
|
||||
let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
|
||||
|
||||
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||
assert!(TestStakeAndSlash::repatriate_reserved(
|
||||
&3,
|
||||
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
|
||||
test_stake()
|
||||
)
|
||||
.is_err());
|
||||
assert_eq!(Balances::free_balance(3), test_stake());
|
||||
assert_eq!(Balances::reserved_balance(3), test_stake());
|
||||
assert_eq!(Balances::free_balance(beneficiary_account), 0);
|
||||
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_bridge_relayers
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-04-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/rip-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_bridge_relayers
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/relayers/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
|
||||
/// Weight functions needed for pezpallet_bridge_relayers.
|
||||
pub trait WeightInfo {
|
||||
fn claim_rewards() -> Weight;
|
||||
fn claim_rewards_to() -> Weight;
|
||||
fn register() -> Weight;
|
||||
fn deregister() -> Weight;
|
||||
fn slash_and_deregister() -> Weight;
|
||||
fn register_relayer_reward() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_bridge_relayers` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances TotalIssuance (r:1 w:0)
|
||||
///
|
||||
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn claim_rewards() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `294`
|
||||
// Estimated: `8592`
|
||||
// Minimum execution time: 77_614 nanoseconds.
|
||||
Weight::from_parts(79_987_000, 8592)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances TotalIssuance (r:1 w:0)
|
||||
///
|
||||
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn claim_rewards_to() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `294`
|
||||
// Estimated: `8592`
|
||||
// Minimum execution time: 77_614 nanoseconds.
|
||||
Weight::from_parts(79_987_000, 8592)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn register() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `87`
|
||||
// Estimated: `7843`
|
||||
// Minimum execution time: 39_590 nanoseconds.
|
||||
Weight::from_parts(40_546_000, 7843)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn deregister() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `264`
|
||||
// Estimated: `7843`
|
||||
// Minimum execution time: 43_332 nanoseconds.
|
||||
Weight::from_parts(45_087_000, 7843)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn slash_and_deregister() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `380`
|
||||
// Estimated: `11412`
|
||||
// Minimum execution time: 42_358 nanoseconds.
|
||||
Weight::from_parts(43_539_000, 11412)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
fn register_relayer_reward() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `12`
|
||||
// Estimated: `3530`
|
||||
// Minimum execution time: 6_338 nanoseconds.
|
||||
Weight::from_parts(6_526_000, 3530)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances TotalIssuance (r:1 w:0)
|
||||
///
|
||||
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn claim_rewards() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `294`
|
||||
// Estimated: `8592`
|
||||
// Minimum execution time: 77_614 nanoseconds.
|
||||
Weight::from_parts(79_987_000, 8592)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances TotalIssuance (r:1 w:0)
|
||||
///
|
||||
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn claim_rewards_to() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `294`
|
||||
// Estimated: `8592`
|
||||
// Minimum execution time: 77_614 nanoseconds.
|
||||
Weight::from_parts(79_987_000, 8592)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn register() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `87`
|
||||
// Estimated: `7843`
|
||||
// Minimum execution time: 39_590 nanoseconds.
|
||||
Weight::from_parts(40_546_000, 7843)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn deregister() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `264`
|
||||
// Estimated: `7843`
|
||||
// Minimum execution time: 43_332 nanoseconds.
|
||||
Weight::from_parts(45_087_000, 7843)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn slash_and_deregister() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `380`
|
||||
// Estimated: `11412`
|
||||
// Minimum execution time: 42_358 nanoseconds.
|
||||
Weight::from_parts(43_539_000, 11412)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
fn register_relayer_reward() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `12`
|
||||
// Estimated: `3530`
|
||||
// Minimum execution time: 6_338 nanoseconds.
|
||||
Weight::from_parts(6_526_000, 3530)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::WeightInfo;
|
||||
|
||||
use pezframe_support::pezpallet_prelude::Weight;
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
/// Returns weight, that needs to be added to the pre-dispatch weight of message delivery call,
|
||||
/// if `BridgeRelayersTransactionExtension` signed extension is deployed at runtime level.
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Self::slash_and_deregister().max(Self::register_relayer_reward())
|
||||
}
|
||||
|
||||
/// Returns weight, that needs to be added to the pre-dispatch weight of message delivery
|
||||
/// confirmation call, if `BridgeRelayersTransactionExtension` signed extension is deployed at
|
||||
/// runtime level.
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Self::register_relayer_reward()
|
||||
}
|
||||
|
||||
/// Returns weight that we need to deduct from the message delivery call weight that has
|
||||
/// completed successfully.
|
||||
///
|
||||
/// Usually, the weight of `slash_and_deregister` is larger than the weight of the
|
||||
/// `register_relayer_reward`. So if relayer has been rewarded, we want to deduct the difference
|
||||
/// to get the actual post-dispatch weight.
|
||||
fn extra_weight_of_successful_receive_messages_proof_call() -> Weight {
|
||||
Self::slash_and_deregister().saturating_sub(Self::register_relayer_reward())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WeightInfo> WeightInfoExt for T {}
|
||||
@@ -0,0 +1,75 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-teyrchains"
|
||||
version = "0.7.0"
|
||||
description = "Module that allows bridged relay chains to exchange information on their teyrchains' heads."
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-bridge-teyrchains"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
bp-teyrchains = { workspace = true }
|
||||
pezpallet-bridge-grandpa = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-header-pez-chain = { workspace = true, default-features = true }
|
||||
bp-test-utils = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-teyrchains/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-bridge-grandpa/std",
|
||||
"scale-info/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"bp-teyrchains/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-bridge-grandpa/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-bridge-grandpa/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,90 @@
|
||||
# Bridge Teyrchains Pezpallet
|
||||
|
||||
The bridge teyrchains pezpallet is a light client for one or several teyrchains of the bridged relay chain.
|
||||
It serves as a source of finalized teyrchain headers and is used when you need to build a bridge with
|
||||
a teyrchain.
|
||||
|
||||
The pezpallet requires [bridge GRANDPA pezpallet](../grandpa/) to be deployed at the same chain - it is used
|
||||
to verify storage proofs, generated at the bridged relay chain.
|
||||
|
||||
## A Brief Introduction into Teyrchains Finality
|
||||
|
||||
You can find detailed information on teyrchains finality in the
|
||||
[PezkuwiChain-SDK](https://github.com/pezkuwichain/pezkuwi-sdk) repository. This section gives a brief overview of how the
|
||||
teyrchain finality works and how to build a light client for a teyrchain.
|
||||
|
||||
The main thing there is that the teyrchain generates blocks on its own, but it can't achieve finality without
|
||||
help of its relay chain. Instead, the teyrchain collators create a block and hand it over to the relay chain
|
||||
validators. Validators validate the block and register the new teyrchain head in the
|
||||
[`Heads` map](https://github.com/pezkuwichain/pezkuwi-sdk/blob/bc5005217a8c2e7c95b9011c96d7e619879b1200/polkadot/runtime/parachains/src/paras/mod.rs#L683-L686)
|
||||
of the [`paras`](https://github.com/paritytech/polkadot-sdk/tree/master/polkadot/runtime/parachains/src/paras) pezpallet,
|
||||
deployed at the relay chain. Keep in mind that this pezpallet, deployed at a relay chain, is **NOT** a bridge pezpallet,
|
||||
even though the names are similar.
|
||||
|
||||
And what the bridge teyrchains pezpallet does, is simply verifying storage proofs of teyrchain heads within that
|
||||
`Heads` map. It does that using relay chain header, that has been previously imported by the
|
||||
[bridge GRANDPA pezpallet](../grandpa/). Once the proof is verified, the pezpallet knows that the given teyrchain
|
||||
header has been finalized by the relay chain. The teyrchain header fields may then be used to verify storage
|
||||
proofs, coming from the teyrchain. This allows the pezpallet to be used e.g. as a source of finality for the messages
|
||||
pezpallet.
|
||||
|
||||
## Pezpallet Operations
|
||||
|
||||
The main entrypoint of the pezpallet is the `submit_teyrchain_heads` call. It has three arguments:
|
||||
|
||||
- storage proof of teyrchain heads from the `Heads` map;
|
||||
|
||||
- teyrchain identifiers and hashes of their heads from the storage proof;
|
||||
|
||||
- the relay block, at which the storage proof has been generated.
|
||||
|
||||
The pezpallet may track multiple teyrchains. And the teyrchains may use different primitives - one may use 128-bit block
|
||||
numbers, other - 32-bit. To avoid extra decode operations, the pezpallet is using relay chain block number to order
|
||||
teyrchain headers. Any finalized descendant of finalized relay block `RB`, which has teyrchain block `PB` in
|
||||
its `Heads` map, is guaranteed to have either `PB`, or its descendant. So teyrchain block number grows with relay
|
||||
block number.
|
||||
|
||||
The pezpallet may reject teyrchain head if it already knows better (or the same) head. In addition, pezpallet rejects
|
||||
heads of untracked teyrchains.
|
||||
|
||||
The pezpallet doesn't track anything behind teyrchain heads. So it requires no initialization - it is ready to accept
|
||||
headers right after deployment.
|
||||
|
||||
## Non-Essential Functionality
|
||||
|
||||
There may be a special account in every runtime where the bridge teyrchains module is deployed. This
|
||||
account, named 'module owner', is like a module-level sudo account - he's able to halt and
|
||||
resume all module operations without requiring runtime upgrade. Calls that are related to this
|
||||
account are:
|
||||
|
||||
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
|
||||
|
||||
- `fn set_operating_mode()`: the module owner (or sudo account) may call this function to stop all
|
||||
module operations. After this call, all finality proofs will be rejected until further `set_operating_mode` call'.
|
||||
This call may be used when something extraordinary happens with the bridge.
|
||||
|
||||
If pezpallet owner is not defined, the governance may be used to make those calls.
|
||||
|
||||
## Signed Extension to Reject Obsolete Headers
|
||||
|
||||
It'd be better for anyone (for chain and for submitters) to reject all transactions that are submitting
|
||||
already known teyrchain heads to the pezpallet. This way, we leave block space to other useful transactions and
|
||||
we don't charge concurrent submitters for their honest actions.
|
||||
|
||||
To deal with that, we have a [signed extension](./src/call_ext.rs) that may be added to the runtime.
|
||||
It does exactly what is required - rejects all transactions with already known heads. The submitter
|
||||
pays nothing for such transactions - they're simply removed from the transaction pool, when the block
|
||||
is built.
|
||||
|
||||
The signed extension, however, is a bit limited - it only works with transactions that provide single
|
||||
teyrchain head. So it won't work with multiple teyrchain heads transactions. This fits our needs
|
||||
for [Kusama <> PezkuwiChain bridge](../../docs/pezkuwi-kusama-bridge-overview.md). If you need to deal
|
||||
with other transaction formats, you may implement similar extension for your runtime.
|
||||
|
||||
You may also take a look at the [`generate_bridge_reject_obsolete_headers_and_messages`](../../bin/runtime-common/src/lib.rs)
|
||||
macro that bundles several similar signed extensions in a single one.
|
||||
|
||||
## Teyrchains Finality Relay
|
||||
|
||||
We have an offchain actor, who is watching for new teyrchain heads and submits them to the bridged chain.
|
||||
It is the teyrchains relay - you may look at the [crate level documentation and the code](../../relays/teyrchains/).
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchains finality pezpallet benchmarking.
|
||||
|
||||
use crate::{
|
||||
weights_ext::DEFAULT_TEYRCHAIN_HEAD_SIZE, Call, RelayBlockHash, RelayBlockHasher,
|
||||
RelayBlockNumber,
|
||||
};
|
||||
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHeadsProof, ParaId};
|
||||
use pezbp_runtime::UnverifiedStorageProofParams;
|
||||
use pezframe_benchmarking::{account, benchmarks_instance_pallet};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// Pezpallet we're benchmarking here.
|
||||
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
|
||||
|
||||
/// Trait that must be implemented by runtime to benchmark the teyrchains finality pezpallet.
|
||||
pub trait Config<I: 'static>: crate::Config<I> {
|
||||
/// Returns vector of supported teyrchains.
|
||||
fn teyrchains() -> Vec<ParaId>;
|
||||
/// Generate teyrchain heads proof and prepare environment for verifying this proof.
|
||||
fn prepare_teyrchain_heads_proof(
|
||||
teyrchains: &[ParaId],
|
||||
teyrchain_head_size: u32,
|
||||
proof_params: UnverifiedStorageProofParams,
|
||||
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>);
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
where_clause {
|
||||
where
|
||||
<T as pezpallet_bridge_grandpa::Config<T::BridgesGrandpaPalletInstance>>::BridgedChain:
|
||||
pezbp_runtime::Chain<
|
||||
BlockNumber = RelayBlockNumber,
|
||||
Hash = RelayBlockHash,
|
||||
Hasher = RelayBlockHasher,
|
||||
>,
|
||||
}
|
||||
|
||||
// Benchmark `submit_teyrchain_heads` extrinsic with different number of teyrchains.
|
||||
submit_teyrchain_heads_with_n_teyrchains {
|
||||
let p in 1..(T::teyrchains().len() + 1) as u32;
|
||||
|
||||
let sender = account("sender", 0, 0);
|
||||
let mut teyrchains = T::teyrchains();
|
||||
let _ = if p <= teyrchains.len() as u32 {
|
||||
teyrchains.split_off(p as usize)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
tracing::trace!(target: crate::LOG_TARGET, "=== {:?}", teyrchains.len());
|
||||
let (relay_block_number, relay_block_hash, teyrchain_heads_proof, teyrchains_heads) = T::prepare_teyrchain_heads_proof(
|
||||
&teyrchains,
|
||||
DEFAULT_TEYRCHAIN_HEAD_SIZE,
|
||||
UnverifiedStorageProofParams::default(),
|
||||
);
|
||||
let at_relay_block = (relay_block_number, relay_block_hash);
|
||||
}: submit_teyrchain_heads(RawOrigin::Signed(sender), at_relay_block, teyrchains_heads, teyrchain_heads_proof)
|
||||
verify {
|
||||
for teyrchain in teyrchains {
|
||||
assert!(crate::Pezpallet::<T, I>::best_teyrchain_head(teyrchain).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark `submit_teyrchain_heads` extrinsic with 1kb proof size.
|
||||
submit_teyrchain_heads_with_1kb_proof {
|
||||
let sender = account("sender", 0, 0);
|
||||
let teyrchains = vec![T::teyrchains()[0]];
|
||||
let (relay_block_number, relay_block_hash, teyrchain_heads_proof, teyrchains_heads) = T::prepare_teyrchain_heads_proof(
|
||||
&teyrchains,
|
||||
DEFAULT_TEYRCHAIN_HEAD_SIZE,
|
||||
UnverifiedStorageProofParams::from_db_size(1024),
|
||||
);
|
||||
let at_relay_block = (relay_block_number, relay_block_hash);
|
||||
}: submit_teyrchain_heads(RawOrigin::Signed(sender), at_relay_block, teyrchains_heads, teyrchain_heads_proof)
|
||||
verify {
|
||||
for teyrchain in teyrchains {
|
||||
assert!(crate::Pezpallet::<T, I>::best_teyrchain_head(teyrchain).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark `submit_teyrchain_heads` extrinsic with 16kb proof size.
|
||||
submit_teyrchain_heads_with_16kb_proof {
|
||||
let sender = account("sender", 0, 0);
|
||||
let teyrchains = vec![T::teyrchains()[0]];
|
||||
let (relay_block_number, relay_block_hash, teyrchain_heads_proof, teyrchains_heads) = T::prepare_teyrchain_heads_proof(
|
||||
&teyrchains,
|
||||
DEFAULT_TEYRCHAIN_HEAD_SIZE,
|
||||
UnverifiedStorageProofParams::from_db_size(16 * 1024),
|
||||
);
|
||||
let at_relay_block = (relay_block_number, relay_block_hash);
|
||||
}: submit_teyrchain_heads(RawOrigin::Signed(sender), at_relay_block, teyrchains_heads, teyrchain_heads_proof)
|
||||
verify {
|
||||
for teyrchain in teyrchains {
|
||||
assert!(crate::Pezpallet::<T, I>::best_teyrchain_head(teyrchain).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pezpallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{Config, GrandpaPalletOf, Pezpallet, RelayBlockNumber};
|
||||
use bp_header_pez_chain::HeaderChain;
|
||||
use pezbp_runtime::{HeaderId, OwnedBridgeModule};
|
||||
use bp_teyrchains::{BestParaHeadHash, SubmitTeyrchainHeadsInfo};
|
||||
use pezframe_support::{
|
||||
dispatch::CallableCallFor,
|
||||
traits::{Get, IsSubType},
|
||||
};
|
||||
use pezpallet_bridge_grandpa::SubmitFinalityProofHelper;
|
||||
use pezsp_runtime::{
|
||||
traits::Zero,
|
||||
transaction_validity::{InvalidTransaction, TransactionValidityError},
|
||||
RuntimeDebug,
|
||||
};
|
||||
|
||||
/// Verified `SubmitTeyrchainHeadsInfo`.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct VerifiedSubmitTeyrchainHeadsInfo {
|
||||
/// Base call information.
|
||||
pub base: SubmitTeyrchainHeadsInfo,
|
||||
/// A difference between bundled bridged relay chain header and relay chain header number
|
||||
/// used to prove best bridged teyrchain header, known to us before the call.
|
||||
pub improved_by: RelayBlockNumber,
|
||||
}
|
||||
|
||||
/// Helper struct that provides methods for working with the `SubmitTeyrchainHeads` call.
|
||||
pub struct SubmitTeyrchainHeadsHelper<T: Config<I>, I: 'static> {
|
||||
_phantom_data: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> SubmitTeyrchainHeadsHelper<T, I> {
|
||||
/// Check that is called from signed extension and takes the `is_free_execution_expected`
|
||||
/// into account.
|
||||
pub fn check_obsolete_from_extension(
|
||||
update: &SubmitTeyrchainHeadsInfo,
|
||||
) -> Result<RelayBlockNumber, TransactionValidityError> {
|
||||
// first do all base checks
|
||||
let improved_by = Self::check_obsolete(update)?;
|
||||
|
||||
// if we don't expect free execution - no more checks
|
||||
if !update.is_free_execution_expected {
|
||||
return Ok(improved_by);
|
||||
}
|
||||
|
||||
// reject if no more free slots remaining in the block
|
||||
if !SubmitFinalityProofHelper::<T, T::BridgesGrandpaPalletInstance>::has_free_header_slots()
|
||||
{
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
"The free teyrchain head can't be updated: no more free slots left in the block."
|
||||
);
|
||||
|
||||
return Err(InvalidTransaction::Call.into());
|
||||
}
|
||||
|
||||
// if free headers interval is not configured and call is expected to execute
|
||||
// for free => it is a relayer error, it should've been able to detect that.
|
||||
let free_headers_interval = match T::FreeHeadersInterval::get() {
|
||||
Some(free_headers_interval) => free_headers_interval,
|
||||
None => return Ok(improved_by),
|
||||
};
|
||||
|
||||
// reject if we are importing teyrchain headers too often
|
||||
if improved_by < free_headers_interval {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
%improved_by,
|
||||
"The free teyrchain head can't be updated: it improves previous
|
||||
best head while at least {free_headers_interval} is expected."
|
||||
);
|
||||
|
||||
return Err(InvalidTransaction::Stale.into());
|
||||
}
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check if the para head provided by the `SubmitTeyrchainHeads` is better than the best one
|
||||
/// we know.
|
||||
pub fn check_obsolete(
|
||||
update: &SubmitTeyrchainHeadsInfo,
|
||||
) -> Result<RelayBlockNumber, TransactionValidityError> {
|
||||
// check if we know better teyrchain head already
|
||||
let improved_by = match crate::ParasInfo::<T, I>::get(update.para_id) {
|
||||
Some(stored_best_head) => {
|
||||
let improved_by = match update
|
||||
.at_relay_block
|
||||
.0
|
||||
.checked_sub(stored_best_head.best_head_hash.at_relay_block_number)
|
||||
{
|
||||
Some(improved_by) if improved_by > Zero::zero() => improved_by,
|
||||
_ => {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
"The teyrchain head can't be updated. The teyrchain head \
|
||||
was already updated at better relay chain block {} >= {}.",
|
||||
stored_best_head.best_head_hash.at_relay_block_number,
|
||||
update.at_relay_block.0
|
||||
);
|
||||
return Err(InvalidTransaction::Stale.into());
|
||||
},
|
||||
};
|
||||
|
||||
if stored_best_head.best_head_hash.head_hash == update.para_head_hash {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
para_head_hash=%update.para_head_hash,
|
||||
"The teyrchain head can't be updated. The teyrchain head hash \
|
||||
was already updated at block {} < {}.",
|
||||
stored_best_head.best_head_hash.at_relay_block_number,
|
||||
update.at_relay_block.0
|
||||
);
|
||||
return Err(InvalidTransaction::Stale.into());
|
||||
}
|
||||
|
||||
improved_by
|
||||
},
|
||||
None => RelayBlockNumber::MAX,
|
||||
};
|
||||
|
||||
// let's check if our chain had no reorgs and we still know the relay chain header
|
||||
// used to craft the proof
|
||||
if GrandpaPalletOf::<T, I>::finalized_header_state_root(update.at_relay_block.1).is_none() {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
at_relay_block=?update.at_relay_block,
|
||||
"The teyrchain head can't be updated. Relay chain header used to create \
|
||||
teyrchain proof is missing from the storage."
|
||||
);
|
||||
|
||||
return Err(InvalidTransaction::Call.into());
|
||||
}
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check if the `SubmitTeyrchainHeads` was successfully executed.
|
||||
pub fn was_successful(update: &SubmitTeyrchainHeadsInfo) -> bool {
|
||||
match crate::ParasInfo::<T, I>::get(update.para_id) {
|
||||
Some(stored_best_head) =>
|
||||
stored_best_head.best_head_hash ==
|
||||
BestParaHeadHash {
|
||||
at_relay_block_number: update.at_relay_block.0,
|
||||
head_hash: update.para_head_hash,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing a call that is a sub type of this pezpallet's call.
|
||||
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
|
||||
{
|
||||
/// Create a new instance of `SubmitTeyrchainHeadsInfo` from a `SubmitTeyrchainHeads` call with
|
||||
/// one single teyrchain entry.
|
||||
fn one_entry_submit_teyrchain_heads_info(&self) -> Option<SubmitTeyrchainHeadsInfo> {
|
||||
match self.is_sub_type() {
|
||||
Some(crate::Call::<T, I>::submit_teyrchain_heads {
|
||||
ref at_relay_block,
|
||||
ref teyrchains,
|
||||
..
|
||||
}) => match &teyrchains[..] {
|
||||
&[(para_id, para_head_hash)] => Some(SubmitTeyrchainHeadsInfo {
|
||||
at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1),
|
||||
para_id,
|
||||
para_head_hash,
|
||||
is_free_execution_expected: false,
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
Some(crate::Call::<T, I>::submit_teyrchain_heads_ex {
|
||||
ref at_relay_block,
|
||||
ref teyrchains,
|
||||
is_free_execution_expected,
|
||||
..
|
||||
}) => match &teyrchains[..] {
|
||||
&[(para_id, para_head_hash)] => Some(SubmitTeyrchainHeadsInfo {
|
||||
at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1),
|
||||
para_id,
|
||||
para_head_hash,
|
||||
is_free_execution_expected: *is_free_execution_expected,
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new instance of `SubmitTeyrchainHeadsInfo` from a `SubmitTeyrchainHeads` call with
|
||||
/// one single teyrchain entry, if the entry is for the provided teyrchain id.
|
||||
fn submit_teyrchain_heads_info_for(&self, para_id: u32) -> Option<SubmitTeyrchainHeadsInfo> {
|
||||
self.one_entry_submit_teyrchain_heads_info()
|
||||
.filter(|update| update.para_id.0 == para_id)
|
||||
}
|
||||
|
||||
/// Validate teyrchain heads in order to avoid "mining" transactions that provide
|
||||
/// outdated bridged teyrchain heads. Without this validation, even honest relayers
|
||||
/// may lose their funds if there are multiple relays running and submitting the
|
||||
/// same information.
|
||||
///
|
||||
/// This validation only works with transactions that are updating single teyrchain
|
||||
/// head. We can't use unbounded validation - it may take too long and either break
|
||||
/// block production, or "eat" significant portion of block production time literally
|
||||
/// for nothing. In addition, the single-teyrchain-head-per-transaction is how the
|
||||
/// pezpallet will be used in our environment.
|
||||
fn check_obsolete_submit_teyrchain_heads(
|
||||
&self,
|
||||
) -> Result<Option<VerifiedSubmitTeyrchainHeadsInfo>, TransactionValidityError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let update = match self.one_entry_submit_teyrchain_heads_info() {
|
||||
Some(update) => update,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
if Pezpallet::<T, I>::ensure_not_halted().is_err() {
|
||||
return Err(InvalidTransaction::Call.into());
|
||||
}
|
||||
|
||||
SubmitTeyrchainHeadsHelper::<T, I>::check_obsolete_from_extension(&update)
|
||||
.map(|improved_by| Some(VerifiedSubmitTeyrchainHeadsInfo { base: update, improved_by }))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: 'static> CallSubType<T, I> for T::RuntimeCall
|
||||
where
|
||||
T: Config<I>,
|
||||
T::RuntimeCall: IsSubType<CallableCallFor<Pezpallet<T, I>, T>>,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
mock::{run_test, FreeHeadersInterval, RuntimeCall, TestRuntime},
|
||||
CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockHash, RelayBlockNumber,
|
||||
};
|
||||
use bp_header_pez_chain::StoredHeaderData;
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHeadsProof, ParaId};
|
||||
use pezbp_runtime::BasicOperatingMode;
|
||||
use bp_teyrchains::BestParaHeadHash;
|
||||
|
||||
fn validate_submit_teyrchain_heads(
|
||||
num: RelayBlockNumber,
|
||||
teyrchains: Vec<(ParaId, ParaHash)>,
|
||||
) -> bool {
|
||||
RuntimeCall::Teyrchains(crate::Call::<TestRuntime, ()>::submit_teyrchain_heads_ex {
|
||||
at_relay_block: (num, [num as u8; 32].into()),
|
||||
teyrchains,
|
||||
teyrchain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
.check_obsolete_submit_teyrchain_heads()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn validate_free_submit_teyrchain_heads(
|
||||
num: RelayBlockNumber,
|
||||
teyrchains: Vec<(ParaId, ParaHash)>,
|
||||
) -> bool {
|
||||
RuntimeCall::Teyrchains(crate::Call::<TestRuntime, ()>::submit_teyrchain_heads_ex {
|
||||
at_relay_block: (num, [num as u8; 32].into()),
|
||||
teyrchains,
|
||||
teyrchain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
|
||||
is_free_execution_expected: true,
|
||||
})
|
||||
.check_obsolete_submit_teyrchain_heads()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn insert_relay_block(num: RelayBlockNumber) {
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime, crate::Instance1>::insert(
|
||||
RelayBlockHash::from([num as u8; 32]),
|
||||
StoredHeaderData { number: num, state_root: RelayBlockHash::from([10u8; 32]) },
|
||||
);
|
||||
}
|
||||
|
||||
fn sync_to_relay_header_10() {
|
||||
ParasInfo::<TestRuntime, ()>::insert(
|
||||
ParaId(1),
|
||||
ParaInfo {
|
||||
best_head_hash: BestParaHeadHash {
|
||||
at_relay_block_number: 10,
|
||||
head_hash: [1u8; 32].into(),
|
||||
},
|
||||
next_imported_hash_position: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_header_from_the_obsolete_relay_block() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#5 => tx is
|
||||
// rejected
|
||||
sync_to_relay_header_10();
|
||||
assert!(!validate_submit_teyrchain_heads(5, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_header_from_the_same_relay_block() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
||||
// rejected
|
||||
sync_to_relay_header_10();
|
||||
assert!(!validate_submit_teyrchain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_header_from_new_relay_block_with_same_hash() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
||||
// rejected
|
||||
sync_to_relay_header_10();
|
||||
assert!(!validate_submit_teyrchain_heads(20, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_header_if_pallet_is_halted() {
|
||||
run_test(|| {
|
||||
// when pezpallet is halted => tx is rejected
|
||||
sync_to_relay_header_10();
|
||||
PalletOperatingMode::<TestRuntime, ()>::put(BasicOperatingMode::Halted);
|
||||
|
||||
assert!(!validate_submit_teyrchain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_header() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#15 => tx is
|
||||
// accepted
|
||||
sync_to_relay_header_10();
|
||||
insert_relay_block(15);
|
||||
assert!(validate_submit_teyrchain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_if_more_than_one_teyrchain_is_submitted() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#5, but another
|
||||
// teyrchain head is also supplied => tx is accepted
|
||||
sync_to_relay_header_10();
|
||||
assert!(validate_submit_teyrchain_heads(
|
||||
5,
|
||||
vec![(ParaId(1), [1u8; 32].into()), (ParaId(2), [1u8; 32].into())]
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_initial_teyrchain_head_if_missing_relay_chain_header() {
|
||||
run_test(|| {
|
||||
// when relay chain header is unknown => "obsolete"
|
||||
assert!(!validate_submit_teyrchain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
// when relay chain header is unknown => "ok"
|
||||
insert_relay_block(10);
|
||||
assert!(validate_submit_teyrchain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_free_teyrchain_head_if_missing_relay_chain_header() {
|
||||
run_test(|| {
|
||||
sync_to_relay_header_10();
|
||||
// when relay chain header is unknown => "obsolete"
|
||||
assert!(!validate_submit_teyrchain_heads(15, vec![(ParaId(2), [15u8; 32].into())]));
|
||||
// when relay chain header is unknown => "ok"
|
||||
insert_relay_block(15);
|
||||
assert!(validate_submit_teyrchain_heads(15, vec![(ParaId(2), [15u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_free_teyrchain_head_if_no_free_slots_remaining() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#15 => tx should
|
||||
// be accepted
|
||||
sync_to_relay_header_10();
|
||||
insert_relay_block(15);
|
||||
// ... but since we have specified `is_free_execution_expected = true`, it'll be
|
||||
// rejected
|
||||
assert!(!validate_free_submit_teyrchain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||
// ... if we have specify `is_free_execution_expected = false`, it'll be accepted
|
||||
assert!(validate_submit_teyrchain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_free_teyrchain_head_if_improves_by_is_below_expected() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#15 => tx should
|
||||
// be accepted
|
||||
sync_to_relay_header_10();
|
||||
insert_relay_block(10 + FreeHeadersInterval::get() - 1);
|
||||
insert_relay_block(10 + FreeHeadersInterval::get());
|
||||
// try to submit at 10 + FreeHeadersInterval::get() - 1 => failure
|
||||
let relay_header = 10 + FreeHeadersInterval::get() - 1;
|
||||
assert!(!validate_free_submit_teyrchain_heads(
|
||||
relay_header,
|
||||
vec![(ParaId(1), [2u8; 32].into())]
|
||||
));
|
||||
// try to submit at 10 + FreeHeadersInterval::get() => ok
|
||||
let relay_header = 10 + FreeHeadersInterval::get();
|
||||
assert!(validate_free_submit_teyrchain_heads(
|
||||
relay_header,
|
||||
vec![(ParaId(1), [2u8; 32].into())]
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,313 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use bp_pezkuwi_core::teyrchains::ParaId;
|
||||
use pezbp_runtime::{Chain, ChainId, Teyrchain};
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types, traits::ConstU32, weights::Weight,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
testing::H256,
|
||||
traits::{BlakeTwo256, Header as HeaderT},
|
||||
MultiSignature, StateVersion,
|
||||
};
|
||||
|
||||
use crate as pezpallet_bridge_teyrchains;
|
||||
|
||||
pub type AccountId = u64;
|
||||
|
||||
pub type RelayBlockHeader =
|
||||
pezsp_runtime::generic::Header<crate::RelayBlockNumber, crate::RelayBlockHasher>;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
pub const PARAS_PALLET_NAME: &str = "Paras";
|
||||
pub const UNTRACKED_TEYRCHAIN_ID: u32 = 10;
|
||||
// use exact expected encoded size: `vec_len_size + header_number_size + state_root_hash_size`
|
||||
pub const MAXIMAL_TEYRCHAIN_HEAD_DATA_SIZE: u32 = 1 + 8 + 32;
|
||||
// total teyrchains that we use in tests
|
||||
pub const TOTAL_TEYRCHAINS: u32 = 4;
|
||||
|
||||
pub type RegularTeyrchainHeader = pezsp_runtime::testing::Header;
|
||||
pub type RegularTeyrchainHasher = BlakeTwo256;
|
||||
pub type BigTeyrchainHeader = pezsp_runtime::generic::Header<u128, BlakeTwo256>;
|
||||
|
||||
pub struct Teyrchain1;
|
||||
|
||||
impl Chain for Teyrchain1 {
|
||||
const ID: ChainId = *b"pch1";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = RegularTeyrchainHasher;
|
||||
type Header = RegularTeyrchainHeader;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for Teyrchain1 {
|
||||
const TEYRCHAIN_ID: u32 = 1;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
pub struct Teyrchain2;
|
||||
|
||||
impl Chain for Teyrchain2 {
|
||||
const ID: ChainId = *b"pch2";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = RegularTeyrchainHasher;
|
||||
type Header = RegularTeyrchainHeader;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for Teyrchain2 {
|
||||
const TEYRCHAIN_ID: u32 = 2;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
pub struct Teyrchain3;
|
||||
|
||||
impl Chain for Teyrchain3 {
|
||||
const ID: ChainId = *b"pch3";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = RegularTeyrchainHasher;
|
||||
type Header = RegularTeyrchainHeader;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for Teyrchain3 {
|
||||
const TEYRCHAIN_ID: u32 = 3;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
// this teyrchain is using u128 as block number and stored head data size exceeds limit
|
||||
pub struct BigTeyrchain;
|
||||
|
||||
impl Chain for BigTeyrchain {
|
||||
const ID: ChainId = *b"bpch";
|
||||
|
||||
type BlockNumber = u128;
|
||||
type Hash = H256;
|
||||
type Hasher = RegularTeyrchainHasher;
|
||||
type Header = BigTeyrchainHeader;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for BigTeyrchain {
|
||||
const TEYRCHAIN_ID: u32 = 4;
|
||||
const MAX_HEADER_SIZE: u32 = 2_048;
|
||||
}
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Grandpa1: pezpallet_bridge_grandpa::<Instance1>::{Pezpallet, Event<T>},
|
||||
Grandpa2: pezpallet_bridge_grandpa::<Instance2>::{Pezpallet, Event<T>},
|
||||
Teyrchains: pezpallet_bridge_teyrchains::{Call, Pezpallet, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const HeadersToKeep: u32 = 5;
|
||||
pub const FreeHeadersInterval: u32 = 15;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config<pezpallet_bridge_grandpa::Instance1> for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<2>;
|
||||
type FreeHeadersInterval = FreeHeadersInterval;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config<pezpallet_bridge_grandpa::Instance2> for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<2>;
|
||||
type FreeHeadersInterval = FreeHeadersInterval;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const HeadsToKeep: u32 = 4;
|
||||
pub const ParasPalletName: &'static str = PARAS_PALLET_NAME;
|
||||
pub GetTenFirstTeyrchains: Vec<ParaId> = (0..10).map(ParaId).collect();
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_teyrchains::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type BridgesGrandpaPalletInstance = pezpallet_bridge_grandpa::Instance1;
|
||||
type ParasPalletName = ParasPalletName;
|
||||
type ParaStoredHeaderDataBuilder = (Teyrchain1, Teyrchain2, Teyrchain3, BigTeyrchain);
|
||||
type HeadsToKeep = HeadsToKeep;
|
||||
type MaxParaHeadDataSize = ConstU32<MAXIMAL_TEYRCHAIN_HEAD_DATA_SIZE>;
|
||||
type OnNewHead = ();
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl pezpallet_bridge_teyrchains::benchmarking::Config<()> for TestRuntime {
|
||||
fn teyrchains() -> Vec<ParaId> {
|
||||
vec![
|
||||
ParaId(Teyrchain1::TEYRCHAIN_ID),
|
||||
ParaId(Teyrchain2::TEYRCHAIN_ID),
|
||||
ParaId(Teyrchain3::TEYRCHAIN_ID),
|
||||
]
|
||||
}
|
||||
|
||||
fn prepare_teyrchain_heads_proof(
|
||||
teyrchains: &[ParaId],
|
||||
_teyrchain_head_size: u32,
|
||||
_proof_params: pezbp_runtime::UnverifiedStorageProofParams,
|
||||
) -> (
|
||||
crate::RelayBlockNumber,
|
||||
crate::RelayBlockHash,
|
||||
bp_pezkuwi_core::teyrchains::ParaHeadsProof,
|
||||
Vec<(ParaId, bp_pezkuwi_core::teyrchains::ParaHash)>,
|
||||
) {
|
||||
// in mock run we only care about benchmarks correctness, not the benchmark results
|
||||
// => ignore size related arguments
|
||||
let (state_root, proof, teyrchains) =
|
||||
bp_test_utils::prepare_teyrchain_heads_proof::<RegularTeyrchainHeader>(
|
||||
teyrchains.iter().map(|p| (p.0, crate::tests::head_data(p.0, 1))).collect(),
|
||||
);
|
||||
let relay_genesis_hash = crate::tests::initialize(state_root);
|
||||
(0, relay_genesis_hash, proof, teyrchains)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
const ID: ChainId = *b"tbch";
|
||||
|
||||
type BlockNumber = crate::RelayBlockNumber;
|
||||
type Hash = crate::RelayBlockHash;
|
||||
type Hasher = crate::RelayBlockHasher;
|
||||
type Header = RelayBlockHeader;
|
||||
|
||||
type AccountId = AccountId;
|
||||
type Balance = u32;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::testing::TestSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for TestBridgedChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 16;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 64;
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
pezsp_io::TestExternalities::new(Default::default())
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
System::reset_events();
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
/// Return test relay chain header with given number.
|
||||
pub fn test_relay_header(
|
||||
num: crate::RelayBlockNumber,
|
||||
state_root: crate::RelayBlockHash,
|
||||
) -> RelayBlockHeader {
|
||||
RelayBlockHeader::new(
|
||||
num,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tools for teyrchain head proof verification.
|
||||
|
||||
use crate::{Config, GrandpaPalletOf, RelayBlockHash, RelayBlockHasher};
|
||||
use bp_header_pez_chain::{HeaderChain, HeaderChainError};
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHead, ParaId};
|
||||
use pezbp_runtime::{RawStorageProof, StorageProofChecker, StorageProofError};
|
||||
use bp_teyrchains::teyrchain_head_storage_key_at_source;
|
||||
use codec::Decode;
|
||||
use pezframe_support::traits::Get;
|
||||
|
||||
/// Abstraction over storage proof manipulation, hiding implementation details of actual storage
|
||||
/// proofs.
|
||||
pub trait StorageProofAdapter<T: Config<I>, I: 'static> {
|
||||
/// Read and decode optional value from the proof.
|
||||
fn read_and_decode_optional_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<Option<D>, StorageProofError>;
|
||||
|
||||
/// Checks if each key was read.
|
||||
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError>;
|
||||
|
||||
/// Read teyrchain head from storage proof.
|
||||
fn read_teyrchain_head(
|
||||
&mut self,
|
||||
teyrchain: ParaId,
|
||||
) -> Result<Option<ParaHead>, StorageProofError> {
|
||||
let teyrchain_head_key =
|
||||
teyrchain_head_storage_key_at_source(T::ParasPalletName::get(), teyrchain);
|
||||
self.read_and_decode_optional_value(&teyrchain_head_key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Actual storage proof adapter for teyrchain proofs.
|
||||
pub type TeyrchainsStorageProofAdapter<T, I> = RawStorageProofAdapter<T, I>;
|
||||
|
||||
/// A `StorageProofAdapter` implementation for raw storage proofs.
|
||||
pub struct RawStorageProofAdapter<T: Config<I>, I: 'static> {
|
||||
storage: StorageProofChecker<RelayBlockHasher>,
|
||||
_dummy: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> RawStorageProofAdapter<T, I> {
|
||||
/// Try to create a new instance of `RawStorageProofAdapter`.
|
||||
pub fn try_new_with_verified_storage_proof(
|
||||
relay_block_hash: RelayBlockHash,
|
||||
storage_proof: RawStorageProof,
|
||||
) -> Result<Self, HeaderChainError> {
|
||||
GrandpaPalletOf::<T, I>::verify_storage_proof(relay_block_hash, storage_proof)
|
||||
.map(|storage| RawStorageProofAdapter::<T, I> { storage, _dummy: Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> StorageProofAdapter<T, I> for RawStorageProofAdapter<T, I> {
|
||||
fn read_and_decode_optional_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<Option<D>, StorageProofError> {
|
||||
self.storage.read_and_decode_opt_value(key.as_ref())
|
||||
}
|
||||
|
||||
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError> {
|
||||
self.storage.ensure_no_unused_nodes()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_bridge_teyrchains
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-06-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `serban-ROG-Zephyrus`, CPU: `12th Gen Intel(R) Core(TM) i7-12700H`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/unknown-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_bridge_teyrchains
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/teyrchains/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pezpallet_bridge_teyrchains.
|
||||
pub trait WeightInfo {
|
||||
fn submit_teyrchain_heads_with_n_teyrchains(p: u32) -> Weight;
|
||||
fn submit_teyrchain_heads_with_1kb_proof() -> Weight;
|
||||
fn submit_teyrchain_heads_with_16kb_proof() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_bridge_teyrchains` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `p` is `[1, 2]`.
|
||||
fn submit_teyrchain_heads_with_n_teyrchains(_p: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 30_211 nanoseconds.
|
||||
Weight::from_parts(32_633_893, 3038)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
fn submit_teyrchain_heads_with_1kb_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 30_830 nanoseconds.
|
||||
Weight::from_parts(31_801_000, 3038)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
fn submit_teyrchain_heads_with_16kb_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 44_736 nanoseconds.
|
||||
Weight::from_parts(45_296_000, 3038)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `p` is `[1, 2]`.
|
||||
fn submit_teyrchain_heads_with_n_teyrchains(_p: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 30_211 nanoseconds.
|
||||
Weight::from_parts(32_633_893, 3038)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
fn submit_teyrchain_heads_with_1kb_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 30_830 nanoseconds.
|
||||
Weight::from_parts(31_801_000, 3038)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
fn submit_teyrchain_heads_with_16kb_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 44_736 nanoseconds.
|
||||
Weight::from_parts(45_296_000, 3038)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::{BridgeWeight, WeightInfo};
|
||||
|
||||
use pezbp_runtime::Size;
|
||||
use pezframe_support::weights::{RuntimeDbWeight, Weight};
|
||||
|
||||
/// Size of the regular teyrchain head.
|
||||
///
|
||||
/// It's not that we are expecting all teyrchain heads to share the same size or that we would
|
||||
/// reject all heads that have larger/lesser size. It is about head size that we use in benchmarks.
|
||||
/// Relayer would need to pay additional fee for extra bytes.
|
||||
///
|
||||
/// 384 is a bit larger (1.3 times) than the size of the randomly chosen Pezkuwi block.
|
||||
pub const DEFAULT_TEYRCHAIN_HEAD_SIZE: u32 = 384;
|
||||
|
||||
/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at
|
||||
/// some generic chain.
|
||||
pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
// Our configuration assumes that the runtime has special signed extensions used to:
|
||||
//
|
||||
// 1) boost priority of `submit_teyrchain_heads` transactions;
|
||||
//
|
||||
// 2) slash relayer if he submits an invalid transaction.
|
||||
//
|
||||
// We read and update storage values of other pallets (`pezpallet-bridge-relayers` and
|
||||
// balances/assets pezpallet). So we need to add this weight to the weight of our call.
|
||||
// Hence two following methods.
|
||||
|
||||
/// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions
|
||||
/// that are declared at runtime level.
|
||||
fn submit_teyrchain_heads_overhead_from_runtime() -> Weight;
|
||||
|
||||
/// Storage proof overhead, that is included in every storage proof.
|
||||
///
|
||||
/// The relayer would pay some extra fee for additional proof bytes, since they mean
|
||||
/// more hashing operations.
|
||||
fn expected_extra_storage_proof_size() -> u32;
|
||||
|
||||
/// Weight of the teyrchain heads delivery extrinsic.
|
||||
fn submit_teyrchain_heads_weight(
|
||||
db_weight: RuntimeDbWeight,
|
||||
proof: &impl Size,
|
||||
teyrchains_count: u32,
|
||||
) -> Weight {
|
||||
// weight of the `submit_teyrchain_heads` with exactly `teyrchains_count` teyrchain
|
||||
// heads of the default size (`DEFAULT_TEYRCHAIN_HEAD_SIZE`)
|
||||
let base_weight = Self::submit_teyrchain_heads_with_n_teyrchains(teyrchains_count);
|
||||
|
||||
// overhead because of extra storage proof bytes
|
||||
let expected_proof_size = teyrchains_count
|
||||
.saturating_mul(DEFAULT_TEYRCHAIN_HEAD_SIZE)
|
||||
.saturating_add(Self::expected_extra_storage_proof_size());
|
||||
let actual_proof_size = proof.size();
|
||||
let proof_size_overhead = Self::storage_proof_size_overhead(
|
||||
actual_proof_size.saturating_sub(expected_proof_size),
|
||||
);
|
||||
|
||||
// potential pruning weight (refunded if hasn't happened)
|
||||
let pruning_weight =
|
||||
Self::teyrchain_head_pruning_weight(db_weight).saturating_mul(teyrchains_count as u64);
|
||||
|
||||
base_weight
|
||||
.saturating_add(proof_size_overhead)
|
||||
.saturating_add(pruning_weight)
|
||||
.saturating_add(Self::submit_teyrchain_heads_overhead_from_runtime())
|
||||
}
|
||||
|
||||
/// Returns weight of single teyrchain head storage update.
|
||||
///
|
||||
/// This weight only includes db write operations that happens if teyrchain head is actually
|
||||
/// updated. All extra weights (weight of storage proof validation, additional checks, ...) is
|
||||
/// not included.
|
||||
fn teyrchain_head_storage_write_weight(db_weight: RuntimeDbWeight) -> Weight {
|
||||
// it's just a couple of operations - we need to write the hash (`ImportedParaHashes`) and
|
||||
// the head itself (`ImportedParaHeads`. Pruning is not included here
|
||||
db_weight.writes(2)
|
||||
}
|
||||
|
||||
/// Returns weight of single teyrchain head pruning.
|
||||
fn teyrchain_head_pruning_weight(db_weight: RuntimeDbWeight) -> Weight {
|
||||
// it's just one write operation, we don't want any benchmarks for that
|
||||
db_weight.writes(1)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when storage proof of given size is received.
|
||||
fn storage_proof_size_overhead(extra_proof_bytes: u32) -> Weight {
|
||||
let extra_byte_weight = (Self::submit_teyrchain_heads_with_16kb_proof() -
|
||||
Self::submit_teyrchain_heads_with_1kb_proof()) /
|
||||
(15 * 1024);
|
||||
extra_byte_weight.saturating_mul(extra_proof_bytes as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightInfoExt for () {
|
||||
fn submit_teyrchain_heads_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: pezframe_system::Config> WeightInfoExt for BridgeWeight<T> {
|
||||
fn submit_teyrchain_heads_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
[package]
|
||||
name = "pezpallet-xcm-bridge-hub-router"
|
||||
description = "Bridge hub interface for sibling/parent chains with dynamic fees support."
|
||||
version = "0.5.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-xcm-bridge-hub-router"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-xcm-bridge-hub-router = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
# Pezkuwi Dependencies
|
||||
pezkuwi-runtime-teyrchains = { workspace = true }
|
||||
xcm = { workspace = true }
|
||||
xcm-builder = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-xcm-bridge-hub-router/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezkuwi-runtime-teyrchains/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
"xcm-builder/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-xcm-bridge-hub-router/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezkuwi-runtime-teyrchains/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! XCM bridge hub router pezpallet benchmarks.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::{Bridge, BridgeState, Call};
|
||||
use pezframe_benchmarking::{benchmarks_instance_pallet, BenchmarkError};
|
||||
use pezframe_support::traits::{EnsureOrigin, Get, Hooks, UnfilteredDispatchable};
|
||||
use pezkuwi_runtime_teyrchains::FeeTracker;
|
||||
use pezsp_runtime::{traits::Zero, Saturating};
|
||||
use xcm::prelude::*;
|
||||
|
||||
/// Pezpallet we're benchmarking here.
|
||||
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
|
||||
|
||||
/// Trait that must be implemented by runtime to be able to benchmark pezpallet properly.
|
||||
pub trait Config<I: 'static>: crate::Config<I> {
|
||||
/// Fill up queue so it becomes congested.
|
||||
fn make_congested();
|
||||
|
||||
/// Returns destination which is valid for this router instance.
|
||||
/// (Needs to pass `T::Bridges`)
|
||||
/// Make sure that `SendXcm` will pass.
|
||||
fn ensure_bridged_target_destination() -> Result<Location, BenchmarkError> {
|
||||
Ok(Location::new(
|
||||
Self::UniversalLocation::get().len() as u8,
|
||||
[GlobalConsensus(Self::BridgedNetworkId::get().unwrap())],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
on_initialize_when_non_congested {
|
||||
Bridge::<T, I>::put(BridgeState {
|
||||
is_congested: false,
|
||||
delivery_fee_factor: crate::Pezpallet::<T, I>::MIN_FEE_FACTOR.saturating_mul(2.into()),
|
||||
});
|
||||
}: {
|
||||
crate::Pezpallet::<T, I>::on_initialize(Zero::zero())
|
||||
}
|
||||
|
||||
on_initialize_when_congested {
|
||||
Bridge::<T, I>::put(BridgeState {
|
||||
is_congested: false,
|
||||
delivery_fee_factor: crate::Pezpallet::<T, I>::MIN_FEE_FACTOR.saturating_mul(2.into()),
|
||||
});
|
||||
let _ = T::ensure_bridged_target_destination()?;
|
||||
T::make_congested();
|
||||
}: {
|
||||
crate::Pezpallet::<T, I>::on_initialize(Zero::zero())
|
||||
}
|
||||
|
||||
report_bridge_status {
|
||||
Bridge::<T, I>::put(BridgeState::default());
|
||||
|
||||
let origin: T::RuntimeOrigin = T::BridgeHubOrigin::try_successful_origin().expect("expected valid BridgeHubOrigin");
|
||||
let bridge_id = Default::default();
|
||||
let is_congested = true;
|
||||
|
||||
let call = Call::<T, I>::report_bridge_status { bridge_id, is_congested };
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert!(Bridge::<T, I>::get().is_congested);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Pezpallet that may be used instead of `SovereignPaidRemoteExporter` in the XCM router
|
||||
//! configuration. The main thing that the pezpallet offers is the dynamic message fee,
|
||||
//! that is computed based on the bridge queues state. It starts exponentially increasing
|
||||
//! if the queue between this chain and the sibling/child bridge hub is congested.
|
||||
//!
|
||||
//! All other bridge hub queues offer some backpressure mechanisms. So if at least one
|
||||
//! of all queues is congested, it will eventually lead to the growth of the queue at
|
||||
//! this chain.
|
||||
//!
|
||||
//! **A note on terminology**: when we mention the bridge hub here, we mean the chain that
|
||||
//! has the messages pezpallet deployed (`pezpallet-bridge-grandpa`, `pezpallet-bridge-messages`,
|
||||
//! `pezpallet-xcm-bridge-hub`, ...). It may be the system bridge hub teyrchain or any other
|
||||
//! chain.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_xcm_bridge_hub_router::MINIMAL_DELIVERY_FEE_FACTOR;
|
||||
pub use bp_xcm_bridge_hub_router::{BridgeState, XcmChannelStatusProvider};
|
||||
use codec::Encode;
|
||||
use pezframe_support::traits::Get;
|
||||
use pezkuwi_runtime_teyrchains::FeeTracker;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{FixedPointNumber, FixedU128};
|
||||
use pezsp_std::vec::Vec;
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{ExporterFor, InspectMessageQueues, SovereignPaidRemoteExporter};
|
||||
|
||||
pub use pezpallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
pub mod benchmarking;
|
||||
pub mod weights;
|
||||
|
||||
mod mock;
|
||||
|
||||
/// Maximal size of the XCM message that may be sent over bridge.
|
||||
///
|
||||
/// This should be less than the maximal size, allowed by the messages pezpallet, because
|
||||
/// the message itself is wrapped in other structs and is double encoded.
|
||||
pub const HARD_MESSAGE_SIZE_LIMIT: u32 = 32 * 1024;
|
||||
|
||||
/// The target that will be used when publishing logs related to this pezpallet.
|
||||
///
|
||||
/// This doesn't match the pattern used by other bridge pallets (`runtime::bridge-*`). But this
|
||||
/// pezpallet has significant differences with those pallets. The main one is that is intended to
|
||||
/// be deployed at sending chains. Other bridge pallets are likely to be deployed at the separate
|
||||
/// bridge hub teyrchain.
|
||||
pub const LOG_TARGET: &str = "xcm::bridge-hub-router";
|
||||
|
||||
#[pezframe_support::pezpallet]
|
||||
pub mod pezpallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pezpallet::config]
|
||||
pub trait Config<I: 'static = ()>: pezframe_system::Config {
|
||||
/// The overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
/// Benchmarks results from runtime we're plugged into.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// Universal location of this runtime.
|
||||
type UniversalLocation: Get<InteriorLocation>;
|
||||
/// Relative location of the supported sibling bridge hub.
|
||||
type SiblingBridgeHubLocation: Get<Location>;
|
||||
/// The bridged network that this config is for if specified.
|
||||
/// Also used for filtering `Bridges` by `BridgedNetworkId`.
|
||||
/// If not specified, allows all networks pass through.
|
||||
type BridgedNetworkId: Get<Option<NetworkId>>;
|
||||
/// Configuration for supported **bridged networks/locations** with **bridge location** and
|
||||
/// **possible fee**. Allows to externalize better control over allowed **bridged
|
||||
/// networks/locations**.
|
||||
type Bridges: ExporterFor;
|
||||
/// Checks the XCM version for the destination.
|
||||
type DestinationVersion: GetVersion;
|
||||
|
||||
/// Origin of the sibling bridge hub that is allowed to report bridge status.
|
||||
type BridgeHubOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
/// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location.
|
||||
type ToBridgeHubSender: SendXcm;
|
||||
/// Local XCM channel manager.
|
||||
type LocalXcmChannelManager: XcmChannelStatusProvider;
|
||||
|
||||
/// Additional fee that is paid for every byte of the outbound message.
|
||||
type ByteFee: Get<u128>;
|
||||
/// Asset that is used to paid bridge fee.
|
||||
type FeeAsset: Get<AssetId>;
|
||||
}
|
||||
|
||||
#[pezpallet::pezpallet]
|
||||
pub struct Pezpallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
#[pezpallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pezpallet<T, I> {
|
||||
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
|
||||
// if XCM channel is still congested, we don't change anything
|
||||
if T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get()) {
|
||||
return T::WeightInfo::on_initialize_when_congested();
|
||||
}
|
||||
|
||||
// if bridge has reported congestion, we don't change anything
|
||||
let mut bridge = Self::bridge();
|
||||
if bridge.is_congested {
|
||||
return T::WeightInfo::on_initialize_when_congested();
|
||||
}
|
||||
|
||||
let previous_factor = Self::get_fee_factor(());
|
||||
// if we can't decrease the delivery fee factor anymore, we don't change anything
|
||||
if !Self::do_decrease_fee_factor(&mut bridge.delivery_fee_factor) {
|
||||
return T::WeightInfo::on_initialize_when_congested();
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
from=%previous_factor,
|
||||
to=%bridge.delivery_fee_factor,
|
||||
"Bridge channel is uncongested. Decreased fee factor"
|
||||
);
|
||||
Self::deposit_event(Event::DeliveryFeeFactorDecreased {
|
||||
new_value: bridge.delivery_fee_factor,
|
||||
});
|
||||
|
||||
Bridge::<T, I>::put(bridge);
|
||||
|
||||
T::WeightInfo::on_initialize_when_non_congested()
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Notification about congested bridge queue.
|
||||
#[pezpallet::call_index(0)]
|
||||
#[pezpallet::weight(T::WeightInfo::report_bridge_status())]
|
||||
pub fn report_bridge_status(
|
||||
origin: OriginFor<T>,
|
||||
// this argument is not currently used, but to ease future migration, we'll keep it
|
||||
// here
|
||||
bridge_id: H256,
|
||||
is_congested: bool,
|
||||
) -> DispatchResult {
|
||||
T::BridgeHubOrigin::ensure_origin(origin)?;
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
from=?bridge_id,
|
||||
congested=%is_congested,
|
||||
"Received bridge status"
|
||||
);
|
||||
|
||||
Bridge::<T, I>::mutate(|bridge| {
|
||||
bridge.is_congested = is_congested;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Bridge that we are using.
|
||||
///
|
||||
/// **bridges-v1** assumptions: all outbound messages through this router are using single lane
|
||||
/// and to single remote consensus. If there is some other remote consensus that uses the same
|
||||
/// bridge hub, the separate pezpallet instance shall be used, In `v2` we'll have all required
|
||||
/// primitives (lane-id aka bridge-id, derived from XCM locations) to support multiple bridges
|
||||
/// by the same pezpallet instance.
|
||||
#[pezpallet::storage]
|
||||
pub type Bridge<T: Config<I>, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>;
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Bridge that we are using.
|
||||
pub fn bridge() -> BridgeState {
|
||||
Bridge::<T, I>::get()
|
||||
}
|
||||
|
||||
/// Called when new message is sent (queued to local outbound XCM queue) over the bridge.
|
||||
pub(crate) fn on_message_sent_to_bridge(message_size: u32) {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?message_size, "on_message_sent_to_bridge"
|
||||
);
|
||||
let _ = Bridge::<T, I>::try_mutate(|bridge| {
|
||||
let is_channel_with_bridge_hub_congested =
|
||||
T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get());
|
||||
let is_bridge_congested = bridge.is_congested;
|
||||
|
||||
// if outbound queue is not congested AND bridge has not reported congestion, do
|
||||
// nothing
|
||||
if !is_channel_with_bridge_hub_congested && !is_bridge_congested {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let previous_factor = Self::get_fee_factor(());
|
||||
// ok - we need to increase the fee factor, let's do that
|
||||
<Self as FeeTracker>::do_increase_fee_factor(
|
||||
&mut bridge.delivery_fee_factor,
|
||||
message_size as u128,
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
from=%previous_factor,
|
||||
to=%bridge.delivery_fee_factor,
|
||||
"Bridge channel is congested. Increased fee factor"
|
||||
);
|
||||
Self::deposit_event(Event::DeliveryFeeFactorIncreased {
|
||||
new_value: bridge.delivery_fee_factor,
|
||||
});
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::event]
|
||||
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// Delivery fee factor has been decreased.
|
||||
DeliveryFeeFactorDecreased {
|
||||
/// New value of the `DeliveryFeeFactor`.
|
||||
new_value: FixedU128,
|
||||
},
|
||||
/// Delivery fee factor has been increased.
|
||||
DeliveryFeeFactorIncreased {
|
||||
/// New value of the `DeliveryFeeFactor`.
|
||||
new_value: FixedU128,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// We'll be using `SovereignPaidRemoteExporter` to send remote messages over the sibling/child
|
||||
/// bridge hub.
|
||||
type ViaBridgeHubExporter<T, I> = SovereignPaidRemoteExporter<
|
||||
Pezpallet<T, I>,
|
||||
<T as Config<I>>::ToBridgeHubSender,
|
||||
<T as Config<I>>::UniversalLocation,
|
||||
>;
|
||||
|
||||
// This pezpallet acts as the `ExporterFor` for the `SovereignPaidRemoteExporter` to compute
|
||||
// message fee using fee factor.
|
||||
impl<T: Config<I>, I: 'static> ExporterFor for Pezpallet<T, I> {
|
||||
fn exporter_for(
|
||||
network: &NetworkId,
|
||||
remote_location: &InteriorLocation,
|
||||
message: &Xcm<()>,
|
||||
) -> Option<(Location, Option<Asset>)> {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?network, ?remote_location, msg=?message, "exporter_for"
|
||||
);
|
||||
// ensure that the message is sent to the expected bridged network (if specified).
|
||||
if let Some(bridged_network) = T::BridgedNetworkId::get() {
|
||||
if *network != bridged_network {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
bridged_network_id=?bridged_network, ?network, "Router does not support bridging!"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that the message is sent to the expected bridged network and location.
|
||||
let (bridge_hub_location, maybe_payment) =
|
||||
match T::Bridges::exporter_for(network, remote_location, message) {
|
||||
Some((bridge_hub_location, maybe_payment))
|
||||
if bridge_hub_location.eq(&T::SiblingBridgeHubLocation::get()) =>
|
||||
(bridge_hub_location, maybe_payment),
|
||||
_ => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
bridged_network_id=?T::BridgedNetworkId::get(),
|
||||
sibling_bridge_hub_location=?T::SiblingBridgeHubLocation::get(),
|
||||
?network,
|
||||
?remote_location,
|
||||
"Router configured does not support bridging!"
|
||||
);
|
||||
return None;
|
||||
},
|
||||
};
|
||||
|
||||
// take `base_fee` from `T::Brides`, but it has to be the same `T::FeeAsset`
|
||||
let base_fee = match maybe_payment {
|
||||
Some(payment) => match payment {
|
||||
Asset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount,
|
||||
invalid_asset => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
bridged_network_id=?T::BridgedNetworkId::get(),
|
||||
fee_asset=?T::FeeAsset::get(),
|
||||
with=?invalid_asset,
|
||||
?bridge_hub_location,
|
||||
?network,
|
||||
?remote_location,
|
||||
"Router is configured for `T::FeeAsset` which is not compatible for bridging!"
|
||||
);
|
||||
return None;
|
||||
},
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// compute fee amount. Keep in mind that this is only the bridge fee. The fee for sending
|
||||
// message from this chain to child/sibling bridge hub is determined by the
|
||||
// `Config::ToBridgeHubSender`
|
||||
let message_size = message.encoded_size();
|
||||
let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get());
|
||||
let fee_sum = base_fee.saturating_add(message_fee);
|
||||
let fee_factor = Self::get_fee_factor(());
|
||||
let fee = fee_factor.saturating_mul_int(fee_sum);
|
||||
|
||||
let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None };
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
to=?(network, remote_location),
|
||||
bridge_fee=?fee,
|
||||
%fee_factor,
|
||||
"Going to send message ({message_size} bytes) over bridge."
|
||||
);
|
||||
|
||||
Some((bridge_hub_location, fee))
|
||||
}
|
||||
}
|
||||
|
||||
// This pezpallet acts as the `SendXcm` to the sibling/child bridge hub instead of regular
|
||||
// XCMP/DMP transport. This allows injecting dynamic message fees into XCM programs that
|
||||
// are going to the bridged network.
|
||||
impl<T: Config<I>, I: 'static> SendXcm for Pezpallet<T, I> {
|
||||
type Ticket = (u32, <T::ToBridgeHubSender as SendXcm>::Ticket);
|
||||
|
||||
fn validate(
|
||||
dest: &mut Option<Location>,
|
||||
xcm: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
tracing::trace!(target: LOG_TARGET, msg=?xcm, destination=?dest, "validate");
|
||||
|
||||
// In case of success, the `ViaBridgeHubExporter` can modify XCM instructions and consume
|
||||
// `dest` / `xcm`, so we retain the clone of original message and the destination for later
|
||||
// `DestinationVersion` validation.
|
||||
let xcm_to_dest_clone = xcm.clone();
|
||||
let dest_clone = dest.clone();
|
||||
|
||||
// First, use the inner exporter to validate the destination to determine if it is even
|
||||
// routable. If it is not, return an error. If it is, then the XCM is extended with
|
||||
// instructions to pay the message fee at the sibling/child bridge hub. The cost will
|
||||
// include both the cost of (1) delivery to the sibling bridge hub (returned by
|
||||
// `Config::ToBridgeHubSender`) and (2) delivery to the bridged bridge hub (returned by
|
||||
// `Self::exporter_for`).
|
||||
match ViaBridgeHubExporter::<T, I>::validate(dest, xcm) {
|
||||
Ok((ticket, cost)) => {
|
||||
// If the ticket is ok, it means we are routing with this router, so we need to
|
||||
// apply more validations to the cloned `dest` and `xcm`, which are required here.
|
||||
let xcm_to_dest_clone = xcm_to_dest_clone.ok_or(SendError::MissingArgument)?;
|
||||
let dest_clone = dest_clone.ok_or(SendError::MissingArgument)?;
|
||||
|
||||
// We won't have access to `dest` and `xcm` in the `deliver` method, so we need to
|
||||
// precompute everything required here. However, `dest` and `xcm` were consumed by
|
||||
// `ViaBridgeHubExporter`, so we need to use their clones.
|
||||
let message_size = xcm_to_dest_clone.encoded_size() as _;
|
||||
|
||||
// The bridge doesn't support oversized or overweight messages. Therefore, it's
|
||||
// better to drop such messages here rather than at the bridge hub. Let's check the
|
||||
// message size."
|
||||
if message_size > HARD_MESSAGE_SIZE_LIMIT {
|
||||
return Err(SendError::ExceedsMaxMessageSize);
|
||||
}
|
||||
|
||||
// We need to ensure that the known `dest`'s XCM version can comprehend the current
|
||||
// `xcm` program. This may seem like an additional, unnecessary check, but it is
|
||||
// not. A similar check is probably performed by the `ViaBridgeHubExporter`, which
|
||||
// attempts to send a versioned message to the sibling bridge hub. However, the
|
||||
// local bridge hub may have a higher XCM version than the remote `dest`. Once
|
||||
// again, it is better to discard such messages here than at the bridge hub (e.g.,
|
||||
// to avoid losing funds).
|
||||
let destination_version = T::DestinationVersion::get_version_for(&dest_clone)
|
||||
.ok_or(SendError::DestinationUnsupported)?;
|
||||
VersionedXcm::from(xcm_to_dest_clone)
|
||||
.into_version(destination_version)
|
||||
.map_err(|()| SendError::DestinationUnsupported)?;
|
||||
|
||||
Ok(((message_size, ticket), cost))
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::trace!(target: LOG_TARGET, error=?e, "validate - ViaBridgeHubExporter");
|
||||
Err(e)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
// use router to enqueue message to the sibling/child bridge hub. This also should handle
|
||||
// payment for passing through this queue.
|
||||
let (message_size, ticket) = ticket;
|
||||
let xcm_hash = ViaBridgeHubExporter::<T, I>::deliver(ticket)?;
|
||||
|
||||
// increase delivery fee factor if required
|
||||
Self::on_message_sent_to_bridge(message_size);
|
||||
|
||||
tracing::trace!(target: LOG_TARGET, ?xcm_hash, "deliver - message sent");
|
||||
Ok(xcm_hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> InspectMessageQueues for Pezpallet<T, I> {
|
||||
fn clear_messages() {}
|
||||
|
||||
/// This router needs to implement `InspectMessageQueues` but doesn't have to
|
||||
/// return any messages, since it just reuses the `XcmpQueue` router.
|
||||
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> FeeTracker for Pezpallet<T, I> {
|
||||
type Id = ();
|
||||
|
||||
const MIN_FEE_FACTOR: FixedU128 = MINIMAL_DELIVERY_FEE_FACTOR;
|
||||
|
||||
fn get_fee_factor(_id: Self::Id) -> FixedU128 {
|
||||
Self::bridge().delivery_fee_factor
|
||||
}
|
||||
|
||||
fn set_fee_factor(_id: Self::Id, val: FixedU128) {
|
||||
let mut bridge = Self::bridge();
|
||||
bridge.delivery_fee_factor = val;
|
||||
Bridge::<T, I>::put(bridge);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezframe_support::assert_ok;
|
||||
use mock::*;
|
||||
|
||||
use pezframe_support::traits::Hooks;
|
||||
use pezframe_system::{EventRecord, Phase};
|
||||
use pezsp_runtime::traits::One;
|
||||
|
||||
fn congested_bridge(delivery_fee_factor: FixedU128) -> BridgeState {
|
||||
BridgeState { is_congested: true, delivery_fee_factor }
|
||||
}
|
||||
|
||||
fn uncongested_bridge(delivery_fee_factor: FixedU128) -> BridgeState {
|
||||
BridgeState { is_congested: false, delivery_fee_factor }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_fee_factor_is_one() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
Bridge::<TestRuntime, ()>::get(),
|
||||
uncongested_bridge(Pezpallet::<TestRuntime, ()>::MIN_FEE_FACTOR),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_factor_is_not_decreased_from_on_initialize_when_xcm_channel_is_congested() {
|
||||
run_test(|| {
|
||||
Bridge::<TestRuntime, ()>::put(uncongested_bridge(FixedU128::from_rational(125, 100)));
|
||||
TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get());
|
||||
|
||||
// it should not decrease, because queue is congested
|
||||
let old_delivery = XcmBridgeHubRouter::bridge();
|
||||
XcmBridgeHubRouter::on_initialize(One::one());
|
||||
assert_eq!(XcmBridgeHubRouter::bridge(), old_delivery);
|
||||
assert_eq!(System::events(), vec![]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_has_reported_congestion() {
|
||||
run_test(|| {
|
||||
Bridge::<TestRuntime, ()>::put(congested_bridge(FixedU128::from_rational(125, 100)));
|
||||
|
||||
// it should not decrease, because bridge congested
|
||||
let old_bridge = XcmBridgeHubRouter::bridge();
|
||||
XcmBridgeHubRouter::on_initialize(One::one());
|
||||
assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge);
|
||||
assert_eq!(System::events(), vec![]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() {
|
||||
run_test(|| {
|
||||
let initial_fee_factor = FixedU128::from_rational(125, 100);
|
||||
Bridge::<TestRuntime, ()>::put(uncongested_bridge(initial_fee_factor));
|
||||
|
||||
// it should eventually decrease to one
|
||||
while XcmBridgeHubRouter::bridge().delivery_fee_factor >
|
||||
Pezpallet::<TestRuntime, ()>::MIN_FEE_FACTOR
|
||||
{
|
||||
XcmBridgeHubRouter::on_initialize(One::one());
|
||||
}
|
||||
|
||||
// verify that it doesn't decrease anymore
|
||||
XcmBridgeHubRouter::on_initialize(One::one());
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::bridge(),
|
||||
uncongested_bridge(Pezpallet::<TestRuntime, ()>::MIN_FEE_FACTOR)
|
||||
);
|
||||
|
||||
// check emitted event
|
||||
let first_system_event = System::events().first().cloned();
|
||||
assert_eq!(
|
||||
first_system_event,
|
||||
Some(EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::XcmBridgeHubRouter(Event::DeliveryFeeFactorDecreased {
|
||||
new_value: initial_fee_factor / XcmBridgeHubRouter::EXPONENTIAL_FEE_BASE,
|
||||
}),
|
||||
topics: vec![],
|
||||
})
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_applicable_if_destination_is_within_other_network() {
|
||||
run_test(|| {
|
||||
// unroutable dest
|
||||
let dest = Location::new(2, [GlobalConsensus(ByGenesis([0; 32])), Teyrchain(1000)]);
|
||||
let xcm: Xcm<()> = vec![ClearOrigin].into();
|
||||
|
||||
// check that router does not consume when `NotApplicable`
|
||||
let mut xcm_wrapper = Some(xcm.clone());
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
|
||||
Err(SendError::NotApplicable),
|
||||
);
|
||||
// XCM is NOT consumed and untouched
|
||||
assert_eq!(Some(xcm.clone()), xcm_wrapper);
|
||||
|
||||
// check the full `send_xcm`
|
||||
assert_eq!(send_xcm::<XcmBridgeHubRouter>(dest, xcm,), Err(SendError::NotApplicable),);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exceeds_max_message_size_if_size_is_above_hard_limit() {
|
||||
run_test(|| {
|
||||
// routable dest with XCM version
|
||||
let dest =
|
||||
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)]);
|
||||
// oversized XCM
|
||||
let xcm: Xcm<()> = vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into();
|
||||
|
||||
// dest is routable with the inner router
|
||||
assert_ok!(ViaBridgeHubExporter::<TestRuntime, ()>::validate(
|
||||
&mut Some(dest.clone()),
|
||||
&mut Some(xcm.clone())
|
||||
));
|
||||
|
||||
// check for oversized message
|
||||
let mut xcm_wrapper = Some(xcm.clone());
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
|
||||
Err(SendError::ExceedsMaxMessageSize),
|
||||
);
|
||||
// XCM is consumed by the inner router
|
||||
assert!(xcm_wrapper.is_none());
|
||||
|
||||
// check the full `send_xcm`
|
||||
assert_eq!(
|
||||
send_xcm::<XcmBridgeHubRouter>(dest, xcm,),
|
||||
Err(SendError::ExceedsMaxMessageSize),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destination_unsupported_if_wrap_version_fails() {
|
||||
run_test(|| {
|
||||
// routable dest but we don't know XCM version
|
||||
let dest = UnknownXcmVersionForRoutableLocation::get();
|
||||
let xcm: Xcm<()> = vec![ClearOrigin].into();
|
||||
|
||||
// dest is routable with the inner router
|
||||
assert_ok!(ViaBridgeHubExporter::<TestRuntime, ()>::validate(
|
||||
&mut Some(dest.clone()),
|
||||
&mut Some(xcm.clone())
|
||||
));
|
||||
|
||||
// check that it does not pass XCM version check
|
||||
let mut xcm_wrapper = Some(xcm.clone());
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
|
||||
Err(SendError::DestinationUnsupported),
|
||||
);
|
||||
// XCM is consumed by the inner router
|
||||
assert!(xcm_wrapper.is_none());
|
||||
|
||||
// check the full `send_xcm`
|
||||
assert_eq!(
|
||||
send_xcm::<XcmBridgeHubRouter>(dest, xcm,),
|
||||
Err(SendError::DestinationUnsupported),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_proper_delivery_price() {
|
||||
run_test(|| {
|
||||
let dest = Location::new(2, [GlobalConsensus(BridgedNetworkId::get())]);
|
||||
let xcm: Xcm<()> = vec![ClearOrigin].into();
|
||||
let msg_size = xcm.encoded_size();
|
||||
|
||||
// initially the base fee is used: `BASE_FEE + BYTE_FEE * msg_size + HRMP_FEE`
|
||||
let expected_fee = BASE_FEE + BYTE_FEE * (msg_size as u128) + HRMP_FEE;
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut Some(xcm.clone()))
|
||||
.unwrap()
|
||||
.1
|
||||
.get(0),
|
||||
Some(&(BridgeFeeAsset::get(), expected_fee).into()),
|
||||
);
|
||||
|
||||
// but when factor is larger than one, it increases the fee, so it becomes:
|
||||
// `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE`
|
||||
let factor = FixedU128::from_rational(125, 100);
|
||||
Bridge::<TestRuntime, ()>::put(uncongested_bridge(factor));
|
||||
let expected_fee =
|
||||
(FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) *
|
||||
factor)
|
||||
.into_inner() / FixedU128::DIV +
|
||||
HRMP_FEE;
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm)).unwrap().1.get(0),
|
||||
Some(&(BridgeFeeAsset::get(), expected_fee).into()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sent_message_doesnt_increase_factor_if_queue_is_uncongested() {
|
||||
run_test(|| {
|
||||
let old_bridge = XcmBridgeHubRouter::bridge();
|
||||
assert_eq!(
|
||||
send_xcm::<XcmBridgeHubRouter>(
|
||||
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)]),
|
||||
vec![ClearOrigin].into(),
|
||||
)
|
||||
.map(drop),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
assert!(TestToBridgeHubSender::is_message_sent());
|
||||
assert_eq!(old_bridge, XcmBridgeHubRouter::bridge());
|
||||
|
||||
assert_eq!(System::events(), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sent_message_increases_factor_if_xcm_channel_is_congested() {
|
||||
run_test(|| {
|
||||
TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get());
|
||||
|
||||
let old_bridge = XcmBridgeHubRouter::bridge();
|
||||
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
|
||||
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)]),
|
||||
vec![ClearOrigin].into(),
|
||||
)
|
||||
.map(drop));
|
||||
|
||||
assert!(TestToBridgeHubSender::is_message_sent());
|
||||
assert!(
|
||||
old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
|
||||
);
|
||||
|
||||
// check emitted event
|
||||
let first_system_event = System::events().first().cloned();
|
||||
assert!(matches!(
|
||||
first_system_event,
|
||||
Some(EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::XcmBridgeHubRouter(
|
||||
Event::DeliveryFeeFactorIncreased { .. }
|
||||
),
|
||||
..
|
||||
})
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sent_message_increases_factor_if_bridge_has_reported_congestion() {
|
||||
run_test(|| {
|
||||
Bridge::<TestRuntime, ()>::put(congested_bridge(
|
||||
Pezpallet::<TestRuntime, ()>::MIN_FEE_FACTOR,
|
||||
));
|
||||
|
||||
let old_bridge = XcmBridgeHubRouter::bridge();
|
||||
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
|
||||
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)]),
|
||||
vec![ClearOrigin].into(),
|
||||
)
|
||||
.map(drop));
|
||||
|
||||
assert!(TestToBridgeHubSender::is_message_sent());
|
||||
assert!(
|
||||
old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
|
||||
);
|
||||
|
||||
// check emitted event
|
||||
let first_system_event = System::events().first().cloned();
|
||||
assert!(matches!(
|
||||
first_system_event,
|
||||
Some(EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::XcmBridgeHubRouter(
|
||||
Event::DeliveryFeeFactorIncreased { .. }
|
||||
),
|
||||
..
|
||||
})
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_messages_does_not_return_anything() {
|
||||
run_test(|| {
|
||||
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
|
||||
(Parent, Parent, GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)).into(),
|
||||
vec![ClearOrigin].into()
|
||||
));
|
||||
assert_eq!(XcmBridgeHubRouter::get_messages(), vec![]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pezpallet_xcm_bridge_hub_router;
|
||||
|
||||
use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
|
||||
use codec::Encode;
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types,
|
||||
traits::{Contains, Equals},
|
||||
};
|
||||
use pezsp_runtime::{traits::ConstU128, BuildStorage};
|
||||
use pezsp_std::cell::RefCell;
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{InspectMessageQueues, NetworkExportTable, NetworkExportTableItem};
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
/// HRMP fee.
|
||||
pub const HRMP_FEE: u128 = 500;
|
||||
/// Base bridge fee.
|
||||
pub const BASE_FEE: u128 = 1_000_000;
|
||||
/// Byte bridge fee.
|
||||
pub const BYTE_FEE: u128 = 1_000;
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
XcmBridgeHubRouter: pezpallet_xcm_bridge_hub_router::{Pezpallet, Storage, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub ThisNetworkId: NetworkId = Pezkuwi;
|
||||
pub BridgedNetworkId: NetworkId = Kusama;
|
||||
pub UniversalLocation: InteriorLocation = [GlobalConsensus(ThisNetworkId::get()), Teyrchain(1000)].into();
|
||||
pub SiblingBridgeHubLocation: Location = ParentThen([Teyrchain(1002)].into()).into();
|
||||
pub BridgeFeeAsset: AssetId = Location::parent().into();
|
||||
pub BridgeTable: Vec<NetworkExportTableItem>
|
||||
= vec![
|
||||
NetworkExportTableItem::new(
|
||||
BridgedNetworkId::get(),
|
||||
None,
|
||||
SiblingBridgeHubLocation::get(),
|
||||
Some((BridgeFeeAsset::get(), BASE_FEE).into())
|
||||
)
|
||||
];
|
||||
pub UnknownXcmVersionForRoutableLocation: Location = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(9999)]);
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
impl pezpallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type SiblingBridgeHubLocation = SiblingBridgeHubLocation;
|
||||
type BridgedNetworkId = BridgedNetworkId;
|
||||
type Bridges = NetworkExportTable<BridgeTable>;
|
||||
type DestinationVersion =
|
||||
LatestOrNoneForLocationVersionChecker<Equals<UnknownXcmVersionForRoutableLocation>>;
|
||||
|
||||
type BridgeHubOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type ToBridgeHubSender = TestToBridgeHubSender;
|
||||
type LocalXcmChannelManager = TestLocalXcmChannelManager;
|
||||
|
||||
type ByteFee = ConstU128<BYTE_FEE>;
|
||||
type FeeAsset = BridgeFeeAsset;
|
||||
}
|
||||
|
||||
pub struct LatestOrNoneForLocationVersionChecker<Location>(pezsp_std::marker::PhantomData<Location>);
|
||||
impl<LocationValue: Contains<Location>> GetVersion
|
||||
for LatestOrNoneForLocationVersionChecker<LocationValue>
|
||||
{
|
||||
fn get_version_for(dest: &Location) -> Option<XcmVersion> {
|
||||
if LocationValue::contains(dest) {
|
||||
return None;
|
||||
}
|
||||
Some(XCM_VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestToBridgeHubSender;
|
||||
|
||||
impl TestToBridgeHubSender {
|
||||
pub fn is_message_sent() -> bool {
|
||||
!Self::get_messages().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
impl SendXcm for TestToBridgeHubSender {
|
||||
type Ticket = (Location, Xcm<()>);
|
||||
|
||||
fn validate(
|
||||
destination: &mut Option<Location>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
let pair = (destination.take().unwrap(), message.take().unwrap());
|
||||
Ok((pair, (BridgeFeeAsset::get(), HRMP_FEE).into()))
|
||||
}
|
||||
|
||||
fn deliver(pair: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
let hash = fake_message_hash(&pair.1);
|
||||
SENT_XCM.with(|q| q.borrow_mut().push(pair));
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl InspectMessageQueues for TestToBridgeHubSender {
|
||||
fn clear_messages() {
|
||||
SENT_XCM.with(|q| q.borrow_mut().clear());
|
||||
}
|
||||
|
||||
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
|
||||
SENT_XCM.with(|q| {
|
||||
(*q.borrow())
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|(location, message)| {
|
||||
(
|
||||
VersionedLocation::from(location.clone()),
|
||||
vec![VersionedXcm::from(message.clone())],
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestLocalXcmChannelManager;
|
||||
|
||||
impl TestLocalXcmChannelManager {
|
||||
pub fn make_congested(with: &Location) {
|
||||
pezframe_support::storage::unhashed::put(
|
||||
&(b"TestLocalXcmChannelManager.Congested", with).encode()[..],
|
||||
&true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl XcmChannelStatusProvider for TestLocalXcmChannelManager {
|
||||
fn is_congested(with: &Location) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(
|
||||
&(b"TestLocalXcmChannelManager.Congested", with).encode()[..],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
|
||||
pezsp_io::TestExternalities::new(t)
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
System::reset_events();
|
||||
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn fake_message_hash<T>(message: &Xcm<T>) -> XcmHash {
|
||||
message.using_encoded(pezsp_io::hashing::blake2_256)
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_xcm_bridge_hub_router
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-08-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`
|
||||
//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/rip-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_xcm_bridge_hub_router
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/xcm-bridge-hub-router/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pezpallet_xcm_bridge_hub_router.
|
||||
pub trait WeightInfo {
|
||||
fn on_initialize_when_non_congested() -> Weight;
|
||||
fn on_initialize_when_congested() -> Weight;
|
||||
fn report_bridge_status() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
///
|
||||
/// Storage: `XcmBridgeHubRouter::DeliveryFeeFactor` (r:1 w:1)
|
||||
///
|
||||
/// Proof: `XcmBridgeHubRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16),
|
||||
/// added: 511, mode: `MaxEncodedLen`)
|
||||
fn on_initialize_when_non_congested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `52`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 11_141 nanoseconds.
|
||||
Weight::from_parts(11_339_000, 3517)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
|
||||
/// (r:1 w:0)
|
||||
///
|
||||
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
|
||||
/// w:0)
|
||||
fn on_initialize_when_congested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `82`
|
||||
// Estimated: `3547`
|
||||
// Minimum execution time: 4_239 nanoseconds.
|
||||
Weight::from_parts(4_383_000, 3547).saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
}
|
||||
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
|
||||
///
|
||||
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
|
||||
/// 512, mode: `MaxEncodedLen`)
|
||||
fn report_bridge_status() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `53`
|
||||
// Estimated: `1502`
|
||||
// Minimum execution time: 10_427 nanoseconds.
|
||||
Weight::from_parts(10_682_000, 1502)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
|
||||
/// (r:1 w:0)
|
||||
///
|
||||
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
|
||||
/// w:0)
|
||||
///
|
||||
/// Storage: `XcmBridgeHubRouter::DeliveryFeeFactor` (r:1 w:1)
|
||||
///
|
||||
/// Proof: `XcmBridgeHubRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16),
|
||||
/// added: 511, mode: `MaxEncodedLen`)
|
||||
fn on_initialize_when_non_congested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `52`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 11_141 nanoseconds.
|
||||
Weight::from_parts(11_339_000, 3517)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
|
||||
/// (r:1 w:0)
|
||||
///
|
||||
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
|
||||
/// w:0)
|
||||
fn on_initialize_when_congested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `82`
|
||||
// Estimated: `3547`
|
||||
// Minimum execution time: 4_239 nanoseconds.
|
||||
Weight::from_parts(4_383_000, 3547).saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
}
|
||||
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
|
||||
///
|
||||
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
|
||||
/// 512, mode: `MaxEncodedLen`)
|
||||
fn report_bridge_status() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `53`
|
||||
// Estimated: `1502`
|
||||
// Minimum execution time: 10_427 nanoseconds.
|
||||
Weight::from_parts(10_682_000, 1502)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
[package]
|
||||
name = "pezpallet-xcm-bridge-hub"
|
||||
description = "Module that adds dynamic bridges/lanes support to XCM infrastructure at the bridge hub."
|
||||
version = "0.2.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-xcm-bridge-hub"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-messages = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
bp-xcm-bridge-hub = { workspace = true }
|
||||
pezpallet-bridge-messages = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
# Pezkuwi Dependencies
|
||||
xcm = { workspace = true }
|
||||
xcm-builder = { workspace = true }
|
||||
xcm-executor = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-xcm-bridge-hub-router = { workspace = true }
|
||||
pezpallet-balances = { workspace = true }
|
||||
pezpallet-xcm-bridge-hub-router = { workspace = true }
|
||||
pezkuwi-teyrchain-primitives = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-xcm-bridge-hub-router/std",
|
||||
"bp-xcm-bridge-hub/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-bridge-messages/std",
|
||||
"pezpallet-xcm-bridge-hub-router/std",
|
||||
"pezkuwi-teyrchain-primitives/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
"xcm-builder/std",
|
||||
"xcm-executor/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-xcm-bridge-hub-router/runtime-benchmarks",
|
||||
"bp-xcm-bridge-hub/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-bridge-messages/runtime-benchmarks",
|
||||
"pezpallet-xcm-bridge-hub-router/runtime-benchmarks",
|
||||
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
"xcm-executor/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-bridge-messages/try-runtime",
|
||||
"pezpallet-xcm-bridge-hub-router/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The code that allows to use the pezpallet (`pezpallet-xcm-bridge-hub`) as inbound
|
||||
//! bridge messages dispatcher. Internally, it just forwards inbound blob to the
|
||||
//! XCM-level blob dispatcher, which pushes message to some other queue (e.g.
|
||||
//! to HRMP queue with the sibling target chain).
|
||||
//!
|
||||
//! This code is executed at the target bridge hub.
|
||||
|
||||
use crate::{Config, Pezpallet, LOG_TARGET};
|
||||
|
||||
use bp_messages::target_chain::{DispatchMessage, MessageDispatch};
|
||||
use pezbp_runtime::messages::MessageDispatchResult;
|
||||
use bp_xcm_bridge_hub::{LocalXcmChannelManager, XcmAsPlainPayload};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::{weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound};
|
||||
use pezpallet_bridge_messages::{Config as BridgeMessagesConfig, WeightInfoExt};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::SaturatedConversion;
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{DispatchBlob, DispatchBlobError};
|
||||
|
||||
/// Message dispatch result type for single message.
|
||||
#[derive(
|
||||
CloneNoBound,
|
||||
EqNoBound,
|
||||
PartialEqNoBound,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Debug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub enum XcmBlobMessageDispatchResult {
|
||||
/// We've been unable to decode message payload.
|
||||
InvalidPayload,
|
||||
/// Message has been dispatched.
|
||||
Dispatched,
|
||||
/// Message has **NOT** been dispatched because of given error.
|
||||
NotDispatched(#[codec(skip)] Option<DispatchBlobError>),
|
||||
}
|
||||
|
||||
/// An easy way to access associated messages pezpallet weights.
|
||||
type MessagesPalletWeights<T, I> =
|
||||
<T as BridgeMessagesConfig<<T as Config<I>>::BridgeMessagesPalletInstance>>::WeightInfo;
|
||||
|
||||
impl<T: Config<I>, I: 'static> MessageDispatch for Pezpallet<T, I>
|
||||
where
|
||||
T: BridgeMessagesConfig<T::BridgeMessagesPalletInstance, InboundPayload = XcmAsPlainPayload>,
|
||||
{
|
||||
type DispatchPayload = XcmAsPlainPayload;
|
||||
type DispatchLevelResult = XcmBlobMessageDispatchResult;
|
||||
type LaneId = T::LaneId;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
Pezpallet::<T, I>::bridge_by_lane_id(&lane)
|
||||
.and_then(|(_, bridge)| (*bridge.bridge_origin_relative_location).try_into().ok())
|
||||
.map(|recipient: Location| !T::LocalXcmChannelManager::is_congested(&recipient))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
match message.data.payload {
|
||||
Ok(ref payload) => {
|
||||
let payload_size = payload.encoded_size().saturated_into();
|
||||
MessagesPalletWeights::<T, I>::message_dispatch_weight(payload_size)
|
||||
},
|
||||
Err(_) => Weight::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
message: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
let payload = match message.data.payload {
|
||||
Ok(payload) => payload,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - payload error"
|
||||
);
|
||||
return MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
|
||||
};
|
||||
},
|
||||
};
|
||||
let dispatch_level_result = match T::BlobDispatcher::dispatch_blob(payload) {
|
||||
Ok(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - `DispatchBlob::dispatch_blob` was ok"
|
||||
);
|
||||
XcmBlobMessageDispatchResult::Dispatched
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - `DispatchBlob::dispatch_blob` failed"
|
||||
);
|
||||
XcmBlobMessageDispatchResult::NotDispatched(Some(e))
|
||||
},
|
||||
};
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf};
|
||||
|
||||
use bp_messages::{target_chain::DispatchMessageData, LaneIdType, MessageKey};
|
||||
use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
|
||||
use pezframe_support::assert_ok;
|
||||
use pezpallet_bridge_messages::InboundLaneStorage;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
fn bridge() -> (Box<BridgeLocations>, TestLaneIdType) {
|
||||
let origin = OpenBridgeOrigin::sibling_teyrchain_origin();
|
||||
let with = bridged_asset_hub_universal_location();
|
||||
let locations =
|
||||
XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
|
||||
let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
|
||||
(locations, lane_id)
|
||||
}
|
||||
|
||||
fn run_test_with_opened_bridge(test: impl FnOnce()) {
|
||||
run_test(|| {
|
||||
let (bridge, lane_id) = bridge();
|
||||
|
||||
if !Bridges::<TestRuntime, ()>::contains_key(bridge.bridge_id()) {
|
||||
// insert bridge
|
||||
Bridges::<TestRuntime, ()>::insert(
|
||||
bridge.bridge_id(),
|
||||
Bridge {
|
||||
bridge_origin_relative_location: Box::new(
|
||||
bridge.bridge_origin_relative_location().clone().into(),
|
||||
),
|
||||
bridge_origin_universal_location: Box::new(
|
||||
bridge.bridge_origin_universal_location().clone().into(),
|
||||
),
|
||||
bridge_destination_universal_location: Box::new(
|
||||
bridge.bridge_destination_universal_location().clone().into(),
|
||||
),
|
||||
state: BridgeState::Opened,
|
||||
bridge_owner_account: LocationToAccountId::convert_location(
|
||||
bridge.bridge_origin_relative_location(),
|
||||
)
|
||||
.expect("valid accountId"),
|
||||
deposit: 0,
|
||||
lane_id,
|
||||
},
|
||||
);
|
||||
LaneToBridge::<TestRuntime, ()>::insert(lane_id, bridge.bridge_id());
|
||||
|
||||
// create lanes
|
||||
let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
|
||||
if lanes_manager.create_inbound_lane(lane_id).is_ok() {
|
||||
assert_eq!(
|
||||
0,
|
||||
lanes_manager
|
||||
.active_inbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.storage()
|
||||
.data()
|
||||
.last_confirmed_nonce
|
||||
);
|
||||
}
|
||||
if lanes_manager.create_outbound_lane(lane_id).is_ok() {
|
||||
assert!(lanes_manager
|
||||
.active_outbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.is_empty());
|
||||
}
|
||||
}
|
||||
assert_ok!(XcmOverBridge::do_try_state());
|
||||
|
||||
test();
|
||||
});
|
||||
}
|
||||
|
||||
fn invalid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
|
||||
DispatchMessage {
|
||||
key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
|
||||
data: DispatchMessageData { payload: Err(codec::Error::from("test")) },
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
|
||||
DispatchMessage {
|
||||
key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
|
||||
data: DispatchMessageData { payload: Ok(vec![42]) },
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatcher_is_inactive_when_channel_with_target_chain_is_congested() {
|
||||
run_test_with_opened_bridge(|| {
|
||||
TestLocalXcmChannelManager::make_congested();
|
||||
assert!(!XcmOverBridge::is_active(bridge().1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatcher_is_active_when_channel_with_target_chain_is_not_congested() {
|
||||
run_test_with_opened_bridge(|| {
|
||||
assert!(XcmOverBridge::is_active(bridge().1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_weight_is_zero_if_we_have_failed_to_decode_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(XcmOverBridge::dispatch_weight(&mut invalid_message()), Weight::zero());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_weight_is_non_zero_if_we_have_decoded_message() {
|
||||
run_test(|| {
|
||||
assert_ne!(XcmOverBridge::dispatch_weight(&mut valid_message()), Weight::zero());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_is_not_dispatched_when_we_have_failed_to_decode_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
XcmOverBridge::dispatch(invalid_message()),
|
||||
MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
|
||||
},
|
||||
);
|
||||
assert!(!TestBlobDispatcher::is_dispatched());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_is_dispatched_when_we_have_decoded_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
XcmOverBridge::dispatch(valid_message()),
|
||||
MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::Dispatched,
|
||||
},
|
||||
);
|
||||
assert!(TestBlobDispatcher::is_dispatched());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,873 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The code that allows to use the pezpallet (`pezpallet-xcm-bridge-hub`) as XCM message
|
||||
//! exporter at the sending bridge hub. Internally, it just enqueues outbound blob
|
||||
//! in the messages pezpallet queue.
|
||||
//!
|
||||
//! This code is executed at the source bridge hub.
|
||||
|
||||
use crate::{Config, Pezpallet, LOG_TARGET};
|
||||
|
||||
use crate::{BridgeOf, Bridges};
|
||||
|
||||
use bp_messages::{
|
||||
source_chain::{MessagesBridge, OnMessagesDelivered},
|
||||
MessageNonce,
|
||||
};
|
||||
use bp_xcm_bridge_hub::{BridgeId, BridgeState, LocalXcmChannelManager, XcmAsPlainPayload};
|
||||
use pezframe_support::{ensure, traits::Get};
|
||||
use pezpallet_bridge_messages::{
|
||||
Config as BridgeMessagesConfig, Error, Pezpallet as BridgeMessagesPallet,
|
||||
};
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter};
|
||||
use xcm_executor::traits::ExportXcm;
|
||||
|
||||
/// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we
|
||||
/// suspend a bridge.
|
||||
const OUTBOUND_LANE_CONGESTED_THRESHOLD: MessageNonce = 8_192;
|
||||
|
||||
/// After we have suspended the bridge, we wait until number of messages in the outbound bridge
|
||||
/// queue drops to this count, before sending resuming the bridge.
|
||||
const OUTBOUND_LANE_UNCONGESTED_THRESHOLD: MessageNonce = 1_024;
|
||||
|
||||
/// An easy way to access `HaulBlobExporter`.
|
||||
pub type PalletAsHaulBlobExporter<T, I> = HaulBlobExporter<
|
||||
DummyHaulBlob,
|
||||
<T as Config<I>>::BridgedNetwork,
|
||||
<T as Config<I>>::DestinationVersion,
|
||||
<T as Config<I>>::MessageExportPrice,
|
||||
>;
|
||||
/// An easy way to access associated messages pezpallet.
|
||||
type MessagesPallet<T, I> = BridgeMessagesPallet<T, <T as Config<I>>::BridgeMessagesPalletInstance>;
|
||||
|
||||
impl<T: Config<I>, I: 'static> ExportXcm for Pezpallet<T, I>
|
||||
where
|
||||
T: BridgeMessagesConfig<T::BridgeMessagesPalletInstance, OutboundPayload = XcmAsPlainPayload>,
|
||||
{
|
||||
type Ticket = (
|
||||
BridgeId,
|
||||
BridgeOf<T, I>,
|
||||
<MessagesPallet<T, I> as MessagesBridge<T::OutboundPayload, T::LaneId>>::SendMessageArgs,
|
||||
XcmHash,
|
||||
);
|
||||
|
||||
fn validate(
|
||||
network: NetworkId,
|
||||
channel: u32,
|
||||
universal_source: &mut Option<InteriorLocation>,
|
||||
destination: &mut Option<InteriorLocation>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> Result<(Self::Ticket, Assets), SendError> {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?network,
|
||||
?channel,
|
||||
?universal_source,
|
||||
?destination,
|
||||
"Validate for network"
|
||||
);
|
||||
|
||||
// `HaulBlobExporter` may consume the `universal_source` and `destination` arguments, so
|
||||
// let's save them before
|
||||
let bridge_origin_universal_location =
|
||||
universal_source.clone().ok_or(SendError::MissingArgument)?;
|
||||
// Note: watch out this is `ExportMessage::destination`, which is relative to the `network`,
|
||||
// which means it does not contain `GlobalConsensus`, We need to find `BridgeId` with
|
||||
// `Self::bridge_locations` which requires **universal** location for destination.
|
||||
let bridge_destination_universal_location = {
|
||||
let dest = destination.clone().ok_or(SendError::MissingArgument)?;
|
||||
match dest.global_consensus() {
|
||||
Ok(dest_network) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?dest,
|
||||
?dest_network,
|
||||
?network,
|
||||
"Destination is already universal, checking if matches: {:?}",
|
||||
dest_network == network
|
||||
);
|
||||
ensure!(dest_network == network, SendError::NotApplicable);
|
||||
// ok, `dest` looks like a universal location, so let's use it
|
||||
dest
|
||||
},
|
||||
Err(_) => {
|
||||
// `dest` is not a universal location, so we need to prepend it with
|
||||
// `GlobalConsensus`.
|
||||
dest.pushed_front_with(GlobalConsensus(network)).map_err(|error_data| {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET, error=?error_data,
|
||||
"Destination is not a universal and prepending failed!"
|
||||
);
|
||||
SendError::NotApplicable
|
||||
})?
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// prepare the origin relative location
|
||||
let bridge_origin_relative_location =
|
||||
bridge_origin_universal_location.relative_to(&T::UniversalLocation::get());
|
||||
|
||||
// then we are able to compute the `BridgeId` and find `LaneId` used to send messages
|
||||
let locations = Self::bridge_locations(
|
||||
bridge_origin_relative_location,
|
||||
bridge_destination_universal_location.into(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET, error=?e,
|
||||
"Validate `bridge_locations` with error"
|
||||
);
|
||||
SendError::NotApplicable
|
||||
})?;
|
||||
let bridge = Self::bridge(locations.bridge_id()).ok_or_else(|| {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
bridge_origin_relative_location=?locations.bridge_origin_relative_location(),
|
||||
bridge_destination_universal_location=?locations.bridge_destination_universal_location(),
|
||||
"No opened bridge for requested"
|
||||
);
|
||||
SendError::NotApplicable
|
||||
})?;
|
||||
|
||||
// check if we are able to route the message. We use existing `HaulBlobExporter` for that.
|
||||
// It will make all required changes and will encode message properly, so that the
|
||||
// `DispatchBlob` at the bridged bridge hub will be able to decode it
|
||||
let ((blob, id), price) = PalletAsHaulBlobExporter::<T, I>::validate(
|
||||
network,
|
||||
channel,
|
||||
universal_source,
|
||||
destination,
|
||||
message,
|
||||
)?;
|
||||
|
||||
let bridge_message = MessagesPallet::<T, I>::validate_message(bridge.lane_id, &blob)
|
||||
.map_err(|e| {
|
||||
match e {
|
||||
Error::LanesManager(ref ei) => {
|
||||
tracing::error!(target: LOG_TARGET, error=?ei, "LanesManager")
|
||||
},
|
||||
Error::MessageRejectedByPallet(ref ei) => {
|
||||
tracing::error!(target: LOG_TARGET, error=?ei, "MessageRejectedByPallet")
|
||||
},
|
||||
Error::ReceptionConfirmation(ref ei) => {
|
||||
tracing::error!(target: LOG_TARGET, error=?ei, "ReceptionConfirmation")
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
topic_id=?id,
|
||||
bridge_id=?locations,
|
||||
lane_id=?bridge.lane_id,
|
||||
"XCM message cannot be exported"
|
||||
);
|
||||
SendError::Transport("BridgeValidateError")
|
||||
})?;
|
||||
|
||||
Ok(((*locations.bridge_id(), bridge, bridge_message, id), price))
|
||||
}
|
||||
|
||||
fn deliver(
|
||||
(bridge_id, bridge, bridge_message, id): Self::Ticket,
|
||||
) -> Result<XcmHash, SendError> {
|
||||
let artifacts = MessagesPallet::<T, I>::send_message(bridge_message);
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
topic_id=?id,
|
||||
bridge_id=?bridge_id,
|
||||
lane_id=?bridge.lane_id,
|
||||
nonce=%artifacts.nonce,
|
||||
"XCM message has been enqueued"
|
||||
);
|
||||
|
||||
// maybe we need switch to congested state
|
||||
Self::on_bridge_message_enqueued(bridge_id, bridge, artifacts.enqueued_messages);
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> OnMessagesDelivered<T::LaneId> for Pezpallet<T, I> {
|
||||
fn on_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) {
|
||||
Self::on_bridge_messages_delivered(lane_id, enqueued_messages);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Called when new message is pushed onto outbound bridge queue.
|
||||
fn on_bridge_message_enqueued(
|
||||
bridge_id: BridgeId,
|
||||
bridge: BridgeOf<T, I>,
|
||||
enqueued_messages: MessageNonce,
|
||||
) {
|
||||
// if the bridge queue is not congested, we don't want to do anything
|
||||
let is_congested = enqueued_messages > OUTBOUND_LANE_CONGESTED_THRESHOLD;
|
||||
if !is_congested {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/83 we either need fishermens
|
||||
// to watch this rule violation (suspended, but keep sending new messages), or we need a
|
||||
// hard limit for that like other XCM queues have
|
||||
|
||||
// check if the lane is already suspended. If it is, do nothing. We still accept new
|
||||
// messages to the suspended bridge, hoping that it'll be actually resumed soon
|
||||
if bridge.state == BridgeState::Suspended {
|
||||
return;
|
||||
}
|
||||
|
||||
// else - suspend the bridge
|
||||
let result_bridge_origin_relative_location =
|
||||
(*bridge.bridge_origin_relative_location).clone().try_into();
|
||||
let bridge_origin_relative_location = match &result_bridge_origin_relative_location {
|
||||
Ok(bridge_origin_relative_location) => bridge_origin_relative_location,
|
||||
Err(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?bridge_id,
|
||||
origin_location=?bridge.bridge_origin_relative_location,
|
||||
"Failed to convert"
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
};
|
||||
let suspend_result =
|
||||
T::LocalXcmChannelManager::suspend_bridge(bridge_origin_relative_location, bridge_id);
|
||||
match suspend_result {
|
||||
Ok(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?bridge_id,
|
||||
originated_by=?bridge.bridge_origin_relative_location,
|
||||
"Suspended"
|
||||
);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
?bridge_id,
|
||||
originated_by=?bridge.bridge_origin_relative_location,
|
||||
"Failed to suspended"
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
||||
// and remember that we have suspended the bridge
|
||||
Bridges::<T, I>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Suspended;
|
||||
});
|
||||
}
|
||||
|
||||
/// Must be called whenever we receive a message delivery confirmation.
|
||||
fn on_bridge_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) {
|
||||
// if the bridge queue is still congested, we don't want to do anything
|
||||
let is_congested = enqueued_messages > OUTBOUND_LANE_UNCONGESTED_THRESHOLD;
|
||||
if is_congested {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have not suspended the bridge before (or it is closed), we don't want to do
|
||||
// anything
|
||||
let (bridge_id, bridge) = match Self::bridge_by_lane_id(&lane_id) {
|
||||
Some(bridge) if bridge.1.state == BridgeState::Suspended => bridge,
|
||||
_ => {
|
||||
// if there is no bridge or it has been closed, then we don't need to send resume
|
||||
// signal to the local origin - it has closed bridge itself, so it should have
|
||||
// alrady pruned everything else
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// else - resume the bridge
|
||||
let bridge_origin_relative_location = (*bridge.bridge_origin_relative_location).try_into();
|
||||
let bridge_origin_relative_location = match bridge_origin_relative_location {
|
||||
Ok(bridge_origin_relative_location) => bridge_origin_relative_location,
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
"Failed to convert",
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let resume_result =
|
||||
T::LocalXcmChannelManager::resume_bridge(&bridge_origin_relative_location, bridge_id);
|
||||
match resume_result {
|
||||
Ok(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
originated_by=?bridge_origin_relative_location,
|
||||
"Resumed",
|
||||
);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
originated_by=?bridge_origin_relative_location,
|
||||
"Failed to resume"
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
||||
// and forget that we have previously suspended the bridge
|
||||
Bridges::<T, I>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Opened;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy implementation of the `HaulBlob` trait that is never called.
|
||||
///
|
||||
/// We are using `HaulBlobExporter`, which requires `HaulBlob` implementation. It assumes that
|
||||
/// there's a single channel between two bridge hubs - `HaulBlob` only accepts the blob and nothing
|
||||
/// else. But bridge messages pezpallet may have a dedicated channel (lane) for every pair of bridged
|
||||
/// chains. So we are using our own `ExportXcm` implementation, but to utilize `HaulBlobExporter` we
|
||||
/// still need this `DummyHaulBlob`.
|
||||
pub struct DummyHaulBlob;
|
||||
|
||||
impl HaulBlob for DummyHaulBlob {
|
||||
fn haul_blob(_blob: XcmAsPlainPayload) -> Result<(), HaulBlobError> {
|
||||
Err(HaulBlobError::Transport("DummyHaulBlob"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf};
|
||||
|
||||
use pezbp_runtime::RangeInclusiveExt;
|
||||
use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
|
||||
use pezframe_support::{assert_ok, traits::EnsureOrigin};
|
||||
use pezpallet_bridge_messages::InboundLaneStorage;
|
||||
use xcm_builder::{NetworkExportTable, UnpaidRemoteExporter};
|
||||
use xcm_executor::traits::{export_xcm, ConvertLocation};
|
||||
|
||||
fn universal_source() -> InteriorLocation {
|
||||
SiblingUniversalLocation::get()
|
||||
}
|
||||
|
||||
fn bridged_relative_destination() -> InteriorLocation {
|
||||
BridgedRelativeDestination::get()
|
||||
}
|
||||
|
||||
fn bridged_universal_destination() -> InteriorLocation {
|
||||
BridgedUniversalDestination::get()
|
||||
}
|
||||
|
||||
fn open_lane(origin: RuntimeOrigin) -> (BridgeLocations, TestLaneIdType) {
|
||||
// open expected outbound lane
|
||||
let with = bridged_asset_hub_universal_location();
|
||||
let locations =
|
||||
XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
|
||||
let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
|
||||
|
||||
if !Bridges::<TestRuntime, ()>::contains_key(locations.bridge_id()) {
|
||||
// insert bridge
|
||||
Bridges::<TestRuntime, ()>::insert(
|
||||
locations.bridge_id(),
|
||||
Bridge {
|
||||
bridge_origin_relative_location: Box::new(SiblingLocation::get().into()),
|
||||
bridge_origin_universal_location: Box::new(
|
||||
locations.bridge_origin_universal_location().clone().into(),
|
||||
),
|
||||
bridge_destination_universal_location: Box::new(
|
||||
locations.bridge_destination_universal_location().clone().into(),
|
||||
),
|
||||
state: BridgeState::Opened,
|
||||
bridge_owner_account: LocationToAccountId::convert_location(
|
||||
locations.bridge_origin_relative_location(),
|
||||
)
|
||||
.expect("valid accountId"),
|
||||
deposit: 0,
|
||||
lane_id,
|
||||
},
|
||||
);
|
||||
LaneToBridge::<TestRuntime, ()>::insert(lane_id, locations.bridge_id());
|
||||
|
||||
// create lanes
|
||||
let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
|
||||
if lanes_manager.create_inbound_lane(lane_id).is_ok() {
|
||||
assert_eq!(
|
||||
0,
|
||||
lanes_manager
|
||||
.active_inbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.storage()
|
||||
.data()
|
||||
.last_confirmed_nonce
|
||||
);
|
||||
}
|
||||
if lanes_manager.create_outbound_lane(lane_id).is_ok() {
|
||||
assert!(lanes_manager
|
||||
.active_outbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.is_empty());
|
||||
}
|
||||
}
|
||||
assert_ok!(XcmOverBridge::do_try_state());
|
||||
|
||||
(*locations, lane_id)
|
||||
}
|
||||
|
||||
fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) {
|
||||
let (locations, lane_id) = open_lane(OpenBridgeOrigin::sibling_teyrchain_origin());
|
||||
|
||||
// now let's try to enqueue message using our `ExportXcm` implementation
|
||||
export_xcm::<XcmOverBridge>(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
locations.bridge_origin_universal_location().clone(),
|
||||
locations.bridge_destination_universal_location().clone(),
|
||||
vec![Instruction::ClearOrigin].into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
(*locations.bridge_id(), lane_id)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_works() {
|
||||
run_test(|| {
|
||||
let (_, lane_id) = open_lane_and_send_regular_message();
|
||||
|
||||
// double check that the message has been pushed to the expected lane
|
||||
// (it should already been checked during `send_message` call)
|
||||
assert!(!LanesManagerOf::<TestRuntime, ()>::new()
|
||||
.active_outbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_does_not_suspend_the_bridge_if_outbound_bridge_queue_is_not_congested() {
|
||||
run_test(|| {
|
||||
let (bridge_id, _) = open_lane_and_send_regular_message();
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_does_not_suspend_the_bridge_if_it_is_already_suspended() {
|
||||
run_test(|| {
|
||||
let (bridge_id, _) = open_lane_and_send_regular_message();
|
||||
Bridges::<TestRuntime, ()>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Suspended;
|
||||
});
|
||||
for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD {
|
||||
open_lane_and_send_regular_message();
|
||||
}
|
||||
|
||||
open_lane_and_send_regular_message();
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_suspends_the_bridge_if_outbound_bridge_queue_is_congested() {
|
||||
run_test(|| {
|
||||
let (bridge_id, _) = open_lane_and_send_regular_message();
|
||||
for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD {
|
||||
open_lane_and_send_regular_message();
|
||||
}
|
||||
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
|
||||
|
||||
open_lane_and_send_regular_message();
|
||||
assert!(TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_is_not_resumed_if_outbound_bridge_queue_is_still_congested() {
|
||||
run_test(|| {
|
||||
let (bridge_id, lane_id) = open_lane_and_send_regular_message();
|
||||
Bridges::<TestRuntime, ()>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Suspended;
|
||||
});
|
||||
XcmOverBridge::on_bridge_messages_delivered(
|
||||
lane_id,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1,
|
||||
);
|
||||
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_is_not_resumed_if_it_was_not_suspended_before() {
|
||||
run_test(|| {
|
||||
let (bridge_id, lane_id) = open_lane_and_send_regular_message();
|
||||
XcmOverBridge::on_bridge_messages_delivered(
|
||||
lane_id,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
|
||||
);
|
||||
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_is_resumed_when_enough_messages_are_delivered() {
|
||||
run_test(|| {
|
||||
let (bridge_id, lane_id) = open_lane_and_send_regular_message();
|
||||
Bridges::<TestRuntime, ()>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Suspended;
|
||||
});
|
||||
XcmOverBridge::on_bridge_messages_delivered(
|
||||
lane_id,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
|
||||
);
|
||||
|
||||
assert!(TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_fails_if_argument_is_missing() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut None,
|
||||
&mut Some(bridged_relative_destination()),
|
||||
&mut Some(Vec::new().into()),
|
||||
),
|
||||
Err(SendError::MissingArgument),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
&mut None,
|
||||
&mut Some(Vec::new().into()),
|
||||
),
|
||||
Err(SendError::MissingArgument),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_computes_correct_lane_id() {
|
||||
run_test(|| {
|
||||
assert_ne!(bridged_universal_destination(), bridged_relative_destination());
|
||||
|
||||
let locations = BridgeLocations::bridge_locations(
|
||||
UniversalLocation::get(),
|
||||
SiblingLocation::get(),
|
||||
bridged_universal_destination(),
|
||||
BridgedRelayNetwork::get(),
|
||||
)
|
||||
.unwrap();
|
||||
let expected_bridge_id = locations.bridge_id();
|
||||
let expected_lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
|
||||
|
||||
if LanesManagerOf::<TestRuntime, ()>::new()
|
||||
.create_outbound_lane(expected_lane_id)
|
||||
.is_ok()
|
||||
{
|
||||
Bridges::<TestRuntime, ()>::insert(
|
||||
expected_bridge_id,
|
||||
Bridge {
|
||||
bridge_origin_relative_location: Box::new(
|
||||
locations.bridge_origin_relative_location().clone().into(),
|
||||
),
|
||||
bridge_origin_universal_location: Box::new(
|
||||
locations.bridge_origin_universal_location().clone().into(),
|
||||
),
|
||||
bridge_destination_universal_location: Box::new(
|
||||
locations.bridge_destination_universal_location().clone().into(),
|
||||
),
|
||||
state: BridgeState::Opened,
|
||||
bridge_owner_account: [0u8; 32].into(),
|
||||
deposit: 0,
|
||||
lane_id: expected_lane_id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let ticket = XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
// Note: The `ExportMessage` expects relative `InteriorLocation` in the
|
||||
// `BridgedRelayNetwork`.
|
||||
&mut Some(bridged_relative_destination()),
|
||||
&mut Some(Vec::new().into()),
|
||||
)
|
||||
.unwrap()
|
||||
.0;
|
||||
assert_eq!(&ticket.0, expected_bridge_id);
|
||||
assert_eq!(ticket.1.lane_id, expected_lane_id);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_is_compatible_with_pallet_xcm_bridge_hub_router() {
|
||||
run_test(|| {
|
||||
// valid routable destination
|
||||
let dest = Location::new(2, BridgedUniversalDestination::get());
|
||||
|
||||
// open bridge
|
||||
let origin = OpenBridgeOrigin::sibling_teyrchain_origin();
|
||||
let origin_as_location =
|
||||
OpenBridgeOriginOf::<TestRuntime, ()>::try_origin(origin.clone()).unwrap();
|
||||
let (_, expected_lane_id) = open_lane(origin);
|
||||
|
||||
// check before - no messages
|
||||
assert_eq!(
|
||||
pezpallet_bridge_messages::Pezpallet::<TestRuntime, ()>::outbound_lane_data(
|
||||
expected_lane_id
|
||||
)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.saturating_len(),
|
||||
0
|
||||
);
|
||||
|
||||
// send `ExportMessage(message)` by `UnpaidRemoteExporter`.
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location);
|
||||
assert_ok!(send_xcm::<
|
||||
UnpaidRemoteExporter<
|
||||
NetworkExportTable<BridgeTable>,
|
||||
ExecuteXcmOverSendXcm,
|
||||
UniversalLocation,
|
||||
>,
|
||||
>(dest.clone(), Xcm::<()>::default()));
|
||||
|
||||
// we need to set `UniversalLocation` for `sibling_teyrchain_origin` for
|
||||
// `XcmOverBridgeWrappedWithExportMessageRouterInstance`.
|
||||
ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get()));
|
||||
// send `ExportMessage(message)` by `pezpallet_xcm_bridge_hub_router`.
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(SiblingLocation::get());
|
||||
assert_ok!(send_xcm::<XcmOverBridgeWrappedWithExportMessageRouter>(
|
||||
dest.clone(),
|
||||
Xcm::<()>::default()
|
||||
));
|
||||
|
||||
// check after - a message ready to be relayed
|
||||
assert_eq!(
|
||||
pezpallet_bridge_messages::Pezpallet::<TestRuntime, ()>::outbound_lane_data(
|
||||
expected_lane_id
|
||||
)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.saturating_len(),
|
||||
2
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_works() {
|
||||
run_test(|| {
|
||||
let xcm: Xcm<()> = vec![ClearOrigin].into();
|
||||
|
||||
// check that router does not consume when `NotApplicable`
|
||||
let mut xcm_wrapper = Some(xcm.clone());
|
||||
let mut universal_source_wrapper = Some(universal_source());
|
||||
|
||||
// wrong `NetworkId`
|
||||
let mut dest_wrapper = Some(bridged_relative_destination());
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
NetworkId::ByGenesis([0; 32]),
|
||||
0,
|
||||
&mut universal_source_wrapper,
|
||||
&mut dest_wrapper,
|
||||
&mut xcm_wrapper,
|
||||
),
|
||||
Err(SendError::NotApplicable),
|
||||
);
|
||||
// dest and xcm is NOT consumed and untouched
|
||||
assert_eq!(&Some(xcm.clone()), &xcm_wrapper);
|
||||
assert_eq!(&Some(universal_source()), &universal_source_wrapper);
|
||||
assert_eq!(&Some(bridged_relative_destination()), &dest_wrapper);
|
||||
|
||||
// dest starts with wrong `NetworkId`
|
||||
let mut invalid_dest_wrapper = Some(
|
||||
[GlobalConsensus(NetworkId::ByGenesis([0; 32])), Teyrchain(BRIDGED_ASSET_HUB_ID)]
|
||||
.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
&mut invalid_dest_wrapper,
|
||||
&mut xcm_wrapper,
|
||||
),
|
||||
Err(SendError::NotApplicable),
|
||||
);
|
||||
// dest and xcm is NOT consumed and untouched
|
||||
assert_eq!(&Some(xcm.clone()), &xcm_wrapper);
|
||||
assert_eq!(&Some(universal_source()), &universal_source_wrapper);
|
||||
assert_eq!(
|
||||
&Some(
|
||||
[
|
||||
GlobalConsensus(NetworkId::ByGenesis([0; 32]),),
|
||||
Teyrchain(BRIDGED_ASSET_HUB_ID)
|
||||
]
|
||||
.into()
|
||||
),
|
||||
&invalid_dest_wrapper
|
||||
);
|
||||
|
||||
// no opened lane for dest
|
||||
let mut dest_without_lane_wrapper =
|
||||
Some([GlobalConsensus(BridgedRelayNetwork::get()), Teyrchain(5679)].into());
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
&mut dest_without_lane_wrapper,
|
||||
&mut xcm_wrapper,
|
||||
),
|
||||
Err(SendError::NotApplicable),
|
||||
);
|
||||
// dest and xcm is NOT consumed and untouched
|
||||
assert_eq!(&Some(xcm.clone()), &xcm_wrapper);
|
||||
assert_eq!(&Some(universal_source()), &universal_source_wrapper);
|
||||
assert_eq!(
|
||||
&Some([GlobalConsensus(BridgedRelayNetwork::get(),), Teyrchain(5679)].into()),
|
||||
&dest_without_lane_wrapper
|
||||
);
|
||||
|
||||
// ok
|
||||
let _ = open_lane(OpenBridgeOrigin::sibling_teyrchain_origin());
|
||||
let mut dest_wrapper = Some(bridged_relative_destination());
|
||||
assert_ok!(XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
&mut dest_wrapper,
|
||||
&mut xcm_wrapper,
|
||||
));
|
||||
// dest and xcm IS consumed
|
||||
assert_eq!(None, xcm_wrapper);
|
||||
assert_eq!(&Some(universal_source()), &universal_source_wrapper);
|
||||
assert_eq!(None, dest_wrapper);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn congestion_with_pallet_xcm_bridge_hub_router_works() {
|
||||
run_test(|| {
|
||||
// valid routable destination
|
||||
let dest = Location::new(2, BridgedUniversalDestination::get());
|
||||
|
||||
fn router_bridge_state() -> pezpallet_xcm_bridge_hub_router::BridgeState {
|
||||
pezpallet_xcm_bridge_hub_router::Bridge::<
|
||||
TestRuntime,
|
||||
XcmOverBridgeWrappedWithExportMessageRouterInstance,
|
||||
>::get()
|
||||
}
|
||||
|
||||
// open two bridges
|
||||
let origin = OpenBridgeOrigin::sibling_teyrchain_origin();
|
||||
let origin_as_location =
|
||||
OpenBridgeOriginOf::<TestRuntime, ()>::try_origin(origin.clone()).unwrap();
|
||||
let (bridge_1, expected_lane_id_1) = open_lane(origin);
|
||||
|
||||
// we need to set `UniversalLocation` for `sibling_teyrchain_origin` for
|
||||
// `XcmOverBridgeWrappedWithExportMessageRouterInstance`.
|
||||
ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get()));
|
||||
|
||||
// check before
|
||||
// bridges are opened
|
||||
assert_eq!(
|
||||
XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
|
||||
BridgeState::Opened
|
||||
);
|
||||
|
||||
// the router is uncongested
|
||||
assert!(!router_bridge_state().is_congested);
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id()));
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
|
||||
|
||||
// make bridges congested with sending too much messages
|
||||
for _ in 1..(OUTBOUND_LANE_CONGESTED_THRESHOLD + 2) {
|
||||
// send `ExportMessage(message)` by `pezpallet_xcm_bridge_hub_router`.
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location.clone());
|
||||
assert_ok!(send_xcm::<XcmOverBridgeWrappedWithExportMessageRouter>(
|
||||
dest.clone(),
|
||||
Xcm::<()>::default()
|
||||
));
|
||||
}
|
||||
|
||||
// checks after
|
||||
// bridges are suspended
|
||||
assert_eq!(
|
||||
XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
|
||||
BridgeState::Suspended,
|
||||
);
|
||||
// the router is congested
|
||||
assert!(router_bridge_state().is_congested);
|
||||
assert!(TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id()));
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
|
||||
|
||||
// make bridges uncongested to trigger resume signal
|
||||
XcmOverBridge::on_bridge_messages_delivered(
|
||||
expected_lane_id_1,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
|
||||
);
|
||||
|
||||
// bridge is again opened
|
||||
assert_eq!(
|
||||
XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
|
||||
BridgeState::Opened
|
||||
);
|
||||
// the router is uncongested
|
||||
assert!(!router_bridge_state().is_congested);
|
||||
assert!(TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,154 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A module that is responsible for migration of storage.
|
||||
|
||||
use crate::{Config, Pezpallet, LOG_TARGET};
|
||||
use pezframe_support::{
|
||||
traits::{Get, OnRuntimeUpgrade, StorageVersion},
|
||||
weights::Weight,
|
||||
};
|
||||
use xcm::prelude::{InteriorLocation, Location};
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
|
||||
|
||||
/// This migration does not modify storage but can be used to open a bridge and link it to the
|
||||
/// specified LaneId. This is useful when we want to open a bridge and use a custom LaneId instead
|
||||
/// of the pre-calculated one provided by the `fn open_bridge extrinsic`.
|
||||
/// Or perhaps if you want to ensure that your runtime (e.g., for testing) always has an open
|
||||
/// bridge.
|
||||
pub struct OpenBridgeForLane<
|
||||
T,
|
||||
I,
|
||||
Lane,
|
||||
CreateLane,
|
||||
SourceRelativeLocation,
|
||||
BridgedUniversalLocation,
|
||||
>(
|
||||
core::marker::PhantomData<(
|
||||
T,
|
||||
I,
|
||||
Lane,
|
||||
CreateLane,
|
||||
SourceRelativeLocation,
|
||||
BridgedUniversalLocation,
|
||||
)>,
|
||||
);
|
||||
impl<
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
Lane: Get<T::LaneId>,
|
||||
CreateLane: Get<bool>,
|
||||
SourceRelativeLocation: Get<Location>,
|
||||
BridgedUniversalLocation: Get<InteriorLocation>,
|
||||
> OnRuntimeUpgrade
|
||||
for OpenBridgeForLane<T, I, Lane, CreateLane, SourceRelativeLocation, BridgedUniversalLocation>
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let bridge_origin_relative_location = SourceRelativeLocation::get();
|
||||
let bridge_destination_universal_location = BridgedUniversalLocation::get();
|
||||
let lane_id = Lane::get();
|
||||
let create_lane = CreateLane::get();
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
?create_lane,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - going to open bridge"
|
||||
);
|
||||
|
||||
let locations = match Pezpallet::<T, I>::bridge_locations(
|
||||
bridge_origin_relative_location.clone(),
|
||||
bridge_destination_universal_location.clone(),
|
||||
) {
|
||||
Ok(locations) => locations,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
"OpenBridgeForLane - on_runtime_upgrade failed to construct bridge_locations"
|
||||
);
|
||||
return T::DbWeight::get().reads(0);
|
||||
},
|
||||
};
|
||||
|
||||
// check if already exists
|
||||
if let Some((bridge_id, bridge)) = Pezpallet::<T, I>::bridge_by_lane_id(&lane_id) {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?bridge,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
"OpenBridgeForLane - already exist!"
|
||||
);
|
||||
if &bridge_id != locations.bridge_id() {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?bridge,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - check you parameters, because a different bridge exist for requested!"
|
||||
);
|
||||
}
|
||||
|
||||
return T::DbWeight::get().reads(2);
|
||||
}
|
||||
|
||||
if let Err(e) = Pezpallet::<T, I>::do_open_bridge(locations, lane_id, create_lane) {
|
||||
tracing::error!(target: LOG_TARGET, error=?e, "OpenBridgeForLane - do_open_bridge failed");
|
||||
T::DbWeight::get().reads(6)
|
||||
} else {
|
||||
tracing::info!(target: LOG_TARGET, "OpenBridgeForLane - do_open_bridge passed!");
|
||||
T::DbWeight::get().reads_writes(6, 4)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(_state: pezsp_std::vec::Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
let bridge_origin_relative_location = SourceRelativeLocation::get();
|
||||
let bridge_destination_universal_location = BridgedUniversalLocation::get();
|
||||
let lane_id = Lane::get();
|
||||
|
||||
// check that requested bridge is stored
|
||||
let Ok(locations) = Pezpallet::<T, I>::bridge_locations(
|
||||
bridge_origin_relative_location.clone(),
|
||||
bridge_destination_universal_location.clone(),
|
||||
) else {
|
||||
return Err(pezsp_runtime::DispatchError::Other("Invalid locations!"));
|
||||
};
|
||||
let Some((bridge_id, _)) = Pezpallet::<T, I>::bridge_by_lane_id(&lane_id) else {
|
||||
return Err(pezsp_runtime::DispatchError::Other("Missing bridge!"));
|
||||
};
|
||||
pezframe_support::ensure!(
|
||||
locations.bridge_id() == &bridge_id,
|
||||
"Bridge is not stored correctly!"
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - post_upgrade found opened bridge"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pezpallet_xcm_bridge_hub;
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, MessageDispatch},
|
||||
ChainWithMessages, HashedLaneId, MessageNonce,
|
||||
};
|
||||
use pezbp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf};
|
||||
use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{
|
||||
assert_ok, derive_impl, parameter_types,
|
||||
traits::{EnsureOrigin, Equals, Everything, Get, OriginTrait},
|
||||
weights::RuntimeDbWeight,
|
||||
};
|
||||
use pezkuwi_teyrchain_primitives::primitives::Sibling;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
testing::Header as BizinikiwiHeader,
|
||||
traits::{BlakeTwo256, ConstU128, ConstU32, IdentityLookup},
|
||||
AccountId32, BuildStorage, StateVersion,
|
||||
};
|
||||
use pezsp_std::cell::RefCell;
|
||||
use xcm::{latest::PEZKUWICHAIN_GENESIS_HASH, prelude::*};
|
||||
use xcm_builder::{
|
||||
AllowUnpaidExecutionFrom, DispatchBlob, DispatchBlobError, FixedWeightBounds,
|
||||
InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset,
|
||||
SiblingTeyrchainConvertsVia,
|
||||
};
|
||||
use xcm_executor::{traits::ConvertOrigin, XcmExecutor};
|
||||
|
||||
pub type AccountId = AccountId32;
|
||||
pub type Balance = u64;
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
pub type TestLaneIdType = HashedLaneId;
|
||||
|
||||
pub const SIBLING_ASSET_HUB_ID: u32 = 2001;
|
||||
pub const THIS_BRIDGE_HUB_ID: u32 = 2002;
|
||||
pub const BRIDGED_ASSET_HUB_ID: u32 = 1001;
|
||||
|
||||
pezframe_support::construct_runtime! {
|
||||
pub enum TestRuntime {
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Balances: pezpallet_balances::{Pezpallet, Event<T>},
|
||||
Messages: pezpallet_bridge_messages::{Pezpallet, Call, Event<T>},
|
||||
XcmOverBridge: pezpallet_xcm_bridge_hub::{Pezpallet, Call, HoldReason, Event<T>},
|
||||
XcmOverBridgeWrappedWithExportMessageRouter: pezpallet_xcm_bridge_hub_router = 57,
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
||||
pub const ExistentialDeposit: Balance = 1;
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type AccountId = AccountId;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
type Block = Block;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = TestMessagesWeights;
|
||||
|
||||
type ThisChain = ThisUnderlyingChain;
|
||||
type BridgedChain = BridgedUnderlyingChain;
|
||||
type BridgedHeaderChain = BridgedHeaderChain;
|
||||
|
||||
type OutboundPayload = Vec<u8>;
|
||||
type InboundPayload = Vec<u8>;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
type DeliveryPayments = ();
|
||||
type DeliveryConfirmationPayments = ();
|
||||
type OnMessagesDelivered = ();
|
||||
|
||||
type MessageDispatch = TestMessageDispatch;
|
||||
}
|
||||
|
||||
pub struct TestMessagesWeights;
|
||||
|
||||
impl pezpallet_bridge_messages::WeightInfo for TestMessagesWeights {
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_n_messages_proof(_: u32) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_n_bytes_message_proof(_: u32) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(_n: u32) -> Weight {
|
||||
Weight::from_parts(1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::WeightInfoExt for TestMessagesWeights {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const RelayNetwork: NetworkId = NetworkId::Kusama;
|
||||
pub UniversalLocation: InteriorLocation = [
|
||||
GlobalConsensus(RelayNetwork::get()),
|
||||
Teyrchain(THIS_BRIDGE_HUB_ID),
|
||||
].into();
|
||||
pub SiblingLocation: Location = Location::new(1, [Teyrchain(SIBLING_ASSET_HUB_ID)]);
|
||||
pub SiblingUniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Teyrchain(SIBLING_ASSET_HUB_ID)].into();
|
||||
|
||||
pub const BridgedRelayNetwork: NetworkId = NetworkId::ByGenesis([1; 32]);
|
||||
pub BridgedRelayNetworkLocation: Location = (Parent, GlobalConsensus(BridgedRelayNetwork::get())).into();
|
||||
pub BridgedRelativeDestination: InteriorLocation = [Teyrchain(BRIDGED_ASSET_HUB_ID)].into();
|
||||
pub BridgedUniversalDestination: InteriorLocation = [GlobalConsensus(BridgedRelayNetwork::get()), Teyrchain(BRIDGED_ASSET_HUB_ID)].into();
|
||||
pub const NonBridgedRelayNetwork: NetworkId = NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH);
|
||||
|
||||
pub const BridgeDeposit: Balance = 100_000;
|
||||
|
||||
// configuration for pezpallet_xcm_bridge_hub_router
|
||||
pub BridgeHubLocation: Location = Here.into();
|
||||
pub BridgeFeeAsset: AssetId = Location::here().into();
|
||||
pub BridgeTable: Vec<NetworkExportTableItem>
|
||||
= vec![
|
||||
NetworkExportTableItem::new(
|
||||
BridgedRelayNetwork::get(),
|
||||
None,
|
||||
BridgeHubLocation::get(),
|
||||
None
|
||||
)
|
||||
];
|
||||
pub UnitWeightCost: Weight = Weight::from_parts(10, 10);
|
||||
}
|
||||
|
||||
/// **Universal** `InteriorLocation` of bridged asset hub.
|
||||
pub fn bridged_asset_hub_universal_location() -> InteriorLocation {
|
||||
BridgedUniversalDestination::get()
|
||||
}
|
||||
|
||||
impl pezpallet_xcm_bridge_hub::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type BridgedNetwork = BridgedRelayNetworkLocation;
|
||||
type BridgeMessagesPalletInstance = ();
|
||||
|
||||
type MessageExportPrice = ();
|
||||
type DestinationVersion = AlwaysLatest;
|
||||
|
||||
type ForceOrigin = pezframe_system::EnsureNever<()>;
|
||||
type OpenBridgeOrigin = OpenBridgeOrigin;
|
||||
type BridgeOriginAccountIdConverter = LocationToAccountId;
|
||||
|
||||
type BridgeDeposit = BridgeDeposit;
|
||||
type Currency = Balances;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type AllowWithoutBridgeDeposit = Equals<ParentRelayChainLocation>;
|
||||
|
||||
type LocalXcmChannelManager = TestLocalXcmChannelManager;
|
||||
|
||||
type BlobDispatcher = TestBlobDispatcher;
|
||||
}
|
||||
|
||||
/// A router instance simulates a scenario where the router is deployed on a different chain than
|
||||
/// the `MessageExporter`. This means that the router sends an `ExportMessage`.
|
||||
pub type XcmOverBridgeWrappedWithExportMessageRouterInstance = ();
|
||||
impl pezpallet_xcm_bridge_hub_router::Config<XcmOverBridgeWrappedWithExportMessageRouterInstance>
|
||||
for TestRuntime
|
||||
{
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
|
||||
type UniversalLocation = ExportMessageOriginUniversalLocation;
|
||||
type SiblingBridgeHubLocation = BridgeHubLocation;
|
||||
type BridgedNetworkId = BridgedRelayNetwork;
|
||||
type Bridges = NetworkExportTable<BridgeTable>;
|
||||
type DestinationVersion = AlwaysLatest;
|
||||
|
||||
// We convert to root `here` location with `BridgeHubLocationXcmOriginAsRoot`
|
||||
type BridgeHubOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
// **Note**: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which
|
||||
// calls the `ExportXcm` implementation of `pezpallet_xcm_bridge_hub` as the
|
||||
// `MessageExporter`.
|
||||
type ToBridgeHubSender = ExecuteXcmOverSendXcm;
|
||||
type LocalXcmChannelManager = TestLocalXcmChannelManager;
|
||||
|
||||
type ByteFee = ConstU128<0>;
|
||||
type FeeAsset = BridgeFeeAsset;
|
||||
}
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl xcm_executor::Config for XcmConfig {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type XcmSender = ();
|
||||
type XcmEventEmitter = ();
|
||||
type AssetTransactor = ();
|
||||
type OriginConverter = BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>;
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = ();
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = AllowUnpaidExecutionFrom<Everything>;
|
||||
type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, ConstU32<100>>;
|
||||
type Trader = ();
|
||||
type ResponseHandler = ();
|
||||
type AssetTrap = ();
|
||||
type AssetClaims = ();
|
||||
type SubscriptionService = ();
|
||||
type PalletInstancesInfo = ();
|
||||
type MaxAssetsIntoHolding = ();
|
||||
type AssetLocker = ();
|
||||
type AssetExchanger = ();
|
||||
type FeeManager = ();
|
||||
// We just set `MessageExporter` as our `pezpallet_xcm_bridge_hub` instance.
|
||||
type MessageExporter = (XcmOverBridge,);
|
||||
type UniversalAliases = ();
|
||||
type CallDispatcher = RuntimeCall;
|
||||
type SafeCallFilter = Everything;
|
||||
type Aliasers = ();
|
||||
type TransactionalProcessor = ();
|
||||
type HrmpNewChannelOpenRequestHandler = ();
|
||||
type HrmpChannelAcceptedHandler = ();
|
||||
type HrmpChannelClosingHandler = ();
|
||||
type XcmRecorder = ();
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static EXECUTE_XCM_ORIGIN: RefCell<Option<Location>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// The `SendXcm` implementation directly executes XCM using `XcmExecutor`.
|
||||
///
|
||||
/// We ensure that the `ExportMessage` produced by `pezpallet_xcm_bridge_hub_router` is compatible with
|
||||
/// the `ExportXcm` implementation of `pezpallet_xcm_bridge_hub`.
|
||||
///
|
||||
/// Note: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which calls the
|
||||
/// `ExportXcm` implementation of `pezpallet_xcm_bridge_hub` as `MessageExporter`.
|
||||
pub struct ExecuteXcmOverSendXcm;
|
||||
impl SendXcm for ExecuteXcmOverSendXcm {
|
||||
type Ticket = Xcm<()>;
|
||||
|
||||
fn validate(
|
||||
_: &mut Option<Location>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
Ok((message.take().unwrap(), Assets::new()))
|
||||
}
|
||||
|
||||
fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
let xcm: Xcm<RuntimeCall> = ticket.into();
|
||||
|
||||
let origin = EXECUTE_XCM_ORIGIN.with(|o| o.borrow().clone().unwrap());
|
||||
let mut hash = xcm.using_encoded(pezsp_io::hashing::blake2_256);
|
||||
let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
|
||||
origin,
|
||||
xcm,
|
||||
&mut hash,
|
||||
Weight::MAX,
|
||||
Weight::zero(),
|
||||
);
|
||||
assert_ok!(outcome.ensure_complete());
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
impl InspectMessageQueues for ExecuteXcmOverSendXcm {
|
||||
fn clear_messages() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
impl ExecuteXcmOverSendXcm {
|
||||
pub fn set_origin_for_execute(origin: Location) {
|
||||
EXECUTE_XCM_ORIGIN.with(|o| *o.borrow_mut() = Some(origin));
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic way to set different universal location for the origin which sends `ExportMessage`.
|
||||
pub struct ExportMessageOriginUniversalLocation;
|
||||
impl ExportMessageOriginUniversalLocation {
|
||||
pub(crate) fn set(universal_location: Option<InteriorLocation>) {
|
||||
EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| *o.borrow_mut() = universal_location);
|
||||
}
|
||||
}
|
||||
impl Get<InteriorLocation> for ExportMessageOriginUniversalLocation {
|
||||
fn get() -> InteriorLocation {
|
||||
EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| {
|
||||
o.borrow()
|
||||
.clone()
|
||||
.expect("`EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION` is not set!")
|
||||
})
|
||||
}
|
||||
}
|
||||
thread_local! {
|
||||
pub static EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION: RefCell<Option<InteriorLocation>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub struct BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>(
|
||||
pezsp_std::marker::PhantomData<RuntimeOrigin>,
|
||||
);
|
||||
impl<RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
|
||||
for BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>
|
||||
{
|
||||
fn convert_origin(
|
||||
origin: impl Into<Location>,
|
||||
kind: OriginKind,
|
||||
) -> Result<RuntimeOrigin, Location> {
|
||||
let origin = origin.into();
|
||||
if kind == OriginKind::Xcm && origin.eq(&BridgeHubLocation::get()) {
|
||||
Ok(RuntimeOrigin::root())
|
||||
} else {
|
||||
Err(origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for specifying how a `Location` can be converted into an `AccountId`. This is used
|
||||
/// when determining ownership of accounts for asset transacting and when attempting to use XCM
|
||||
/// `Transact` in order to determine the dispatch Origin.
|
||||
pub type LocationToAccountId = (
|
||||
// The parent (Relay-chain) origin converts to the parent `AccountId`.
|
||||
ParentIsPreset<AccountId>,
|
||||
// Sibling teyrchain origins convert to AccountId via the `ParaId::into`.
|
||||
SiblingTeyrchainConvertsVia<Sibling, AccountId>,
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub ParentRelayChainLocation: Location = Location { parents: 1, interior: Here };
|
||||
}
|
||||
pub struct OpenBridgeOrigin;
|
||||
|
||||
impl OpenBridgeOrigin {
|
||||
pub fn parent_relay_chain_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([0u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn parent_relay_chain_universal_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([1u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn sibling_teyrchain_origin() -> RuntimeOrigin {
|
||||
let mut account = [0u8; 32];
|
||||
account[..4].copy_from_slice(&SIBLING_ASSET_HUB_ID.encode()[..4]);
|
||||
RuntimeOrigin::signed(account.into())
|
||||
}
|
||||
|
||||
pub fn sibling_teyrchain_universal_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([2u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn origin_without_sovereign_account() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([3u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn disallowed_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([42u8; 32].into())
|
||||
}
|
||||
}
|
||||
|
||||
impl EnsureOrigin<RuntimeOrigin> for OpenBridgeOrigin {
|
||||
type Success = Location;
|
||||
|
||||
fn try_origin(o: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
|
||||
let signer = o.clone().into_signer();
|
||||
if signer == Self::parent_relay_chain_origin().into_signer() {
|
||||
return Ok(ParentRelayChainLocation::get());
|
||||
} else if signer == Self::parent_relay_chain_universal_origin().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 2,
|
||||
interior: GlobalConsensus(RelayNetwork::get()).into(),
|
||||
});
|
||||
} else if signer == Self::sibling_teyrchain_universal_origin().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 2,
|
||||
interior: [GlobalConsensus(RelayNetwork::get()), Teyrchain(SIBLING_ASSET_HUB_ID)]
|
||||
.into(),
|
||||
});
|
||||
} else if signer == Self::origin_without_sovereign_account().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 1,
|
||||
interior: [Teyrchain(SIBLING_ASSET_HUB_ID), OnlyChild].into(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut sibling_account = [0u8; 32];
|
||||
sibling_account[..4].copy_from_slice(&SIBLING_ASSET_HUB_ID.encode()[..4]);
|
||||
if signer == Some(sibling_account.into()) {
|
||||
return Ok(Location { parents: 1, interior: Teyrchain(SIBLING_ASSET_HUB_ID).into() });
|
||||
}
|
||||
|
||||
Err(o)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
|
||||
Ok(Self::parent_relay_chain_origin())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type OpenBridgeOriginOf<T, I> =
|
||||
<T as pezpallet_xcm_bridge_hub::Config<I>>::OpenBridgeOrigin;
|
||||
|
||||
pub struct TestLocalXcmChannelManager;
|
||||
|
||||
impl TestLocalXcmChannelManager {
|
||||
pub fn make_congested() {
|
||||
pezframe_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Congested", &true);
|
||||
}
|
||||
|
||||
fn suspended_key(bridge: &BridgeId) -> Vec<u8> {
|
||||
[b"TestLocalXcmChannelManager.Suspended", bridge.encode().as_slice()].concat()
|
||||
}
|
||||
fn resumed_key(bridge: &BridgeId) -> Vec<u8> {
|
||||
[b"TestLocalXcmChannelManager.Resumed", bridge.encode().as_slice()].concat()
|
||||
}
|
||||
|
||||
pub fn is_bridge_suspended(bridge: &BridgeId) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(&Self::suspended_key(bridge))
|
||||
}
|
||||
|
||||
pub fn is_bridge_resumed(bridge: &BridgeId) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(&Self::resumed_key(bridge))
|
||||
}
|
||||
|
||||
fn build_congestion_message(bridge: &BridgeId, is_congested: bool) -> Vec<Instruction<()>> {
|
||||
use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall;
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, scale_info::TypeInfo)]
|
||||
enum Call {
|
||||
#[codec(index = 57)]
|
||||
XcmOverBridgeWrappedWithExportMessageRouter(XcmBridgeHubRouterCall),
|
||||
}
|
||||
|
||||
pezsp_std::vec![
|
||||
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
||||
Transact {
|
||||
origin_kind: OriginKind::Xcm,
|
||||
fallback_max_weight: None,
|
||||
call: Call::XcmOverBridgeWrappedWithExportMessageRouter(
|
||||
XcmBridgeHubRouterCall::report_bridge_status {
|
||||
bridge_id: bridge.inner(),
|
||||
is_congested,
|
||||
}
|
||||
)
|
||||
.encode()
|
||||
.into(),
|
||||
},
|
||||
ExpectTransactStatus(MaybeErrorCode::Success),
|
||||
]
|
||||
}
|
||||
|
||||
fn report_bridge_status(
|
||||
local_origin: &Location,
|
||||
bridge: &BridgeId,
|
||||
is_congested: bool,
|
||||
key: Vec<u8>,
|
||||
) -> Result<(), SendError> {
|
||||
// send as BridgeHub would send to sibling chain
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(BridgeHubLocation::get());
|
||||
let result = send_xcm::<ExecuteXcmOverSendXcm>(
|
||||
local_origin.clone(),
|
||||
Self::build_congestion_message(&bridge, is_congested).into(),
|
||||
);
|
||||
|
||||
if result.is_ok() {
|
||||
pezframe_support::storage::unhashed::put(&key, &true);
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalXcmChannelManager for TestLocalXcmChannelManager {
|
||||
type Error = SendError;
|
||||
|
||||
fn is_congested(_with: &Location) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Congested")
|
||||
}
|
||||
|
||||
fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
|
||||
Self::report_bridge_status(local_origin, &bridge, true, Self::suspended_key(&bridge))
|
||||
}
|
||||
|
||||
fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
|
||||
Self::report_bridge_status(local_origin, &bridge, false, Self::resumed_key(&bridge))
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_xcm_bridge_hub_router::XcmChannelStatusProvider for TestLocalXcmChannelManager {
|
||||
fn is_congested(with: &Location) -> bool {
|
||||
<Self as LocalXcmChannelManager>::is_congested(with)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestBlobDispatcher;
|
||||
|
||||
impl TestBlobDispatcher {
|
||||
pub fn is_dispatched() -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(b"TestBlobDispatcher.Dispatched")
|
||||
}
|
||||
}
|
||||
|
||||
impl DispatchBlob for TestBlobDispatcher {
|
||||
fn dispatch_blob(_blob: Vec<u8>) -> Result<(), DispatchBlobError> {
|
||||
pezframe_support::storage::unhashed::put(b"TestBlobDispatcher.Dispatched", &true);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThisUnderlyingChain;
|
||||
|
||||
impl Chain for ThisUnderlyingChain {
|
||||
const ID: ChainId = *b"tuch";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BizinikiwiHeader;
|
||||
type AccountId = AccountId;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
u32::MAX
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for ThisUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithThisChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
pub type BridgedHeaderHash = H256;
|
||||
pub type BridgedChainHeader = BizinikiwiHeader;
|
||||
|
||||
pub struct BridgedUnderlyingChain;
|
||||
impl Chain for BridgedUnderlyingChain {
|
||||
const ID: ChainId = *b"bgdc";
|
||||
type BlockNumber = u64;
|
||||
type Hash = BridgedHeaderHash;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = AccountId;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
4096
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for BridgedUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithBridgedChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
pub struct BridgedHeaderChain;
|
||||
impl bp_header_pez_chain::HeaderChain<BridgedUnderlyingChain> for BridgedHeaderChain {
|
||||
fn finalized_header_state_root(
|
||||
_hash: HashOf<BridgedUnderlyingChain>,
|
||||
) -> Option<HashOf<BridgedUnderlyingChain>> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test message dispatcher.
|
||||
pub struct TestMessageDispatch;
|
||||
|
||||
impl TestMessageDispatch {
|
||||
pub fn deactivate(lane: TestLaneIdType) {
|
||||
pezframe_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for TestMessageDispatch {
|
||||
type DispatchPayload = Vec<u8>;
|
||||
type DispatchLevelResult = ();
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
pezframe_support::storage::unhashed::take::<bool>(&(b"inactive", lane).encode()[..]) !=
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
_message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
_: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
||||
}
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
pezsp_io::TestExternalities::new(
|
||||
pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(),
|
||||
)
|
||||
.execute_with(test)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
[package]
|
||||
name = "bp-beefy"
|
||||
description = "Primitives of pezpallet-bridge-beefy module."
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
publish = false
|
||||
documentation = "https://docs.rs/bp-beefy"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["bit-vec", "derive"], workspace = true }
|
||||
scale-info = { features = ["bit-vec", "derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
binary-merkle-tree = { workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezpallet-beefy-mmr = { workspace = true }
|
||||
pezpallet-mmr = { workspace = true }
|
||||
pezsp-consensus-beefy = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"binary-merkle-tree/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezpallet-beefy-mmr/std",
|
||||
"pezpallet-mmr/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-consensus-beefy/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"binary-merkle-tree/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezpallet-beefy-mmr/runtime-benchmarks",
|
||||
"pezpallet-mmr/runtime-benchmarks",
|
||||
"pezsp-consensus-beefy/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives that are used to interact with BEEFY bridge pezpallet.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub use binary_merkle_tree::merkle_root;
|
||||
pub use pezpallet_beefy_mmr::BeefyEcdsaToEthereum;
|
||||
pub use pezpallet_mmr::{
|
||||
primitives::{DataOrHash as MmrDataOrHash, LeafProof as MmrProof},
|
||||
verify_leaves_proof as verify_mmr_leaves_proof,
|
||||
};
|
||||
pub use pezsp_consensus_beefy::{
|
||||
ecdsa_crypto::{
|
||||
AuthorityId as EcdsaValidatorId, AuthoritySignature as EcdsaValidatorSignature,
|
||||
},
|
||||
known_payloads::MMR_ROOT_ID as MMR_ROOT_PAYLOAD_ID,
|
||||
mmr::{BeefyAuthoritySet, MmrLeafVersion},
|
||||
BeefyAuthorityId, Commitment, Payload as BeefyPayload, SignedCommitment, ValidatorSet,
|
||||
ValidatorSetId, BEEFY_ENGINE_ID,
|
||||
};
|
||||
|
||||
use pezbp_runtime::{BasicOperatingMode, BlockNumberOf, Chain, HashOf};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::Parameter;
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pezsp_runtime::{
|
||||
traits::{Convert, MaybeSerializeDeserialize},
|
||||
RuntimeAppPublic, RuntimeDebug,
|
||||
};
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// Bizinikiwi-based chain with BEEFY && MMR pallets deployed.
|
||||
///
|
||||
/// Both BEEFY and MMR pallets and their clients may be configured to use different
|
||||
/// primitives. Some of types can be configured in low-level pallets, but are constrained
|
||||
/// when BEEFY+MMR bundle is used.
|
||||
pub trait ChainWithBeefy: Chain {
|
||||
/// The hashing algorithm used to compute the digest of the BEEFY commitment.
|
||||
///
|
||||
/// Corresponds to the hashing algorithm, used by `pezsc_consensus_beefy::BeefyKeystore`.
|
||||
type CommitmentHasher: pezsp_runtime::traits::Hash;
|
||||
|
||||
/// The hashing algorithm used to build the MMR.
|
||||
///
|
||||
/// The same algorithm is also used to compute merkle roots in BEEFY
|
||||
/// (e.g. validator addresses root in leaf data).
|
||||
///
|
||||
/// Corresponds to the `Hashing` field of the `pezpallet-mmr` configuration.
|
||||
type MmrHashing: pezsp_runtime::traits::Hash<Output = Self::MmrHash>;
|
||||
|
||||
/// The output type of the hashing algorithm used to build the MMR.
|
||||
///
|
||||
/// This type is actually stored in the MMR.
|
||||
|
||||
/// Corresponds to the `Hash` field of the `pezpallet-mmr` configuration.
|
||||
type MmrHash: pezsp_std::hash::Hash
|
||||
+ Parameter
|
||||
+ Copy
|
||||
+ AsRef<[u8]>
|
||||
+ Default
|
||||
+ MaybeSerializeDeserialize
|
||||
+ PartialOrd;
|
||||
|
||||
/// The type expected for the MMR leaf extra data.
|
||||
type BeefyMmrLeafExtra: Parameter;
|
||||
|
||||
/// A way to identify a BEEFY validator.
|
||||
///
|
||||
/// Corresponds to the `BeefyId` field of the `pezpallet-beefy` configuration.
|
||||
type AuthorityId: BeefyAuthorityId<Self::CommitmentHasher> + Parameter;
|
||||
|
||||
/// A way to convert validator id to its raw representation in the BEEFY merkle tree.
|
||||
///
|
||||
/// Corresponds to the `BeefyAuthorityToMerkleLeaf` field of the `pezpallet-beefy-mmr`
|
||||
/// configuration.
|
||||
type AuthorityIdToMerkleLeaf: Convert<Self::AuthorityId, Vec<u8>>;
|
||||
}
|
||||
|
||||
/// BEEFY validator id used by given Bizinikiwi chain.
|
||||
pub type BeefyAuthorityIdOf<C> = <C as ChainWithBeefy>::AuthorityId;
|
||||
/// BEEFY validator set, containing both validator identifiers and the numeric set id.
|
||||
pub type BeefyAuthoritySetOf<C> = ValidatorSet<BeefyAuthorityIdOf<C>>;
|
||||
/// BEEFY authority set, containing both validator identifiers and the numeric set id.
|
||||
pub type BeefyAuthoritySetInfoOf<C> = pezsp_consensus_beefy::mmr::BeefyAuthoritySet<MmrHashOf<C>>;
|
||||
/// BEEFY validator signature used by given Bizinikiwi chain.
|
||||
pub type BeefyValidatorSignatureOf<C> =
|
||||
<<C as ChainWithBeefy>::AuthorityId as RuntimeAppPublic>::Signature;
|
||||
/// Signed BEEFY commitment used by given Bizinikiwi chain.
|
||||
pub type BeefySignedCommitmentOf<C> =
|
||||
SignedCommitment<BlockNumberOf<C>, BeefyValidatorSignatureOf<C>>;
|
||||
/// Hash algorithm, used to compute the digest of the BEEFY commitment before signing it.
|
||||
pub type BeefyCommitmentHasher<C> = <C as ChainWithBeefy>::CommitmentHasher;
|
||||
/// Hash algorithm used in Beefy MMR construction by given Bizinikiwi chain.
|
||||
pub type MmrHashingOf<C> = <C as ChainWithBeefy>::MmrHashing;
|
||||
/// Hash type, used in MMR construction by given Bizinikiwi chain.
|
||||
pub type MmrHashOf<C> = <C as ChainWithBeefy>::MmrHash;
|
||||
/// BEEFY MMR proof type used by the given Bizinikiwi chain.
|
||||
pub type MmrProofOf<C> = MmrProof<MmrHashOf<C>>;
|
||||
/// The type of the MMR leaf extra data used by the given Bizinikiwi chain.
|
||||
pub type BeefyMmrLeafExtraOf<C> = <C as ChainWithBeefy>::BeefyMmrLeafExtra;
|
||||
/// A way to convert a validator id to its raw representation in the BEEFY merkle tree, used by
|
||||
/// the given Bizinikiwi chain.
|
||||
pub type BeefyAuthorityIdToMerkleLeafOf<C> = <C as ChainWithBeefy>::AuthorityIdToMerkleLeaf;
|
||||
/// Actual type of leafs in the BEEFY MMR.
|
||||
pub type BeefyMmrLeafOf<C> = pezsp_consensus_beefy::mmr::MmrLeaf<
|
||||
BlockNumberOf<C>,
|
||||
HashOf<C>,
|
||||
MmrHashOf<C>,
|
||||
BeefyMmrLeafExtraOf<C>,
|
||||
>;
|
||||
|
||||
/// Data required for initializing the BEEFY pezpallet.
|
||||
///
|
||||
/// Provides the initial context that the bridge needs in order to know
|
||||
/// where to start the sync process from.
|
||||
#[derive(Encode, Decode, RuntimeDebug, PartialEq, Clone, TypeInfo, Serialize, Deserialize)]
|
||||
pub struct InitializationData<BlockNumber, Hash> {
|
||||
/// Pezpallet operating mode.
|
||||
pub operating_mode: BasicOperatingMode,
|
||||
/// Number of the best block, finalized by BEEFY.
|
||||
pub best_block_number: BlockNumber,
|
||||
/// BEEFY authority set that will be finalizing descendants of the `best_beefy_block_number`
|
||||
/// block.
|
||||
pub authority_set: BeefyAuthoritySet<Hash>,
|
||||
}
|
||||
|
||||
/// Basic data, stored by the pezpallet for every imported commitment.
|
||||
#[derive(Encode, Decode, RuntimeDebug, PartialEq, TypeInfo)]
|
||||
pub struct ImportedCommitment<BlockNumber, BlockHash, MmrHash> {
|
||||
/// Block number and hash of the finalized block parent.
|
||||
pub parent_number_and_hash: (BlockNumber, BlockHash),
|
||||
/// MMR root at the imported block.
|
||||
pub mmr_root: MmrHash,
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
[package]
|
||||
name = "bp-header-pez-chain"
|
||||
description = "A common interface for describing what a bridge pezpallet should be able to do."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-header-pez-chain"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
finality-grandpa = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezsp-consensus-grandpa = { features = ["serde"], workspace = true }
|
||||
pezsp-core = { features = ["serde"], workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-test-utils = { workspace = true, default-features = true }
|
||||
hex = { workspace = true, default-features = true }
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"finality-grandpa/std",
|
||||
"pezframe-support/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-consensus-grandpa/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezsp-consensus-grandpa/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines structures related to calls of the `pezpallet-bridge-grandpa` pezpallet.
|
||||
|
||||
use crate::{justification, InitializationData};
|
||||
|
||||
use pezbp_runtime::HeaderOf;
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{weights::Weight, RuntimeDebugNoBound};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_consensus_grandpa::SetId;
|
||||
use pezsp_runtime::traits::{Header as HeaderT, Zero};
|
||||
use pezsp_std::{boxed::Box, fmt::Debug};
|
||||
|
||||
/// A minimized version of `pezpallet-bridge-grandpa::Call` that can be used without a runtime.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeGrandpaCall<Header: HeaderT> {
|
||||
/// `pezpallet-bridge-grandpa::Call::submit_finality_proof`
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof {
|
||||
/// The header that we are going to finalize.
|
||||
finality_target: Box<Header>,
|
||||
/// Finality justification for the `finality_target`.
|
||||
justification: justification::GrandpaJustification<Header>,
|
||||
},
|
||||
/// `pezpallet-bridge-grandpa::Call::initialize`
|
||||
#[codec(index = 1)]
|
||||
initialize {
|
||||
/// All data, required to initialize the pezpallet.
|
||||
init_data: InitializationData<Header>,
|
||||
},
|
||||
/// `pezpallet-bridge-grandpa::Call::submit_finality_proof_ex`
|
||||
#[codec(index = 4)]
|
||||
submit_finality_proof_ex {
|
||||
/// The header that we are going to finalize.
|
||||
finality_target: Box<Header>,
|
||||
/// Finality justification for the `finality_target`.
|
||||
justification: justification::GrandpaJustification<Header>,
|
||||
/// An identifier of the validators set, that have signed the justification.
|
||||
current_set_id: SetId,
|
||||
},
|
||||
}
|
||||
|
||||
/// The `BridgeGrandpaCall` for a pezpallet that bridges with given `C`;
|
||||
pub type BridgeGrandpaCallOf<C> = BridgeGrandpaCall<HeaderOf<C>>;
|
||||
|
||||
/// A digest information on the `BridgeGrandpaCall::submit_finality_proof` call.
|
||||
#[derive(Copy, Clone, PartialEq, RuntimeDebugNoBound)]
|
||||
pub struct SubmitFinalityProofInfo<N: Debug> {
|
||||
/// Number of the finality target.
|
||||
pub block_number: N,
|
||||
/// An identifier of the validators set that has signed the submitted justification.
|
||||
/// It might be `None` if deprecated version of the `submit_finality_proof` is used.
|
||||
pub current_set_id: Option<SetId>,
|
||||
/// If `true`, then the call proves new **mandatory** header.
|
||||
pub is_mandatory: bool,
|
||||
/// If `true`, then the call must be free (assuming that everything else is valid) to
|
||||
/// be treated as valid.
|
||||
pub is_free_execution_expected: bool,
|
||||
/// Extra weight that we assume is included in the call.
|
||||
///
|
||||
/// We have some assumptions about headers and justifications of the bridged chain.
|
||||
/// We know that if our assumptions are correct, then the call must not have the
|
||||
/// weight above some limit. The fee paid for weight above that limit, is never refunded.
|
||||
pub extra_weight: Weight,
|
||||
/// Extra size (in bytes) that we assume are included in the call.
|
||||
///
|
||||
/// We have some assumptions about headers and justifications of the bridged chain.
|
||||
/// We know that if our assumptions are correct, then the call must not have the
|
||||
/// weight above some limit. The fee paid for bytes above that limit, is never refunded.
|
||||
pub extra_size: u32,
|
||||
}
|
||||
|
||||
impl<N: Debug> SubmitFinalityProofInfo<N> {
|
||||
/// Returns `true` if call size/weight is below our estimations for regular calls.
|
||||
pub fn fits_limits(&self) -> bool {
|
||||
self.extra_weight.is_zero() && self.extra_size.is_zero()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for checking GRANDPA Finality Proofs.
|
||||
//!
|
||||
//! Adapted copy of bizinikiwi/client/finality-grandpa/src/justification.rs. If origin
|
||||
//! will ever be moved to the pezsp_consensus_grandpa, we should reuse that implementation.
|
||||
|
||||
mod verification;
|
||||
|
||||
use crate::ChainWithGrandpa;
|
||||
pub use verification::{
|
||||
equivocation::{EquivocationsCollector, GrandpaEquivocationsFinder},
|
||||
optimizer::verify_and_optimize_justification,
|
||||
strict::verify_justification,
|
||||
AncestryChain, Error as JustificationVerificationError, JustificationVerificationContext,
|
||||
PrecommitError,
|
||||
};
|
||||
|
||||
use pezbp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthoritySignature};
|
||||
use pezsp_runtime::{traits::Header as HeaderT, RuntimeDebug, SaturatedConversion};
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// A GRANDPA Justification is a proof that a given header was finalized
|
||||
/// at a certain height and with a certain set of authorities.
|
||||
///
|
||||
/// This particular proof is used to prove that headers on a bridged chain
|
||||
/// (so not our chain) have been finalized correctly.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct GrandpaJustification<Header: HeaderT> {
|
||||
/// The round (voting period) this justification is valid for.
|
||||
pub round: u64,
|
||||
/// The set of votes for the chain which is to be finalized.
|
||||
pub commit:
|
||||
finality_grandpa::Commit<Header::Hash, Header::Number, AuthoritySignature, AuthorityId>,
|
||||
/// A proof that the chain of blocks in the commit are related to each other.
|
||||
pub votes_ancestries: Vec<Header>,
|
||||
}
|
||||
|
||||
// A proper Debug impl for no-std is not possible for the `GrandpaJustification` since the `Commit`
|
||||
// type only implements Debug that for `std` here:
|
||||
// https://github.com/paritytech/finality-grandpa/blob/8c45a664c05657f0c71057158d3ba555ba7d20de/src/lib.rs#L275
|
||||
// so we do a manual impl.
|
||||
#[cfg(not(feature = "std"))]
|
||||
impl<H: HeaderT> core::fmt::Debug for GrandpaJustification<H> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "GrandpaJustification {{ round: {:?}, commit: <wasm:stripped>, votes_ancestries: {:?} }}", self.round, self.votes_ancestries)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: HeaderT> GrandpaJustification<H> {
|
||||
/// Returns reasonable size of justification using constants from the provided chain.
|
||||
///
|
||||
/// An imprecise analogue of `MaxEncodedLen` implementation. We don't use it for
|
||||
/// any precise calculations - that's just an estimation.
|
||||
pub fn max_reasonable_size<C>(required_precommits: u32) -> u32
|
||||
where
|
||||
C: Chain + ChainWithGrandpa,
|
||||
{
|
||||
// we don't need precise results here - just estimations, so some details
|
||||
// are removed from computations (e.g. bytes required to encode vector length)
|
||||
|
||||
// structures in `finality_grandpa` crate are not implementing `MaxEncodedLength`, so
|
||||
// here's our estimation for the `finality_grandpa::Commit` struct size
|
||||
//
|
||||
// precommit is: hash + number
|
||||
// signed precommit is: precommit + signature (64b) + authority id
|
||||
// commit is: hash + number + vec of signed precommits
|
||||
let signed_precommit_size: u32 = BlockNumberOf::<C>::max_encoded_len()
|
||||
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into())
|
||||
.saturating_add(64)
|
||||
.saturating_add(AuthorityId::max_encoded_len().saturated_into())
|
||||
.saturated_into();
|
||||
let max_expected_signed_commit_size = signed_precommit_size
|
||||
.saturating_mul(required_precommits)
|
||||
.saturating_add(BlockNumberOf::<C>::max_encoded_len().saturated_into())
|
||||
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into());
|
||||
|
||||
let max_expected_votes_ancestries_size =
|
||||
C::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY.saturating_mul(C::AVERAGE_HEADER_SIZE);
|
||||
|
||||
// justification is round number (u64=8b), a signed GRANDPA commit and the
|
||||
// `votes_ancestries` vector
|
||||
8u32.saturating_add(max_expected_signed_commit_size)
|
||||
.saturating_add(max_expected_votes_ancestries_size)
|
||||
}
|
||||
|
||||
/// Return identifier of header that this justification claims to finalize.
|
||||
pub fn commit_target_id(&self) -> HeaderId<H::Hash, H::Number> {
|
||||
HeaderId(self.commit.target_number, self.commit.target_hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: HeaderT> crate::FinalityProof<H::Hash, H::Number> for GrandpaJustification<H> {
|
||||
fn target_header_hash(&self) -> H::Hash {
|
||||
self.commit.target_hash
|
||||
}
|
||||
|
||||
fn target_header_number(&self) -> H::Number {
|
||||
self.commit.target_number
|
||||
}
|
||||
}
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Failed to decode justification.
|
||||
JustificationDecode,
|
||||
}
|
||||
|
||||
/// Given GRANDPA authorities set size, return number of valid authorities votes that the
|
||||
/// justification must have to be valid.
|
||||
///
|
||||
/// This function assumes that all authorities have the same vote weight.
|
||||
pub fn required_justification_precommits(authorities_set_length: u32) -> u32 {
|
||||
authorities_set_length - authorities_set_length.saturating_sub(1) / 3
|
||||
}
|
||||
|
||||
/// Decode justification target.
|
||||
pub fn decode_justification_target<Header: HeaderT>(
|
||||
raw_justification: &[u8],
|
||||
) -> Result<(Header::Hash, Header::Number), Error> {
|
||||
GrandpaJustification::<Header>::decode(&mut &*raw_justification)
|
||||
.map(|justification| (justification.commit.target_hash, justification.commit.target_number))
|
||||
.map_err(|_| Error::JustificationDecode)
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for extracting equivocations from multiple GRANDPA Finality Proofs.
|
||||
|
||||
use crate::{
|
||||
justification::{
|
||||
verification::{
|
||||
Error as JustificationVerificationError, IterationFlow,
|
||||
JustificationVerificationContext, JustificationVerifier, PrecommitError,
|
||||
SignedPrecommit,
|
||||
},
|
||||
GrandpaJustification,
|
||||
},
|
||||
ChainWithGrandpa, FindEquivocations,
|
||||
};
|
||||
|
||||
use pezbp_runtime::{BlockNumberOf, HashOf, HeaderOf};
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit};
|
||||
use pezsp_runtime::traits::Header as HeaderT;
|
||||
use pezsp_std::{
|
||||
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
||||
prelude::*,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
enum AuthorityVotes<Header: HeaderT> {
|
||||
SingleVote(SignedPrecommit<Header>),
|
||||
Equivocation(
|
||||
finality_grandpa::Equivocation<AuthorityId, Precommit<Header>, AuthoritySignature>,
|
||||
),
|
||||
}
|
||||
|
||||
/// Structure that can extract equivocations from multiple GRANDPA justifications.
|
||||
pub struct EquivocationsCollector<'a, Header: HeaderT> {
|
||||
round: u64,
|
||||
context: &'a JustificationVerificationContext,
|
||||
|
||||
votes: BTreeMap<AuthorityId, AuthorityVotes<Header>>,
|
||||
}
|
||||
|
||||
impl<'a, Header: HeaderT> EquivocationsCollector<'a, Header> {
|
||||
/// Create a new instance of `EquivocationsCollector`.
|
||||
pub fn new(
|
||||
context: &'a JustificationVerificationContext,
|
||||
base_justification: &GrandpaJustification<Header>,
|
||||
) -> Result<Self, JustificationVerificationError> {
|
||||
let mut checker = Self { round: base_justification.round, context, votes: BTreeMap::new() };
|
||||
|
||||
checker.verify_justification(
|
||||
(base_justification.commit.target_hash, base_justification.commit.target_number),
|
||||
checker.context,
|
||||
base_justification,
|
||||
)?;
|
||||
|
||||
Ok(checker)
|
||||
}
|
||||
|
||||
/// Parse additional justifications for equivocations.
|
||||
pub fn parse_justifications(&mut self, justifications: &[GrandpaJustification<Header>]) {
|
||||
let round = self.round;
|
||||
for justification in
|
||||
justifications.iter().filter(|justification| round == justification.round)
|
||||
{
|
||||
// We ignore the Errors received here since we don't care if the proofs are valid.
|
||||
// We only care about collecting equivocations.
|
||||
let _ = self.verify_justification(
|
||||
(justification.commit.target_hash, justification.commit.target_number),
|
||||
self.context,
|
||||
justification,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the equivocation proofs that have been collected.
|
||||
pub fn into_equivocation_proofs(self) -> Vec<EquivocationProof<Header::Hash, Header::Number>> {
|
||||
let mut equivocations = vec![];
|
||||
for (_authority, vote) in self.votes {
|
||||
if let AuthorityVotes::Equivocation(equivocation) = vote {
|
||||
equivocations.push(EquivocationProof::new(
|
||||
self.context.authority_set_id,
|
||||
pezsp_consensus_grandpa::Equivocation::Precommit(equivocation),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
equivocations
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Header: HeaderT> JustificationVerifier<Header> for EquivocationsCollector<'a, Header> {
|
||||
fn process_duplicate_votes_ancestries(
|
||||
&mut self,
|
||||
_duplicate_votes_ancestries: Vec<usize>,
|
||||
) -> Result<(), JustificationVerificationError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_redundant_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_known_authority_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
_signed: &SignedPrecommit<Header>,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_unknown_authority_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_unrelated_ancestry_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_invalid_signature_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>) {
|
||||
match self.votes.get_mut(&signed.id) {
|
||||
Some(vote) => match vote {
|
||||
AuthorityVotes::SingleVote(first_vote) => {
|
||||
if first_vote.precommit != signed.precommit {
|
||||
*vote = AuthorityVotes::Equivocation(finality_grandpa::Equivocation {
|
||||
round_number: self.round,
|
||||
identity: signed.id.clone(),
|
||||
first: (first_vote.precommit.clone(), first_vote.signature.clone()),
|
||||
second: (signed.precommit.clone(), signed.signature.clone()),
|
||||
});
|
||||
}
|
||||
},
|
||||
AuthorityVotes::Equivocation(_) => {},
|
||||
},
|
||||
None => {
|
||||
self.votes.insert(signed.id.clone(), AuthorityVotes::SingleVote(signed.clone()));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn process_redundant_votes_ancestries(
|
||||
&mut self,
|
||||
_redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
) -> Result<(), JustificationVerificationError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for finding equivocations in GRANDPA proofs.
|
||||
pub struct GrandpaEquivocationsFinder<C>(pezsp_std::marker::PhantomData<C>);
|
||||
|
||||
impl<C: ChainWithGrandpa>
|
||||
FindEquivocations<
|
||||
GrandpaJustification<HeaderOf<C>>,
|
||||
JustificationVerificationContext,
|
||||
EquivocationProof<HashOf<C>, BlockNumberOf<C>>,
|
||||
> for GrandpaEquivocationsFinder<C>
|
||||
{
|
||||
type Error = JustificationVerificationError;
|
||||
|
||||
fn find_equivocations(
|
||||
verification_context: &JustificationVerificationContext,
|
||||
synced_proof: &GrandpaJustification<HeaderOf<C>>,
|
||||
source_proofs: &[GrandpaJustification<HeaderOf<C>>],
|
||||
) -> Result<Vec<EquivocationProof<HashOf<C>, BlockNumberOf<C>>>, Self::Error> {
|
||||
let mut equivocations_collector =
|
||||
EquivocationsCollector::new(verification_context, synced_proof)?;
|
||||
|
||||
equivocations_collector.parse_justifications(source_proofs);
|
||||
|
||||
Ok(equivocations_collector.into_equivocation_proofs())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for checking GRANDPA Finality Proofs.
|
||||
|
||||
pub mod equivocation;
|
||||
pub mod optimizer;
|
||||
pub mod strict;
|
||||
|
||||
use crate::{justification::GrandpaJustification, AuthoritySet};
|
||||
|
||||
use pezbp_runtime::HeaderId;
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthoritySignature, SetId};
|
||||
use pezsp_runtime::{traits::Header as HeaderT, RuntimeDebug};
|
||||
use pezsp_std::{
|
||||
collections::{
|
||||
btree_map::{
|
||||
BTreeMap,
|
||||
Entry::{Occupied, Vacant},
|
||||
},
|
||||
btree_set::BTreeSet,
|
||||
},
|
||||
prelude::*,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
type SignedPrecommit<Header> = finality_grandpa::SignedPrecommit<
|
||||
<Header as HeaderT>::Hash,
|
||||
<Header as HeaderT>::Number,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// Votes ancestries with useful methods.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct AncestryChain<Header: HeaderT> {
|
||||
/// We expect all forks in the ancestry chain to be descendants of base.
|
||||
base: HeaderId<Header::Hash, Header::Number>,
|
||||
/// Header hash => parent header hash mapping.
|
||||
parents: BTreeMap<Header::Hash, Header::Hash>,
|
||||
/// Hashes of headers that were not visited by `ancestry()`.
|
||||
unvisited: BTreeSet<Header::Hash>,
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> AncestryChain<Header> {
|
||||
/// Creates a new instance of `AncestryChain` starting from a `GrandpaJustification`.
|
||||
///
|
||||
/// Returns the `AncestryChain` and a `Vec` containing the `votes_ancestries` entries
|
||||
/// that were ignored when creating it, because they are duplicates.
|
||||
pub fn new(
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> (AncestryChain<Header>, Vec<usize>) {
|
||||
let mut parents = BTreeMap::new();
|
||||
let mut unvisited = BTreeSet::new();
|
||||
let mut ignored_idxs = Vec::new();
|
||||
for (idx, ancestor) in justification.votes_ancestries.iter().enumerate() {
|
||||
let hash = ancestor.hash();
|
||||
match parents.entry(hash) {
|
||||
Occupied(_) => {
|
||||
ignored_idxs.push(idx);
|
||||
},
|
||||
Vacant(entry) => {
|
||||
entry.insert(*ancestor.parent_hash());
|
||||
unvisited.insert(hash);
|
||||
},
|
||||
}
|
||||
}
|
||||
(AncestryChain { base: justification.commit_target_id(), parents, unvisited }, ignored_idxs)
|
||||
}
|
||||
|
||||
/// Returns the hash of a block's parent if the block is present in the ancestry.
|
||||
pub fn parent_hash_of(&self, hash: &Header::Hash) -> Option<&Header::Hash> {
|
||||
self.parents.get(hash)
|
||||
}
|
||||
|
||||
/// Returns a route if the precommit target block is a descendant of the `base` block.
|
||||
pub fn ancestry(
|
||||
&self,
|
||||
precommit_target_hash: &Header::Hash,
|
||||
precommit_target_number: &Header::Number,
|
||||
) -> Option<Vec<Header::Hash>> {
|
||||
if precommit_target_number < &self.base.number() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut route = vec![];
|
||||
let mut current_hash = *precommit_target_hash;
|
||||
loop {
|
||||
if current_hash == self.base.hash() {
|
||||
break;
|
||||
}
|
||||
|
||||
current_hash = match self.parent_hash_of(¤t_hash) {
|
||||
Some(parent_hash) => {
|
||||
let is_visited_before = self.unvisited.get(¤t_hash).is_none();
|
||||
if is_visited_before {
|
||||
// If the current header has been visited in a previous call, it is a
|
||||
// descendent of `base` (we assume that the previous call was successful).
|
||||
return Some(route);
|
||||
}
|
||||
route.push(current_hash);
|
||||
|
||||
*parent_hash
|
||||
},
|
||||
None => return None,
|
||||
};
|
||||
}
|
||||
|
||||
Some(route)
|
||||
}
|
||||
|
||||
fn mark_route_as_visited(&mut self, route: Vec<Header::Hash>) {
|
||||
for hash in route {
|
||||
self.unvisited.remove(&hash);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_fully_visited(&self) -> bool {
|
||||
self.unvisited.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Could not convert `AuthorityList` to `VoterSet`.
|
||||
InvalidAuthorityList,
|
||||
/// Justification is finalizing unexpected header.
|
||||
InvalidJustificationTarget,
|
||||
/// The justification contains duplicate headers in its `votes_ancestries` field.
|
||||
DuplicateVotesAncestries,
|
||||
/// Error validating a precommit
|
||||
Precommit(PrecommitError),
|
||||
/// The cumulative weight of all votes in the justification is not enough to justify commit
|
||||
/// header finalization.
|
||||
TooLowCumulativeWeight,
|
||||
/// The justification contains extra (unused) headers in its `votes_ancestries` field.
|
||||
RedundantVotesAncestries,
|
||||
}
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum PrecommitError {
|
||||
/// Justification contains redundant votes.
|
||||
RedundantAuthorityVote,
|
||||
/// Justification contains unknown authority precommit.
|
||||
UnknownAuthorityVote,
|
||||
/// Justification contains duplicate authority precommit.
|
||||
DuplicateAuthorityVote,
|
||||
/// The authority has provided an invalid signature.
|
||||
InvalidAuthoritySignature,
|
||||
/// The justification contains precommit for header that is not a descendant of the commit
|
||||
/// header.
|
||||
UnrelatedAncestryVote,
|
||||
}
|
||||
|
||||
/// The context needed for validating GRANDPA finality proofs.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct JustificationVerificationContext {
|
||||
/// The authority set used to verify the justification.
|
||||
pub voter_set: VoterSet<AuthorityId>,
|
||||
/// The ID of the authority set used to verify the justification.
|
||||
pub authority_set_id: SetId,
|
||||
}
|
||||
|
||||
impl TryFrom<AuthoritySet> for JustificationVerificationContext {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(authority_set: AuthoritySet) -> Result<Self, Self::Error> {
|
||||
let voter_set =
|
||||
VoterSet::new(authority_set.authorities).ok_or(Error::InvalidAuthorityList)?;
|
||||
Ok(JustificationVerificationContext { voter_set, authority_set_id: authority_set.set_id })
|
||||
}
|
||||
}
|
||||
|
||||
enum IterationFlow {
|
||||
Run,
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// Verification callbacks.
|
||||
trait JustificationVerifier<Header: HeaderT> {
|
||||
/// Called when there are duplicate headers in the votes ancestries.
|
||||
fn process_duplicate_votes_ancestries(
|
||||
&mut self,
|
||||
duplicate_votes_ancestries: Vec<usize>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn process_redundant_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_known_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
signed: &SignedPrecommit<Header>,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_unknown_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError>;
|
||||
|
||||
fn process_unrelated_ancestry_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_invalid_signature_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError>;
|
||||
|
||||
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>);
|
||||
|
||||
/// Called when there are redundant headers in the votes ancestries.
|
||||
fn process_redundant_votes_ancestries(
|
||||
&mut self,
|
||||
redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn verify_justification(
|
||||
&mut self,
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
// ensure that it is justification for the expected header
|
||||
if (justification.commit.target_hash, justification.commit.target_number) !=
|
||||
finalized_target
|
||||
{
|
||||
return Err(Error::InvalidJustificationTarget);
|
||||
}
|
||||
|
||||
let threshold = context.voter_set.threshold().get();
|
||||
let (mut chain, ignored_idxs) = AncestryChain::new(justification);
|
||||
let mut signature_buffer = Vec::new();
|
||||
let mut cumulative_weight = 0u64;
|
||||
|
||||
if !ignored_idxs.is_empty() {
|
||||
self.process_duplicate_votes_ancestries(ignored_idxs)?;
|
||||
}
|
||||
|
||||
for (precommit_idx, signed) in justification.commit.precommits.iter().enumerate() {
|
||||
if cumulative_weight >= threshold {
|
||||
let action =
|
||||
self.process_redundant_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// authority must be in the set
|
||||
let authority_info = match context.voter_set.get(&signed.id) {
|
||||
Some(authority_info) => {
|
||||
// The implementer may want to do extra checks here.
|
||||
// For example to see if the authority has already voted in the same round.
|
||||
let action = self
|
||||
.process_known_authority_vote(precommit_idx, signed)
|
||||
.map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
authority_info
|
||||
},
|
||||
None => {
|
||||
self.process_unknown_authority_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
// all precommits must be descendants of the target block
|
||||
let maybe_route =
|
||||
chain.ancestry(&signed.precommit.target_hash, &signed.precommit.target_number);
|
||||
if maybe_route.is_none() {
|
||||
let action = self
|
||||
.process_unrelated_ancestry_vote(precommit_idx)
|
||||
.map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// verify authority signature
|
||||
if !pezsp_consensus_grandpa::check_message_signature_with_buffer(
|
||||
&finality_grandpa::Message::Precommit(signed.precommit.clone()),
|
||||
&signed.id,
|
||||
&signed.signature,
|
||||
justification.round,
|
||||
context.authority_set_id,
|
||||
&mut signature_buffer,
|
||||
)
|
||||
.is_valid()
|
||||
{
|
||||
self.process_invalid_signature_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
// now we can count the vote since we know that it is valid
|
||||
self.process_valid_vote(signed);
|
||||
if let Some(route) = maybe_route {
|
||||
chain.mark_route_as_visited(route);
|
||||
cumulative_weight = cumulative_weight.saturating_add(authority_info.weight().get());
|
||||
}
|
||||
}
|
||||
|
||||
// check that the cumulative weight of validators that voted for the justification target
|
||||
// (or one of its descendants) is larger than the required threshold.
|
||||
if cumulative_weight < threshold {
|
||||
return Err(Error::TooLowCumulativeWeight);
|
||||
}
|
||||
|
||||
// check that there are no extra headers in the justification
|
||||
if !chain.is_fully_visited() {
|
||||
self.process_redundant_votes_ancestries(chain.unvisited)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for optimizing GRANDPA Finality Proofs.
|
||||
|
||||
use crate::justification::{
|
||||
verification::{Error, JustificationVerifier, PrecommitError},
|
||||
GrandpaJustification,
|
||||
};
|
||||
|
||||
use crate::justification::verification::{
|
||||
IterationFlow, JustificationVerificationContext, SignedPrecommit,
|
||||
};
|
||||
use pezsp_consensus_grandpa::AuthorityId;
|
||||
use pezsp_runtime::traits::Header as HeaderT;
|
||||
use pezsp_std::{collections::btree_set::BTreeSet, prelude::*, vec, vec::Vec};
|
||||
|
||||
// Verification callbacks for justification optimization.
|
||||
struct JustificationOptimizer<Header: HeaderT> {
|
||||
votes: BTreeSet<AuthorityId>,
|
||||
|
||||
extra_precommits: Vec<usize>,
|
||||
duplicate_votes_ancestries_idxs: Vec<usize>,
|
||||
redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> JustificationOptimizer<Header> {
|
||||
fn optimize(self, justification: &mut GrandpaJustification<Header>) {
|
||||
for invalid_precommit_idx in self.extra_precommits.into_iter().rev() {
|
||||
justification.commit.precommits.remove(invalid_precommit_idx);
|
||||
}
|
||||
if !self.duplicate_votes_ancestries_idxs.is_empty() {
|
||||
for idx in self.duplicate_votes_ancestries_idxs.iter().rev() {
|
||||
justification.votes_ancestries.swap_remove(*idx);
|
||||
}
|
||||
}
|
||||
if !self.redundant_votes_ancestries.is_empty() {
|
||||
justification
|
||||
.votes_ancestries
|
||||
.retain(|header| !self.redundant_votes_ancestries.contains(&header.hash()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> JustificationVerifier<Header> for JustificationOptimizer<Header> {
|
||||
fn process_duplicate_votes_ancestries(
|
||||
&mut self,
|
||||
duplicate_votes_ancestries: Vec<usize>,
|
||||
) -> Result<(), Error> {
|
||||
self.duplicate_votes_ancestries_idxs = duplicate_votes_ancestries.to_vec();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_redundant_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
Ok(IterationFlow::Skip)
|
||||
}
|
||||
|
||||
fn process_known_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
signed: &SignedPrecommit<Header>,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
// Skip duplicate votes
|
||||
if self.votes.contains(&signed.id) {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
return Ok(IterationFlow::Skip);
|
||||
}
|
||||
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_unknown_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_unrelated_ancestry_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
Ok(IterationFlow::Skip)
|
||||
}
|
||||
|
||||
fn process_invalid_signature_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>) {
|
||||
self.votes.insert(signed.id.clone());
|
||||
}
|
||||
|
||||
fn process_redundant_votes_ancestries(
|
||||
&mut self,
|
||||
redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
) -> Result<(), Error> {
|
||||
self.redundant_votes_ancestries = redundant_votes_ancestries;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify and optimize given justification by removing unknown and duplicate votes.
|
||||
pub fn verify_and_optimize_justification<Header: HeaderT>(
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &mut GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
let mut optimizer = JustificationOptimizer {
|
||||
votes: BTreeSet::new(),
|
||||
extra_precommits: vec![],
|
||||
duplicate_votes_ancestries_idxs: vec![],
|
||||
redundant_votes_ancestries: Default::default(),
|
||||
};
|
||||
optimizer.verify_justification(finalized_target, context, justification)?;
|
||||
optimizer.optimize(justification);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user