mirror of
https://github.com/0glabs/0g-storage-node.git
synced 2024-11-10 10:05:17 +00:00
Supports to sync partial chunks (#4)
* refactor p2p signed message * add new pubsub messages in network layer to find chunks * handle find chunks pubsub message in router * Supports to sync partial chunks * add admin rpc to sync chunks * limit number of chunks to sync at a time * refactor code to sync file and chunks * add more switches to trigger file sync * fix ut failure * refactor code
This commit is contained in:
parent
971d344acd
commit
9b4b0436c3
@ -1,6 +1,6 @@
|
|||||||
use network::{
|
use network::{
|
||||||
libp2p::identity,
|
libp2p::identity,
|
||||||
types::{AnnounceFile, SignedAnnounceFile},
|
types::{AnnounceFile, SignedAnnounceFile, SignedMessage},
|
||||||
Multiaddr, PeerId,
|
Multiaddr, PeerId,
|
||||||
};
|
};
|
||||||
use shared_types::{timestamp_now, TxID};
|
use shared_types::{timestamp_now, TxID};
|
||||||
@ -42,6 +42,6 @@ impl AnnounceFileBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let keypair = identity::Keypair::generate_secp256k1();
|
let keypair = identity::Keypair::generate_secp256k1();
|
||||||
msg.into_signed(&keypair).unwrap()
|
SignedMessage::sign_message(msg, &keypair).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,12 @@ pub struct GossipCache {
|
|||||||
example: Option<Duration>,
|
example: Option<Duration>,
|
||||||
/// Timeout for FindFile messages.
|
/// Timeout for FindFile messages.
|
||||||
find_file: Option<Duration>,
|
find_file: Option<Duration>,
|
||||||
|
/// Timeout for FindChunks messages.
|
||||||
|
find_chunks: Option<Duration>,
|
||||||
/// Timeout for AnnounceFile.
|
/// Timeout for AnnounceFile.
|
||||||
announce_file: Option<Duration>,
|
announce_file: Option<Duration>,
|
||||||
|
/// Timeout for AnnounceChunks.
|
||||||
|
announce_chunks: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -33,8 +37,12 @@ pub struct GossipCacheBuilder {
|
|||||||
example: Option<Duration>,
|
example: Option<Duration>,
|
||||||
/// Timeout for blocks FindFile messages.
|
/// Timeout for blocks FindFile messages.
|
||||||
find_file: Option<Duration>,
|
find_file: Option<Duration>,
|
||||||
|
/// Timeout for blocks FindChunks messages.
|
||||||
|
find_chunks: Option<Duration>,
|
||||||
/// Timeout for AnnounceFile messages.
|
/// Timeout for AnnounceFile messages.
|
||||||
announce_file: Option<Duration>,
|
announce_file: Option<Duration>,
|
||||||
|
/// Timeout for AnnounceChunks messages.
|
||||||
|
announce_chunks: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -58,18 +66,32 @@ impl GossipCacheBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Timeout for FindChunks messages.
|
||||||
|
pub fn find_chunks_timeout(mut self, timeout: Duration) -> Self {
|
||||||
|
self.find_chunks = Some(timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Timeout for AnnounceFile messages.
|
/// Timeout for AnnounceFile messages.
|
||||||
pub fn announce_file_timeout(mut self, timeout: Duration) -> Self {
|
pub fn announce_file_timeout(mut self, timeout: Duration) -> Self {
|
||||||
self.announce_file = Some(timeout);
|
self.announce_file = Some(timeout);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Timeout for AnnounceChunks messages.
|
||||||
|
pub fn announce_chunks_timeout(mut self, timeout: Duration) -> Self {
|
||||||
|
self.announce_chunks = Some(timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self) -> GossipCache {
|
pub fn build(self) -> GossipCache {
|
||||||
let GossipCacheBuilder {
|
let GossipCacheBuilder {
|
||||||
default_timeout,
|
default_timeout,
|
||||||
example,
|
example,
|
||||||
find_file,
|
find_file,
|
||||||
|
find_chunks,
|
||||||
announce_file,
|
announce_file,
|
||||||
|
announce_chunks,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
GossipCache {
|
GossipCache {
|
||||||
@ -77,7 +99,9 @@ impl GossipCacheBuilder {
|
|||||||
topic_msgs: HashMap::default(),
|
topic_msgs: HashMap::default(),
|
||||||
example: example.or(default_timeout),
|
example: example.or(default_timeout),
|
||||||
find_file: find_file.or(default_timeout),
|
find_file: find_file.or(default_timeout),
|
||||||
|
find_chunks: find_chunks.or(default_timeout),
|
||||||
announce_file: announce_file.or(default_timeout),
|
announce_file: announce_file.or(default_timeout),
|
||||||
|
announce_chunks: announce_chunks.or(default_timeout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +118,9 @@ impl GossipCache {
|
|||||||
let expire_timeout = match topic.kind() {
|
let expire_timeout = match topic.kind() {
|
||||||
GossipKind::Example => self.example,
|
GossipKind::Example => self.example,
|
||||||
GossipKind::FindFile => self.find_file,
|
GossipKind::FindFile => self.find_file,
|
||||||
|
GossipKind::FindChunks => self.find_chunks,
|
||||||
GossipKind::AnnounceFile => self.announce_file,
|
GossipKind::AnnounceFile => self.announce_file,
|
||||||
|
GossipKind::AnnounceChunks => self.announce_chunks,
|
||||||
};
|
};
|
||||||
|
|
||||||
let expire_timeout = match expire_timeout {
|
let expire_timeout = match expire_timeout {
|
||||||
|
@ -6,5 +6,8 @@ mod topics;
|
|||||||
pub type Enr = discv5::enr::Enr<discv5::enr::CombinedKey>;
|
pub type Enr = discv5::enr::Enr<discv5::enr::CombinedKey>;
|
||||||
|
|
||||||
pub use globals::NetworkGlobals;
|
pub use globals::NetworkGlobals;
|
||||||
pub use pubsub::{AnnounceFile, FindFile, PubsubMessage, SignedAnnounceFile, SnappyTransform};
|
pub use pubsub::{
|
||||||
|
AnnounceChunks, AnnounceFile, FindChunks, FindFile, HasSignature, PubsubMessage,
|
||||||
|
SignedAnnounceChunks, SignedAnnounceFile, SignedMessage, SnappyTransform,
|
||||||
|
};
|
||||||
pub use topics::{GossipEncoding, GossipKind, GossipTopic, CORE_TOPICS};
|
pub use topics::{GossipEncoding, GossipKind, GossipTopic, CORE_TOPICS};
|
||||||
|
@ -120,6 +120,14 @@ pub struct FindFile {
|
|||||||
pub timestamp: u32,
|
pub timestamp: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
|
||||||
|
pub struct FindChunks {
|
||||||
|
pub tx_id: TxID,
|
||||||
|
pub index_start: u64, // inclusive
|
||||||
|
pub index_end: u64, // exclusive
|
||||||
|
pub timestamp: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Encode, Decode)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Encode, Decode)]
|
||||||
pub struct AnnounceFile {
|
pub struct AnnounceFile {
|
||||||
pub tx_id: TxID,
|
pub tx_id: TxID,
|
||||||
@ -128,46 +136,65 @@ pub struct AnnounceFile {
|
|||||||
pub timestamp: u32,
|
pub timestamp: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnnounceFile {
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Encode, Decode)]
|
||||||
pub fn into_signed(self, keypair: &Keypair) -> Result<SignedAnnounceFile, SigningError> {
|
pub struct AnnounceChunks {
|
||||||
let raw = self.as_ssz_bytes();
|
pub tx_id: TxID,
|
||||||
|
pub index_start: u64, // inclusive
|
||||||
|
pub index_end: u64, // exclusive
|
||||||
|
pub peer_id: WrappedPeerId,
|
||||||
|
pub at: WrappedMultiaddr,
|
||||||
|
pub timestamp: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Encode, Decode)]
|
||||||
|
pub struct SignedMessage<T: Encode + Decode> {
|
||||||
|
pub inner: T,
|
||||||
|
pub signature: Vec<u8>,
|
||||||
|
pub resend_timestamp: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Encode + Decode> SignedMessage<T> {
|
||||||
|
pub fn sign_message(msg: T, keypair: &Keypair) -> Result<SignedMessage<T>, SigningError> {
|
||||||
|
let raw = msg.as_ssz_bytes();
|
||||||
let signature = keypair.sign(&raw)?;
|
let signature = keypair.sign(&raw)?;
|
||||||
|
|
||||||
Ok(SignedAnnounceFile {
|
Ok(SignedMessage {
|
||||||
inner: self,
|
inner: msg,
|
||||||
signature,
|
signature,
|
||||||
resend_timestamp: 0,
|
resend_timestamp: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Encode, Decode)]
|
impl<T: Encode + Decode> Deref for SignedMessage<T> {
|
||||||
pub struct SignedAnnounceFile {
|
type Target = T;
|
||||||
pub inner: AnnounceFile,
|
|
||||||
pub signature: Vec<u8>,
|
|
||||||
pub resend_timestamp: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SignedAnnounceFile {
|
|
||||||
pub fn verify_signature(&self, public_key: &PublicKey) -> bool {
|
|
||||||
let raw = self.inner.as_ssz_bytes();
|
|
||||||
public_key.verify(&raw, &self.signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for SignedAnnounceFile {
|
|
||||||
type Target = AnnounceFile;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait HasSignature {
|
||||||
|
fn verify_signature(&self, public_key: &PublicKey) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Encode + Decode> HasSignature for SignedMessage<T> {
|
||||||
|
fn verify_signature(&self, public_key: &PublicKey) -> bool {
|
||||||
|
let raw = self.inner.as_ssz_bytes();
|
||||||
|
public_key.verify(&raw, &self.signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SignedAnnounceFile = SignedMessage<AnnounceFile>;
|
||||||
|
pub type SignedAnnounceChunks = SignedMessage<AnnounceChunks>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum PubsubMessage {
|
pub enum PubsubMessage {
|
||||||
ExampleMessage(u64),
|
ExampleMessage(u64),
|
||||||
FindFile(FindFile),
|
FindFile(FindFile),
|
||||||
|
FindChunks(FindChunks),
|
||||||
AnnounceFile(SignedAnnounceFile),
|
AnnounceFile(SignedAnnounceFile),
|
||||||
|
AnnounceChunks(SignedAnnounceChunks),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the `DataTransform` trait of gossipsub to employ snappy compression
|
// Implements the `DataTransform` trait of gossipsub to employ snappy compression
|
||||||
@ -242,7 +269,9 @@ impl PubsubMessage {
|
|||||||
match self {
|
match self {
|
||||||
PubsubMessage::ExampleMessage(_) => GossipKind::Example,
|
PubsubMessage::ExampleMessage(_) => GossipKind::Example,
|
||||||
PubsubMessage::FindFile(_) => GossipKind::FindFile,
|
PubsubMessage::FindFile(_) => GossipKind::FindFile,
|
||||||
|
PubsubMessage::FindChunks(_) => GossipKind::FindChunks,
|
||||||
PubsubMessage::AnnounceFile(_) => GossipKind::AnnounceFile,
|
PubsubMessage::AnnounceFile(_) => GossipKind::AnnounceFile,
|
||||||
|
PubsubMessage::AnnounceChunks(_) => GossipKind::AnnounceChunks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,9 +296,16 @@ impl PubsubMessage {
|
|||||||
GossipKind::FindFile => Ok(PubsubMessage::FindFile(
|
GossipKind::FindFile => Ok(PubsubMessage::FindFile(
|
||||||
FindFile::from_ssz_bytes(data).map_err(|e| format!("{:?}", e))?,
|
FindFile::from_ssz_bytes(data).map_err(|e| format!("{:?}", e))?,
|
||||||
)),
|
)),
|
||||||
|
GossipKind::FindChunks => Ok(PubsubMessage::FindChunks(
|
||||||
|
FindChunks::from_ssz_bytes(data).map_err(|e| format!("{:?}", e))?,
|
||||||
|
)),
|
||||||
GossipKind::AnnounceFile => Ok(PubsubMessage::AnnounceFile(
|
GossipKind::AnnounceFile => Ok(PubsubMessage::AnnounceFile(
|
||||||
SignedAnnounceFile::from_ssz_bytes(data).map_err(|e| format!("{:?}", e))?,
|
SignedAnnounceFile::from_ssz_bytes(data).map_err(|e| format!("{:?}", e))?,
|
||||||
)),
|
)),
|
||||||
|
GossipKind::AnnounceChunks => Ok(PubsubMessage::AnnounceChunks(
|
||||||
|
SignedAnnounceChunks::from_ssz_bytes(data)
|
||||||
|
.map_err(|e| format!("{:?}", e))?,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,7 +321,9 @@ impl PubsubMessage {
|
|||||||
match &self {
|
match &self {
|
||||||
PubsubMessage::ExampleMessage(data) => data.as_ssz_bytes(),
|
PubsubMessage::ExampleMessage(data) => data.as_ssz_bytes(),
|
||||||
PubsubMessage::FindFile(data) => data.as_ssz_bytes(),
|
PubsubMessage::FindFile(data) => data.as_ssz_bytes(),
|
||||||
|
PubsubMessage::FindChunks(data) => data.as_ssz_bytes(),
|
||||||
PubsubMessage::AnnounceFile(data) => data.as_ssz_bytes(),
|
PubsubMessage::AnnounceFile(data) => data.as_ssz_bytes(),
|
||||||
|
PubsubMessage::AnnounceChunks(data) => data.as_ssz_bytes(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,9 +337,15 @@ impl std::fmt::Display for PubsubMessage {
|
|||||||
PubsubMessage::FindFile(msg) => {
|
PubsubMessage::FindFile(msg) => {
|
||||||
write!(f, "FindFile message: {:?}", msg)
|
write!(f, "FindFile message: {:?}", msg)
|
||||||
}
|
}
|
||||||
|
PubsubMessage::FindChunks(msg) => {
|
||||||
|
write!(f, "FindChunks message: {:?}", msg)
|
||||||
|
}
|
||||||
PubsubMessage::AnnounceFile(msg) => {
|
PubsubMessage::AnnounceFile(msg) => {
|
||||||
write!(f, "AnnounceFile message: {:?}", msg)
|
write!(f, "AnnounceFile message: {:?}", msg)
|
||||||
}
|
}
|
||||||
|
PubsubMessage::AnnounceChunks(msg) => {
|
||||||
|
write!(f, "AnnounceChunks message: {:?}", msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,16 @@ pub const TOPIC_PREFIX: &str = "eth2";
|
|||||||
pub const SSZ_SNAPPY_ENCODING_POSTFIX: &str = "ssz_snappy";
|
pub const SSZ_SNAPPY_ENCODING_POSTFIX: &str = "ssz_snappy";
|
||||||
pub const EXAMPLE_TOPIC: &str = "example";
|
pub const EXAMPLE_TOPIC: &str = "example";
|
||||||
pub const FIND_FILE_TOPIC: &str = "find_file";
|
pub const FIND_FILE_TOPIC: &str = "find_file";
|
||||||
|
pub const FIND_CHUNKS_TOPIC: &str = "find_chunks";
|
||||||
pub const ANNOUNCE_FILE_TOPIC: &str = "announce_file";
|
pub const ANNOUNCE_FILE_TOPIC: &str = "announce_file";
|
||||||
|
pub const ANNOUNCE_CHUNKS_TOPIC: &str = "announce_chunks";
|
||||||
|
|
||||||
pub const CORE_TOPICS: [GossipKind; 2] = [GossipKind::FindFile, GossipKind::AnnounceFile];
|
pub const CORE_TOPICS: [GossipKind; 4] = [
|
||||||
|
GossipKind::FindFile,
|
||||||
|
GossipKind::FindChunks,
|
||||||
|
GossipKind::AnnounceFile,
|
||||||
|
GossipKind::AnnounceChunks,
|
||||||
|
];
|
||||||
|
|
||||||
/// A gossipsub topic which encapsulates the type of messages that should be sent and received over
|
/// A gossipsub topic which encapsulates the type of messages that should be sent and received over
|
||||||
/// the pubsub protocol and the way the messages should be encoded.
|
/// the pubsub protocol and the way the messages should be encoded.
|
||||||
@ -30,7 +37,9 @@ pub struct GossipTopic {
|
|||||||
pub enum GossipKind {
|
pub enum GossipKind {
|
||||||
Example,
|
Example,
|
||||||
FindFile,
|
FindFile,
|
||||||
|
FindChunks,
|
||||||
AnnounceFile,
|
AnnounceFile,
|
||||||
|
AnnounceChunks,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The known encoding types for gossipsub messages.
|
/// The known encoding types for gossipsub messages.
|
||||||
@ -67,7 +76,9 @@ impl GossipTopic {
|
|||||||
let kind = match topic_parts[2] {
|
let kind = match topic_parts[2] {
|
||||||
EXAMPLE_TOPIC => GossipKind::Example,
|
EXAMPLE_TOPIC => GossipKind::Example,
|
||||||
FIND_FILE_TOPIC => GossipKind::FindFile,
|
FIND_FILE_TOPIC => GossipKind::FindFile,
|
||||||
|
FIND_CHUNKS_TOPIC => GossipKind::FindChunks,
|
||||||
ANNOUNCE_FILE_TOPIC => GossipKind::AnnounceFile,
|
ANNOUNCE_FILE_TOPIC => GossipKind::AnnounceFile,
|
||||||
|
ANNOUNCE_CHUNKS_TOPIC => GossipKind::AnnounceChunks,
|
||||||
_ => return Err(format!("Unknown topic: {}", topic)),
|
_ => return Err(format!("Unknown topic: {}", topic)),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -93,7 +104,9 @@ impl From<GossipTopic> for String {
|
|||||||
let kind = match topic.kind {
|
let kind = match topic.kind {
|
||||||
GossipKind::Example => EXAMPLE_TOPIC,
|
GossipKind::Example => EXAMPLE_TOPIC,
|
||||||
GossipKind::FindFile => FIND_FILE_TOPIC,
|
GossipKind::FindFile => FIND_FILE_TOPIC,
|
||||||
|
GossipKind::FindChunks => FIND_CHUNKS_TOPIC,
|
||||||
GossipKind::AnnounceFile => ANNOUNCE_FILE_TOPIC,
|
GossipKind::AnnounceFile => ANNOUNCE_FILE_TOPIC,
|
||||||
|
GossipKind::AnnounceChunks => ANNOUNCE_CHUNKS_TOPIC,
|
||||||
};
|
};
|
||||||
|
|
||||||
format!("/{}/{}/{}", TOPIC_PREFIX, kind, encoding)
|
format!("/{}/{}/{}", TOPIC_PREFIX, kind, encoding)
|
||||||
@ -109,7 +122,9 @@ impl std::fmt::Display for GossipTopic {
|
|||||||
let kind = match self.kind {
|
let kind = match self.kind {
|
||||||
GossipKind::Example => EXAMPLE_TOPIC,
|
GossipKind::Example => EXAMPLE_TOPIC,
|
||||||
GossipKind::FindFile => FIND_FILE_TOPIC,
|
GossipKind::FindFile => FIND_FILE_TOPIC,
|
||||||
|
GossipKind::FindChunks => FIND_CHUNKS_TOPIC,
|
||||||
GossipKind::AnnounceFile => ANNOUNCE_FILE_TOPIC,
|
GossipKind::AnnounceFile => ANNOUNCE_FILE_TOPIC,
|
||||||
|
GossipKind::AnnounceChunks => ANNOUNCE_CHUNKS_TOPIC,
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(f, "/{}/{}/{}", TOPIC_PREFIX, kind, encoding)
|
write!(f, "/{}/{}/{}", TOPIC_PREFIX, kind, encoding)
|
||||||
|
@ -3,11 +3,14 @@ use std::{ops::Neg, sync::Arc};
|
|||||||
use file_location_cache::FileLocationCache;
|
use file_location_cache::FileLocationCache;
|
||||||
use network::{
|
use network::{
|
||||||
rpc::StatusMessage,
|
rpc::StatusMessage,
|
||||||
types::{AnnounceFile, FindFile, SignedAnnounceFile},
|
types::{
|
||||||
|
AnnounceChunks, AnnounceFile, FindChunks, FindFile, HasSignature, SignedAnnounceChunks,
|
||||||
|
SignedAnnounceFile, SignedMessage,
|
||||||
|
},
|
||||||
Keypair, MessageAcceptance, MessageId, NetworkGlobals, NetworkMessage, PeerId, PeerRequestId,
|
Keypair, MessageAcceptance, MessageId, NetworkGlobals, NetworkMessage, PeerId, PeerRequestId,
|
||||||
PublicKey, PubsubMessage, Request, RequestId, Response,
|
PublicKey, PubsubMessage, Request, RequestId, Response,
|
||||||
};
|
};
|
||||||
use shared_types::{timestamp_now, TxID};
|
use shared_types::{bytes_to_chunks, timestamp_now, TxID};
|
||||||
use storage_async::Store;
|
use storage_async::Store;
|
||||||
use sync::{SyncMessage, SyncSender};
|
use sync::{SyncMessage, SyncSender};
|
||||||
use tokio::sync::{mpsc, RwLock};
|
use tokio::sync::{mpsc, RwLock};
|
||||||
@ -40,6 +43,21 @@ fn peer_id_to_public_key(peer_id: &PeerId) -> Result<PublicKey, String> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_signature(msg: &dyn HasSignature, peer_id: &PeerId, propagation_source: PeerId) -> bool {
|
||||||
|
match peer_id_to_public_key(peer_id) {
|
||||||
|
Ok(pub_key) => msg.verify_signature(&pub_key),
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
?err,
|
||||||
|
?peer_id,
|
||||||
|
?propagation_source,
|
||||||
|
"Failed to verify signature"
|
||||||
|
);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Libp2pEventHandler {
|
pub struct Libp2pEventHandler {
|
||||||
/// A collection of global variables, accessible outside of the network service.
|
/// A collection of global variables, accessible outside of the network service.
|
||||||
network_globals: Arc<NetworkGlobals>,
|
network_globals: Arc<NetworkGlobals>,
|
||||||
@ -213,7 +231,9 @@ impl Libp2pEventHandler {
|
|||||||
match message {
|
match message {
|
||||||
PubsubMessage::ExampleMessage(_) => MessageAcceptance::Ignore,
|
PubsubMessage::ExampleMessage(_) => MessageAcceptance::Ignore,
|
||||||
PubsubMessage::FindFile(msg) => self.on_find_file(msg).await,
|
PubsubMessage::FindFile(msg) => self.on_find_file(msg).await,
|
||||||
|
PubsubMessage::FindChunks(msg) => self.on_find_chunks(msg).await,
|
||||||
PubsubMessage::AnnounceFile(msg) => self.on_announce_file(propagation_source, msg),
|
PubsubMessage::AnnounceFile(msg) => self.on_announce_file(propagation_source, msg),
|
||||||
|
PubsubMessage::AnnounceChunks(msg) => self.on_announce_chunks(propagation_source, msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +257,7 @@ impl Libp2pEventHandler {
|
|||||||
timestamp,
|
timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut signed = match msg.into_signed(&self.local_keypair) {
|
let mut signed = match SignedMessage::sign_message(msg, &self.local_keypair) {
|
||||||
Ok(signed) => signed,
|
Ok(signed) => signed,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(%tx_id.seq, %e, "Failed to sign AnnounceFile message");
|
error!(%tx_id.seq, %e, "Failed to sign AnnounceFile message");
|
||||||
@ -292,28 +312,106 @@ impl Libp2pEventHandler {
|
|||||||
MessageAcceptance::Accept
|
MessageAcceptance::Accept
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn construct_announce_chunks_message(
|
||||||
|
&self,
|
||||||
|
tx_id: TxID,
|
||||||
|
index_start: u64,
|
||||||
|
index_end: u64,
|
||||||
|
) -> Option<PubsubMessage> {
|
||||||
|
let peer_id = *self.network_globals.peer_id.read();
|
||||||
|
let addr = self
|
||||||
|
.network_globals
|
||||||
|
.listen_multiaddrs
|
||||||
|
.read()
|
||||||
|
.first()?
|
||||||
|
.clone();
|
||||||
|
let timestamp = timestamp_now();
|
||||||
|
|
||||||
|
let msg = AnnounceChunks {
|
||||||
|
tx_id,
|
||||||
|
index_start,
|
||||||
|
index_end,
|
||||||
|
peer_id: peer_id.into(),
|
||||||
|
at: addr.into(),
|
||||||
|
timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut signed = match SignedMessage::sign_message(msg, &self.local_keypair) {
|
||||||
|
Ok(signed) => signed,
|
||||||
|
Err(e) => {
|
||||||
|
error!(%tx_id.seq, %e, "Failed to sign AnnounceChunks message");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
signed.resend_timestamp = timestamp;
|
||||||
|
|
||||||
|
Some(PubsubMessage::AnnounceChunks(signed))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_find_chunks(&self, msg: FindChunks) -> MessageAcceptance {
|
||||||
|
// validate message
|
||||||
|
if msg.index_start >= msg.index_end {
|
||||||
|
debug!(?msg, "Invalid chunk index range");
|
||||||
|
return MessageAcceptance::Reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify timestamp
|
||||||
|
let d = duration_since(msg.timestamp);
|
||||||
|
if d < TOLERABLE_DRIFT.neg() || d > *FIND_FILE_TIMEOUT {
|
||||||
|
debug!(%msg.timestamp, "Invalid timestamp, ignoring FindFile message");
|
||||||
|
return MessageAcceptance::Ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we have specified chunks even file not finalized yet
|
||||||
|
// validate end index
|
||||||
|
let tx = match self.store.get_tx_by_seq_number(msg.tx_id.seq).await {
|
||||||
|
Ok(Some(tx)) if tx.id() == msg.tx_id => tx,
|
||||||
|
_ => return MessageAcceptance::Accept,
|
||||||
|
};
|
||||||
|
|
||||||
|
// validate index range
|
||||||
|
if let Ok(size) = usize::try_from(tx.size) {
|
||||||
|
let num_chunks = bytes_to_chunks(size);
|
||||||
|
if msg.index_end > num_chunks as u64 {
|
||||||
|
debug!(?msg, "Invalid chunk end index for FindChunks message");
|
||||||
|
return MessageAcceptance::Reject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(qhz): check if there is better way to check existence of requested chunks.
|
||||||
|
let _ = match self
|
||||||
|
.store
|
||||||
|
.get_chunks_by_tx_and_index_range(
|
||||||
|
msg.tx_id.seq,
|
||||||
|
msg.index_start as usize,
|
||||||
|
msg.index_end as usize,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Some(_)) => (),
|
||||||
|
_ => return MessageAcceptance::Accept,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(?msg, "Found chunks to respond FindChunks message");
|
||||||
|
|
||||||
|
match self.construct_announce_chunks_message(msg.tx_id, msg.index_start, msg.index_end) {
|
||||||
|
Some(msg) => {
|
||||||
|
self.publish(msg);
|
||||||
|
MessageAcceptance::Ignore
|
||||||
|
}
|
||||||
|
// propagate FindFile query to other nodes
|
||||||
|
None => MessageAcceptance::Accept,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_announce_file(
|
fn on_announce_file(
|
||||||
&self,
|
&self,
|
||||||
propagation_source: PeerId,
|
propagation_source: PeerId,
|
||||||
msg: SignedAnnounceFile,
|
msg: SignedAnnounceFile,
|
||||||
) -> MessageAcceptance {
|
) -> MessageAcceptance {
|
||||||
// verify message signature
|
// verify message signature
|
||||||
let pk = match peer_id_to_public_key(&msg.peer_id) {
|
if !verify_signature(&msg, &msg.peer_id, propagation_source) {
|
||||||
Ok(pk) => pk,
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Failed to convert peer id {:?} to public key: {:?}",
|
|
||||||
msg.peer_id, e
|
|
||||||
);
|
|
||||||
return MessageAcceptance::Reject;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !msg.verify_signature(&pk) {
|
|
||||||
warn!(
|
|
||||||
"Received message with invalid signature from peer {:?}",
|
|
||||||
propagation_source
|
|
||||||
);
|
|
||||||
return MessageAcceptance::Reject;
|
return MessageAcceptance::Reject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,6 +434,29 @@ impl Libp2pEventHandler {
|
|||||||
|
|
||||||
MessageAcceptance::Accept
|
MessageAcceptance::Accept
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_announce_chunks(
|
||||||
|
&self,
|
||||||
|
propagation_source: PeerId,
|
||||||
|
msg: SignedAnnounceChunks,
|
||||||
|
) -> MessageAcceptance {
|
||||||
|
// verify message signature
|
||||||
|
if !verify_signature(&msg, &msg.peer_id, propagation_source) {
|
||||||
|
return MessageAcceptance::Reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// propagate gossip to peers
|
||||||
|
let d = duration_since(msg.resend_timestamp);
|
||||||
|
if d < TOLERABLE_DRIFT.neg() || d > *ANNOUNCE_FILE_TIMEOUT {
|
||||||
|
debug!(%msg.resend_timestamp, "Invalid resend timestamp, ignoring AnnounceChunks message");
|
||||||
|
return MessageAcceptance::Ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// notify sync layer
|
||||||
|
self.send_to_sync(SyncMessage::AnnounceChunksGossip { msg: msg.inner });
|
||||||
|
|
||||||
|
MessageAcceptance::Accept
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -12,6 +12,14 @@ pub trait Rpc {
|
|||||||
#[method(name = "startSyncFile")]
|
#[method(name = "startSyncFile")]
|
||||||
async fn start_sync_file(&self, tx_seq: u64) -> RpcResult<()>;
|
async fn start_sync_file(&self, tx_seq: u64) -> RpcResult<()>;
|
||||||
|
|
||||||
|
#[method(name = "startSyncChunks")]
|
||||||
|
async fn start_sync_chunks(
|
||||||
|
&self,
|
||||||
|
tx_seq: u64,
|
||||||
|
start_index: u64,
|
||||||
|
end_index: u64, // exclusive
|
||||||
|
) -> RpcResult<()>;
|
||||||
|
|
||||||
#[method(name = "getSyncStatus")]
|
#[method(name = "getSyncStatus")]
|
||||||
async fn get_sync_status(&self, tx_seq: u64) -> RpcResult<String>;
|
async fn get_sync_status(&self, tx_seq: u64) -> RpcResult<String>;
|
||||||
|
|
||||||
|
@ -47,6 +47,36 @@ impl RpcServer for RpcServerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self), err)]
|
||||||
|
async fn start_sync_chunks(
|
||||||
|
&self,
|
||||||
|
tx_seq: u64,
|
||||||
|
start_index: u64,
|
||||||
|
end_index: u64,
|
||||||
|
) -> RpcResult<()> {
|
||||||
|
info!("admin_startSyncChunks({tx_seq}, {start_index}, {end_index})");
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.ctx
|
||||||
|
.request_sync(SyncRequest::SyncChunks {
|
||||||
|
tx_seq,
|
||||||
|
start_index,
|
||||||
|
end_index,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
SyncResponse::SyncFile { err } => {
|
||||||
|
if err.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(error::internal_error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(error::internal_error("unexpected response type")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self), err)]
|
#[tracing::instrument(skip(self), err)]
|
||||||
async fn get_sync_status(&self, tx_seq: u64) -> RpcResult<String> {
|
async fn get_sync_status(&self, tx_seq: u64) -> RpcResult<String> {
|
||||||
info!("admin_getSyncStatus({tx_seq})");
|
info!("admin_getSyncStatus({tx_seq})");
|
||||||
|
@ -6,7 +6,6 @@ use log_entry_sync::{CacheConfig, ContractAddress, LogSyncConfig};
|
|||||||
use miner::MinerConfig;
|
use miner::MinerConfig;
|
||||||
use network::NetworkConfig;
|
use network::NetworkConfig;
|
||||||
use rpc::RPCConfig;
|
use rpc::RPCConfig;
|
||||||
use std::time::Duration;
|
|
||||||
use storage::StorageConfig;
|
use storage::StorageConfig;
|
||||||
|
|
||||||
impl ZgsConfig {
|
impl ZgsConfig {
|
||||||
@ -148,11 +147,4 @@ impl ZgsConfig {
|
|||||||
router_config.libp2p_nodes = network_config.libp2p_nodes.to_vec();
|
router_config.libp2p_nodes = network_config.libp2p_nodes.to_vec();
|
||||||
Ok(router_config)
|
Ok(router_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync_config(&self) -> sync::Config {
|
|
||||||
let mut config = self.sync.clone();
|
|
||||||
config.find_peer_timeout = Duration::from_secs(self.find_peer_timeout_secs);
|
|
||||||
config.enable_chunk_request = self.enable_chunk_request;
|
|
||||||
config
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -57,10 +57,6 @@ build_config! {
|
|||||||
(mine_contract_address, (String), "".to_string())
|
(mine_contract_address, (String), "".to_string())
|
||||||
(miner_id, (Option<String>), None)
|
(miner_id, (Option<String>), None)
|
||||||
(miner_key, (Option<String>), None)
|
(miner_key, (Option<String>), None)
|
||||||
|
|
||||||
// sync
|
|
||||||
(find_peer_timeout_secs, (u64), 30)
|
|
||||||
(enable_chunk_request, (bool), true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
@ -68,6 +64,7 @@ build_config! {
|
|||||||
pub struct ZgsConfig {
|
pub struct ZgsConfig {
|
||||||
pub raw_conf: RawConfiguration,
|
pub raw_conf: RawConfiguration,
|
||||||
|
|
||||||
|
// sync config, configured by [sync] section by `config` crate.
|
||||||
pub sync: sync::Config,
|
pub sync: sync::Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ async fn start_node(context: RuntimeContext, config: ZgsConfig) -> Result<Client
|
|||||||
let log_sync_config = config.log_sync_config()?;
|
let log_sync_config = config.log_sync_config()?;
|
||||||
let miner_config = config.mine_config()?;
|
let miner_config = config.mine_config()?;
|
||||||
let router_config = config.router_config(&network_config)?;
|
let router_config = config.router_config(&network_config)?;
|
||||||
let sync_config = config.sync_config();
|
|
||||||
|
|
||||||
ClientBuilder::default()
|
ClientBuilder::default()
|
||||||
.with_runtime_context(context)
|
.with_runtime_context(context)
|
||||||
@ -27,7 +26,7 @@ async fn start_node(context: RuntimeContext, config: ZgsConfig) -> Result<Client
|
|||||||
.with_file_location_cache()
|
.with_file_location_cache()
|
||||||
.with_network(&network_config)
|
.with_network(&network_config)
|
||||||
.await?
|
.await?
|
||||||
.with_sync(sync_config)
|
.with_sync(config.sync)
|
||||||
.await?
|
.await?
|
||||||
.with_miner(miner_config)
|
.with_miner(miner_config)
|
||||||
.await?
|
.await?
|
||||||
|
@ -3,14 +3,47 @@ mod serial;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub use serial::{FailureReason, SerialSyncController, SyncState};
|
pub use serial::{FailureReason, SerialSyncController, SyncState, MAX_CHUNKS_TO_REQUEST};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct FileSyncGoal {
|
||||||
|
/// File chunks in total.
|
||||||
|
pub num_chunks: u64,
|
||||||
|
/// Chunk index to sync from (starts from 0, inclusive).
|
||||||
|
pub index_start: u64,
|
||||||
|
/// Chunk index to sync to (exclusive).
|
||||||
|
pub index_end: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSyncGoal {
|
||||||
|
pub fn new(num_chunks: u64, index_start: u64, index_end: u64) -> Self {
|
||||||
|
assert!(
|
||||||
|
index_start < index_end && index_end <= num_chunks,
|
||||||
|
"invalid index_end"
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
num_chunks,
|
||||||
|
index_start,
|
||||||
|
index_end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_file(num_chunks: u64) -> Self {
|
||||||
|
Self::new(num_chunks, 0, num_chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_all_chunks(&self) -> bool {
|
||||||
|
self.index_start == 0 && self.index_end == self.num_chunks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FileSyncInfo {
|
pub struct FileSyncInfo {
|
||||||
pub elapsed_secs: u64,
|
pub elapsed_secs: u64,
|
||||||
pub peers: usize,
|
pub peers: usize,
|
||||||
pub num_chunks: u64,
|
pub goal: FileSyncGoal,
|
||||||
pub next_chunks: u64,
|
pub next_chunks: u64,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use crate::context::SyncNetworkContext;
|
use crate::context::SyncNetworkContext;
|
||||||
use crate::controllers::peers::{PeerState, SyncPeers};
|
use crate::controllers::peers::{PeerState, SyncPeers};
|
||||||
use crate::controllers::FileSyncInfo;
|
use crate::controllers::{FileSyncGoal, FileSyncInfo};
|
||||||
use file_location_cache::FileLocationCache;
|
use file_location_cache::FileLocationCache;
|
||||||
use libp2p::swarm::DialError;
|
use libp2p::swarm::DialError;
|
||||||
|
use network::types::FindChunks;
|
||||||
use network::{
|
use network::{
|
||||||
multiaddr::Protocol, rpc::GetChunksRequest, types::FindFile, Multiaddr, NetworkMessage,
|
multiaddr::Protocol, rpc::GetChunksRequest, types::FindFile, Multiaddr, NetworkMessage,
|
||||||
PeerAction, PeerId, PubsubMessage, SyncId as RequestId,
|
PeerAction, PeerId, PubsubMessage, SyncId as RequestId,
|
||||||
@ -14,7 +15,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use storage_async::Store;
|
use storage_async::Store;
|
||||||
|
|
||||||
const MAX_CHUNKS_TO_REQUEST: u64 = 2 * 1024;
|
pub const MAX_CHUNKS_TO_REQUEST: u64 = 2 * 1024;
|
||||||
const MAX_REQUEST_FAILURES: usize = 100;
|
const MAX_REQUEST_FAILURES: usize = 100;
|
||||||
const PEER_REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
|
const PEER_REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(5);
|
const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
@ -63,8 +64,8 @@ pub struct SerialSyncController {
|
|||||||
|
|
||||||
since: Instant,
|
since: Instant,
|
||||||
|
|
||||||
/// The size of the file to be synced.
|
/// File sync goal.
|
||||||
num_chunks: u64,
|
goal: FileSyncGoal,
|
||||||
|
|
||||||
/// The next chunk id that we need to retrieve.
|
/// The next chunk id that we need to retrieve.
|
||||||
next_chunk: u64,
|
next_chunk: u64,
|
||||||
@ -91,7 +92,7 @@ pub struct SerialSyncController {
|
|||||||
impl SerialSyncController {
|
impl SerialSyncController {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
tx_id: TxID,
|
tx_id: TxID,
|
||||||
num_chunks: u64,
|
goal: FileSyncGoal,
|
||||||
ctx: Arc<SyncNetworkContext>,
|
ctx: Arc<SyncNetworkContext>,
|
||||||
store: Store,
|
store: Store,
|
||||||
file_location_cache: Arc<FileLocationCache>,
|
file_location_cache: Arc<FileLocationCache>,
|
||||||
@ -100,8 +101,8 @@ impl SerialSyncController {
|
|||||||
tx_seq: tx_id.seq,
|
tx_seq: tx_id.seq,
|
||||||
tx_id,
|
tx_id,
|
||||||
since: Instant::now(),
|
since: Instant::now(),
|
||||||
num_chunks,
|
goal,
|
||||||
next_chunk: 0,
|
next_chunk: goal.index_start,
|
||||||
failures: 0,
|
failures: 0,
|
||||||
state: SyncState::Idle,
|
state: SyncState::Idle,
|
||||||
peers: Default::default(),
|
peers: Default::default(),
|
||||||
@ -115,7 +116,7 @@ impl SerialSyncController {
|
|||||||
FileSyncInfo {
|
FileSyncInfo {
|
||||||
elapsed_secs: self.since.elapsed().as_secs(),
|
elapsed_secs: self.since.elapsed().as_secs(),
|
||||||
peers: self.peers.count(&[PeerState::Connected]),
|
peers: self.peers.count(&[PeerState::Connected]),
|
||||||
num_chunks: self.num_chunks,
|
goal: self.goal,
|
||||||
next_chunks: self.next_chunk,
|
next_chunks: self.next_chunk,
|
||||||
state: format!("{:?}", self.state),
|
state: format!("{:?}", self.state),
|
||||||
}
|
}
|
||||||
@ -127,7 +128,7 @@ impl SerialSyncController {
|
|||||||
|
|
||||||
/// Resets the status to re-sync file when failed.
|
/// Resets the status to re-sync file when failed.
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.next_chunk = 0;
|
self.next_chunk = self.goal.index_start;
|
||||||
self.failures = 0;
|
self.failures = 0;
|
||||||
self.state = SyncState::Idle;
|
self.state = SyncState::Idle;
|
||||||
// remove disconnected peers
|
// remove disconnected peers
|
||||||
@ -137,6 +138,23 @@ impl SerialSyncController {
|
|||||||
fn try_find_peers(&mut self) {
|
fn try_find_peers(&mut self) {
|
||||||
info!(%self.tx_seq, "Finding peers");
|
info!(%self.tx_seq, "Finding peers");
|
||||||
|
|
||||||
|
if self.goal.is_all_chunks() {
|
||||||
|
self.publish_find_file();
|
||||||
|
} else {
|
||||||
|
self.publish_find_chunks();
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
let (since, updated) = match self.state {
|
||||||
|
SyncState::FindingPeers { since, .. } => (since, now),
|
||||||
|
_ => (now, now),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.state = SyncState::FindingPeers { since, updated };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn publish_find_file(&mut self) {
|
||||||
// try from cache
|
// try from cache
|
||||||
let mut found_new_peer = false;
|
let mut found_new_peer = false;
|
||||||
|
|
||||||
@ -149,21 +167,23 @@ impl SerialSyncController {
|
|||||||
found_new_peer = self.on_peer_found(peer_id, addr) || found_new_peer;
|
found_new_peer = self.on_peer_found(peer_id, addr) || found_new_peer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found_new_peer {
|
if found_new_peer {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.ctx.publish(PubsubMessage::FindFile(FindFile {
|
self.ctx.publish(PubsubMessage::FindFile(FindFile {
|
||||||
tx_id: self.tx_id,
|
tx_id: self.tx_id,
|
||||||
timestamp: timestamp_now(),
|
timestamp: timestamp_now(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let now = Instant::now();
|
fn publish_find_chunks(&self) {
|
||||||
|
self.ctx.publish(PubsubMessage::FindChunks(FindChunks {
|
||||||
let (since, updated) = match self.state {
|
tx_id: self.tx_id,
|
||||||
SyncState::FindingPeers { since, .. } => (since, now),
|
index_start: self.goal.index_start,
|
||||||
_ => (now, now),
|
index_end: self.goal.index_end,
|
||||||
};
|
timestamp: timestamp_now(),
|
||||||
|
}));
|
||||||
self.state = SyncState::FindingPeers { since, updated };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_connect(&mut self) {
|
fn try_connect(&mut self) {
|
||||||
@ -201,7 +221,7 @@ impl SerialSyncController {
|
|||||||
|
|
||||||
// request next chunk array
|
// request next chunk array
|
||||||
let from_chunk = self.next_chunk;
|
let from_chunk = self.next_chunk;
|
||||||
let to_chunk = std::cmp::min(from_chunk + MAX_CHUNKS_TO_REQUEST, self.num_chunks);
|
let to_chunk = std::cmp::min(from_chunk + MAX_CHUNKS_TO_REQUEST, self.goal.index_end);
|
||||||
|
|
||||||
let request_id = network::RequestId::Sync(RequestId::SerialSync { tx_id: self.tx_id });
|
let request_id = network::RequestId::Sync(RequestId::SerialSync { tx_id: self.tx_id });
|
||||||
|
|
||||||
@ -412,11 +432,17 @@ impl SerialSyncController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepare to download next
|
// prepare to download next
|
||||||
if self.next_chunk < self.num_chunks {
|
if self.next_chunk < self.goal.index_end {
|
||||||
self.state = SyncState::Idle;
|
self.state = SyncState::Idle;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// completed to download chunks
|
||||||
|
if !self.goal.is_all_chunks() {
|
||||||
|
self.state = SyncState::Completed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// finalize tx if all chunks downloaded
|
// finalize tx if all chunks downloaded
|
||||||
match self
|
match self
|
||||||
.store
|
.store
|
||||||
@ -1317,7 +1343,8 @@ mod tests {
|
|||||||
since: Instant::now(),
|
since: Instant::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
controller.num_chunks = 2048;
|
controller.goal.num_chunks = 2048;
|
||||||
|
controller.goal.index_end = 2048;
|
||||||
|
|
||||||
controller.on_response(peer_id, chunks).await;
|
controller.on_response(peer_id, chunks).await;
|
||||||
match controller.get_status() {
|
match controller.get_status() {
|
||||||
@ -1499,7 +1526,7 @@ mod tests {
|
|||||||
|
|
||||||
let controller = SerialSyncController::new(
|
let controller = SerialSyncController::new(
|
||||||
tx_id,
|
tx_id,
|
||||||
num_chunks as u64,
|
FileSyncGoal::new_file(num_chunks as u64),
|
||||||
ctx,
|
ctx,
|
||||||
Store::new(store, task_executor),
|
Store::new(store, task_executor),
|
||||||
file_location_cache,
|
file_location_cache,
|
||||||
|
@ -13,30 +13,25 @@ use serde::Deserialize;
|
|||||||
pub use service::{SyncMessage, SyncReceiver, SyncRequest, SyncResponse, SyncSender, SyncService};
|
pub use service::{SyncMessage, SyncReceiver, SyncRequest, SyncResponse, SyncSender, SyncService};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub auto_sync_disabled: bool,
|
pub auto_sync_enabled: bool,
|
||||||
pub max_sync_files: usize,
|
pub max_sync_files: usize,
|
||||||
#[serde(deserialize_with = "deserialize_duration")]
|
#[serde(deserialize_with = "deserialize_duration")]
|
||||||
pub find_peer_timeout: Duration,
|
pub find_peer_timeout: Duration,
|
||||||
pub enable_chunk_request: bool,
|
pub sync_file_by_rpc_enabled: bool,
|
||||||
|
pub sync_file_on_announcement_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
auto_sync_disabled: false,
|
auto_sync_enabled: false,
|
||||||
max_sync_files: 100,
|
max_sync_files: 100,
|
||||||
find_peer_timeout: Duration::from_secs(30),
|
find_peer_timeout: Duration::from_secs(30),
|
||||||
enable_chunk_request: false,
|
sync_file_by_rpc_enabled: true,
|
||||||
|
sync_file_on_announcement_enabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn disable_auto_sync(mut self) -> Self {
|
|
||||||
self.auto_sync_disabled = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
use crate::auto_sync::AutoSyncManager;
|
use crate::auto_sync::AutoSyncManager;
|
||||||
use crate::context::SyncNetworkContext;
|
use crate::context::SyncNetworkContext;
|
||||||
use crate::controllers::{FailureReason, FileSyncInfo, SerialSyncController, SyncState};
|
use crate::controllers::{
|
||||||
|
FailureReason, FileSyncGoal, FileSyncInfo, SerialSyncController, SyncState,
|
||||||
|
MAX_CHUNKS_TO_REQUEST,
|
||||||
|
};
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use file_location_cache::FileLocationCache;
|
use file_location_cache::FileLocationCache;
|
||||||
use libp2p::swarm::DialError;
|
use libp2p::swarm::DialError;
|
||||||
use log_entry_sync::LogSyncEvent;
|
use log_entry_sync::LogSyncEvent;
|
||||||
|
use network::types::AnnounceChunks;
|
||||||
use network::{
|
use network::{
|
||||||
rpc::GetChunksRequest, rpc::RPCResponseErrorCode, Multiaddr, NetworkMessage, PeerId,
|
rpc::GetChunksRequest, rpc::RPCResponseErrorCode, Multiaddr, NetworkMessage, PeerId,
|
||||||
PeerRequestId, SyncId as RequestId,
|
PeerRequestId, SyncId as RequestId,
|
||||||
@ -56,14 +60,31 @@ pub enum SyncMessage {
|
|||||||
peer_id: PeerId,
|
peer_id: PeerId,
|
||||||
addr: Multiaddr,
|
addr: Multiaddr,
|
||||||
},
|
},
|
||||||
|
AnnounceChunksGossip {
|
||||||
|
msg: AnnounceChunks,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SyncRequest {
|
pub enum SyncRequest {
|
||||||
SyncStatus { tx_seq: u64 },
|
SyncStatus {
|
||||||
SyncFile { tx_seq: u64 },
|
tx_seq: u64,
|
||||||
FileSyncInfo { tx_seq: Option<u64> },
|
},
|
||||||
TerminateFileSync { tx_seq: u64, is_reverted: bool },
|
SyncFile {
|
||||||
|
tx_seq: u64,
|
||||||
|
},
|
||||||
|
SyncChunks {
|
||||||
|
tx_seq: u64,
|
||||||
|
start_index: u64,
|
||||||
|
end_index: u64,
|
||||||
|
},
|
||||||
|
FileSyncInfo {
|
||||||
|
tx_seq: Option<u64>,
|
||||||
|
},
|
||||||
|
TerminateFileSync {
|
||||||
|
tx_seq: u64,
|
||||||
|
is_reverted: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -134,7 +155,7 @@ impl SyncService {
|
|||||||
|
|
||||||
let manager =
|
let manager =
|
||||||
AutoSyncManager::new(store.clone(), sync_send.clone(), config.clone()).await?;
|
AutoSyncManager::new(store.clone(), sync_send.clone(), config.clone()).await?;
|
||||||
if !config.auto_sync_disabled {
|
if config.auto_sync_enabled {
|
||||||
manager.spwn(&executor, event_recv);
|
manager.spwn(&executor, event_recv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,6 +239,8 @@ impl SyncService {
|
|||||||
} => {
|
} => {
|
||||||
self.on_announce_file_gossip(tx_id, peer_id, addr).await;
|
self.on_announce_file_gossip(tx_id, peer_id, addr).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncMessage::AnnounceChunksGossip { msg } => self.on_announce_chunks_gossip(msg).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,24 +260,19 @@ impl SyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SyncRequest::SyncFile { tx_seq } => {
|
SyncRequest::SyncFile { tx_seq } => {
|
||||||
if !self.controllers.contains_key(&tx_seq)
|
let result = self.on_sync_file_request(tx_seq, None).await;
|
||||||
&& self.controllers.len() >= self.config.max_sync_files
|
let _ = sender.send(SyncResponse::SyncFile { err: result });
|
||||||
{
|
|
||||||
let _ = sender.send(SyncResponse::SyncFile {
|
|
||||||
err: format!(
|
|
||||||
"max sync file limitation reached: {:?}",
|
|
||||||
self.config.max_sync_files
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let err = match self.on_start_sync_file(tx_seq, None).await {
|
SyncRequest::SyncChunks {
|
||||||
Ok(()) => "".into(),
|
tx_seq,
|
||||||
Err(err) => err.to_string(),
|
start_index,
|
||||||
};
|
end_index,
|
||||||
|
} => {
|
||||||
let _ = sender.send(SyncResponse::SyncFile { err });
|
let result = self
|
||||||
|
.on_sync_file_request(tx_seq, Some((start_index, end_index)))
|
||||||
|
.await;
|
||||||
|
let _ = sender.send(SyncResponse::SyncFile { err: result });
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncRequest::FileSyncInfo { tx_seq } => {
|
SyncRequest::FileSyncInfo { tx_seq } => {
|
||||||
@ -348,6 +366,12 @@ impl SyncService {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ban peer if requested too many chunks
|
||||||
|
if request.index_end - request.index_start > MAX_CHUNKS_TO_REQUEST {
|
||||||
|
self.ctx.ban_peer(peer_id, "Too many chunks requested");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// ban peer if invalid tx requested
|
// ban peer if invalid tx requested
|
||||||
let tx = match self.store.get_tx_by_seq_number(request.tx_id.seq).await? {
|
let tx = match self.store.get_tx_by_seq_number(request.tx_id.seq).await? {
|
||||||
Some(tx) => tx,
|
Some(tx) => tx,
|
||||||
@ -463,15 +487,37 @@ impl SyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn on_sync_file_request(
|
||||||
|
&mut self,
|
||||||
|
tx_seq: u64,
|
||||||
|
maybe_range: Option<(u64, u64)>,
|
||||||
|
) -> String {
|
||||||
|
if maybe_range.is_none() && !self.config.sync_file_by_rpc_enabled {
|
||||||
|
return "Disabled to sync file".into();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.controllers.contains_key(&tx_seq)
|
||||||
|
&& self.controllers.len() >= self.config.max_sync_files
|
||||||
|
{
|
||||||
|
return format!(
|
||||||
|
"Max sync file limitation reached: {}",
|
||||||
|
self.config.max_sync_files
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.on_start_sync_file(tx_seq, maybe_range, None).await {
|
||||||
|
Ok(()) => "".into(),
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn on_start_sync_file(
|
async fn on_start_sync_file(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx_seq: u64,
|
tx_seq: u64,
|
||||||
|
maybe_range: Option<(u64, u64)>,
|
||||||
maybe_peer: Option<(PeerId, Multiaddr)>,
|
maybe_peer: Option<(PeerId, Multiaddr)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!(%tx_seq, "Start to sync file");
|
info!(%tx_seq, "Start to sync file");
|
||||||
if !self.config.enable_chunk_request {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove failed entry if caused by tx reverted, so as to re-sync
|
// remove failed entry if caused by tx reverted, so as to re-sync
|
||||||
// file with latest tx_id.
|
// file with latest tx_id.
|
||||||
@ -498,7 +544,7 @@ impl SyncService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let num_chunks = match usize::try_from(tx.size) {
|
let num_chunks = match usize::try_from(tx.size) {
|
||||||
Ok(size) => bytes_to_chunks(size),
|
Ok(size) => bytes_to_chunks(size) as u64,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error!(%tx_seq, "Unexpected transaction size: {}", tx.size);
|
error!(%tx_seq, "Unexpected transaction size: {}", tx.size);
|
||||||
bail!("Unexpected transaction size");
|
bail!("Unexpected transaction size");
|
||||||
@ -510,9 +556,18 @@ impl SyncService {
|
|||||||
bail!("File already exists");
|
bail!("File already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (index_start, index_end) = match maybe_range {
|
||||||
|
Some((start, end)) => (start, end),
|
||||||
|
None => (0, num_chunks),
|
||||||
|
};
|
||||||
|
|
||||||
|
if index_start >= index_end || index_end > num_chunks {
|
||||||
|
bail!("invalid chunk range");
|
||||||
|
}
|
||||||
|
|
||||||
entry.insert(SerialSyncController::new(
|
entry.insert(SerialSyncController::new(
|
||||||
tx.id(),
|
tx.id(),
|
||||||
num_chunks as u64,
|
FileSyncGoal::new(num_chunks, index_start, index_end),
|
||||||
self.ctx.clone(),
|
self.ctx.clone(),
|
||||||
self.store.clone(),
|
self.store.clone(),
|
||||||
self.file_location_cache.clone(),
|
self.file_location_cache.clone(),
|
||||||
@ -542,8 +597,16 @@ impl SyncService {
|
|||||||
|
|
||||||
// File already in sync
|
// File already in sync
|
||||||
if let Some(controller) = self.controllers.get_mut(&tx_seq) {
|
if let Some(controller) = self.controllers.get_mut(&tx_seq) {
|
||||||
|
let info = controller.get_sync_info();
|
||||||
|
if info.goal.is_all_chunks() {
|
||||||
controller.on_peer_found(peer_id, addr);
|
controller.on_peer_found(peer_id, addr);
|
||||||
controller.transition();
|
controller.transition();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.config.sync_file_on_announcement_enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,12 +621,30 @@ impl SyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now, always sync files among all nodes
|
// Now, always sync files among all nodes
|
||||||
if let Err(err) = self.on_start_sync_file(tx_seq, Some((peer_id, addr))).await {
|
if let Err(err) = self
|
||||||
|
.on_start_sync_file(tx_seq, None, Some((peer_id, addr)))
|
||||||
|
.await
|
||||||
|
{
|
||||||
// FIXME(zz): This is possible for tx missing. Is it expected?
|
// FIXME(zz): This is possible for tx missing. Is it expected?
|
||||||
error!(%tx_seq, %err, "Failed to sync file");
|
error!(%tx_seq, %err, "Failed to sync file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn on_announce_chunks_gossip(&mut self, msg: AnnounceChunks) {
|
||||||
|
info!(?msg, "Received AnnounceChunks gossip");
|
||||||
|
|
||||||
|
if let Some(controller) = self.controllers.get_mut(&msg.tx_id.seq) {
|
||||||
|
let info = controller.get_sync_info();
|
||||||
|
if !info.goal.is_all_chunks()
|
||||||
|
&& info.goal.index_start == msg.index_start
|
||||||
|
&& info.goal.index_end == msg.index_end
|
||||||
|
{
|
||||||
|
controller.on_peer_found(msg.peer_id.into(), msg.at.into());
|
||||||
|
controller.transition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Terminate file sync of `min_tx_seq`.
|
/// Terminate file sync of `min_tx_seq`.
|
||||||
/// If `is_reverted` is `true` (means confirmed transactions reverted),
|
/// If `is_reverted` is `true` (means confirmed transactions reverted),
|
||||||
/// also terminate `tx_seq` greater than `min_tx_seq`
|
/// also terminate `tx_seq` greater than `min_tx_seq`
|
||||||
@ -679,6 +760,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_sync_service(&self, with_peer_store: bool) -> SyncSender {
|
async fn spawn_sync_service(&self, with_peer_store: bool) -> SyncSender {
|
||||||
|
self.spawn_sync_service_with_config(with_peer_store, Config::default())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn spawn_sync_service_with_config(
|
||||||
|
&self,
|
||||||
|
with_peer_store: bool,
|
||||||
|
config: Config,
|
||||||
|
) -> SyncSender {
|
||||||
let store = if with_peer_store {
|
let store = if with_peer_store {
|
||||||
self.peer_store.clone()
|
self.peer_store.clone()
|
||||||
} else {
|
} else {
|
||||||
@ -686,7 +776,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SyncService::spawn_with_config(
|
SyncService::spawn_with_config(
|
||||||
Config::default().disable_auto_sync(),
|
config,
|
||||||
self.runtime.task_executor.clone(),
|
self.runtime.task_executor.clone(),
|
||||||
self.network_send.clone(),
|
self.network_send.clone(),
|
||||||
store,
|
store,
|
||||||
@ -719,7 +809,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut sync = SyncService {
|
let mut sync = SyncService {
|
||||||
config: Config::default().disable_auto_sync(),
|
config: Config::default(),
|
||||||
msg_recv: sync_recv,
|
msg_recv: sync_recv,
|
||||||
ctx: Arc::new(SyncNetworkContext::new(network_send)),
|
ctx: Arc::new(SyncNetworkContext::new(network_send)),
|
||||||
store,
|
store,
|
||||||
@ -754,7 +844,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut sync = SyncService {
|
let mut sync = SyncService {
|
||||||
config: Config::default().disable_auto_sync(),
|
config: Config::default(),
|
||||||
msg_recv: sync_recv,
|
msg_recv: sync_recv,
|
||||||
ctx: Arc::new(SyncNetworkContext::new(network_send)),
|
ctx: Arc::new(SyncNetworkContext::new(network_send)),
|
||||||
store,
|
store,
|
||||||
@ -1070,7 +1160,7 @@ mod tests {
|
|||||||
let (network_send, mut network_recv) = mpsc::unbounded_channel::<NetworkMessage>();
|
let (network_send, mut network_recv) = mpsc::unbounded_channel::<NetworkMessage>();
|
||||||
let (_event_send, event_recv) = broadcast::channel(16);
|
let (_event_send, event_recv) = broadcast::channel(16);
|
||||||
let sync_send = SyncService::spawn_with_config(
|
let sync_send = SyncService::spawn_with_config(
|
||||||
Config::default().disable_auto_sync(),
|
Config::default(),
|
||||||
runtime.task_executor.clone(),
|
runtime.task_executor.clone(),
|
||||||
network_send,
|
network_send,
|
||||||
store.clone(),
|
store.clone(),
|
||||||
@ -1341,7 +1431,9 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_announce_file() {
|
async fn test_announce_file() {
|
||||||
let mut runtime = TestSyncRuntime::new(vec![1535], 0);
|
let mut runtime = TestSyncRuntime::new(vec![1535], 0);
|
||||||
let sync_send = runtime.spawn_sync_service(false).await;
|
let mut config = Config::default();
|
||||||
|
config.sync_file_on_announcement_enabled = true;
|
||||||
|
let sync_send = runtime.spawn_sync_service_with_config(false, config).await;
|
||||||
|
|
||||||
let tx_seq = 0u64;
|
let tx_seq = 0u64;
|
||||||
let address: Multiaddr = "/ip4/127.0.0.1/tcp/10000".parse().unwrap();
|
let address: Multiaddr = "/ip4/127.0.0.1/tcp/10000".parse().unwrap();
|
||||||
|
@ -27,6 +27,8 @@ log_sync_start_block_number = 134253180
|
|||||||
log_page_size = 999
|
log_page_size = 999
|
||||||
|
|
||||||
# [sync]
|
# [sync]
|
||||||
# auto_sync_disabled = false
|
# auto_sync_enabled = false
|
||||||
# max_sync_files = 8
|
# max_sync_files = 8
|
||||||
# find_peer_timeout = "30s"
|
# find_peer_timeout = "30s"
|
||||||
|
# sync_file_by_rpc_enabled = true
|
||||||
|
# sync_file_on_announcement_enabled = false
|
||||||
|
Loading…
Reference in New Issue
Block a user