Remove bridges subtree

This commit is contained in:
Serban Iorga
2024-04-09 16:02:09 +03:00
committed by Bastian Köcher
parent d38f6e6728
commit 9a3e2c8c5a
179 changed files with 0 additions and 34372 deletions
-26
View File
@@ -1,26 +0,0 @@
**/target/
**/.env
**/.env2
**/rust-toolchain
hfuzz_target
hfuzz_workspace
**/Cargo.lock
**/*.rs.bk
*.o
*.so
*.rlib
*.dll
.gdb_history
*.exe
.DS_Store
.cargo
.idea
.vscode
*.iml
*.swp
*.swo
-80
View File
@@ -1,80 +0,0 @@
# 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
-675
View File
@@ -1,675 +0,0 @@
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>.
-116
View File
@@ -1,116 +0,0 @@
# Parity Bridges Common
This is a collection of components for building bridges.
These components include Substrate 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 Substrate 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/paritytech/parity-bridges-common.git
cd parity-bridges-common
cargo build --all
cargo test --all
```
Also you can build the repo with [Parity CI Docker
image](https://github.com/paritytech/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/paritytech/scripts#reproduce-ci-locally).
If you need more information about setting up your development environment [Substrate's Installation
page](https://docs.substrate.io/main-docs/install/) is a good resource.
## High-Level Architecture
This repo has support for bridging foreign chains together using a combination of Substrate 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 Substrate based chains.
```
+---------------+ +---------------+
| | | |
| Rococo | | Westend |
| | | |
+-------+-------+ +-------+-------+
^ ^
| +---------------+ |
| | | |
+-----> | Bridge Relay | <-------+
| |
+---------------+
```
The Rococo chain must be able to accept Westend 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 Rococo headers via RPC and submit them to the Westend
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 // Substrate 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
│ ├── parachains // On-Chain Parachains 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 Rococo <> Westend 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/paritytech/polkadot-sdk/tree/master//cumulus/parachains/runtimes/bridge-hubs/README.md).
-18
View File
@@ -1,18 +0,0 @@
# 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 an 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.
-100
View File
@@ -1,100 +0,0 @@
[package]
name = "bridge-runtime-common"
version = "0.7.0"
description = "Common types and functions that may be used by substrate-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"
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] }
hash-db = { version = "0.16.0", default-features = false }
log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
static_assertions = { version = "1.1", optional = true }
# Bridge dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-parachains = { path = "../../primitives/parachains", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-relayers = { path = "../../primitives/relayers", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-xcm-bridge-hub = { path = "../../primitives/xcm-bridge-hub", default-features = false }
bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false }
pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false }
pallet-bridge-messages = { path = "../../modules/messages", default-features = false }
pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false }
pallet-bridge-relayers = { path = "../../modules/relayers", default-features = false }
# Substrate dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../substrate/frame/system", default-features = false }
pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false }
pallet-utility = { path = "../../../substrate/frame/utility", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-core = { path = "../../../substrate/primitives/core", default-features = false }
sp-io = { path = "../../../substrate/primitives/io", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
sp-trie = { path = "../../../substrate/primitives/trie", default-features = false }
# Polkadot dependencies
xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false }
xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false }
[dev-dependencies]
bp-test-utils = { path = "../../primitives/test-utils" }
pallet-balances = { path = "../../../substrate/frame/balances" }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-messages/std",
"bp-parachains/std",
"bp-polkadot-core/std",
"bp-relayers/std",
"bp-runtime/std",
"bp-xcm-bridge-hub-router/std",
"bp-xcm-bridge-hub/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"hash-db/std",
"log/std",
"pallet-bridge-grandpa/std",
"pallet-bridge-messages/std",
"pallet-bridge-parachains/std",
"pallet-bridge-relayers/std",
"pallet-transaction-payment/std",
"pallet-utility/std",
"scale-info/std",
"sp-api/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
"xcm-builder/std",
"xcm/std",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-bridge-grandpa/runtime-benchmarks",
"pallet-bridge-messages/runtime-benchmarks",
"pallet-bridge-parachains/runtime-benchmarks",
"pallet-bridge-relayers/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
]
integrity-test = ["static_assertions"]
-348
View File
@@ -1,348 +0,0 @@
// 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 crate::{messages, messages::MessageBridge};
use bp_messages::{InboundLaneData, MessageNonce};
use bp_runtime::{Chain, ChainId};
use codec::Encode;
use frame_support::{storage::generator::StorageValue, traits::Get, weights::Weight};
use frame_system::limits;
use pallet_bridge_messages::WeightInfoExt as _;
/// 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 ) => {
{
// 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)
use frame_system::{Config as SystemConfig, pallet_prelude::{BlockNumberFor, HeaderFor}};
use static_assertions::assert_type_eq_all;
assert_type_eq_all!(<$r as SystemConfig>::Nonce, bp_runtime::NonceOf<$this>);
assert_type_eq_all!(BlockNumberFor<$r>, bp_runtime::BlockNumberOf<$this>);
assert_type_eq_all!(<$r as SystemConfig>::Hash, bp_runtime::HashOf<$this>);
assert_type_eq_all!(<$r as SystemConfig>::Hashing, bp_runtime::HasherOf<$this>);
assert_type_eq_all!(<$r as SystemConfig>::AccountId, bp_runtime::AccountIdOf<$this>);
assert_type_eq_all!(HeaderFor<$r>, bp_runtime::HeaderOf<$this>);
}
}
);
/// Macro that ensures that the bridge GRANDPA pallet is configured properly to bridge with given
/// chain.
#[macro_export]
macro_rules! assert_bridge_grandpa_pallet_types(
( runtime: $r:path, with_bridged_chain_grandpa_instance: $i:path, bridged_chain: $bridged:path ) => {
{
// 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)
use pallet_bridge_grandpa::Config as GrandpaConfig;
use static_assertions::assert_type_eq_all;
assert_type_eq_all!(<$r as GrandpaConfig<$i>>::BridgedChain, $bridged);
}
}
);
/// Macro that ensures that the bridge messages pallet 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,
bridge: $bridge:path
) => {
{
// 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)
use $crate::messages::{
source::{FromThisChainMessagePayload, TargetHeaderChainAdapter},
target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter},
AccountIdOf, BalanceOf, BridgedChain, ThisChain,
};
use pallet_bridge_messages::Config as MessagesConfig;
use static_assertions::assert_type_eq_all;
assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, FromThisChainMessagePayload);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundRelayer, AccountIdOf<BridgedChain<$bridge>>);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::TargetHeaderChain, TargetHeaderChainAdapter<$bridge>);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::SourceHeaderChain, SourceHeaderChainAdapter<$bridge>);
}
}
);
/// Macro that combines four other macro calls - `assert_chain_types`, `assert_bridge_types`,
/// `assert_bridge_grandpa_pallet_types` and `assert_bridge_messages_pallet_types`. It may be used
/// at the chain that is implementing complete standard messages bridge (i.e. with bridge GRANDPA
/// and messages pallets deployed).
#[macro_export]
macro_rules! assert_complete_bridge_types(
(
runtime: $r:path,
with_bridged_chain_grandpa_instance: $gi:path,
with_bridged_chain_messages_instance: $mi:path,
bridge: $bridge:path,
this_chain: $this:path,
bridged_chain: $bridged:path,
) => {
$crate::assert_chain_types!(runtime: $r, this_chain: $this);
$crate::assert_bridge_grandpa_pallet_types!(
runtime: $r,
with_bridged_chain_grandpa_instance: $gi,
bridged_chain: $bridged
);
$crate::assert_bridge_messages_pallet_types!(
runtime: $r,
with_bridged_chain_messages_instance: $mi,
bridge: $bridge
);
}
);
/// 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: frame_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 pallet configuration are valid.
pub fn assert_bridge_grandpa_pallet_constants<R, GI>()
where
R: pallet_bridge_grandpa::Config<GI>,
GI: 'static,
{
assert!(
R::HeadersToKeep::get() > 0,
"HeadersToKeep ({}) must be larger than zero",
R::HeadersToKeep::get(),
);
}
/// Parameters for asserting messages pallet constants.
#[derive(Debug)]
pub struct AssertBridgeMessagesPalletConstants {
/// Maximal number of unrewarded relayer entries in a confirmation transaction at the bridged
/// chain.
pub max_unrewarded_relayers_in_bridged_confirmation_tx: MessageNonce,
/// Maximal number of unconfirmed messages in a confirmation transaction at the bridged chain.
pub max_unconfirmed_messages_in_bridged_confirmation_tx: MessageNonce,
/// Identifier of the bridged chain.
pub bridged_chain_id: ChainId,
}
/// Test that the constants, used in messages pallet configuration are valid.
pub fn assert_bridge_messages_pallet_constants<R, MI>(params: AssertBridgeMessagesPalletConstants)
where
R: pallet_bridge_messages::Config<MI>,
MI: 'static,
{
assert!(
!R::ActiveOutboundLanes::get().is_empty(),
"ActiveOutboundLanes ({:?}) must not be empty",
R::ActiveOutboundLanes::get(),
);
assert!(
R::MaxUnrewardedRelayerEntriesAtInboundLane::get() <= params.max_unrewarded_relayers_in_bridged_confirmation_tx,
"MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}",
R::MaxUnrewardedRelayerEntriesAtInboundLane::get(),
params.max_unrewarded_relayers_in_bridged_confirmation_tx,
);
assert!(
R::MaxUnconfirmedMessagesAtInboundLane::get() <= params.max_unconfirmed_messages_in_bridged_confirmation_tx,
"MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}",
R::MaxUnconfirmedMessagesAtInboundLane::get(),
params.max_unconfirmed_messages_in_bridged_confirmation_tx,
);
assert_eq!(R::BridgedChainId::get(), params.bridged_chain_id);
}
/// Parameters for asserting bridge pallet names.
#[derive(Debug)]
pub struct AssertBridgePalletNames<'a> {
/// Name of the messages pallet, deployed at the bridged chain and used to bridge with this
/// chain.
pub with_this_chain_messages_pallet_name: &'a str,
/// Name of the GRANDPA pallet, deployed at this chain and used to bridge with the bridged
/// chain.
pub with_bridged_chain_grandpa_pallet_name: &'a str,
/// Name of the messages pallet, deployed at this chain and used to bridge with the bridged
/// chain.
pub with_bridged_chain_messages_pallet_name: &'a str,
}
/// Tests that bridge pallet names used in `construct_runtime!()` macro call are matching constants
/// from chain primitives crates.
pub fn assert_bridge_pallet_names<B, R, GI, MI>(params: AssertBridgePalletNames)
where
B: MessageBridge,
R: pallet_bridge_grandpa::Config<GI> + pallet_bridge_messages::Config<MI>,
GI: 'static,
MI: 'static,
{
assert_eq!(B::BRIDGED_MESSAGES_PALLET_NAME, params.with_this_chain_messages_pallet_name);
assert_eq!(
pallet_bridge_grandpa::PalletOwner::<R, GI>::storage_value_final_key().to_vec(),
bp_runtime::storage_value_key(params.with_bridged_chain_grandpa_pallet_name, "PalletOwner",).0,
);
assert_eq!(
pallet_bridge_messages::PalletOwner::<R, MI>::storage_value_final_key().to_vec(),
bp_runtime::storage_value_key(
params.with_bridged_chain_messages_pallet_name,
"PalletOwner",
)
.0,
);
}
/// Parameters for asserting complete standard messages bridge.
#[derive(Debug)]
pub struct AssertCompleteBridgeConstants<'a> {
/// Parameters to assert this chain constants.
pub this_chain_constants: AssertChainConstants,
/// Parameters to assert messages pallet constants.
pub messages_pallet_constants: AssertBridgeMessagesPalletConstants,
/// Parameters to assert pallet names constants.
pub pallet_names: AssertBridgePalletNames<'a>,
}
/// All bridge-related constants tests for the complete standard messages bridge (i.e. with bridge
/// GRANDPA and messages pallets deployed).
pub fn assert_complete_bridge_constants<R, GI, MI, B>(params: AssertCompleteBridgeConstants)
where
R: frame_system::Config
+ pallet_bridge_grandpa::Config<GI>
+ pallet_bridge_messages::Config<MI>,
GI: 'static,
MI: 'static,
B: MessageBridge,
{
assert_chain_constants::<R>(params.this_chain_constants);
assert_bridge_grandpa_pallet_constants::<R, GI>();
assert_bridge_messages_pallet_constants::<R, MI>(params.messages_pallet_constants);
assert_bridge_pallet_names::<B, R, GI, MI>(params.pallet_names);
}
/// Check that the message lane weights are correct.
pub fn check_message_lane_weights<
C: Chain,
T: frame_system::Config + pallet_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 `RefundBridgedParachainMessages` 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 pallet_bridge_messages::Config<MI>>::WeightInfo;
// check basic weight assumptions
pallet_bridge_messages::ensure_weights_are_correct::<Weights<T, MessagesPalletInstance>>();
// check that weights allow us to receive messages
let max_incoming_message_proof_size = bridged_chain_extra_storage_proof_size
.saturating_add(messages::target::maximal_incoming_message_size(C::max_extrinsic_size()));
pallet_bridge_messages::ensure_able_to_receive_message::<Weights<T, MessagesPalletInstance>>(
C::max_extrinsic_size(),
C::max_extrinsic_weight(),
max_incoming_message_proof_size,
messages::target::maximal_incoming_message_dispatch_weight(C::max_extrinsic_weight()),
);
// check that weights allow us to receive delivery confirmations
let max_incoming_inbound_lane_data_proof_size =
InboundLaneData::<()>::encoded_size_hint_u32(this_chain_max_unrewarded_relayers as _);
pallet_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 `RefundBridgedParachainMessages` 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 pallet
// 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()
);
}
}
-223
View File
@@ -1,223 +0,0 @@
// 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)]
use crate::messages_call_ext::MessagesCallSubType;
use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType;
use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype;
use sp_runtime::transaction_validity::TransactionValidity;
pub mod messages;
pub mod messages_api;
pub mod messages_benchmarking;
pub mod messages_call_ext;
pub mod messages_generation;
pub mod messages_xcm_extension;
pub mod parachains_benchmarking;
pub mod priority_calculator;
pub mod refund_relayer_extension;
mod mock;
#[cfg(feature = "integrity-test")]
pub mod integrity;
const LOG_TARGET_BRIDGE_DISPATCH: &str = "runtime::bridge-dispatch";
/// A duplication of the `FilterCall` trait.
///
/// We need this trait in order to be able to implement it for the messages pallet,
/// since the implementation is done outside of the pallet crate.
pub trait BridgeRuntimeFilterCall<Call> {
/// Checks if a runtime call is valid.
fn validate(call: &Call) -> TransactionValidity;
}
impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall> for pallet_bridge_grandpa::Pallet<T, I>
where
T: pallet_bridge_grandpa::Config<I>,
T::RuntimeCall: GrandpaCallSubType<T, I>,
{
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
}
}
impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
for pallet_bridge_parachains::Pallet<T, I>
where
T: pallet_bridge_parachains::Config<I>,
T::RuntimeCall: ParachainsCallSubtype<T, I>,
{
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
}
}
impl<T: pallet_bridge_messages::Config<I>, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
for pallet_bridge_messages::Pallet<T, I>
where
T::RuntimeCall: MessagesCallSubType<T, I>,
{
/// 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(call: &T::RuntimeCall) -> TransactionValidity {
call.check_obsolete_call()
}
}
/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
///
/// ## Example
///
/// ```nocompile
/// generate_bridge_reject_obsolete_headers_and_messages!{
/// Call, AccountId
/// BridgeRococoGrandpa, BridgeRococoMessages,
/// BridgeRococoParachains
/// }
/// ```
///
/// 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, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)]
pub struct BridgeRejectObsoleteHeadersAndMessages;
impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages {
const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
type AccountId = $account_id;
type Call = $call;
type AdditionalSigned = ();
type Pre = ();
fn additional_signed(&self) -> sp_std::result::Result<
(),
sp_runtime::transaction_validity::TransactionValidityError,
> {
Ok(())
}
fn validate(
&self,
_who: &Self::AccountId,
call: &Self::Call,
_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
_len: usize,
) -> sp_runtime::transaction_validity::TransactionValidity {
let valid = sp_runtime::transaction_validity::ValidTransaction::default();
$(
let valid = valid
.combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?);
)*
Ok(valid)
}
fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
self.validate(who, call, info, len).map(drop)
}
}
};
}
#[cfg(test)]
mod tests {
use crate::BridgeRuntimeFilterCall;
use frame_support::{assert_err, assert_ok};
use sp_runtime::{
traits::SignedExtension,
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
};
pub struct MockCall {
data: u32,
}
impl sp_runtime::traits::Dispatchable for MockCall {
type RuntimeOrigin = ();
type Config = ();
type Info = ();
type PostInfo = ();
fn dispatch(
self,
_origin: Self::RuntimeOrigin,
) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
unimplemented!()
}
}
struct FirstFilterCall;
impl BridgeRuntimeFilterCall<MockCall> for FirstFilterCall {
fn validate(call: &MockCall) -> TransactionValidity {
if call.data <= 1 {
return InvalidTransaction::Custom(1).into()
}
Ok(ValidTransaction { priority: 1, ..Default::default() })
}
}
struct SecondFilterCall;
impl BridgeRuntimeFilterCall<MockCall> for SecondFilterCall {
fn validate(call: &MockCall) -> TransactionValidity {
if call.data <= 2 {
return InvalidTransaction::Custom(2).into()
}
Ok(ValidTransaction { priority: 2, ..Default::default() })
}
}
#[test]
fn test() {
generate_bridge_reject_obsolete_headers_and_messages!(
MockCall,
(),
FirstFilterCall,
SecondFilterCall
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0),
InvalidTransaction::Custom(1)
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0),
InvalidTransaction::Custom(2)
);
assert_ok!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0),
ValidTransaction { priority: 3, ..Default::default() }
)
}
}
-701
View File
@@ -1,701 +0,0 @@
// 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/>.
//! Types that allow runtime to act as a source/target endpoint of message lanes.
//!
//! Messages are assumed to be encoded `Call`s of the target chain. Call-dispatch
//! pallet is used to dispatch incoming messages. Message identified by a tuple
//! of to elements - message lane id and message nonce.
pub use bp_runtime::{RangeInclusiveExt, UnderlyingChainOf, UnderlyingChainProvider};
use bp_header_chain::HeaderChain;
use bp_messages::{
source_chain::TargetHeaderChain,
target_chain::{ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData,
VerificationError,
};
use bp_runtime::{Chain, RawStorageProof, Size, StorageProofChecker};
use codec::{Decode, Encode};
use frame_support::{traits::Get, weights::Weight};
use hash_db::Hasher;
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use sp_std::{convert::TryFrom, marker::PhantomData, vec::Vec};
/// Bidirectional message bridge.
pub trait MessageBridge {
/// Name of the paired messages pallet instance at the Bridged chain.
///
/// Should be the name that is used in the `construct_runtime!()` macro.
const BRIDGED_MESSAGES_PALLET_NAME: &'static str;
/// This chain in context of message bridge.
type ThisChain: ThisChainWithMessages;
/// Bridged chain in context of message bridge.
type BridgedChain: BridgedChainWithMessages;
/// Bridged header chain.
type BridgedHeaderChain: HeaderChain<UnderlyingChainOf<Self::BridgedChain>>;
}
/// This chain that has `pallet-bridge-messages` module.
pub trait ThisChainWithMessages: UnderlyingChainProvider {
/// Call origin on the chain.
type RuntimeOrigin;
}
/// Bridged chain that has `pallet-bridge-messages` module.
pub trait BridgedChainWithMessages: UnderlyingChainProvider {}
/// This chain in context of message bridge.
pub type ThisChain<B> = <B as MessageBridge>::ThisChain;
/// Bridged chain in context of message bridge.
pub type BridgedChain<B> = <B as MessageBridge>::BridgedChain;
/// Hash used on the chain.
pub type HashOf<C> = bp_runtime::HashOf<<C as UnderlyingChainProvider>::Chain>;
/// Hasher used on the chain.
pub type HasherOf<C> = bp_runtime::HasherOf<UnderlyingChainOf<C>>;
/// Account id used on the chain.
pub type AccountIdOf<C> = bp_runtime::AccountIdOf<UnderlyingChainOf<C>>;
/// Type of balances that is used on the chain.
pub type BalanceOf<C> = bp_runtime::BalanceOf<UnderlyingChainOf<C>>;
/// Sub-module that is declaring types required for processing This -> Bridged chain messages.
pub mod source {
use super::*;
/// Message payload for This -> Bridged chain messages.
pub type FromThisChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
/// Maximal size of outbound message payload.
pub struct FromThisChainMaximalOutboundPayloadSize<B>(PhantomData<B>);
impl<B: MessageBridge> Get<u32> for FromThisChainMaximalOutboundPayloadSize<B> {
fn get() -> u32 {
maximal_message_size::<B>()
}
}
/// Messages delivery proof from bridged chain:
///
/// - hash of finalized header;
/// - storage proof of inbound lane state;
/// - lane id.
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
/// Hash of the bridge header the proof is for.
pub bridged_header_hash: BridgedHeaderHash,
/// Storage trie proof generated for [`Self::bridged_header_hash`].
pub storage_proof: RawStorageProof,
/// Lane id of which messages were delivered and the proof is for.
pub lane: LaneId,
}
impl<BridgedHeaderHash> Size for FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
fn size(&self) -> u32 {
u32::try_from(
self.storage_proof
.iter()
.fold(0usize, |sum, node| sum.saturating_add(node.len())),
)
.unwrap_or(u32::MAX)
}
}
/// 'Parsed' message delivery proof - inbound lane id and its state.
pub type ParsedMessagesDeliveryProofFromBridgedChain<B> =
(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>);
/// Return maximal message size of This -> Bridged chain message.
pub fn maximal_message_size<B: MessageBridge>() -> u32 {
super::target::maximal_incoming_message_size(
UnderlyingChainOf::<BridgedChain<B>>::max_extrinsic_size(),
)
}
/// `TargetHeaderChain` implementation that is using default types and perform default checks.
pub struct TargetHeaderChainAdapter<B>(PhantomData<B>);
impl<B: MessageBridge> TargetHeaderChain<FromThisChainMessagePayload, AccountIdOf<ThisChain<B>>>
for TargetHeaderChainAdapter<B>
{
type MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>;
fn verify_message(payload: &FromThisChainMessagePayload) -> Result<(), VerificationError> {
verify_chain_message::<B>(payload)
}
fn verify_messages_delivery_proof(
proof: Self::MessagesDeliveryProof,
) -> Result<(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>), VerificationError> {
verify_messages_delivery_proof::<B>(proof)
}
}
/// Do basic Bridged-chain specific verification of This -> Bridged chain message.
///
/// Ok result from this function means that the delivery transaction with this message
/// may be 'mined' by the target chain.
pub fn verify_chain_message<B: MessageBridge>(
payload: &FromThisChainMessagePayload,
) -> Result<(), VerificationError> {
// IMPORTANT: any error that is returned here is fatal for the bridge, because
// this code is executed at the bridge hub and message sender actually lives
// at some sibling parachain. So we are failing **after** the message has been
// sent and we can't report it back to sender (unless error report mechanism is
// embedded into message and its dispatcher).
// apart from maximal message size check (see below), we should also check the message
// dispatch weight here. But we assume that the bridged chain will just push the message
// to some queue (XCMP, UMP, DMP), so the weight is constant and fits the block.
// The maximal size of extrinsic at Substrate-based chain depends on the
// `frame_system::Config::MaximumBlockLength` and
// `frame_system::Config::AvailableBlockRatio` constants. This check is here to be sure that
// the lane won't stuck because message is too large to fit into delivery transaction.
//
// **IMPORTANT NOTE**: the delivery transaction contains storage proof of the message, not
// the message itself. The proof is always larger than the message. But unless chain state
// is enormously large, it should be several dozens/hundreds of bytes. The delivery
// transaction also contains signatures and signed extensions. Because of this, we reserve
// 1/3 of the the maximal extrinsic size for this data.
if payload.len() > maximal_message_size::<B>() as usize {
return Err(VerificationError::MessageTooLarge)
}
Ok(())
}
/// Verify proof of This -> Bridged chain messages delivery.
///
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
/// parachains, please use the `verify_messages_delivery_proof_from_parachain`.
pub fn verify_messages_delivery_proof<B: MessageBridge>(
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>,
) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<B>, VerificationError> {
let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } =
proof;
let mut storage =
B::BridgedHeaderChain::storage_proof_checker(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(
B::BRIDGED_MESSAGES_PALLET_NAME,
&lane,
);
let inbound_lane_data = storage
.read_and_decode_mandatory_value(storage_inbound_lane_data_key.0.as_ref())
.map_err(VerificationError::InboundLaneStorage)?;
// check that the storage proof doesn't have any untouched trie nodes
storage.ensure_no_unused_nodes().map_err(VerificationError::StorageProof)?;
Ok((lane, inbound_lane_data))
}
}
/// Sub-module that is declaring types required for processing Bridged -> This chain messages.
pub mod target {
use super::*;
/// Decoded Bridged -> This message payload.
pub type FromBridgedChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
/// Messages proof from bridged chain:
///
/// - hash of finalized header;
/// - storage proof of messages and (optionally) outbound lane state;
/// - lane id;
/// - nonces (inclusive range) of messages which are included in this proof.
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct FromBridgedChainMessagesProof<BridgedHeaderHash> {
/// Hash of the finalized bridged header the proof is for.
pub bridged_header_hash: BridgedHeaderHash,
/// A storage trie proof of messages being delivered.
pub storage_proof: RawStorageProof,
/// Messages in this proof are sent over this lane.
pub lane: LaneId,
/// Nonce of the first message being delivered.
pub nonces_start: MessageNonce,
/// Nonce of the last message being delivered.
pub nonces_end: MessageNonce,
}
impl<BridgedHeaderHash> Size for FromBridgedChainMessagesProof<BridgedHeaderHash> {
fn size(&self) -> u32 {
u32::try_from(
self.storage_proof
.iter()
.fold(0usize, |sum, node| sum.saturating_add(node.len())),
)
.unwrap_or(u32::MAX)
}
}
/// Return maximal dispatch weight of the message we're able to receive.
pub fn maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
maximal_extrinsic_weight / 2
}
/// Return maximal message size given maximal extrinsic size.
pub fn maximal_incoming_message_size(maximal_extrinsic_size: u32) -> u32 {
maximal_extrinsic_size / 3 * 2
}
/// `SourceHeaderChain` implementation that is using default types and perform default checks.
pub struct SourceHeaderChainAdapter<B>(PhantomData<B>);
impl<B: MessageBridge> SourceHeaderChain for SourceHeaderChainAdapter<B> {
type MessagesProof = FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>;
fn verify_messages_proof(
proof: Self::MessagesProof,
messages_count: u32,
) -> Result<ProvedMessages<Message>, VerificationError> {
verify_messages_proof::<B>(proof, messages_count)
}
}
/// Verify proof of Bridged -> This chain messages.
///
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
/// parachains, please use the `verify_messages_proof_from_parachain`.
///
/// 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<B: MessageBridge>(
proof: FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>,
messages_count: u32,
) -> Result<ProvedMessages<Message>, VerificationError> {
let FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane,
nonces_start,
nonces_end,
} = proof;
let storage =
B::BridgedHeaderChain::storage_proof_checker(bridged_header_hash, storage_proof)
.map_err(VerificationError::HeaderChain)?;
let mut parser = StorageProofCheckerAdapter::<_, B> { storage, _dummy: Default::default() };
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.checked_len().unwrap_or(0);
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)?;
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)?,
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 trie nodes
parser
.storage
.ensure_no_unused_nodes()
.map_err(VerificationError::StorageProof)?;
// We only support single lane messages in this generated_schema
let mut proved_messages = ProvedMessages::new();
proved_messages.insert(lane, proved_lane_messages);
Ok(proved_messages)
}
struct StorageProofCheckerAdapter<H: Hasher, B> {
storage: StorageProofChecker<H>,
_dummy: sp_std::marker::PhantomData<B>,
}
impl<H: Hasher, B: MessageBridge> StorageProofCheckerAdapter<H, B> {
fn read_and_decode_outbound_lane_data(
&mut self,
lane_id: &LaneId,
) -> Result<Option<OutboundLaneData>, VerificationError> {
let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key(
B::BRIDGED_MESSAGES_PALLET_NAME,
lane_id,
);
self.storage
.read_and_decode_opt_value(storage_outbound_lane_data_key.0.as_ref())
.map_err(VerificationError::OutboundLaneStorage)
}
fn read_and_decode_message_payload(
&mut self,
message_key: &MessageKey,
) -> Result<MessagePayload, VerificationError> {
let storage_message_key = bp_messages::storage_keys::message_key(
B::BRIDGED_MESSAGES_PALLET_NAME,
&message_key.lane_id,
message_key.nonce,
);
self.storage
.read_and_decode_mandatory_value(storage_message_key.0.as_ref())
.map_err(VerificationError::MessageStorage)
}
}
}
/// The `BridgeMessagesCall` used by a chain.
pub type BridgeMessagesCallOf<C> = bp_messages::BridgeMessagesCall<
bp_runtime::AccountIdOf<C>,
target::FromBridgedChainMessagesProof<bp_runtime::HashOf<C>>,
source::FromBridgedChainMessagesDeliveryProof<bp_runtime::HashOf<C>>,
>;
#[cfg(test)]
mod tests {
use super::*;
use crate::{
messages_generation::{
encode_all_messages, encode_lane_data, prepare_messages_storage_proof,
},
mock::*,
};
use bp_header_chain::{HeaderChainError, StoredHeaderDataBuilder};
use bp_runtime::{HeaderId, StorageProofError};
use codec::Encode;
use sp_core::H256;
use sp_runtime::traits::Header as _;
#[test]
fn verify_chain_message_rejects_message_with_too_large_declared_weight() {
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
42;
BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT -
1
])
.is_err());
}
#[test]
fn verify_chain_message_rejects_message_too_large_message() {
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
0;
source::maximal_message_size::<OnThisChainBridge>()
as usize + 1
],)
.is_err());
}
#[test]
fn verify_chain_message_accepts_maximal_message() {
assert_eq!(
source::verify_chain_message::<OnThisChainBridge>(&vec![
0;
source::maximal_message_size::<OnThisChainBridge>()
as _
],),
Ok(()),
);
}
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>,
test: impl Fn(target::FromBridgedChainMessagesProof<H256>) -> R,
) -> R {
let (state_root, storage_proof) = prepare_messages_storage_proof::<OnThisChainBridge>(
TEST_LANE_ID,
1..=nonces_end,
outbound_lane_data,
bp_runtime::StorageProofSize::Minimal(0),
vec![42],
encode_message,
encode_outbound_lane_data,
);
sp_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();
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(HeaderId(
0,
bridged_header_hash,
));
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
bridged_header.build(),
);
test(target::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, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(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, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(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, |proof| {
let bridged_header_hash =
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::remove(bridged_header_hash);
target::verify_messages_proof::<OnThisChainBridge>(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, |proof| {
let bridged_header_hash =
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
BridgedChainHeader::new(
0,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
.build(),
);
target::verify_messages_proof::<OnThisChainBridge>(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, |mut proof| {
let node = proof.storage_proof.pop().unwrap();
proof.storage_proof.push(node.clone());
proof.storage_proof.push(node);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
},),
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
StorageProofError::DuplicateNodesInProof
))),
);
}
#[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, |mut proof| {
proof.storage_proof.push(vec![42]);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
},),
Err(VerificationError::StorageProof(StorageProofError::UnusedNodesInTheProof)),
);
}
#[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,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
),
Err(VerificationError::MessageStorage(StorageProofError::StorageValueEmpty)),
);
}
#[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,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
),
Err(VerificationError::MessageStorage(StorageProofError::StorageValueDecodeFailed(_))),
);
}
#[test]
fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() {
matches!(
using_messages_proof(
10,
Some(OutboundLaneData {
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
},
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
),
Err(VerificationError::OutboundLaneStorage(
StorageProofError::StorageValueDecodeFailed(_)
)),
);
}
#[test]
fn message_proof_is_rejected_if_it_is_empty() {
assert_eq!(
using_messages_proof(0, None, encode_all_messages, encode_lane_data, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(proof, 0)
},),
Err(VerificationError::EmptyMessageProof),
);
}
#[test]
fn non_empty_message_proof_without_messages_is_accepted() {
assert_eq!(
using_messages_proof(
0,
Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 0),
),
Ok(vec![(
TEST_LANE_ID,
ProvedLaneMessages {
lane_state: Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
messages: Vec::new(),
},
)]
.into_iter()
.collect()),
);
}
#[test]
fn non_empty_message_proof_is_accepted() {
assert_eq!(
using_messages_proof(
1,
Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 1),
),
Ok(vec![(
TEST_LANE_ID,
ProvedLaneMessages {
lane_state: Some(OutboundLaneData {
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],
}],
},
)]
.into_iter()
.collect()),
);
}
#[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, |mut proof| {
proof.nonces_end = u64::MAX;
target::verify_messages_proof::<OnThisChainBridge>(proof, u32::MAX)
},),
Err(VerificationError::MessagesCountMismatch),
);
}
}
@@ -1,66 +0,0 @@
// 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, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails,
};
use sp_std::vec::Vec;
/// Implementation of the `To*OutboundLaneApi::message_details`.
pub fn outbound_message_details<Runtime, MessagesPalletInstance>(
lane: LaneId,
begin: MessageNonce,
end: MessageNonce,
) -> Vec<OutboundMessageDetails>
where
Runtime: pallet_bridge_messages::Config<MessagesPalletInstance>,
MessagesPalletInstance: 'static,
{
(begin..=end)
.filter_map(|nonce| {
let message_data =
pallet_bridge_messages::Pallet::<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: frame_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: LaneId,
messages: Vec<(MessagePayload, OutboundMessageDetails)>,
) -> Vec<InboundMessageDetails>
where
Runtime: pallet_bridge_messages::Config<MessagesPalletInstance>,
MessagesPalletInstance: 'static,
{
messages
.into_iter()
.map(|(payload, details)| {
pallet_bridge_messages::Pallet::<Runtime, MessagesPalletInstance>::inbound_message_data(
lane, payload, details,
)
})
.collect()
}
@@ -1,314 +0,0 @@
// 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
//! `bridge_runtime_common::messages` implementation.
#![cfg(feature = "runtime-benchmarks")]
use crate::{
messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
AccountIdOf, BridgedChain, HashOf, MessageBridge, ThisChain,
},
messages_generation::{
encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof,
prepare_messages_storage_proof,
},
};
use bp_messages::MessagePayload;
use bp_polkadot_core::parachains::ParaHash;
use bp_runtime::{Chain, Parachain, StorageProofSize, UnderlyingChainOf};
use codec::Encode;
use frame_support::weights::Weight;
use pallet_bridge_messages::benchmarking::{MessageDeliveryProofParams, MessageProofParams};
use sp_runtime::traits::{Header, Zero};
use sp_std::prelude::*;
use xcm::latest::prelude::*;
/// Prepare inbound bridge message according to given message proof parameters.
fn prepare_inbound_message(
params: &MessageProofParams,
successful_dispatch_message_generator: impl Fn(usize) -> MessagePayload,
) -> MessagePayload {
// we only care about **this** message size when message proof needs to be `Minimal`
let expected_size = match params.size {
StorageProofSize::Minimal(size) => size as usize,
_ => 0,
};
// 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 pallet, linked to the chain that
/// uses GRANDPA finality. For parachains, please use the `prepare_message_proof_from_parachain`
/// function.
pub fn prepare_message_proof_from_grandpa_chain<R, FI, B>(
params: MessageProofParams,
message_generator: impl Fn(usize) -> MessagePayload,
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>, Weight)
where
R: pallet_bridge_grandpa::Config<FI, BridgedChain = UnderlyingChainOf<BridgedChain<B>>>,
FI: 'static,
B: MessageBridge,
{
// prepare storage proof
let (state_root, storage_proof) = prepare_messages_storage_proof::<B>(
params.lane,
params.message_nonces.clone(),
params.outbound_lane_data.clone(),
params.size,
prepare_inbound_message(&params, message_generator),
encode_all_messages,
encode_lane_data,
);
// 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 pallet, linked to the chain that
/// uses parachain finality. For GRANDPA chains, please use the
/// `prepare_message_proof_from_grandpa_chain` function.
pub fn prepare_message_proof_from_parachain<R, PI, B>(
params: MessageProofParams,
message_generator: impl Fn(usize) -> MessagePayload,
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>, Weight)
where
R: pallet_bridge_parachains::Config<PI>,
PI: 'static,
B: MessageBridge,
UnderlyingChainOf<BridgedChain<B>>: Chain<Hash = ParaHash> + Parachain,
{
// prepare storage proof
let (state_root, storage_proof) = prepare_messages_storage_proof::<B>(
params.lane,
params.message_nonces.clone(),
params.outbound_lane_data.clone(),
params.size,
prepare_inbound_message(&params, message_generator),
encode_all_messages,
encode_lane_data,
);
// update runtime storage
let (_, bridged_header_hash) =
insert_header_to_parachains_pallet::<R, PI, UnderlyingChainOf<BridgedChain<B>>>(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 pallet, linked to the chain that
/// uses GRANDPA finality. For parachains, please use the
/// `prepare_message_delivery_proof_from_parachain` function.
pub fn prepare_message_delivery_proof_from_grandpa_chain<R, FI, B>(
params: MessageDeliveryProofParams<AccountIdOf<ThisChain<B>>>,
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>
where
R: pallet_bridge_grandpa::Config<FI, BridgedChain = UnderlyingChainOf<BridgedChain<B>>>,
FI: 'static,
B: MessageBridge,
{
// prepare storage proof
let lane = params.lane;
let (state_root, storage_proof) = prepare_message_delivery_storage_proof::<B>(
params.lane,
params.inbound_lane_data,
params.size,
);
// 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 pallet, linked to the chain that
/// uses parachain finality. For GRANDPA chains, please use the
/// `prepare_message_delivery_proof_from_grandpa_chain` function.
pub fn prepare_message_delivery_proof_from_parachain<R, PI, B>(
params: MessageDeliveryProofParams<AccountIdOf<ThisChain<B>>>,
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>
where
R: pallet_bridge_parachains::Config<PI>,
PI: 'static,
B: MessageBridge,
UnderlyingChainOf<BridgedChain<B>>: Chain<Hash = ParaHash> + Parachain,
{
// prepare storage proof
let lane = params.lane;
let (state_root, storage_proof) = prepare_message_delivery_storage_proof::<B>(
params.lane,
params.inbound_lane_data,
params.size,
);
// update runtime storage
let (_, bridged_header_hash) =
insert_header_to_parachains_pallet::<R, PI, UnderlyingChainOf<BridgedChain<B>>>(state_root);
FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: bridged_header_hash.into(),
storage_proof,
lane,
}
}
/// Insert header to the bridge GRANDPA pallet.
pub(crate) fn insert_header_to_grandpa_pallet<R, GI>(
state_root: bp_runtime::HashOf<R::BridgedChain>,
) -> (bp_runtime::BlockNumberOf<R::BridgedChain>, bp_runtime::HashOf<R::BridgedChain>)
where
R: pallet_bridge_grandpa::Config<GI>,
GI: 'static,
R::BridgedChain: bp_runtime::Chain,
{
let bridged_block_number = Zero::zero();
let bridged_header = bp_runtime::HeaderOf::<R::BridgedChain>::new(
bridged_block_number,
Default::default(),
state_root,
Default::default(),
Default::default(),
);
let bridged_header_hash = bridged_header.hash();
pallet_bridge_grandpa::initialize_for_benchmarks::<R, GI>(bridged_header);
(bridged_block_number, bridged_header_hash)
}
/// Insert header to the bridge parachains pallet.
pub(crate) fn insert_header_to_parachains_pallet<R, PI, PC>(
state_root: bp_runtime::HashOf<PC>,
) -> (bp_runtime::BlockNumberOf<PC>, bp_runtime::HashOf<PC>)
where
R: pallet_bridge_parachains::Config<PI>,
PI: 'static,
PC: Chain<Hash = ParaHash> + Parachain,
{
let bridged_block_number = Zero::zero();
let bridged_header = bp_runtime::HeaderOf::<PC>::new(
bridged_block_number,
Default::default(),
state_root,
Default::default(),
Default::default(),
);
let bridged_header_hash = bridged_header.hash();
pallet_bridge_parachains::initialize_for_benchmarks::<R, PI, PC>(bridged_header);
(bridged_block_number, bridged_header_hash)
}
/// Returns callback which generates `BridgeMessage` from Polkadot 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::V4(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(),
);
log::trace!(
target: "runtime::bridge-benchmarks",
"generate_xcm_builder_bridge_message_sample with expected_message_size: {}, location_encoded_size: {}, xcm_size: {}, xcm_data_size: {}",
expected_message_size, location_encoded_size, xcm_size, xcm_data_size,
);
let xcm = xcm::VersionedXcm::<()>::V4(
vec![Instruction::<()>::ExpectPallet {
index: 0,
name: vec![42; xcm_data_size],
module_name: vec![],
crate_major: 0,
min_crate_minor: 0,
}]
.into(),
);
// this is the `BridgeMessage` from polkadot 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()
}
}
@@ -1,692 +0,0 @@
// 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/>.
//! Signed extension for the `pallet-bridge-messages` that is able to reject obsolete
//! (and some other invalid) transactions.
use crate::messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
};
use bp_messages::{target_chain::MessageDispatch, InboundLaneData, LaneId, MessageNonce};
use bp_runtime::OwnedBridgeModule;
use frame_support::{
dispatch::CallableCallFor,
traits::{Get, IsSubType},
};
use pallet_bridge_messages::{Config, Pallet};
use sp_runtime::{transaction_validity::TransactionValidity, RuntimeDebug};
use sp_std::ops::RangeInclusive;
/// Generic info about a messages delivery/confirmation proof.
#[derive(PartialEq, RuntimeDebug)]
pub struct BaseMessagesProofInfo {
/// Message lane, used by the call.
pub lane_id: LaneId,
/// Nonces of messages, included in the call.
///
/// For delivery transaction, it is nonces of bundled messages. For confirmation
/// transaction, it is nonces that are to be confirmed during the call.
pub bundled_range: RangeInclusive<MessageNonce>,
/// Nonce of the best message, stored by this chain before the call is dispatched.
///
/// For delivery transaction, it is the nonce of best delivered message before the call.
/// For confirmation transaction, it is the nonce of best confirmed message before the call.
pub best_stored_nonce: MessageNonce,
}
impl BaseMessagesProofInfo {
/// Returns true if `bundled_range` continues the `0..=best_stored_nonce` range.
fn appends_to_stored_nonce(&self) -> bool {
Some(*self.bundled_range.start()) == self.best_stored_nonce.checked_add(1)
}
}
/// Occupation state of the unrewarded relayers vector.
#[derive(PartialEq, RuntimeDebug)]
#[cfg_attr(test, derive(Default))]
pub struct UnrewardedRelayerOccupation {
/// The number of remaining unoccupied entries for new relayers.
pub free_relayer_slots: MessageNonce,
/// The number of messages that we are ready to accept.
pub free_message_slots: MessageNonce,
}
/// Info about a `ReceiveMessagesProof` call which tries to update a single lane.
#[derive(PartialEq, RuntimeDebug)]
pub struct ReceiveMessagesProofInfo {
/// Base messages proof info
pub base: BaseMessagesProofInfo,
/// State of unrewarded relayers vector.
pub unrewarded_relayers: UnrewardedRelayerOccupation,
}
impl ReceiveMessagesProofInfo {
/// Returns true if:
///
/// - either inbound lane is ready to accept bundled messages;
///
/// - or there are no bundled messages, but the inbound lane is blocked by too many unconfirmed
/// messages and/or unrewarded relayers.
fn is_obsolete(&self, is_dispatcher_active: bool) -> bool {
// if dispatcher is inactive, we don't accept any delivery transactions
if !is_dispatcher_active {
return true
}
// transactions with zero bundled nonces are not allowed, unless they're message
// delivery transactions, which brings reward confirmations required to unblock
// the lane
if self.base.bundled_range.is_empty() {
let empty_transactions_allowed =
// we allow empty transactions when we can't accept delivery from new relayers
self.unrewarded_relayers.free_relayer_slots == 0 ||
// or if we can't accept new messages at all
self.unrewarded_relayers.free_message_slots == 0;
return !empty_transactions_allowed
}
// otherwise we require bundled messages to continue stored range
!self.base.appends_to_stored_nonce()
}
}
/// Info about a `ReceiveMessagesDeliveryProof` call which tries to update a single lane.
#[derive(PartialEq, RuntimeDebug)]
pub struct ReceiveMessagesDeliveryProofInfo(pub BaseMessagesProofInfo);
impl ReceiveMessagesDeliveryProofInfo {
/// Returns true if outbound lane is ready to accept confirmations of bundled messages.
fn is_obsolete(&self) -> bool {
self.0.bundled_range.is_empty() || !self.0.appends_to_stored_nonce()
}
}
/// Info about a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call
/// which tries to update a single lane.
#[derive(PartialEq, RuntimeDebug)]
pub enum CallInfo {
/// Messages delivery call info.
ReceiveMessagesProof(ReceiveMessagesProofInfo),
/// Messages delivery confirmation call info.
ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo),
}
impl CallInfo {
/// Returns range of messages, bundled with the call.
pub fn bundled_messages(&self) -> RangeInclusive<MessageNonce> {
match *self {
Self::ReceiveMessagesProof(ref info) => info.base.bundled_range.clone(),
Self::ReceiveMessagesDeliveryProof(ref info) => info.0.bundled_range.clone(),
}
}
}
/// Helper struct that provides methods for working with a call supported by `CallInfo`.
pub struct CallHelper<T: Config<I>, I: 'static> {
_phantom_data: sp_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: &CallInfo) -> bool {
match info {
CallInfo::ReceiveMessagesProof(info) => {
let inbound_lane_data =
pallet_bridge_messages::InboundLanes::<T, I>::get(info.base.lane_id);
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()
},
CallInfo::ReceiveMessagesDeliveryProof(info) => {
let outbound_lane_data =
pallet_bridge_messages::OutboundLanes::<T, I>::get(info.0.lane_id);
outbound_lane_data.latest_received_nonce == *info.0.bundled_range.end()
},
}
}
}
/// Trait representing a call that is a sub type of `pallet_bridge_messages::Call`.
pub trait MessagesCallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
IsSubType<CallableCallFor<Pallet<T, I>, T>>
{
/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call.
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo>;
/// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from
/// a `ReceiveMessagesDeliveryProof` call.
fn receive_messages_delivery_proof_info(&self) -> Option<ReceiveMessagesDeliveryProofInfo>;
/// Create a new instance of `CallInfo` from a `ReceiveMessagesProof`
/// or a `ReceiveMessagesDeliveryProof` call.
fn call_info(&self) -> Option<CallInfo>;
/// Create a new instance of `CallInfo` from a `ReceiveMessagesProof`
/// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane.
fn call_info_for(&self, lane_id: LaneId) -> Option<CallInfo>;
/// 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 pallet rules obsolete (like
/// submitting a call when a pallet 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<
BridgedHeaderHash,
SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain<
MessagesProof = FromBridgedChainMessagesProof<BridgedHeaderHash>,
>,
TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain<
<T as Config<I>>::OutboundPayload,
<T as frame_system::Config>::AccountId,
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash>,
>,
Call: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
T: frame_system::Config<RuntimeCall = Call>
+ Config<I, SourceHeaderChain = SourceHeaderChain, TargetHeaderChain = TargetHeaderChain>,
I: 'static,
> MessagesCallSubType<T, I> for T::RuntimeCall
{
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo> {
if let Some(pallet_bridge_messages::Call::<T, I>::receive_messages_proof {
ref proof,
..
}) = self.is_sub_type()
{
let inbound_lane_data = pallet_bridge_messages::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> {
if let Some(pallet_bridge_messages::Call::<T, I>::receive_messages_delivery_proof {
ref proof,
ref relayers_state,
..
}) = self.is_sub_type()
{
let outbound_lane_data = pallet_bridge_messages::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<CallInfo> {
if let Some(info) = self.receive_messages_proof_info() {
return Some(CallInfo::ReceiveMessagesProof(info))
}
if let Some(info) = self.receive_messages_delivery_proof_info() {
return Some(CallInfo::ReceiveMessagesDeliveryProof(info))
}
None
}
fn call_info_for(&self, lane_id: LaneId) -> Option<CallInfo> {
self.call_info().filter(|info| {
let actual_lane_id = match info {
CallInfo::ReceiveMessagesProof(info) => info.base.lane_id,
CallInfo::ReceiveMessagesDeliveryProof(info) => info.0.lane_id,
};
actual_lane_id == lane_id
})
}
fn check_obsolete_call(&self) -> TransactionValidity {
let is_pallet_halted = Pallet::<T, I>::ensure_not_halted().is_err();
match self.call_info() {
Some(proof_info) if is_pallet_halted => {
log::trace!(
target: pallet_bridge_messages::LOG_TARGET,
"Rejecting messages transaction on halted pallet: {:?}",
proof_info
);
return sp_runtime::transaction_validity::InvalidTransaction::Call.into()
},
Some(CallInfo::ReceiveMessagesProof(proof_info))
if proof_info.is_obsolete(T::MessageDispatch::is_active()) =>
{
log::trace!(
target: pallet_bridge_messages::LOG_TARGET,
"Rejecting obsolete messages delivery transaction: {:?}",
proof_info
);
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
},
Some(CallInfo::ReceiveMessagesDeliveryProof(proof_info))
if proof_info.is_obsolete() =>
{
log::trace!(
target: pallet_bridge_messages::LOG_TARGET,
"Rejecting obsolete messages confirmation transaction: {:?}",
proof_info,
);
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
},
_ => {},
}
Ok(sp_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<T::InboundRelayer>,
) -> UnrewardedRelayerOccupation {
UnrewardedRelayerOccupation {
free_relayer_slots: T::MaxUnrewardedRelayerEntriesAtInboundLane::get()
.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::MaxUnconfirmedMessagesAtInboundLane::get().saturating_sub(unconfirmed_messages)
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
},
messages_call_ext::MessagesCallSubType,
mock::{
DummyMessageDispatch, MaxUnconfirmedMessagesAtInboundLane,
MaxUnrewardedRelayerEntriesAtInboundLane, TestRuntime, ThisChainRuntimeCall,
},
};
use bp_messages::{DeliveredMessages, UnrewardedRelayer, UnrewardedRelayersState};
use sp_std::ops::RangeInclusive;
fn fill_unrewarded_relayers() {
let mut inbound_lane_state =
pallet_bridge_messages::InboundLanes::<TestRuntime>::get(LaneId([0, 0, 0, 0]));
for n in 0..MaxUnrewardedRelayerEntriesAtInboundLane::get() {
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
relayer: Default::default(),
messages: DeliveredMessages { begin: n + 1, end: n + 1 },
});
}
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
LaneId([0, 0, 0, 0]),
inbound_lane_state,
);
}
fn fill_unrewarded_messages() {
let mut inbound_lane_state =
pallet_bridge_messages::InboundLanes::<TestRuntime>::get(LaneId([0, 0, 0, 0]));
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
relayer: Default::default(),
messages: DeliveredMessages {
begin: 1,
end: MaxUnconfirmedMessagesAtInboundLane::get(),
},
});
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
LaneId([0, 0, 0, 0]),
inbound_lane_state,
);
}
fn deliver_message_10() {
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
LaneId([0, 0, 0, 0]),
bp_messages::InboundLaneData { relayers: Default::default(), last_confirmed_nonce: 10 },
);
}
fn validate_message_delivery(
nonces_start: bp_messages::MessageNonce,
nonces_end: bp_messages::MessageNonce,
) -> bool {
ThisChainRuntimeCall::BridgeMessages(
pallet_bridge_messages::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: frame_support::weights::Weight::zero(),
proof: FromBridgedChainMessagesProof {
bridged_header_hash: Default::default(),
storage_proof: vec![],
lane: LaneId([0, 0, 0, 0]),
nonces_start,
nonces_end,
},
},
)
.check_obsolete_call()
.is_ok()
}
#[test]
fn extension_rejects_obsolete_messages() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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();
DummyMessageDispatch::deactivate();
assert!(!validate_message_delivery(11, 15));
});
}
#[test]
fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots(
) {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
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(
) {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
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(
) {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
fill_unrewarded_messages();
assert!(validate_message_delivery(
MaxUnconfirmedMessagesAtInboundLane::get(),
MaxUnconfirmedMessagesAtInboundLane::get() - 1
));
});
}
#[test]
fn extension_accepts_new_messages() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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() {
pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(
LaneId([0, 0, 0, 0]),
bp_messages::OutboundLaneData {
oldest_unpruned_nonce: 0,
latest_received_nonce: 10,
latest_generated_nonce: 10,
},
);
}
fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool {
ThisChainRuntimeCall::BridgeMessages(
pallet_bridge_messages::Call::<TestRuntime>::receive_messages_delivery_proof {
proof: FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: Default::default(),
storage_proof: Vec::new(),
lane: LaneId([0, 0, 0, 0]),
},
relayers_state: UnrewardedRelayersState {
last_delivered_nonce,
..Default::default()
},
},
)
.check_obsolete_call()
.is_ok()
}
#[test]
fn extension_rejects_obsolete_confirmations() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
confirm_message_10();
fill_unrewarded_relayers();
assert!(!validate_message_confirmation(10));
});
}
#[test]
fn extension_accepts_new_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// 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(&CallInfo::ReceiveMessagesProof(
ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: LaneId([0, 0, 0, 0]),
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 {
MaxUnconfirmedMessagesAtInboundLane::get()
},
},
},
))
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn was_successful_returns_false_for_failed_reward_confirmation_transaction() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
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() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
assert!(was_message_delivery_successful(10..=9, true));
});
}
#[test]
fn was_successful_returns_false_for_failed_delivery() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
deliver_message_10();
assert!(!was_message_delivery_successful(10..=12, false));
});
}
#[test]
fn was_successful_returns_false_for_partially_successful_delivery() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
deliver_message_10();
assert!(!was_message_delivery_successful(9..=12, false));
});
}
#[test]
fn was_successful_returns_true_for_successful_delivery() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
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(&CallInfo::ReceiveMessagesDeliveryProof(
ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
lane_id: LaneId([0, 0, 0, 0]),
bundled_range,
best_stored_nonce: 0, // doesn't matter for `was_successful`
}),
))
}
#[test]
fn was_successful_returns_false_for_failed_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
confirm_message_10();
assert!(!was_message_confirmation_successful(10..=12));
});
}
#[test]
fn was_successful_returns_false_for_partially_successful_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
confirm_message_10();
assert!(!was_message_confirmation_successful(9..=12));
});
}
#[test]
fn was_successful_returns_true_for_successful_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
confirm_message_10();
assert!(was_message_confirmation_successful(9..=10));
});
}
}
@@ -1,150 +0,0 @@
// 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 crate::messages::{AccountIdOf, BridgedChain, HashOf, HasherOf, MessageBridge, ThisChain};
use bp_messages::{
storage_keys, InboundLaneData, LaneId, MessageKey, MessageNonce, MessagePayload,
OutboundLaneData,
};
use bp_runtime::{record_all_trie_keys, RawStorageProof, StorageProofSize};
use codec::Encode;
use sp_std::{ops::RangeInclusive, prelude::*};
use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
/// 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.
pub fn prepare_messages_storage_proof<B>(
lane: LaneId,
message_nonces: RangeInclusive<MessageNonce>,
outbound_lane_data: Option<OutboundLaneData>,
size: StorageProofSize,
message_payload: MessagePayload,
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
) -> (HashOf<BridgedChain<B>>, RawStorageProof)
where
B: MessageBridge,
HashOf<BridgedChain<B>>: 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<B>>>::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, &message_payload) {
Some(message_payload) =>
if i == 0 {
grow_trie_leaf_value(message_payload, size)
} else {
message_payload
},
None => continue,
};
let storage_key = storage_keys::message_key(
B::BRIDGED_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(B::BRIDGED_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 storage_proof = record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain<B>>>, _>(&mdb, &root)
.map_err(|_| "record_all_trie_keys has failed")
.expect("record_all_trie_keys should not fail in benchmarks");
(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<B>(
lane: LaneId,
inbound_lane_data: InboundLaneData<AccountIdOf<ThisChain<B>>>,
size: StorageProofSize,
) -> (HashOf<BridgedChain<B>>, RawStorageProof)
where
B: MessageBridge,
{
// prepare Bridged chain storage with inbound lane state
let storage_key = storage_keys::inbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &lane).0;
let mut root = Default::default();
let mut mdb = MemoryDB::default();
{
let mut trie =
TrieDBMutBuilderV1::<HasherOf<BridgedChain<B>>>::new(&mut mdb, &mut root).build();
let inbound_lane_data = grow_trie_leaf_value(inbound_lane_data.encode(), size);
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<B>>>, _>(&mdb, &root)
.map_err(|_| "record_all_trie_keys has failed")
.expect("record_all_trie_keys should not fail in benchmarks");
(root, storage_proof)
}
/// Add extra data to the trie leaf value so that it'll be of given size.
pub fn grow_trie_leaf_value(mut value: Vec<u8>, size: StorageProofSize) -> Vec<u8> {
match size {
StorageProofSize::Minimal(_) => (),
StorageProofSize::HasLargeLeaf(size) if size as usize > value.len() => {
value.extend(sp_std::iter::repeat(42u8).take(size as usize - value.len()));
},
StorageProofSize::HasLargeLeaf(_) => (),
}
value
}
@@ -1,502 +0,0 @@
// 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/>.
//! Module provides utilities for easier XCM handling, e.g:
//! `XcmExecutor` -> `MessageSender` -> `OutboundMessageQueue`
//! |
//! `Relayer`
//! |
//! `XcmRouter` <- `MessageDispatch` <- `InboundMessageQueue`
use bp_messages::{
source_chain::OnMessagesDelivered,
target_chain::{DispatchMessage, MessageDispatch},
LaneId, MessageNonce,
};
use bp_runtime::messages::MessageDispatchResult;
pub use bp_xcm_bridge_hub::XcmAsPlainPayload;
use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
use codec::{Decode, Encode};
use frame_support::{traits::Get, weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound};
use pallet_bridge_messages::{
Config as MessagesConfig, OutboundLanesCongestedSignals, WeightInfoExt as MessagesPalletWeights,
};
use scale_info::TypeInfo;
use sp_runtime::SaturatedConversion;
use sp_std::{fmt::Debug, marker::PhantomData};
use xcm::prelude::*;
use xcm_builder::{DispatchBlob, DispatchBlobError};
/// Message dispatch result type for single message.
#[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, 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>),
}
/// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages
///
/// It needs to be used at the target bridge hub.
pub struct XcmBlobMessageDispatch<DispatchBlob, Weights, Channel> {
_marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, Channel)>,
}
impl<
BlobDispatcher: DispatchBlob,
Weights: MessagesPalletWeights,
Channel: XcmChannelStatusProvider,
> MessageDispatch for XcmBlobMessageDispatch<BlobDispatcher, Weights, Channel>
{
type DispatchPayload = XcmAsPlainPayload;
type DispatchLevelResult = XcmBlobMessageDispatchResult;
fn is_active() -> bool {
!Channel::is_congested()
}
fn dispatch_weight(message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
match message.data.payload {
Ok(ref payload) => {
let payload_size = payload.encoded_size().saturated_into();
Weights::message_dispatch_weight(payload_size)
},
Err(_) => Weight::zero(),
}
}
fn dispatch(
message: DispatchMessage<Self::DispatchPayload>,
) -> MessageDispatchResult<Self::DispatchLevelResult> {
let payload = match message.data.payload {
Ok(payload) => payload,
Err(e) => {
log::error!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"[XcmBlobMessageDispatch] payload error: {:?} - message_nonce: {:?}",
e,
message.key.nonce
);
return MessageDispatchResult {
unspent_weight: Weight::zero(),
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
}
},
};
let dispatch_level_result = match BlobDispatcher::dispatch_blob(payload) {
Ok(_) => {
log::debug!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob was ok - message_nonce: {:?}",
message.key.nonce
);
XcmBlobMessageDispatchResult::Dispatched
},
Err(e) => {
log::error!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?} - message_nonce: {:?}",
e, message.key.nonce
);
XcmBlobMessageDispatchResult::NotDispatched(Some(e))
},
};
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result }
}
}
/// A pair of sending chain location and message lane, used by this chain to send messages
/// over the bridge.
#[cfg_attr(feature = "std", derive(Debug, Eq, PartialEq))]
pub struct SenderAndLane {
/// Sending chain relative location.
pub location: Location,
/// Message lane, used by the sending chain.
pub lane: LaneId,
}
impl SenderAndLane {
/// Create new object using provided location and lane.
pub fn new(location: Location, lane: LaneId) -> Self {
SenderAndLane { location, lane }
}
}
/// [`XcmBlobHauler`] is responsible for sending messages to the bridge "point-to-point link" from
/// one side, where on the other it can be dispatched by [`XcmBlobMessageDispatch`].
pub trait XcmBlobHauler {
/// Runtime that has messages pallet deployed.
type Runtime: MessagesConfig<Self::MessagesInstance>;
/// Instance of the messages pallet that is used to send messages.
type MessagesInstance: 'static;
/// Actual XCM message sender (`HRMP` or `UMP`) to the source chain
/// location (`Self::SenderAndLane::get().location`).
type ToSourceChainSender: SendXcm;
/// An XCM message that is sent to the sending chain when the bridge queue becomes congested.
type CongestedMessage: Get<Option<Xcm<()>>>;
/// An XCM message that is sent to the sending chain when the bridge queue becomes not
/// congested.
type UncongestedMessage: Get<Option<Xcm<()>>>;
/// Returns `true` if we want to handle congestion.
fn supports_congestion_detection() -> bool {
Self::CongestedMessage::get().is_some() || Self::UncongestedMessage::get().is_some()
}
}
/// XCM bridge adapter which connects [`XcmBlobHauler`] with [`pallet_bridge_messages`] and
/// makes sure that XCM blob is sent to the outbound lane to be relayed.
///
/// It needs to be used at the source bridge hub.
pub struct XcmBlobHaulerAdapter<XcmBlobHauler, Lanes>(
sp_std::marker::PhantomData<(XcmBlobHauler, Lanes)>,
);
impl<
H: XcmBlobHauler,
Lanes: Get<sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))>>,
> OnMessagesDelivered for XcmBlobHaulerAdapter<H, Lanes>
{
fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) {
if let Some(sender_and_lane) =
Lanes::get().iter().find(|link| link.0.lane == lane).map(|link| &link.0)
{
// notify XCM queue manager about updated lane state
LocalXcmQueueManager::<H>::on_bridge_messages_delivered(
sender_and_lane,
enqueued_messages,
);
}
}
}
/// Manager of local XCM queues (and indirectly - underlying transport channels) that
/// controls the queue state.
///
/// It needs to be used at the source bridge hub.
pub struct LocalXcmQueueManager<H>(PhantomData<H>);
/// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we
/// send a "congestion" XCM message to the sending chain.
const OUTBOUND_LANE_CONGESTED_THRESHOLD: MessageNonce = 8_192;
/// After we have sent "congestion" XCM message to the sending chain, we wait until number
/// of messages in the outbound bridge queue drops to this count, before sending `uncongestion`
/// XCM message.
const OUTBOUND_LANE_UNCONGESTED_THRESHOLD: MessageNonce = 1_024;
impl<H: XcmBlobHauler> LocalXcmQueueManager<H> {
/// Must be called whenever we push a message to the bridge lane.
pub fn on_bridge_message_enqueued(
sender_and_lane: &SenderAndLane,
enqueued_messages: MessageNonce,
) {
// skip if we dont want to handle congestion
if !H::supports_congestion_detection() {
return
}
// if we have already sent the congestion signal, we don't want to do anything
if Self::is_congested_signal_sent(sender_and_lane.lane) {
return
}
// 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
}
log::info!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"Sending 'congested' XCM message to {:?} to avoid overloading lane {:?}: there are\
{} messages queued at the bridge queue",
sender_and_lane.location,
sender_and_lane.lane,
enqueued_messages,
);
if let Err(e) = Self::send_congested_signal(sender_and_lane) {
log::info!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"Failed to send the 'congested' XCM message to {:?}: {:?}",
sender_and_lane.location,
e,
);
}
}
/// Must be called whenever we receive a message delivery confirmation.
pub fn on_bridge_messages_delivered(
sender_and_lane: &SenderAndLane,
enqueued_messages: MessageNonce,
) {
// skip if we don't want to handle congestion
if !H::supports_congestion_detection() {
return
}
// if we have not sent the congestion signal before, we don't want to do anything
if !Self::is_congested_signal_sent(sender_and_lane.lane) {
return
}
// 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
}
log::info!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"Sending 'uncongested' XCM message to {:?}. Lane {:?}: there are\
{} messages queued at the bridge queue",
sender_and_lane.location,
sender_and_lane.lane,
enqueued_messages,
);
if let Err(e) = Self::send_uncongested_signal(sender_and_lane) {
log::info!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"Failed to send the 'uncongested' XCM message to {:?}: {:?}",
sender_and_lane.location,
e,
);
}
}
/// Returns true if we have sent "congested" signal to the `sending_chain_location`.
fn is_congested_signal_sent(lane: LaneId) -> bool {
OutboundLanesCongestedSignals::<H::Runtime, H::MessagesInstance>::get(lane)
}
/// Send congested signal to the `sending_chain_location`.
fn send_congested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> {
if let Some(msg) = H::CongestedMessage::get() {
send_xcm::<H::ToSourceChainSender>(sender_and_lane.location.clone(), msg)?;
OutboundLanesCongestedSignals::<H::Runtime, H::MessagesInstance>::insert(
sender_and_lane.lane,
true,
);
}
Ok(())
}
/// Send `uncongested` signal to the `sending_chain_location`.
fn send_uncongested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> {
if let Some(msg) = H::UncongestedMessage::get() {
send_xcm::<H::ToSourceChainSender>(sender_and_lane.location.clone(), msg)?;
OutboundLanesCongestedSignals::<H::Runtime, H::MessagesInstance>::remove(
sender_and_lane.lane,
);
}
Ok(())
}
}
/// Adapter for the implementation of `GetVersion`, which attempts to find the minimal
/// configured XCM version between the destination `dest` and the bridge hub location provided as
/// `Get<Location>`.
pub struct XcmVersionOfDestAndRemoteBridge<Version, RemoteBridge>(
sp_std::marker::PhantomData<(Version, RemoteBridge)>,
);
impl<Version: GetVersion, RemoteBridge: Get<Location>> GetVersion
for XcmVersionOfDestAndRemoteBridge<Version, RemoteBridge>
{
fn get_version_for(dest: &Location) -> Option<XcmVersion> {
let dest_version = Version::get_version_for(dest);
let bridge_hub_version = Version::get_version_for(&RemoteBridge::get());
match (dest_version, bridge_hub_version) {
(Some(dv), Some(bhv)) => Some(sp_std::cmp::min(dv, bhv)),
(Some(dv), None) => Some(dv),
(None, Some(bhv)) => Some(bhv),
(None, None) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::*;
use bp_messages::OutboundLaneData;
use frame_support::parameter_types;
use pallet_bridge_messages::OutboundLanes;
parameter_types! {
pub TestSenderAndLane: SenderAndLane = SenderAndLane {
location: Location::new(1, [Parachain(1000)]),
lane: TEST_LANE_ID,
};
pub TestLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))> = sp_std::vec![
(TestSenderAndLane::get(), (NetworkId::ByGenesis([0; 32]), InteriorLocation::Here))
];
pub DummyXcmMessage: Xcm<()> = Xcm::new();
}
struct DummySendXcm;
impl DummySendXcm {
fn messages_sent() -> u32 {
frame_support::storage::unhashed::get(b"DummySendXcm").unwrap_or(0)
}
}
impl SendXcm for DummySendXcm {
type Ticket = ();
fn validate(
_destination: &mut Option<Location>,
_message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
Ok(((), Default::default()))
}
fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> {
let messages_sent: u32 = Self::messages_sent();
frame_support::storage::unhashed::put(b"DummySendXcm", &(messages_sent + 1));
Ok(XcmHash::default())
}
}
struct TestBlobHauler;
impl XcmBlobHauler for TestBlobHauler {
type Runtime = TestRuntime;
type MessagesInstance = ();
type ToSourceChainSender = DummySendXcm;
type CongestedMessage = DummyXcmMessage;
type UncongestedMessage = DummyXcmMessage;
}
type TestBlobHaulerAdapter = XcmBlobHaulerAdapter<TestBlobHauler, TestLanes>;
fn fill_up_lane_to_congestion() -> MessageNonce {
let latest_generated_nonce = OUTBOUND_LANE_CONGESTED_THRESHOLD;
OutboundLanes::<TestRuntime, ()>::insert(
TEST_LANE_ID,
OutboundLaneData {
oldest_unpruned_nonce: 0,
latest_received_nonce: 0,
latest_generated_nonce,
},
);
latest_generated_nonce
}
#[test]
fn congested_signal_is_not_sent_twice() {
run_test(|| {
let enqueued = fill_up_lane_to_congestion();
// next sent message leads to congested signal
LocalXcmQueueManager::<TestBlobHauler>::on_bridge_message_enqueued(
&TestSenderAndLane::get(),
enqueued + 1,
);
assert_eq!(DummySendXcm::messages_sent(), 1);
// next sent message => we don't sent another congested signal
LocalXcmQueueManager::<TestBlobHauler>::on_bridge_message_enqueued(
&TestSenderAndLane::get(),
enqueued,
);
assert_eq!(DummySendXcm::messages_sent(), 1);
});
}
#[test]
fn congested_signal_is_not_sent_when_outbound_lane_is_not_congested() {
run_test(|| {
LocalXcmQueueManager::<TestBlobHauler>::on_bridge_message_enqueued(
&TestSenderAndLane::get(),
1,
);
assert_eq!(DummySendXcm::messages_sent(), 0);
});
}
#[test]
fn congested_signal_is_sent_when_outbound_lane_is_congested() {
run_test(|| {
let enqueued = fill_up_lane_to_congestion();
// next sent message leads to congested signal
LocalXcmQueueManager::<TestBlobHauler>::on_bridge_message_enqueued(
&TestSenderAndLane::get(),
enqueued + 1,
);
assert_eq!(DummySendXcm::messages_sent(), 1);
assert!(LocalXcmQueueManager::<TestBlobHauler>::is_congested_signal_sent(TEST_LANE_ID));
});
}
#[test]
fn uncongested_signal_is_not_sent_when_messages_are_delivered_at_other_lane() {
run_test(|| {
LocalXcmQueueManager::<TestBlobHauler>::send_congested_signal(&TestSenderAndLane::get()).unwrap();
assert_eq!(DummySendXcm::messages_sent(), 1);
// when we receive a delivery report for other lane, we don't send an uncongested signal
TestBlobHaulerAdapter::on_messages_delivered(LaneId([42, 42, 42, 42]), 0);
assert_eq!(DummySendXcm::messages_sent(), 1);
});
}
#[test]
fn uncongested_signal_is_not_sent_when_we_havent_send_congested_signal_before() {
run_test(|| {
TestBlobHaulerAdapter::on_messages_delivered(TEST_LANE_ID, 0);
assert_eq!(DummySendXcm::messages_sent(), 0);
});
}
#[test]
fn uncongested_signal_is_not_sent_if_outbound_lane_is_still_congested() {
run_test(|| {
LocalXcmQueueManager::<TestBlobHauler>::send_congested_signal(&TestSenderAndLane::get()).unwrap();
assert_eq!(DummySendXcm::messages_sent(), 1);
TestBlobHaulerAdapter::on_messages_delivered(
TEST_LANE_ID,
OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1,
);
assert_eq!(DummySendXcm::messages_sent(), 1);
});
}
#[test]
fn uncongested_signal_is_sent_if_outbound_lane_is_uncongested() {
run_test(|| {
LocalXcmQueueManager::<TestBlobHauler>::send_congested_signal(&TestSenderAndLane::get()).unwrap();
assert_eq!(DummySendXcm::messages_sent(), 1);
TestBlobHaulerAdapter::on_messages_delivered(
TEST_LANE_ID,
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
);
assert_eq!(DummySendXcm::messages_sent(), 2);
});
}
}
-427
View File
@@ -1,427 +0,0 @@
// 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 crate::messages::{
source::{
FromThisChainMaximalOutboundPayloadSize, FromThisChainMessagePayload,
TargetHeaderChainAdapter,
},
target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter},
BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages,
};
use bp_header_chain::{ChainWithGrandpa, HeaderChain};
use bp_messages::{
target_chain::{DispatchMessage, MessageDispatch},
LaneId, MessageNonce,
};
use bp_parachains::SingleParaStoredHeaderDataBuilder;
use bp_relayers::PayRewardFromAccount;
use bp_runtime::{
messages::MessageDispatchResult, Chain, ChainId, Parachain, UnderlyingChainProvider,
};
use codec::{Decode, Encode};
use frame_support::{
derive_impl, parameter_types,
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight},
};
use pallet_transaction_payment::Multiplier;
use sp_runtime::{
testing::H256,
traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8},
FixedPointNumber, Perquintill,
};
/// 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;
/// Runtime call origin at `ThisChain`.
pub type ThisChainCallOrigin = RuntimeOrigin;
/// Header of `ThisChain`.
pub type ThisChainHeader = sp_runtime::generic::Header<ThisChainBlockNumber, ThisChainHasher>;
/// Block of `ThisChain`.
pub type ThisChainBlock = frame_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 =
sp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
/// Rewards payment procedure.
pub type TestPaymentProcedure = PayRewardFromAccount<Balances, ThisChainAccountId>;
/// Stake that we are using in tests.
pub type TestStake = ConstU64<5_000>;
/// Stake and slash mechanism to use in tests.
pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
ThisChainAccountId,
ThisChainBlockNumber,
Balances,
ReserveId,
TestStake,
ConstU32<8>,
>;
/// Message lane used in tests.
pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 0]);
/// Bridged chain id used in tests.
pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg";
/// Maximal extrinsic weight at the `BridgedChain`.
pub const BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT: usize = 2048;
/// Maximal extrinsic size at the `BridgedChain`.
pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
frame_support::construct_runtime! {
pub enum TestRuntime
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Utility: pallet_utility,
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
BridgeGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage, Event<T>},
BridgeParachains: pallet_bridge_parachains::{Pallet, Call, Storage, Event<T>},
BridgeMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
}
}
crate::generate_bridge_reject_obsolete_headers_and_messages! {
ThisChainRuntimeCall, ThisChainAccountId,
BridgeGrandpa, BridgeParachains, BridgeMessages
}
parameter_types! {
pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID];
pub const BridgedChainId: ChainId = TEST_BRIDGED_CHAIN_ID;
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 = sp_runtime::traits::Bounded::max_value();
pub const MaxUnrewardedRelayerEntriesAtInboundLane: MessageNonce = 16;
pub const MaxUnconfirmedMessagesAtInboundLane: MessageNonce = 1_000;
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Hash = ThisChainHash;
type Hashing = ThisChainHasher;
type AccountId = ThisChainAccountId;
type Block = ThisChainBlock;
type AccountData = pallet_balances::AccountData<ThisChainBalance>;
type BlockHashCount = ConstU32<250>;
}
impl pallet_utility::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PalletsOrigin = OriginCaller;
type WeightInfo = ();
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for TestRuntime {
type ReserveIdentifier = [u8; 8];
type AccountStore = System;
}
#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)]
impl pallet_transaction_payment::Config for TestRuntime {
type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter<Balances, ()>;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = IdentityFee<ThisChainBalance>;
type LengthToFee = ConstantMultiplier<ThisChainBalance, TransactionByteFee>;
type FeeMultiplierUpdate = pallet_transaction_payment::TargetedFeeAdjustment<
TestRuntime,
TargetBlockFullness,
AdjustmentVariable,
MinimumMultiplier,
MaximumMultiplier,
>;
type RuntimeEvent = RuntimeEvent;
}
impl pallet_bridge_grandpa::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = BridgedUnderlyingChain;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>;
type HeadersToKeep = ConstU32<8>;
type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
}
impl pallet_bridge_parachains::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgesGrandpaPalletInstance = ();
type ParasPalletName = BridgedParasPalletName;
type ParaStoredHeaderDataBuilder =
SingleParaStoredHeaderDataBuilder<BridgedUnderlyingParachain>;
type HeadsToKeep = ConstU32<8>;
type MaxParaHeadDataSize = ConstU32<1024>;
type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight<TestRuntime>;
}
impl pallet_bridge_messages::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = pallet_bridge_messages::weights::BridgeWeight<TestRuntime>;
type ActiveOutboundLanes = ActiveOutboundLanes;
type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane;
type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane;
type MaximalOutboundPayloadSize = FromThisChainMaximalOutboundPayloadSize<OnThisChainBridge>;
type OutboundPayload = FromThisChainMessagePayload;
type InboundPayload = FromBridgedChainMessagePayload;
type InboundRelayer = BridgedChainAccountId;
type DeliveryPayments = ();
type TargetHeaderChain = TargetHeaderChainAdapter<OnThisChainBridge>;
type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter<
TestRuntime,
(),
ConstU64<100_000>,
>;
type OnMessagesDelivered = ();
type SourceHeaderChain = SourceHeaderChainAdapter<OnThisChainBridge>;
type MessageDispatch = DummyMessageDispatch;
type BridgedChainId = BridgedChainId;
}
impl pallet_bridge_relayers::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type Reward = ThisChainBalance;
type PaymentProcedure = TestPaymentProcedure;
type StakeAndSlash = TestStakeAndSlash;
type WeightInfo = ();
}
/// Dummy message dispatcher.
pub struct DummyMessageDispatch;
impl DummyMessageDispatch {
pub fn deactivate() {
frame_support::storage::unhashed::put(&b"inactive"[..], &false);
}
}
impl MessageDispatch for DummyMessageDispatch {
type DispatchPayload = Vec<u8>;
type DispatchLevelResult = ();
fn is_active() -> bool {
frame_support::storage::unhashed::take::<bool>(&b"inactive"[..]) != Some(false)
}
fn dispatch_weight(_message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
Weight::zero()
}
fn dispatch(
_: DispatchMessage<Self::DispatchPayload>,
) -> MessageDispatchResult<Self::DispatchLevelResult> {
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
}
}
/// Bridge that is deployed on `ThisChain` and allows sending/receiving messages to/from
/// `BridgedChain`.
#[derive(Debug, PartialEq, Eq)]
pub struct OnThisChainBridge;
impl MessageBridge for OnThisChainBridge {
const BRIDGED_MESSAGES_PALLET_NAME: &'static str = "";
type ThisChain = ThisChain;
type BridgedChain = BridgedChain;
type BridgedHeaderChain = pallet_bridge_grandpa::GrandpaChainHeaders<TestRuntime, ()>;
}
/// Bridge that is deployed on `BridgedChain` and allows sending/receiving messages to/from
/// `ThisChain`.
#[derive(Debug, PartialEq, Eq)]
pub struct OnBridgedChainBridge;
impl MessageBridge for OnBridgedChainBridge {
const BRIDGED_MESSAGES_PALLET_NAME: &'static str = "";
type ThisChain = BridgedChain;
type BridgedChain = ThisChain;
type BridgedHeaderChain = ThisHeaderChain;
}
/// Dummy implementation of `HeaderChain` for `ThisChain` at the `BridgedChain`.
pub struct ThisHeaderChain;
impl HeaderChain<ThisUnderlyingChain> for ThisHeaderChain {
fn finalized_header_state_root(_hash: HashOf<ThisChain>) -> Option<HashOf<ThisChain>> {
unreachable!()
}
}
/// Call origin at `BridgedChain`.
#[derive(Clone, Debug)]
pub struct BridgedChainOrigin;
impl From<BridgedChainOrigin>
for Result<frame_system::RawOrigin<BridgedChainAccountId>, BridgedChainOrigin>
{
fn from(
_origin: BridgedChainOrigin,
) -> Result<frame_system::RawOrigin<BridgedChainAccountId>, BridgedChainOrigin> {
unreachable!()
}
}
/// 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 = sp_runtime::MultiSignature;
fn max_extrinsic_size() -> u32 {
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}
/// The chain where we are in tests.
pub struct ThisChain;
impl UnderlyingChainProvider for ThisChain {
type Chain = ThisUnderlyingChain;
}
impl ThisChainWithMessages for ThisChain {
type RuntimeOrigin = ThisChainCallOrigin;
}
impl BridgedChainWithMessages for ThisChain {}
/// Underlying chain of `BridgedChain`.
pub struct BridgedUnderlyingChain;
/// Some parachain under `BridgedChain` consensus.
pub struct BridgedUnderlyingParachain;
/// Runtime call of the `BridgedChain`.
#[derive(Decode, Encode)]
pub struct BridgedChainCall;
impl Chain for BridgedUnderlyingChain {
const ID: ChainId = *b"buch";
type BlockNumber = BridgedChainBlockNumber;
type Hash = BridgedChainHash;
type Hasher = BridgedChainHasher;
type Header = BridgedChainHeader;
type AccountId = BridgedChainAccountId;
type Balance = BridgedChainBalance;
type Nonce = u32;
type Signature = sp_runtime::MultiSignature;
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 Chain for BridgedUnderlyingParachain {
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 = sp_runtime::MultiSignature;
fn max_extrinsic_size() -> u32 {
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}
impl Parachain for BridgedUnderlyingParachain {
const PARACHAIN_ID: u32 = 42;
}
/// The other, bridged chain, used in tests.
pub struct BridgedChain;
impl UnderlyingChainProvider for BridgedChain {
type Chain = BridgedUnderlyingChain;
}
impl ThisChainWithMessages for BridgedChain {
type RuntimeOrigin = BridgedChainOrigin;
}
impl BridgedChainWithMessages for BridgedChain {}
/// Run test within test externalities.
pub fn run_test(test: impl FnOnce()) {
sp_io::TestExternalities::new(Default::default()).execute_with(test)
}
@@ -1,88 +0,0 @@
// 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 parachains finality module.
#![cfg(feature = "runtime-benchmarks")]
use crate::{
messages_benchmarking::insert_header_to_grandpa_pallet,
messages_generation::grow_trie_leaf_value,
};
use bp_parachains::parachain_head_storage_key_at_source;
use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
use bp_runtime::{record_all_trie_keys, StorageProofSize};
use codec::Encode;
use frame_support::traits::Get;
use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
use sp_std::prelude::*;
use sp_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_parachain_heads_proof<R, PI>(
parachains: &[ParaId],
parachain_head_size: u32,
size: StorageProofSize,
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>)
where
R: pallet_bridge_parachains::Config<PI>
+ pallet_bridge_grandpa::Config<R::BridgesGrandpaPalletInstance>,
PI: 'static,
<R as pallet_bridge_grandpa::Config<R::BridgesGrandpaPalletInstance>>::BridgedChain:
bp_runtime::Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash>,
{
let parachain_head = ParaHead(vec![0u8; parachain_head_size as usize]);
// insert all heads to the trie
let mut parachain_heads = Vec::with_capacity(parachains.len());
let mut storage_keys = Vec::with_capacity(parachains.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 parachain heads
for (i, parachain) in parachains.into_iter().enumerate() {
let storage_key =
parachain_head_storage_key_at_source(R::ParasPalletName::get(), *parachain);
let leaf_data = if i == 0 {
grow_trie_leaf_value(parachain_head.encode(), size)
} else {
parachain_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);
parachain_heads.push((*parachain, parachain_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 }, parachain_heads)
}
@@ -1,202 +0,0 @@
// 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 bp_messages::MessageNonce;
use frame_support::traits::Get;
use sp_runtime::transaction_validity::TransactionPriority;
// reexport everything from `integrity_tests` module
#[allow(unused_imports)]
pub use integrity_tests::*;
/// Compute priority boost for message delivery transaction that delivers
/// given number of messages.
pub fn compute_priority_boost<PriorityBoostPerMessage>(
messages: MessageNonce,
) -> TransactionPriority
where
PriorityBoostPerMessage: Get<TransactionPriority>,
{
// we don't want any boost for transaction with single message => minus one
PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1))
}
#[cfg(not(feature = "integrity-test"))]
mod integrity_tests {}
#[cfg(feature = "integrity-test")]
mod integrity_tests {
use super::compute_priority_boost;
use bp_messages::MessageNonce;
use bp_runtime::PreComputedSize;
use frame_support::{
dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo},
traits::Get,
};
use pallet_bridge_messages::WeightInfoExt;
use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::{
traits::{Dispatchable, UniqueSaturatedInto, Zero},
transaction_validity::TransactionPriority,
FixedPointOperand, SaturatedConversion, Saturating,
};
type BalanceOf<T> =
<<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<
T,
>>::Balance;
/// 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:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
PriorityBoostPerMessage: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
let priority_boost_per_message = PriorityBoostPerMessage::get();
let maximal_messages_in_delivery_transaction =
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
for messages in 1..=maximal_messages_in_delivery_transaction {
let base_priority = estimate_message_delivery_transaction_priority::<
Runtime,
MessagesInstance,
>(messages, Zero::zero());
let priority_boost = compute_priority_boost::<PriorityBoostPerMessage>(messages);
let priority_with_boost = base_priority + priority_boost;
let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into());
let priority_with_tip =
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(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 PriorityBoostPerMessage value ({}) must be fixed to: {}",
priority_boost_per_message,
compute_priority_boost_per_message::<Runtime, MessagesInstance>(
tip_boost_per_message
),
);
}
}
}
/// Compute priority boost that we give to message delivery transaction for additional message.
#[cfg(feature = "integrity-test")]
fn compute_priority_boost_per_message<Runtime, MessagesInstance>(
tip_boost_per_message: BalanceOf<Runtime>,
) -> TransactionPriority
where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// estimate priority of transaction that delivers one message and has large tip
let maximal_messages_in_delivery_transaction =
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
let small_with_tip_priority =
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(
1,
tip_boost_per_message
.saturating_mul(maximal_messages_in_delivery_transaction.saturated_into()),
);
// estimate priority of transaction that delivers maximal number of messages, but has no tip
let large_without_tip_priority = estimate_message_delivery_transaction_priority::<
Runtime,
MessagesInstance,
>(maximal_messages_in_delivery_transaction, Zero::zero());
small_with_tip_priority
.saturating_sub(large_without_tip_priority)
.saturating_div(maximal_messages_in_delivery_transaction - 1)
}
/// 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:
pallet_transaction_payment::Config + pallet_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::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::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::WeightInfo::receive_messages_proof_weight(
&PreComputedSize(transaction_size as _),
messages as _,
estimated_message_dispatch_weight.saturating_mul(messages),
);
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
transaction_size as _,
tip,
Zero::zero(),
)
}
}
File diff suppressed because it is too large Load Diff
@@ -1,30 +0,0 @@
[package]
name = "bp-asset-hub-rococo"
description = "Primitives of AssetHubRococo parachain 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
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
# Substrate Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
# Bridge Dependencies
bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false }
[features]
default = ["std"]
std = [
"bp-xcm-bridge-hub-router/std",
"codec/std",
"frame-support/std",
"scale-info/std",
]
@@ -1,48 +0,0 @@
// 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/>.
//! Module with configuration which reflects AssetHubRococo runtime setup.
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use scale_info::TypeInfo;
pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall;
/// `AssetHubRococo` Runtime `Call` enum.
///
/// The enum represents a subset of possible `Call`s we can send to `AssetHubRococo` chain.
/// Ideally this code would be auto-generated from metadata, because we want to
/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
///
/// All entries here (like pretty much in the entire file) must be kept in sync with
/// `AssetHubRococo` `construct_runtime`, so that we maintain SCALE-compatibility.
#[allow(clippy::large_enum_variant)]
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub enum Call {
/// `ToWestendXcmRouter` bridge pallet.
#[codec(index = 45)]
ToWestendXcmRouter(XcmBridgeHubRouterCall),
}
frame_support::parameter_types! {
/// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`.
pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144);
}
/// Identifier of AssetHubRococo in the Rococo relay chain.
pub const ASSET_HUB_ROCOCO_PARACHAIN_ID: u32 = 1000;
@@ -1,30 +0,0 @@
[package]
name = "bp-asset-hub-westend"
description = "Primitives of AssetHubWestend parachain runtime."
version = "0.3.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
# Substrate Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
# Bridge Dependencies
bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false }
[features]
default = ["std"]
std = [
"bp-xcm-bridge-hub-router/std",
"codec/std",
"frame-support/std",
"scale-info/std",
]
@@ -1,48 +0,0 @@
// 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/>.
//! Module with configuration which reflects AssetHubWestend runtime setup.
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use scale_info::TypeInfo;
pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall;
/// `AssetHubWestend` Runtime `Call` enum.
///
/// The enum represents a subset of possible `Call`s we can send to `AssetHubWestend` chain.
/// Ideally this code would be auto-generated from metadata, because we want to
/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
///
/// All entries here (like pretty much in the entire file) must be kept in sync with
/// `AssetHubWestend` `construct_runtime`, so that we maintain SCALE-compatibility.
#[allow(clippy::large_enum_variant)]
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub enum Call {
/// `ToRococoXcmRouter` bridge pallet.
#[codec(index = 34)]
ToRococoXcmRouter(XcmBridgeHubRouterCall),
}
frame_support::parameter_types! {
/// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`.
pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144);
}
/// Identifier of AssetHubWestend in the Westend relay chain.
pub const ASSET_HUB_WESTEND_PARACHAIN_ID: u32 = 1000;
@@ -1,41 +0,0 @@
[package]
name = "bp-bridge-hub-cumulus"
description = "Primitives for BridgeHub parachain 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
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Based Dependencies
frame-system = { path = "../../../substrate/frame/system", default-features = false }
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
# Polkadot Dependencies
polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false }
[features]
default = ["std"]
std = [
"bp-messages/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"frame-support/std",
"frame-system/std",
"polkadot-primitives/std",
"sp-api/std",
"sp-std/std",
]
@@ -1,170 +0,0 @@
// 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 Cumulus-based bridge hubs.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_polkadot_core::{
AccountId, AccountInfoStorageMapKeyProvider, AccountPublic, Balance, BlockNumber, Hash, Hasher,
Hashing, Header, Nonce, Perbill, Signature, SignedBlock, UncheckedExtrinsic,
EXTRA_STORAGE_PROOF_SIZE, TX_EXTRA_BYTES,
};
use bp_messages::*;
use bp_polkadot_core::SuffixedCommonSignedExtension;
use bp_runtime::extensions::{
BridgeRejectObsoleteHeadersAndMessages, RefundBridgedParachainMessagesSchema,
};
use frame_support::{
dispatch::DispatchClass,
parameter_types,
sp_runtime::{MultiAddress, MultiSigner},
weights::constants,
};
use frame_system::limits;
use sp_std::time::Duration;
/// Average block interval in Cumulus-based parachains.
///
/// Corresponds to the `MILLISECS_PER_BLOCK` from `parachains_common` crate.
pub const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(12);
/// All cumulus bridge hubs allow normal extrinsics to fill block up to 75 percent.
///
/// This is a copy-paste from the cumulus repo's `parachains-common` crate.
pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
/// All cumulus bridge hubs chains allow for 0.5 seconds of compute with a 6-second average block
/// time.
///
/// This is a copy-paste from the cumulus repo's `parachains-common` crate.
const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(constants::WEIGHT_REF_TIME_PER_SECOND, 0)
.saturating_div(2)
.set_proof_size(polkadot_primitives::MAX_POV_SIZE as u64);
/// We allow for 2 seconds of compute with a 6 second average block.
const MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING: Weight = Weight::from_parts(
constants::WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
polkadot_primitives::MAX_POV_SIZE as u64,
);
/// All cumulus bridge hubs assume that about 5 percent of the block weight is consumed by
/// `on_initialize` handlers. This is used to limit the maximal weight of a single extrinsic.
///
/// This is a copy-paste from the cumulus repo's `parachains-common` crate.
pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(5);
parameter_types! {
/// Size limit of the Cumulus-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 Cumulus-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 Cumulus-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 Cumulus parachains. 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 pallet, 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 Cumulus-based parachains.
/// 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 Cumulus-based parachains.
/// 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 SignedExtension = SuffixedCommonSignedExtension<(
BridgeRejectObsoleteHeadersAndMessages,
RefundBridgedParachainMessagesSchema,
)>;
@@ -1,37 +0,0 @@
[package]
name = "bp-bridge-hub-kusama"
description = "Primitives of BridgeHubKusama parachain runtime."
version = "0.6.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-bridge-hub-cumulus = { path = "../chain-bridge-hub-cumulus", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-bridge-hub-cumulus/std",
"bp-messages/std",
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -1,93 +0,0 @@
// 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/>.
//! Module with configuration which reflects BridgeHubKusama runtime setup (AccountId, Headers,
//! Hashes...)
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_bridge_hub_cumulus::*;
use bp_messages::*;
use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, ChainId, Parachain,
};
use frame_support::{
dispatch::DispatchClass,
sp_runtime::{MultiAddress, MultiSigner},
};
use sp_runtime::RuntimeDebug;
/// BridgeHubKusama parachain.
#[derive(RuntimeDebug)]
pub struct BridgeHubKusama;
impl Chain for BridgeHubKusama {
const ID: ChainId = *b"bhks";
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hasher = Hasher;
type Header = Header;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = Nonce;
type Signature = Signature;
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 Parachain for BridgeHubKusama {
const PARACHAIN_ID: u32 = BRIDGE_HUB_KUSAMA_PARACHAIN_ID;
}
impl ChainWithMessages for BridgeHubKusama {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
WITH_BRIDGE_HUB_KUSAMA_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;
}
/// 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, ()>;
/// Identifier of BridgeHubKusama in the Kusama relay chain.
pub const BRIDGE_HUB_KUSAMA_PARACHAIN_ID: u32 = 1002;
/// Name of the With-BridgeHubKusama messages pallet instance that is deployed at bridged chains.
pub const WITH_BRIDGE_HUB_KUSAMA_MESSAGES_PALLET_NAME: &str = "BridgeKusamaMessages";
/// Name of the With-BridgeHubKusama bridge-relayers pallet instance that is deployed at bridged
/// chains.
pub const WITH_BRIDGE_HUB_KUSAMA_RELAYERS_PALLET_NAME: &str = "BridgeRelayers";
decl_bridge_finality_runtime_apis!(bridge_hub_kusama);
decl_bridge_messages_runtime_apis!(bridge_hub_kusama);
@@ -1,38 +0,0 @@
[package]
name = "bp-bridge-hub-polkadot"
description = "Primitives of BridgeHubPolkadot parachain runtime."
version = "0.6.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-bridge-hub-cumulus = { path = "../chain-bridge-hub-cumulus", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-bridge-hub-cumulus/std",
"bp-messages/std",
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -1,85 +0,0 @@
// 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/>.
//! Module with configuration which reflects BridgeHubPolkadot runtime setup
//! (AccountId, Headers, Hashes...)
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_bridge_hub_cumulus::*;
use bp_messages::*;
use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, ChainId, Parachain,
};
use frame_support::dispatch::DispatchClass;
use sp_runtime::RuntimeDebug;
/// BridgeHubPolkadot parachain.
#[derive(RuntimeDebug)]
pub struct BridgeHubPolkadot;
impl Chain for BridgeHubPolkadot {
const ID: ChainId = *b"bhpd";
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hasher = Hasher;
type Header = Header;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = Nonce;
type Signature = Signature;
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 Parachain for BridgeHubPolkadot {
const PARACHAIN_ID: u32 = BRIDGE_HUB_POLKADOT_PARACHAIN_ID;
}
impl ChainWithMessages for BridgeHubPolkadot {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
WITH_BRIDGE_HUB_POLKADOT_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;
}
/// Identifier of BridgeHubPolkadot in the Polkadot relay chain.
pub const BRIDGE_HUB_POLKADOT_PARACHAIN_ID: u32 = 1002;
/// Name of the With-BridgeHubPolkadot messages pallet instance that is deployed at bridged chains.
pub const WITH_BRIDGE_HUB_POLKADOT_MESSAGES_PALLET_NAME: &str = "BridgePolkadotMessages";
/// Name of the With-BridgeHubPolkadot bridge-relayers pallet instance that is deployed at bridged
/// chains.
pub const WITH_BRIDGE_HUB_POLKADOT_RELAYERS_PALLET_NAME: &str = "BridgeRelayers";
decl_bridge_finality_runtime_apis!(bridge_hub_polkadot);
decl_bridge_messages_runtime_apis!(bridge_hub_polkadot);
@@ -1,37 +0,0 @@
[package]
name = "bp-bridge-hub-rococo"
description = "Primitives of BridgeHubRococo parachain runtime."
version = "0.7.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-bridge-hub-cumulus = { path = "../chain-bridge-hub-cumulus", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-bridge-hub-cumulus/std",
"bp-messages/std",
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -1,111 +0,0 @@
// 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/>.
//! Module with configuration which reflects BridgeHubRococo runtime setup (AccountId, Headers,
//! Hashes...)
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_bridge_hub_cumulus::*;
use bp_messages::*;
use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, ChainId, Parachain,
};
use frame_support::dispatch::DispatchClass;
use sp_runtime::{MultiAddress, MultiSigner, RuntimeDebug};
/// BridgeHubRococo parachain.
#[derive(RuntimeDebug)]
pub struct BridgeHubRococo;
impl Chain for BridgeHubRococo {
const ID: ChainId = *b"bhro";
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hasher = Hasher;
type Header = Header;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = Nonce;
type Signature = Signature;
fn max_extrinsic_size() -> u32 {
*BlockLength::get().max.get(DispatchClass::Normal)
}
fn max_extrinsic_weight() -> Weight {
BlockWeightsForAsyncBacking::get()
.get(DispatchClass::Normal)
.max_extrinsic
.unwrap_or(Weight::MAX)
}
}
impl Parachain for BridgeHubRococo {
const PARACHAIN_ID: u32 = BRIDGE_HUB_ROCOCO_PARACHAIN_ID;
}
impl ChainWithMessages for BridgeHubRococo {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
WITH_BRIDGE_HUB_ROCOCO_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;
}
/// 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, ()>;
/// Identifier of BridgeHubRococo in the Rococo relay chain.
pub const BRIDGE_HUB_ROCOCO_PARACHAIN_ID: u32 = 1013;
/// Name of the With-BridgeHubRococo messages pallet instance that is deployed at bridged chains.
pub const WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME: &str = "BridgeRococoMessages";
/// Name of the With-BridgeHubRococo bridge-relayers pallet instance that is deployed at bridged
/// chains.
pub const WITH_BRIDGE_HUB_ROCOCO_RELAYERS_PALLET_NAME: &str = "BridgeRelayers";
/// Pallet index of `BridgeWestendMessages: pallet_bridge_messages::<Instance3>`.
pub const WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX: u8 = 51;
/// Pallet index of `BridgePolkadotBulletinMessages: pallet_bridge_messages::<Instance4>`.
pub const WITH_BRIDGE_ROCOCO_TO_BULLETIN_MESSAGES_PALLET_INDEX: u8 = 61;
decl_bridge_finality_runtime_apis!(bridge_hub_rococo);
decl_bridge_messages_runtime_apis!(bridge_hub_rococo);
frame_support::parameter_types! {
/// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Rococo
/// BridgeHub.
/// (initially was calculated by test `BridgeHubRococo::can_calculate_weight_for_paid_export_message_with_reserve_transfer` + `33%`)
pub const BridgeHubRococoBaseXcmFeeInRocs: u128 = 59_034_266;
/// Transaction fee that is paid at the Rococo BridgeHub for delivering single inbound message.
/// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`)
pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 5_651_581_649;
/// Transaction fee that is paid at the Rococo BridgeHub for delivering single outbound message confirmation.
/// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`)
pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_901_781;
}
@@ -1,38 +0,0 @@
[package]
name = "bp-bridge-hub-westend"
description = "Primitives of BridgeHubWestend parachain runtime."
version = "0.3.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-bridge-hub-cumulus = { path = "../chain-bridge-hub-cumulus", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-bridge-hub-cumulus/std",
"bp-messages/std",
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -1,102 +0,0 @@
// 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/>.
//! Module with configuration which reflects BridgeHubWestend runtime setup
//! (AccountId, Headers, Hashes...)
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_bridge_hub_cumulus::*;
use bp_messages::*;
use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, ChainId, Parachain,
};
use frame_support::dispatch::DispatchClass;
use sp_runtime::RuntimeDebug;
/// BridgeHubWestend parachain.
#[derive(RuntimeDebug)]
pub struct BridgeHubWestend;
impl Chain for BridgeHubWestend {
const ID: ChainId = *b"bhwd";
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hasher = Hasher;
type Header = Header;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = Nonce;
type Signature = Signature;
fn max_extrinsic_size() -> u32 {
*BlockLength::get().max.get(DispatchClass::Normal)
}
fn max_extrinsic_weight() -> Weight {
BlockWeightsForAsyncBacking::get()
.get(DispatchClass::Normal)
.max_extrinsic
.unwrap_or(Weight::MAX)
}
}
impl Parachain for BridgeHubWestend {
const PARACHAIN_ID: u32 = BRIDGE_HUB_WESTEND_PARACHAIN_ID;
}
impl ChainWithMessages for BridgeHubWestend {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
WITH_BRIDGE_HUB_WESTEND_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;
}
/// Identifier of BridgeHubWestend in the Westend relay chain.
pub const BRIDGE_HUB_WESTEND_PARACHAIN_ID: u32 = 1002;
/// Name of the With-BridgeHubWestend messages pallet instance that is deployed at bridged chains.
pub const WITH_BRIDGE_HUB_WESTEND_MESSAGES_PALLET_NAME: &str = "BridgeWestendMessages";
/// Name of the With-BridgeHubWestend bridge-relayers pallet instance that is deployed at bridged
/// chains.
pub const WITH_BRIDGE_HUB_WESTEND_RELAYERS_PALLET_NAME: &str = "BridgeRelayers";
/// Pallet index of `BridgeRococoMessages: pallet_bridge_messages::<Instance1>`.
pub const WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX: u8 = 44;
decl_bridge_finality_runtime_apis!(bridge_hub_westend);
decl_bridge_messages_runtime_apis!(bridge_hub_westend);
frame_support::parameter_types! {
/// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Westend
/// BridgeHub.
/// (initially was calculated by test `BridgeHubWestend::can_calculate_weight_for_paid_export_message_with_reserve_transfer` + `33%`)
pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 17_756_830_000;
/// Transaction fee that is paid at the Westend BridgeHub for delivering single inbound message.
/// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`)
pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 1_695_489_961_344;
/// Transaction fee that is paid at the Westend BridgeHub for delivering single outbound message confirmation.
/// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`)
pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 1_618_309_961_344;
}
-36
View File
@@ -1,36 +0,0 @@
[package]
name = "bp-kusama"
description = "Primitives of Kusama runtime."
version = "0.5.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-std/std",
]
-78
View File
@@ -1,78 +0,0 @@
// 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 the Kusama chain.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain, ChainId};
use frame_support::weights::Weight;
/// Kusama Chain
pub struct Kusama;
impl Chain for Kusama {
const ID: ChainId = *b"ksma";
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hasher = Hasher;
type Header = Header;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = Nonce;
type Signature = Signature;
fn max_extrinsic_size() -> u32 {
max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
max_extrinsic_weight()
}
}
impl ChainWithGrandpa for Kusama {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_KUSAMA_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;
}
// The SignedExtension used by Kusama.
pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
/// Name of the parachains pallet in the Kusama runtime.
pub const PARAS_PALLET_NAME: &str = "Paras";
/// Name of the With-Kusama GRANDPA pallet instance that is deployed at bridged chains.
pub const WITH_KUSAMA_GRANDPA_PALLET_NAME: &str = "BridgeKusamaGrandpa";
/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot
/// parachains.
///
/// It includes the block number and state root, so it shall be near 40 bytes, but let's have some
/// reserve.
pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
decl_bridge_finality_runtime_apis!(kusama, grandpa);
@@ -1,46 +0,0 @@
[package]
name = "bp-polkadot-bulletin"
description = "Primitives of Polkadot 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
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
# Bridge Dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../substrate/frame/system", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-messages/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
"sp-api/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -1,227 +0,0 @@
// 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/>.
//! Polkadot Bulletin Chain primitives.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
use bp_header_chain::ChainWithGrandpa;
use bp_messages::{ChainWithMessages, MessageNonce};
use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis,
extensions::{
CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion,
CheckWeight, GenericSignedExtension, GenericSignedExtensionSchema,
},
Chain, ChainId, TransactionEra,
};
use codec::{Decode, Encode};
use frame_support::{
dispatch::DispatchClass,
parameter_types,
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
};
use frame_system::limits;
use scale_info::TypeInfo;
use sp_runtime::{traits::DispatchInfoOf, transaction_validity::TransactionValidityError, Perbill};
// This chain reuses most of Polkadot primitives.
pub use bp_polkadot_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 Polkadot Bulletin chain.
pub const MAX_AUTHORITIES_COUNT: u32 = 100;
/// Name of the With-Polkadot Bulletin chain GRANDPA pallet instance that is deployed at bridged
/// chains.
pub const WITH_POLKADOT_BULLETIN_GRANDPA_PALLET_NAME: &str = "BridgePolkadotBulletinGrandpa";
/// Name of the With-Polkadot Bulletin chain messages pallet instance that is deployed at bridged
/// chains.
pub const WITH_POLKADOT_BULLETIN_MESSAGES_PALLET_NAME: &str = "BridgePolkadotBulletinMessages";
// 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 Cumulus parachains. They are limited
// by the maximal transaction weight/size. Since block limits at Bulletin Chain are larger than
// at the Cumulus Bridge Hubs, we could reuse the same values.
/// Maximal number of unrewarded relayer entries at inbound lane for Cumulus-based parachains.
pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024;
/// Maximal number of unconfirmed messages at inbound lane for Cumulus-based parachains.
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 = GenericSignedExtensionSchema<(), ()>;
/// Signed extension schema, used by Polkadot Bulletin.
pub type SignedExtensionSchema = GenericSignedExtension<(
(
CheckNonZeroSender,
CheckSpecVersion,
CheckTxVersion,
CheckGenesis<Hash>,
CheckEra<Hash>,
CheckNonce<Nonce>,
CheckWeight,
),
ValidateSigned,
)>;
/// Signed extension, used by Polkadot Bulletin.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub struct SignedExtension(SignedExtensionSchema);
impl sp_runtime::traits::SignedExtension for SignedExtension {
const IDENTIFIER: &'static str = "Not needed.";
type AccountId = ();
type Call = ();
type AdditionalSigned =
<SignedExtensionSchema as sp_runtime::traits::SignedExtension>::AdditionalSigned;
type Pre = ();
fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
self.0.additional_signed()
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
impl SignedExtension {
/// 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(GenericSignedExtension::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 Polkadot Bulletin chain.
pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio(
10 * 1024 * 1024,
NORMAL_DISPATCH_RATIO,
);
}
/// Polkadot Bulletin Chain declaration.
pub struct PolkadotBulletin;
impl Chain for PolkadotBulletin {
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;
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 PolkadotBulletin {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_POLKADOT_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 PolkadotBulletin {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
WITH_POLKADOT_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!(polkadot_bulletin, grandpa);
decl_bridge_messages_runtime_apis!(polkadot_bulletin);
-36
View File
@@ -1,36 +0,0 @@
[package]
name = "bp-polkadot"
description = "Primitives of Polkadot runtime."
version = "0.5.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-std/std",
]
-80
View File
@@ -1,80 +0,0 @@
// 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 the Polkadot chain.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{
decl_bridge_finality_runtime_apis, extensions::PrevalidateAttests, Chain, ChainId,
};
use frame_support::weights::Weight;
/// Polkadot Chain
pub struct Polkadot;
impl Chain for Polkadot {
const ID: ChainId = *b"pdot";
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hasher = Hasher;
type Header = Header;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = Nonce;
type Signature = Signature;
fn max_extrinsic_size() -> u32 {
max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
max_extrinsic_weight()
}
}
impl ChainWithGrandpa for Polkadot {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_POLKADOT_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;
}
/// The SignedExtension used by Polkadot.
pub type SignedExtension = SuffixedCommonSignedExtension<PrevalidateAttests>;
/// Name of the parachains pallet in the Polkadot runtime.
pub const PARAS_PALLET_NAME: &str = "Paras";
/// Name of the With-Polkadot GRANDPA pallet instance that is deployed at bridged chains.
pub const WITH_POLKADOT_GRANDPA_PALLET_NAME: &str = "BridgePolkadotGrandpa";
/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot
/// parachains.
///
/// It includes the block number and state root, so it shall be near 40 bytes, but let's have some
/// reserve.
pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
decl_bridge_finality_runtime_apis!(polkadot, grandpa);
-36
View File
@@ -1,36 +0,0 @@
[package]
name = "bp-rococo"
description = "Primitives of Rococo runtime."
version = "0.6.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-std/std",
]
-78
View File
@@ -1,78 +0,0 @@
// 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 the Rococo chain.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain, ChainId};
use frame_support::weights::Weight;
/// Rococo Chain
pub struct Rococo;
impl Chain for Rococo {
const ID: ChainId = *b"roco";
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hasher = Hasher;
type Header = Header;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = Nonce;
type Signature = Signature;
fn max_extrinsic_size() -> u32 {
max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
max_extrinsic_weight()
}
}
impl ChainWithGrandpa for Rococo {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_ROCOCO_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;
}
// The SignedExtension used by Rococo.
pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
/// Name of the parachains pallet in the Rococo runtime.
pub const PARAS_PALLET_NAME: &str = "Paras";
/// Name of the With-Rococo GRANDPA pallet instance that is deployed at bridged chains.
pub const WITH_ROCOCO_GRANDPA_PALLET_NAME: &str = "BridgeRococoGrandpa";
/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Rococo
/// parachains.
///
/// It includes the block number and state root, so it shall be near 40 bytes, but let's have some
/// reserve.
pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
decl_bridge_finality_runtime_apis!(rococo, grandpa);
-36
View File
@@ -1,36 +0,0 @@
[package]
name = "bp-westend"
description = "Primitives of Westend runtime."
version = "0.3.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Bridge Dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Based Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-api = { path = "../../../substrate/primitives/api", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-std/std",
]
-78
View File
@@ -1,78 +0,0 @@
// 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 the Westend chain.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain, ChainId};
use frame_support::weights::Weight;
/// Westend Chain
pub struct Westend;
impl Chain for Westend {
const ID: ChainId = *b"wend";
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hasher = Hasher;
type Header = Header;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = Nonce;
type Signature = Signature;
fn max_extrinsic_size() -> u32 {
max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
max_extrinsic_weight()
}
}
impl ChainWithGrandpa for Westend {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_WESTEND_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;
}
// The SignedExtension used by Westend.
pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
/// Name of the parachains pallet in the Rococo runtime.
pub const PARAS_PALLET_NAME: &str = "Paras";
/// Name of the With-Westend GRANDPA pallet instance that is deployed at bridged chains.
pub const WITH_WESTEND_GRANDPA_PALLET_NAME: &str = "BridgeWestendGrandpa";
/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Westend
/// parachains.
///
/// It includes the block number and state root, so it shall be near 40 bytes, but let's have some
/// reserve.
pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
decl_bridge_finality_runtime_apis!(westend, grandpa);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

-85
View File
@@ -1,85 +0,0 @@
<!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>
-47
View File
@@ -1,47 +0,0 @@
<!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>
-184
View File
@@ -1,184 +0,0 @@
# 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 Rococo <> Westend (Kusama <> Polkadot) bridge, please refer to the [Polkadot <>
Kusama Bridge](./polkadot-kusama-bridge-overview.md).
## Purpose
This repo contains all components required to build a trustless connection between standalone Substrate chains, that are
using GRANDPA finality, their parachains or any combination of those. On top of this connection, we offer a messaging
pallet that provides means to organize messages exchange.
On top of that layered infrastructure, anyone may build their own bridge applications - e.g. [XCM
messaging](./polkadot-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 pallet needs to be deployed at both, source and target chains.
### Bridge GRANDPA Finality Pallet
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 pallet 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 pallet and relayer pays no fee for such headers submission.
The pallet 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: [pallet level documentation and code](../modules/grandpa/).
### Bridge Parachains Finality Pallet
Parachains are not supposed to have their own finality, so we can't use bridge GRANDPA pallet to verify their finality
proofs. Instead, they rely on their relay chain finality. The parachain header is considered final, when it is accepted
by the [`paras`
pallet](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 parachains pallet accepts storage proof of one or several parachain heads, inserted to the
[`Heads`](https://github.com/paritytech/polkadot/blob/1a034bd6de0e76721d19aed02a538bcef0787260/runtime/parachains/src/paras/mod.rs#L642)
map of the [`paras`
pallet](https://github.com/paritytech/polkadot/tree/1a034bd6de0e76721d19aed02a538bcef0787260/runtime/parachains/src/paras).
To verify this storage proof, the pallet uses relay chain header, imported earlier by the bridge GRANDPA pallet.
The pallet may track multiple parachains at once and those parachains may use different primitives. So the parachain
header decoding never happens at the pallet level. For maintaining the headers order, the pallet uses relay chain header
number.
More: [pallet level documentation and code](../modules/parachains/).
### Bridge Messages Pallet
The pallet 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 pallet 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 pallet 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 pallet:
- the message itself may mean anything, the pallet doesn't care about its content;
- the message dispatch happens during delivery, but it is decoupled from the pallet code;
- the messages proof and messages delivery proof are verified outside of the pallet;
- the relayers incentivization scheme is defined outside of the pallet.
Outside of the messaging pallet, 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 pallet, 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 [Polkadot <> Kusama Bridge](./polkadot-kusama-bridge-overview.md) document.
More: [pallet level documentation and code](../modules/messages/).
### Bridge Relayers Pallet
The pallet 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 pallet.
More: [pallet 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 Pallet, 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 pallet will be unable to move
forward.
More: [GRANDPA Finality Relay Sequence Diagram](./grandpa-finality-relay.html), [pallet level documentation and
code](../relays/finality/).
### Parachains Finality Relay
The relay connects to the source _relay_ chain and the target chain nodes. It doesn't need to connect to the tracked
parachain 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`
pallet](https://github.com/paritytech/polkadot/tree/1a034bd6de0e76721d19aed02a538bcef0787260/runtime/parachains/src/paras)
in source chain, and compares the value with the best parachain head, stored in the bridge parachains pallet at the
target chain. If new parachain 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 pallet to be deployed nearby), the parachains 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: [Parachains Finality Relay Sequence Diagram](./parachains-finality-relay.html), [code](../relays/parachains/).
### 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 pallet 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 pallet,
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+parachains finality relays, depending on the type of
connected chain.
More: [Messages Relay Sequence Diagram](./messages-relay.html), [pallet 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 parachain 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 parachain 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-substrate/src/cli/relay_headers_and_messages/).
-78
View File
@@ -1,78 +0,0 @@
<!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>
@@ -1,55 +0,0 @@
<!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>
@@ -1,129 +0,0 @@
# Polkadot <> 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 Polkadot. 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](./polkadot-kusama-bridge.html).
## Bridge Hubs
All operations at relay chain are expensive. Ideally all non-mandatory transactions must happen on parachains. That's
why we are planning to have two parachains - Polkadot Bridge Hub under Polkadot 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 DOT at Polkadot 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 Parachains
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 parachains 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 parachains.
Our first planned bridge will connect the Polkadot and Kusama Asset Hubs. A bridge between those two parachains would
allow Asset Hub Polkadot accounts to hold wrapped KSM tokens and Asset Hub Kusama accounts to hold wrapped DOT tokens.
For that bridge (pair of parachains under different consensus systems) we'll be using the lane 00000000. Later, when
other parachains 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/Polkadot GRANDPA
justifications to the bridge hubs at the other side. It'll also relay finalized Kusama Bridge Hub and Polkadot 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 parachain finality transactions. The relayer will pay the full cost for
submitting parachain 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/substrate/blob/891d6a5c870ab88521183facafc811a203bb6541/frame/utility/src/lib.rs#L326)
transaction. Of course, the calls inside the batch must be linked - e.g. the submitted parachain head must be used to
prove messages. Relay header must be used to prove parachain 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 parachains using the bridge, use sovereign accounts on both sides of the bridge
to cover relayer rewards.
Bridged Parachains will have sovereign accounts at bridge hubs. For example, the Kusama Asset Hub will have an account
at the Polkadot Bridge Hub. The Polkadot 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 parachains, 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 Polkadot Parachain.
### 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
[RefundRelayerForMessagesFromParachain](../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 parachain 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/substrate/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.
-67
View File
@@ -1,67 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Polkadot &lt;&gt; Kusama Bridge</title>
</head>
<body>
<h1>Polkadot &lt;&gt; 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&lt;===&gt;|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>
-343
View File
@@ -1,343 +0,0 @@
# 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 -> Polkadot 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 Polkadot 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/paritytech/polkadot-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/paritytech/parity-bridges-common/issues/2486).
</details>
## Prerequisites
Let's focus on the bridge between Polkadot 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 Polkadot relay chain;
- an address of ws/wss RPC endpoint of the Kusama Bridge Hub chain;
- an address of ws/wss RPC endpoint of the Polkadot Bridge Hub chain;
- an account on Kusama Bridge Hub;
- an account on Polkadot 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(Parachain(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 DOT](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 Polkadot 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 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 | Your 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 Polkadot JS apps to submit an extrinsic. If you want priority boost for your transactions
on the Kusama Bridge Hub, open the
[Polkadot JS Apps](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fkusama-bridge-hub-rpc.polkadot.io#/extrinsics)
and submit the `register` extrinsic from the `bridgeRelayers` pallet:
![Register Extrinsic](./bridge-relayers-register.png)
To deregister, submit the simple `deregister` extrinsic when registration is expired:
![Deregister Extrinsic](./bridge-relayers-deregister.png)
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 Polkadot <> Kusama
messages (as of today):
- to deliver a single Polkadot -> Kusama message, you would need to pay around `0.06 KSM` at Kusama
Bridge Hub and around `1.62 DOT` at Polkadot Bridge Hub;
- to deliver a single Kusama -> Polkadot message, you would need to pay around `1.70 DOT` at Polkadot
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 pallet that tracks all rewards.
To check your rewards at the Kusama Bridge Hub, go to the
[Polkadot JS Apps](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fkusama-bridge-hub-rpc.polkadot.io#/chainstate)
targeting Kusama Bridge Hub, select the `bridgeRelayers` pallet, choose `relayerRewards` map and
your relayer account. Then:
- set the `laneId` to `0x00000001`
- set the `bridgedChainId` to `bhpd`;
- check the 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
[Polkadot JS Apps](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fkusama-bridge-hub-rpc.polkadot.io#/extrinsics)
and submit the following transaction (make sure to change `owner` before):
![Claim Rewards Extrinsic](./bridge-relayers-claim-rewards.png)
To claim rewards on Polkadot 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 Rococo <> Westend 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 Rococo <> Westend 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-substrate-relay>
export ROCOCO_HOST=<rococo-ws-rpc-host-here>
export ROCOCO_PORT=<rococo-ws-rpc-port-here>
# or set it to '--rococo-secure' if wss is used above
export ROCOCO_IS_SECURE=
export BRIDGE_HUB_ROCOCO_HOST=<bridge-hub-rococo-ws-rpc-host-here>
export BRIDGE_HUB_ROCOCO_PORT=<bridge-hub-rococo-ws-rpc-port-here>
# or set it to '--bridge-hub-rococo-secure' if wss is used above
export BRIDGE_HUB_ROCOCO_IS_SECURE=
export BRIDGE_HUB_ROCOCO_KEY_FILE=<absolute-path-to-file-with-account-key-at-bridge-hub-rococo>
export WESTEND_HOST=<westend-wss-rpc-host-here>
export WESTEND_PORT=<westend-wss-rpc-port-here>
# or set it to '--westend-secure' if wss is used above
export WESTEND_IS_SECURE=
export BRIDGE_HUB_WESTEND_HOST=<bridge-hub-westend-ws-rpc-host-here>
export BRIDGE_HUB_WESTEND_PORT=<bridge-hub-westend-ws-rpc-port-here>
# or set it to '--bridge-hub-westend-secure ' if wss is used above
export BRIDGE_HUB_WESTEND_IS_SECURE=
export BRIDGE_HUB_WESTEND_KEY_FILE=<absolute-path-to-file-with-account-key-at-bridge-hub-westend>
# 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_ROCOCO_KEY_FILE:/bhr.key \
-v $BRIDGE_HUB_WESTEND_KEY_FILE:/bhw.key \
$DOCKER_IMAGE \
relay-headers-and-messages bridge-hub-rococo-bridge-hub-westend \
--rococo-host $ROCOCO_HOST \
--rococo-port $ROCOCO_PORT \
$ROCOCO_IS_SECURE \
--rococo-version-mode Auto \
--bridge-hub-rococo-host $BRIDGE_HUB_ROCOCO_HOST \
--bridge-hub-rococo-port $BRIDGE_HUB_ROCOCO_PORT \
$BRIDGE_HUB_ROCOCO_IS_SECURE \
--bridge-hub-rococo-version-mode Auto \
--bridge-hub-rococo-signer-file /bhr.key \
--bridge-hub-rococo-transactions-mortality 16 \
--westend-host $WESTEND_HOST \
--westend-port $WESTEND_PORT \
$WESTEND_IS_SECURE \
--westend-version-mode Auto \
--bridge-hub-westend-host $BRIDGE_HUB_WESTEND_HOST \
--bridge-hub-westend-port $BRIDGE_HUB_WESTEND_PORT \
$BRIDGE_HUB_WESTEND_IS_SECURE \
--bridge-hub-westend-version-mode Auto \
--bridge-hub-westend-signer-file /bhw.key \
--bridge-hub-westend-transactions-mortality 16 \
--lane 00000002
```
### Starting your Polkadot <> 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-rococo-bridge-hub-westend \
--prometheus-host 0.0.0.0 \ # tell `substrate-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://wiki.polkadot.network/docs/maintain-guides-how-to-monitor-your-node)
guide from Polkadot wiki.
We have our own set of Grafana dashboards and alerts. You may use them for inspiration.
Please find them in this folder:
- for Rococo <> Westend bridge: [rococo-westend](https://github.com/paritytech/parity-bridges-common/tree/master/deployments/bridges/rococo-westend).
- for Polkadot <> Kusama bridge: *work in progress, coming soon*
-72
View File
@@ -1,72 +0,0 @@
[package]
name = "pallet-bridge-grandpa"
version = "0.7.0"
description = "Module implementing GRANDPA on-chain light client used for bridging consensus of substrate-based chains."
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
finality-grandpa = { version = "0.16.2", default-features = false }
log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
# Bridge Dependencies
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
# Substrate Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../substrate/frame/system", default-features = false }
sp-consensus-grandpa = { path = "../../../substrate/primitives/consensus/grandpa", default-features = false, features = ["serde"] }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, features = ["serde"] }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
sp-trie = { path = "../../../substrate/primitives/trie", default-features = false }
# Optional Benchmarking Dependencies
bp-test-utils = { path = "../../primitives/test-utils", default-features = false, optional = true }
frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true }
[dev-dependencies]
sp-core = { path = "../../../substrate/primitives/core" }
sp-io = { path = "../../../substrate/primitives/io" }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-runtime/std",
"bp-test-utils/std",
"codec/std",
"finality-grandpa/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-consensus-grandpa/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
]
runtime-benchmarks = [
"bp-test-utils",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
-101
View File
@@ -1,101 +0,0 @@
# Bridge GRANDPA Pallet
The bridge GRANDPA pallet 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 pallet
a basic pallet of all bridges with Substrate-based chains. It is used by all bridge types (bridge between
standalone chains, between parachains and any combination of those) and is used by other bridge pallets.
It is used by the parachains light client (bridge parachains pallet) and by messages pallet.
## A Brief Introduction into GRANDPA Finality
You can find detailed information on GRANDPA, by exploring its [repository](https://github.com/paritytech/finality-grandpa).
Here is the minimal required GRANDPA information to understand how pallet works.
Any Substrate 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) Substrate-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 pallet. As the name says, the light client need to import all such headers
to be able to operate properly.
## Pallet Operations
The main entrypoint of the pallet 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 pallet 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 pallet calls is zero.
When the pallet 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.
## Pallet Initialization
As the previous section states, there are two things that are mandatory for pallet operations: best finalized
header and the current validators set. Without it the pallet 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 pallet 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 pallet 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 pallet. 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) 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/).
-142
View File
@@ -1,142 +0,0 @@
// 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 Pallet.
//!
//! The main dispatchable for the GRANDPA pallet 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_chain::justification::required_justification_precommits;
use bp_runtime::BasicOperatingMode;
use bp_test_utils::{
accounts, make_justification_for_header, JustificationGeneratorParams, TEST_GRANDPA_ROUND,
TEST_GRANDPA_SET_ID,
};
use frame_benchmarking::{benchmarks_instance_pallet, whitelisted_caller};
use frame_system::RawOrigin;
use sp_consensus_grandpa::AuthorityId;
use sp_runtime::traits::{One, Zero};
use sp_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;
if max_bridged_authorities > 128 {
sp_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 pallet.
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()));
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
}
-426
View File
@@ -1,426 +0,0 @@
// 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, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error,
Pallet,
};
use bp_header_chain::{
justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size,
ChainWithGrandpa, GrandpaConsensusLogReader,
};
use bp_runtime::{BlockNumberOf, OwnedBridgeModule};
use codec::Encode;
use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight};
use sp_consensus_grandpa::SetId;
use sp_runtime::{
traits::{Header, Zero},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
RuntimeDebug, SaturatedConversion,
};
/// Info about a `SubmitParachainHeads` call which tries to update a single parachain.
#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
pub struct SubmitFinalityProofInfo<N> {
/// 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>,
/// 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> 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()
}
}
/// Helper struct that provides methods for working with the `SubmitFinalityProof` call.
pub struct SubmitFinalityProofHelper<T: Config<I>, I: 'static> {
_phantom_data: sp_std::marker::PhantomData<(T, I)>,
}
impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
/// 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.
pub fn check_obsolete(
finality_target: BlockNumberOf<T::BridgedChain>,
current_set_id: Option<SetId>,
) -> Result<(), Error<T, I>> {
let best_finalized = crate::BestFinalized::<T, I>::get().ok_or_else(|| {
log::trace!(
target: crate::LOG_TARGET,
"Cannot finalize header {:?} because pallet is not yet initialized",
finality_target,
);
<Error<T, I>>::NotInitialized
})?;
if best_finalized.number() >= finality_target {
log::trace!(
target: crate::LOG_TARGET,
"Cannot finalize obsolete header: bundled {:?}, best {:?}",
finality_target,
best_finalized,
);
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 {
log::trace!(
target: crate::LOG_TARGET,
"Cannot finalize header signed by unknown authority set: bundled {:?}, best {:?}",
current_set_id,
actual_set_id,
);
return Err(Error::<T, I>::InvalidAuthoritySetId)
}
}
Ok(())
}
/// Check if the `SubmitFinalityProof` was successfully executed.
pub fn was_successful(finality_target: BlockNumberOf<T::BridgedChain>) -> bool {
match crate::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 pallet's call.
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
IsSubType<CallableCallFor<Pallet<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,
))
} else if let Some(crate::Call::<T, I>::submit_finality_proof_ex {
finality_target,
justification,
current_set_id,
}) = self.is_sub_type()
{
return Some(submit_finality_proof_info_from_args::<T, I>(
finality_target,
justification,
Some(*current_set_id),
))
}
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.
fn check_obsolete_submit_finality_proof(&self) -> TransactionValidity
where
Self: Sized,
{
let finality_target = match self.submit_finality_proof_info() {
Some(finality_proof) => finality_proof,
_ => return Ok(ValidTransaction::default()),
};
if Pallet::<T, I>::ensure_not_halted().is_err() {
return InvalidTransaction::Call.into()
}
match SubmitFinalityProofHelper::<T, I>::check_obsolete(
finality_target.block_number,
finality_target.current_set_id,
) {
Ok(_) => Ok(ValidTransaction::default()),
Err(Error::<T, I>::OldHeader) => InvalidTransaction::Stale.into(),
Err(_) => InvalidTransaction::Call.into(),
}
}
}
impl<T: Config<I>, I: 'static> CallSubType<T, I> for T::RuntimeCall where
T::RuntimeCall: IsSubType<CallableCallFor<Pallet<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>,
) -> SubmitFinalityProofInfo<BridgedBlockNumber<T, I>> {
let block_number = *finality_target.number();
// the `submit_finality_proof` call will reject justifications with invalid, duplicate,
// unknown and extra signatures. It'll also reject justifications with less than necessary
// signatures. So we do not care about extra weight because of additional signatures here.
let precommits_len = justification.commit.precommits.len().saturated_into();
let required_precommits = precommits_len;
// 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 votes_ancestries_len = justification.votes_ancestries.len().saturated_into();
let extra_weight =
if votes_ancestries_len > T::BridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY {
T::WeightInfo::submit_finality_proof(precommits_len, votes_ancestries_len)
} else {
Weight::zero()
};
// check if the `finality_target` is a mandatory header. If so, we are ready to refund larger
// size
let is_mandatory_finality_target =
GrandpaConsensusLogReader::<BridgedBlockNumber<T, I>>::find_scheduled_change(
finality_target.digest(),
)
.is_some();
// we can estimate extra call size easily, without any additional significant overhead
let actual_call_size: u32 = finality_target
.encoded_size()
.saturating_add(justification.encoded_size())
.saturated_into();
let max_expected_call_size = max_expected_submit_finality_proof_arguments_size::<T::BridgedChain>(
is_mandatory_finality_target,
required_precommits,
);
let extra_size = actual_call_size.saturating_sub(max_expected_call_size);
SubmitFinalityProofInfo { block_number, current_set_id, extra_weight, extra_size }
}
#[cfg(test)]
mod tests {
use crate::{
call_ext::CallSubType,
mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime},
BestFinalized, Config, CurrentAuthoritySet, PalletOperatingMode, StoredAuthoritySet,
SubmitFinalityProofInfo, WeightInfo,
};
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{BasicOperatingMode, HeaderId};
use bp_test_utils::{
make_default_justification, make_justification_for_header, JustificationGeneratorParams,
TEST_GRANDPA_SET_ID,
};
use frame_support::weights::Weight;
use sp_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,
};
RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call,
))
.is_ok()
}
fn sync_to_header_10() {
let header10_hash = sp_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 pallet 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_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,
})
);
// 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,
});
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,
})
);
}
#[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,
});
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,
});
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,
});
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,
});
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight);
}
}
File diff suppressed because it is too large Load Diff
-112
View File
@@ -1,112 +0,0 @@
// 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_chain::ChainWithGrandpa;
use bp_runtime::{Chain, ChainId};
use frame_support::{
construct_runtime, derive_impl, parameter_types, traits::Hooks, weights::Weight,
};
use sp_core::sr25519::Signature;
pub type AccountId = u64;
pub type TestHeader = sp_runtime::testing::Header;
pub type TestNumber = u64;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
pub const MAX_BRIDGED_AUTHORITIES: u32 = 5;
use crate as grandpa;
construct_runtime! {
pub enum TestRuntime
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Grandpa: grandpa::{Pallet, Call, Event<T>},
}
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
}
parameter_types! {
pub const MaxFreeMandatoryHeadersPerBlock: u32 = 2;
pub const HeadersToKeep: u32 = 5;
}
impl grandpa::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = TestBridgedChain;
type MaxFreeMandatoryHeadersPerBlock = MaxFreeMandatoryHeadersPerBlock;
type HeadersToKeep = HeadersToKeep;
type WeightInfo = ();
}
#[derive(Debug)]
pub struct TestBridgedChain;
impl Chain for TestBridgedChain {
const ID: ChainId = *b"tbch";
type BlockNumber = frame_system::pallet_prelude::BlockNumberFor<TestRuntime>;
type Hash = <TestRuntime as frame_system::Config>::Hash;
type Hasher = <TestRuntime as frame_system::Config>::Hashing;
type Header = TestHeader;
type AccountId = AccountId;
type Balance = u64;
type Nonce = u64;
type Signature = Signature;
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() -> sp_io::TestExternalities {
sp_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)
}
@@ -1,136 +0,0 @@
// 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_chain::{AuthoritySet, ChainWithGrandpa};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{traits::Get, BoundedVec, CloneNoBound, RuntimeDebugNoBound};
use scale_info::TypeInfo;
use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
use sp_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 pallet 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
}
}
-167
View File
@@ -1,167 +0,0 @@
// 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 pallet_bridge_grandpa
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE 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
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_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 frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_bridge_grandpa.
pub trait WeightInfo {
fn submit_finality_proof(p: u32, v: u32) -> Weight;
}
/// Weights for `pallet_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: frame_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))
}
}
// 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))
}
}
-64
View File
@@ -1,64 +0,0 @@
[package]
name = "pallet-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
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
log = { workspace = true }
num-traits = { version = "0.2", default-features = false }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
# Bridge dependencies
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Dependencies
frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true }
frame-support = { path = "../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../substrate/frame/system", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[dev-dependencies]
bp-test-utils = { path = "../../primitives/test-utils" }
pallet-balances = { path = "../../../substrate/frame/balances" }
sp-io = { path = "../../../substrate/primitives/io" }
[features]
default = ["std"]
std = [
"bp-messages/std",
"bp-runtime/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"num-traits/std",
"scale-info/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"sp-runtime/try-runtime",
]
-201
View File
@@ -1,201 +0,0 @@
# Bridge Messages Pallet
The messages pallet 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 an 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<>Polkadot bridge](../../docs/polkadot-kusama-bridge-overview.md) we are using lane as a channel of
communication between two parachains of different relay chains. For example, lane `[0, 0, 0, 0]` is used for Polkadot <>
Kusama Asset Hub communications. Other lanes may be used to bridge other parachains.
## Message Workflow
The pallet 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 pallet. 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 pallet. 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 pallet 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 Substrate-to-Substrate bridge we're using runtime storage
proofs. Other bridges may use transaction proofs, Substrate 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 Substrate-based chains, you may want to look at
the [bridge-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 pallet may be used to build virtual channels between multiple chains, as we do in our [Polkadot <>
Kusama bridge](../../docs/polkadot-kusama-bridge-overview.md). There, the pallet actually bridges only two parachains -
Kusama Bridge Hub and Polkadot Bridge Hub. However, other Kusama and Polkadot parachains 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 parachain 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 pallet 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 pallet. 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 Messages to the Bridged Chain?
The `pallet_bridge_messages::Config` trait has 3 main associated types that are used to work with outbound messages. The
`pallet_bridge_messages::Config::TargetHeaderChain` defines how we see the bridged chain as the target for our outbound
messages. It must be able to check that the bridged chain may accept our message - like that the message has size below
maximal possible transaction size of the chain and so on. And when the relayer sends us a confirmation transaction, this
implementation must be able to parse and verify the proof of messages delivery. Normally, you would reuse the same
(configurable) type on all chains that are sending messages to the same bridged chain.
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 [`pallet-bridge-relayers`](../relayers/) pallet 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.
### How to plug-in Messages Module to 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::Config::SourceHeaderChain` defines how we see the bridged chain as the source of our inbound
messages. When relayer sends us a delivery transaction, this implementation must be able to parse and verify the proof
of messages wrapped in this transaction. Normally, you would reuse the same (configurable) type on all chains that are
sending messages to the same bridged chain.
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.
### 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?
Two settings that are used to check messages in the `send_message()` function. The
`pallet_bridge_messages::Config::ActiveOutboundLanes` is an array of all message lanes, that may be used to send
messages. All messages sent using other lanes are rejected. All messages that have size above
`pallet_bridge_messages::Config::MaximalOutboundPayloadSize` will also be 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::ConfigMaxUnrewardedRelayerEntriesAtInboundLane` 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::MaxUnconfirmedMessagesAtInboundLane` 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 Substrate-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 halt_operations()`: the module owner (or sudo account) may call this function to stop all module operations. After
this call, all message-related transactions will be rejected until further `resume_operations` call'. This call may be
used when something extraordinary happens with the bridge;
- `fn resume_operations()`: module owner may call this function to resume bridge operations. The module will resume its
regular operations after this call.
If pallet 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/).
@@ -1,461 +0,0 @@
// 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 pallet benchmarking.
use crate::{
inbound_lane::InboundLaneStorage, outbound_lane, weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH,
Call, OutboundLanes, RuntimeInboundLaneStorage,
};
use bp_messages::{
source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, DeliveredMessages,
InboundLaneData, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer,
UnrewardedRelayersState,
};
use bp_runtime::StorageProofSize;
use codec::Decode;
use frame_benchmarking::{account, benchmarks_instance_pallet};
use frame_support::weights::Weight;
use frame_system::RawOrigin;
use sp_runtime::{traits::TrailingZeroInput, BoundedVec};
use sp_std::{ops::RangeInclusive, prelude::*};
const SEED: u32 = 0;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config<I>, I: 'static = ()>(crate::Pallet<T, I>);
/// Benchmark-specific message proof parameters.
#[derive(Debug)]
pub struct MessageProofParams {
/// 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 size: StorageProofSize,
}
/// Benchmark-specific message delivery proof parameters.
#[derive(Debug)]
pub struct MessageDeliveryProofParams<ThisChainAccountId> {
/// 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 size: StorageProofSize,
}
/// Trait that must be implemented by runtime.
pub trait Config<I: 'static>: crate::Config<I> {
/// Lane id to use in benchmarks.
///
/// By default, lane 00000000 is used.
fn bench_lane_id() -> LaneId {
LaneId([0, 0, 0, 0])
}
/// Return id of relayer account at the bridged chain.
///
/// By default, zero account is returned.
fn bridged_relayer_id() -> Self::InboundRelayer {
Self::InboundRelayer::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::SourceHeaderChain as SourceHeaderChain>::MessagesProof, Weight);
/// Prepare messages delivery proof to receive by the module.
fn prepare_message_delivery_proof(
params: MessageDeliveryProofParams<Self::AccountId>,
) -> <Self::TargetHeaderChain as TargetHeaderChain<Self::OutboundPayload, Self::AccountId>>::MessagesDeliveryProof;
/// 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;
}
benchmarks_instance_pallet! {
//
// Benchmarks that are used directly by the runtime calls weight formulae.
//
// 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.
//
// This is base benchmark for all other message delivery benchmarks.
receive_single_message_proof {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: None,
is_successful_dispatch_expected: false,
size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
21,
);
}
// Benchmark `receive_messages_proof` extrinsic with two 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.
//
// The weight of single message delivery could be approximated as
// `weight(receive_two_messages_proof) - 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.
receive_two_messages_proof {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=22,
outbound_lane_data: None,
is_successful_dispatch_expected: false,
size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 2, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
22,
);
}
// 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.
receive_single_message_proof_with_outbound_lane_state {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: Some(OutboundLaneData {
oldest_unpruned_nonce: 21,
latest_received_nonce: 20,
latest_generated_nonce: 21,
}),
is_successful_dispatch_expected: false,
size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
let lane_state = crate::InboundLanes::<T, I>::get(&T::bench_lane_id());
assert_eq!(lane_state.last_delivered_nonce(), 21);
assert_eq!(lane_state.last_confirmed_nonce, 20);
}
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions:
// * the proof has large leaf with total size of approximately 1KB;
// * 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.
//
// With single KB of messages proof, the weight of the call is increased (roughly) by
// `(receive_single_message_proof_16KB - receive_single_message_proof_1_kb) / 15`.
receive_single_message_proof_1_kb {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: None,
is_successful_dispatch_expected: false,
size: StorageProofSize::HasLargeLeaf(1024),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
21,
);
}
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions:
// * the proof has large leaf with total size of approximately 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.
//
// Size of proof grows because it contains extra trie nodes in it.
//
// With single KB of messages proof, the weight of the call is increased (roughly) by
// `(receive_single_message_proof_16KB - receive_single_message_proof) / 15`.
receive_single_message_proof_16_kb {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: None,
is_successful_dispatch_expected: false,
size: StorageProofSize::HasLargeLeaf(16 * 1024),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
21,
);
}
// 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.
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 {
relayers: vec![UnrewardedRelayer {
relayer: relayer_id.clone(),
messages: DeliveredMessages::new(1),
}].into_iter().collect(),
last_confirmed_nonce: 0,
},
size: StorageProofSize::Minimal(0),
});
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state)
verify {
assert_eq!(OutboundLanes::<T, I>::get(T::bench_lane_id()).latest_received_nonce, 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)`.
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 {
relayers: vec![UnrewardedRelayer {
relayer: relayer_id.clone(),
messages: delivered_messages,
}].into_iter().collect(),
last_confirmed_nonce: 0,
},
size: StorageProofSize::Minimal(0),
});
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state)
verify {
assert_eq!(OutboundLanes::<T, I>::get(T::bench_lane_id()).latest_received_nonce, 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)`.
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 {
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,
},
size: StorageProofSize::Minimal(0),
});
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer1_id.clone()), proof, relayers_state)
verify {
assert_eq!(OutboundLanes::<T, I>::get(T::bench_lane_id()).latest_received_nonce, 2);
assert!(T::is_relayer_rewarded(&relayer1_id));
assert!(T::is_relayer_rewarded(&relayer2_id));
}
//
// Benchmarks that the runtime developers may use for proper pallet 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.
receive_single_message_proof_with_dispatch {
// maybe dispatch weight relies on the message size too?
let i in EXPECTED_DEFAULT_MESSAGE_LENGTH .. EXPECTED_DEFAULT_MESSAGE_LENGTH * 16;
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: None,
is_successful_dispatch_expected: true,
size: StorageProofSize::Minimal(i),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
21,
);
assert!(T::is_message_successfully_dispatched(21));
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
}
fn send_regular_message<T: Config<I>, I: 'static>() {
let mut outbound_lane = outbound_lane::<T, I>(T::bench_lane_id());
outbound_lane.send_message(BoundedVec::try_from(vec![]).expect("We craft valid messages"));
}
fn receive_messages<T: Config<I>, I: 'static>(nonce: MessageNonce) {
let mut inbound_lane_storage =
RuntimeInboundLaneStorage::<T, I>::from_lane_id(T::bench_lane_id());
inbound_lane_storage.set_data(InboundLaneData {
relayers: vec![UnrewardedRelayer {
relayer: T::bridged_relayer_id(),
messages: DeliveredMessages::new(nonce),
}]
.into_iter()
.collect(),
last_confirmed_nonce: 0,
});
}
@@ -1,556 +0,0 @@
// 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::Config;
use bp_messages::{
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
DeliveredMessages, InboundLaneData, LaneId, MessageKey, MessageNonce, OutboundLaneData,
ReceptionResult, UnrewardedRelayer,
};
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use frame_support::traits::Get;
use scale_info::{Type, TypeInfo};
use sp_runtime::RuntimeDebug;
use sp_std::prelude::PartialEq;
/// Inbound lane storage.
pub trait InboundLaneStorage {
/// Id of relayer on source chain.
type Relayer: Clone + PartialEq;
/// Lane id.
fn id(&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 get_or_init_data(&mut self) -> InboundLaneData<Self::Relayer>;
/// Update lane data in the storage.
fn set_data(&mut self, data: InboundLaneData<Self::Relayer>);
}
/// 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<T::InboundRelayer>);
impl<T: Config<I>, I: 'static> sp_std::ops::Deref for StoredInboundLaneData<T, I> {
type Target = InboundLaneData<T::InboundRelayer>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Config<I>, I: 'static> sp_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<T::InboundRelayer>
{
fn from(data: StoredInboundLaneData<T, I>) -> Self {
data.0
}
}
impl<T: Config<I>, I: 'static> EncodeLike<StoredInboundLaneData<T, I>>
for InboundLaneData<T::InboundRelayer>
{
}
impl<T: Config<I>, I: 'static> TypeInfo for StoredInboundLaneData<T, I> {
type Identity = Self;
fn type_info() -> Type {
InboundLaneData::<T::InboundRelayer>::type_info()
}
}
impl<T: Config<I>, I: 'static> MaxEncodedLen for StoredInboundLaneData<T, I> {
fn max_encoded_len() -> usize {
InboundLaneData::<T::InboundRelayer>::encoded_size_hint(
T::MaxUnrewardedRelayerEntriesAtInboundLane::get() 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 }
}
/// Returns `mut` storage reference.
pub fn storage_mut(&mut self) -> &mut S {
&mut self.storage
}
/// 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.get_or_init_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>(
&mut self,
relayer_at_bridged_chain: &S::Relayer,
nonce: MessageNonce,
message_data: DispatchMessageData<Dispatch::DispatchPayload>,
) -> ReceptionResult<Dispatch::DispatchLevelResult> {
let mut data = self.storage.get_or_init_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)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
inbound_lane,
mock::{
dispatch_result, inbound_message_data, inbound_unrewarded_relayers_state, run_test,
unrewarded_relayer, TestMessageDispatch, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID,
TEST_RELAYER_A, TEST_RELAYER_B, TEST_RELAYER_C,
},
RuntimeInboundLaneStorage,
};
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
receive_regular_message(&mut lane, 1);
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 10,
..Default::default()
}),
None,
);
assert_eq!(lane.storage.get_or_init_data().last_confirmed_nonce, 0);
});
}
#[test]
fn receive_status_update_ignores_obsolete_status() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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.get_or_init_data().last_confirmed_nonce, 3);
assert_eq!(
lane.receive_state_update(OutboundLaneData {
latest_received_nonce: 3,
..Default::default()
}),
None,
);
assert_eq!(lane.storage.get_or_init_data().last_confirmed_nonce, 3);
});
}
#[test]
fn receive_status_update_works() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
receive_regular_message(&mut lane, 1);
receive_regular_message(&mut lane, 2);
receive_regular_message(&mut lane, 3);
assert_eq!(lane.storage.get_or_init_data().last_confirmed_nonce, 0);
assert_eq!(
lane.storage.get_or_init_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.get_or_init_data().last_confirmed_nonce, 2);
assert_eq!(
lane.storage.get_or_init_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.get_or_init_data().last_confirmed_nonce, 3);
assert_eq!(lane.storage.get_or_init_data().relayers, vec![]);
});
}
#[test]
fn receive_status_update_works_with_batches_from_relayers() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
let mut seed_storage_data = lane.storage.get_or_init_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.get_or_init_data().last_confirmed_nonce, 3);
assert_eq!(
lane.storage.get_or_init_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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
assert_eq!(
lane.receive_message::<TestMessageDispatch>(
&TEST_RELAYER_A,
10,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceptionResult::InvalidNonce
);
assert_eq!(lane.storage.get_or_init_data().last_delivered_nonce(), 0);
});
}
#[test]
fn fails_to_receive_messages_above_unrewarded_relayer_entries_limit_per_lane() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
let max_nonce =
<TestRuntime as Config>::MaxUnrewardedRelayerEntriesAtInboundLane::get();
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
let max_nonce = <TestRuntime as Config>::MaxUnconfirmedMessagesAtInboundLane::get();
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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.get_or_init_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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
receive_regular_message(&mut lane, 1);
assert_eq!(lane.storage.get_or_init_data().last_delivered_nonce(), 1);
});
}
#[test]
fn unspent_weight_is_returned_by_receive_message() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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,
},
);
});
}
}
File diff suppressed because it is too large Load Diff
-461
View File
@@ -1,461 +0,0 @@
// 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::{Config, StoredMessagePayload};
use bp_messages::{
calc_relayers_rewards,
source_chain::{DeliveryConfirmationPayments, OnMessagesDelivered, TargetHeaderChain},
target_chain::{
DeliveryPayments, DispatchMessage, DispatchMessageData, MessageDispatch,
ProvedLaneMessages, ProvedMessages, SourceHeaderChain,
},
DeliveredMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce,
UnrewardedRelayer, UnrewardedRelayersState, VerificationError,
};
use bp_runtime::{messages::MessageDispatchResult, Size};
use codec::{Decode, Encode};
use frame_support::{
derive_impl, parameter_types,
weights::{constants::RocksDbWeight, Weight},
};
use scale_info::TypeInfo;
use sp_runtime::BuildStorage;
use std::{
collections::{BTreeMap, VecDeque},
ops::RangeInclusive,
};
pub type AccountId = u64;
pub type Balance = u64;
#[derive(Decode, 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 = ();
type Block = frame_system::mocking::MockBlock<TestRuntime>;
use crate as pallet_bridge_messages;
frame_support::construct_runtime! {
pub enum TestRuntime
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Event<T>},
Messages: pallet_bridge_messages::{Pallet, Call, Event<T>},
}
}
pub type DbWeight = RocksDbWeight;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
type AccountData = pallet_balances::AccountData<Balance>;
type DbWeight = DbWeight;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for TestRuntime {
type ReserveIdentifier = [u8; 8];
type AccountStore = System;
}
parameter_types! {
pub const MaxMessagesToPruneAtOnce: u64 = 10;
pub const MaxUnrewardedRelayerEntriesAtInboundLane: u64 = 16;
pub const MaxUnconfirmedMessagesAtInboundLane: u64 = 128;
pub const TestBridgedChainId: bp_runtime::ChainId = *b"test";
pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID, TEST_LANE_ID_2];
}
/// weights of messages pallet calls we use in tests.
pub type TestWeightInfo = ();
impl Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = TestWeightInfo;
type ActiveOutboundLanes = ActiveOutboundLanes;
type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane;
type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane;
type MaximalOutboundPayloadSize = frame_support::traits::ConstU32<MAX_OUTBOUND_PAYLOAD_SIZE>;
type OutboundPayload = TestPayload;
type InboundPayload = TestPayload;
type InboundRelayer = TestRelayer;
type DeliveryPayments = TestDeliveryPayments;
type TargetHeaderChain = TestTargetHeaderChain;
type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments;
type OnMessagesDelivered = TestOnMessagesDelivered;
type SourceHeaderChain = TestSourceHeaderChain;
type MessageDispatch = TestMessageDispatch;
type BridgedChainId = TestBridgedChainId;
}
#[cfg(feature = "runtime-benchmarks")]
impl crate::benchmarking::Config<()> for TestRuntime {
fn bench_lane_id() -> LaneId {
TEST_LANE_ID
}
fn prepare_message_proof(
params: crate::benchmarking::MessageProofParams,
) -> (TestMessagesProof, Weight) {
// in mock run we only care about benchmarks correctness, not the benchmark results
// => ignore size related arguments
let (messages, total_dispatch_weight) =
params.message_nonces.into_iter().map(|n| message(n, REGULAR_PAYLOAD)).fold(
(Vec::new(), Weight::zero()),
|(mut messages, total_dispatch_weight), message| {
let weight = REGULAR_PAYLOAD.declared_weight;
messages.push(message);
(messages, total_dispatch_weight.saturating_add(weight))
},
);
let mut proof: TestMessagesProof = Ok(messages).into();
proof.result.as_mut().unwrap().get_mut(0).unwrap().1.lane_state = params.outbound_lane_data;
(proof, total_dispatch_weight)
}
fn prepare_message_delivery_proof(
params: crate::benchmarking::MessageDeliveryProofParams<AccountId>,
) -> TestMessagesDeliveryProof {
// in mock run we only care about benchmarks correctness, not the benchmark results
// => ignore size related arguments
TestMessagesDeliveryProof(Ok((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
}
}
/// Maximal outbound payload size.
pub const MAX_OUTBOUND_PAYLOAD_SIZE: u32 = 4096;
/// 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;
/// Error that is returned by all test implementations.
pub const TEST_ERROR: &str = "Test error";
/// Lane that we're using in tests.
pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 1]);
/// Secondary lane that we're using in tests.
pub const TEST_LANE_ID_2: LaneId = LaneId([0, 0, 0, 2]);
/// Inactive outbound lane.
pub const TEST_LANE_ID_3: LaneId = LaneId([0, 0, 0, 3]);
/// Regular message payload.
pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50);
/// Payload that is rejected by `TestTargetHeaderChain`.
pub const PAYLOAD_REJECTED_BY_TARGET_CHAIN: TestPayload = message_payload(1, 50);
/// Vec of proved messages, grouped by lane.
pub type MessagesByLaneVec = Vec<(LaneId, ProvedLaneMessages<Message>)>;
/// Test messages proof.
#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub struct TestMessagesProof {
pub result: Result<MessagesByLaneVec, ()>,
}
impl Size for TestMessagesProof {
fn size(&self) -> u32 {
0
}
}
impl From<Result<Vec<Message>, ()>> for TestMessagesProof {
fn from(result: Result<Vec<Message>, ()>) -> Self {
Self {
result: result.map(|messages| {
let mut messages_by_lane: BTreeMap<LaneId, ProvedLaneMessages<Message>> =
BTreeMap::new();
for message in messages {
messages_by_lane.entry(message.key.lane_id).or_default().messages.push(message);
}
messages_by_lane.into_iter().collect()
}),
}
}
}
/// Messages delivery proof used in tests.
#[derive(Debug, Encode, Decode, Eq, Clone, PartialEq, TypeInfo)]
pub struct TestMessagesDeliveryProof(pub Result<(LaneId, InboundLaneData<TestRelayer>), ()>);
impl Size for TestMessagesDeliveryProof {
fn size(&self) -> u32 {
0
}
}
/// Target header chain that is used in tests.
#[derive(Debug, Default)]
pub struct TestTargetHeaderChain;
impl TargetHeaderChain<TestPayload, TestRelayer> for TestTargetHeaderChain {
type MessagesDeliveryProof = TestMessagesDeliveryProof;
fn verify_message(payload: &TestPayload) -> Result<(), VerificationError> {
if *payload == PAYLOAD_REJECTED_BY_TARGET_CHAIN {
Err(VerificationError::Other(TEST_ERROR))
} else {
Ok(())
}
}
fn verify_messages_delivery_proof(
proof: Self::MessagesDeliveryProof,
) -> Result<(LaneId, InboundLaneData<TestRelayer>), VerificationError> {
proof.0.map_err(|_| VerificationError::Other(TEST_ERROR))
}
}
/// 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();
frame_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();
frame_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();
frame_support::storage::unhashed::take::<bool>(&key).is_some()
}
}
impl DeliveryConfirmationPayments<AccountId> for TestDeliveryConfirmationPayments {
type Error = &'static str;
fn pay_reward(
_lane_id: LaneId,
messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
_confirmation_relayer: &AccountId,
received_range: &RangeInclusive<MessageNonce>,
) -> MessageNonce {
let relayers_rewards = calc_relayers_rewards(messages_relayers, received_range);
let rewarded_relayers = relayers_rewards.len();
for (relayer, reward) in &relayers_rewards {
let key = (b":relayer-reward:", relayer, reward).encode();
frame_support::storage::unhashed::put(&key, &true);
}
rewarded_relayers as _
}
}
/// Source header chain that is used in tests.
#[derive(Debug)]
pub struct TestSourceHeaderChain;
impl SourceHeaderChain for TestSourceHeaderChain {
type MessagesProof = TestMessagesProof;
fn verify_messages_proof(
proof: Self::MessagesProof,
_messages_count: u32,
) -> Result<ProvedMessages<Message>, VerificationError> {
proof
.result
.map(|proof| proof.into_iter().collect())
.map_err(|_| VerificationError::Other(TEST_ERROR))
}
}
/// Test message dispatcher.
#[derive(Debug)]
pub struct TestMessageDispatch;
impl TestMessageDispatch {
pub fn deactivate() {
frame_support::storage::unhashed::put(b"TestMessageDispatch.IsCongested", &true)
}
}
impl MessageDispatch for TestMessageDispatch {
type DispatchPayload = TestPayload;
type DispatchLevelResult = TestDispatchLevelResult;
fn is_active() -> bool {
!frame_support::storage::unhashed::get_or_default::<bool>(
b"TestMessageDispatch.IsCongested",
)
}
fn dispatch_weight(message: &mut DispatchMessage<TestPayload>) -> Weight {
match message.data.payload.as_ref() {
Ok(payload) => payload.declared_weight,
Err(_) => Weight::zero(),
}
}
fn dispatch(
message: DispatchMessage<TestPayload>,
) -> MessageDispatchResult<TestDispatchLevelResult> {
match message.data.payload.as_ref() {
Ok(payload) => 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<(LaneId, MessageNonce)> {
frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered")
}
}
impl OnMessagesDelivered for TestOnMessagesDelivered {
fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) {
frame_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 {
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: bp_messages::LaneId) -> UnrewardedRelayersState {
let inbound_lane_data = crate::InboundLanes::<TestRuntime, ()>::get(lane).0;
UnrewardedRelayersState::from(&inbound_lane_data)
}
/// Return test externalities to use in tests.
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<TestRuntime> { balances: vec![(ENDOWED_ACCOUNT, 1_000_000)] }
.assimilate_storage(&mut t)
.unwrap();
sp_io::TestExternalities::new(t)
}
/// Run pallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
new_test_ext().execute_with(test)
}
@@ -1,424 +0,0 @@
// 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::{DeliveredMessages, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer};
use codec::{Decode, Encode};
use frame_support::{
weights::{RuntimeDbWeight, Weight},
BoundedVec, PalletError,
};
use num_traits::Zero;
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use sp_std::collections::vec_deque::VecDeque;
/// Outbound lane storage.
pub trait OutboundLaneStorage {
type StoredMessagePayload;
/// Lane id.
fn id(&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);
}
/// Outbound message data wrapper that implements `MaxEncodedLen`.
pub type StoredMessagePayload<T, I> = BoundedVec<u8, <T as Config<I>>::MaximalOutboundPayloadSize>;
/// Result of messages receival confirmation.
#[derive(Encode, Decode, 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.
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()
}
/// 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.
log::trace!(
target: LOG_TARGET,
"Messages delivery proof contains too many messages to confirm: {} vs declared {}",
confirmed_messages.total_messages(),
max_allowed_messages,
);
return Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected)
}
ensure_unrewarded_relayers_are_correct(confirmed_messages.end, relayers)?;
data.latest_received_nonce = confirmed_messages.end;
self.storage.set_data(data);
Ok(Some(confirmed_messages))
}
/// Prune at most `max_messages_to_prune` already received messages.
///
/// Returns weight, consumed by messages pruning and lane state update.
pub fn prune_messages(
&mut self,
db_weight: RuntimeDbWeight,
mut remaining_weight: Weight,
) -> Weight {
let write_weight = db_weight.writes(1);
let two_writes_weight = write_weight + write_weight;
let mut spent_weight = Weight::zero();
let mut data = self.storage.data();
while remaining_weight.all_gte(two_writes_weight) &&
data.oldest_unpruned_nonce <= data.latest_received_nonce
{
self.storage.remove_message(&data.oldest_unpruned_nonce);
spent_weight += write_weight;
remaining_weight -= write_weight;
data.oldest_unpruned_nonce += 1;
}
if !spent_weight.is_zero() {
spent_weight += write_weight;
self.storage.set_data(data);
}
spent_weight
}
}
/// 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::{
mock::{
outbound_message_data, run_test, unrewarded_relayer, TestRelayer, TestRuntime,
REGULAR_PAYLOAD, TEST_LANE_ID,
},
outbound_lane,
};
use frame_support::weights::constants::RocksDbWeight;
use sp_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 = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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.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);
});
}
#[test]
fn confirm_delivery_rejects_nonce_lesser_than_latest_received() {
run_test(|| {
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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.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.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);
});
}
#[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).into_iter())
.chain(unrewarded_relayers(3..=3).into_iter())
.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).into_iter())
.chain(unrewarded_relayers(2..=3).into_iter())
.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).into_iter())
.chain(unrewarded_relayers(2..=2).into_iter())
.collect(),
),
Err(ReceptionConfirmationError::NonConsecutiveUnrewardedRelayerEntries),
);
}
#[test]
fn prune_messages_works() {
run_test(|| {
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
// when lane is empty, nothing is pruned
assert_eq!(
lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)),
Weight::zero()
);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
// when nothing is confirmed, nothing is pruned
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!(lane.storage.message(&1).is_some());
assert!(lane.storage.message(&2).is_some());
assert!(lane.storage.message(&3).is_some());
assert_eq!(
lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)),
Weight::zero()
);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
// after confirmation, some messages are received
assert_eq!(
lane.confirm_delivery(2, 2, &unrewarded_relayers(1..=2)),
Ok(Some(delivered_messages(1..=2))),
);
assert_eq!(
lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)),
RocksDbWeight::get().writes(3),
);
assert!(lane.storage.message(&1).is_none());
assert!(lane.storage.message(&2).is_none());
assert!(lane.storage.message(&3).is_some());
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3);
// after last message is confirmed, everything is pruned
assert_eq!(
lane.confirm_delivery(1, 3, &unrewarded_relayers(3..=3)),
Ok(Some(delivered_messages(3..=3))),
);
assert_eq!(
lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)),
RocksDbWeight::get().writes(2),
);
assert!(lane.storage.message(&1).is_none());
assert!(lane.storage.message(&2).is_none());
assert!(lane.storage.message(&3).is_none());
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
});
}
#[test]
fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() {
run_test(|| {
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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))),
);
});
}
}
-525
View File
@@ -1,525 +0,0 @@
// 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 pallet_bridge_messages
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-03-23, 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
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_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 frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_bridge_messages.
pub trait WeightInfo {
fn receive_single_message_proof() -> Weight;
fn receive_two_messages_proof() -> Weight;
fn receive_single_message_proof_with_outbound_lane_state() -> Weight;
fn receive_single_message_proof_1_kb() -> Weight;
fn receive_single_message_proof_16_kb() -> 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_message_proof_with_dispatch(i: u32) -> Weight;
}
/// Weights for `pallet_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: frame_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: `618`
// Estimated: `57170`
// Minimum execution time: 52_321 nanoseconds.
Weight::from_parts(54_478_000, 57170)
.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_two_messages_proof() -> Weight {
// Proof Size summary in bytes:
// Measured: `618`
// Estimated: `57170`
// Minimum execution time: 64_597 nanoseconds.
Weight::from_parts(69_267_000, 57170)
.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: `618`
// Estimated: `57170`
// Minimum execution time: 64_079 nanoseconds.
Weight::from_parts(65_905_000, 57170)
.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_1_kb() -> Weight {
// Proof Size summary in bytes:
// Measured: `618`
// Estimated: `57170`
// Minimum execution time: 50_588 nanoseconds.
Weight::from_parts(53_544_000, 57170)
.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_16_kb() -> Weight {
// Proof Size summary in bytes:
// Measured: `618`
// Estimated: `57170`
// Minimum execution time: 78_269 nanoseconds.
Weight::from_parts(81_748_000, 57170)
.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(65), added: 2540,
/// mode: MaxEncodedLen)
fn receive_delivery_proof_for_single_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `579`
// Estimated: `9584`
// Minimum execution time: 45_786 nanoseconds.
Weight::from_parts(47_382_000, 9584)
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(2_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(65), added: 2540,
/// mode: MaxEncodedLen)
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
// Proof Size summary in bytes:
// Measured: `596`
// Estimated: `9584`
// Minimum execution time: 44_544 nanoseconds.
Weight::from_parts(45_451_000, 9584)
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(2_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(65), added: 2540,
/// mode: MaxEncodedLen)
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
// Proof Size summary in bytes:
// Measured: `596`
// Estimated: `12124`
// Minimum execution time: 47_344 nanoseconds.
Weight::from_parts(48_311_000, 12124)
.saturating_add(T::DbWeight::get().reads(5_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 InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
///
/// The range of component `i` is `[128, 2048]`.
fn receive_single_message_proof_with_dispatch(i: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `618`
// Estimated: `57170`
// Minimum execution time: 52_385 nanoseconds.
Weight::from_parts(54_919_468, 57170)
// Standard Error: 108
.saturating_add(Weight::from_parts(3_286, 0).saturating_mul(i.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: `618`
// Estimated: `57170`
// Minimum execution time: 52_321 nanoseconds.
Weight::from_parts(54_478_000, 57170)
.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_two_messages_proof() -> Weight {
// Proof Size summary in bytes:
// Measured: `618`
// Estimated: `57170`
// Minimum execution time: 64_597 nanoseconds.
Weight::from_parts(69_267_000, 57170)
.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: `618`
// Estimated: `57170`
// Minimum execution time: 64_079 nanoseconds.
Weight::from_parts(65_905_000, 57170)
.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_1_kb() -> Weight {
// Proof Size summary in bytes:
// Measured: `618`
// Estimated: `57170`
// Minimum execution time: 50_588 nanoseconds.
Weight::from_parts(53_544_000, 57170)
.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_16_kb() -> Weight {
// Proof Size summary in bytes:
// Measured: `618`
// Estimated: `57170`
// Minimum execution time: 78_269 nanoseconds.
Weight::from_parts(81_748_000, 57170)
.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(65), added: 2540,
/// mode: MaxEncodedLen)
fn receive_delivery_proof_for_single_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `579`
// Estimated: `9584`
// Minimum execution time: 45_786 nanoseconds.
Weight::from_parts(47_382_000, 9584)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(2_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(65), added: 2540,
/// mode: MaxEncodedLen)
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
// Proof Size summary in bytes:
// Measured: `596`
// Estimated: `9584`
// Minimum execution time: 44_544 nanoseconds.
Weight::from_parts(45_451_000, 9584)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(2_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(65), added: 2540,
/// mode: MaxEncodedLen)
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
// Proof Size summary in bytes:
// Measured: `596`
// Estimated: `12124`
// Minimum execution time: 47_344 nanoseconds.
Weight::from_parts(48_311_000, 12124)
.saturating_add(RocksDbWeight::get().reads(5_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 InboundLanes (r:1 w:1)
///
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
/// 51655, mode: MaxEncodedLen)
///
/// The range of component `i` is `[128, 2048]`.
fn receive_single_message_proof_with_dispatch(i: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `618`
// Estimated: `57170`
// Minimum execution time: 52_385 nanoseconds.
Weight::from_parts(54_919_468, 57170)
// Standard Error: 108
.saturating_add(Weight::from_parts(3_286, 0).saturating_mul(i.into()))
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
}
-488
View File
@@ -1,488 +0,0 @@
// 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 bp_runtime::{PreComputedSize, Size};
use frame_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
// verify `receive_messages_proof` weight components
assert_ne!(W::receive_messages_proof_overhead().ref_time(), 0);
assert_ne!(W::receive_messages_proof_overhead().proof_size(), 0);
// W::receive_messages_proof_messages_overhead(1).ref_time() may be zero because:
// the message processing code (`InboundLane::receive_message`) is minimal and may not be
// accounted by our benchmarks
assert_eq!(W::receive_messages_proof_messages_overhead(1).proof_size(), 0);
// 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'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
// (`pallet-bridge-relayers` and balances/assets pallet). 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 transaction_overhead = Self::receive_messages_proof_overhead();
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_delivery_weight =
Self::receive_messages_proof_messages_overhead(MessageNonce::from(messages_count));
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),
);
transaction_overhead
.saturating_add(transaction_overhead_from_runtime)
.saturating_add(outbound_state_delivery_weight)
.saturating_add(messages_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 overhead of message delivery transaction (`receive_messages_proof`).
fn receive_messages_proof_overhead() -> Weight {
let weight_of_two_messages_and_two_tx_overheads =
Self::receive_single_message_proof().saturating_mul(2);
let weight_of_two_messages_and_single_tx_overhead = Self::receive_two_messages_proof();
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 given a number of messages with
/// message delivery transaction (`receive_messages_proof`).
fn receive_messages_proof_messages_overhead(messages: MessageNonce) -> Weight {
let weight_of_two_messages_and_single_tx_overhead = Self::receive_two_messages_proof();
let weight_of_single_message_and_single_tx_overhead = Self::receive_single_message_proof();
weight_of_two_messages_and_single_tx_overhead
.saturating_sub(weight_of_single_message_and_single_tx_overhead)
.saturating_mul(messages as _)
}
/// 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_message_proof_16_kb() -
Self::receive_single_message_proof_1_kb()) /
(15 * 1024);
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 {
// There may be a tiny overweight/underweight here, because we don't account how message
// size affects all steps before dispatch. But the effect should be small enough and we
// may ignore it.
Self::receive_single_message_proof_with_dispatch(message_size)
.saturating_sub(Self::receive_single_message_proof())
}
}
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: frame_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::{mock::TestRuntime, weights::BridgeWeight};
#[test]
fn ensure_default_weights_are_correct() {
ensure_weights_are_correct::<BridgeWeight<TestRuntime>>();
}
}
-71
View File
@@ -1,71 +0,0 @@
[package]
name = "pallet-bridge-parachains"
version = "0.7.0"
description = "Module that allows bridged relay chains to exchange information on their parachains' heads."
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
repository.workspace = true
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
# Bridge Dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-parachains = { path = "../../primitives/parachains", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
pallet-bridge-grandpa = { path = "../grandpa", default-features = false }
# Substrate Dependencies
frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true }
frame-support = { path = "../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../substrate/frame/system", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
sp-trie = { path = "../../../substrate/primitives/trie", default-features = false }
[dev-dependencies]
bp-header-chain = { path = "../../primitives/header-chain" }
bp-test-utils = { path = "../../primitives/test-utils" }
sp-core = { path = "../../../substrate/primitives/core" }
sp-io = { path = "../../../substrate/primitives/io" }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-parachains/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-bridge-grandpa/std",
"scale-info/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-bridge-grandpa/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-bridge-grandpa/try-runtime",
"sp-runtime/try-runtime",
]
-90
View File
@@ -1,90 +0,0 @@
# Bridge Parachains Pallet
The bridge parachains pallet is a light client for one or several parachains of the bridged relay chain.
It serves as a source of finalized parachain headers and is used when you need to build a bridge with
a parachain.
The pallet requires [bridge GRANDPA pallet](../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 Parachains Finality
You can find detailed information on parachains finality in the
[Polkadot-SDK](https://github.com/paritytech/polkadot-sdk) repository. This section gives a brief overview of how the
parachain finality works and how to build a light client for a parachain.
The main thing there is that the parachain generates blocks on its own, but it can't achieve finality without
help of its relay chain. Instead, the parachain collators create a block and hand it over to the relay chain
validators. Validators validate the block and register the new parachain head in the
[`Heads` map](https://github.com/paritytech/polkadot-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) pallet,
deployed at the relay chain. Keep in mind that this pallet, deployed at a relay chain, is **NOT** a bridge pallet,
even though the names are similar.
And what the bridge parachains pallet does, is simply verifying storage proofs of parachain heads within that
`Heads` map. It does that using relay chain header, that has been previously imported by the
[bridge GRANDPA pallet](../grandpa/). Once the proof is verified, the pallet knows that the given parachain
header has been finalized by the relay chain. The parachain header fields may then be used to verify storage
proofs, coming from the parachain. This allows the pallet to be used e.g. as a source of finality for the messages
pallet.
## Pallet Operations
The main entrypoint of the pallet is the `submit_parachain_heads` call. It has three arguments:
- storage proof of parachain heads from the `Heads` map;
- parachain identifiers and hashes of their heads from the storage proof;
- the relay block, at which the storage proof has been generated.
The pallet may track multiple parachains. And the parachains may use different primitives - one may use 128-bit block
numbers, other - 32-bit. To avoid extra decode operations, the pallet is using relay chain block number to order
parachain headers. Any finalized descendant of finalized relay block `RB`, which has parachain block `PB` in
its `Heads` map, is guaranteed to have either `PB`, or its descendant. So parachain block number grows with relay
block number.
The pallet may reject parachain head if it already knows better (or the same) head. In addition, pallet rejects
heads of untracked parachains.
The pallet doesn't track anything behind parachain 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 parachains 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 pallet 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 parachain heads to the pallet. 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) 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
parachain head. So it won't work with multiple parachain heads transactions. This fits our needs
for [Kusama <> Polkadot bridge](../../docs/polkadot-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.
## Parachains Finality Relay
We have an offchain actor, who is watching for new parachain heads and submits them to the bridged chain.
It is the parachains relay - you may look at the [crate level documentation and the code](../../relays/parachains/).
@@ -1,116 +0,0 @@
// 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/>.
//! Parachains finality pallet benchmarking.
use crate::{
weights_ext::DEFAULT_PARACHAIN_HEAD_SIZE, Call, RelayBlockHash, RelayBlockHasher,
RelayBlockNumber,
};
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
use bp_runtime::StorageProofSize;
use frame_benchmarking::{account, benchmarks_instance_pallet};
use frame_system::RawOrigin;
use sp_std::prelude::*;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config<I>, I: 'static = ()>(crate::Pallet<T, I>);
/// Trait that must be implemented by runtime to benchmark the parachains finality pallet.
pub trait Config<I: 'static>: crate::Config<I> {
/// Returns vector of supported parachains.
fn parachains() -> Vec<ParaId>;
/// Generate parachain heads proof and prepare environment for verifying this proof.
fn prepare_parachain_heads_proof(
parachains: &[ParaId],
parachain_head_size: u32,
proof_size: StorageProofSize,
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>);
}
benchmarks_instance_pallet! {
where_clause {
where
<T as pallet_bridge_grandpa::Config<T::BridgesGrandpaPalletInstance>>::BridgedChain:
bp_runtime::Chain<
BlockNumber = RelayBlockNumber,
Hash = RelayBlockHash,
Hasher = RelayBlockHasher,
>,
}
// Benchmark `submit_parachain_heads` extrinsic with different number of parachains.
submit_parachain_heads_with_n_parachains {
let p in 1..(T::parachains().len() + 1) as u32;
let sender = account("sender", 0, 0);
let mut parachains = T::parachains();
let _ = if p <= parachains.len() as u32 {
parachains.split_off(p as usize)
} else {
Default::default()
};
log::trace!(target: crate::LOG_TARGET, "=== {:?}", parachains.len());
let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::Minimal(0),
);
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
}
}
// Benchmark `submit_parachain_heads` extrinsic with 1kb proof size.
submit_parachain_heads_with_1kb_proof {
let sender = account("sender", 0, 0);
let parachains = vec![T::parachains()[0]];
let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::HasLargeLeaf(1024),
);
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
}
}
// Benchmark `submit_parachain_heads` extrinsic with 16kb proof size.
submit_parachain_heads_with_16kb_proof {
let sender = account("sender", 0, 0);
let parachains = vec![T::parachains()[0]];
let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::HasLargeLeaf(16 * 1024),
);
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
}
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
}
-263
View File
@@ -1,263 +0,0 @@
// 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, Pallet, RelayBlockNumber};
use bp_parachains::BestParaHeadHash;
use bp_polkadot_core::parachains::{ParaHash, ParaId};
use bp_runtime::OwnedBridgeModule;
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
use sp_runtime::{
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
RuntimeDebug,
};
/// Info about a `SubmitParachainHeads` call which tries to update a single parachain.
#[derive(PartialEq, RuntimeDebug)]
pub struct SubmitParachainHeadsInfo {
/// Number of the finalized relay block that has been used to prove parachain finality.
pub at_relay_block_number: RelayBlockNumber,
/// Parachain identifier.
pub para_id: ParaId,
/// Hash of the bundled parachain head.
pub para_head_hash: ParaHash,
}
/// Helper struct that provides methods for working with the `SubmitParachainHeads` call.
pub struct SubmitParachainHeadsHelper<T: Config<I>, I: 'static> {
_phantom_data: sp_std::marker::PhantomData<(T, I)>,
}
impl<T: Config<I>, I: 'static> SubmitParachainHeadsHelper<T, I> {
/// Check if the para head provided by the `SubmitParachainHeads` is better than the best one
/// we know.
pub fn is_obsolete(update: &SubmitParachainHeadsInfo) -> bool {
let stored_best_head = match crate::ParasInfo::<T, I>::get(update.para_id) {
Some(stored_best_head) => stored_best_head,
None => return false,
};
if stored_best_head.best_head_hash.at_relay_block_number >= update.at_relay_block_number {
log::trace!(
target: crate::LOG_TARGET,
"The parachain head can't be updated. The parachain head for {:?} \
was already updated at better relay chain block {} >= {}.",
update.para_id,
stored_best_head.best_head_hash.at_relay_block_number,
update.at_relay_block_number
);
return true
}
if stored_best_head.best_head_hash.head_hash == update.para_head_hash {
log::trace!(
target: crate::LOG_TARGET,
"The parachain head can't be updated. The parachain head hash for {:?} \
was already updated to {} at block {} < {}.",
update.para_id,
update.para_head_hash,
stored_best_head.best_head_hash.at_relay_block_number,
update.at_relay_block_number
);
return true
}
false
}
/// Check if the `SubmitParachainHeads` was successfully executed.
pub fn was_successful(update: &SubmitParachainHeadsInfo) -> 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_number,
head_hash: update.para_head_hash,
},
None => false,
}
}
}
/// Trait representing a call that is a sub type of this pallet's call.
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
IsSubType<CallableCallFor<Pallet<T, I>, T>>
{
/// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with
/// one single parachain entry.
fn one_entry_submit_parachain_heads_info(&self) -> Option<SubmitParachainHeadsInfo> {
if let Some(crate::Call::<T, I>::submit_parachain_heads {
ref at_relay_block,
ref parachains,
..
}) = self.is_sub_type()
{
if let &[(para_id, para_head_hash)] = parachains.as_slice() {
return Some(SubmitParachainHeadsInfo {
at_relay_block_number: at_relay_block.0,
para_id,
para_head_hash,
})
}
}
None
}
/// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with
/// one single parachain entry, if the entry is for the provided parachain id.
fn submit_parachain_heads_info_for(&self, para_id: u32) -> Option<SubmitParachainHeadsInfo> {
self.one_entry_submit_parachain_heads_info()
.filter(|update| update.para_id.0 == para_id)
}
/// Validate parachain heads in order to avoid "mining" transactions that provide
/// outdated bridged parachain 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 parachain
/// 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-parachain-head-per-transaction is how the
/// pallet will be used in our environment.
fn check_obsolete_submit_parachain_heads(&self) -> TransactionValidity
where
Self: Sized,
{
let update = match self.one_entry_submit_parachain_heads_info() {
Some(update) => update,
None => return Ok(ValidTransaction::default()),
};
if Pallet::<T, I>::ensure_not_halted().is_err() {
return InvalidTransaction::Call.into()
}
if SubmitParachainHeadsHelper::<T, I>::is_obsolete(&update) {
return InvalidTransaction::Stale.into()
}
Ok(ValidTransaction::default())
}
}
impl<T, I: 'static> CallSubType<T, I> for T::RuntimeCall
where
T: Config<I>,
T::RuntimeCall: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
{
}
#[cfg(test)]
mod tests {
use crate::{
mock::{run_test, RuntimeCall, TestRuntime},
CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockNumber,
};
use bp_parachains::BestParaHeadHash;
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
use bp_runtime::BasicOperatingMode;
fn validate_submit_parachain_heads(
num: RelayBlockNumber,
parachains: Vec<(ParaId, ParaHash)>,
) -> bool {
RuntimeCall::Parachains(crate::Call::<TestRuntime, ()>::submit_parachain_heads {
at_relay_block: (num, Default::default()),
parachains,
parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() },
})
.check_obsolete_submit_parachain_heads()
.is_ok()
}
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_parachain_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_parachain_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_parachain_heads(20, vec![(ParaId(1), [1u8; 32].into())]));
});
}
#[test]
fn extension_rejects_header_if_pallet_is_halted() {
run_test(|| {
// when pallet is halted => tx is rejected
sync_to_relay_header_10();
PalletOperatingMode::<TestRuntime, ()>::put(BasicOperatingMode::Halted);
assert!(!validate_submit_parachain_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();
assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
});
}
#[test]
fn extension_accepts_if_more_than_one_parachain_is_submitted() {
run_test(|| {
// when current best finalized is #10 and we're trying to import header#5, but another
// parachain head is also supplied => tx is accepted
sync_to_relay_header_10();
assert!(validate_submit_parachain_heads(
5,
vec![(ParaId(1), [1u8; 32].into()), (ParaId(2), [1u8; 32].into())]
));
});
}
}
File diff suppressed because it is too large Load Diff
-328
View File
@@ -1,328 +0,0 @@
// 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_chain::ChainWithGrandpa;
use bp_polkadot_core::parachains::ParaId;
use bp_runtime::{Chain, ChainId, Parachain};
use frame_support::{
construct_runtime, derive_impl, parameter_types, traits::ConstU32, weights::Weight,
};
use sp_runtime::{
testing::H256,
traits::{BlakeTwo256, Header as HeaderT},
MultiSignature,
};
use crate as pallet_bridge_parachains;
pub type AccountId = u64;
pub type RelayBlockHeader =
sp_runtime::generic::Header<crate::RelayBlockNumber, crate::RelayBlockHasher>;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
pub const PARAS_PALLET_NAME: &str = "Paras";
pub const UNTRACKED_PARACHAIN_ID: u32 = 10;
// use exact expected encoded size: `vec_len_size + header_number_size + state_root_hash_size`
pub const MAXIMAL_PARACHAIN_HEAD_DATA_SIZE: u32 = 1 + 8 + 32;
// total parachains that we use in tests
pub const TOTAL_PARACHAINS: u32 = 4;
pub type RegularParachainHeader = sp_runtime::testing::Header;
pub type RegularParachainHasher = BlakeTwo256;
pub type BigParachainHeader = sp_runtime::generic::Header<u128, BlakeTwo256>;
pub struct Parachain1;
impl Chain for Parachain1 {
const ID: ChainId = *b"pch1";
type BlockNumber = u64;
type Hash = H256;
type Hasher = RegularParachainHasher;
type Header = RegularParachainHeader;
type AccountId = u64;
type Balance = u64;
type Nonce = u64;
type Signature = MultiSignature;
fn max_extrinsic_size() -> u32 {
0
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}
impl Parachain for Parachain1 {
const PARACHAIN_ID: u32 = 1;
}
pub struct Parachain2;
impl Chain for Parachain2 {
const ID: ChainId = *b"pch2";
type BlockNumber = u64;
type Hash = H256;
type Hasher = RegularParachainHasher;
type Header = RegularParachainHeader;
type AccountId = u64;
type Balance = u64;
type Nonce = u64;
type Signature = MultiSignature;
fn max_extrinsic_size() -> u32 {
0
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}
impl Parachain for Parachain2 {
const PARACHAIN_ID: u32 = 2;
}
pub struct Parachain3;
impl Chain for Parachain3 {
const ID: ChainId = *b"pch3";
type BlockNumber = u64;
type Hash = H256;
type Hasher = RegularParachainHasher;
type Header = RegularParachainHeader;
type AccountId = u64;
type Balance = u64;
type Nonce = u64;
type Signature = MultiSignature;
fn max_extrinsic_size() -> u32 {
0
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}
impl Parachain for Parachain3 {
const PARACHAIN_ID: u32 = 3;
}
// this parachain is using u128 as block number and stored head data size exceeds limit
pub struct BigParachain;
impl Chain for BigParachain {
const ID: ChainId = *b"bpch";
type BlockNumber = u128;
type Hash = H256;
type Hasher = RegularParachainHasher;
type Header = BigParachainHeader;
type AccountId = u64;
type Balance = u64;
type Nonce = u64;
type Signature = MultiSignature;
fn max_extrinsic_size() -> u32 {
0
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}
impl Parachain for BigParachain {
const PARACHAIN_ID: u32 = 4;
}
construct_runtime! {
pub enum TestRuntime
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Grandpa1: pallet_bridge_grandpa::<Instance1>::{Pallet, Event<T>},
Grandpa2: pallet_bridge_grandpa::<Instance2>::{Pallet, Event<T>},
Parachains: pallet_bridge_parachains::{Call, Pallet, Event<T>},
}
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
}
parameter_types! {
pub const HeadersToKeep: u32 = 5;
}
impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance1> for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = TestBridgedChain;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<2>;
type HeadersToKeep = HeadersToKeep;
type WeightInfo = ();
}
impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance2> for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = TestBridgedChain;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<2>;
type HeadersToKeep = HeadersToKeep;
type WeightInfo = ();
}
parameter_types! {
pub const HeadsToKeep: u32 = 4;
pub const ParasPalletName: &'static str = PARAS_PALLET_NAME;
pub GetTenFirstParachains: Vec<ParaId> = (0..10).map(ParaId).collect();
}
impl pallet_bridge_parachains::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
type ParasPalletName = ParasPalletName;
type ParaStoredHeaderDataBuilder = (Parachain1, Parachain2, Parachain3, BigParachain);
type HeadsToKeep = HeadsToKeep;
type MaxParaHeadDataSize = ConstU32<MAXIMAL_PARACHAIN_HEAD_DATA_SIZE>;
}
#[cfg(feature = "runtime-benchmarks")]
impl pallet_bridge_parachains::benchmarking::Config<()> for TestRuntime {
fn parachains() -> Vec<ParaId> {
vec![
ParaId(Parachain1::PARACHAIN_ID),
ParaId(Parachain2::PARACHAIN_ID),
ParaId(Parachain3::PARACHAIN_ID),
]
}
fn prepare_parachain_heads_proof(
parachains: &[ParaId],
_parachain_head_size: u32,
_proof_size: bp_runtime::StorageProofSize,
) -> (
crate::RelayBlockNumber,
crate::RelayBlockHash,
bp_polkadot_core::parachains::ParaHeadsProof,
Vec<(ParaId, bp_polkadot_core::parachains::ParaHash)>,
) {
// in mock run we only care about benchmarks correctness, not the benchmark results
// => ignore size related arguments
let (state_root, proof, parachains) =
bp_test_utils::prepare_parachain_heads_proof::<RegularParachainHeader>(
parachains.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, parachains)
}
}
#[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 = sp_runtime::testing::TestSignature;
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;
}
#[derive(Debug)]
pub struct OtherBridgedChain;
impl Chain for OtherBridgedChain {
const ID: ChainId = *b"obch";
type BlockNumber = u64;
type Hash = crate::RelayBlockHash;
type Hasher = crate::RelayBlockHasher;
type Header = sp_runtime::generic::Header<u64, crate::RelayBlockHasher>;
type AccountId = AccountId;
type Balance = u32;
type Nonce = u32;
type Signature = sp_runtime::testing::TestSignature;
fn max_extrinsic_size() -> u32 {
unreachable!()
}
fn max_extrinsic_weight() -> Weight {
unreachable!()
}
}
impl ChainWithGrandpa for OtherBridgedChain {
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() -> sp_io::TestExternalities {
sp_io::TestExternalities::new(Default::default())
}
/// Run pallet 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(),
)
}
-273
View File
@@ -1,273 +0,0 @@
// 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 pallet_bridge_parachains
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE 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
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_bridge_parachains
// --extrinsic=*
// --execution=wasm
// --wasm-execution=Compiled
// --heap-pages=4096
// --output=./modules/parachains/src/weights.rs
// --template=./.maintain/bridge-weight-template.hbs
#![allow(clippy::all)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_bridge_parachains.
pub trait WeightInfo {
fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight;
fn submit_parachain_heads_with_1kb_proof() -> Weight;
fn submit_parachain_heads_with_16kb_proof() -> Weight;
}
/// Weights for `pallet_bridge_parachains` 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: frame_system::Config> WeightInfo for BridgeWeight<T> {
/// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownParachains 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: BridgeUnknownParachains ParasInfo (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added:
/// 555, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size:
/// Some(64), added: 1549, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size:
/// Some(196), added: 1681, mode: MaxEncodedLen)
///
/// The range of component `p` is `[1, 2]`.
fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `366`
// Estimated: `4648`
// Minimum execution time: 36_701 nanoseconds.
Weight::from_parts(38_597_828, 4648)
// Standard Error: 190_859
.saturating_add(Weight::from_parts(60_685, 0).saturating_mul(p.into()))
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownParachains 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: BridgeUnknownParachains ParasInfo (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added:
/// 555, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size:
/// Some(64), added: 1549, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size:
/// Some(196), added: 1681, mode: MaxEncodedLen)
fn submit_parachain_heads_with_1kb_proof() -> Weight {
// Proof Size summary in bytes:
// Measured: `366`
// Estimated: `4648`
// Minimum execution time: 38_189 nanoseconds.
Weight::from_parts(39_252_000, 4648)
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownParachains 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: BridgeUnknownParachains ParasInfo (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added:
/// 555, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size:
/// Some(64), added: 1549, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size:
/// Some(196), added: 1681, mode: MaxEncodedLen)
fn submit_parachain_heads_with_16kb_proof() -> Weight {
// Proof Size summary in bytes:
// Measured: `366`
// Estimated: `4648`
// Minimum execution time: 62_868 nanoseconds.
Weight::from_parts(63_581_000, 4648)
.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: BridgeUnknownParachains PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownParachains 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: BridgeUnknownParachains ParasInfo (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added:
/// 555, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size:
/// Some(64), added: 1549, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size:
/// Some(196), added: 1681, mode: MaxEncodedLen)
///
/// The range of component `p` is `[1, 2]`.
fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `366`
// Estimated: `4648`
// Minimum execution time: 36_701 nanoseconds.
Weight::from_parts(38_597_828, 4648)
// Standard Error: 190_859
.saturating_add(Weight::from_parts(60_685, 0).saturating_mul(p.into()))
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
/// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownParachains 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: BridgeUnknownParachains ParasInfo (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added:
/// 555, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size:
/// Some(64), added: 1549, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size:
/// Some(196), added: 1681, mode: MaxEncodedLen)
fn submit_parachain_heads_with_1kb_proof() -> Weight {
// Proof Size summary in bytes:
// Measured: `366`
// Estimated: `4648`
// Minimum execution time: 38_189 nanoseconds.
Weight::from_parts(39_252_000, 4648)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
/// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0)
///
/// Proof: BridgeUnknownParachains 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: BridgeUnknownParachains ParasInfo (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added:
/// 555, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size:
/// Some(64), added: 1549, mode: MaxEncodedLen)
///
/// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1)
///
/// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size:
/// Some(196), added: 1681, mode: MaxEncodedLen)
fn submit_parachain_heads_with_16kb_proof() -> Weight {
// Proof Size summary in bytes:
// Measured: `366`
// Estimated: `4648`
// Minimum execution time: 62_868 nanoseconds.
Weight::from_parts(63_581_000, 4648)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
}
@@ -1,107 +0,0 @@
// 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 bp_runtime::Size;
use frame_support::weights::{RuntimeDbWeight, Weight};
/// Size of the regular parachain head.
///
/// It's not that we are expecting all parachain 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 Polkadot block.
pub const DEFAULT_PARACHAIN_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 {
/// 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 parachain heads delivery extrinsic.
fn submit_parachain_heads_weight(
db_weight: RuntimeDbWeight,
proof: &impl Size,
parachains_count: u32,
) -> Weight {
// weight of the `submit_parachain_heads` with exactly `parachains_count` parachain
// heads of the default size (`DEFAULT_PARACHAIN_HEAD_SIZE`)
let base_weight = Self::submit_parachain_heads_with_n_parachains(parachains_count);
// overhead because of extra storage proof bytes
let expected_proof_size = parachains_count
.saturating_mul(DEFAULT_PARACHAIN_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::parachain_head_pruning_weight(db_weight).saturating_mul(parachains_count as u64);
base_weight.saturating_add(proof_size_overhead).saturating_add(pruning_weight)
}
/// Returns weight of single parachain head storage update.
///
/// This weight only includes db write operations that happens if parachain head is actually
/// updated. All extra weights (weight of storage proof validation, additional checks, ...) is
/// not included.
fn parachain_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 parachain head pruning.
fn parachain_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_parachain_heads_with_16kb_proof() -
Self::submit_parachain_heads_with_1kb_proof()) /
(15 * 1024);
extra_byte_weight.saturating_mul(extra_proof_bytes as u64)
}
}
impl WeightInfoExt for () {
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
}
impl<T: frame_system::Config> WeightInfoExt for BridgeWeight<T> {
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
}
-71
View File
@@ -1,71 +0,0 @@
[package]
name = "pallet-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
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
# Bridge dependencies
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-relayers = { path = "../../primitives/relayers", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
pallet-bridge-messages = { path = "../messages", default-features = false }
# Substrate Dependencies
frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true }
frame-support = { path = "../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../substrate/frame/system", default-features = false }
sp-arithmetic = { path = "../../../substrate/primitives/arithmetic", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[dev-dependencies]
bp-runtime = { path = "../../primitives/runtime" }
pallet-balances = { path = "../../../substrate/frame/balances" }
sp-io = { path = "../../../substrate/primitives/io" }
sp-runtime = { path = "../../../substrate/primitives/runtime" }
[features]
default = ["std"]
std = [
"bp-messages/std",
"bp-relayers/std",
"bp-runtime/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-bridge-messages/std",
"scale-info/std",
"sp-arithmetic/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-bridge-messages/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"pallet-bridge-messages/try-runtime",
"sp-runtime/try-runtime",
]
-14
View File
@@ -1,14 +0,0 @@
# Bridge Relayers Pallet
The pallet 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 pallet 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 pallet 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 <> Polkadot bridge](../../docs/polkadot-kusama-bridge-overview.md) documentation.
@@ -1,131 +0,0 @@
// 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 Pallet.
#![cfg(feature = "runtime-benchmarks")]
use crate::*;
use bp_messages::LaneId;
use bp_relayers::RewardsAccountOwner;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_system::RawOrigin;
use sp_runtime::traits::One;
/// Reward amount that is (hopefully) is larger than existential deposit across all chains.
const REWARD_AMOUNT: u32 = u32::MAX;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config>(crate::Pallet<T>);
/// Trait that must be implemented by runtime.
pub trait Config: crate::Config {
/// Prepare environment for paying given reward for serving given lane.
fn prepare_rewards_account(account_params: RewardsAccountParams, reward: Self::Reward);
/// Give enough balance to given account.
fn deposit_account(account: Self::AccountId, balance: Self::Reward);
}
benchmarks! {
// Benchmark `claim_rewards` call.
claim_rewards {
let lane = LaneId([0, 0, 0, 0]);
let account_params =
RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain);
let relayer: T::AccountId = whitelisted_caller();
let reward = T::Reward::from(REWARD_AMOUNT);
T::prepare_rewards_account(account_params, reward);
RelayerRewards::<T>::insert(&relayer, account_params, reward);
}: _(RawOrigin::Signed(relayer), account_params)
verify {
// 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
}
// Benchmark `register` call.
register {
let relayer: T::AccountId = whitelisted_caller();
let valid_till = frame_system::Pallet::<T>::block_number()
.saturating_add(crate::Pallet::<T>::required_registration_lease())
.saturating_add(One::one())
.saturating_add(One::one());
T::deposit_account(relayer.clone(), crate::Pallet::<T>::required_stake());
}: _(RawOrigin::Signed(relayer.clone()), valid_till)
verify {
assert!(crate::Pallet::<T>::is_registration_active(&relayer));
}
// Benchmark `deregister` call.
deregister {
let relayer: T::AccountId = whitelisted_caller();
let valid_till = frame_system::Pallet::<T>::block_number()
.saturating_add(crate::Pallet::<T>::required_registration_lease())
.saturating_add(One::one())
.saturating_add(One::one());
T::deposit_account(relayer.clone(), crate::Pallet::<T>::required_stake());
crate::Pallet::<T>::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap();
frame_system::Pallet::<T>::set_block_number(valid_till.saturating_add(One::one()));
}: _(RawOrigin::Signed(relayer.clone()))
verify {
assert!(!crate::Pallet::<T>::is_registration_active(&relayer));
}
// Benchmark `slash_and_deregister` method of the pallet. We are adding this weight to
// the weight of message delivery call if `RefundBridgedParachainMessages` signed extension
// is deployed at runtime level.
slash_and_deregister {
// prepare and register relayer account
let relayer: T::AccountId = whitelisted_caller();
let valid_till = frame_system::Pallet::<T>::block_number()
.saturating_add(crate::Pallet::<T>::required_registration_lease())
.saturating_add(One::one())
.saturating_add(One::one());
T::deposit_account(relayer.clone(), crate::Pallet::<T>::required_stake());
crate::Pallet::<T>::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap();
// create slash destination account
let lane = LaneId([0, 0, 0, 0]);
let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain);
T::prepare_rewards_account(slash_destination, Zero::zero());
}: {
crate::Pallet::<T>::slash_and_deregister(&relayer, slash_destination)
}
verify {
assert!(!crate::Pallet::<T>::is_registration_active(&relayer));
}
// Benchmark `register_relayer_reward` method of the pallet. We are adding this weight to
// the weight of message delivery call if `RefundBridgedParachainMessages` signed extension
// is deployed at runtime level.
register_relayer_reward {
let lane = LaneId([0, 0, 0, 0]);
let relayer: T::AccountId = whitelisted_caller();
let account_params =
RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain);
}: {
crate::Pallet::<T>::register_relayer_reward(account_params, &relayer, One::one());
}
verify {
assert_eq!(RelayerRewards::<T>::get(relayer, &account_params), Some(One::one()));
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
}
-922
View File
@@ -1,922 +0,0 @@
// 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 is used to store relayer rewards and (in the future) to
//! coordinate relations between relayers.
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
use bp_relayers::{
PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash,
};
use bp_runtime::StorageDoubleMapKeyProvider;
use frame_support::fail;
use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero};
use sp_runtime::{traits::CheckedSub, Saturating};
use sp_std::marker::PhantomData;
pub use pallet::*;
pub use payment_adapter::DeliveryConfirmationPaymentsAdapter;
pub use stake_adapter::StakeAndSlashNamed;
pub use weights::WeightInfo;
pub use weights_ext::WeightInfoExt;
pub mod benchmarking;
mod mock;
mod payment_adapter;
mod stake_adapter;
mod weights_ext;
pub mod weights;
/// The target that will be used when publishing logs related to this pallet.
pub const LOG_TARGET: &str = "runtime::bridge-relayers";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
/// `RelayerRewardsKeyProvider` for given configuration.
type RelayerRewardsKeyProviderOf<T> =
RelayerRewardsKeyProvider<<T as frame_system::Config>::AccountId, <T as Config>::Reward>;
#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type of relayer reward.
type Reward: AtLeast32BitUnsigned + Copy + Parameter + MaxEncodedLen;
/// Pay rewards scheme.
type PaymentProcedure: PaymentProcedure<Self::AccountId, Self::Reward>;
/// Stake and slash scheme.
type StakeAndSlash: StakeAndSlash<Self::AccountId, BlockNumberFor<Self>, Self::Reward>;
/// Pallet call weights.
type WeightInfo: WeightInfoExt;
}
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Claim accumulated rewards.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::claim_rewards())]
pub fn claim_rewards(
origin: OriginFor<T>,
rewards_account_params: RewardsAccountParams,
) -> DispatchResult {
let relayer = ensure_signed(origin)?;
RelayerRewards::<T>::try_mutate_exists(
&relayer,
rewards_account_params,
|maybe_reward| -> DispatchResult {
let reward = maybe_reward.take().ok_or(Error::<T>::NoRewardForRelayer)?;
T::PaymentProcedure::pay_reward(&relayer, rewards_account_params, reward)
.map_err(|e| {
log::trace!(
target: LOG_TARGET,
"Failed to pay {:?} rewards to {:?}: {:?}",
rewards_account_params,
relayer,
e,
);
Error::<T>::FailedToPayReward
})?;
Self::deposit_event(Event::<T>::RewardPaid {
relayer: relayer.clone(),
rewards_account_params,
reward,
});
Ok(())
},
)
}
/// Register relayer or update its registration.
///
/// Registration allows relayer to get priority boost for its message delivery transactions.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::register())]
pub fn register(origin: OriginFor<T>, valid_till: BlockNumberFor<T>) -> DispatchResult {
let relayer = ensure_signed(origin)?;
// valid till must be larger than the current block number and the lease must be larger
// than the `RequiredRegistrationLease`
let lease = valid_till.saturating_sub(frame_system::Pallet::<T>::block_number());
ensure!(
lease > Pallet::<T>::required_registration_lease(),
Error::<T>::InvalidRegistrationLease
);
RegisteredRelayers::<T>::try_mutate(&relayer, |maybe_registration| -> DispatchResult {
let mut registration = maybe_registration
.unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() });
// new `valid_till` must be larger (or equal) than the old one
ensure!(
valid_till >= registration.valid_till,
Error::<T>::CannotReduceRegistrationLease,
);
registration.valid_till = valid_till;
// regarding stake, there are three options:
// - if relayer stake is larger than required stake, we may do unreserve
// - if relayer stake equals to required stake, we do nothing
// - if relayer stake is smaller than required stake, we do additional reserve
let required_stake = Pallet::<T>::required_stake();
if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) {
Self::do_unreserve(&relayer, to_unreserve)?;
} else if let Some(to_reserve) = required_stake.checked_sub(&registration.stake) {
T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| {
log::trace!(
target: LOG_TARGET,
"Failed to reserve {:?} on relayer {:?} account: {:?}",
to_reserve,
relayer,
e,
);
Error::<T>::FailedToReserve
})?;
}
registration.stake = required_stake;
log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer);
Self::deposit_event(Event::<T>::RegistrationUpdated {
relayer: relayer.clone(),
registration,
});
*maybe_registration = Some(registration);
Ok(())
})
}
/// `Deregister` relayer.
///
/// After this call, message delivery transactions of the relayer won't get any priority
/// boost.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::deregister())]
pub fn deregister(origin: OriginFor<T>) -> DispatchResult {
let relayer = ensure_signed(origin)?;
RegisteredRelayers::<T>::try_mutate(&relayer, |maybe_registration| -> DispatchResult {
let registration = match maybe_registration.take() {
Some(registration) => registration,
None => fail!(Error::<T>::NotRegistered),
};
// we can't deregister until `valid_till + 1`
ensure!(
registration.valid_till < frame_system::Pallet::<T>::block_number(),
Error::<T>::RegistrationIsStillActive,
);
// if stake is non-zero, we should do unreserve
if !registration.stake.is_zero() {
Self::do_unreserve(&relayer, registration.stake)?;
}
log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer);
Self::deposit_event(Event::<T>::Deregistered { relayer: relayer.clone() });
*maybe_registration = None;
Ok(())
})
}
}
impl<T: Config> Pallet<T> {
/// Returns true if given relayer registration is active at current block.
///
/// This call respects both `RequiredStake` and `RequiredRegistrationLease`, meaning that
/// it'll return false if registered stake is lower than required or if remaining lease
/// is less than `RequiredRegistrationLease`.
pub fn is_registration_active(relayer: &T::AccountId) -> bool {
let registration = match Self::registered_relayer(relayer) {
Some(registration) => registration,
None => return false,
};
// registration is inactive if relayer stake is less than required
if registration.stake < Self::required_stake() {
return false
}
// registration is inactive if it ends soon
let remaining_lease = registration
.valid_till
.saturating_sub(frame_system::Pallet::<T>::block_number());
if remaining_lease <= Self::required_registration_lease() {
return false
}
true
}
/// Slash and `deregister` relayer. This function slashes all staked balance.
///
/// It may fail inside, but error is swallowed and we only log it.
pub fn slash_and_deregister(
relayer: &T::AccountId,
slash_destination: RewardsAccountParams,
) {
let registration = match RegisteredRelayers::<T>::take(relayer) {
Some(registration) => registration,
None => {
log::trace!(
target: crate::LOG_TARGET,
"Cannot slash unregistered relayer {:?}",
relayer,
);
return
},
};
match T::StakeAndSlash::repatriate_reserved(
relayer,
slash_destination,
registration.stake,
) {
Ok(failed_to_slash) if failed_to_slash.is_zero() => {
log::trace!(
target: crate::LOG_TARGET,
"Relayer account {:?} has been slashed for {:?}. Funds were deposited to {:?}",
relayer,
registration.stake,
slash_destination,
);
},
Ok(failed_to_slash) => {
log::trace!(
target: crate::LOG_TARGET,
"Relayer account {:?} has been partially slashed for {:?}. Funds were deposited to {:?}. \
Failed to slash: {:?}",
relayer,
registration.stake,
slash_destination,
failed_to_slash,
);
},
Err(e) => {
// TODO: document this. Where?
// it may fail if there's no beneficiary account. For us it means that this
// account must exists before we'll deploy the bridge
log::debug!(
target: crate::LOG_TARGET,
"Failed to slash relayer account {:?}: {:?}. Maybe beneficiary account doesn't exist? \
Beneficiary: {:?}, amount: {:?}, failed to slash: {:?}",
relayer,
e,
slash_destination,
registration.stake,
registration.stake,
);
},
}
}
/// Register reward for given relayer.
pub fn register_relayer_reward(
rewards_account_params: RewardsAccountParams,
relayer: &T::AccountId,
reward: T::Reward,
) {
if reward.is_zero() {
return
}
RelayerRewards::<T>::mutate(
relayer,
rewards_account_params,
|old_reward: &mut Option<T::Reward>| {
let new_reward = old_reward.unwrap_or_else(Zero::zero).saturating_add(reward);
*old_reward = Some(new_reward);
log::trace!(
target: crate::LOG_TARGET,
"Relayer {:?} can now claim reward for serving payer {:?}: {:?}",
relayer,
rewards_account_params,
new_reward,
);
Self::deposit_event(Event::<T>::RewardRegistered {
relayer: relayer.clone(),
rewards_account_params,
reward,
});
},
);
}
/// Return required registration lease.
pub(crate) fn required_registration_lease() -> BlockNumberFor<T> {
<T::StakeAndSlash as StakeAndSlash<
T::AccountId,
BlockNumberFor<T>,
T::Reward,
>>::RequiredRegistrationLease::get()
}
/// Return required stake.
pub(crate) fn required_stake() -> T::Reward {
<T::StakeAndSlash as StakeAndSlash<
T::AccountId,
BlockNumberFor<T>,
T::Reward,
>>::RequiredStake::get()
}
/// `Unreserve` given amount on relayer account.
fn do_unreserve(relayer: &T::AccountId, amount: T::Reward) -> DispatchResult {
let failed_to_unreserve = T::StakeAndSlash::unreserve(relayer, amount);
if !failed_to_unreserve.is_zero() {
log::trace!(
target: LOG_TARGET,
"Failed to unreserve {:?}/{:?} on relayer {:?} account",
failed_to_unreserve,
amount,
relayer,
);
fail!(Error::<T>::FailedToUnreserve)
}
Ok(())
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Relayer reward has been registered and may be claimed later.
RewardRegistered {
/// Relayer account that can claim reward.
relayer: T::AccountId,
/// Relayer can claim reward from this account.
rewards_account_params: RewardsAccountParams,
/// Reward amount.
reward: T::Reward,
},
/// Reward has been paid to the relayer.
RewardPaid {
/// Relayer account that has been rewarded.
relayer: T::AccountId,
/// Relayer has received reward from this account.
rewards_account_params: RewardsAccountParams,
/// Reward amount.
reward: T::Reward,
},
/// Relayer registration has been added or updated.
RegistrationUpdated {
/// Relayer account that has been registered.
relayer: T::AccountId,
/// Relayer registration.
registration: Registration<BlockNumberFor<T>, T::Reward>,
},
/// Relayer has been `deregistered`.
Deregistered {
/// Relayer account that has been `deregistered`.
relayer: T::AccountId,
},
/// Relayer has been slashed and `deregistered`.
SlashedAndDeregistered {
/// Relayer account that has been `deregistered`.
relayer: T::AccountId,
/// Registration that was removed.
registration: Registration<BlockNumberFor<T>, T::Reward>,
},
}
#[pallet::error]
pub enum Error<T> {
/// No reward can be claimed by given relayer.
NoRewardForRelayer,
/// Reward payment procedure has failed.
FailedToPayReward,
/// The relayer has tried to register for past block or registration lease
/// is too short.
InvalidRegistrationLease,
/// New registration lease is less than the previous one.
CannotReduceRegistrationLease,
/// Failed to reserve enough funds on relayer account.
FailedToReserve,
/// Failed to `unreserve` enough funds on relayer account.
FailedToUnreserve,
/// Cannot `deregister` if not registered.
NotRegistered,
/// Failed to `deregister` relayer, because lease is still active.
RegistrationIsStillActive,
}
/// Map of the relayer => accumulated reward.
#[pallet::storage]
#[pallet::getter(fn relayer_reward)]
pub type RelayerRewards<T: Config> = StorageDoubleMap<
_,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Hasher1,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Key1,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Hasher2,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Key2,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Value,
OptionQuery,
>;
/// Relayers that have reserved some of their balance to get free priority boost
/// for their message delivery transactions.
///
/// Other relayers may submit transactions as well, but they will have default
/// priority and will be rejected (without significant tip) in case if registered
/// relayer is present.
#[pallet::storage]
#[pallet::getter(fn registered_relayer)]
pub type RegisteredRelayers<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
Registration<BlockNumberFor<T>, T::Reward>,
OptionQuery,
>;
}
#[cfg(test)]
mod tests {
use super::*;
use mock::{RuntimeEvent as TestEvent, *};
use crate::Event::{RewardPaid, RewardRegistered};
use bp_messages::LaneId;
use bp_relayers::RewardsAccountOwner;
use frame_support::{
assert_noop, assert_ok,
traits::fungible::{Inspect, Mutate},
};
use frame_system::{EventRecord, Pallet as System, Phase};
use sp_runtime::DispatchError;
fn get_ready_for_events() {
System::<TestRuntime>::set_block_number(1);
System::<TestRuntime>::reset_events();
}
#[test]
fn register_relayer_reward_emit_event() {
run_test(|| {
get_ready_for_events();
Pallet::<TestRuntime>::register_relayer_reward(
TEST_REWARDS_ACCOUNT_PARAMS,
&REGULAR_RELAYER,
100,
);
// Check if the `RewardRegistered` event was emitted.
assert_eq!(
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(RewardRegistered {
relayer: REGULAR_RELAYER,
rewards_account_params: TEST_REWARDS_ACCOUNT_PARAMS,
reward: 100
}),
topics: vec![],
}),
);
});
}
#[test]
fn root_cant_claim_anything() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::root(),
TEST_REWARDS_ACCOUNT_PARAMS
),
DispatchError::BadOrigin,
);
});
}
#[test]
fn relayer_cant_claim_if_no_reward_exists() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(REGULAR_RELAYER),
TEST_REWARDS_ACCOUNT_PARAMS
),
Error::<TestRuntime>::NoRewardForRelayer,
);
});
}
#[test]
fn relayer_cant_claim_if_payment_procedure_fails() {
run_test(|| {
RelayerRewards::<TestRuntime>::insert(
FAILING_RELAYER,
TEST_REWARDS_ACCOUNT_PARAMS,
100,
);
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(FAILING_RELAYER),
TEST_REWARDS_ACCOUNT_PARAMS
),
Error::<TestRuntime>::FailedToPayReward,
);
});
}
#[test]
fn relayer_can_claim_reward() {
run_test(|| {
get_ready_for_events();
RelayerRewards::<TestRuntime>::insert(
REGULAR_RELAYER,
TEST_REWARDS_ACCOUNT_PARAMS,
100,
);
assert_ok!(Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(REGULAR_RELAYER),
TEST_REWARDS_ACCOUNT_PARAMS
));
assert_eq!(
RelayerRewards::<TestRuntime>::get(REGULAR_RELAYER, TEST_REWARDS_ACCOUNT_PARAMS),
None
);
// Check if the `RewardPaid` event was emitted.
assert_eq!(
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(RewardPaid {
relayer: REGULAR_RELAYER,
rewards_account_params: TEST_REWARDS_ACCOUNT_PARAMS,
reward: 100
}),
topics: vec![],
}),
);
});
}
#[test]
fn pay_reward_from_account_actually_pays_reward() {
type Balances = pallet_balances::Pallet<TestRuntime>;
type PayLaneRewardFromAccount = bp_relayers::PayRewardFromAccount<Balances, AccountId>;
run_test(|| {
let in_lane_0 = RewardsAccountParams::new(
LaneId([0, 0, 0, 0]),
*b"test",
RewardsAccountOwner::ThisChain,
);
let out_lane_1 = RewardsAccountParams::new(
LaneId([0, 0, 0, 1]),
*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);
Balances::mint_into(&in_lane0_rewards_account, 100).unwrap();
Balances::mint_into(&out_lane1_rewards_account, 100).unwrap();
assert_eq!(Balances::balance(&in_lane0_rewards_account), 100);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
assert_eq!(Balances::balance(&1), 0);
PayLaneRewardFromAccount::pay_reward(&1, in_lane_0, 100).unwrap();
assert_eq!(Balances::balance(&in_lane0_rewards_account), 0);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
assert_eq!(Balances::balance(&1), 100);
PayLaneRewardFromAccount::pay_reward(&1, out_lane_1, 100).unwrap();
assert_eq!(Balances::balance(&in_lane0_rewards_account), 0);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 0);
assert_eq!(Balances::balance(&1), 200);
});
}
#[test]
fn register_fails_if_valid_till_is_a_past_block() {
run_test(|| {
System::<TestRuntime>::set_block_number(100);
assert_noop!(
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 50),
Error::<TestRuntime>::InvalidRegistrationLease,
);
});
}
#[test]
fn register_fails_if_valid_till_lease_is_less_than_required() {
run_test(|| {
System::<TestRuntime>::set_block_number(100);
assert_noop!(
Pallet::<TestRuntime>::register(
RuntimeOrigin::signed(REGISTER_RELAYER),
99 + Lease::get()
),
Error::<TestRuntime>::InvalidRegistrationLease,
);
});
}
#[test]
fn register_works() {
run_test(|| {
get_ready_for_events();
assert_ok!(Pallet::<TestRuntime>::register(
RuntimeOrigin::signed(REGISTER_RELAYER),
150
));
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
assert_eq!(
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
Some(Registration { valid_till: 150, stake: Stake::get() }),
);
assert_eq!(
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(Event::RegistrationUpdated {
relayer: REGISTER_RELAYER,
registration: Registration { valid_till: 150, stake: Stake::get() },
}),
topics: vec![],
}),
);
});
}
#[test]
fn register_fails_if_new_valid_till_is_lesser_than_previous() {
run_test(|| {
assert_ok!(Pallet::<TestRuntime>::register(
RuntimeOrigin::signed(REGISTER_RELAYER),
150
));
assert_noop!(
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 125),
Error::<TestRuntime>::CannotReduceRegistrationLease,
);
});
}
#[test]
fn register_fails_if_it_cant_unreserve_some_balance_if_required_stake_decreases() {
run_test(|| {
RegisteredRelayers::<TestRuntime>::insert(
REGISTER_RELAYER,
Registration { valid_till: 150, stake: Stake::get() + 1 },
);
assert_noop!(
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150),
Error::<TestRuntime>::FailedToUnreserve,
);
});
}
#[test]
fn register_unreserves_some_balance_if_required_stake_decreases() {
run_test(|| {
get_ready_for_events();
RegisteredRelayers::<TestRuntime>::insert(
REGISTER_RELAYER,
Registration { valid_till: 150, stake: Stake::get() + 1 },
);
TestStakeAndSlash::reserve(&REGISTER_RELAYER, Stake::get() + 1).unwrap();
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 1);
let free_balance = Balances::free_balance(REGISTER_RELAYER);
assert_ok!(Pallet::<TestRuntime>::register(
RuntimeOrigin::signed(REGISTER_RELAYER),
150
));
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance + 1);
assert_eq!(
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
Some(Registration { valid_till: 150, stake: Stake::get() }),
);
assert_eq!(
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(Event::RegistrationUpdated {
relayer: REGISTER_RELAYER,
registration: Registration { valid_till: 150, stake: Stake::get() }
}),
topics: vec![],
}),
);
});
}
#[test]
fn register_fails_if_it_cant_reserve_some_balance() {
run_test(|| {
Balances::set_balance(&REGISTER_RELAYER, 0);
assert_noop!(
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150),
Error::<TestRuntime>::FailedToReserve,
);
});
}
#[test]
fn register_fails_if_it_cant_reserve_some_balance_if_required_stake_increases() {
run_test(|| {
RegisteredRelayers::<TestRuntime>::insert(
REGISTER_RELAYER,
Registration { valid_till: 150, stake: Stake::get() - 1 },
);
Balances::set_balance(&REGISTER_RELAYER, 0);
assert_noop!(
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150),
Error::<TestRuntime>::FailedToReserve,
);
});
}
#[test]
fn register_reserves_some_balance_if_required_stake_increases() {
run_test(|| {
get_ready_for_events();
RegisteredRelayers::<TestRuntime>::insert(
REGISTER_RELAYER,
Registration { valid_till: 150, stake: Stake::get() - 1 },
);
TestStakeAndSlash::reserve(&REGISTER_RELAYER, Stake::get() - 1).unwrap();
let free_balance = Balances::free_balance(REGISTER_RELAYER);
assert_ok!(Pallet::<TestRuntime>::register(
RuntimeOrigin::signed(REGISTER_RELAYER),
150
));
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance - 1);
assert_eq!(
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
Some(Registration { valid_till: 150, stake: Stake::get() }),
);
assert_eq!(
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(Event::RegistrationUpdated {
relayer: REGISTER_RELAYER,
registration: Registration { valid_till: 150, stake: Stake::get() }
}),
topics: vec![],
}),
);
});
}
#[test]
fn deregister_fails_if_not_registered() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime>::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)),
Error::<TestRuntime>::NotRegistered,
);
});
}
#[test]
fn deregister_fails_if_registration_is_still_active() {
run_test(|| {
assert_ok!(Pallet::<TestRuntime>::register(
RuntimeOrigin::signed(REGISTER_RELAYER),
150
));
System::<TestRuntime>::set_block_number(100);
assert_noop!(
Pallet::<TestRuntime>::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)),
Error::<TestRuntime>::RegistrationIsStillActive,
);
});
}
#[test]
fn deregister_works() {
run_test(|| {
get_ready_for_events();
assert_ok!(Pallet::<TestRuntime>::register(
RuntimeOrigin::signed(REGISTER_RELAYER),
150
));
System::<TestRuntime>::set_block_number(151);
let reserved_balance = Balances::reserved_balance(REGISTER_RELAYER);
let free_balance = Balances::free_balance(REGISTER_RELAYER);
assert_ok!(Pallet::<TestRuntime>::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)));
assert_eq!(
Balances::reserved_balance(REGISTER_RELAYER),
reserved_balance - Stake::get()
);
assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance + Stake::get());
assert_eq!(
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(Event::Deregistered { relayer: REGISTER_RELAYER }),
topics: vec![],
}),
);
});
}
#[test]
fn is_registration_active_is_false_for_unregistered_relayer() {
run_test(|| {
assert!(!Pallet::<TestRuntime>::is_registration_active(&REGISTER_RELAYER));
});
}
#[test]
fn is_registration_active_is_false_when_stake_is_too_low() {
run_test(|| {
RegisteredRelayers::<TestRuntime>::insert(
REGISTER_RELAYER,
Registration { valid_till: 150, stake: Stake::get() - 1 },
);
assert!(!Pallet::<TestRuntime>::is_registration_active(&REGISTER_RELAYER));
});
}
#[test]
fn is_registration_active_is_false_when_remaining_lease_is_too_low() {
run_test(|| {
System::<TestRuntime>::set_block_number(150 - Lease::get());
RegisteredRelayers::<TestRuntime>::insert(
REGISTER_RELAYER,
Registration { valid_till: 150, stake: Stake::get() },
);
assert!(!Pallet::<TestRuntime>::is_registration_active(&REGISTER_RELAYER));
});
}
#[test]
fn is_registration_active_is_true_when_relayer_is_properly_registeered() {
run_test(|| {
System::<TestRuntime>::set_block_number(150 - Lease::get());
RegisteredRelayers::<TestRuntime>::insert(
REGISTER_RELAYER,
Registration { valid_till: 151, stake: Stake::get() },
);
assert!(Pallet::<TestRuntime>::is_registration_active(&REGISTER_RELAYER));
});
}
}
-149
View File
@@ -1,149 +0,0 @@
// 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 pallet_bridge_relayers;
use bp_messages::LaneId;
use bp_relayers::{
PayRewardFromAccount, PaymentProcedure, RewardsAccountOwner, RewardsAccountParams,
};
use frame_support::{
derive_impl, parameter_types, traits::fungible::Mutate, weights::RuntimeDbWeight,
};
use sp_runtime::BuildStorage;
pub type AccountId = u64;
pub type Balance = u64;
pub type BlockNumber = u64;
pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
AccountId,
BlockNumber,
Balances,
ReserveId,
Stake,
Lease,
>;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
frame_support::construct_runtime! {
pub enum TestRuntime
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Event<T>},
Relayers: pallet_bridge_relayers::{Pallet, Call, Event<T>},
}
}
parameter_types! {
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
pub const ExistentialDeposit: Balance = 1;
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
pub const Stake: Balance = 1_000;
pub const Lease: BlockNumber = 8;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
type AccountData = pallet_balances::AccountData<Balance>;
type DbWeight = DbWeight;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for TestRuntime {
type ReserveIdentifier = [u8; 8];
type AccountStore = System;
}
impl pallet_bridge_relayers::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type Reward = Balance;
type PaymentProcedure = TestPaymentProcedure;
type StakeAndSlash = TestStakeAndSlash;
type WeightInfo = ();
}
#[cfg(feature = "runtime-benchmarks")]
impl pallet_bridge_relayers::benchmarking::Config for TestRuntime {
fn prepare_rewards_account(account_params: RewardsAccountParams, reward: Balance) {
let rewards_account =
bp_relayers::PayRewardFromAccount::<Balances, AccountId>::rewards_account(
account_params,
);
Self::deposit_account(rewards_account, reward);
}
fn deposit_account(account: Self::AccountId, balance: Self::Reward) {
Balances::mint_into(&account, balance.saturating_add(ExistentialDeposit::get())).unwrap();
}
}
/// Message lane that we're using in tests.
pub const TEST_REWARDS_ACCOUNT_PARAMS: RewardsAccountParams =
RewardsAccountParams::new(LaneId([0, 0, 0, 0]), *b"test", RewardsAccountOwner::ThisChain);
/// Regular relayer that may receive rewards.
pub const REGULAR_RELAYER: AccountId = 1;
/// Relayer that can't receive rewards.
pub const FAILING_RELAYER: AccountId = 2;
/// Relayer that is able to register.
pub const REGISTER_RELAYER: AccountId = 42;
/// Payment procedure that rejects payments to the `FAILING_RELAYER`.
pub struct TestPaymentProcedure;
impl TestPaymentProcedure {
pub fn rewards_account(params: RewardsAccountParams) -> AccountId {
PayRewardFromAccount::<(), AccountId>::rewards_account(params)
}
}
impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
type Error = ();
fn pay_reward(
relayer: &AccountId,
_lane_id: RewardsAccountParams,
_reward: Balance,
) -> Result<(), Self::Error> {
match *relayer {
FAILING_RELAYER => Err(()),
_ => Ok(()),
}
}
}
/// Return test externalities to use in tests.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
sp_io::TestExternalities::new(t)
}
/// Run pallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
new_test_ext().execute_with(|| {
Balances::mint_into(&REGISTER_RELAYER, ExistentialDeposit::get() + 10 * Stake::get())
.unwrap();
test()
})
}
@@ -1,158 +0,0 @@
// 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 pallet to be used as a payment mechanism for the messages pallet.
use crate::{Config, Pallet};
use bp_messages::{
source_chain::{DeliveryConfirmationPayments, RelayersRewards},
LaneId, MessageNonce,
};
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
use frame_support::{sp_runtime::SaturatedConversion, traits::Get};
use sp_arithmetic::traits::{Saturating, Zero};
use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive};
/// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism
/// for the messages pallet.
pub struct DeliveryConfirmationPaymentsAdapter<T, MI, DeliveryReward>(
PhantomData<(T, MI, DeliveryReward)>,
);
impl<T, MI, DeliveryReward> DeliveryConfirmationPayments<T::AccountId>
for DeliveryConfirmationPaymentsAdapter<T, MI, DeliveryReward>
where
T: Config + pallet_bridge_messages::Config<MI>,
MI: 'static,
DeliveryReward: Get<T::Reward>,
{
type Error = &'static str;
fn pay_reward(
lane_id: LaneId,
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>(messages_relayers, received_range);
let rewarded_relayers = relayers_rewards.len();
register_relayers_rewards::<T>(
confirmation_relayer,
relayers_rewards,
RewardsAccountParams::new(
lane_id,
T::BridgedChainId::get(),
RewardsAccountOwner::BridgedChain,
),
DeliveryReward::get(),
);
rewarded_relayers as _
}
}
// Update rewards to given relayers, optionally rewarding confirmation relayer.
fn register_relayers_rewards<T: Config>(
confirmation_relayer: &T::AccountId,
relayers_rewards: RelayersRewards<T::AccountId>,
lane_id: RewardsAccountParams,
delivery_fee: T::Reward,
) {
// reward every relayer except `confirmation_relayer`
let mut confirmation_relayer_reward = T::Reward::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::Reward::saturated_from(messages).saturating_mul(delivery_fee);
if relayer != *confirmation_relayer {
Pallet::<T>::register_relayer_reward(lane_id, &relayer, relayer_reward);
} else {
confirmation_relayer_reward =
confirmation_relayer_reward.saturating_add(relayer_reward);
}
}
// finally - pay reward to confirmation relayer
Pallet::<T>::register_relayer_reward(
lane_id,
confirmation_relayer,
confirmation_relayer_reward,
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{mock::*, RelayerRewards};
const RELAYER_1: AccountId = 1;
const RELAYER_2: AccountId = 2;
const RELAYER_3: AccountId = 3;
fn relayers_rewards() -> RelayersRewards<AccountId> {
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_REWARDS_ACCOUNT_PARAMS,
50,
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_REWARDS_ACCOUNT_PARAMS),
Some(100)
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_REWARDS_ACCOUNT_PARAMS),
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_REWARDS_ACCOUNT_PARAMS,
50,
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_REWARDS_ACCOUNT_PARAMS),
Some(100)
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_REWARDS_ACCOUNT_PARAMS),
Some(150)
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_3, TEST_REWARDS_ACCOUNT_PARAMS),
None
);
});
}
}
@@ -1,186 +0,0 @@
// 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 pallet.
use bp_relayers::{PayRewardFromAccount, RewardsAccountParams, StakeAndSlash};
use codec::Codec;
use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency};
use sp_runtime::{traits::Get, DispatchError, DispatchResult};
use sp_std::{fmt::Debug, marker::PhantomData};
/// `StakeAndSlash` that works with `NamedReservableCurrency` and uses named
/// reservations.
///
/// **WARNING**: this implementation assumes that the relayers pallet 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: RewardsAccountParams,
amount: Currency::Balance,
) -> Result<Currency::Balance, DispatchError> {
let beneficiary_account =
PayRewardFromAccount::<(), AccountId>::rewards_account(beneficiary);
Currency::repatriate_reserved_named(
&ReserveId::get(),
relayer,
&beneficiary_account,
amount,
BalanceStatus::Free,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::*;
use frame_support::traits::fungible::Mutate;
fn test_stake() -> Balance {
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_REWARDS_ACCOUNT_PARAMS;
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, beneficiary, 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, beneficiary, 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, beneficiary, 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_REWARDS_ACCOUNT_PARAMS;
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, beneficiary, 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);
});
}
}
-259
View File
@@ -1,259 +0,0 @@
// 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 pallet_bridge_relayers
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE 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
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_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 frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_bridge_relayers.
pub trait WeightInfo {
fn claim_rewards() -> Weight;
fn register() -> Weight;
fn deregister() -> Weight;
fn slash_and_deregister() -> Weight;
fn register_relayer_reward() -> Weight;
}
/// Weights for `pallet_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: frame_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 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 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))
}
}
@@ -1,49 +0,0 @@
// 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 frame_support::pallet_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 `RefundBridgedParachainMessages` 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 `RefundBridgedParachainMessages` 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 {}
@@ -1,67 +0,0 @@
[package]
name = "pallet-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
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["bit-vec", "derive", "serde"] }
# Bridge dependencies
bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false }
# Substrate Dependencies
frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true }
frame-support = { path = "../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../substrate/frame/system", default-features = false }
sp-core = { path = "../../../substrate/primitives/core", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
# Polkadot Dependencies
xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false }
xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false }
[dev-dependencies]
sp-io = { path = "../../../substrate/primitives/io" }
sp-std = { path = "../../../substrate/primitives/std" }
[features]
default = ["std"]
std = [
"bp-xcm-bridge-hub-router/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
"xcm-builder/std",
"xcm/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
@@ -1,95 +0,0 @@
// 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 pallet benchmarks.
#![cfg(feature = "runtime-benchmarks")]
use crate::{Bridge, Call};
use bp_xcm_bridge_hub_router::{BridgeState, MINIMAL_DELIVERY_FEE_FACTOR};
use frame_benchmarking::{benchmarks_instance_pallet, BenchmarkError};
use frame_support::traits::{EnsureOrigin, Get, Hooks, UnfilteredDispatchable};
use sp_runtime::traits::Zero;
use xcm::prelude::*;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config<I>, I: 'static = ()>(crate::Pallet<T, I>);
/// Trait that must be implemented by runtime to be able to benchmark pallet 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: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR,
});
}: {
crate::Pallet::<T, I>::on_initialize(Zero::zero())
}
on_initialize_when_congested {
Bridge::<T, I>::put(BridgeState {
is_congested: false,
delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR,
});
let _ = T::ensure_bridged_target_destination()?;
T::make_congested();
}: {
crate::Pallet::<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);
}
send_message {
let dest = T::ensure_bridged_target_destination()?;
let xcm = sp_std::vec![].into();
// make local queue congested, because it means additional db write
T::make_congested();
}: {
send_xcm::<crate::Pallet<T, I>>(dest, xcm).expect("message is sent")
}
verify {
assert!(Bridge::<T, I>::get().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR);
}
}
@@ -1,568 +0,0 @@
// 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/>.
//! Pallet that may be used instead of `SovereignPaidRemoteExporter` in the XCM router
//! configuration. The main thing that the pallet 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 pallet deployed (`pallet-bridge-grandpa`, `pallet-bridge-messages`,
//! `pallet-xcm-bridge-hub`, ...). It may be the system bridge hub parachain or any other
//! chain.
#![cfg_attr(not(feature = "std"), no_std)]
use bp_xcm_bridge_hub_router::{
BridgeState, XcmChannelStatusProvider, MINIMAL_DELIVERY_FEE_FACTOR,
};
use codec::Encode;
use frame_support::traits::Get;
use sp_core::H256;
use sp_runtime::{FixedPointNumber, FixedU128, Saturating};
use xcm::prelude::*;
use xcm_builder::{ExporterFor, SovereignPaidRemoteExporter};
pub use pallet::*;
pub use weights::WeightInfo;
pub mod benchmarking;
pub mod weights;
mod mock;
/// The factor that is used to increase current message fee factor when bridge experiencing
/// some lags.
const EXPONENTIAL_FEE_BASE: FixedU128 = FixedU128::from_rational(105, 100); // 1.05
/// The factor that is used to increase current message fee factor for every sent kilobyte.
const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0.001
/// Maximal size of the XCM message that may be sent over bridge.
///
/// This should be less than the maximal size, allowed by the messages pallet, 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 pallet.
///
/// This doesn't match the pattern used by other bridge pallets (`runtime::bridge-*`). But this
/// pallet 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 parachain.
pub const LOG_TARGET: &str = "xcm::bridge-hub-router";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
/// Benchmarks results from runtime we're plugged into.
type WeightInfo: WeightInfo;
/// Universal location of this runtime.
type UniversalLocation: Get<InteriorLocation>;
/// 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;
/// Underlying channel with the sibling bridge hub. It must match the channel, used
/// by the `Self::ToBridgeHubSender`.
type WithBridgeHubChannel: 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>;
}
#[pallet::pallet]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
// TODO: make sure that `WithBridgeHubChannel::is_congested` returns true if either
// of XCM channels (outbound/inbound) is suspended. Because if outbound is suspended
// that is definitely congestion. If inbound is suspended, then we are not able to
// receive the "report_bridge_status" signal (that maybe sent by the bridge hub).
// if the channel with sibling/child bridge hub is suspended, we don't change
// anything
if T::WithBridgeHubChannel::is_congested() {
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()
}
// if fee factor is already minimal, we don't change anything
if bridge.delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR {
return T::WeightInfo::on_initialize_when_congested()
}
let previous_factor = bridge.delivery_fee_factor;
bridge.delivery_fee_factor =
MINIMAL_DELIVERY_FEE_FACTOR.max(bridge.delivery_fee_factor / EXPONENTIAL_FEE_BASE);
log::info!(
target: LOG_TARGET,
"Bridge queue is uncongested. Decreased fee factor from {} to {}",
previous_factor,
bridge.delivery_fee_factor,
);
Bridge::<T, I>::put(bridge);
T::WeightInfo::on_initialize_when_non_congested()
}
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Notification about congested bridge queue.
#[pallet::call_index(0)]
#[pallet::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 {
let _ = T::BridgeHubOrigin::ensure_origin(origin)?;
log::info!(
target: LOG_TARGET,
"Received bridge status from {:?}: congested = {}",
bridge_id,
is_congested,
);
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 pallet 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 pallet instance.
#[pallet::storage]
#[pallet::getter(fn bridge)]
pub type Bridge<T: Config<I>, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// 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) {
let _ = Bridge::<T, I>::try_mutate(|bridge| {
let is_channel_with_bridge_hub_congested = T::WithBridgeHubChannel::is_congested();
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(())
}
// ok - we need to increase the fee factor, let's do that
let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024))
.saturating_mul(MESSAGE_SIZE_FEE_BASE);
let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor);
let previous_factor = bridge.delivery_fee_factor;
bridge.delivery_fee_factor =
bridge.delivery_fee_factor.saturating_mul(total_factor);
log::info!(
target: LOG_TARGET,
"Bridge channel is congested. Increased fee factor from {} to {}",
previous_factor,
bridge.delivery_fee_factor,
);
Ok(())
});
}
}
}
/// We'll be using `SovereignPaidRemoteExporter` to send remote messages over the sibling/child
/// bridge hub.
type ViaBridgeHubExporter<T, I> = SovereignPaidRemoteExporter<
Pallet<T, I>,
<T as Config<I>>::ToBridgeHubSender,
<T as Config<I>>::UniversalLocation,
>;
// This pallet acts as the `ExporterFor` for the `SovereignPaidRemoteExporter` to compute
// message fee using fee factor.
impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> {
fn exporter_for(
network: &NetworkId,
remote_location: &InteriorLocation,
message: &Xcm<()>,
) -> Option<(Location, Option<Asset>)> {
// 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 {
log::trace!(
target: LOG_TARGET,
"Router with bridged_network_id {:?} does not support bridging to network {:?}!",
bridged_network,
network,
);
return None
}
}
// ensure that the message is sent to the expected bridged network and location.
let Some((bridge_hub_location, maybe_payment)) =
T::Bridges::exporter_for(network, remote_location, message)
else {
log::trace!(
target: LOG_TARGET,
"Router with bridged_network_id {:?} does not support bridging to network {:?} and remote_location {:?}!",
T::BridgedNetworkId::get(),
network,
remote_location,
);
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 => {
log::error!(
target: LOG_TARGET,
"Router with bridged_network_id {:?} is configured for `T::FeeAsset` {:?} which is not \
compatible with {:?} for bridge_hub_location: {:?} for bridging to {:?}/{:?}!",
T::BridgedNetworkId::get(),
T::FeeAsset::get(),
invalid_asset,
bridge_hub_location,
network,
remote_location,
);
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::bridge().delivery_fee_factor;
let fee = fee_factor.saturating_mul_int(fee_sum);
let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None };
log::info!(
target: LOG_TARGET,
"Going to send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}",
(network, remote_location),
message_size,
fee,
fee_factor
);
Some((bridge_hub_location, fee))
}
}
// This pallet 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 Pallet<T, I> {
type Ticket = (u32, <T::ToBridgeHubSender as SendXcm>::Ticket);
fn validate(
dest: &mut Option<Location>,
xcm: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
// `dest` and `xcm` are required here
let dest_ref = dest.as_ref().ok_or(SendError::MissingArgument)?;
let xcm_ref = xcm.as_ref().ok_or(SendError::MissingArgument)?;
// we won't have an access to `dest` and `xcm` in the `deliver` method, so precompute
// everything required here
let message_size = xcm_ref.encoded_size() as _;
// bridge doesn't support oversized/overweight messages now. So it is better to drop such
// messages here 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_ref)
.ok_or(SendError::DestinationUnsupported)?;
let _ = VersionedXcm::from(xcm_ref.clone())
.into_version(destination_version)
.map_err(|()| SendError::DestinationUnsupported)?;
// just use exporter to validate destination and insert instructions to pay message fee
// at the sibling/child bridge hub
//
// the cost will include both cost of: (1) to-sibling bridge hub delivery (returned by
// the `Config::ToBridgeHubSender`) and (2) to-bridged bridge hub delivery (returned by
// `Self::exporter_for`)
ViaBridgeHubExporter::<T, I>::validate(dest, xcm)
.map(|(ticket, cost)| ((message_size, ticket), cost))
}
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);
Ok(xcm_hash)
}
}
#[cfg(test)]
mod tests {
use super::*;
use frame_support::assert_ok;
use mock::*;
use frame_support::traits::Hooks;
use sp_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(MINIMAL_DELIVERY_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)));
TestWithBridgeHubChannel::make_congested();
// it should not decrease, because xcm channel is congested
let old_bridge = XcmBridgeHubRouter::bridge();
XcmBridgeHubRouter::on_initialize(One::one());
assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge);
})
}
#[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);
})
}
#[test]
fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() {
run_test(|| {
Bridge::<TestRuntime, ()>::put(uncongested_bridge(FixedU128::from_rational(125, 100)));
// it should eventually decreased to one
while XcmBridgeHubRouter::bridge().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR {
XcmBridgeHubRouter::on_initialize(One::one());
}
// verify that it doesn't decreases anymore
XcmBridgeHubRouter::on_initialize(One::one());
assert_eq!(
XcmBridgeHubRouter::bridge(),
uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR)
);
})
}
#[test]
fn not_applicable_if_destination_is_within_other_network() {
run_test(|| {
assert_eq!(
send_xcm::<XcmBridgeHubRouter>(
Location::new(2, [GlobalConsensus(Rococo), Parachain(1000)]),
vec![].into(),
),
Err(SendError::NotApplicable),
);
});
}
#[test]
fn exceeds_max_message_size_if_size_is_above_hard_limit() {
run_test(|| {
assert_eq!(
send_xcm::<XcmBridgeHubRouter>(
Location::new(2, [GlobalConsensus(Rococo), Parachain(1000)]),
vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into(),
),
Err(SendError::ExceedsMaxMessageSize),
);
});
}
#[test]
fn destination_unsupported_if_wrap_version_fails() {
run_test(|| {
assert_eq!(
send_xcm::<XcmBridgeHubRouter>(
UnknownXcmVersionLocation::get(),
vec![ClearOrigin].into(),
),
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_xcm_channel_is_uncongested() {
run_test(|| {
let old_bridge = XcmBridgeHubRouter::bridge();
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
vec![ClearOrigin].into(),
)
.map(drop));
assert!(TestToBridgeHubSender::is_message_sent());
assert_eq!(old_bridge, XcmBridgeHubRouter::bridge());
});
}
#[test]
fn sent_message_increases_factor_if_xcm_channel_is_congested() {
run_test(|| {
TestWithBridgeHubChannel::make_congested();
let old_bridge = XcmBridgeHubRouter::bridge();
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
vec![ClearOrigin].into(),
)
.map(drop));
assert!(TestToBridgeHubSender::is_message_sent());
assert!(
old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
);
});
}
#[test]
fn sent_message_increases_factor_if_bridge_has_reported_congestion() {
run_test(|| {
Bridge::<TestRuntime, ()>::put(congested_bridge(MINIMAL_DELIVERY_FEE_FACTOR));
let old_bridge = XcmBridgeHubRouter::bridge();
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
vec![ClearOrigin].into(),
)
.map(drop));
assert!(TestToBridgeHubSender::is_message_sent());
assert!(
old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
);
});
}
}
@@ -1,148 +0,0 @@
// 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 pallet_xcm_bridge_hub_router;
use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
use frame_support::{
construct_runtime, derive_impl, parameter_types,
traits::{Contains, Equals},
};
use frame_system::EnsureRoot;
use sp_runtime::{traits::ConstU128, BuildStorage};
use xcm::prelude::*;
use xcm_builder::{NetworkExportTable, NetworkExportTableItem};
pub type AccountId = u64;
type Block = frame_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: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
XcmBridgeHubRouter: pallet_xcm_bridge_hub_router::{Pallet, Storage},
}
}
parameter_types! {
pub ThisNetworkId: NetworkId = Polkadot;
pub BridgedNetworkId: NetworkId = Kusama;
pub UniversalLocation: InteriorLocation = [GlobalConsensus(ThisNetworkId::get()), Parachain(1000)].into();
pub SiblingBridgeHubLocation: Location = ParentThen([Parachain(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 UnknownXcmVersionLocation: Location = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(9999)]);
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
}
impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
type WeightInfo = ();
type UniversalLocation = UniversalLocation;
type BridgedNetworkId = BridgedNetworkId;
type Bridges = NetworkExportTable<BridgeTable>;
type DestinationVersion =
LatestOrNoneForLocationVersionChecker<Equals<UnknownXcmVersionLocation>>;
type BridgeHubOrigin = EnsureRoot<AccountId>;
type ToBridgeHubSender = TestToBridgeHubSender;
type WithBridgeHubChannel = TestWithBridgeHubChannel;
type ByteFee = ConstU128<BYTE_FEE>;
type FeeAsset = BridgeFeeAsset;
}
pub struct LatestOrNoneForLocationVersionChecker<Location>(sp_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 {
frame_support::storage::unhashed::get_or_default(b"TestToBridgeHubSender.Sent")
}
}
impl SendXcm for TestToBridgeHubSender {
type Ticket = ();
fn validate(
_destination: &mut Option<Location>,
_message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
Ok(((), (BridgeFeeAsset::get(), HRMP_FEE).into()))
}
fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> {
frame_support::storage::unhashed::put(b"TestToBridgeHubSender.Sent", &true);
Ok([0u8; 32])
}
}
pub struct TestWithBridgeHubChannel;
impl TestWithBridgeHubChannel {
pub fn make_congested() {
frame_support::storage::unhashed::put(b"TestWithBridgeHubChannel.Congested", &true);
}
}
impl XcmChannelStatusProvider for TestWithBridgeHubChannel {
fn is_congested() -> bool {
frame_support::storage::unhashed::get_or_default(b"TestWithBridgeHubChannel.Congested")
}
}
/// Return test externalities to use in tests.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
sp_io::TestExternalities::new(t)
}
/// Run pallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
new_test_ext().execute_with(test)
}
@@ -1,208 +0,0 @@
// 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 pallet_xcm_bridge_hub_router
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE 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
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_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 frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_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;
fn send_message() -> Weight;
}
/// Weights for `pallet_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: frame_system::Config> WeightInfo for BridgeWeight<T> {
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
///
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
/// 512, mode: `MaxEncodedLen`)
///
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
/// (r:1 w:0)
///
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
/// w:0)
fn on_initialize_when_non_congested() -> Weight {
// Proof Size summary in bytes:
// Measured: `53`
// Estimated: `3518`
// Minimum execution time: 11_934 nanoseconds.
Weight::from_parts(12_201_000, 3518)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
///
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
/// 512, mode: `MaxEncodedLen`)
///
/// 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: `94`
// Estimated: `3559`
// Minimum execution time: 9_010 nanoseconds.
Weight::from_parts(9_594_000, 3559)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(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))
}
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
///
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
/// 512, mode: `MaxEncodedLen`)
///
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
/// (r:1 w:0)
///
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
/// w:0)
fn send_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3517`
// Minimum execution time: 19_709 nanoseconds.
Weight::from_parts(20_110_000, 3517)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
///
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
/// 512, mode: `MaxEncodedLen`)
///
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
/// (r:1 w:0)
///
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
/// w:0)
fn on_initialize_when_non_congested() -> Weight {
// Proof Size summary in bytes:
// Measured: `53`
// Estimated: `3518`
// Minimum execution time: 11_934 nanoseconds.
Weight::from_parts(12_201_000, 3518)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
///
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
/// 512, mode: `MaxEncodedLen`)
///
/// 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: `94`
// Estimated: `3559`
// Minimum execution time: 9_010 nanoseconds.
Weight::from_parts(9_594_000, 3559)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(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))
}
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
///
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
/// 512, mode: `MaxEncodedLen`)
///
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
/// (r:1 w:0)
///
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
/// w:0)
fn send_message() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3517`
// Minimum execution time: 19_709 nanoseconds.
Weight::from_parts(20_110_000, 3517)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
}
-78
View File
@@ -1,78 +0,0 @@
[package]
name = "pallet-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
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
# Bridge Dependencies
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-xcm-bridge-hub = { path = "../../primitives/xcm-bridge-hub", default-features = false }
pallet-bridge-messages = { path = "../messages", default-features = false }
bridge-runtime-common = { path = "../../bin/runtime-common", default-features = false }
# Substrate Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../substrate/frame/system", default-features = false }
sp-core = { path = "../../../substrate/primitives/core", default-features = false }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
# Polkadot Dependencies
xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false }
xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false }
xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm/xcm-executor", default-features = false }
[dev-dependencies]
bp-header-chain = { path = "../../primitives/header-chain" }
pallet-balances = { path = "../../../substrate/frame/balances" }
sp-io = { path = "../../../substrate/primitives/io" }
[features]
default = ["std"]
std = [
"bp-messages/std",
"bp-runtime/std",
"bp-xcm-bridge-hub/std",
"bridge-runtime-common/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-bridge-messages/std",
"scale-info/std",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",
]
runtime-benchmarks = [
"bridge-runtime-common/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-bridge-messages/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"pallet-bridge-messages/try-runtime",
"sp-runtime/try-runtime",
]
@@ -1,206 +0,0 @@
// 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 pallet (`pallet-xcm-bridge-hub`) as XCM message
//! exporter at the sending bridge hub. Internally, it just enqueues outbound blob
//! in the messages pallet queue.
//!
//! This code is executed at the source bridge hub.
use crate::{Config, Pallet, LOG_TARGET};
use bp_messages::source_chain::MessagesBridge;
use bp_xcm_bridge_hub::XcmAsPlainPayload;
use bridge_runtime_common::messages_xcm_extension::{LocalXcmQueueManager, SenderAndLane};
use pallet_bridge_messages::{Config as BridgeMessagesConfig, Pallet as BridgeMessagesPallet};
use xcm::prelude::*;
use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter};
use xcm_executor::traits::ExportXcm;
/// 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 pallet.
type MessagesPallet<T, I> = BridgeMessagesPallet<T, <T as Config<I>>::BridgeMessagesPalletInstance>;
impl<T: Config<I>, I: 'static> ExportXcm for Pallet<T, I>
where
T: BridgeMessagesConfig<T::BridgeMessagesPalletInstance, OutboundPayload = XcmAsPlainPayload>,
{
type Ticket = (
SenderAndLane,
<MessagesPallet<T, I> as MessagesBridge<T::OutboundPayload>>::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> {
// Find supported lane_id.
let sender_and_lane = Self::lane_for(
universal_source.as_ref().ok_or(SendError::MissingArgument)?,
(&network, destination.as_ref().ok_or(SendError::MissingArgument)?),
)
.ok_or(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(sender_and_lane.lane, &blob)
.map_err(|e| {
log::debug!(
target: LOG_TARGET,
"XCM message {:?} cannot be exported because of bridge error {:?} on bridge {:?}",
id,
e,
sender_and_lane.lane,
);
SendError::Transport("BridgeValidateError")
})?;
Ok(((sender_and_lane, bridge_message, id), price))
}
fn deliver((sender_and_lane, bridge_message, id): Self::Ticket) -> Result<XcmHash, SendError> {
let lane_id = sender_and_lane.lane;
let artifacts = MessagesPallet::<T, I>::send_message(bridge_message);
log::info!(
target: LOG_TARGET,
"XCM message {:?} has been enqueued at bridge {:?} with nonce {}",
id,
lane_id,
artifacts.nonce,
);
// notify XCM queue manager about updated lane state
LocalXcmQueueManager::<T::LanesSupport>::on_bridge_message_enqueued(
&sender_and_lane,
artifacts.enqueued_messages,
);
Ok(id)
}
}
/// 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 pallet 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::*;
use frame_support::assert_ok;
use xcm_executor::traits::export_xcm;
fn universal_source() -> InteriorLocation {
[GlobalConsensus(RelayNetwork::get()), Parachain(SIBLING_ASSET_HUB_ID)].into()
}
fn universal_destination() -> InteriorLocation {
BridgedDestination::get()
}
#[test]
fn export_works() {
run_test(|| {
assert_ok!(export_xcm::<XcmOverBridge>(
BridgedRelayNetwork::get(),
0,
universal_source(),
universal_destination(),
vec![Instruction::ClearOrigin].into(),
));
})
}
#[test]
fn export_fails_if_argument_is_missing() {
run_test(|| {
assert_eq!(
XcmOverBridge::validate(
BridgedRelayNetwork::get(),
0,
&mut None,
&mut Some(universal_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(|| {
let expected_lane_id = TEST_LANE_ID;
assert_eq!(
XcmOverBridge::validate(
BridgedRelayNetwork::get(),
0,
&mut Some(universal_source()),
&mut Some(universal_destination()),
&mut Some(Vec::new().into()),
)
.unwrap()
.0
.0
.lane,
expected_lane_id,
);
})
}
}
-118
View File
@@ -1,118 +0,0 @@
// 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/>.
//! Module that adds XCM support to bridge pallets.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
use bridge_runtime_common::messages_xcm_extension::XcmBlobHauler;
use pallet_bridge_messages::Config as BridgeMessagesConfig;
use xcm::prelude::*;
pub use exporter::PalletAsHaulBlobExporter;
pub use pallet::*;
mod exporter;
mod mock;
/// The target that will be used when publishing logs related to this pallet.
pub const LOG_TARGET: &str = "runtime::bridge-xcm";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use bridge_runtime_common::messages_xcm_extension::SenderAndLane;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::BlockNumberFor;
#[pallet::config]
#[pallet::disable_frame_system_supertrait_check]
pub trait Config<I: 'static = ()>:
BridgeMessagesConfig<Self::BridgeMessagesPalletInstance>
{
/// Runtime's universal location.
type UniversalLocation: Get<InteriorLocation>;
// TODO: https://github.com/paritytech/parity-bridges-common/issues/1666 remove `ChainId` and
// replace it with the `NetworkId` - then we'll be able to use
// `T as pallet_bridge_messages::Config<T::BridgeMessagesPalletInstance>::BridgedChain::NetworkId`
/// Bridged network as relative location of bridged `GlobalConsensus`.
#[pallet::constant]
type BridgedNetwork: Get<Location>;
/// Associated messages pallet instance that bridges us with the
/// `BridgedNetworkId` consensus.
type BridgeMessagesPalletInstance: 'static;
/// Price of single message export to the bridged consensus (`Self::BridgedNetworkId`).
type MessageExportPrice: Get<Assets>;
/// Checks the XCM version for the destination.
type DestinationVersion: GetVersion;
/// Get point-to-point links with bridged consensus (`Self::BridgedNetworkId`).
/// (this will be replaced with dynamic on-chain bridges - `Bridges V2`)
type Lanes: Get<sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))>>;
/// Support for point-to-point links
/// (this will be replaced with dynamic on-chain bridges - `Bridges V2`)
type LanesSupport: XcmBlobHauler;
}
#[pallet::pallet]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
fn integrity_test() {
assert!(
Self::bridged_network_id().is_some(),
"Configured `T::BridgedNetwork`: {:?} does not contain `GlobalConsensus` junction with `NetworkId`",
T::BridgedNetwork::get()
)
}
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Returns dedicated/configured lane identifier.
pub(crate) fn lane_for(
source: &InteriorLocation,
dest: (&NetworkId, &InteriorLocation),
) -> Option<SenderAndLane> {
let source = source.clone().relative_to(&T::UniversalLocation::get());
// Check that we have configured a point-to-point lane for 'source' and `dest`.
T::Lanes::get()
.into_iter()
.find_map(|(lane_source, (lane_dest_network, lane_dest))| {
if lane_source.location == source &&
&lane_dest_network == dest.0 &&
Self::bridged_network_id().as_ref() == Some(dest.0) &&
&lane_dest == dest.1
{
Some(lane_source)
} else {
None
}
})
}
/// Returns some `NetworkId` if contains `GlobalConsensus` junction.
fn bridged_network_id() -> Option<NetworkId> {
match T::BridgedNetwork::get().take_first_interior() {
Some(GlobalConsensus(network)) => Some(network),
_ => None,
}
}
}
}
-317
View File
@@ -1,317 +0,0 @@
// 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 pallet_xcm_bridge_hub;
use bp_messages::{
target_chain::{DispatchMessage, MessageDispatch},
LaneId,
};
use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, UnderlyingChainProvider};
use bridge_runtime_common::{
messages::{
source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter,
BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages,
},
messages_xcm_extension::{SenderAndLane, XcmBlobHauler},
};
use codec::Encode;
use frame_support::{derive_impl, parameter_types, traits::ConstU32, weights::RuntimeDbWeight};
use sp_core::H256;
use sp_runtime::{
testing::Header as SubstrateHeader,
traits::{BlakeTwo256, IdentityLookup},
AccountId32, BuildStorage,
};
use xcm::prelude::*;
pub type AccountId = AccountId32;
pub type Balance = u64;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
pub const SIBLING_ASSET_HUB_ID: u32 = 2001;
pub const THIS_BRIDGE_HUB_ID: u32 = 2002;
pub const BRIDGED_ASSET_HUB_ID: u32 = 1001;
pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 1]);
frame_support::construct_runtime! {
pub enum TestRuntime {
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Event<T>},
Messages: pallet_bridge_messages::{Pallet, Call, Event<T>},
XcmOverBridge: pallet_xcm_bridge_hub::{Pallet},
}
}
parameter_types! {
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
pub const ExistentialDeposit: Balance = 1;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type AccountId = AccountId;
type AccountData = pallet_balances::AccountData<Balance>;
type Block = Block;
type Lookup = IdentityLookup<Self::AccountId>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for TestRuntime {
type AccountStore = System;
}
parameter_types! {
pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID];
}
impl pallet_bridge_messages::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = TestMessagesWeights;
type BridgedChainId = ();
type ActiveOutboundLanes = ActiveOutboundLanes;
type MaxUnrewardedRelayerEntriesAtInboundLane = ();
type MaxUnconfirmedMessagesAtInboundLane = ();
type MaximalOutboundPayloadSize = ConstU32<2048>;
type OutboundPayload = Vec<u8>;
type InboundPayload = Vec<u8>;
type InboundRelayer = ();
type DeliveryPayments = ();
type TargetHeaderChain = TargetHeaderChainAdapter<OnThisChainBridge>;
type DeliveryConfirmationPayments = ();
type OnMessagesDelivered = ();
type SourceHeaderChain = SourceHeaderChainAdapter<OnThisChainBridge>;
type MessageDispatch = TestMessageDispatch;
}
pub struct TestMessagesWeights;
impl pallet_bridge_messages::WeightInfo for TestMessagesWeights {
fn receive_single_message_proof() -> Weight {
Weight::zero()
}
fn receive_single_message_proof_with_outbound_lane_state() -> 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_two_messages_proof() -> Weight {
Weight::zero()
}
fn receive_single_message_proof_1_kb() -> Weight {
Weight::zero()
}
fn receive_single_message_proof_16_kb() -> Weight {
Weight::zero()
}
fn receive_single_message_proof_with_dispatch(_: u32) -> Weight {
Weight::from_parts(1, 0)
}
}
impl pallet_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 const BridgedRelayNetwork: NetworkId = NetworkId::Polkadot;
pub BridgedRelayNetworkLocation: Location = (Parent, GlobalConsensus(BridgedRelayNetwork::get())).into();
pub const NonBridgedRelayNetwork: NetworkId = NetworkId::Rococo;
pub const BridgeReserve: Balance = 100_000;
pub UniversalLocation: InteriorLocation = [
GlobalConsensus(RelayNetwork::get()),
Parachain(THIS_BRIDGE_HUB_ID),
].into();
pub const Penalty: Balance = 1_000;
}
impl pallet_xcm_bridge_hub::Config for TestRuntime {
type UniversalLocation = UniversalLocation;
type BridgedNetwork = BridgedRelayNetworkLocation;
type BridgeMessagesPalletInstance = ();
type MessageExportPrice = ();
type DestinationVersion = AlwaysLatest;
type Lanes = TestLanes;
type LanesSupport = TestXcmBlobHauler;
}
parameter_types! {
pub TestSenderAndLane: SenderAndLane = SenderAndLane {
location: Location::new(1, [Parachain(SIBLING_ASSET_HUB_ID)]),
lane: TEST_LANE_ID,
};
pub BridgedDestination: InteriorLocation = [
Parachain(BRIDGED_ASSET_HUB_ID)
].into();
pub TestLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))> = sp_std::vec![
(TestSenderAndLane::get(), (BridgedRelayNetwork::get(), BridgedDestination::get()))
];
}
pub struct TestXcmBlobHauler;
impl XcmBlobHauler for TestXcmBlobHauler {
type Runtime = TestRuntime;
type MessagesInstance = ();
type ToSourceChainSender = ();
type CongestedMessage = ();
type UncongestedMessage = ();
}
pub struct ThisChain;
impl Chain for ThisChain {
const ID: ChainId = *b"tuch";
type BlockNumber = u64;
type Hash = H256;
type Hasher = BlakeTwo256;
type Header = SubstrateHeader;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = u64;
type Signature = sp_runtime::MultiSignature;
fn max_extrinsic_size() -> u32 {
u32::MAX
}
fn max_extrinsic_weight() -> Weight {
Weight::MAX
}
}
pub struct BridgedChain;
pub type BridgedHeaderHash = H256;
pub type BridgedChainHeader = SubstrateHeader;
impl Chain for BridgedChain {
const ID: ChainId = *b"tuch";
type BlockNumber = u64;
type Hash = BridgedHeaderHash;
type Hasher = BlakeTwo256;
type Header = BridgedChainHeader;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = u64;
type Signature = sp_runtime::MultiSignature;
fn max_extrinsic_size() -> u32 {
4096
}
fn max_extrinsic_weight() -> Weight {
Weight::MAX
}
}
/// Test message dispatcher.
pub struct TestMessageDispatch;
impl TestMessageDispatch {
pub fn deactivate(lane: LaneId) {
frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
}
}
impl MessageDispatch for TestMessageDispatch {
type DispatchPayload = Vec<u8>;
type DispatchLevelResult = ();
fn is_active() -> bool {
frame_support::storage::unhashed::take::<bool>(&(b"inactive").encode()[..]) != Some(false)
}
fn dispatch_weight(_message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
Weight::zero()
}
fn dispatch(
_: DispatchMessage<Self::DispatchPayload>,
) -> MessageDispatchResult<Self::DispatchLevelResult> {
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
}
}
pub struct WrappedThisChain;
impl UnderlyingChainProvider for WrappedThisChain {
type Chain = ThisChain;
}
impl ThisChainWithMessages for WrappedThisChain {
type RuntimeOrigin = RuntimeOrigin;
}
pub struct WrappedBridgedChain;
impl UnderlyingChainProvider for WrappedBridgedChain {
type Chain = BridgedChain;
}
impl BridgedChainWithMessages for WrappedBridgedChain {}
pub struct BridgedHeaderChain;
impl bp_header_chain::HeaderChain<BridgedChain> for BridgedHeaderChain {
fn finalized_header_state_root(
_hash: HashOf<WrappedBridgedChain>,
) -> Option<HashOf<WrappedBridgedChain>> {
unreachable!()
}
}
/// Bridge that is deployed on `ThisChain` and allows sending/receiving messages to/from
/// `BridgedChain`.
#[derive(Debug, PartialEq, Eq)]
pub struct OnThisChainBridge;
impl MessageBridge for OnThisChainBridge {
const BRIDGED_MESSAGES_PALLET_NAME: &'static str = "";
type ThisChain = WrappedThisChain;
type BridgedChain = WrappedBridgedChain;
type BridgedHeaderChain = BridgedHeaderChain;
}
/// Run pallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
sp_io::TestExternalities::new(
frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(),
)
.execute_with(test)
}
@@ -1,49 +0,0 @@
[package]
name = "bp-header-chain"
description = "A common interface for describing what a bridge pallet 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
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
finality-grandpa = { version = "0.16.2", default-features = false }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
serde = { features = ["alloc", "derive"], workspace = true }
# Bridge dependencies
bp-runtime = { path = "../runtime", default-features = false }
# Substrate Dependencies
frame-support = { path = "../../../substrate/frame/support", default-features = false }
sp-core = { path = "../../../substrate/primitives/core", default-features = false, features = ["serde"] }
sp-consensus-grandpa = { path = "../../../substrate/primitives/consensus/grandpa", default-features = false, features = ["serde"] }
sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, features = ["serde"] }
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
[dev-dependencies]
bp-test-utils = { path = "../test-utils" }
hex = "0.4"
hex-literal = "0.4"
[features]
default = ["std"]
std = [
"bp-runtime/std",
"codec/std",
"finality-grandpa/std",
"frame-support/std",
"scale-info/std",
"serde/std",
"sp-consensus-grandpa/std",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -1,132 +0,0 @@
// 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 substrate/client/finality-grandpa/src/justification.rs. If origin
//! will ever be moved to the sp_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 bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::RuntimeDebugNoBound;
use scale_info::TypeInfo;
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature};
use sp_runtime::{traits::Header as HeaderT, RuntimeDebug, SaturatedConversion};
use sp_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, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound)]
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>,
}
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)
}
@@ -1,200 +0,0 @@
// 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 bp_runtime::{BlockNumberOf, HashOf, HeaderOf};
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit};
use sp_runtime::traits::Header as HeaderT;
use sp_std::{
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
prelude::*,
};
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,
sp_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>(sp_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())
}
}
@@ -1,333 +0,0 @@
// 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 bp_runtime::HeaderId;
use finality_grandpa::voter_set::VoterSet;
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, SetId};
use sp_runtime::{traits::Header as HeaderT, RuntimeDebug};
use sp_std::{
collections::{
btree_map::{
BTreeMap,
Entry::{Occupied, Vacant},
},
btree_set::BTreeSet,
},
prelude::*,
};
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(&current_hash) {
Some(parent_hash) => {
let is_visited_before = self.unvisited.get(&current_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 !sp_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,
) {
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(())
}
}

Some files were not shown because too many files have changed in this diff Show More