mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-19 14:41:02 +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