Unverified Commit 66ef2d3b authored by Gavin Wood's avatar Gavin Wood Committed by GitHub
Browse files

XCM: Automatic Version Negotiation (#3736)



* XCM: Automatic Version Negotiation

* Introduce the version instructions and subscription trait

* Notification and subscription data migration

* Version change subscriptions

* Fixes

* Formatting

* Spelling

* Fixes

* Fixes

* Automatic unsubscription

* Formatting

* Expose remote origin in VM and ensure it is unchanged from actual origin in subscription instructions.

* Barrier

* Unsubscription extrinsic

* Remove top_level param

* Formatting

* Fixes

* Automatic subscription

* Formatting

* Spelling

* Unit tests for XCM executor

* Formatting

* Spellin

* Unit test for XCM pallet subscriber side

* Formatting

* More tests

* Formatting

* Fixes

* Subscription-side tests

* Formatting

* Unit tests for XCM pallet

* Formatting

* Update roadmap/implementers-guide/src/types/overseer-protocol.md
Co-authored-by: Shawn Tabrizi's avatarShawn Tabrizi <shawntabrizi@gmail.com>

* Remove commented code

* Grumbles

* Multi-stage XCM version migration

* Formatting

* v1 subscriptions backport

* Warning

* Spelling

* Fix grumbles

* Formatting

* Avoid running through old notifications

* Formatting
Co-authored-by: Shawn Tabrizi's avatarShawn Tabrizi <shawntabrizi@gmail.com>
parent 15f8e4a6
Pipeline #155289 passed with stages
in 41 minutes and 12 seconds
......@@ -441,7 +441,7 @@ enum DisputeCoordinatorMessage {
pending_confirmation: oneshot::Sender<ImportStatementsResult>
},
/// Fetch a list of all recent disputes that the co-ordinator is aware of.
/// These are disputes which have occured any time in recent sessions, which may have already concluded.
/// These are disputes which have occurred any time in recent sessions, which may have already concluded.
RecentDisputes(ResponseChannel<Vec<(SessionIndex, CandidateHash)>>),
/// Fetch a list of all active disputes that the co-ordinator is aware of.
/// These disputes are either unconcluded or recently concluded.
......@@ -699,7 +699,7 @@ The Runtime API subsystem is responsible for providing an interface to the state
This is fueled by an auxiliary type encapsulating all request types defined in the Runtime API section of the guide.
> TODO: link to the Runtime API section. Not possible currently because of https://github.com/Michael-F-Bryan/mdbook-linkcheck/issues/25. Once v0.7.1 is released it will work.
> To do: link to the Runtime API section. Not possible currently because of https://github.com/Michael-F-Bryan/mdbook-linkcheck/issues/25. Once v0.7.1 is released it will work.
```rust
enum RuntimeApiRequest {
......
......@@ -1307,6 +1307,7 @@ impl xcm_executor::Config for XcmConfig {
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
type SubscriptionService = XcmPallet;
}
parameter_types! {
......@@ -1326,7 +1327,6 @@ pub type LocalOriginToLocation = (
// And a usual Signed origin to be used in XCM as a corresponding AccountId32
SignedToAccountId32<Origin, AccountId, KusamaNetwork>,
);
impl pallet_xcm::Config for Runtime {
type Event = Event;
type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<Origin, LocalOriginToLocation>;
......@@ -1342,6 +1342,8 @@ impl pallet_xcm::Config for Runtime {
type LocationInverter = LocationInverter<Ancestry>;
type Origin = Origin;
type Call = Call;
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
}
parameter_types! {
......
......@@ -675,6 +675,7 @@ impl xcm_executor::Config for XcmConfig {
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
type SubscriptionService = XcmPallet;
}
parameter_types! {
......@@ -706,6 +707,8 @@ impl pallet_xcm::Config for Runtime {
type LocationInverter = LocationInverter<Ancestry>;
type Origin = Origin;
type Call = Call;
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
}
impl parachains_session_info::Config for Runtime {}
......
......@@ -518,6 +518,8 @@ impl pallet_xcm::Config for Runtime {
type XcmReserveTransferFilter = Everything;
type Origin = Origin;
type Call = Call;
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
}
impl parachains_hrmp::Config for Runtime {
......@@ -540,7 +542,7 @@ impl pallet_test_notifier::Config for Runtime {
pub mod pallet_test_notifier {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use pallet_xcm::{ensure_response, QueryId};
use pallet_xcm::ensure_response;
use sp_runtime::DispatchResult;
use xcm::latest::prelude::*;
......
......@@ -88,4 +88,5 @@ impl xcm_executor::Config for XcmConfig {
type ResponseHandler = super::Xcm;
type AssetTrap = super::Xcm;
type AssetClaims = super::Xcm;
type SubscriptionService = super::Xcm;
}
......@@ -949,6 +949,7 @@ impl xcm_executor::Config for XcmConfig {
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
type SubscriptionService = XcmPallet;
}
/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location
......@@ -973,6 +974,8 @@ impl pallet_xcm::Config for Runtime {
type LocationInverter = LocationInverter<Ancestry>;
type Origin = Origin;
type Call = Call;
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
}
construct_runtime! {
......
This diff is collapsed.
......@@ -22,11 +22,11 @@ use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32};
pub use sp_std::{cell::RefCell, fmt::Debug, marker::PhantomData};
use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, Case,
ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser,
CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete,
LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation,
TakeWeightCredit,
AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia,
ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible,
FixedWeightBounds, IsConcrete, LocationInverter, SignedAccountId32AsNative,
SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
};
use xcm_executor::XcmExecutor;
......@@ -133,9 +133,16 @@ construct_runtime!(
thread_local! {
pub static SENT_XCM: RefCell<Vec<(MultiLocation, Xcm<()>)>> = RefCell::new(Vec::new());
}
pub fn sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> {
pub(crate) fn sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> {
SENT_XCM.with(|q| (*q.borrow()).clone())
}
pub(crate) fn take_sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> {
SENT_XCM.with(|q| {
let mut r = Vec::new();
std::mem::swap(&mut r, &mut *q.borrow_mut());
r
})
}
/// Sender that never returns error, always sends
pub struct TestSendXcm;
impl SendXcm for TestSendXcm {
......@@ -236,6 +243,7 @@ pub type Barrier = (
TakeWeightCredit,
AllowTopLevelPaidExecutionFrom<Everything>,
AllowKnownQueryResponses<XcmPallet>,
AllowSubscriptionsFrom<Everything>,
);
pub struct XcmConfig;
......@@ -253,10 +261,15 @@ impl xcm_executor::Config for XcmConfig {
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
type SubscriptionService = XcmPallet;
}
pub type LocalOriginToLocation = SignedToAccountId32<Origin, AccountId, AnyNetwork>;
parameter_types! {
pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 2;
}
impl pallet_xcm::Config for Test {
type Event = Event;
type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<Origin, LocalOriginToLocation>;
......@@ -270,6 +283,8 @@ impl pallet_xcm::Config for Test {
type LocationInverter = LocationInverter<Ancestry>;
type Origin = Origin;
type Call = Call;
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
type AdvertisedXcmVersion = AdvertisedXcmVersion;
}
impl origin::Config for Test {}
......
......@@ -14,13 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use crate::{mock::*, AssetTraps, QueryStatus};
use frame_support::{assert_noop, assert_ok, traits::Currency};
use crate::{
mock::*, AssetTraps, CurrentMigration, Error, LatestVersionedMultiLocation, Queries,
QueryStatus, VersionDiscoveryQueue, VersionNotifiers, VersionNotifyTargets,
};
use frame_support::{
assert_noop, assert_ok,
traits::{Currency, Hooks},
};
use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId};
use sp_runtime::traits::{BlakeTwo256, Hash};
use std::convert::TryInto;
use xcm::{latest::prelude::*, VersionedMultiAssets, VersionedXcm};
use xcm_executor::XcmExecutor;
use xcm::prelude::*;
use xcm_builder::AllowKnownQueryResponses;
use xcm_executor::{traits::ShouldExecute, XcmExecutor};
const ALICE: AccountId = AccountId::new([0u8; 32]);
const BOB: AccountId = AccountId::new([1u8; 32]);
......@@ -375,3 +382,423 @@ fn trapped_assets_can_be_claimed() {
);
});
}
#[test]
fn fake_latest_versioned_multilocation_works() {
use codec::Encode;
let remote = Parachain(1000).into();
let versioned_remote = LatestVersionedMultiLocation(&remote);
assert_eq!(versioned_remote.encode(), VersionedMultiLocation::from(remote.clone()).encode());
}
#[test]
fn basic_subscription_works() {
new_test_ext_with_balances(vec![]).execute_with(|| {
let remote = Parachain(1000).into();
assert_ok!(XcmPallet::force_subscribe_version_notify(
Origin::root(),
Box::new(remote.clone().into()),
));
assert_eq!(
Queries::<Test>::iter().collect::<Vec<_>>(),
vec![(
0,
QueryStatus::VersionNotifier { origin: remote.clone().into(), is_active: false }
)]
);
assert_eq!(
VersionNotifiers::<Test>::iter().collect::<Vec<_>>(),
vec![(2, remote.clone().into(), 0)]
);
assert_eq!(
take_sent_xcm(),
vec![(
remote.clone(),
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]),
),]
);
let weight = BaseXcmWeight::get();
let mut message = Xcm::<()>(vec![
// Remote supports XCM v1
QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) },
]);
assert_ok!(AllowKnownQueryResponses::<XcmPallet>::should_execute(
&remote,
&mut message,
weight,
&mut 0
));
});
}
#[test]
fn subscriptions_increment_id() {
new_test_ext_with_balances(vec![]).execute_with(|| {
let remote = Parachain(1000).into();
assert_ok!(XcmPallet::force_subscribe_version_notify(
Origin::root(),
Box::new(remote.clone().into()),
));
let remote2 = Parachain(1001).into();
assert_ok!(XcmPallet::force_subscribe_version_notify(
Origin::root(),
Box::new(remote2.clone().into()),
));
assert_eq!(
take_sent_xcm(),
vec![
(
remote.clone(),
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]),
),
(
remote2.clone(),
Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: 0 }]),
),
]
);
});
}
#[test]
fn double_subscription_fails() {
new_test_ext_with_balances(vec![]).execute_with(|| {
let remote = Parachain(1000).into();
assert_ok!(XcmPallet::force_subscribe_version_notify(
Origin::root(),
Box::new(remote.clone().into()),
));
assert_noop!(
XcmPallet::force_subscribe_version_notify(
Origin::root(),
Box::new(remote.clone().into())
),
Error::<Test>::AlreadySubscribed,
);
})
}
#[test]
fn unsubscribe_works() {
new_test_ext_with_balances(vec![]).execute_with(|| {
let remote = Parachain(1000).into();
assert_ok!(XcmPallet::force_subscribe_version_notify(
Origin::root(),
Box::new(remote.clone().into()),
));
assert_ok!(XcmPallet::force_unsubscribe_version_notify(
Origin::root(),
Box::new(remote.clone().into())
));
assert_noop!(
XcmPallet::force_unsubscribe_version_notify(
Origin::root(),
Box::new(remote.clone().into())
),
Error::<Test>::NoSubscription,
);
assert_eq!(
take_sent_xcm(),
vec![
(
remote.clone(),
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]),
),
(remote.clone(), Xcm(vec![UnsubscribeVersion]),),
]
);
});
}
/// Parachain 1000 is asking us for a version subscription.
#[test]
fn subscription_side_works() {
new_test_ext_with_balances(vec![]).execute_with(|| {
AdvertisedXcmVersion::set(1);
let remote = Parachain(1000).into();
let weight = BaseXcmWeight::get();
let message = Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]);
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote.clone(), message, weight);
assert_eq!(r, Outcome::Complete(weight));
let instr = QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) };
assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]);
// A runtime upgrade which doesn't alter the version sends no notifications.
XcmPallet::on_runtime_upgrade();
XcmPallet::on_initialize(1);
assert_eq!(take_sent_xcm(), vec![]);
// New version.
AdvertisedXcmVersion::set(2);
// A runtime upgrade which alters the version does send notifications.
XcmPallet::on_runtime_upgrade();
XcmPallet::on_initialize(2);
let instr = QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) };
assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]);
});
}
#[test]
fn subscription_side_upgrades_work_with_notify() {
new_test_ext_with_balances(vec![]).execute_with(|| {
AdvertisedXcmVersion::set(1);
// An entry from a previous runtime with v0 XCM.
let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000));
let v0_location = VersionedMultiLocation::from(v0_location);
VersionNotifyTargets::<Test>::insert(0, v0_location, (69, 0, 1));
let v1_location = Parachain(1001).into().versioned();
VersionNotifyTargets::<Test>::insert(1, v1_location, (70, 0, 1));
let v2_location = Parachain(1002).into().versioned();
VersionNotifyTargets::<Test>::insert(2, v2_location, (71, 0, 1));
// New version.
AdvertisedXcmVersion::set(2);
// A runtime upgrade which alters the version does send notifications.
XcmPallet::on_runtime_upgrade();
XcmPallet::on_initialize(1);
let instr0 = QueryResponse { query_id: 69, max_weight: 0, response: Response::Version(2) };
let instr1 = QueryResponse { query_id: 70, max_weight: 0, response: Response::Version(2) };
let instr2 = QueryResponse { query_id: 71, max_weight: 0, response: Response::Version(2) };
let mut sent = take_sent_xcm();
sent.sort_by_key(|k| match (k.1).0[0] {
QueryResponse { query_id: q, .. } => q,
_ => 0,
});
assert_eq!(
sent,
vec![
(Parachain(1000).into(), Xcm(vec![instr0])),
(Parachain(1001).into(), Xcm(vec![instr1])),
(Parachain(1002).into(), Xcm(vec![instr2])),
]
);
let mut contents = VersionNotifyTargets::<Test>::iter().collect::<Vec<_>>();
contents.sort_by_key(|k| k.2);
assert_eq!(
contents,
vec![
(2, Parachain(1000).into().versioned(), (69, 0, 2)),
(2, Parachain(1001).into().versioned(), (70, 0, 2)),
(2, Parachain(1002).into().versioned(), (71, 0, 2)),
]
);
});
}
#[test]
fn subscription_side_upgrades_work_without_notify() {
new_test_ext_with_balances(vec![]).execute_with(|| {
// An entry from a previous runtime with v0 XCM.
let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000));
let v0_location = VersionedMultiLocation::from(v0_location);
VersionNotifyTargets::<Test>::insert(0, v0_location, (69, 0, 2));
let v1_location = Parachain(1001).into().versioned();
VersionNotifyTargets::<Test>::insert(1, v1_location, (70, 0, 2));
let v2_location = Parachain(1002).into().versioned();
VersionNotifyTargets::<Test>::insert(2, v2_location, (71, 0, 2));
// A runtime upgrade which alters the version does send notifications.
XcmPallet::on_runtime_upgrade();
XcmPallet::on_initialize(1);
let mut contents = VersionNotifyTargets::<Test>::iter().collect::<Vec<_>>();
contents.sort_by_key(|k| k.2);
assert_eq!(
contents,
vec![
(2, Parachain(1000).into().versioned(), (69, 0, 2)),
(2, Parachain(1001).into().versioned(), (70, 0, 2)),
(2, Parachain(1002).into().versioned(), (71, 0, 2)),
]
);
});
}
#[test]
fn subscriber_side_subscription_works() {
new_test_ext_with_balances(vec![]).execute_with(|| {
let remote = Parachain(1000).into();
assert_ok!(XcmPallet::force_subscribe_version_notify(
Origin::root(),
Box::new(remote.clone().into()),
));
take_sent_xcm();
// Assume subscription target is working ok.
let weight = BaseXcmWeight::get();
let message = Xcm(vec![
// Remote supports XCM v1
QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) },
]);
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote.clone(), message, weight);
assert_eq!(r, Outcome::Complete(weight));
assert_eq!(take_sent_xcm(), vec![]);
// This message cannot be sent to a v1 remote.
let v2_msg = Xcm::<()>(vec![Trap(0)]);
assert_eq!(XcmPallet::wrap_version(&remote, v2_msg.clone()), Err(()));
let message = Xcm(vec![
// Remote upgraded to XCM v2
QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) },
]);
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote.clone(), message, weight);
assert_eq!(r, Outcome::Complete(weight));
// This message can now be sent to remote as it's v2.
assert_eq!(
XcmPallet::wrap_version(&remote, v2_msg.clone()),
Ok(VersionedXcm::from(v2_msg))
);
});
}
/// We should autosubscribe when we don't know the remote's version.
#[test]
fn auto_subscription_works() {
new_test_ext_with_balances(vec![]).execute_with(|| {
let remote0 = Parachain(1000).into();
let remote1 = Parachain(1001).into();
assert_ok!(XcmPallet::force_default_xcm_version(Origin::root(), Some(1)));
// Wrapping a version for a destination we don't know elicits a subscription.
let v1_msg = xcm::v1::Xcm::<()>::QueryResponse {
query_id: 1,
response: xcm::v1::Response::Assets(vec![].into()),
};
let v2_msg = Xcm::<()>(vec![Trap(0)]);
assert_eq!(
XcmPallet::wrap_version(&remote0, v1_msg.clone()),
Ok(VersionedXcm::from(v1_msg.clone())),
);
assert_eq!(XcmPallet::wrap_version(&remote0, v2_msg.clone()), Err(()));
let expected = vec![(remote0.clone().into(), 2)];
assert_eq!(VersionDiscoveryQueue::<Test>::get().into_inner(), expected);
assert_eq!(XcmPallet::wrap_version(&remote0, v2_msg.clone()), Err(()));
assert_eq!(XcmPallet::wrap_version(&remote1, v2_msg.clone()), Err(()));
let expected = vec![(remote0.clone().into(), 3), (remote1.clone().into(), 1)];
assert_eq!(VersionDiscoveryQueue::<Test>::get().into_inner(), expected);
XcmPallet::on_initialize(1);
assert_eq!(
take_sent_xcm(),
vec![(
remote0.clone(),
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]),
)]
);
// Assume remote0 is working ok and XCM version 2.
let weight = BaseXcmWeight::get();
let message = Xcm(vec![
// Remote supports XCM v2
QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) },
]);
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote0.clone(), message, weight);
assert_eq!(r, Outcome::Complete(weight));
// This message can now be sent to remote0 as it's v2.
assert_eq!(
XcmPallet::wrap_version(&remote0, v2_msg.clone()),
Ok(VersionedXcm::from(v2_msg.clone()))
);
XcmPallet::on_initialize(2);
assert_eq!(
take_sent_xcm(),
vec![(
remote1.clone(),
Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: 0 }]),
)]
);
// Assume remote1 is working ok and XCM version 1.
let weight = BaseXcmWeight::get();
let message = Xcm(vec![
// Remote supports XCM v1
QueryResponse { query_id: 1, max_weight: 0, response: Response::Version(1) },
]);
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote1.clone(), message, weight);
assert_eq!(r, Outcome::Complete(weight));
// v2 messages cannot be sent to remote1...
assert_eq!(XcmPallet::wrap_version(&remote1, v1_msg.clone()), Ok(VersionedXcm::V1(v1_msg)));
assert_eq!(XcmPallet::wrap_version(&remote1, v2_msg.clone()), Err(()));
})
}
#[test]
fn subscription_side_upgrades_work_with_multistage_notify() {
new_test_ext_with_balances(vec![]).execute_with(|| {
AdvertisedXcmVersion::set(1);
// An entry from a previous runtime with v0 XCM.
let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000));
let v0_location = VersionedMultiLocation::from(v0_location);
VersionNotifyTargets::<Test>::insert(0, v0_location, (69, 0, 1));
let v1_location = Parachain(1001).into().versioned();
VersionNotifyTargets::<Test>::insert(1, v1_location, (70, 0, 1));
let v2_location = Parachain(1002).into().versioned();
VersionNotifyTargets::<Test>::insert(2, v2_location, (71, 0, 1));
// New version.
AdvertisedXcmVersion::set(2);
// A runtime upgrade which alters the version does send notifications.
XcmPallet::on_runtime_upgrade();
let mut maybe_migration = CurrentMigration::<Test>::take();
let mut counter = 0;
while let Some(migration) = maybe_migration.take() {
counter += 1;
let (_, m) = XcmPallet::check_xcm_version_change(migration, 0);
maybe_migration = m;
}
assert_eq!(counter, 4);
let instr0 = QueryResponse { query_id: 69, max_weight: 0, response: Response::Version(2) };
let instr1 = QueryResponse { query_id: 70, max_weight: 0, response: Response::Version(2) };
let instr2 = QueryResponse { query_id: 71, max_weight: 0, response: Response::Version(2) };