Persist/load DHT on shutdown/startup (#659)
* Store dht enrs on shutdown * Load enrs on startup and add tests * Remove enr_entries from behavior * Move all dht persisting logic to `NetworkService` * Move `PersistedDht` from eth2-libp2p to network crate * Add test to confirm dht persistence * Add logging * Remove extra call to beacon_chain persist * Expose only mutable `add_enr` method from behaviour * Fix tests * Fix merge errors
This commit is contained in:
parent
89f05e4a4f
commit
23a35c3767
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2665,9 +2665,11 @@ dependencies = [
|
|||||||
"eth2_ssz 0.1.2",
|
"eth2_ssz 0.1.2",
|
||||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"genesis 0.1.0",
|
||||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rlp 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"sloggers 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"sloggers 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -61,11 +61,3 @@ impl<T: BeaconChainTypes> Client<T> {
|
|||||||
self.libp2p_network.as_ref().map(|n| n.local_enr())
|
self.libp2p_network.as_ref().map(|n| n.local_enr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: BeaconChainTypes> Drop for Client<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(beacon_chain) = &self.beacon_chain {
|
|
||||||
let _result = beacon_chain.persist();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -3,6 +3,7 @@ use crate::rpc::{RPCEvent, RPCMessage, RPC};
|
|||||||
use crate::GossipTopic;
|
use crate::GossipTopic;
|
||||||
use crate::{error, NetworkConfig};
|
use crate::{error, NetworkConfig};
|
||||||
use crate::{Topic, TopicHash};
|
use crate::{Topic, TopicHash};
|
||||||
|
use enr::Enr;
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use libp2p::{
|
use libp2p::{
|
||||||
core::identity::Keypair,
|
core::identity::Keypair,
|
||||||
@ -254,6 +255,16 @@ impl<TSubstream: AsyncRead + AsyncWrite> Behaviour<TSubstream> {
|
|||||||
pub fn peer_unbanned(&mut self, peer_id: &PeerId) {
|
pub fn peer_unbanned(&mut self, peer_id: &PeerId) {
|
||||||
self.discovery.peer_unbanned(peer_id);
|
self.discovery.peer_unbanned(peer_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all enr entries in the DHT.
|
||||||
|
pub fn enr_entries(&mut self) -> impl Iterator<Item = &Enr> {
|
||||||
|
self.discovery.enr_entries()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an ENR to the routing table of the discovery mechanism.
|
||||||
|
pub fn add_enr(&mut self, enr: Enr) {
|
||||||
|
self.discovery.add_enr(enr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The types of events than can be obtained from polling the behaviour.
|
/// The types of events than can be obtained from polling the behaviour.
|
||||||
|
@ -148,6 +148,11 @@ impl<TSubstream> Discovery<TSubstream> {
|
|||||||
self.banned_peers.remove(peer_id);
|
self.banned_peers.remove(peer_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all enr entries in the DHT.
|
||||||
|
pub fn enr_entries(&mut self) -> impl Iterator<Item = &Enr> {
|
||||||
|
self.discovery.enr_entries()
|
||||||
|
}
|
||||||
|
|
||||||
/// Search for new peers using the underlying discovery mechanism.
|
/// Search for new peers using the underlying discovery mechanism.
|
||||||
fn find_peers(&mut self) {
|
fn find_peers(&mut self) {
|
||||||
// pick a random NodeId
|
// pick a random NodeId
|
||||||
|
@ -6,6 +6,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sloggers = "0.3.4"
|
sloggers = "0.3.4"
|
||||||
|
genesis = { path = "../genesis" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
beacon_chain = { path = "../beacon_chain" }
|
beacon_chain = { path = "../beacon_chain" }
|
||||||
@ -24,3 +25,4 @@ smallvec = "1.0.0"
|
|||||||
# TODO: Remove rand crate for mainnet
|
# TODO: Remove rand crate for mainnet
|
||||||
rand = "0.7.2"
|
rand = "0.7.2"
|
||||||
fnv = "1.0.6"
|
fnv = "1.0.6"
|
||||||
|
rlp = "0.4.3"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod message_handler;
|
pub mod message_handler;
|
||||||
pub mod message_processor;
|
pub mod message_processor;
|
||||||
|
pub mod persisted_dht;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
|
|
||||||
|
51
beacon_node/network/src/persisted_dht.rs
Normal file
51
beacon_node/network/src/persisted_dht.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use eth2_libp2p::Enr;
|
||||||
|
use rlp;
|
||||||
|
use store::{DBColumn, Error as StoreError, SimpleStoreItem};
|
||||||
|
|
||||||
|
/// 32-byte key for accessing the `DhtEnrs`.
|
||||||
|
pub const DHT_DB_KEY: &str = "PERSISTEDDHTPERSISTEDDHTPERSISTE";
|
||||||
|
|
||||||
|
/// Wrapper around dht for persistence to disk.
|
||||||
|
pub struct PersistedDht {
|
||||||
|
pub enrs: Vec<Enr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleStoreItem for PersistedDht {
|
||||||
|
fn db_column() -> DBColumn {
|
||||||
|
DBColumn::DhtEnrs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_store_bytes(&self) -> Vec<u8> {
|
||||||
|
rlp::encode_list(&self.enrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_store_bytes(bytes: &[u8]) -> Result<Self, StoreError> {
|
||||||
|
let rlp = rlp::Rlp::new(bytes);
|
||||||
|
let enrs: Vec<Enr> = rlp
|
||||||
|
.as_list()
|
||||||
|
.map_err(|e| StoreError::RlpError(format!("{}", e)))?;
|
||||||
|
Ok(PersistedDht { enrs })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use eth2_libp2p::Enr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use store::{MemoryStore, Store};
|
||||||
|
use types::Hash256;
|
||||||
|
use types::MinimalEthSpec;
|
||||||
|
#[test]
|
||||||
|
fn test_persisted_dht() {
|
||||||
|
let store = Arc::new(MemoryStore::<MinimalEthSpec>::open());
|
||||||
|
let enrs = vec![Enr::from_str("enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8").unwrap()];
|
||||||
|
let key = Hash256::from_slice(&DHT_DB_KEY.as_bytes());
|
||||||
|
store
|
||||||
|
.put(&key, &PersistedDht { enrs: enrs.clone() })
|
||||||
|
.unwrap();
|
||||||
|
let dht: PersistedDht = store.get(&key).unwrap().unwrap();
|
||||||
|
assert_eq!(dht.enrs, enrs);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::message_handler::{HandlerMessage, MessageHandler};
|
use crate::message_handler::{HandlerMessage, MessageHandler};
|
||||||
|
use crate::persisted_dht::{PersistedDht, DHT_DB_KEY};
|
||||||
use crate::NetworkConfig;
|
use crate::NetworkConfig;
|
||||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
@ -9,10 +10,12 @@ use eth2_libp2p::{PubsubMessage, RPCEvent};
|
|||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use slog::{debug, info, trace};
|
use slog::{debug, error, info, trace};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use store::Store;
|
||||||
use tokio::runtime::TaskExecutor;
|
use tokio::runtime::TaskExecutor;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
use types::Hash256;
|
||||||
|
|
||||||
/// The time in seconds that a peer will be banned and prevented from reconnecting.
|
/// The time in seconds that a peer will be banned and prevented from reconnecting.
|
||||||
const BAN_PEER_TIMEOUT: u64 = 30;
|
const BAN_PEER_TIMEOUT: u64 = 30;
|
||||||
@ -21,6 +24,8 @@ const BAN_PEER_TIMEOUT: u64 = 30;
|
|||||||
pub struct Service<T: BeaconChainTypes> {
|
pub struct Service<T: BeaconChainTypes> {
|
||||||
libp2p_service: Arc<Mutex<LibP2PService>>,
|
libp2p_service: Arc<Mutex<LibP2PService>>,
|
||||||
libp2p_port: u16,
|
libp2p_port: u16,
|
||||||
|
store: Arc<T::Store>,
|
||||||
|
log: slog::Logger,
|
||||||
_libp2p_exit: oneshot::Sender<()>,
|
_libp2p_exit: oneshot::Sender<()>,
|
||||||
_network_send: mpsc::UnboundedSender<NetworkMessage>,
|
_network_send: mpsc::UnboundedSender<NetworkMessage>,
|
||||||
_phantom: PhantomData<T>,
|
_phantom: PhantomData<T>,
|
||||||
@ -35,6 +40,8 @@ impl<T: BeaconChainTypes> Service<T> {
|
|||||||
) -> error::Result<(Arc<Self>, mpsc::UnboundedSender<NetworkMessage>)> {
|
) -> error::Result<(Arc<Self>, mpsc::UnboundedSender<NetworkMessage>)> {
|
||||||
// build the network channel
|
// build the network channel
|
||||||
let (network_send, network_recv) = mpsc::unbounded_channel::<NetworkMessage>();
|
let (network_send, network_recv) = mpsc::unbounded_channel::<NetworkMessage>();
|
||||||
|
// Get a reference to the beacon chain store
|
||||||
|
let store = beacon_chain.store.clone();
|
||||||
// launch message handler thread
|
// launch message handler thread
|
||||||
let message_handler_send = MessageHandler::spawn(
|
let message_handler_send = MessageHandler::spawn(
|
||||||
beacon_chain,
|
beacon_chain,
|
||||||
@ -49,17 +56,32 @@ impl<T: BeaconChainTypes> Service<T> {
|
|||||||
network_log.clone(),
|
network_log.clone(),
|
||||||
)?));
|
)?));
|
||||||
|
|
||||||
|
// Load DHT from store
|
||||||
|
let key = Hash256::from_slice(&DHT_DB_KEY.as_bytes());
|
||||||
|
let enrs: Vec<Enr> = match store.get(&key) {
|
||||||
|
Ok(Some(p)) => {
|
||||||
|
let p: PersistedDht = p;
|
||||||
|
p.enrs
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
for enr in enrs {
|
||||||
|
libp2p_service.lock().swarm.add_enr(enr);
|
||||||
|
}
|
||||||
|
|
||||||
let libp2p_exit = spawn_service(
|
let libp2p_exit = spawn_service(
|
||||||
libp2p_service.clone(),
|
libp2p_service.clone(),
|
||||||
network_recv,
|
network_recv,
|
||||||
message_handler_send,
|
message_handler_send,
|
||||||
executor,
|
executor,
|
||||||
network_log,
|
network_log.clone(),
|
||||||
config.propagation_percentage,
|
config.propagation_percentage,
|
||||||
)?;
|
)?;
|
||||||
let network_service = Service {
|
let network_service = Service {
|
||||||
libp2p_service,
|
libp2p_service,
|
||||||
libp2p_port: config.libp2p_port,
|
libp2p_port: config.libp2p_port,
|
||||||
|
store,
|
||||||
|
log: network_log,
|
||||||
_libp2p_exit: libp2p_exit,
|
_libp2p_exit: libp2p_exit,
|
||||||
_network_send: network_send.clone(),
|
_network_send: network_send.clone(),
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
@ -117,6 +139,25 @@ impl<T: BeaconChainTypes> Service<T> {
|
|||||||
pub fn libp2p_service(&self) -> Arc<Mutex<LibP2PService>> {
|
pub fn libp2p_service(&self) -> Arc<Mutex<LibP2PService>> {
|
||||||
self.libp2p_service.clone()
|
self.libp2p_service.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to persist the enrs in the DHT to `self.store`.
|
||||||
|
pub fn persist_dht(&self) -> Result<(), store::Error> {
|
||||||
|
let enrs: Vec<Enr> = self
|
||||||
|
.libp2p_service()
|
||||||
|
.lock()
|
||||||
|
.swarm
|
||||||
|
.enr_entries()
|
||||||
|
.map(|x| x.clone())
|
||||||
|
.collect();
|
||||||
|
info!(
|
||||||
|
self.log,
|
||||||
|
"Persisting DHT to store";
|
||||||
|
"Number of peers" => format!("{}", enrs.len()),
|
||||||
|
);
|
||||||
|
let key = Hash256::from_slice(&DHT_DB_KEY.as_bytes());
|
||||||
|
self.store.put(&key, &PersistedDht { enrs })?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_service(
|
fn spawn_service(
|
||||||
@ -307,3 +348,127 @@ pub enum NetworkMessage {
|
|||||||
/// Disconnect and bans a peer id.
|
/// Disconnect and bans a peer id.
|
||||||
Disconnect { peer_id: PeerId },
|
Disconnect { peer_id: PeerId },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: BeaconChainTypes> Drop for Service<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Err(e) = self.persist_dht() {
|
||||||
|
error!(
|
||||||
|
self.log,
|
||||||
|
"Failed to persist DHT on drop";
|
||||||
|
"error" => format!("{:?}", e)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
self.log,
|
||||||
|
"Saved DHT state";
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use beacon_chain::builder::BeaconChainBuilder;
|
||||||
|
use eth2_libp2p::Enr;
|
||||||
|
use genesis::{generate_deterministic_keypairs, interop_genesis_state};
|
||||||
|
use slog::Logger;
|
||||||
|
use sloggers::{null::NullLoggerBuilder, Build};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use store::{migrate::NullMigrator, SimpleDiskStore};
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
use types::{EthSpec, MinimalEthSpec};
|
||||||
|
|
||||||
|
fn get_logger() -> Logger {
|
||||||
|
let builder = NullLoggerBuilder;
|
||||||
|
builder.build().expect("should build logger")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dht_persistence() {
|
||||||
|
// Create new LevelDB store
|
||||||
|
let path = "/tmp";
|
||||||
|
let store = Arc::new(SimpleDiskStore::open(&std::path::PathBuf::from(path)).unwrap());
|
||||||
|
// Create a `BeaconChain` object to pass to `Service`
|
||||||
|
let validator_count = 8;
|
||||||
|
let genesis_time = 13371337;
|
||||||
|
|
||||||
|
let log = get_logger();
|
||||||
|
let spec = MinimalEthSpec::default_spec();
|
||||||
|
|
||||||
|
let genesis_state = interop_genesis_state(
|
||||||
|
&generate_deterministic_keypairs(validator_count),
|
||||||
|
genesis_time,
|
||||||
|
&spec,
|
||||||
|
)
|
||||||
|
.expect("should create interop genesis state");
|
||||||
|
let chain = BeaconChainBuilder::new(MinimalEthSpec)
|
||||||
|
.logger(log.clone())
|
||||||
|
.store(store)
|
||||||
|
.store_migrator(NullMigrator)
|
||||||
|
.genesis_state(genesis_state)
|
||||||
|
.expect("should build state using recent genesis")
|
||||||
|
.dummy_eth1_backend()
|
||||||
|
.expect("should build the dummy eth1 backend")
|
||||||
|
.null_event_handler()
|
||||||
|
.testing_slot_clock(std::time::Duration::from_secs(1))
|
||||||
|
.expect("should configure testing slot clock")
|
||||||
|
.reduced_tree_fork_choice()
|
||||||
|
.expect("should add fork choice to builder")
|
||||||
|
.build()
|
||||||
|
.expect("should build");
|
||||||
|
let beacon_chain = Arc::new(chain);
|
||||||
|
let enr1 = Enr::from_str("enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8").unwrap();
|
||||||
|
let enr2 = Enr::from_str("enr:-IS4QJ2d11eu6dC7E7LoXeLMgMP3kom1u3SE8esFSWvaHoo0dP1jg8O3-nx9ht-EO3CmG7L6OkHcMmoIh00IYWB92QABgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQIB_c-jQMOXsbjWkbN-Oj99H57gfId5pfb4wa1qxwV4CIN1ZHCCIyk").unwrap();
|
||||||
|
let enrs = vec![enr1, enr2];
|
||||||
|
|
||||||
|
let runtime = Runtime::new().unwrap();
|
||||||
|
|
||||||
|
// Create new network service
|
||||||
|
let (service, _) = Service::new(
|
||||||
|
beacon_chain.clone(),
|
||||||
|
&NetworkConfig::default(),
|
||||||
|
&runtime.executor(),
|
||||||
|
log.clone(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Add enrs manually to dht
|
||||||
|
for enr in enrs.iter() {
|
||||||
|
service.libp2p_service().lock().swarm.add_enr(enr.clone());
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
enrs.len(),
|
||||||
|
service
|
||||||
|
.libp2p_service()
|
||||||
|
.lock()
|
||||||
|
.swarm
|
||||||
|
.enr_entries()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.len(),
|
||||||
|
"DHT should have 2 enrs"
|
||||||
|
);
|
||||||
|
// Drop the service value
|
||||||
|
std::mem::drop(service);
|
||||||
|
|
||||||
|
// Recover the network service from beacon chain store and fresh network config
|
||||||
|
let (recovered_service, _) = Service::new(
|
||||||
|
beacon_chain,
|
||||||
|
&NetworkConfig::default(),
|
||||||
|
&runtime.executor(),
|
||||||
|
log.clone(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
enrs.len(),
|
||||||
|
recovered_service
|
||||||
|
.libp2p_service()
|
||||||
|
.lock()
|
||||||
|
.swarm
|
||||||
|
.enr_entries()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.len(),
|
||||||
|
"Recovered DHT should have 2 enrs"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@ pub enum Error {
|
|||||||
PartialBeaconStateError,
|
PartialBeaconStateError,
|
||||||
HotColdDBError(HotColdDBError),
|
HotColdDBError(HotColdDBError),
|
||||||
DBError { message: String },
|
DBError { message: String },
|
||||||
|
RlpError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DecodeError> for Error {
|
impl From<DecodeError> for Error {
|
||||||
|
@ -171,6 +171,7 @@ pub enum DBColumn {
|
|||||||
BeaconStateRoots,
|
BeaconStateRoots,
|
||||||
BeaconHistoricalRoots,
|
BeaconHistoricalRoots,
|
||||||
BeaconRandaoMixes,
|
BeaconRandaoMixes,
|
||||||
|
DhtEnrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<&'static str> for DBColumn {
|
impl Into<&'static str> for DBColumn {
|
||||||
@ -187,6 +188,7 @@ impl Into<&'static str> for DBColumn {
|
|||||||
DBColumn::BeaconStateRoots => "bsr",
|
DBColumn::BeaconStateRoots => "bsr",
|
||||||
DBColumn::BeaconHistoricalRoots => "bhr",
|
DBColumn::BeaconHistoricalRoots => "bhr",
|
||||||
DBColumn::BeaconRandaoMixes => "brm",
|
DBColumn::BeaconRandaoMixes => "brm",
|
||||||
|
DBColumn::DhtEnrs => "dht",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user