Skip to content
GitLab
Explore
Sign in
parity
Mirrored projects
polkadot-sdk
Compare revisions
be1d7d05e5f32b1bfa67aec2f6344d2c6ff60f50 to aceda4659509d426d364188fa72555de58b887ba
Expand all
Show whitespace changes
Inline
Side-by-side
bridges/chains/chain-rococo/src/lib.rs
View file @
aceda465
...
...
@@ -67,6 +67,8 @@ 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"
;
/// Name of the With-Rococo parachains pallet instance that is deployed at bridged chains.
pub
const
WITH_ROCOCO_BRIDGE_PARACHAINS_PALLET_NAME
:
&
str
=
"BridgeRococoParachains"
;
/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Rococo
/// parachains.
...
...
bridges/chains/chain-westend/src/lib.rs
View file @
aceda465
...
...
@@ -67,6 +67,8 @@ 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"
;
/// Name of the With-Westend parachains pallet instance that is deployed at bridged chains.
pub
const
WITH_WESTEND_BRIDGE_PARACHAINS_PALLET_NAME
:
&
str
=
"BridgeWestendParachains"
;
/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Westend
/// parachains.
...
...
bridges/modules/grandpa/src/call_ext.rs
View file @
aceda465
...
...
@@ -15,20 +15,24 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use
crate
::{
weights
::
WeightInfo
,
BridgedBlockNumber
,
BridgedHeader
,
Config
,
CurrentAuthoritySet
,
Error
,
Pallet
,
weights
::
WeightInfo
,
BestFinalized
,
BridgedBlockNumber
,
BridgedHeader
,
Config
,
CurrentAuthoritySet
,
Error
,
FreeHeadersRemaining
,
Pallet
,
};
use
bp_header_chain
::{
justification
::
GrandpaJustification
,
max_expected_submit_finality_proof_arguments_size
,
ChainWithGrandpa
,
GrandpaConsensusLogReader
,
};
use
bp_runtime
::{
BlockNumberOf
,
OwnedBridgeModule
};
use
bp_runtime
::{
BlockNumberOf
,
Chain
,
OwnedBridgeModule
};
use
codec
::
Encode
;
use
frame_support
::{
dispatch
::
CallableCallFor
,
traits
::
IsSubType
,
weights
::
Weight
};
use
frame_support
::{
dispatch
::
CallableCallFor
,
traits
::{
Get
,
IsSubType
},
weights
::
Weight
,
};
use
sp_consensus_grandpa
::
SetId
;
use
sp_runtime
::{
traits
::{
Header
,
Zero
},
transaction_validity
::{
InvalidTransaction
,
TransactionValidity
,
ValidTransaction
},
traits
::{
CheckedSub
,
Header
,
Zero
},
transaction_validity
::{
InvalidTransaction
,
TransactionValidity
Error
},
RuntimeDebug
,
SaturatedConversion
,
};
...
...
@@ -40,6 +44,11 @@ pub struct SubmitFinalityProofInfo<N> {
/// An identifier of the validators set that has signed the submitted justification.
/// It might be `None` if deprecated version of the `submit_finality_proof` is used.
pub
current_set_id
:
Option
<
SetId
>
,
/// If `true`, then the call proves new **mandatory** header.
pub
is_mandatory
:
bool
,
/// If `true`, then the call must be free (assuming that everything else is valid) to
/// be treated as valid.
pub
is_free_execution_expected
:
bool
,
/// Extra weight that we assume is included in the call.
///
/// We have some assumptions about headers and justifications of the bridged chain.
...
...
@@ -54,6 +63,16 @@ pub struct SubmitFinalityProofInfo<N> {
pub
extra_size
:
u32
,
}
/// Verified `SubmitFinalityProofInfo<N>`.
#[derive(Copy,
Clone,
PartialEq,
RuntimeDebug)]
pub
struct
VerifiedSubmitFinalityProofInfo
<
N
>
{
/// Base call information.
pub
base
:
SubmitFinalityProofInfo
<
N
>
,
/// A difference between bundled bridged header and best bridged header known to us
/// before the call.
pub
improved_by
:
N
,
}
impl
<
N
>
SubmitFinalityProofInfo
<
N
>
{
/// Returns `true` if call size/weight is below our estimations for regular calls.
pub
fn
fits_limits
(
&
self
)
->
bool
{
...
...
@@ -67,14 +86,86 @@ pub struct SubmitFinalityProofHelper<T: Config<I>, I: 'static> {
}
impl
<
T
:
Config
<
I
>
,
I
:
'static
>
SubmitFinalityProofHelper
<
T
,
I
>
{
/// Returns `true` if we may fit more free headers into the current block. If `false` is
/// returned, the call will be paid even if `is_free_execution_expected` has been set
/// to `true`.
pub
fn
has_free_header_slots
()
->
bool
{
// `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept
// this header for free. That is a small cheat - it is `None` if executed outside of
// transaction (e.g. during block initialization). Normal relayer would never submit
// such calls, but if he did, that is not our problem. During normal transactions,
// the `FreeHeadersRemaining` is always `Some(_)`.
let
free_headers_remaining
=
FreeHeadersRemaining
::
<
T
,
I
>
::
get
()
.unwrap_or
(
u32
::
MAX
);
free_headers_remaining
>
0
}
/// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the
/// best one we know (2) if `current_set_id` matches the current authority set id, if specified
/// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected`
/// is `true`.
///
/// Returns number of headers between the current best finalized header, known to the pallet
/// and the bundled header.
pub
fn
check_obsolete_from_extension
(
call_info
:
&
SubmitFinalityProofInfo
<
BlockNumberOf
<
T
::
BridgedChain
>>
,
)
->
Result
<
BlockNumberOf
<
T
::
BridgedChain
>
,
Error
<
T
,
I
>>
{
// do basic checks first
let
improved_by
=
Self
::
check_obsolete
(
call_info
.block_number
,
call_info
.current_set_id
)
?
;
// if submitter has NOT specified that it wants free execution, then we are done
if
!
call_info
.is_free_execution_expected
{
return
Ok
(
improved_by
);
}
// else - if we can not accept more free headers, "reject" the transaction
if
!
Self
::
has_free_header_slots
()
{
log
::
trace!
(
target
:
crate
::
LOG_TARGET
,
"Cannot accept free {:?} header {:?}. No more free slots remaining"
,
T
::
BridgedChain
::
ID
,
call_info
.block_number
,
);
return
Err
(
Error
::
<
T
,
I
>
::
FreeHeadersLimitExceded
);
}
// ensure that the `improved_by` is larger than the configured free interval
if
!
call_info
.is_mandatory
{
if
let
Some
(
free_headers_interval
)
=
T
::
FreeHeadersInterval
::
get
()
{
if
improved_by
<
free_headers_interval
.into
()
{
log
::
trace!
(
target
:
crate
::
LOG_TARGET
,
"Cannot accept free {:?} header {:?}. Too small difference
\
between submitted headers: {:?} vs {}"
,
T
::
BridgedChain
::
ID
,
call_info
.block_number
,
improved_by
,
free_headers_interval
,
);
return
Err
(
Error
::
<
T
,
I
>
::
BelowFreeHeaderInterval
);
}
}
}
// we do not check whether the header matches free submission criteria here - it is the
// relayer responsibility to check that
Ok
(
improved_by
)
}
/// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best
/// one we know. Additionally, checks if `current_set_id` matches the current authority set
/// id, if specified.
/// id, if specified. This method is called by the call code and the transaction extension,
/// so it does not check the free execution.
///
/// Returns number of headers between the current best finalized header, known to the pallet
/// and the bundled header.
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
(||
{
)
->
Result
<
BlockNumberOf
<
T
::
BridgedChain
>
,
Error
<
T
,
I
>>
{
let
best_finalized
=
BestFinalized
::
<
T
,
I
>
::
get
()
.ok_or_else
(||
{
log
::
trace!
(
target
:
crate
::
LOG_TARGET
,
"Cannot finalize header {:?} because pallet is not yet initialized"
,
...
...
@@ -83,7 +174,9 @@ impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
<
Error
<
T
,
I
>>
::
NotInitialized
})
?
;
if
best_finalized
.number
()
>=
finality_target
{
let
improved_by
=
match
finality_target
.checked_sub
(
&
best_finalized
.number
())
{
Some
(
improved_by
)
if
improved_by
>
Zero
::
zero
()
=>
improved_by
,
_
=>
{
log
::
trace!
(
target
:
crate
::
LOG_TARGET
,
"Cannot finalize obsolete header: bundled {:?}, best {:?}"
,
...
...
@@ -92,7 +185,8 @@ impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
);
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
;
...
...
@@ -108,12 +202,12 @@ impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
}
}
Ok
(
()
)
Ok
(
improved_by
)
}
/// Check if the `SubmitFinalityProof` was successfully executed.
pub
fn
was_successful
(
finality_target
:
BlockNumberOf
<
T
::
BridgedChain
>
)
->
bool
{
match
crate
::
BestFinalized
::
<
T
,
I
>
::
get
()
{
match
BestFinalized
::
<
T
,
I
>
::
get
()
{
Some
(
best_finalized
)
=>
best_finalized
.number
()
==
finality_target
,
None
=>
false
,
}
...
...
@@ -135,17 +229,20 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
finality_target
,
justification
,
None
,
false
,
))
}
else
if
let
Some
(
crate
::
Call
::
<
T
,
I
>
::
submit_finality_proof_ex
{
finality_target
,
justification
,
current_set_id
,
is_free_execution_expected
,
})
=
self
.is_sub_type
()
{
return
Some
(
submit_finality_proof_info_from_args
::
<
T
,
I
>
(
finality_target
,
justification
,
Some
(
*
current_set_id
),
*
is_free_execution_expected
,
))
}
...
...
@@ -155,26 +252,36 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
/// 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
///
/// Returns `Ok(None)` if the call is not the `submit_finality_proof` call of our pallet.
/// Returns `Ok(Some(_))` if the call is the `submit_finality_proof` call of our pallet and
/// we believe the call brings header that improves the pallet state.
/// Returns `Err(_)` if the call is the `submit_finality_proof` call of our pallet and we
/// believe that the call will fail.
fn
check_obsolete_submit_finality_proof
(
&
self
,
)
->
Result
<
Option
<
VerifiedSubmitFinalityProofInfo
<
BridgedBlockNumber
<
T
,
I
>>>
,
TransactionValidityError
,
>
where
Self
:
Sized
,
{
let
finality_target
=
match
self
.submit_finality_proof_info
()
{
let
call_info
=
match
self
.submit_finality_proof_info
()
{
Some
(
finality_proof
)
=>
finality_proof
,
_
=>
return
Ok
(
ValidTransaction
::
default
()
),
_
=>
return
Ok
(
None
),
};
if
Pallet
::
<
T
,
I
>
::
ensure_not_halted
()
.is_err
()
{
return
InvalidTransaction
::
Call
.into
()
return
Err
(
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
(),
let
result
=
SubmitFinalityProofHelper
::
<
T
,
I
>
::
check_obsolete_from_extension
(
&
call_info
);
match
result
{
Ok
(
improved_by
)
=>
Ok
(
Some
(
VerifiedSubmitFinalityProofInfo
{
base
:
call_info
,
improved_by
})),
Err
(
Error
::
<
T
,
I
>
::
OldHeader
)
=>
Err
(
InvalidTransaction
::
Stale
.into
()),
Err
(
_
)
=>
Err
(
InvalidTransaction
::
Call
.into
()),
}
}
}
...
...
@@ -189,6 +296,7 @@ pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
finality_target
:
&
BridgedHeader
<
T
,
I
>
,
justification
:
&
GrandpaJustification
<
BridgedHeader
<
T
,
I
>>
,
current_set_id
:
Option
<
SetId
>
,
is_free_execution_expected
:
bool
,
)
->
SubmitFinalityProofInfo
<
BridgedBlockNumber
<
T
,
I
>>
{
let
block_number
=
*
finality_target
.number
();
...
...
@@ -230,16 +338,26 @@ pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
);
let
extra_size
=
actual_call_size
.saturating_sub
(
max_expected_call_size
);
SubmitFinalityProofInfo
{
block_number
,
current_set_id
,
extra_weight
,
extra_size
}
SubmitFinalityProofInfo
{
block_number
,
current_set_id
,
is_mandatory
:
is_mandatory_finality_target
,
is_free_execution_expected
,
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
,
mock
::{
run_test
,
test_header
,
FreeHeadersInterval
,
RuntimeCall
,
TestBridgedChain
,
TestNumber
,
TestRuntime
,
},
BestFinalized
,
Config
,
CurrentAuthoritySet
,
FreeHeadersRemaining
,
PalletOperatingMode
,
StoredAuthoritySet
,
SubmitFinalityProofInfo
,
WeightInfo
,
};
use
bp_header_chain
::
ChainWithGrandpa
;
use
bp_runtime
::{
BasicOperatingMode
,
HeaderId
};
...
...
@@ -247,6 +365,7 @@ mod tests {
make_default_justification
,
make_justification_for_header
,
JustificationGeneratorParams
,
TEST_GRANDPA_SET_ID
,
};
use
codec
::
Encode
;
use
frame_support
::
weights
::
Weight
;
use
sp_runtime
::{
testing
::
DigestItem
,
traits
::
Header
as
_
,
SaturatedConversion
};
...
...
@@ -256,6 +375,7 @@ mod tests {
justification
:
make_default_justification
(
&
test_header
(
num
)),
// not initialized => zero
current_set_id
:
0
,
is_free_execution_expected
:
false
,
};
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
RuntimeCall
::
Grandpa
(
bridge_grandpa_call
,
...
...
@@ -311,6 +431,121 @@ mod tests {
});
}
#[test]
fn
extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted
(
)
{
run_test
(||
{
let
bridge_grandpa_call
=
crate
::
Call
::
<
TestRuntime
,
()
>
::
submit_finality_proof_ex
{
finality_target
:
Box
::
new
(
test_header
(
10
+
FreeHeadersInterval
::
get
()
as
u64
)),
justification
:
make_default_justification
(
&
test_header
(
10
+
FreeHeadersInterval
::
get
()
as
u64
,
)),
current_set_id
:
0
,
is_free_execution_expected
:
true
,
};
sync_to_header_10
();
// when we can accept free headers => Ok
FreeHeadersRemaining
::
<
TestRuntime
,
()
>
::
put
(
2
);
assert!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
RuntimeCall
::
Grandpa
(
bridge_grandpa_call
.clone
(),
),)
.is_ok
());
// when we can NOT accept free headers => Err
FreeHeadersRemaining
::
<
TestRuntime
,
()
>
::
put
(
0
);
assert!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
RuntimeCall
::
Grandpa
(
bridge_grandpa_call
.clone
(),
),)
.is_err
());
// when called outside of transaction => Ok
FreeHeadersRemaining
::
<
TestRuntime
,
()
>
::
kill
();
assert!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
RuntimeCall
::
Grandpa
(
bridge_grandpa_call
,
),)
.is_ok
());
})
}
#[test]
fn
extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected
(
)
{
run_test
(||
{
let
bridge_grandpa_call
=
crate
::
Call
::
<
TestRuntime
,
()
>
::
submit_finality_proof_ex
{
finality_target
:
Box
::
new
(
test_header
(
100
)),
justification
:
make_default_justification
(
&
test_header
(
100
)),
current_set_id
:
0
,
is_free_execution_expected
:
true
,
};
sync_to_header_10
();
// when `improved_by` is less than the free interval
BestFinalized
::
<
TestRuntime
,
()
>
::
put
(
HeaderId
(
100
-
FreeHeadersInterval
::
get
()
as
u64
+
1
,
sp_core
::
H256
::
default
(),
));
assert!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
RuntimeCall
::
Grandpa
(
bridge_grandpa_call
.clone
(),
),)
.is_err
());
// when `improved_by` is equal to the free interval
BestFinalized
::
<
TestRuntime
,
()
>
::
put
(
HeaderId
(
100
-
FreeHeadersInterval
::
get
()
as
u64
,
sp_core
::
H256
::
default
(),
));
assert!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
RuntimeCall
::
Grandpa
(
bridge_grandpa_call
.clone
(),
),)
.is_ok
());
// when `improved_by` is larger than the free interval
BestFinalized
::
<
TestRuntime
,
()
>
::
put
(
HeaderId
(
100
-
FreeHeadersInterval
::
get
()
as
u64
-
1
,
sp_core
::
H256
::
default
(),
));
assert!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
RuntimeCall
::
Grandpa
(
bridge_grandpa_call
.clone
(),
),)
.is_ok
());
// when `improved_by` is less than the free interval BUT it is a mandatory header
let
mut
mandatory_header
=
test_header
(
100
);
let
consensus_log
=
sp_consensus_grandpa
::
ConsensusLog
::
<
TestNumber
>
::
ScheduledChange
(
sp_consensus_grandpa
::
ScheduledChange
{
next_authorities
:
bp_test_utils
::
authority_list
(),
delay
:
0
,
},
);
mandatory_header
.digest
=
sp_runtime
::
Digest
{
logs
:
vec!
[
DigestItem
::
Consensus
(
sp_consensus_grandpa
::
GRANDPA_ENGINE_ID
,
consensus_log
.encode
(),
)],
};
let
justification
=
make_justification_for_header
(
JustificationGeneratorParams
{
header
:
mandatory_header
.clone
(),
set_id
:
1
,
..
Default
::
default
()
});
let
bridge_grandpa_call
=
crate
::
Call
::
<
TestRuntime
,
()
>
::
submit_finality_proof_ex
{
finality_target
:
Box
::
new
(
mandatory_header
),
justification
,
current_set_id
:
0
,
is_free_execution_expected
:
true
,
};
BestFinalized
::
<
TestRuntime
,
()
>
::
put
(
HeaderId
(
100
-
FreeHeadersInterval
::
get
()
as
u64
+
1
,
sp_core
::
H256
::
default
(),
));
assert!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
RuntimeCall
::
Grandpa
(
bridge_grandpa_call
.clone
(),
),)
.is_ok
());
})
}
#[test]
fn
extension_accepts_new_header
()
{
run_test
(||
{
...
...
@@ -336,6 +571,8 @@ mod tests {
current_set_id
:
None
,
extra_weight
:
Weight
::
zero
(),
extra_size
:
0
,
is_mandatory
:
false
,
is_free_execution_expected
:
false
,
})
);
...
...
@@ -345,6 +582,7 @@ mod tests {
finality_target
:
Box
::
new
(
test_header
(
42
)),
justification
:
make_default_justification
(
&
test_header
(
42
)),
current_set_id
:
777
,
is_free_execution_expected
:
false
,
});
assert_eq!
(
deprecated_call
.submit_finality_proof_info
(),
...
...
@@ -353,6 +591,8 @@ mod tests {
current_set_id
:
Some
(
777
),
extra_weight
:
Weight
::
zero
(),
extra_size
:
0
,
is_mandatory
:
false
,
is_free_execution_expected
:
false
,
})
);
}
...
...
@@ -370,6 +610,7 @@ mod tests {
finality_target
:
Box
::
new
(
small_finality_target
),
justification
:
small_justification
,
current_set_id
:
TEST_GRANDPA_SET_ID
,
is_free_execution_expected
:
false
,
});
assert_eq!
(
small_call
.submit_finality_proof_info
()
.unwrap
()
.extra_size
,
0
);
...
...
@@ -387,6 +628,7 @@ mod tests {
finality_target
:
Box
::
new
(
large_finality_target
),
justification
:
large_justification
,
current_set_id
:
TEST_GRANDPA_SET_ID
,
is_free_execution_expected
:
false
,
});
assert_ne!
(
large_call
.submit_finality_proof_info
()
.unwrap
()
.extra_size
,
0
);
}
...
...
@@ -406,6 +648,7 @@ mod tests {
finality_target
:
Box
::
new
(
finality_target
.clone
()),
justification
,
current_set_id
:
TEST_GRANDPA_SET_ID
,
is_free_execution_expected
:
false
,
});
assert_eq!
(
call
.submit_finality_proof_info
()
.unwrap
()
.extra_weight
,
Weight
::
zero
());
...
...
@@ -420,7 +663,52 @@ mod tests {
finality_target
:
Box
::
new
(
finality_target
),
justification
,
current_set_id
:
TEST_GRANDPA_SET_ID
,
is_free_execution_expected
:
false
,
});
assert_eq!
(
call
.submit_finality_proof_info
()
.unwrap
()
.extra_weight
,
call_weight
);
}
#[test]
fn
check_obsolete_submit_finality_proof_returns_correct_improved_by
()
{
run_test
(||
{
fn
make_call
(
number
:
u64
)
->
RuntimeCall
{
RuntimeCall
::
Grandpa
(
crate
::
Call
::
<
TestRuntime
,
()
>
::
submit_finality_proof_ex
{
finality_target
:
Box
::
new
(
test_header
(
number
)),
justification
:
make_default_justification
(
&
test_header
(
number
)),
current_set_id
:
0
,
is_free_execution_expected
:
false
,
})
}
sync_to_header_10
();
// when the difference between headers is 1
assert_eq!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
make_call
(
11
))
.unwrap
()
.unwrap
()
.improved_by
,
1
,
);
// when the difference between headers is 2
assert_eq!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
make_call
(
12
))
.unwrap
()
.unwrap
()
.improved_by
,
2
,
);
})
}
#[test]
fn
check_obsolete_submit_finality_proof_ignores_other_calls
()
{
run_test
(||
{
let
call
=
RuntimeCall
::
System
(
frame_system
::
Call
::
<
TestRuntime
>
::
remark
{
remark
:
vec!
[
42
]
});
assert_eq!
(
RuntimeCall
::
check_obsolete_submit_finality_proof
(
&
call
),
Ok
(
None
));
})
}
}
bridges/modules/grandpa/src/lib.rs
View file @
aceda465
...
...
@@ -44,6 +44,7 @@ use bp_header_chain::{
};
use
bp_runtime
::{
BlockNumberOf
,
HashOf
,
HasherOf
,
HeaderId
,
HeaderOf
,
OwnedBridgeModule
};
use
frame_support
::{
dispatch
::
PostDispatchInfo
,
ensure
,
DefaultNoBound
};
use
sp_consensus_grandpa
::
SetId
;
use
sp_runtime
::{
traits
::{
Header
as
HeaderT
,
Zero
},
SaturatedConversion
,
...
...
@@ -57,6 +58,7 @@ mod storage_types;
/// Module, containing weights for this pallet.
pub
mod
weights
;
pub
mod
weights_ext
;
#[cfg(feature
=
"runtime-benchmarks"
)]
pub
mod
benchmarking
;
...
...
@@ -65,6 +67,7 @@ pub mod benchmarking;
pub
use
call_ext
::
*
;
pub
use
pallet
::
*
;
pub
use
weights
::
WeightInfo
;
pub
use
weights_ext
::
WeightInfoExt
;
/// The target that will be used when publishing logs related to this pallet.
pub
const
LOG_TARGET
:
&
str
=
"runtime::bridge-grandpa"
;
...
...
@@ -101,17 +104,31 @@ pub mod pallet {
/// The chain we are bridging to here.
type
BridgedChain
:
ChainWithGrandpa
;
/// Maximal number of "free"
mandatory
header transactions per block.
/// Maximal number of "free" header transactions per block.
///
/// To be able to track the bridged chain, the pallet requires all headers that are
/// changing GRANDPA authorities set at the bridged chain (we call them mandatory).
/// So it is a common good deed to submit mandatory headers to the pallet. However, if the
/// bridged chain gets compromised, its validators may generate as many mandatory headers
/// as they want. And they may fill the whole block (at this chain) for free. This constants
/// limits number of calls that we may refund in a single block. All calls above this
/// limit are accepted, but are not refunded.
/// So it is a common good deed to submit mandatory headers to the pallet.
///
/// The pallet may be configured (see `[Self::FreeHeadersInterval]`) to import some
/// non-mandatory headers for free as well. It also may be treated as a common good
/// deed, because it may help to reduce bridge fees - this cost may be deducted from
/// bridge fees, paid by message senders.
///
/// However, if the bridged chain gets compromised, its validators may generate as many
/// "free" headers as they want. And they may fill the whole block (at this chain) for
/// free. This constants limits number of calls that we may refund in a single block.
/// All calls above this limit are accepted, but are not refunded.
#[pallet::constant]
type
MaxFreeHeadersPerBlock
:
Get
<
u32
>
;
/// The distance between bridged chain headers, that may be submitted for free. The
/// first free header is header number zero, the next one is header number
/// `FreeHeadersInterval::get()` or any of its descendant if that header has not
/// been submitted. In other words, interval between free headers should be at least
/// `FreeHeadersInterval`.
#[pallet::constant]
type
Max
Free
MandatoryHeadersPerBlock
:
Get
<
u32
>
;
type
Free
HeadersInterval
:
Get
<
Option
<
u32
>
>
;
/// Maximal number of finalized headers to keep in the storage.
///
...
...
@@ -124,7 +141,7 @@ pub mod pallet {
type
HeadersToKeep
:
Get
<
u32
>
;
/// Weights gathered through benchmarking.
type
WeightInfo
:
WeightInfo
;
type
WeightInfo
:
WeightInfo
Ext
;
}
#[pallet::pallet]
...
...
@@ -133,12 +150,12 @@ pub mod pallet {
#[pallet::hooks]
impl
<
T
:
Config
<
I
>
,
I
:
'static
>
Hooks
<
BlockNumberFor
<
T
>>
for
Pallet
<
T
,
I
>
{
fn
on_initialize
(
_n
:
BlockNumberFor
<
T
>
)
->
Weight
{
Free
Mandatory
HeadersRemaining
::
<
T
,
I
>
::
put
(
T
::
MaxFree
Mandatory
HeadersPerBlock
::
get
());
FreeHeadersRemaining
::
<
T
,
I
>
::
put
(
T
::
MaxFreeHeadersPerBlock
::
get
());
Weight
::
zero
()
}
fn
on_finalize
(
_n
:
BlockNumberFor
<
T
>
)
{
Free
Mandatory
HeadersRemaining
::
<
T
,
I
>
::
kill
();
FreeHeadersRemaining
::
<
T
,
I
>
::
kill
();
}
}
...
...
@@ -155,7 +172,7 @@ pub mod pallet {
/// `submit_finality_proof_ex` instead. Semantically, this call is an equivalent of the
/// `submit_finality_proof_ex` call without current authority set id check.
#[pallet::call_index(
0
)]
#[pallet::weight(
<
T::WeightInfo
as
WeightInfo
>
::submit_finality_proof(
#[pallet::weight(T::WeightInfo::submit_finality_proof
_weight
(
justification
.
commit
.
precommits
.
len()
.
saturated_into(),
justification
.
votes_ancestries
.
len()
.
saturated_into(),
))]
...
...
@@ -175,6 +192,8 @@ pub mod pallet {
// the `submit_finality_proof_ex` also reads this value, but it is done from the
// cache, so we don't treat it as an additional db access
<
CurrentAuthoritySet
<
T
,
I
>>
::
get
()
.set_id
,
// cannot enforce free execution using this call
false
,
)
}
...
...
@@ -250,8 +269,14 @@ pub mod pallet {
/// - verification is not optimized or invalid;
///
/// - header contains forced authorities set change or change with non-zero delay.
///
/// The `is_free_execution_expected` parameter is not really used inside the call. It is
/// used by the transaction extension, which should be registered at the runtime level. If
/// this parameter is `true`, the transaction will be treated as invalid, if the call won't
/// be executed for free. If transaction extension is not used by the runtime, this
/// parameter is not used at all.
#[pallet::call_index(
4
)]
#[pallet::weight(
<
T::WeightInfo
as
WeightInfo
>
::submit_finality_proof(
#[pallet::weight(T::WeightInfo::submit_finality_proof
_weight
(
justification
.
commit
.
precommits
.
len()
.
saturated_into(),
justification
.
votes_ancestries
.
len()
.
saturated_into(),
))]
...
...
@@ -260,6 +285,7 @@ pub mod pallet {
finality_target
:
Box
<
BridgedHeader
<
T
,
I
>>
,
justification
:
GrandpaJustification
<
BridgedHeader
<
T
,
I
>>
,
current_set_id
:
sp_consensus_grandpa
::
SetId
,
_is_free_execution_expected
:
bool
,
)
->
DispatchResultWithPostInfo
{
Self
::
ensure_not_halted
()
.map_err
(
Error
::
<
T
,
I
>
::
BridgeModule
)
?
;
ensure_signed
(
origin
)
?
;
...
...
@@ -273,6 +299,7 @@ pub mod pallet {
// it checks whether the `number` is better than the current best block number
// and whether the `current_set_id` matches the best known set id
let
improved_by
=
SubmitFinalityProofHelper
::
<
T
,
I
>
::
check_obsolete
(
number
,
Some
(
current_set_id
))
?
;
let
authority_set
=
<
CurrentAuthoritySet
<
T
,
I
>>
::
get
();
...
...
@@ -283,23 +310,16 @@ pub mod pallet {
let
maybe_new_authority_set
=
try_enact_authority_change
::
<
T
,
I
>
(
&
finality_target
,
set_id
)
?
;
let
may_refund_call_fee
=
may
be_new_authority_set
.is_some
()
&&
// if we have seen too many mandatory headers in this block, we don't want to refund
Self
::
free_mandatory_headers_remaining
()
>
0
&&
// if arguments out of expected bounds, we don't want to refund
submit_finality_proof_info_from_args
::
<
T
,
I
>
(
&
finality_target
,
&
justification
,
Some
(
current_set_id
))
.fits_limits
(
);
let
may_refund_call_fee
=
may
_refund_call_fee
::
<
T
,
I
>
(
&
finality_target
,
&
justification
,
current_set_id
,
improved_by
,
);
if
may_refund_call_fee
{
FreeMandatoryHeadersRemaining
::
<
T
,
I
>
::
mutate
(|
count
|
{
*
count
=
count
.saturating_sub
(
1
)
});
on_free_header_imported
::
<
T
,
I
>
();
}
insert_header
::
<
T
,
I
>
(
*
finality_target
,
hash
);
log
::
info!
(
target
:
LOG_TARGET
,
"Successfully imported finalized header with hash {:?}!"
,
hash
);
// mandatory header is a header that changes authorities set. The pallet can't go
// further without importing this header. So every bridge MUST import mandatory headers.
...
...
@@ -311,6 +331,13 @@ pub mod pallet {
// to pay for the transaction.
let
pays_fee
=
if
may_refund_call_fee
{
Pays
::
No
}
else
{
Pays
::
Yes
};
log
::
info!
(
target
:
LOG_TARGET
,
"Successfully imported finalized header with hash {:?}! Free: {}"
,
hash
,
if
may_refund_call_fee
{
"Yes"
}
else
{
"No"
},
);
// the proof size component of the call weight assumes that there are
// `MaxBridgedAuthorities` in the `CurrentAuthoritySet` (we use `MaxEncodedLen`
// estimation). But if their number is lower, then we may "refund" some `proof_size`,
...
...
@@ -335,20 +362,18 @@ pub mod pallet {
}
}
/// Number mandatory headers that we may accept in the current block for free (returning
/// `Pays::No`).
/// Number of free header submissions that we may yet accept in the current block.
///
/// If the `Free
Mandatory
HeadersRemaining` hits zero, all following mandatory headers in the
/// If the `FreeHeadersRemaining` hits zero, all following mandatory headers in the
/// current block are accepted with fee (`Pays::Yes` is returned).
///
/// The `Free
Mandatory
HeadersRemaining` is an ephemeral value that is set to
/// `MaxFree
Mandatory
HeadersPerBlock` at each block initialization and is killed on block
/// The `FreeHeadersRemaining` is an ephemeral value that is set to
/// `MaxFreeHeadersPerBlock` at each block initialization and is killed on block
/// finalization. So it never ends up in the storage trie.
#[pallet::storage]
#[pallet::whitelist_storage]
#[pallet::getter(fn
free_mandatory_headers_remaining)]
pub
(
super
)
type
FreeMandatoryHeadersRemaining
<
T
:
Config
<
I
>
,
I
:
'static
=
()
>
=
StorageValue
<
_
,
u32
,
ValueQuery
>
;
pub
type
FreeHeadersRemaining
<
T
:
Config
<
I
>
,
I
:
'static
=
()
>
=
StorageValue
<
_
,
u32
,
OptionQuery
>
;
/// Hash of the header used to bootstrap the pallet.
#[pallet::storage]
...
...
@@ -473,6 +498,68 @@ pub mod pallet {
/// The `current_set_id` argument of the `submit_finality_proof_ex` doesn't match
/// the id of the current set, known to the pallet.
InvalidAuthoritySetId
,
/// The submitter wanted free execution, but we can't fit more free transactions
/// to the block.
FreeHeadersLimitExceded
,
/// The submitter wanted free execution, but the difference between best known and
/// bundled header numbers is below the `FreeHeadersInterval`.
BelowFreeHeaderInterval
,
}
/// Called when new free header is imported.
pub
fn
on_free_header_imported
<
T
:
Config
<
I
>
,
I
:
'static
>
()
{
FreeHeadersRemaining
::
<
T
,
I
>
::
mutate
(|
count
|
{
*
count
=
match
*
count
{
None
=>
None
,
// the signed extension expects that `None` means outside of block
// execution - i.e. when transaction is validated from the transaction pool,
// so use `saturating_sub` and don't go from `Some(0)`->`None`
Some
(
count
)
=>
Some
(
count
.saturating_sub
(
1
)),
}
});
}
/// Return true if we may refund transaction cost to the submitter. In other words,
/// this transaction is considered as common good deed w.r.t to pallet configuration.
fn
may_refund_call_fee
<
T
:
Config
<
I
>
,
I
:
'static
>
(
finality_target
:
&
BridgedHeader
<
T
,
I
>
,
justification
:
&
GrandpaJustification
<
BridgedHeader
<
T
,
I
>>
,
current_set_id
:
SetId
,
improved_by
:
BridgedBlockNumber
<
T
,
I
>
,
)
->
bool
{
// if we have refunded too much at this block => not refunding
if
FreeHeadersRemaining
::
<
T
,
I
>
::
get
()
.unwrap_or
(
0
)
==
0
{
return
false
;
}
// if size/weight of call is larger than expected => not refunding
let
call_info
=
submit_finality_proof_info_from_args
::
<
T
,
I
>
(
&
finality_target
,
&
justification
,
Some
(
current_set_id
),
// this function is called from the transaction body and we do not want
// to do MAY-be-free-executed checks here - they had to be done in the
// transaction extension before
false
,
);
if
!
call_info
.fits_limits
()
{
return
false
;
}
// if that's a mandatory header => refund
if
call_info
.is_mandatory
{
return
true
;
}
// if configuration allows free non-mandatory headers and the header
// matches criteria => refund
if
let
Some
(
free_headers_interval
)
=
T
::
FreeHeadersInterval
::
get
()
{
if
improved_by
>=
free_headers_interval
.into
()
{
return
true
;
}
}
false
}
/// Check the given header for a GRANDPA scheduled authority set change. If a change
...
...
@@ -692,8 +779,8 @@ pub fn initialize_for_benchmarks<T: Config<I>, I: 'static>(header: BridgedHeader
mod
tests
{
use
super
::
*
;
use
crate
::
mock
::{
run_test
,
test_header
,
RuntimeEvent
as
TestEvent
,
RuntimeOrigin
,
System
,
TestBridgedChain
,
TestHeader
,
TestNumber
,
TestRuntime
,
MAX_BRIDGED_AUTHORITIES
,
run_test
,
test_header
,
FreeHeadersInterval
,
RuntimeEvent
as
TestEvent
,
RuntimeOrigin
,
System
,
TestBridgedChain
,
TestHeader
,
TestNumber
,
TestRuntime
,
MAX_BRIDGED_AUTHORITIES
,
};
use
bp_header_chain
::
BridgeGrandpaCall
;
use
bp_runtime
::
BasicOperatingMode
;
...
...
@@ -747,6 +834,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
)
}
...
...
@@ -766,6 +854,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
set_id
,
false
,
)
}
...
...
@@ -794,6 +883,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
set_id
,
false
,
)
}
...
...
@@ -1009,6 +1099,7 @@ mod tests {
Box
::
new
(
header
.clone
()),
justification
.clone
(),
TEST_GRANDPA_SET_ID
,
false
,
),
<
Error
<
TestRuntime
>>
::
InvalidJustification
);
...
...
@@ -1018,6 +1109,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
next_set_id
,
false
,
),
<
Error
<
TestRuntime
>>
::
InvalidAuthoritySetId
);
...
...
@@ -1039,6 +1131,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
),
<
Error
<
TestRuntime
>>
::
InvalidJustification
);
...
...
@@ -1069,6 +1162,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
),
<
Error
<
TestRuntime
>>
::
InvalidAuthoritySet
);
...
...
@@ -1108,6 +1202,7 @@ mod tests {
Box
::
new
(
header
.clone
()),
justification
.clone
(),
TEST_GRANDPA_SET_ID
,
false
,
);
assert_ok!
(
result
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
frame_support
::
dispatch
::
Pays
::
No
);
...
...
@@ -1171,6 +1266,7 @@ mod tests {
Box
::
new
(
header
.clone
()),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
);
assert_ok!
(
result
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
frame_support
::
dispatch
::
Pays
::
Yes
);
...
...
@@ -1203,6 +1299,7 @@ mod tests {
Box
::
new
(
header
.clone
()),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
);
assert_ok!
(
result
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
frame_support
::
dispatch
::
Pays
::
Yes
);
...
...
@@ -1233,6 +1330,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
),
<
Error
<
TestRuntime
>>
::
UnsupportedScheduledChange
);
...
...
@@ -1259,6 +1357,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
),
<
Error
<
TestRuntime
>>
::
UnsupportedScheduledChange
);
...
...
@@ -1285,6 +1384,7 @@ mod tests {
Box
::
new
(
header
),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
),
<
Error
<
TestRuntime
>>
::
TooManyAuthoritiesInSet
);
...
...
@@ -1350,12 +1450,13 @@ mod tests {
Box
::
new
(
header
),
invalid_justification
,
TEST_GRANDPA_SET_ID
,
false
,
)
};
initialize_substrate_bridge
();
for
_
in
0
..<
TestRuntime
as
Config
>
::
MaxFree
Mandatory
HeadersPerBlock
::
get
()
+
1
{
for
_
in
0
..<
TestRuntime
as
Config
>
::
MaxFreeHeadersPerBlock
::
get
()
+
1
{
assert_err!
(
submit_invalid_request
(),
<
Error
<
TestRuntime
>>
::
InvalidJustification
);
}
...
...
@@ -1423,6 +1524,64 @@ mod tests {
})
}
#[test]
fn
may_import_non_mandatory_header_for_free
()
{
run_test
(||
{
initialize_substrate_bridge
();
// set best finalized to `100`
const
BEST
:
u8
=
12
;
fn
reset_best
()
{
BestFinalized
::
<
TestRuntime
,
()
>
::
set
(
Some
(
HeaderId
(
BEST
as
_
,
Default
::
default
(),
)));
}
// non-mandatory header is imported with fee
reset_best
();
let
non_free_header_number
=
BEST
+
FreeHeadersInterval
::
get
()
as
u8
-
1
;
let
result
=
submit_finality_proof
(
non_free_header_number
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
Pays
::
Yes
);
// non-mandatory free header is imported without fee
reset_best
();
let
free_header_number
=
BEST
+
FreeHeadersInterval
::
get
()
as
u8
;
let
result
=
submit_finality_proof
(
free_header_number
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
Pays
::
No
);
// another non-mandatory free header is imported without fee
let
free_header_number
=
BEST
+
FreeHeadersInterval
::
get
()
as
u8
*
2
;
let
result
=
submit_finality_proof
(
free_header_number
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
Pays
::
No
);
// now the rate limiter starts charging fees even for free headers
let
free_header_number
=
BEST
+
FreeHeadersInterval
::
get
()
as
u8
*
3
;
let
result
=
submit_finality_proof
(
free_header_number
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
Pays
::
Yes
);
// check that we can import for free if `improved_by` is larger
// than the free interval
next_block
();
reset_best
();
let
free_header_number
=
FreeHeadersInterval
::
get
()
as
u8
+
42
;
let
result
=
submit_finality_proof
(
free_header_number
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
Pays
::
No
);
// check that the rate limiter shares the counter between mandatory
// and free non-mandatory headers
next_block
();
reset_best
();
let
free_header_number
=
BEST
+
FreeHeadersInterval
::
get
()
as
u8
*
4
;
let
result
=
submit_finality_proof
(
free_header_number
);
assert_eq!
(
result
.unwrap
()
.pays_fee
,
Pays
::
No
);
let
result
=
submit_mandatory_finality_proof
(
free_header_number
+
1
,
1
);
assert_eq!
(
result
.expect
(
"call failed"
)
.pays_fee
,
Pays
::
No
);
let
result
=
submit_mandatory_finality_proof
(
free_header_number
+
2
,
2
);
assert_eq!
(
result
.expect
(
"call failed"
)
.pays_fee
,
Pays
::
Yes
);
});
}
#[test]
fn
should_prune_headers_over_headers_to_keep_parameter
()
{
run_test
(||
{
...
...
@@ -1519,9 +1678,23 @@ mod tests {
Box
::
new
(
header
),
justification
,
TEST_GRANDPA_SET_ID
,
false
,
),
DispatchError
::
BadOrigin
,
);
})
}
#[test]
fn
on_free_header_imported_never_sets_to_none
()
{
run_test
(||
{
FreeHeadersRemaining
::
<
TestRuntime
,
()
>
::
set
(
Some
(
2
));
on_free_header_imported
::
<
TestRuntime
,
()
>
();
assert_eq!
(
FreeHeadersRemaining
::
<
TestRuntime
,
()
>
::
get
(),
Some
(
1
));
on_free_header_imported
::
<
TestRuntime
,
()
>
();
assert_eq!
(
FreeHeadersRemaining
::
<
TestRuntime
,
()
>
::
get
(),
Some
(
0
));
on_free_header_imported
::
<
TestRuntime
,
()
>
();
assert_eq!
(
FreeHeadersRemaining
::
<
TestRuntime
,
()
>
::
get
(),
Some
(
0
));
})
}
}
bridges/modules/grandpa/src/mock.rs
View file @
aceda465
...
...
@@ -48,14 +48,16 @@ impl frame_system::Config for TestRuntime {
}
parameter_types!
{
pub
const
MaxFreeMandatoryHeadersPerBlock
:
u32
=
2
;
pub
const
MaxFreeHeadersPerBlock
:
u32
=
2
;
pub
const
FreeHeadersInterval
:
u32
=
32
;
pub
const
HeadersToKeep
:
u32
=
5
;
}
impl
grandpa
::
Config
for
TestRuntime
{
type
RuntimeEvent
=
RuntimeEvent
;
type
BridgedChain
=
TestBridgedChain
;
type
MaxFreeMandatoryHeadersPerBlock
=
MaxFreeMandatoryHeadersPerBlock
;
type
MaxFreeHeadersPerBlock
=
MaxFreeHeadersPerBlock
;
type
FreeHeadersInterval
=
FreeHeadersInterval
;
type
HeadersToKeep
=
HeadersToKeep
;
type
WeightInfo
=
();
}
...
...
bridges/modules/grandpa/src/weights_ext.rs
0 → 100644
View file @
aceda465
// 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
frame_support
::
weights
::
Weight
;
/// Extended weight info.
pub
trait
WeightInfoExt
:
WeightInfo
{
// Our configuration assumes that the runtime has special signed extensions used to:
//
// 1) boost priority of `submit_finality_proof` transactions;
//
// 2) slash relayer if he submits an invalid transaction.
//
// We read and update storage values of other pallets (`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 `submit_finality_proof` call weight by signed extensions
/// that are declared at runtime level.
fn
submit_finality_proof_overhead_from_runtime
()
->
Weight
;
// Functions that are directly mapped to extrinsics weights.
/// Weight of message delivery extrinsic.
fn
submit_finality_proof_weight
(
precommits_len
:
u32
,
votes_ancestries_len
:
u32
)
->
Weight
{
let
base_weight
=
Self
::
submit_finality_proof
(
precommits_len
,
votes_ancestries_len
);
base_weight
.saturating_add
(
Self
::
submit_finality_proof_overhead_from_runtime
())
}
}
impl
<
T
:
frame_system
::
Config
>
WeightInfoExt
for
BridgeWeight
<
T
>
{
fn
submit_finality_proof_overhead_from_runtime
()
->
Weight
{
Weight
::
zero
()
}
}
impl
WeightInfoExt
for
()
{
fn
submit_finality_proof_overhead_from_runtime
()
->
Weight
{
Weight
::
zero
()
}
}
bridges/modules/parachains/src/call_ext.rs
View file @
aceda465
...
...
@@ -14,25 +14,45 @@
// 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
crate
::{
Config
,
GrandpaPalletOf
,
Pallet
,
RelayBlockHash
,
RelayBlockNumber
};
use
bp_header_chain
::
HeaderChain
;
use
bp_parachains
::
BestParaHeadHash
;
use
bp_polkadot_core
::
parachains
::{
ParaHash
,
ParaId
};
use
bp_runtime
::
OwnedBridgeModule
;
use
frame_support
::{
dispatch
::
CallableCallFor
,
traits
::
IsSubType
};
use
bp_runtime
::{
HeaderId
,
OwnedBridgeModule
};
use
frame_support
::{
dispatch
::
CallableCallFor
,
traits
::{
Get
,
IsSubType
},
};
use
pallet_bridge_grandpa
::
SubmitFinalityProofHelper
;
use
sp_runtime
::{
transaction_validity
::{
InvalidTransaction
,
TransactionValidity
,
ValidTransaction
},
traits
::
Zero
,
transaction_validity
::{
InvalidTransaction
,
TransactionValidityError
},
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
,
/// Number and hash of the finalized relay block that has been used to prove parachain
/// finality.
pub
at_relay_block
:
HeaderId
<
RelayBlockHash
,
RelayBlockNumber
>
,
/// Parachain identifier.
pub
para_id
:
ParaId
,
/// Hash of the bundled parachain head.
pub
para_head_hash
:
ParaHash
,
/// If `true`, then the call must be free (assuming that everything else is valid) to
/// be treated as valid.
pub
is_free_execution_expected
:
bool
,
}
/// Verified `SubmitParachainHeadsInfo`.
#[derive(PartialEq,
RuntimeDebug)]
pub
struct
VerifiedSubmitParachainHeadsInfo
{
/// Base call information.
pub
base
:
SubmitParachainHeadsInfo
,
/// A difference between bundled bridged relay chain header and relay chain header number
/// used to prove best bridged parachain header, known to us before the call.
pub
improved_by
:
RelayBlockNumber
,
}
/// Helper struct that provides methods for working with the `SubmitParachainHeads` call.
...
...
@@ -41,25 +61,82 @@ pub struct SubmitParachainHeadsHelper<T: Config<I>, I: 'static> {
}
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
,
/// Check that is called from signed extension and takes the `is_free_execution_expected`
/// into account.
pub
fn
check_obsolete_from_extension
(
update
:
&
SubmitParachainHeadsInfo
,
)
->
Result
<
RelayBlockNumber
,
TransactionValidityError
>
{
// first do all base checks
let
improved_by
=
Self
::
check_obsolete
(
update
)
?
;
// if we don't expect free execution - no more checks
if
!
update
.is_free_execution_expected
{
return
Ok
(
improved_by
);
}
// reject if no more free slots remaining in the block
if
!
SubmitFinalityProofHelper
::
<
T
,
T
::
BridgesGrandpaPalletInstance
>
::
has_free_header_slots
()
{
log
::
trace!
(
target
:
crate
::
LOG_TARGET
,
"The free parachain {:?} head can't be updated: no more free slots
\
left in the block."
,
update
.para_id
,
);
return
Err
(
InvalidTransaction
::
Call
.into
());
}
// if free headers interval is not configured and call is expected to execute
// for free => it is a relayer error, it should've been able to detect that.
let
free_headers_interval
=
match
T
::
FreeHeadersInterval
::
get
()
{
Some
(
free_headers_interval
)
=>
free_headers_interval
,
None
=>
return
Ok
(
improved_by
),
};
if
stored_best_head
.best_head_hash.at_relay_block_number
>=
update
.at_relay_block_number
{
// reject if we are importing parachain headers too often
if
improved_by
<
free_headers_interval
{
log
::
trace!
(
target
:
crate
::
LOG_TARGET
,
"The free parachain {:?} head can't be updated: it improves previous
best head by {} while at least {} is expected."
,
update
.para_id
,
improved_by
,
free_headers_interval
,
);
return
Err
(
InvalidTransaction
::
Stale
.into
());
}
Ok
(
improved_by
)
}
/// Check if the para head provided by the `SubmitParachainHeads` is better than the best one
/// we know.
pub
fn
check_obsolete
(
update
:
&
SubmitParachainHeadsInfo
,
)
->
Result
<
RelayBlockNumber
,
TransactionValidityError
>
{
// check if we know better parachain head already
let
improved_by
=
match
crate
::
ParasInfo
::
<
T
,
I
>
::
get
(
update
.para_id
)
{
Some
(
stored_best_head
)
=>
{
let
improved_by
=
match
update
.at_relay_block
.0
.checked_sub
(
stored_best_head
.best_head_hash.at_relay_block_number
)
{
Some
(
improved_by
)
if
improved_by
>
Zero
::
zero
()
=>
improved_by
,
_
=>
{
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
update
.at_relay_block
.0
);
return
true
}
return
Err
(
InvalidTransaction
::
Stale
.into
())
},
};
if
stored_best_head
.best_head_hash.head_hash
==
update
.para_head_hash
{
log
::
trace!
(
...
...
@@ -69,12 +146,32 @@ impl<T: Config<I>, I: 'static> SubmitParachainHeadsHelper<T, I> {
update
.para_id
,
update
.para_head_hash
,
stored_best_head
.best_head_hash.at_relay_block_number
,
update
.at_relay_block_number
update
.at_relay_block
.0
);
return
Err
(
InvalidTransaction
::
Stale
.into
())
}
improved_by
},
None
=>
RelayBlockNumber
::
MAX
,
};
// let's check if our chain had no reorgs and we still know the relay chain header
// used to craft the proof
if
GrandpaPalletOf
::
<
T
,
I
>
::
finalized_header_state_root
(
update
.at_relay_block
.1
)
.is_none
()
{
log
::
trace!
(
target
:
crate
::
LOG_TARGET
,
"The parachain {:?} head can't be updated. Relay chain header {}/{} used to create
\
parachain proof is missing from the storage."
,
update
.para_id
,
update
.at_relay_block
.0
,
update
.at_relay_block
.1
,
);
return
true
return
Err
(
InvalidTransaction
::
Call
.into
())
}
false
Ok
(
improved_by
)
}
/// Check if the `SubmitParachainHeads` was successfully executed.
...
...
@@ -83,7 +180,7 @@ impl<T: Config<I>, I: 'static> SubmitParachainHeadsHelper<T, I> {
Some
(
stored_best_head
)
=>
stored_best_head
.best_head_hash
==
BestParaHeadHash
{
at_relay_block_number
:
update
.at_relay_block
_number
,
at_relay_block_number
:
update
.at_relay_block
.0
,
head_hash
:
update
.para_head_hash
,
},
None
=>
false
,
...
...
@@ -98,22 +195,36 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
/// 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
{
match
self
.is_sub_type
()
{
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
,
})
=>
match
&
parachains
[
..
]
{
&
[(
para_id
,
para_head_hash
)]
=>
Some
(
SubmitParachainHeadsInfo
{
at_relay_block
:
HeaderId
(
at_relay_block
.0
,
at_relay_block
.1
),
para_id
,
para_head_hash
,
})
}
is_free_execution_expected
:
false
,
}),
_
=>
None
,
},
Some
(
crate
::
Call
::
<
T
,
I
>
::
submit_parachain_heads_ex
{
ref
at_relay_block
,
ref
parachains
,
is_free_execution_expected
,
..
})
=>
match
&
parachains
[
..
]
{
&
[(
para_id
,
para_head_hash
)]
=>
Some
(
SubmitParachainHeadsInfo
{
at_relay_block
:
HeaderId
(
at_relay_block
.0
,
at_relay_block
.1
),
para_id
,
para_head_hash
,
is_free_execution_expected
:
*
is_free_execution_expected
,
}),
_
=>
None
,
},
_
=>
None
,
}
None
}
/// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with
...
...
@@ -133,24 +244,23 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
/// 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
fn
check_obsolete_submit_parachain_heads
(
&
self
,
)
->
Result
<
Option
<
VerifiedSubmitParachainHeadsInfo
>
,
TransactionValidityError
>
where
Self
:
Sized
,
{
let
update
=
match
self
.one_entry_submit_parachain_heads_info
()
{
Some
(
update
)
=>
update
,
None
=>
return
Ok
(
ValidTransaction
::
default
()
),
None
=>
return
Ok
(
None
),
};
if
Pallet
::
<
T
,
I
>
::
ensure_not_halted
()
.is_err
()
{
return
InvalidTransaction
::
Call
.into
()
}
if
SubmitParachainHeadsHelper
::
<
T
,
I
>
::
is_obsolete
(
&
update
)
{
return
InvalidTransaction
::
Stale
.into
()
return
Err
(
InvalidTransaction
::
Call
.into
())
}
Ok
(
ValidTransaction
::
default
())
SubmitParachainHeadsHelper
::
<
T
,
I
>
::
check_obsolete_from_extension
(
&
update
)
.map
(|
improved_by
|
Some
(
VerifiedSubmitParachainHeadsInfo
{
base
:
update
,
improved_by
}))
}
}
...
...
@@ -164,9 +274,10 @@ where
#[cfg(test)]
mod
tests
{
use
crate
::{
mock
::{
run_test
,
RuntimeCall
,
TestRuntime
},
CallSubType
,
PalletOperatingMode
,
ParaInfo
,
ParasInfo
,
RelayBlockNumber
,
mock
::{
run_test
,
FreeHeadersInterval
,
RuntimeCall
,
TestRuntime
},
CallSubType
,
PalletOperatingMode
,
ParaInfo
,
ParasInfo
,
RelayBlockHash
,
RelayBlockNumber
,
};
use
bp_header_chain
::
StoredHeaderData
;
use
bp_parachains
::
BestParaHeadHash
;
use
bp_polkadot_core
::
parachains
::{
ParaHash
,
ParaHeadsProof
,
ParaId
};
use
bp_runtime
::
BasicOperatingMode
;
...
...
@@ -175,15 +286,37 @@ mod tests {
num
:
RelayBlockNumber
,
parachains
:
Vec
<
(
ParaId
,
ParaHash
)
>
,
)
->
bool
{
RuntimeCall
::
Parachains
(
crate
::
Call
::
<
TestRuntime
,
()
>
::
submit_parachain_heads
{
at_relay_block
:
(
num
,
Default
::
default
()),
RuntimeCall
::
Parachains
(
crate
::
Call
::
<
TestRuntime
,
()
>
::
submit_parachain_heads_ex
{
at_relay_block
:
(
num
,
[
num
as
u8
;
32
]
.into
()),
parachains
,
parachain_heads_proof
:
ParaHeadsProof
{
storage_proof
:
Vec
::
new
()
},
is_free_execution_expected
:
false
,
})
.check_obsolete_submit_parachain_heads
()
.is_ok
()
}
fn
validate_free_submit_parachain_heads
(
num
:
RelayBlockNumber
,
parachains
:
Vec
<
(
ParaId
,
ParaHash
)
>
,
)
->
bool
{
RuntimeCall
::
Parachains
(
crate
::
Call
::
<
TestRuntime
,
()
>
::
submit_parachain_heads_ex
{
at_relay_block
:
(
num
,
[
num
as
u8
;
32
]
.into
()),
parachains
,
parachain_heads_proof
:
ParaHeadsProof
{
storage_proof
:
Vec
::
new
()
},
is_free_execution_expected
:
true
,
})
.check_obsolete_submit_parachain_heads
()
.is_ok
()
}
fn
insert_relay_block
(
num
:
RelayBlockNumber
)
{
pallet_bridge_grandpa
::
ImportedHeaders
::
<
TestRuntime
,
crate
::
Instance1
>
::
insert
(
RelayBlockHash
::
from
([
num
as
u8
;
32
]),
StoredHeaderData
{
number
:
num
,
state_root
:
RelayBlockHash
::
from
([
10u8
;
32
])
},
);
}
fn
sync_to_relay_header_10
()
{
ParasInfo
::
<
TestRuntime
,
()
>
::
insert
(
ParaId
(
1
),
...
...
@@ -244,6 +377,7 @@ mod tests {
// when current best finalized is #10 and we're trying to import header#15 => tx is
// accepted
sync_to_relay_header_10
();
insert_relay_block
(
15
);
assert!
(
validate_submit_parachain_heads
(
15
,
vec!
[(
ParaId
(
1
),
[
2u8
;
32
]
.into
())]));
});
}
...
...
@@ -260,4 +394,65 @@ mod tests {
));
});
}
#[test]
fn
extension_rejects_initial_parachain_head_if_missing_relay_chain_header
()
{
run_test
(||
{
// when relay chain header is unknown => "obsolete"
assert!
(
!
validate_submit_parachain_heads
(
10
,
vec!
[(
ParaId
(
1
),
[
1u8
;
32
]
.into
())]));
// when relay chain header is unknown => "ok"
insert_relay_block
(
10
);
assert!
(
validate_submit_parachain_heads
(
10
,
vec!
[(
ParaId
(
1
),
[
1u8
;
32
]
.into
())]));
});
}
#[test]
fn
extension_rejects_free_parachain_head_if_missing_relay_chain_header
()
{
run_test
(||
{
sync_to_relay_header_10
();
// when relay chain header is unknown => "obsolete"
assert!
(
!
validate_submit_parachain_heads
(
15
,
vec!
[(
ParaId
(
2
),
[
15u8
;
32
]
.into
())]));
// when relay chain header is unknown => "ok"
insert_relay_block
(
15
);
assert!
(
validate_submit_parachain_heads
(
15
,
vec!
[(
ParaId
(
2
),
[
15u8
;
32
]
.into
())]));
});
}
#[test]
fn
extension_rejects_free_parachain_head_if_no_free_slots_remaining
()
{
run_test
(||
{
// when current best finalized is #10 and we're trying to import header#15 => tx should
// be accepted
sync_to_relay_header_10
();
insert_relay_block
(
15
);
// ... but since we have specified `is_free_execution_expected = true`, it'll be
// rejected
assert!
(
!
validate_free_submit_parachain_heads
(
15
,
vec!
[(
ParaId
(
1
),
[
2u8
;
32
]
.into
())]));
// ... if we have specify `is_free_execution_expected = false`, it'll be accepted
assert!
(
validate_submit_parachain_heads
(
15
,
vec!
[(
ParaId
(
1
),
[
2u8
;
32
]
.into
())]));
});
}
#[test]
fn
extension_rejects_free_parachain_head_if_improves_by_is_below_expected
()
{
run_test
(||
{
// when current best finalized is #10 and we're trying to import header#15 => tx should
// be accepted
sync_to_relay_header_10
();
insert_relay_block
(
10
+
FreeHeadersInterval
::
get
()
-
1
);
insert_relay_block
(
10
+
FreeHeadersInterval
::
get
());
// try to submit at 10 + FreeHeadersInterval::get() - 1 => failure
let
relay_header
=
10
+
FreeHeadersInterval
::
get
()
-
1
;
assert!
(
!
validate_free_submit_parachain_heads
(
relay_header
,
vec!
[(
ParaId
(
1
),
[
2u8
;
32
]
.into
())]
));
// try to submit at 10 + FreeHeadersInterval::get() => ok
let
relay_header
=
10
+
FreeHeadersInterval
::
get
();
assert!
(
validate_free_submit_parachain_heads
(
relay_header
,
vec!
[(
ParaId
(
1
),
[
2u8
;
32
]
.into
())]
));
});
}
}
bridges/modules/parachains/src/lib.rs
View file @
aceda465
This diff is collapsed.
Click to expand it.
bridges/modules/parachains/src/mock.rs
View file @
aceda465
...
...
@@ -70,6 +70,7 @@ impl Chain for Parachain1 {
impl
Parachain
for
Parachain1
{
const
PARACHAIN_ID
:
u32
=
1
;
const
MAX_HEADER_SIZE
:
u32
=
1_024
;
}
pub
struct
Parachain2
;
...
...
@@ -96,6 +97,7 @@ impl Chain for Parachain2 {
impl
Parachain
for
Parachain2
{
const
PARACHAIN_ID
:
u32
=
2
;
const
MAX_HEADER_SIZE
:
u32
=
1_024
;
}
pub
struct
Parachain3
;
...
...
@@ -122,6 +124,7 @@ impl Chain for Parachain3 {
impl
Parachain
for
Parachain3
{
const
PARACHAIN_ID
:
u32
=
3
;
const
MAX_HEADER_SIZE
:
u32
=
1_024
;
}
// this parachain is using u128 as block number and stored head data size exceeds limit
...
...
@@ -149,6 +152,7 @@ impl Chain for BigParachain {
impl
Parachain
for
BigParachain
{
const
PARACHAIN_ID
:
u32
=
4
;
const
MAX_HEADER_SIZE
:
u32
=
2_048
;
}
construct_runtime!
{
...
...
@@ -168,12 +172,14 @@ impl frame_system::Config for TestRuntime {
parameter_types!
{
pub
const
HeadersToKeep
:
u32
=
5
;
pub
const
FreeHeadersInterval
:
u32
=
15
;
}
impl
pallet_bridge_grandpa
::
Config
<
pallet_bridge_grandpa
::
Instance1
>
for
TestRuntime
{
type
RuntimeEvent
=
RuntimeEvent
;
type
BridgedChain
=
TestBridgedChain
;
type
MaxFreeMandatoryHeadersPerBlock
=
ConstU32
<
2
>
;
type
MaxFreeHeadersPerBlock
=
ConstU32
<
2
>
;
type
FreeHeadersInterval
=
FreeHeadersInterval
;
type
HeadersToKeep
=
HeadersToKeep
;
type
WeightInfo
=
();
}
...
...
@@ -181,7 +187,8 @@ impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance1> for TestRun
impl
pallet_bridge_grandpa
::
Config
<
pallet_bridge_grandpa
::
Instance2
>
for
TestRuntime
{
type
RuntimeEvent
=
RuntimeEvent
;
type
BridgedChain
=
TestBridgedChain
;
type
MaxFreeMandatoryHeadersPerBlock
=
ConstU32
<
2
>
;
type
MaxFreeHeadersPerBlock
=
ConstU32
<
2
>
;
type
FreeHeadersInterval
=
FreeHeadersInterval
;
type
HeadersToKeep
=
HeadersToKeep
;
type
WeightInfo
=
();
}
...
...
bridges/modules/parachains/src/weights_ext.rs
View file @
aceda465
...
...
@@ -36,6 +36,20 @@ pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
/// Extended weight info.
pub
trait
WeightInfoExt
:
WeightInfo
{
// Our configuration assumes that the runtime has special signed extensions used to:
//
// 1) boost priority of `submit_parachain_heads` transactions;
//
// 2) slash relayer if he submits an invalid transaction.
//
// 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 `submit_finality_proof` call weight by signed extensions
/// that are declared at runtime level.
fn
submit_parachain_heads_overhead_from_runtime
()
->
Weight
;
/// Storage proof overhead, that is included in every storage proof.
///
/// The relayer would pay some extra fee for additional proof bytes, since they mean
...
...
@@ -65,7 +79,10 @@ pub trait WeightInfoExt: WeightInfo {
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
)
base_weight
.saturating_add
(
proof_size_overhead
)
.saturating_add
(
pruning_weight
)
.saturating_add
(
Self
::
submit_parachain_heads_overhead_from_runtime
())
}
/// Returns weight of single parachain head storage update.
...
...
@@ -95,12 +112,20 @@ pub trait WeightInfoExt: WeightInfo {
}
impl
WeightInfoExt
for
()
{
fn
submit_parachain_heads_overhead_from_runtime
()
->
Weight
{
Weight
::
zero
()
}
fn
expected_extra_storage_proof_size
()
->
u32
{
EXTRA_STORAGE_PROOF_SIZE
}
}
impl
<
T
:
frame_system
::
Config
>
WeightInfoExt
for
BridgeWeight
<
T
>
{
fn
submit_parachain_heads_overhead_from_runtime
()
->
Weight
{
Weight
::
zero
()
}
fn
expected_extra_storage_proof_size
()
->
u32
{
EXTRA_STORAGE_PROOF_SIZE
}
...
...
bridges/primitives/parachains/src/lib.rs
View file @
aceda465
...
...
@@ -116,6 +116,10 @@ impl ParaStoredHeaderData {
/// Stored parachain head data builder.
pub
trait
ParaStoredHeaderDataBuilder
{
/// Maximal parachain head size that we may accept for free. All heads above
/// this limit are submitted for a regular fee.
fn
max_free_head_size
()
->
u32
;
/// Return number of parachains that are supported by this builder.
fn
supported_parachains
()
->
u32
;
...
...
@@ -127,6 +131,10 @@ pub trait ParaStoredHeaderDataBuilder {
pub
struct
SingleParaStoredHeaderDataBuilder
<
C
:
Parachain
>
(
PhantomData
<
C
>
);
impl
<
C
:
Parachain
>
ParaStoredHeaderDataBuilder
for
SingleParaStoredHeaderDataBuilder
<
C
>
{
fn
max_free_head_size
()
->
u32
{
C
::
MAX_HEADER_SIZE
}
fn
supported_parachains
()
->
u32
{
1
}
...
...
@@ -147,6 +155,17 @@ impl<C: Parachain> ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBui
#[impl_trait_for_tuples::impl_for_tuples(
1
,
30
)]
#[tuple_types_custom_trait_bound(Parachain)]
impl
ParaStoredHeaderDataBuilder
for
C
{
fn
max_free_head_size
()
->
u32
{
let
mut
result
=
0_u32
;
for_tuples!
(
#
(
result
=
sp_std
::
cmp
::
max
(
result
,
SingleParaStoredHeaderDataBuilder
::
<
C
>
::
max_free_head_size
(),
);
)
*
);
result
}
fn
supported_parachains
()
->
u32
{
let
mut
result
=
0
;
for_tuples!
(
#
(
...
...
bridges/primitives/runtime/src/chain.rs
View file @
aceda465
...
...
@@ -236,6 +236,12 @@ where
pub
trait
Parachain
:
Chain
{
/// Parachain identifier.
const
PARACHAIN_ID
:
u32
;
/// Maximal size of the parachain header.
///
/// This isn't a strict limit. The relayer may submit larger headers and the
/// pallet will accept the call. The limit is only used to compute whether
/// the refund can be made.
const
MAX_HEADER_SIZE
:
u32
;
}
impl
<
T
>
Parachain
for
T
...
...
@@ -244,6 +250,8 @@ where
<
T
as
UnderlyingChainProvider
>
::
Chain
:
Parachain
,
{
const
PARACHAIN_ID
:
u32
=
<<
T
as
UnderlyingChainProvider
>
::
Chain
as
Parachain
>
::
PARACHAIN_ID
;
const
MAX_HEADER_SIZE
:
u32
=
<<
T
as
UnderlyingChainProvider
>
::
Chain
as
Parachain
>
::
MAX_HEADER_SIZE
;
}
/// Adapter for `Get<u32>` to access `PARACHAIN_ID` from `trait Parachain`
...
...
@@ -306,6 +314,11 @@ macro_rules! decl_bridge_finality_runtime_apis {
pub
const
[
<
BEST_FINALIZED_
$chain:upper
_HEADER_METHOD
>
]:
&
str
=
stringify!
([
<
$chain:camel
FinalityApi_best_finalized
>
]);
/// Name of the `<ThisChain>FinalityApi::free_headers_interval` runtime method.
pub
const
[
<
FREE_HEADERS_INTERVAL_FOR_
$chain:upper
_METHOD
>
]:
&
str
=
stringify!
([
<
$chain:camel
FinalityApi_free_headers_interval
>
]);
$
(
/// Name of the `<ThisChain>FinalityApi::accepted_<consensus>_finality_proofs`
/// runtime method.
...
...
@@ -322,6 +335,13 @@ macro_rules! decl_bridge_finality_runtime_apis {
/// Returns number and hash of the best finalized header known to the bridge module.
fn
best_finalized
()
->
Option
<
bp_runtime
::
HeaderId
<
Hash
,
BlockNumber
>>
;
/// Returns free headers interval, if it is configured in the runtime.
/// The caller expects that if his transaction improves best known header
/// at least by the free_headers_interval`, it will be fee-free.
///
/// See [`pallet_bridge_grandpa::Config::FreeHeadersInterval`] for details.
fn
free_headers_interval
()
->
Option
<
BlockNumber
>
;
$
(
/// Returns the justifications accepted in the current block.
fn
[
<
synced_headers_
$consensus:lower
_info
>
](
...
...
bridges/relays/client-substrate/src/chain.rs
View file @
aceda465
...
...
@@ -46,6 +46,12 @@ pub trait Chain: ChainBase + Clone {
/// Keep in mind that this method is normally provided by the other chain, which is
/// bridged with this chain.
const
BEST_FINALIZED_HEADER_ID_METHOD
:
&
'static
str
;
/// Name of the runtime API method that is returning interval between source chain
/// headers that may be submitted for free to the target chain.
///
/// Keep in mind that this method is normally provided by the other chain, which is
/// bridged with this chain.
const
FREE_HEADERS_INTERVAL_METHOD
:
&
'static
str
;
/// Average block interval.
///
...
...
@@ -75,6 +81,9 @@ pub trait ChainWithRuntimeVersion: Chain {
pub
trait
RelayChain
:
Chain
{
/// Name of the `runtime_parachains::paras` pallet in the runtime of this chain.
const
PARAS_PALLET_NAME
:
&
'static
str
;
/// Name of the `pallet-bridge-parachains`, deployed at the **bridged** chain to sync
/// parachains of **this** chain.
const
WITH_CHAIN_BRIDGE_PARACHAINS_PALLET_NAME
:
&
'static
str
;
}
/// Substrate-based chain that is using direct GRANDPA finality from minimal relay-client point of
...
...
bridges/relays/client-substrate/src/test_chain.rs
View file @
aceda465
...
...
@@ -56,6 +56,7 @@ impl bp_runtime::Chain for TestChain {
impl
Chain
for
TestChain
{
const
NAME
:
&
'static
str
=
"Test"
;
const
BEST_FINALIZED_HEADER_ID_METHOD
:
&
'static
str
=
"TestMethod"
;
const
FREE_HEADERS_INTERVAL_METHOD
:
&
'static
str
=
"TestMethod"
;
const
AVERAGE_BLOCK_INTERVAL
:
Duration
=
Duration
::
from_millis
(
0
);
type
SignedBlock
=
sp_runtime
::
generic
::
SignedBlock
<
...
...
@@ -110,6 +111,7 @@ impl bp_runtime::Chain for TestParachainBase {
impl
bp_runtime
::
Parachain
for
TestParachainBase
{
const
PARACHAIN_ID
:
u32
=
1000
;
const
MAX_HEADER_SIZE
:
u32
=
1_024
;
}
/// Parachain that may be used in tests.
...
...
@@ -123,6 +125,7 @@ impl bp_runtime::UnderlyingChainProvider for TestParachain {
impl
Chain
for
TestParachain
{
const
NAME
:
&
'static
str
=
"TestParachain"
;
const
BEST_FINALIZED_HEADER_ID_METHOD
:
&
'static
str
=
"TestParachainMethod"
;
const
FREE_HEADERS_INTERVAL_METHOD
:
&
'static
str
=
"TestParachainMethod"
;
const
AVERAGE_BLOCK_INTERVAL
:
Duration
=
Duration
::
from_millis
(
0
);
type
SignedBlock
=
sp_runtime
::
generic
::
SignedBlock
<
...
...
bridges/relays/finality/README.md
View file @
aceda465
...
...
@@ -33,7 +33,9 @@ node. The transaction is then tracked by the relay until it is mined and finaliz
The main entrypoint for the crate is the
[
`run` function
](
./src/finality_loop.rs
)
, which takes source and target
clients and
[
`FinalitySyncParams`
](
./src/finality_loop.rs
)
parameters. The most important parameter is the
`only_mandatory_headers`
- it is set to
`true`
, the relay will only submit mandatory headers. Since transactions
with mandatory headers are fee-free, the cost of running such relay is zero (in terms of fees).
with mandatory headers are fee-free, the cost of running such relay is zero (in terms of fees). If a similar,
`only_free_headers`
parameter, is set to
`true`
, then free headers (if configured in the runtime) are also
relayed.
## Finality Relay Metrics
...
...
bridges/relays/finality/src/finality_loop.rs
View file @
aceda465
...
...
@@ -29,7 +29,7 @@ use crate::{
use
async_trait
::
async_trait
;
use
backoff
::{
backoff
::
Backoff
,
ExponentialBackoff
};
use
futures
::{
future
::
Fuse
,
select
,
Future
,
FutureExt
};
use
num_traits
::
Saturating
;
use
num_traits
::
{
Saturating
,
Zero
}
;
use
relay_utils
::{
metrics
::
MetricsParams
,
relay_loop
::
Client
as
RelayClient
,
retry_backoff
,
FailedClient
,
HeaderId
,
MaybeConnectionError
,
TrackedTransactionStatus
,
TransactionTracker
,
...
...
@@ -39,6 +39,17 @@ use std::{
time
::{
Duration
,
Instant
},
};
/// Type of headers that we relay.
#[derive(Debug,
Clone,
Copy,
PartialEq)]
pub
enum
HeadersToRelay
{
/// Relay all headers.
All
,
/// Relay only mandatory headers.
Mandatory
,
/// Relay only free (including mandatory) headers.
Free
,
}
/// Finality proof synchronization loop parameters.
#[derive(Debug,
Clone)]
pub
struct
FinalitySyncParams
{
...
...
@@ -63,7 +74,7 @@ pub struct FinalitySyncParams {
/// Timeout before we treat our transactions as lost and restart the whole sync process.
pub
stall_timeout
:
Duration
,
/// If true, only mandatory headers are relayed.
pub
only_mandatory_headers
:
bool
,
pub
headers_to_relay
:
HeadersToRelay
,
}
/// Source client used in finality synchronization loop.
...
...
@@ -90,11 +101,16 @@ pub trait TargetClient<P: FinalitySyncPipeline>: RelayClient {
&
self
,
)
->
Result
<
HeaderId
<
P
::
Hash
,
P
::
Number
>
,
Self
::
Error
>
;
/// Get free source headers submission interval, if it is configured in the
/// target runtime.
async
fn
free_source_headers_interval
(
&
self
)
->
Result
<
Option
<
P
::
Number
>
,
Self
::
Error
>
;
/// Submit header finality proof.
async
fn
submit_finality_proof
(
&
self
,
header
:
P
::
Header
,
proof
:
P
::
FinalityProof
,
is_free_execution_expected
:
bool
,
)
->
Result
<
Self
::
TransactionTracker
,
Self
::
Error
>
;
}
...
...
@@ -104,9 +120,13 @@ pub fn metrics_prefix<P: FinalitySyncPipeline>() -> String {
format!
(
"{}_to_{}_Sync"
,
P
::
SOURCE_NAME
,
P
::
TARGET_NAME
)
}
/// Finality sync information.
pub
struct
SyncInfo
<
P
:
FinalitySyncPipeline
>
{
/// Best finalized header at the source client.
pub
best_number_at_source
:
P
::
Number
,
/// Best source header, known to the target client.
pub
best_number_at_target
:
P
::
Number
,
/// Whether the target client follows the same fork as the source client do.
pub
is_using_same_fork
:
bool
,
}
...
...
@@ -183,6 +203,7 @@ impl<Tracker: TransactionTracker, Number: Debug + PartialOrd> Transaction<Tracke
target_client
:
&
TC
,
header
:
P
::
Header
,
justification
:
P
::
FinalityProof
,
is_free_execution_expected
:
bool
,
)
->
Result
<
Self
,
TC
::
Error
>
{
let
header_number
=
header
.number
();
log
::
debug!
(
...
...
@@ -193,7 +214,9 @@ impl<Tracker: TransactionTracker, Number: Debug + PartialOrd> Transaction<Tracke
P
::
TARGET_NAME
,
);
let
tracker
=
target_client
.submit_finality_proof
(
header
,
justification
)
.await
?
;
let
tracker
=
target_client
.submit_finality_proof
(
header
,
justification
,
is_free_execution_expected
)
.await
?
;
Ok
(
Transaction
{
tracker
,
header_number
})
}
...
...
@@ -292,6 +315,7 @@ impl<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>> Finality
pub
async
fn
select_header_to_submit
(
&
mut
self
,
info
:
&
SyncInfo
<
P
>
,
free_headers_interval
:
Option
<
P
::
Number
>
,
)
->
Result
<
Option
<
JustifiedHeader
<
P
>>
,
Error
<
P
,
SC
::
Error
,
TC
::
Error
>>
{
// to see that the loop is progressing
log
::
trace!
(
...
...
@@ -302,9 +326,15 @@ impl<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>> Finality
);
// read missing headers
let
selector
=
JustifiedHeaderSelector
::
new
::
<
SC
,
TC
>
(
&
self
.source_client
,
info
)
.await
?
;
let
selector
=
JustifiedHeaderSelector
::
new
::
<
SC
,
TC
>
(
&
self
.source_client
,
info
,
self
.sync_params.headers_to_relay
,
free_headers_interval
,
)
.await
?
;
// if we see that the header schedules GRANDPA change, we need to submit it
if
self
.sync_params.
only_mandatory_headers
{
if
self
.sync_params.
headers_to_relay
==
HeadersToRelay
::
Mandatory
{
return
Ok
(
selector
.select_mandatory
())
}
...
...
@@ -312,7 +342,12 @@ impl<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>> Finality
// => even if we have already selected some header and its persistent finality proof,
// we may try to select better header by reading non-persistent proofs from the stream
self
.finality_proofs_buf
.fill
(
&
mut
self
.finality_proofs_stream
);
let
maybe_justified_header
=
selector
.select
(
&
self
.finality_proofs_buf
);
let
maybe_justified_header
=
selector
.select
(
info
,
self
.sync_params.headers_to_relay
,
free_headers_interval
,
&
self
.finality_proofs_buf
,
);
// remove obsolete 'recent' finality proofs + keep its size under certain limit
let
oldest_finality_proof_to_keep
=
maybe_justified_header
...
...
@@ -329,6 +364,7 @@ impl<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>> Finality
pub
async
fn
run_iteration
(
&
mut
self
,
free_headers_interval
:
Option
<
P
::
Number
>
,
)
->
Result
<
Option
<
Transaction
<
TC
::
TransactionTracker
,
P
::
Number
>>
,
Error
<
P
,
SC
::
Error
,
TC
::
Error
>
,
...
...
@@ -345,10 +381,14 @@ impl<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>> Finality
}
// submit new header if we have something new
match
self
.select_header_to_submit
(
&
info
)
.await
?
{
match
self
.select_header_to_submit
(
&
info
,
free_headers_interval
)
.await
?
{
Some
(
header
)
=>
{
let
transaction
=
Transaction
::
submit
(
&
self
.target_client
,
header
.header
,
header
.proof
)
let
transaction
=
Transaction
::
submit
(
&
self
.target_client
,
header
.header
,
header
.proof
,
self
.sync_params.headers_to_relay
==
HeadersToRelay
::
Free
,
)
.await
.map_err
(
Error
::
Target
)
?
;
self
.best_submitted_number
=
Some
(
transaction
.header_number
);
...
...
@@ -378,9 +418,11 @@ impl<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>> Finality
let
exit_signal
=
exit_signal
.fuse
();
futures
::
pin_mut!
(
exit_signal
,
proof_submission_tx_tracker
);
let
free_headers_interval
=
free_headers_interval
(
&
self
.target_client
)
.await
?
;
loop
{
// run loop iteration
let
next_tick
=
match
self
.run_iteration
()
.await
{
let
next_tick
=
match
self
.run_iteration
(
free_headers_interval
)
.await
{
Ok
(
Some
(
tx
))
=>
{
proof_submission_tx_tracker
.set
(
tx
.track
::
<
P
,
SC
,
_
>
(
self
.target_client
.clone
())
.fuse
());
...
...
@@ -433,6 +475,52 @@ impl<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>> Finality
}
}
async
fn
free_headers_interval
<
P
:
FinalitySyncPipeline
>
(
target_client
:
&
impl
TargetClient
<
P
>
,
)
->
Result
<
Option
<
P
::
Number
>
,
FailedClient
>
{
match
target_client
.free_source_headers_interval
()
.await
{
Ok
(
Some
(
free_headers_interval
))
if
!
free_headers_interval
.is_zero
()
=>
{
log
::
trace!
(
target
:
"bridge"
,
"Free headers interval for {} headers at {} is: {:?}"
,
P
::
SOURCE_NAME
,
P
::
TARGET_NAME
,
free_headers_interval
,
);
Ok
(
Some
(
free_headers_interval
))
},
Ok
(
Some
(
_free_headers_interval
))
=>
{
log
::
trace!
(
target
:
"bridge"
,
"Free headers interval for {} headers at {} is zero. Not submitting any free headers"
,
P
::
SOURCE_NAME
,
P
::
TARGET_NAME
,
);
Ok
(
None
)
},
Ok
(
None
)
=>
{
log
::
trace!
(
target
:
"bridge"
,
"Free headers interval for {} headers at {} is None. Not submitting any free headers"
,
P
::
SOURCE_NAME
,
P
::
TARGET_NAME
,
);
Ok
(
None
)
},
Err
(
e
)
=>
{
log
::
error!
(
target
:
"bridge"
,
"Failed to read free headers interval for {} headers at {}: {:?}"
,
P
::
SOURCE_NAME
,
P
::
TARGET_NAME
,
e
,
);
Err
(
FailedClient
::
Target
)
},
}
}
/// Run finality proofs synchronization loop.
pub
async
fn
run
<
P
:
FinalitySyncPipeline
>
(
source_client
:
impl
SourceClient
<
P
>
,
...
...
@@ -509,7 +597,7 @@ mod tests {
tick
:
Duration
::
from_secs
(
0
),
recent_finality_proofs_limit
:
1024
,
stall_timeout
:
Duration
::
from_secs
(
1
),
only_mandatory_headers
:
false
,
headers_to_relay
:
HeadersToRelay
::
All
,
}
}
...
...
@@ -593,8 +681,8 @@ mod tests {
);
}
fn
run_
only_mandatory_headers
_mode_test
(
only_mandatory_headers
:
bool
,
fn
run_
headers_to_relay
_mode_test
(
headers_to_relay
:
HeadersToRelay
,
has_mandatory_headers
:
bool
,
)
->
Option
<
JustifiedHeader
<
TestFinalitySyncPipeline
>>
{
let
(
exit_sender
,
_
)
=
futures
::
channel
::
mpsc
::
unbounded
();
...
...
@@ -619,7 +707,7 @@ mod tests {
tick
:
Duration
::
from_secs
(
0
),
recent_finality_proofs_limit
:
0
,
stall_timeout
:
Duration
::
from_secs
(
0
),
only_mandatory_headers
,
headers_to_relay
,
},
None
,
);
...
...
@@ -628,16 +716,22 @@ mod tests {
best_number_at_target
:
5
,
is_using_same_fork
:
true
,
};
finality_loop
.select_header_to_submit
(
&
info
)
.await
.unwrap
()
finality_loop
.select_header_to_submit
(
&
info
,
Some
(
3
)
)
.await
.unwrap
()
})
}
#[test]
fn
select_header_to_submit_skips_non_mandatory_headers_when_only_mandatory_headers_are_required
(
)
{
assert_eq!
(
run_only_mandatory_headers_mode_test
(
true
,
false
),
None
);
fn
select_header_to_submit_may_select_non_mandatory_header
()
{
assert_eq!
(
run_headers_to_relay_mode_test
(
HeadersToRelay
::
Mandatory
,
false
),
None
);
assert_eq!
(
run_only_mandatory_headers_mode_test
(
false
,
false
),
run_headers_to_relay_mode_test
(
HeadersToRelay
::
Free
,
false
),
Some
(
JustifiedHeader
{
header
:
TestSourceHeader
(
false
,
10
,
10
),
proof
:
TestFinalityProof
(
10
)
}),
);
assert_eq!
(
run_headers_to_relay_mode_test
(
HeadersToRelay
::
All
,
false
),
Some
(
JustifiedHeader
{
header
:
TestSourceHeader
(
false
,
10
,
10
),
proof
:
TestFinalityProof
(
10
)
...
...
@@ -646,17 +740,23 @@ mod tests {
}
#[test]
fn
select_header_to_submit_selects_mandatory_headers_when_only_mandatory_headers_are_required
()
{
fn
select_header_to_submit_may_select_mandatory_header
()
{
assert_eq!
(
run_headers_to_relay_mode_test
(
HeadersToRelay
::
Mandatory
,
true
),
Some
(
JustifiedHeader
{
header
:
TestSourceHeader
(
true
,
8
,
8
),
proof
:
TestFinalityProof
(
8
)
}),
);
assert_eq!
(
run_
only_mandatory_headers_mode_test
(
tru
e
,
true
),
run_
headers_to_relay_mode_test
(
HeadersToRelay
::
Fre
e
,
true
),
Some
(
JustifiedHeader
{
header
:
TestSourceHeader
(
true
,
8
,
8
),
proof
:
TestFinalityProof
(
8
)
}),
);
assert_eq!
(
run_
only_mandatory_headers_mode_test
(
false
,
true
),
run_
headers_to_relay_mode_test
(
HeadersToRelay
::
All
,
true
),
Some
(
JustifiedHeader
{
header
:
TestSourceHeader
(
true
,
8
,
8
),
proof
:
TestFinalityProof
(
8
)
...
...
@@ -690,7 +790,7 @@ mod tests {
test_sync_params
(),
Some
(
metrics_sync
.clone
()),
);
finality_loop
.run_iteration
()
.await
.unwrap
()
finality_loop
.run_iteration
(
None
)
.await
.unwrap
()
});
assert!
(
!
metrics_sync
.is_using_same_fork
());
...
...
bridges/relays/finality/src/headers.rs
View file @
aceda465
This diff is collapsed.
Click to expand it.
bridges/relays/finality/src/lib.rs
View file @
aceda465
...
...
@@ -21,7 +21,9 @@
pub
use
crate
::{
base
::{
FinalityPipeline
,
SourceClientBase
},
finality_loop
::{
metrics_prefix
,
run
,
FinalitySyncParams
,
SourceClient
,
TargetClient
},
finality_loop
::{
metrics_prefix
,
run
,
FinalitySyncParams
,
HeadersToRelay
,
SourceClient
,
TargetClient
,
},
finality_proofs
::{
FinalityProofsBuf
,
FinalityProofsStream
},
sync_loop_metrics
::
SyncLoopMetrics
,
};
...
...
bridges/relays/finality/src/mock.rs
View file @
aceda465
...
...
@@ -198,10 +198,15 @@ impl TargetClient<TestFinalitySyncPipeline> for TestTargetClient {
Ok
(
data
.target_best_block_id
)
}
async
fn
free_source_headers_interval
(
&
self
)
->
Result
<
Option
<
TestNumber
>
,
TestError
>
{
Ok
(
Some
(
3
))
}
async
fn
submit_finality_proof
(
&
self
,
header
:
TestSourceHeader
,
proof
:
TestFinalityProof
,
_is_free_execution_expected
:
bool
,
)
->
Result
<
TestTransactionTracker
,
TestError
>
{
let
mut
data
=
self
.data
.lock
();
(
self
.on_method_call
)(
&
mut
data
);
...
...
bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs
View file @
aceda465
...
...
@@ -19,11 +19,15 @@
use
async_trait
::
async_trait
;
use
structopt
::
StructOpt
;
use
relay_utils
::
metrics
::{
GlobalMetrics
,
StandaloneMetric
};
use
relay_utils
::{
metrics
::{
GlobalMetrics
,
StandaloneMetric
},
UniqueSaturatedInto
,
};
use
crate
::{
cli
::{
bridge
::
*
,
chain_schema
::
*
,
PrometheusParams
},
finality
::
SubstrateFinalitySyncPipeline
,
HeadersToRelay
,
};
/// Chain headers relaying params.
...
...
@@ -33,6 +37,10 @@ pub struct RelayHeadersParams {
/// are relayed.
#[structopt(long)]
only_mandatory_headers
:
bool
,
/// If passed, only free headers (mandatory and every Nth header, if configured in runtime)
/// are relayed. Overrides `only_mandatory_headers`.
#[structopt(long)]
only_free_headers
:
bool
,
#[structopt(flatten)]
source
:
SourceConnectionParams
,
#[structopt(flatten)]
...
...
@@ -43,11 +51,37 @@ pub struct RelayHeadersParams {
prometheus_params
:
PrometheusParams
,
}
/// Single header relaying params.
#[derive(StructOpt)]
pub
struct
RelayHeaderParams
{
#[structopt(flatten)]
source
:
SourceConnectionParams
,
#[structopt(flatten)]
target
:
TargetConnectionParams
,
#[structopt(flatten)]
target_sign
:
TargetSigningParams
,
/// Number of the source chain header that we want to relay. It must have a persistent
/// storage proof at the [`Self::source`] node, otherwise the command will fail.
#[structopt(long)]
number
:
u128
,
}
impl
RelayHeadersParams
{
fn
headers_to_relay
(
&
self
)
->
HeadersToRelay
{
match
(
self
.only_mandatory_headers
,
self
.only_free_headers
)
{
(
_
,
true
)
=>
HeadersToRelay
::
Free
,
(
true
,
false
)
=>
HeadersToRelay
::
Mandatory
,
_
=>
HeadersToRelay
::
All
,
}
}
}
/// Trait used for relaying headers between 2 chains.
#[async_trait]
pub
trait
HeadersRelayer
:
RelayToRelayHeadersCliBridge
{
/// Relay headers.
async
fn
relay_headers
(
data
:
RelayHeadersParams
)
->
anyhow
::
Result
<
()
>
{
let
headers_to_relay
=
data
.headers_to_relay
();
let
source_client
=
data
.source.into_client
::
<
Self
::
Source
>
()
.await
?
;
let
target_client
=
data
.target.into_client
::
<
Self
::
Target
>
()
.await
?
;
let
target_transactions_mortality
=
data
.target_sign.target_transactions_mortality
;
...
...
@@ -67,10 +101,29 @@ pub trait HeadersRelayer: RelayToRelayHeadersCliBridge {
crate
::
finality
::
run
::
<
Self
::
Finality
>
(
source_client
,
target_client
,
data
.only_mandatory_headers
,
headers_to_relay
,
target_transactions_params
,
metrics_params
,
)
.await
}
/// Relay single header. No checks are made to ensure that transaction will succeed.
async
fn
relay_header
(
data
:
RelayHeaderParams
)
->
anyhow
::
Result
<
()
>
{
let
source_client
=
data
.source.into_client
::
<
Self
::
Source
>
()
.await
?
;
let
target_client
=
data
.target.into_client
::
<
Self
::
Target
>
()
.await
?
;
let
target_transactions_mortality
=
data
.target_sign.target_transactions_mortality
;
let
target_sign
=
data
.target_sign.to_keypair
::
<
Self
::
Target
>
()
?
;
crate
::
finality
::
relay_single_header
::
<
Self
::
Finality
>
(
source_client
,
target_client
,
crate
::
TransactionParams
{
signer
:
target_sign
,
mortality
:
target_transactions_mortality
,
},
data
.number
.unique_saturated_into
(),
)
.await
}
}
Prev
1
2
3
4
5
6
…
17
Next