mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-04 13:24:39 +00:00
Add flag to disable attestation APIs
This commit is contained in:
@@ -94,6 +94,7 @@ pub struct ChainConfig {
|
|||||||
/// The delay in milliseconds applied by the node between sending each blob or data column batch.
|
/// The delay in milliseconds applied by the node between sending each blob or data column batch.
|
||||||
/// This doesn't apply if the node is the block proposer.
|
/// This doesn't apply if the node is the block proposer.
|
||||||
pub blob_publication_batch_interval: Duration,
|
pub blob_publication_batch_interval: Duration,
|
||||||
|
pub disable_attesting: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChainConfig {
|
impl Default for ChainConfig {
|
||||||
@@ -129,6 +130,7 @@ impl Default for ChainConfig {
|
|||||||
enable_sampling: false,
|
enable_sampling: false,
|
||||||
blob_publication_batches: 4,
|
blob_publication_batches: 4,
|
||||||
blob_publication_batch_interval: Duration::from_millis(300),
|
blob_publication_batch_interval: Duration::from_millis(300),
|
||||||
|
disable_attesting: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1859,6 +1859,13 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||||
reprocess_tx: Option<Sender<ReprocessQueueMessage>>,
|
reprocess_tx: Option<Sender<ReprocessQueueMessage>>,
|
||||||
log: Logger| async move {
|
log: Logger| async move {
|
||||||
|
if chain.config.disable_attesting {
|
||||||
|
return convert_rejection::<Response<String>>(Err(
|
||||||
|
warp_utils::reject::custom_bad_request("Attesting disabled".to_string()),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
let attestations = attestations.into_iter().map(Either::Left).collect();
|
let attestations = attestations.into_iter().map(Either::Left).collect();
|
||||||
let result = crate::publish_attestations::publish_attestations(
|
let result = crate::publish_attestations::publish_attestations(
|
||||||
task_spawner,
|
task_spawner,
|
||||||
@@ -1891,6 +1898,12 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||||
reprocess_tx: Option<Sender<ReprocessQueueMessage>>,
|
reprocess_tx: Option<Sender<ReprocessQueueMessage>>,
|
||||||
log: Logger| async move {
|
log: Logger| async move {
|
||||||
|
if chain.config.disable_attesting {
|
||||||
|
return convert_rejection::<Response<String>>(Err(
|
||||||
|
warp_utils::reject::custom_bad_request("Attesting disabled".to_string()),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
let attestations =
|
let attestations =
|
||||||
match crate::publish_attestations::deserialize_attestation_payload::<T>(
|
match crate::publish_attestations::deserialize_attestation_payload::<T>(
|
||||||
payload, fork_name, &log,
|
payload, fork_name, &log,
|
||||||
@@ -1937,49 +1950,66 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
task_spawner: TaskSpawner<T::EthSpec>,
|
task_spawner: TaskSpawner<T::EthSpec>,
|
||||||
chain: Arc<BeaconChain<T>>,
|
chain: Arc<BeaconChain<T>>,
|
||||||
query: api_types::AttestationPoolQuery| {
|
query: api_types::AttestationPoolQuery| {
|
||||||
task_spawner.blocking_response_task(Priority::P1, move || {
|
async move {
|
||||||
let query_filter = |data: &AttestationData| {
|
if chain.config.disable_attesting {
|
||||||
query.slot.is_none_or(|slot| slot == data.slot)
|
return convert_rejection::<Response<String>>(Err(
|
||||||
&& query
|
warp_utils::reject::custom_bad_request(
|
||||||
.committee_index
|
"Attesting disabled".to_string(),
|
||||||
.is_none_or(|index| index == data.index)
|
),
|
||||||
};
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
task_spawner
|
||||||
|
.blocking_response_task(Priority::P1, move || {
|
||||||
|
let query_filter = |data: &AttestationData| {
|
||||||
|
query.slot.is_none_or(|slot| slot == data.slot)
|
||||||
|
&& query
|
||||||
|
.committee_index
|
||||||
|
.is_none_or(|index| index == data.index)
|
||||||
|
};
|
||||||
|
|
||||||
let mut attestations = chain.op_pool.get_filtered_attestations(query_filter);
|
let mut attestations =
|
||||||
attestations.extend(
|
chain.op_pool.get_filtered_attestations(query_filter);
|
||||||
chain
|
attestations.extend(
|
||||||
.naive_aggregation_pool
|
chain
|
||||||
.read()
|
.naive_aggregation_pool
|
||||||
.iter()
|
.read()
|
||||||
.filter(|&att| query_filter(att.data()))
|
.iter()
|
||||||
.cloned(),
|
.filter(|&att| query_filter(att.data()))
|
||||||
);
|
.cloned(),
|
||||||
// Use the current slot to find the fork version, and convert all messages to the
|
);
|
||||||
// current fork's format. This is to ensure consistent message types matching
|
// Use the current slot to find the fork version, and convert all messages to the
|
||||||
// `Eth-Consensus-Version`.
|
// current fork's format. This is to ensure consistent message types matching
|
||||||
let current_slot =
|
// `Eth-Consensus-Version`.
|
||||||
chain
|
let current_slot = chain.slot_clock.now().ok_or(
|
||||||
.slot_clock
|
warp_utils::reject::custom_server_error(
|
||||||
.now()
|
"unable to read slot clock".to_string(),
|
||||||
.ok_or(warp_utils::reject::custom_server_error(
|
),
|
||||||
"unable to read slot clock".to_string(),
|
)?;
|
||||||
))?;
|
let fork_name =
|
||||||
let fork_name = chain.spec.fork_name_at_slot::<T::EthSpec>(current_slot);
|
chain.spec.fork_name_at_slot::<T::EthSpec>(current_slot);
|
||||||
let attestations = attestations
|
let attestations = attestations
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|att| {
|
.filter(|att| {
|
||||||
(fork_name.electra_enabled() && matches!(att, Attestation::Electra(_)))
|
(fork_name.electra_enabled()
|
||||||
|| (!fork_name.electra_enabled()
|
&& matches!(att, Attestation::Electra(_)))
|
||||||
&& matches!(att, Attestation::Base(_)))
|
|| (!fork_name.electra_enabled()
|
||||||
|
&& matches!(att, Attestation::Base(_)))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let res = fork_versioned_response(
|
||||||
|
endpoint_version,
|
||||||
|
fork_name,
|
||||||
|
&attestations,
|
||||||
|
)?;
|
||||||
|
Ok(add_consensus_version_header(
|
||||||
|
warp::reply::json(&res).into_response(),
|
||||||
|
fork_name,
|
||||||
|
))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.await
|
||||||
|
}
|
||||||
let res = fork_versioned_response(endpoint_version, fork_name, &attestations)?;
|
|
||||||
Ok(add_consensus_version_header(
|
|
||||||
warp::reply::json(&res).into_response(),
|
|
||||||
fork_name,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2200,12 +2230,24 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
signatures: Vec<SyncCommitteeMessage>,
|
signatures: Vec<SyncCommitteeMessage>,
|
||||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||||
log: Logger| {
|
log: Logger| {
|
||||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
async move {
|
||||||
sync_committees::process_sync_committee_signatures(
|
if chain.config.disable_attesting {
|
||||||
signatures, network_tx, &chain, log,
|
return convert_rejection::<Response<String>>(Err(
|
||||||
)?;
|
warp_utils::reject::custom_bad_request(
|
||||||
Ok(api_types::GenericResponse::from(()))
|
"Attesting disabled".to_string(),
|
||||||
})
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
task_spawner
|
||||||
|
.blocking_json_task(Priority::P0, move || {
|
||||||
|
sync_committees::process_sync_committee_signatures(
|
||||||
|
signatures, network_tx, &chain, log,
|
||||||
|
)?;
|
||||||
|
Ok(api_types::GenericResponse::from(()))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3419,10 +3461,22 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
indices: api_types::ValidatorIndexData,
|
indices: api_types::ValidatorIndexData,
|
||||||
task_spawner: TaskSpawner<T::EthSpec>,
|
task_spawner: TaskSpawner<T::EthSpec>,
|
||||||
chain: Arc<BeaconChain<T>>| {
|
chain: Arc<BeaconChain<T>>| {
|
||||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
async move {
|
||||||
not_synced_filter?;
|
if chain.config.disable_attesting {
|
||||||
attester_duties::attester_duties(epoch, &indices.0, &chain)
|
return convert_rejection::<Response<String>>(Err(
|
||||||
})
|
warp_utils::reject::custom_bad_request(
|
||||||
|
"Attesting disabled".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
task_spawner
|
||||||
|
.blocking_json_task(Priority::P0, move || {
|
||||||
|
not_synced_filter?;
|
||||||
|
attester_duties::attester_duties(epoch, &indices.0, &chain)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3447,10 +3501,22 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
indices: api_types::ValidatorIndexData,
|
indices: api_types::ValidatorIndexData,
|
||||||
task_spawner: TaskSpawner<T::EthSpec>,
|
task_spawner: TaskSpawner<T::EthSpec>,
|
||||||
chain: Arc<BeaconChain<T>>| {
|
chain: Arc<BeaconChain<T>>| {
|
||||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
async move {
|
||||||
not_synced_filter?;
|
if chain.config.disable_attesting {
|
||||||
sync_committees::sync_committee_duties(epoch, &indices.0, &chain)
|
return convert_rejection::<Response<String>>(Err(
|
||||||
})
|
warp_utils::reject::custom_bad_request(
|
||||||
|
"Attesting disabled".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
task_spawner
|
||||||
|
.blocking_json_task(Priority::P0, move || {
|
||||||
|
not_synced_filter?;
|
||||||
|
sync_committees::sync_committee_duties(epoch, &indices.0, &chain)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3468,23 +3534,35 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
not_synced_filter: Result<(), Rejection>,
|
not_synced_filter: Result<(), Rejection>,
|
||||||
task_spawner: TaskSpawner<T::EthSpec>,
|
task_spawner: TaskSpawner<T::EthSpec>,
|
||||||
chain: Arc<BeaconChain<T>>| {
|
chain: Arc<BeaconChain<T>>| {
|
||||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
async move {
|
||||||
not_synced_filter?;
|
if chain.config.disable_attesting {
|
||||||
chain
|
return convert_rejection::<Response<String>>(Err(
|
||||||
.get_aggregated_sync_committee_contribution(&sync_committee_data)
|
warp_utils::reject::custom_bad_request(
|
||||||
.map_err(|e| {
|
"Attesting disabled".to_string(),
|
||||||
warp_utils::reject::custom_bad_request(format!(
|
),
|
||||||
"unable to fetch sync contribution: {:?}",
|
))
|
||||||
e
|
.await;
|
||||||
))
|
}
|
||||||
})?
|
task_spawner
|
||||||
.map(api_types::GenericResponse::from)
|
.blocking_json_task(Priority::P0, move || {
|
||||||
.ok_or_else(|| {
|
not_synced_filter?;
|
||||||
warp_utils::reject::custom_not_found(
|
chain
|
||||||
"no matching sync contribution found".to_string(),
|
.get_aggregated_sync_committee_contribution(&sync_committee_data)
|
||||||
)
|
.map_err(|e| {
|
||||||
|
warp_utils::reject::custom_bad_request(format!(
|
||||||
|
"unable to fetch sync contribution: {:?}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.map(api_types::GenericResponse::from)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
warp_utils::reject::custom_not_found(
|
||||||
|
"no matching sync contribution found".to_string(),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
.await
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3509,6 +3587,15 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
chain: Arc<BeaconChain<T>>,
|
chain: Arc<BeaconChain<T>>,
|
||||||
aggregates: Vec<SignedAggregateAndProof<T::EthSpec>>,
|
aggregates: Vec<SignedAggregateAndProof<T::EthSpec>>,
|
||||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>, log: Logger| {
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>, log: Logger| {
|
||||||
|
async move {
|
||||||
|
if chain.config.disable_attesting {
|
||||||
|
return convert_rejection::<Response<String>>(Err(
|
||||||
|
warp_utils::reject::custom_bad_request(
|
||||||
|
"Attesting disabled".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
||||||
not_synced_filter?;
|
not_synced_filter?;
|
||||||
let seen_timestamp = timestamp_now();
|
let seen_timestamp = timestamp_now();
|
||||||
@@ -3604,7 +3691,8 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
})
|
}).await
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3625,36 +3713,58 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
contributions: Vec<SignedContributionAndProof<T::EthSpec>>,
|
contributions: Vec<SignedContributionAndProof<T::EthSpec>>,
|
||||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||||
log: Logger| {
|
log: Logger| {
|
||||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
async move {
|
||||||
not_synced_filter?;
|
if chain.config.disable_attesting {
|
||||||
sync_committees::process_signed_contribution_and_proofs(
|
return convert_rejection::<Response<String>>(Err(
|
||||||
contributions,
|
warp_utils::reject::custom_bad_request(
|
||||||
network_tx,
|
"Attesting disabled".to_string(),
|
||||||
&chain,
|
),
|
||||||
log,
|
))
|
||||||
)?;
|
.await;
|
||||||
Ok(api_types::GenericResponse::from(()))
|
}
|
||||||
})
|
task_spawner
|
||||||
|
.blocking_json_task(Priority::P0, move || {
|
||||||
|
not_synced_filter?;
|
||||||
|
sync_committees::process_signed_contribution_and_proofs(
|
||||||
|
contributions,
|
||||||
|
network_tx,
|
||||||
|
&chain,
|
||||||
|
log,
|
||||||
|
)?;
|
||||||
|
Ok(api_types::GenericResponse::from(()))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// POST validator/beacon_committee_subscriptions
|
// POST validator/beacon_committee_subscriptions
|
||||||
let post_validator_beacon_committee_subscriptions = eth_v1
|
let post_validator_beacon_committee_subscriptions =
|
||||||
.and(warp::path("validator"))
|
eth_v1
|
||||||
.and(warp::path("beacon_committee_subscriptions"))
|
.and(warp::path("validator"))
|
||||||
.and(warp::path::end())
|
.and(warp::path("beacon_committee_subscriptions"))
|
||||||
.and(warp_utils::json::json())
|
.and(warp::path::end())
|
||||||
.and(validator_subscription_tx_filter.clone())
|
.and(warp_utils::json::json())
|
||||||
.and(task_spawner_filter.clone())
|
.and(validator_subscription_tx_filter.clone())
|
||||||
.and(chain_filter.clone())
|
.and(task_spawner_filter.clone())
|
||||||
.and(log_filter.clone())
|
.and(chain_filter.clone())
|
||||||
.then(
|
.and(log_filter.clone())
|
||||||
|subscriptions: Vec<api_types::BeaconCommitteeSubscription>,
|
.then(
|
||||||
validator_subscription_tx: Sender<ValidatorSubscriptionMessage>,
|
|subscriptions: Vec<api_types::BeaconCommitteeSubscription>,
|
||||||
task_spawner: TaskSpawner<T::EthSpec>,
|
validator_subscription_tx: Sender<ValidatorSubscriptionMessage>,
|
||||||
chain: Arc<BeaconChain<T>>,
|
task_spawner: TaskSpawner<T::EthSpec>,
|
||||||
log: Logger| {
|
chain: Arc<BeaconChain<T>>,
|
||||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
log: Logger| {
|
||||||
|
async move {
|
||||||
|
if chain.config.disable_attesting {
|
||||||
|
return convert_rejection::<Response<String>>(Err(
|
||||||
|
warp_utils::reject::custom_bad_request(
|
||||||
|
"Attesting disabled".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
||||||
let subscriptions: std::collections::BTreeSet<_> = subscriptions
|
let subscriptions: std::collections::BTreeSet<_> = subscriptions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|subscription| {
|
.map(|subscription| {
|
||||||
@@ -3686,9 +3796,10 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
}).await
|
||||||
},
|
}
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// POST validator/prepare_beacon_proposer
|
// POST validator/prepare_beacon_proposer
|
||||||
let post_validator_prepare_beacon_proposer = eth_v1
|
let post_validator_prepare_beacon_proposer = eth_v1
|
||||||
@@ -3945,6 +4056,15 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
chain: Arc<BeaconChain<T>>,
|
chain: Arc<BeaconChain<T>>,
|
||||||
log: Logger
|
log: Logger
|
||||||
| {
|
| {
|
||||||
|
async move {
|
||||||
|
if chain.config.disable_attesting {
|
||||||
|
return convert_rejection::<Response<String>>(Err(
|
||||||
|
warp_utils::reject::custom_bad_request(
|
||||||
|
"Attesting disabled".to_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
task_spawner.blocking_json_task(Priority::P0, move || {
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
||||||
for subscription in subscriptions {
|
for subscription in subscriptions {
|
||||||
chain
|
chain
|
||||||
@@ -3969,7 +4089,8 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
}).await
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -553,6 +553,14 @@ pub fn cli_app() -> Command {
|
|||||||
.action(ArgAction::Set)
|
.action(ArgAction::Set)
|
||||||
.display_order(0)
|
.display_order(0)
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("disable-attesting")
|
||||||
|
.long("disable-attesting")
|
||||||
|
.help("Turn off attestation related APIs so that we have some hope of producing \
|
||||||
|
blocks")
|
||||||
|
.action(ArgAction::Set)
|
||||||
|
.display_order(0)
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("http-sse-capacity-multiplier")
|
Arg::new("http-sse-capacity-multiplier")
|
||||||
.long("http-sse-capacity-multiplier")
|
.long("http-sse-capacity-multiplier")
|
||||||
|
|||||||
@@ -191,6 +191,10 @@ pub fn get_config<E: EthSpec>(
|
|||||||
client_config.chain.enable_light_client_server = false;
|
client_config.chain.enable_light_client_server = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cli_args.get_flag("disable-attesting") {
|
||||||
|
client_config.chain.disable_attesting = true;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? {
|
if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? {
|
||||||
client_config.chain.shuffling_cache_size = cache_size;
|
client_config.chain.shuffling_cache_size = cache_size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2579,6 +2579,16 @@ fn light_client_http_server_disabled() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn disable_attesting() {
|
||||||
|
CommandLineTest::new()
|
||||||
|
.flag("disable-attesting", None)
|
||||||
|
.run_with_zero_port()
|
||||||
|
.with_config(|config| {
|
||||||
|
assert!(config.chain.disable_attesting);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gui_flag() {
|
fn gui_flag() {
|
||||||
CommandLineTest::new()
|
CommandLineTest::new()
|
||||||
|
|||||||
Reference in New Issue
Block a user