mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 13:07:56 +00:00
Remove bridges subtree
This commit is contained in:
committed by
Bastian Köcher
parent
d38f6e6728
commit
9a3e2c8c5a
@@ -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
|
||||
@@ -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
@@ -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>.
|
||||
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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"]
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms, 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(¶ms, 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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);
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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);
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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);
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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/).
|
||||
@@ -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.
|
||||
@@ -1,67 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Polkadot <> Kusama Bridge</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Polkadot <> Kusama Bridge</h1>
|
||||
<p>
|
||||
Our bridge connects two parachains - Kusama Bridge Hub and Polkadot Bridge Hub. Messages that
|
||||
are sent over bridge have XCM format and we are using existing architecture to dispatch them.
|
||||
Since both Polkadot, Kusama and their parachains already have means to exchange XCM messages
|
||||
within the same consensus system (HRMP, VMP, ...), it means that we are able to connect all those
|
||||
chains with our bridge.
|
||||
</p>
|
||||
<p>
|
||||
In our architecture, the lane that is used to relay messages over the bridge is determined by
|
||||
the XCM source and destinations. So e.g. bridge between Asset Hubs Polkadot and Kusama (and opposite direction)
|
||||
will use the lane 00000000, bridge between some other Polkadot Parachain and some other Kusama Parachain
|
||||
will use the lane 00000001 and so on.
|
||||
</p>
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
subgraph Polkadot Consensus
|
||||
polkadot(((Polkadot)))
|
||||
asset_hub_polkadot(((Polkadot Asset Hub)))
|
||||
polkadot_bh(((Polkadot Bridge Hub)))
|
||||
|
||||
polkadot---asset_hub_polkadot
|
||||
polkadot---polkadot_bh
|
||||
|
||||
asset_hub_polkadot-->|Send Message Using HRMP|polkadot_bh
|
||||
|
||||
polkadot_bh-->|Send Message Using HRMP|asset_hub_polkadot
|
||||
asset_hub_polkadot-->|Dispatch the Message|asset_hub_polkadot
|
||||
end
|
||||
subgraph Kusama Consensus
|
||||
kusama_bh(((Kusama Bridge Hub)))
|
||||
asset_hub_kusama(((Kusama Asset Hub)))
|
||||
kusama(((Kusama)))
|
||||
|
||||
kusama---asset_hub_kusama
|
||||
kusama---kusama_bh
|
||||
|
||||
kusama_bh-->|Send Message Using HRMP|asset_hub_kusama
|
||||
asset_hub_kusama-->|Dispatch the Message|asset_hub_kusama
|
||||
|
||||
asset_hub_kusama-->|Send Message Using HRMP|kusama_bh
|
||||
end
|
||||
|
||||
polkadot_bh<===>|Message is relayed to the Bridged Chain using lane 00000000|kusama_bh
|
||||
|
||||
linkStyle 2 stroke:red
|
||||
linkStyle 7 stroke:red
|
||||
linkStyle 8 stroke:red
|
||||
|
||||
linkStyle 3 stroke:green
|
||||
linkStyle 4 stroke:green
|
||||
linkStyle 9 stroke:green
|
||||
</div>
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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:
|
||||
|
||||

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

|
||||
|
||||
At any time, you can prolong your registration by calling the `register` with the larger `validTill`.
|
||||
|
||||
## Costs
|
||||
|
||||
Your relayer account (on both Bridge Hubs) must hold enough funds to be able to pay costs of bridge
|
||||
transactions. If your relayer behaves correctly, those costs will be compensated and you will be
|
||||
able to claim it later.
|
||||
|
||||
**IMPORTANT**: you may add tip to your bridge transactions to boost their priority. But our
|
||||
compensation mechanism never refunds transaction tip, so all tip tokens will be lost.
|
||||
|
||||
<details>
|
||||
<summary>Types of bridge transactions</summary>
|
||||
|
||||
There are two types of bridge transactions:
|
||||
|
||||
- message delivery transaction brings queued message(s) from one Bridge Hub to another. We record
|
||||
the fact that this specific (your) relayer has delivered those messages;
|
||||
|
||||
- message confirmation transaction confirms that some message have been delivered and also brings
|
||||
back information on how many messages (your) relayer has delivered. We use this information later
|
||||
to register delivery rewards on the source chain.
|
||||
|
||||
Several messages/confirmations may be included in a single bridge transaction. Apart from this
|
||||
data, bridge transaction may include finality and storage proofs, required to prove authenticity of
|
||||
this data.
|
||||
|
||||
</details>
|
||||
|
||||
To deliver and get reward for a single message, the relayer needs to submit two transactions. One
|
||||
at the source Bridge Hub and one at the target Bridge Hub. Below are costs for 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):
|
||||
|
||||

|
||||
|
||||
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*
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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/).
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
@@ -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))),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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>>();
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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(
|
||||
¶chains,
|
||||
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(
|
||||
¶chains,
|
||||
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(
|
||||
¶chains,
|
||||
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)
|
||||
}
|
||||
@@ -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
@@ -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(),
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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(®istration.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,
|
||||
®ULAR_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(®ISTER_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(®ISTER_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(®ISTER_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(®ISTER_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(®ISTER_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(®ISTER_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(®ISTER_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(®ISTER_RELAYER));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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(®ISTER_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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(¤t_hash) {
|
||||
Some(parent_hash) => {
|
||||
let is_visited_before = self.unvisited.get(¤t_hash).is_none();
|
||||
if is_visited_before {
|
||||
// If the current header has been visited in a previous call, it is a
|
||||
// descendent of `base` (we assume that the previous call was successful).
|
||||
return Some(route)
|
||||
}
|
||||
route.push(current_hash);
|
||||
|
||||
*parent_hash
|
||||
},
|
||||
None => return None,
|
||||
};
|
||||
}
|
||||
|
||||
Some(route)
|
||||
}
|
||||
|
||||
fn mark_route_as_visited(&mut self, route: Vec<Header::Hash>) {
|
||||
for hash in route {
|
||||
self.unvisited.remove(&hash);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_fully_visited(&self) -> bool {
|
||||
self.unvisited.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Could not convert `AuthorityList` to `VoterSet`.
|
||||
InvalidAuthorityList,
|
||||
/// Justification is finalizing unexpected header.
|
||||
InvalidJustificationTarget,
|
||||
/// The justification contains duplicate headers in its `votes_ancestries` field.
|
||||
DuplicateVotesAncestries,
|
||||
/// Error validating a precommit
|
||||
Precommit(PrecommitError),
|
||||
/// The cumulative weight of all votes in the justification is not enough to justify commit
|
||||
/// header finalization.
|
||||
TooLowCumulativeWeight,
|
||||
/// The justification contains extra (unused) headers in its `votes_ancestries` field.
|
||||
RedundantVotesAncestries,
|
||||
}
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum PrecommitError {
|
||||
/// Justification contains redundant votes.
|
||||
RedundantAuthorityVote,
|
||||
/// Justification contains unknown authority precommit.
|
||||
UnknownAuthorityVote,
|
||||
/// Justification contains duplicate authority precommit.
|
||||
DuplicateAuthorityVote,
|
||||
/// The authority has provided an invalid signature.
|
||||
InvalidAuthoritySignature,
|
||||
/// The justification contains precommit for header that is not a descendant of the commit
|
||||
/// header.
|
||||
UnrelatedAncestryVote,
|
||||
}
|
||||
|
||||
/// The context needed for validating GRANDPA finality proofs.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct JustificationVerificationContext {
|
||||
/// The authority set used to verify the justification.
|
||||
pub voter_set: VoterSet<AuthorityId>,
|
||||
/// The ID of the authority set used to verify the justification.
|
||||
pub authority_set_id: SetId,
|
||||
}
|
||||
|
||||
impl TryFrom<AuthoritySet> for JustificationVerificationContext {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(authority_set: AuthoritySet) -> Result<Self, Self::Error> {
|
||||
let voter_set =
|
||||
VoterSet::new(authority_set.authorities).ok_or(Error::InvalidAuthorityList)?;
|
||||
Ok(JustificationVerificationContext { voter_set, authority_set_id: authority_set.set_id })
|
||||
}
|
||||
}
|
||||
|
||||
enum IterationFlow {
|
||||
Run,
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// Verification callbacks.
|
||||
trait JustificationVerifier<Header: HeaderT> {
|
||||
/// Called when there are duplicate headers in the votes ancestries.
|
||||
fn process_duplicate_votes_ancestries(
|
||||
&mut self,
|
||||
duplicate_votes_ancestries: Vec<usize>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn process_redundant_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_known_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
signed: &SignedPrecommit<Header>,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_unknown_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError>;
|
||||
|
||||
fn process_unrelated_ancestry_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_invalid_signature_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError>;
|
||||
|
||||
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>);
|
||||
|
||||
/// Called when there are redundant headers in the votes ancestries.
|
||||
fn process_redundant_votes_ancestries(
|
||||
&mut self,
|
||||
redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn verify_justification(
|
||||
&mut self,
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
// ensure that it is justification for the expected header
|
||||
if (justification.commit.target_hash, justification.commit.target_number) !=
|
||||
finalized_target
|
||||
{
|
||||
return Err(Error::InvalidJustificationTarget)
|
||||
}
|
||||
|
||||
let threshold = context.voter_set.threshold().get();
|
||||
let (mut chain, ignored_idxs) = AncestryChain::new(justification);
|
||||
let mut signature_buffer = Vec::new();
|
||||
let mut cumulative_weight = 0u64;
|
||||
|
||||
if !ignored_idxs.is_empty() {
|
||||
self.process_duplicate_votes_ancestries(ignored_idxs)?;
|
||||
}
|
||||
|
||||
for (precommit_idx, signed) in justification.commit.precommits.iter().enumerate() {
|
||||
if cumulative_weight >= threshold {
|
||||
let action =
|
||||
self.process_redundant_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// authority must be in the set
|
||||
let authority_info = match context.voter_set.get(&signed.id) {
|
||||
Some(authority_info) => {
|
||||
// The implementer may want to do extra checks here.
|
||||
// For example to see if the authority has already voted in the same round.
|
||||
let action = self
|
||||
.process_known_authority_vote(precommit_idx, signed)
|
||||
.map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue
|
||||
}
|
||||
|
||||
authority_info
|
||||
},
|
||||
None => {
|
||||
self.process_unknown_authority_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
// all precommits must be descendants of the target block
|
||||
let maybe_route =
|
||||
chain.ancestry(&signed.precommit.target_hash, &signed.precommit.target_number);
|
||||
if maybe_route.is_none() {
|
||||
let action = self
|
||||
.process_unrelated_ancestry_vote(precommit_idx)
|
||||
.map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// verify authority signature
|
||||
if !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
Reference in New Issue
Block a user