restartup fixes on lost internet connection
This commit is contained in:
parent
12916c0b11
commit
650c079288
23
README.md
23
README.md
|
|
@ -6,6 +6,7 @@ For the protocol design, mining rules, balance-sheet model and orphan correction
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
|
- [System Requirements](#system-requirements)
|
||||||
- [Network Requirements](#network-requirements)
|
- [Network Requirements](#network-requirements)
|
||||||
- [Build Instructions](#build-instructions)
|
- [Build Instructions](#build-instructions)
|
||||||
- [PostgreSQL Setup](#postgresql-setup)
|
- [PostgreSQL Setup](#postgresql-setup)
|
||||||
|
|
@ -14,6 +15,13 @@ For the protocol design, mining rules, balance-sheet model and orphan correction
|
||||||
- [Startup](#startup)
|
- [Startup](#startup)
|
||||||
- [Additional Documentation](#additional-documentation)
|
- [Additional Documentation](#additional-documentation)
|
||||||
|
|
||||||
|
## System Requirements:
|
||||||
|
|
||||||
|
- Dedicated Internet
|
||||||
|
- PC / Laptop / Hosted VPS or cloud hostingg
|
||||||
|
- Minimum 16GB memory 24 - 32 GB recommended
|
||||||
|
- i5 or higher CPU (or AMD equivalent CPU)
|
||||||
|
|
||||||
## Network Requirements
|
## Network Requirements
|
||||||
|
|
||||||
A public node needs to be reachable by other peers.
|
A public node needs to be reachable by other peers.
|
||||||
|
|
@ -26,6 +34,7 @@ A public node needs to be reachable by other peers.
|
||||||
|
|
||||||
Loopback and private addresses are useful for local testing, but they should not be announced by a public node.
|
Loopback and private addresses are useful for local testing, but they should not be announced by a public node.
|
||||||
|
|
||||||
|
|
||||||
## Build Instructions
|
## Build Instructions
|
||||||
|
|
||||||
Install Rust and build from the repository root.
|
Install Rust and build from the repository root.
|
||||||
|
|
@ -84,7 +93,7 @@ The Windows installer downloads and installs PostgreSQL when needed, then create
|
||||||
|
|
||||||
### Manual PostgreSQL Setup
|
### Manual PostgreSQL Setup
|
||||||
|
|
||||||
Manual PostgreSQL instructions should live in [docs/POSTGRES.md](docs/POSTGRES.md). Until that file is fully written, use the CLI installer unless you already know how to create the database, user and permissions manually.
|
Manual PostgreSQL instructions should live in [docs/POSTGRES.md](src/branch/main/docs/POSTGRES.md). Until that file is fully written, use the CLI installer unless you already know how to create the database, user and permissions manually.
|
||||||
|
|
||||||
## Settings Configuration
|
## Settings Configuration
|
||||||
|
|
||||||
|
|
@ -158,7 +167,7 @@ dbname = contractless_db
|
||||||
- `error` shows only error logs.
|
- `error` shows only error logs.
|
||||||
- `off` disables log output.
|
- `off` disables log output.
|
||||||
|
|
||||||
Detailed settings notes should live in [docs/SETTINGS.md](docs/SETTINGS.md).
|
Detailed settings notes should live in [docs/SETTINGS.md](src/branch/main/docs/SETTINGS.md).
|
||||||
|
|
||||||
## Runtime Paths
|
## Runtime Paths
|
||||||
|
|
||||||
|
|
@ -303,8 +312,8 @@ Node flags:
|
||||||
## Additional Documentation
|
## Additional Documentation
|
||||||
|
|
||||||
- [Whitepaper](https://contractless.community/contractless_whitepaper.pdf)
|
- [Whitepaper](https://contractless.community/contractless_whitepaper.pdf)
|
||||||
- [Manual PostgreSQL setup](docs/POSTGRES.md)
|
- [Manual PostgreSQL setup](src/branch/main/docs/POSTGRES.md)
|
||||||
- [Settings reference](docs/SETTINGS.md)
|
- [Settings reference](src/branch/main/docs/SETTINGS.md)
|
||||||
- [CLI tools reference](docs/CLI_TOOLS.md)
|
- [CLI tools reference](src/branch/main/docs/CLI_TOOLS.md)
|
||||||
- [Transaction reference](docs/TRANSACTIONS.md)
|
- [Transaction reference](src/branch/main/docs/TRANSACTIONS.md)
|
||||||
- [Developer guide](docs/DEV.md)
|
- [Developer guide](src/branch/main/docs/DEV.md)
|
||||||
|
|
|
||||||
18
docs/DEV.md
18
docs/DEV.md
|
|
@ -108,7 +108,7 @@ Typical uses:
|
||||||
- inspect transaction fields
|
- inspect transaction fields
|
||||||
- apply or undo balance-sheet effects
|
- apply or undo balance-sheet effects
|
||||||
|
|
||||||
For user-facing transaction behavior, see [TRANSACTIONS.md](TRANSACTIONS.md).
|
For user-facing transaction behavior, see [TRANSACTIONS.md](src/branch/main/docs/TRANSACTIONS.md).
|
||||||
|
|
||||||
### Common Hashing and Types
|
### Common Hashing and Types
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ Typical uses:
|
||||||
- inspect client request helpers
|
- inspect client request helpers
|
||||||
- inspect response structures
|
- inspect response structures
|
||||||
|
|
||||||
For raw client handshake notes, see [DEV_CLIENTS.md](DEV_CLIENTS.md).
|
For raw client handshake notes, see [DEV_CLIENTS.md](src/branch/main/docs/DEV_CLIENTS.md).
|
||||||
|
|
||||||
### Standalone Tool Connections
|
### Standalone Tool Connections
|
||||||
|
|
||||||
|
|
@ -225,7 +225,7 @@ When adding a tool:
|
||||||
- use `standalone_tools::connections` for RPC client requests
|
- use `standalone_tools::connections` for RPC client requests
|
||||||
- keep RPC payloads binary across the TCP stream
|
- keep RPC payloads binary across the TCP stream
|
||||||
- print human-readable or JSON-decoded output only after the binary response is received
|
- print human-readable or JSON-decoded output only after the binary response is received
|
||||||
- add the tool to [CLI_TOOLS.md](CLI_TOOLS.md)
|
- add the tool to [CLI_TOOLS.md](src/branch/main/docs/CLI_TOOLS.md)
|
||||||
|
|
||||||
## Adding New RPC Commands
|
## Adding New RPC Commands
|
||||||
|
|
||||||
|
|
@ -242,9 +242,9 @@ When adding a command:
|
||||||
|
|
||||||
## Documentation Map
|
## Documentation Map
|
||||||
|
|
||||||
- [README.md](../README.md): build, configure and start a node
|
- [README.md](src/branch/main/README.md): build, configure and start a node
|
||||||
- [CLI_TOOLS.md](CLI_TOOLS.md): command-line tool reference
|
- [CLI_TOOLS.md](src/branch/main/docs/CLI_TOOLS.md): command-line tool reference
|
||||||
- [TRANSACTIONS.md](TRANSACTIONS.md): transaction behavior reference
|
- [TRANSACTIONS.md](src/branch/main/docs/TRANSACTIONS.md): transaction behavior reference
|
||||||
- [POSTGRES.md](POSTGRES.md): manual PostgreSQL setup
|
- [POSTGRES.md](src/branch/main/docs/POSTGRES.md): manual PostgreSQL setup
|
||||||
- [SETTINGS.md](SETTINGS.md): `settings.ini` field reference
|
- [SETTINGS.md](src/branch/main/docs/SETTINGS.md): `settings.ini` field reference
|
||||||
- [DEV_CLIENTS.md](DEV_CLIENTS.md): low-level client handshake and request notes
|
- [DEV_CLIENTS.md](src/branch/main/docs/DEV_CLIENTS.md): low-level client handshake and request notes
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use blockchain::common::cli_prompts::prompt_visible;
|
use blockchain::common::cli_prompts::prompt_visible;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::standalone_tools::vanity_resolver::canonical_vanity_address_input;
|
||||||
use blockchain::wallets::structures::Wallet;
|
use blockchain::wallets::structures::Wallet;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -21,5 +22,9 @@ async fn main() {
|
||||||
.to_string()
|
.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("{}", Wallet::short_address_validation(&address));
|
let is_valid = Wallet::short_address_validation(&address)
|
||||||
|
|| canonical_vanity_address_input(&address)
|
||||||
|
.is_some_and(|vanity| Wallet::short_address_validation(&vanity));
|
||||||
|
|
||||||
|
println!("{is_valid}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -537,6 +537,13 @@ impl Connection {
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn count_miner_connections(&self) -> usize {
|
||||||
|
self.connection_map
|
||||||
|
.values()
|
||||||
|
.filter(|info| ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner))
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
// Return ready peer streams so broadcast-style paths do not send
|
// Return ready peer streams so broadcast-style paths do not send
|
||||||
// network-wide traffic to peers still completing startup sync.
|
// network-wide traffic to peers still completing startup sync.
|
||||||
pub fn get_all_streams(&self) -> Vec<Arc<Mutex<TcpStream>>> {
|
pub fn get_all_streams(&self) -> Vec<Arc<Mutex<TcpStream>>> {
|
||||||
|
|
@ -860,6 +867,17 @@ pub async fn peer_connection_count() -> usize {
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn miner_connection_count() -> usize {
|
||||||
|
// Recovery logic uses raw miner sockets to avoid starting another
|
||||||
|
// bootstrap attempt while an accepted peer is still finishing setup.
|
||||||
|
CONNECTIONS
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.map(|connection| connection.count_miner_connections())
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn mark_peer_wallet_registry_synced(key: &str) -> bool {
|
pub async fn mark_peer_wallet_registry_synced(key: &str) -> bool {
|
||||||
CONNECTIONS
|
CONNECTIONS
|
||||||
.write()
|
.write()
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use crate::rpc::command_maps::{
|
||||||
RPC_WALLET_REGISTRY_SYNC,
|
RPC_WALLET_REGISTRY_SYNC,
|
||||||
};
|
};
|
||||||
use crate::standalone_tools::transaction_creator::create_transaction_request;
|
use crate::standalone_tools::transaction_creator::create_transaction_request;
|
||||||
|
use crate::standalone_tools::vanity_resolver::canonical_vanity_address_input;
|
||||||
use crate::timeout;
|
use crate::timeout;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::Duration;
|
use crate::Duration;
|
||||||
|
|
@ -553,7 +554,7 @@ async fn build_request_bytes(
|
||||||
// Lookup canonical short owner address by registered vanity address.
|
// Lookup canonical short owner address by registered vanity address.
|
||||||
45 => {
|
45 => {
|
||||||
let command_number: u8 = RPC_VANITY_OWNER_LOOKUP;
|
let command_number: u8 = RPC_VANITY_OWNER_LOOKUP;
|
||||||
let address_bytes = Wallet::normalize_to_short_address(&command_input)
|
let address_bytes = canonical_vanity_address_input(&command_input)
|
||||||
.and_then(|vanity| Wallet::vanity_address_to_bytes(&vanity))
|
.and_then(|vanity| Wallet::vanity_address_to_bytes(&vanity))
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
io::Error::new(io::ErrorKind::InvalidInput, "Invalid vanity address")
|
io::Error::new(io::ErrorKind::InvalidInput, "Invalid vanity address")
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,45 @@ use crate::rpc::command_maps::RPC_VANITY_OWNER_LOOKUP;
|
||||||
use crate::standalone_tools::connections::handshake::{self, HandshakeWallet};
|
use crate::standalone_tools::connections::handshake::{self, HandshakeWallet};
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
|
pub fn canonical_vanity_address_input(address: &str) -> Option<String> {
|
||||||
|
let trimmed = address.trim();
|
||||||
|
let (payload, network_suffix) = trimmed.rsplit_once('.')?;
|
||||||
|
|
||||||
|
let network_suffix = network_suffix.trim().to_ascii_lowercase();
|
||||||
|
if Wallet::map_wallet_to_byte(&network_suffix) == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = payload.trim();
|
||||||
|
if payload.is_empty() || payload.len() > Wallet::SHORT_ADDRESS_HASH_BYTES_LENGTH {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if !payload.chars().all(|ch| ch.is_ascii_alphabetic()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(format!(
|
||||||
|
"{:>width$}.{network_suffix}",
|
||||||
|
payload.to_ascii_lowercase(),
|
||||||
|
width = Wallet::SHORT_ADDRESS_HASH_BYTES_LENGTH
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn resolve_wallet_address_input(
|
pub async fn resolve_wallet_address_input(
|
||||||
address: &str,
|
address: &str,
|
||||||
wallet: &Wallet,
|
wallet: &Wallet,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let normalized = Wallet::normalize_to_short_address(address.trim())
|
let trimmed = address.trim();
|
||||||
.ok_or_else(|| "Invalid wallet address.".to_string())?;
|
if let Some(normalized) = Wallet::normalize_to_short_address(trimmed) {
|
||||||
|
|
||||||
if Wallet::short_address_to_bytes(&normalized).is_some() {
|
if Wallet::short_address_to_bytes(&normalized).is_some() {
|
||||||
return Ok(normalized);
|
return Ok(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
if Wallet::vanity_address_to_bytes(&normalized).is_none() {
|
|
||||||
return Err("Invalid wallet address.".to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup_vanity_owner(&normalized, wallet).await
|
let canonical_vanity = canonical_vanity_address_input(trimmed)
|
||||||
|
.ok_or_else(|| "Invalid wallet address.".to_string())?;
|
||||||
|
|
||||||
|
lookup_vanity_owner(&canonical_vanity, wallet).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn lookup_vanity_owner(vanity_address: &str, wallet: &Wallet) -> Result<String, String> {
|
async fn lookup_vanity_owner(vanity_address: &str, wallet: &Wallet) -> Result<String, String> {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::common::network_startup::get_connections;
|
use crate::common::network_startup::get_connections;
|
||||||
use crate::log::{error, info};
|
use crate::log::{error, info, warn};
|
||||||
use crate::miner::flag::{
|
use crate::miner::flag::{
|
||||||
clear_mining_stop_request, set_mining_state, set_node_mode, MiningState, NodeMode,
|
clear_mining_stop_request, is_normal_mode, set_mining_state, set_node_mode, MiningState,
|
||||||
|
NodeMode,
|
||||||
};
|
};
|
||||||
|
use crate::records::memory::connections::{miner_connection_count, peer_connection_count};
|
||||||
use crate::records::memory::response_channels::Command;
|
use crate::records::memory::response_channels::Command;
|
||||||
use crate::rpc::client::handshake::connect_and_handshake;
|
use crate::rpc::client::handshake::connect_and_handshake;
|
||||||
use crate::rpc::client::structs::Connect;
|
use crate::rpc::client::structs::Connect;
|
||||||
|
|
@ -31,8 +33,26 @@ pub async fn handle_connections(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// try the configured bootstrap peers one by one until a
|
let connected = attempt_bootstrap_connections(db, wallet, map, "startup").await?;
|
||||||
// handshake succeeds or the list is exhausted
|
if connected {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Startup can continue as a standalone node even if no bootstrap peer is reachable.
|
||||||
|
set_node_mode(NodeMode::Normal);
|
||||||
|
clear_mining_stop_request();
|
||||||
|
set_mining_state(MiningState::Idle);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn attempt_bootstrap_connections(
|
||||||
|
db: Db,
|
||||||
|
wallet: Arc<Wallet>,
|
||||||
|
map: Arc<Mutex<Command>>,
|
||||||
|
context: &str,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
// Try the configured bootstrap peers one by one until a
|
||||||
|
// handshake succeeds or the list is exhausted.
|
||||||
let filtered_servers = get_connections().await;
|
let filtered_servers = get_connections().await;
|
||||||
let mut last_error: Option<String> = None;
|
let mut last_error: Option<String> = None;
|
||||||
|
|
||||||
|
|
@ -60,8 +80,12 @@ pub async fn handle_connections(
|
||||||
|
|
||||||
let err_string = match connect_and_handshake(connect_params).await {
|
let err_string = match connect_and_handshake(connect_params).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
|
if context == "startup" {
|
||||||
info!("Connected to {server}");
|
info!("Connected to {server}");
|
||||||
return Ok(());
|
} else {
|
||||||
|
info!("[reconnect] bootstrap recovery connected to {server}");
|
||||||
|
}
|
||||||
|
return Ok(true);
|
||||||
}
|
}
|
||||||
Err(err) => err.to_string(),
|
Err(err) => err.to_string(),
|
||||||
};
|
};
|
||||||
|
|
@ -71,19 +95,69 @@ pub async fn handle_connections(
|
||||||
if err_string.contains("The connection is already in the connection manager Please wait 10 minutes and try again") {
|
if err_string.contains("The connection is already in the connection manager Please wait 10 minutes and try again") {
|
||||||
return Err(err_string);
|
return Err(err_string);
|
||||||
}
|
}
|
||||||
|
if context == "startup" {
|
||||||
error!("Error connecting to {server}: {err_string}");
|
error!("Error connecting to {server}: {err_string}");
|
||||||
|
} else {
|
||||||
|
warn!("[reconnect] bootstrap recovery failed to connect to {server}: {err_string}");
|
||||||
|
}
|
||||||
last_error = Some(err_string.clone());
|
last_error = Some(err_string.clone());
|
||||||
sleep(Duration::from_secs(5)).await;
|
sleep(Duration::from_secs(5)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = last_error {
|
if let Some(err) = last_error {
|
||||||
|
if context == "startup" {
|
||||||
info!("No bootstrap peers connected during startup: {err}");
|
info!("No bootstrap peers connected during startup: {err}");
|
||||||
} else {
|
} else {
|
||||||
info!("No bootstrap peers connected during startup.");
|
warn!("[reconnect] bootstrap recovery found no peers: {err}");
|
||||||
}
|
}
|
||||||
// Startup can continue as a standalone node even if no bootstrap peer is reachable.
|
} else {
|
||||||
set_node_mode(NodeMode::Normal);
|
if context == "startup" {
|
||||||
clear_mining_stop_request();
|
info!("No bootstrap peers connected during startup.");
|
||||||
set_mining_state(MiningState::Idle);
|
} else {
|
||||||
Ok(())
|
warn!("[reconnect] bootstrap recovery has no configured peers to try");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_isolated_bootstrap_recovery(
|
||||||
|
db: Db,
|
||||||
|
wallet: Arc<Wallet>,
|
||||||
|
map: Arc<Mutex<Command>>,
|
||||||
|
) {
|
||||||
|
let outgoing_connections = crate::Settings::load()
|
||||||
|
.map(|settings| settings.outgoing_connections)
|
||||||
|
.unwrap_or(0);
|
||||||
|
if outgoing_connections == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
sleep(Duration::from_secs(60)).await;
|
||||||
|
|
||||||
|
if !is_normal_mode() || peer_connection_count().await > 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if miner_connection_count().await > 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("[reconnect] no operational peers remain; retrying bootstrap recovery");
|
||||||
|
match attempt_bootstrap_connections(
|
||||||
|
db.clone(),
|
||||||
|
wallet.clone(),
|
||||||
|
map.clone(),
|
||||||
|
"reconnect",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(true) => {}
|
||||||
|
Ok(false) => {}
|
||||||
|
Err(err) => warn!("[reconnect] bootstrap recovery aborted: {err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::records::memory::response_channels::Command;
|
||||||
use crate::records::record_chain::rewards_tx::rebuild_immature_reward_window;
|
use crate::records::record_chain::rewards_tx::rebuild_immature_reward_window;
|
||||||
use crate::rpc::server::start_rpc::start_rpc;
|
use crate::rpc::server::start_rpc::start_rpc;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::startup::connections::handle_connections;
|
use crate::startup::connections::{handle_connections, spawn_isolated_bootstrap_recovery};
|
||||||
use crate::startup::daemonize::{install_shutdown_cleanup, remove_registered_pid_file};
|
use crate::startup::daemonize::{install_shutdown_cleanup, remove_registered_pid_file};
|
||||||
use crate::startup::initialize_startup::open_chain_state;
|
use crate::startup::initialize_startup::open_chain_state;
|
||||||
use crate::verifications::verification_service::initialize_global_verification_service;
|
use crate::verifications::verification_service::initialize_global_verification_service;
|
||||||
|
|
@ -165,6 +165,7 @@ pub async fn run_unlocked_node(wallet: Arc<Wallet>, install_shutdown: bool) -> R
|
||||||
handle_connections(db.clone(), wallet.clone(), map.clone())
|
handle_connections(db.clone(), wallet.clone(), map.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Startup connection error: {e}"))?;
|
.map_err(|e| format!("Startup connection error: {e}"))?;
|
||||||
|
spawn_isolated_bootstrap_recovery(db.clone(), wallet.clone(), map.clone());
|
||||||
|
|
||||||
create_genesis_transaction(
|
create_genesis_transaction(
|
||||||
&db,
|
&db,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue