mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-19 16:55:43 +00:00
Dispute Coordinator: Batch queries (#3459)
* disputes: Allow batch queries in dispute-coordinator This commit moves to batch queries when responding to QueryCandidateVotes messages. This simplifies the code in the provisioner and dispute-coordinator by no longer requiring to make use of a FuturesOrdered when awaiting multiple quries. Instead, the provisioner need only request the batch itself. * node/approval-voting: Address Feedback to fail on query element missing. * Address feedback * Fix implementer's guide
This commit is contained in:
@@ -444,18 +444,27 @@ async fn handle_incoming(
|
|||||||
let _ = rx.send(collect_active(recent_disputes, now));
|
let _ = rx.send(collect_active(recent_disputes, now));
|
||||||
}
|
}
|
||||||
DisputeCoordinatorMessage::QueryCandidateVotes(
|
DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
session,
|
query,
|
||||||
candidate_hash,
|
|
||||||
rx
|
rx
|
||||||
) => {
|
) => {
|
||||||
let candidate_votes = db::v1::load_candidate_votes(
|
let mut query_output = Vec::new();
|
||||||
store,
|
for (session_index, candidate_hash) in query.into_iter() {
|
||||||
&config.column_config(),
|
if let Some(v) = db::v1::load_candidate_votes(
|
||||||
session,
|
store,
|
||||||
&candidate_hash,
|
&config.column_config(),
|
||||||
)?;
|
session_index,
|
||||||
|
&candidate_hash,
|
||||||
let _ = rx.send(candidate_votes.map(Into::into));
|
)? {
|
||||||
|
query_output.push((session_index, candidate_hash, v.into()));
|
||||||
|
} else {
|
||||||
|
tracing::debug!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
session_index,
|
||||||
|
"No votes found for candidate",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = rx.send(query_output);
|
||||||
}
|
}
|
||||||
DisputeCoordinatorMessage::IssueLocalStatement(
|
DisputeCoordinatorMessage::IssueLocalStatement(
|
||||||
session,
|
session,
|
||||||
|
|||||||
@@ -332,13 +332,12 @@ fn conflicting_votes_lead_to_dispute_participation() {
|
|||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
virtual_overseer.send(FromOverseer::Communication {
|
virtual_overseer.send(FromOverseer::Communication {
|
||||||
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
session,
|
vec![(session, candidate_hash)],
|
||||||
candidate_hash,
|
|
||||||
tx,
|
tx,
|
||||||
),
|
),
|
||||||
}).await;
|
}).await;
|
||||||
|
|
||||||
let votes = rx.await.unwrap().unwrap();
|
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
|
||||||
assert_eq!(votes.valid.len(), 1);
|
assert_eq!(votes.valid.len(), 1);
|
||||||
assert_eq!(votes.invalid.len(), 1);
|
assert_eq!(votes.invalid.len(), 1);
|
||||||
}
|
}
|
||||||
@@ -360,13 +359,12 @@ fn conflicting_votes_lead_to_dispute_participation() {
|
|||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
virtual_overseer.send(FromOverseer::Communication {
|
virtual_overseer.send(FromOverseer::Communication {
|
||||||
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
session,
|
vec![(session, candidate_hash)],
|
||||||
candidate_hash,
|
|
||||||
tx,
|
tx,
|
||||||
),
|
),
|
||||||
}).await;
|
}).await;
|
||||||
|
|
||||||
let votes = rx.await.unwrap().unwrap();
|
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
|
||||||
assert_eq!(votes.valid.len(), 1);
|
assert_eq!(votes.valid.len(), 1);
|
||||||
assert_eq!(votes.invalid.len(), 2);
|
assert_eq!(votes.invalid.len(), 2);
|
||||||
}
|
}
|
||||||
@@ -430,13 +428,12 @@ fn positive_votes_dont_trigger_participation() {
|
|||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
virtual_overseer.send(FromOverseer::Communication {
|
virtual_overseer.send(FromOverseer::Communication {
|
||||||
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
session,
|
vec![(session, candidate_hash)],
|
||||||
candidate_hash,
|
|
||||||
tx,
|
tx,
|
||||||
),
|
),
|
||||||
}).await;
|
}).await;
|
||||||
|
|
||||||
let votes = rx.await.unwrap().unwrap();
|
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
|
||||||
assert_eq!(votes.valid.len(), 1);
|
assert_eq!(votes.valid.len(), 1);
|
||||||
assert!(votes.invalid.is_empty());
|
assert!(votes.invalid.is_empty());
|
||||||
}
|
}
|
||||||
@@ -465,13 +462,12 @@ fn positive_votes_dont_trigger_participation() {
|
|||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
virtual_overseer.send(FromOverseer::Communication {
|
virtual_overseer.send(FromOverseer::Communication {
|
||||||
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
session,
|
vec![(session, candidate_hash)],
|
||||||
candidate_hash,
|
|
||||||
tx,
|
tx,
|
||||||
),
|
),
|
||||||
}).await;
|
}).await;
|
||||||
|
|
||||||
let votes = rx.await.unwrap().unwrap();
|
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
|
||||||
assert_eq!(votes.valid.len(), 2);
|
assert_eq!(votes.valid.len(), 2);
|
||||||
assert!(votes.invalid.is_empty());
|
assert!(votes.invalid.is_empty());
|
||||||
}
|
}
|
||||||
@@ -536,13 +532,12 @@ fn wrong_validator_index_is_ignored() {
|
|||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
virtual_overseer.send(FromOverseer::Communication {
|
virtual_overseer.send(FromOverseer::Communication {
|
||||||
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
msg: DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
session,
|
vec![(session, candidate_hash)],
|
||||||
candidate_hash,
|
|
||||||
tx,
|
tx,
|
||||||
),
|
),
|
||||||
}).await;
|
}).await;
|
||||||
|
|
||||||
let votes = rx.await.unwrap().unwrap();
|
let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone();
|
||||||
assert!(votes.valid.is_empty());
|
assert!(votes.valid.is_empty());
|
||||||
assert!(votes.invalid.is_empty());
|
assert!(votes.invalid.is_empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ use bitvec::vec::BitVec;
|
|||||||
use futures::{
|
use futures::{
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
stream::FuturesOrdered,
|
|
||||||
};
|
};
|
||||||
use polkadot_node_subsystem::{
|
use polkadot_node_subsystem::{
|
||||||
errors::{ChainApiError, RuntimeApiError}, PerLeafSpan, SubsystemSender, jaeger,
|
errors::{ChainApiError, RuntimeApiError}, PerLeafSpan, SubsystemSender, jaeger,
|
||||||
@@ -568,37 +567,22 @@ async fn select_disputes(
|
|||||||
|
|
||||||
// Load all votes for all disputes from the coordinator.
|
// Load all votes for all disputes from the coordinator.
|
||||||
let dispute_candidate_votes = {
|
let dispute_candidate_votes = {
|
||||||
let mut awaited_votes = FuturesOrdered::new();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
sender.send_message(DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
|
recent_disputes,
|
||||||
|
tx,
|
||||||
|
).into()).await;
|
||||||
|
|
||||||
let n_disputes = recent_disputes.len();
|
match rx.await {
|
||||||
for (session_index, candidate_hash) in recent_disputes {
|
Ok(v) => v,
|
||||||
let (tx, rx) = oneshot::channel();
|
Err(oneshot::Canceled) => {
|
||||||
sender.send_message(DisputeCoordinatorMessage::QueryCandidateVotes(
|
tracing::debug!(
|
||||||
session_index,
|
target: LOG_TARGET,
|
||||||
candidate_hash,
|
"Unable to query candidate votes - subsystem disconnected?",
|
||||||
tx,
|
);
|
||||||
).into()).await;
|
Vec::new()
|
||||||
|
|
||||||
awaited_votes.push(async move {
|
|
||||||
rx.await
|
|
||||||
.map_err(Error::CanceledCandidateVotes)
|
|
||||||
.map(|maybe_votes| maybe_votes.map(|v| (session_index, candidate_hash, v)))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sadly `StreamExt::collect` requires `Default`, so we have to do this more
|
|
||||||
// manually.
|
|
||||||
let mut vote_sets = Vec::with_capacity(n_disputes);
|
|
||||||
while let Some(res) = awaited_votes.next().await {
|
|
||||||
// sanity check - anything present in recent disputes should have
|
|
||||||
// candidate votes. but we might race with block import on
|
|
||||||
// session boundaries.
|
|
||||||
if let Some(vote_set) = res? {
|
|
||||||
vote_sets.push(vote_set);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vote_sets
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Transform all `CandidateVotes` into `MultiDisputeStatementSet`.
|
// Transform all `CandidateVotes` into `MultiDisputeStatementSet`.
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
//! # Sending and receiving of `DisputeRequest`s.
|
//! # Sending and receiving of `DisputeRequest`s.
|
||||||
//!
|
//!
|
||||||
//! This subsystem essentially consists of two parts:
|
//! This subsystem essentially consists of two parts:
|
||||||
@@ -136,7 +135,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<AD> DisputeDistributionSubsystem<AD>
|
impl<AD> DisputeDistributionSubsystem<AD>
|
||||||
where
|
where
|
||||||
AD: AuthorityDiscovery + Clone,
|
AD: AuthorityDiscovery + Clone,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -352,11 +352,12 @@ async fn get_candidate_votes<Context: SubsystemContext>(
|
|||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
ctx.send_message(AllMessages::DisputeCoordinator(
|
ctx.send_message(AllMessages::DisputeCoordinator(
|
||||||
DisputeCoordinatorMessage::QueryCandidateVotes(
|
DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
session_index,
|
vec![(session_index, candidate_hash)],
|
||||||
candidate_hash,
|
|
||||||
tx
|
tx
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
rx.await.map_err(|_| NonFatal::AskCandidateVotesCanceled)
|
rx.await
|
||||||
|
.map(|v| v.get(0).map(|inner| inner.to_owned().2))
|
||||||
|
.map_err(|_| NonFatal::AskCandidateVotesCanceled)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,9 +151,9 @@ fn received_request_triggers_import() {
|
|||||||
message.clone().into(),
|
message.clone().into(),
|
||||||
ImportStatementsResult::InvalidImport,
|
ImportStatementsResult::InvalidImport,
|
||||||
true,
|
true,
|
||||||
move |handle, req_tx, message|
|
move |handle, req_tx, message|
|
||||||
nested_network_dispute_request(
|
nested_network_dispute_request(
|
||||||
handle,
|
handle,
|
||||||
req_tx,
|
req_tx,
|
||||||
MOCK_AUTHORITY_DISCOVERY.get_peer_id_by_authority(Sr25519Keyring::Bob),
|
MOCK_AUTHORITY_DISCOVERY.get_peer_id_by_authority(Sr25519Keyring::Bob),
|
||||||
message.clone().into(),
|
message.clone().into(),
|
||||||
@@ -226,8 +226,8 @@ fn received_request_triggers_import() {
|
|||||||
|
|
||||||
// But should work fine for Bob:
|
// But should work fine for Bob:
|
||||||
nested_network_dispute_request(
|
nested_network_dispute_request(
|
||||||
&mut handle,
|
&mut handle,
|
||||||
&mut req_tx,
|
&mut req_tx,
|
||||||
MOCK_AUTHORITY_DISCOVERY.get_peer_id_by_authority(Sr25519Keyring::Bob),
|
MOCK_AUTHORITY_DISCOVERY.get_peer_id_by_authority(Sr25519Keyring::Bob),
|
||||||
message.clone().into(),
|
message.clone().into(),
|
||||||
ImportStatementsResult::ValidImport,
|
ImportStatementsResult::ValidImport,
|
||||||
@@ -258,15 +258,15 @@ fn disputes_are_recovered_at_startup() {
|
|||||||
handle.recv().await,
|
handle.recv().await,
|
||||||
AllMessages::DisputeCoordinator(
|
AllMessages::DisputeCoordinator(
|
||||||
DisputeCoordinatorMessage::QueryCandidateVotes(
|
DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||||
session_index,
|
query,
|
||||||
candidate_hash,
|
|
||||||
tx,
|
tx,
|
||||||
)
|
)
|
||||||
) => {
|
) => {
|
||||||
|
let (session_index, candidate_hash) = query.get(0).unwrap().clone();
|
||||||
assert_eq!(session_index, MOCK_SESSION_INDEX);
|
assert_eq!(session_index, MOCK_SESSION_INDEX);
|
||||||
assert_eq!(candidate_hash, candidate.hash());
|
assert_eq!(candidate_hash, candidate.hash());
|
||||||
let unchecked: UncheckedDisputeMessage = message.into();
|
let unchecked: UncheckedDisputeMessage = message.into();
|
||||||
tx.send(Some(CandidateVotes {
|
tx.send(vec![(session_index, candidate_hash, CandidateVotes {
|
||||||
candidate_receipt: candidate,
|
candidate_receipt: candidate,
|
||||||
valid: vec![(
|
valid: vec![(
|
||||||
unchecked.valid_vote.kind,
|
unchecked.valid_vote.kind,
|
||||||
@@ -278,7 +278,7 @@ fn disputes_are_recovered_at_startup() {
|
|||||||
unchecked.invalid_vote.validator_index,
|
unchecked.invalid_vote.validator_index,
|
||||||
unchecked.invalid_vote.signature
|
unchecked.invalid_vote.signature
|
||||||
)],
|
)],
|
||||||
}))
|
})])
|
||||||
.expect("Receiver should stay alive.");
|
.expect("Receiver should stay alive.");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -473,7 +473,7 @@ async fn send_network_dispute_request(
|
|||||||
) -> oneshot::Receiver<sc_network::config::OutgoingResponse> {
|
) -> oneshot::Receiver<sc_network::config::OutgoingResponse> {
|
||||||
let (pending_response, rx_response) = oneshot::channel();
|
let (pending_response, rx_response) = oneshot::channel();
|
||||||
let req = sc_network::config::IncomingRequest {
|
let req = sc_network::config::IncomingRequest {
|
||||||
peer,
|
peer,
|
||||||
payload: message.encode(),
|
payload: message.encode(),
|
||||||
pending_response,
|
pending_response,
|
||||||
};
|
};
|
||||||
@@ -492,7 +492,7 @@ async fn nested_network_dispute_request<'a, F, O>(
|
|||||||
import_result: ImportStatementsResult,
|
import_result: ImportStatementsResult,
|
||||||
need_session_info: bool,
|
need_session_info: bool,
|
||||||
inner: F,
|
inner: F,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
F: FnOnce(
|
F: FnOnce(
|
||||||
&'a mut TestSubsystemContextHandle<DisputeDistributionMessage>,
|
&'a mut TestSubsystemContextHandle<DisputeDistributionMessage>,
|
||||||
@@ -539,7 +539,7 @@ async fn nested_network_dispute_request<'a, F, O>(
|
|||||||
pending_confirmation
|
pending_confirmation
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Do the inner thing:
|
// Do the inner thing:
|
||||||
inner(handle, req_tx, message).await;
|
inner(handle, req_tx, message).await;
|
||||||
|
|
||||||
@@ -558,7 +558,7 @@ async fn nested_network_dispute_request<'a, F, O>(
|
|||||||
match import_result {
|
match import_result {
|
||||||
ImportStatementsResult::ValidImport => {
|
ImportStatementsResult::ValidImport => {
|
||||||
let result = result.unwrap();
|
let result = result.unwrap();
|
||||||
let decoded =
|
let decoded =
|
||||||
<DisputeResponse as Decode>::decode(&mut result.as_slice()).unwrap();
|
<DisputeResponse as Decode>::decode(&mut result.as_slice()).unwrap();
|
||||||
|
|
||||||
assert!(decoded == DisputeResponse::Confirmed);
|
assert!(decoded == DisputeResponse::Confirmed);
|
||||||
|
|||||||
@@ -232,7 +232,10 @@ pub enum DisputeCoordinatorMessage {
|
|||||||
/// These disputes are either unconcluded or recently concluded.
|
/// These disputes are either unconcluded or recently concluded.
|
||||||
ActiveDisputes(oneshot::Sender<Vec<(SessionIndex, CandidateHash)>>),
|
ActiveDisputes(oneshot::Sender<Vec<(SessionIndex, CandidateHash)>>),
|
||||||
/// Get candidate votes for a candidate.
|
/// Get candidate votes for a candidate.
|
||||||
QueryCandidateVotes(SessionIndex, CandidateHash, oneshot::Sender<Option<CandidateVotes>>),
|
QueryCandidateVotes(
|
||||||
|
Vec<(SessionIndex, CandidateHash)>,
|
||||||
|
oneshot::Sender<Vec<(SessionIndex, CandidateHash, CandidateVotes)>>,
|
||||||
|
),
|
||||||
/// Sign and issue local dispute votes. A value of `true` indicates validity, and `false` invalidity.
|
/// Sign and issue local dispute votes. A value of `true` indicates validity, and `false` invalidity.
|
||||||
IssueLocalStatement(SessionIndex, CandidateHash, CandidateReceipt, bool),
|
IssueLocalStatement(SessionIndex, CandidateHash, CandidateReceipt, bool),
|
||||||
/// Determine the highest undisputed block within the given chain, based on where candidates
|
/// Determine the highest undisputed block within the given chain, based on where candidates
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ Do nothing.
|
|||||||
according to the `SessionInfo` of the dispute candidate's session, the
|
according to the `SessionInfo` of the dispute candidate's session, the
|
||||||
`DisputeStatus` should be set to `ConcludedPositive(now)` unless it was
|
`DisputeStatus` should be set to `ConcludedPositive(now)` unless it was
|
||||||
already `ConcludedNegative`.
|
already `ConcludedNegative`.
|
||||||
13. If the dispute now has supermajority votes in the "invalid" direction,
|
13. If the dispute now has supermajority votes in the "invalid" direction,
|
||||||
the `DisputeStatus` should be set to `ConcludedNegative(now)`. If it
|
the `DisputeStatus` should be set to `ConcludedNegative(now)`. If it
|
||||||
was `ConcludedPositive` before, the timestamp `now` should be copied
|
was `ConcludedPositive` before, the timestamp `now` should be copied
|
||||||
from the previous status. It will be pruned after some time and all chains
|
from the previous status. It will be pruned after some time and all chains
|
||||||
@@ -143,7 +143,8 @@ Do nothing.
|
|||||||
|
|
||||||
### On `DisputeCoordinatorMessage::QueryCandidateVotes`
|
### On `DisputeCoordinatorMessage::QueryCandidateVotes`
|
||||||
|
|
||||||
* Load `"candidate-votes"` and return the data within or `None` if missing.
|
* Load `"candidate-votes"` for every `(SessionIndex, CandidateHash)` in the query and return data within each `CandidateVote`.
|
||||||
|
If a particular `candidate-vote` is missing, that particular request is ommitted from the response.
|
||||||
|
|
||||||
### On `DisputeCoordinatorMessage::IssueLocalStatement`
|
### On `DisputeCoordinatorMessage::IssueLocalStatement`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user