Merge remote-tracking branch 'grin_ardocrat/lmdb_update' into grim
This commit is contained in:
@@ -3,4 +3,31 @@ FROM mcr.microsoft.com/devcontainers/rust:latest
|
||||
|
||||
# Install Required Dependencies
|
||||
RUN apt-get -qq update
|
||||
RUN apt-get install -y build-essential cmake git libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev pkg-config libssl-dev llvm
|
||||
RUN apt-get install -y \
|
||||
build-essential \
|
||||
clang \
|
||||
cmake \
|
||||
fuse \
|
||||
gh \
|
||||
git \
|
||||
libgit2-dev \
|
||||
libncurses5-dev \
|
||||
libncursesw5-dev \
|
||||
libssl-dev \
|
||||
llvm \
|
||||
pkg-config \
|
||||
snapd \
|
||||
squashfuse \
|
||||
sudo \
|
||||
zlib1g-dev
|
||||
|
||||
RUN echo '#!/bin/bash\n\
|
||||
/usr/lib/snapd/snapd &\n\
|
||||
sleep 5\n\
|
||||
exec "$@"' > /start-snapd.sh && \
|
||||
chmod +x /start-snapd.sh
|
||||
|
||||
VOLUME ["/sys/fs/cgroup"]
|
||||
|
||||
ENTRYPOINT ["/start-snapd.sh"]
|
||||
CMD ["bash"]
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"github.copilot-chat"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"runArgs": ["--privileged", "-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro", "-v", "/tmp:/tmp"]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
name: Snap Package
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- staging
|
||||
|
||||
jobs:
|
||||
build-snap:
|
||||
name: Build snap
|
||||
strategy:
|
||||
matrix:
|
||||
runs-on: [ubuntu-latest, ubuntu-24.04-arm]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare snapcraft project
|
||||
run: |
|
||||
grade=devel
|
||||
if [ "${{ github.ref_name }}" = "master" ]; then
|
||||
grade=stable
|
||||
fi
|
||||
|
||||
mkdir -p snap
|
||||
sed "s/SNAP_GRADE/$grade/" .packaging/snaps/snapcraft.yaml > snap/snapcraft.yaml
|
||||
|
||||
- name: Build snap package
|
||||
id: build
|
||||
uses: snapcore/action-build@v1
|
||||
with:
|
||||
path: .
|
||||
|
||||
- name: Show snap package
|
||||
run: find . -maxdepth 1 -name "*.snap" -ls
|
||||
|
||||
- name: Upload snap artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: grin-snap
|
||||
path: "*.snap"
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,40 @@
|
||||
name: grin
|
||||
adopt-info: grin
|
||||
summary: Minimal implementation of the Mimblewimble protocol
|
||||
description: |
|
||||
https://grin.mw/
|
||||
|
||||
confinement: strict
|
||||
grade: SNAP_GRADE
|
||||
base: core22
|
||||
architectures:
|
||||
- build-on: [arm64]
|
||||
build-for: [arm64]
|
||||
- build-on: [amd64]
|
||||
build-for: [amd64]
|
||||
|
||||
parts:
|
||||
grin:
|
||||
plugin: rust
|
||||
source: .
|
||||
override-pull: |
|
||||
craftctl default
|
||||
craftctl set version="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n 1)"
|
||||
build-packages:
|
||||
- build-essential
|
||||
- clang
|
||||
- cmake
|
||||
- libncurses5-dev
|
||||
- libncursesw5-dev
|
||||
- libssl-dev
|
||||
- llvm
|
||||
- pkg-config
|
||||
- zlib1g-dev
|
||||
|
||||
apps:
|
||||
grin:
|
||||
environment:
|
||||
LC_ALL: C.UTF-8
|
||||
LANG: C.UTF-8
|
||||
command: bin/grin
|
||||
plugs: [network, network-bind]
|
||||
+41
-9
@@ -12,7 +12,36 @@ More documentation or updates/fixes to existing documentation are also very welc
|
||||
|
||||
We generally prefer you to PR your work earlier rather than later. This ensures everyone else has a better idea of what's being worked on, and can help reduce wasted effort. If work on your PR has just begun, please feel free to create the PR with [WIP] (work in progress) in the PR title, and let us know when it's ready for review in the comments.
|
||||
|
||||
Since mainnet has been released, the bar for having PRs accepted has been raised. Before submitting your PR for approval, please be ensure it:
|
||||
All changes should first be submitted against the `staging` branch and follow the PR rules below. The `staging` branch is tested by the community before changes are merged into `master` by an admin when appropriate.
|
||||
|
||||
```text
|
||||
Contributor branch
|
||||
+-- Pull request
|
||||
+-- staging branch
|
||||
+-- testing (community)
|
||||
+-- Admin merge
|
||||
+-- master
|
||||
```
|
||||
|
||||
## Creating a Pull Request
|
||||
|
||||
Create your work on a dedicated branch based on the latest `staging` branch.
|
||||
|
||||
```sh
|
||||
git checkout staging
|
||||
git pull
|
||||
git checkout -b my-change
|
||||
```
|
||||
|
||||
Fork the Grin repository and add your fork as a git remote.
|
||||
|
||||
```sh
|
||||
git remote add my-fork <your-fork-url>
|
||||
```
|
||||
|
||||
Push your branch to your fork and open a GitHub pull request from your fork's branch into the Grin repository's `staging` branch. You can do this through the GitHub web interface.
|
||||
|
||||
Since mainnet has been released, the bar for having PRs accepted has been raised. Before submitting your PR for approval, please ensure it:
|
||||
* Includes a proper description of what problems the PR addresses, as well as a detailed explanation as to what it changes
|
||||
* Explains whether/how the change is consensus breaking or breaks existing client functionality
|
||||
* Contains unit tests exercising new/changed functionality
|
||||
@@ -26,23 +55,26 @@ The development team will be happy to help and guide you with any of these point
|
||||
|
||||
# Find Us
|
||||
|
||||
When you are starting to contribute to grin, we really would appreciate if you come by the gitter chat channels.
|
||||
When you are starting to contribute to grin, we really would appreciate if you come by one of the community channels.
|
||||
|
||||
In case of problems with trying out grin, before starting to contribute, there's the [grincoin#support](https://keybase.io/team/grincoin) on Keybase. Write there about what you've done, what you want to do, and maybe paste logs through a text paste webservice.
|
||||
If you run into problems while trying out Grin before contributing, ask in [grincoin#support](https://keybase.io/team/grincoin) on Keybase. Describe what you've done, what you want to do, and include logs using a paste service if needed.
|
||||
|
||||
* Please [join the grincoin#general on Keybase](https://keybase.io/team/grincoin) to get a feeling for the community.
|
||||
* And see the developers chat channel [grincoin#dev on Keybase](https://keybase.io/team/grincoin) if you have questions about source code files.
|
||||
* Join the development discussion on [Telegram](https://t.me/grindevelopment).
|
||||
* [Grin Governance Council Public](https://t.me/Grin_Governance) channel where biweekly meetings are held for governance and development issues.
|
||||
* [Grin General Telegram channel](https://t.me/grinprivacy/1) for general community discussion.
|
||||
* Please [join grincoin#general on Keybase](https://keybase.io/team/grincoin) to get a feeling for the community.
|
||||
* See the developers' chat channel [grincoin#dev on Keybase](https://keybase.io/team/grincoin) if you have questions about source code files.
|
||||
If you explain what you're looking at and what you want to do, we'll try to help you along the way.
|
||||
* See `docs/*.md` and the folder structure explanations, [the wiki](https://github.com/mimblewimble/docs/wiki) and the official [Grin documentation](https://docs.grin.mw/).
|
||||
* Further information and discussions are in the [Forum](https://forum.grin.mw), the [website](https://grin.mw), the [mailing list](https://lists.launchpad.net/mimblewimble/) and news channels like the [Reddit/grincoin](https://www.reddit.com/r/grincoin/) and a (mostly unfiltered!) Twitter bot that collects headlines, mailing list posts, and reddit posts related to Mimblewimble/Grin: [@grinmw](https://twitter.com/grinmw)
|
||||
* Further information and discussions are in the [Forum](https://forum.grin.mw), the [website](https://grin.mw), the [mailing list](https://lists.launchpad.net/mimblewimble/), and news channels like [Reddit/grincoin](https://www.reddit.com/r/grincoin/).
|
||||
|
||||
## Testing
|
||||
|
||||
Run all tests with `cargo test --all` and please remember to test locally before creating a PR on github.
|
||||
Run all tests with `cargo test --all` and please remember to test locally before creating a PR against `staging`.
|
||||
|
||||
### Check Travis output
|
||||
### Check CI output
|
||||
|
||||
After creating a PR on github, the code will be tested automatically by Travis CI, and from the results you'll get a red or green light. The test can take a while, and you'll have a "yellow traffic light" on your PR until Travis CI is done testing.
|
||||
After creating a PR, the code will be tested automatically by CI. The test can take a while, and your PR may remain pending until CI is done testing.
|
||||
|
||||
### Building quality
|
||||
|
||||
|
||||
@@ -30,9 +30,8 @@ To get involved, read our [contributing docs](CONTRIBUTING.md).
|
||||
|
||||
Find us:
|
||||
|
||||
* Telegram: [Grin Development](https://t.me/grindevelopment)
|
||||
* Chat: [Keybase](https://keybase.io/team/grincoin), more instructions on how to join [here](https://grin.mw/community).
|
||||
* Mailing list: join the [~Mimblewimble team](https://launchpad.net/~mimblewimble) and subscribe on Launchpad.
|
||||
* Twitter for the Grin council: [@grincouncil](https://twitter.com/grincouncil)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ use crate::{rest::*, BlockListing};
|
||||
use std::sync::Weak;
|
||||
|
||||
/// Main interface into all node API functions.
|
||||
/// Node APIs are split into two seperate blocks of functionality
|
||||
/// Node APIs are split into two separate blocks of functionality
|
||||
/// called the ['Owner'](struct.Owner.html) and ['Foreign'](struct.Foreign.html) APIs
|
||||
///
|
||||
/// Methods in this API are intended to be 'single use'.
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ use std::net::SocketAddr;
|
||||
use std::sync::Weak;
|
||||
|
||||
/// Main interface into all node API functions.
|
||||
/// Node APIs are split into two seperate blocks of functionality
|
||||
/// Node APIs are split into two separate blocks of functionality
|
||||
/// called the ['Owner'](struct.Owner.html) and ['Foreign'](struct.Foreign.html) APIs
|
||||
///
|
||||
/// Methods in this API are intended to be 'single use'.
|
||||
|
||||
+3
-2
@@ -43,7 +43,7 @@ use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Orphan pool size is limited by MAX_ORPHAN_SIZE
|
||||
@@ -171,8 +171,9 @@ impl Chain {
|
||||
genesis: Block,
|
||||
pow_verifier: fn(&BlockHeader) -> Result<(), pow::Error>,
|
||||
archive_mode: bool,
|
||||
db_migration_prog_tx: Option<mpsc::Sender<i8>>,
|
||||
) -> Result<Chain, Error> {
|
||||
let store = Arc::new(store::ChainStore::new(&db_root)?);
|
||||
let store = Arc::new(store::ChainStore::new(&db_root, db_migration_prog_tx)?);
|
||||
|
||||
// open the txhashset, creating a new one if necessary
|
||||
let mut txhashset = txhashset::TxHashSet::open(db_root.clone(), store.clone(), None)?;
|
||||
|
||||
+6
-2
@@ -28,7 +28,7 @@ use grin_core::ser;
|
||||
use grin_store as store;
|
||||
use grin_store::{option_to_not_found, Error};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
const STORE_SUBPATH: &str = "chain";
|
||||
|
||||
@@ -67,13 +67,17 @@ pub struct ChainStore {
|
||||
|
||||
impl ChainStore {
|
||||
/// Create new chain store
|
||||
pub fn new(db_root: &str) -> Result<ChainStore, Error> {
|
||||
pub fn new(
|
||||
db_root: &str,
|
||||
db_migration_prog_tx: Option<mpsc::Sender<i8>>,
|
||||
) -> Result<ChainStore, Error> {
|
||||
let db = store::Store::new(
|
||||
db_root,
|
||||
None,
|
||||
Some(STORE_SUBPATH),
|
||||
DB_PREFIXES.to_vec(),
|
||||
None,
|
||||
db_migration_prog_tx,
|
||||
)?;
|
||||
Ok(ChainStore { db })
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ pub fn init_chain(dir_name: &str, genesis: Block) -> Chain {
|
||||
genesis,
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ fn setup_with_status_adapter(dir_name: &str, genesis: Block, adapter: Arc<Status
|
||||
genesis,
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1057,6 +1058,7 @@ fn actual_diff_iter_output() {
|
||||
genesis_block,
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let iter = chain.difficulty_iter().unwrap();
|
||||
|
||||
@@ -38,7 +38,7 @@ fn test_store_kernel_idx() {
|
||||
|
||||
let commit = Commitment::from_vec(vec![]);
|
||||
|
||||
let store = ChainStore::new(chain_dir).unwrap();
|
||||
let store = ChainStore::new(chain_dir, None).unwrap();
|
||||
let mut batch = store.batch().unwrap();
|
||||
let index = store::nrd_recent_kernel_index();
|
||||
|
||||
@@ -185,7 +185,7 @@ fn test_store_kernel_idx_pop_back() {
|
||||
|
||||
let commit = Commitment::from_vec(vec![]);
|
||||
|
||||
let store = ChainStore::new(chain_dir).unwrap();
|
||||
let store = ChainStore::new(chain_dir, None).unwrap();
|
||||
let mut batch = store.batch().unwrap();
|
||||
let index = store::nrd_recent_kernel_index();
|
||||
|
||||
@@ -293,7 +293,7 @@ fn test_store_kernel_idx_rewind() {
|
||||
|
||||
let commit = Commitment::from_vec(vec![]);
|
||||
|
||||
let store = ChainStore::new(chain_dir).unwrap();
|
||||
let store = ChainStore::new(chain_dir, None).unwrap();
|
||||
let mut batch = store.batch().unwrap();
|
||||
let index = store::nrd_recent_kernel_index();
|
||||
|
||||
@@ -395,7 +395,7 @@ fn test_store_kernel_idx_multiple_commits() {
|
||||
let commit = Commitment::from_vec(vec![]);
|
||||
let commit2 = Commitment::from_vec(vec![1]);
|
||||
|
||||
let store = ChainStore::new(chain_dir).unwrap();
|
||||
let store = ChainStore::new(chain_dir, None).unwrap();
|
||||
let mut batch = store.batch().unwrap();
|
||||
let index = store::nrd_recent_kernel_index();
|
||||
|
||||
@@ -483,7 +483,7 @@ fn test_store_kernel_idx_clear() -> Result<(), Error> {
|
||||
let commit = Commitment::from_vec(vec![]);
|
||||
let commit2 = Commitment::from_vec(vec![1]);
|
||||
|
||||
let store = ChainStore::new(chain_dir)?;
|
||||
let store = ChainStore::new(chain_dir, None)?;
|
||||
let index = store::nrd_recent_kernel_index();
|
||||
|
||||
// Add a couple of single entries to the index and commit the batch.
|
||||
|
||||
@@ -47,6 +47,7 @@ fn test_coinbase_maturity() {
|
||||
genesis_block,
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ fn test_header_perf_impl(is_test_chain: bool, src_root_dir: &str, dest_root_dir:
|
||||
genesis.clone(),
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -62,6 +63,7 @@ fn test_header_perf_impl(is_test_chain: bool, src_root_dir: &str, dest_root_dir:
|
||||
genesis.clone(),
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
@@ -74,6 +74,7 @@ impl SegmenterResponder {
|
||||
genesis,
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
@@ -134,6 +135,7 @@ impl DesegmenterRequestor {
|
||||
genesis,
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
|
||||
@@ -52,6 +52,7 @@ fn test_pibd_chain_validation_impl(is_test_chain: bool, src_root_dir: &str) {
|
||||
genesis.clone(),
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
@@ -38,7 +38,7 @@ fn test_unexpected_zip() {
|
||||
let db_root = format!(".grin_txhashset_zip");
|
||||
clean_output_dir(&db_root);
|
||||
{
|
||||
let chain_store = ChainStore::new(&db_root).unwrap();
|
||||
let chain_store = ChainStore::new(&db_root, None).unwrap();
|
||||
let store = Arc::new(chain_store);
|
||||
txhashset::TxHashSet::open(db_root.clone(), store.clone(), None).unwrap();
|
||||
let head = BlockHeader::default();
|
||||
|
||||
@@ -262,8 +262,11 @@ fn comments() -> HashMap<String, String> {
|
||||
"host".to_string(),
|
||||
"
|
||||
#The interface on which to listen.
|
||||
#0.0.0.0 will listen on all interfaces, allowing others to interact
|
||||
#127.0.0.1 will listen on the local machine only
|
||||
#:: will listen on all IPv6 interfaces and may also accept IPv4 depending on OS socket settings
|
||||
#0.0.0.0 will listen on all IPv4 interfaces, allowing others to interact
|
||||
#Set host to 0.0.0.0 if only IPv4 listening is desired
|
||||
#127.0.0.1 will listen on the local machine only over IPv4
|
||||
#::1 will listen on the local machine only over IPv6
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
+3
-1
@@ -252,7 +252,9 @@ which can be signed by the attacker because Carol's blinding factor cancels out
|
||||
|
||||
This output (`(113 + 99)*G + 2*H`) requires that both the numbers 113 and 99 are known in order to be spent; the attacker
|
||||
would thus have successfully locked Carol's UTXO. The requirement for a range proof for the blinding factor prevents this
|
||||
because the attacker doesn't know the number 113 and thus neither (113 + 99). A more detailed description of range proofs is further detailed in the [range proof paper](https://eprint.iacr.org/2017/1066.pdf).
|
||||
because the attacker doesn't know the number 113 and thus neither (113 + 99). In other words, without knowing the private
|
||||
key (blinding factor), the attacker would not know the value in the output and would not be able to produce a valid range proof for it.
|
||||
A more detailed description of range proofs is further detailed in the [range proof paper](https://eprint.iacr.org/2017/1066.pdf).
|
||||
|
||||
#### Putting It All Together
|
||||
|
||||
|
||||
+2
-2
@@ -113,10 +113,10 @@ Now, (hopefully) armed with a basic understanding of what the Cuckoo Cycle algor
|
||||
|
||||
## Mining in Grin
|
||||
|
||||
The Cuckoo Cycle outlined above forms the basis of Grin's mining process, however Grin uses two variantion of Cuckoo Cycle in tandem with several other systems to create a Proof-of-Work.
|
||||
The Cuckoo Cycle outlined above forms the basis of Grin's mining process, however Grin uses two variations of Cuckoo Cycle in tandem with several other systems to create a Proof-of-Work.
|
||||
|
||||
1. for GPUs: Cuckaroo on 2^29 edges
|
||||
* Tweaked every 6 months to maitain ASIC resistance.
|
||||
* Tweaked every 6 months to maintain ASIC resistance.
|
||||
* 90% of rewards at launch, linearly decreasing to 0% in 2 years.
|
||||
* Variant of Cuckoo that enforces so-called ``mean'' mining.
|
||||
* Takes 5.5GB of memory (perhaps 4GB with slowdown).
|
||||
|
||||
@@ -124,6 +124,7 @@ impl PeerStore {
|
||||
Some(STORE_SUBPATH),
|
||||
vec![PEER_PREFIX],
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
Ok(PeerStore { db })
|
||||
}
|
||||
|
||||
+1
-1
@@ -293,7 +293,7 @@ pub struct P2PConfig {
|
||||
/// Default address for peer-to-peer connections.
|
||||
impl Default for P2PConfig {
|
||||
fn default() -> P2PConfig {
|
||||
let ipaddr = "0.0.0.0".parse().unwrap();
|
||||
let ipaddr = "::".parse().unwrap();
|
||||
P2PConfig {
|
||||
host: ipaddr,
|
||||
port: 3414,
|
||||
|
||||
@@ -55,6 +55,7 @@ pub fn init_chain(dir_name: &str, genesis: Block) -> Chain {
|
||||
genesis,
|
||||
pow::verify_size,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use std::sync::Arc;
|
||||
use chrono::prelude::Utc;
|
||||
use rand::prelude::*;
|
||||
|
||||
use crate::api;
|
||||
use crate::chain;
|
||||
use crate::core::global::{ChainTypes, DEFAULT_FUTURE_TIME_LIMIT};
|
||||
use crate::core::{core, libtx, pow};
|
||||
@@ -28,6 +27,7 @@ use crate::p2p;
|
||||
use crate::pool;
|
||||
use crate::pool::types::DandelionConfig;
|
||||
use crate::store;
|
||||
use crate::{api, Server};
|
||||
|
||||
/// Error type wrapping underlying module errors.
|
||||
#[derive(Debug)]
|
||||
@@ -405,3 +405,19 @@ impl DandelionEpoch {
|
||||
self.relay_peer.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Server initialization status.
|
||||
pub enum ServerInitStatus {
|
||||
/// Database loading.
|
||||
LoadDatabase,
|
||||
/// Database migration progress.
|
||||
DBMigrationProgress(i8),
|
||||
/// P2P server initialization.
|
||||
StartSync,
|
||||
/// API server initialization.
|
||||
StartAPI,
|
||||
/// Server instance after successful initialization.
|
||||
FinishedLoading(Server),
|
||||
/// Error on initialization.
|
||||
ErrorLoading(Error),
|
||||
}
|
||||
|
||||
@@ -35,18 +35,20 @@ use crate::util::StopState;
|
||||
|
||||
/// DNS Seeds with contacts associated - Mainnet
|
||||
pub const MAINNET_DNS_SEEDS: &[&str] = &[
|
||||
"mainnet-seed.grinnode.live", // info@grinnode.live
|
||||
"grincoin.org", // xmpp:aglkm@conversations.im
|
||||
"main.gri.mw", // admin@gri.mw
|
||||
"mainnet.grinffindor.org", // support@grinffindor.org
|
||||
"main-seed.grin.money", // support@grinily.com
|
||||
"mainnet-seed.grinnode.live", // info@grinnode.live
|
||||
"grincoin.org", // xmpp:aglkm@conversations.im
|
||||
"main.gri.mw", // admin@gri.mw
|
||||
"mainnet.grinffindor.org", // support@grinffindor.org
|
||||
"main-seed.grin.money", // support@grinily.com
|
||||
"mainnet.fountainoffairfortune.it", // support@fountainoffairfortune.it
|
||||
];
|
||||
/// DNS Seeds with contacts associated - Testnet
|
||||
pub const TESTNET_DNS_SEEDS: &[&str] = &[
|
||||
"testnet.grincoin.org", // xmpp:aglkm@conversations.im
|
||||
"test.gri.mw", // admin@gri.mw
|
||||
"testnet.grinffindor.org", // support@grinffindor.org
|
||||
"test-seed.grin.money", // support@grinily.com
|
||||
"testnet.grincoin.org", // xmpp:aglkm@conversations.im
|
||||
"test.gri.mw", // admin@gri.mw
|
||||
"testnet.grinffindor.org", // support@grinffindor.org
|
||||
"test-seed.grin.money", // support@grinily.com
|
||||
"testnet.fountainoffairfortune.it", // support@fountainoffairfortune.it
|
||||
];
|
||||
|
||||
pub fn connect_and_monitor(
|
||||
|
||||
+37
-14
@@ -16,6 +16,7 @@
|
||||
//! the peer-to-peer server, the blockchain and the transaction pool) and acts
|
||||
//! as a facade.
|
||||
|
||||
use fs2::FileExt;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
@@ -25,8 +26,6 @@ use std::{
|
||||
thread::{self, JoinHandle},
|
||||
time::{self, Duration},
|
||||
};
|
||||
|
||||
use fs2::FileExt;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::api;
|
||||
@@ -39,7 +38,7 @@ use crate::common::hooks::{init_chain_hooks, init_net_hooks};
|
||||
use crate::common::stats::{
|
||||
ChainStats, DiffBlock, DiffStats, PeerStats, ServerStateInfo, ServerStats, TxStats,
|
||||
};
|
||||
use crate::common::types::{Error, ServerConfig, StratumServerConfig};
|
||||
use crate::common::types::{Error, ServerConfig, ServerInitStatus, StratumServerConfig};
|
||||
use crate::core::core::hash::{Hashed, ZERO_HASH};
|
||||
use crate::core::ser::ProtocolVersion;
|
||||
use crate::core::{consensus, genesis, global, pow};
|
||||
@@ -52,7 +51,6 @@ use crate::pool;
|
||||
use crate::util::file::get_first_line;
|
||||
use crate::util::{RwLock, StopState};
|
||||
use futures::channel::oneshot;
|
||||
use grin_util::logger::LogEntry;
|
||||
|
||||
/// Arcified thread-safe TransactionPool with type parameters used by server components
|
||||
pub type ServerTxPool = Arc<RwLock<pool::TransactionPool<PoolToChainAdapter, PoolToNetAdapter>>>;
|
||||
@@ -84,20 +82,16 @@ impl Server {
|
||||
/// Instantiates and starts a new server. Optionally takes a callback
|
||||
/// for the server to send an ARC copy of itself, to allow another process
|
||||
/// to poll info about the server status
|
||||
pub fn start<F>(
|
||||
pub fn start(
|
||||
config: ServerConfig,
|
||||
logs_rx: Option<mpsc::Receiver<LogEntry>>,
|
||||
mut info_callback: F,
|
||||
stop_state: Option<Arc<StopState>>,
|
||||
server_tx: Option<mpsc::Sender<ServerInitStatus>>,
|
||||
api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>),
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
F: FnMut(Server, Option<mpsc::Receiver<LogEntry>>),
|
||||
{
|
||||
) -> Result<Server, Error> {
|
||||
let mining_config = config.stratum_mining_config.clone();
|
||||
let enable_test_miner = config.run_test_miner;
|
||||
let test_miner_wallet_url = config.test_miner_wallet_url.clone();
|
||||
let serv = Server::new(config, stop_state, api_chan)?;
|
||||
let serv = Server::new(config, stop_state, server_tx, api_chan)?;
|
||||
|
||||
if let Some(c) = mining_config {
|
||||
let enable_stratum_server = c.enable_stratum_server;
|
||||
@@ -118,8 +112,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
info_callback(serv, logs_rx);
|
||||
Ok(())
|
||||
Ok(serv)
|
||||
}
|
||||
|
||||
// Exclusive (advisory) lock_file to ensure we do not run multiple
|
||||
@@ -151,6 +144,7 @@ impl Server {
|
||||
pub fn new(
|
||||
config: ServerConfig,
|
||||
stop_state: Option<Arc<StopState>>,
|
||||
server_tx: Option<mpsc::Sender<ServerInitStatus>>,
|
||||
api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>),
|
||||
) -> Result<Server, Error> {
|
||||
// Obtain our lock_file or fail immediately with an error.
|
||||
@@ -193,12 +187,33 @@ impl Server {
|
||||
|
||||
info!("Starting server, genesis block: {}", genesis.hash());
|
||||
|
||||
if let Some(ref server_tx) = server_tx {
|
||||
let _ = server_tx.send(ServerInitStatus::LoadDatabase);
|
||||
}
|
||||
|
||||
let (db_migration_prog_tx, db_migration_prog_rx) = mpsc::channel::<i8>();
|
||||
if let Some(ref server_tx) = server_tx {
|
||||
let server_tx = server_tx.clone();
|
||||
thread::spawn(move || loop {
|
||||
match db_migration_prog_rx.recv() {
|
||||
Ok(p) => {
|
||||
if p == 100 {
|
||||
break;
|
||||
}
|
||||
let _ = server_tx.send(ServerInitStatus::DBMigrationProgress(p));
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let shared_chain = Arc::new(chain::Chain::init(
|
||||
config.db_root.clone(),
|
||||
chain_adapter.clone(),
|
||||
genesis.clone(),
|
||||
pow::verify_size,
|
||||
archive_mode,
|
||||
Some(db_migration_prog_tx),
|
||||
)?);
|
||||
|
||||
pool_adapter.set_chain(shared_chain.clone());
|
||||
@@ -220,6 +235,10 @@ impl Server {
|
||||
};
|
||||
debug!("Capabilities: {:?}", capabilities);
|
||||
|
||||
if let Some(ref server_tx) = server_tx {
|
||||
let _ = server_tx.send(ServerInitStatus::StartSync);
|
||||
}
|
||||
|
||||
let p2p_server = Arc::new(p2p::Server::new(
|
||||
&config.db_root,
|
||||
capabilities,
|
||||
@@ -265,6 +284,10 @@ impl Server {
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(ref server_tx) = server_tx {
|
||||
let _ = server_tx.send(ServerInitStatus::StartAPI);
|
||||
}
|
||||
|
||||
info!("Starting rest apis at: {}", &config.api_http_addr);
|
||||
let api_secret = get_first_line(config.api_secret_path.clone());
|
||||
let foreign_api_secret = get_first_line(config.foreign_api_secret_path.clone());
|
||||
|
||||
+47
-37
@@ -28,47 +28,54 @@ use crate::tui::ui;
|
||||
use futures::channel::oneshot;
|
||||
use grin_p2p::msg::PeerAddrs;
|
||||
use grin_p2p::PeerAddr;
|
||||
use grin_servers::common::types::ServerInitStatus;
|
||||
use grin_servers::Server;
|
||||
use grin_util::logger::LogEntry;
|
||||
use grin_util::StopState;
|
||||
use std::sync::mpsc;
|
||||
|
||||
/// wrap below to allow UI to clean up on stop
|
||||
/// Start node server at TUI or non-TUI mode.
|
||||
pub fn start_server(
|
||||
config: servers::ServerConfig,
|
||||
logs_rx: Option<mpsc::Receiver<LogEntry>>,
|
||||
api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>),
|
||||
) {
|
||||
start_server_tui(config, logs_rx, api_chan);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
fn start_server_tui(
|
||||
config: servers::ServerConfig,
|
||||
logs_rx: Option<mpsc::Receiver<LogEntry>>,
|
||||
api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>),
|
||||
) {
|
||||
// Run the UI controller.. here for now for simplicity to access
|
||||
// everything it might need
|
||||
if config.run_tui.unwrap_or(false) {
|
||||
let exit_code = if config.run_tui.unwrap_or(false) {
|
||||
warn!("Starting GRIN in UI mode...");
|
||||
servers::Server::start(
|
||||
config,
|
||||
logs_rx,
|
||||
|serv: servers::Server, logs_rx: Option<mpsc::Receiver<LogEntry>>| {
|
||||
let mut controller = ui::Controller::new(logs_rx.unwrap()).unwrap_or_else(|e| {
|
||||
panic!("Error loading UI controller: {}", e);
|
||||
});
|
||||
controller.run(serv);
|
||||
},
|
||||
None,
|
||||
api_chan,
|
||||
)
|
||||
.unwrap();
|
||||
// Run the UI controller.
|
||||
let (serv_tx, serv_rx) = mpsc::channel::<ServerInitStatus>();
|
||||
let mut controller = ui::Controller::new(logs_rx, serv_rx).unwrap_or_else(|e| {
|
||||
panic!("Error loading UI controller: {}", e);
|
||||
});
|
||||
let serv_tx_clone = serv_tx.clone();
|
||||
let stop_state = Arc::new(StopState::new());
|
||||
let stop_state_clone = stop_state.clone();
|
||||
thread::spawn(move || {
|
||||
match Server::start(
|
||||
config,
|
||||
Some(stop_state_clone.clone()),
|
||||
Some(serv_tx_clone.clone()),
|
||||
api_chan,
|
||||
) {
|
||||
Ok(s) => {
|
||||
if stop_state_clone.is_stopped() {
|
||||
s.stop();
|
||||
return;
|
||||
}
|
||||
let _ = serv_tx_clone.send(ServerInitStatus::FinishedLoading(s));
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = serv_tx_clone.send(ServerInitStatus::ErrorLoading(e));
|
||||
}
|
||||
}
|
||||
});
|
||||
let exit_code = controller.run();
|
||||
stop_state.stop();
|
||||
exit_code
|
||||
} else {
|
||||
warn!("Starting GRIN w/o UI...");
|
||||
servers::Server::start(
|
||||
config,
|
||||
logs_rx,
|
||||
|serv: servers::Server, _: Option<mpsc::Receiver<LogEntry>>| {
|
||||
match Server::start(config, None, None, api_chan) {
|
||||
Ok(s) => {
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
ctrlc::set_handler(move || {
|
||||
@@ -79,13 +86,16 @@ fn start_server_tui(
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
warn!("Received SIGINT (Ctrl+C) or SIGTERM (kill).");
|
||||
serv.stop();
|
||||
},
|
||||
None,
|
||||
api_chan,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
s.stop();
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error starting GRIN: {:?}", e);
|
||||
1
|
||||
}
|
||||
}
|
||||
};
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
/// Handles the server part of the command line, mostly running, starting and
|
||||
|
||||
+100
-25
@@ -15,6 +15,12 @@
|
||||
//! Basic TUI to better output the overall system status and status
|
||||
//! of various subsystems
|
||||
|
||||
use super::constants::MAIN_MENU;
|
||||
use crate::built_info;
|
||||
use crate::servers::Server;
|
||||
use crate::tui::constants::{ROOT_STACK, VIEW_BASIC_STATUS, VIEW_MINING, VIEW_PEER_SYNC};
|
||||
use crate::tui::types::{TUIStatusListener, UIMessage};
|
||||
use crate::tui::{logs, menu, mining, peers, status, version};
|
||||
use chrono::prelude::Utc;
|
||||
use cursive::direction::Orientation;
|
||||
use cursive::theme::BaseColor::{Black, Blue, Cyan, White};
|
||||
@@ -29,24 +35,20 @@ use cursive::views::{
|
||||
CircularFocus, Dialog, LinearLayout, Panel, SelectView, StackView, TextView, ViewRef,
|
||||
};
|
||||
use cursive::{CursiveRunnable, CursiveRunner};
|
||||
use std::sync::mpsc;
|
||||
use std::{thread, time};
|
||||
|
||||
use super::constants::MAIN_MENU;
|
||||
use crate::built_info;
|
||||
use crate::servers::Server;
|
||||
use crate::tui::constants::{ROOT_STACK, VIEW_BASIC_STATUS, VIEW_MINING, VIEW_PEER_SYNC};
|
||||
use crate::tui::types::{TUIStatusListener, UIMessage};
|
||||
use crate::tui::{logs, menu, mining, peers, status, version};
|
||||
use grin_core::global;
|
||||
use grin_servers::common::types::{Error, ServerInitStatus};
|
||||
use grin_util::logger::LogEntry;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::{thread, time};
|
||||
|
||||
pub struct UI {
|
||||
cursive: CursiveRunner<CursiveRunnable>,
|
||||
ui_rx: mpsc::Receiver<UIMessage>,
|
||||
ui_tx: mpsc::Sender<UIMessage>,
|
||||
controller_tx: mpsc::Sender<ControllerMessage>,
|
||||
logs_rx: mpsc::Receiver<LogEntry>,
|
||||
logs_rx: Option<mpsc::Receiver<LogEntry>>,
|
||||
show_dialog: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
fn modify_theme(theme: &mut Theme) {
|
||||
@@ -65,7 +67,7 @@ impl UI {
|
||||
/// Create a new UI
|
||||
pub fn new(
|
||||
controller_tx: mpsc::Sender<ControllerMessage>,
|
||||
logs_rx: mpsc::Receiver<LogEntry>,
|
||||
logs_rx: Option<mpsc::Receiver<LogEntry>>,
|
||||
) -> UI {
|
||||
let (ui_tx, ui_rx) = mpsc::channel::<UIMessage>();
|
||||
|
||||
@@ -75,6 +77,7 @@ impl UI {
|
||||
ui_rx,
|
||||
controller_tx,
|
||||
logs_rx,
|
||||
show_dialog: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
|
||||
// Create UI objects, etc
|
||||
@@ -102,7 +105,7 @@ impl UI {
|
||||
built_info::PKG_VERSION,
|
||||
global::get_chain_type()
|
||||
),
|
||||
Color::Dark(BaseColor::Green),
|
||||
Dark(BaseColor::Green),
|
||||
));
|
||||
|
||||
let main_layer = LinearLayout::new(Orientation::Vertical)
|
||||
@@ -117,17 +120,21 @@ impl UI {
|
||||
let mut theme = grin_ui.cursive.current_theme().clone();
|
||||
modify_theme(&mut theme);
|
||||
grin_ui.cursive.set_theme(theme);
|
||||
|
||||
grin_ui.cursive.add_fullscreen_layer(main_layer);
|
||||
|
||||
// Configure a callback (shutdown, for the first test)
|
||||
let controller_tx_clone = grin_ui.controller_tx.clone();
|
||||
let show_dialog_clone = grin_ui.show_dialog.clone();
|
||||
grin_ui.cursive.add_global_callback('q', move |c| {
|
||||
if show_dialog_clone.load(Ordering::Relaxed) {
|
||||
c.pop_layer();
|
||||
}
|
||||
let content = StyledString::styled("Shutting down...", Color::Light(BaseColor::Yellow));
|
||||
c.add_layer(CircularFocus::new(Dialog::around(TextView::new(content))).wrap_tab());
|
||||
controller_tx_clone
|
||||
.send(ControllerMessage::Shutdown)
|
||||
.unwrap();
|
||||
let _ = controller_tx_clone.send(ControllerMessage::Shutdown);
|
||||
});
|
||||
|
||||
grin_ui.cursive.set_fps(3);
|
||||
grin_ui
|
||||
}
|
||||
@@ -139,8 +146,10 @@ impl UI {
|
||||
return false;
|
||||
}
|
||||
|
||||
while let Some(message) = self.logs_rx.try_iter().next() {
|
||||
logs::TUILogsView::update(&mut self.cursive, message);
|
||||
if let Some(logs_rx) = &self.logs_rx {
|
||||
while let Some(message) = logs_rx.try_iter().next() {
|
||||
logs::TUILogsView::update(&mut self.cursive, message);
|
||||
}
|
||||
}
|
||||
|
||||
// Process any pending UI messages
|
||||
@@ -174,6 +183,8 @@ impl UI {
|
||||
pub struct Controller {
|
||||
rx: mpsc::Receiver<ControllerMessage>,
|
||||
ui: UI,
|
||||
serv_rx: mpsc::Receiver<ServerInitStatus>,
|
||||
server: Option<Server>,
|
||||
}
|
||||
|
||||
pub enum ControllerMessage {
|
||||
@@ -182,39 +193,103 @@ pub enum ControllerMessage {
|
||||
|
||||
impl Controller {
|
||||
/// Create a new controller
|
||||
pub fn new(logs_rx: mpsc::Receiver<LogEntry>) -> Result<Controller, String> {
|
||||
pub fn new(
|
||||
logs_rx: Option<mpsc::Receiver<LogEntry>>,
|
||||
serv_rx: mpsc::Receiver<ServerInitStatus>,
|
||||
) -> Result<Controller, String> {
|
||||
let (tx, rx) = mpsc::channel::<ControllerMessage>();
|
||||
Ok(Controller {
|
||||
rx,
|
||||
ui: UI::new(tx, logs_rx),
|
||||
serv_rx,
|
||||
server: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Server initialization status.
|
||||
pub fn init_status(&mut self, text: &str, pop: bool) {
|
||||
if pop {
|
||||
self.ui.cursive.pop_layer();
|
||||
}
|
||||
let content = StyledString::styled(text, Color::Light(BaseColor::Green));
|
||||
self.ui
|
||||
.cursive
|
||||
.add_layer(CircularFocus::new(Dialog::around(TextView::new(content))).wrap_tab());
|
||||
self.ui.show_dialog.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Server initialization error.
|
||||
pub fn init_error(&mut self, e: Error) {
|
||||
let content = StyledString::styled(format!("{:?}", e), Color::Light(BaseColor::Red));
|
||||
self.ui.cursive.add_layer(
|
||||
CircularFocus::new(Dialog::around(TextView::new(content)).button("Exit", |s| {
|
||||
s.quit();
|
||||
}))
|
||||
.wrap_tab(),
|
||||
);
|
||||
self.ui.show_dialog.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Server UI after initialization.
|
||||
pub fn server(&mut self, server: &Server) {
|
||||
if let Ok(stats) = server.get_server_stats() {
|
||||
self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the controller
|
||||
pub fn run(&mut self, server: Server) {
|
||||
pub fn run(&mut self) -> i32 {
|
||||
self.init_status("Starting server...", false);
|
||||
|
||||
let stat_update_interval = 1;
|
||||
let mut next_stat_update = Utc::now().timestamp() + stat_update_interval;
|
||||
let delay = time::Duration::from_millis(50);
|
||||
let mut exit_code = 0;
|
||||
while self.ui.step() {
|
||||
if let Some(message) = self.rx.try_iter().next() {
|
||||
match message {
|
||||
return match message {
|
||||
ControllerMessage::Shutdown => {
|
||||
warn!("Shutdown in progress, please wait");
|
||||
self.ui.stop();
|
||||
server.stop();
|
||||
return;
|
||||
if let Some(s) = self.server.take() {
|
||||
s.stop();
|
||||
}
|
||||
exit_code
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(m) = self.serv_rx.try_iter().next() {
|
||||
match m {
|
||||
ServerInitStatus::LoadDatabase => self.init_status("Loading database...", true),
|
||||
ServerInitStatus::StartSync => self.init_status("Start syncing...", true),
|
||||
ServerInitStatus::StartAPI => self.init_status("Starting API...", true),
|
||||
ServerInitStatus::FinishedLoading(s) => {
|
||||
self.ui.cursive.pop_layer();
|
||||
self.ui.show_dialog.store(false, Ordering::Relaxed);
|
||||
self.server = Some(s)
|
||||
}
|
||||
ServerInitStatus::ErrorLoading(e) => {
|
||||
exit_code = 1;
|
||||
self.init_error(e);
|
||||
}
|
||||
ServerInitStatus::DBMigrationProgress(p) => {
|
||||
let status = format!("Migrating database: {}%, please wait...", p);
|
||||
self.init_status(status.as_str(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if Utc::now().timestamp() > next_stat_update {
|
||||
next_stat_update = Utc::now().timestamp() + stat_update_interval;
|
||||
if let Ok(stats) = server.get_server_stats() {
|
||||
self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap();
|
||||
if let Some(server) = &self.server {
|
||||
if let Ok(stats) = server.get_server_stats() {
|
||||
self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
thread::sleep(delay);
|
||||
}
|
||||
server.stop();
|
||||
exit_code
|
||||
}
|
||||
}
|
||||
|
||||
+155
-102
@@ -19,7 +19,7 @@ use heed::{Database, Env, EnvOpenOptions, RoTxn, RwTxn, WithoutTls};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::sync::{mpsc, Arc, OnceLock};
|
||||
use std::time::Duration;
|
||||
use std::{fs, thread};
|
||||
|
||||
@@ -87,6 +87,8 @@ const DEFAULT_DB_VERSION: ProtocolVersion = ProtocolVersion(3);
|
||||
pub const DEFAULT_ENV_NAME: &'static str = "lmdb";
|
||||
/// Default multi-database environment without prefixes.
|
||||
const DEFAULT_MULTI_DB_ENV_NAME: &'static str = "multi_lmdb";
|
||||
/// Migration completion marker in the default database.
|
||||
const MIGRATION_COMPLETE_KEY: &[u8] = b"__grin_migration_complete";
|
||||
/// Prefix key separator.
|
||||
pub const PREFIX_KEY_SEPARATOR: u8 = b':';
|
||||
|
||||
@@ -159,6 +161,7 @@ impl Store {
|
||||
db_name: Option<&str>,
|
||||
prefixes: Vec<u8>,
|
||||
max_readers: Option<u32>,
|
||||
db_migration_prog_tx: Option<mpsc::Sender<i8>>,
|
||||
) -> Result<Store, Error> {
|
||||
let full_path = Path::new(root_path)
|
||||
.join(DEFAULT_MULTI_DB_ENV_NAME)
|
||||
@@ -225,25 +228,28 @@ impl Store {
|
||||
}
|
||||
|
||||
// Database setup.
|
||||
let r_env_map = env_map.read();
|
||||
let env = r_env_map.get(&full_path).unwrap().env.clone();
|
||||
let mut write = env.write_txn()?;
|
||||
let def_name = db_name.unwrap_or(DEFAULT_ENV_NAME);
|
||||
let def_db = env.create_database(&mut write, Some(def_name))?;
|
||||
let mut dbs_map = HashMap::<u8, Database<Bytes, Bytes>>::new();
|
||||
for p in prefixes {
|
||||
let db = env.create_database(&mut write, Some(p.to_string().as_str()))?;
|
||||
dbs_map.insert(p, db);
|
||||
}
|
||||
write.commit()?;
|
||||
let s = {
|
||||
let r_env_map = env_map.read();
|
||||
let env = r_env_map.get(&full_path).unwrap().env.clone();
|
||||
let mut write = env.write_txn()?;
|
||||
let def_name = db_name.unwrap_or(DEFAULT_ENV_NAME);
|
||||
let def_db = env.create_database(&mut write, Some(def_name))?;
|
||||
let mut dbs_map = HashMap::<u8, Database<Bytes, Bytes>>::new();
|
||||
for p in prefixes {
|
||||
let db = env.create_database(&mut write, Some(p.to_string().as_str()))?;
|
||||
dbs_map.insert(p, db);
|
||||
}
|
||||
write.commit()?;
|
||||
|
||||
let s = Store {
|
||||
env: env.clone(),
|
||||
env_path: full_path.clone(),
|
||||
pre_dbs: Arc::new(dbs_map),
|
||||
def_db,
|
||||
version: DEFAULT_DB_VERSION,
|
||||
alloc_chunk_size,
|
||||
let s = Store {
|
||||
env: env.clone(),
|
||||
env_path: full_path.clone(),
|
||||
pre_dbs: Arc::new(dbs_map),
|
||||
def_db,
|
||||
version: DEFAULT_DB_VERSION,
|
||||
alloc_chunk_size,
|
||||
};
|
||||
s
|
||||
};
|
||||
|
||||
// Migrate to default environment if needed.
|
||||
@@ -251,25 +257,42 @@ impl Store {
|
||||
if env_name != DEFAULT_MULTI_DB_ENV_NAME {
|
||||
let migrate_from = Path::new(root_path).join(env_name);
|
||||
if migrate_from.exists() {
|
||||
match s.migrate_to_default_env(db_name, &migrate_from) {
|
||||
Ok(_) => match fs::remove_dir_all(&migrate_from) {
|
||||
Ok(_) => {}
|
||||
let delete_old_db_file = || -> Result<(), Error> {
|
||||
match fs::remove_dir_all(&migrate_from) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
return Err(Error::FileErr(format!(
|
||||
"Can not remove old DB file: {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("DB {} migration error: {:?}", env_name, e);
|
||||
match s.clear() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("Can not clear new DB after unsuccessful migration: {:?}", e)
|
||||
}
|
||||
};
|
||||
if s.migration_complete()? {
|
||||
if let Err(e) = delete_old_db_file() {
|
||||
return Err(e);
|
||||
}
|
||||
} else {
|
||||
let _ = s.clear();
|
||||
match s.migrate_to_default_env(db_name, &migrate_from, db_migration_prog_tx) {
|
||||
Ok(_) => {
|
||||
if let Err(e) = delete_old_db_file() {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
return Err(e);
|
||||
Err(e) => {
|
||||
error!("DB {} migration error: {:?}", env_name, e);
|
||||
match s.clear() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Can not clear new DB after unsuccessful migration: {:?}",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,13 +301,31 @@ impl Store {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Check if migration has already completed successfully.
|
||||
fn migration_complete(&self) -> Result<bool, Error> {
|
||||
let read = self.env.read_txn()?;
|
||||
Ok(self.def_db.get(&read, MIGRATION_COMPLETE_KEY)?.is_some())
|
||||
}
|
||||
|
||||
/// Mark migration as successfully completed.
|
||||
fn set_migration_complete(&self, write: &mut RwTxn<'_>) -> Result<(), Error> {
|
||||
self.def_db.put(write, MIGRATION_COMPLETE_KEY, b"1")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Migrate database from provided path to default environment.
|
||||
fn migrate_to_default_env(
|
||||
&self,
|
||||
from_name: Option<&str>,
|
||||
from_path: &Path,
|
||||
db_migration_prog_tx: Option<mpsc::Sender<i8>>,
|
||||
) -> Result<(), Error> {
|
||||
info!("Migrating DB {:?}, please wait...", from_path);
|
||||
|
||||
if let Some(migration_prog_tx) = &db_migration_prog_tx {
|
||||
let _ = migration_prog_tx.send(0i8);
|
||||
}
|
||||
|
||||
let from_env = unsafe {
|
||||
let mut options = EnvOpenOptions::new().read_txn_without_tls();
|
||||
let env_options = options.map_size(self.alloc_chunk_size).max_dbs(24);
|
||||
@@ -307,7 +348,16 @@ impl Store {
|
||||
let mut write_to = self.env.write_txn()?;
|
||||
let read_from = from_env.read_txn()?;
|
||||
let mut count = 0;
|
||||
for kv in db_from.iter(&read_from)? {
|
||||
let total = db_from.iter(&read_from)?.count();
|
||||
let mut prev_prog = 0;
|
||||
for (index, kv) in db_from.iter(&read_from)?.enumerate() {
|
||||
if let Some(migration_prog_tx) = &db_migration_prog_tx {
|
||||
let prog = 100 * index / total;
|
||||
if prev_prog != prog && prog != 100 {
|
||||
prev_prog = prog;
|
||||
let _ = migration_prog_tx.send(prog as i8);
|
||||
}
|
||||
}
|
||||
let (k, v) = kv?;
|
||||
if k.len() > 1 && k[1] == PREFIX_KEY_SEPARATOR {
|
||||
let db_name = k.split_at(1).0;
|
||||
@@ -323,7 +373,13 @@ impl Store {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
self.set_migration_complete(&mut write_to)?;
|
||||
write_to.commit()?;
|
||||
|
||||
if let Some(migration_prog_tx) = &db_migration_prog_tx {
|
||||
let _ = migration_prog_tx.send(100i8);
|
||||
}
|
||||
|
||||
info!("Migrated {} records from {:?}", count, from_path);
|
||||
Ok(())
|
||||
}
|
||||
@@ -367,7 +423,7 @@ impl Store {
|
||||
/// Wait while database is resizing.
|
||||
fn wait_for_resize(&self) {
|
||||
loop {
|
||||
if !ENV_MAP
|
||||
if ENV_MAP
|
||||
.get()
|
||||
.unwrap()
|
||||
.read()
|
||||
@@ -375,11 +431,13 @@ impl Store {
|
||||
.unwrap()
|
||||
.resizing
|
||||
.load(Ordering::Relaxed)
|
||||
&& self.open_txs_count() == 0
|
||||
{
|
||||
break;
|
||||
debug!("Wait on resizing DB {}", self.env_path);
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
continue;
|
||||
}
|
||||
trace!("Wait on resizing DB {}", self.env_path);
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,9 +457,16 @@ impl Store {
|
||||
let env_path = self.env_path.clone();
|
||||
let env = self.env.clone();
|
||||
|
||||
{
|
||||
let mut w_env_map = ENV_MAP.get().unwrap().write();
|
||||
let env_state = w_env_map.get_mut(&env_path).unwrap();
|
||||
env_state.resizing.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
// Resize immediately or at another thread to not interrupt current
|
||||
// transaction waiting all open transactions to be closed.
|
||||
if self.open_txs_count() != 0 {
|
||||
debug!("Waiting txs to be closed before DB {} resize", env_path);
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
let txs_count = ENV_MAP
|
||||
@@ -414,19 +479,9 @@ impl Store {
|
||||
.load(Ordering::Relaxed);
|
||||
if txs_count == 0 {
|
||||
debug!("Start resizing DB {}", env_path);
|
||||
ENV_MAP
|
||||
.get()
|
||||
.unwrap()
|
||||
.write()
|
||||
.get_mut(&env_path)
|
||||
.unwrap()
|
||||
.resizing
|
||||
.store(true, Ordering::Relaxed);
|
||||
// Wait to make sure there are no more active txs left.
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
@@ -443,17 +498,15 @@ impl Store {
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
let mut w_env_map = ENV_MAP.get().unwrap().write();
|
||||
let env_state = w_env_map.get_mut(&env_path).unwrap();
|
||||
|
||||
debug!("Start immediate resizing DB {}", env_path);
|
||||
env_state.resizing.store(true, Ordering::Relaxed);
|
||||
unsafe {
|
||||
match env.resize(new_size) {
|
||||
Ok(_) => debug!("End resizing DB {}", env_path),
|
||||
Err(e) => error!("Resize DB {} error: {:?}", env_path, e),
|
||||
}
|
||||
}
|
||||
let mut w_env_map = ENV_MAP.get().unwrap().write();
|
||||
let env_state = w_env_map.get_mut(&env_path).unwrap();
|
||||
env_state.resizing.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
@@ -577,27 +630,27 @@ impl Store {
|
||||
self.wait_for_resize();
|
||||
|
||||
TxCounter::on_change_tx_count(&self.env_path, true);
|
||||
match self.env.clone().static_read_txn() {
|
||||
Ok(read) => {
|
||||
let db_res = self.get_db(db_key);
|
||||
match db_res {
|
||||
Ok(db) => Ok(DatabaseIterator::new(
|
||||
self,
|
||||
Arc::new(db.clone()),
|
||||
read,
|
||||
deserialize,
|
||||
)),
|
||||
Err(e) => {
|
||||
TxCounter::on_change_tx_count(&self.env_path, false);
|
||||
Err(Error::from(e))
|
||||
let res = {
|
||||
match self.env.clone().static_read_txn() {
|
||||
Ok(read) => {
|
||||
let db_res = self.get_db(db_key);
|
||||
match db_res {
|
||||
Ok(db) => Ok(DatabaseIterator::new(
|
||||
self,
|
||||
Arc::new(db.clone()),
|
||||
read,
|
||||
deserialize,
|
||||
)),
|
||||
Err(e) => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(e)),
|
||||
}
|
||||
Err(e) => {
|
||||
TxCounter::on_change_tx_count(&self.env_path, false);
|
||||
Err(Error::from(e))
|
||||
}
|
||||
};
|
||||
if res.is_err() {
|
||||
TxCounter::on_change_tx_count(&self.env_path, false);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Builds a new batch to be used with this store.
|
||||
@@ -605,13 +658,11 @@ impl Store {
|
||||
self.maybe_resize();
|
||||
|
||||
TxCounter::on_change_tx_count(&self.env_path, true);
|
||||
match Batch::new(self) {
|
||||
Ok(batch) => Ok(batch),
|
||||
Err(e) => {
|
||||
TxCounter::on_change_tx_count(&self.env_path, false);
|
||||
Err(e)
|
||||
}
|
||||
let res = { Batch::new(self) };
|
||||
if res.is_err() {
|
||||
TxCounter::on_change_tx_count(&self.env_path, false);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,28 +792,27 @@ impl<'a> Batch<'a> {
|
||||
self.store.wait_for_resize();
|
||||
|
||||
TxCounter::on_change_tx_count(&self.store.env_path, true);
|
||||
let read = self.write.nested_read_txn();
|
||||
match read {
|
||||
Ok(read) => {
|
||||
let db_res = self.store.get_db(db_key);
|
||||
match db_res {
|
||||
Ok(db) => Ok(DatabaseIterator::new(
|
||||
self.store,
|
||||
Arc::new(db.clone()),
|
||||
read,
|
||||
deserialize,
|
||||
)),
|
||||
Err(e) => {
|
||||
TxCounter::on_change_tx_count(&self.store.env_path, false);
|
||||
Err(Error::from(e))
|
||||
let res = {
|
||||
match self.write.nested_read_txn() {
|
||||
Ok(read) => {
|
||||
let db_res = self.store.get_db(db_key);
|
||||
match db_res {
|
||||
Ok(db) => Ok(DatabaseIterator::new(
|
||||
self.store,
|
||||
Arc::new(db.clone()),
|
||||
read,
|
||||
deserialize,
|
||||
)),
|
||||
Err(e) => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(e)),
|
||||
}
|
||||
Err(e) => {
|
||||
TxCounter::on_change_tx_count(&self.store.env_path, false);
|
||||
Err(Error::from(e))
|
||||
}
|
||||
};
|
||||
if res.is_err() {
|
||||
TxCounter::on_change_tx_count(&self.store.env_path, false);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Gets a `Readable` value from the database by provided key and deserialization strategy.
|
||||
@@ -801,19 +851,22 @@ impl<'a> Batch<'a> {
|
||||
/// commit, abandoned otherwise.
|
||||
pub fn child(&mut self) -> Result<Batch<'_>, Error> {
|
||||
TxCounter::on_change_tx_count(&self.store.env_path, true);
|
||||
match self.store.env.nested_write_txn(&mut self.write) {
|
||||
Ok(write) => Ok(Batch {
|
||||
store: self.store,
|
||||
write,
|
||||
tx_counter: TxCounter {
|
||||
env_path: self.store.env_path.clone(),
|
||||
},
|
||||
}),
|
||||
Err(e) => {
|
||||
TxCounter::on_change_tx_count(&self.store.env_path, false);
|
||||
Err(Error::from(e))
|
||||
let res = {
|
||||
match self.store.env.nested_write_txn(&mut self.write) {
|
||||
Ok(write) => Ok(Batch {
|
||||
store: self.store,
|
||||
write,
|
||||
tx_counter: TxCounter {
|
||||
env_path: self.store.env_path.clone(),
|
||||
},
|
||||
}),
|
||||
Err(e) => Err(Error::from(e)),
|
||||
}
|
||||
};
|
||||
if res.is_err() {
|
||||
TxCounter::on_change_tx_count(&self.store.env_path, false);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -79,7 +79,7 @@ fn test_exists() -> Result<(), store::Error> {
|
||||
setup(test_dir);
|
||||
|
||||
let prefix = b'P';
|
||||
let store = store::Store::new(test_dir, Some("test1"), None, vec![prefix], None)?;
|
||||
let store = store::Store::new(test_dir, Some("test1"), None, vec![prefix], None, None)?;
|
||||
|
||||
let key = [0, 0, 0, 1];
|
||||
let value = [1, 1, 1, 1];
|
||||
@@ -109,7 +109,7 @@ fn test_iter() -> Result<(), store::Error> {
|
||||
setup(test_dir);
|
||||
|
||||
let prefix = b'P';
|
||||
let store = store::Store::new(test_dir, Some("test1"), None, vec![prefix], None)?;
|
||||
let store = store::Store::new(test_dir, Some("test1"), None, vec![prefix], None, None)?;
|
||||
|
||||
let key = [0, 0, 0, 1];
|
||||
let value = [1, 1, 1, 1];
|
||||
@@ -148,7 +148,7 @@ fn lmdb_allocate() -> Result<(), store::Error> {
|
||||
// Allocate more than the initial chunk, ensuring
|
||||
// the DB resizes underneath
|
||||
{
|
||||
let store = store::Store::new(test_dir, Some("test1"), None, vec![prefix], None)?;
|
||||
let store = store::Store::new(test_dir, Some("test1"), None, vec![prefix], None, None)?;
|
||||
|
||||
for i in 0..WRITE_CHUNK_SIZE * 2 {
|
||||
println!("Allocating chunk: {}", i);
|
||||
@@ -164,7 +164,7 @@ fn lmdb_allocate() -> Result<(), store::Error> {
|
||||
println!("***********************************");
|
||||
// Open env again and keep adding
|
||||
{
|
||||
let store = store::Store::new(test_dir, Some("test1"), None, vec![prefix], None)?;
|
||||
let store = store::Store::new(test_dir, Some("test1"), None, vec![prefix], None, None)?;
|
||||
for i in 0..WRITE_CHUNK_SIZE * 2 {
|
||||
println!("Allocating chunk: {}", i);
|
||||
let chunk = PhatChunkStruct::new();
|
||||
@@ -253,6 +253,7 @@ fn test_migration() -> Result<(), store::Error> {
|
||||
Some(DEFAULT_ENV_NAME),
|
||||
vec![test_prefix_1, test_prefix_2],
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Check we can see key value.
|
||||
|
||||
Reference in New Issue
Block a user